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
78 lines
6.8 KiB
JavaScript
78 lines
6.8 KiB
JavaScript
// Uses for waste rock and sand: glass bricks from a fan furnace, the stone cutter, the silo and
|
|
// builders' orders.
|
|
const { load, suite, flatWorld, count } = require('./harness');
|
|
const { run } = load();
|
|
const { check, done } = suite();
|
|
const bars = (m) => run(`ents.filter(e => e.m === ${m}).length`);
|
|
|
|
// ---- glass: a brick furnace over an ingot mold, a fan blowing in through a side grate,
|
|
// charged with sand and coal
|
|
flatWorld(run, 240, 160, 100);
|
|
run('money = 9999; bot.x = 60; bot.y = 40;');
|
|
const bp = JSON.parse(run("JSON.stringify(smelterBlueprint('ingot', 1, 3, RBRICK))"));
|
|
const ox = 24, oy = 25 - bp.bh;
|
|
run(`(() => { const bp = ${JSON.stringify(bp)};
|
|
for (const [dx, dy, code, v] of bp.blocks) placeBlock(code, v, ${ox} + dx, ${oy} + dy, true);
|
|
for (const [k, dx, dy, wl] of bp.molds) addMold(k, ${ox} + dx, ${oy} + dy, wl, true);
|
|
// a grate low in the left wall, and a fan a block out from it blowing in
|
|
removeBlock(${ox}, ${oy} + 3); placeBlock(BP_GRATE, 0, ${ox}, ${oy} + 3, true);
|
|
addMachine('fan', ${ox} - 2, ${oy} + 3, 0);
|
|
})()`);
|
|
const cx0 = (ox + 1) * 4, cx1 = (ox + bp.bw - 1) * 4 - 1, cy0 = (oy + 1) * 4, cy1 = (oy + 4) * 4 - 1;
|
|
run(`{ const r = mulberry32(9); for (let y = ${cy0 + 5}; y <= ${cy1}; y++) for (let x = ${cx0}; x <= ${cx1}; x++) if (mat[y * W + x] === EMPTY) setCell(y * W + x, r() < 0.5 ? SAND : COAL, AMB[y], 0); }`);
|
|
const sand0 = count(run, run('SAND'));
|
|
run(`for (let k = 0; k < 60; k++) { toolIgnite(${cx0 + 3}, ${cy0 + 5}); toolIgnite(${cx1 - 3}, ${cy0 + 5}); simTick(); }`);
|
|
let peak = 0;
|
|
for (let t = 0; t < 6; t++) { run('for (let k = 0; k < 600; k++) simTick();'); peak = Math.max(peak, run(`(() => { let p = 0; for (let y = ${cy0}; y <= ${cy1}; y++) for (let x = ${cx0}; x <= ${cx1}; x++) p = Math.max(p, temp[y * W + x]); return Math.round(p); })()`)); }
|
|
const st = run('molds[0].state'), glassIn = run('molds[0].cavs.flat().filter(i => mat[i] === GLASS).length');
|
|
check('a fan furnace melts the sand and the molten glass runs into the ingot mold', glassIn >= 8, `peak ${peak}°C, ${sand0} sand -> ${count(run, run('SAND'))}, ${glassIn} glass in the mold, state ${st}`);
|
|
const msg = run('collectMold(molds[0])');
|
|
check('knocking it out gives glass bricks', bars(run('BARGL')) >= 2, `${bars(run('BARGL'))} bricks: ${msg}`);
|
|
check('glass bricks sell for much more than the sand in them', run('PRICE[BARGL]') >= 8 * run('PRICE[SAND]') * 10, run('PRICE[BARGL]'));
|
|
check('glass in a bit mold is too brittle to make a bit', run("METAL_TIER[GLASS]") === 0);
|
|
|
|
// ---- the stone cutter: 8 rubble to a stone brick; other grains drop through
|
|
flatWorld(run, 240, 160, 100);
|
|
run("money = 9999; bot.x = 60; bot.y = 40; stats.bricks = 0; placeMachineAt('cutter', 0, 26, 18); for (let bx = 24; bx <= 34; bx++) placeBlock(BP_BELT, 1, bx, 21, true);"); // x 104..111, y 72..79; out at y 80, onto a belt
|
|
run('for (let y = 72; y < 77; y++) for (let x = 106; x < 110; x++) setCell(y * W + x, RSTONE, AMB[y], 0);'); // 20 grains
|
|
for (let k = 0; k < 4; k++) { run('for (let k = 0; k < 150; k++) simTick();'); run('for (let y = 72; y < 77; y++) for (let x = 106; x < 110; x++) if (mat[y * W + x] === EMPTY) setCell(y * W + x, RSTONE, AMB[y], 0);'); }
|
|
run('for (let k = 0; k < 300; k++) simTick();');
|
|
const fed = run('stats.bricks') * 8 + run("machines.find(m => m && m.type === 'cutter').cut");
|
|
check('the stone cutter turns every 8 rubble into a stone brick', bars(run('BARSB')) === run('stats.bricks') && run('stats.bricks') >= 8 && count(run, run('RSTONE'), 104, 72, 111, 79) === 0, `${bars(run('BARSB'))} bricks from ${fed} rubble`);
|
|
check('and they ride a belt away from under it', run('ents.filter(e => e.m === BARSB && e.ix > 120).length') >= 3, run('JSON.stringify(ents.filter(e => e.m === BARSB).map(e => e.ix))'));
|
|
check('stone bricks build cut stone walls, 2 a block', run('WALL_OF[BARSB]') === run('CUTST') && JSON.stringify(run('wallCost(BARSB, 16)')) === JSON.stringify([[run('BARSB'), 2]]));
|
|
check('cut stone stands more heat than rough stone', run('MELT[CUTST]') > run('MELT[STONE]'));
|
|
run('bag[BARSB] = 4; bagTotal = 4; placeBlock(BP_WALL, BARSB, 10, 20, false);');
|
|
const wallOk = count(run, run('CUTST')) === 16 && run('bag[BARSB]') === 2;
|
|
run('removeBlock(10, 20)');
|
|
check('a cut stone wall is laid from 2 bricks and gives them back', wallOk && run('bag[BARSB] + ents.filter(e => e.m === BARSB).length') >= 4 - 0, `${run('bag[BARSB]')} in the bag`);
|
|
|
|
// ---- the silo: one material in through the hatch, out of the gate when it is open
|
|
flatWorld(run, 240, 160, 100);
|
|
run("money = 9999; bot.x = 60; bot.y = 30; placeMachineAt('silo', 0, 26, 1);"); // x 104..115, y 4..27; gate at y 27
|
|
const silo = 'machines.find(m => m && m.type === "silo")';
|
|
run('for (let k = 0; k < 30; k++) { for (let x = 107; x < 113; x++) setCell(2 * W + x, RSTONE, AMB[2], 0); simTick(); }');
|
|
check('grains dropped in its hatch go into the silo', run(`${silo}.sn`) >= 150 && run(`${silo}.sm`) === run('RSTONE'), run(`${silo}.sn`));
|
|
run('for (let x = 107; x < 113; x++) setCell(2 * W + x, SAND, AMB[2], 0); for (let k = 0; k < 20; k++) simTick();');
|
|
check('a different material does not go in on top', run(`${silo}.sm`) === run('RSTONE') && count(run, run('SAND'), 104, 0, 115, 6) > 0);
|
|
check('it will not be taken down while it holds something', /Empty the silo/.test(run('removePieceAt(110, 12)')) && run(`!!${silo}`));
|
|
const held = run(`${silo}.sn`);
|
|
run('interactAt(110, 12); for (let k = 0; k < 60; k++) simTick();');
|
|
const out1 = count(run, run('RSTONE'), 100, 27, 120, 99);
|
|
check('F opens the gate and it pours out underneath', out1 > 30 && run(`${silo}.sn`) === held - out1, `${out1} out, ${run(`${silo}.sn`)} left of ${held}`);
|
|
run('interactAt(110, 12); for (let k = 0; k < 60; k++) simTick();');
|
|
check('F shuts it again', count(run, run('RSTONE'), 100, 27, 120, 99) === out1);
|
|
run('(() => { const s = serializeWorld(); deserializeWorld(JSON.parse(JSON.stringify(s.meta)), s.buffers); })()');
|
|
check('what is in a silo is saved', run(`${silo}.sn`) === held - out1 && run(`${silo}.sm`) === run('RSTONE'));
|
|
|
|
// ---- builders' orders: bulk rubble, gravel and stone bricks at a few times a tiny price
|
|
flatWorld(run, 240, 160, 100);
|
|
run('seen[RSTONE] = 1; seen[GRAVEL] = 1; seen[BARSB] = 1; orders = []; ordersDone = 0;');
|
|
const os = JSON.parse(run('JSON.stringify(Array.from({ length: 40 }, () => makeBuildersOrder()))'));
|
|
const okOrders = os.every(o => o && o.who === 'Builders' && [run('RSTONE'), run('GRAVEL'), run('SAND'), run('BARSB')].includes(o.m) && o.pay > 0 && o.pay < 400);
|
|
check("builders post orders for rubble, gravel and stone bricks that pay a little", okOrders, os.slice(0, 4).map(o => `${o.qty} of ${o.m} for $${o.pay}`).join(', '));
|
|
const regular = JSON.parse(run('JSON.stringify(Array.from({ length: 60 }, () => { const o = makeOrder(); return o && !o.who ? o.m : 0; }))'));
|
|
check('ordinary buyers never ask for thousands of grains of rubble', !regular.includes(run('RSTONE')) && !regular.includes(run('GRAVEL')));
|
|
|
|
done();
|