172 lines
8.6 KiB
JavaScript
172 lines
8.6 KiB
JavaScript
/**
|
||
* Dev-only visual probe for the JOB BOARD console (js/ui/JobBoardWindow.js):
|
||
* boots the real flow (menu → new game → land on the home world → surface),
|
||
* then parks at the stage named in the URL hash, with a report in a DOM
|
||
* <pre> the screenshot can always read:
|
||
*
|
||
* #drawer (default) — the HOME world's EXPLORE: no drawer at all (the
|
||
* Job Board slot is absent — data/actionbar.json →
|
||
* `isHomeSystem: false`, safe space), the slot's press is a no-op
|
||
* with the console's ack ("NOTHING TO EXPLORE HERE").
|
||
* #board — the board console OPEN over the surface (nearly
|
||
* full-screen, depth 80 — over the deck + drawer + the upper-left
|
||
* HUD), driven through the scene's own seams (the slot's press →
|
||
* the toggle): the left feed (assets/videos/jobboard.mp4, muted
|
||
* loop), the right JOB BOARD title + the AVAILABLE JOBS / JOB
|
||
* DETAILS placeholder sections, the deck stood down (the slot's
|
||
* press stays live as the second door; the ✕ is the way-out).
|
||
* PROBE STAGING: the home world's isHomeSystem flag is flipped
|
||
* for this shot so the slot exists here (on a real non-home world
|
||
* it exists naturally — the drawer's item list is the
|
||
* where-clause's, covered by dev/explore.test.mjs).
|
||
* #left — …and back: the slot's press again (the second
|
||
* door) — the console down, the deck live, the slot still JOB
|
||
* BOARD (no relabel either way).
|
||
*
|
||
* node dev/server.mjs 8080
|
||
* brave --headless --disable-gpu --remote-debugging-port=9333 --window-size=1280,720 about:blank
|
||
* node dev/cdp-shot.mjs "http://127.0.0.1:8080/dev/jobboard-shot.html#drawer" jb-drawer.png "window.__JB && window.__JB.stage && !/FAILED/.test(window.__JB.stage)" 45000
|
||
* node dev/cdp-shot.mjs "http://127.0.0.1:8080/dev/jobboard-shot.html#board" jb-board.png "window.__JB && window.__JB.stage && !/FAILED/.test(window.__JB.stage)" 45000
|
||
* node dev/cdp-shot.mjs "http://127.0.0.1:8080/dev/jobboard-shot.html#left" jb-left.png "window.__JB && window.__JB.stage && !/FAILED/.test(window.__JB.stage)" 45000
|
||
*/
|
||
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);
|
||
|
||
// 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 = [MenuScene, GameScene, SurfaceScene];
|
||
const game = new Phaser.Game(gameConfig);
|
||
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('booting… (menu → new game → home world → surface)');
|
||
|
||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||
async function waitFor(fn, tries = 200, everyMs = 100) {
|
||
for (let i = 0; i < tries; i++) {
|
||
const v = fn();
|
||
if (v) return v;
|
||
await sleep(everyMs);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
const STAGE = (location.hash || '#drawer').slice(1) || 'drawer';
|
||
const deckReport = (ss) =>
|
||
(ss.actionBar?.slots ?? []).filter((s) => s.live).map((s) => `${s.id}:${s.disabled ? 'off' : 'on'}`);
|
||
const slotLabel = (ss) => {
|
||
const b = (ss.exploreSubBar?.buttons ?? []).find((x) => x.id === 'jobBoard');
|
||
return b ? b.btn.labelText.text : '(absent — no drawer on the home world)';
|
||
};
|
||
|
||
try {
|
||
// 1) The real menu flow: NEW GAME.
|
||
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 : null; }, 200);
|
||
if (!gs) throw new Error('GameScene never booted');
|
||
await sleep(600);
|
||
|
||
// 2) Land on the home world the game's own way (the target is built
|
||
// WHILE the home flag still reads true — the HUD's "Home World"
|
||
// label comes from it).
|
||
const target = gs.commsTargetFor(gs.planet);
|
||
if (STAGE !== 'drawer') gs.isHomeSystem = false; // PROBE STAGING — the slot's visual on this world
|
||
gs.startLanding(target);
|
||
const ss = await waitFor(() => { const c = game.scene.getScene('SurfaceScene'); return c?.jobBoardWindow ? c : null; }, 200);
|
||
if (!ss) throw new Error('surface never booted');
|
||
await sleep(700);
|
||
ss.startSurface(); // force the loop stage (don't wait the landing clip out)
|
||
const forcePlay = (v) => { if (v?.video) { v.video.muted = true; v.video.play().catch(() => {}); } };
|
||
forcePlay(ss.surfaceVideo);
|
||
await sleep(900);
|
||
|
||
const win = ss.jobBoardWindow;
|
||
|
||
if (STAGE === 'drawer') {
|
||
// The HOME world's EXPLORE — the slot is a no-op (no drawer: the
|
||
// Job Board's `isHomeSystem: false` clause leaves the item list
|
||
// empty, and a world with no items gets no drawer).
|
||
ss.toggleExplore();
|
||
await sleep(400); // the console note in + hold
|
||
window.__JB = {
|
||
stage: 'drawer',
|
||
home: ss.planetName,
|
||
isHomeSystem: gs.isHomeSystem === true,
|
||
drawerPresent: !!ss.exploreSubBar,
|
||
drawerItems: (ss.exploreSubBar?.buttons ?? []).map((b) => b.id),
|
||
noteG: ss.noteG ? ss.noteG.map((g) => g?.text) : null,
|
||
deck: deckReport(ss),
|
||
errors,
|
||
};
|
||
setReport(JSON.stringify(window.__JB, null, 1));
|
||
} else {
|
||
// The slot's player path: the drawer's JOB BOARD press → the toggle
|
||
// (the drawer's onAction seam — the same call the button's press
|
||
// makes on every non-home world).
|
||
ss.toggleExplore();
|
||
await sleep(1100); // the drawer's unfold
|
||
ss.exploreAction('jobBoard');
|
||
await sleep(1900); // the board’s reveal + the title’s decode
|
||
// Headless autoplay policy: force the feed visible (muted).
|
||
if (win.video?.video) { win.video.video.muted = true; win.video.video.play().catch(() => {}); }
|
||
await sleep(700); // let a frame land
|
||
window.__JB = {
|
||
stage: 'board',
|
||
world: ss.planetName,
|
||
staged: 'isHomeSystem flipped for the slot’s visual (a real non-home world has it naturally)',
|
||
jobBoardMode: ss.jobBoardMode,
|
||
openState: win?.openState,
|
||
title: win?.rightTitle?.text,
|
||
titlePos: win?.rightTitle ? [Math.round(win.rightTitle.x), Math.round(win.rightTitle.y)] : null,
|
||
jobs: win?.jobsSection ? { a: +win.jobsSection.bg.alpha.toFixed(2), y: Math.round(win.jobsSection.bg.y), head: win.jobsSection.headTxt.text, ha: +win.jobsSection.headTxt.alpha.toFixed(2) } : null,
|
||
detail: win?.detailSection ? { a: +win.detailSection.bg.alpha.toFixed(2), y: Math.round(win.detailSection.bg.y), head: win.detailSection.headTxt.text, ha: +win.detailSection.headTxt.alpha.toFixed(2) } : null,
|
||
geo: win?.geo ? { bodyY: win.geo.bodyY, bodyBottom: win.geo.bodyBottom, rightX: win.geo.rightX, rightW: win.geo.rightW, availTop: win.geo.availTop, availH: win.geo.availH, detailPanelY: win.geo.detailPanelY, detailH: win.geo.detailH } : null,
|
||
video: win?.video ? { muted: win.video.video.muted, loop: win.video.loop, volume: win.video.volume } : null,
|
||
slotLabel: slotLabel(ss),
|
||
drawerItems: (ss.exploreSubBar?.buttons ?? []).map((b) => b.id),
|
||
deck: deckReport(ss),
|
||
errors,
|
||
};
|
||
setReport(JSON.stringify(window.__JB, null, 1));
|
||
|
||
if (STAGE === 'left') {
|
||
// LEAVE — the slot's press again (the player's exact way out).
|
||
ss.exploreAction('jobBoard');
|
||
await sleep(900); // the close + the relabel back
|
||
window.__JB = {
|
||
stage: 'left',
|
||
world: ss.planetName,
|
||
jobBoardMode: ss.jobBoardMode,
|
||
openState: win?.openState,
|
||
slotLabel: slotLabel(ss),
|
||
deck: deckReport(ss),
|
||
errors,
|
||
};
|
||
setReport(JSON.stringify(window.__JB, null, 1));
|
||
}
|
||
}
|
||
} catch (err) {
|
||
window.__JB = { stage: 'FAILED', error: String(err?.message ?? err), errors };
|
||
setReport(JSON.stringify(window.__JB, null, 1));
|
||
}
|