/**
* Сценарий interact — интерьеры и интерактивные объекты.
* 1) дверь: клик по дому Ирвина -> интерьер house_elder;
* 2) сундук (once): предмет в сумке, used:<id>, повтор молчит;
* 3) очаг: реакция зависит от флага quest_bells_done;
* 4) записка (once): флаг read_note;
* 5) выход через проём: step kind 'return' -> Звенец, герой на тайле входа;
* 6) лавка Милы: прилавок меняет реакцию с флагом.
* Запуск: node tools/agent.mjs run tools/checks/interact.mjs
*/
import { startDevServer, openGame, Checks } from '../agent-lib.mjs';
export default async function ({ pretty }) {
const c = new Checks('interact');
const server = await startDevServer();
let ctx;
let entryTile = null; // тайл, с которого герой кликнул дверь (цель kind 'return')
try {
await c.run('дверь: дом Ирвина -> интерьер', async () => {
ctx = await openGame({ url: server.url, newGame: true });
await ctx.agent.command('scene:sleepAll');
// Добираемся до Звенца пешком (луга -> тропа (26,14)).
const w0 = await ctx.agent.walkTo(26, 14, { timeoutTicks: 3000 });
c.expect(w0, 'walkTo(26,14) не дошёл');
const wz = await ctx.agent.waitFor('s.area === "zvenets"', { timeoutTicks: 600 });
c.expect(wz.ok, 'не в Звенце после перехода', { area: wz.snapshot.area });
await ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 600 });
// Подводим героя к дому (тайл дома (7,6)) и кликаем по нему.
const wh = await ctx.agent.walkTo(8, 6, { timeoutTicks: 2000 });
c.expect(wh, 'не дошёл до дома Ирвина');
entryTile = (await ctx.agent.snapshot()).hero.tile;
await ctx.agent.tapTile(7, 6);
const w = await ctx.agent.waitFor('s.area === "house_elder"', { timeoutTicks: 1200 });
c.expect(w.ok, 'клик по дому не открыл интерьер', { area: w.snapshot.area });
await ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 600 });
const s = await ctx.agent.snapshot();
c.expect(s.hero.tile.x === 5 && s.hero.tile.y === 7, 'герой не у проёма (5,7)', s.hero.tile);
return null;
});
await c.run('сундук: предмет + used, повтор молчит', async () => {
const before = await ctx.agent.snapshot();
const had = before.inventory.find((i) => i.id === 'cloth')?.count ?? 0;
// Клик по сундуку (3,3) — герой подходит и открывает.
await ctx.agent.tapTile(3, 3);
const w = await ctx.agent.waitFor(
`(s.inventory.find((i) => i.id === 'cloth')?.count ?? 0) === ${had + 1}`,
{ timeoutTicks: 1200 }
);
c.expect(w.ok, 'сундук не дал полотно', { inventory: w.snapshot.inventory });
const s = await ctx.agent.snapshot();
const chest = (s.interactables ?? []).find((o) => o.id === 'chest_elder');
c.expect(chest?.used === true, 'сундук не помечен used', chest);
c.expect((s.flags ?? []).includes('used:chest_elder'), 'нет флага used:chest_elder');
// Повтор: тик последнего тоста не меняется.
const t0 = s.lastToast?.tick ?? -1;
await ctx.agent.tapTile(3, 3);
await ctx.agent.step(120);
const s2 = await ctx.agent.snapshot();
c.expect((s2.lastToast?.tick ?? -1) === t0, 'повторное взаимодействие не молчит', s2.lastToast);
return null;
});
await c.run('очаг: реакция меняется с флагом', async () => {
await ctx.agent.tapTile(8, 0);
await ctx.agent.step(120);
const s1 = await ctx.agent.snapshot();
const text1 = s1.lastToast?.text ?? '';
c.expect(text1.includes('щепки'), 'реакция очага до флага не та', text1);
await ctx.agent.command('scene:setFlag', { flag: 'quest_bells_done' });
await ctx.agent.tapTile(8, 0);
await ctx.agent.step(120);
const s2 = await ctx.agent.snapshot();
c.expect(
(s2.lastToast?.text ?? '').includes('ровнее'),
'реакция очага после флага не та',
s2.lastToast
);
return null;
});
await c.run('записка: флаг read_note', async () => {
await ctx.agent.tapTile(10, 4);
await ctx.agent.step(120);
const s = await ctx.agent.snapshot();
c.expect((s.flags ?? []).includes('read_note'), 'записка не подняла read_note', s.flags);
return null;
});
await c.run('выход через проём: возврат в Звенец', async () => {
// Клик по тайлу проёма (5,8) — герой доходит, step-переход kind 'return'.
await ctx.agent.tapTile(5, 8);
const w = await ctx.agent.waitFor('s.area === "zvenets"', { timeoutTicks: 1200 });
c.expect(w.ok, 'проём не вернул в Звенец', { area: w.snapshot.area });
await ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 600 });
const s = await ctx.agent.snapshot();
c.expect(
s.hero.tile.x === entryTile.x && s.hero.tile.y === entryTile.y,
'герой не на тайле, откуда кликнул дверь',
{ entry: entryTile, after: s.hero.tile }
);
return null;
});
await c.run('лавка Милы: прилавок меняет реакцию', async () => {
// Клик по лавке (21,9) — интерьер; прилавок (5,3).
await ctx.agent.tapTile(21, 9);
const w = await ctx.agent.waitFor('s.area === "shop"', { timeoutTicks: 1200 });
c.expect(w.ok, 'лавка не открылась', { area: w.snapshot.area });
await ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 600 });
await ctx.agent.tapTile(5, 3);
await ctx.agent.step(120);
const s1 = await ctx.agent.snapshot();
const text1 = s1.lastToast?.text ?? '';
c.expect(text1.includes('соль'), 'реакция прилавка до флага не та', text1);
await ctx.agent.tapTile(5, 3);
await ctx.agent.step(120);
const s2 = await ctx.agent.snapshot();
c.expect(
(s2.lastToast?.text ?? '').includes('цветы'),
'прилавок не изменил реакцию после флага',
s2.lastToast
);
return null;
});
await c.run('снапшот: интерактивные объекты области', async () => {
const s = await ctx.agent.snapshot();
const list = s.interactables ?? [];
c.expect(list.some((o) => o.id === 'counter_shop'), 'прилавка нет в снапшоте', list);
c.expect(list.every((o) => typeof o.used === 'boolean'), 'объект без поля used', list);
return null;
});
} finally {
await ctx?.browser?.close();
server.stop();
}
return c.finish({ pretty }).ok ? 0 : 1;
}