Newer
Older
rpg / apps / game / tools / checks / music.mjs
/**
 * Сценарий music — музыкальный слой: тема лугов генерируется правилами
 * (data/music.ts, сид) и компилируется движковым секвенсором в рантайме
 * (playTheme → playLoopBuffer), факт запуска — в window.__gameAudioLog.
 * 1) новая игра в лугах: music/meadows в логе (тема играет) + амбиент;
 * 2) тема не перезапускается при возврате в ту же область (guard);
 * 3) уход в Звенец (темы нет): слот темы снят (themeKey '') и тема лугов
 *    не перезапускается. Динамическая музыка разрешена: стем (настороженность)
 *    и тема боя (погоня тяжеловеса у тропы — его аггро-радиус на пути).
 * Запуск: node tools/agent.mjs run tools/checks/music.mjs
 */
import { withChecks } from '../lib.mjs';

export default async function ({ pretty }) {
    return withChecks(
        'music',
        async (t) => {
            const { c } = t;
            /** Сколько раз ключ встречается в логе (null-лог → 0). */
            const countKey = async (key) =>
                ((await t.readAudioLog()) ?? []).filter((e) => e.key === key).length;
            /** Слот темы страницы (window.__game.themeKey) или null. */
            const themeKey = () =>
                t.ctx.page.evaluate(() => {
                    const g = window.__game;
                    return g ? g.themeKey : null;
                });
            await c.run('новая игра в лугах: тема скомпилирована и играет', async () => {
                await t.boot();
                const log = await t.waitAudioLog((l) => l.some((e) => e.key === 'music/meadows'));
                c.expect(
                    log.some((e) => e.key === 'music/meadows'),
                    'темы лугов нет в аудио-логе (компиляция не удалась?)',
                    log
                );
                c.expect(
                    log.some((e) => e.key === 'ambience/meadows'),
                    'амбиент лугов пропал при старте темы'
                );
                return null;
            });
            await c.run('возврат в ту же область: тема не перезапускается (guard)', async () => {
                const before = await countKey('music/meadows');
                // Шагнуть туда-обратно мимо переходов, сцена не меняется — guard ключа.
                await t.ctx.agent.walkTo(15, 14, { timeoutTicks: 1500 });
                await t.ctx.agent.walkTo(14, 14, { timeoutTicks: 1500 });
                c.expect((await countKey('music/meadows')) === before, 'тема перезапустилась без смены ключа', {
                    before,
                    after: await countKey('music/meadows')
                });
                return null;
            });
            await c.run('уход в Звенец: слот темы снят, тема лугов не перезапускается', async () => {
                const before = await countKey('music/meadows');
                // Пешком к тропе в Звенец (26,14) — честный step-переход.
                const walked = await t.ctx.agent.walkTo(26, 14, { timeoutTicks: 3000 });
                c.expect(walked, 'walkTo(26,14) не дошёл до перехода');
                // Слот темы освобождается (тема Звенца пуста; тема боя погони —
                // временный ключ, ждём его снятия после деаггро).
                for (let i = 0; i < 12 && (await themeKey()) !== ''; i++) {
                    await t.ctx.agent.step(30);
                }
                c.expect((await themeKey()) === '', 'слот темы не снят при уходе из лугов');
                c.expect(
                    (await countKey('music/meadows')) === before,
                    'тема лугов перезапустилась (guard ключа сломан)',
                    { before, after: await countKey('music/meadows') }
                );
                return null;
            });
        },
        { pretty }
    );
}