65ee146043
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>
300 lines
11 KiB
JavaScript
300 lines
11 KiB
JavaScript
/**
|
|
* 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}%)`);
|