From e3e01a84b1428a3af5d2281bf051aea5fb219863 Mon Sep 17 00:00:00 2001 From: 44r0n7 <44r0n7+gitea@pm.me> Date: Thu, 29 Oct 2026 20:00:48 -0400 Subject: [PATCH] Lake banks slope down to the water instead of ending in a cliff (t96) Digging basins to the lower rim left a 9-18 grain vertical cliff on each lake's high side (owner screenshots, seed 766733). From each edge inward the ground now falls at most 0.7 grains a column and is never raised above the natural surface. New no-cliff check in t_lakes.js; world hashes re-baselined (shallow ore: 2 cells changed, on a lake bank). Co-Authored-By: Claude Opus 5.5 --- docs/DESIGN.md | 4 ++++ docs/agent-log.md | 6 ++++++ src/sim/worldgen.js | 17 ++++++++++++++--- test/t_corner_seal.js | 6 +++--- test/t_lakes.js | 11 ++++++++--- test/t_world.js | 4 ++-- 6 files changed, 37 insertions(+), 11 deletions(-) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index d3322c2..93baeed 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -607,6 +607,10 @@ above, including: and each lake's half-width is capped so its edge stays 80 grains from spawn (and the dock). Lakes are smaller and lower: about 180-250 grains wide instead of 250-330, 5-15 grains lower. The random draws keep their order. + Dug to the lower rim, the high side ended in a vertical cliff as tall as the gap between the + rims (15-18 grains on seeds 766733, 7, 42; owner screenshots). From each edge inward the + ground may now fall at most 0.7 grains a column and is never raised above the natural + surface, so the high side is a sloped bank down to the water. ## Ideas not built yet diff --git a/docs/agent-log.md b/docs/agent-log.md index 00c729c..33977fd 100644 --- a/docs/agent-log.md +++ b/docs/agent-log.md @@ -15,6 +15,12 @@ open or not verified. Keep each entry short; the git history has the details. 414 of 28,291 cells changed, all under the two new lake basins). t_lakes.py width threshold 300 -> 150. Golden fixtures now also freeze surfY/dirtY/AMB from the previous generator. - Full `run_all.py --browser`: see the commit; t_smooth.py is the known flake (t99). +- Follow-up (owner screenshots, seeds 766733/12446/23565): the lower-rim lip left a 9-18 grain + cliff on each lake's high side. Banks now slope (at most 0.7 grains a column inward from each + edge, never above natural ground); max step beside lakes is 1 on all checked seeds. New + t_lakes.js check "lake banks slope with no cliff" fails on the cliff version for 6 of 7 seeds. + Remaining 17-18 grain steps are at the trade dock's own foundation edge (seed 12446 had it + before this change too); not changed. ## 2026-10-29: t96 round 2 — level lakes during generation (Codex) diff --git a/src/sim/worldgen.js b/src/sim/worldgen.js index 4acc0f6..d291099 100644 --- a/src/sim/worldgen.js +++ b/src/sim/worldgen.js @@ -74,12 +74,23 @@ function* worldSteps(seedIn, w, h) { if (f.k !== 'lake') continue; const lw = Math.min(Math.round(90 + rng() * 40), Math.abs(f.x - spawnX) - 80), deep = 22 + rng() * 8; const lip = Math.max(surfY[Math.max(0, f.x - lw)], surfY[Math.min(w - 1, f.x + lw)]) - 1; - for (let x = f.x - lw; x <= f.x + lw; x++) { - if (x < 0 || x >= w) continue; + const x0 = Math.max(0, f.x - lw), x1 = Math.min(w - 1, f.x + lw), nat = surfY.slice(x0, x1 + 1); + for (let x = x0; x <= x1; x++) { const u = (x - f.x) / lw, depth = Math.max(0, 1 - u * u) * deep; - if (depth > 0) { surfY[x] = Math.max(surfY[x], Math.round(lip + depth)); dirtY[x] = Math.max(dirtY[x], surfY[x] + 8); } + if (depth > 0) surfY[x] = Math.max(surfY[x], Math.round(lip + depth)); lakeTop[x] = lip + 2; } + // Dug to the lower rim, the high side would end in a cliff as tall as the gap between the + // rims: from each edge inward the ground may fall at most 0.7 grains a column, never above nature. + for (const d of [1, -1]) { + let prev = surfY[d > 0 ? Math.max(0, x0 - 1) : Math.min(w - 1, x1 + 1)]; + for (let x = d > 0 ? x0 : x1; x >= x0 && x <= x1; x += d) { + prev = Math.min(prev + 0.7, surfY[x]); + surfY[x] = Math.max(nat[x - x0], Math.round(prev)); + prev = Math.max(prev, surfY[x]); + } + } + for (let x = x0; x <= x1; x++) dirtY[x] = Math.max(dirtY[x], surfY[x] + 8); for (let x = Math.max(0, f.x - lw - 6); x <= Math.min(w - 1, f.x + lw + 6); x++) noTree[x] = 1; } for (const f of feats) { diff --git a/test/t_corner_seal.js b/test/t_corner_seal.js index 2c7f552..f41147b 100644 --- a/test/t_corner_seal.js +++ b/test/t_corner_seal.js @@ -6,9 +6,9 @@ const { check, done } = suite(); // t96 re-baseline: only lake generation (rim height, spawn clearance, levelling) changes these material bytes. for (const [seed, expected] of [ - [777, '7d60a0f1a92b42438471c8c15c5cc92576a75a4aa54b8c519c42904acb7088a6'], - [77, '724eb1730ca5fead6aeaa13994beb2e066bb9d65c7db7e0b790857e2d72b7c2a'], - [4242, '9ffdabf1ffe8ab6cd5855ccfe05c31221d1a0517d960bf2e2273018e9dbf03f9'], + [777, 'dc0a19b076f341bcd944e1692651446198f57c7353db5fdea36d6bdd886ab6ff'], + [77, 'aa979290d81a758e6ff68fa22b06513fb546aa73a80d09e69b35899a46758a0b'], + [4242, '25737574612fe0395a2b0304a1ac698f817aa68bcd9e2b13a24ba794201ba6dc'], ]) { run(`generateWorld(${seed}, 512, 384)`); check(`seed ${seed} restores corner sealing`,run('CORNER_SEAL')===true); diff --git a/test/t_lakes.js b/test/t_lakes.js index fd02f47..2ac8c66 100644 --- a/test/t_lakes.js +++ b/test/t_lakes.js @@ -17,9 +17,9 @@ function surfacePools() { })()`); } for (const [seed, expected] of [ - [7, '4c4778417e6cf6e9577b831e66111bd374068a14e15642bae304b8a45a68b879'], - [42, '907f2ed79b9fda1ae99c5ee55fa19ce85eff8ffdcd7dadac14a2deb42223217d'], - [424242, '40080ea6039d0d4247e36fd9a5837d0675100414f198d77419ecf500c7f9d6ad'], + [7, '5e230523dc4dda212e7c67ceb7f3b496ff201d826366e1d7c379efd1351d1956'], + [42, 'cc3d150a91caac6ab88dfefbd45d85c5bc5c38fc77d7913b9de157647f4b9d7b'], + [424242, '1339c90f4f7f1d245c2e21aa4b8d23b21c199b8251392256f6de5ed704e2b396'], [2, null], [1, null], // Before t96 round 3 these lakes ran over their lower rim up to the trade dock. @@ -36,6 +36,11 @@ for (const [seed, expected] of [ check(`seed ${seed} conserves water through levelling`, run('mat.reduce((n, m) => n + (m === WATER), 0) === waterBeforeLevel')); const initial = surfacePools(); check(`seed ${seed} starts with flat surface pools`, initial.length > 0 && initial.every(p => p.max - p.min <= 1), JSON.stringify(initial)); + // Lakes dug to their lower rim once left a cliff on the high side (seed 766733's 15-grain step). + check(`seed ${seed} lake banks slope with no cliff`, 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; }; + return ${JSON.stringify(initial)}.every(p => { 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) { if (Math.abs(top(x + 1) - top(x)) > 3) return false; } return true; }); })()`), JSON.stringify(initial)); check(`seed ${seed} keeps its lakes clear of the trade dock`, run(`(() => { const d = machines.find(m => m && m.type === 'dock'); return ${JSON.stringify(initial)}.every(p => p.x1 < d.x - 8 || p.x0 > d.x + 24 + 8); })()`), JSON.stringify(initial)); check(`seed ${seed} restores pinned generation switches`, run('LSTEP === 8 && LFILM === 4 && SORTP === 0.06 && CORNER_SEAL && POWDER_FAN')); diff --git a/test/t_world.js b/test/t_world.js index a0f82c8..76873b0 100644 --- a/test/t_world.js +++ b/test/t_world.js @@ -185,8 +185,8 @@ oreNames.forEach((name, j) => { }); const totalOre = oreBalance.counts.reduce((a, b) => a + b, 0); check('total ore is about 4 to 4.5 times the original', totalOre >= 162519 * 4 && totalOre <= 162519 * 4.5, totalOre); -// t96 round 3: lake basins moved, so ore under them changed (414 cells, all under the two lakes). -check('shallow ore matches the original generator', oreBalance.shallowHash === 607138519, oreBalance.shallowHash); +// t96: lake basins moved and their high banks now slope, so ore there changed (only at the two lakes). +check('shallow ore matches the original generator', oreBalance.shallowHash === 1640715415, oreBalance.shallowHash); run('generateWorld(99, 4096, 1536, null)'); const marks = run('(()=>{let lakes=0,peaks=0,inLake=false;for(let x=1;x= 2 && marks[1] >= 2, marks);