Newer
Older
rpg / apps / game / tools / checks / hero_depth.mjs
/**
 * Сценарий hero_depth — герой за tall-объектами (задача 19):
 * деревья/дома-тайлы/валуны живут в одном depth-слое с героем (глубина tx+ty),
 * поэтому с юга герой поверх дерева, с севера — дерево поверх героя.
 * Дерево ищется по коллизии: blocked-клетка с проходимым севером и югом,
 * не покрытая footprint'ом пропов.
 * Запуск: node tools/agent.mjs run tools/checks/hero_depth.mjs
 */
import { withChecks } from '../lib.mjs';

export default async function () {
    return withChecks(
        'hero_depth',
        async (t) => {
            const { c } = t;
            await c.run('снапшот: ищем дерево (blocked с проходимым севером/югом)', async () => {
                await t.boot();
                await t.sleepEnemies();
                // Новая игра стартует в лугах — переход не нужен.
                const s = await t.ctx.agent.snapshot();
                const w = s.collision.width;
                const blocked = (x, y) => s.collision.blocked[y * w + x] === 1;
                const covered = (x, y) =>
                    (s.collision.props ?? []).some(
                        (p) => x >= p.x && x < p.x + p.w && y >= p.y && y < p.y + p.h
                    );
                const cand = [];
                for (let y = 1; y < s.collision.height - 1; y++) {
                    for (let x = 1; x < w - 1; x++) {
                        if (blocked(x, y) && !covered(x, y) && !blocked(x, y - 1) && !blocked(x, y + 1)) {
                            cand.push({ x, y });
                        }
                    }
                }
                c.expect(cand.length > 0, 'в лугах не нашлось дерева с проходимым севером/югом');
                t.tree = cand[0];
                return null;
            });
            await c.run('глубина: с юга герой поверх, с севера — за деревом', async () => {
                const { x, y } = t.tree;
                const teleport = async (tx, ty, name) => {
                    await t.ctx.agent.command('scene:teleport', { x: tx, y: ty });
                    await t.ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 60 });
                    const s = await t.ctx.agent.snapshot();
                    c.expect(
                        s.hero.tile.x === tx && s.hero.tile.y === ty,
                        `телепорт в (${tx},${ty}) не удался`,
                        s.hero.tile
                    );
                    await t.ctx.agent.step(2, { render: true });
                    await t.ctx.page.screenshot({ path: `/tmp/rpg_hero_depth_${name}.png` });
                    // Численно: zIndex дерева (tx+ty) против героя — кто «ближе»
                    // к камере, тот и рисуется позже (sortableChildren).
                    const z = await t.ctx.page.evaluate(() => {
                        const scene = window.__game.scenes.current;
                        return {
                            heroZ: scene.player.view.zIndex,
                            // zIndex всех прочих детей actors: среди них — дерево.
                            treeZs: scene.actors.children
                                .filter((ch) => ch !== scene.player.view)
                                .map((ch) => ch.zIndex)
                        };
                    });
                    c.expect(
                        z.treeZs.includes(x + y),
                        `tall-вьюха дерева (z=${x + y}) не в actors-слое`,
                        z.treeZs?.slice(0, 12)
                    );
                    return z;
                };
                const south = await teleport(x, y + 1, 'south'); // герой перед деревом
                const north = await teleport(x, y - 1, 'north'); // герой за деревом
                c.expect(
                    south.heroZ > x + y && north.heroZ < x + y,
                    'глубина героя относительно дерева не меняется при телепорте',
                    { south: south.heroZ, north: north.heroZ, tree: x + y }
                );
                return null;
            });
            const inv = await t.ctx.agent.invariants();
            const errs = inv.filter((i) => i.severity === 'error');
            c.expect(errs.length === 0, 'инварианты не чисты', errs);
        }
    );
}