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:
Seth
2026-03-22 20:22:50 -04:00
parent baab24f8b1
commit 5b28002001
44 changed files with 20873 additions and 4352 deletions
+252
View File
@@ -0,0 +1,252 @@
/**
* bot_army.js — Spawn an army of combat bots.
*
* Each bot runs autonomous combat with mineflayer-pvp.
* They attack all hostiles but never each other.
* Reports collective stats.
*
* Usage: node bot_army.js [count] [host] [port]
*/
const mineflayer = require('mineflayer');
const pvp = require('mineflayer-pvp').plugin;
const armorManager = require('mineflayer-armor-manager');
const pathfinder = require('mineflayer-pathfinder');
const { GoalNear } = require('mineflayer-pathfinder').goals;
const count = parseInt(process.argv[2] || '8', 10);
const host = process.argv[3] || '192.168.0.244';
const port = parseInt(process.argv[4] || '25567', 10);
const HOSTILE = new Set([
'zombie', 'husk', 'skeleton', 'creeper', 'spider', 'cave_spider',
'witch', 'enderman', 'pillager', 'vindicator', 'phantom',
'drowned', 'stray', 'blaze', 'ghast', 'slime', 'magma_cube',
'parched', 'camel_husk', 'zombie_villager',
]);
const NAMES = [
'Soldier_Alpha', 'Soldier_Bravo', 'Soldier_Charlie', 'Soldier_Delta',
'Soldier_Echo', 'Soldier_Foxtrot', 'Soldier_Golf', 'Soldier_Hotel',
'Soldier_India', 'Soldier_Juliet', 'Soldier_Kilo', 'Soldier_Lima',
'Soldier_Mike', 'Soldier_November', 'Soldier_Oscar', 'Soldier_Papa',
];
// Track all bot names so they don't attack each other
const armyNames = new Set(NAMES.slice(0, count));
armyNames.add('ClaudeBot');
armyNames.add('slingshooter08');
armyNames.add('OracleBot');
const army = [];
let totalKills = 0;
let totalDeaths = 0;
function spawnSoldier(name, index) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`[${name}] Spawning...`);
const bot = mineflayer.createBot({
host, port,
username: name,
auth: 'offline',
version: '1.21.11',
});
bot.loadPlugin(pvp);
bot.loadPlugin(armorManager);
bot.loadPlugin(pathfinder.pathfinder);
const soldier = { bot, name, kills: 0, deaths: 0, alive: false };
bot.once('spawn', () => {
soldier.alive = true;
// Configure pathfinder
try {
const mcData = require('minecraft-data')(bot.version);
const movements = new pathfinder.Movements(bot, mcData);
movements.allowSprinting = true;
movements.canDig = false;
bot.pathfinder.setMovements(movements);
} catch (e) {}
console.log(`[${name}] Ready at (${Math.floor(bot.entity.position.x)}, ${Math.floor(bot.entity.position.y)}, ${Math.floor(bot.entity.position.z)})`);
resolve(soldier);
});
// Respawn on death
bot.on('death', () => {
soldier.alive = false;
soldier.deaths++;
totalDeaths++;
console.log(`[${name}] DIED (kills: ${soldier.kills}, deaths: ${soldier.deaths})`);
setTimeout(() => { try { bot.respawn(); } catch (e) {} }, 2000);
});
bot.on('spawn', () => {
if (soldier.deaths > 0) {
soldier.alive = true;
}
});
// Track kills
bot.on('entityDead', (entity) => {
const ename = (entity.name || entity.type || '').toLowerCase();
if (HOSTILE.has(ename)) {
soldier.kills++;
totalKills++;
}
});
// Counter-attack when hit
bot.on('entityHurt', (entity) => {
if (entity !== bot.entity) return;
const attacker = findNearestHostile(bot, 8);
if (attacker && !bot.pvp.target) {
bot.pvp.attack(attacker);
}
});
bot.on('kicked', (reason) => {
console.log(`[${name}] Kicked: ${JSON.stringify(reason).slice(0, 80)}`);
});
bot.on('error', (err) => {
console.error(`[${name}] Error: ${err.message}`);
});
army.push(soldier);
}, index * 3000); // Stagger spawns by 3 seconds
});
}
function findNearestHostile(bot, maxDist) {
if (!bot.entity) return null;
let nearest = null;
let nearestDist = maxDist;
for (const e of Object.values(bot.entities)) {
if (e === bot.entity) continue;
// Never attack army members or players
if (e.type === 'player') continue;
const ename = (e.name || e.type || '').toLowerCase();
if (!HOSTILE.has(ename)) continue;
const d = e.position.distanceTo(bot.entity.position);
if (d < nearestDist) {
nearestDist = d;
nearest = e;
}
}
return nearest;
}
// ── Autonomous combat loop for all bots ──
setInterval(() => {
for (const soldier of army) {
if (!soldier.alive || !soldier.bot.entity) continue;
const bot = soldier.bot;
const hp = bot.health;
// Find nearest hostile
const target = findNearestHostile(bot, 16);
// Flee if critical
if (hp < 5 && target) {
bot.pvp.stop();
const pos = bot.entity.position;
const dx = pos.x - target.position.x;
const dz = pos.z - target.position.z;
const len = Math.sqrt(dx * dx + dz * dz) || 1;
bot.lookAt(pos.offset((dx / len) * 10, 0, (dz / len) * 10));
bot.setControlState('forward', true);
bot.setControlState('sprint', true);
setTimeout(() => {
bot.setControlState('forward', false);
bot.setControlState('sprint', false);
}, 3000);
continue;
}
// Attack nearest hostile
if (target && !bot.pvp.target) {
bot.pvp.attack(target);
}
// If no target but pvp active, stop
if (!target && bot.pvp.target) {
bot.pvp.stop();
}
}
}, 800);
// ── Status report every 10 seconds ──
setInterval(() => {
const alive = army.filter(s => s.alive).length;
const fighting = army.filter(s => s.bot.pvp?.target).length;
console.log(`\n=== ARMY STATUS: ${alive}/${army.length} alive | ${fighting} fighting | Kills: ${totalKills} | Deaths: ${totalDeaths} ===`);
for (const s of army) {
if (!s.bot.entity) continue;
const pos = s.bot.entity.position;
const target = s.bot.pvp?.target;
const status = !s.alive ? 'DEAD' : target ? `FIGHTING ${target.name || target.type}` : 'PATROLLING';
console.log(` [${s.name}] HP:${Math.round(s.bot.health)} K:${s.kills} D:${s.deaths}${status}`);
}
}, 10000);
// ── Startup ──
console.log(`\n╔════════════════════════════════════════╗`);
console.log(`║ MORTDECAI BOT ARMY — ${count} SOLDIERS ║`);
console.log(`║ Target: ${host}:${port}`);
console.log(`║ Rules: Kill all hostiles, protect allies ║`);
console.log(`╚════════════════════════════════════════╝\n`);
// Whitelist soldiers (skip if dev server / no whitelist)
const { execSync } = require('child_process');
const rconPort = port === 25567 ? 25577 : 25578;
const rconPass = port === 25567 ? 'REDACTED_RCON' : 'REDACTED_RCON';
try {
for (let i = 0; i < count; i++) {
execSync(`python3 -c "from mcrcon import MCRcon; m=MCRcon('${host}','${rconPass}',port=${rconPort}); m.connect(); m.command('whitelist add ${NAMES[i]}'); m.disconnect()"`, { timeout: 5000, stdio: 'pipe' });
}
console.log(`Whitelisted ${count} soldiers`);
} catch (e) {
console.log('Whitelist skipped (dev server or already done)');
}
// Spawn soldiers
(async () => {
for (let i = 0; i < count; i++) {
await spawnSoldier(NAMES[i], 0);
await new Promise(r => setTimeout(r, 3000));
}
console.log(`\nAll ${count} soldiers deployed!`);
// Gear them all up via RCON
try {
execSync(`python3 -c "
from mcrcon import MCRcon
with MCRcon('${host}', '${rconPass}', port=${rconPort}) as mcr:
soldiers = ${JSON.stringify(NAMES.slice(0, count))}
for name in soldiers:
mcr.command(f'give {name} minecraft:iron_sword 1')
mcr.command(f'give {name} minecraft:cooked_beef 16')
mcr.command(f'item replace entity {name} armor.chest with minecraft:iron_chestplate')
mcr.command(f'item replace entity {name} armor.legs with minecraft:iron_leggings')
mcr.command(f'item replace entity {name} armor.feet with minecraft:iron_boots')
mcr.command(f'item replace entity {name} armor.head with minecraft:iron_helmet')
mcr.command(f'effect give {name} minecraft:regeneration 600 1')
print(f'Geared up {len(soldiers)} soldiers')
"`, { timeout: 30000 });
} catch (e) {
console.log('Gear-up error:', e.message?.slice(0, 80));
}
})();
// Graceful shutdown
process.on('SIGINT', () => {
console.log(`\n\nFinal score: ${totalKills} kills, ${totalDeaths} deaths`);
console.log('Disconnecting army...');
army.forEach(s => { try { s.bot.end(); } catch (e) {} });
setTimeout(() => process.exit(0), 2000);
});
+266
View File
@@ -0,0 +1,266 @@
/**
* combat_bot.js — Full combat-capable bot using mineflayer plugins.
*
* Uses:
* mineflayer-pvp — combat AI (attack, strafe, shield)
* mineflayer-pathfinder — A* navigation
* mineflayer-armor-manager — auto-equip best armor
* mineflayer-auto-eat — auto-eat when hungry/hurt
*
* The bot autonomously fights, eats, equips armor, and pathfinds.
* AI frames via stdin set high-level goals.
*/
const mineflayer = require('mineflayer');
const pathfinder = require('mineflayer-pathfinder');
const pvp = require('mineflayer-pvp').plugin;
const armorManager = require('mineflayer-armor-manager');
const autoEat = require('mineflayer-auto-eat').loader;
const { GoalNear, GoalFollow, GoalBlock } = require('mineflayer-pathfinder').goals;
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',
});
// Load plugins
bot.loadPlugin(pathfinder.pathfinder);
bot.loadPlugin(pvp);
bot.loadPlugin(armorManager);
bot.loadPlugin(autoEat);
let deaths = 0;
let kills = 0;
let alive = false;
const HOSTILE = new Set([
'zombie', 'husk', 'skeleton', 'creeper', 'spider', 'cave_spider',
'witch', 'enderman', 'pillager', 'vindicator', 'phantom',
'drowned', 'stray', 'blaze', 'ghast', 'slime', 'magma_cube',
'parched', 'camel_husk',
]);
// ── Spawn setup ──
bot.once('spawn', () => {
alive = true;
deaths++;
// Configure pathfinder
const mcData = require('minecraft-data')(bot.version);
const movements = new pathfinder.Movements(bot, mcData);
movements.allowSprinting = true;
movements.canDig = false; // Don't destroy blocks
bot.pathfinder.setMovements(movements);
// Configure auto-eat
bot.autoEat.options = {
priority: 'foodPoints',
startAt: 16,
bannedFood: [],
};
out({ event: 'ready', msg: 'Plugins loaded. Combat-ready.' });
setTimeout(() => out(observe()), 3000);
});
bot.on('spawn', () => {
if (deaths > 1) {
alive = true;
out({ event: 'respawn', deaths });
setTimeout(() => out(observe()), 3000);
}
});
// ── Combat events ──
bot.on('entityHurt', (entity) => {
if (entity === bot.entity) {
// I'm being hit — find attacker and fight back
const attacker = nearestHostile(8);
if (attacker && !bot.pvp.target) {
out({ event: 'counter_attack', target: attacker.name || attacker.type, dist: Math.round(attacker.position.distanceTo(bot.entity.position)) });
bot.pvp.attack(attacker);
}
}
});
bot.on('playerCollect', (collector, collected) => {
if (collector === bot.entity) {
out({ event: 'pickup', item: collected.name || 'item' });
}
});
bot.on('death', () => {
alive = false;
deaths++;
out({ event: 'death', deaths, kills });
setTimeout(() => { try { bot.respawn(); } catch (e) {} }, 2000);
});
bot.on('chat', (username, message) => {
if (username === bot.username) return;
out({ event: 'chat', from: username, msg: message });
});
bot.on('kicked', (reason) => {
out({ event: 'kicked', reason: typeof reason === 'object' ? JSON.stringify(reason) : String(reason) });
process.exit(1);
});
// Track kills
bot.on('entityDead', (entity) => {
const name = (entity.name || entity.type || '').toLowerCase();
if (HOSTILE.has(name)) {
kills++;
out({ event: 'kill', target: name, totalKills: kills });
}
});
// ── Autonomous combat loop ──
setInterval(() => {
if (!alive || !bot.entity) return;
const hp = bot.health;
const nearest = nearestHostile(16);
// If low HP and being attacked, flee
if (hp < 6 && nearest && nearest.position.distanceTo(bot.entity.position) < 6) {
bot.pvp.stop();
fleeSmart(nearest);
return;
}
// If hostile nearby and not already fighting, engage
if (nearest && !bot.pvp.target) {
const dist = nearest.position.distanceTo(bot.entity.position);
if (dist < 10) {
bot.pvp.attack(nearest);
}
}
}, 1000);
// ── Periodic status report ──
setInterval(() => {
if (!alive) return;
out(observe());
}, 6000);
// ── Helper functions ──
function nearestHostile(maxDist) {
if (!bot.entity) return null;
let nearest = null;
let nearestDist = maxDist || 16;
for (const e of Object.values(bot.entities)) {
if (e === bot.entity) continue;
const name = (e.name || e.type || '').toLowerCase();
if (!HOSTILE.has(name)) continue;
const d = e.position.distanceTo(bot.entity.position);
if (d < nearestDist) {
nearestDist = d;
nearest = e;
}
}
return nearest;
}
function fleeSmart(from) {
if (!from || !bot.entity) return;
const pos = bot.entity.position;
const dx = pos.x - from.position.x;
const dz = pos.z - from.position.z;
const len = Math.sqrt(dx * dx + dz * dz) || 1;
const fleeX = pos.x + (dx / len) * 30;
const fleeZ = pos.z + (dz / len) * 30;
try {
bot.pathfinder.setGoal(new GoalNear(fleeX, pos.y, fleeZ, 2));
} catch (e) {
// Fallback: just sprint
bot.setControlState('forward', true);
bot.setControlState('sprint', true);
setTimeout(() => {
bot.setControlState('forward', false);
bot.setControlState('sprint', false);
}, 3000);
}
}
function observe() {
if (!bot.entity) return { alive: false };
const pos = bot.entity.position;
const mobs = [];
for (const e of Object.values(bot.entities)) {
if (e === bot.entity) continue;
const d = Math.round(e.position.distanceTo(pos));
if (d > 24) continue;
const name = e.name || e.type;
const hostile = HOSTILE.has((name || '').toLowerCase());
if (e.type === 'player') {
mobs.push({ type: 'player', name: e.username, dist: d });
} else if (hostile || d < 10) {
mobs.push({ type: name, dist: d, hostile });
}
}
return {
pos: { x: Math.floor(pos.x), y: Math.floor(pos.y), z: Math.floor(pos.z) },
hp: Math.round(bot.health * 10) / 10,
food: bot.food,
day: (bot.time?.timeOfDay || 0) < 13000,
inv: bot.inventory.items().map(i => `${i.name}x${i.count}`).join(', ') || 'empty',
armor: [5, 6, 7, 8].map(s => bot.inventory.slots[s]?.name).filter(Boolean).join(', ') || 'none',
weapon: bot.heldItem?.name || 'fist',
fighting: bot.pvp.target ? (bot.pvp.target.name || bot.pvp.target.type) : null,
mobs: mobs.sort((a, b) => a.dist - b.dist).slice(0, 8),
kills,
deaths,
};
}
function out(obj) {
console.log(JSON.stringify(obj));
}
// ── AI Frame commands ──
const rl = readline.createInterface({ input: process.stdin });
rl.on('line', (line) => {
const cmd = line.trim();
if (cmd === 'look') out(observe());
else if (cmd === 'hunt') {
// Actively seek and kill nearest hostile
const target = nearestHostile(30);
if (target) {
bot.pvp.attack(target);
out({ action: 'hunting', target: target.name || target.type, dist: Math.round(target.position.distanceTo(bot.entity.position)) });
} else {
out({ action: 'hunt', msg: 'no hostiles nearby' });
}
}
else if (cmd === 'stop') { bot.pvp.stop(); bot.pathfinder.stop(); out({ action: 'stopped' }); }
else if (cmd.startsWith('goto ')) {
const [, x, y, z] = cmd.split(' ').map(Number);
bot.pathfinder.setGoal(new GoalNear(x, y, z, 2));
out({ action: 'navigating', target: { x, y, z } });
}
else if (cmd.startsWith('follow ')) {
const name = cmd.slice(7);
const player = bot.players[name];
if (player?.entity) {
bot.pathfinder.setGoal(new GoalFollow(player.entity, 3), true);
out({ action: 'following', target: name });
}
}
else if (cmd.startsWith('pray ')) { bot.chat(cmd); out({ action: 'pray' }); }
else if (cmd.startsWith('sudo ')) { bot.chat(cmd); out({ action: 'sudo' }); }
else if (cmd.startsWith('say ')) { bot.chat(cmd.slice(4)); }
else if (cmd === 'quit') { bot.end(); process.exit(0); }
else { out({ commands: ['look', 'hunt', 'stop', 'goto X Y Z', 'follow PLAYER', 'pray MSG', 'sudo CMD', 'say MSG', 'quit'] }); }
});
+232
View File
@@ -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'] }));
}
});
+231
View File
@@ -0,0 +1,231 @@
/**
* march_army.js — Bots march in rank-and-file formation around a circular path.
*
* The gateway (RCON) acts as guardian angel — paving the road,
* healing, buffing, clearing threats, and putting on a show.
*/
const mineflayer = require('mineflayer');
const pvp = require('mineflayer-pvp').plugin;
const http = require('http');
const host = process.argv[2] || '192.168.0.244';
const port = parseInt(process.argv[3] || '25568', 10);
const COUNT = parseInt(process.argv[4] || '8', 10);
const NAMES = [
'Guard_I', 'Guard_II', 'Guard_III', 'Guard_IV',
'Guard_V', 'Guard_VI', 'Guard_VII', 'Guard_VIII',
];
const HOSTILE = new Set(['zombie', 'husk', 'skeleton', 'creeper', 'spider', 'drowned', 'parched', 'phantom', 'witch']);
const bots = [];
let marchAngle = 0;
const RADIUS = 25;
const MARCH_SPEED = 0.02; // radians per tick — slow stately march
const FORMATION_SPACING = Math.PI / (COUNT * 1.2); // spacing between soldiers
// ── RCON helper (gateway control) ──
function rcon(cmd) {
return new Promise((resolve) => {
const body = JSON.stringify({ command: cmd });
const req = http.request({
hostname: host, port: 25578, path: '/api/chat',
method: 'POST', headers: { 'Content-Type': 'application/json' },
}, () => resolve());
req.on('error', () => resolve());
// Actually just use python for reliability
resolve();
});
}
function rconExec(cmd) {
const { execSync } = require('child_process');
try {
execSync(`python3 -c "from mcrcon import MCRcon; m=MCRcon('${host}','REDACTED_RCON',port=25578); m.connect(); m.command('${cmd.replace(/'/g, "\\'")}'); m.disconnect()"`, { timeout: 3000, stdio: 'pipe' });
} catch (e) {}
}
// ── Spawn soldiers ──
function spawnSoldier(name, index) {
return new Promise((resolve) => {
console.log(`[${name}] Deploying...`);
const bot = mineflayer.createBot({
host, port, username: name, auth: 'offline', version: '1.21.11',
});
bot.loadPlugin(pvp);
const soldier = { bot, name, index, alive: false, kills: 0 };
bot.once('spawn', () => {
soldier.alive = true;
console.log(`[${name}] Ready`);
resolve(soldier);
});
bot.on('death', () => {
soldier.alive = false;
setTimeout(() => { try { bot.respawn(); } catch (e) {} }, 2000);
});
bot.on('spawn', () => { soldier.alive = true; });
bot.on('entityHurt', (e) => {
if (e !== bot.entity) return;
// Counter-attack
for (const ent of Object.values(bot.entities)) {
if (ent === bot.entity || ent.type === 'player') continue;
if (HOSTILE.has((ent.name || '').toLowerCase()) && ent.position.distanceTo(bot.entity.position) < 6) {
bot.pvp.attack(ent);
break;
}
}
});
bot.on('entityDead', (e) => {
if (HOSTILE.has((e.name || '').toLowerCase())) soldier.kills++;
});
bot.on('kicked', () => {
console.log(`[${name}] Kicked, retrying in 10s...`);
setTimeout(() => spawnSoldier(name, index).then(s => { bots[index] = s; }), 10000);
});
bot.on('error', () => {});
bots.push(soldier);
});
}
// ── March loop — move soldiers in formation ──
setInterval(() => {
marchAngle += MARCH_SPEED;
for (let i = 0; i < bots.length; i++) {
const s = bots[i];
if (!s || !s.alive || !s.bot.entity) continue;
// Calculate this soldier's position on the circle
const angle = marchAngle + (i * FORMATION_SPACING);
const targetX = Math.cos(angle) * RADIUS;
const targetZ = Math.sin(angle) * RADIUS;
const targetY = 80; // road level
// Look in march direction (tangent to circle)
const lookAngle = angle + Math.PI / 2;
const lookX = s.bot.entity.position.x + Math.cos(lookAngle) * 5;
const lookZ = s.bot.entity.position.z + Math.sin(lookAngle) * 5;
// Move toward target position
const pos = s.bot.entity.position;
const dx = targetX - pos.x;
const dz = targetZ - pos.z;
const dist = Math.sqrt(dx * dx + dz * dz);
if (dist > 2) {
// Sprint to catch up
s.bot.lookAt(s.bot.entity.position.offset(dx, 0, dz));
s.bot.setControlState('forward', true);
s.bot.setControlState('sprint', dist > 5);
} else {
// In position — walk slowly
const target = s.bot.entity.position.offset(
Math.cos(angle + Math.PI / 2) * 3,
0,
Math.sin(angle + Math.PI / 2) * 3
);
s.bot.lookAt(target);
s.bot.setControlState('forward', true);
s.bot.setControlState('sprint', false);
}
}
}, 200);
// ── Guardian Angel — gateway clears threats and buffs soldiers every 15s ──
setInterval(() => {
const { execSync } = require('child_process');
try {
execSync(`python3 -c "
from mcrcon import MCRcon
import math
with MCRcon('${host}', 'REDACTED_RCON', port=25578) as mcr:
# Kill all hostiles near the march route
mcr.command('kill @e[type=minecraft:zombie,distance=..60]')
mcr.command('kill @e[type=minecraft:husk,distance=..60]')
mcr.command('kill @e[type=minecraft:skeleton,distance=..60]')
mcr.command('kill @e[type=minecraft:spider,distance=..60]')
mcr.command('kill @e[type=minecraft:creeper,distance=..60]')
mcr.command('kill @e[type=minecraft:phantom,distance=..60]')
# Buff all soldiers
names = ${JSON.stringify(NAMES.slice(0, COUNT))}
for name in names:
mcr.command(f'effect give {name} minecraft:regeneration 30 1')
mcr.command(f'effect give {name} minecraft:resistance 30 1')
mcr.command(f'effect give {name} minecraft:glowing 30 0')
# Dramatic effects — fireworks at the lead soldier's position
angle = ${marchAngle} if ${marchAngle} else 0
fx = int(25 * math.cos(angle))
fz = int(25 * math.sin(angle))
mcr.command(f'summon minecraft:firework_rocket {fx} 82 {fz}')
"`, { timeout: 10000, stdio: 'pipe' });
} catch (e) {}
}, 15000);
// ── Status report ──
setInterval(() => {
const alive = bots.filter(s => s.alive).length;
const totalKills = bots.reduce((sum, s) => sum + s.kills, 0);
console.log(`MARCH: ${alive}/${bots.length} marching | Kills: ${totalKills} | Angle: ${(marchAngle * 180 / Math.PI).toFixed(0)}°`);
}, 8000);
// ── Deploy ──
console.log(`\n⚔️ MORTDECAI ROYAL GUARD — ${COUNT} soldiers deploying ⚔️\n`);
(async () => {
for (let i = 0; i < COUNT; i++) {
await spawnSoldier(NAMES[i], i);
await new Promise(r => setTimeout(r, 6000));
}
console.log(`\n🛡️ All ${COUNT} guards deployed! Marching begins.\n`);
// Gear up
const { execSync } = require('child_process');
try {
execSync(`python3 -c "
from mcrcon import MCRcon
with MCRcon('${host}', 'REDACTED_RCON', port=25578) as mcr:
names = ${JSON.stringify(NAMES.slice(0, COUNT))}
for name in names:
mcr.command(f'give {name} minecraft:iron_sword 1')
mcr.command(f'item replace entity {name} armor.chest with minecraft:diamond_chestplate')
mcr.command(f'item replace entity {name} armor.legs with minecraft:diamond_leggings')
mcr.command(f'item replace entity {name} armor.feet with minecraft:diamond_boots')
mcr.command(f'item replace entity {name} armor.head with minecraft:diamond_helmet')
mcr.command(f'effect give {name} minecraft:regeneration 600 2')
mcr.command(f'effect give {name} minecraft:glowing 600 0')
# TP first to march starting positions
import math
for i, name in enumerate(names):
angle = i * math.pi / (len(names) * 1.2)
x = int(25 * math.cos(angle))
z = int(25 * math.sin(angle))
mcr.command(f'tp {name} {x} 80 {z}')
print(f'Guards geared and positioned')
mcr.command('tellraw @a [{\"text\":\"⚔️ \",\"color\":\"gold\"},{\"text\":\"THE ROYAL GUARD MARCHES! \",\"color\":\"gold\",\"bold\":true},{\"text\":\"Behold the divine army in formation.\",\"color\":\"yellow\"}]')
# TP slingshooter to watch from center monument
mcr.command('tp slingshooter08 0 90 0 0 30')
"`, { timeout: 30000, stdio: 'pipe' });
} catch (e) {
console.log('Gear-up error:', e.message?.slice(0, 80));
}
})();
process.on('SIGINT', () => {
console.log('\nDismissing the guard...');
bots.forEach(s => { try { s.bot.end(); } catch (e) {} });
setTimeout(() => process.exit(0), 2000);
});
+298
View File
@@ -0,0 +1,298 @@
/**
* mortal_claude.js — Claude experiences Minecraft as a mortal.
*
* No RCON. No creative mode. No cheats. Just survival.
* Accepts commands via stdin, reports observations as JSON.
* Can pray to God and use sudo like a real player.
*/
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',
});
let alive = true;
let spawnCount = 0;
// ── Observation ──
function observe() {
if (!bot.entity) return { error: 'not spawned' };
const pos = bot.entity.position;
const obs = {
pos: { x: Math.floor(pos.x), y: Math.floor(pos.y), z: Math.floor(pos.z) },
hp: Math.round(bot.health * 10) / 10,
food: bot.food,
xp: bot.experience?.level || 0,
time: bot.time?.timeOfDay || 0,
isDay: (bot.time?.timeOfDay || 0) < 13000,
rain: bot.isRaining || false,
below: bname(pos.offset(0, -1, 0)),
at: bname(pos),
};
// Nearby threats and friendlies
obs.mobs = [];
obs.players = [];
for (const e of Object.values(bot.entities)) {
if (e === bot.entity) continue;
const d = Math.round(e.position.distanceTo(pos));
if (d > 24) continue;
if (e.type === 'player') {
obs.players.push({ name: e.username, dist: d });
} else if (e.type === 'mob' || e.type === 'hostile') {
obs.mobs.push({ type: e.name || e.type, dist: d });
}
}
// Inventory summary
const items = bot.inventory.items();
if (items.length) {
obs.inventory = items.map(i => `${i.name}x${i.count}`).join(', ');
} else {
obs.inventory = 'empty';
}
// Armor
const armor = [bot.inventory.slots[5], bot.inventory.slots[6], bot.inventory.slots[7], bot.inventory.slots[8]];
obs.armor = armor.filter(Boolean).map(a => a.name).join(', ') || 'none';
// Terrain scan (compact)
const terrain = new Map();
for (let dx = -3; dx <= 3; dx++) {
for (let dz = -3; dz <= 3; dz++) {
const b = bot.blockAt(pos.offset(dx, -1, dz));
if (b) {
const n = b.name;
terrain.set(n, (terrain.get(n) || 0) + 1);
}
}
}
obs.ground = Object.fromEntries(terrain);
return obs;
}
function bname(vec) {
const b = bot.blockAt(vec);
return b ? b.name : '?';
}
// ── Survival actions ──
function eat() {
const food = bot.inventory.items().find(i =>
['cooked_beef', 'bread', 'cooked_porkchop', 'cooked_chicken', 'cooked_mutton',
'golden_apple', 'apple', 'baked_potato', 'cooked_cod', 'cooked_salmon',
'sweet_berries', 'melon_slice', 'carrot', 'potato'].includes(i.name)
);
if (food) {
bot.equip(food, 'hand').then(() => {
bot.activateItem();
setTimeout(() => bot.deactivateItem(), 1800);
}).catch(() => {});
return food.name;
}
return null;
}
function flee() {
// Run away from nearest hostile
const pos = bot.entity.position;
let nearestMob = null;
let nearestDist = Infinity;
for (const e of Object.values(bot.entities)) {
if (e === bot.entity || e.type === 'player') continue;
const d = e.position.distanceTo(pos);
if (d < nearestDist && d < 16) {
nearestDist = d;
nearestMob = e;
}
}
if (nearestMob) {
// Run opposite direction
const dx = pos.x - nearestMob.position.x;
const dz = pos.z - nearestMob.position.z;
const len = Math.sqrt(dx * dx + dz * dz) || 1;
const target = pos.offset((dx / len) * 10, 0, (dz / len) * 10);
bot.lookAt(target);
bot.setControlState('forward', true);
bot.setControlState('sprint', true);
setTimeout(() => {
bot.setControlState('forward', false);
bot.setControlState('sprint', false);
}, 4000);
return true;
}
return false;
}
function attack() {
const pos = bot.entity.position;
let nearest = null;
let nearestDist = Infinity;
for (const e of Object.values(bot.entities)) {
if (e === bot.entity || e.type === 'player') continue;
const d = e.position.distanceTo(pos);
if (d < 4 && d < nearestDist) {
nearestDist = d;
nearest = e;
}
}
if (nearest) {
// Equip best weapon
const weapon = bot.inventory.items().find(i => i.name.includes('sword') || i.name.includes('axe'));
if (weapon) bot.equip(weapon, 'hand').catch(() => {});
bot.attack(nearest);
return nearest.name || nearest.type;
}
return null;
}
// ── Events ──
bot.on('login', () => {
out({ event: 'login', name: bot.username });
});
bot.on('spawn', () => {
spawnCount++;
alive = true;
// Wait for chunks
setTimeout(() => {
const o = observe();
out({ event: spawnCount === 1 ? 'first_spawn' : 'respawn', ...o });
}, 3000);
});
bot.on('chat', (username, message) => {
if (username === bot.username) return;
out({ event: 'chat', from: username, msg: message });
});
bot.on('whisper', (username, message) => {
out({ event: 'whisper', from: username, msg: message });
});
bot.on('health', () => {
if (bot.health <= 5 && alive) {
out({ event: 'low_hp', hp: Math.round(bot.health * 10) / 10, food: bot.food });
}
});
bot.on('death', () => {
alive = false;
out({ event: 'death', spawns: spawnCount, msg: 'I died.' });
// Auto respawn
setTimeout(() => {
try { bot.respawn(); } catch (e) {}
}, 2000);
});
bot.on('entityHurt', (entity) => {
if (entity === bot.entity) {
out({ event: 'hurt', hp: Math.round(bot.health * 10) / 10 });
}
});
bot.on('kicked', (reason) => {
out({ event: 'kicked', reason: typeof reason === 'object' ? JSON.stringify(reason) : String(reason) });
process.exit(1);
});
bot.on('error', (err) => {
out({ event: 'error', msg: err.message });
});
// ── Output ──
function out(obj) {
console.log(JSON.stringify(obj));
}
// ── Command interface ──
const rl = readline.createInterface({ input: process.stdin });
rl.on('line', async (line) => {
const cmd = line.trim();
if (!cmd) return;
if (cmd === 'look') {
out(observe());
}
else if (cmd === 'eat') {
const food = eat();
out({ action: 'eat', food: food || 'nothing edible' });
}
else if (cmd === 'flee') {
const fled = flee();
out({ action: 'flee', success: fled });
if (fled) setTimeout(() => out(observe()), 4500);
}
else if (cmd === 'fight') {
const target = attack();
out({ action: 'fight', target: target || 'nothing nearby' });
}
else if (cmd === 'forward') {
bot.setControlState('forward', true);
setTimeout(() => { bot.setControlState('forward', false); out(observe()); }, 3000);
}
else if (cmd === 'back') {
bot.setControlState('back', true);
setTimeout(() => { bot.setControlState('back', false); out(observe()); }, 2000);
}
else if (cmd === 'left') {
bot.setControlState('left', true);
setTimeout(() => { bot.setControlState('left', false); out(observe()); }, 1500);
}
else if (cmd === 'right') {
bot.setControlState('right', true);
setTimeout(() => { bot.setControlState('right', false); out(observe()); }, 1500);
}
else if (cmd === 'jump') {
bot.setControlState('jump', true);
setTimeout(() => { bot.setControlState('jump', false); }, 500);
}
else if (cmd === 'sprint') {
bot.setControlState('forward', true);
bot.setControlState('sprint', true);
setTimeout(() => {
bot.setControlState('forward', false);
bot.setControlState('sprint', false);
out(observe());
}, 5000);
}
else if (cmd.startsWith('pray ')) {
bot.chat(cmd); // sends "pray <message>" to the server
out({ action: 'pray', prayer: cmd.slice(5) });
}
else if (cmd.startsWith('sudo ')) {
bot.chat(cmd);
out({ action: 'sudo', command: cmd.slice(5) });
}
else if (cmd.startsWith('say ')) {
bot.chat(cmd.slice(4));
}
else if (cmd === 'inventory') {
const items = bot.inventory.items();
out({ inventory: items.map(i => ({ name: i.name, count: i.count })) });
}
else if (cmd === 'quit') {
bot.end();
process.exit(0);
}
else {
out({ error: 'commands: look, eat, flee, fight, forward, back, left, right, jump, sprint, pray <msg>, sudo <cmd>, say <msg>, inventory, quit' });
}
});
+210
View File
@@ -0,0 +1,210 @@
/**
* multilingual_bots.js — Test mortdecai's multilingual handling.
*
* Uses translategemma:27b on Matt's Strix Halo to translate prayers into
* various languages, then sends them to the dev server via mineflayer bots.
*
* Usage: node multilingual_bots.js [count] [host] [port]
* Defaults: 2 bots, 192.168.0.244:25568
*
* Requires: translategemma:27b on Matt's Ollama (via billing gateway)
*/
const mineflayer = require('mineflayer');
const http = require('http');
const count = parseInt(process.argv[2] || '2', 10);
const host = process.argv[3] || '192.168.0.244';
const port = parseInt(process.argv[4] || '25568', 10);
// Matt's Ollama via billing gateway proxy on steel141
const OLLAMA_URL = 'http://192.168.0.141:11436';
const TRANSLATE_MODEL = 'translategemma:27b';
const LANGUAGES = [
'Japanese', 'Spanish', 'French', 'German', 'Russian',
'Korean', 'Portuguese', 'Italian', 'Chinese (Simplified)',
'Arabic', 'Hindi', 'Turkish', 'Polish', 'Dutch',
'Swedish', 'Thai', 'Vietnamese', 'Indonesian',
];
const ENGLISH_PRAYERS = [
'Lord, give me a diamond sword',
'God please heal me I am dying',
'Make it stop raining',
'I need food desperately',
'Teleport me to spawn',
'Give me the best armor you have',
'Smite the zombies attacking me',
'I want to fly',
'Make it nighttime',
'Give all players diamonds',
'Build me a house',
'Where should I go?',
'Help there are creepers everywhere',
'Give me 64 torches',
'I am lost in a cave please help',
'Change the weather to clear',
'Grant me strength and speed',
'I offer my wheat as tribute',
'The skeletons are too strong',
'Bless this land',
];
const ENGLISH_SUDO = [
'sudo give me creative mode',
'sudo set time to day',
'sudo kill all hostile mobs nearby',
'sudo give me 32 cooked beef',
'sudo tp me to 0 64 0',
'sudo gamemode survival',
'sudo weather clear',
'sudo give me a bow and arrows',
'sudo effect give me speed',
'sudo set difficulty to hard',
];
// --- Ollama translation ---
function ollamaTranslate(text, targetLang) {
return new Promise((resolve, reject) => {
const body = JSON.stringify({
model: TRANSLATE_MODEL,
messages: [
{
role: 'user',
content: `Translate the following Minecraft player message to ${targetLang}. Return ONLY the translated text, nothing else.\n\n"${text}"`,
},
],
stream: false,
options: { temperature: 0.3, num_predict: 200 },
});
const url = new URL(OLLAMA_URL + '/api/chat');
const options = {
hostname: url.hostname,
port: url.port,
path: url.pathname,
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
timeout: 60000,
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => {
try {
const parsed = JSON.parse(data);
const translated = (parsed.message?.content || '').trim().replace(/^["']|["']$/g, '');
resolve(translated || text); // fallback to English if empty
} catch (e) {
resolve(text);
}
});
});
req.on('error', () => resolve(text));
req.on('timeout', () => { req.destroy(); resolve(text); });
req.write(body);
req.end();
});
}
// --- Bot management ---
const bots = [];
let totalSent = 0;
function spawnBot(name, index) {
console.log(`[${name}] Connecting to ${host}:${port}...`);
const bot = mineflayer.createBot({
host,
port,
username: name,
auth: 'offline',
version: '1.21.11',
});
bot.on('login', () => {
console.log(`[${name}] Connected (${bots.length + 1}/${count})`);
});
bot.on('spawn', () => {
console.log(`[${name}] Spawned — starting multilingual prayers`);
scheduleNext(bot, name);
});
bot.on('chat', (username, message) => {
if (username === name) return;
if (message.includes('GOD') || message.includes('SUDO') || message.includes('DEV GOD')) {
console.log(`[${name}] RECV: ${message.slice(0, 120)}`);
}
});
bot.on('kicked', (reason) => {
console.log(`[${name}] Kicked: ${JSON.stringify(reason).slice(0, 100)}`);
});
bot.on('error', (err) => {
console.error(`[${name}] Error: ${err.message}`);
});
bots.push(bot);
}
async function scheduleNext(bot, name) {
// Random delay 20-60 seconds
const delay = 20000 + Math.random() * 40000;
setTimeout(async () => {
if (!bot.entity) return;
try {
// Pick random language and prayer
const lang = LANGUAGES[Math.floor(Math.random() * LANGUAGES.length)];
const isPray = Math.random() > 0.3; // 70% prayers, 30% sudo
const pool = isPray ? ENGLISH_PRAYERS : ENGLISH_SUDO;
const english = pool[Math.floor(Math.random() * pool.length)];
// Translate
console.log(`[${name}] Translating to ${lang}: "${english}"`);
const translated = await ollamaTranslate(english, lang);
// Send as prayer or sudo
const prefix = isPray ? 'pray ' : '';
const message = prefix + translated;
totalSent++;
console.log(`[${name}] SEND (#${totalSent}) [${lang}]: ${message.slice(0, 100)}`);
bot.chat(message);
} catch (err) {
console.error(`[${name}] Translation error: ${err.message}`);
}
scheduleNext(bot, name);
}, delay);
}
// --- Startup ---
console.log(`Spawning ${count} multilingual bots on ${host}:${port}`);
console.log(`Using ${TRANSLATE_MODEL} on ${OLLAMA_URL} for translation`);
console.log(`Languages: ${LANGUAGES.join(', ')}`);
console.log(`Interaction interval: 20-60s per bot`);
console.log('Press Ctrl+C to stop\n');
const BOT_NAMES = [
'LinguaBot_0', 'LinguaBot_1', 'LinguaBot_2', 'LinguaBot_3',
'LinguaBot_4', 'LinguaBot_5', 'LinguaBot_6', 'LinguaBot_7',
];
for (let i = 0; i < count; i++) {
setTimeout(() => spawnBot(BOT_NAMES[i] || `LinguaBot_${i}`, i), i * 5000);
}
// Graceful shutdown
process.on('SIGINT', () => {
console.log(`\nShutting down ${bots.length} bots (${totalSent} total messages sent)...`);
bots.forEach((b) => { try { b.end(); } catch (_) {} });
setTimeout(() => process.exit(0), 2000);
});
+178
View File
@@ -0,0 +1,178 @@
/**
* rl_bot.js — Minimal mineflayer bot for RL training.
*
* Communicates via stdin (actions) / stdout (observations) as JSON.
* Designed to be spawned by minecraft_env.py as a subprocess.
*
* Usage: node rl_bot.js [host] [port] [username]
*/
const mineflayer = require('mineflayer');
const pvp = require('mineflayer-pvp').plugin;
const readline = require('readline');
const host = process.argv[2] || '192.168.0.244';
const port = parseInt(process.argv[3] || '25568', 10);
const username = process.argv[4] || 'RLBot';
const HOSTILE = new Set([
'zombie', 'husk', 'skeleton', 'creeper', 'spider', 'cave_spider',
'witch', 'enderman', 'drowned', 'stray', 'phantom', 'parched',
'camel_husk', 'slime', 'magma_cube',
]);
const bot = mineflayer.createBot({ host, port, username, auth: 'offline', version: '1.21.11' });
bot.loadPlugin(pvp);
let alive = false;
let kills = 0;
let deaths = 0;
let died = false;
function out(obj) {
try { process.stdout.write(JSON.stringify(obj) + '\n'); } catch (e) {}
}
function observe() {
if (!bot.entity) return { hp: 0, died: true };
const pos = bot.entity.position;
const mobs = [];
for (const e of Object.values(bot.entities)) {
if (e === bot.entity) continue;
const d = Math.round(e.position.distanceTo(pos));
if (d > 24) continue;
const name = (e.name || e.type || '').toLowerCase();
const hostile = HOSTILE.has(name);
if (e.type === 'player') continue; // ignore players
if (hostile || d < 8) {
mobs.push({ type: name, dist: d, hostile });
}
}
return {
pos: { x: Math.floor(pos.x), y: Math.floor(pos.y), z: Math.floor(pos.z) },
hp: Math.round(bot.health * 10) / 10,
food: bot.food,
day: (bot.time?.timeOfDay || 0) < 13000,
below: bot.blockAt(pos.offset(0, -1, 0))?.name || '?',
inv: bot.inventory.items().map(i => `${i.name}x${i.count}`).join(', ') || 'empty',
armor: [5, 6, 7, 8].map(s => bot.inventory.slots[s]?.name).filter(Boolean).join(', ') || 'none',
mobs: mobs.sort((a, b) => a.dist - b.dist).slice(0, 8),
kills,
deaths,
died,
};
}
function nearestHostile(maxDist) {
if (!bot.entity) return null;
let nearest = null;
let nd = maxDist || 16;
for (const e of Object.values(bot.entities)) {
if (e === bot.entity || e.type === 'player') continue;
if (!HOSTILE.has((e.name || '').toLowerCase())) continue;
const d = e.position.distanceTo(bot.entity.position);
if (d < nd) { nd = d; nearest = e; }
}
return nearest;
}
function stopMoving() {
['forward', 'back', 'left', 'right', 'sprint', 'jump'].forEach(s => bot.setControlState(s, false));
}
// ── Events ──
bot.once('spawn', () => {
alive = true;
died = false;
out({ event: 'ready', ...observe() });
});
bot.on('spawn', () => { alive = true; died = false; });
bot.on('death', () => {
alive = false;
died = true;
deaths++;
out({ event: 'death', ...observe() });
setTimeout(() => { try { bot.respawn(); } catch (e) {} }, 2000);
});
bot.on('entityDead', (e) => {
if (HOSTILE.has((e.name || '').toLowerCase())) kills++;
});
bot.on('kicked', () => process.exit(1));
bot.on('error', () => {});
// ── Actions (from stdin) ──
const rl = readline.createInterface({ input: process.stdin });
rl.on('line', (line) => {
const cmd = line.trim();
died = false; // reset per-tick death flag
if (cmd === 'observe') {
out(observe());
}
else if (cmd === 'forward') {
stopMoving();
bot.setControlState('forward', true);
setTimeout(() => { bot.setControlState('forward', false); out(observe()); }, 500);
}
else if (cmd === 'fight') {
const target = nearestHostile(6);
if (target) {
const weapon = bot.inventory.items().find(i => i.name.includes('sword'));
if (weapon) bot.equip(weapon, 'hand').catch(() => {});
bot.lookAt(target.position.offset(0, 1, 0));
bot.attack(target);
}
out(observe());
}
else if (cmd === 'flee') {
const target = nearestHostile(16);
if (target) {
const pos = bot.entity.position;
const dx = pos.x - target.position.x;
const dz = pos.z - target.position.z;
const len = Math.sqrt(dx * dx + dz * dz) || 1;
bot.lookAt(pos.offset((dx / len) * 10, 0, (dz / len) * 10));
}
stopMoving();
bot.setControlState('forward', true);
bot.setControlState('sprint', true);
bot.setControlState('jump', true);
setTimeout(() => {
bot.setControlState('jump', false);
setTimeout(() => { stopMoving(); out(observe()); }, 400);
}, 200);
}
else if (cmd === 'eat') {
const food = bot.inventory.items().find(i =>
['cooked_beef', 'bread', 'cooked_porkchop', 'golden_apple',
'cooked_chicken', 'apple', 'baked_potato', 'carrot'].includes(i.name)
);
if (food) {
bot.equip(food, 'hand').then(() => {
bot.activateItem();
setTimeout(() => { bot.deactivateItem(); out(observe()); }, 1600);
}).catch(() => out(observe()));
} else {
out(observe());
}
}
else if (cmd === 'sprint') {
stopMoving();
bot.setControlState('forward', true);
bot.setControlState('sprint', true);
setTimeout(() => { stopMoving(); out(observe()); }, 500);
}
else if (cmd === 'idle') {
stopMoving();
out(observe());
}
else if (cmd === 'quit') {
bot.end();
process.exit(0);
}
});
+282
View File
@@ -0,0 +1,282 @@
/**
* survival_bot.js — Frame-based AI with reactive micro-scripts between frames.
*
* The bot runs continuous survival behaviors autonomously:
* - Attack nearby hostiles (sword swing every 600ms)
* - Eat when hungry or low HP
* - Flee when overwhelmed
* - Shield when taking ranged damage
*
* AI frames come via stdin commands that set the HIGH-LEVEL policy.
* Between frames, the bot executes the policy autonomously.
*/
const mineflayer = require('mineflayer');
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',
});
// ── Policy state (set by AI frames, executed by tick loop) ──
let policy = {
mode: 'survive', // 'survive' | 'explore' | 'flee' | 'idle'
fleeHp: 5, // flee threshold
fightRange: 5, // engage hostiles within this range
exploreDir: null, // {x, z} direction to walk
waypoint: null, // {x, y, z} target position
};
let alive = false;
let deaths = 0;
let tickCount = 0;
const HOSTILE = new Set([
'zombie', 'husk', 'skeleton', 'creeper', 'spider', 'cave_spider',
'witch', 'enderman', 'pillager', 'vindicator', 'ravager', 'phantom',
'drowned', 'stray', 'blaze', 'ghast', 'slime', 'magma_cube',
'parched', 'camel_husk',
]);
// ── Core tick loop (runs every 600ms — sword cooldown rate) ──
setInterval(() => {
if (!alive || !bot.entity) return;
tickCount++;
const pos = bot.entity.position;
const hp = bot.health;
const food = bot.food;
// Find nearest hostile
let nearestHostile = null;
let nearestDist = Infinity;
let hostileCount = 0;
for (const e of Object.values(bot.entities)) {
if (e === bot.entity) continue;
const name = (e.name || e.type || '').toLowerCase();
if (!HOSTILE.has(name)) continue;
const d = e.position.distanceTo(pos);
if (d > 24) continue;
hostileCount++;
if (d < nearestDist) {
nearestDist = d;
nearestHostile = e;
}
}
// ── REACTIVE BEHAVIORS (priority order) ──
// 1. CRITICAL: Eat if low HP and have food
if (hp < 10 && food < 20 && !bot.food >= 20) {
tryEat();
}
// 2. FLEE if HP critical or overwhelmed
if (hp < policy.fleeHp || (hostileCount >= 3 && hp < 12)) {
fleeFrom(nearestHostile);
return;
}
// 3. FIGHT if hostile in range
if (nearestHostile && nearestDist < policy.fightRange) {
// Equip sword if not already
equipWeapon();
// Face and attack
bot.lookAt(nearestHostile.position.offset(0, 1, 0));
bot.attack(nearestHostile);
// Strafe sideways while fighting (dodge)
if (tickCount % 4 < 2) {
bot.setControlState('left', true);
bot.setControlState('right', false);
} else {
bot.setControlState('left', false);
bot.setControlState('right', true);
}
return;
}
// 4. APPROACH if hostile nearby but out of melee range
if (nearestHostile && nearestDist < 12 && nearestDist > policy.fightRange && policy.mode === 'survive') {
bot.lookAt(nearestHostile.position.offset(0, 1, 0));
bot.setControlState('forward', true);
bot.setControlState('sprint', nearestDist > 8);
return;
}
// 5. Stop strafing when not fighting
bot.setControlState('left', false);
bot.setControlState('right', false);
// 6. EXPLORE if no threats
if (policy.mode === 'explore' && policy.waypoint) {
const dist = pos.distanceTo(new (require('vec3'))(policy.waypoint.x, policy.waypoint.y, policy.waypoint.z));
if (dist > 3) {
bot.lookAt(new (require('vec3'))(policy.waypoint.x, pos.y, policy.waypoint.z));
bot.setControlState('forward', true);
bot.setControlState('sprint', dist > 10);
} else {
bot.setControlState('forward', false);
bot.setControlState('sprint', false);
out({ event: 'arrived', waypoint: policy.waypoint });
policy.waypoint = null;
}
} else if (policy.mode !== 'idle') {
bot.setControlState('forward', false);
bot.setControlState('sprint', false);
}
// 7. EAT if food is low (passive)
if (food < 14 && hp < 18) {
tryEat();
}
// 8. Report status every 10 ticks (~6 seconds)
if (tickCount % 10 === 0) {
out(observe());
}
}, 600);
// ── Helper functions ──
function equipWeapon() {
const sword = bot.inventory.items().find(i => i.name.includes('sword'));
if (sword && bot.heldItem?.name !== sword.name) {
bot.equip(sword, 'hand').catch(() => {});
}
}
function tryEat() {
const food = bot.inventory.items().find(i =>
['cooked_beef', 'bread', 'cooked_porkchop', 'golden_apple', 'baked_potato',
'cooked_chicken', 'apple', 'cooked_cod', 'carrot'].includes(i.name)
);
if (food) {
bot.equip(food, 'hand').then(() => {
bot.activateItem();
setTimeout(() => bot.deactivateItem(), 1800);
}).catch(() => {});
}
}
function fleeFrom(entity) {
if (!entity) {
// Random direction
bot.setControlState('forward', true);
bot.setControlState('sprint', true);
return;
}
const pos = bot.entity.position;
const dx = pos.x - entity.position.x;
const dz = pos.z - entity.position.z;
const len = Math.sqrt(dx * dx + dz * dz) || 1;
const target = pos.offset((dx / len) * 20, 0, (dz / len) * 20);
bot.lookAt(target);
bot.setControlState('forward', true);
bot.setControlState('sprint', true);
bot.setControlState('jump', true);
setTimeout(() => bot.setControlState('jump', false), 400);
}
function observe() {
if (!bot.entity) return { alive: false };
const pos = bot.entity.position;
const mobs = [];
for (const e of Object.values(bot.entities)) {
if (e === bot.entity) continue;
const d = Math.round(e.position.distanceTo(pos));
if (d > 24) continue;
const name = e.name || e.type;
if (e.type === 'player') {
mobs.push({ type: 'player', name: e.username, dist: d });
} else {
mobs.push({ type: name, dist: d, hostile: HOSTILE.has((name || '').toLowerCase()) });
}
}
// Compact terrain
const ground = {};
for (let dx = -2; dx <= 2; dx++) {
for (let dz = -2; dz <= 2; dz++) {
const b = bot.blockAt(pos.offset(dx, -1, dz));
if (b) ground[b.name] = (ground[b.name] || 0) + 1;
}
}
return {
frame: tickCount,
pos: { x: Math.floor(pos.x), y: Math.floor(pos.y), z: Math.floor(pos.z) },
hp: Math.round(bot.health * 10) / 10,
food: bot.food,
day: (bot.time?.timeOfDay || 0) < 13000,
below: bot.blockAt(pos.offset(0, -1, 0))?.name || '?',
inv: bot.inventory.items().map(i => `${i.name}x${i.count}`).join(', ') || 'empty',
armor: [5, 6, 7, 8].map(s => bot.inventory.slots[s]?.name).filter(Boolean).join(', ') || 'none',
mobs: mobs.sort((a, b) => a.dist - b.dist).slice(0, 8),
ground,
policy: policy.mode,
deaths,
};
}
function out(obj) {
console.log(JSON.stringify(obj));
}
// ── Events ──
bot.on('login', () => out({ event: 'login' }));
bot.on('spawn', () => {
alive = true;
deaths++;
setTimeout(() => out({ event: deaths === 1 ? 'first_spawn' : 'respawn', ...observe() }), 3000);
});
bot.on('death', () => {
alive = false;
out({ event: 'death', deaths, msg: 'I died.' });
setTimeout(() => { try { bot.respawn(); } catch (e) {} }, 2000);
});
bot.on('chat', (username, message) => {
if (username === bot.username) return;
out({ event: 'chat', from: username, msg: message });
});
bot.on('kicked', (reason) => {
out({ event: 'kicked', reason: typeof reason === 'object' ? JSON.stringify(reason) : String(reason) });
process.exit(1);
});
// ── AI Frame commands (stdin) ──
const rl = readline.createInterface({ input: process.stdin });
rl.on('line', (line) => {
const cmd = line.trim();
if (cmd === 'look') out(observe());
else if (cmd === 'survive') { policy.mode = 'survive'; out({ policy: 'survive' }); }
else if (cmd === 'idle') { policy.mode = 'idle'; stopMoving(); out({ policy: 'idle' }); }
else if (cmd === 'flee') { policy.mode = 'survive'; policy.fleeHp = 15; out({ policy: 'flee_mode' }); }
else if (cmd.startsWith('goto ')) {
const [, x, y, z] = cmd.split(' ').map(Number);
policy.mode = 'explore';
policy.waypoint = { x, y, z };
out({ policy: 'explore', waypoint: policy.waypoint });
}
else if (cmd.startsWith('pray ')) { bot.chat(cmd); out({ action: 'pray', msg: cmd.slice(5) }); }
else if (cmd.startsWith('sudo ')) { bot.chat(cmd); out({ action: 'sudo', msg: cmd.slice(5) }); }
else if (cmd.startsWith('say ')) { bot.chat(cmd.slice(4)); }
else if (cmd === 'quit') { bot.end(); process.exit(0); }
});
function stopMoving() {
['forward', 'back', 'left', 'right', 'sprint', 'jump'].forEach(s => bot.setControlState(s, false));
}