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,109 @@
|
||||
// How belts, filters and screw tubes move what is on or in them.
|
||||
// What rests on a belt or filter rides along: the whole stack standing on the carrying surface
|
||||
// steps one cell toward d together (belts run front of the line first, so the way ahead is
|
||||
// clear), the way a real belt carries a heap, not just its bottom layer. A filter's own
|
||||
// material drops through it instead, unless the filter below is already full.
|
||||
const CARRY_DEPTH = 96;
|
||||
function carryColumn(a, d, pass) {
|
||||
for (let L = 0; L < CARRY_DEPTH; L++, a -= W) {
|
||||
if (a < 0) return;
|
||||
const m = mat[a];
|
||||
if (m === EMPTY || (PH[m] === PH_SOLID && !part[a])) return; // the top of the heap, or a ceiling
|
||||
if (PH[m] !== PH_POWDER || stamp[a] === tickStamp || occ[a]) continue;
|
||||
if (pass && pass[m]) {
|
||||
if (enter(m, PH_POWDER, a + W, 0, 1)) continue; // falls through on its own
|
||||
// the rollers shake the load: what the filter passes works its way down through the rest
|
||||
const u = mat[a + W];
|
||||
if (L > 0 && !pass[u] && PH[u] === PH_POWDER && stamp[a + W] !== tickStamp && !occ[a + W] && rnd() < 0.5) { const x = a % W, y = (a / W) | 0; move(a, a + W, x, y, x, y + 1); continue; }
|
||||
}
|
||||
const x = a % W, nx = x + d;
|
||||
if (nx < 0 || nx >= W) return;
|
||||
const j = a + d, tj = mat[j];
|
||||
// grains on a wet belt push through the water rather than stopping at it
|
||||
const wet = PH[tj] === PH_LIQUID && !occ[j] && stamp[j] !== tickStamp && (!part[j] || part[j] === P_FILTER || part[j] === P_GOPEN);
|
||||
if (!wet && !enter(m, PH_POWDER, j, d, 0)) continue;
|
||||
const y = (a / W) | 0, bj = j + W;
|
||||
// carried off the end with nothing under it: it leaves at belt speed and falls
|
||||
if (mat[j] === EMPTY && !part[j] && bj < N && (mat[bj] === EMPTY || PH[mat[bj]] === PH_GAS) && !occ[bj] && !part[bj]) launchGrain(a, nx, y, d * 0.5, 0);
|
||||
else move(a, j, x, y, nx, y);
|
||||
stats.moved++;
|
||||
}
|
||||
}
|
||||
// Belts are perforated: liquid resting on one drips through to whatever open space is under it.
|
||||
function beltDrain(b) {
|
||||
const a = b - W, x = b % W;
|
||||
let d = b;
|
||||
for (let k = 0; k < 16 && d < N; k++) { const m = mat[d]; if (m !== BELTL && m !== BELTR) break; d += W; }
|
||||
if (d >= N || mat[d] === BELTL || mat[d] === BELTR) return;
|
||||
if (!passable(mat[a], PH_LIQUID, d, 0, 1)) return;
|
||||
move(a, d, x, (a / W) | 0, x, (d / W) | 0);
|
||||
}
|
||||
// Take a grain out of cell i and send it flying from (x, y): what leaves a belt or a tube mouth
|
||||
function launchGrain(i, x, y, vx, vy) {
|
||||
const m = mat[i], T = temp[i];
|
||||
mat[i] = EMPTY; flags[i] = 0; life[i] = 0; stamp[i] = tickStamp;
|
||||
wakeXY(i % W, (i / W) | 0);
|
||||
flyers.push({ x: x + 0.5, y: y + 0.5, vx, vy, age: 0, m, T, free: true, run: 0 });
|
||||
}
|
||||
let filterR = [], filterL = [], filtersDirty = true;
|
||||
function rebuildFilters() {
|
||||
filtersDirty = false; filterR = []; filterL = [];
|
||||
for (let b = 0; b < NB; b++) if (blockP[b] === BP_FILTER) (blockV[b] & 128 ? filterR : filterL).push(b);
|
||||
// front of the line first, so a full run moves as one
|
||||
filterR.sort((a, c) => (c % BW) - (a % BW)); filterL.sort((a, c) => (a % BW) - (c % BW));
|
||||
}
|
||||
function filterCarry(b, d) {
|
||||
if (blockP[b] !== BP_FILTER) return;
|
||||
const bx = b % BW, y = ((b / BW) | 0) * BS;
|
||||
if (y < 1) return;
|
||||
const c = filterCfg.get(b), pass = c ? c.pass : null;
|
||||
for (let k = 0; k < BS; k++) {
|
||||
const x = d > 0 ? bx * BS + BS - 1 - k : bx * BS + k, top = y * W + x;
|
||||
if (part[top] === P_FILTER) carryColumn(top - W, d, pass);
|
||||
}
|
||||
}
|
||||
// Change what a filter does; grains inside it that no longer pass are pushed out on top.
|
||||
function setFilter(b, cfg) {
|
||||
if (blockP[b] !== BP_FILTER) return false;
|
||||
const rec = filterRec(cfg);
|
||||
filterCfg.set(b, rec);
|
||||
const bx = b % BW, by = (b / BW) | 0;
|
||||
const cells = blockCellList(bx, by).filter(i => PH[mat[i]] === PH_POWDER && !rec.pass[mat[i]]);
|
||||
for (let y = by * BS + BS - 1; y >= by * BS; y--) for (const i of cells) if (((i / W) | 0) === y) evictCells([i], by * BS);
|
||||
return true;
|
||||
}
|
||||
// The run of filters in a row that touch block b and do the same thing it does
|
||||
function filterRun(b) {
|
||||
if (blockP[b] !== BP_FILTER) return [];
|
||||
const f = filterInfo(b), dir = blockV[b] & 128, by = (b / BW) | 0, out = [b];
|
||||
for (const s of [-1, 1]) for (let bx = (b % BW) + s; bx >= 0 && bx < BW; bx += s) {
|
||||
const c = by * BW + bx;
|
||||
if (blockP[c] !== BP_FILTER || (blockV[c] & 128) !== dir || !sameFilter(filterInfo(c), f)) break;
|
||||
out.push(c);
|
||||
}
|
||||
return out.sort((a, c) => a - c);
|
||||
}
|
||||
|
||||
// A cell a grain can be pushed into: empty, or liquid or gas that simply trades places with it
|
||||
function roomFor(j) { const t = mat[j]; return t === EMPTY || PH[t] === PH_GAS || PH[t] === PH_LIQUID; }
|
||||
// An empty screw cell draws in loose material from behind it or from its sides
|
||||
// (not from the cell it pushes into), so a tube end sitting in a pile feeds itself.
|
||||
function tubeIntake(i, p, dx, dy) {
|
||||
const x = i % W, y = (i / W) | 0;
|
||||
const cands = [[-dx, -dy], [dy, dx], [-dy, -dx]];
|
||||
const start = (tick + i) % 3;
|
||||
for (let c = 0; c < 3; c++) {
|
||||
const [ox, oy] = cands[(start + c) % 3];
|
||||
const nx = x + ox, ny = y + oy;
|
||||
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
|
||||
const j = ny * W + nx, m = mat[j];
|
||||
if (m === EMPTY || occ[j] || stamp[j] === tickStamp) continue;
|
||||
if (PH[m] !== PH_POWDER) continue; // the screw only takes loose material; liquids just flow through
|
||||
if (stamp[i] === tickStamp) return;
|
||||
const pj = part[j];
|
||||
if (pj >= P_TU && pj <= P_TR) continue; // other tube cells move their own contents
|
||||
if (pj === P_GRATE || pj === P_FILTER) continue;
|
||||
move(j, i, nx, ny, x, y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user