Newer
Older
rpg / apps / game / tools / checks / final.mjs
/**
 * Сценарий final — финал акта 2: правильный звон Старой Машины.
 * 1) путь акта 2: Звенец -> роща -> разъезд -> люк в зал Машины;
 * 2) без настроенного резонатора Машина отказывает (locked-текст);
 * 3) чит-настройка (quest_resonator_done): действие E у Машины -> реакция ставит
 *    machine_lulled и запускает катсцену (тост «дышит тише», конец);
 * 4) повторное действие: Машина спит;
 * 5) инварианты чисты после финала.
 * Запуск: node tools/agent.mjs run tools/checks/final.mjs
 */
import { withChecks } from '../lib.mjs';

export default async function ({ pretty }) {
    return withChecks(
        'final',
        async (t) => {
            const { c } = t;
            await c.run('путь акта 2: спуск через люк в зал Машины', async () => {
                await t.boot();
                await t.sleepEnemies();
                await t.goToArea('zvenets', { x: 26, y: 14 });
                await t.sleepEnemies();
                await t.ctx.agent.command('scene:teleport', { x: 25, y: 10 });
                await t.goToArea('rust_grove', { x: 26, y: 10 });
                await t.sleepEnemies();
                await t.goToArea('old_siding', { x: 22, y: 10 });
                await t.sleepEnemies();
                await t.goToArea('machine_hall', { x: 15, y: 15 });
                const s = await t.ctx.agent.snapshot();
                c.expect(s.area === 'machine_hall', 'герой не в зале Машины', s.area);
                return null;
            });
            await c.run('без резонатора Машина отказывает', async () => {
                const used = await t.useInteractable('old_machine');
                c.expect(used, 'не подошли к Машине');
                const w = await t.ctx.agent.waitFor('(s.lastToast?.text ?? "").includes("не настроен")', {
                    timeoutTicks: 1200
                });
                c.expect(w.ok, 'Машина не отказала без резонатора', w.snapshot.lastToast);
                c.expect(!w.snapshot.flags.includes('machine_lulled'), 'флаг machine_lulled без настройки');
                return null;
            });
            await c.run('правильный звон: катсцена, machine_lulled', async () => {
                await t.ctx.agent.command('scene:setFlag', { flag: 'quest_resonator_done' });
                const used = await t.useInteractable('old_machine');
                c.expect(used, 'не подошли к Машине');
                const f = await t.ctx.agent.waitFor('s.flags.includes("machine_lulled")', {
                    timeoutTicks: 1200
                });
                c.expect(f.ok, 'реакция не подняла machine_lulled', f.snapshot.flags);
                const toast = await t.ctx.agent.waitFor('(s.lastToast?.text ?? "").includes("дышит тише")', {
                    timeoutTicks: 1200
                });
                c.expect(toast.ok, 'нет тоста финала', toast.snapshot.lastToast);
                await t.ctx.agent.waitFor('!s.cutscene?.active', { timeoutTicks: 1200 });
                return null;
            });
            await c.run('повторное действие: Машина спит', async () => {
                const used = await t.useInteractable('old_machine');
                c.expect(used, 'не подошли к Машине');
                const w = await t.ctx.agent.waitFor('(s.lastToast?.text ?? "").includes("спит")', {
                    timeoutTicks: 1200
                });
                c.expect(w.ok, 'повторная реакция не про «сон»', w.snapshot.lastToast);
                return null;
            });
            await c.run('инварианты чисты после финала', async () => {
                await t.expectInvariantsClean('после финала акта 2');
                return null;
            });
        },
        { pretty }
    );
}