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/telegraph-probe.mjs
// Depth-6+ telegraph probe: does the wind layer die? errors? cadence?
// Flag-toggle turns cycle the shift countdown with zero death risk.
import { createServer } from 'node:http';
import { readFile } from 'node:fs/promises';
import { join, extname } from 'node:path';
import { chromium } from 'playwright';

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();
const page = await browser.newPage({ viewport: { width: 900, height: 600 } });
const errors = [];
page.on('pageerror', (e) => errors.push(String(e.stack || e).split('\n').slice(0, 3).join(' | ')));
page.on('console', (m) => { if (m.type() === 'error') errors.push('console: ' + m.text().slice(0, 200)); });

await page.goto(svc.url, { waitUntil: 'load' });
await page.waitForFunction(() => window.minesweeperDx !== undefined);

// instrumentation: shadow instance-level draw/step counters + long tasks + rAF dt
await page.evaluate(() => {
  const app = window.minesweeperDx;
  const fx = app.fx;
  window.__probe = { draws: 0, steps: 0, events: [], lastDrawT: 0, gaps: 0 };
  const od = fx.draw.bind(fx);
  fx.draw = (...a) => {
    window.__probe.draws++;
    const t = performance.now();
    if (t - window.__probe.lastDrawT > 500 && window.__probe.lastDrawT > 0) window.__probe.gaps++;
    window.__probe.lastDrawT = t;
    return od(...a);
  };
  const os = fx.step.bind(fx);
  fx.step = (...a) => { window.__probe.steps++; return os(...a); };
  const oe = fx.event.bind(fx);
  fx.event = (ev) => { window.__probe.events.push({ k: ev.kind, t: Math.round(performance.now()), ...(ev.turns !== undefined ? { turns: ev.turns } : {}) }); return oe(ev); };
  try {
    new PerformanceObserver((l) => {
      for (const e of l.getEntries()) if (e.duration > 60) (window.__probe.longTasks ||= []).push(Math.round(e.duration));
    }).observe({ entryTypes: ['longtask'] });
  } catch {}
});

// reach depth 6: clear boards 0..5, Go Deeper each time
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');
}
// close the shifter intro modal if present
await page.evaluate(() => { const m = document.querySelector('#shifterIntro'); if (m && !m.hidden) document.querySelector('#shifterIntroOk').click(); });
await page.waitForTimeout(400);

const depth = await page.evaluate(() => ({
  depth: window.minesweeperDx.board.game.depth,
  telegraph: window.minesweeperDx.board.game.telegraph,
  wind: window.minesweeperDx.fx.telegraphWind,
  nextShiftIn: window.minesweeperDx.board.game.nextShiftIn,
}));
console.log('depth6 state:', JSON.stringify(depth));

// canvas live-pixel sampler over the board rect
async function canvasSig() {
  return page.evaluate(() => {
    const c = document.getElementById('fx');
    const ctx = c.getContext('2d');
    const r = document.querySelector('.board').getBoundingClientRect();
    const dpr = c.width / parseFloat(c.style.width);
    const d = ctx.getImageData(Math.floor(r.left * dpr), Math.floor(r.top * dpr), Math.ceil(r.width * dpr), Math.ceil(r.height * dpr)).data;
    let n = 0, sum = 0;
    for (let i = 3; i < d.length; i += 40) if (d[i]) { n++; sum += d[i]; }
    return { n, sum };
  });
}

// sample liveness across ~90 flag-toggle turns (shiftIntervalFor(6)=28)
const samples = [];
const t0 = Date.now();
let turn = 0;
const cellA = '.cell[data-x="0"][data-y="0"]';
const cellB = '.cell[data-x="1"][data-y="0"]';
while (turn < 90) {
  // two flag actions = 2 turns each round; alternate tiles
  await page.locator(turn % 2 ? cellA : cellB).click({ button: 'right' });
  turn += 1;
  await page.locator(turn % 2 ? cellA : cellB).click({ button: 'right' });
  turn += 1;
  if (turn % 10 === 0) {
    const s = await page.evaluate(() => ({
      t: Math.round(performance.now()),
      turn: window.minesweeperDx.board.game.turn,
      shiftIn: window.minesweeperDx.board.game.nextShiftIn,
      wind: window.minesweeperDx.fx.telegraphWind !== null,
      draws: window.__probe.draws,
      steps: window.__probe.steps,
      fxN: window.minesweeperDx.fx.effects?.length ?? 'n/a',
    }));
    const sig = await canvasSig();
    samples.push({ ...s, sig });
    console.log('sample', JSON.stringify(s), 'canvas', JSON.stringify(sig));
  }
}
await page.waitForTimeout(600);

const final = await page.evaluate(() => ({
  draws: window.__probe.draws,
  steps: window.__probe.steps,
  gaps: window.__probe.gaps,
  wind: window.minesweeperDx.fx.telegraphWind,
  events: window.__probe.events.slice(0, 40),
  eventCount: window.__probe.events.length,
  longTasks: window.__probe.longTasks || [],
  phase: window.minesweeperDx.phase,
  turn: window.minesweeperDx.board.game.turn,
  windLive: window.minesweeperDx.fx.wind !== undefined ? 'n/a' : 'private',
}));
const sigEnd = await canvasSig();
await page.waitForTimeout(700);
const sigLater = await canvasSig();
console.log('final:', JSON.stringify(final));
console.log('canvas end sig:', JSON.stringify(sigEnd), '→ after 700ms idle:', JSON.stringify(sigLater),
  '(identical sigs while wind=true ⇒ wind frozen)');
console.log('page errors:', errors.length ? errors : 'none');

await browser.close();
svc.close();