Back to Shiftfield
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.

tests/residue-probe.mjs
// Residue probe: fire each flash-capable event at d6 (wind live), wait for
// effects to end, then sample canvas pixels OUTSIDE the wind-erase margin
// (rect ± 17..70 px) for leftover alpha. Run from repo root.
import { chromium } from 'playwright';
import { spawn } from 'node:child_process';
import net from 'node:net';

const PORT = 4189;
const srv = spawn('npx', ['vite', 'preview', '--host', '127.0.0.1', '--port', String(PORT)], { stdio: 'ignore' });
for (let i = 0; i < 40; i++) {
  const up = await new Promise(res => { const s = net.connect(PORT, '127.0.0.1'); s.once('connect', () => { s.destroy(); res(true); }); s.once('error', () => res(false)); });
  if (up) break; await new Promise(r => setTimeout(r, 500));
}
const browser = await chromium.launch(); // GPU-ish canvas like the user's session
const page = await browser.newPage({ viewport: { width: 1600, height: 1000 }, deviceScaleFactor: 2 });
const errs = [];
page.on('pageerror', e => errs.push(String(e)));
await page.goto(`http://127.0.0.1:${PORT}/`);
await page.waitForSelector('#startRunBtn'); await page.click('#startRunBtn');
await page.waitForSelector('.board');

const sampleRing = () => page.evaluate(() => {
  const cv = document.querySelector('#fx');
  const cbox = cv.getBoundingClientRect(); // C2: canvas may be board-boxed, not viewport
  const dpr = cv.width / cbox.width;
  const ctx = cv.getContext('2d');
  const r = document.querySelector('.board').getBoundingClientRect();
  const W = cv.width, H = cv.height;
  const L = Math.max(0, Math.floor((r.left - 70 - cbox.left) * dpr)), R = Math.min(W, Math.ceil((r.right + 70 - cbox.left) * dpr));
  const T = Math.max(0, Math.floor((r.top - 70 - cbox.top) * dpr)), B = Math.min(H, Math.ceil((r.bottom + 70 - cbox.top) * dpr));
  const img = ctx.getImageData(L, T, R - L, B - T);
  const il = Math.round(17 * dpr), ir = Math.round((R - L) - 17 * dpr); // ring band inside sampled img
  const it = Math.round(17 * dpr), ib = Math.round((B - T) - 17 * dpr);
  let worst = 0, wx = 0, wy = 0, wr = 0, wg = 0, wb = 0;
  for (let y = 0; y < B - T; y++) {
    const inYBand = y < it || y >= ib;
    for (let x = 0; x < R - L; x++) {
      const inXBand = x < il || x >= ir;
      if (!(inXBand || inYBand)) continue; // ring margin 17..70 only
      const i = (y * (R - L) + x) * 4;
      const a = img.data[i + 3];
      if (a > worst) { worst = a; wx = x; wy = y; wr = img.data[i]; wg = img.data[i + 1]; wb = img.data[i + 2]; }
    }
  }
  return { worst, wx, wy, color: [wr, wg, wb] };
});

const fire = (payloads) => page.evaluate((ps) => {
  const fx = window.minesweeperDx.fx;
  for (const p of ps) fx.onEvent(p);
}, payloads);

async function trial(label, payloads, settleMs = 1400) {
  await page.waitForTimeout(300);
  const before = await sampleRing();
  await fire(payloads);
  await page.waitForTimeout(settleMs); // ttl max ~1.1s
  const after = await sampleRing();
  const flag = after.worst > before.worst + 8 ? 'STUCK-COLOR!' : 'clean';
  console.log(`${label.padEnd(34)} ring max alpha: before=${before.worst} after=${after.worst} ${flag}${after.worst > 8 ? ` color=${after.color} at(${after.wx},${after.wy})` : ''}`);
}

// depth 6 so wind keeps the loop alive between frames (the eraser that
// should remove everything outside its own rect+16 is partial, not full)
await page.evaluate(() => {
  const g = window.minesweeperDx.board.game;
  g.depth = 6; g.nextShiftIn = 6; g.telegraph = [1, 1]; g.world.mineCount = 15;
  window.minesweeperDx.board.refresh(); window.minesweeperDx.sync(g);
});

await trial('shift-warning diag (1,1)', [{ type: 'shift-warning', dx: 1, dy: 1, turns: 1 }]);
await trial('shift-warning diag (-1,1)', [{ type: 'shift-warning', dx: -1, dy: 1, turns: 1 }]);
await trial('shift commit sweepBurst', [{ type: 'shift', dx: 1, dy: -1 }]);
await trial('shielded blue flash', [{ type: 'boom', cell: '0,0', shielded: true, cause: 'SHIELDED' }]);
await trial('full boom (chroma)', [{ type: 'boom', cell: '0,0', shielded: false, cause: 'MINE' }]);
await trial('warn+boom same turn', [
  { type: 'shift-warning', dx: 1, dy: 1, turns: 1 },
  { type: 'boom', cell: '0,0', shielded: false, cause: 'MINE' },
]);
await trial('boom then warn tail', [
  { type: 'boom', cell: '0,0', shielded: false, cause: 'MINE' },
]);
// second warning while first still alive (overlapping bbox bookkeeping)
await page.evaluate(() => window.minesweeperDx.fx.onEvent({ type: 'shift-warning', dx: 1, dy: 1, turns: 1 }));
await page.waitForTimeout(150);
await page.evaluate(() => window.minesweeperDx.fx.onEvent({ type: 'shift-warning', dx: -1, dy: -1, turns: 2 }));
await page.waitForTimeout(1400);
const overlap = await sampleRing();
console.log(`overlapping warnings ring: ${JSON.stringify(overlap)} ${overlap.worst > 8 ? 'STUCK-COLOR!' : 'clean'}`);
console.log('page errors:', errs.length ? errs : 'none');
await browser.close(); srv.kill();