← back to Wallco Ai
scripts/drunk_monkeys_v2_batch.js
95 lines
#!/usr/bin/env node
/**
* v2 batch — uses drunk_monkeys_v2_prompts (subject-first, short, explicit
* cocktail props in prompt body) + the strengthened solid-colors prompt
* suffix wired into generate_designs.js (2026-05-21 directive).
*
* Differs from v1 (drunk_monkeys_birds_subdued_parallel.js):
* - Imports v2 prompt builder
* - Category = 'drunk-monkeys-v2' (separable from the 'drunk-monkeys-birds-subdued' v1 batch)
* - Same 6-worker parallel pool, 600s per-design timeout
*
* Usage: node scripts/drunk_monkeys_v2_batch.js [N] [CONCURRENCY]
* defaults: N=100, CONCURRENCY=6
*/
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
const { buildN } = require('./drunk_monkeys_v2_prompts');
const { checkPrompt } = require('./settlement-gate');
const N = parseInt(process.argv[2] || '100', 10);
const PARLEL = parseInt(process.argv[3] || '6', 10);
const ROOT = path.join(__dirname, '..');
const CATEGORY = 'drunk-monkeys-v2';
const LOG = `/tmp/dmbs/v2-${new Date().toISOString().replace(/[:.]/g, '-')}.log`;
fs.mkdirSync(path.dirname(LOG), { recursive: true });
const t0 = Date.now();
function L(msg) {
const line = `[${new Date().toISOString()}] ${msg}`;
console.log(line);
fs.appendFileSync(LOG, line + '\n');
}
L(`=== drunk-monkeys-v2: ${N} designs × ${PARLEL} workers ===`);
L(`log file: ${LOG}`);
const items = buildN(N).filter((it, i) => {
const g = checkPrompt(it.prompt);
if (g.verdict !== 'OK') { L(`[skip ${i+1}] settlement ${g.verdict}`); return false; }
return true;
});
L(`prompts: ${items.length} (${N - items.length} settlement-skipped)`);
let ok = 0, fail = 0, idx = 0;
const inflight = new Map();
function runOne(item, i) {
return new Promise((resolve) => {
const child = spawn('node', [
path.join(ROOT, 'scripts', 'generate_designs.js'),
'--n', '1',
'--kind', 'seamless_tile',
'--category', CATEGORY,
'--prompts', item.prompt,
], { cwd: ROOT });
let stderr = '';
child.stderr.on('data', d => { stderr += d.toString(); });
const killTimer = setTimeout(() => { try { child.kill('SIGKILL'); } catch (e){} }, 600_000);
child.on('close', (code) => {
clearTimeout(killTimer);
if (code === 0) {
ok++;
L(` ✓ [${i+1}] palette=${item.palette} animal=${item.animal_summary} (${ok}+${fail}=${ok+fail}/${items.length} ${((Date.now()-t0)/60000).toFixed(1)}min)`);
} else {
fail++;
L(` ✗ [${i+1}] FAIL code=${code} ${stderr.slice(0,140).replace(/\n/g,' ')}`);
}
resolve();
});
});
}
const heart = setInterval(() => {
L(` ❤ heartbeat — done=${ok+fail}/${items.length} ok=${ok} fail=${fail} inflight=${inflight.size} elapsed=${((Date.now()-t0)/60000).toFixed(1)}min`);
}, 30_000);
(async () => {
const workers = Array.from({ length: PARLEL }, async () => {
while (idx < items.length) {
const myIdx = idx++;
const item = items[myIdx];
inflight.set(myIdx, item);
await runOne(item, myIdx);
inflight.delete(myIdx);
}
});
await Promise.all(workers);
clearInterval(heart);
L(`=== batch done: ${ok} ok / ${fail} fail of ${items.length} (${((Date.now()-t0)/60000).toFixed(1)} min) ===`);
L('refreshing designs.json snapshot…');
spawn('python3', [path.join(ROOT, 'scripts', 'refresh_designs_snapshot.py')],
{ cwd: ROOT, stdio: 'inherit' });
process.exit(fail === 0 ? 0 : (ok > 0 ? 0 : 1));
})();