/** * Dev: the deep-space station VARIANTS (spacestations.png, frames 0..2) * end-to-end — the sprite in-world, the comms panel (the variant label, * REQUEST LANDING live), and the station landing flow (the ss-land / * ss-takeoff clips, the silent flat deck — surface/shop clips are * authored later): * * world — a deep-space station in its system, framed (the variant art * the spread pass assigned it — settlement.stationFrame) * panel — the comms panel on that station (variant label + REQUEST * LANDING enabled) * land — startLanding through the scene's real path (SurfaceScene up, * the station's land clip queued under the station key) * deck — the surface deck on a station (no surface clip — the flat * deck; SHOP is a "no shop" note) * * The starting system (VESTRA — Oribreicor) holds a deep-space station * ("Terminal 11", variant 1 — the smuggler's bar), so all four states are * one system. * * node dev/server.mjs 8080 * node dev/wdshot.mjs http://localhost:8080/dev/station-shot.html /tmp/station-world.png \ * 'return await window.__STATION_SHOT__("world");' 40000 5000 * node dev/wdshot.mjs http://localhost:8080/dev/station-shot.html /tmp/station-panel.png \ * 'return await window.__STATION_SHOT__("panel");' 40000 5000 * node dev/wdshot.mjs http://localhost:8080/dev/station-shot.html /tmp/station-land.png \ * 'return await window.__STATION_SHOT__("land");' 40000 5000 * node dev/wdshot.mjs http://localhost:8080/dev/station-shot.html /tmp/station-deck.png \ * 'return await window.__STATION_SHOT__("deck");' 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'; import { SurfaceScene } from '../js/scenes/SurfaceScene.js'; const data = await ConfigLoader.load(); config.init(data); // The game measures its UI text in the web font — 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)), ]); } } // The deterministic starting system (VESTRA — Oribreicor holds a // deep-space station — variant 1, "Terminal 11"). A different starting // system (and thus station variant) can be pinned per run: // http://localhost:8080/dev/station-shot.html?seed=probe-0 (variant 0) // http://localhost:8080/dev/station-shot.html?seed=probe-25 (variant 2) globalThis.__ORBIT_DEV_SEED = (globalThis.location?.search && new URLSearchParams(globalThis.location.search).get('seed')) || 'VESTRA'; const gameConfig = createGameConfig(); gameConfig.scene = [GameScene, SurfaceScene]; // GameScene boots first if (typeof Phaser !== 'undefined') Phaser.NoAudioContext = true; const game = new Phaser.Game(gameConfig); window.game = game; /** * Drive the station-variant states through the scene's real paths and * report a result object for dev/wdshot.mjs to print. */ window.__STATION_SHOT__ = async (which = 'world') => { const g = window.game; const s = g.scene.getScene('GameScene'); if (!s || !s.systemStations) return { ready: false, note: s ? 'scene not ready (yet?)' : 'no scene' }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); // ---- The station (the system's deep-space station) ------------------- const st = s.systemStations.find((o) => o.kind === 'deepSpaceStation') ?? s.systemStations[0] ?? null; if (!st) return { ready: false, note: 'no free-space station in this system' }; // ---- Camera: frame the station at screen (640, 430) ------------------ const sx = 640; const sy = 430; s.ship.stop(); s.hideHint?.(); const cam = s.cameras.main; if (which === 'world') { // Park the ship just off the rim (it was flying there), lower-right. const rr = st.radius + 70; s.ship.x = st.x + rr * Math.cos(0.5); s.ship.y = st.y + rr * Math.sin(0.5); cam.scrollX = st.x - sx; cam.scrollY = st.y - sy; s.cameraFollowShip = false; for (let i = 0; i < 200; i++) g.loop.step(performance.now()); cam.scrollX = st.x - sx; cam.scrollY = st.y - sy; const r = { ready: true, which, station: st.discoveryName, kind: st.kind, size: st.size, sheetFrame: st.sheetFrame, variantLabel: config.get(`stations.typeLabels.${st.sheetFrame}`), childType: st.list?.[0]?.type ?? st.list?.[0]?.constructor?.name, childTexture: st.list?.[0]?.texture?.key ?? null, childScale: st.list?.[0]?.scale, comms: s.commsTargetFor(st), }; window.__STATION_SHOT__RESULT__ = r; return r; } if (which === 'panel') { s.ship.x = st.x + (st.radius + 70) * Math.cos(0.5); s.ship.y = st.y + (st.radius + 70) * Math.sin(0.5); cam.scrollX = st.x - sx; cam.scrollY = st.y - sy; s.openCommsPanel(st, { worldX: st.x, worldY: st.y, x: sx, y: sy }); 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 = st.x - sx; cam.scrollY = st.y - sy; const cp = s.commsPanel; const r = { ready: true, which, station: st.discoveryName, sheetFrame: st.sheetFrame, name: cp.nameText.text, kindLabel: cp.settledText?.text ?? null, settled: cp.settled, btn1: cp.btn1.text.text, btn1Disabled: cp.btn1.disabled, btn2: cp.btn2.text.text, onScreen: cp.rect ? (() => { const lx = cp.rect.x - cam.scrollX; const ly = cp.rect.y - cam.scrollY; return lx >= -1 && ly >= -1 && lx + cp.rect.w <= s.scale.width + 1 && ly + cp.rect.h <= s.scale.height + 1; })() : false, }; window.__STATION_SHOT__RESULT__ = r; return r; } if (which === 'land') { // The real entry point — the comms panel's own REQUEST LANDING path. const target = s.commsTargetFor(st); if (!target?.isStation || !target?.canLand) { const r = { ready: false, which, target }; window.__STATION_SHOT__RESULT__ = r; return r; } s.startLanding(target); await sleep(300); const ss = g.scene.getScene('SurfaceScene'); const landKey = `__surf_st_land_${ss.planetFrame}`; const r = { ready: true, which, station: st.discoveryName, isStation: ss.isStation, planetFrame: ss.planetFrame, phase: ss.phase, musicSpec: ss.musicSpec, landKey, landCached: ss.cache.video.has(landKey), landVideo: ss.landVideo !== null, gsSleeping: s.sys.isSleeping(), }; window.__STATION_SHOT__RESULT__ = r; return r; } if (which === 'deck') { // Land, then force the deck (the station has NO surface clip — the // flat deck is the station's surface state today). const target = s.commsTargetFor(st); if (!target?.isStation || !target?.canLand) { const r = { ready: false, which, target }; window.__STATION_SHOT__RESULT__ = r; return r; } s.startLanding(target); await sleep(300); const ss = g.scene.getScene('SurfaceScene'); ss.startSurface(); await sleep(300); const ids = (ss.actionBar?.slots ?? []).map((slot) => slot.id); const loopKey = `__surf_st_loop_${ss.planetFrame}`; const r = { ready: true, which, station: st.discoveryName, isStation: ss.isStation, planetFrame: ss.planetFrame, phase: ss.phase, deckSlots: ids, loopKey, loopCached: ss.cache.video.has(loopKey), surfaceVideo: ss.surfaceVideo !== null, hudName: ss.hudName?.text ?? null, hudLine: ss.hudLine?.text ?? null, }; window.__STATION_SHOT__RESULT__ = r; return r; } const r = { ready: false, which, note: 'unknown mode' }; window.__STATION_SHOT__RESULT__ = r; return r; };