Water pours off ledges several times faster, raining clouds spread boiled-off water (t21)

- Liquids step up to 8 cells a tick toward a drop (fewer the thicker they are), and a top-layer
  grain looks 4 times as far for one, so pools empty and thin film drains. A 100-wide pool
  empties in about 50 s; the old code left 24-650 grains stranded on wide ledges.
- Only a top-layer grain stepping sideways wakes the row ahead, so settled lakes still sleep.
- World generation settles at the old speed: seeds make the same worlds as before.
- Cooled steam under a roof no longer stays steam for ever (its heat area slept).
- Raining clouds keep drifting at half pace and rain over a band either side, so a waterfall into
  a hot shaft no longer rains straight back on its own lake.
- New test/t_water.js; t_weather rain-location checks; t_conveyors counts steam in flight;
  t_molds seeds the random burning-bed check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 20:44:06 -04:00
co-authored by Claude Opus 5.5
parent 99d04b4972
commit ea1dd387b1
10 changed files with 195 additions and 20 deletions
+15 -4
View File
@@ -21,6 +21,13 @@ function swapCells(i, j) {
function move(i, j, x, y, nx, ny) {
swapCells(i, j);
wakeXY(x, y); wakeXY(nx, ny);
if (ny === y && LFILM > 1 && PH[mat[j]] === PH_LIQUID && (y === 0 || PH[mat[i - W]] !== PH_LIQUID)) {
// a top-layer grain stepping sideways may clear the path to a drop for surface grains far
// along the row, so wake them; moves inside a body of water do not (settled lakes must sleep)
const reach = SPREAD[mat[j]] * LFILM, row = (y >> 5) * CW;
const first = Math.max(0, x - reach) >> 5, last = Math.min(W - 1, x + reach) >> 5;
for (let cx = first; cx <= last; cx++) awake[row + cx] = 3;
}
if (part[j] === P_WHEEL) { const mm = machines[mach[j]]; if (mm) mm.flow += PH[mat[j]] === PH_LIQUID ? 1 : 0.5; }
const d = temp[j] - AMB[ny];
if (d > 25 || d < -25) wakeHeatXY(nx, ny);
@@ -55,6 +62,10 @@ function enter(m, ph, j, dx, dy) {
// Heavy grains settle through a loose pile of much lighter ones, slowly. A sorting
// step keeps its area awake for a while so a pile finishes sorting even at rest.
let SORTP = 0.003;
// Liquids step up to LSTEP cells toward a drop in one tick (fewer the thicker they are), and a top-layer grain looks LFILM
// times as far for one (a two-times range strands the far end of a 200-grain-wide ledge).
// World generation settles with both at 1, so every seed keeps making the same world.
let LSTEP = 8, LFILM = 4;
function powder(i, x, y, m) {
if (y >= H - 1) return;
const b = i + W;
@@ -97,7 +108,7 @@ function liquid(i, x, y, m) {
if (nx >= 0 && nx < W && enter(m, PH_LIQUID, b - d, -d, 1)) { move(i, b - d, x, y, nx, y + 1); return; }
}
if (rnd() > FLUID[m]) return;
const sp = SPREAD[m];
const sp = SPREAD[m] * (y === 0 || PH[mat[i - W]] !== PH_LIQUID ? LFILM : 1);
const d0 = (RS & 4) ? 1 : -1;
for (let pass = 0; pass < 2; pass++) {
const d = pass ? -d0 : d0;
@@ -107,8 +118,8 @@ function liquid(i, x, y, m) {
const j = i + d * s;
if (!passable(m, PH_LIQUID, j, d, 0)) break;
if (y < H - 1 && passable(m, PH_LIQUID, j + W, 0, 1)) {
// step one cell toward the drop (keeps flow continuous and local)
move(i, i + d, x, y, x + d, y);
const k = Math.min(s, Math.max(1, (LSTEP * FLUID[m]) | 0)); // thick liquids take shorter steps
move(i, i + d * k, x, y, x + d * k, y);
return;
}
}
@@ -127,7 +138,7 @@ function gas(i, x, y, m) {
if (l <= 1) { mat[i] = EMPTY; life[i] = 0; wakeXY(x, y); return; }
life[i] = l - 1;
}
}
} else if (m === STEAM && temp[i] < CONDT[STEAM]) wakeHeatXY(x, y); // cooled steam drifting under a roof needs the heat pass to condense it
if (y === 0 || (m === STEAM && y <= weather.skyY + 4 && y < roofY[x])) {
// steam that has risen to cloud height in the open sky joins the clouds
if (m === STEAM) absorbSteam(x);
+6 -4
View File
@@ -71,15 +71,17 @@ function weatherTick() {
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
// clouds made of your steam build up, then rain it back out over where they hang and a band
// either side, so water boiled off one spot does not all fall back on it
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; }
if (w.raining[b]) { w.moist[b] -= spawnRain(Math.max(0, (b - 1) * WBAND), Math.min(W, (b + 2) * 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
// and drift a little downwind every second, raining ones at half the pace; 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; }
for (let b = 0; b < n; b++) { const mv = w.moist[b] * f * (w.raining[b] ? 0.5 : 1); out[b] += w.moist[b] - mv; if (b + d >= 0 && b + d < n) out[b + d] += mv; else out[b] += mv; }
w.moist = out;
}
}
+3 -1
View File
@@ -303,8 +303,9 @@ function* worldSteps(seedIn, w, h) {
}
fillNoise();
yield [0.75, 'Settling loose ground'];
// settle loose material
// settle loose material, with water at the speed it had when the seeds were made
awake.fill(3);
const lstep = LSTEP, lfilm = LFILM; LSTEP = LFILM = 1;
for (let k = 0; k < 260; k++) {
tick++; tickStamp = (tickStamp % 255) + 1;
simMove();
@@ -312,6 +313,7 @@ function* worldSteps(seedIn, w, h) {
if (!any) break;
if ((k & 31) === 0) yield [0.75 + 0.24 * k / 260, 'Settling loose ground'];
}
LSTEP = lstep; LFILM = lfilm;
yield [0.99, 'Finishing'];
// re-ambient after settling, then start clean
for (let i = 0; i < N; i++) { const m = mat[i]; if (m !== MAGMA) temp[i] = AMB[(i / w) | 0]; life[i] = PH[m] === PH_LIQUID && FRZ[m] ? 255 : 0; }