orbit/dev/shop-shot.mjs

116 lines
5.1 KiB
JavaScript

/**
* Dev-only SHOP PANEL geometry probe (real browser, via cdp-firefox.mjs):
* boots the game, lands on the home world, forces the surface stage, opens
* the shop, clicks "Buy/Sell", waits for the slide-in, then reports the
* on-screen geometry of the planet HUD text + the SELL/BUY panels + the
* sub-bar — so the panel's top edge vs. the planet text is verifiable
* numerically. Also sets window.__SHOP_SHOT__ so a driver can screenshot
* the moment the numbers are ready.
*
* node dev/server.mjs 8126
* node dev/cdp-firefox.mjs http://127.0.0.1:8126/dev/shop-shot.html \
* 'return window.__SHOP_SHOT__;' 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_SHOT__ = { pass: false, error: String(msg), ...extra };
console.error('[shop-shot] FAIL:', msg);
};
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;
const bounds = (o) => {
if (!o) return null;
const wb = o.worldBounds ?? (o.getBounds && o.getBounds());
if (wb && Number.isFinite(wb.x)) {
return { x: Math.round(wb.x), y: Math.round(wb.y), w: Math.round(wb.width), h: Math.round(wb.height) };
}
return { x: Math.round(o.x ?? 0), y: Math.round(o.y ?? 0) };
};
(async () => {
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');
// Land on the home world + force the surface stage (skip the clip).
const target = gs.commsTargetFor(gs.planet);
gs.startLanding(target);
await sleep(300);
const ss = game.scene.getScene('SurfaceScene');
if (!ss) throw new Error('SurfaceScene never registered');
// Wait for the scene to finish loading (this build: RUNNING = 5) before
// driving it.
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 + ')');
await sleep(300);
// Give the hold a couple of goods so the SELL grid shows filled bays.
const goods = config.get('goods.goods', []);
gs.ship.cargo.add([goods[0].id, goods[1].id]);
// Open the shop the real way; if the shop clip is missing in this
// environment (headless video), build the view directly (same UI path).
ss.toggleShop();
await sleep(200);
if (!ss.shopView) ss.buildShopView();
const sv = ss.shopView;
if (!sv) throw new Error('no shopView');
// Click "Buy/Sell" (reveal the trade panel) and let the slide-in settle.
sv.setTab('trade');
await sleep(900);
const chain = (o) => o ? {
x: o.x, y: o.y, scaleX: o.scaleX, scaleY: o.scaleY,
ox: o.originX, oy: o.originY, dw: o.displayWidth, dh: o.displayHeight,
active: o.active, parent: o.parent ? (o.parent.isContainer ? 'container' : o.parent.type ?? String(o.parent)) : null,
} : null;
const out = {
pass: true,
screen: { w: ss.scale.width, h: ss.scale.height },
camera: { scrollX: ss.cameras?.main?.scrollX, scrollY: ss.cameras?.main?.scrollY, zoom: ss.cameras?.main?.zoom },
topY: sv.topY ?? null,
hudName: bounds(ss.hudName),
hudLine: bounds(ss.hudLine),
sellGrid: bounds(sv.sellGrid),
buyGrid: bounds(sv.buyGrid),
subBar: bounds(sv.subBar),
panel: { w: sv.sellGrid?.width ?? null, h: sv.sellGrid?.height ?? null },
chain: {
shopView: chain(sv),
content: chain(sv._content),
sellGrid: chain(sv.sellGrid),
buyGrid: chain(sv.buyGrid),
},
// Where the panel ACTUALLY renders: the plate + first cell + title are
// real children at known local coords — their world matrices are the truth.
actual: {
sellTitle_world: (() => { const t = sv.sellGrid.title; if (t?.getTopLeft) { const v = t.getTopLeft(undefined, true); return { x: Math.round(v.x), y: Math.round(v.y) }; } return null; })(),
sellCellName_world: (() => { const t = sv.sellGrid.cells?.[0]?.name; if (t?.getTopLeft) { const v = t.getTopLeft(undefined, true); return { x: Math.round(v.x), y: Math.round(v.y) }; } return null; })(),
buyTitle_world: (() => { const t = sv.buyGrid.title; if (t?.getTopLeft) { const v = t.getTopLeft(undefined, true); return { x: Math.round(v.x), y: Math.round(v.y) }; } return null; })(),
net_world: (() => { const t = sv._previewText; if (t?.getTopLeft) { const v = t.getTopLeft(undefined, true); return { x: Math.round(v.x), y: Math.round(v.y) }; } return null; })(),
},
};
window.__SHOP_SHOT__ = out;
console.info('[shop-shot] OK:', JSON.stringify(out));
})().catch((e) => fail(String(e?.message || e)));