162 lines
7.0 KiB
JavaScript
162 lines
7.0 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 → take-off clip plays, then (forced complete) 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: the take-off clip plays first, then overlay stopped,
|
||
// flight scene woken, ship where it was.
|
||
const takeoffKey = `__surf_takeoff_${ss.planetFrame}`;
|
||
const takeoffCached = ss.cache.video.has(takeoffKey);
|
||
ss.takeOff();
|
||
await sleep(250);
|
||
if (takeoffCached) {
|
||
step('takeoff-clip', ss.takeoffVideo !== null && ss.sys.isActive() === true
|
||
&& ss.surfaceVideo === null && ss.actionBar?.visible === false,
|
||
{ takeoffKey, surfaceVideo: ss.surfaceVideo, deckVisible: ss.actionBar?.visible });
|
||
// the test does not wait out the 15–20s clip — force its completion
|
||
ss.takeoffVideo.emit('complete', ss.takeoffVideo);
|
||
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);
|
||
if (ss2.cache.video.has(`__surf_takeoff_${ss2.planetFrame}`) && ss2.takeoffVideo) {
|
||
step('re-takeoff-clip', ss2.takeoffVideo !== null && ss2.sys.isActive() === true);
|
||
ss2.takeoffVideo.emit('complete', ss2.takeoffVideo);
|
||
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)));
|