1
0
mirror of https://github.com/Tolik-Trek/DOOM2.git synced 2026-06-15 09:01:34 +03:00
DOOM2/INFO/doom-энжин.md

250 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# DOOM2 / Sprinter Sp2000 — справочник по движку
Краткий технический справочник по архитектуре оригинального движка
DOOM2 для Sprinter Sp2000 (CPU Z84C15 @ 21 МГц, FPGA-акселератор).
Документ описывает то, что **уже работает в репозитории**: форматы
файлов-ассетов, ключевые таблицы в памяти, поведение основных
процедур рейкастера. Деталями акселератора и порта SCALE занимается
отдельный документ [doom-аксель.md](doom-аксель.md).
Кодировка исходников `D2_FRAM.asm`, `DOOM2.asm`**CP866**. При
правках использовать `iconv` (см. [cp866-edit-workflow в памяти]).
## 1. Память и слоты
| Адрес | Кто | Описание |
|--------------|---------|------------------------------------------------|
| `#0000..#1FFF` | SLOT0 (RAM) | Код `D2_FRAM.asm` (`ORG #1000`) |
| `#2000..#23FF` | SLOT0 (RAM) | `TABLE_W` — относ. карта (4 квадранта × 16×16) |
| `#4000..#7FFF` | SLOT1 | RAM-диск/файлы (страница 16 КБ): `d2_table.tbl`, `map_wall.res`, текстуры стен и т.п. |
| `#8000..#BFFF` | SLOT2 | Код `DOOM2.asm` (`ORG #8000`) |
| `#A000..#AFFF` | RAM | `TABLE_X` (`table_x.tbl` — SCALE-LUT) |
| `#C000..#FFFF` | Screen | Экран (двойная буферизация через `SCREEN_1`) |
Подключение страниц: `OUT (SLOT1),A` (`#5E`) — выбор страницы под
`#4000..#7FFF`, `OUT (SLOT3),A` (`#7E`) — режим экрана / wall-режим.
## 2. Карта `Bin/map_wall.res`
Файл = **256 КБ = 16 страниц по 16 КБ**. Загружается через
`RAMBlkIDs.map_wall`; `TABLE_WALL` (DOOM2.asm) хранит список реальных
страниц блока (17 байт).
### 2.1 Раскладка страницы 0 (карта)
Внутри первой 16-КБ страницы — **четыре слоя карты** по 4 КБ каждый.
В адресном пространстве CPU (после `OUT (SLOT1),map_wall_page_0`):
| Адрес CPU | Слой | Назначение |
|-----------|--------------------|------------|
| `#4000..#4FFF` | «декоративный» | визуальный слой (стены/двери, ASCII-коды) |
| `#5000..#5FFF` | **видимость** | то, что трассируется лучом (читает `TRACE`) |
| `#6000..#6FFF` | альтернативный | (см. RECALC ниже) |
| `#7000..#7FFF` | **коллизия** | проходимость для игрока/монстров (`MAP_PLACE`) |
Все слои имеют **одинаковую** клеточную раскладку:
* строка = 62 клетки + `0x0D 0x0A` (CR+LF) = **64 байта**;
* в коде используется адрес `H = (R >> 2) | base_section`,
`L = ((R & 3) << 6) | C` ⇒ ячейка `(R, C)` лежит по адресу
`base + R*64 + C`.
### 2.2 Кодировка клеток (ASCII в редакторе уровней)
| ASCII | Hex | Что |
|-------|-------|------------------|
| ` ` | `#20` | пусто |
| `0..>` | `#30..#3E` | стена 0..14 (индекс в `TABLE_WALL[1+id]`) |
| `S` | `#53` | старт игрока |
| `M` | `#4D` | монстр |
| `N` | `#4E` | факел (огни) |
| `O` | `#4F` | бочка |
| `P` | `#50` | огонь BFG |
Полный список — в `RECALC_MAP` (`DOOM2.asm`, `CALL Z,START_POS / MONSTR_POS / …`).
### 2.3 `RECALC_MAP` — преобразование уровня
Один раз после загрузки уровня. Сканирует все 4 слоя:
* В слоях `#4xxx, #5xxx, #6xxx`: ASCII → **номер страницы текстуры
стены** (через `TABLE_WALL[1 + (ascii - '0')]`); пробел/нестенки →
`#00``#5xxx`) или `#FF``#4xxx, #6xxx`).
* В слое `#7xxx`: ASCII → `#5F` (стена/препятствие) или `#00`
(проходимо).
* Заодно регистрирует стартовую позицию, монстров и т.п. через
`START_POS`, `MONSTR_POS*` (вызовы `CALL Z,…` по коду клетки).
После `RECALC_MAP` слой `#5xxx` готов для `TRACE`/`MAKE_MAP`,
слой `#7xxx` — для `MAP_PLACE` (проверка коллизий).
## 3. Относительная карта `TABLE_W`
`MAKE_MAP` (D2_FRAM.asm) строит вокруг игрока **4 квадрантные карты
16×16 = 256 байт каждая** в основном ОЗУ:
```
TABLE_W EQU #2000 ; квадрант 0 (источник: +X = +C)
TABLE_W+#100 ; квадрант 1 (поворот 90°)
TABLE_W+#200 ; квадрант 2 (поворот 180°)
TABLE_W+#300 ; квадрант 3 (поворот 270°)
```
* Каждый квадрант = пред-повёрнутая копия мировой карты `#5xxx`
вокруг игрока, чтобы трассировка читала одной 8-битной арифметикой
(`LD A,(DE)`, где `D` — старший байт квадранта, `E``lo`-байт
из таблицы `d2_table.tbl`).
* В пределах квадранта 16×16 (адрес `cy * 16 + cx`) игрок находится
в верхнем-левом углу относительной системы — реальные пред-повороты
делают так, что «вперёд» соответствует положительному направлению
обхода независимо от того, куда игрок смотрит.
* Дальность ограничена 16-битной адресацией одной страницы 256 байт
**16 клеток** в каждую сторону. Расширение до 24 потребовало бы
16-битной адресации в горячем цикле (см. историю провалившейся
попытки в git-логе ветки `beta` до этого коммита).
## 4. Формат `Bin/d2_table.tbl`
**512 КБ = 32 страницы × 16384 = 512 записей × 32 байта** на страницу.
Страницы — пары `(EVEN, ODD)`, **по одной паре на CORNER**
(субпозицию игрока в клетке, 0..15). Адресуются через `TABLE_TRACE`
(массив 33 байта в `DOOM2.asm`): чётный байт пары = номер страницы
`PLACE_L` (offset-страница), нечётный = `PLACE_L1/L2` (projection).
### 4.1 Запись 32 байта = две половины по 16 (бит 4 регистра L)
**EVEN-страница (offset / трассировка):**
* `lo[0..15]` — **низкий байт** адреса клетки в квадрантной карте
`TABLE_W` на каждом из 16 шагов луча (DDA-путь). Опорный луч
«прямо вперёд» = `1, 2, …, 16`.
* `hi[0..15]` = `0`.
**ODD-страница (projection):**
* `lo[0..15]` — **код высоты** на каждом шаге = индекс в
`table_x.tbl` (см. §5). Перспективный закон: ближе → больше
(полноэкранно), дальше → меньше.
* `hi[0..15]` — **u-координата** столбца текстуры стены
(+16 на субпозицию = «тир» в адресации страницы стены).
### 4.2 Адресация записи в кадре
Внутри страницы запись адресуется регистрами `(H, L)`:
* `H ∈ #40..#7F` (6 бит) — **крупный угол** внутри квадранта
(`(ANGLE_M >> 7) & #3F`). 64 значения покрывают 90°.
* `L_top3 ∈ {0, 32, 64, …, 224}` — **подгруппа из 8 экранных
колонок** (`L & #E0`, шаг `ADD A,32`).
* Вместе: 64 × 8 = 512 = страница. За кадр движок проходит
**40 H × 8 L = 320 экранных лучей** (FOV 60° из квадранта 90°).
Квадрант (0..3) выбирается старшими битами `ANGLE_M` через
`LD D,A` (`D` — старший байт страницы `TABLE_W`).
### 4.3 Закон проекции (реверс)
* `N = K / perp_dist`, `K ≈ 315`;
* `perp = d_eucl * cos(ray_ang - view_ang)` (фиш-ай-фикс);
* экран 256 пикс., `N > 256` → полноэкранно + клип через `table_x`
(буфер-offset);
* `height_code`:
* `A < 128` → частичная стена, `N = 256 - 2A` пикс.;
* `A ≥ 128` → полноэкранная стена с клипом по 6-битному
буфер-offset (`A - 128 = 0..63`).
> **Бит-в-бит репликация оригинала невозможна** — fixed-point-формат
> инструмента-генератора утерян. Резерв: `Resources/tools/gen_d2_table/`
> печёт формат-совместимую копию `Build/d2_table.gen.tbl` из
> задокументированной математики (без претензии на бинарное
> совпадение с `Bin/d2_table.tbl`).
## 5. `Bin/table_x.tbl` — SCALE-LUT
256-байтовая таблица в RAM `#A000` (низкий байт коэффициента
порта SCALE) + парная `#A100` (старший байт). Полный 16-битный
коэффициент = `(B << 8) | L`, где:
* `bits[15..10]` — смещение в 256-байтовом буфере акселератора
(0..63), используется при полноэкранных стенах для клипа;
* `bits[9..0]` — fixed-point **2.8** шага чтения буфера на каждый
выводимый пиксель (целая часть 0..3, дробная /256).
* Коэффициент `#0100` = `step = 1.000` = **масштаб 1:1**.
Подробнее про порт SCALE, схему `OUT (BC),r` и расшифровку битов —
[doom-аксель.md](doom-аксель.md).
`height_code` из `d2_table.tbl` (см. §4.1) — это **индекс в
`table_x.tbl`**: `TABLE_X[height_code]` (низкий байт) +
`TABLE_X[height_code + #100]` (старший байт) дают коэффициент SCALE,
который выгружается в порт `#C7` парой `LD B,(HL) / OUT (C),L`
(см. `TRACE_CONT`).
## 6. Алгоритм `TRACE` (упрощённо)
Из `D2_FRAM.asm`:
```
TRACE:
SLOT3 := #50 ; экран mode
SCALE := #0100 ; 1:1 (BC=#0100, OUT (C),C)
; ---- небо (sky scroll по углу) ----
LD HL,(ANGLE_M); ADD HL,HL; …
LD C,80
SKY_LOOP_1: ACC_CopyBlock + ACC_CopyScreenBlock ×4 (4 пикс. колонки)
; ---- веер лучей ----
EXX; LD DE,(SCREEN_1); LD C,0; EXX
LD HL,ANGLE_M; D := TABLE_W/256 + quadrant; H := #40..#7F (угол)
LD B,40 ; 40 групп × 8 колонок = 320 лучей
TRACE_NEXT_:
EXX; LD HL,(PLACE_L); … EXX
PLACE_L+1: LD A,(TABLE_TRACE+…); OUT (SLOT1),A ; subpos page (offsets)
LD (CONT_PAGE),A
TRACE_LOOP:
REPT 16
LD E,(HL) ; offset байт текущего шага
LD A,(DE) ; клетка в относ. карте (`TABLE_W`)
AND A
JP NZ,TRACE_CONT ; стенка → рисовать
INC L ; следующий шаг луча
ENDR
JP TRACE_EMPTY ; 16 шагов пусто → пол/потолок
NEXT_ANGLE:
INC DE (alt-DE) ; ↑ экранная колонка
L := (L & #E0) + 32 ; следующий луч в подгруппе 8
(если L переполнен) INC H; если H ≥ #80 — новая группа (квадрант)
DJNZ ...
```
`TRACE_CONT` (нечётная страница `PLACE_L1/2`) читает `height_code` и
`u_col`, подгружает 64-байтовый столбец текстуры стены, выставляет
SCALE из `table_x.tbl` и рисует столбец через `ACC_CopyScreenBlock`.
## 7. CORNER (субпозиция игрока)
`PRECALC_PLACE` вычисляет 4-битный CORNER (0..15) из:
* `X_COORD+1 & 3` — субпозиция X внутри клетки (2 бита);
* `Y_COORD+1 & 3` — субпозиция Y внутри клетки (2 бита);
* квадранта взгляда (`BIT 6,H` / `BIT 7,H`) — XOR/swap-nibble
для канонизации (одна таблица переиспользуется на 4 поворота).
CORNER служит индексом в `TABLE_TRACE`:
`PLACE_L = TABLE_TRACE + 2*CORNER` — пара
`(offset-страница, projection-страница)` в `d2_table.tbl`. Меняется
при пересечении границы клетки.
## 8. Генератор `Resources/tools/gen_d2_table/`
* `gen_d2_table.asm` — обёртка под sjasmplus (Lua-блок, Z80-код не
производится).
* `gen_d2_table.lua` — Lua-генератор: 16 CORNER × (EVEN+ODD) ×
512 записей × 32 байта = 524288 байт в `Build/d2_table.gen.tbl`.
Использует supercover-DDA + `K=315 / perp` для проекции.
* Запуск из корня проекта:
```
sjasmplus --nologo Resources/tools/gen_d2_table/gen_d2_table.asm
```
* Выход формат-совместим, **но не бит-в-бит** с `Bin/d2_table.tbl`
(оригинальный fixed-point утерян).