191 lines
8.1 KiB
JavaScript
191 lines
8.1 KiB
JavaScript
/**
|
|
* Z-order + input-state probe (headless browser — NOT a Node test).
|
|
*
|
|
* Verifies the two menu fixes against the real engine:
|
|
*
|
|
* Z-ORDER (paint order — the scene display list is depth-sorted
|
|
* ascending, and containers expand their children inline at their
|
|
* slot, so list order IS back-to-front paint order):
|
|
* stars(0-2) < planet(5) < ship(10) < HUD(30) < compass(40)
|
|
* < toast(45) < command deck(50) < menu sub-bar(60)
|
|
* < save pop-up(70)
|
|
* ... and the confirm dialog is the pop-up's last content child (only
|
|
* the toast trails it, and it sits outside the dialog's area), so it
|
|
* paints on top of the pop-up.
|
|
*
|
|
* INPUT STATE (v4 gates hit-testing on `input.enabled`):
|
|
* sub-bar buttons inert while closed, live while open
|
|
* pop-up scrim / cards / footer inert while hidden, live while shown
|
|
* confirm scrim + buttons inert while the dialog is hidden, live
|
|
* while it is up
|
|
*
|
|
* Served by dev/zorder-test.html; results land in `window.__ZORDER__`:
|
|
*
|
|
* python3 -m http.server 8091
|
|
* node dev/cdp-firefox.mjs http://127.0.0.1:8091/dev/zorder-test.html
|
|
*/
|
|
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);
|
|
|
|
globalThis.__ORBIT_DEV_SEED = 'ZORDER';
|
|
const gameConfig = createGameConfig();
|
|
gameConfig.scene = [GameScene];
|
|
if (typeof Phaser !== 'undefined') Phaser.NoAudioContext = true;
|
|
|
|
const game = new Phaser.Game(gameConfig);
|
|
window.game = game;
|
|
|
|
const results = [];
|
|
const check = (label, cond) => {
|
|
const pass = !!cond;
|
|
results.push({ label, pass });
|
|
console.log(`${pass ? '✔' : '✘ FAIL'} ${label}`);
|
|
};
|
|
|
|
// Throttling-proof wait on the game's own clock (see clickout-test).
|
|
const gameClock = () => {
|
|
try {
|
|
const s = window.game.scene.getScenes(true)[0];
|
|
if (s && typeof s.time.now === 'number') return s.time.now;
|
|
} catch {}
|
|
return null;
|
|
};
|
|
const wait = (ms) => new Promise((resolve) => {
|
|
const base = gameClock();
|
|
if (base === null) {
|
|
setTimeout(() => resolve(), ms);
|
|
return;
|
|
}
|
|
const poll = () => {
|
|
const now = gameClock();
|
|
if (now !== null && now - base >= ms) return resolve();
|
|
requestAnimationFrame(poll);
|
|
};
|
|
requestAnimationFrame(poll);
|
|
});
|
|
|
|
const run = async () => {
|
|
await wait(900); // boot
|
|
const scene = game.scene.getScene('GameScene');
|
|
check('boots into GameScene', game.scene.isActive('GameScene'));
|
|
|
|
// ---- declared depths ---------------------------------------------------
|
|
check('planets sit at depth 5', scene.planet.depth === 5);
|
|
check('the ship sits at depth 10', scene.ship.depth === 10);
|
|
check('the command deck sits at depth 50', scene.actionBar.depth === 50);
|
|
check('the menu sub-bar sits at depth 60', scene.menuSubBar.depth === 60);
|
|
check('the save pop-up sits at depth 70', scene.savePanel.depth === 70);
|
|
|
|
// ---- paint order: the scene display list -------------------------------
|
|
const dl = scene.sys.displayList.getChildren();
|
|
const idx = (o) => dl.indexOf(o);
|
|
const iPlanet = idx(scene.planet);
|
|
const iShip = idx(scene.ship);
|
|
const iCompass = idx(scene.compass);
|
|
const iDeck = idx(scene.actionBar);
|
|
const iSubBar = idx(scene.menuSubBar);
|
|
const iPanel = idx(scene.savePanel);
|
|
const name = (o) => o === scene.planet ? 'planet' : o === scene.ship ? 'ship'
|
|
: o === scene.compass ? 'compass' : o === scene.actionBar ? 'actionBar'
|
|
: o === scene.menuSubBar ? 'menuSubBar' : o === scene.savePanel ? 'savePanel'
|
|
: (o.type || 'obj') + '@' + (o._depth !== undefined ? o._depth : o.depth);
|
|
console.log('displayList tail:', dl.slice(-8).map(name).join(' < '));
|
|
check('every probed object is in the display list',
|
|
[iPlanet, iShip, iCompass, iDeck, iSubBar, iPanel].every((i) => i >= 0));
|
|
results.push({ label: `DL planet=${iPlanet} ship=${iShip} compass=${iCompass} actionBar=${iDeck} menuSubBar=${iSubBar} savePanel=${iPanel} len=${dl.length}`, pass: true });
|
|
check('the display list is depth-sorted (ascending)',
|
|
dl.slice(-20).every((o, i, a) => i === 0 || o._depth >= a[i - 1]._depth));
|
|
check('world (planet, ship) paints UNDER the command deck', iPlanet < iDeck && iShip < iDeck);
|
|
check('the command deck paints UNDER the sub-bar', iDeck < iSubBar);
|
|
check('the sub-bar paints UNDER the save pop-up', iSubBar < iPanel);
|
|
const kids = (() => {
|
|
const p = scene.savePanel;
|
|
if (p.list && Array.isArray(p.list)) return p.list; // v4 Container children live in `list`
|
|
const c = p.children;
|
|
if (!c) return null;
|
|
if (Array.isArray(c)) return c;
|
|
if (c.list && Array.isArray(c.list)) return c.list;
|
|
if (typeof c.getChildren === 'function') return c.getChildren();
|
|
return null;
|
|
})();
|
|
check('the confirm dialog paints above the panel contents (only the toast trails it)', (() => {
|
|
if (!Array.isArray(kids) || kids.length === 0) return false;
|
|
const ci = kids.indexOf(scene.savePanel.confirm);
|
|
return ci >= 0 && kids.slice(ci + 1).every((c) => c === scene.savePanel.toast);
|
|
})());
|
|
|
|
// ---- input states (v4: input.enabled) ----------------------------------
|
|
const enabled = (o) => !!(o.input && o.input.enabled);
|
|
check('sub-bar buttons are input-INERT while the bar is closed',
|
|
scene.menuSubBar.buttons.every((s) => !enabled(s.btn.panel)));
|
|
check('pop-up scrim/cards/footer are input-INERT while hidden',
|
|
!enabled(scene.savePanel.scrim)
|
|
&& scene.savePanel.cards.every((c) => !enabled(c.panel))
|
|
&& !enabled(scene.savePanel.cancelBtn.panel) && !enabled(scene.savePanel.downloadBtn.panel));
|
|
check('confirm scrim + buttons are input-INERT while the dialog is hidden',
|
|
!enabled(scene.savePanel.confirm.scrim)
|
|
&& !enabled(scene.savePanel.confirm.confirmBtn.panel)
|
|
&& !enabled(scene.savePanel.confirm.cancelBtn.panel));
|
|
|
|
scene.menuAction(); // open the sub-bar
|
|
await wait(450);
|
|
check('sub-bar buttons are input-LIVE while the bar is open',
|
|
scene.menuSubBar.isOpen && scene.menuSubBar.buttons.every((s) => enabled(s.btn.panel)));
|
|
|
|
scene.subBarAction('save'); // open the pop-up (save mode)
|
|
await wait(450);
|
|
check('pop-up scrim + cards + footer are input-LIVE while shown',
|
|
scene.savePanel.isOpen
|
|
&& enabled(scene.savePanel.scrim)
|
|
&& scene.savePanel.cards.every((c) => enabled(c.panel))
|
|
&& enabled(scene.savePanel.cancelBtn.panel) && enabled(scene.savePanel.downloadBtn.panel));
|
|
|
|
scene.savePanel.confirmOverwrite(1, { galaxyName: 'X', savedAt: new Date().toISOString() });
|
|
await wait(300);
|
|
check('confirm scrim + buttons are input-LIVE while the dialog is up',
|
|
scene.savePanel.confirm.isOpen
|
|
&& enabled(scene.savePanel.confirm.scrim)
|
|
&& enabled(scene.savePanel.confirm.confirmBtn.panel)
|
|
&& enabled(scene.savePanel.confirm.cancelBtn.panel));
|
|
scene.savePanel.confirm.cancel();
|
|
await wait(350);
|
|
check('confirm goes inert again after CANCEL',
|
|
scene.savePanel.confirm.isOpen === false
|
|
&& !enabled(scene.savePanel.confirm.scrim)
|
|
&& !enabled(scene.savePanel.confirm.confirmBtn.panel));
|
|
|
|
scene.savePanel.close();
|
|
await wait(400);
|
|
scene.menuSubBar.close();
|
|
await wait(400);
|
|
check('sub-bar goes inert again after close',
|
|
scene.menuSubBar.state === 'closed'
|
|
&& scene.menuSubBar.buttons.every((s) => !enabled(s.btn.panel)));
|
|
check('pop-up goes inert again after close',
|
|
scene.savePanel.isOpen === false && !enabled(scene.savePanel.scrim)
|
|
&& scene.savePanel.cards.every((c) => !enabled(c.panel)));
|
|
};
|
|
|
|
let done = false;
|
|
game.events.once('ready', async () => {
|
|
try {
|
|
await run();
|
|
} catch (err) {
|
|
results.push({ label: `THREW: ${String(err && err.message || err)}`, pass: false });
|
|
}
|
|
const pass = results.length > 0 && results.every((r) => r.pass);
|
|
window.__ZORDER__ = { pass, results, errors: window.__CAPTURED_ERRORS__ || [] };
|
|
done = true;
|
|
console.log(pass ? 'ZORDER PASS' : 'ZORDER FAIL');
|
|
});
|
|
|
|
setTimeout(() => {
|
|
if (!done) window.__ZORDER__ = { pass: false, results: [...results, { label: 'TIMED OUT (300s)', pass: false }] };
|
|
}, 300000);
|