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:
+116
@@ -0,0 +1,116 @@
|
||||
// Water conservation, settling, ledge pouring, boundaries, pipes and phase changes.
|
||||
// Tall worlds keep geothermal heat out of the ponds.
|
||||
const { load, suite, flatWorld, put, rect, count, ticks } = require('./harness');
|
||||
const { run } = load();
|
||||
const { check, done } = suite();
|
||||
const started = Date.now();
|
||||
const water = () => run('worldWater()');
|
||||
const awake = () => run('awake.reduce((n, v) => n + (v > 0), 0)');
|
||||
const grains = m => count(run, run(m));
|
||||
function world(w = 320, ground = 120) {
|
||||
flatWorld(run, w, 600, ground);
|
||||
run('RS = 12345;');
|
||||
}
|
||||
function basin(x0, x1, top = 90) {
|
||||
rect(run, x0 - 1, top, x0 - 1, 119, run('STONE'));
|
||||
rect(run, x1 + 1, top, x1 + 1, 119, run('STONE'));
|
||||
}
|
||||
world(160); basin(40, 79);
|
||||
rect(run, 40, 100, 79, 119, run('WATER'));
|
||||
const closed = water(); ticks(run, 36000);
|
||||
check('a stone pond keeps every grain for ten minutes and sleeps', water() === closed && awake() === 0, `${closed} -> ${water()}, ${awake()} awake areas; old: 800 water, 0 awake`);
|
||||
world(240);
|
||||
rect(run, 115, 60, 124, 99, run('WATER')); ticks(run, 3600);
|
||||
const heights = run(`(() => { const a = []; for (let x = 0; x < W; x++) {
|
||||
let y = 120; for (let j = 0; j < 120; j++) if (mat[j*W+x] === WATER) { y = j; break; }
|
||||
a.push(y);
|
||||
} return a; })()`);
|
||||
const uneven = Math.max(...heights) - Math.min(...heights);
|
||||
check('a falling column spreads into a flat sleeping pool', uneven <= 2 && awake() === 0 && water() === 400, `surface difference ${uneven}, ${awake()} awake, ${water()}/400 grains; old: difference 4, 0 awake, 400 water`);
|
||||
// Old 2400-tick counts: 24 (40x20), 900 (100x20), 1440 (200x10), 0 (middle).
|
||||
// Old supplied 7200-tick counts: 254 left for 100x20, 1027 for 200x10.
|
||||
// Old code eventually sleeps with 24, 171 and 645 grains left, even at 100080 ticks.
|
||||
// Deadlines below allow the new flow to reach <= 2% and <= 20 grains.
|
||||
for (const [width, depth, middle] of [[40, 20, false], [100, 20, false], [200, 10, false], [40, 20, true]]) {
|
||||
world();
|
||||
const left = middle ? 140 : 0, right = left + width - 1;
|
||||
rect(run, right + 1, 120, 319, 199, run('EMPTY'));
|
||||
if (middle) rect(run, 0, 120, left - 1, 199, run('EMPTY'));
|
||||
rect(run, left, 120 - depth, right, 119, run('WATER'));
|
||||
const deadline = width === 200 ? 13200 : width === 100 ? 4500 : 900;
|
||||
const before = water(); ticks(run, deadline);
|
||||
const remaining = count(run, run('WATER'), left, 0, right, 119);
|
||||
check(`${width}-wide pool pours off ${middle ? 'both sides' : 'the side ledge'} promptly`, remaining <= Math.min(20, before * 0.02) && water() === before, `${remaining}/${before} left at ${deadline} ticks, total ${water()}; old at same deadline: ${middle ? 0 : width === 40 ? 142 : width === 100 ? 524 : 706}`);
|
||||
}
|
||||
for (const edge of [0, 319]) {
|
||||
world();
|
||||
const left = edge === 0 ? 0 : 310, right = left + 9;
|
||||
rect(run, edge === 0 ? 10 : 309, 90, edge === 0 ? 10 : 309, 119, run('STONE'));
|
||||
rect(run, left, 100, right, 119, run('WATER')); ticks(run, 1800);
|
||||
const local = count(run, run('WATER'), left, 90, right, 119);
|
||||
check(`water rests against the ${edge === 0 ? 'left' : 'right'} world wall without wrapping or leaking`, local === 200 && grains('WATER') === 200 && water() === 200 && awake() === 0, `${local}/200 inside, ${grains('WATER')} worldwide, ${awake()} awake; old: 200 inside/worldwide, 0 awake`);
|
||||
}
|
||||
for (const y of [0, 599]) {
|
||||
world(80, 600);
|
||||
// Isolate movement from geothermal boiling at the bottom boundary.
|
||||
run('AMB.fill(20); temp.fill(20);');
|
||||
rect(run, 30, y, 39, y, run('WATER')); ticks(run, 1800);
|
||||
check(`water dropped on the ${y === 0 ? 'top' : 'bottom'} row settles inside the world`, water() === 10 && count(run, run('WATER'), 0, 599, 79, 599) === 10 && awake() === 0, `${water()}/10 grains, ${awake()} awake; old: 10 water, 0 awake`);
|
||||
}
|
||||
for (const m of ['WATER', 'SAND', 'STEAM']) {
|
||||
world(80, 600); run('AMB.fill(20); temp.fill(20);');
|
||||
for (const x of [0, 79]) for (const y of [0, 599]) put(run, x, y, run(m));
|
||||
ticks(run, 600);
|
||||
check(`${m.toLowerCase()} at all four corners runs without throwing`, m !== 'WATER' || water() === 4, `${grains(m)} grains remain, water total ${water()}; old: ${m === 'STEAM' ? 0 : 4} grains, water total ${m === 'SAND' ? 0 : 4}`);
|
||||
}
|
||||
// t_pipes already checks line transport, conservation, valves and full pipes.
|
||||
function pumpRig(sealed) {
|
||||
world(240); basin(40, 71, 80);
|
||||
rect(run, 40, 90, 71, 119, run('WATER'));
|
||||
run(`placeBlock(BP_PUMP, 1, 17, 29, true);
|
||||
for (let by = 19; by < 29; by++) placeBlock(BP_PIPE, 0, 17, by, true);
|
||||
for (let bx = 18; bx < 30; bx++) placeBlock(BP_PIPE, 0, bx, 19, true);
|
||||
placeBlock(BP_OUTLET, 0, 30, 19, true);`);
|
||||
basin(120, sealed ? 123 : 159, 80);
|
||||
run('updateRoof(); rebuildPipes();');
|
||||
}
|
||||
pumpRig(false);
|
||||
const pumped = water(); ticks(run, 6000);
|
||||
// The last 25 bottom-row grains are beyond the intake (x65..67); no drop draws them closer.
|
||||
const unreachable = count(run, run('WATER'), 40, 80, 71, 119);
|
||||
check('a pump drains a pond down to its intake', unreachable === 25 && count(run, run('WATER'), 40, 119, 64, 119) === 25 && water() === pumped, `${unreachable} unreachable, total ${pumped} -> ${water()}; old: 25 unreachable, 960 total`);
|
||||
pumpRig(true);
|
||||
const filling = water(); ticks(run, 2400);
|
||||
const full = count(run, run('WATER'), 120, 80, 123, 119), held = run('pipeNets[0].n');
|
||||
ticks(run, 600);
|
||||
check('a full closed basin stops its outlet without losing water', full === 160 && count(run, run('WATER'), 120, 80, 123, 119) === full && run('pipeNets[0].n') === held && water() === filling, `${full}/160 in basin, line ${held} -> ${run('pipeNets[0].n')}, total ${filling} -> ${water()}; old: 160 basin, 384 line, 960 total`);
|
||||
world(160); basin(40, 79);
|
||||
run('for (let y = 60; y < 140; y++) AMB[y] = -6;');
|
||||
rect(run, 40, 110, 79, 119, run('WATER'));
|
||||
const frozen = water(); ticks(run, 6000);
|
||||
check('a pond in freezing air ices over without losing water', grains('ICE') >= 40 && water() === frozen, `${grains('ICE')} ice, ${grains('WATER')} water, total ${water()}/${frozen}; old: 400 ice, 0 water, 400 total`);
|
||||
run('AMB.fill(20); for (let y = 60; y < 140; y++) for (let x = 39; x <= 80; x++) wakeHeatXY(x, y);');
|
||||
ticks(run, 6000);
|
||||
check('a thawed pond returns all its water', grains('WATER') === frozen && water() === frozen, `${grains('WATER')}/${frozen} water, ${grains('ICE')} ice; old: 400 water, 0 ice`);
|
||||
world(160); basin(40, 79, 80);
|
||||
rect(run, 39, 79, 80, 79, run('STONE'));
|
||||
run('for (let y = 116; y < 120; y++) for (let x = 40; x < 80; x++) setCell(y*W+x, COPPER, 600, 0);');
|
||||
rect(run, 40, 115, 79, 115, run('WATER')); run('updateRoof();');
|
||||
const boiling = water(); let peakSteam = 0;
|
||||
for (let k = 0; k < 120; k++) { ticks(run, 5); peakSteam = Math.max(peakSteam, grains('STEAM')); }
|
||||
check('water on hot copper boils into steam', peakSteam > 0 && water() === boiling, `peak ${peakSteam} steam, total ${water()}/${boiling}; old: peak 20 steam, 40 total`);
|
||||
ticks(run, 24000);
|
||||
check('steam under a roof returns to water without escaping', grains('WATER') === boiling && water() === boiling && grains('STEAM') === 0, `${grains('WATER')}/${boiling} water, ${grains('STEAM')} steam; old: 37 water, 3 steam`);
|
||||
// a pool pouring into a shaft down to rock above 200°C: the water boils off and goes into the
|
||||
// clouds, which spread it out downwind, so the pool really drains and no water is made or lost
|
||||
flatWorld(run, 640, 400, 120);
|
||||
const hotY = run('(() => { for (let y = 120; y < H; y++) if (AMB[y] > 200) return y; return -1; })()');
|
||||
rect(run, 60, 100, 61, 119, run('STONE')); rect(run, 62, 105, 159, 119, run('WATER'));
|
||||
rect(run, 160, 120, 163, hotY + 20, run('EMPTY'));
|
||||
run(`for (let y = ${hotY}; y <= ${hotY + 21}; y++) for (let x = 159; x <= 164; x++) if (mat[y*W+x] === STONE) temp[y*W+x] = AMB[y]; weather.wind = 0.3; updateRoof();`);
|
||||
const shaftPool = () => count(run, run('WATER'), 40, 0, 220, 119);
|
||||
const shaftWater = water(), shaftPool0 = shaftPool();
|
||||
ticks(run, 20000);
|
||||
check('a pool pouring into a hot shaft drains away and no water is made or lost', water() === shaftWater && shaftPool() < shaftPool0 * 0.4, `pool ${shaftPool0} -> ${shaftPool()}, ${Math.round(run('weather.moist.reduce((a, v) => a + v, 0)'))} in the clouds, total ${shaftWater} -> ${water()}`);
|
||||
console.log(`Water suite time: ${Date.now() - started} ms`);
|
||||
done();
|
||||
Reference in New Issue
Block a user