164 lines
6.9 KiB
JavaScript
164 lines
6.9 KiB
JavaScript
/**
|
|
* 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)));
|