Files
Mortdecai/ingame/prayer_bots.js
T
Seth 029bd28a58 Gemini-powered prayer bots, POS cost printer, first LoRA training run
Prayer bots (ingame/prayer_bots.js):
- 3 Mineflayer bots that actively pray, sudo, and bug_log on dev server
- Gemini 2.5 Flash Lite generates diverse natural prompts on the fly
- Falls back to static pool if Gemini unavailable
- 15-45s interval per bot, 50/35/10/5 pray/sudo/bug/chat split

POS status printer (scripts/training_status_printer.py):
- Prints training data collection status to Epson TM-m30
- Tracks: dataset size, audit logs, bot activity, Gemini API cost, service status
- Triggers on $0.50 cost threshold (configurable), checks every 15 min
- --dry-run, --check, --force flags

Training:
- First LoRA run completed (233 examples, 3 epochs, loss 1.5→0.10)
- GGUF exported and loaded into Ollama as qwen3-8b-mc-lora on steel141
- Model is bad (expected) — hallucinating Chinese, leaking system prompt
- Deployed to dev server for live testing and data collection
- bf16 fix for Ampere GPU, system prompts included in training conversations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:36:08 -04:00

274 lines
8.0 KiB
JavaScript

/**
* prayer_bots.js -- Mineflayer bots that actively pray, sudo, and bug_log.
*
* Uses Gemini Flash Lite to generate diverse, natural prompts on the fly.
* Falls back to static pools if Gemini is unavailable.
*
* Usage: node prayer_bots.js [count] [host] [port]
* Defaults: 3 bots, 192.168.0.244:25568
*/
const mineflayer = require('mineflayer');
const https = require('https');
const count = parseInt(process.argv[2] || '3', 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}`;
// --- Gemini prompt generation ---
const PRAYER_GEN_PROMPT = `You are generating test prompts for a Minecraft server AI. The server has two chat commands:
- "pray <message>" — talk to an AI God character who grants/denies requests
- "sudo <command>" — ask for server commands in natural language
Generate 5 diverse prompts that a Minecraft player might type. Mix these types:
- Humble prayers asking for items, effects, or help
- Greedy/demanding prayers
- Creative roleplay prayers
- Offensive/blasphemous prayers (mild, for testing punishment responses)
- Sudo commands for items, effects, world changes, building
- Sudo edge cases (typos, vague requests, impossible things)
- Ambiguous or weird messages
Return ONLY a JSON array of strings, no other text. Example:
["pray lord give me a sword", "sudo set time to night", "pray LMAO", "sudo give me uhhh some blocks I guess", "pray dear god I offer you my wheat as tribute"]
Be creative. Use casual gamer language. Vary between formal prayers and slang. Include typos sometimes.`;
function geminiGenerate() {
return new Promise((resolve, reject) => {
const body = JSON.stringify({
contents: [{ parts: [{ text: PRAYER_GEN_PROMPT }] }],
generationConfig: { temperature: 1.2, maxOutputTokens: 400 },
});
const url = new URL(GEMINI_URL);
const options = {
hostname: url.hostname,
path: url.pathname + url.search,
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const json = JSON.parse(data);
const text = json.candidates?.[0]?.content?.parts?.[0]?.text || '';
// Extract JSON array from response (may be wrapped in ```json ... ```)
const cleaned = text.replace(/```json\s*/g, '').replace(/```\s*/g, '');
const match = cleaned.match(/\[[\s\S]*\]/);
if (match) {
const prompts = JSON.parse(match[0]);
resolve(prompts.filter(p => typeof p === 'string' && p.length > 0));
} else {
reject(new Error('No JSON array in Gemini response'));
}
} catch (e) {
reject(e);
}
});
});
req.on('error', reject);
req.setTimeout(15000, () => { req.destroy(); reject(new Error('Gemini timeout')); });
req.write(body);
req.end();
});
}
// --- Prompt pool (Gemini-fed + static fallback) ---
let promptPool = [];
let geminiErrors = 0;
// Static fallback pool
const STATIC_PRAYERS = [
"pray lord I am hungry and have nothing",
"pray god please give me tools to survive",
"pray almighty one, bless me with protection",
"pray please heal me I am dying",
"pray give me 1000 diamonds NOW",
"pray I demand the best armor in the game",
"pray lord I wish to build you a temple of gold",
"pray god show me a sign that you exist",
"pray PENIS",
"pray there is no god",
"pray hello",
"pray asdfghjkl",
];
const STATIC_SUDO = [
"sudo give me a diamond sword",
"sudo give me 64 torches",
"sudo make it rain",
"sudo set time to day",
"sudo give me full diamond armor with protection 4",
"sudo kill all hostile mobs",
"sudo help",
"sudo give me dimand sword",
"sudo fly",
"sudo build a house",
];
const BUG_REPORTS = [
"bug_log no response from god",
"bug_log command did not work",
"bug_log I got nothing",
"bug_log wrong item given",
"bug_log empty response",
"bug_log god ignored me",
];
async function refillPool() {
try {
const prompts = await geminiGenerate();
promptPool.push(...prompts);
console.log(`[${ts()}] [Gemini] Generated ${prompts.length} prompts (pool: ${promptPool.length})`);
geminiErrors = 0;
} catch (e) {
geminiErrors++;
console.log(`[${ts()}] [Gemini] Error (${geminiErrors}): ${e.message}`);
// Fall back to static pool
if (promptPool.length < 5) {
const statics = [...STATIC_PRAYERS, ...STATIC_SUDO];
for (let i = 0; i < 10; i++) {
promptPool.push(statics[Math.floor(Math.random() * statics.length)]);
}
}
}
}
function getNextPrompt() {
// Refill when low
if (promptPool.length < 5) {
refillPool();
}
if (promptPool.length > 0) {
return promptPool.splice(Math.floor(Math.random() * promptPool.length), 1)[0];
}
// Emergency fallback
const all = [...STATIC_PRAYERS, ...STATIC_SUDO];
return all[Math.floor(Math.random() * all.length)];
}
// --- Bot logic ---
const bots = [];
let connected = 0;
function ts() {
return new Date().toISOString().slice(11, 19);
}
function randomDelay(minSec, maxSec) {
return (minSec + Math.random() * (maxSec - minSec)) * 1000;
}
function spawnBot(index) {
const name = `PrayBot_${index}`;
console.log(`[${ts()}] [${name}] Connecting to ${host}:${port}...`);
const bot = mineflayer.createBot({
host,
port,
username: name,
auth: 'offline',
version: '1.21.11',
viewDistance: 'tiny',
});
bot._name = name;
bot._msgCount = 0;
bot._lastResponse = null;
bot._noResponseCount = 0;
bots.push(bot);
bot.on('login', () => {
connected++;
console.log(`[${ts()}] [${name}] Connected (${connected}/${count})`);
setTimeout(() => interactionLoop(bot), randomDelay(10, 20));
});
bot.on('message', (msg) => {
const text = msg.toString();
if (text.includes('GOD') || text.includes('SUDO') || text.includes('BUG_LOG')) {
console.log(`[${ts()}] [${name}] RECV: ${text.substring(0, 150)}`);
bot._lastResponse = text;
bot._noResponseCount = 0;
}
});
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', () => {
console.log(`[${ts()}] [${name}] Disconnected`);
connected--;
});
}
function interactionLoop(bot) {
if (!bot.entity) return;
bot._msgCount++;
let message;
const roll = Math.random();
if (roll < 0.10 && bot._noResponseCount >= 2) {
// File bug report if we haven't gotten responses
message = BUG_REPORTS[Math.floor(Math.random() * BUG_REPORTS.length)];
} else {
message = getNextPrompt();
bot._noResponseCount++;
}
console.log(`[${ts()}] [${bot._name}] SEND (#${bot._msgCount}): ${message}`);
bot.chat(message);
// 15-45s between messages per bot
const delay = randomDelay(15, 45);
setTimeout(() => interactionLoop(bot), delay);
}
// Pre-fill the pool before bots connect
refillPool();
// Spawn bots staggered (10s apart to avoid throttle)
for (let i = 0; i < count; i++) {
setTimeout(() => spawnBot(i), i * 10000);
}
// Periodically refill from Gemini
setInterval(() => {
if (promptPool.length < 10) refillPool();
}, 60000);
// 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} prayer bots on ${host}:${port}`);
console.log(`[${ts()}] Using Gemini ${GEMINI_MODEL} for prompt generation`);
console.log(`[${ts()}] Interaction interval: 15-45s per bot`);
console.log(`[${ts()}] Press Ctrl+C to stop`);