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,85 @@
|
||||
// Weather: clouds from steam, rain and snow, and the world water count.
|
||||
// ---------- Weather ----------
|
||||
// Steam that rises into the open sky is gathered into clouds above where it rose. The clouds
|
||||
// drift slowly with the wind and rain it back out, or snow where the ground is below freezing.
|
||||
// Water is only ever moved around, never made, so the world neither floods nor runs dry. If
|
||||
// some does leave (dropped into the trade dock, say), a shower blows in from beyond the claim
|
||||
// now and then with just enough to make it up.
|
||||
const WBAND = 64;
|
||||
let WS = 0x1b873593 | 0;
|
||||
function wrnd() { WS ^= WS << 13; WS ^= WS >>> 17; WS ^= WS << 5; return (WS >>> 0) * 2.3283064365386963e-10; }
|
||||
let weather = { moist: new Float32Array(1), raining: new Uint8Array(1), shower: null, next: 0, wind: 0.3, seen: false, skyY: 1 };
|
||||
function resetWeather() {
|
||||
let top = H; for (let x = 0; x < W; x++) if (surfY[x] < top) top = surfY[x];
|
||||
weather = { moist: new Float32Array(Math.ceil(W / WBAND)), raining: new Uint8Array(Math.ceil(W / WBAND)), shower: null, next: tick + rainGap(), wind: wrnd() < 0.5 ? -0.3 : 0.3, seen: false, skyY: Math.max(1, top - 40), base: 0 };
|
||||
}
|
||||
function rainGap() { return 3600 * (5 + wrnd() * 7); } // 5 to 12 minutes between checks on the world's water
|
||||
// All the world's water: everything that is water or will be again (ice, snow, steam, clouds,
|
||||
// rain in the air, what is in your bag). weather.base is how much a world started with.
|
||||
function worldWater() {
|
||||
let n = 0;
|
||||
for (let i = 0; i < N; i++) { const m = mat[i]; if (m === WATER || m === ICE || m === SNOW || m === STEAM) n++; }
|
||||
for (let b = 0; b < weather.moist.length; b++) n += weather.moist[b];
|
||||
for (const f of flyers) if (f.m === WATER || f.m === SNOW) n++;
|
||||
for (const pn of pipeNets) if (pn.m === WATER) n += pn.n; // water in pipes still counts
|
||||
for (const mm of machines) if (mm && mm.type === 'silo' && mm.sm === SNOW) n += mm.sn; // and snow in silos
|
||||
return Math.round(n + bag[WATER] + bag[SNOW]);
|
||||
}
|
||||
function absorbSteam(x) { const b = Math.min(weather.moist.length - 1, Math.max(0, (x / WBAND) | 0)); weather.moist[b] += 1; }
|
||||
// Send about k drops (snowflakes where it is freezing) down from the clouds over x0..x1; returns how many fell
|
||||
function spawnRain(x0, x1, k) {
|
||||
if (flyers.length > 2500) return 0;
|
||||
let n = Math.floor(k) + (wrnd() < k - Math.floor(k) ? 1 : 0), made = 0;
|
||||
for (; n > 0; n--) {
|
||||
const x = Math.floor(x0 + wrnd() * (x1 - x0));
|
||||
if (x < 0 || x >= W) continue;
|
||||
const y = Math.min(weather.skyY, Math.max(1, roofY[x] - 30));
|
||||
const ground = Math.min(H - 1, roofY[x]), cold = AMB[ground] < 0;
|
||||
flyers.push({ x: x + wrnd(), y: y + wrnd() * 6, vx: weather.wind * (cold ? 0.35 : 0.12), vy: cold ? 0.15 : 0.5, age: 0, m: cold ? SNOW : WATER, T: cold ? Math.min(AMB[ground], -2) : Math.max(4, Math.min(18, AMB[ground])), free: true, run: 0, rain: true });
|
||||
made++;
|
||||
}
|
||||
return made;
|
||||
}
|
||||
// How hard it is raining over column x, 0 (dry) to 1 (a downpour)
|
||||
function rainAt(x) {
|
||||
let r = 0;
|
||||
const sh = weather.shower;
|
||||
if (sh && x >= sh.x0 - 40 && x <= sh.x1 + 40) r += sh.rate * sh.ramp * (x >= sh.x0 && x <= sh.x1 ? 1 : 0.5);
|
||||
const b = Math.min(weather.moist.length - 1, Math.max(0, (x / WBAND) | 0)), mo = weather.moist[b];
|
||||
if (weather.raining[b]) r += Math.min(3, 0.5 + mo / 100);
|
||||
return Math.min(1, r / 2);
|
||||
}
|
||||
function weatherTick() {
|
||||
const w = weather, n = w.moist.length;
|
||||
if (!w.shower && tick >= w.next) {
|
||||
// a shower from beyond the claim only when the world is short of the water it started with
|
||||
const short = w.base ? w.base - worldWater() : 0;
|
||||
if (short > Math.max(60, w.base * 0.03)) {
|
||||
const len = Math.min(W, 160 + wrnd() * 380), x0 = wrnd() * Math.max(1, W - len);
|
||||
w.shower = { x0, x1: x0 + len, rate: 0.6 + wrnd() * 1.6, until: tick + 60 * (40 + Math.floor(wrnd() * 80)), ramp: 0, left: short };
|
||||
w.wind = (wrnd() - 0.5) * 0.8;
|
||||
emit({ t: 'weather', kind: 'shower', x0: Math.round(x0), x1: Math.round(x0 + len) });
|
||||
} else w.next = tick + rainGap();
|
||||
}
|
||||
if (w.shower) {
|
||||
const sh = w.shower;
|
||||
sh.ramp = tick < sh.until ? Math.min(1, sh.ramp + 1 / 300) : sh.ramp - 1 / 300; // fades in and out over 5 s
|
||||
sh.x0 += w.wind * 0.03; sh.x1 += w.wind * 0.03;
|
||||
if (sh.ramp <= 0) { w.shower = null; w.next = tick + rainGap(); }
|
||||
else if (!(sh.left <= 0)) {
|
||||
const made = spawnRain(sh.x0, sh.x1, Math.min(sh.left === undefined ? 1e9 : sh.left, sh.rate * sh.ramp * (sh.x1 - sh.x0) / 300));
|
||||
if (sh.left !== undefined) { sh.left -= made; if (sh.left <= 0 && sh.until > tick) sh.until = tick; } // made up the shortfall: it passes
|
||||
}
|
||||
}
|
||||
// clouds made of your steam build up, then rain it back out where they hang
|
||||
for (let b = 0; b < n; b++) {
|
||||
if (!w.raining[b] && w.moist[b] > 150) w.raining[b] = 1;
|
||||
if (w.raining[b]) { w.moist[b] -= spawnRain(b * WBAND, (b + 1) * WBAND, Math.min(3, 0.5 + w.moist[b] / 100)); if (w.moist[b] < 12) w.raining[b] = 0; }
|
||||
}
|
||||
// and drift a little downwind every second; at the edge of the world they pile up instead
|
||||
if (tick % 60 === 0) {
|
||||
const out = new Float32Array(n), d = w.wind > 0 ? 1 : -1, f = Math.min(0.12, Math.abs(w.wind) * 0.1);
|
||||
for (let b = 0; b < n; b++) { const mv = w.raining[b] ? 0 : w.moist[b] * f; out[b] += w.moist[b] - mv; if (b + d >= 0 && b + d < n) out[b + d] += mv; else out[b] += mv; }
|
||||
w.moist = out;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user