/** * 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)));