/**
* Сценарий 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();
// Столб у тропы в Звенец (луга, восток) — подходим и жмём E.
const opened = await t.useInteractable('sign_meadows_east');
c.expect(opened, 'не подошли к столбу');
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 () => {
const opened = await t.useInteractable('mote_1');
c.expect(opened, 'не подошли к моту');
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 () => {
let opened = await t.useInteractable('mote_2');
c.expect(opened, 'не подошли ко второму моту');
const w2 = await t.ctx.agent.waitFor('s.vars.motes === 2', { timeoutTicks: 1500 });
c.expect(w2.ok, 'второй мот не поднял вар до 2', w2.snapshot.vars);
opened = await t.useInteractable('mote_3');
c.expect(opened, 'не подошли к третьему моту');
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-переход; у берега — столб.
await t.goToArea('ponds', { x: 2, y: 2 });
const opened = await t.useInteractable('sign_ponds');
c.expect(opened, 'не подошли к знаку наката');
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); верёвка у башни — подходим и звоним.
await t.goToArea('zvenets', { x: 26, y: 14 });
const opened = await t.useInteractable('bell_rope');
c.expect(opened, 'не подошли к верёвке колокола');
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.useInteractable('bell_rope');
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): полотно.
const talked = await t.talkTo('trader');
c.expect(talked, 'первый диалог с Милой не открылся');
const d1 = await t.ctx.agent.runDialogue(900);
c.expect(d1, 'первый диалог с Милой не завершился');
// Повтор (trader_repeat): при моты >= 1 после первой реплики идёт
// ветка о мота́х — листаем вручную и ловим её текст.
const talked2 = await t.talkTo('trader');
c.expect(talked2, 'повторный диалог не открылся');
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 }
);
}