/**
* Гейт аудио v2 (трек «аудио»): на слух в headless не проверить — читаем
* DEV-шпион audioInfo() (кольцевой буфер фактических запусков, 24 записи).
* 1) вход в игру; 2) тишина на старте (нет амбиента/боевых звуков);
* 3) шаги героя играют (варианты step_ash); 4) атака — bell_hit;
* 5) плевок плевуна — spit (позиционный, с затуханием); 6) резонанс —
* bell_low; 7) лог ограничен 24 записями.
*
* Запуск: node apps/demo/tools/checks/audio.mjs [--port 5313]
*/
import { fileURLToPath } from 'node:url';
import { launchBrowser, startDevServer, openGame, parseArgs } from '@rpg/engine/tools/agent-lib.mjs';
const args = parseArgs(process.argv.slice(2));
const port = Number(args.port ?? 5313);
const DEMO_DIR = fileURLToPath(new URL('../../', import.meta.url));
const server = startDevServer({ port, cwd: DEMO_DIR });
const report = { name: 'audio', ok: false, checks: [] };
const check = (title, ok, info = null) => {
report.checks.push({ title, ok, info });
console.error(`${ok ? '✓' : '✗'} ${title}${info ? ' — ' + JSON.stringify(info) : ''}`);
};
/** Ключи в DEV-логе (фактические запуски звука). */
const keys = (client) => client.call('audioInfo').then((i) => i.log.map((e) => e.key));
/** Чистая поляна у точки старта: герой (40,60) вокс = (13.33,20) юнит. */
const H = { x: 40, z: 60 };
try {
await server.waitReady;
const browser = await launchBrowser();
try {
const client = await openGame(browser, { url: `http://localhost:${port}/` });
const errs = client.errors();
check('нет pageerror при загрузке', errs.length === 0, errs.slice(0, 3));
await client.call('setManual', true);
// Демка стартует в меню — входим в игру и проходим фейд (48 тиков) + запас
await client.call('startGame', false);
await client.call('stepTicks', 60);
const si = await client.call('sceneInfo');
check('вошли в игру', si.current === 'game' && !si.transitioning, si);
// На лугах тишина: амбиента в демке нет, герой не двигался, боя не было
const log0 = await keys(client);
check('на старте тишина (шпион подключён)', Array.isArray(log0) && log0.length === 0, log0);
// Шаги героя: движение → step_ash в логе (варианты #0/#1/#2 — буферы
// рантайм-синтеза; до unlock играл бы базовый спек)
await client.call('setInput', 1, 1);
await client.call('stepTicks', 30);
await client.call('setInput', null, null);
await client.call('stepTicks', 2);
const log1 = await keys(client);
check('шаги героя играют (step_ash)', log1.some((k) => k.startsWith('sfx/step_')), log1);
// Атака конусом у ног героя: звон удара (volume с позиции d=0 → base)
await client.call('attackInput', true);
await client.call('attackInput', false);
await client.call('stepTicks', 5);
const log2 = await keys(client);
check('атака сыграла bell_hit', log2.some((k) => k === 'sfx/bell_hit'), log2);
// Плевок плевуна: позиционный звук — сгусток в радиусе слышимости (12),
// но не в конусе (2.17 юнита > 1.0): звон будит, плевун стреляет (~2.2 с)
// Кулдаун конуса 0.45 с — ждём, иначе атака-пробуждение не выйдет
await client.call('stepTicks', 30);
await client.call('combatClear');
await client.call('heroTeleport', H.x, H.z);
await client.call('stepTicks', 2);
await client.call('combatSpawn', 'spitter', 15.5, 20);
await client.call('attackInput', true);
await client.call('attackInput', false); // звон-пробуждение без урона
await client.call('stepTicks', 240);
const log3 = await keys(client);
check('плевок сыграл spit', log3.some((k) => k === 'sfx/spit'), log3);
// Резонанс: заряд ≥ 0.6 с → низкий звон (мимо сгустков — тишина боя)
await client.call('combatClear');
await client.call('stepTicks', 2);
await client.call('attackInput', true);
await client.call('stepTicks', 40); // 0.67 с — заряд накоплен
await client.call('attackInput', false);
await client.call('stepTicks', 5);
const log4 = await keys(client);
check('резонанс сыграл bell_low', log4.some((k) => k === 'sfx/bell_low'), log4);
// Кольцевой буфер: долгая прогулка — шагов больше 24, лог обрезается
await client.call('setInput', 1, 1);
await client.call('stepTicks', 600); // ~25 шагов при 2.5 шага/с
await client.call('setInput', null, null);
await client.call('stepTicks', 2);
const info = await client.call('audioInfo');
check('лог ограничен 24 записями', info.log.length <= 24 && info.log.length > 0, info.log.length);
await client.call('combatClear');
report.ok = report.checks.every((c) => c.ok);
} finally {
await browser.close();
}
} catch (err) {
check('гейт упал', false, String(err));
} finally {
server.stop();
console.log(JSON.stringify(report));
}