# Демо «Типичная ночь в Мытищинском лесу» — ночной лес, свет, волны, апгрейды

Самый крупный пример игры на `$`: зомби-шутер в духе Vampire Survivors.
Игрок сам стреляет по ближайшему врагу, игрок только уворачивается и собирает
добычу. В темноте видно ровно столько, сколько светит фонарь.

```bash
./build/russiano2d --game demos --scene shooter_witch   # сам бой
./build/russiano2d --game demos --scene witch_menu      # меню-интро
```

| Управление | Что делает |
|---|---|
| `WASD` / стрелки | идти |
| `1` / `2` / `3` или мышь | выбрать карту апгрейда |
| `F` | переключить камерный пресет вручную |
| `R` | заново после смерти |
| `Esc` | в меню демо |

---

## 1. Что показывает движок

| Слой | Чем сделан | Где смотреть |
|---|---|---|
| лес | `<tilemap>` из ASCII, **автотайл** тропы по соседям | `buildForest()` |
| деревья | спрайты с коллизиями и подсказками для акустики | `buildForest()` |
| свет | `<light>` (мягкое свечение) + `engine.light.visibility` для теней от стволов | `buildLights()`, `drawVisibility()` |
| звук | `$.audio.zone/obstacles/damping`, реверберация поляны, панорама выстрелов | `installAudio()` |
| VFX | кровь, лужи, трассеры (`$.fx.ribbon`), молнии, ударные волны, hit-stop, тряска | `tickGore()`, `$.fx.*` |
| камера | пресеты кадра: «ночной лес», «кровавая луна» | `tickPresets()`, `$.gfx.postPreset` |
| апгрейды | пауза + три карты, выбранное меняет оружие и статы | `openUpgrade()`, `tickWeapons()` |
| меню и смерть | отдельные сцены `witch_menu` и экран проигрыша | `createMenuScene()` |

## 2. Как это устроено

Две сцены в одном демо: `witch_menu` — интро-меню (кнопка «Типичная ночь в Мытищинском лесу» в лаунчере
ведёт именно сюда), `shooter_witch` — сам забег. Обе живут в одном файле и
делят общие данные, но мир строится заново на входе в каждую — так видно, что
`$.scene` меняет и музыку, и весь мир.

```js
// земля: автотайл сам подбирает тайлы тропы по соседям
const ground = $.tilemap.fromASCII(rows, { '.': 1, ',': 2, 'p': PATH_MARK });
ground.autotile({ mode: 'terrain', terrain: 'path' });

// фонарь: свет — обычный узел, платится одним свечением на кадр
$('<light>', { radius: 235, intensity: 1, color: '#ffbe73', falloff: 2.2 })
    .at(lamp.x, lamp.y).appendTo($.world);
```

## 3. Своё такое демо

1. **Мир.** `<tilemap>` + `autotile` для органичной земли, спрайты для
   объектов — `docs/highlevel/tilemap.md`.
2. **Свет.** `<light>` на фонарях; если нужны тени от препятствий —
   `engine.light.visibility(...)` и рисование полигона из `$.render`
   (демо `light` больше нет, рабочий пример остался здесь).
3. **Волны.** Таймер + спавн за экраном: `$.time.every(ms, …)` и проверка,
   что враг вне кадра.
4. **Прогресс.** Кристаллы как `<pickup>`, уровень — свой счётчик,
   карты апгрейдов — пауза `$.time.pause()` и три кнопки.
5. **Проверка без рук.** `python3 tests/agent/demos_test.py shooter_witch` —
   тест гоняет 900 кадров боя и требует, чтобы враги гибли и в журнале не было
   ошибок игрового кода.

## 4. Производительность

Демо сознательно тяжёлое: тысячи тайлов, десятки врагов, кровь и частицы.
Ориентиры, на которые смотрели при написании: список узлов мира
переиспользуется, свет — узлы `<light>` вместо ручных полигонов на каждого
врага, кровь — пул объектов (`$.pool`). Если своё демо тормозит, начните с
`$.debug.profiler.on(true)` и `F1` — разбор в
[`docs/HIGH_LEVEL_API_PERF.md`](HIGH_LEVEL_API_PERF).

## 5. Ассеты

Лес — CC0 Kenney RPG/Urban, персонаж — лист из `demos/assets/art/characters`,
музыка и звуки — CC0 Juhani Junkala. Полный список с лицензиями:
[`demos/assets/CREDITS.md`](demos/assets/CREDITS).
