From 807217b22c0af7126c87ff2383d1a24198ac9335 Mon Sep 17 00:00:00 2001 From: 44r0n7 <44r0n7+gitea@pm.me> Date: Wed, 28 Oct 2026 18:58:40 -0400 Subject: [PATCH] Loose walls slump to a natural slope instead of eroding layer by layer (t93) Co-Authored-By: Claude Opus 5.5 --- docs/DESIGN.md | 7 +++++ docs/PROJECT_MAP.md | 4 ++- docs/agent-log.md | 44 ++++++++++++++++++++++++++++ src/sim/cells.js | 21 ++++++++++++-- test/t_pile_collapse.js | 48 +++++++++++++++++++++++++++++++ test/t_threads.js | 3 ++ tools/scenes/t93-pile-collapse.js | 22 ++++++++++++++ 7 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 test/t_pile_collapse.js create mode 100644 tools/scenes/t93-pile-collapse.js diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 23c4b1f..fae6a33 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -548,6 +548,13 @@ above, including: and upward escapes when blocked. Grains fall normally after each hop. Dust must show as a few soft puffs where grains move; the previous rubble-coloured debris squares were not visible. +- **Loose walls release from their face (t93, 2026-10-28).** The owner wants tall loose + rubble faces to collapse quickly toward a pile instead of shedding one surface layer at + a time. An open diagonal step with another empty grain below it bypasses the sticky delay; + shallow steps retain the material's stickiness. A legal sticky step that misses its chance + keeps its chunk awake to retry. Burning fuel beds keep their tuned settling behavior. + Grain stamps, sealed corners, transport barriers and per-chunk random streams still apply. + ## Ideas not built yet - **Concrete**, as a stronger wall material from rubble. diff --git a/docs/PROJECT_MAP.md b/docs/PROJECT_MAP.md index 94a9d4e..074f519 100644 --- a/docs/PROJECT_MAP.md +++ b/docs/PROJECT_MAP.md @@ -254,7 +254,7 @@ Exploration only: never changes grains or the random stream. - functions: fogAlloc, fogMark, fogSeen, fogInit, fogLook, fogRevealAround, fogRevealAll - variables: FOG, FOG_SENSE, FOG_SENSE_FADE, fog, FW, FH -### src/sim/cells.js (223 lines) +### src/sim/cells.js (240 lines) Cell helpers (setCell, move, enter) and the falling-sand movement of powders, liquids and gases (simMove). - functions: setCell, swapCells, swapCellData, move, enter, enterFree, powder, passable, liquid, liquidSearch, gas, wakeBotPowder, simMove, moveChunk - variables: SORTP, SORTP_FUEL, LSTEP, LFILM, CORNER_SEAL @@ -656,6 +656,7 @@ The frame loop and boot. - `test/t_nozzle.js`: Blowing aims within the nozzle's reach, with room for the brush and settling. - `test/t_perf.js`: Profiling fixtures start in the same state after previous scenes and warm-up ticks. - `test/t_physics.js`: Sand and other powders, molten metal and gases: heaps, sorting, sinking, setting, rising and fading. Water has its own suite (t_water.js). Tall worlds keep geothermal heat out of the way. +- `test/t_pile_collapse.js`: Tall loose faces release together; supported heaps stop moving, including when revisited. - `test/t_pipes.js`: Pipes, pumps and outlets move liquids; the Grinder grinds waste rock for trace ore. - `test/t_racks.js`: Rack intake, FIFO storage, gate, removal and saved-state validation. - `test/t_render_dirty.js`: Render invalidation must not depend on whether a grain moved or the chunk stayed awake. @@ -709,3 +710,4 @@ The frame loop and boot. - `tools/scenes/t40-sloped-belt.js`: Self-contained: a flat feed climbs, crosses the top, and runs downhill; a filter sorts below. - `tools/scenes/t88-light.js`: Frozen light comparisons, also used by the WebGPU browser regression suite. - `tools/scenes/t88-renderer.js`: Paused comparison scene: ordinary grains, fog, heat, trees and exceptional geometry. +- `tools/scenes/t93-pile-collapse.js`: Left to right: 40x20 walls of sand, mixed rubble, and rubble with 20% clay. Capture at --ticks 0 and --ticks 180, --zoom 3 --no-light --freeze. diff --git a/docs/agent-log.md b/docs/agent-log.md index f30461f..7ca2224 100644 --- a/docs/agent-log.md +++ b/docs/agent-log.md @@ -5,6 +5,50 @@ note or request it was for, what changed, the files touched, the tests run, and open or not verified. Keep each entry short; the git history has the details. +## 2026-10-28: t93 loose faces slump without sticky-step stalls (Codex) + +- Inspected the owner's screenshot and traced `powder`, `enterFree`, `move`, `moveChunk`, + waking and checkerboard worker passes. Movement-only 40x20 walls on a flat floor, with + normal sleep countdown and the bot far away: sand reaches a <=1-grain surface step in + 44 ticks; rubble (stone/copper ore/iron ore/dirt) in 89; 20% clay never reaches it through + 2400 ticks (2-grain ledge). Always-awake control reaches it in 249 ticks, pure clay in 585; + STICK=1 takes 44 for every mix. Cause: probabilistic sticky diagonal steps throttle face + release, and three-tick chunk sleep can strand a legal step after missed rolls. Bottom-up + scan and stamps still allow sand to collapse; empty destinations are accepted before the + target-stamp check. No scheduler or stamp changes were needed. +- Added a fallback only after a missed sticky roll: an open diagonal with another empty cell + below releases immediately; a legal shallow step wakes its chunk to retry. Retained the + original fast path, density/displacement rolls, built-surface sliding, sealed corners, + occupancy, transport and cleat checks, one-step stamps and per-chunk streams. Burning + grains/supports retain their tuned behavior: extending the fallback into burning beds + failed vent-fed furnace cleanliness. A shared-loop rewrite was also rejected after profiling. +- New `t_pile_collapse` has 17 checks: all 800 grains retained; sand, mixed rubble and + 20% clay faces <=2-grain surface steps by 180 ticks at offsets 0 and 17; cohesive pure + clay <=3. After 1200 forced-awake settling ticks, another 600 leave all heap material bytes + unchanged and chunks sleep afterward. Settled clay is 27 high versus sand 23. Exact final + test against original cells.js fails three collapse checks; final code passes. Added a + clay/rubble face spanning chunk rows to the real 0/1/2/8-worker/reversed-pass hash fixture. +- Final `python3 test/run_all.py --browser` exited 0, ALL PASSED: 33 headless suites, + including furnace 9, collapse 17 and threads 12; browser gameplay 361, smooth 28, + console 18, caps 7, GPU 15, goldens 28, light 71 and Firefox 2. Evidence: + `/tmp/t93-final-browser.log`. Earlier full browser run also passed. Diff whitespace check passes. +- Final isolated hardware profile, NVIDIA Blackwell, requested command: + `python3 tools/perf.py --scenes settled,heat --looks pixel --zooms 6 --compare tools/perf-after-t88.json`. + Original-code control -> final total ms/tick: settled 2.0179 -> 1.9790 (-1.9%), + heat 2.7255 -> 2.7650 (+1.5%). Against historical t88: 1.8056 -> 1.9790 (+9.6%), + 2.6231 -> 2.7650 (+5.4%). Final movement costs 0.9715/1.3816 ms/tick versus control + 0.9951/1.3647. Earlier profiles varied, including unchanged heat work; final run had no + concurrent tests. Evidence: `/tmp/t93-perf-fast.json`, `/tmp/t93-idle-control.json`, + `/tmp/t93-red-exact.log`, `/tmp/t93-collapse-final.log`. +- Captured and inspected pixel `shots/t93-before.png` and final `shots/t93-after.png`: + three 40x20 walls become sloped heaps after 180 full simulation ticks. Reproduce with + `tools/scenes/t93-pile-collapse.js`, `--ticks 0` / `--ticks 180`, `--zoom 3 --no-light --freeze`. + No page errors. Rendering code unchanged; smooth screenshot not taken. +- Files: `src/sim/cells.js`, `test/t_pile_collapse.js`, `test/t_threads.js`, + `tools/scenes/t93-pile-collapse.js`, `docs/DESIGN.md`, generated `docs/PROJECT_MAP.md`, + t93 records and this log. All subtasks ticked; t93 moved to Review with a completion + report and game checks. No commit. Owner playtesting remains open. + ## 2026-10-28: t92 follow-up, visible dust and a botward channel through rubble (Codex) - Traced shake events through `drainEvents` to `spawnDebris` and `drawParticles`. Events were diff --git a/src/sim/cells.js b/src/sim/cells.js index b2be7a8..63effb2 100644 --- a/src/sim/cells.js +++ b/src/sim/cells.js @@ -100,12 +100,29 @@ function powder(i, x, y, m, barriers = cleatChunks[(y >> 5) * CW + (x >> 5)]) { } // stickiness is grain against grain: on anything built (a wall, a slope, a belt) every material // slides freely, so a chute carries clay as well as sand while clay heaps stay steep - if (rnd() < STICK[m] || (PH[t] === PH_SOLID && !(flags[b] & F_NAT))) { - const d = (RS & 2) ? 1 : -1; + const slide = rnd() < STICK[m] || (PH[t] === PH_SOLID && !(flags[b] & F_NAT)); + const d = (RS & 2) ? 1 : -1; + if (slide) { let nx = x + d; if (nx >= 0 && nx < W && (!CORNER_SEAL || (PH[mat[b]] !== PH_SOLID && (!barriers || !cleatMask[b])) || (PH[mat[i + d]] !== PH_SOLID && (!barriers || !cleatMask[i + d]))) && (!barriers || (!cleatMask[i] && !cleatMask[b + d])) && enterFree(m, PH_POWDER, b + d, d, 1)) { move(i, b + d, x, y, nx, y + 1); return; } nx = x - d; if (nx >= 0 && nx < W && (!CORNER_SEAL || (PH[mat[b]] !== PH_SOLID && (!barriers || !cleatMask[b])) || (PH[mat[i - d]] !== PH_SOLID && (!barriers || !cleatMask[i - d]))) && (!barriers || (!cleatMask[i] && !cleatMask[b - d])) && enterFree(m, PH_POWDER, b - d, -d, 1)) { move(i, b - d, x, y, nx, y + 1); return; } + return; + } + if ((flags[i] | flags[b]) & F_BURN) return; // keep the tuned settling of furnace beds + for (let side = 0; side < 2; side++) { + const dx = side ? -d : d, nx = x + dx, j = b + dx; + if (nx < 0 || nx >= W || (mat[j] !== EMPTY && PH[mat[j]] !== PH_GAS)) continue; + if (CORNER_SEAL && (PH[t] === PH_SOLID || (barriers && cleatMask[b])) && + (PH[mat[i + dx]] === PH_SOLID || (barriers && cleatMask[i + dx]))) continue; + if (barriers && (cleatMask[i] || cleatMask[j])) continue; + if (!passable(m, PH_POWDER, j, dx, 1)) continue; + // Cohesion delays shallow slides, not a face with two open grains below the step. + if (y + 2 < H && mat[j + W] === EMPTY) { + move(i, j, x, y, nx, y + 1); return; + } + // A missed sticky step must get another chance before its chunk sleeps. + chunkWake((y >> 5) * CW + (x >> 5)); } } function passable(m, ph, j, dx, dy) { diff --git a/test/t_pile_collapse.js b/test/t_pile_collapse.js new file mode 100644 index 0000000..787061d --- /dev/null +++ b/test/t_pile_collapse.js @@ -0,0 +1,48 @@ +// Tall loose faces release together; supported heaps stop moving, including when revisited. +const {load, suite, flatWorld} = require('./harness'); +const {createHash} = require('node:crypto'); +const {run} = load(); +const {check, done} = suite(); +function wall(material, offset = 0) { + flatWorld(run, 192, 600, 96); + run(`RS=123; worldSeed=7; bot.x=500; bot.y=40; + for(let y=56;y<96;y++)for(let x=${80+offset};x<${100+offset};x++)setCell(y*W+x,${material},20,0);`); +} +function motion(n, revisit = false) { + run(`for(let k=0;k<${n};k++) { + tick++;tickStamp=tickStamp%255+1; + if (${revisit}) awake.fill(3); + simMove(); + for(let c=0;c{const h=[];let n=0,top=96; + for(let x=0;x createHash('sha256').update(run('mat')).digest('hex'); +const rubble = '[RSTONE,CUORE,FEORE,LDIRT][(x+y)%4]'; +let sandHeight; +for(const [name, material] of [['sand','SAND'],['mixed rubble',rubble],['rubble with clay',`(x+y)%5===0?LCLAY:${rubble}`],['clay','LCLAY']]) { + for(const offset of [0,17]) { + wall(material,offset); + const maxCliff = name==='clay' ? 3 : 2; + let slopedAt=0; + for(let k=1;k<=180;k++) {motion(1);if(profile().cliff<=maxCliff&&!slopedAt)slopedAt=k;} + const p=profile(); + check(`${name} face slumps within 180 ticks (offset ${offset})`,slopedAt>0&&p.cliff<=maxCliff&&p.n===800,JSON.stringify({slopedAt,...p})); + motion(1200,true); + if(name==='sand'&&offset===0)sandHeight=profile().height; + if(name==='clay'&&offset===0)check('sticky clay holds a steeper settled heap than sand',profile().height>sandHeight,`sand ${sandHeight}, clay ${profile().height}`); + const settled=hash(); + // Force visits so sleeping cannot hide continued creeping. + motion(600,true);motion(3); + check(`${name} settled heap stays put and sleeps (offset ${offset})`,hash()===settled&&run('awake.every(v=>v===0)')&&profile().n===800); + } +} +done(); diff --git a/test/t_threads.js b/test/t_threads.js index 5e1e4a4..f6ac78b 100644 --- a/test/t_threads.js +++ b/test/t_threads.js @@ -26,6 +26,9 @@ function setup() { const m=materials[(random()*materials.length)|0]; setCell(y*W+x,m,m===MAGMA?1450:m===MCU?1200:m===STEAM?110:20,0); } + // A cohesive vertical face spans chunk rows and must collapse identically in every pool. + for(let y=50;y<90;y++)for(let x=260;x<280;x++) + setCell(y*W+x,(x+y)%5===0?LCLAY:RSTONE,20,0); addMachine('wheel',40,35,0); for(let x=0;x= floor ? STONE : EMPTY, 20, y >= floor ? F_NAT : 0); + } + } + for (let pile = 0; pile < 3; pile++) { + const start = left + 60 + pile * 120; + for (let y = floor - 40; y < floor; y++) for (let x = start; x < start + 20; x++) { + const m = pile === 0 ? SAND : pile === 2 && (x + y) % 5 === 0 ? LCLAY : rubble[(x + y) % 4]; + setCell(y * W + x, m, 20, 0); + } + } + updateRoof(); + window.CAM = [left + 195, floor - 24]; +}