/**
* Сценарий interact-world — взаимодействие с миром шире (батч 3).
* 1) столб у тропы: тост с текстом знака;
* 2) подбор мота: предмет в сумке, вар motes растёт, used:<id>;
* 3) второй и третий моты: вар копится (addVar, порядок не важен);
* 4) знак наката у прудов;
* 5) верёвка колокола: флаг rang_tower, повтор — другой текст;
* 6) Мила: знакомство -> повторный диалог с веткой по мотам;
* 7) инвариант interact-used-consistent чист.
* Запуск: node tools/agent.mjs run tools/checks/interact-world.mjs
*/
import { withChecks } from '../lib.mjs';
export default async function ({ pretty }) {
return withChecks(
'interact-world',
async (t) => {
const { c } = t;
await c.run('столб у тропы: тост с текстом знака', async () => {
await t.boot();
await t.sleepEnemies();
// Столб (25,13) у тропы в Звенец — клик издалека: герой подойдёт сам.
await t.ctx.agent.tapTile(25, 13);
const w = await t.ctx.agent.waitFor('(s.lastToast?.text ?? "").includes("Звенец")', {
timeoutTicks: 1500
});
c.expect(w.ok, 'знак не показал текст', w.snapshot.lastToast);
return null;
});
await c.run('мот 1: предмет + вар + used', async () => {
await t.ctx.agent.tapTile(11, 14);
const w = await t.ctx.agent.waitFor(
'(s.inventory.find((i) => i.id === "mote")?.count ?? 0) === 1 && s.vars.motes === 1',
{ timeoutTicks: 1500 }
);
c.expect(w.ok, 'подбор мота не дал предмет и вар', {
inventory: w.snapshot.inventory,
vars: w.snapshot.vars
});
const s = await t.ctx.agent.snapshot();
const mote = (s.interactables ?? []).find((o) => o.id === 'mote_1');
c.expect(mote?.used === true, 'мот не помечен used', mote);
return null;
});
await c.run('моты 2 и 3: вар копится (addVar)', async () => {
await t.ctx.agent.tapTile(14, 20);
const w2 = await t.ctx.agent.waitFor('s.vars.motes === 2', { timeoutTicks: 1500 });
c.expect(w2.ok, 'второй мот не поднял вар до 2', w2.snapshot.vars);
await t.ctx.agent.tapTile(14, 6);
const w3 = await t.ctx.agent.waitFor('s.vars.motes === 3', { timeoutTicks: 1500 });
c.expect(w3.ok, 'третий мот не поднял вар до 3', w3.snapshot.vars);
return null;
});
await c.run('знак наката у прудов', async () => {
// Тропа на пруды (2,2): step-переход; у берега — столб (3,2).
await t.goToArea('ponds', { x: 2, y: 2 });
await t.ctx.agent.tapTile(3, 2);
const ws = await t.ctx.agent.waitFor('(s.lastToast?.text ?? "").includes("накатом")', {
timeoutTicks: 900
});
c.expect(ws.ok, 'знак наката не показал текст', ws.snapshot.lastToast);
// Обратно на луга (2,2) прудов.
await t.goToArea('meadows', { x: 2, y: 2 });
return null;
});
await c.run('верёвка колокола: флаг + повтор с другим текстом', async () => {
// Звенец через тропу (26,14); верёвка у башни (13,7) — клик издалека.
await t.goToArea('zvenets', { x: 26, y: 14 });
await t.ctx.agent.tapTile(13, 7);
const wr = await t.ctx.agent.waitFor('s.flags.includes("rang_tower")', {
timeoutTicks: 1500
});
c.expect(wr.ok, 'звон не поднял rang_tower', wr.snapshot.flags);
const s1 = await t.ctx.agent.snapshot();
const text1 = s1.lastToast?.text ?? '';
c.expect(text1.includes('ушёл в землю'), 'первый звон — не тот текст', text1);
await t.ctx.agent.tapTile(13, 7);
await t.ctx.agent.step(150);
const s2 = await t.ctx.agent.snapshot();
const text2 = s2.lastToast?.text ?? '';
c.expect(
text2.includes('снова') && text2 !== text1,
'повторный звон не сменил текст',
text2
);
return null;
});
await c.run('Мила: знакомство -> повтор с веткой по мотам', async () => {
// Первое знакомство (trader_first): полотно.
await t.ctx.agent.tapTile(17, 11);
const d1 = await t.ctx.agent.runDialogue(900);
c.expect(d1, 'первый диалог с Милой не завершился');
// Повтор (trader_repeat): при моты >= 1 после первой реплики идёт
// ветка о мота́х — листаем вручную и ловим её текст.
await t.ctx.agent.tapTile(17, 11);
const o = await t.ctx.agent.waitFor('s.dialogue != null', { timeoutTicks: 900 });
c.expect(o.ok, 'повторный диалог не открылся');
await t.ctx.agent.press('advance'); // догнать печать (typewriter)
await t.ctx.agent.press('advance'); // следующая реплика
const w = await t.ctx.agent.waitFor(
's.dialogue != null && (s.dialogue.text ?? "").includes("Моты")',
{ timeoutTicks: 300 }
);
c.expect(w.ok, 'в повторном диалоге нет ветки о мота́х', w.snapshot.dialogue);
const d2 = await t.ctx.agent.runDialogue(600);
c.expect(d2, 'повторный диалог не завершился');
return null;
});
await c.run('инвариант: used-флаги и снапшот согласованы', async () => {
await t.expectInvariantsClean('после обхода мира');
return null;
});
},
{ pretty }
);
}