/** * Dev: screenshot the mining context menu — in the REAL web font — and * report whether the buttons fit inside the panel. * * The menu sizes itself from its buttons (js/ui/MiningPopup.js), and the * buttons measure their label in whatever font is live at construction. * index.html waits for the typefaces before boot (js/main.js → awaitFonts), * so this probe does the same before creating the game. * * python3 -m http.server 8080 * node dev/wdshot.mjs http://localhost:8080/dev/popup-shot.html /tmp/popup.png \ * 'window.__POPUP_SHOT__(); return window.__POPUP_SHOT__RESULT__;' 40000 5000 */ 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); // The game measures its UI text in the web font (Centauri) — wait for it // BEFORE the scenes build any text (same contract as js/main.js). const __fonts = globalThis.document?.fonts; if (__fonts && typeof __fonts.load === 'function') { const fams = ['header', 'body'] .map((k) => config.get(`theme.fonts.${k}.family`)) .filter((f) => typeof f === 'string' && f.length > 0); if (fams.length > 0) { await Promise.race([ Promise.allSettled(fams.map((f) => __fonts.load(`16px "${f}"`).catch(() => {}))), new Promise((r) => setTimeout(r, 2000)), ]); } } // A deterministic galaxy (same clusters every run) + quiet audio in headless. globalThis.__ORBIT_DEV_SEED = 'MINING'; const gameConfig = createGameConfig(); gameConfig.scene = [GameScene]; // GameScene boots first if (typeof Phaser !== 'undefined') Phaser.NoAudioContext = true; const game = new Phaser.Game(gameConfig); window.game = game; /** * Frame the first cluster, open the mining menu on it, pump until the open * tween is done, and report the panel/button geometry (px). */ window.__POPUP_SHOT__ = () => { const g = window.game; const s = g.scene.getScene('GameScene'); if (!s || !Array.isArray(s.asteroidClusters) || s.asteroidClusters.length === 0) { return { ready: false, note: s ? 'no clusters (yet?)' : 'no scene' }; } const c = s.asteroidClusters[0]; const m = c.members[0]; // Park the ship by the rock so the camera frames it, and centre on it. s.ship.stop(); s.ship.x = m.wx + 34; s.ship.y = m.wy - 22; const cam = s.cameras.main; cam.scrollX = m.wx - cam.width / 2; cam.scrollY = m.wy - cam.height / 2; const rock = { cluster: c, member: m, x: m.wx, y: m.wy }; s.openMiningMenu(rock, { x: cam.width / 2, y: m.wy - cam.scrollY }); // Pump until the open tween (150 ms) is done — this box freezes rAF. for (let i = 0; i < 300 && s.miningPopup.alpha < 0.999; i++) g.loop.step(performance.now()); const pop = s.miningPopup; const r = { ready: true, fontStatus: __fonts?.status ?? 'n/a', header: pop.headerText.text, headerW: Math.round(pop.headerText.width), panelW: pop.W, panelH: pop.H, primary: { label: pop.primaryBtn.labelText.text, labelW: Math.round(pop.primaryBtn.labelText.width), btnW: pop.primaryBtn.style.width }, cancel: { label: pop.cancelBtn.labelText.text, labelW: Math.round(pop.cancelBtn.labelText.width), btnW: pop.cancelBtn.style.width }, primaryFits: pop.primaryBtn.style.width + 24 <= pop.W, cancelFits: pop.cancelBtn.style.width + 24 <= pop.W, headerFits: pop.headerText.width <= pop.W - 16, }; window.__POPUP_SHOT__RESULT__ = r; return r; };