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/wind-load-probe.mjs
// Probe A: telegraph wind layer under emulated CPU load (user bug 1).
// Measures, at throttle legs 1/6/20x with wind live at depth 6:
//  - rAF dt distribution (what the game sees as "frame time")
//  - wind paint cadence (# of draw() calls + stroke() on #fx per wall-sec; B2 halves it)
//  - apparent dot-phase progress vs wall time (dt-clamp velocity collapse => "freeze")
//  - fx canvas CONTENT inside the board rect (nonzero-alpha samples => "disappear"?)
//  - console '[fx] draw frame threw' occurrences (T2 death log)
import { createServer } from 'node:http';
import { readFile } from 'node:fs/promises';
import { join, extname } from 'node:path';
import { createRequire } from 'node:module';
const require = createRequire('/mnt/Mirror/Projects/Hermes/minesweeper-dx/');
const { chromium } = require('/mnt/Mirror/Projects/Hermes/minesweeper-dx/node_modules/playwright/index.js');

const dist = '/mnt/Mirror/Projects/Hermes/minesweeper-dx/dist';
const MIME = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css' };
async function serveDist() {
  const server = createServer(async (req, res) => {
    const p = req.url === '/' ? '/index.html' : req.url.split('?')[0];
    try {
      const body = await readFile(join(dist, p.slice(1)));
      res.writeHead(200, { 'content-type': MIME[extname(p)] ?? 'application/octet-stream' });
      res.end(body);
    } catch { res.writeHead(403).end('denied'); }
  });
  await new Promise((ok) => server.listen(0, '127.0.0.1', ok));
  return { url: `http://127.0.0.1:${server.address().port}/`, close: () => server.close() };
}
const svc = await serveDist();
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 warns = [];
page.on('console', (m) => { if (m.text().includes('[fx] draw frame threw')) warns.push(m.text().slice(0, 160)); });
page.on('pageerror', (e) => warns.push('PAGEERROR ' + String(e).slice(0, 160)));
await page.goto(svc.url, { waitUntil: 'load' });
await page.waitForFunction(() => window.minesweeperDx !== undefined);

// reach depth 6: clear boards with full safe reveals, advance x6 (pattern from telegraph-cost-probe)
await page.click('#startRunBtn');
for (let d = 0; d < 6; d++) {
  await page.evaluate(() => {
    const g = window.minesweeperDx; const eng = g.board.game;
    for (const t of eng.world.tiles) if (!t.hasMine && t.state === 'HIDDEN') eng.reveal(t.coord[0], t.coord[1]);
    g.board.refresh(); g.sync(eng);
  });
  await page.click('#continueBtn');
}
await page.evaluate(() => { const m = document.querySelector('#shifterIntro'); if (m && !m.hidden) document.querySelector('#shifterIntroOk').click(); });
const wind0 = await page.evaluate(() => {
  const w = window.minesweeperDx.fx.telegraphWind;
  return w ? { dx: w.dx, dy: w.dy, frozen: w.frozen } : null;
});
console.log('wind live at depth 6:', JSON.stringify(wind0));

const cdp = await page.context().newCDPSession(page);

async function leg(rate, ms) {
  await cdp.send('Emulation.setCPUThrottlingRate', { rate });
  await page.waitForTimeout(400); // settle
  const stats = await page.evaluate((ms) => new Promise((res) => {
    const fx = window.minesweeperDx.fx;
    const canvas = document.getElementById('fx');
    const i = { draws: 0, strokes: 0, frames: [], dts: [], phase: [], pixels: [], pxErr: '' };
    const od = fx.draw; fx.draw = (...a) => { i.draws++; return od.apply(fx, a); };
    const ctx = canvas.getContext('2d');
    const os = ctx.stroke; ctx.stroke = function (...a) { if (this.canvas.id === 'fx') i.strokes++; return os.apply(this, a); };
    // in-page master clock: rAF chain for dt + dot-phase sampling
    const t0 = performance.now();
    let last = t0;
    const sumPhase = () => { let s = 0; const w = fx.wind; if (w) for (const d of w.dots) s += d.u + d.v; return s; };
    const p0 = sumPhase();
    const tick = (t) => {
      i.frames.push(t); i.dts.push(t - last); last = t;
      i.phase.push([t - t0, sumPhase() - p0]);
      if (t - t0 < ms) requestAnimationFrame(tick); else finish();
    };
    // board-rect pixel content of the fx canvas (wind visibility) — coarse stride, device px
    const samplePixels = () => {
      try {
        const b = document.querySelector('.board').getBoundingClientRect();
        const dpr = canvas.width / canvas.getBoundingClientRect().width;
        const x = Math.max(0, Math.floor(b.left * dpr)), y = Math.max(0, Math.floor(b.top * dpr));
        const w = Math.min(canvas.width - x, Math.ceil(b.width * dpr));
        const h = Math.min(canvas.height - y, Math.ceil(b.height * dpr));
        if (!w || !h) { i.pixels.push([performance.now() - t0, -1]); return; }
        const img = ctx.getImageData(x, y, w, h);
        let n = 0; const stride = 4 * 37;
        for (let k = 3; k < img.data.length; k += stride) if (img.data[k] !== 0) n++;
        i.pixels.push([performance.now() - t0, n]);
      } catch (e) { i.pxErr = String(e).slice(0, 80); }
    };
    const pm = setInterval(samplePixels, 900);
    function finish() {
      clearInterval(pm);
      fx.draw = od; ctx.stroke = os;
      const dts = i.dts.slice(1).sort((a, b) => a - b);
      const q = (p) => dts[Math.min(dts.length - 1, Math.floor(dts.length * p))];
      const wall = i.frames[i.frames.length - 1] - t0;
      // apparent velocity collapse: sum(min(48,dt)) / sum(dt) over the window
      const clamped = i.dts.slice(1).reduce((s, d) => s + Math.min(48, d), 0);
      const real = i.dts.slice(1).reduce((s, d) => s + d, 0);
      // dot phase over wall time (last sample / first meaningful sample slope proxy)
      const ph = i.phase; const k = Math.floor(ph.length * 0.8);
      res({
        fps: (i.frames.length - 1) / (wall / 1000),
        dt_med: q(0.5), dt_p95: q(0.95), dt_max: dts[dts.length - 1],
        clampFactor: clamped / real,
        drawsPerSec: i.draws / (wall / 1000), strokesPerSec: i.strokes / (wall / 1000),
        phaseNorm: ph[ph.length - 1][1], phaseOverWall: ph[k][1] / (ph[k][0] / 1000),
        pixels: i.pixels, pxErr: i.pxErr,
      });
    }
    requestAnimationFrame(tick);
  }), ms);
  console.log(`leg ${rate}x:`, JSON.stringify(stats));
  await cdp.send('Emulation.setCPUThrottlingRate', { rate: 1 });
}

await leg(1, 6000);
await leg(6, 6000);
await leg(20, 6000);

// T4 path: synthetic contextlost -> wind drawing must pause (ctx2d null, draw no-ops),
// contextrestored -> resumes. Verifies the recovery wiring, not Chrome's firing.
console.log('--- synthetic contextlost/restored ---');
const cl = await page.evaluate(() => new Promise((res) => {
  const fx = window.minesweeperDx.fx;
  const canvas = document.getElementById('fx');
  let draws = 0;
  const od = fx.draw; fx.draw = (...a) => { draws++; return od.apply(fx, a); };
  const c0 = draws;
  canvas.dispatchEvent(new Event('contextlost'));
  setTimeout(() => {
    const lostDraws = draws - c0;
    canvas.dispatchEvent(new Event('contextrestored'));
    setTimeout(() => {
      const backDraws = draws - c0 - lostDraws;
      fx.draw = od;
      res({ lostDraws, backDraws });
    }, 1200);
  }, 1200);
}));
console.log('contextlost leg (draws during lost window / after restore):', JSON.stringify(cl));

console.log('fx-death console logs:', JSON.stringify(warns));
await browser.close();
svc.close();