/** * Visual probe for a screenshot + numeric overlap report (headless brave, * via dev/cdp-shot.mjs): boots the game, lands, opens the shop, opens the * trade scout on a BUY row that has remote exits, and STOPS THERE (the * scout panel is the shot's subject — no exit tapped, no dialog). It * measures every text box in the panel (local coords) and reports any * pairs whose bounds intersect, so text overlap is verifiable numerically * and visually. * * /opt/brave.com/brave/brave --headless --disable-gpu --remote-debugging-port=9333 about:blank & * python3 -m http.server 8129 * node dev/cdp-shot.mjs http://127.0.0.1:8129/dev/scout-overlap.html \ * /tmp/scout-overlap.png 'window.__SCOUT_OVERLAP__ && true' 90000 */ 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'; import { scoutGood } from '../js/economy/TradeScout.js'; // A fixed galaxy seed (GameScene.ensureGalaxy honors it) so before/after // runs of this probe compare the SAME system names / world names. globalThis.__ORBIT_DEV_SEED = 'scout-overlap-probe'; 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 () => { 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) { window.__SCOUT_OVERLAP__ = { pass: false, error: 'no GameScene' }; return; } // 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; // A known ledger + cargo (something to sell), and the run has ENTERED // every system — the scout then has remote exits to offer. const goods = config.get('goods.goods', []); gs.ship.credits = 10000; gs.ship.cargo.add([goods[0].id]); const sysId = gs.systemRecord.id; for (const id of gs.galaxy.byId.keys()) gs.visitedSystems.add(id); gs.startLanding(gs.commsTargetFor(gs.planet)); await sleep(300); const ss = game.scene.getScene('SurfaceScene'); if (!ss) { window.__SCOUT_OVERLAP__ = { pass: false, error: 'no SurfaceScene' }; return; } 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); await sleep(900); // the planet HUD decode ss.toggleShop(); await sleep(200); if (!ss.shopView) ss.buildShopView(); const sv = ss.shopView; if (!sv) { window.__SCOUT_OVERLAP__ = { pass: false, error: 'no shopView' }; return; } sv.setTab('trade'); await sleep(900); // the slide-in // Open the scout on a BUY row that has a REMOTE profitable exit — the // fullest panel state (chips + 3 exit rows). Do NOT tap an exit row. const visited = [...gs.visitedSystems]; let row = null, exits = null; for (const r of sv.buyList.rows) { const d = scoutGood(r.id, { galaxy: gs.galaxy, systemId: sysId, cls: sv.cls, buyUnit: r.price, visited, topN: 3 }); if (!d) continue; const rem = d.exits.find((e) => e.systemId !== sysId && e.profit > 0); if (rem) { row = r; exits = d.exits; break; } } if (!row || !exits) { window.__SCOUT_OVERLAP__ = { pass: false, error: 'no remote exit to open' }; return; } sv._onRowTap(row); await sleep(400); const panel = sv._scout; if (!panel) { window.__SCOUT_OVERLAP__ = { pass: false, error: 'no scout panel' }; return; } // Collect every text box in the panel (container-local coords). In this // Giedi build the panel's own `_content` array is the flat list of // per-show objects (text/graphics/chips). Convert origin-anchored x/y to // the true top-left (left = x - origin.x*width) so right-/center-aligned // text (price, profit, footer) measures correctly. const texts = []; const isText = (o) => o && o.text != null && typeof o.width === 'number'; const take = (o) => { if (isText(o)) { const ox = o.originX ?? o.origin?.x ?? 0, oy = o.originY ?? o.origin?.y ?? 0; texts.push({ text: String(o.text).slice(0, 60), left: Math.round(o.x - ox * o.width), top: Math.round(o.y - oy * o.height), w: Math.round(o.width), h: Math.round(o.height), }); } else for (const c of o?.list ?? o?.children?.list ?? []) take(c); // containers (chips) }; for (const o of panel._content ?? []) take(o); texts.sort((a, b) => (a.top - b.top) || (a.left - b.left)); // Pairwise overlap of the true text boxes (ignore sub-pixel kissing). const overlaps = []; for (let i = 0; i < texts.length; i++) { for (let j = i + 1; j < texts.length; j++) { const a = texts[i], b = texts[j]; const ix = Math.min(a.left + a.w, b.left + b.w) - Math.max(a.left, b.left); const iy = Math.min(a.top + a.h, b.top + b.h) - Math.max(a.top, b.top); if (ix > 2 && iy > 2) overlaps.push({ a: a.text, b: b.text, ix: Math.round(ix), iy: Math.round(iy) }); } } window.__SCOUT_OVERLAP__ = { pass: overlaps.length === 0, panel: { width: panel.width, height: panel.height, rows: panel._exitRows?.length }, good: row?.id, exits: exits.map((e) => ({ sys: e.systemName, world: e.objectName, cls: e.cls, price: e.price, profit: e.profit, hops: e.hops })), texts, overlaps, }; console.info('[scout-overlap] texts=' + texts.length + ' overlaps=' + overlaps.length); })().catch((err) => { window.__SCOUT_OVERLAP__ = { pass: false, error: String(err && err.message || err) }; });