144 lines
6.2 KiB
JavaScript
144 lines
6.2 KiB
JavaScript
/**
|
|
* THE USER'S EXACT PATH — click the home planet, then click the
|
|
* REQUEST LANDING button in the comms panel (real pointer events, no
|
|
* direct scene calls). This is the path the headless flow test skipped
|
|
* (it called startLanding() directly) — and it caught the lastTarget
|
|
* pruning bug where the panel's button payload lost `isPlanet`/`frame`.
|
|
*
|
|
* PUMP-DRIVEN (like dev/mining-test.mjs): this box's headless Firefox
|
|
* freezes rAF/timers after first paint, so the page is advanced by the
|
|
* runner's wake — each poll calls __LC_TICK__(), which steps the Phaser
|
|
* loop once and advances the stage machine one step.
|
|
*
|
|
* python3 -m http.server 8123
|
|
* node dev/cdp-firefox.mjs http://127.0.0.1:8123/dev/landing-click.html \
|
|
* 'window.__LC_TICK__(); return window.__LANDED__;' 60000 30
|
|
*/
|
|
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 data = await ConfigLoader.load();
|
|
config.init(data);
|
|
|
|
const gameConfig = createGameConfig();
|
|
gameConfig.scene = [GameScene, SurfaceScene];
|
|
|
|
const game = new Phaser.Game(gameConfig);
|
|
window.game = game;
|
|
|
|
const out = { pass: false, steps: [], error: null };
|
|
const step = (label, cond, diag) => {
|
|
out.steps.push(label);
|
|
if (!cond) {
|
|
out.error = `${label}${diag ? ': ' + JSON.stringify(diag).slice(0, 300) : ''}`;
|
|
window.__LANDED__ = out;
|
|
throw new Error(out.error);
|
|
}
|
|
};
|
|
|
|
let stage = 0;
|
|
let pendingPoke = null; // { x, y } — a canvas pointer click to dispatch this tick
|
|
const diag = () => {
|
|
let gs = null, cp = null;
|
|
try { gs = game.scene?.getScene('GameScene') ?? null; } catch { /* not up yet */ }
|
|
if (gs?.commsPanel) cp = { open: gs.commsPanel.isOpen, state: gs.commsPanel.state };
|
|
window.__LC_STATE__ = {
|
|
stage, steps: out.steps.slice(), error: out.error,
|
|
gs: gs ? { status: gs.sys.getStatus(), ship: !!gs.ship, cp } : 'none',
|
|
ss: (() => { try { const s = game.scene.getScene('SurfaceScene'); return s ? { status: s.sys.getStatus(), phase: s.phase } : null; } catch { return null; } })(),
|
|
};
|
|
};
|
|
const canvas = () => game.canvas;
|
|
const poke = (sx, sy) => {
|
|
const opts = {
|
|
bubbles: true, cancelable: true, pointerId: 1, pointerType: 'mouse',
|
|
isPrimary: true, clientX: sx, clientY: sy, button: 0, buttons: 1,
|
|
};
|
|
canvas().dispatchEvent(new PointerEvent('pointerover', opts));
|
|
canvas().dispatchEvent(new PointerEvent('pointerdown', opts));
|
|
canvas().dispatchEvent(new PointerEvent('pointerup', { ...opts, buttons: 0 }));
|
|
};
|
|
const scene = (key) => { try { return game.scene?.getScene(key) ?? null; } catch { return null; } };
|
|
|
|
function tick() {
|
|
diag();
|
|
// v4: do NOT step the loop before boot completes — `game.scene` (the
|
|
// scene manager) is created during boot, and pumping loop.step() before
|
|
// that wedges the boot (the scene never comes up). The mining test's
|
|
// pump guards the same way (it only steps once g.loop is live).
|
|
if (!game.loop || !game.scene) return;
|
|
try {
|
|
// (a) dispatch this tick's click, (b) one engine frame, (c) advance.
|
|
if (pendingPoke) { poke(pendingPoke.x, pendingPoke.y); pendingPoke = null; }
|
|
game.loop.step(performance.now());
|
|
|
|
const gs = scene('GameScene');
|
|
const ss = scene('SurfaceScene');
|
|
|
|
switch (stage) {
|
|
case 0: // boot: the flight scene is live
|
|
if (!(gs && gs.ship)) return;
|
|
step('boot', true, { frame: gs.planet.sheetFrame });
|
|
{
|
|
// Click a VISIBLE point of the planet disc: the ship spawns on
|
|
// the rim (camera centered on the ship), so the disc rim facing
|
|
// the ship is the on-screen part — click just inside that rim.
|
|
const cam = gs.cameras.main;
|
|
const dx = gs.ship.x - gs.planet.x, dy = gs.ship.y - gs.planet.y;
|
|
const d = Math.hypot(dx, dy) || 1;
|
|
const wx = gs.planet.x + (dx / d) * gs.planet.radius * 0.98;
|
|
const wy = gs.planet.y + (dy / d) * gs.planet.radius * 0.98;
|
|
const px = wx - cam.scrollX, py = wy - cam.scrollY;
|
|
step('planet-on-screen', px > 8 && px < 1272 && py > 8 && py < 712, { px, py, planetRadius: gs.planet.radius });
|
|
pendingPoke = { x: px, y: py };
|
|
}
|
|
stage = 1;
|
|
return;
|
|
|
|
case 1: // the planet click opened the comms panel
|
|
if (!(gs.commsPanel && gs.commsPanel.isOpen)) return;
|
|
step('comms-opened', true);
|
|
step('button-enabled', gs.commsPanel.btn1.disabled === false,
|
|
{ label: gs.commsPanel.btn1.text?.text, disabled: gs.commsPanel.btn1.disabled });
|
|
{
|
|
const t = gs.commsPanel.lastTarget;
|
|
step('lastTarget-isPlanet', t?.isPlanet === true, { lastTarget: t });
|
|
step('lastTarget-frame', t?.frame === gs.planet.sheetFrame,
|
|
{ frame: t?.frame, sheetFrame: gs.planet.sheetFrame });
|
|
// Panel is origin (0, 0.5): local (0, H/2) sits at (cp.x, cp.y).
|
|
const cam = gs.cameras.main;
|
|
const cp = gs.commsPanel;
|
|
const bx = cp.x + cp.btn1.x - cam.scrollX;
|
|
const by = cp.y + cp.btn1.y - cp.H / 2 - cam.scrollY;
|
|
step('button-on-screen', bx > 8 && bx < 1272 && by > 8 && by < 712, { bx, by });
|
|
pendingPoke = { x: bx, y: by };
|
|
}
|
|
stage = 2;
|
|
return;
|
|
|
|
case 2: // the button press landed the ship
|
|
if (!(ss && ss.sys.isActive())) return;
|
|
step('panel-closed', gs.commsPanel.isOpen === false, { state: gs.commsPanel.state });
|
|
step('surface-running', true,
|
|
{ keys: game.scene.getScenes().map((s) => `${s.sys.id}:${s.sys.getStatus()}`) });
|
|
step('game-sleeping', gs.sys.isSleeping() === true, { gsStatus: gs.sys.getStatus() });
|
|
step('phase-landing', ss.phase === 'landing', { phase: ss.phase, landVideo: !!ss.landVideo });
|
|
step('handoff-frame', ss.planetFrame === gs.planet.sheetFrame,
|
|
{ ss: ss.planetFrame, planet: gs.planet.sheetFrame });
|
|
out.pass = true;
|
|
window.__LANDED__ = out;
|
|
stage = 3;
|
|
return;
|
|
}
|
|
} catch (err) {
|
|
out.error = String(err?.message ?? err);
|
|
window.__LANDED__ = out;
|
|
stage = 99; // stop
|
|
}
|
|
}
|
|
window.__LC_TICK__ = tick;
|