# Архитектура (ECS на miniplex) Проект переписан на TypeScript + PixiJS с ECS-ядром на [miniplex](https://github.com/hmans/miniplex). Этот документ — описание того, как всё устроено сейчас. ## Слои ```txt ввод (InputSource) → шина событий → симуляция (мир + системы) → шина событий → рендер (Pixi) и DOM-UI ``` Каждый слой знает только соседей через интерфейсы: логика не знает про Pixi и DOM, рендер не содержит игровой логики, ввод не знает про игру — только команды. ## Сущности и компоненты (src/ecs) Сущность — объект с опциональными компонентами; «есть компонент» = свойство не `undefined`. | Компонент | Смысл | | -------------------------------------------------------- | ------------------------------------------------------------------------- | | `position`, `velocity`, `damping` | Движение (враги, снаряды, частицы — одна система интеграции) | | `enemyKind`, `enemyBox` | Тип (simple/heavy/boss) и габариты рамки | | `armor` | Слои слов: текущий слой отображается и уязвим только он | | `projectile`, `player`, `target` / `targetPoint`, `fuse` | Снаряд: слово, кто выстрелил, цель (или точка промаха), задержка выстрела | | `lifetime`, `particle` | Частицы — полноценные сущности, а не особый массив | | `waveState` | Синглтон-сущность с состоянием волны и баннером | Броня единая для всех типов: simple — 1 слой из 1 слова, heavy — 2 слоя по 1 слову (порядок строгий), boss — 3 слоя по 3 слова (внутри слоя порядок свободный). Урон снимает только текущий слой; после каждого попадания вью перерисовывается по диффу `layerIndex:killed`. ## Системы (порядок за кадр, src/systems) 1. `fuseSystem` — снаряд стоит у стрелка, пока не истечёт задержка ввода (150 мс), потом получает скорость. 2. `homingSystem` — наведение на цель, касание → событие `projectile:hit`; цель исчезла — снаряд удаляется. 3. `missSystem` — промах долетает до точки → `projectile:miss`. 4. `waveSystem` — таймеры волны, спавн, пауза, боссы; пишет в синглтон `waveState`. 5. `integrationSystem` — единое движение для всех сущностей с position+velocity (и damping). 6. `cullSystem` — снаряды за экраном. 7. `escapeSystem` — враг дошёл до низа → `enemy:escaped` → урон. 8. `lifetimeSystem` — затухание частиц. `Simulation` (src/game) задаёт порядок систем, принимает команды ввода (поиск цели по слову), обрабатывает попадания/промахи/урон и держит скаляры партии (счёт, жизни, фаза, тряска) в `GameStore` — по договорённости это не сущности. ## Ввод (src/input) — отдельный интерфейс Игра зависит только от порта `InputSource`, а не от библиотеки: ```ts interface InputSource { readonly origin: "local" | "chat"; start(): void; stop(): void; onCommand(handler: (command: InputCommand) => void): () => void; } type InputCommand = | { kind: "start" } // !play, награда за баллы канала и т.п. | { kind: "clear" } | { kind: "word"; word: string; player: Player }; ``` Сейчас реализованы `TwitchChatInputSource` (tmi.js, парсит `!play` и первое слово сообщения) и `LocalKeyboardInputSource` (тест без Twitch). Будущий переход на Twurple и запуск через баллы канала = новый источник, который эмитит `{ kind: 'start' }` на событие награды; `InputRouter` просто регистрирует его рядом с остальными. ## Рендер (src/render) - `GridLayer` — сетка одним `TilingSprite` (вместо ~40 `stroke()` за кадр). - `ParticleLayer` — все частицы в одном `ParticleContainer` (один draw call, пул объектов). - Свечение — заранее запечённая радиальная текстура с аддитивным блендингом вместо попиксельного `shadowBlur`. - Вью врагов/снарядов создаются и уничтожаются по сигналам мира (`world.onEntityAdded/onEntityRemoved`), каждый кадр только синхронизируют позицию/альфу. - Тряска экрана — смещение корневого контейнера, состояние в `GameStore`. - Цикл: внешний `GameClock` (rAF, delta в 60-кадрах), pixi-ticker остановлен; в простое (интро закончилось, партия не идёт) рендер вообще не тикает — canvas хранит последний кадр. Сбой одного кадра не убивает цикл. ## События (src/game/events.ts) Типизированная шина `EventBus`: ввод, бой (`projectile:hit/miss`, `enemy:*`) и состояние партии (`game:started/over`, `score/wave/lives`, `effect:glitch`). Рендер и DOM-UI — только подписчики. ## DOM-UI (src/ui) HUD, экраны старта/поражения, таблица зрителей (имена вставляются через `textContent` — ник из чата не должен стать разметкой), фидбек ввода, масштабирование оверлея под окно. ## Отладка В консоли браузера доступен хук `window.__sw` — мир, стор, шина, метрики и рендерер (`__sw.world.with('armor').entities`, `__sw.store.score` и т.д.).