157 lines
6.1 KiB
JavaScript
157 lines
6.1 KiB
JavaScript
/**
|
||
* 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;
|
||
};
|