orbit/dev/build-check.mjs

148 lines
7.2 KiB
JavaScript

/**
* Dev-only: the BUILD system self-check — plays the real flow
* (menu → NEW GAME → land on the home world → open the Build console)
* and paints a plain-text report (top-left, outside the canvas) that
* answers "why is my home world's Tether - Level 1 not BUILT?":
*
* 1. Is the browser running the current JS? (stale-module probe —
* a hard cache-bypass reload is the usual cure)
* 2. Which world is home, and what does the build state say about it?
* 3. What tether does the home world hold (L2's planet gate)?
* 4. The console's actual row states — before and after the
* tether_l2 research — L1 must read BUILT; L2 must flip from
* LOCKED (research) to BUILDABLE (home already holds a L1 tether).
*
* node dev/slow-server.mjs 8080 # or any static server on the repo
* → open http://127.0.0.1:8080/dev/build-check.html
*
* Headless screenshot:
* node dev/cdp-shot.mjs "http://127.0.0.1:8080/dev/build-check.html" \
* out.png "document.getElementById('report').textContent.includes('SELF-CHECK DONE')"
*/
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 { MenuScene } from '../js/scenes/MenuScene.js';
import { GameScene } from '../js/scenes/GameScene.js';
import { SurfaceScene } from '../js/scenes/SurfaceScene.js';
const data = await ConfigLoader.load();
config.init(data);
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 game = new Phaser.Game({ ...createGameConfig(), scene: [MenuScene, GameScene, SurfaceScene] });
window.game = game;
const report = document.createElement('pre');
report.id = 'report';
report.style.cssText = 'position:fixed;left:10px;top:10px;z-index:9999;max-width:72%;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('SELF-CHECK RUNNING… (menu → new game → home → build console)');
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function waitFor(fn, tries = 150, everyMs = 100) {
for (let i = 0; i < tries; i++) {
const v = fn();
if (v) return v;
await sleep(everyMs);
}
return null;
}
try {
// 0) Stale-JS probe — does the loaded GameScene carry the build system
// at all? (A browser module cache from an older session is the
// classic cause of "I cleared the JSON cache but it's still wrong.")
const GS = GameScene;
const jsProbe = {
buildStateSeam: typeof GS.prototype._seedStartingBuilds === 'function',
beginBuild: typeof GS.prototype.beginBuild === 'function',
buildsConfig: Object.keys(data.builds?.builds ?? {}).join(',') || '(none — stale data/builds.json?)',
};
// 1) The real menu flow: NEW GAME (fresh seed, registry reset).
const menu = await waitFor(() => { const m = game.scene.getScene('MenuScene'); return m?.newGameBtn ? m : null; });
if (!menu) throw new Error('menu never booted');
menu.startNewGame();
const gs = await waitFor(() => { const g = game.scene.getScene('GameScene'); return g?.ship && g?.buildState ? g : null; }, 200);
if (!gs) throw new Error('GameScene never booted');
await sleep(800);
const home = gs.planet.discoveryName;
const builtMap = Object.fromEntries([...gs.buildState.built.entries()].map(([k, v]) => [k, [...v]]));
const homeTether = gs.tetherField.tethers.filter((t) => t.label === home || (t.x === 0 && t.y === 0)).map((t) => `${t.id}:L${t.level}`);
// 2) Land on the home world the game's own way.
gs.startLanding(gs.commsTargetFor(gs.planet));
const ss = await waitFor(() => { const c = game.scene.getScene('SurfaceScene'); return c?.buildWindow ? c : null; });
if (!ss) throw new Error('surface never booted');
await sleep(700);
// 3) The console's truth — the same pure functions the window paints.
const { rowState, missingRequirements } = await import('../js/build/BuildModel.js');
const win = ss.buildWindow;
win.open();
await sleep(400);
const row = (id) => {
const r = win.lists.get('planet')?.rows?.find((x) => x.id === id);
if (!r) return 'NO ROW';
const ctx = win.ctxFor(id);
const missing = missingRequirements(r.def, ctx);
return `${rowState(r.def, ctx, win._activeOnPlanet(), id).toUpperCase()}${missing.length ? ' (needs: ' + missing.join(' + ') + ')' : ''}`;
};
const before = `L1: ${row('tether-l1')} L2: ${row('tether-l2')}`;
// 4) The research is "complete" → L2's only remaining gate on home is
// the planet's own tether level (it holds L1, so it must pass).
gs.researchState.unlock('exploration', 'tether_l2');
win.refresh();
await sleep(200);
const after = `L1: ${row('tether-l1')} L2: ${row('tether-l2')}`;
win.close();
const l1ok = builtMap[home]?.includes('tether-l1');
const l2ok = after.includes('L2: AVAILABLE');
const lines = [
'SELF-CHECK DONE — the home world starts with its Tether - Level 1 BUILT.',
`JS probe: seed=${jsProbe.buildStateSeam} beginBuild=${jsProbe.beginBuild} builds=[${jsProbe.buildsConfig}]`,
`home world: ${home}`,
`built records: ${JSON.stringify(builtMap)}`,
`home tether: ${homeTether.join(', ') || 'NONE'}`,
`console rows (fresh): ${before}`,
`console rows (research done): ${after}`,
l1ok && l2ok
? 'RESULT: OK — if YOUR game differs, the browser is running stale JS'
+ ' (hard reload, Ctrl+Shift+R) or you are on a DIFFERENT world (home is the one with the'
+ ' tether barrier ring, tagged "Home World" in comms).'
: `RESULT: PROBLEM — L1 built=${l1ok}, L2 available=${l2ok}`,
errors.length ? `ERRORS:\n${errors.slice(0, 3).join('\n')}` : 'no console errors',
];
setReport(lines);
window.__BUILD_CHECK = { ready: true, lines, errors };
console.info('build-check: report painted');
} catch (err) {
// Even a failed run should hand back the useful facts: which JS is
// actually loaded (stale-cache probe), what state exists, what broke.
const gs = game.scene.getScene('GameScene');
const seedFn = typeof gs?._seedStartingBuilds === 'function';
setReport([
`FATAL: ${err.message}`,
`JS probe: seed=${seedFn} beginBuild=${typeof gs?.beginBuild === 'function'}` +
(seedFn
? ''
: ' ← STALE JS — the browser served a cached older module. Use `node dev/server.mjs 8080`' +
' (sends Cache-Control: no-store) and/or DevTools → Network → Disable cache, then reload.'),
`home=${gs?.planet?.discoveryName ?? 'n/a'} builtMap=${gs?.buildState ? JSON.stringify(Object.fromEntries([...gs.buildState.built.entries()].map(([k, v]) => [k, [...v]]))) : 'n/a'}`,
errors.length ? `ERRORS:\n${errors.slice(0, 3).join('\n')}` : 'no console errors',
]);
window.__BUILD_CHECK = { ready: true, fatal: String(err.message), errors };
console.error('build-check: fatal', err);
}