/**
* Оперативный просмотр участка карты агентом: headless-браузер, состояние
* сюжета (область/флаги/вары/время суток), камера на точку мира БЕЗ
* телепорта героя, тики, снимок, выход. Для «увидеть» — дебаг и визуальные
* вопросы; для проверок — числовые снапшоты моста (детерминизм).
*
* Запуск (от v2/):
* node apps/game/tools/shot.mjs --area rust_grove --at 24,16 \
* [--flags act2_hook,rang_tower] [--vars tones=3] [--clock 1134] \
* [--ash off] [--ticks 10] [--port 5322] --out /tmp/v2_shot.png
*/
import { fileURLToPath } from 'node:url';
import { AgentClient, launchBrowser, startDevServer, parseArgs } from '@rpg/engine/tools/agent-lib.mjs';
const args = parseArgs(process.argv.slice(2));
const port = Number(args.port ?? 5322);
const out = args.out ?? '/tmp/v2_shot.png';
/** «24,16» → {x, y} в тайлах v1. */
function tile(s) {
const [x, y] = String(s).split(',').map(Number);
if (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error(`--at ожидает tx,ty — дано ${s}`);
return { x, y };
}
const server = startDevServer({ port, cwd: fileURLToPath(new URL('../', import.meta.url)) });
try {
await server.waitReady;
const browser = await launchBrowser();
try {
// Загрузка с диагностикой: pageerror/консоль видны даже при таймауте.
const page = await browser.newPage();
const pageErrors = [];
page.on('pageerror', (e) => pageErrors.push(String(e)));
page.on('console', (m) => console.error('[console]', m.type(), m.text().slice(0, 200)));
await page.goto(`http://localhost:${port}/`, { waitUntil: 'domcontentloaded', timeout: 60000 });
try {
await page.waitForFunction('window.__beacon === true', { timeout: 60000 });
} catch {
console.error('маяк не зажёгся; pageerror:', pageErrors.slice(0, 5));
for (const l of (page.consoleLogs ?? []).slice(0, 10)) console.error(l);
throw new Error('игра не загрузилась');
}
await new Promise((r) => setTimeout(r, 1200)); // первый кадр, прогрев
const client = new AgentClient(page);
client.pageErrors = pageErrors; // openGame делает это сам — тут вручную
await client.call('setManual', true);
if (args.ash === 'off') await client.call('setAsh', false); // чистый мир
if (args.clock !== undefined) await client.call('setClock', Number(args.clock));
if (args.flags || args.vars) {
await client.call('setState', {
flags: args.flags ? String(args.flags).split(',').filter(Boolean) : [],
vars: args.vars
? Object.fromEntries(String(args.vars).split(',').filter(Boolean).map((kv) => kv.split('=')))
: {},
});
}
await client.call('gotoArea', args.area ?? 'meadows');
// Камера на участок (герой остаётся на спавне, триггеры не трогаем).
if (args.at) {
const t = tile(args.at);
const c = await client.call('tileCenter', t.x, t.y);
await client.call('setCam', c.x, c.z);
}
await client.call('stepTicks', Number(args.ticks ?? 10));
const path = await client.screenshot(out);
console.log(`снимок: ${path} (область ${args.area ?? 'meadows'}, камера ${args.at ?? 'у спавна'})`);
const errs = client.errors();
if (errs.length) console.error('pageerror:', errs.slice(0, 3));
} finally {
await browser.close();
}
} catch (e) {
console.error(e);
process.exit(1);
}
process.exit(0); // chromium-хэндлы держат event loop — выходим явно