orbit/dev/route-shot.mjs

155 lines
6.2 KiB
JavaScript

/**
* Dev-only: boot GameScene, set a DESTINATION (a far star's first planet),
* and open the MAP console's GALAXY tab so a headless screenshot shows:
* · the route (the active SET DESTINATION path) in ORANGE on the lanes,
* · the destination star circled in orange,
* · the compass's route gate (orange, full-sized).
*
* node dev/server.mjs 8083 # static server
* node dev/shot-firefox.mjs \
* "http://127.0.0.1:8083/dev/route-shot.html" \
* "window.__ROUTE_SHOT ? window.__ROUTE_SHOT.ready : null" \
* ROUTE_SHOT.png 40000
*
* The report (top-left) lists console errors + the route state.
*/
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';
const data = await ConfigLoader.load();
config.init(data);
// Capture console errors + uncaught exceptions for the report.
const errors = [];
const origErr = console.error.bind(console);
console.error = (...a) => { errors.push(a.map(String).join(' ')); origErr(...a); };
window.addEventListener('error', (e) => errors.push(String(e.message)));
window.addEventListener('unhandledrejection', (e) => errors.push(`rejection: ${e.reason}`));
const gameConfig = createGameConfig();
gameConfig.scene = [GameScene];
const game = new Phaser.Game(gameConfig);
window.game = game;
// The report lives OUTSIDE the canvas — a DOM <pre> the screenshot can
// always read (headless canvases don't have to cooperate).
const report = document.createElement('pre');
report.id = 'report';
report.style.cssText = 'position:fixed;left:10px;top:10px;z-index:9999;max-width:70%;margin:0;padding:8px 12px;font:13px/1.5 monospace;color:#eaf6ff;background:rgba(6,20,16,0.92);border:1px solid #1b3a5a;white-space:pre-wrap;';
document.body.appendChild(report);
const setReport = (lines) => { report.textContent = (Array.isArray(lines) ? lines : [lines]).join('\n'); };
setReport('booting…');
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function waitScene() {
for (let i = 0; i < 100; i++) {
const s = game.scene.getScene('GameScene');
if (s && s.ship && s.mapWindow) return s;
await sleep(100);
}
throw new Error('GameScene never booted');
}
try {
const s = await waitScene();
await sleep(800); // let the boot settle
// Pick a FAR destination: the furthest star from the current system
// (a multi-hop route, so the orange path is visible on several lanes).
const sysId = s.systemRecord.id;
const records = s.galaxy.records;
const cur = records.find((r) => r.id === sysId);
const others = records.filter((r) => r.id !== sysId);
others.sort((a, b) => {
const da = Math.hypot(a.x - cur.x, a.y - cur.y);
const db = Math.hypot(b.x - cur.x, b.y - cur.y);
return db - da; // farthest first
});
// Use the 3rd-furthest (leave some margin so the route is a real path,
// not a single hop).
const dest = others[2] ?? others[0];
if (!dest) throw new Error('no destination candidate');
// Discover the destination's first planet (so it has an objectId).
const destContent = s.galaxy.contentOf(dest.id);
const destPlanet = (destContent.planets ?? [])[0];
const destObjId = destPlanet?.name ?? null;
// Set the destination (the route is derived from it).
s.setDestination(dest.id, destObjId);
await sleep(400);
// Open the MAP console's GALAXY tab (the route + destination ring are
// drawn there). Use the window's own open() (reveal tween + state).
s.mapWindow._switchMode('galaxy');
s.mapWindow.open();
await sleep(1600); // let the reveal tween + ripple settle + the route paint
// Collect the route state for the report.
const plan = s.routePlan();
const routeKeys = s.routeEdgeKeys();
const snap = s.galaxySnapshot();
const destStar = snap?.systems?.find((x) => x.isDestination);
const routeEdges = (snap?.edges ?? []).filter((e) => e.route);
// --- Now switch to the SYSTEM tab and open the destination system's chart.
// --- Discover the destination object first (so it's drawn on the chart),
// --- then the destination ring should appear around it.
const destSysId = dest.id;
if (destPlanet) {
// Discover the destination planet (so it's drawn on the chart).
let known = s.discovery.bySystem.get(destSysId);
if (!known) { known = new Set(); s.discovery.bySystem.set(destSysId, known); }
s.discovery.check(destSysId, destPlanet.x, destPlanet.y, [{ id: destPlanet.name, x: destPlanet.x, y: destPlanet.y, radius: 100 }]);
}
await sleep(300);
// Switch to the SYSTEM tab and open the destination system's chart.
s.mapWindow._switchMode('system');
s.mapWindow._openSystem(destSysId);
await sleep(1200); // let the chart repaint
// Collect the chart state for the report.
const chartSnap = s.systemChartSnapshotFor(destSysId);
const destInChart = chartSnap?.objects?.find((o) => o.id === chartSnap.destinationId);
window.__ROUTE_SHOT = {
ready: true,
errors,
destSystem: dest.id,
destName: dest.name,
destObjId,
plan: { path: plan?.path, hops: plan?.hops },
routeKeys,
destStar: destStar ? { id: destStar.id, name: destStar.name } : null,
routeEdgeCount: routeEdges.length,
destinationId: snap?.destinationId,
chartDestinationId: chartSnap?.destinationId,
chartDestDiscovered: destInChart?.discovered,
chartDestName: destInChart?.name,
};
const lines = [
`DESTINATION: ${dest.name} (${dest.id})`,
`OBJECT: ${destObjId}`,
`ROUTE: ${plan?.path?.join(' → ') ?? 'null'}`,
`HOPS: ${plan?.hops}`,
`ROUTE EDGES: ${routeEdges.length}`,
`DEST STAR: ${destStar ? `${destStar.name} (${destStar.id})` : 'null'}`,
`CHART DEST: ${chartSnap?.destinationId ?? 'null'}`,
`CHART DEST DISCOVERED: ${destInChart?.discovered}`,
`CHART DEST NAME: ${destInChart?.name}`,
`ERRORS: ${errors.length ? errors.join(' | ') : 'none'}`,
];
setReport(lines);
console.log('ROUTE SHOT READY', JSON.stringify(window.__ROUTE_SHOT, null, 2));
} catch (e) {
const lines = ['FATAL: ' + (e?.message ?? e), ...errors];
setReport(lines);
window.__ROUTE_SHOT = { ready: true, errors: [...errors, String(e?.message ?? e)] };
}