/** * Dev: screenshot the comms panel — in the real web font, at real panel * geometry, in the states the player sees it: * * home settled (home world, pinned +20 — full red→green bar, * REQUEST LANDING live) * planet a settled system world with a standing set (the probe * pins it at −8: bar lit through the red run, REQUEST * LANDING grayed — standing ≤ −4) * unsettled a system world with no settlement (LAND + CANCEL, no bar) * station a free-space station (settled, neutral 0 — half bar) * * It calls the scene's real path (GameScene.openCommsPanel with a * hand-built pointer), frames the click point at (640, 430) so the * panel opens UP (the common case), and pumps until the decode + * bar draw-in are done. * * python3 -m http.server 8080 * node dev/wdshot.mjs http://localhost:8080/dev/comms-shot.html /tmp/comms-home.png \ * 'window.__COMMS_SHOT__("home"); return window.__COMMS_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 objects every run) + quiet audio in headless. // 'VESTRA's starting system has everything the panel's states need: // settled worlds, unsettled worlds, and free-space stations. globalThis.__ORBIT_DEV_SEED = 'VESTRA'; 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 chosen object with its click point at screen (640, 430), * open the comms panel through the scene's real path, pump until the * name decode + the reputation bar draw-in are done, and report the * panel's state (name, side, buttons, the lit-mark count). */ window.__COMMS_SHOT__ = (which = 'home') => { const g = window.game; const s = g.scene.getScene('GameScene'); if (!s || !s.commsPanel) return { ready: false, note: s ? 'no commsPanel (yet?)' : 'no scene' }; s.ship.stop(); s.hideHint?.(); // ---- Pick the object ------------------------------------------------- let obj = null; let note = ''; const settledPlanet = () => { for (const p of s.systemPlanets) { const rec = (s.systemContent.planets ?? []).find((r) => r.name === p.discoveryName); if (rec && (s.systemContent.settlements ?? []).some((x) => x.anchor?.type === 'planet' && x.anchor?.ordinal === rec.ordinal)) return p; } return null; }; const unsettledPlanet = () => { for (const p of s.systemPlanets) { const rec = (s.systemContent.planets ?? []).find((r) => r.name === p.discoveryName); if (rec && !(s.systemContent.settlements ?? []).some((x) => x.anchor?.type === 'planet' && x.anchor?.ordinal === rec.ordinal)) return p; } return null; }; if (which === 'home') obj = s.planet; else if (which === 'planet') { obj = settledPlanet(); if (obj) { const t = s.commsTargetFor(obj); if (t.key) s.reputation.set(t.key, -8); // standing ≤ −4 → REQUEST LANDING grayed } note = obj ? '' : 'no settled planet in this system'; } else if (which === 'unsettled') { obj = unsettledPlanet(); note = obj ? '' : 'no unsettled planet in this system'; } else if (which === 'station') { obj = s.systemStations?.[0] ?? null; note = obj ? '' : 'no free-space station in this system'; } if (!obj) return { ready: false, note: note || 'no object' }; // ---- Frame it: click point at screen (640, 430) → panel opens UP ----- const wx = obj.x; const wy = obj.y; const sx = 640; const sy = 430; // Park the ship just off the rim (it was flying there), lower-right. const rr = obj.radius + 70; s.ship.x = wx + rr * Math.cos(0.5); s.ship.y = wy + rr * Math.sin(0.5); const cam = s.cameras.main; cam.scrollX = wx - sx; cam.scrollY = wy - sy; // ---- Open through the scene's real path ------------------------------ s.openCommsPanel(obj, { worldX: wx, worldY: wy, x: sx, y: sy }); // Freeze the camera follow (the shot, not the flight, is under test), // then pump until the decode + bar draw-in + the open tween are done — // this box freezes rAF (same trick as popup-shot). s.cameraFollowShip = false; const done = () => s.commsPanel.alpha >= 0.999 && s.commsPanel.nameDec === null && s.commsPanel.repRevealT0 === null; for (let i = 0; i < 6000 && !done(); i++) g.loop.step(performance.now()); cam.scrollX = wx - sx; cam.scrollY = wy - sy; const cp = s.commsPanel; const r = { ready: true, fontStatus: __fonts?.status ?? 'n/a', which, note, object: obj.discoveryName, side: cp.side, name: cp.nameText.text, settled: cp.settled, H: cp.H, btn1: cp.btn1.text.text, btn1Disabled: cp.btn1.disabled, btn2: cp.btn2.text.text, repValue: cp.repValue.visible ? cp.repValue.text : null, rect: cp.rect && { x: Math.round(cp.rect.x), y: Math.round(cp.rect.y), w: cp.rect.w, h: cp.rect.h }, onScreen: cp.rect ? (() => { const cam2 = s.cameras.main; const lx = cp.rect.x - cam2.scrollX; const ly = cp.rect.y - cam2.scrollY; return lx >= -1 && ly >= -1 && lx + cp.rect.w <= s.scale.width + 1 && ly + cp.rect.h <= s.scale.height + 1; })() : false, }; window.__COMMS_SHOT__RESULT__ = r; return r; };