Newer
Older
rpg / v2 / apps / game / tools / shot.mjs
/**
 * Оперативный просмотр участка карты агентом: 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 — выходим явно