/** * 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)));