/**
* Сценарий 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;
}