orbit/dev/scout-overlap.mjs

133 lines
5.8 KiB
JavaScript

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