orbit/dev/surface-hud.mjs

115 lines
5.4 KiB
JavaScript

/**
* Dev-only SURFACE HUD test: boots into the GameScene, lands on the home
* world, and checks the new upper-left HUD (SurfaceScene.buildHud):
*
* - the launch payload carries the world's type + tether level
* - the name text: Ethnocentric (header face), the menu title's shade
* (data/menu.json → colors.title), a black stroke behind it
* - the line below: "TYPE, TETHER LEVEL N" in the body face (Centauri)
* - both lines decode in (the shared scramble) and settle on the
* final strings
* - destroyHud() tears it down (take-off/shutdown path)
*
* python3 -m http.server 8124
* node dev/cdp-firefox.mjs http://127.0.0.1:8124/dev/surface-hud.html \
* 'return window.__SURFACE_HUD_TEST__;'
*/
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 fail = (msg, extra = {}) => {
window.__SURFACE_HUD_TEST__ = { pass: false, error: msg, diag: extra };
console.error('[surface-hud] FAIL:', msg, extra);
};
window.__FLOW_STARTED__ = true;
const step = (name) => {
window.__STEPS__ = (window.__STEPS__ ?? []).concat(name);
};
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const data = await ConfigLoader.load();
config.init(data);
const gameConfig = createGameConfig();
gameConfig.scene = [GameScene, SurfaceScene];
const game = new Phaser.Game(gameConfig);
window.game = game;
(async () => {
// 1 — the flight scene is live (v4 boots asynchronously).
let gs = null;
for (let i = 0; i < 300; i++) {
gs = game.scene?.getScene('GameScene') ?? null;
if (gs && gs.ship) break;
await sleep(100);
}
if (!gs || !gs.ship) throw new Error('GameScene never came up');
step('boot');
// 2 — the tether lookup the launch payload relies on.
const target = gs.commsTargetFor(gs.planet);
const level = gs.tetherLevelFor(target.name);
if (!(level === gs.tetherField.tethers.find((t) => t.label === target.name)?.level))
throw new Error('tetherLevelFor mismatch', { level, tethers: gs.tetherField.tethers });
if (gs.tetherLevelFor('NOT A PLANET') !== 0) throw new Error('unknown world must be 0');
step('tether-level-lookup');
// 3 — the real entry point: the launch payload carries type + level.
gs.startLanding(target);
await sleep(250);
const ss = game.scene.getScene('SurfaceScene');
if (!ss || !ss.sys.isActive()) throw new Error('SurfaceScene did not launch');
if (ss.planetType !== target.kindLabel) throw new Error('planetType', { got: ss.planetType, want: target.kindLabel });
if (ss.tetherLevel !== level) throw new Error('tetherLevel', { got: ss.tetherLevel, want: level });
step('launch-payload', { name: ss.planetName, type: ss.planetType, level: ss.tetherLevel });
// 4 — the HUD exists: auto-built with the loop clip (startSurface), or
// built directly when headless never cached the video.
ss.startSurface();
await sleep(50);
if (!ss.hudName) ss.buildHud();
if (!ss.hudName || !ss.hudLine || !ss.hudLines) throw new Error('HUD missing after buildHud');
step('hud-built');
// 5 — the decode settles on the final strings (poll a few seconds).
const nameFinal = String(ss.planetName).toUpperCase();
const lineFinal = `${ss.planetType}, Tether Level ${ss.tetherLevel}`;
let settled = false;
for (let i = 0; i < 200 && !settled; i++) {
await sleep(100);
if (ss.hudLines === null && ss.hudName.text === nameFinal && ss.hudLine.text === lineFinal) settled = true;
}
if (!settled) throw new Error('decode did not settle', {
name: ss.hudName?.text, line: ss.hudLine?.text, hudLines: !!ss.hudLines,
});
step('decode-settled', { name: ss.hudName.text, line: ss.hudLine.text });
// 6 — the styling contract.
const nStyle = ss.hudName.style;
const lStyle = ss.hudLine.style;
if (!/Ethnocentric/.test(nStyle.fontFamily)) throw new Error('name font', { got: nStyle.fontFamily });
if (!/Centauri/.test(lStyle.fontFamily)) throw new Error('line font', { got: lStyle.fontFamily });
const titleCss = '#' + (config.get('menu.colors.title', '#eaf6ff').slice(1) || 'eaf6ff');
if (nStyle.color.toLowerCase() !== titleCss) throw new Error('name shade', { got: nStyle.color, want: titleCss });
if (nStyle.stroke.toLowerCase() !== '#000000' || nStyle.strokeThickness !== 4)
throw new Error('name stroke', { got: [nStyle.stroke, nStyle.strokeThickness] });
if (lStyle.stroke.toLowerCase() !== '#000000') throw new Error('line stroke', { got: lStyle.stroke });
if (!(ss.hudName.x >= 16 && ss.hudName.y >= 12)) throw new Error('corner placement', { x: ss.hudName.x, y: ss.hudName.y });
if (!(ss.hudLine.y > ss.hudName.y)) throw new Error('line sits below the name');
step('styling');
// 7 — teardown (the take-off/shutdown path).
ss.destroyHud();
if (ss.hudName !== null || ss.hudLine !== null || ss.hudLines !== null) throw new Error('destroyHud left state');
step('teardown');
window.__SURFACE_HUD_TEST__ = { pass: true, steps: window.__STEPS__ };
console.info('[surface-hud] PASS:', window.__STEPS__.join(' → '));
})().catch((err) => fail(String(err && err.message || err), {
gs: game.scene?.getScene('GameScene')?.sys.getStatus?.(),
ss: game.scene?.getScene('SurfaceScene') ? { phase: game.scene.getScene('SurfaceScene').phase } : null,
}));