Files
Mortdecai/ingame/march_army.js
T
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

232 lines
8.2 KiB
JavaScript

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