# Рендер: Renderer, pixel-perfect, камера, IsoDepthLayer, частицы

## Renderer и pixel-perfect

Виртуальное разрешение (например 480×270) растягивается на экран **целым** числом —
каждый виртуальный пиксель всегда N×N экранных. Это и есть pixel-perfect:

- `computeScale(vw, vh, winW, winH)` — максимальный целый масштаб, при котором
  виртуальный экран влезает в окно;
- `renderer.resize(newScale)` — смена масштаба (CSS-размер канваса);
  Engine делает это автоматически при ресайзе окна (`autoResize: true`);
- канвасу выставлены `image-rendering: pixelated`, `antialias: false`, `roundPixels: true`.

Корневые контейнеры:

```ts
engine.renderer.worldRoot  // мир — сюда применяется камера
engine.renderer.uiRoot     // UI поверх мира, камерой не двигается
```

Оверлей fade-переходов добавляется в `uiRoot` самим Engine — всегда верхний слой.

## Camera

Позиция камеры — центр взгляда в **мировых виртуальных пикселях**:

```ts
const camera = engine.camera;
camera.follow(targetX, targetY);        // следить за точкой
camera.bounds = { x, y, width, height }; // прямоугольник мира
```

Важно: границы могут начинаться с отрицательных координат. Изометрическая карта
(ромб) уходит в минус по X — её западный угол при 28×28 тайлах ≈ `-448`:

```ts
const size = map.screenSize; // { width, height } в экранных пикселях
camera.bounds = { x: -size.width / 2, y: 0, width: size.width, height: size.height };
```

Позиция округляется до целого пикселя (`apply`), поэтому арт не дрожит при движении.

## IsoDepthLayer

Сортировка глубины для сущностей на изометрической карте: глубина = `tx + ty`
(чем юго-восточнее, тем «ближе»). Герой, NPC, деревья добавляются сюда, а не в слой карты:

```ts
import { IsoDepthLayer } from '@rpg/engine';

const actors = new IsoDepthLayer();       // sortableChildren = true внутри
engine.renderer.worldRoot.addChild(actors);

actors.add(heroView, tileX, tileY);       // добавить и выставить глубину
actors.setDepth(heroView, tileX, tileY);  // обновить при движении (по смене тайла)
```

Слой карты (`IsometricTileMap.view`) и слой сущностей — соседи: карта рисует
высокие объекты со своей внутренней сортировкой, сущности сортируются отдельно.
Для корректного перекрытия герой/дерево должны быть в одном слое с деревьями —
либо используйте `tall`-объекты карты и держите сущности поверх (см. recipes).

## Particles

Эмиттер частиц для атмосферы (пепел, мотыльки, пыль, искры). Частицы — подкрашенные
квадратики 1–4 виртуальных пикселя, детерминированные по seed:

```ts
import { ParticleEmitter } from '@rpg/engine';

const moths = new ParticleEmitter({
    color: 0xf0d878,
    rate: 3,                            // частиц в секунду
    lifetime: [2, 5],                   // сек
    velocity: { x: [-6, 6], y: [-9, -3] },
    acceleration: { y: 1.5 },           // «тяжёлый» полёт мотылька
    size: 2,
    spawnArea: { width: 480, height: 200 },
    seed: 42
});
moths.position.set(240, 135);
engine.renderer.worldRoot.addChild(moths);

// в update сцены:
moths.update(dt);
// при выходе со сцены:
moths.clear(); moths.destroy();
```

## Порядок отрисовки

Стек сцен рисуется снизу вверх; внутри сцены порядок задают дочерние контейнеры
(`worldRoot`: карта → сущности → эффекты; `uiRoot`: HUD → диалоги → fade-оверлей).
`SceneManager.render()` вызывает `render()` всех сцен в стеке (не только верхней).