Newer
Older
rpg / apps / game / tools / checks / audio.mjs
/**
 * Сценарий audio — звук мира через DEV-шпион window.__gameAudioLog
 * (на слух в headless не проверить: читаем фактические запуски sfx/амбиента).
 * 1) новая игра: амбиент лугов запущен ровно один раз;
 * 2) шаги: step_* в логе, боевых звуков нет;
 * 3) атака: bell_hit;
 * 4) guard амбиента: повторный playAmbience того же ключа не перезапускает;
 * 5) лог ограничен 24 записями.
 * Запуск: node tools/agent.mjs run tools/checks/audio.mjs
 */
import { withChecks } from '../lib.mjs';

export default async function ({ pretty }) {
    return withChecks(
        'audio',
        async (t) => {
            const { c } = t;
            await c.run('новая игра: амбиент лугов запущен ровно один раз', async () => {
                await t.boot();
                const log = await t.readAudioLog();
                c.expect(Array.isArray(log), 'DEV-лог аудио недоступен (прод-сборка?)', log);
                const amb = log.filter((e) => e.key === 'ambience/meadows');
                c.expect(amb.length === 1, 'амбиент лугов должен стартовать ровно один раз', amb);
                return null;
            });
            await c.run('шаги: step_* в логе, боевых звуков нет', async () => {
                await t.sleepEnemies();
                const before = (await t.readAudioLog()).filter((e) => e.key.startsWith('sfx/step_')).length;
                await t.ctx.agent.walkTo(16, 14, { timeoutTicks: 1500 });
                const log = await t.waitAudioLog(
                    (l) => l.filter((e) => e.key.startsWith('sfx/step_')).length > before
                );
                const steps = log.filter((e) => e.key.startsWith('sfx/step_'));
                c.expect(steps.length > before, 'шагов в логе нет', { steps: steps.length });
                const combat = log.filter((e) => ['sfx/hurt', 'sfx/ash_die', 'sfx/spit'].includes(e.key));
                c.expect(combat.length === 0, 'боевые звуки без боя', combat);
                return null;
            });
            await c.run('атака: bell_hit в логе', async () => {
                await t.ctx.agent.press('attack');
                const log = await t.waitAudioLog((l) => l.some((e) => e.key === 'sfx/bell_hit'));
                c.expect(log.some((e) => e.key === 'sfx/bell_hit'), 'атака не сыграла bell_hit', log);
                return null;
            });
            await c.run('guard амбиента: тот же ключ не перезапускается', async () => {
                const count = async () =>
                    (await t.readAudioLog()).filter((e) => e.key === 'ambience/meadows').length;
                const before = await count();
                await t.ctx.page.evaluate(() => window.__game.playAmbience('ambience/meadows'));
                await t.ctx.agent.step(60);
                c.expect((await count()) === before, 'повторный playAmbience перезапустил трек', {
                    before,
                    after: await count()
                });
                return null;
            });
            await c.run('лог ограничен 24 записями', async () => {
                // Прогулка туда-обратно — шагов больше, чем вмещает кольцевой буфер.
                for (const [x, y] of [[17, 14], [15, 14], [17, 14], [15, 14], [17, 14], [15, 14]]) {
                    await t.ctx.agent.walkTo(x, y, { timeoutTicks: 1500 });
                }
                const log = await t.readAudioLog();
                c.expect(log.length <= 24, 'лог не обрезается до 24', log.length);
                c.expect(log.length > 0, 'лог пуст после прогулки');
                return null;
            });
        },
        { pretty }
    );
}