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.
// C2 regression: fx canvas must run board-sized in the wind-only steady
// state, flip to viewport while a burst is live, and flip back after —
// with wind pixels still landing in the right place either way.
import { chromium } from 'playwright';
import { spawn } from 'node:child_process';
import net from 'node:net';
const PORT = 4190;
const srv = spawn('npx', ['vite', 'preview', '--host', '127.0.0.1', '--port', String(PORT)], {
cwd: new URL('..', import.meta.url).pathname, 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();
const page = await browser.newPage({ viewport: { width: 1600, height: 1000 } });
const errs = [];
page.on('pageerror', (e) => errs.push(String(e)));
await page.goto(`http://127.0.0.1:${PORT}/`);
await page.click('#startRunBtn');
await page.waitForSelector('.board');
// arm wind (depth-6-equivalent telegraph; QA hook, never game logic)
await page.evaluate(() => window.minesweeperDx.fx.setTelegraph({ dx: 1, dy: 0, urgency: 1, frozen: false }));
await page.waitForFunction(() => window.minesweeperDx.fx.fullCanvas === false, null, { timeout: 4000 });
await page.waitForTimeout(300); // a few steady frames at the new geometry
const steady = await page.evaluate(() => {
const fx = window.minesweeperDx.fx;
const cv = document.querySelector('#fx');
const cb = cv.getBoundingClientRect();
const r = document.querySelector('.board').getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
// wind pixels must land INSIDE the canvas region (mapping sanity):
const ctx = cv.getContext('2d');
const img = ctx.getImageData(0, 0, cv.width, cv.height).data;
let sig = 0;
for (let i = 3; i < img.length; i += 4004) sig += img[i];
return {
full: fx.fullCanvas,
canvasPx: cv.width, viewportPx: Math.floor(window.innerWidth * dpr),
boxedCoversBoard: cb.left <= r.left && cb.top <= r.top && cb.right >= r.right && cb.bottom >= r.bottom,
boxedSmaller: cv.width < window.innerWidth * dpr && cv.height < window.innerHeight * dpr,
windPixels: sig > 0,
};
});
console.log('steady wind:', JSON.stringify(steady));
// boom -> full mode
await page.evaluate(() => window.minesweeperDx.fx.event({ kind: 'boom', cell: '5,5', cause: 'qa', shielded: false }));
await page.waitForTimeout(150);
const burst = await page.evaluate(() => {
const fx = window.minesweeperDx.fx;
const cv = document.querySelector('#fx');
const dpr = window.devicePixelRatio || 1;
return { full: fx.fullCanvas, fullViewport: cv.width === Math.floor(window.innerWidth * dpr) };
});
console.log('burst:', JSON.stringify(burst));
// after the burst tail drains -> back to board box
await page.waitForTimeout(2500);
const after = await page.evaluate(() => {
const fx = window.minesweeperDx.fx;
const cv = document.querySelector('#fx');
const dpr = window.devicePixelRatio || 1;
return {
full: fx.fullCanvas,
boxed: cv.width < window.innerWidth * dpr && cv.height < window.innerHeight * dpr,
};
});
console.log('after tail:', JSON.stringify(after));
const ok =
steady.full === false && steady.boxedCoversBoard && steady.boxedSmaller && steady.windPixels &&
burst.full === true && burst.fullViewport &&
after.full === false && after.boxed && errs.length === 0;
console.log(ok ? 'C2 PASS' : 'C2 FAIL', 'pageerrors:', errs.length);
await browser.close();
srv.kill();
process.exit(ok ? 0 : 1);
