// Generated lakes sit in a smooth bowl filled to its spill level: flat, contained, clay-bedded, // stable, with natural banks, dry tree bases and the dock clear. const { load, suite, ticks } = require('./harness'); const { createHash } = require('node:crypto'); const { run } = load(); const { check, done } = suite(); // Surface pools with their depth, water count, and the ground top of the dry column on each side. function surfacePools() { return run(`(() => { const top = x => { let y = 0; while (y < H && (mat[y * W + x] === EMPTY || mat[y * W + x] === WATER)) y++; return y; }; const pools = []; let pool = null; for (let x = 0; x < W; x++) { let y = 0; while (y < H && mat[y * W + x] === EMPTY) y++; if (mat[y * W + x] !== WATER) { pool = null; continue; } let b = y; while (b < H && mat[b * W + x] === WATER) b++; if (!pool) { pool = { x0: x, x1: x, width: 0, min: y, max: y, deep: 0, water: 0, clay: true }; pools.push(pool); } pool.x1 = x; pool.width++; pool.min = Math.min(pool.min, y); pool.max = Math.max(pool.max, y); pool.deep = Math.max(pool.deep, b - y); pool.water += b - y; pool.clay = pool.clay && mat[b * W + x] === CLAY; } return pools.filter(p => p.width > 20).map(p => Object.assign(p, { left: top(p.x0 - 1), right: top(p.x1 + 1) })); })()`); } // Largest ground step within 40 columns of each pool, away from the dock's foundation. function bankStep(pools) { return run(`(() => { const d = machines.find(m => m && m.type === 'dock'); const top = x => { let y = 0; while (y < H && (mat[y * W + x] === EMPTY || mat[y * W + x] === WATER)) y++; return y; }; let worst = 0; for (const p of ${JSON.stringify(pools)}) for (let x = Math.max(1, p.x0 - 40); x < Math.min(W - 1, p.x1 + 40); x++) if (x < d.x - 2 || x > d.x + 26) worst = Math.max(worst, Math.abs(top(x + 1) - top(x))); return worst; })()`); } function lakeChecks(seed, pools) { check(`seed ${seed} has a real lake`, pools.some(p => p.width >= 120) && pools.every(p => p.deep >= 12), JSON.stringify(pools)); check(`seed ${seed} lakes are exactly flat`, pools.every(p => p.max === p.min), JSON.stringify(pools)); // Contained: the dry column on each side reaches at least the water's surface, so nothing can spill. check(`seed ${seed} lakes are held by their banks`, pools.every(p => p.left <= p.min && p.right <= p.min), JSON.stringify(pools)); check(`seed ${seed} lake beds are clay`, pools.every(p => p.clay)); // Banks follow the land: round 3 left a 15-grain cliff, round 4 a ruler-straight ramp. check(`seed ${seed} lake banks have no cliff`, bankStep(pools) <= 3, bankStep(pools)); check(`seed ${seed} keeps its lakes clear of the trade dock`, run(`(() => { const d = machines.find(m => m && m.type === 'dock'); return ${JSON.stringify(pools)}.every(p => p.x1 < d.x - 8 || p.x0 > d.x + 24 + 8); })()`), JSON.stringify(pools)); check(`seed ${seed} has no submerged tree base`, run('treeBases.every(i => !treeHeld(i) || mat[i] !== WATER)')); check(`seed ${seed} retains dry trees`, run('treeBases.filter(i => treeHeld(i)).length > 20')); } // Spill levels on a hand-made surface: each column fills to the lower of its two highest sides. run('alloc(16, 64); surfY.set([20, 30, 34, 32, 26, 30, 36, 33, 24, 40, 40, 22, 25, 25, 25, 25]); globalThis.levels = new Int16Array(16);'); // The highest ground right of x=1 is 22 (x=11), lower than the 20 on the left, so x=1..11 fill // to 22; past x=11 nothing on the right rises above 25. The deepest water is 18 (x=9, 10). check('spill levels fill each basin to its lower rim', run('spillLevels(0, 15, levels) === 18 && [20,22,22,22,22,22,22,22,22,22,22,22,25,25,25,25].every((v, x) => levels[x] === v)'), run('Array.from(levels)')); for (const [seed, expected] of [ [7, '3e97601ea08027e7f21fa3d52ec300fb7e13c630a1ddad0a7be6ca7ab4b07592'], [42, 'ecf71e4309c0b19654fbc17dbb0b5717023842d331f4f12c010d3312e9f9383f'], [424242, '235a33a7c85118d05cc9f0508acaa266b61d5f9f100703c0a095d760fe80066d'], [2, null], [1, null], // These lakes once ran over their lower rim up to the trade dock. [5, null], [6, null], ]) { run(`generateWorld(${seed}, 2048, 1024); weather.next = 1e15;`); const hash = createHash('sha256'); for (const name of ['mat', 'temp']) hash.update(Buffer.from(run(name).buffer)); const actual = hash.digest('hex'); if (expected) check(`seed ${seed} matches its generation hash`, actual === expected, actual); const pools = surfacePools(); lakeChecks(seed, pools); check(`seed ${seed} restores pinned generation switches`, run('LSTEP === 8 && LFILM === 4 && SORTP === 0.06 && CORNER_SEAL && POWDER_FAN')); check(`seed ${seed} starts every water area awake`, run(`mat.every((m, i) => m !== WATER || awake[((i / W) >> 5) * CW + ((i % W) >> 5)] > 0)`)); // Stable: no leak into a cave and no slump; the surface and the water in each lake stay put. ticks(run, 3000); const later = surfacePools(); check(`seed ${seed} lakes keep their surface and water for 3000 ticks`, later.length === pools.length && later.every((p, k) => p.x0 === pools[k].x0 && p.x1 === pools[k].x1 && p.min === pools[k].min && p.max === pools[k].max && p.water === pools[k].water), JSON.stringify({ pools, later })); } // One of the owner's Huge seeds, whose round-3/4 banks showed the cliff and the ramp. run('generateWorld(766733, 6144, 2048); weather.next = 1e15;'); lakeChecks(766733, surfacePools()); done();