Swarm bots, RCON validation, Haiku distillation complete

Swarm bots (ingame/swarm_bots.js):
- 10 survival bots with generated names (SwiftWolf, DarkWolf, etc.)
- All bots wander, take damage, auto-respawn, pray when hurt
- Gemini + Dolphin(5%) + Multilingual(3%) prompt generation
- 20-60s interaction interval per bot

Distillation results:
- 222 sudo examples via Haiku ($0.28)
- 122 god examples via Haiku ($0.37) — with God Soul personality
- Total: 344 distilled, $0.65 spent of $5 budget
- RCON validation: 74.7% fully valid, 30 real errors out of ~1000 commands

validate_distilled.py:
- Executes distilled commands on live server via RCON
- Distinguishes real errors from benign (no player online)
- Tags each example with validation status

Dev server switched to Claude Haiku via Anthropic API:
- llm_provider: anthropic with $5 budget cap
- Auto-fallback to Ollama when budget exhausted
- Cost tracking with logging

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-18 19:18:19 -04:00
parent 961f53ea7d
commit 65ee146043
5 changed files with 1224 additions and 2 deletions
+299
View File
@@ -0,0 +1,299 @@
/**
* swarm_bots.js -- Many bots in survival mode that wander, pray, sudo, and die.
*
* All bots move around, take damage, auto-respawn, and generate diverse
* training data through organic interactions with the AI God.
*
* Uses Gemini for prompt generation, Dolphin for offensive prompts.
* Server runs Claude Haiku as the God model for high-quality responses.
*
* Usage: node swarm_bots.js [count] [host] [port]
* Defaults: 10 bots, 192.168.0.244:25568
*/
const mineflayer = require('mineflayer');
const https = require('https');
const http = require('http');
const count = parseInt(process.argv[2] || '10', 10);
const host = process.argv[3] || '192.168.0.244';
const port = parseInt(process.argv[4] || '25568', 10);
const GEMINI_KEY = 'REDACTED_GEMINI_KEY_2';
const GEMINI_MODEL = 'gemini-2.5-flash-lite';
const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_KEY}`;
const DOLPHIN_URL = 'http://192.168.0.141:11434/api/chat';
const DOLPHIN_MODEL = 'dolphin-mistral';
// --- Name generation ---
const ADJECTIVES = [
'Swift', 'Dark', 'Brave', 'Wild', 'Stone', 'Iron', 'Fire', 'Shadow',
'Storm', 'Frost', 'Gold', 'Silver', 'Red', 'Blue', 'Midnight', 'Ancient',
'Lone', 'Grim', 'Bright', 'Pale', 'Dusk', 'Dawn', 'Silent', 'Void',
];
const NOUNS = [
'Wolf', 'Hawk', 'Bear', 'Fox', 'Miner', 'Knight', 'Scout', 'Archer',
'Smith', 'Sage', 'Rogue', 'Blade', 'Shield', 'Hammer', 'Nomad', 'Pilgrim',
'Crafter', 'Builder', 'Hunter', 'Seeker', 'Ghost', 'Ember', 'Spark', 'Shard',
];
function generateName(index) {
const adj = ADJECTIVES[index % ADJECTIVES.length];
const noun = NOUNS[Math.floor(index / ADJECTIVES.length) % NOUNS.length];
const num = Math.floor(index / (ADJECTIVES.length * NOUNS.length));
return num > 0 ? `${adj}${noun}${num}` : `${adj}${noun}`;
}
// --- Prompt pools ---
const GEMINI_PROMPT = `Generate 10 diverse Minecraft player chat messages. Mix "pray" and "sudo" commands.
Include: humble prayers, greedy demands, roleplay, questions, typos, slang, edge cases.
Return ONLY a JSON array of strings.`;
const DOLPHIN_PROMPT = `Generate 10 offensive/rude Minecraft chat messages prefixed with "pray".
Include profanity, blasphemy, trolling, crude humor. Be creative.
Return ONLY a JSON array of strings.`;
let promptPool = [];
let dolphinPool = [];
let totalChats = 0;
const DOLPHIN_RATE = 0.05;
const MULTILINGUAL_RATE = 0.03;
const MULTILINGUAL = [
"pray dios dame una espada de diamante", "pray señor necesito comida",
"sudo dame 64 antorchas", "pray seigneur donnez-moi une armure",
"pray gott gib mir ein diamantschwert", "sudo gib mir 64 fackeln",
"pray deus me ajude estou morrendo", "pray боже дай мне алмазный меч",
"pray 神様ダイヤモンドの剣をください", "pray 신이시여 다이아몬드 검을 주세요",
"pray 上帝请给我钻石剑", "pray يا إلهي أعطني سيف الماس",
];
const STATIC = [
"pray lord I am hungry", "pray god give me tools", "pray help me I'm dying",
"pray give me diamonds", "pray PENIS", "pray there is no god",
"sudo give me a diamond sword", "sudo set time to day", "sudo make it rain",
"sudo give me iron armor", "sudo kill all zombies", "sudo help",
"sudo build a house", "sudo give me food", "sudo tp me to spawn",
"bug_log no response", "bug_log wrong item", "bug_log empty response",
];
const DESPERATE = [
"pray GOD PLEASE HEAL ME", "pray im dying save me", "pray lord I need food NOW",
"sudo heal me", "sudo give me golden apples", "pray help mobs everywhere",
];
function ts() { return new Date().toISOString().slice(11, 19); }
function pick(arr) { return arr[Math.floor(Math.random() * arr.length)]; }
function delay(min, max) { return (min + Math.random() * (max - min)) * 1000; }
// --- Gemini ---
function geminiRefill() {
const body = JSON.stringify({
contents: [{ parts: [{ text: GEMINI_PROMPT }] }],
generationConfig: { temperature: 1.2, maxOutputTokens: 400 },
});
const url = new URL(GEMINI_URL);
const req = https.request({
hostname: url.hostname, path: url.pathname + url.search,
method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
}, (res) => {
let data = '';
res.on('data', c => data += c);
res.on('end', () => {
try {
const text = JSON.parse(data).candidates?.[0]?.content?.parts?.[0]?.text || '';
const cleaned = text.replace(/```json\s*/g, '').replace(/```\s*/g, '');
const match = cleaned.match(/\[[\s\S]*\]/);
if (match) {
const prompts = JSON.parse(match[0]).filter(p => typeof p === 'string' && p.length > 0);
promptPool.push(...prompts);
console.log(`[${ts()}] [Gemini] +${prompts.length} prompts (pool:${promptPool.length})`);
}
} catch (e) { console.log(`[${ts()}] [Gemini] Error: ${e.message}`); }
});
});
req.on('error', e => console.log(`[${ts()}] [Gemini] ${e.message}`));
req.setTimeout(15000, () => req.destroy());
req.write(body); req.end();
}
// --- Dolphin ---
function dolphinRefill() {
const body = JSON.stringify({
model: DOLPHIN_MODEL,
messages: [{ role: 'user', content: DOLPHIN_PROMPT }],
stream: false, options: { temperature: 1.5, num_predict: 800 },
});
const url = new URL(DOLPHIN_URL);
const req = http.request({
hostname: url.hostname, port: url.port, path: '/api/chat',
method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
}, (res) => {
let data = '';
res.on('data', c => data += c);
res.on('end', () => {
try {
const text = JSON.parse(data).message?.content || '';
const cleaned = text.replace(/```json\s*/g, '').replace(/```\s*/g, '');
const match = cleaned.match(/\[[\s\S]*\]/);
if (match) {
const prompts = JSON.parse(match[0]).filter(p => typeof p === 'string' && p.length > 0);
dolphinPool.push(...prompts);
console.log(`[${ts()}] [Dolphin] +${prompts.length} offensive (pool:${dolphinPool.length})`);
}
} catch (e) { console.log(`[${ts()}] [Dolphin] ${e.message}`); }
});
});
req.on('error', e => console.log(`[${ts()}] [Dolphin] ${e.message}`));
req.setTimeout(60000, () => req.destroy());
req.write(body); req.end();
}
function getPrompt() {
if (promptPool.length < 5) geminiRefill();
if (dolphinPool.length < 3) dolphinRefill();
return promptPool.length > 0
? promptPool.splice(Math.floor(Math.random() * promptPool.length), 1)[0]
: pick(STATIC);
}
// --- Bot logic ---
const bots = [];
let connected = 0;
function spawnBot(index) {
const name = generateName(index);
console.log(`[${ts()}] [${name}] Connecting...`);
const bot = mineflayer.createBot({
host, port, username: name, auth: 'offline', version: '1.21.11', viewDistance: 'tiny',
});
bot._name = name;
bot._msgCount = 0;
bot._noResp = 0;
bot._prayedLow = false;
bots.push(bot);
bot.on('login', () => {
connected++;
console.log(`[${ts()}] [${name}] Connected (${connected}/${count})`);
// Start wandering and chatting after random delay
setTimeout(() => wander(bot), delay(5, 15));
setTimeout(() => interact(bot), delay(10, 30));
});
bot.on('message', (msg) => {
const text = msg.toString();
if (text.includes('GOD') || text.includes('SUDO') || text.includes('BUG_LOG')) {
bot._noResp = 0;
}
});
// Auto-respawn
bot.on('death', () => {
console.log(`[${ts()}] [${name}] DIED`);
setTimeout(() => {
try { bot.chat('pray lord I have fallen, grant me mercy upon my return'); } catch(e) {}
}, 2000);
setTimeout(() => { try { bot.respawn(); } catch(e) {} }, 4000);
});
// Pray when hurt
bot.on('health', () => {
if (!bot.health) return;
if (bot.health < 6 && !bot._prayedLow) {
bot._prayedLow = true;
bot.chat(pick(DESPERATE));
console.log(`[${ts()}] [${name}] LOW HP (${bot.health})`);
}
if (bot.health >= 15) bot._prayedLow = false;
});
bot.on('error', (err) => console.error(`[${ts()}] [${name}] Error: ${err.message}`));
bot.on('kicked', (reason) => {
console.log(`[${ts()}] [${name}] Kicked: ${reason}`);
connected--;
setTimeout(() => spawnBot(index), 60000);
});
bot.on('end', () => { connected--; });
}
function wander(bot) {
if (!bot.entity) return;
try {
// Random walk: pick a direction and walk for a bit
const yaw = Math.random() * Math.PI * 2;
bot.look(yaw, 0);
// Walk forward
bot.setControlState('forward', true);
setTimeout(() => {
bot.setControlState('forward', false);
// Occasionally jump
if (Math.random() < 0.3) {
bot.setControlState('jump', true);
setTimeout(() => bot.setControlState('jump', false), 300);
}
}, delay(1, 4));
} catch(e) {}
// Wander again in 3-8 seconds
setTimeout(() => wander(bot), delay(3, 8));
}
function interact(bot) {
if (!bot.entity) return;
bot._msgCount++;
totalChats++;
let message;
const roll = Math.random();
if (roll < 0.08 && bot._noResp >= 2) {
message = pick(STATIC.filter(s => s.startsWith('bug_log')));
} else if (Math.random() < MULTILINGUAL_RATE) {
message = pick(MULTILINGUAL);
} else if (Math.random() < DOLPHIN_RATE && dolphinPool.length > 0) {
message = dolphinPool.splice(Math.floor(Math.random() * dolphinPool.length), 1)[0];
} else {
message = getPrompt();
bot._noResp++;
}
console.log(`[${ts()}] [${bot._name}] #${bot._msgCount}: ${message.substring(0, 60)}`);
bot.chat(message);
// Next interaction in 20-60s (slower than before — Haiku is fast but we want quality over quantity)
setTimeout(() => interact(bot), delay(20, 60));
}
// Pre-fill pools
geminiRefill();
dolphinRefill();
// Stagger spawns (5s apart to avoid throttle)
for (let i = 0; i < count; i++) {
setTimeout(() => spawnBot(i), i * 5000);
}
// Refill pools periodically
setInterval(() => { if (promptPool.length < 15) geminiRefill(); }, 45000);
setInterval(() => { if (dolphinPool.length < 5) dolphinRefill(); }, 120000);
// Graceful shutdown
process.on('SIGINT', () => {
console.log(`\n[${ts()}] Shutting down ${bots.length} bots...`);
bots.forEach(b => { try { b.quit(); } catch(e) {} });
setTimeout(() => process.exit(0), 2000);
});
console.log(`[${ts()}] Spawning ${count} survival bots on ${host}:${port}`);
console.log(`[${ts()}] Names: ${Array.from({length: Math.min(count, 5)}, (_, i) => generateName(i)).join(', ')}...`);
console.log(`[${ts()}] All bots wander, fight, die, respawn, and pray`);
console.log(`[${ts()}] Gemini + Dolphin(${DOLPHIN_RATE*100}%) + Multilingual(${MULTILINGUAL_RATE*100}%)`);