/**
* Сценарий art_b4 — визуальная проверка AI-арта B4 (задача 5):
* столбы, моты, верёвка колокола, очаг, записка, прилавок, резонатор,
* деревья-механизмы, Старая Машина (view.tex) и иконки сумки 8×8.
* Скриншоты — /tmp/rpg_b4_*.png; численных assert'ов минимум: смена области,
* открытая сумка, инварианты. Запуск: node tools/agent.mjs run tools/checks/art_b4.mjs
*/
import { withChecks } from '../lib.mjs';
const shot = async (t, name) => {
await t.ctx.agent.step(2, { render: true });
await t.ctx.page.screenshot({ path: `/tmp/rpg_b4_${name}.png` });
};
export default async function ({ pretty }) {
return withChecks(
'art_b4',
async (t) => {
const { c } = t;
let a; // агентный мост появляется после boot
await c.run('луга: столб и мот', async () => {
await t.boot();
a = t.ctx.agent;
await t.sleepEnemies();
await a.command('scene:teleport', { x: 47, y: 27 });
await a.waitFor('!s.transitioning', { timeoutTicks: 60 });
await shot(t, 'sign_meadows');
await a.command('scene:teleport', { x: 15, y: 28 });
await a.waitFor('!s.transitioning', { timeoutTicks: 60 });
await shot(t, 'mote');
return null;
});
await c.run('сумка: иконки предметов', async () => {
for (const id of ['clock', 'cloth', 'bellflower', 'salt', 'water_flask', 'mote', 'map_scroll']) {
await a.command('scene:give', { id });
}
await a.press('inventory');
// Открытие — push с fade: сцена сменится, потом fade отыграть.
await a.waitFor('s.scene !== "location"', { timeoutTicks: 300 });
await a.step(14, { render: true });
await t.ctx.page.screenshot({ path: '/tmp/rpg_b4_inventory.png' });
await a.press('menu');
await a.waitFor('s.scene === "location"', { timeoutTicks: 300 });
return null;
});
await c.run('Звенец: столб и верёвка колокола', async () => {
await t.goToArea('zvenets', { x: 54, y: 28 });
await a.command('scene:teleport', { x: 5, y: 19 });
await a.waitFor('!s.transitioning', { timeoutTicks: 60 });
await shot(t, 'sign_zvenets');
await a.command('scene:teleport', { x: 27, y: 15 });
await a.waitFor('!s.transitioning', { timeoutTicks: 60 });
await shot(t, 'bell_rope');
return null;
});
await c.run('дом Ирвина: очаг и записка', async () => {
c.expect(await t.interactAt(13, 12), 'дверь дома Ирвина не открылась');
await a.waitFor('s.area === "house_elder"', { timeoutTicks: 1200 });
await a.waitFor('!s.transitioning', { timeoutTicks: 600 });
await a.command('scene:teleport', { x: 8, y: 2 });
await a.waitFor('!s.transitioning', { timeoutTicks: 60 });
await shot(t, 'hearth_note');
// Шаг в проём (5,8) — назад в Звенец.
c.expect(await a.walkTo(5, 8, { timeoutTicks: 3000 }), 'не вышли из дома');
await a.waitFor('s.area === "zvenets"', { timeoutTicks: 1200 });
await a.waitFor('!s.transitioning', { timeoutTicks: 600 });
return null;
});
await c.run('лавка Милы: прилавок', async () => {
c.expect(await t.interactAt(43, 19), 'дверь лавки не открылась');
await a.waitFor('s.area === "shop"', { timeoutTicks: 1200 });
await a.waitFor('!s.transitioning', { timeoutTicks: 600 });
await a.command('scene:teleport', { x: 5, y: 5 });
await a.waitFor('!s.transitioning', { timeoutTicks: 60 });
await shot(t, 'counter_shop');
c.expect(await a.walkTo(5, 8, { timeoutTicks: 3000 }), 'не вышли из лавки');
await a.waitFor('s.area === "zvenets"', { timeoutTicks: 1200 });
await a.waitFor('!s.transitioning', { timeoutTicks: 600 });
return null;
});
await c.run('роща: дерево-механизм и резонатор', async () => {
await t.goToArea('rust_grove', { x: 54, y: 20 });
const s = await a.snapshot();
const tree = (s.interactables ?? []).find((i) => i.id === 'tone_tree_1');
c.expect(tree, 'tone_tree_1 не найден', s.interactables?.map((i) => i.id));
await a.command('scene:teleport', { x: tree.tile.x, y: tree.tile.y + 2 });
await a.waitFor('!s.transitioning', { timeoutTicks: 60 });
await shot(t, 'tone_tree');
await a.command('scene:teleport', { x: 24, y: 18 });
await a.waitFor('!s.transitioning', { timeoutTicks: 60 });
await shot(t, 'resonator');
return null;
});
await c.run('зал Машины: Старая Машина', async () => {
await t.goToArea('old_siding', { x: 46, y: 20 });
await t.goToArea('machine_hall', { x: 28, y: 31 });
await a.command('scene:teleport', { x: 13, y: 4 });
await a.waitFor('!s.transitioning', { timeoutTicks: 60 });
await shot(t, 'machine_hall');
return null;
});
const inv = await a.invariants();
const errs = inv.filter((i) => i.severity === 'error');
c.expect(errs.length === 0, 'инварианты не чисты', errs);
},
{ pretty }
);
}