Lakes fill only to their lower rim and keep clear of spawn and the dock (t96)
The lake lip was Math.min(surfY), the highest ground (y grows downward), so water was poured above the lower rim and, once levelled, ran up to the trade dock on 4 of 12 seeds. The lip is now the lower rim edge minus one, and each lake stays 80 grains from spawn. Seed hashes and the shallow-ore hash re-baselined (changes only under lake basins); golden fixtures also freeze surfY/dirtY/AMB from the previous generator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -599,6 +599,14 @@ above, including:
|
|||||||
existing foundation first so it cannot erase newly levelled water. Keep the pinned settling
|
existing foundation first so it cannot erase newly levelled water. Keep the pinned settling
|
||||||
values, startup water wakes, and round-1 wet/water-connected-bank tree filtering. Existing
|
values, startup water wakes, and round-1 wet/water-connected-bank tree filtering. Existing
|
||||||
load wakes and later snowmelt/flooding rules stay as they are.
|
load wakes and later snowmelt/flooding rules stay as they are.
|
||||||
|
- **Lakes fill only to their lower rim and keep clear of spawn (t96 round 3, 2026-10-29).**
|
||||||
|
The generator took the basin's `lip` as `Math.min(surfY)`, the highest ground (y grows
|
||||||
|
downward), so water was poured above the lower rim and ran out toward low ground; once
|
||||||
|
levelled it reached the trade dock on 4 of 12 seeds. The owner chose option 2: the lip is
|
||||||
|
now the lower of the two rim edges, minus one, so a lake is filled to just under that rim,
|
||||||
|
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.
|
||||||
|
|
||||||
## Ideas not built yet
|
## Ideas not built yet
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -124,7 +124,8 @@ screenshots go to `shots/t72-{retrace,escape,open}.png`.
|
|||||||
joins 2, sky 12/2, lamp 12, magma 12): the old noise had seams in the references too.
|
joins 2, sky 12/2, lamp 12, magma 12): the old noise had seams in the references too.
|
||||||
The remaining crops are frozen pre-removal legacy images. The compressed
|
The remaining crops are frozen pre-removal legacy images. The compressed
|
||||||
`golden-world-640x*.json.gz` fixtures retain the Phase 2 world bytes that produced them,
|
`golden-world-640x*.json.gz` fixtures retain the Phase 2 world bytes that produced them,
|
||||||
isolating renderer checks from worldgen RNG changes. Inspect the matching
|
isolating renderer checks from worldgen RNG changes; t96 round 3 added the old `surfY`, `dirtY` and `AMB`
|
||||||
|
columns too, because the renderer reads them and lake generation changed them. Inspect the matching
|
||||||
`shots/golden-*.png` after visible changes.
|
`shots/golden-*.png` after visible changes.
|
||||||
- `test/t_gpu.py` checks default limits (four storage textures per stage), dirty-upload
|
- `test/t_gpu.py` checks default limits (four storage textures per stage), dirty-upload
|
||||||
readback after 240 busy ticks with scrolling and both looks, exact dirty/full image equality,
|
readback after 240 busy ticks with scrolling and both looks, exact dirty/full image equality,
|
||||||
|
|||||||
@@ -4,6 +4,18 @@ What agents have done, newest first. Add an entry for every piece of work: the d
|
|||||||
note or request it was for, what changed, the files touched, the tests run, and anything left
|
note or request it was for, what changed, the files touched, the tests run, and anything left
|
||||||
open or not verified. Keep each entry short; the git history has the details.
|
open or not verified. Keep each entry short; the git history has the details.
|
||||||
|
|
||||||
|
## 2026-10-29: t96 round 3, lakes fill to their lower rim (Claude)
|
||||||
|
|
||||||
|
- Owner chose option 2. src/sim/worldgen.js: lake `lip` is the lower rim edge minus one (was
|
||||||
|
the highest ground in the basin), and lake half-width is capped to keep 80 grains from spawn
|
||||||
|
and the dock. Same random draws. Lakes are smaller and stay off the dock (seeds 5/6/7/42 used to
|
||||||
|
reach it).
|
||||||
|
- Tests: t_lakes.js adds seeds 5 and 6 and a dock-clearance check (fails on the old generator for
|
||||||
|
7/42/5/6); hashes re-baselined in t_lakes.js, t_corner_seal.js and t_world.js (shallow ore:
|
||||||
|
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).
|
||||||
|
|
||||||
## 2026-10-29: t96 round 2 — level lakes during generation (Codex)
|
## 2026-10-29: t96 round 2 — level lakes during generation (Codex)
|
||||||
|
|
||||||
- Level 50+ grain water bodies with typed-array flood fills, exact volume and centered partial
|
- Level 50+ grain water bodies with typed-array flood fills, exact volume and centered partial
|
||||||
|
|||||||
+4
-3
@@ -67,12 +67,13 @@ function* worldSteps(seedIn, w, h) {
|
|||||||
else dirtY[x] = Math.min(dirtY[x], surfY[x] + 20);
|
else dirtY[x] = Math.min(dirtY[x], surfY[x] + 20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// lakes: a basin dug below the lowest point of its rim, filled to just above it
|
// lakes: a basin dug below the lower of its two rims, filled to just under that rim (y grows
|
||||||
|
// downward, so the lower rim is the larger surfY); kept 80 grains clear of spawn and the dock
|
||||||
const lakeTop = new Int16Array(w).fill(h), noTree = new Uint8Array(w), desAt = new Uint8Array(w);
|
const lakeTop = new Int16Array(w).fill(h), noTree = new Uint8Array(w), desAt = new Uint8Array(w);
|
||||||
for (const f of feats) {
|
for (const f of feats) {
|
||||||
if (f.k !== 'lake') continue;
|
if (f.k !== 'lake') continue;
|
||||||
const lw = Math.round(90 + rng() * 40), deep = 22 + rng() * 8;
|
const lw = Math.min(Math.round(90 + rng() * 40), Math.abs(f.x - spawnX) - 80), deep = 22 + rng() * 8;
|
||||||
let lip = 1e9; for (let x = f.x - lw; x <= f.x + lw; x++) if (x >= 0 && x < w) lip = Math.min(lip, surfY[x]);
|
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++) {
|
for (let x = f.x - lw; x <= f.x + lw; x++) {
|
||||||
if (x < 0 || x >= w) continue;
|
if (x < 0 || x >= w) continue;
|
||||||
const u = (x - f.x) / lw, depth = Math.max(0, 1 - u * u) * deep;
|
const u = (x - f.x) / lw, depth = Math.max(0, 1 - u * u) * deep;
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -4,11 +4,11 @@ const { createHash } = require('node:crypto');
|
|||||||
const { run } = load();
|
const { run } = load();
|
||||||
const { check, done } = suite();
|
const { check, done } = suite();
|
||||||
|
|
||||||
// t96 re-baseline: only the generation lake-levelling pass changes these material bytes.
|
// t96 re-baseline: only lake generation (rim height, spawn clearance, levelling) changes these material bytes.
|
||||||
for (const [seed, expected] of [
|
for (const [seed, expected] of [
|
||||||
[777, '1fac6b4b6420f328d7de87c0813e328303271e7605d85ce541e108361aff0d90'],
|
[777, '7d60a0f1a92b42438471c8c15c5cc92576a75a4aa54b8c519c42904acb7088a6'],
|
||||||
[77, 'de3ec38547585486924f53abb82cb3c0f76e0bd24b75873ddf96599b5886b52a'],
|
[77, '724eb1730ca5fead6aeaa13994beb2e066bb9d65c7db7e0b790857e2d72b7c2a'],
|
||||||
[4242, '57cd89b5518c3e342fc1b91243cd068dc2e38a2d42de84a1c2ee1cbf936c2f1a'],
|
[4242, '9ffdabf1ffe8ab6cd5855ccfe05c31221d1a0517d960bf2e2273018e9dbf03f9'],
|
||||||
]) {
|
]) {
|
||||||
run(`generateWorld(${seed}, 512, 384)`);
|
run(`generateWorld(${seed}, 512, 384)`);
|
||||||
check(`seed ${seed} restores corner sealing`,run('CORNER_SEAL')===true);
|
check(`seed ${seed} restores corner sealing`,run('CORNER_SEAL')===true);
|
||||||
|
|||||||
+8
-3
@@ -17,11 +17,14 @@ function surfacePools() {
|
|||||||
})()`);
|
})()`);
|
||||||
}
|
}
|
||||||
for (const [seed, expected] of [
|
for (const [seed, expected] of [
|
||||||
[7, '669a4ee87496be27473d2987d9a8935be1a19b29a8f2433d306f32a1616f3bca'],
|
[7, '4c4778417e6cf6e9577b831e66111bd374068a14e15642bae304b8a45a68b879'],
|
||||||
[42, 'efeadd2bff0ce3eaf81e5a08a998ce767e9667e3b27b8623e928f202669f640e'],
|
[42, '907f2ed79b9fda1ae99c5ee55fa19ce85eff8ffdcd7dadac14a2deb42223217d'],
|
||||||
[424242, '24b5e6ccc4e7d5eb9d54c3ff0fa5dfe84185b83e75b07261ac007b4f920f0640'],
|
[424242, '40080ea6039d0d4247e36fd9a5837d0675100414f198d77419ecf500c7f9d6ad'],
|
||||||
[2, null],
|
[2, null],
|
||||||
[1, null],
|
[1, null],
|
||||||
|
// Before t96 round 3 these lakes ran over their lower rim up to the trade dock.
|
||||||
|
[5, null],
|
||||||
|
[6, null],
|
||||||
]) {
|
]) {
|
||||||
run(`generateWorld(${seed}, 2048, 1024, (f, label) => {
|
run(`generateWorld(${seed}, 2048, 1024, (f, label) => {
|
||||||
if (f === 0.99 && label === 'Finishing') globalThis.waterBeforeLevel = mat.reduce((n, m) => n + (m === WATER), 0);
|
if (f === 0.99 && label === 'Finishing') globalThis.waterBeforeLevel = mat.reduce((n, m) => n + (m === WATER), 0);
|
||||||
@@ -33,6 +36,8 @@ for (const [seed, expected] of [
|
|||||||
check(`seed ${seed} conserves water through levelling`, run('mat.reduce((n, m) => n + (m === WATER), 0) === waterBeforeLevel'));
|
check(`seed ${seed} conserves water through levelling`, run('mat.reduce((n, m) => n + (m === WATER), 0) === waterBeforeLevel'));
|
||||||
const initial = surfacePools();
|
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));
|
check(`seed ${seed} starts with flat surface pools`, initial.length > 0 && initial.every(p => p.max - p.min <= 1), 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'));
|
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)`));
|
check(`seed ${seed} starts every water area awake`, run(`mat.every((m, i) => m !== WATER || awake[((i / W) >> 5) * CW + ((i % W) >> 5)] > 0)`));
|
||||||
check(`seed ${seed} has no submerged tree base`, run('treeBases.every(i => !treeHeld(i) || mat[i] !== WATER)'));
|
check(`seed ${seed} has no submerged tree base`, run('treeBases.every(i => !treeHeld(i) || mat[i] !== WATER)'));
|
||||||
|
|||||||
+1
-1
@@ -47,7 +47,7 @@ with launch(viewport={'width':1280,'height':760}) as (_, pg, url):
|
|||||||
pools=pg.evaluate(measure)
|
pools=pg.evaluate(measure)
|
||||||
assert pools and all(p['max']-p['min']<=1 for p in pools),pools
|
assert pools and all(p['max']-p['min']<=1 for p in pools),pools
|
||||||
print(state,result,pools,flush=True)
|
print(state,result,pools,flush=True)
|
||||||
assert result['n']>300 and result['hi']-result['lo']<=1 and result['wet']==0
|
assert result['n']>150 and result['hi']-result['lo']<=1 and result['wet']==0
|
||||||
pg.evaluate('snapCamera();renderDirty.fill(1);')
|
pg.evaluate('snapCamera();renderDirty.fill(1);')
|
||||||
pg.wait_for_timeout(300)
|
pg.wait_for_timeout(300)
|
||||||
pg.screenshot(path=str(ROOT/f'shots/t96-{state}-settled.png'))
|
pg.screenshot(path=str(ROOT/f'shots/t96-{state}-settled.png'))
|
||||||
|
|||||||
+2
-1
@@ -185,7 +185,8 @@ oreNames.forEach((name, j) => {
|
|||||||
});
|
});
|
||||||
const totalOre = oreBalance.counts.reduce((a, b) => a + b, 0);
|
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);
|
check('total ore is about 4 to 4.5 times the original', totalOre >= 162519 * 4 && totalOre <= 162519 * 4.5, totalOre);
|
||||||
check('shallow ore matches the original generator', oreBalance.shallowHash === 706875613, oreBalance.shallowHash);
|
// 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);
|
||||||
run('generateWorld(99, 4096, 1536, null)');
|
run('generateWorld(99, 4096, 1536, null)');
|
||||||
const marks = run('(()=>{let lakes=0,peaks=0,inLake=false;for(let x=1;x<W-1;x++){const l=mat[(surfY[x]-1)*W+x]===WATER;if(l&&!inLake)lakes++;inLake=l;if(surfY[x]<surfBase-100&&surfY[x]<=surfY[x-1]&&surfY[x]<surfY[x+1])peaks++;}return [lakes,peaks]})()');
|
const marks = run('(()=>{let lakes=0,peaks=0,inLake=false;for(let x=1;x<W-1;x++){const l=mat[(surfY[x]-1)*W+x]===WATER;if(l&&!inLake)lakes++;inLake=l;if(surfY[x]<surfBase-100&&surfY[x]<=surfY[x-1]&&surfY[x]<surfY[x+1])peaks++;}return [lakes,peaks]})()');
|
||||||
check('a large world has several lakes and mountains across it', marks[0] >= 2 && marks[1] >= 2, marks);
|
check('a large world has several lakes and mountains across it', marks[0] >= 2 && marks[1] >= 2, marks);
|
||||||
|
|||||||
Reference in New Issue
Block a user