164 lines
8.3 KiB
JavaScript
164 lines
8.3 KiB
JavaScript
/**
|
|
* Dev-only: exercise TRESTLE's introduction pair in the live GameScene —
|
|
* the FIRST planet the player discovers in a system that is NOT home
|
|
* (trigger 'firstForeignPlanet'):
|
|
*
|
|
* trestle-meetup → ack: box holds (muted looping video + text + voice)
|
|
* until ACKNOWLEDGE.
|
|
* trestle-quest → ack: starts the moment the meetup is ACKed (delayMs 0).
|
|
*
|
|
* The roster is overridden at boot so ONLY the pair is live (the newGame
|
|
* welcome stays out of the FIFO) — but everything else is the shipped
|
|
* code: the real character entry (his clip + his speech keys), the real
|
|
* GameScene.celebrateDiscovery arm (the scene flag + the witness check),
|
|
* the real hub (FIFO, the ack contract, the ledger), the real box.
|
|
*
|
|
* The witness matrix (all through the real celebrateDiscovery):
|
|
* 1. home system, a planet discovered → NO arm (home doesn't count)
|
|
* 2. foreign system, a cluster → NO arm (planets are the witness)
|
|
* 3. foreign system, a PLANET → the pair arms, in config order
|
|
*
|
|
* Headless note: the AudioContext is suspended, so the clips never reach
|
|
* a natural end — the ACK clicks drive the flow (ack comms hold until
|
|
* ACKNOWLEDGE), which is exactly the player's path.
|
|
*
|
|
* node dev/cdp-probe.mjs "http://127.0.0.1:3000/dev/comms-trestle.html" \
|
|
* "window.__trestleResult ? JSON.stringify(window.__trestleResult) : null" 180000
|
|
*/
|
|
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();
|
|
|
|
// --- override: ONLY the trestle pair is live (the welcome stays out) ----
|
|
const shipped = data.comms;
|
|
data.comms = {
|
|
...shipped,
|
|
comms: (shipped?.comms ?? []).filter((c) => c.trigger === 'firstForeignPlanet'),
|
|
};
|
|
config.init(data);
|
|
|
|
const gameConfig = createGameConfig();
|
|
gameConfig.scene = [GameScene];
|
|
const game = new Phaser.Game(gameConfig);
|
|
window.game = game;
|
|
|
|
const report = document.createElement('pre');
|
|
report.id = 'report';
|
|
report.style.cssText = 'position:fixed;left:10px;top:10px;z-index:9999;margin:0;padding:8px 12px;font:12px/1.5 monospace;color:#eaf6ff;background:rgba(6,20,16,0.9);border:1px solid #1b3a5a;max-width:55%;white-space:pre-wrap;';
|
|
document.body.appendChild(report);
|
|
const setReport = (lines) => { report.textContent = lines.join('\n'); };
|
|
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
const until = async (fn, ms = 20000) => {
|
|
const t0 = Date.now();
|
|
for (;;) {
|
|
const v = fn();
|
|
if (v) return v;
|
|
if (Date.now() - t0 > ms) return null;
|
|
await sleep(120);
|
|
}
|
|
};
|
|
|
|
setTimeout(async () => {
|
|
const d = [];
|
|
const errors = [];
|
|
const origErr = console.error;
|
|
console.error = (...a) => { errors.push(a.map(String).join(' ')); origErr(...a); };
|
|
try {
|
|
const s = game.scene.getScene('GameScene');
|
|
const ready = await until(() => (s && s.ship && s.comms ? s : null));
|
|
if (!ready) throw new Error('scene never ready');
|
|
|
|
// The scene booted in the HOME system (isHomeSystem true). The roster
|
|
// override keeps newGame quiet — verify the deck is live + nothing armed.
|
|
await sleep(600);
|
|
const quiet0 = s.comms.active === false && s.comms.queue.length === 0;
|
|
d.push(`boot: active=${s.comms.active} (expect false) queue=${s.comms.queue.length} (expect 0)`);
|
|
|
|
// A real planet of this system (the arm's witness: discoveryId match).
|
|
const planet = (s.systemPlanets ?? [])[0];
|
|
const cluster = (s.asteroidClusters ?? [])[0];
|
|
if (!planet) throw new Error('no system planet to discover');
|
|
const disc = (o, name, typeLabel) => ({
|
|
id: o.discoveryId,
|
|
x: o.x,
|
|
y: o.y,
|
|
radius: o.radius ?? o.bound ?? 30,
|
|
name,
|
|
typeLabel,
|
|
});
|
|
|
|
// ---- witness 1 — home system: a planet does NOT count -------------
|
|
if (s.isHomeSystem !== true) throw new Error('scene should boot in the home system');
|
|
s.celebrateDiscovery(disc(planet, planet.discoveryName, 'Rocky World'));
|
|
await sleep(400);
|
|
const homeGuard = s.comms.active === false && s.comms.queue.length === 0 && s._firstForeignPlanetSeen === false;
|
|
d.push(`witness 1 (home planet): armed=${s.comms.queue.length} (expect 0 — home doesn't count) flag=${s._firstForeignPlanetSeen} (expect false)`);
|
|
|
|
// ---- witness 2 — foreign system: a CLUSTER does not count ---------
|
|
s.isHomeSystem = false; // simulate the jump: the scene now holds a foreign system
|
|
if (cluster) {
|
|
s.celebrateDiscovery(disc(cluster, cluster.discoveryName, 'Asteroid Cluster'));
|
|
await sleep(400);
|
|
const clusterGuard = s.comms.active === false && s.comms.queue.length === 0 && s._firstForeignPlanetSeen === false;
|
|
d.push(`witness 2 (foreign cluster): armed=${s.comms.queue.length} (expect 0 — planets are the witness) flag=${s._firstForeignPlanetSeen} (expect false)`);
|
|
if (!clusterGuard) throw new Error('a cluster armed the planet comm');
|
|
} else {
|
|
d.push('witness 2 (foreign cluster): skipped (no cluster in this system)');
|
|
}
|
|
|
|
// ---- witness 3 — foreign system: the first PLANET — the pair ------
|
|
s.celebrateDiscovery(disc(planet, planet.discoveryName, 'Rocky World'));
|
|
const armOk = s._firstForeignPlanetSeen === true && s.comms.queue.length + (s.comms.active ? 1 : 0) === 2;
|
|
d.push(`witness 3 (foreign planet): flag=${s._firstForeignPlanetSeen} (expect true) armed=${s.comms.queue.length + (s.comms.active ? 1 : 0)} (expect 2 — the pair)`);
|
|
if (!armOk) throw new Error('the planet did not arm the pair');
|
|
|
|
const ackStep = async (id, voiceKey, videoKey) => {
|
|
const live = await until(() => (s.comms.active && s.comms.current?.id === id ? s : null), 20000);
|
|
if (!live) return false;
|
|
const box = s.comms.box;
|
|
const el = box?.video?.video;
|
|
const clipIsHim =
|
|
box.videoSpec?.key === videoKey && // his loader key (not OAC's)
|
|
(!el || (el.currentSrc ?? '').includes('trestle.mp4')); // his file on the wire
|
|
const contract =
|
|
s.comms.paused === true && // the ack pauses the action
|
|
!!box &&
|
|
!!box.button && // the ACKNOWLEDGE button
|
|
clipIsHim &&
|
|
!!el && el.muted === true && el.loop === true && el.paused === false && // muted, looping, playing
|
|
s.sound.isPlaying(voiceKey) === true; // his voice
|
|
d.push(`${id}: paused=${s.comms.paused} box=${!!box} ackBtn=${!!box?.button} clip=${el ? el.paused === false && el.muted : '—'} loop=${el?.loop ?? '—'} videoKey=${box?.videoSpec?.key ?? '—'} elSrc=${el ? (el.currentSrc ?? '').split('/').pop() : '—'} voice=${voiceKey} playing=${s.sound.isPlaying(voiceKey)}`);
|
|
// Press ACKNOWLEDGE (the player's one move while it holds).
|
|
box.button.panel.emit('pointerdown');
|
|
await until(() => (s.comms.current?.id !== id ? true : null), 6000);
|
|
return contract;
|
|
};
|
|
|
|
const meetupOk = await ackStep('trestle-meetup', 'comms_au_trestle-intro-01', 'comms_vid_trestle');
|
|
await sleep(300);
|
|
const questOk = await ackStep('trestle-quest', 'comms_au_trestle-intro-02', 'comms_vid_trestle');
|
|
|
|
// ---- the ledger holds; the pair re-arms nothing --------------------
|
|
await sleep(700); // > the endedWithin(200) race window
|
|
const quiet = s.comms.active === false && s.comms.queue.length === 0;
|
|
const ledger = s.registry.get('commsPlayed');
|
|
d.push(`ledger: played=${JSON.stringify(ledger ? Array.from(ledger) : [])}`);
|
|
const playedOk = !!ledger && ledger.has('trestle-meetup') && ledger.has('trestle-quest');
|
|
const rearm = s.comms.trigger('firstForeignPlanet');
|
|
d.push(`re-arm: firstForeignPlanet=${rearm} (expect 0 — the ledger holds)`);
|
|
|
|
const allOk = quiet0 && homeGuard && armOk && meetupOk && questOk && quiet && playedOk && rearm === 0 && errors.length === 0;
|
|
const errLine = errors.length ? ['console errors:', ...errors.slice(0, 4)] : [];
|
|
window.__trestleResult = { ok: allOk, details: [...d, ...errLine] };
|
|
setReport(['TRESTLE PAIR SELF-CHECK ' + (allOk ? 'PASS ✔' : 'FAIL ✘'), '', ...d, ...errLine]);
|
|
} catch (err) {
|
|
window.__trestleResult = { ok: false, details: ['EXCEPTION: ' + String(err?.stack ?? err), ...d] };
|
|
setReport(['TRESTLE PAIR SELF-CHECK FAIL ✘', String(err?.stack ?? err), ...d]);
|
|
}
|
|
}, 600);
|