0.6.0 training session: Oracle Bot, RL combat, Mind's Eye, multilingual pipeline
Major changes from this session: Training: - 0.6.0 training running: 9B on steel141 3090 Ti, 27B on rented H100 NVL - 7,256 merged training examples (up from 3,183) - New training data: failure modes (85), midloop messaging (27), prompt injection defense (29), personality (32), gold from quarantine bank (232), new tool examples (30), claude's own experience (10) - All training data RCON-validated at 100% pass rate - Bake-off: gemma3:27b 66%, qwen3.5:27b 61%, translategemma:27b 56% Oracle Bot (Mind's Eye): - Invisible spectator bot (mineflayer) streams world state via WebSocket - HTML5 Canvas frontend at mind.mortdec.ai - Real-time tool trace visualization with expandable entries - Streaming model tokens during inference - Gateway integration: fire-and-forget POST /trace on every tool call Reinforcement Learning: - Gymnasium environment wrapping mineflayer bot (minecraft_env.py) - PPO training via Stable Baselines3 (10K param policy network) - Behavioral cloning pretraining (97.5% accuracy on expert policy) - Infinite training loop with auto-restart and checkpoint resume - Bot learns combat, survival, navigation from raw experience Bot Army: - 8-soldier marching formation with autonomous combat - Combat bots using mineflayer-pvp, pathfinder, armor-manager - Multilingual prayer bots via translategemma:27b (18 languages) - Frame-based AI architecture: LLM planner + reactive micro-scripts Infrastructure: - Fixed mattpc.sethpc.xyz billing gateway (API key + player list parser) - Billing gateway now tracks all LAN traffic (LAN auto-auth) - Gateway fallback for empty god-mode responses - Updated mortdec.ai landing page Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* explorer_bot.js — Claude explores the world through a mineflayer bot's eyes.
|
||||
*
|
||||
* Connects to the server, reports what it sees, and accepts movement commands
|
||||
* via stdin. Outputs structured JSON observations.
|
||||
*
|
||||
* Usage: node explorer_bot.js [host] [port]
|
||||
*/
|
||||
|
||||
const mineflayer = require('mineflayer');
|
||||
const { Vec3 } = require('vec3');
|
||||
const readline = require('readline');
|
||||
|
||||
const host = process.argv[2] || '192.168.0.244';
|
||||
const port = parseInt(process.argv[3] || '25567', 10);
|
||||
|
||||
const bot = mineflayer.createBot({
|
||||
host,
|
||||
port,
|
||||
username: 'ClaudeBot',
|
||||
auth: 'offline',
|
||||
version: '1.21.11',
|
||||
});
|
||||
|
||||
// ── Observation functions ──
|
||||
|
||||
function observe() {
|
||||
if (!bot.entity) return '{"error": "not spawned"}';
|
||||
|
||||
const pos = bot.entity.position;
|
||||
const obs = {
|
||||
position: { x: Math.floor(pos.x), y: Math.floor(pos.y), z: Math.floor(pos.z) },
|
||||
health: bot.health,
|
||||
food: bot.food,
|
||||
gamemode: bot.game.gameMode,
|
||||
time: bot.time.timeOfDay,
|
||||
isRaining: bot.isRaining,
|
||||
};
|
||||
|
||||
// Block below and around
|
||||
obs.blockBelow = blockName(pos.offset(0, -1, 0));
|
||||
obs.blockAt = blockName(pos);
|
||||
obs.blockAbove = blockName(pos.offset(0, 2, 0));
|
||||
|
||||
// Look in all 4 directions for interesting blocks
|
||||
obs.surroundings = {};
|
||||
const dirs = { north: [0, 0, -1], south: [0, 0, 1], east: [1, 0, 0], west: [-1, 0, 0] };
|
||||
for (const [name, [dx, dy, dz]] of Object.entries(dirs)) {
|
||||
const blocks = [];
|
||||
for (let dist = 1; dist <= 8; dist++) {
|
||||
const b = blockName(pos.offset(dx * dist, 0, dz * dist));
|
||||
if (b !== 'air') blocks.push({ dist, block: b });
|
||||
}
|
||||
if (blocks.length) obs.surroundings[name] = blocks;
|
||||
}
|
||||
|
||||
// Nearby entities
|
||||
obs.entities = [];
|
||||
for (const entity of Object.values(bot.entities)) {
|
||||
if (entity === bot.entity) continue;
|
||||
const dist = entity.position.distanceTo(pos);
|
||||
if (dist <= 20) {
|
||||
obs.entities.push({
|
||||
type: entity.type === 'player' ? 'player' : (entity.name || entity.type),
|
||||
name: entity.username || null,
|
||||
distance: Math.round(dist),
|
||||
position: {
|
||||
x: Math.floor(entity.position.x),
|
||||
y: Math.floor(entity.position.y),
|
||||
z: Math.floor(entity.position.z),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Scan a 5x5 area at foot level for terrain overview
|
||||
obs.terrain = [];
|
||||
for (let dx = -5; dx <= 5; dx++) {
|
||||
for (let dz = -5; dz <= 5; dz++) {
|
||||
// Find surface block (scan down from y+3)
|
||||
for (let dy = 3; dy >= -3; dy--) {
|
||||
const b = bot.blockAt(pos.offset(dx, dy, dz));
|
||||
if (b && b.name !== 'air') {
|
||||
obs.terrain.push({ dx, dz, y: Math.floor(pos.y) + dy, block: b.name });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(obs);
|
||||
}
|
||||
|
||||
function blockName(vec) {
|
||||
const b = bot.blockAt(vec);
|
||||
return b ? b.name : 'unloaded';
|
||||
}
|
||||
|
||||
// ── Movement functions ──
|
||||
|
||||
async function walkTo(x, y, z) {
|
||||
const goal = new Vec3(x, y, z);
|
||||
console.log(JSON.stringify({ action: 'walking', target: { x, y, z } }));
|
||||
|
||||
// Simple movement: look at target and walk forward
|
||||
await bot.lookAt(goal);
|
||||
bot.setControlState('forward', true);
|
||||
|
||||
// Walk until close or timeout
|
||||
return new Promise((resolve) => {
|
||||
const check = setInterval(() => {
|
||||
const dist = bot.entity.position.distanceTo(goal);
|
||||
if (dist < 2) {
|
||||
bot.setControlState('forward', false);
|
||||
clearInterval(check);
|
||||
resolve(true);
|
||||
}
|
||||
}, 200);
|
||||
|
||||
setTimeout(() => {
|
||||
bot.setControlState('forward', false);
|
||||
clearInterval(check);
|
||||
resolve(false);
|
||||
}, 10000); // 10s max walk
|
||||
});
|
||||
}
|
||||
|
||||
async function lookAround() {
|
||||
const snapshots = [];
|
||||
const yaws = [0, Math.PI / 2, Math.PI, -Math.PI / 2]; // N, E, S, W
|
||||
const names = ['north', 'east', 'south', 'west'];
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await bot.look(yaws[i], 0, true);
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
|
||||
// What do I see in this direction?
|
||||
const pos = bot.entity.position;
|
||||
const dx = Math.round(Math.sin(yaws[i]));
|
||||
const dz = Math.round(-Math.cos(yaws[i]));
|
||||
|
||||
const visible = [];
|
||||
for (let dist = 1; dist <= 16; dist++) {
|
||||
const b = bot.blockAt(pos.offset(dx * dist, 0, dz * dist));
|
||||
const bUp = bot.blockAt(pos.offset(dx * dist, 1, dz * dist));
|
||||
const bDown = bot.blockAt(pos.offset(dx * dist, -1, dz * dist));
|
||||
if (b && b.name !== 'air') visible.push({ dist, block: b.name, level: 'eye' });
|
||||
if (bDown && bDown.name !== 'air' && bDown.name !== (b && b.name)) visible.push({ dist, block: bDown.name, level: 'ground' });
|
||||
}
|
||||
snapshots.push({ direction: names[i], blocks: visible.slice(0, 8) });
|
||||
}
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
// ── Event handlers ──
|
||||
|
||||
bot.on('login', () => {
|
||||
console.log(JSON.stringify({ event: 'login', username: bot.username }));
|
||||
});
|
||||
|
||||
bot.on('spawn', async () => {
|
||||
console.log(JSON.stringify({ event: 'spawn', ...JSON.parse(observe()) }));
|
||||
|
||||
// Initial look around
|
||||
const views = await lookAround();
|
||||
console.log(JSON.stringify({ event: 'look_around', views }));
|
||||
});
|
||||
|
||||
bot.on('chat', (username, message) => {
|
||||
if (username === bot.username) return;
|
||||
console.log(JSON.stringify({ event: 'chat', from: username, message }));
|
||||
});
|
||||
|
||||
bot.on('health', () => {
|
||||
console.log(JSON.stringify({ event: 'health', health: bot.health, food: bot.food }));
|
||||
});
|
||||
|
||||
bot.on('death', () => {
|
||||
console.log(JSON.stringify({ event: 'death' }));
|
||||
});
|
||||
|
||||
bot.on('kicked', (reason) => {
|
||||
console.log(JSON.stringify({ event: 'kicked', reason: typeof reason === 'object' ? JSON.stringify(reason) : reason }));
|
||||
});
|
||||
|
||||
bot.on('error', (err) => {
|
||||
console.error(JSON.stringify({ event: 'error', message: err.message }));
|
||||
});
|
||||
|
||||
// ── Command interface (stdin) ──
|
||||
|
||||
const rl = readline.createInterface({ input: process.stdin });
|
||||
|
||||
rl.on('line', async (line) => {
|
||||
const cmd = line.trim();
|
||||
|
||||
if (cmd === 'observe' || cmd === 'look') {
|
||||
console.log(observe());
|
||||
}
|
||||
else if (cmd === 'around') {
|
||||
const views = await lookAround();
|
||||
console.log(JSON.stringify({ action: 'look_around', views }));
|
||||
}
|
||||
else if (cmd.startsWith('walk ')) {
|
||||
const parts = cmd.split(' ');
|
||||
const x = parseInt(parts[1]), y = parseInt(parts[2]), z = parseInt(parts[3]);
|
||||
const arrived = await walkTo(x, y, z);
|
||||
console.log(JSON.stringify({ action: 'walk_result', arrived, ...JSON.parse(observe()) }));
|
||||
}
|
||||
else if (cmd === 'forward') {
|
||||
bot.setControlState('forward', true);
|
||||
setTimeout(() => {
|
||||
bot.setControlState('forward', false);
|
||||
console.log(observe());
|
||||
}, 3000);
|
||||
}
|
||||
else if (cmd === 'jump') {
|
||||
bot.setControlState('jump', true);
|
||||
setTimeout(() => bot.setControlState('jump', false), 500);
|
||||
setTimeout(() => console.log(observe()), 1000);
|
||||
}
|
||||
else if (cmd.startsWith('say ')) {
|
||||
bot.chat(cmd.slice(4));
|
||||
}
|
||||
else if (cmd === 'quit') {
|
||||
bot.end();
|
||||
process.exit(0);
|
||||
}
|
||||
else {
|
||||
console.log(JSON.stringify({ error: 'unknown command', commands: ['observe', 'around', 'walk X Y Z', 'forward', 'jump', 'say MSG', 'quit'] }));
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user