Loose walls slump to a natural slope instead of eroding layer by layer (t93)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -548,6 +548,13 @@ above, including:
|
|||||||
and upward escapes when blocked. Grains fall normally after each hop. Dust must show as a few
|
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.
|
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
|
## Ideas not built yet
|
||||||
|
|
||||||
- **Concrete**, as a stronger wall material from rubble.
|
- **Concrete**, as a stronger wall material from rubble.
|
||||||
|
|||||||
+3
-1
@@ -254,7 +254,7 @@ Exploration only: never changes grains or the random stream.
|
|||||||
- functions: fogAlloc, fogMark, fogSeen, fogInit, fogLook, fogRevealAround, fogRevealAll
|
- functions: fogAlloc, fogMark, fogSeen, fogInit, fogLook, fogRevealAround, fogRevealAll
|
||||||
- variables: FOG, FOG_SENSE, FOG_SENSE_FADE, fog, FW, FH
|
- 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).
|
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
|
- functions: setCell, swapCells, swapCellData, move, enter, enterFree, powder, passable, liquid, liquidSearch, gas, wakeBotPowder, simMove, moveChunk
|
||||||
- variables: SORTP, SORTP_FUEL, LSTEP, LFILM, CORNER_SEAL
|
- 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_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_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_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_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_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.
|
- `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/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-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/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.
|
||||||
|
|||||||
@@ -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.
|
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)
|
## 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
|
- Traced shake events through `drainEvents` to `spawnDebris` and `drawParticles`. Events were
|
||||||
|
|||||||
+19
-2
@@ -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
|
// 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
|
// 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 slide = rnd() < STICK[m] || (PH[t] === PH_SOLID && !(flags[b] & F_NAT));
|
||||||
const d = (RS & 2) ? 1 : -1;
|
const d = (RS & 2) ? 1 : -1;
|
||||||
|
if (slide) {
|
||||||
let nx = x + d;
|
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; }
|
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;
|
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; }
|
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) {
|
function passable(m, ph, j, dx, dy) {
|
||||||
|
|||||||
@@ -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<NCH;c++)if(awake[c])awake[c]--;
|
||||||
|
}`);
|
||||||
|
}
|
||||||
|
function profile() {
|
||||||
|
return run(`(()=>{const h=[];let n=0,top=96;
|
||||||
|
for(let x=0;x<W;x++){let y=0;while(y<96&&PH[mat[y*W+x]]!==PH_POWDER)y++;
|
||||||
|
h.push(96-y);top=Math.min(top,y);
|
||||||
|
for(;y<96;y++)if(PH[mat[y*W+x]]===PH_POWDER)n++;
|
||||||
|
}
|
||||||
|
let cliff=0;for(let x=1;x<W;x++)cliff=Math.max(cliff,Math.abs(h[x]-h[x-1]));
|
||||||
|
return {cliff,height:96-top,n};})()`);
|
||||||
|
}
|
||||||
|
const hash = () => 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();
|
||||||
@@ -26,6 +26,9 @@ function setup() {
|
|||||||
const m=materials[(random()*materials.length)|0];
|
const m=materials[(random()*materials.length)|0];
|
||||||
setCell(y*W+x,m,m===MAGMA?1450:m===MCU?1200:m===STEAM?110:20,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);
|
addMachine('wheel',40,35,0);
|
||||||
for(let x=0;x<W;x++)setCell(31*W+x,STONE,20,F_NAT);
|
for(let x=0;x<W;x++)setCell(31*W+x,STONE,20,F_NAT);
|
||||||
setCell(31*W+W-10,EMPTY,20,0);
|
setCell(31*W+W-10,EMPTY,20,0);
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// 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.
|
||||||
|
{
|
||||||
|
const left = Math.max(4, SX - 400), floor = SY + 40;
|
||||||
|
const rubble = [RSTONE, CUORE, FEORE, LDIRT];
|
||||||
|
for (let x = left; x < left + 390; x++) {
|
||||||
|
surfY[x] = floor; dirtY[x] = floor + 8;
|
||||||
|
for (let y = floor - 90; y < floor + 8; y++) {
|
||||||
|
bgm[y * W + x] = 0;
|
||||||
|
setCell(y * W + x, y >= 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];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user