orbit/dev/jump-cargo.mjs

145 lines
5.9 KiB
JavaScript

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