/** * Dev: the UPGRADED CARGO HOLD survives a gate JUMP (v0.5): * * The bar scene's DONE beat builds the hold out (GameScene. * onVignetteComplete: effects.cargoHold, then the 'special-package' * in it). The hold is SHIP STATE — so it must ride every transport. * A gate jump is its own transport: jumpThroughGate re-stages the run * through the save pipeline (captureState → rec.ship → prepareLoad → * scene.restart → applyRestore on a FRESH ship, which starts at the * 0-bay baseline — data/ship.json). * * This test proves the beat survives that hop end-to-end with the * REAL jumpThroughGate (no stubs): * * 1. fresh VESTRA run → 0 bays, empty hold (the baseline) * 2. stage the beat → 4 bays, 'special-package' aboard * 3. real jump through an ACTIVE gate → the scene restarts onto the * destination system * 4. the FRESH ship after the jump keeps the bays (cargoHold 4, * capacity 4) AND the package (the jump's rec.ship carries * cargoHold; without it the restore would re-seed the 0 baseline * and shed the goods) * * node dev/server.mjs 8099 * node dev/cdp-firefox.mjs http://localhost:8099/dev/jump-cargo.html \ * 'return window.__JUMP_CARGO__;' 90000 */ 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'; const data = await ConfigLoader.load(); config.init(data); // 'VESTRA's starting system holds three gates (the gate-comms seed). globalThis.__ORBIT_DEV_SEED = 'VESTRA'; const gameConfig = createGameConfig(); gameConfig.scene = [GameScene]; // GameScene boots first if (typeof Phaser !== 'undefined') Phaser.NoAudioContext = true; const game = new Phaser.Game(gameConfig); window.game = game; /* ---- helpers (the gate-comms harness pattern) ------------------------- */ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); /** Pump the loop until `ok()` or the budget elapses. */ async function pump(s, ok, ms = 4000) { const t0 = Date.now(); while (!ok() && Date.now() - t0 < ms) { s.game.loop.step(performance.now()); await sleep(0); } return ok(); } async function ready() { const t0 = Date.now(); for (;;) { const g = window.game; const s = g && g.scene?.getScene?.('GameScene'); if (s && s.sys?.isActive && s.ship && (s.systemGates?.length ?? 0) >= 1) return s; if (Date.now() - t0 > 45000) throw new Error('game never became ready'); await sleep(50); } } /* ---- the test ---------------------------------------------------------- */ async function run() { const out = { steps: [] }; const step = (name, pass, extra = {}) => out.steps.push({ name, pass, ...extra }); const s = await ready(); s.cameraFollowShip = false; s.hideHint?.(); s.ship.stop(); const gateA = s.systemGates[0]; // ---- 1. the fresh baseline --------------------------------------------- step('a fresh ship starts at the 0-bay baseline (data/ship.json)', s.ship.stats.cargoHold === 0 && s.ship.cargo.capacity === 0 && s.ship.cargo.items.length === 0); // ---- 2. stage the beat (the bar scene's DONE press, verbatim) ----------- s.ship.setCargoHold(4); // GameScene.onVignetteComplete: effects.cargoHold s.ship.cargo.add('special-package'); // then the quest's package step('the beat is live (4 bays, the package aboard)', s.ship.stats.cargoHold === 4 && s.ship.cargo.capacity === 4 && s.ship.cargo.has('special-package') === true); // Keep the ship OUT of the gate's keepout (solid — the ship can't // overlap it) before the jump re-stages the run. s.ship.x = gateA.x + 2500; s.ship.y = gateA.y + 2000; // ---- 3. the REAL jump (no stubs) ----------------------------------------- gateA.active = true; // (the research tech would do this — same flag) const destId = gateA.gate?.to; const beforeShip = s.ship; step('the jump target is a known system', !!destId && s.galaxy.byId.has(destId) === true); const fromSystem = s.galaxy.currentSystemId; s.jumpThroughGate(gateA); // The cut: jump clip (or its stall guard) → _finishJump → scene.restart // → a FRESH scene on the destination. The scene INSTANCE survives // (restart), but this.ship is a new object — that is the restart tell. // applyRestore runs at the END of create(), so wait for the beat to // be back on the fresh ship (cargoHold restored + package aboard). const restored = await pump( s, () => s.ship !== beforeShip && s.ship.stats.cargoHold === 4 && s.ship.cargo.has('special-package') === true, 30000, ); step('the jump completed (the scene restarted onto the destination)', s.ship !== beforeShip && game.scene.isActive('GameScene')); step('the ship is in the destination system', s.galaxy.currentSystemId === destId && s.galaxy.currentSystemId !== fromSystem); // ---- 4. the beat survived the hop ---------------------------------------- step('THE BEAT SURVIVES THE JUMP: the fresh ship keeps the bays (cargoHold 4, capacity 4)', !!restored && s.ship.stats.cargoHold === 4 && s.ship.cargo.capacity === 4, { cargoHold: s.ship?.stats?.cargoHold, capacity: s.ship?.cargo?.capacity }); step('THE BEAT SURVIVES THE JUMP: the package is still aboard', !!restored && s.ship.cargo.has('special-package') === true && JSON.stringify(s.ship.cargo.items) === JSON.stringify(['special-package']), { items: s.ship?.cargo?.items }); out.pass = out.steps.every((st) => st.pass); return out; } window.__JUMP_CARGO__ = null; run() .then((out) => { window.__JUMP_CARGO__ = out; console.log(out.pass ? 'JUMP-CARGO PASS' : 'JUMP-CARGO FAIL'); }) .catch((err) => { console.error(err); window.__JUMP_CARGO__ = { pass: false, steps: [{ name: `DRIVER CRASHED: ${err.message}`, pass: false }] }; });