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.
// Fix verification: A1 (wall-time integration), A4 (watchdog + DOM fallback),
// A3 (layout-forcing rate). Run from repo root.
import { chromium } from 'playwright';
import { spawn } from 'node:child_process';
const PORT = 4187;
const srv = spawn('npx', ['vite', 'preview', '--host', '127.0.0.1', '--port', String(PORT)], { stdio: 'ignore' });
import net from 'node:net';
for (let i = 0; i < 40; i++) { // poll for preview server (npx cold start)
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({ args: ['--disable-gpu', '--disable-accelerated-2d-canvas'] });
const page = await browser.newPage({ viewport: { width: 1600, height: 1000 }, deviceScaleFactor: 2 });
const logs = [];
page.on('console', m => logs.push(m.text()));
await page.goto(`http://127.0.0.1:${PORT}/`);
const start = async () => {
await page.waitForSelector('#startRunBtn'); await page.click('#startRunBtn');
await page.waitForSelector('.board');
};
const cdp = await page.context().newCDPSession(page);
// ---------- A1: stepWind dt sum vs WALL at 20x ----------
console.log('--- A1: integration sees wall time (20x throttle) ---');
await start();
await page.evaluate(() => {
const g = window.minesweeperDx.board.game;
g.depth = 6; g.nextShiftIn = 9; g.telegraph = [-1, 1];
g.world.mineCount = 20;
window.minesweeperDx.board.refresh(); window.minesweeperDx.sync(g);
});
await page.evaluate(() => {
const fx = window.minesweeperDx.fx;
const proto = Object.getPrototypeOf(fx);
const orig = proto.stepWind;
window.__stepSum = 0; window.__stepN = 0; window.__tFirst = 0; window.__tLast = 0;
proto.stepWind = function (dt) {
window.__stepSum += dt; window.__stepN++;
const t = performance.now();
if (!window.__tFirst) window.__tFirst = t;
window.__tLast = t;
return orig.call(this, dt);
};
});
await cdp.send('Emulation.setCPUThrottlingRate', { rate: 20 });
let wall = await page.evaluate(() => { const a = performance.now(); window.__t0 = a; return a; });
await page.waitForTimeout(4000);
const a1 = await page.evaluate(() => ({ sumDt: window.__stepSum, n: window.__stepN, wall: performance.now() - window.__t0, tFirst: window.__tFirst - window.__t0, tLast: window.__tLast - window.__t0 }));
await cdp.send('Emulation.setCPUThrottlingRate', { rate: 1 });
console.log(`stepWind Σdt/wall = ${(a1.sumDt / a1.wall).toFixed(3)} (was 0.48 at 15fps; want >=0.95) n=${a1.n}`);
console.log(` coverage: loop active ${a1.tFirst.toFixed(0)}..${a1.tLast.toFixed(0)} ms of ${a1.wall.toFixed(0)} | Σdt vs loop span = ${(a1.sumDt / (a1.tLast - a1.tFirst)).toFixed(3)} avgDt=${(a1.sumDt / a1.n).toFixed(1)}ms`);
// ---------- A4: stuck-dead context simulation ----------
console.log('--- A4: watchdog when contextrestored never fires ---');
const a4 = await page.evaluate(async () => {
const app = window.minesweeperDx;
const canvas = document.getElementById('fx');
const ctx = app.fx.ctx2d ?? canvas.getContext('2d');
// stub: make the context READ as dead to the probe (getImageData -> zeros)
const origGet = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = function (...a) {
return { data: new Uint8ClampedArray(4), width: 1, height: 1 };
};
canvas.dispatchEvent(new Event('contextlost'));
await new Promise(r => setTimeout(r, 2600)); // >1.2s arm + >1 probe attempt
const board = document.querySelector('.board');
const deadMid = {
htmlClass: document.documentElement.classList.contains('fx-dead'),
boardTelegraphClass: board.classList.contains('board-shift-telegraph'),
glow: getComputedStyle(board).boxShadow.includes('232, 177, 60') || getComputedStyle(board).boxShadow.includes('rgba(232'),
};
// unstub -> next probe should recover
CanvasRenderingContext2D.prototype.getImageData = origGet;
await new Promise(r => setTimeout(r, 1600));
const after = {
htmlClass: document.documentElement.classList.contains('fx-dead'),
ctx2d: !!app.fx.ctx2d,
boardTelegraphClass: board.classList.contains('board-shift-telegraph'),
};
return { deadMid, after };
});
console.log('stuck-dead window:', JSON.stringify(a4.deadMid));
console.log('after recovery: ', JSON.stringify(a4.after));
console.log('watchdog logs:', logs.filter(l => l.includes('[fx]')).slice(0, 4));
// ---------- A3: layout-forcing rate while wind live (idle, 6x) ----------
console.log('--- A3: LayoutCount/sec with wind live (6x) ---');
await cdp.send('Performance.enable');
const m0 = await cdp.send('Performance.getMetrics');
const t0 = Date.now();
await page.waitForTimeout(4000);
const m1 = await cdp.send('Performance.getMetrics');
const g = (m, n) => m.metrics.find(x => x.name === n)?.value ?? -1;
const secs = (Date.now() - t0) / 1000;
console.log(`LayoutCount/sec=${((g(m1, 'LayoutCount') - g(m0, 'LayoutCount')) / secs).toFixed(2)} (was ~30-40/s forced by per-frame rect fetch) RecalcStyle/sec=${((g(m1, 'RecalcStyleCount') - g(m0, 'RecalcStyleCount')) / secs).toFixed(2)}`);
await browser.close(); srv.kill();
