/** * Dev-only SHOP HUD test: boots the game, lands on the home world, forces * the surface stage, opens the buy/sell screen, and checks the corner * readout (SurfaceScene.buildShopHud) — the main game's own credits + * minerals pair, rebuilt over the shop clip in the upper right: * * - the HUD is built with the shop view (credits line + minerals line * + hold bar, upper right, the GameScene recipe) * - the credits line reads the ship's ledger and sits at the corner * margin; the minerals line stacks below it (topOffset recipe) * - a confirmed trade moves the credits readout (count-up settles on * the new ledger) * - leaving the shop tears the readout down with the shop view * * python3 -m http.server 8125 * node dev/cdp-firefox.mjs http://127.0.0.1:8125/dev/shop-hud.html \ * 'return window.__SHOP_HUD_TEST__;' 60000 */ 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.__SHOP_HUD_TEST__ = { pass: false, error: String(msg), ...extra }; console.error('[shop-hud] FAIL:', msg, extra); }; const step = (name, cond, extra = {}) => { if (!cond) throw new Error(`${name}: ${JSON.stringify(extra).slice(0, 300)}`); window.__STEPS__ = (window.__STEPS__ ?? []).concat(name); }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const waitFor = async (fn, timeoutMs = 3000) => { const t0 = Date.now(); for (;;) { if (fn()) return true; if (Date.now() - t0 > timeoutMs) return false; await sleep(50); } }; 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', true, { frame: gs.planet.sheetFrame }); // The probe drives the trade panel on the home world — lift the TRADE gate // (the 'Special Delivery' capital-landing witness) so Buy/Sell is live. gs.story.capitalLanded = true; // 2 — a known ledger + cargo (something to sell). const goods = config.get('goods.goods', []); const A = goods[0].id; gs.ship.credits = 10000; gs.ship.minerals = 37; gs.ship.cargo.add([A]); step('ledger-seeded', gs.ship.credits === 10000 && gs.ship.minerals === 37); // 3 — the real entry point + the surface stage (skip the clip). gs.startLanding(gs.commsTargetFor(gs.planet)); await sleep(300); const ss = game.scene.getScene('SurfaceScene'); if (!ss) throw new Error('SurfaceScene never registered'); for (let i = 0; i < 120 && ss.sys.getStatus() !== 5; i++) await sleep(250); ss.startSurface(); for (let i = 0; i < 40 && ss.phase !== 'surface'; i++) await sleep(100); if (ss.phase !== 'surface') throw new Error('surface stage never reached (phase=' + ss.phase + ')'); step('surface-stage', true); // 4 — open the shop the real way; headless (no shop clip) falls back to // the direct build (same UI path — the HUD is part of buildShopView). ss.toggleShop(); await sleep(200); if (!ss.shopView) ss.buildShopView(); if (!ss.shopView) throw new Error('no shopView'); step('shop-open', true, { shopMode: ss.shopMode }); // 5 — THE HUD: the corner readout is up with the shop view. const hud = ss.shopHud; step('hud-built', !!hud && !!hud.creditsHud && !!hud.mineralHud, { hud: !!hud, credits: !!hud?.creditsHud, minerals: !!hud?.mineralHud }); const W = game.scale.width; // 1280 const padC = Number(config.get('creditshud.pad', 16)); const padM = Number(config.get('mineralhud.pad', 16)); // The CREDITS line: the ship's ledger, right-anchored at the corner margin // (origin 1,0 — x is the RIGHT edge, y the top). The count-up is a ~420 ms // tween — poll until it settles on the seeded ledger. const cl = hud.creditsHud.label; const wantC = `CREDITS ${gs.ship.credits.toLocaleString('en-US')}`; await waitFor(() => cl.text === wantC); step('credits-line', cl.text === wantC && Math.abs(cl.x - (W - padC)) < 1 && Math.abs(cl.y - padC) < 1, { text: cl.text, x: cl.x, y: cl.y, right: W - padC }); // The MINERALS line: stacks BELOW the credits line (topOffset recipe), // right-anchored the same way — n / CAP like the main game's corner. const ml = hud.mineralHud.label; const expectM = `MINERALS ${gs.ship.minerals} / ${gs.ship.stats.mineralStorage}`; step('minerals-line', ml.text === expectM && Math.abs(ml.x - (W - padM)) < 1, { text: ml.text, want: expectM, x: ml.x, y: ml.y }); step('minerals-below-credits', ml.y > cl.y, { min: ml.y, cred: cl.y }); // The hold BAR: below its label, inside the screen. const mh = hud.mineralHud; step('minerals-bar', Number.isFinite(mh.barX) && Number.isFinite(mh.barY) && mh.barY > ml.y && mh.barX + mh.barW <= W, { barX: mh.barX, barY: mh.barY, barW: mh.barW }); // Rendered ABOVE the shop clip (the video is depth 10; the HUD is 30). step('hud-above-clip', (cl.depth ?? 0) >= 30, { depth: cl.depth }); // 6 — A TRADE: sell the bay, confirm → the credits line moves (the // count-up settles on the new ledger; the minerals line is untouched). const sv = ss.shopView; sv.setTab('trade'); await sleep(600); // the slide-in sv.sellGrid.toggleBay(0); const pv = sv.preview(); const expectCredits = gs.ship.credits + pv.sellTotal; step('trade-confirmed', sv.confirm() === true, { sell: pv.sellTotal }); step('ledger-moved', gs.ship.credits === expectCredits, { credits: gs.ship.credits, want: expectCredits }); // The count-up is a ~420 ms tween — poll until it settles on the new ledger. const wantC2 = `CREDITS ${expectCredits.toLocaleString('en-US')}`; await waitFor(() => cl.text === wantC2); step('hud-follows-trade', cl.text === wantC2, { text: cl.text, want: expectCredits }); step('minerals-untouched', ml.text === expectM, { text: ml.text }); // 7 — LEAVE SHOP: the readout dies with the shop view. ss.leaveShop(); await sleep(100); step('hud-torn-down', ss.shopHud === null && ss.shopView === null, { hud: ss.shopHud, view: ss.shopView }); const kids = ss.sys.displayList?.getChildren?.() ?? []; step('labels-destroyed', !kids.includes(cl) && !kids.includes(ml), { clIn: kids.includes(cl), mlIn: kids.includes(ml) }); window.__SHOP_HUD_TEST__ = { pass: true, steps: window.__STEPS__ ?? [] }; console.info('[shop-hud] PASS:', (window.__STEPS__ ?? []).join(' → ')); })().catch((err) => fail(String(err && err.message || err)));