SOURCE / PINNED RELEASE
Made of little things.
Shiftfield
- Release
- 5a170c505f6b…
- Author-recorded commit
- a2e23e0171aa…
- License
- LICENSE
- Author’s source reference
- nostr://npub16nzc4ql748r2kv9202rgzrt0fmtk4n3tzsjl86yf6ty9agqe9u0qjcc7hc/wss%3A%2F%2Fgit.napplet.soy%2F/n-e4fb6341ccd
Archive hash verified: 66de7623efd91da1…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Minesweeper DX — Phase 1 collision suite.
// Headless, pure logic, no DOM. Node loads the .ts engine module directly
// (strip-types). Every assertion maps 1:1 to a "law" in master spec §2.
import assert from 'node:assert/strict';
import test from 'node:test';
import { MinesweeperEngine, GRID_SIZE, MINE_COUNT, mulberry32 } from '../src/engine.ts';
import { GameEngine, shiftIntervalFor } from '../src/game.ts';
import { ZERO_UPGRADES } from '../src/upgrades.ts';
// Deterministic authoring helper: clear the board, then stamp given cells.
function placeMines(e, cells) {
for (const t of e.tiles) t.hasMine = false;
for (const [x, y] of cells) e.tileAt(x, y).hasMine = true;
}
// 1. MINE_ON_REVEALED — revealing a tile with a mine detonates.
test('mine on a REVEALED tile detonates', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[3, 4]]);
const r = e.reveal(3, 4);
assert.equal(r.result, 'detonated');
assert.equal(e.explosion, 'MINE_ON_REVEALED');
assert.ok(e.isExploded());
});
// 2. MINE_ON_MINE — placing a mine onto an occupied tile detonates.
test('placing a mine on an existing mine detonates', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[0, 0]]);
assert.equal(e.placeMineAt(0, 0), 'MINE_ON_MINE');
assert.ok(e.isExploded());
});
// 3. Toroidal wrap — x=9 shifting east wraps around to x=0.
test('a mine at x=9 shifting east wraps around to x=0', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[9, 5]]);
e.shift(1, 0);
assert.equal(e.tileAt(0, 5).hasMine, true);
assert.equal(e.tileAt(9, 5).hasMine, false);
});
// 4. Toroidal wrap — y=0 shifting north (dy=-1) wraps to y=9.
test('a mine at y=0 shifting north wraps around to y=9', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[4, 0]]);
e.shift(0, -1);
assert.equal(e.tileAt(4, 9).hasMine, true);
});
// 5. Anchor — a mine whose TARGET is FLAGGED is canceled (impassable into).
test('a mine cannot shift into a flagged (locked) tile', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[0, 0]]);
e.flag(1, 0);
const r = e.shift(1, 0);
assert.equal(r.blocked, 1);
assert.equal(r.moved, 0);
assert.equal(e.tileAt(0, 0).hasMine, true);
assert.equal(e.explosion, null);
});
// 6. Anchor — a mine SITTING ON a flagged tile cannot move out of it.
test('a mine on a flagged tile is locked and cannot shift away', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[3, 3]]);
e.flag(3, 3);
const r = e.shift(1, 0);
assert.equal(r.blocked, 1);
assert.equal(r.moved, 0);
assert.equal(e.tileAt(3, 3).hasMine, true);
assert.equal(e.explosion, null);
});
// 7. Impassability both ways — two mines adjacent to a flagged tile: the
// flagged mine can't move OUT, and the neighbor can't move IN. Both
// blocked, no explosion. (The old sequential engine let the neighbor
// "pass through"; the anchor rule forbids this.)
test('no mine may move out of or into a locked tile', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[3, 0], [2, 0]]);
e.flag(3, 0);
const r = e.shift(1, 0);
assert.equal(r.blocked, 2);
assert.equal(r.moved, 0);
assert.equal(e.explosion, null);
assert.equal(e.tileAt(3, 0).hasMine, true);
assert.equal(e.tileAt(2, 0).hasMine, true);
});
// 7a'. Shift collision: an UNFLAGGED mine blocked because its TARGET is
// locked stays in place; a mine behind it would land ON it →
// MINE_ON_MINE, shift aborts, and ShiftResult.collisionCell reports the
// cell the shield absorb must flag (ruling 2026-10-27).
test('a shift collision aborts the shift and reports the collision cell', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[3, 0], [2, 0]]); // (2,0) behind (3,0), moving east
e.flag(4, 0); // (3,0)'s target is locked → (3,0) stays
const r = e.shift(1, 0);
assert.equal(e.explosion, 'MINE_ON_MINE');
assert.equal(r.collisionCell, 0 * 10 + 3);
assert.equal(e.tileAt(2, 0).hasMine, true, 'shift aborted: nobody moved');
assert.equal(e.tileAt(3, 0).hasMine, true);
});
// 7b. MINE_ON_MINE cannot arise from a uniform shift: a toroidal translation
// is a bijection, so two distinct mines always end on distinct tiles.
// Sifting a full shift and checking the invariant confirms it.
test('a uniform shift can never produce mine-on-mine', () => {
const dirs = [[1,0],[-1,0],[0,1],[0,-1],[1,1],[1,-1],[-1,1],[-1,-1]];
for (const seed of [1, 2, 3, 7, 100, 999]) {
const e = new MinesweeperEngine(seed);
for (const [dx, dy] of dirs) e.shift(dx, dy);
const counts = new Map();
for (const t of e.tiles) {
if (t.hasMine) counts.set(t.coord[0] * 10 + t.coord[1], (counts.get(t.coord[0] * 10 + t.coord[1]) ?? 0) + 1);
const n = t.coord[0] * 10 + t.coord[1];
if ((counts.get(n) ?? 0) > 1) throw new Error('mine-on-mine leaked');
}
}
});
// 8. Uniform simultaneity — two adjacent mines both move: no cascade, no
// collision. (Under a sequential shift the second would land on the first's
// empty square target; uniform motion keeps them one cell apart.)
test('adjacent mines shift uniformly, not in cascade', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[3, 0], [4, 0]]);
const r = e.shift(1, 0);
assert.equal(r.moved, 2);
assert.equal(r.blocked, 0);
assert.equal(e.explosion, null);
assert.equal(e.tileAt(4, 0).hasMine, true);
assert.equal(e.tileAt(5, 0).hasMine, true);
assert.equal(e.tileAt(3, 0).hasMine, false);
});
// 9. Flag never detonates.
test('flagging a mine never detonates', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[5, 5]]);
e.flag(5, 5);
assert.equal(e.explosion, null);
assert.equal(e.tileAt(5, 5).hasMine, true);
});
// 10. Reveal on a safe hidden tile succeeds.
test('revealing a hidden tile with no mine is safe', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[9, 9]]);
assert.equal(e.reveal(5, 5).result, 'safe'); // numbered tile: opens alone
});
// 11. Reveal on a locked tile is inert.
test('revealing a flagged tile is a no-op and never detonates', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[5, 5]]);
e.flag(5, 5);
const r = e.reveal(5, 5);
assert.equal(r.result, 'safe');
assert.equal(r.cleared, 0);
assert.equal(e.explosion, null);
});
// 12. A mine revealed, then shifted onto another revealed cell, detonates.
test('a shift that puts a mine onto a REVEALED tile detonates', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[1, 5]]);
e.reveal(2, 5);
e.shift(1, 0);
assert.equal(e.explosion, 'MINE_ON_REVEALED');
});
// 13. Default board: exactly 15 mines, no duplicates, all within bounds.
test('default board has exactly 15 unique mines', () => {
const e = new MinesweeperEngine(1);
assert.equal(e.mineCount(), MINE_COUNT);
assert.equal(MINE_COUNT, 15);
const seen = new Set();
for (const t of e.tiles) {
if (t.hasMine) {
const key = t.coord[0] * 100 + t.coord[1];
assert.ok(!seen.has(key), `duplicate mine at ${t.coord}`);
seen.add(key);
assert.ok(t.coord[0] >= 0 && t.coord[0] < GRID_SIZE);
assert.ok(t.coord[1] >= 0 && t.coord[1] < GRID_SIZE);
}
}
});
// 14. isBoardCleared — true only when every non-mine tile is REVEALED.
test('isBoardCleared requires every non-mine tile to be revealed', () => {
const e = new MinesweeperEngine(1, 1);
placeMines(e, [[0, 0]]);
assert.equal(e.isBoardCleared(), false);
for (const t of e.tiles) {
if (!t.hasMine && t.state === 'HIDDEN') t.state = 'REVEALED';
}
assert.equal(e.isBoardCleared(), true);
});
// 15. Counts: reveals and flags are tracked.
test('revealed and flagged counters track tile states', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[2, 2]]);
e.reveal(0, 0, { flood: false }); // counter test: one tile, no cascade
e.flag(1, 1);
assert.equal(e.revealedCount(), 1);
assert.equal(e.flaggedCount(), 1);
});
// 16. RNG distribution — REGRESSION GUARD for the broken-mulberry32 bug.
// A mulberry32 missing its Math.imul avalanche steps outputs near-perfectly
// correlated consecutive draws. placeMines() draws x then y back-to-back, so
// the broken generator pinned EVERY mine onto two toroidal diagonals
// (y - x mod 10 ∈ {5, 6}) — minesweeper with a guaranteed mine highway.
// Check: across many random seeds, mines spread over ALL 10 diagonals.
test('mines spread uniformly across toroidal diagonals (RNG avalanche)', () => {
const RUNS = 300;
const diag = new Array(10).fill(0);
for (let run = 0; run < RUNS; run++) {
// Spread the seeds hard: mixed high/low, odd/even — not a tidy 0..N sweep.
const seed = (run * 0x9e3779b9 + 0xdeadbeef) >>> 0;
const e = new MinesweeperEngine(seed);
for (const t of e.tiles) {
if (t.hasMine) diag[((t.coord[1] - t.coord[0]) % 10 + 10) % 10]++;
}
}
const total = RUNS * MINE_COUNT;
const expected = total / 10;
// Under the broken generator, 8 of 10 diagonals are EXACTLY zero.
for (let d = 0; d < 10; d++) {
assert.ok(
diag[d] > expected * 0.5,
`diagonal ${d} starved (${diag[d]} vs expected ~${expected}) — RNG lost its avalanche?`,
);
assert.ok(diag[d] < expected * 1.5, `diagonal ${d} overloaded (${diag[d]} vs ~${expected})`);
}
});
// 17. Canonical mulberry32 vectors — pins the reference implementation so a
// future "cleanup" can't silently swap in a weaker scramble again.
test('mulberry32 matches canonical reference outputs', () => {
const r = mulberry32(42);
assert.equal(r(), 0.6011037519201636);
assert.equal(r(), 0.44829055899754167);
assert.equal(r(), 0.8524657934904099);
});
// --- flood reveal (classic zero-region open) -----------------------------
// 18. A numbered tile (adjacent mine count > 0) opens ALONE: no cascade.
test('a numbered reveal opens only the clicked tile', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[5, 5]]);
const r = e.reveal(5, 4); // adjacent to the mine → number
assert.equal(e.adjacentMineCount(5, 4), 1);
assert.equal(r.cleared, 1);
assert.equal(e.revealedCount(), 1);
});
// 19. A zero-adjacent reveal cascades: zero region opens, numbered border
// opens but stops the cascade; mine tiles stay hidden.
test('zero-region reveal floods the open area with a numbered border', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[0, 0]]);
const r = e.reveal(5, 5);
assert.equal(e.adjacentMineCount(5, 5), 0);
// 10×10 torus with one mine at (0,0): 91 zero-adjacent tiles open plus the
// 8-tile numbered border ring; the mine itself is a wall. 99 of 100.
assert.equal(r.cleared, 99);
assert.equal(e.tileAt(0, 0).state, 'HIDDEN'); // wall — flood never detonates
assert.equal(e.tileAt(1, 1).state, 'REVEALED'); // numbered border opens...
assert.equal(e.adjacentMineCount(1, 1), 1); // the lone mine contributes 1
assert.equal(e.explosion, null);
for (const t of e.tiles) if (t.state === 'REVEALED') assert.equal(t.hasMine, false);
});
// 20. Flood stops at the numbered border: a region enclosed by numbers does
// not leak past it. Corner mine → flood from the far side stops at the
// mine's neighbor ring.
test('flood opens the numbered border but does not conduct through it', () => {
const e = new MinesweeperEngine(1, 0);
// Ring of mines around (5,5) leaves (5,5) a numbered tile surrounded by
// walls; flood from (5,5) must open nothing beyond it.
const ring = [];
for (let dx = -1; dx <= 1; dx++)
for (let dy = -1; dy <= 1; dy++)
if (!(dx === 0 && dy === 0)) ring.push([5 + dx, 5 + dy]);
placeMines(e, ring);
const r = e.reveal(5, 5);
assert.equal(e.adjacentMineCount(5, 5), 8);
assert.equal(r.cleared, 1);
});
// 21. FLAGGED tiles are flood walls: they neither open nor conduct.
test('flood cannot open or pass through a flagged tile', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[9, 9]]);
e.flag(0, 5); // sits in the open region (torus: (9,9) neighbors are 0,8..8,0)
const before = e.revealedCount();
const r = e.reveal(5, 5);
assert.ok(r.cleared > 1);
assert.equal(e.tileAt(0, 5).state, 'FLAGGED');
assert.equal(e.explosion, null);
assert.equal(e.revealedCount(), before + r.cleared);
});
// 22. flood:false opts out (ability square reveals use it).
test('reveal with flood disabled opens exactly one tile', () => {
const e = new MinesweeperEngine(1, 0);
placeMines(e, [[9, 9]]);
const r = e.reveal(5, 5, { flood: false });
assert.equal(r.cleared, 1);
assert.equal(e.revealedCount(), 1);
});
// ---------------------------------------------------------------------------
// T1 regression (audit telegraph_depth6_bug_audit_2026-10-28): an exploding or
// collision-aborted shift must STILL re-arm the countdown and reroll the
// telegraph. Before the fix, tickShift() early-returned on MINE_ON_MINE /
// MINE_ON_FIRE BEFORE the reset, leaving nextShiftIn stuck at 0 → negative:
// a full shift fired on EVERY later turn (pulse storm, telegraph locked
// forever, shields drained one per turn).
// ---------------------------------------------------------------------------
// Depth-6 game (first shift depth) on an empty hand-stamped world, 1 shield.
function shieldedDepth6(seed) {
return new GameEngine(
6, undefined, seed,
new MinesweeperEngine(seed, 0),
{ ...ZERO_UPGRADES, shield: 1 },
);
}
test('a shield-absorbed shift collision re-arms the countdown (T1)', () => {
const g = shieldedDepth6(11);
g.flag(0, 9); // setup action: burns board turn 1
placeMines(g.world, [[3, 0], [2, 0]]); // (2,0) behind (3,0), shifting east
g.world.flag(4, 0); // (3,0)'s target locked → collision
g.telegraph = [1, 0];
g.nextShiftIn = 1;
g.flag(0, 9); // remove flag → commit → tickShift fires the colliding shift
assert.equal(g.world.explosion, null, 'the charge absorbed the collision');
assert.equal(g.shieldCharges, 0);
assert.equal(g.world.tileAt(2, 0).hasMine, true, 'shift aborted: nobody moved');
assert.equal(g.world.tileAt(3, 0).state, 'FLAGGED', 'absorb anchored the chain');
assert.equal(g.nextShiftIn, shiftIntervalFor(6), 'countdown re-armed, not stuck ≤ 0');
// The cascade is dead: 20 more turns must not re-fire a shift or explode.
for (let i = 0; i < 20; i++) g.flag(0, 9);
assert.equal(g.world.explosion, null, 'no per-turn shift storm after the absorb');
assert.ok(g.nextShiftIn > 0, 'countdown still positive');
});
test('a shield-absorbed shift-onto-fire re-arms the countdown (T1)', () => {
const g = shieldedDepth6(12);
g.flag(0, 9); // board turn 1 (fire empty → spread no-ops)
placeMines(g.world, [[3, 0]]);
g.fire.add(0 * 10 + 4); // fire exactly where the mine will land
g.telegraph = [1, 0];
g.nextShiftIn = 1;
g.flag(0, 9); // board turn 2 (even → no fire-spread noise this commit)
assert.equal(g.world.explosion, null, 'the charge absorbed MINE_ON_FIRE');
assert.equal(g.shieldCharges, 0);
assert.equal(g.world.tileAt(4, 0).hasMine, true, 'the shift itself landed');
assert.ok(!g.fire.has(4), 'the absorb extinguished the contact fire');
assert.equal(g.nextShiftIn, shiftIntervalFor(6), 'countdown re-armed after absorbed fire contact');
});
