mirror of
https://github.com/Tolik-Trek/DOOM2.git
synced 2026-06-15 09:01:34 +03:00
250 lines
14 KiB
Markdown
250 lines
14 KiB
Markdown
# 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 утерян).
|