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
50 lines
4.4 KiB
JavaScript
50 lines
4.4 KiB
JavaScript
// Plays a starter design the way a player would: paste the blueprint, let the
|
|
// bot build it from the bag, blow an even ore/coal mix inside, light it, wait,
|
|
// knock out the castings. Usage: node tools/experiments/starter.js [design id] [ore] [coal]
|
|
const { load, flatWorld, ticks } = require('../../test/harness');
|
|
const { run } = load();
|
|
const id = process.argv[2] || 'smelter1', ore = +(process.argv[3] || 40), coal = +(process.argv[4] || 40);
|
|
flatWorld(run, 240, 160, 100);
|
|
for (let k = 0; k < +(process.argv[5] || 0); k++) run('rnd()'); // shake the dice: how robust is the design?
|
|
run(`bag[RSTONE] = 900; bag[LCLAY] = 200; bag[COAL] = ${coal}; bag[CUORE] = ${ore}; bagTotal = 0; for (let k = 0; k < NMAT; k++) bagTotal += bag[k];`);
|
|
const d = run(`JSON.stringify(STARTER_DESIGNS.find(d => d.id === '${id}').bp())`);
|
|
const bp = JSON.parse(d);
|
|
// sit it on the ground: bottom row at block 24 (ground starts at block 25)
|
|
const ox = 24, oy = 25 - bp.bh;
|
|
run(`bot.x = ${ox * 4 + bp.bw * 2 - 2}; bot.y = ${oy * 4 - 14}; pasteGhost(${d}, ${ox}, ${oy});`);
|
|
for (let k = 0; k < 40 && run('ghostBlocks.size + ghostMolds.length') > 0; k++) ticks(run, 30);
|
|
console.log(id, `${bp.bw}x${bp.bh} blocks;`, 'ghosts left', run('ghostBlocks.size + ghostMolds.length'), 'molds', run('molds.length'), 'stone used', 900 - run('bag[RSTONE]'), 'clay used', 200 - run('bag[LCLAY]'));
|
|
// fly inside (the bot passes through walls) and blow the mix across the chamber floor
|
|
const cx0 = (ox + 1) * 4, cx1 = (ox + bp.bw - 1) * 4 - 1, floorY = (oy + 4) * 4 - 1;
|
|
run(`bot.x = ${(cx0 + cx1) / 2 - 2}; bot.y = ${oy * 4};`);
|
|
run(`GCOL = [CUORE, COAL];`);
|
|
for (let k = 0; k < 400 && run('bag[CUORE] + bag[COAL]') > 0; k++) {
|
|
const tx = cx0 + 1 + (k * 5) % (cx1 - cx0 - 2);
|
|
run(`toolBlow(${tx}, ${floorY - 1}, 0, CUORE, GCOL); simTick();`);
|
|
}
|
|
ticks(run, 60);
|
|
const inside = run(`(()=>{let o=0,c=0;for(let y=${oy * 4};y<=${floorY};y++)for(let x=${cx0};x<=${cx1};x++){const m=mat[y*W+x];if(m===CUORE)o++;else if(m===COAL)c++;}return [o,c]})()`);
|
|
console.log('charge inside', inside, 'left in bag', run('[bag[CUORE], bag[COAL]]'));
|
|
// light the top of the pile from inside, then leave
|
|
// like a player: hold the igniter on a few coal grains that touch air
|
|
const spots = run(`(()=>{const out=[];for(let y=${oy * 4 + 4};y<=${floorY};y++)for(let x=${cx0};x<=${cx1};x++){const i=y*W+x;if(mat[i]===COAL&&mat[i-W]===EMPTY)out.push([x,y]);}return out})()`);
|
|
const pick = [spots[0], spots[Math.floor(spots.length / 2)], spots[spots.length - 1]].filter(Boolean);
|
|
run(`for (let k=0;k<50;k++){ ${pick.map(([x, y]) => `toolIgnite(${x}, ${y});`).join('')} simTick(); }`);
|
|
run(`bot.x = ${ox * 4 - 30}; bot.y = ${oy * 4 - 20};`);
|
|
let t = 0;
|
|
for (const dt of [900, 900, 1800, 1800]) {
|
|
ticks(run, dt); t += dt;
|
|
console.log(`${t / 60}s`, run(`JSON.stringify(molds.map(m => m.state + (m.ready.length ? ':' + m.ready.length : '') + (m.flaw ? ':' + m.flaw : '')))`),
|
|
'ore left', run(`(()=>{let n=0;for(let i=0;i<N;i++)if(mat[i]===CUORE)n++;return n})()`), 'copper set outside molds', run(`(()=>{let n=0;const cav=new Set(molds.flatMap(m=>m.cavs.flat()));for(let i=0;i<N;i++)if(mat[i]===COPPER&&!cav.has(i))n++;return n})()`),
|
|
'in molds', run(`(()=>{let n=0;for(const i of molds.flatMap(m=>m.cavs.flat()))if(mat[i]===COPPER||mat[i]===MCU)n++;return n})()`), 'slag', run(`(()=>{let n=0;for(let i=0;i<N;i++)if(mat[i]===SLAG)n++;return n})()`), 'molten', run(`(()=>{let n=0;for(let i=0;i<N;i++)if(mat[i]===MCU)n++;return n})()`));
|
|
}
|
|
function dump(title) {
|
|
console.log('--', title);
|
|
const ch = { [run('EMPTY')]: '.', [run('COAL')]: 'c', [run('CUORE')]: 'o', [run('MCU')]: 'M', [run('COPPER')]: '=', [run('SLAG')]: 's', [run('ASH')]: 'a', [run('FIRE')]: '^', [run('SMOKE')]: '~', [run('STONE')]: '#', [run('SCRCU')]: '%' };
|
|
const rows = run(`(()=>{const out=[];for (let y = ${oy * 4 - 2}; y < ${(oy + bp.bh) * 4 + 2}; y++) { const r = []; for (let x = ${ox * 4 - 2}; x < ${(ox + bp.bw) * 4 + 2}; x++) { const i = y * W + x; r.push([mat[i], part[i], Math.round(temp[i])]); } out.push(r); } return out; })()`);
|
|
for (const r of rows) console.log(r.map(([m, p]) => p === 5 && m === 0 ? '+' : p === 5 ? (ch[m] || '?').toUpperCase() : (ch[m] || '?')).join(''));
|
|
}
|
|
if (process.env.DUMP) dump('before knocking out');
|
|
const msgs = run(`JSON.stringify(molds.map(m => collectMold(m)))`);
|
|
console.log('knock out:', msgs, '| bars on the ground', run('ents.length'), '| bit tier', run('player.bitTier'));
|