Newer
Older
rpg / apps / game / tools / checks / big_house.mjs
/**
 * Сценарий big_house — большой дом 2×2 в Звенце (первый проп с footprint):
 * 1) коллизия: footprint непроходим, соседний тайл проходим;
 * 2) scene:walk в дом — false, approach — к соседу;
 * 3) глубина: герой с юга (перед домом) и с севера (за домом) — скриншоты.
 * Запуск: node tools/agent.mjs run tools/checks/big_house.mjs
 */
import { withChecks } from '../lib.mjs';

export default async function () {
    return withChecks(
        'big_house',
        async (t) => {
            const { c } = t;
            await c.run('снапшот: проп дома в collision-слое', async () => {
                await t.boot();
                await t.sleepEnemies();
                await t.goToArea('zvenets', { x: 54, y: 28 });
                const s = await t.ctx.agent.snapshot();
                // collision-слой снапшота отдаёт пропы без id — ищем по footprint.
                const prop = (s.collision.props ?? []).find((p) => p.x === 46 && p.y === 24);
                c.expect(!!prop, 'проп (46,24) 2×2 не в collision-слое', s.collision.props);
                c.expect(prop && prop.w === 2 && prop.h === 2, 'footprint пропа не 2×2', prop);
                const idx = (x, y) => y * s.collision.width + x;
                c.expect(
                    s.collision.blocked[idx(46, 24)] === 1 && s.collision.blocked[idx(47, 25)] === 1,
                    'клетки footprint пропа не заблокированы'
                );
                return null;
            });
            await c.run('scene:walkable: дом непроходим, сосед свободен', async () => {
                const walk = (x, y) => t.ctx.agent.command('scene:walkable', { x, y });
                c.expect((await walk(46, 24)) === false, 'северо-запад дома проходим');
                c.expect((await walk(47, 25)) === false, 'юго-восток дома проходим');
                c.expect((await walk(48, 24)) === true, 'тайл восточнее дома заблокирован');
                return null;
            });
            await c.run('scene:walk в дом — false, approach — к соседу', async () => {
                await t.ctx.agent.command('scene:teleport', { x: 48, y: 24 });
                await t.ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 60 });
                const walked = await t.ctx.agent.command('scene:walk', { x: 46, y: 24 });
                c.expect(walked === false, 'scene:walk провёл героя в дом', walked);
                const dest = await t.ctx.agent.approach(46, 24);
                c.expect(dest !== null, 'approach к дому не нашёл соседа');
                return null;
            });
            await c.run('глубина: герой с юга и с севера дома', async () => {
                const shot = async (name) => {
                    await t.ctx.agent.step(2, { render: true });
                    await t.ctx.page.screenshot({ path: `/tmp/rpg_big_house_${name}.png` });
                };
                const teleport = async (x, y) => {
                    await t.ctx.agent.command('scene:teleport', { x, y });
                    await t.ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 60 });
                    const s = await t.ctx.agent.snapshot();
                    c.expect(
                        s.hero.tile.x === x && s.hero.tile.y === y,
                        `телепорт в (${x},${y}) не удался`,
                        s.hero.tile
                    );
                };
                // Юг: герой вплотную перед домом — перекрывает его низ.
                await teleport(46, 26);
                await shot('south');
                // Север: дом ближе к камере — перекрывает героя целиком.
                await teleport(46, 23);
                await shot('north');
                return null;
            });
            const inv = await t.ctx.agent.invariants();
            const errs = inv.filter((i) => i.severity === 'error');
            c.expect(errs.length === 0, 'инварианты с пропом не чисты', errs);
        }
    );
}