orbit/dev/surface-flow.mjs

145 lines
6.2 KiB
JavaScript

/**
* Dev-only SURFACE FLOW test: boots straight into the GameScene (smoke
* style), then drives the landing flow end-to-end and reports a result
* object for dev/cdp-firefox.mjs to poll:
*
* python3 -m http.server 8123
* node dev/cdp-firefox.mjs http://127.0.0.1:8123/dev/surface-test.html \
* 'return window.__SURFACE_TEST__;'
*
* Steps:
* 1. wait for the GameScene to be ready
* 2. GameScene.startLanding(commsTargetFor(home world)) — the real entry
* 3. assert SurfaceScene launched, GameScene paused, landing clip present
* 4. force the surface stage (skip the clip's runtime)
* 5. assert deck slots (shop/takeoff/menu), save UI, surface clip
* 6. menuAction → sub-bar opens; save panel opens
* 7. takeOff → SurfaceScene stopped, GameScene resumed, ship preserved
* 8. land again → takeOff again (repeat-launch/re-stop cycle)
*/
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';
const fail = (msg, extra = {}) => {
const gs = window.game?.scene?.getScene?.('GameScene');
const ss = window.game?.scene?.getScene?.('SurfaceScene');
window.__SURFACE_TEST__ = {
pass: false,
error: msg,
steps: window.__STEPS__ ?? [],
diag: {
sceneKeys: Array.isArray(window.game?.scene?.keys)
? window.game.scene.keys.slice()
: Object.keys(window.game?.scene?.keys ?? {}),
gs: gs ? { status: gs.sys.getStatus(), hasShip: !!gs.ship } : null,
ss: ss ? { status: ss.sys.getStatus(), phase: ss.phase } : 'missing',
...extra,
},
};
console.error('[surface-flow] FAIL:', msg, extra);
};
window.__FLOW_STARTED__ = true; // module graph loaded — for dev/cdp-firefox.mjs debugging
const step = (name, cond, extra = {}) => {
if (!cond) throw new Error(`${name}: ${JSON.stringify(extra).slice(0, 300)}`);
window.__STEPS__ = (window.__STEPS__ ?? []).concat(name);
};
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 () => {
// 1 — wait for the flight scene to be live (v4 boots asynchronously
// after `new Phaser.Game`).
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) throw new Error('GameScene never came up (gs=' + String(gs) + ')');
step('boot', true, { frame: gs.planet.sheetFrame });
const shipBefore = { x: gs.ship.x, y: gs.ship.y };
const target = gs.commsTargetFor(gs.planet);
step('comms-target', target.isPlanet === true && target.canLand === true, target);
// 2 — the real entry point (v4: launch + sleep the flight world).
gs.startLanding(target);
await sleep(250);
const ss = game.scene.getScene('SurfaceScene');
step('launched', ss.sys.isActive() === true,
{ ssStatus: ss.sys.getStatus(), gsStatus: gs.sys.getStatus() });
step('game-sleeping', gs.sys.isSleeping() === true, { gsStatus: gs.sys.getStatus() });
step('frame', Number.isInteger(ss.planetFrame) && ss.planetFrame === Number(target.frame),
{ ssFrame: ss.planetFrame, targetFrame: target.frame });
// 3 — the landing clip was queued + attached (or legitimately absent).
const landKey = `__surf_land_${ss.planetFrame}`;
const landCached = ss.cache.video.has(landKey);
step('landing-clip', landCached ? ss.landVideo !== null : true, { landCached, landKey });
// 4 — force the surface stage (the test does not wait out the clip).
ss.startSurface();
await sleep(300);
step('surface-stage', ss.phase === 'surface');
// 5 — the deck.
const ids = (ss.actionBar?.slots ?? []).map((s) => s.id);
step('deck-slots',
ids.length === 6 && ids[0] === 'shop' && ids[4] === 'takeoff' && ids[5] === 'menu'
&& ids[1] === 'build' && ids[2] === 'ship' && ids[3] === null,
{ ids });
step('save-ui', !!ss.menuSubBar && !!ss.savePanel && !!ss.saveManager,
{ subBar: !!ss.menuSubBar, panel: !!ss.savePanel });
const loopKey = `__surf_loop_${ss.planetFrame}`;
const loopCached = ss.cache.video.has(loopKey);
step('surface-clip', loopCached ? ss.surfaceVideo !== null : true, { loopCached });
// 6 — the menu door (sub-bar → save pop-up) works on the surface.
ss.menuAction();
await sleep(120);
step('subbar-open', ss.menuSubBar.isOpen === true);
ss.subBarAction('save');
await sleep(120);
step('savepanel-open', ss.savePanel.isOpen === true);
// A real save round-trip THROUGH the paused GameScene (stateScene):
// slot 1 is empty → onCard saves straight (no confirm beat).
ss.savePanel.onCard(1);
step('save-from-surface', ss.saveManager.hasAny() === true, { hasAny: ss.saveManager.hasAny() });
ss.savePanel.close();
ss.menuSubBar.dismiss();
// 7 — TAKE OFF: overlay stopped, flight scene woken, ship where it was.
ss.takeOff();
await sleep(250);
step('takeoff', ss.sys.isActive() === false && gs.sys.isActive() === true,
{ ssStatus: ss.sys.getStatus(), gsStatus: gs.sys.getStatus() });
step('ship-preserved', Math.abs(gs.ship.x - shipBefore.x) < 1e-6
&& Math.abs(gs.ship.y - shipBefore.y) < 1e-6,
{ before: shipBefore, after: { x: gs.ship.x, y: gs.ship.y } });
// 8 — land again + take off again (the re-launch cycle must stay clean).
gs.startLanding(gs.commsTargetFor(gs.planet));
await sleep(250);
const ss2 = game.scene.getScene('SurfaceScene');
step('re-land', ss2.sys.isActive() === true);
ss2.startSurface();
await sleep(120);
step('re-surface', ss2.phase === 'surface' && ss2.actionBar !== null);
ss2.takeOff();
await sleep(250);
step('re-takeoff', ss2.sys.isActive() === false && gs.sys.isActive() === true);
window.__SURFACE_TEST__ = { pass: true, steps: window.__STEPS__ };
console.info('[surface-flow] PASS:', (window.__STEPS__ ?? []).join(' → '));
})().catch((err) => fail(String(err && err.message || err)));