New-world lakes start level; no trees in water (t96)
This commit is contained in:
@@ -590,6 +590,13 @@ above, including:
|
||||
ordinary repose rules take over. Keep stamped moves, barriers, burning-bed protection and
|
||||
worker independence. The existing worldgen POWDER_FAN switch disables new face releases.
|
||||
|
||||
- **New-world lakes wake immediately; keep the seed layout (t96, 2026-10-29).** Keep the
|
||||
recorded worldgen settling speeds and material/temperature output. Wake water after the
|
||||
final startup reset so unfinished lakes level without drilling. Retain tree candidates and
|
||||
their random draws, then omit whole trees whose bases are wet or on an open bank connected
|
||||
to water at base height after settling. This keeps trees out of the lake as it spreads.
|
||||
Existing load wake scheduling and later snowmelt/flooding rules stay as they are.
|
||||
|
||||
## Ideas not built yet
|
||||
|
||||
- **Concrete**, as a stronger wall material from rubble.
|
||||
|
||||
+2
-1
@@ -383,7 +383,7 @@ World generation helpers: noise and removal of tiny hard rock specks.
|
||||
- functions: makeNoise, fbm, clearHardSpecks
|
||||
- variables: SPECK_MIN
|
||||
|
||||
### src/sim/worldgen.js (286 lines)
|
||||
### src/sim/worldgen.js (303 lines)
|
||||
World generation: staged terrain, ore, caves and placement, with synchronous and async entry points.
|
||||
- functions: generateWorld, generateWorldAsync
|
||||
|
||||
@@ -656,6 +656,7 @@ The frame loop and boot.
|
||||
- `test/t_gpu_golden.py`: Frozen world references; seven smooth crops corrected for continuous noise in t91.
|
||||
- `test/t_gpu_light.py`: The former CPU light behavioural checks, now read from the actual GPU light grid.
|
||||
- `test/t_joins.js`: The oracle states the geometric rule independently of the precomputed neighbour tables.
|
||||
- `test/t_lakes.js`: Generated lakes wake without a tool, settle on their own, and have dry tree bases.
|
||||
- `test/t_load.js`: Reload wakes nearby areas first, without losing distant water, fire or saved state.
|
||||
- `test/t_molds.js`: Molds and furnaces stay usable: ingots drop below and keep their stubs, plugs over molds are cleared, spoiled castings can be knocked out, grains never drop through a grate by teleporting, and slag lets the fire breathe.
|
||||
- `test/t_nozzle.js`: Blowing aims within the nozzle's reach, with room for the brush and settling.
|
||||
|
||||
@@ -69,6 +69,11 @@ seeded sand pile bounds in `t_corner_seal.js`; the heat oracle now supports pass
|
||||
The conveyor fixture uses a wider feed pile, the screen fixture warms longer, and `t_load.js`
|
||||
checks waking within eight ticks separately from the probabilistic consumption of fuel.
|
||||
|
||||
`test/t_lakes.js` pins SHA-256 over raw `mat` followed by Float32 `temp` bytes for seeds
|
||||
7, 42 and 424242 at 2048x1024. These hashes predate t96 and must not be re-baselined for a
|
||||
wake/background-tree change. It also checks generated tree bases, startup wakes, and the
|
||||
seed-7 lake leveling within two grains after 6,000 undisturbed ticks, fresh and reloaded.
|
||||
|
||||
## Browser tests
|
||||
|
||||
- They need the `.venv` from README.md → Develop; plain `python3` switches to it on its own.
|
||||
|
||||
@@ -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
|
||||
open or not verified. Keep each entry short; the git history has the details.
|
||||
|
||||
## 2026-10-29: t96 wake new-world lakes and keep trees off their banks (Codex)
|
||||
|
||||
- Wake water after startup reset; omit whole tree candidates on wet/water-connected low banks.
|
||||
Original mat+temp hashes match for seeds 7/42/424242; pinned settling, physics and save format unchanged.
|
||||
- Files: src/sim/worldgen.js, test/t_lakes.js, docs/{DESIGN,TESTING,PROJECT_MAP}.md, tasks/t96* and this log.
|
||||
- Tests failed first; all 38 headless suites pass (23 lake checks; /tmp/t96-headless.log).
|
||||
Only browser test: /tmp/playwright-test-t96.py (fresh/reloaded seed 7, pixel, two helpers, no page errors;
|
||||
/tmp/t96-browser-final.log). Inspected shots/t96-{fresh,fresh-settled,reloaded-settled}.png.
|
||||
Sandbox graphics startup stalled; outside-sandbox run passed. No full browser suite.
|
||||
- Lake moves on tick 1; levels within two grains by 6,000 ticks (100 seconds). Later snowmelt
|
||||
can flood other trees; that behavior is unchanged. Owner playtest pending. No commit.
|
||||
|
||||
## 2026-10-29: t95 separate nozzle and rain flight allowances (Codex)
|
||||
|
||||
- Nozzle counts bag flyers only, up to 4,000, with “Too much in the air.” feedback;
|
||||
|
||||
+19
-2
@@ -190,12 +190,14 @@ function* worldSteps(seedIn, w, h) {
|
||||
}
|
||||
yield [0.72, 'Snow and trees'];
|
||||
// trees
|
||||
const trees = [];
|
||||
for (let x = 12; x < w - 12;) {
|
||||
const sy = surfY[x];
|
||||
const ok = mat[sy * w + x] === GRASS && mat[sy * w + x + 1] === GRASS && !noTree[x] && Math.abs(x - spawnX) > 6 && !(x > spawnX + 14 && x < spawnX + 72); // none where the dock goes
|
||||
if (ok && rng() < 0.7) {
|
||||
const tree = { x, y: sy - 1, cells: [] }; trees.push(tree);
|
||||
const th = 9 + Math.floor(rng() * 8);
|
||||
const setW = (xx, yy) => { if (xx < 0 || yy < 1 || xx >= w) return; const i = yy * w + xx; if (mat[i] === EMPTY) bgm[i] = WOOD; };
|
||||
const setW = (xx, yy) => { if (xx < 0 || yy < 1 || xx >= w) return; const i = yy * w + xx; if (mat[i] === EMPTY) { bgm[i] = WOOD; tree.cells.push(i, WOOD); } };
|
||||
for (let k = 1; k <= th; k++) for (let dx = 0; dx < 2; dx++) setW(x + dx, sy - k);
|
||||
// two branches
|
||||
for (const side of [-1, 1]) { const by = sy - th + 3 + Math.floor(rng() * 3); for (let k = 1; k <= 3; k++) setW(x + (side < 0 ? -k : 1 + k), by - (k >> 1)); }
|
||||
@@ -206,7 +208,7 @@ function* worldSteps(seedIn, w, h) {
|
||||
for (let yy = Math.floor(cy - r); yy <= cy + r; yy++) for (let xx = Math.floor(cx - r); xx <= cx + r; xx++) {
|
||||
if (xx < 0 || yy < 1 || xx >= w) continue;
|
||||
const ex = (xx - cx) / r, ey = (yy - cy) / (r * 0.85);
|
||||
if (ex * ex + ey * ey <= 1 && hash2(xx, yy, worldSeed + 7) % 9 !== 0) { const i = yy * w + xx; if (mat[i] === EMPTY && !bgm[i]) bgm[i] = LEAVES; }
|
||||
if (ex * ex + ey * ey <= 1 && hash2(xx, yy, worldSeed + 7) % 9 !== 0) { const i = yy * w + xx; if (mat[i] === EMPTY && !bgm[i]) { bgm[i] = LEAVES; tree.cells.push(i, LEAVES); } }
|
||||
}
|
||||
}
|
||||
x += 16 + Math.floor(rng() * 20);
|
||||
@@ -258,9 +260,24 @@ function* worldSteps(seedIn, w, h) {
|
||||
// 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; }
|
||||
tick = 0;
|
||||
// Settling can carry lakes past noTree; leave their connected low banks treeless too.
|
||||
bgm.fill(0);
|
||||
for (const tree of trees) {
|
||||
let wet = false;
|
||||
for (let root = tree.x; root <= tree.x + 1 && !wet; root++) for (const d of [-1, 1]) {
|
||||
for (let x = root; x >= 0 && x < w; x += d) {
|
||||
const m = mat[tree.y * w + x];
|
||||
if (m === WATER) { wet = true; break; }
|
||||
if (m !== EMPTY && PH[m] !== PH_GAS) break;
|
||||
}
|
||||
}
|
||||
if (!wet) for (let k = 0; k < tree.cells.length; k += 2) bgm[tree.cells[k]] = tree.cells[k + 1];
|
||||
}
|
||||
findTreeBases();
|
||||
placeDock(spawnX + 34);
|
||||
hot.fill(0); awake.fill(0); updateRoof();
|
||||
// Keep the generated grains intact, but let unfinished lakes level from the first tick.
|
||||
for (let i = 0; i < N; i++) if (mat[i] === WATER) wakeIdx(i);
|
||||
resetEconomy(); resetWeather();
|
||||
weather.base = worldWater();
|
||||
bot.x = spawnX - 2; bot.y = surfY[spawnX] - 14; bot.vx = 0; bot.vy = 0;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Generated lakes wake without a tool, settle on their own, and have dry tree bases.
|
||||
const { load, suite, ticks } = require('./harness');
|
||||
const { createHash } = require('node:crypto');
|
||||
const { run } = load();
|
||||
const { check, done } = suite();
|
||||
const water = run('WATER');
|
||||
function surfacePools() {
|
||||
return run(`(() => {
|
||||
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; }
|
||||
if (!pool) { pool = { x0: x, x1: x, width: 0, min: y, max: y }; pools.push(pool); }
|
||||
pool.x1 = x; pool.width++; pool.min = Math.min(pool.min, y); pool.max = Math.max(pool.max, y);
|
||||
}
|
||||
return pools.filter(p => p.width > 20);
|
||||
})()`);
|
||||
}
|
||||
for (const [seed, expected] of [
|
||||
[7, '865ac320f743ff5ed573acab3333773b5ebc2e10f15600438dd15f1767acb72e'],
|
||||
[42, '94930e108f7bd948c6f3dbe8b15b2950eed695cb3d57376d05a8ad781c2015b7'],
|
||||
[424242, '8b8a4b44c2cc98d3ab86515eb55f38ce7cb3d6a283e29e0ed758af937ea8e8c8'],
|
||||
]) {
|
||||
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));
|
||||
check(`seed ${seed} preserves generated material and temperature bytes`, hash.digest('hex') === expected);
|
||||
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} 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'));
|
||||
// Seed 7 reproduces the owner's long slope. No drill, forced wakes, or terrain edits.
|
||||
if (seed === 7) {
|
||||
run('globalThis.lakeSave = serializeWorld();');
|
||||
for (const state of ['fresh', 'reloaded']) {
|
||||
if (state === 'reloaded') run('deserializeWorld(lakeSave.meta, lakeSave.buffers); weather.next = 1e15;');
|
||||
check(`${state} world starts with dry tree bases`, run('treeBases.every(i => !treeHeld(i) || mat[i] !== WATER)'));
|
||||
const before = run('mat.slice(270 * W, 340 * W)');
|
||||
const wakeTicks = state === 'fresh' ? 1 : 171;
|
||||
ticks(run, wakeTicks);
|
||||
check(`${state} lake moves within its startup wake window`, run('mat.subarray(270 * W, 340 * W)').some((m, i) => m !== before[i] && (m === water || before[i] === water)));
|
||||
ticks(run, 6000 - wakeTicks);
|
||||
const pools = surfacePools();
|
||||
check(`${state} lake levels within two grains without disturbance`, pools.length > 0 && pools.every(p => p.max - p.min <= 2), JSON.stringify(pools));
|
||||
// Reload can also thaw mountain snow into new puddles; check the original lake here.
|
||||
const lake = pools.reduce((a, b) => a.width > b.width ? a : b);
|
||||
check(`${state} original lake has no submerged tree base after settling`, run(`treeBases.every(i => i % W < ${lake.x0} || i % W > ${lake.x1} || !treeHeld(i) || mat[i] !== WATER)`));
|
||||
}
|
||||
}
|
||||
}
|
||||
done();
|
||||
Reference in New Issue
Block a user