/**
* Сценарий 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);
}
);
}