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:
2026-10-23 16:05:31 -04:00
co-authored by Claude Opus 5.5
commit b270c4e818
97 changed files with 11710 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
// Smelter design experiments, built with the same block pieces a player uses.
// Layout (block rows, 4x4 grains each), on flat ground at row 25:
// roof with a vent ........ row g-CH-1
// chamber, CH blocks tall .. charge of coal + copper ore inside
// grate floor ............. row g = 23 (liquids pass, powders do not)
// ingot mold .............. row 24, sitting on the ground
// side walls run from the roof down past the mold.
// Usage: node t_smelter.js [key=value ...]
const { load, flatWorld, ticks } = require('../../test/harness');
const opt = Object.assign({ wall: 'RSTONE', ratio: 0.33, ch: 4, vent: 1, grate: 1, fill: 0.65, mix: 'mix', ign: 'top', mold: 'ingot', T: 7200, dump: 0, lid: 0, base: 'RSTONE', cw: 4 },
Object.fromEntries(process.argv.slice(2).map(a => a.split('=')).map(([k, v]) => [k, isNaN(+v) ? v : +v])));
const { run } = load();
flatWorld(run, 200, 160, 100);
run('bot.x = 90; bot.y = 50;');
const bx = 23, g = 23, CH = opt.ch, top = g - CH - 1, CWB = opt.cw;
const W_ = opt.wall;
const B = (code, v, x, y) => run(`placeBlock(${code}, ${v}, ${x}, ${y}, true)`);
// mold on the ground
for (let k = 0; k < CWB / 4; k++) {
const moldErr = run(`addMold('${opt.mold}', ${bx + k * 4}, 24, ${opt.base}, true)`);
if (typeof moldErr === 'string') { console.log('mold error', moldErr); process.exit(1); }
}
// floor: grate (or plain walls with nothing) over the mold
for (let x = bx; x < bx + CWB; x++) if (opt.grate) B('BP_GRATE', 0, x, g);
// side walls from roof down to the mold row, roof with a vent in the middle
if (W_ !== 'none') {
for (let y = top; y <= 24; y++) { B('BP_WALL', W_, bx - 1, y); B('BP_WALL', W_, bx + CWB, y); }
const mid = bx + (CWB >> 1);
const ventX = opt.vent === 2 ? [mid - 1, mid] : opt.vent === 1 ? [mid] : [];
for (let x = bx; x < bx + CWB; x++) if (!ventX.includes(x)) B('BP_WALL', W_, x, top);
}
// charge
const x0 = bx * 4, x1 = (bx + CWB) * 4 - 1, yBot = g * 4 - 1, yTop = Math.round(yBot - CH * 4 * opt.fill) + 1;
run(`{ const r = mulberry32(11); for (let y = ${yTop}; y <= ${yBot}; y++) for (let x = ${x0}; x <= ${x1}; x++) {
const i = y*W+x; if (mat[i] !== EMPTY || part[i]) continue;
let ore;
if ('${opt.mix}' === 'layers') ore = ((${yBot} - y) >> 1) % 3 === 1;
else if ('${opt.mix}' === 'coalbottom') ore = y < ${yTop} + (${yBot} - ${yTop}) * ${opt.ratio};
else ore = r() < ${opt.ratio};
setCell(i, ore ? CUORE : COAL, AMB[y], 0);
} }`);
const ore0 = run(`(()=>{let n=0;for(let i=0;i<N;i++) if(mat[i]===CUORE) n++; return n})()`);
const coal0 = run(`(()=>{let n=0;for(let i=0;i<N;i++) if(mat[i]===COAL) n++; return n})()`);
// light it like a player would: igniter on the top surface (or bottom corner)
const iy = opt.ign === 'top' ? yTop : yBot - 1;
run(`for (let k=0;k<45;k++){ ${[x0 + 2, x0 + 8, x1 - 2].map(x => `toolIgnite(${x}, ${iy});`).join('')} simTick(); }`);
const stat = () => run(`(() => {
let mcu = 0, cu = 0, scr = 0, ore = 0, coal = 0, inCav = 0, peak = 0, slag = 0;
const mo = molds[0], cav = new Set(molds.flatMap(m => m.cavs.flat()));
for (let i = 0; i < N; i++) { const m = mat[i];
if (m === MCU) mcu++; else if (m === COPPER) cu++; else if (m === SCRCU) scr++; else if (m === CUORE) ore++; else if (m === COAL) coal++; else if (m === SLAG) slag++;
if ((m === MCU || m === COPPER) && cav.has(i)) inCav++; }
for (let y = ${top * 4}; y < ${g * 4}; y++) for (let x = ${x0}; x <= ${x1}; x++) peak = Math.max(peak, temp[y*W+x]);
return { mcu, cu, scr, ore, coal, slag, inCav, cav: cav.size, peak: Math.round(peak), state: molds.map(m => m.state).join('+'), ready: molds.reduce((a, m) => a + m.ready.length, 0), fill: +mo.fill.toFixed(2) };
})()`);
console.log(JSON.stringify(opt), `ore ${ore0} coal ${coal0}`);
let el = 0;
for (const dt of [900, 900, 1800, 1800, 1800, 1800, 1800, 1800].filter((_, k, a) => a.slice(0, k + 1).reduce((p, q) => p + q, 0) <= opt.T)) {
ticks(run, dt); el += dt;
const s = stat();
console.log(`${String(el / 60).padStart(4)}s peak ${String(s.peak).padStart(4)}°C ore ${String(s.ore).padStart(3)} coal ${String(s.coal).padStart(3)} molten ${String(s.mcu).padStart(3)} set ${String(s.cu).padStart(3)} in mold ${s.inCav}/${s.cav} mold ${s.state}${s.ready ? ' ' + s.ready + ' bars' : ''}`);
}
if (opt.dump) {
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('BRICK')]: 'B', [run('CLAY')]: 'C' };
for (let y = top * 4 - 2; y < 104; y++) { let l = ''; for (let x = x0 - 6; x <= x1 + 6; x++) { const i = run(`${y}*W+${x}`); const m = run(`mat[${i}]`); l += run(`part[${i}]`) === 5 && m === 0 ? '+' : (ch[m] || '?'); } console.log(l); }
}