Square wall corners hold grains, liquids and gas (t39)
Powder, liquid and gas no longer step diagonally between two solid cells; flyers and diagonal tube intakes obey the same corner. World generation settles with the rule off, so seeds make the same worlds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -98,6 +98,14 @@ Details and numbers live in the code and in `docs/systems/`.
|
|||||||
|
|
||||||
## Decisions
|
## Decisions
|
||||||
|
|
||||||
|
- **Square corners hold grains (t39, 2026-10-26).** The owner wants square wall staircases to
|
||||||
|
hold material instead of leaking through each touching corner. Powder, liquid and gas cannot
|
||||||
|
step diagonally between two solid neighbours; powder neighbours still allow free piles to
|
||||||
|
slide as before. Free flyers and diagonal screw intakes obey the same solid-corner boundary.
|
||||||
|
Screw lane transport is unchanged. World generation disables cell corner sealing while
|
||||||
|
settling loose ground, preserving existing seed layouts. This supersedes the square-wall
|
||||||
|
leakage described in the t36 decision below.
|
||||||
|
|
||||||
The newest are at the bottom. The dates are when the decision was made or built.
|
The newest are at the bottom. The dates are when the decision was made or built.
|
||||||
|
|
||||||
**Up to 2026-10-19** (early design, stated by the owner): everything in "What the owner wants"
|
**Up to 2026-10-19** (early design, stated by the owner): everything in "What the owner wants"
|
||||||
|
|||||||
+5
-4
@@ -230,10 +230,10 @@ 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 (207 lines)
|
### src/sim/cells.js (208 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, move, enter, powder, passable, liquid, gas, wakeBotPowder, simMove
|
- functions: setCell, swapCells, move, enter, powder, passable, liquid, gas, wakeBotPowder, simMove
|
||||||
- variables: SORTP, SORTP_FUEL, LSTEP, LFILM
|
- variables: SORTP, SORTP_FUEL, LSTEP, LFILM, CORNER_SEAL
|
||||||
|
|
||||||
### src/sim/heat.js (265 lines)
|
### src/sim/heat.js (265 lines)
|
||||||
Heat: conduction, melting and freezing, reactions, fire and air, steel.
|
Heat: conduction, melting and freezing, reactions, fire and air, steel.
|
||||||
@@ -264,7 +264,7 @@ Pipes, pumps and outlets: each joined line holds one liquid as a count, not as g
|
|||||||
Per-tick machine work: steam engines, fans, belts, filters, screw tubes (machinesTick).
|
Per-tick machine work: steam engines, fans, belts, filters, screw tubes (machinesTick).
|
||||||
- functions: engineIntake, fanBlow, machinesTick
|
- functions: engineIntake, fanBlow, machinesTick
|
||||||
|
|
||||||
### src/sim/conveyors.js (201 lines)
|
### src/sim/conveyors.js (202 lines)
|
||||||
How belts, filters and screw tubes move what is on or in them. What rests on a belt or filter rides along: the whole stack standing on the carrying surface steps one cell toward d together (belts run front of the line first, so the way ahead is clear). If a powder grain is blocked, the load above it stands still and the belt slips underneath. A filter's own material drops through it instead, unless it is already full.
|
How belts, filters and screw tubes move what is on or in them. What rests on a belt or filter rides along: the whole stack standing on the carrying surface steps one cell toward d together (belts run front of the line first, so the way ahead is clear). If a powder grain is blocked, the load above it stands still and the belt slips underneath. A filter's own material drops through it instead, unless it is already full.
|
||||||
- functions: conveyorStep, carryColumn, holdSlopeColumn, beltDrain, launchGrain, rebuildFilters, filterCarry, carryBeltsAndFilters, setFilter, filterRun, roomFor, tubeSpread, tubeIntake, diagonalTubeNext
|
- functions: conveyorStep, carryColumn, holdSlopeColumn, beltDrain, launchGrain, rebuildFilters, filterCarry, carryBeltsAndFilters, setFilter, filterRun, roomFor, tubeSpread, tubeIntake, diagonalTubeNext
|
||||||
- variables: CARRY_DEPTH, filterR, filterL, filtersDirty
|
- variables: CARRY_DEPTH, filterR, filterL, filtersDirty
|
||||||
@@ -297,7 +297,7 @@ Trees (background layer), acorns, saplings, and waking freezing water.
|
|||||||
- functions: cutTree, treeHeld, findTreeBases, treeSupportTick, woodRoom, treeFall, coldTick, plantTick, setTreeCell, saplingTick, igniteTree
|
- functions: cutTree, treeHeld, findTreeBases, treeSupportTick, woodRoom, treeFall, coldTick, plantTick, setTreeCell, saplingTick, igniteTree
|
||||||
- variables: treeBases, saplings, treeDraw
|
- variables: treeBases, saplings, treeDraw
|
||||||
|
|
||||||
### src/sim/vacuum.js (176 lines)
|
### src/sim/vacuum.js (179 lines)
|
||||||
The vacuum, blowing grains out, grains in flight (flyers), and the flame tool. Material the vacuum would start on at the cursor: a bar, else the nearest loose grain.
|
The vacuum, blowing grains out, grains in flight (flyers), and the flame tool. Material the vacuum would start on at the cursor: a bar, else the nearest loose grain.
|
||||||
- functions: vacuumTargetAt, toolVacuum, nozzle, toolBlow, flyOpen, depositNear, bagTemp, landFlyer, flyTick, toolIgnite
|
- functions: vacuumTargetAt, toolVacuum, nozzle, toolBlow, flyOpen, depositNear, bagTemp, landFlyer, flyTick, toolIgnite
|
||||||
- variables: blowRR, FLY_G, FLY_Q
|
- variables: blowRR, FLY_G, FLY_Q
|
||||||
@@ -514,6 +514,7 @@ The frame loop and boot.
|
|||||||
- `test/t_basics.js`: Basics: drilling to rubble, the vacuum and blowing, walls, the ingot mold and bars, belts, blueprints, save/load.
|
- `test/t_basics.js`: Basics: drilling to rubble, the vacuum and blowing, walls, the ingot mold and bars, belts, blueprints, save/load.
|
||||||
- `test/t_build.js`: Building: undo (Ctrl+Z), cut, replace a machine with Ctrl, pasting ghosts, and the fan heat warning.
|
- `test/t_build.js`: Building: undo (Ctrl+Z), cut, replace a machine with Ctrl, pasting ghosts, and the fan heat warning.
|
||||||
- `test/t_conveyors.js`: Screw tubes that never carry liquid (it flows through), tube and belt throughput, filters.
|
- `test/t_conveyors.js`: Screw tubes that never carry liquid (it flows through), tube and belt throughput, filters.
|
||||||
|
- `test/t_corner_seal.js`: Corner-touching square walls seal loose grains without changing free piles or worldgen.
|
||||||
- `test/t_diag_tubes.js`: Diagonal screws: sealed four-lane corner transfers, bars, joins and persistence.
|
- `test/t_diag_tubes.js`: Diagonal screws: sealed four-lane corner transfers, bars, joins and persistence.
|
||||||
- `test/t_drill.js`: The resonance drill: pulses from the bot shake rock until it shatters. Widths trade area for speed, tuning shatters only one material and drains it out of the wall, built blocks are left alone, and the drill says why when nothing breaks.
|
- `test/t_drill.js`: The resonance drill: pulses from the bot shake rock until it shatters. Widths trade area for speed, tuning shatters only one material and drains it out of the wall, built blocks are left alone, and the drill says why when nothing breaks.
|
||||||
- `test/t_econ.js`: Economy: the dock (instant sale), orders, prices, the market dip, money for machines, alloys.
|
- `test/t_econ.js`: Economy: the dock (instant sale), orders, prices, the market dip, money for machines, alloys.
|
||||||
|
|||||||
@@ -4,6 +4,34 @@ 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-26: t39: seal touching square-block corners (Codex)
|
||||||
|
|
||||||
|
- `cells.js`: six inline checks reject diagonal moves between two PH_SOLID neighbours, with
|
||||||
|
no new random calls. `worldgen.js` saves/disables/restores CORNER_SEAL alongside LSTEP/LFILM
|
||||||
|
while settling. No material ids, save fields or piece geometry changed.
|
||||||
|
- Reproduced two further leaks: free flyers (`vacuum.js`) and diagonal tube mouths pulling sand
|
||||||
|
between external walls (`conveyors.js`); blocked those steps only. Horizontal liquid scans
|
||||||
|
already check each intervening cell; vacuum collection removes grains into the bag. Bars
|
||||||
|
resolve ordinary movement by axis. Screw lane transport remains explicit and unchanged.
|
||||||
|
- `test/t_corner_seal.js`: 23 checks, including both staircase orientations for sand/water/steam,
|
||||||
|
all four diagonal intakes, a flyer, world hashes/flag restoration, and an open pile that remains
|
||||||
|
57x17 with identical material layout and random state when sealing is disabled.
|
||||||
|
- All 25 headless suites pass. Before/after outputs: `t_slopes` square-funnel outflow 379 -> 339
|
||||||
|
(more sand retained on steps); `t_drill` ore outside the wall 56 -> 55 (solid corners hold ore);
|
||||||
|
`t_world` awake chunks after 900 gameplay ticks following reload 148 -> 149 (different settling
|
||||||
|
after corner blocking). Disabling the checks restores drill/world baseline output exactly.
|
||||||
|
Other existing output is identical except timings. Evidence: /tmp/t39-before, /tmp/t39-after.
|
||||||
|
- Full mat/temp/flags hashes match original code for seeds 777/77/4242 at 512x384 and 2048x1024,
|
||||||
|
plus 424242 at 1024x640 (/tmp/t39-world-hashes-{before,after}.txt).
|
||||||
|
- Factory benchmark at 1024x640: idle 0.69 -> 0.68 ms/tick; 1/4/8 modules
|
||||||
|
2.05/2.36/2.65 -> 2.13/2.44/2.70 ms/tick. Timing is indicative: settling changes, and this
|
||||||
|
fixture reported zero machine-moved grains, so it does not prove loaded transport performance.
|
||||||
|
- Rebuilt dist and regenerated PROJECT_MAP. Inspected `shots/t39-corners.png` in pixel look:
|
||||||
|
sand rests along the upper steps and pours through the central outlet.
|
||||||
|
- Final `python3 test/run_all.py --browser`: ALL PASSED (25 headless suites, 323 play checks,
|
||||||
|
15 smooth checks). Updated t39 subtasks/report for Review and recorded the design decision.
|
||||||
|
No commit made; owner playtest remains.
|
||||||
|
|
||||||
## 2026-10-25: t40 art pass: sloped belts and filters drawn as one belt (Claude Code, Codex)
|
## 2026-10-25: t40 art pass: sloped belts and filters drawn as one belt (Claude Code, Codex)
|
||||||
|
|
||||||
- Sloped runs draw as the flat belt (rollers, studs) or filter (tinted mesh) band turned to 45°,
|
- Sloped runs draw as the flat belt (rollers, studs) or filter (tinted mesh) band turned to 45°,
|
||||||
|
|||||||
+8
-7
@@ -67,8 +67,9 @@ let SORTP = 0.06;
|
|||||||
const SORTP_FUEL = 0.003;
|
const SORTP_FUEL = 0.003;
|
||||||
// Liquids step up to LSTEP cells toward a drop in one tick (fewer the thicker they are), and a top-layer grain looks LFILM
|
// Liquids step up to LSTEP cells toward a drop in one tick (fewer the thicker they are), and a top-layer grain looks LFILM
|
||||||
// times as far for one (a two-times range strands the far end of a 200-grain-wide ledge).
|
// times as far for one (a two-times range strands the far end of a 200-grain-wide ledge).
|
||||||
// World generation settles with both at 1, so every seed keeps making the same world.
|
// World generation settles with both at 1 and corner sealing off, so seeds keep the same world.
|
||||||
let LSTEP = 8, LFILM = 4;
|
let LSTEP = 8, LFILM = 4;
|
||||||
|
let CORNER_SEAL = true;
|
||||||
function powder(i, x, y, m) {
|
function powder(i, x, y, m) {
|
||||||
if (y >= H - 1) return;
|
if (y >= H - 1) return;
|
||||||
const b = i + W;
|
const b = i + W;
|
||||||
@@ -82,9 +83,9 @@ function powder(i, x, y, m) {
|
|||||||
if (rnd() < STICK[m]) {
|
if (rnd() < STICK[m]) {
|
||||||
const d = (RS & 2) ? 1 : -1;
|
const d = (RS & 2) ? 1 : -1;
|
||||||
let nx = x + d;
|
let nx = x + d;
|
||||||
if (nx >= 0 && nx < W && enter(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 || PH[mat[i + d]] !== PH_SOLID) && enter(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 && enter(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 || PH[mat[i - d]] !== PH_SOLID) && enter(m, PH_POWDER, b - d, -d, 1)) { move(i, b - d, x, y, nx, y + 1); return; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function passable(m, ph, j, dx, dy) {
|
function passable(m, ph, j, dx, dy) {
|
||||||
@@ -106,9 +107,9 @@ function liquid(i, x, y, m) {
|
|||||||
if (enter(m, PH_LIQUID, b, 0, 1)) { move(i, b, x, y, x, y + 1); return; }
|
if (enter(m, PH_LIQUID, b, 0, 1)) { move(i, b, x, y, x, y + 1); return; }
|
||||||
const d = (RS & 2) ? 1 : -1;
|
const d = (RS & 2) ? 1 : -1;
|
||||||
let nx = x + d;
|
let nx = x + d;
|
||||||
if (nx >= 0 && nx < W && enter(m, PH_LIQUID, 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 || PH[mat[i + d]] !== PH_SOLID) && enter(m, PH_LIQUID, 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 && enter(m, PH_LIQUID, 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 || PH[mat[i - d]] !== PH_SOLID) && enter(m, PH_LIQUID, b - d, -d, 1)) { move(i, b - d, x, y, nx, y + 1); return; }
|
||||||
}
|
}
|
||||||
if (rnd() > FLUID[m]) return;
|
if (rnd() > FLUID[m]) return;
|
||||||
const sp = SPREAD[m] * (y === 0 || PH[mat[i - W]] !== PH_LIQUID ? LFILM : 1);
|
const sp = SPREAD[m] * (y === 0 || PH[mat[i - W]] !== PH_LIQUID ? LFILM : 1);
|
||||||
@@ -153,9 +154,9 @@ function gas(i, x, y, m) {
|
|||||||
if (enter(m, PH_GAS, u, 0, -1)) { move(i, u, x, y, x, y - 1); return; }
|
if (enter(m, PH_GAS, u, 0, -1)) { move(i, u, x, y, x, y - 1); return; }
|
||||||
const d = (RS & 2) ? 1 : -1;
|
const d = (RS & 2) ? 1 : -1;
|
||||||
let nx = x + d;
|
let nx = x + d;
|
||||||
if (nx >= 0 && nx < W && enter(m, PH_GAS, u + d, d, -1)) { move(i, u + d, x, y, nx, y - 1); return; }
|
if (nx >= 0 && nx < W && (!CORNER_SEAL || PH[mat[u]] !== PH_SOLID || PH[mat[i + d]] !== PH_SOLID) && enter(m, PH_GAS, u + d, d, -1)) { move(i, u + d, x, y, nx, y - 1); return; }
|
||||||
nx = x - d;
|
nx = x - d;
|
||||||
if (nx >= 0 && nx < W && enter(m, PH_GAS, u - d, -d, -1)) { move(i, u - d, x, y, nx, y - 1); return; }
|
if (nx >= 0 && nx < W && (!CORNER_SEAL || PH[mat[u]] !== PH_SOLID || PH[mat[i - d]] !== PH_SOLID) && enter(m, PH_GAS, u - d, -d, -1)) { move(i, u - d, x, y, nx, y - 1); return; }
|
||||||
if (rnd() < 0.6) {
|
if (rnd() < 0.6) {
|
||||||
nx = x + d;
|
nx = x + d;
|
||||||
if (nx >= 0 && nx < W && mat[i + d] === EMPTY && passable(m, PH_GAS, i + d, d, 0)) { move(i, i + d, x, y, nx, y); }
|
if (nx >= 0 && nx < W && mat[i + d] === EMPTY && passable(m, PH_GAS, i + d, d, 0)) { move(i, i + d, x, y, nx, y); }
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ function tubeIntake(i, p, dx, dy) {
|
|||||||
const oy = diagonal ? (k === 0 ? 0 : -dy) : k === 0 ? -dy : k === 1 ? dx : -dx;
|
const oy = diagonal ? (k === 0 ? 0 : -dy) : k === 0 ? -dy : k === 1 ? dx : -dx;
|
||||||
const nx = x + ox, ny = y + oy;
|
const nx = x + ox, ny = y + oy;
|
||||||
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
|
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
|
||||||
|
if (ox && oy && PH[mat[y * W + nx]] === PH_SOLID && PH[mat[ny * W + x]] === PH_SOLID) continue;
|
||||||
const j = ny * W + nx, m = mat[j];
|
const j = ny * W + nx, m = mat[j];
|
||||||
if (m === EMPTY || occ[j] || stamp[j] === tickStamp) continue;
|
if (m === EMPTY || occ[j] || stamp[j] === tickStamp) continue;
|
||||||
if (PH[m] !== PH_POWDER) continue; // the screw only takes loose material; liquids just flow through
|
if (PH[m] !== PH_POWDER) continue; // the screw only takes loose material; liquids just flow through
|
||||||
|
|||||||
+4
-1
@@ -146,7 +146,10 @@ function flyTick() {
|
|||||||
if (cx < 0 || cx >= W) { f.vx = 0; continue; } // the world's edge stops it; it drops from there
|
if (cx < 0 || cx >= W) { f.vx = 0; continue; } // the world's edge stops it; it drops from there
|
||||||
if (cy >= H) { landFlyer(f, px, H - 1); done = true; break; }
|
if (cy >= H) { landFlyer(f, px, H - 1); done = true; break; }
|
||||||
if (cy < 0) { f.x = nx; f.y = ny; continue; }
|
if (cy < 0) { f.x = nx; f.y = ny; continue; }
|
||||||
const open = flyOpen(f.m, cy * W + cx);
|
// A free flyer must not cut across the point where two solid cells meet.
|
||||||
|
const sealed = cx !== px && cy !== py && px >= 0 && px < W && py >= 0 && py < H &&
|
||||||
|
PH[mat[py * W + cx]] === PH_SOLID && PH[mat[cy * W + px]] === PH_SOLID;
|
||||||
|
const open = !sealed && flyOpen(f.m, cy * W + cx);
|
||||||
if (!f.free) {
|
if (!f.free) {
|
||||||
// still inside whatever the bot is flying through: pass until the first open cell
|
// still inside whatever the bot is flying through: pass until the first open cell
|
||||||
// a blocked nozzle spills: the grain backs up into the nearest open space
|
// a blocked nozzle spills: the grain backs up into the nearest open space
|
||||||
|
|||||||
+2
-2
@@ -245,7 +245,7 @@ function* worldSteps(seedIn, w, h) {
|
|||||||
yield [0.75, 'Settling loose ground'];
|
yield [0.75, 'Settling loose ground'];
|
||||||
// settle loose material at the water and sorting speeds the seeds were made with
|
// settle loose material at the water and sorting speeds the seeds were made with
|
||||||
awake.fill(3);
|
awake.fill(3);
|
||||||
const lstep = LSTEP, lfilm = LFILM, sortp = SORTP; LSTEP = LFILM = 1; SORTP = 0.003;
|
const lstep = LSTEP, lfilm = LFILM, sortp = SORTP, cornerSeal = CORNER_SEAL; LSTEP = LFILM = 1; SORTP = 0.003; CORNER_SEAL = false;
|
||||||
for (let k = 0; k < 260; k++) {
|
for (let k = 0; k < 260; k++) {
|
||||||
tick++; tickStamp = (tickStamp % 255) + 1;
|
tick++; tickStamp = (tickStamp % 255) + 1;
|
||||||
simMove();
|
simMove();
|
||||||
@@ -253,7 +253,7 @@ function* worldSteps(seedIn, w, h) {
|
|||||||
if (!any) break;
|
if (!any) break;
|
||||||
if ((k & 31) === 0) yield [0.75 + 0.24 * k / 260, 'Settling loose ground'];
|
if ((k & 31) === 0) yield [0.75 + 0.24 * k / 260, 'Settling loose ground'];
|
||||||
}
|
}
|
||||||
LSTEP = lstep; LFILM = lfilm; SORTP = sortp;
|
LSTEP = lstep; LFILM = lfilm; SORTP = sortp; CORNER_SEAL = cornerSeal;
|
||||||
yield [0.99, 'Finishing'];
|
yield [0.99, 'Finishing'];
|
||||||
// re-ambient after settling, then start clean
|
// 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; }
|
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; }
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// Corner-touching square walls seal loose grains without changing free piles or worldgen.
|
||||||
|
const { load, suite, flatWorld, rect, count, ticks } = require('./harness');
|
||||||
|
const { createHash } = require('node:crypto');
|
||||||
|
const { run } = load();
|
||||||
|
const { check, done } = suite();
|
||||||
|
|
||||||
|
for (const [seed, expected] of [
|
||||||
|
[777, '040644fb50c891911383ef0206ea988677d7ec447a3e6637cefa99364e765861'],
|
||||||
|
[77, '2e6af816af54c8de4fe9c7188607f7af503ef844b2087245779bcf39e8330bdb'],
|
||||||
|
[4242, 'f09270f4ac5da9347bc277c9959e31ee797313b4a994598b4647dcf852532782'],
|
||||||
|
]) {
|
||||||
|
run(`generateWorld(${seed}, 512, 384)`);
|
||||||
|
check(`seed ${seed} restores corner sealing`,run('CORNER_SEAL')===true);
|
||||||
|
check(`seed ${seed} keeps its pre-seal material hash`, createHash('sha256').update(run('mat')).digest('hex') === expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const material of ['SAND', 'WATER', 'STEAM']) for (const mirror of [false, true]) {
|
||||||
|
flatWorld(run, 160, 256, 224);
|
||||||
|
// Span the whole world so escaping an end cannot be confused with crossing a corner.
|
||||||
|
run(`RS=12345; AMB.fill(20); temp.fill(20); weather.skyY=-1000;
|
||||||
|
for(let bx=0;bx<40;bx++) placeBlock(BP_WALL,RSTONE,bx,${mirror ? '47-bx' : '8+bx'},true);`);
|
||||||
|
const gas = material === 'STEAM';
|
||||||
|
rect(run, 70, gas ? 166 : 20, 89, gas ? 170 : 24, run(material));
|
||||||
|
if (gas) {
|
||||||
|
// Isolate gas movement from cooling/condensation, including chunk sleeping.
|
||||||
|
run(`temp.fill(120); var gasLeaks=0; for(let k=0;k<500;k++){tick++;tickStamp=tickStamp%255+1;simMove();
|
||||||
|
for(let y=0;y<H;y++)for(let x=0;x<W;x++)if(mat[y*W+x]===STEAM && y<(${mirror ? '47-(x>>2)' : '8+(x>>2)'})*4) gasLeaks++;}`);
|
||||||
|
} else ticks(run, 500);
|
||||||
|
const result = run(`(()=>{
|
||||||
|
let leaked=0,held=0,x0=W,x1=0,y0=H,y1=0;
|
||||||
|
for(let y=0;y<H;y++)for(let x=0;x<W;x++)if(mat[y*W+x]===${material}) {
|
||||||
|
const top=(${mirror ? '47-(x>>2)' : '8+(x>>2)'})*4;
|
||||||
|
if(${gas ? 'y<top' : 'y>=top+4'}) leaked++; else held++;
|
||||||
|
x0=Math.min(x0,x);x1=Math.max(x1,x);y0=Math.min(y0,y);y1=Math.max(y1,y);
|
||||||
|
}
|
||||||
|
return {leaked,held,width:x1-x0+1,height:y1-y0+1};
|
||||||
|
})()`);
|
||||||
|
check(`${material} stays ${gas ? 'under' : 'above'} ${mirror ? 'rising' : 'descending'} square stairs`, result.leaked === 0 && result.held === 100 && (!gas || run('gasLeaks')===0), JSON.stringify(result)+(gas ? ` crossings ${run('gasLeaks')}` : ''));
|
||||||
|
if (!gas) check(`${material} piles and slides along the steps`, result.height > 1 && count(run,run(material),70,0,89,223)<100, JSON.stringify(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A screw mouth must not draw sand through two external wall cells either.
|
||||||
|
for (const direction of [4,5,6,7]) {
|
||||||
|
flatWorld(run,160,160,140);
|
||||||
|
run(`placeBlock(BP_TUBE,${direction},20,20,true);
|
||||||
|
var dx=TDX[TUBE_PARTS[${direction}]],dy=TDY[TUBE_PARTS[${direction}]];
|
||||||
|
var x=dx>0?80:83,y=dy>0?80:83;
|
||||||
|
setCell((y-dy)*W+x-dx,SAND,20,0);
|
||||||
|
setCell((y-dy)*W+x,STONE,20,0);setCell(y*W+x-dx,STONE,20,0);
|
||||||
|
tubeIntake(y*W+x,part[y*W+x],dx,dy);`);
|
||||||
|
check(`diagonal tube ${direction} cannot pull through a solid corner`,run('mat[(y-dy)*W+x-dx]===SAND'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A free flyer can take the same diagonal shortcut between wall cells.
|
||||||
|
flatWorld(run, 160, 160, 140);
|
||||||
|
run(`setCell(79*W+80,STONE,20,0);setCell(80*W+79,STONE,20,0);
|
||||||
|
flyers=[{x:79.5,y:79.5,vx:1,vy:1,age:0,m:SAND,T:20,free:true,run:0}];flyTick();`);
|
||||||
|
check('a flying grain cannot cross a sealed corner', run('flyers.length===0 && mat[79*W+79]===SAND'));
|
||||||
|
|
||||||
|
flatWorld(run, 240, 160, 120);
|
||||||
|
run('RS=12345;');
|
||||||
|
rect(run, 110, 40, 129, 59, run('SAND'));
|
||||||
|
ticks(run, 600);
|
||||||
|
const pile = run(`(()=>{let x0=W,x1=0,y0=H,y1=0;
|
||||||
|
for(let y=0;y<H;y++)for(let x=0;x<W;x++)if(mat[y*W+x]===SAND){x0=Math.min(x0,x);x1=Math.max(x1,x);y0=Math.min(y0,y);y1=Math.max(y1,y);}
|
||||||
|
return [x1-x0+1,y1-y0+1];})()`);
|
||||||
|
// Before the fix: 57 wide, 17 high. Small bounds allow random-state differences from prior scenes.
|
||||||
|
check('open-ground sand still forms the same size pile', pile[0]>=55 && pile[0]<=59 && pile[1]>=16 && pile[1]<=18 && count(run,run('SAND'))===400, pile);
|
||||||
|
const sealedPile = createHash('sha256').update(run('mat')).digest('hex'), sealedRS=run('RS');
|
||||||
|
flatWorld(run,240,160,120);
|
||||||
|
run('RS=12345; CORNER_SEAL=false;');
|
||||||
|
rect(run,110,40,129,59,run('SAND'));ticks(run,600);
|
||||||
|
check('open pile and random sequence exactly match unsealed movement', createHash('sha256').update(run('mat')).digest('hex')===sealedPile && run('RS')===sealedRS);
|
||||||
|
run('CORNER_SEAL=true;');
|
||||||
|
done();
|
||||||
Reference in New Issue
Block a user