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 { startDevServer, openGame, Checks } from '../lib.mjs';

/** Прочитать DEV-лог аудио из страницы. */
const readLog = (page) => page.evaluate(() => window.__gameAudioLog ?? null);

/**
 * Ждать условие на логе: декод WAV в headless занимает заметное время,
 * поэтому после действия шагаем и опрашиваем лог, а не читаем его сразу.
 */
async function waitAudio({ page, agent }, pred, tries = 12) {
    let log = await readLog(page);
    for (let i = 0; i < tries && !pred(log); i++) {
        await agent.step(30);
        log = await readLog(page);
    }
    return log;
}

export default async function ({ pretty }) {
    const c = new Checks('audio');
    const server = await startDevServer();
    let ctx;
    try {
        await c.run('новая игра: амбиент лугов запущен ровно один раз', async () => {
            ctx = await openGame({ url: server.url, newGame: true });
            await ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 600 });
            const log = await readLog(ctx.page);
            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 ctx.agent.command('scene:sleepAll');
            const before = (await readLog(ctx.page)).filter((e) => e.key.startsWith('sfx/step_')).length;
            await ctx.agent.walkTo(16, 14, { timeoutTicks: 1500 });
            const log = await waitAudio(
                ctx,
                (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 ctx.agent.press('attack');
            const log = await waitAudio(ctx, (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 = () =>
                readLog(ctx.page).then((log) => log.filter((e) => e.key === 'ambience/meadows').length);
            const before = await count();
            await ctx.page.evaluate(() => window.__game.playAmbience('ambience/meadows'));
            await 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 ctx.agent.walkTo(x, y, { timeoutTicks: 1500 });
            }
            const log = await readLog(ctx.page);
            c.expect(log.length <= 24, 'лог не обрезается до 24', log.length);
            c.expect(log.length > 0, 'лог пуст после прогулки');
            return null;
        });
    } finally {
        await ctx?.browser?.close();
        server.stop();
    }
    return c.finish({ pretty }).ok ? 0 : 1;
}