148 lines
7.2 KiB
JavaScript
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);
|
|
}
|