Newer
Older
rpg / docs / engine / practices.md

Практики работы агента с движком и игрой

Живой документ: каждая новая находка (грабля, удачный приём, экономящая время последовательность) пополняет этот файл в том же коммите, где она появилась. Структура — по ситуациям агента, не по подсистемам: нашёл свою ситуацию → скопировал последовательность шагов.

Ситуация: проверяю своё изменение

  1. npm run typecheck — быстрый отсев.
  2. npx vitest run <затронутый тест> — точечный прогон.
  3. node tools/agent.mjs check --pretty — полный прогон (typecheck + тесты + карты + карты-файлы равны генераторам + браузерная проверка инвариантов).
  4. Смоук акта 1 (node tools/smoke-act1.mjs) — не сломал ли существующий геймплей.

Ситуация: добавляю локацию

  1. Генератор в tools/maps/gen.mjs (детерминированный сид) → npm run maps.
  2. LocationId + LocationDef в data/locations.ts: spawn/npcs/enemies/exits.
  3. Экспорт через AREAS/areaOf и загрузка в BootScene по Object.keys(AREAS).
  4. Валидатор проверит спавн/врагов/exits автоматически (agent:invariants): инвариант in-wall у врага — почти всегда реальный баг данных (враг поставлен на воду/дерево). Чинить данные, а не валидатор.
  5. Сценарий в tools/checks/: переход туда и обратно через walkTo + waitFor.

Ситуация: добавляю NPC/диалог

  1. NpcDef в data/npcs.ts, граф в data/dialogues.ts.
  2. Валидатор проверит: несуществующие next/choiceserror, недостижимые узлы → warn (сироты допустимы, но проверь, что это не забытая ветка).
  3. Проверка через мост: walkTo до соседнего тайла → tapTile(NPC)runDialogue() → прочитать flags/dialogue.text из снапшота.

Ситуация: упал смоук / проверка

См. agent.md «waitFor и переходы сцен». Частые причины, по частоте:

  1. Действие проглочено fade-переходом: begin() молча отбрасывает параллельный переход, сцена игнорирует клики при transitioning. Перед действием — waitFor('!s.transitioning').
  2. Клик по тайлу вне экрана: в смоук-скриптах с реальными кликами цель за краем канваса (виртуальный y > 270) клик мимо. Через мост не актуально (tapTile в юнитах), но в старых скриптах — целься в промежуточный тайл.
  3. walkTo в занятый тайл (NPC, высокий объект): тайл непроходим, маршрут не найден. Кликни в сам NPC-тайл (tapTile) — сцена подведёт героя сама.
  4. Гонка rAF и инъекции: инъекция и шаг должны быть в одном JS-стеке — высокоуровневые методы моста (tapTile/press/key/walkTo) уже атомарны; сырые inject* из страницы + отдельный step — гонка с rAF.
  5. s.error в снапшоте — мост поймал исключение, читай текст.

Ситуация: меняю API движка

  1. Обнови соответствующий docs/engine/*.md и этот файл (если появился новый приём) в том же коммите.
  2. Экспорт только через packages/engine/src/index.ts — под-пути движка импортировать нельзя.
  3. npm run typecheck ловит разрывы в обоих пакетах; тесты движка — в Vitest без браузера (Pixi-зависимости — через стабы, см. GameLoop.manual.test.ts).

Ситуация: пишу новый сценарий tools/checks/

  1. Каркас: import { startDevServer, openGame, Checks } from '../agent-lib.mjs'; export default async function ({ pretty }); вернуть c.finish({pretty}).
  2. Проверка: возвращает false или кидает исключение при провале (строка — это детали успеха!); удобнее c.expect(cond, msg, details).
  3. Ввод — через мост в юнитах (walkTo/tapTile/press), не через реальные клики по CSS-пикселям.
  4. Текст реакций мира читай из s.lastToast (текст + тик) — не из скриншотов.

Грабли среды (кратко, подробности в CLAUDE.md)

  • Assets.load без Assets.init() висит навсегда — грузи только через AssetLoader.
  • Headless Chromium: --autoplay-policy=no-user-gesture-required обязателен; ctx.resume() без него не резолвится.
  • Маяки консоли: [boot] ассеты загружены (BootScene), [location] <id> (вход в локацию) — на них строится openGame.
  • window.__agent только в DEV-сборке; для прод-сборки — VITE_AGENT=1 (когда понадобится).
  • Vite-алиас @rpg/engine — только regex; строковый перехватывает под-пути.