# Документация движка @rpg/engine

Движок — переиспользуемая основа для 2D-пиксельных игр в браузере. Первая игра на нём
— «Пепельные луга» (`apps/game`), она же живой пример использования всех API.

## Карта документации

| Файл | О чём |
|---|---|
| [getting-started.md](getting-started.md) | Новая игра с нуля: bootstrap, сцены, ассеты |
| [core.md](core.md) | Engine, GameLoop, EventBus, Tween, GameState, StateMachine, Settings |
| [anim.md](anim.md) | Клипы, SpriteAnimator, реестр тика engine.fx |
| [render.md](render.md) | Renderer, pixel-perfect, камера, IsoDepthLayer, частицы |
| [input.md](input.md) | Действия, клавиатура, мышь/тач, геймпад, виртуальный джойстик |
| [maps.md](maps.md) | Изометрия, A*, коллизии, формат карт, импорт из Tiled |
| [ui-and-dialogue.md](ui-and-dialogue.md) | UI-кит, PixelText, диалоговые графы |
| [cutscene.md](cutscene.md) | CutsceneRunner: кат-сцены из шагов |
| [registry.md](registry.md) | SceneRegistry: объекты сцены, индекс по тайлам, запросы at/near |
| [assets-audio-save.md](assets-audio-save.md) | AssetLoader, атласы, AudioManager, сейвы |
| [art-pipeline.md](art-pipeline.md) | Генератор пиксель-арта, палитра, замена на рисованный арт |
| [recipes.md](recipes.md) | «Как сделать…»: готовые решения типовых задач |
| [agent.md](agent.md) | Агентный мост: EngineAgent, window.__agent, инварианты, apps/game/tools/agent.mjs |
| [practices.md](practices.md) | Живой документ практик агента «по ситуациям» — пополняется в каждом коммите |

## Принципы

### 1. Жёсткая граница API

Игра импортирует движок **только** через `@rpg/engine` (весь API реэкспортирован из
`packages/engine/src/index.ts`). Прямые импорты `pixi.js` в игре — нарушение границы:
нужные типы реэкспортируются движком (`Container`, `Graphics`, `Text`, `Sprite`,
`Texture`). Это позволяет менять внутренности движка, не трогая игры.

### 2. Fixed timestep

Логика обновляется фиксированным шагом 60 Гц (`GameLoop`, аккумулятор, максимум
5 шагов за кадр). `dt` в `Scene.update` всегда одинаковый — физика и таймеры
детерминированы. Рендер происходит после каждого шага.

### 3. Мир в юнитах, UI в пикселях

- Внутренние координаты мира — **мировые юниты** (1 юнит = 1 тайл, float);
  экран — проекция с округлением до целого пикселя только на границе мир→экран
  (пиксель-арт резкий, `image-rendering: pixelated`, `roundPixels: true`).
- Виртуальное разрешение (по умолчанию 480×270) растягивается **целым** числом
  на экран (`computeScale`); UI, текст (VT323) и все display-эффекты живут в нём.
- Дистанции/радиусы/скорости — скаляры в юнитах (`px = units·tileW`, 32 px/юнит).

### 4. Движок жанронезависим

В `packages/engine` нет ни одного упоминания контента конкретной игры: `GameState`
— механика флагов и переменных, `DialogueRunner` — механика графов. Всё содержимое
(тексты, флаги сюжета, карты) живёт в приложении.

### 5. Инъекция вместо окружения

Всё, что зависит от браузера, инъецируется или изолируется: `StorageLike` для сейвов,
`resolveUrl` для ассетов, `parent` для канваса. Чистые модули (математика, A*, диалоги,
формат карт) тестируются в Node без браузера — Vitest.

## Структура пакетов

```
packages/engine/src/
  core/       Engine, GameLoop, EventBus, Tween+easing, GameState, StateMachine, Settings
  scene/      SceneManager (стек сцен + fade-переходы)
  render/     Renderer, Camera, IsoDepthLayer, Particles, computeScale
  input/      InputManager (клавиатура/мышь/тач/геймпад), VirtualJoystick
  map/        IsometricTileMap, pathfinding (A*), mapFormat (JSON+RLE), tiled-импортёр
  math/       Vec2, изометрия, seeded RNG
  dialogue/   DialogueRunner (графы диалогов, view-агностик)
  cutscene/   CutsceneRunner (кат-сцены из шагов, view-агностик)
  ui/         DialogueBox, Panel, Button, MenuList, PixelText
  anim/       FrameAnimation
  audio/      AudioManager (шины master/music/sfx/ambience, лупы, pan/rate)
  assets/     AssetLoader (текстуры, атласы)
  save/       SaveManager (JSON-слоты)
  agent/      EngineAgent, SceneAgent, инварианты (агентный мост)
  ecs/        World/createEntity/query/addSystem
  debug/      DebugOverlay
```

## Известные грабли (важно!)

Подробности — в корневом `CLAUDE.md`; краткий список:

- `Assets.load` без предварительного `Assets.init({})` **висит навсегда** без ошибок —
  `AssetLoader` делает это сам, но если грузите напрямую через Pixi, не забудьте.
- `app.renderer` недоступен до завершения `app.init()` — `Renderer.setup()` это учитывает.
- Экранный bbox изометрического ромба уходит в минус по X — но границы камеры
  задаются в юнитах (`map.worldBounds`), клэмп через проекцию считает это сам.
- `document.fonts` — шрифт загружается асинхронно; `ensurePixelFont()` вызывается
  до создания текста, но фолбэк monospace всегда безопасен.