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,168 @@
|
||||
// Trees (background layer), acorns, saplings, and waking freezing water.
|
||||
// ---------- Trees (background layer) ----------
|
||||
// Trunk cells drill into wood chunks; leaves just fall apart. A tree whose trunk
|
||||
// no longer reaches the ground comes down: every trunk cell drops as wood chunks.
|
||||
function cutTree(i, x, y, tier, amt = 1) {
|
||||
const b = bgm[i];
|
||||
const p = digp[i] + amt * tier.speed * 70 / (1 + HARD[b]);
|
||||
if (p < 255) { digp[i] = p; return false; }
|
||||
digp[i] = 0; bgm[i] = 0;
|
||||
if (b === WOOD && woodRoom(i)) { setCell(i, LWOOD, temp[i], 0); discover(LWOOD, x, y, WOOD, 'dig'); }
|
||||
else if (b === LEAVES && mat[i] === EMPTY && rnd() < 0.03) { setCell(i, ACORN, temp[i], 0); discover(ACORN, x, y, LEAVES, 'dig'); }
|
||||
else { wakeXY(x, y); emit({ t: 'debris', x, y, m: b }); }
|
||||
if (b === WOOD) for (const [dx, dy] of [[1, 0], [-1, 0], [0, -1], [0, 1], [1, -1], [-1, -1]]) {
|
||||
const nx = x + dx, ny = y + dy;
|
||||
if (nx >= 0 && ny >= 0 && nx < W && ny < H && bgm[ny * W + nx]) treeFall(ny * W + nx);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// A trunk cell is held up by solid ground or soil directly beneath it.
|
||||
function treeHeld(i) {
|
||||
if (i + W >= N) return true;
|
||||
const b = mat[i + W];
|
||||
return PH[b] === PH_SOLID || SOIL[b] === 1;
|
||||
}
|
||||
// Base cells of every tree (the lowest trunk cells). Checked now and then: a tree
|
||||
// whose ground has been dug or washed away comes down.
|
||||
let treeBases = [];
|
||||
function findTreeBases() {
|
||||
treeBases = [];
|
||||
for (let i = 0; i < N - W; i++) if (bgm[i] === WOOD && bgm[i + W] !== WOOD) treeBases.push(i);
|
||||
}
|
||||
function treeSupportTick() {
|
||||
for (let k = treeBases.length - 1; k >= 0; k--) {
|
||||
const i = treeBases[k];
|
||||
if (bgm[i] !== WOOD) { treeBases.splice(k, 1); continue; }
|
||||
if (!treeHeld(i)) { treeBases.splice(k, 1); treeFall(i); }
|
||||
}
|
||||
}
|
||||
// Make room for a wood chunk where a trunk cell was: air is fine; water there is
|
||||
// pushed to the nearest open cell (the chunk then floats up).
|
||||
function woodRoom(i) {
|
||||
const m = mat[i];
|
||||
if (m === EMPTY || PH[m] === PH_GAS) return true;
|
||||
if (PH[m] !== PH_LIQUID) return false;
|
||||
const T = temp[i];
|
||||
mat[i] = EMPTY;
|
||||
depositNear(m, T, i % W, (i / W) | 0, i); // if there is nowhere at all, that bit of water is lost
|
||||
return true;
|
||||
}
|
||||
function treeFall(start) {
|
||||
if (bgm[start] !== WOOD) return;
|
||||
// follow the wood (trunk and branches) to see whether any of it still stands on something
|
||||
const seenT = new Set([start]), st = [start], woodCells = [];
|
||||
let grounded = false, bx0 = W, bx1 = 0, by0 = H, by1 = 0;
|
||||
while (st.length && woodCells.length < 2000) {
|
||||
const i = st.pop(); woodCells.push(i);
|
||||
const x = i % W, y = (i / W) | 0;
|
||||
if (treeHeld(i)) { grounded = true; break; }
|
||||
if (x < bx0) bx0 = x; if (x > bx1) bx1 = x; if (y < by0) by0 = y; if (y > by1) by1 = y;
|
||||
for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
|
||||
const nx = x + dx, ny = y + dy; if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
|
||||
const j = ny * W + nx; if (bgm[j] === WOOD && !seenT.has(j)) { seenT.add(j); st.push(j); }
|
||||
}
|
||||
}
|
||||
if (grounded) return;
|
||||
// its crown: leaves connected to it, not straying far from its own wood
|
||||
const cells = woodCells.slice();
|
||||
for (const i of woodCells) st.push(i);
|
||||
bx0 -= 9; bx1 += 9; by0 -= 12; by1 += 4;
|
||||
while (st.length && cells.length < 4000) {
|
||||
const i = st.pop(), x = i % W, y = (i / W) | 0;
|
||||
for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
|
||||
const nx = x + dx, ny = y + dy; if (nx < bx0 || ny < by0 || nx > bx1 || ny > by1 || nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
|
||||
const j = ny * W + nx; if (bgm[j] === LEAVES && !seenT.has(j)) { seenT.add(j); st.push(j); cells.push(j); }
|
||||
}
|
||||
}
|
||||
let wood = 0;
|
||||
// a felled tree drops a few acorns from its crown
|
||||
const crown = cells.filter(i => bgm[i] === LEAVES && mat[i] === EMPTY);
|
||||
const nAcorn = Math.min(crown.length, 1 + (crown.length > 60 ? 1 : 0) + (rnd() < 0.5 ? 1 : 0));
|
||||
for (let k = 0; k < nAcorn; k++) { const i = crown[(rnd() * crown.length) | 0]; if (mat[i] === EMPTY) { setCell(i, ACORN, AMB[(i / W) | 0], 0); bgm[i] = 0; discover(ACORN, i % W, (i / W) | 0, LEAVES, 'dig'); } }
|
||||
for (const i of cells) {
|
||||
const x = i % W, y = (i / W) | 0;
|
||||
if (bgm[i] === WOOD && woodRoom(i)) { setCell(i, LWOOD, temp[i], 0); wood++; }
|
||||
else if (bgm[i] && (x + y) % 3 === 0) emit({ t: 'debris', x, y, m: bgm[i] });
|
||||
bgm[i] = 0; wakeXY(x, y);
|
||||
}
|
||||
if (wood) { discover(LWOOD, cells[0] % W, (cells[0] / W) | 0, WOOD, 'dig'); emit({ t: 'sfx', s: 'decon' }); }
|
||||
}
|
||||
// Acorns resting on soil under open sky sprout after a while; saplings grow
|
||||
// into full trees in the background layer.
|
||||
let saplings = [];
|
||||
// Heat only runs where something is off ambient, so water resting in freezing air
|
||||
// would never be looked at. Wake its area now and then so it can freeze.
|
||||
function coldTick() {
|
||||
for (let y = 0; y < H && AMB[y] < -0.5; y++) {
|
||||
const yw = y * W;
|
||||
for (let x = 0; x < W; x++) if (mat[yw + x] === WATER) { wakeHeatXY(x, y); x |= 31; }
|
||||
}
|
||||
}
|
||||
function plantTick() {
|
||||
for (let x = 0; x < W; x++) {
|
||||
const y = roofY[x];
|
||||
if (y <= 6 || y >= H - 1) continue;
|
||||
const i = y * W + x;
|
||||
if (mat[i] !== ACORN || (flags[i] & F_BURN)) continue;
|
||||
if (!SOIL[mat[i + W]] || temp[i] < 2 || temp[i] > 45) { life[i] = 0; continue; }
|
||||
let room = true;
|
||||
for (let k = 1; k <= 6 && room; k++) { const j = i - k * W; if (PH[mat[j]] !== PH_EMPTY && PH[mat[j]] !== PH_GAS || bgm[j] === WOOD) room = false; }
|
||||
if (!room) continue;
|
||||
if (++life[i] < 14) continue;
|
||||
setCell(i, EMPTY, AMB[y], 0);
|
||||
saplings.push({ x, y: y + 1, age: 0, th: 8 + ((rnd() * 7) | 0), seed: (rnd() * 1e9) | 0 });
|
||||
if (!stats.sprouted) { stats.sprouted = 1; emit({ t: 'sprout', x, y }); }
|
||||
}
|
||||
}
|
||||
let treeDraw = null; // cells the sapling being drawn has set, so the next stage can redraw it
|
||||
function setTreeCell(x, y, m) {
|
||||
if (x < 0 || y < 1 || x >= W || y >= H) return;
|
||||
const i = y * W + x;
|
||||
if (mat[i] !== EMPTY && PH[mat[i]] !== PH_GAS) return;
|
||||
if (m === LEAVES && bgm[i]) return;
|
||||
bgm[i] = m;
|
||||
if (treeDraw) treeDraw.push(i);
|
||||
}
|
||||
function saplingTick() {
|
||||
for (let k = saplings.length - 1; k >= 0; k--) {
|
||||
const sp = saplings[k], base = sp.y * W + sp.x;
|
||||
// the soil it grows from must still be there
|
||||
if (sp.y >= H || !(SOIL[mat[base]] || PH[mat[base]] === PH_SOLID)) { saplings.splice(k, 1); continue; }
|
||||
if (sp.age > 2 && bgm[base - W] !== WOOD) { saplings.splice(k, 1); continue; } // cut down or burned while young
|
||||
// if any trunk it grew is gone (cut or burned), it stops growing and is left as it is
|
||||
if (sp.wood && sp.wood.some(i => bgm[i] !== WOOD)) { saplings.splice(k, 1); continue; }
|
||||
sp.age++;
|
||||
const s = Math.min(1, sp.age / 180);
|
||||
// wipe the previous stage, then draw this one
|
||||
if (sp.cells) for (const i of sp.cells) if (bgm[i] === LEAVES || (bgm[i] === WOOD && i !== base - W)) bgm[i] = 0;
|
||||
treeDraw = sp.cells = [];
|
||||
const th = Math.max(1, Math.round(sp.th * s)), wide = s > 0.5 ? 2 : 1;
|
||||
for (let j = 1; j <= th; j++) for (let dx = 0; dx < wide; dx++) setTreeCell(sp.x + dx, sp.y - j, WOOD);
|
||||
if (s > 0.2) {
|
||||
// the same lumpy crown as wild trees, scaled with age
|
||||
const q = s, h0 = sp.seed;
|
||||
if (s >= 1) for (const side of [-1, 1]) { const by = sp.y - th + 3 + (h0 >> (side > 0 ? 3 : 5)) % 3; for (let j = 1; j <= Math.round(3 * q); j++) setTreeCell(sp.x + (side < 0 ? -j : 1 + j), by - (j >> 1), WOOD); }
|
||||
const blobs = [[0.5, -th - 1, (6 + (h0 % 25) / 10) * q], [-3.5 * q, -th + 1, (4 + (h0 >> 4) % 20 / 10) * q], [4.5 * q, -th + 1, (4 + (h0 >> 8) % 20 / 10) * q], [0.5, -th - 5 * q, (4 + (h0 >> 12) % 20 / 10) * q]];
|
||||
for (const [bx, by, r] of blobs) {
|
||||
if (r < 1) continue;
|
||||
const cx = sp.x + bx, cy = sp.y + by;
|
||||
for (let yy = Math.floor(cy - r); yy <= cy + r; yy++) for (let xx = Math.floor(cx - r); xx <= cx + r; xx++) {
|
||||
const ex = (xx - cx) / r, ey = (yy - cy) / (r * 0.85);
|
||||
if (ex * ex + ey * ey <= 1 && hash2(xx, yy, worldSeed + 7) % 9 !== 0) setTreeCell(xx, yy, LEAVES);
|
||||
}
|
||||
}
|
||||
}
|
||||
treeDraw = null;
|
||||
sp.wood = sp.cells.filter(i => bgm[i] === WOOD);
|
||||
for (const i of sp.wood) if (bgm[i + W] !== WOOD && !treeBases.includes(i)) treeBases.push(i);
|
||||
if (s >= 1) { saplings.splice(k, 1); stats.grown = (stats.grown || 0) + 1; }
|
||||
}
|
||||
}
|
||||
// A flame next to a tree sets that part of it alight: it moves into the
|
||||
// foreground as burning wood or leaves and burns like any other fuel.
|
||||
function igniteTree(j) {
|
||||
const b = bgm[j]; bgm[j] = 0;
|
||||
mat[j] = b; flags[j] = F_BURN; life[j] = FUEL[b]; temp[j] = Math.max(temp[j], IGN[b] + 40);
|
||||
const x = j % W, y = (j / W) | 0;
|
||||
wakeXY(x, y); wakeHeatXY(x, y);
|
||||
}
|
||||
Reference in New Issue
Block a user