Newer
Older
rpg / v2 / apps / demo / tools / checks / audio.mjs
/**
 * Гейт аудио 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));
}