Files
Seth 5b28002001 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>
2026-03-22 20:22:50 -04:00

253 lines
8.5 KiB
JavaScript

/**
* 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);
});