# Matrix Engine

Движок анимированного фона в стиле "цифрового дождя" из Matrix с двумя
интерактивными "кибер-глазами" по центру экрана. Глаза открыты/моргают,
когда VPN выключен, и закрыты (с изменённой цветовой палитрой и паттерном
радужки), когда VPN включён — визуальный индикатор состояния защиты прямо
в фоне лендинга. Реагирует на движение мыши, тач/свайп, наклон телефона
(гироскоп), клики (шоковая волна + глитч-эффекты) и подстраивает качество
рендера под реальный FPS устройства.

## Архитектура

```
Rust (физика/симуляция) --WASM--> Web Worker (JS) --WebGL2--> <canvas>
```

Три слоя, каждый со своей причиной существования:

1. **Rust -> WebAssembly** (`src/lib.rs`). Вся симуляция — движение капель,
   параллакс, взаимодействие с курсором/тачем/наклоном, ударные волны,
   глитчи, логика взгляда и моргания глаз — считается в Rust и компилируется
   в WASM. Быстрый детерминированный PRNG и работа с плоскими `Vec<f32>`
   вместо объектов дают предсказуемую производительность на тысячах капель
   без пауз GC (в отличие от эквивалентной реализации на чистом JS).
2. **Web Worker** (`matrix_worker.js`). Владеет `OffscreenCanvas`, вызывает
   `engine.tick()` и рендерит результат через WebGL2. Всё это происходит
   **не на main thread** — тяжёлый цикл "физика + рендер" не блокирует
   основной поток страницы (React-рендер, обработку кликов по UI, скролл),
   даже если сам кадр матрицы просядет по FPS.
3. **WebGL2 + GPU-инстансинг**. Каждая капля — это не отдельный DOM-узел и
   не отдельный draw call, а один инстанс в `drawArraysInstanced`: тысячи
   символов рисуются одним вызовом на GPU, с данными на каждый инстанс
   (позиция, символ, яркость, масштаб), приходящими прямо из памяти WASM.

Итог: тяжёлые вычисления идут в нативно-скомпилированном Rust, тяжёлый
рендер — на GPU одним батчем, а JS на воркере выступает тонкой склейкой
между ними и не трогает основной поток вовсе.

## Файлы

| Файл | Роль |
|---|---|
| `Cargo.toml` | Манифест Rust-крейта `matrix-engine` (cdylib+rlib), зависимости `wasm-bindgen`, `console_error_panic_hook`. |
| `src/lib.rs` | Вся физика/симуляция на Rust: `FastRng` (xorshift32), структуры `Drop`/`GlitchBlock`/`Shockwave`/`GlassSymbol`, главная структура `MatrixEngine` с методом `tick()`. Пишет результат каждого кадра в плоские `Vec<f32>` буферы, которые JS читает напрямую из линейной памяти WASM (см. раздел ниже). |
| `NetrunnerMatrix.tsx` | React client-компонент. Создаёт `<canvas>`, один раз передаёт его в Web Worker через `transferControlToOffscreen()`, дальше управляет воркером message-протоколом (см. таблицу ниже) и слушает DOM-события (мышь/тач/гироскоп/видимость вкладки/ресайз контейнера). |
| `matrix_worker.js` | Код, реально исполняющийся в Web Worker. `initWebGL()` собирает 3 шейдерные программы, `buildAtlas()` рисует текстурный атлас символов на `OffscreenCanvas` 2D, `updateDynamicScaling()` адаптирует разрешение канваса под реальный FPS, `render()` — главный `requestAnimationFrame`-луп (рендер матрицы+глитчей в offscreen framebuffer, затем полноэкранный пост-процесс). `self.onmessage` — приёмная сторона message-протокола. |
| `gl-utils.js` | Небольшие хелперы компиляции шейдеров и линковки WebGL-программ (`compileShader`, `createProgram`) с логированием ошибок компиляции/линковки. |
| `shaders.js` | Все GLSL ES 3.00 шейдеры как template-строки: `vsMatrix`/`fsMatrix` (рендер символов-капель с лёгким притяжением к курсору), `vsGlitch`/`fsGlitch` (цветные глитч-блоки), `vsOverlay`/`postProcessSource` (полноэкранный пост-процесс — CRT-дисторсия, хроматическая аберрация, шум/тиры и процедурный SDF-рендер двух кибер-глаз). |
| `pkg/` | **Сгенерировано** `wasm-pack build`. Не редактировать вручную — перезаписывается при каждой сборке. |
| `public/wasm-matrix/` (в корне проекта) | **Сгенерировано.** Финальное место, откуда браузер грузит WASM-модуль и JS-файлы движка в рантайме (см. раздел "Сборка"). |

`repomix-output.xml` и `Cargo.lock` — служебные файлы, не требуют
документирования и не должны редактироваться вручную.

## Message-протокол: `NetrunnerMatrix.tsx` <-> `matrix_worker.js`

Main thread -> Worker:

| Тип | Когда отправляется | Назначение |
|---|---|---|
| `INIT` | Один раз при монтировании компонента | Передаёт `OffscreenCanvas` (transferable) и стартовые параметры: размеры, `pixelScale` (DPR), `isMobile`, `isVpnOn`, `isDarkMode`, `fontSize`, путь до ассетов и т.д. |
| `PAUSE` / `RESUME` | По событию `visibilitychange` документа | Останавливает/возобновляет `requestAnimationFrame`-луп, когда вкладка уходит в фон — экономит батарею и CPU/GPU. |
| `RESIZE` | По `ResizeObserver` контейнера | Пересчитывает физический размер канваса, DPR-скейл, пересоздаёт капли под новую ширину (`engine.resize()`) и перестраивает атлас. |
| `THEME` | При смене пропсов `isSecure`/`isDarkMode` | Сообщает воркеру новое состояние VPN и тему — перекрашивает атлас символов и запускает VPN-тир-глитч на экране. |
| `TILT` | По `deviceorientation` (гироскоп телефона) | Передаёт нормализованный наклон по оси `gamma` для лёгкого 3D-параллакса капель. |
| `SHOCKWAVE` / `DRAW_START` / `DRAW_END` / `KINETIC_SWIPE` / `MOUSE_MOVE` | По pointer/touch-событиям на `window` | Интеракции указателя: ударная волна и глитч-всплеск от клика/тапа, кинетический свайп и обычное перемещение курсора (влияют на положение символов и взгляд глаз). |

Worker -> Main thread:

| Тип | Когда отправляется | Назначение |
|---|---|---|
| `READY` | После загрузки WASM-модуля и первого прогретого кадра | Сигнал компоненту, что можно плавно проявить канвас (`opacity` transition), избегая вспышки нерасставленных капель. |

## Протокол памяти WASM <-> JS

`MatrixEngine::tick()` каждый кадр перезаписывает несколько плоских
`Vec<f32>` буферов внутри WASM. Вместо того чтобы возвращать их в JS через
`wasm-bindgen` (что означало бы копирование всего буфера в JS-кучу 60 раз в
секунду), движок экспортирует пары методов `*_ptr()` / `*_len()`:

- `*_ptr()` — сырой указатель (`*const f32`) на начало буфера в линейной
  памяти WASM;
- `*_len()` — актуальная длина в элементах `f32` (не в байтах).

JS-сторона (`matrix_worker.js`) оборачивает эту память в `Float32Array`
напрямую поверх `wasm.memory.buffer`, без копирования, и сразу заливает во
WebGL-буфер через `bufferSubData`. Такой `TypedArray` валиден только до
следующего вызова `engine.tick()` (рост внутреннего `Vec` в Rust может
переместить память) — поэтому view создаётся заново на каждый кадр и не
хранится между кадрами.

Буферы и их stride (шаг полей на один элемент):

| Буфер | `ptr()`/`len()` | Stride | Поля на элемент |
|---|---|---|---|
| Капли дождя | `render_ptr()` / `render_len()` | 5 | `x, y, char_idx, atlas_row, scale` |
| Глитч-блоки | `glitch_ptr()` / `glitch_len()` | 5 | `x, y, w, h, color_type` |
| "Стеклянные" символы | `glass_ptr()` / `glass_len()` | 4 | `x, y, char_idx, alpha` |
| Состояние глаз | `eye_ptr()` / `eye_len()` | 6 (фикс.) | `cx, cy, gaze_x, gaze_y, 0.0 (резерв), eye_closedness` |

Для капель `char_idx` и `atlas_row` вместе выбирают конкретный глиф в
текстурном атласе символов (11 строк: 0..9 — градации яркости символа,
10 — полностью белая "голова" капли), который `buildAtlas()` заранее
отрисовывает на `OffscreenCanvas` 2D в `matrix_worker.js`.

## Сборка

Из папки `matrix-engine/`:

```bash
wasm-pack build --target web --release
```

Либо через готовый скрипт из корня проекта (собирает WASM и раскладывает
воркер/шейдеры/хелперы рядом с ним):

```bash
./build-wasm.sh
```

Результат (`pkg/` + скопированные `matrix_worker.js`, `shaders.js`,
`gl-utils.js`) кладётся в `public/wasm-matrix/` в корне проекта — оттуда
браузер грузит WASM-модуль и JS-файлы движка в рантайме
(см. `assetsPath` в `NetrunnerMatrix.tsx`, по умолчанию `/wasm-matrix`).

В Docker-образе аналогичная сборка (`wasm-pack build --target web
--release` + копирование JS-файлов в `public/wasm-matrix/`) выполняется
отдельным стейджем в корневом `Dockerfile`.

## Дополнительные визуальные слои (депт-сцена и стекло)

Поверх исходной триады "Rust → Worker → WebGL2" добавлены два независимых
визуальных слоя, оба не трогают физику дождя/глаз:

1. **Фон-глубина (дорога/машины)** — `fsBackground` в `shaders.js`,
   подключён в `matrix_worker.js` (`bgProgram`/`vaoBg`). Рисуется ПЕРВЫМ
   внутри `postFramebuffer`, до капель дождя, тем же `vsOverlay`-quad'ом,
   что и постпроцесс. Это единственный способ дать дождю "глубину сзади":
   канвас матрицы создаётся с `alpha:false` и каждый кадр полностью
   перезаписывается, поэтому DOM-слой позади канваса был бы просто не виден
   — сцена должна рисоваться внутри того же фреймбуфера, ДО капель, чтобы
   дождь (уже блендящийся поверх фона) частично её перекрывал, а не скрывал
   целиком. Минималистичная сцена: светящийся горизонт, дорога с
   перспективой к точке схода (асфальт — цвет фона с мелким зернистым шумом)
   и стилизованные машины, едущие в противоположных полосах (эффект
   двустороннего движения). Все формы аспект-корректны (см. `aspect` в
   шейдере) — не искажаются при смене соотношения сторон экрана. Цвет
   акцента синхронизирован с палитрой капель/глаз (циан выкл / фиолет вкл).
2. **Стекло** — `matrix-engine/GlassShatter.tsx`, отдельный React-компонент
   ВНЕ воркера и WASM (простой fullscreen-quad на main thread, GPU-нагрузка
   пренебрежимо мала). Рендерится, в отличие от фона, обычным DOM-канвасом
   ПОВЕРХ канваса матрицы — сюда ограничение `alpha:false` не применяется,
   оно работает только "сквозь" элемент, а не поверх него. Показывается
   только когда `active` (= `isVpnOn`, "глаза закрыты"). Клик/тап по панели
   добавляет трещины (Voronoi-ячейки + growing-радиус вокруг точки удара,
   пул из 6 слотов по аналогии с `Shockwave` в `lib.rs`); после
   `SHATTER_AFTER_HITS` ударов стекло осыпается и через паузу собирается
   заново — эффект зациклен.
