Deepgrain Foundry: move from a claude.ai artifact to a local repository
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
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
// Building: undo (Ctrl+Z), cut, replace a machine with Ctrl, pasting ghosts, and the fan heat warning.
|
||||
const { load, suite, flatWorld, count } = require('./harness');
|
||||
const { run } = load();
|
||||
const { check, done } = suite();
|
||||
const fresh = () => { flatWorld(run, 240, 160, 100); run('money = 1000; bag[RSTONE] = 400; bagTotal = 400; bot.x = 100; bot.y = 60; undoStack = []; clearAllGhosts();'); };
|
||||
const belts = () => run('blockP.reduce((a, v) => a + (v === BP_BELT), 0)');
|
||||
const fans = () => run("machines.filter(m => m && m.type === 'fan').length");
|
||||
|
||||
// placing a line of belts, then undoing it: they come down and the money comes back
|
||||
fresh();
|
||||
const m0 = run('money');
|
||||
run("undoRecord('placing belts', () => placeBlocks(BP_BELT, 1, blockLine(22, 20, 27, 20), undefined, false))");
|
||||
check('a line of 6 belts is placed', belts() === 6 && run('money') === m0 - 72, `${belts()} belts, $${run('money')}`);
|
||||
check('Ctrl+Z takes them back and refunds them', run('undoLast()') === 'Undid placing belts.' && belts() === 0 && run('money') === m0, `${belts()} belts, $${run('money')}`);
|
||||
check('with nothing left, it says so', run('undoLast()') === 'Nothing to undo.');
|
||||
|
||||
// taking a piece back, then undoing: it is built again
|
||||
fresh();
|
||||
run('placeBlocks(BP_BELT, 1, blockLine(22, 20, 25, 20), undefined, false); placeBlock(BP_WALL, RSTONE, 26, 20, false);');
|
||||
const st0 = run('bag[RSTONE]');
|
||||
run("undoRecord('taking back', () => { removePieceAt(23 * 4 + 1, 20 * 4 + 1); removePieceAt(26 * 4 + 1, 20 * 4 + 1); })");
|
||||
check('taking back a belt and a wall block', belts() === 3 && run('blockP[20 * BW + 26]') === 0 && run('bag[RSTONE]') === st0 + 16, `${belts()} belts, ${run('bag[RSTONE]')} rubble`);
|
||||
run('undoLast()');
|
||||
check('undo builds them again from the bag', belts() === 4 && run('blockP[20 * BW + 26]') === run('BP_WALL') && run('bag[RSTONE]') === st0, `${belts()} belts, ${run('bag[RSTONE]')} rubble`);
|
||||
|
||||
// replacing belts with filters, then undoing: the belts come back
|
||||
fresh();
|
||||
run('placeBlocks(BP_BELT, 1, blockLine(22, 20, 25, 20), undefined, false);');
|
||||
run("undoRecord('replacing', () => placeBlocks(BP_FILTER, 128, blockLine(23, 20, 24, 20), { m: [SAND], inv: 0 }, true))");
|
||||
check('Ctrl-dragging filters over belts replaces them', run('blockP[20 * BW + 23]') === run('BP_FILTER') && belts() === 2);
|
||||
run('undoLast()');
|
||||
check('undo puts the belts back', belts() === 4 && run('blockP.reduce((a, v) => a + (v === BP_FILTER), 0)') === 0, belts());
|
||||
|
||||
// Ctrl over built blocks with a machine: they are taken down and the fan goes in their place
|
||||
fresh();
|
||||
run('placeBlocks(BP_BELT, 1, blockLine(22, 20, 25, 20), undefined, false);');
|
||||
check('a fan will not go on a belt without Ctrl', /already/.test(run("canPlaceMachine('fan', 23, 20) || ''")));
|
||||
run("undoRecord('replacing', () => { clearFootprint(23, 20, 1, 1); placeMachineAt('fan', 0, 23, 20); })");
|
||||
check('with Ctrl the belt under it is taken down and the fan placed', fans() === 1 && belts() === 3);
|
||||
run('undoLast()');
|
||||
check('undo takes the fan back and rebuilds the belt', fans() === 0 && belts() === 4);
|
||||
run('for (let y = 76; y < 80; y++) for (let x = 112; x < 116; x++) setCell(y * W + x, STONE, AMB[y], F_NAT);');
|
||||
check('natural rock still stops a replace', /rock/.test(run('clearFootprint(28, 19, 1, 1)')));
|
||||
|
||||
// cut: everything in the box comes down into the bag and onto the clipboard; undo puts it back
|
||||
fresh();
|
||||
run("placeBlocks(BP_BELT, 1, blockLine(22, 20, 25, 20), undefined, false); placeMachineAt('fan', 0, 22, 18);");
|
||||
const mc = run('money');
|
||||
const bp = JSON.parse(run("JSON.stringify(undoRecord('cutting', () => cutRegion(22 * 4, 18 * 4, 26 * 4 - 1, 21 * 4 - 1)))"));
|
||||
check('cut picks up the belts and the fan', bp && bp.blocks.length === 4 && bp.machines.length === 1, bp && `${bp.blocks.length} blocks, ${bp.machines.length} machines`);
|
||||
check('and takes them down, refunded', belts() === 0 && fans() === 0 && run('money') === mc + 48 + 60, `${belts()} belts, ${fans()} fans, $${run('money')}`);
|
||||
run('undoLast()');
|
||||
check('undo after a cut puts it all back', belts() === 4 && fans() === 1 && run('money') === mc, `${belts()} belts, ${fans()} fans, $${run('money')}`);
|
||||
|
||||
// paste: ghosts appear; undo clears them, and takes back any the bot already built
|
||||
fresh();
|
||||
run("placeBlocks(BP_BELT, 1, blockLine(22, 20, 25, 20), undefined, false); window_bp = copyRegion(22 * 4, 20 * 4, 26 * 4 - 1, 21 * 4 - 1);");
|
||||
run("undoRecord('pasting', () => pasteGhost(window_bp, 22, 17))");
|
||||
check('pasting places ghosts', run('ghostBlocks.size') === 4);
|
||||
run('for (let k = 0; k < 400; k++) simTick();');
|
||||
const built = belts();
|
||||
check('the bot builds some or all of them', built > 4, built);
|
||||
check('building from ghosts is never an undo step of its own', run('undoStack.length') === 1, run('undoStack.length'));
|
||||
run('undoLast()');
|
||||
check('undo clears the ghosts and takes back what was built from them', belts() === 4 && run('ghostBlocks.size') === 0, `${belts()} belts, ${run('ghostBlocks.size')} ghosts`);
|
||||
|
||||
// a fan getting near its melting point warns you
|
||||
fresh();
|
||||
run("placeMachineAt('fan', 0, 22, 20); EV.length = 0;");
|
||||
run('for (let y = 80; y < 84; y++) for (let x = 88; x < 92; x++) temp[y * W + x] = 900; for (let k = 0; k < 40; k++) { for (let y = 80; y < 84; y++) for (let x = 88; x < 92; x++) temp[y * W + x] = 900; simTick(); }');
|
||||
const warn = run("EV.filter(e => e.t === 'toast' && /fan/i.test(e.text)).map(e => e.text).join(' | ')");
|
||||
check('a fan at 900°C warns that it is about to melt', /1085/.test(warn) && fans() === 1, warn);
|
||||
|
||||
// old saves: tiny specks of too-hard rock become the rock around them, once
|
||||
fresh();
|
||||
run('for (let y = 120; y < 122; y++) for (let x = 60; x < 62; x++) setCell(y * W + x, FEV, AMB[y], F_NAT); for (let y = 120; y < 130; y++) for (let x = 140; x < 150; x++) setCell(y * W + x, FEV, AMB[y], F_NAT);');
|
||||
const n = run('clearHardSpecks(true)');
|
||||
check('a 4-grain speck of iron ore in stone becomes stone; a 100-grain vein stays', n === 1 && count(run, run('FEV')) === 100 && run('mat[120 * W + 60]') === run('STONE'), `${n} specks, ${count(run, run('FEV'))} vein`);
|
||||
|
||||
done();
|
||||
Reference in New Issue
Block a user