/** * Visual probe for the surface HUD screenshot — PUMP-DRIVEN (like * dev/landing-click.mjs): this box's headless Firefox freezes rAF/timers * after first paint, so the page advances only when the runner's poll * calls __HUD_TICK__(), which steps the engine loop once. It parks on * the surface stage with the upper-left HUD decoded, then HOLDS * (__HUD_SHOT__ = 'surface') so dev/shot-firefox.mjs can grab the frame. * * python3 -m http.server 8131 * node dev/shot-firefox.mjs http://127.0.0.1:8131/dev/surface-hud-shot.html \ * 'window.__HUD_TICK__(); window.__HUD_SHOT__ === "surface" ? window.__HUD_SHOT__ : null' \ * /tmp/surface-hud.png 60000 */ 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'; import { SurfaceScene } from '../js/scenes/SurfaceScene.js'; window.__HUD_SHOT__ = 'booting'; const data = await ConfigLoader.load(); config.init(data); const gameConfig = createGameConfig(); gameConfig.scene = [GameScene, SurfaceScene]; const game = new Phaser.Game(gameConfig); window.game = game; const scene = (key) => { try { return game.scene?.getScene(key) ?? null; } catch { return null; } }; let stage = 0; function tick() { // v4: do NOT step the loop before boot completes — `game.scene` is // created during boot, and pumping loop.step() before that wedges boot. if (!game.loop || !game.scene) return; try { game.loop.step(performance.now()); const gs = scene('GameScene'); const ss = scene('SurfaceScene'); switch (stage) { case 0: // boot: the flight scene is live if (!(gs && gs.ship)) return; gs.startLanding(gs.commsTargetFor(gs.planet)); // the real entry point stage = 1; return; case 1: // the surface scene is up → force the loop stage if (!(ss && ss.sys.isActive())) return; ss.startSurface(); if (!ss.hudName) ss.buildHud(); // headless: the loop clip may never cache if (ss.surfaceVideo && ss.surfaceVideo.video) { ss.surfaceVideo.video.muted = true; // headless autoplay policy ss.surfaceVideo.video.play().catch(() => {}); } stage = 2; return; case 2: // the HUD's decode has settled → HOLD for the screenshot if (ss.hudLines !== null) return; window.__HUD_SHOT__ = 'surface'; window.__HUD_DIAG__ = { gs: gs.sys.getStatus(), ss: ss.sys.getStatus(), ssPhase: ss.phase, loopCached: ss.cache.video.has(`__surf_loop_${ss.planetFrame}`), surfaceVideo: !!ss.surfaceVideo, hudName: ss.hudName?.text, hudLine: ss.hudLine?.text, nameStyle: ss.hudName ? [ss.hudName.style.fontFamily, ss.hudName.style.color, ss.hudName.style.stroke, ss.hudName.style.strokeThickness] : null, lineStyle: ss.hudLine ? [ss.hudLine.style.fontFamily, ss.hudLine.style.color, ss.hudLine.style.stroke] : null, deckIds: (ss.actionBar?.slots ?? []).map((s) => s.id), }; stage = 3; } } catch (err) { window.__HUD_SHOT__ = 'FAILED: ' + String(err?.message ?? err); stage = 99; } } window.__HUD_TICK__ = tick;