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

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

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

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`/`choices` → `error`, недостижимые
   узлы → `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; строковый перехватывает под-пути.