/** * Dev-only: exercise the VISUAL + AUDIO comm paths in the live GameScene. * The shipped roster is ack-only (the newGame welcome); this page overrides * the comms config at boot so 'newGame' arms, in FIFO order: * * t-visual → box opens, clip plays ONCE (loop=false), NO ACK button, * NO pause (comms.paused stays false, the ship is not parked), * and the comm FINISHES ON ITS OWN when the voice ends * (the isPlaying poll → visualHoldMs → fade). * t-audio → NO box at all (comms.box is null), voice plays, no pause, * finishes on its own. * * Headless note: the AudioContext is suspended, so the 12.6 s clip never * reaches its natural end — the test stops the voice with stopByKey(), * which is exactly what the hub's isPlaying poll sees as "the voice ended" * (isPlaying → false). The hub must then finish the comm by itself. * * node dev/cdp-shot.mjs "http://127.0.0.1:8090/dev/comms-types.html" o.png \ * "window.__commsTypes" 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(); // --- override: two newGame comms — one visual, one audio --------------- const voice = 'assets/speech/oac/intro-01.mp3'; data.comms = { ...(data.comms ?? {}), comms: [ { id: 't-visual', from: 'oac', type: 'visual', trigger: 'newGame', text: 'A short visual comm — the action should keep running.', audio: voice, once: true, }, { id: 't-audio', from: 'oac', type: 'audio', trigger: 'newGame', audio: voice, once: true, // second in the queue (FIFO behind the visual) }, ], }; 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 log = (m) => { report.textContent += m + '\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 = []; try { const s = () => game.scene.getScene('GameScene'); const ready = await until(() => { const sc = s(); return sc && sc.ship && sc.comms ? sc : null; }); if (!ready) throw new Error('scene never ready'); // ---- VISUAL ------------------------------------------------------- const live = await until(() => (s().comms.active && s().comms.current?.id === 't-visual' ? s() : null)); if (!live) throw new Error('visual comm never came up'); const box = s().comms.box; const el = box?.video?.video; d.push(`visual: active=true paused=${s().comms.paused} (expect false) box=${!!box} button=${!!box?.button} (expect false)`); d.push(`visual: clip loop=${el ? el.loop : '—'} (expect false) playing=${el ? el.paused === false : '—'}`); d.push(`visual: shipFrozen=${s().ship.body.velocity.length() >= 1} (expect false — the comm did NOT park the ship)`); const visualOk = s().comms.paused === false && // NO pause (the visual contract) !!box && box.button === null && // no ACKNOWLEDGE button !!el && el.loop === false && // the clip plays ONCE el.paused === false; // The voice is talking; end it the way the hub's poll sees an end. const vKey = await until(() => (s().comms._voiceKey && s().sound.isPlaying(s().comms._voiceKey) ? s().comms._voiceKey : null)); if (!vKey) throw new Error('visual voice never started'); s().sound.stopByKey(vKey); const visualDone = await until(() => (s().comms.current?.id !== 't-visual' ? true : null), 5000); d.push(`visual: voice=${vKey} auto-finished=${!!visualDone} boxGone=${s().comms.box === null} paused=${s().comms.paused}`); const ok = visualOk && !!visualDone && s().comms.box === null; // ---- AUDIO -------------------------------------------------------- const aLive = await until(() => (s().comms.active && s().comms.current?.id === 't-audio' ? s() : null), 10000); if (!aLive) throw new Error('audio comm never came up'); const aNoBox = s().comms.box === null; const aPaused = s().comms.paused; const aVoiceKey = s().comms._voiceKey; const aVoice = aVoiceKey ? s().sound.isPlaying(aVoiceKey) : null; d.push(`audio: active=true box=${!!s().comms.box} (expect false) paused=${aPaused} (expect false) voice=${aVoice} (expect true)`); if (aVoiceKey) s().sound.stopByKey(aVoiceKey); const aDone = await until(() => (s().comms.current?.id !== 't-audio' ? true : null), 5000); d.push(`audio: auto-finished=${!!aDone}`); const okAll = ok && aNoBox && aPaused === false && aVoice === true && !!aDone; window.__commsTypes = { ok: okAll, details: d }; console.log('[comms-types]', okAll ? 'PASS' : 'FAIL', d.join(' | ')); } catch (err) { window.__commsTypes = { ok: false, details: [...d, 'EXCEPTION: ' + String(err?.stack ?? err)] }; console.error('[comms-types] FAIL', err); } }, 600);