- Stage 1: Eliminate fish-eye distortion with precomputed tangential ray offset table and fractional DDA depth - Stage 2: Full asset management in EblanStudio (stb_image/stb_image_write, 128x128 BGRA texture packaging, WAV sound manager) - Stage 3: Multi-level room streaming, LevelTransition/CELL_PORTAL trigger system, 16-frame screen fade to black - Stage 4: 60Hz cutscene engine in pure x86 ASM (v4 binary track, waypoint camera motion, shortest-arc pan, CP1251 Russian subtitles, letterbox overlay, audio) - Stage 5: EblanStudio cutscene timeline (F6) and windowless Live ASM Viewport over shared-memory IPC directly rendered into DX11 - Tooling & Tests: comprehensive regression test suite (94 editor + 107 engine tests), IPC tests, and cutscene demo
225 lines
24 KiB
Markdown
225 lines
24 KiB
Markdown
# ТЕХНИЧЕСКОЕ ЗАДАНИЕ ДЛЯ АРТЕМА: EBLAN ENGINE 3D & EBLANSTUDIO
|
||
|
||
> **Назначение документа**: Детальный план разработки следующих ключевых подсистем игрового 2.5D движка на ассемблере (**EBLAN ENGINE 3D**) и сопутствующего C++ визуального редактора (**EblanStudio**).
|
||
> **Целевой стек**: x86 MASM32 (движок), C++17 + Dear ImGui + DirectX 11 (редактор).
|
||
|
||
---
|
||
|
||
## Проверка реализации этапов 1–3 — 21.10.2026
|
||
|
||
- Этап 1: тангенциальные углы лучей, единственная косинусная коррекция, DDA с 8 дробными битами до проекции. В регрессионной сцене прямая стена имеет одинаковую глубину во всех 320 столбцах.
|
||
- Этап 2: PNG/JPG/BMP, BGRA и хромакей, безопасное создание и обновление пакета, стандартные слоты при первом импорте, WAV и манифесты. Портал не использует слот фонаря.
|
||
- Этап 3: EBLN v3 с чтением v1/v2, полная проверка перед заменой комнаты, обработка ошибочного перехода, спавны, затемнение и защита от повторного срабатывания при появлении в портале. F5 работает с временной копией карт и ресурсов.
|
||
- Регрессионные проверки: `python tools/test_regressions.py`. Ручная проверка интерфейса и длительный игровой прогон остаются отдельной проверкой качества.
|
||
- Этапы 4 и 5 реализованы следующим изменением, см. статус ниже.
|
||
|
||
## Реализация этапов 4–5 — 22.10.2026
|
||
|
||
- Этап 4: бинарный трек EBLN v4, чистый ASM, линейные пролёты по точкам, повороты по кратчайшей дуге, паузы, русские субтитры CP1251, letterbox 2.35:1, WAV, затемнения и переходы. Автозапуск при входе, C для повтора и Tab для пропуска; управление игроком блокируется на время сцены. Старые карты v1/v2/v3 читаются.
|
||
- Этап 5: F6 открывает таймлайн с дорожками, перетаскиванием ключей, списком команд, инспектором, установкой точек на карте, автозапуском, Undo/Redo и сохранением. Предпросмотр и перемотка показывают сцену глазами камеры во встроенном игровом виде, а на карте — траекторию. F5 запускает полную игру и связанные комнаты с проверкой WAV.
|
||
- Пример `examples/cutscene_demo/run_demo.bat` показывает пролёт, текст, звук и переход во вторую комнату; основные карты проекта он не меняет.
|
||
- Регрессионный набор расширен проверками формата v4, проигрывателей ASM/C++, оверлея, переходов, редактора и покадрового совпадения состояния.
|
||
- Уточнения исходного ТЗ: сериализуемый `dataPtr` заменён на `dataId`, строки хранятся в отдельном пуле; движение задаётся длительностью. Использованы линейные waypoints, камера во время пролёта не сталкивается со стенами. WAV воспроизводится одним каналом PlaySoundA без внешних библиотек.
|
||
|
||
## Завершение редактора и исправление F5 — 22.10.2026
|
||
|
||
- Встроен игровой вид, который получает кадры от настоящего ASM-рендерера. Доступны режимы «Рядом», «Игровой вид», «Карта», свободный осмотр и камера таймлайна. Изменения стен, цветов, спрайтов и текстур видны без сохранения.
|
||
- Исправлена блокировка F5/F6 при захвате клавиатуры интерфейсом ImGui. Ошибки запуска показываются отдельным диалогом. Начальная карта открывается относительно расположения редактора, независимо от рабочей папки.
|
||
- Автопроверки проверяют горячие клавиши с фокусом в интерфейсе, реальную передачу кадров из x86 ASM в x64 редактор и DX11, обновление геометрии, перемотку кат-сцены, точное совпадение пикселей с отдельным движком и освобождение процесса рендерера.
|
||
- Все пять этапов реализованы. Предпросмотр редактора останавливается на команде перехода между комнатами; полный сценарий переходов и игровой ввод доступны через F5. Это ограничение предпросмотра, а не отсутствие переходов в движке.
|
||
- Итоговая проверка: 94/94 сценария редактора и 107/107 сценариев ASM, совпадение всех 320 углов лучей, покадровое совпадение проигрывателей и точное совпадение пикселей встроенного вида с движком. Обе программы пересобраны. Исходные ресурсы проекта при проверках остались без изменений. Ручное управление окнами было остановлено пользователем через Esc; полный визуальный прогон интерфейса не завершён.
|
||
|
||
Разделы ниже сохраняют исходное ТЗ и описание прежних проблем как историю требований. Актуальные формат и поведение описаны в `AGENTS.md` и `docs/ENGINE_ARCHITECTURE.md`.
|
||
|
||
---
|
||
|
||
## 📋 Оглавление
|
||
1. [Задача 1: Устранение Fish-Eye эффекта и фикс рендера](#задача-1-устранение-fish-eye-эффекта-и-фикс-рендера)
|
||
2. [Задача 2: Полноценный импорт ассетов в EblanStudio](#задача-2-полноценный-импорт-ассетов-в-eblanstudio)
|
||
3. [Задача 3: Многоуровневые локации и стриминг подуровней](#задача-3-многоуровневые-локации-и-стриминг-подуровней)
|
||
4. [Задача 4: Система кат-сцен и скриптовых последовательностей](#задача-4-система-кат-сцен-и-скриптовых-последовательностей)
|
||
5. [Чек-лист готовности и порядок реализации](#чек-лист-готовности-и-порядок-реализации)
|
||
|
||
---
|
||
|
||
## Задача 1: Устранение Fish-Eye эффекта и фикс рендера [ВЫПОЛНЕНО / DONE]
|
||
|
||
### 1.1. Суть проблемы в текущем коде
|
||
В файле [`include/engine_raycaster.inc`](include/engine_raycaster.inc) лучи выпускаются с равномерным шагом угла:
|
||
```masm
|
||
; Линейное распределение углов по ширине экрана:
|
||
rayAngle = playerAngle - FOV_HALF + (screenX * FOV_ANGLES / SCREEN_W)
|
||
```
|
||
- **Геометрическое искажение**: когда экран плоский, равные интервалы пикселей по оси $X$ соответствуют разным угловым приращениям ($\tan \theta \ne \theta$). Лучи, выпущенные с постоянным шагом угла, проецируются на изогнутую цилиндрическую поверхность.
|
||
- **Артефакт "рыбьего глаза"**: стены по краям экрана кажутся вздутыми, а при повороте камеры прямые стены начинают "плавать" и изгибаться волной.
|
||
- **Ошибки DDA**: в секции `apply_fisheye_fix` расстояние повторно умножается на косинус, что при некоторых углах дает артефакт двойного искажения.
|
||
|
||
### 1.2. Решение: Тангенциальная коррекция углов лучей
|
||
Сохраняя радиальную архитектуру лучей (512 дискретных углов), пересчитать угол каждого столбца $x \in [0..319]$ через тангенс:
|
||
|
||
$$\theta_x = \text{playerAngle} + \arctan\left(\left(\frac{2x}{\text{SCREEN\_W}} - 1\right) \cdot \tan\left(\frac{\text{FOV}}{2}\right)\right)$$
|
||
|
||
#### Что нужно сделать:
|
||
1. **Таблица предвычисленных смещений лучей (`tableRayAngleOffset`)**:
|
||
- В [`include/engine_math.inc`](include/engine_math.inc) при вызове `InitMathTables` сгенерировать через FPU таблицу из 320 значений `DWORD`.
|
||
- Для каждого столбца $x$:
|
||
```c
|
||
float cameraX = (2.0f * x / 320.0f) - 1.0f;
|
||
float radOffset = atanf(cameraX * tanf(FOV_RAD / 2.0f));
|
||
int angleOffset = (int)roundf(radOffset * 512.0f / (2.0f * M_PI));
|
||
tableRayAngleOffset[x] = angleOffset;
|
||
```
|
||
2. **Использование в цикле лучей (`include/engine_raycaster.inc`)**:
|
||
- Заменить линейный расчет угла на быстрый табличный:
|
||
```masm
|
||
mov eax, screenX
|
||
mov edx, [tableRayAngleOffset + eax*4]
|
||
add edx, playerAngle
|
||
and edx, ANGLE_MASK
|
||
mov rayAngle, edx
|
||
```
|
||
3. **Коррекция перпендикулярного расстояния**:
|
||
- Перпендикулярная дистанция:
|
||
$$\text{perpWallDist} = \text{rawDist} \cdot \cos(\text{rayAngle} - \text{playerAngle})$$
|
||
- Убедиться, что `sideDistX` и `deltaDistX` не накапливают масштабирующий множитель 64 дважды при вычислении текстурных координат (`texX`).
|
||
|
||
---
|
||
|
||
## Задача 2: Полноценный импорт ассетов в EblanStudio [ВЫПОЛНЕНО / DONE]
|
||
|
||
### 2.1. Требования
|
||
У Артема и дизайнеров уровней должна быть возможность в пару кликов мыши в редакторе загружать свои изображения (PNG) и звуки (WAV) без ручного запуска сторонних питоновских скриптов.
|
||
|
||
### 2.2. Архитектура Asset Manager в C++ (`editor/`)
|
||
1. **Библиотека декодирования картинок**:
|
||
- Подключить `stb_image.h` (однофайловая C++ библиотека без внешних зависимостей) в папку `editor/src/stb_image.h`.
|
||
- Загрузка любого PNG, JPG, BMP в 32-битный массив пикселей:
|
||
```cpp
|
||
int w, h, channels;
|
||
unsigned char* data = stbi_load(filename, &w, &h, &channels, 4); // RGBA32
|
||
```
|
||
2. **Диалоговые окна выбора файлов Win32**:
|
||
- Использовать стандартный `GetOpenFileNameA` для выбора PNG/WAV файлов.
|
||
3. **Конвертация и упаковка текстур (Wall Textures & Sprites)**:
|
||
- Входное изображение ресемплится/обрезается до стандарта движка: **128x128 пикселей**.
|
||
- Цветовые каналы конвертируются из RGBA в BGRA (`00RRGGBBh`), где для прозрачных спрайтов альфа = 0 заменяется на ключевой цвет мадженты `0x00FF00FF` (`COLOR_CHROMA_KEY`).
|
||
- Автоматическая перезапись и сохранение в `assets/textures/texturepack.bin` (заголовок `EBTX` + 16 слотов по 65536 байт).
|
||
4. **Интерфейс в EblanStudio**:
|
||
- Вкладка меню: `Ассеты -> Импорт текстуры стены...` (выбор слота 1..15).
|
||
- Вкладка меню: `Ассеты -> Импорт спрайта...` (добавление в список доступных спрайтов).
|
||
- Вкладка меню: `Ассеты -> Импорт аудио (WAV)...` (копирование звука в `assets/sounds/` с автоматической генерацией алиаса MCI).
|
||
- Превью текстуры прямо в палитре тайлов в левой панели редактора.
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## Задача 3: Многоуровневые локации и стриминг подуровней [ВЫПОЛНЕНО / DONE]
|
||
|
||
### 3.1. Секторно-комнатная модель уровней
|
||
Вместо одной изолированной сетки $32 \times 32$ мир организуется как **граф связанных секторов (комнат)**.
|
||
- Каждый сектор — это отдельный бинарный файл подуровня (например, `map.dat`, `sublevel_02.dat`) со своей сеткой $32 \times 32$, цветами пола/потолка и локальными спрайтами.
|
||
- Сектора соединяются **Порталами / Дверями перехода (Sector Portals)**.
|
||
|
||
### 3.2. Структура данных перехода (Portal Transition)
|
||
В заголовок и сущности карты добавлен тип `CELL_PORTAL = 10` и структура:
|
||
```cpp
|
||
// Структура перехода между подуровнями (42 байта packed)
|
||
struct LevelTransition {
|
||
uint8_t triggerCellX; // Координата X двери/триггера в текущем секторе
|
||
uint8_t triggerCellY; // Координата Y двери/триггера в текущем секторе
|
||
char targetMap[32]; // Имя целевого файла: "sublevel_02.dat"
|
||
uint16_t targetSpawnX; // Точка появления в новом секторе (X)
|
||
uint16_t targetSpawnY; // Точка появления в новом секторе (Y)
|
||
uint16_t targetAngle; // Направление взгляда после перехода
|
||
uint8_t fadeEffect; // 0 = мгновенно, 1 = затемнение экрана (Fade to Black)
|
||
uint8_t reserved; // Выравнивание
|
||
};
|
||
```
|
||
|
||
### 3.3. Логика динамической подгрузки и выгрузки в Ассемблере
|
||
В [`include/engine_map.inc`](include/engine_map.inc):
|
||
1. **Процедура перехода `SwitchSublevel(szNextMap, spawnX, spawnY, spawnAngle, fadeEffect)`**:
|
||
- **Шаг 1 (Fade Out)**: Плавное затемнение буфера экрана `screenBuffer` за 16 кадров с помощью целочисленного масштабирования цветовых каналов RGB.
|
||
- **Шаг 2 (Unload)**: Очистка текущих спрайтов (`mov spriteCount, 0`), сброс флагов и переходных триггеров.
|
||
- **Шаг 3 (Load)**: Чтение нового файла карты через `LoadSublevelBinary` в `gameMap`, обновление цветов неба/пола (`mapCeilingColor`, `mapFloorColor`), загрузка массива спрайтов нового сектора.
|
||
- **Шаг 4 (Spawn)**: Установка новых координат игрока `playerX = spawnX * 64 + 32`, `playerY = spawnY * 64 + 32`, `playerAngle = spawnAngle`.
|
||
- **Шаг 5 (Fade In)**: Плавное 16-кадровое осветление экрана.
|
||
2. **Память и производительность**:
|
||
- Постоянный объем оперативной памяти (~1-2 МБ), буферы очищаются и заполняются новым сектором на лету, позволяя создавать бесконечные цепочки и комплексы комнат.
|
||
- Во время обычного геймплея оверхед пост-процесса затемнения равен нулю (`cmp fadeState, 0; je fade_done`).
|
||
3. **Интеграция в EblanStudio**:
|
||
- Добавлен тайл `10. Портал перехода` с неоново-фиолетовой палитрой и меткой портала на 2D-сетке.
|
||
- Инспектор свойств перехода: выбор файла целевого подуровня (`.dat`), кнопка обзора файловой системы, слайдеры целевого спавна и угла, переключатель Fade to Black, быстрое удаление и кнопка мгновенного перехода к подуровню в редакторе.
|
||
- Поддержка сериализации и десериализации переходов в бинарный формат с сохранением 100% обратной совместимости.
|
||
|
||
---
|
||
|
||
## Задача 4: Система кат-сцен и скриптовых последовательностей [ВЫПОЛНЕНО / DONE]
|
||
|
||
### 4.1. Архитектура: Бинарный трек событий (Keyframe Track)
|
||
Кат-сцена представляет собой упорядоченный во времени список событий (Action Track). Во время кат-сцены управление игрока блокируется, а виртуальный режиссер интерполирует камеру, спавнит звуки и выводит диалоговые субтитры.
|
||
|
||
### 4.2. Формат команд кат-сцены
|
||
Каждая команда — это фиксированная 16-байтовая структура:
|
||
```cpp
|
||
enum CutsceneOpCode : uint8_t {
|
||
OP_END = 0, // Конец кат-сцены, возврат управления
|
||
OP_WAIT, // Ждать N кадров/тиков
|
||
OP_MOVE_CAM_TO, // Плавное перемещение камеры в (X, Y) со скоростью V
|
||
OP_PAN_CAM_TO, // Плавный поворот камеры на угол Angle
|
||
OP_SHOW_SUBTITLE, // Вывести текст субтитра на экране (ID строки)
|
||
OP_HIDE_SUBTITLE, // Скрыть субтитры
|
||
OP_PLAY_SOUND, // Проиграть звук (WAV алиас)
|
||
OP_FADE_SCREEN, // Затемнить / осветлить экран
|
||
OP_CHANGE_SUBLEVEL // Инициировать переход на другой подуровень
|
||
};
|
||
|
||
struct CutsceneEvent {
|
||
uint16_t tickTimestamp; // Кадр запуска от старта сцены
|
||
uint8_t opCode; // Тип команды (CutsceneOpCode)
|
||
uint8_t param8; // Доп. байт параметров (напр. флаг fade)
|
||
int32_t paramA; // Целевой X / угол / длительность
|
||
int32_t paramB; // Целевой Y / скорость
|
||
int32_t dataPtr; // Указатель на текст / имя звука
|
||
};
|
||
```
|
||
|
||
### 4.3. Реализация в движке (MASM32)
|
||
1. **Флаг состояния `isCutsceneActive:DWORD`**:
|
||
- Когда `isCutsceneActive == 1`, в `PollInput` игнорируются клавиши `WASD` и стрелочки.
|
||
2. **Интерполятор камеры `UpdateCutscene`**:
|
||
- Вызывается в `UpdatePlayer`.
|
||
- Если задано `targetCamX`, плавно сдвигает `playerX` к цели:
|
||
```masm
|
||
; Движение playerX -> targetCamX
|
||
mov eax, targetCamX
|
||
sub eax, playerX
|
||
; нормализация шага и добавление скорости
|
||
```
|
||
- Аналогично для угла поворота `playerAngle` по кратчайшей дуге окружности 512.
|
||
3. **Отрисовка 2D-субтитров**:
|
||
- В [`include/engine_render2d.inc`](include/engine_render2d.inc) использовать существующий `DrawString8x8` для вывода полупрозрачной плашки внизу экрана (Letterbox 2.35:1) и текста диалогов с центрированием.
|
||
|
||
### 4.4. Инструментарий в EblanStudio
|
||
- Добавить в редактор панель **"Cutscene Timeline"**:
|
||
- Горизонтальная шкала времени (в секундах / кадрах).
|
||
- Кнопки добавления ключевых точек: `[+ Камера]`, `[+ Текст]`, `[+ Звук]`, `[+ Переход]`.
|
||
- Возможность кликом по карте расставить контрольные точки пролета камеры (Spline/Waypoints).
|
||
- Кнопка предпросмотра пролета прямо в окне редактора.
|
||
|
||
---
|
||
|
||
## Чек-лист готовности и порядок реализации
|
||
|
||
| Этап | Задача | Затронутые файлы | Ожидаемый результат |
|
||
|---|---|---|---|
|
||
| **Этап 1 — готово** | Исправление Fish-Eye и угла лучей | `include/engine_math.inc`, `include/engine_raycaster.inc` | Стены остаются ровными по краям экрана, нет искажения при вращении камеры. |
|
||
| **Этап 2 — готово** | Импорт текстур и спрайтов в редакторе | `editor/src/stb_image.h`, `editor/src/editor_app.cpp` | Кнопка импорта в GUI загружает PNG и обновляет `assets/textures/texturepack.bin`. |
|
||
| **Этап 3 — готово** | Система связанных секторов (комнат) | `editor/src/level_format.h`, `include/engine_map.inc` | Переход между комнатами по триггеру с выгрузкой старой карты и загрузкой новой. |
|
||
| **Этап 4 — готово** | Движок кат-сцен и диалогов | `include/engine_cutscene.inc`, `include/engine_data.inc`, `main.asm` | Пролёт камеры по ключевым точкам, субтитры, звук, затемнения, переходы, блокировка инпута. |
|
||
| **Этап 5 — готово** | Визуальный таймлайн в EblanStudio | `editor/src/editor_cutscene.inl`, `editor/src/editor_viewport.inl`, `editor/src/cutscene_preview.h` | Редактирование траектории и сценария, предпросмотр игрового вида внутри редактора, Undo/Redo, F5 для полной игры. |
|
||
|
||
---
|
||
|
||
> *Файл подготовлен и структурирован для передачи в работу Артему.*
|