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.
// 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();
