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,114 @@
|
||||
// Pipes, pumps and outlets: each joined line holds one liquid as a count, not as grains.
|
||||
// ---------- Pipes ----------
|
||||
// Pipes, pumps and outlets that touch join into a line. A line holds one liquid at a time,
|
||||
// PIPE_CAP grains per block, kept as a count (it is not simulated grain by grain inside).
|
||||
// Pumps draw liquid in at their intake side; outlets let it out where there is room.
|
||||
const PIPE_CAP = 16, PUMP_RATE = 1, OUTLET_RATE = 2, PUMP_MAX_T = 500;
|
||||
const SIDE_V = [[0, -1], [1, 0], [0, 1], [-1, 0]]; // up, right, down, left
|
||||
const PUMP_V = SIDE_V; // pump variants: the way it pushes (its intake is the opposite side)
|
||||
const OUTLET_V = [[0, 1], [-1, 0], [0, -1], [1, 0]]; // outlet variants: down, left, up, right
|
||||
let pipeNets = [], pipeNetAt = null, pipesDirty = true;
|
||||
function sideOf(dx, dy) { for (let s = 0; s < 4; s++) if (SIDE_V[s][0] === dx && SIDE_V[s][1] === dy) return s; return -1; }
|
||||
// the one side of a block that does not join the line: a pump's intake, an outlet's mouth
|
||||
function pipeClosedSide(b) {
|
||||
const c = blockP[b], d = blockV[b] & 3;
|
||||
if (c === BP_PUMP) return sideOf(-PUMP_V[d][0], -PUMP_V[d][1]);
|
||||
if (c === BP_OUTLET) return sideOf(OUTLET_V[d][0], OUTLET_V[d][1]);
|
||||
return -1;
|
||||
}
|
||||
function pipeJoins(b, s) {
|
||||
const bx = b % BW, by = (b / BW) | 0, nx = bx + SIDE_V[s][0], ny = by + SIDE_V[s][1];
|
||||
if (nx < 0 || ny < 0 || nx >= BW || ny >= BH || s === pipeClosedSide(b)) return -1;
|
||||
const d = ny * BW + nx;
|
||||
return isPipeCode(blockP[d]) && pipeClosedSide(d) !== (s + 2) % 4 ? d : -1;
|
||||
}
|
||||
function rebuildPipes() {
|
||||
pipesDirty = false;
|
||||
const old = pipeNets, oldAt = pipeNetAt;
|
||||
pipeNetAt = new Int32Array(NB).fill(-1); pipeNets = [];
|
||||
for (let b0 = 0; b0 < NB; b0++) {
|
||||
if (!isPipeCode(blockP[b0]) || pipeNetAt[b0] >= 0) continue;
|
||||
const id = pipeNets.length, net = { blocks: [], pumps: [], outlets: [], m: 0, n: 0, T: 20, cap: 0, flowT: -999 };
|
||||
pipeNets.push(net); pipeNetAt[b0] = id;
|
||||
const st = [b0];
|
||||
while (st.length) {
|
||||
const b = st.pop(); net.blocks.push(b);
|
||||
if (blockP[b] === BP_PUMP) net.pumps.push(b); else if (blockP[b] === BP_OUTLET) net.outlets.push(b);
|
||||
for (let s = 0; s < 4; s++) { const d = pipeJoins(b, s); if (d >= 0 && pipeNetAt[d] < 0) { pipeNetAt[d] = id; st.push(d); } }
|
||||
}
|
||||
net.cap = net.blocks.length * PIPE_CAP;
|
||||
}
|
||||
// what the old lines held goes with their blocks into the new lines
|
||||
if (oldAt) for (const o of old) {
|
||||
if (!o.n) continue;
|
||||
const share = new Map(); let alive = 0;
|
||||
for (const b of o.blocks) { const id = pipeNetAt[b]; if (id >= 0) { share.set(id, (share.get(id) || 0) + 1); alive++; } }
|
||||
let left = o.n;
|
||||
const parts = [...share];
|
||||
parts.forEach(([id, k], idx) => {
|
||||
const amt = idx === parts.length - 1 ? left : Math.min(left, Math.round(o.n * k / alive));
|
||||
left -= amt;
|
||||
const net = pipeNets[id];
|
||||
if (!amt) return;
|
||||
if (net.n && net.m !== o.m) { if (net.n >= amt) return; net.n = 0; } // two liquids met: the smaller lot is lost
|
||||
net.T = (net.T * net.n + o.T * amt) / (net.n + amt); net.m = o.m; net.n = Math.min(net.cap, net.n + amt);
|
||||
});
|
||||
}
|
||||
}
|
||||
function pipeNetOf(b) { if (pipesDirty) rebuildPipes(); const id = pipeNetAt ? pipeNetAt[b] : -1; return id >= 0 ? pipeNets[id] : null; }
|
||||
// take block b's share of its line's liquid out (it is about to be removed)
|
||||
function pipeTakeShare(b) {
|
||||
const net = pipeNetOf(b);
|
||||
if (!net || !net.n) return null;
|
||||
const k = Math.min(net.n, PIPE_CAP, Math.round(net.n / net.blocks.length));
|
||||
net.n -= k;
|
||||
const r = { m: net.m, n: k, T: net.T };
|
||||
if (!net.n) net.m = 0;
|
||||
return r;
|
||||
}
|
||||
// the cells just outside side s of block b, lane by lane; depth grains further out
|
||||
function faceCell(b, s, lane, depth) {
|
||||
const bx = b % BW, by = (b / BW) | 0;
|
||||
let x, y;
|
||||
if (s === 0) { x = bx * BS + lane; y = by * BS - 1 - depth; }
|
||||
else if (s === 2) { x = bx * BS + lane; y = by * BS + BS + depth; }
|
||||
else if (s === 1) { x = bx * BS + BS + depth; y = by * BS + lane; }
|
||||
else { x = bx * BS - 1 - depth; y = by * BS + lane; }
|
||||
return x < 0 || y < 0 || x >= W || y >= H ? -1 : y * W + x;
|
||||
}
|
||||
function pumpDraw(net, b) {
|
||||
const s = sideOf(-PUMP_V[blockV[b] & 3][0], -PUMP_V[blockV[b] & 3][1]);
|
||||
let got = 0;
|
||||
for (let k = 0; k < 4 && got < PUMP_RATE && net.n < net.cap; k++) {
|
||||
const lane = (k + tick) & 3;
|
||||
for (let depth = 0; depth < 3; depth++) {
|
||||
const j = faceCell(b, s, lane, depth);
|
||||
if (j < 0) break;
|
||||
const m = mat[j];
|
||||
if (PH[m] !== PH_LIQUID) { if (PH[m] === PH_SOLID && !part[j]) break; continue; }
|
||||
if (temp[j] > PUMP_MAX_T || occ[j] || (net.n && m !== net.m)) break;
|
||||
net.T = (net.T * net.n + temp[j]) / (net.n + 1); net.m = m; net.lm = m; net.n++;
|
||||
setCell(j, EMPTY, AMB[(j / W) | 0], 0);
|
||||
got++; net.flowT = tick;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
function outletPour(net, b) {
|
||||
const v = OUTLET_V[blockV[b] & 3], s = sideOf(v[0], v[1]);
|
||||
let put = 0;
|
||||
for (let k = 0; k < 4 && put < OUTLET_RATE && net.n > 0; k++) {
|
||||
const j = faceCell(b, s, (k + tick) & 3, 0);
|
||||
if (j < 0 || mat[j] !== EMPTY || occ[j] || (part[j] && part[j] !== P_GRATE && part[j] !== P_GOPEN) || mach[j]) continue;
|
||||
setCell(j, net.m, net.T, 0);
|
||||
net.n--; put++; net.flowT = tick;
|
||||
}
|
||||
if (!net.n) net.m = 0;
|
||||
}
|
||||
function pipesTick() {
|
||||
if (pipesDirty) rebuildPipes();
|
||||
for (const net of pipeNets) {
|
||||
for (const b of net.pumps) if (!(blockV[b] & 4)) pumpDraw(net, b);
|
||||
if (net.n) for (const b of net.outlets) if (!(blockV[b] & 4)) outletPour(net, b);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user