/** * Dev-only: boot GameScene, open the RESEARCH console, start a SHORT project * and let it COMPLETE LIVE — capturing the completion treatment: the lit * node (accent fill + glow ring + icon halo + stamped seal), the tab * progress pips (filled = researched / hollow = not yet), and the one-shot * ignition mid-flight (ring shells, the fast pulse down the newly-powered * edge, the child flare) with the status strip's PROJECT COMPLETE decode. * * node dev/server.mjs 8090 * node dev/cdp-shot.mjs http://127.0.0.1:8090/dev/research-complete-shot.html \ * /tmp/research-complete.png "window.__RESEARCH_SHOT && window.__RESEARCH_SHOT.ready" \ * 90000 "document.getElementById('report') && (document.getElementById('report').style.display='none')" * * The page sets window.__RESEARCH_SHOT (report + errors) when ready. */ import Phaser from '../js/vendor/phaser.js'; import { config } from '../js/config/Config.js'; import { ConfigLoader } from '../js/config/ConfigLoader.js'; import { createGameConfig } from '../js/config/GameConfig.js'; import { GameScene } from '../js/scenes/GameScene.js'; const data = await ConfigLoader.load(); config.init(data); // Capture console errors + uncaught exceptions for the report. const errors = []; const origErr = console.error.bind(console); console.error = (...a) => { errors.push(a.map(String).join(' ')); origErr(...a); }; window.addEventListener('error', (e) => errors.push(String(e.message))); window.addEventListener('unhandledrejection', (e) => errors.push(`rejection: ${e.reason}`)); const gameConfig = createGameConfig(); gameConfig.scene = [GameScene]; const game = new Phaser.Game(gameConfig); window.game = game; // The report lives OUTSIDE the canvas — a DOM
 the screenshot can
// always read (headless canvases don't have to cooperate).
const report = document.createElement('pre');
report.id = 'report';
report.style.cssText = 'position:fixed;left:10px;top:10px;z-index:9999;max-width:70%;margin:0;padding:8px 12px;font:13px/1.5 monospace;color:#eaf6ff;background:rgba(6,20,16,0.92);border:1px solid #1b3a5a;white-space:pre-wrap;';
document.body.appendChild(report);
const setReport = (lines) => { report.textContent = (Array.isArray(lines) ? lines : [lines]).join('\n'); };
setReport('booting…');

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function waitScene() {
  for (let i = 0; i < 100; i++) {
    const s = game.scene.getScene('GameScene');
    if (s && s.ship && s.researchWindow) return s;
    await sleep(100);
  }
  throw new Error('GameScene never booted');
}

try {
  const s = await waitScene();
  await sleep(800); // let the boot settle (dossier decode, deck flicker)

  // Open the console (the deck RESEARCH button does exactly this).
  s.deckAction('research');
  await sleep(900); // the boot reveal finishes

  // Start a project with a SHORT duration and let it complete live — the
  // completion tick fires _completeResearchFx → the console's ignition.
  s.researchState.start('exploration', 'tether_l2', 500, s.time.now);
  for (let i = 0; i < 100; i++) {
    if (!s.researchState.getActive() && s.researchState.isUnlocked('exploration', 'tether_l2')) break;
    await sleep(50);
  }
  const win = s.researchWindow;
  // The natural ignition already played (and will be over by the time the
  // capture harness lands ~0.5 s after ready). Re-fire it right before the
  // handoff so the capture lands MID-FLIGHT: the soft white ring expanding,
  // the ignition head riding down the edge, the child flare in bloom.
  win.celebrate('exploration', 'tether_l2');
  const burstsLive = win.bursts.length;
  if (burstsLive === 0) errors.push('CELEBRATE MISS — no bursts queued by win.celebrate()');

  const st = s.researchState;
  const entry = win.trees.get('exploration');
  const nodeStates = entry
    ? entry.tree.order.map((id) => `${id}=${win._nodeState(id)}`).join(' ')
    : 'NO TREE';
  const pips = win.tabs
    .map((t) => `${t.id}:${t.pips.map((p) => p.state[0]).join('')}`)
    .join(' ');
  const lines = [
    errors.length === 0 ? 'SMOKE OK — no console errors' : `ERRORS:\n${errors.slice(0, 3).join('\n')}`,
    `window: ${win.openState} · cat=${win.activeCat} · sel=${win.selected ? win.selected.category + '/' + win.selected.id : '—'}`,
    `state: ${st.unlocked.size} unlocked · active=${st.getActive() ? st.getActive().category + '/' + st.getActive().id : '—'}`,
    `nodes: ${nodeStates}`,
    `pips: ${pips}`,
    `bursts: ${win.bursts.length} live (handoff) · ${burstsLive} at celebrate · celebration=${win.celebration ? (win.celebration.played ? 'played' : 'queued') : 'none'}`,
  ];
  setReport(lines);
  window.__RESEARCH_SHOT = { ready: true, lines, errors };
  console.info('research-complete-shot: report painted');
} catch (err) {
  errors.push(`FATAL: ${err.message}`);
  setReport(['FATAL: ' + err.message, ...errors.slice(0, 3)]);
  window.__RESEARCH_SHOT = { ready: true, fatal: String(err.message), errors };
  console.error('research-complete-shot: fatal', err);
}