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/load-accum-probe.mjs
// Probe B: long-session accumulation ("gets slower over time", user bug 2).
// 10 boards at 6x CPU throttle; per-board: JS heap, animation count, DOM nodes,
// idle rAF dt stats, fx loop state. Plus CDP trace Paint/Layout counts for an
// idle window and a shift-commit window.
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) => { const t = m.text(); if (t.includes('[fx]')) warns.push(t.slice(0, 140)); });
page.on('pageerror', (e) => warns.push('PAGEERROR ' + String(e).slice(0, 140)));
await page.goto(svc.url, { waitUntil: 'load' });
await page.waitForFunction(() => window.minesweeperDx !== undefined);
await page.click('#startRunBtn');

const cdp = await page.context().newCDPSession(page);
await cdp.send('Runtime.enable');
await cdp.send('Emulation.setCPUThrottlingRate', { rate: 6 });

async function snapshot(label) {
  const heap = await cdp.send('Runtime.getHeapUsage');
  const s = await page.evaluate(() => new Promise((res) => {
    const g = window.minesweeperDx;
    const ts = []; let last = performance.now(); let n = 0;
    const tick = (t) => { ts.push(t - last); last = t; if (++n < 40) requestAnimationFrame(tick); else {
      ts.sort((a, b) => a - b);
      res({
        dtMed: ts[20], dtP95: ts[38], over33: ts.filter((x) => x > 33).length,
        anims: document.getAnimations().length,
        nodes: document.getElementsByTagName('*').length,
        depth: g.board.game.depth,
        fxLoop: g.fx.raf > 0,
      });
    } } ;
    requestAnimationFrame(tick);
  }));
  console.log(label, JSON.stringify({ heapMB: +(heap.usedSize / 1e6).toFixed(1), totalMB: +(heap.totalSize / 1e6).toFixed(1), ...s }));
}

async function traceWindow(label, ms, fireShift) {
  const chunks = [];
  const onData = (e) => chunks.push(e.value);
  cdp.on('Tracing.dataCollected', onData);
  await cdp.send('Tracing.start', { includedCategories: ['devtools.timeline', 'cc'], transferMode: 'ReportEvents' });
  if (fireShift) {
    await page.evaluate(() => {
      const g = window.minesweeperDx; const eng = g.board.game;
      // walk the countdown into the warning turns, then let the shift land
      let t = 0;
      while (eng.nextShiftIn > 0 && t < 40) { if (t % 2) eng.flag(0, 0); else eng.flag(1, 0); t++; }
      g.board.refresh(); g.sync(eng);
    });
  }
  await page.waitForTimeout(ms);
  await cdp.send('Tracing.end');
  await new Promise((r) => cdp.once('Tracing.tracingComplete', r));
  cdp.off('Tracing.dataCollected', onData);
  const count = {};
  for (const c of chunks) for (const ev of c) if (ev.ph === 'X') count[ev.name] = (count[ev.name] ?? 0) + 1;
  const pick = (n) => count[n] ?? 0;
  console.log(label, JSON.stringify({
    Paint: pick('Paint'), RasterTask: pick('RasterTask'), Layout: pick('Layout'),
    UpdateLayoutTree: pick('UpdateLayoutTree'), PrePaint: pick('PrePaint'),
    Commit: pick('Commit'), time: pick('time'),
  }));
}

for (let b = 1; b <= 10; b++) {
  // clear the board (full safe reveal) then advance — the cost-probe pattern
  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);
  });
  try {
    await page.waitForFunction(() => {
      const c = document.querySelector('#continueBtn');
      return !!c && c.offsetParent !== null && getComputedStyle(c).visibility !== 'hidden';
    }, null, { timeout: 8000 });
  } catch {
    const st = await page.evaluate(() => ({
      scrHidden: document.querySelector('#decisionScreen')?.hidden,
      title: document.querySelector('#summaryTitle')?.textContent,
      cause: document.querySelector('#summaryCause')?.textContent,
      hiddenTiles: window.minesweeperDx.board.game.world.tiles.filter((t) => t.state === 'HIDDEN').length,
    }));
    console.log(`board ${b} BLOCKED:`, JSON.stringify(st));
    // run ended (summary) — restart via menu to keep the accumulation test going
    const restarted = await page.evaluate(() => {
      const s = document.querySelector('#summaryScreen');
      if (s && !s.hidden) { document.querySelector('#summaryMenuBtn').click(); return true; }
      return false;
    });
    if (!restarted) break;
    console.log('  run ended — restarted');
    await page.waitForTimeout(700);
    await page.evaluate(() => document.querySelector('#startRunBtn')?.click());
    await page.waitForTimeout(900);
    continue;
  }
  await page.evaluate(() => document.querySelector('#continueBtn').click());
  await page.evaluate(() => { const m = document.querySelector('#shifterIntro'); if (m && !m.hidden) document.querySelector('#shifterIntroOk').click(); });
  await page.waitForTimeout(1500); // reveal wave + celebrate drains
  await snapshot(`board ${b} idle `);
  if (b === 2 || b === 6 || b === 14) await traceWindow(`  trace idle b${b}   `, 1500, false);
  if (b === 7) await traceWindow(`  trace shift b${b}  `, 1500, true);
}
console.log('fx console:', JSON.stringify(warns));
await browser.close();
svc.close();