Tidy the t93 resting-face spill: shared open-cell and fall helpers, no behaviour change
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,18 @@ open or not verified. Keep each entry short; the git history has the details.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 2026-10-28: t93 round 3 review and tidy (Claude)
|
||||||
|
|
||||||
|
- Checked in the real game (WebGPU Chromium, real mouse): hovering the bot 100 grains up and
|
||||||
|
blowing sand straight above it at width 6 buries the bot and builds the owner's vertical wall.
|
||||||
|
Old rules: the wall still stands 5 s after release. New rules: a slope within 5 s.
|
||||||
|
- Refactor only, no behaviour change (world hash identical before and after): one `powderOpen`
|
||||||
|
test and one `powderFall` loop replace six copies of the open-cell check and three scan loops;
|
||||||
|
comments now say why resting faces need the spill and what the avalanche flag means.
|
||||||
|
- Checked: the new flag bits 8/16 collide with nothing (the renderer reads only F_NAT); stone
|
||||||
|
rubble is free-flowing (STICK 1) so it gets the rule; narrow towers and rubble or loose-dirt
|
||||||
|
mixes collapse on both old and new rules. Full suite with browser: ALL PASSED.
|
||||||
|
|
||||||
## 2026-10-28: t93 round 3, nozzle-built resting sand faces (Codex)
|
## 2026-10-28: t93 round 3, nozzle-built resting sand faces (Codex)
|
||||||
|
|
||||||
- Reproduced the owner's continuous straight-up radius-6 sand nozzle recipe, not a
|
- Reproduced the owner's continuous straight-up radius-6 sand nozzle recipe, not a
|
||||||
|
|||||||
+28
-27
@@ -78,27 +78,33 @@ const SORTP_FUEL = 0.003;
|
|||||||
// World generation settles with both at 1 and corner sealing off, so seeds keep 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, POWDER_FAN = true;
|
let CORNER_SEAL = true, POWDER_FAN = true;
|
||||||
// Every spill checks each crossed cell, including barriers outside the source chunk.
|
// Open to a moving grain: empty, no built part or barrier in the way.
|
||||||
|
function powderOpen(m, j, dx, dy) {
|
||||||
|
return mat[j] === EMPTY && !part[j] && !cleatMask[j] && passable(m, PH_POWDER, j, dx, dy);
|
||||||
|
}
|
||||||
|
// Lengthen a straight fall down column cell j (row y) while the cells below stay open.
|
||||||
|
function powderFall(m, j, y, drop, limit) {
|
||||||
|
while (drop < limit && y + drop + 1 < H && powderOpen(m, j + W * (drop + 1), 0, 1)) drop++;
|
||||||
|
return drop;
|
||||||
|
}
|
||||||
|
// Up to `limit` grains sideways to the last open drop, then down it. Checks every crossed cell,
|
||||||
|
// including barriers outside the source chunk.
|
||||||
function powderSpill(i, x, y, m, dx, limit, avalanche) {
|
function powderSpill(i, x, y, m, dx, limit, avalanche) {
|
||||||
let reach = 0;
|
let reach = 0;
|
||||||
for (let step = 1; step <= limit; step++) {
|
for (let step = 1; step <= limit; step++) {
|
||||||
const nx = x + dx * step, side = i + dx * step, j = side + W;
|
const nx = x + dx * step, side = i + dx * step;
|
||||||
if (nx < 0 || nx >= W || mat[side] !== EMPTY || part[side] || cleatMask[side] ||
|
if (nx < 0 || nx >= W || !powderOpen(m, side, dx, 0)) break;
|
||||||
!passable(m, PH_POWDER, side, dx, 0)) break;
|
if (powderOpen(m, side + W, dx, 1)) reach = step;
|
||||||
if (mat[j] === EMPTY && !part[j] && !cleatMask[j] && passable(m, PH_POWDER, j, dx, 1)) reach = step;
|
|
||||||
}
|
}
|
||||||
if (!reach) return false;
|
if (!reach) return false;
|
||||||
let drop = 1;
|
const drop = powderFall(m, i + dx * reach, y, 1, limit);
|
||||||
for (let step = 2; step <= limit && y + step < H; step++) {
|
|
||||||
const j = i + W * step + dx * reach;
|
|
||||||
if (mat[j] !== EMPTY || part[j] || cleatMask[j] || !passable(m, PH_POWDER, j, 0, 1)) break;
|
|
||||||
drop = step;
|
|
||||||
}
|
|
||||||
if (avalanche) avalancheMove(i, x, y, dx, reach, drop);
|
if (avalanche) avalancheMove(i, x, y, dx, reach, drop);
|
||||||
else move(i, i + W * drop + dx * reach, x, y, x + dx * reach, y + drop);
|
else move(i, i + W * drop + dx * reach, x, y, x + dx * reach, y + drop);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Removing support passes the collapse direction up the column; contact cancels it.
|
// An avalanche is the only memory of motion a grain has: flagged grains keep falling outward next
|
||||||
|
// tick, and the free-flowing grain above inherits the flag because its support just left. Landing
|
||||||
|
// on anything clears it.
|
||||||
function avalancheMove(i, x, y, dx, reach, drop) {
|
function avalancheMove(i, x, y, dx, reach, drop) {
|
||||||
const fl = F_AVALANCHE | (dx > 0 ? F_AVALANCHE_R : 0);
|
const fl = F_AVALANCHE | (dx > 0 ? F_AVALANCHE_R : 0);
|
||||||
if (y && PH[mat[i - W]] === PH_POWDER && STICK[mat[i - W]] === 1) flags[i - W] = (flags[i - W] & ~F_AVALANCHE_R) | fl;
|
if (y && PH[mat[i - W]] === PH_POWDER && STICK[mat[i - W]] === 1) flags[i - W] = (flags[i - W] & ~F_AVALANCHE_R) | fl;
|
||||||
@@ -111,29 +117,24 @@ function powder(i, x, y, m, barriers = cleatChunks[(y >> 5) * CW + (x >> 5)]) {
|
|||||||
if (flags[i] & F_AVALANCHE) {
|
if (flags[i] & F_AVALANCHE) {
|
||||||
const dx = flags[i] & F_AVALANCHE_R ? 1 : -1;
|
const dx = flags[i] & F_AVALANCHE_R ? 1 : -1;
|
||||||
flags[i] &= ~(F_AVALANCHE | F_AVALANCHE_R);
|
flags[i] &= ~(F_AVALANCHE | F_AVALANCHE_R);
|
||||||
|
if (mat[b] === EMPTY && !part[i] && !cleatMask[i] && !(flags[i] & F_BURN)) {
|
||||||
let reach = 0;
|
let reach = 0;
|
||||||
for (let step = 1; step <= 2 && mat[b] === EMPTY; step++) {
|
while (reach < 2 && x + dx * (reach + 1) >= 0 && x + dx * (reach + 1) < W && powderOpen(m, i + dx * (reach + 1), dx, 0)) reach++;
|
||||||
const nx = x + dx * step, j = i + dx * step;
|
const drop = powderFall(m, i + dx * reach, y, 0, 8);
|
||||||
if (nx < 0 || nx >= W || mat[j] !== EMPTY || part[j] || cleatMask[j] || !passable(m, PH_POWDER, j, dx, 0)) break;
|
if (drop) { avalancheMove(i, x, y, dx, reach, drop); return; }
|
||||||
reach = step;
|
|
||||||
}
|
|
||||||
let drop = 0;
|
|
||||||
for (let step = 1; step <= 8 && y + step < H; step++) {
|
|
||||||
const j = i + dx * reach + W * step;
|
|
||||||
if (mat[j] !== EMPTY || part[j] || cleatMask[j] || !passable(m, PH_POWDER, j, 0, 1)) break;
|
|
||||||
drop = step;
|
|
||||||
}
|
|
||||||
if (drop && mat[b] === EMPTY && !part[i] && !cleatMask[i] && !(flags[i] & F_BURN)) {
|
|
||||||
avalancheMove(i, x, y, dx, reach, drop); return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Tall free-flowing bodies topple from rest; short feeds and cohesive grains keep their repose.
|
// A grain on a face, open on one side and backed by powder on the other, either above a step or
|
||||||
|
// mid-face. Grains resting there only ever step one cell, so a tall face crumbles a grain at a
|
||||||
|
// time and stands as a wall (t93): when the body behind is at least 32 grains deep, it spills
|
||||||
|
// like the falling face below. Cohesive grains keep their steeper repose.
|
||||||
if (POWDER_FAN && STICK[m] === 1 && (mat[b] === EMPTY || PH[mat[b]] === PH_POWDER) && !part[i] && !((flags[i] | flags[b]) & F_BURN) && x > 0 && x < W - 1) {
|
if (POWDER_FAN && STICK[m] === 1 && (mat[b] === EMPTY || PH[mat[b]] === PH_POWDER) && !part[i] && !((flags[i] | flags[b]) & F_BURN) && x > 0 && x < W - 1) {
|
||||||
const dx = mat[i - 1] === EMPTY && PH[mat[i + 1]] === PH_POWDER ? -1 :
|
const dx = mat[i - 1] === EMPTY && PH[mat[i + 1]] === PH_POWDER ? -1 :
|
||||||
mat[i + 1] === EMPTY && PH[mat[i - 1]] === PH_POWDER ? 1 : 0;
|
mat[i + 1] === EMPTY && PH[mat[i - 1]] === PH_POWDER ? 1 : 0;
|
||||||
if (dx && !cleatMask[i] && ((PH[mat[b]] === PH_POWDER && y + 2 < H && mat[b + W + dx] === EMPTY) ||
|
if (dx && !cleatMask[i] && ((PH[mat[b]] === PH_POWDER && y + 2 < H && mat[b + W + dx] === EMPTY) ||
|
||||||
(y > 0 && PH[mat[i - W]] === PH_POWDER && mat[i - W + dx] === EMPTY && PH[mat[b - dx]] === PH_POWDER))) {
|
(y > 0 && PH[mat[i - W]] === PH_POWDER && mat[i - W + dx] === EMPTY && PH[mat[b - dx]] === PH_POWDER))) {
|
||||||
// Read at most 24 rows away: with eight-grain writes, same-colour chunks stay independent.
|
// Depth is read 8 grains inside, at most 24 rows up or down: with the spill's 8-grain writes,
|
||||||
|
// that stays within the neighbouring chunks, so same-colour worker chunks never overlap.
|
||||||
const insideX = x - dx * 8;
|
const insideX = x - dx * 8;
|
||||||
let depth = 0;
|
let depth = 0;
|
||||||
if (insideX >= 0 && insideX < W) {
|
if (insideX >= 0 && insideX < W) {
|
||||||
|
|||||||
Reference in New Issue
Block a user