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:
+101
@@ -0,0 +1,101 @@
|
||||
// Pipes, pumps and outlets move liquids; the Refiner grinds waste rock for trace ore.
|
||||
const { load, suite, flatWorld, count } = require('./harness');
|
||||
const { run } = load();
|
||||
const { check, done } = suite();
|
||||
const box = (x0, y0, x1, y1, m, fl = 'F_NAT') => run(`for (let y = ${y0}; y <= ${y1}; y++) for (let x = ${x0}; x <= ${x1}; x++) setCell(y * W + x, ${m}, AMB[y], ${fl})`);
|
||||
const lineWater = () => run('pipeNets.reduce((a, n) => a + (n.m === WATER ? n.n : 0), 0)');
|
||||
const totalWater = () => count(run, run('WATER')) + lineWater();
|
||||
|
||||
// two stone basins; a pump in the bottom corner of the left one pushes water up a pipe, over the
|
||||
// wall and out of an outlet into the right one
|
||||
function rig() {
|
||||
flatWorld(run, 240, 160, 100);
|
||||
run('money = 5000; bot.x = 110; bot.y = 50;');
|
||||
box(36, 80, 39, 99, 'STONE'); box(72, 80, 75, 99, 'STONE'); // left basin walls
|
||||
box(108, 80, 111, 99, 'STONE'); box(144, 80, 147, 99, 'STONE'); // right basin walls
|
||||
box(40, 84, 71, 99, 'WATER', 0);
|
||||
run(`placeBlock(BP_PUMP, 1, 17, 24, false); // pushes right, draws in from the left
|
||||
for (let by = 19; by <= 23; by++) placeBlock(BP_PIPE, 0, 17, by, false);
|
||||
for (let bx = 18; bx <= 28; bx++) placeBlock(BP_PIPE, 0, bx, 19, false);
|
||||
placeBlock(BP_OUTLET, 0, 29, 19, false); // pours down`);
|
||||
}
|
||||
rig();
|
||||
const w0 = totalWater(), ww0 = run('worldWater()');
|
||||
run('for (let k = 0; k < 900; k++) simTick();');
|
||||
const right = count(run, run('WATER'), 112, 60, 143, 99);
|
||||
check('a pump, a pipe over the wall and an outlet move water from one basin to the other', right > 250, `${right} grains in the right basin, ${lineWater()} in the pipes`);
|
||||
check('no water is lost or made on the way', totalWater() === w0, `${w0} -> ${totalWater()}`);
|
||||
check('the weather counts water in pipes, so it sends no rain to make up for it', run('worldWater()') === ww0, `${ww0} -> ${run('worldWater()')}`);
|
||||
// the line is one network: pump, 16 pipes, outlet
|
||||
check('touching pipes, pump and outlet make one line', run('(rebuildPipes(), pipeNets.length)') === 1 && run('pipeNets[0].blocks.length') === 18, `${run('pipeNets.length')} lines, ${run('pipeNets[0].blocks.length')} blocks`);
|
||||
|
||||
// an outlet closed with F holds the water in the line; the pump stops when the line is full
|
||||
rig();
|
||||
run('interactAt(29 * 4 + 1, 19 * 4 + 1); for (let k = 0; k < 900; k++) simTick();');
|
||||
check('with the outlet closed the line fills up and nothing comes out', count(run, run('WATER'), 112, 60, 143, 99) === 0 && lineWater() === 18 * 16, `${lineWater()} in the line`);
|
||||
run('interactAt(29 * 4 + 1, 19 * 4 + 1); for (let k = 0; k < 120; k++) simTick();');
|
||||
check('opened again, it pours', count(run, run('WATER'), 112, 60, 143, 99) > 50);
|
||||
|
||||
// taking a pipe back spills its share where it was
|
||||
rig();
|
||||
run('interactAt(29 * 4 + 1, 19 * 4 + 1); for (let k = 0; k < 900; k++) simTick();');
|
||||
const before = totalWater();
|
||||
run('removeBlock(23, 19); rebuildPipes();');
|
||||
check('taking a full pipe back spills its 16 grains of water there', totalWater() === before && count(run, run('WATER'), 88, 70, 99, 99) > 0, `${before} -> ${totalWater()}`);
|
||||
check('and the line is cut in two', run('pipeNets.length') === 2, run('pipeNets.length'));
|
||||
|
||||
// one liquid per line, and nothing hotter than 500°C
|
||||
rig();
|
||||
run('interactAt(29 * 4 + 1, 19 * 4 + 1); for (let k = 0; k < 60; k++) simTick();'); // outlet closed: some water stays in the line
|
||||
box(40, 84, 71, 99, 'EMPTY', 0); box(40, 90, 71, 99, 'MSN', 0); run('for (let y = 90; y < 100; y++) for (let x = 40; x < 72; x++) temp[y * W + x] = 300;');
|
||||
run('for (let k = 0; k < 120; k++) simTick();');
|
||||
check('a line holding water will not take molten tin', count(run, run('MSN')) + count(run, run('TIN')) === 32 * 10 && run('pipeNets[0].m') === run('WATER'), `${count(run, run('MSN')) + count(run, run('TIN'))} tin left, line holds ${run('MD[pipeNets[0].m].name')}`);
|
||||
rig();
|
||||
box(40, 84, 71, 99, 'EMPTY', 0); box(40, 90, 71, 99, 'MCU', 0); run('for (let y = 90; y < 100; y++) for (let x = 40; x < 72; x++) temp[y * W + x] = 1200;');
|
||||
run('for (let k = 0; k < 60; k++) { for (let y = 90; y < 100; y++) for (let x = 40; x < 72; x++) if (mat[y * W + x] === MCU) temp[y * W + x] = 1200; simTick(); }');
|
||||
check('a pump will not take molten copper at 1200°C', run('pipeNets[0].n') === 0, run('pipeNets[0].n'));
|
||||
|
||||
// what is in the pipes is saved
|
||||
rig();
|
||||
run('interactAt(29 * 4 + 1, 19 * 4 + 1); for (let k = 0; k < 400; k++) simTick();');
|
||||
const held = lineWater();
|
||||
run('(() => { const s = serializeWorld(); deserializeWorld(JSON.parse(JSON.stringify(s.meta)), s.buffers); })()');
|
||||
check('the water in a line survives saving and loading', held > 100 && lineWater() === held, `${held} -> ${lineWater()}`);
|
||||
|
||||
// pipes are thin: water and sand pass through a pipe run instead of being held back by it
|
||||
flatWorld(run, 240, 160, 100);
|
||||
run('money = 5000; bot.x = 60; bot.y = 50;');
|
||||
box(36, 76, 39, 99, 'STONE'); box(88, 76, 91, 99, 'STONE');
|
||||
run('for (let by = 19; by <= 24; by++) placeBlock(BP_PIPE, 0, 15, by, false);'); // a pipe wall at x 60..63
|
||||
box(40, 80, 59, 99, 'WATER', 0);
|
||||
run('for (let k = 0; k < 400; k++) simTick();');
|
||||
check('water flows through a run of pipe', count(run, run('WATER'), 64, 70, 87, 99) > 60, `${count(run, run('WATER'), 64, 70, 87, 99)} grains got past`);
|
||||
box(60, 64, 63, 70, 'SAND', 0); run('for (let k = 0; k < 300; k++) simTick();');
|
||||
check('and sand dropped on a pipe falls down through it', count(run, run('SAND'), 40, 80, 87, 99) > 20, `${count(run, run('SAND'), 40, 80, 87, 99)} of 28 at the bottom`);
|
||||
|
||||
// ---- the Refiner
|
||||
function refinerRig(feed, n) {
|
||||
flatWorld(run, 240, 160, 100);
|
||||
run(`money = 5000; bot.x = 110; bot.y = 40; stats.refined = 0; stats.refinedOre = 0; placeMachineAt('refiner', 0, 26, 15);`); // x 104..115, y 60..71; chute out at y 72
|
||||
run(`{ let k = 0; for (let y = 60; y < 70 && k < ${n}; y++) for (let x = 106; x < 114 && k < ${n}; x++, k++) setCell(y * W + x, ${feed}, AMB[y], 0); }`);
|
||||
}
|
||||
refinerRig('RSTONE', 80);
|
||||
const ore = () => count(run, run('CUORE')) + count(run, run('COAL')) + count(run, run('FEORE')) + count(run, run('SNORE'));
|
||||
// five hopper-loads, each ground before the next goes in
|
||||
for (let k = 0; k < 5; k++) { run('for (let k = 0; k < 300; k++) simTick();'); if (k < 4) run('for (let y = 60; y < 70; y++) for (let x = 106; x < 114; x++) setCell(y * W + x, RSTONE, AMB[y], 0);'); }
|
||||
const fed = 400, sand = count(run, run('SAND')), o = ore();
|
||||
check('the Refiner grinds all the stone rubble it is fed', count(run, run('RSTONE')) === 0 && run('stats.refined') === fed, `${count(run, run('RSTONE'))} left, ${run('stats.refined')} of ${fed} ground`);
|
||||
check('about half as much sand comes out', sand > fed * 0.35 && sand < fed * 0.6, `${sand} sand from ${fed} rubble`);
|
||||
check('with the odd grain of common ore', o > 0 && o < fed * 0.15, `${o} ore grains`);
|
||||
// gravel, sand and dirt are refined too; ore drops straight through
|
||||
refinerRig('LDIRT', 60); run('for (let k = 0; k < 600; k++) simTick();');
|
||||
check('dirt goes through the refiner too', count(run, run('LDIRT')) === 0 && run('stats.refined') === 60);
|
||||
refinerRig('CUORE', 30); run('for (let k = 0; k < 400; k++) simTick();');
|
||||
check('ore it cannot grind drops straight through, all of it', count(run, run('CUORE')) === 30 && count(run, run('CUORE'), 104, 60, 115, 71) === 0, count(run, run('CUORE'), 104, 60, 115, 71));
|
||||
// a blocked chute stops it
|
||||
refinerRig('RSTONE', 40);
|
||||
box(100, 72, 120, 73, 'STONE');
|
||||
run('for (let k = 0; k < 200; k++) simTick();');
|
||||
check('with the space under its chute blocked it stops, and says so', count(run, run('RSTONE')) === 40 && run("machines.find(m => m && m.type === 'refiner').blocked") === true);
|
||||
|
||||
done();
|
||||
Reference in New Issue
Block a user