14 KiB
DOOM2 / Sprinter Sp2000 — справочник по движку
Краткий технический справочник по архитектуре оригинального движка DOOM2 для Sprinter Sp2000 (CPU Z84C15 @ 21 МГц, FPGA-акселератор). Документ описывает то, что уже работает в репозитории: форматы файлов-ассетов, ключевые таблицы в памяти, поведение основных процедур рейкастера. Деталями акселератора и порта SCALE занимается отдельный документ 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.
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 утерян).