The game as of artifact version 24, ready for local development with coding agents. - Source split from three large files into 52 small ones under src/sim, src/render and src/ui, joined in the order in src/manifest.json by build.py. The joined code is unchanged apart from comments; every headless suite gives the same output, and screenshots in both looks are pixel-identical to before. - Light tidying with no change in behaviour: one movement scan loop, shared hazard-stripe and belt shader helpers, unused code removed, a shared check helper for the test suites. - World files can be exported as an ordinary download outside claude.ai. - Tests: broken old scripts removed, experiments moved to tools/, test/run_all.py added. Tools: shot.py (repeatable screenshots), where.py, map.py. - Docs: AGENTS.md, CLAUDE.md, NOTES.md (the owner's to-do inbox), docs/PROJECT_MAP.md, DESIGN.md, TESTING.md, systems notes, the owner's requests and an agent log. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K9kLmYADKSEjuwUN3rFxDA
76 lines
4.6 KiB
JavaScript
76 lines
4.6 KiB
JavaScript
// What does a running factory cost when the player is somewhere else?
|
|
// Builds K self-running modules in a generated world, far from the bot, and
|
|
// measures simulation time per tick. Each module: a coal + copper-ore furnace
|
|
// burning in a stone-block box, a spring feeding a water wheel, a shaft carrying
|
|
// its power to a 10-block belt that hauls sand off a hopper.
|
|
const { load } = require('../../test/harness');
|
|
const { run } = load();
|
|
const seed = 424242, w = +(process.argv[2] || 1024), h = +(process.argv[3] || 640);
|
|
run(`generateWorld(${seed}, ${w}, ${h}, null); player.tankTier = 3;`);
|
|
const awake = () => run('(()=>{let a=0,h=0;for(let c=0;c<NCH;c++){if(awake[c])a++;if(hot[c])h++;}return [a,h,NCH]})()');
|
|
function measure(n) {
|
|
const t0 = performance.now();
|
|
run(`for (let k=0;k<${n};k++) simTick();`);
|
|
return (performance.now() - t0) / n;
|
|
}
|
|
run('for (let k=0;k<600;k++) simTick();');
|
|
const idle = measure(600);
|
|
const [ia, ih, nch] = awake();
|
|
console.log(`world ${w}x${h}: ${nch} areas of 32x32`);
|
|
console.log(`idle: ${idle.toFixed(2)} ms/tick, ${ia} moving areas, ${ih} warm areas`);
|
|
|
|
function buildModule(mx, my) {
|
|
run(`(() => {
|
|
const mx = ${mx}, my = ${my};
|
|
// clear the sky box we build in
|
|
for (let y = my*4 - 4; y < (my+9)*4; y++) for (let x = mx*4 - 4; x < (mx+20)*4; x++) { const i = y*W+x; if (PH[mat[i]] !== PH_SOLID) setCell(i, EMPTY, AMB[y], 0); }
|
|
// furnace: 4x4 blocks of stone with a 2x2 hollow and a vent on top
|
|
for (let by = my; by < my+4; by++) for (let bx = mx; bx < mx+4; bx++) {
|
|
const inner = bx > mx && bx < mx+3 && by > my && by < my+3;
|
|
if (inner || (by === my && bx === mx+2)) continue;
|
|
placeBlock(BP_WALL, RSTONE, bx, by, true);
|
|
}
|
|
const ix = (mx+1)*4, iy = (my+1)*4;
|
|
for (let y = iy+3; y < iy+8; y++) for (let x = ix; x < ix+8; x++) setCell(y*W+x, ((x+y)&3) ? COAL : CUORE, AMB[y], 0);
|
|
for (let x = ix; x < ix+8; x++) { temp[(iy+3)*W+x] = 700; wakeHeatXY(x, iy+3); }
|
|
// spring above a water wheel
|
|
for (let y = my*4; y < my*4+2; y++) for (let x = (mx+6)*4; x < (mx+7)*4; x++) { mat[y*W+x] = SPRING; flags[y*W+x] = F_NAT; }
|
|
addMachine('wheel', mx+5, my+1, 0);
|
|
// shaft from the wheel's side down to a belt line
|
|
placeBlock(BP_SHAFT, 0, mx+8, my+2, true); placeBlock(BP_SHAFT, 0, mx+9, my+2, true);
|
|
placeBlock(BP_SHAFT, 0, mx+9, my+3, true); placeBlock(BP_SHAFT, 0, mx+9, my+4, true);
|
|
for (let bx = mx+9; bx < mx+20; bx++) placeBlock(BP_BELT, 1, bx, my+5, true);
|
|
// sand hopper on the first belt blocks
|
|
for (let y = (my+5)*4 - 12; y < (my+5)*4; y++) for (let x = (mx+10)*4; x < (mx+13)*4; x++) setCell(y*W+x, SAND, AMB[y], 0);
|
|
})()`);
|
|
run('powerDirty = true;');
|
|
}
|
|
const report = [];
|
|
const botBx = run('Math.floor(bot.x / 4)');
|
|
const slots = [];
|
|
for (let mx = 6; mx + 22 < w / 4 && slots.length < 8; mx += 28) if (Math.abs(mx + 10 - botBx) > 24) slots.push(mx);
|
|
for (const K of [1, 4, 8]) {
|
|
if (K > slots.length) break;
|
|
// fresh world for each count, so every module starts fully fuelled
|
|
run(`generateWorld(${seed}, ${w}, ${h}, null); for (let k=0;k<600;k++) simTick();`);
|
|
const modules = slots.slice(0, K);
|
|
for (const mx of modules) buildModule(mx, 8);
|
|
run('for (let k=0;k<300;k++) simTick();');
|
|
const moved0 = run('stats.moved');
|
|
const ms = measure(900);
|
|
const [a, ht] = awake();
|
|
const wheels = run('machines.filter(m => m && m.type === "wheel").map(m => m.out.toFixed(1)).join(" ")');
|
|
const sat = run('nets.slice(1).map(n => n.sat.toFixed(2)).join(" ")');
|
|
const furn = run(`[${modules.map(mx => `(() => { let t = 0; for (let y = 36; y < 44; y++) for (let x = ${(mx + 1) * 4}; x < ${(mx + 3) * 4}; x++) t = Math.max(t, temp[y*W+x]); return Math.round(t); })()`).join(',')}].join(' ')`);
|
|
const moved = run('stats.moved') - moved0;
|
|
const dist = Math.min(...modules.map(mx => Math.abs(mx * 4 + 40 - run('bot.x'))));
|
|
console.log(`\n${K} module${K > 1 ? 's' : ''}: ${ms.toFixed(2)} ms/tick (+${(ms - idle).toFixed(2)} over idle), ${a} moving areas, ${ht} warm areas`);
|
|
console.log(` nearest module ${Math.round(dist)} grains from the bot; wheel output ${wheels}; belt power satisfaction ${sat}`);
|
|
console.log(` furnace peak temps ${furn} °C; grains moved by machines in 15 s: ${moved}`);
|
|
report.push({ K, ms, extra: ms - idle, a, ht });
|
|
}
|
|
const per = report.map(r => (r.extra / r.K).toFixed(2));
|
|
console.log(`\nextra cost per module: ${per.join(' / ')} ms/tick`);
|
|
const budget = 9; // ms per tick left for simulation at 60 ticks/s after rendering
|
|
console.log(`at ~${budget} ms/tick of simulation budget, roughly ${Math.floor((budget - idle) / Math.max(0.01, report[report.length - 1].extra / report[report.length - 1].K))} such modules run at full speed`);
|