/**
* Сценарий 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 { startDevServer, openGame, Checks } from '../agent-lib.mjs';
export default async function ({ pretty }) {
const c = new Checks('interact-world');
const server = await startDevServer();
let ctx;
try {
await c.run('столб у тропы: тост с текстом знака', async () => {
ctx = await openGame({ url: server.url, newGame: true });
await ctx.agent.command('scene:sleepAll');
// Столб (25,13) у тропы в Звенец — клик издалека: герой подойдёт сам.
await ctx.agent.tapTile(25, 13);
const w = await 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 ctx.agent.tapTile(11, 14);
const w = await 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 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 ctx.agent.tapTile(14, 20);
const w2 = await ctx.agent.waitFor('s.vars.motes === 2', { timeoutTicks: 1500 });
c.expect(w2.ok, 'второй мот не поднял вар до 2', w2.snapshot.vars);
await ctx.agent.tapTile(14, 6);
const w3 = await 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).
const w = await ctx.agent.walkTo(2, 2, { timeoutTicks: 3000 });
c.expect(w, 'не дошёл до тропы на пруды');
await ctx.agent.waitFor('s.area === "ponds"', { timeoutTicks: 600 });
await ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 600 });
await ctx.agent.tapTile(3, 2);
const ws = await ctx.agent.waitFor('(s.lastToast?.text ?? "").includes("накатом")', {
timeoutTicks: 900
});
c.expect(ws.ok, 'знак наката не показал текст', ws.snapshot.lastToast);
// Обратно на луга (2,2) прудов.
const wb = await ctx.agent.walkTo(2, 2, { timeoutTicks: 3000 });
c.expect(wb, 'не вернулся к тропе с прудов');
await ctx.agent.waitFor('s.area === "meadows"', { timeoutTicks: 600 });
await ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 600 });
return null;
});
await c.run('верёвка колокола: флаг + повтор с другим текстом', async () => {
// Звенец через тропу (26,14); верёвка у башни (13,7) — клик издалека.
const w = await ctx.agent.walkTo(26, 14, { timeoutTicks: 3000 });
c.expect(w, 'не дошёл до тропы в Звенец');
await ctx.agent.waitFor('s.area === "zvenets"', { timeoutTicks: 600 });
await ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 600 });
await ctx.agent.tapTile(13, 7);
const wr = await ctx.agent.waitFor('s.flags.includes("rang_tower")', {
timeoutTicks: 1500
});
c.expect(wr.ok, 'звон не поднял rang_tower', wr.snapshot.flags);
const s1 = await ctx.agent.snapshot();
const text1 = s1.lastToast?.text ?? '';
c.expect(text1.includes('ушёл в землю'), 'первый звон — не тот текст', text1);
await ctx.agent.tapTile(13, 7);
await ctx.agent.step(150);
const s2 = await ctx.agent.snapshot();
const text2 = s2.lastToast?.text ?? '';
c.expect(
text2.includes('снова') && text2 !== text1,
'повторный звон не сменил текст',
text2
);
return null;
});
await c.run('Мила: знакомство -> повтор с веткой по мотам', async () => {
// Первое знакомство (trader_first): полотно.
await ctx.agent.tapTile(17, 11);
const d1 = await ctx.agent.runDialogue(900);
c.expect(d1, 'первый диалог с Милой не завершился');
// Повтор (trader_repeat): при моты >= 1 после первой реплики идёт
// ветка о мота́х — листаем вручную и ловим её текст.
await ctx.agent.tapTile(17, 11);
const o = await ctx.agent.waitFor('s.dialogue != null', { timeoutTicks: 900 });
c.expect(o.ok, 'повторный диалог не открылся');
await ctx.agent.press('advance');
const w = await ctx.agent.waitFor(
's.dialogue != null && (s.dialogue.text ?? "").includes("Моты")',
{ timeoutTicks: 300 }
);
c.expect(w.ok, 'в повторном диалоге нет ветки о мота́х', w.snapshot.dialogue);
const d2 = await ctx.agent.runDialogue(600);
c.expect(d2, 'повторный диалог не завершился');
return null;
});
await c.run('инвариант: used-флаги и снапшот согласованы', async () => {
const inv = await ctx.agent.invariants();
const bad = inv.filter((i) => i.severity === 'error');
c.expect(bad.length === 0, 'ошибки инвариантов', bad);
return null;
});
} finally {
await ctx?.browser?.close();
server.stop();
}
return c.finish({ pretty }).ok ? 0 : 1;
}