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

14 KiB
Raw Blame History

DOOM2 / Sprinter Sp2000 — справочник по движку

Краткий технический справочник по архитектуре оригинального движка DOOM2 для Sprinter Sp2000 (CPU Z84C15 @ 21 МГц, FPGA-акселератор). Документ описывает то, что уже работает в репозитории: форматы файлов-ассетов, ключевые таблицы в памяти, поведение основных процедур рейкастера. Деталями акселератора и порта SCALE занимается отдельный документ doom-аксель.md.

Кодировка исходников D2_FRAM.asm, DOOM2.asmCP866. При правках использовать 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 — старший байт квадранта, Elo-байт из таблицы 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 утерян).