Живой документ: каждая новая находка (грабля, удачный приём, экономящая время последовательность) пополняет этот файл в том же коммите, где она появилась. Структура — по ситуациям агента, не по подсистемам: нашёл свою ситуацию → скопировал последовательность шагов.
npm run typecheck — быстрый отсев.npx vitest run <затронутый тест> — точечный прогон.node tools/agent.mjs check --pretty — полный прогон (typecheck + тесты + карты + карты-файлы равны генераторам + браузерная проверка инвариантов + сценарий переходов tools/checks/transitions.mjs).node tools/smoke-act1.mjs) — не сломал ли существующий геймплей.tools/maps/gen.mjs (детерминированный сид) → npm run maps.AreaId + AreaDef в data/locations.ts: spawn/npcs/enemies/transitions.AREAS/areaOf и загрузка в BootScene по Object.keys(AREAS).agent:invariants): инвариант in-wall у врага — почти всегда реальный баг данных (враг поставлен на воду/дерево). Чинить данные, а не валидатор.tools/checks/: переход туда и обратно через walkTo + waitFor (готовый образец — tools/checks/transitions.mjs).TransitionDef в data/locations.ts: trigger: 'step' (наступил, выходы между областями) или 'click' (двери/колодцы; герой подходит к тайлу сам). Условия — requiresFlag/requiresItem + lockedText (тост).npm run maps; тайл-триггер может быть непроходимым (колодец) — клик-переход обрабатывается до маршрутизации движения.npm run maps при провале «fresh»-теста чинит файлы, но сначала прочти провал: расхождение «тайлов N, ожидалось W×H» = индекс за пределами карты в генераторе (разреженный массив дорос, см. граблю ниже).tools/checks/transitions.mjs — шаг до перехода, клик-переход туда и обратно, снапшот s.transitions (триггеры и цели).buildInteriorMap() в data/map.ts (рамка WALL, пол FLOOR, дверной проём снизу) — не рисуй рамку руками.TransitionDef { trigger: 'click' } в AreaDef улицы; выход — TransitionDef { target: { kind: 'return' }, trigger: 'step' } в интерьере (проём). returnTo фиксируется в момент клика по двери (тайл героя), не в момент выхода — в сценарии храни тайл клика и сравнивай с ним после возврата.InteractableDef в INTERACTABLES (data/interactables.ts): реакции по when (первая подходящая), once поднимает флаг used:<id>, эффекты — setFlags/setVar/gives, текст виден в s.lastToast.tools/checks/interact.mjs (образец: вход → сундук → used → выход → возврат на тайл клика).waitFor моста видит полный снапшот игры (s.flags, s.inventory, s.interactables) — предикаты над прогрессом пиши против него, а не против сценических полей.NpcDef в data/npcs.ts, граф в data/dialogues.ts.next/choices → error, недостижимые узлы → warn (сироты допустимы, но проверь, что это не забытая ветка).walkTo до соседнего тайла → tapTile(NPC) → runDialogue() → прочитать flags/dialogue.text из снапшота.См. agent.md «waitFor и переходы сцен». Частые причины, по частоте:
begin() молча отбрасывает параллельный переход, сцена игнорирует клики при transitioning. Перед действием — waitFor('!s.transitioning').tapTile в юнитах), но в старых скриптах — целься в промежуточный тайл.tapTile) — сцена подведёт героя сама. Интерактив на ПУТИ: walkTo кликает по узлам маршрута, и клик в тайл мота/цветка срабатывает как взаимодействие — герой останавливается рядом. Начиная с батча 3 такие узлы пропускаются автоматически; в старых сценариях веди героя через промежуточные точки в стороне от объектов.tapTile/press/key/walkTo) уже атомарны; сырые inject* из страницы + отдельный step — гонка с rAF.s.error в снапшоте — мост поймал исключение, читай текст.docs/engine/*.md и этот файл (если появился новый приём) в том же коммите.packages/engine/src/index.ts — под-пути движка импортировать нельзя.npm run typecheck ловит разрывы в обоих пакетах; тесты движка — в Vitest без браузера (Pixi-зависимости — через стабы, см. GameLoop.manual.test.ts).import { startDevServer, openGame, Checks } from '../agent-lib.mjs'; export default async function ({ pretty }); вернуть c.finish({pretty}).false или кидает исключение при провале (строка — это детали успеха!); удобнее c.expect(cond, msg, details).walkTo/tapTile/press), не через реальные клики по CSS-пикселям.s.lastToast (текст + тик) — не из скриншотов.Assets.load без Assets.init() висит навсегда — грузи только через AssetLoader.--autoplay-policy=no-user-gesture-required обязателен; ctx.resume() без него не резолвится.[boot] ассеты загружены (BootScene), [location] <id> (вход в локацию) — на них строится openGame.window.__agent только в DEV-сборке; для прод-сборки — VITE_AGENT=1 (когда понадобится).@rpg/engine — только regex; строковый перехватывает под-пути.tiles[at(x,y)] с индексом за W×H молча дорастит разреженный массив — ловится только тестом «fresh» («тайлов 583, ожидалось 560»). После правки генератора всегда npm run maps.stepResult обязан await fn() — без await промис считается успехом (ok:true, ms:1, details:{}).