/**
* Сценарий resonator — полный цикл квеста «Голос для резонатора» (акт 2).
* 1) крючок акта 2: флаг act2_hook -> Клинт в Ржавой роще;
* 2) Клинт: clint_first -> флаги met_clint + quest_resonator_taken;
* 3) звон у трёх деревьев-механизмов (интерактивы tone_tree_N):
* vars.tones растёт до 3, деревья помечены флагами;
* 4) сдача: clint_tune (квест-стадия ready) -> quest_resonator_done,
* кат-сцена настройки: тост «Резонатор держит тон», конец;
* 5) инварианты чисты после полного квеста.
* Запуск: node tools/agent.mjs run tools/checks/resonator.mjs
*/
import { withChecks } from '../lib.mjs';
/** Тайлы деревьев-механизмов (data/quests.ts QUEST_TONE_TILES). */
const TREES = [
[16, 3],
[6, 5],
[10, 15]
];
/** Тайл Клинта в роще (npcs.ts placement). */
const CLINT = { x: 11, y: 9 };
export default async function ({ pretty }) {
return withChecks(
'resonator',
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:setFlag', { flag: 'act2_hook' });
// К восточному переходу — телепортом в Звенце: пешком из юго-восточного
// спавна маршрут уводит через город и мост не доживает.
await t.ctx.agent.command('scene:teleport', { x: 25, y: 10 });
await t.goToArea('rust_grove', { x: 26, y: 10 });
await t.sleepEnemies();
const s = await t.ctx.agent.snapshot();
c.expect((s.npcs ?? []).some((n) => n.id === 'clint'), 'Клинта нет в роще после act2_hook', s.npcs);
return null;
});
await c.run('Клинт: clint_first, флаги знакомства и взятия квеста', async () => {
await t.ctx.agent.tapTile(CLINT.x, CLINT.y);
const o = await t.ctx.agent.waitFor('s.dialogue?.id === "clint_first"', { timeoutTicks: 2400 });
c.expect(o.ok, 'диалог clint_first не открылся', o.snapshot.dialogue);
const d = await t.ctx.agent.runDialogue(900);
c.expect(d, 'диалог clint_first не завершился');
const w = await t.ctx.agent.waitFor(
's.flags.includes("met_clint") && s.flags.includes("quest_resonator_taken")',
{ timeoutTicks: 300 }
);
c.expect(w.ok, 'граф не поднял флаги знакомства и взятия квеста', w.snapshot.flags);
return null;
});
await c.run('звон у деревьев-механизмов: tones 0 -> 3', async () => {
for (const [i, [tx, ty]] of TREES.entries()) {
await t.ctx.agent.tapTile(tx, ty);
const w = await t.ctx.agent.waitFor(
`s.vars.tones === ${i + 1} && s.flags.includes("tone_tree_${i + 1}")`,
{ timeoutTicks: 2400 }
);
c.expect(w.ok, `дерево (${tx},${ty}) не отдало тон`, { vars: w.snapshot.vars });
}
return null;
});
await c.run('сдача: clint_tune, кат-сцена настройки, флаг done', async () => {
// Звон у деревьев шумит: краулер у (10,15) просыпается и может
// убить героя по дороге к Клинту — усыпляем перед сдачей.
await t.sleepEnemies();
await t.ctx.agent.tapTile(CLINT.x, CLINT.y);
const o = await t.ctx.agent.waitFor('s.dialogue?.id === "clint_tune"', { timeoutTicks: 2400 });
c.expect(o.ok, 'квест-стадия не дала clint_tune', o.snapshot.dialogue);
const d = await t.ctx.agent.runDialogue(900);
c.expect(d, 'диалог clint_tune не завершился');
const w = await t.ctx.agent.waitFor('s.flags.includes("quest_resonator_done")', {
timeoutTicks: 300
});
c.expect(w.ok, 'нет флага сдачи резонатора', w.snapshot.flags);
// Кат-сцена настройки: тост и конец (клики блокируются до её конца).
const toast = await t.ctx.agent.waitFor('(s.lastToast?.text ?? "").includes("держит тон")', {
timeoutTicks: 900
});
c.expect(toast.ok, 'нет тоста настройки', toast.snapshot.lastToast);
await t.ctx.agent.waitFor('!s.cutscene?.active', { timeoutTicks: 900 });
return null;
});
await c.run('инварианты чисты после полного квеста', async () => {
await t.expectInvariantsClean('после квеста резонатора');
return null;
});
},
{ pretty }
);
}