feat: stabilize fixed-tick simulation
Decouple gameplay from presentation and preserve signed movement fractions. Separate cinematic camera ownership with safe body handoff and transactional current-room restarts. Resample input latches on focus regain, discard modal timing debt, and attach cinematic lighting to the view to match editor preview. Verify editor 137/137 and ASM 288/288, cinematic traces, RGB bake and IPC framebuffer parity without changing project assets.
This commit is contained in:
parent
527d92411d
commit
aa64510def
23 changed files with 1407 additions and 726 deletions
24
AGENTS.md
24
AGENTS.md
|
|
@ -14,11 +14,12 @@
|
||||||
```
|
```
|
||||||
EBLAN_ENGINE/
|
EBLAN_ENGINE/
|
||||||
│
|
│
|
||||||
├── main.asm # Entry point, Win32 window, message loop, 60 FPS frame ticker
|
├── main.asm # Entry point, Win32 window, input/tick/render loop
|
||||||
├── build.bat # 1-click build script (supports MASM32 and MSVC BuildTools)
|
├── build.bat # 1-click build script (supports MASM32 and MSVC BuildTools)
|
||||||
├── engine.exe # Compiled game executable
|
├── engine.exe # Compiled game executable
|
||||||
│
|
│
|
||||||
├── include/
|
├── include/
|
||||||
|
│ ├── engine_simulation.inc # Fixed 60 Hz QPC scheduler, states and tick commands
|
||||||
│ ├── engine_defs.inc # Screen resolution, colors, key codes, constants, structs
|
│ ├── engine_defs.inc # Screen resolution, colors, key codes, constants, structs
|
||||||
│ ├── engine_data.inc # Framebuffer, depthbuffer, player state, map array, textures
|
│ ├── engine_data.inc # Framebuffer, depthbuffer, player state, map array, textures
|
||||||
│ ├── engine_math.inc # FPU Sin/Cos table generator, RNG, fixed-point math
|
│ ├── engine_math.inc # FPU Sin/Cos table generator, RNG, fixed-point math
|
||||||
|
|
@ -52,12 +53,12 @@ build.bat
|
||||||
## Core Engine Architecture
|
## Core Engine Architecture
|
||||||
|
|
||||||
### 1. Frame Lifecycle
|
### 1. Frame Lifecycle
|
||||||
Each tick (~60 times per second), `main.asm` executes:
|
Each message-loop pass polls input, pumps fixed simulation, then draws independently:
|
||||||
1. `PollInput`: queries keyboard state (`keyForward`, `keyBackward`, `keyStrafeLeft`, `keyStrafeRight`, `keyTurnLeft`, `keyTurnRight`, `keyAction`).
|
1. `PollInput`: samples held movement and latches pending command edges until consumed by one tick. Focus regain resamples physical latches without emitting actions.
|
||||||
2. `UpdatePlayer`: applies camera turning, calculates forward/strafe movement vectors, runs wall-sliding collision against `gameMap`, checks door interaction.
|
2. `PumpSimulationClock` / `Engine_Tick`: QPC accumulator at exactly 60 Hz, up to eight catch-up ticks. Stalls >250 ms and pause/focus/modal/restart boundaries discard timing debt. Playing updates player, entities, combat, gameplay hook, lights and toast; other states freeze or update only their own systems. `UpdatePlayer` uses signed 16.16 runtime remainders, normalized diagonals and full-body unit-step wall-sliding collision. Portals remain walkable; occupied doors cannot close.
|
||||||
3. `Render3DScene`:
|
3. `Render3DScene`:
|
||||||
- Clears ceiling (`COLOR_CEILING`) and floor (`COLOR_FLOOR`).
|
- Clears ceiling (`COLOR_CEILING`) and floor (`COLOR_FLOOR`).
|
||||||
- Casts 320 rays across FOV (~67.5°).
|
- Selects `viewX/Y/Angle` from player or separate `csCameraX/Y/Angle`, then casts 320 rays across FOV (~67.5°).
|
||||||
- Traverses grid using DDA until a solid cell is hit.
|
- Traverses grid using DDA until a solid cell is hit.
|
||||||
- Computes perpendicular wall distance (fish-eye corrected).
|
- Computes perpendicular wall distance (fish-eye corrected).
|
||||||
- Samples 128x128 texture stripe, applies side shading (Y-walls darkened for 3D depth) and distance fog.
|
- Samples 128x128 texture stripe, applies side shading (Y-walls darkened for 3D depth) and distance fog.
|
||||||
|
|
@ -66,6 +67,11 @@ Each tick (~60 times per second), `main.asm` executes:
|
||||||
4. `Engine_RenderHUD`: draws 2D overlay (title, FPS counter, minimap radar, controls).
|
4. `Engine_RenderHUD`: draws 2D overlay (title, FPS counter, minimap radar, controls).
|
||||||
5. `StretchDIBits`: copies `screenBuffer` (320x240 BGRA) to window DC (1280x960).
|
5. `StretchDIBits`: copies `screenBuffer` (320x240 BGRA) to window DC (1280x960).
|
||||||
|
|
||||||
|
Drawing never advances gameplay timers, closes dialogs or switches rooms.
|
||||||
|
`UpdateScreenFade` advances transitions on ticks; `ApplyScreenFade` only darkens
|
||||||
|
pixels. State precedence: Unfocused, Paused, Dialog, Transition, Dead, Cutscene,
|
||||||
|
Playing. Detailed rules and regression coverage: `docs/STABILIZATION.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## How to Extend This Engine (Guidelines for AI Agents)
|
## How to Extend This Engine (Guidelines for AI Agents)
|
||||||
|
|
@ -196,7 +202,13 @@ Run `python tools\test_regressions.py` on Windows with the build toolchains inst
|
||||||
|
|
||||||
MOVE and PAN interpolate linearly using signed integer math. Camera endpoints must be walkable; movement segments ignore collisions. Each tick advances existing tweens, expires text and waits, then dispatches due commands in stored order. Same-timestamp commands can start independent movement, pan and fade together. WAIT delays overdue commands without shifting their timestamps. A replacement tween starts from the current pose.
|
MOVE and PAN interpolate linearly using signed integer math. Camera endpoints must be walkable; movement segments ignore collisions. Each tick advances existing tweens, expires text and waits, then dispatches due commands in stored order. Same-timestamp commands can start independent movement, pan and fade together. WAIT delays overdue commands without shifting their timestamps. A replacement tween starts from the current pose.
|
||||||
|
|
||||||
Autoplay begins after a room transition finishes. C starts/replays the current map's track; Tab skips it. Movement/actions are blocked during playback. End/skip/level changes stop cinematic sound, clear subtitles and fade, and return control at the current camera pose. Letterbox is 2.35:1, subtitles use up to 3 centered rows of 38 glyphs over a half-brightness panel. WAV names are ASCII basenames of up to 63 characters in `assets/sounds`; playback uses one asynchronous PlaySound channel.
|
Autoplay begins after a room transition finishes. C starts/replays the current map's track; Tab skips it. Movement/actions are blocked during playback. The camera owns `csCameraX/Y/Angle`, leaving the gameplay body stationary; lighting and flashlight follow the selected view. End/skip/level changes stop cinematic sound and clear subtitles/fade. END/Tab transfer the camera pose only if the entire player collision body is walkable; otherwise retain the previous safe player pose. Letterbox is 2.35:1, subtitles use up to 3 centered rows of 38 glyphs over a half-brightness panel. WAV names are ASCII basenames of up to 63 characters in `assets/sounds`; playback uses one asynchronous PlaySound channel.
|
||||||
|
|
||||||
|
`RestartCurrentRoom` is shared by death R and pause item 4. It reloads the current
|
||||||
|
room with the actual entry spawn, then clears gameplay transients and timing debt
|
||||||
|
only after success. Failed reload preserves the room, death/pause and statistics.
|
||||||
|
Genre, weapon/HUD configuration and selected weapon are preserved; authored room
|
||||||
|
lighting is reloaded. EBLN coordinates and IPC layouts are unchanged.
|
||||||
|
|
||||||
F6 opens EblanStudio's timeline. Author commands with buttons, drag diamonds to change timestamps, select overlapping events through the command list, and place MOVE points by clicking the map. The inspector edits parameters and strings. Autoplay, scene data and strings participate in save/load and undo/redo. Preview shows the actual game perspective in the embedded viewport with the camera path on the map; F5 runs the full game, validates referenced WAV files and stages them with linked maps. Editor preview stops at a level-change command. F5/F6 are handled before ImGui keyboard-capture checks.
|
F6 opens EblanStudio's timeline. Author commands with buttons, drag diamonds to change timestamps, select overlapping events through the command list, and place MOVE points by clicking the map. The inspector edits parameters and strings. Autoplay, scene data and strings participate in save/load and undo/redo. Preview shows the actual game perspective in the embedded viewport with the camera path on the map; F5 runs the full game, validates referenced WAV files and stages them with linked maps. Editor preview stops at a level-change command. F5/F6 are handled before ImGui keyboard-capture checks.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,36 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Симуляция и движение (этап 2)
|
||||||
|
|
||||||
|
`include/engine_simulation.inc` отделяет опрос ввода, фиксированные тики 60 Гц и
|
||||||
|
рисование. QPC-аккумулятор хранит `elapsedCounts * 60`; на тик вычитается частота
|
||||||
|
QPC без округления периода. За проход допускается до восьми тиков. Задержка более
|
||||||
|
250 мс, обратный отсчёт QPC, смена фокуса, пауза/возобновление, открытие/закрытие
|
||||||
|
диалога и успешный рестарт сбрасывают накопленный долг. Рендер не интерполируется
|
||||||
|
и не меняет скорость игрового времени.
|
||||||
|
|
||||||
|
`PollInput` сохраняет удерживаемые оси и накапливает фронты команд до следующего
|
||||||
|
тика; `ConsumeInputEdges` потребляет их один раз, включая заблокированные тики.
|
||||||
|
Возврат фокуса синхронизирует физические защёлки без новых команд: удерживаемое
|
||||||
|
движение возобновляется, действие требует нового нажатия.
|
||||||
|
|
||||||
|
`playerX/Y` остаются целыми мировыми координатами. `playerFractionX/Y` хранят
|
||||||
|
знаковые остатки 16.16 только в runtime; деление округляется к нулю симметрично
|
||||||
|
для всех направлений. Противоположные оси сокращаются; диагональ нормализуется
|
||||||
|
множителем 46341/65536. Скорость ходьбы/бега — 3/6 единиц за тик. Столкновения
|
||||||
|
проверяют все клетки квадратного тела `position ± playerRadius`, по единичным
|
||||||
|
подшагам каждой оси с проскальзыванием (до 64 подшагов за тик). Заблокированная
|
||||||
|
ось теряет дробный остаток. Порталы проходимы; дверь не закрывается поверх тела.
|
||||||
|
|
||||||
|
Приоритет состояний: Unfocused → Paused → Dialog → Transition → Dead → Cutscene →
|
||||||
|
Playing. Playing обновляет игрока, сущности, бой, `Game_OnTick`, свет и toast;
|
||||||
|
между подсистемами повторно проверяется общий запрет gameplay. Cutscene обновляет
|
||||||
|
трек/камеру и свет; Transition — только затемнение/загрузку. Пауза, диалог, смерть
|
||||||
|
и потеря фокуса замораживают мировые таймеры. `Engine_Frame` только рисует и
|
||||||
|
представляет кадр; `UpdateScreenFade` обновляет переход, `ApplyScreenFade` меняет
|
||||||
|
только пиксели. Скриншот и счётчик FPS относятся к представлению.
|
||||||
|
|
||||||
## 2. Конвейер лучевого рендеринга (2.5D Raycasting)
|
## 2. Конвейер лучевого рендеринга (2.5D Raycasting)
|
||||||
|
|
||||||
Каждый кадр для экрана шириной 320 пикселей выпускается ровно **320 лучей** (по 1 лучу на каждый вертикальный столбец экрана):
|
Каждый кадр для экрана шириной 320 пикселей выпускается ровно **320 лучей** (по 1 лучу на каждый вертикальный столбец экрана):
|
||||||
|
|
@ -113,14 +143,25 @@ F5 запускает копию графа карт и ресурсов во в
|
||||||
|
|
||||||
`UpdatePlayer` обрабатывает автозапуск после завершения межкомнатного затемнения и ручной запуск клавишей C. При активной сцене управление движением, дверями и обычными порталами заблокировано. UpdateCutscene сначала обновляет уже начатые интерполяции и время показа текста, затем обрабатывает паузу и наступившие команды. Несколько команд с одним временем выполняются в порядке хранения: камера, поворот, текст, звук и затемнение могут работать одновременно. WAIT задерживает обработку следующих команд, но время и уже начатые интерполяции продолжаются.
|
`UpdatePlayer` обрабатывает автозапуск после завершения межкомнатного затемнения и ручной запуск клавишей C. При активной сцене управление движением, дверями и обычными порталами заблокировано. UpdateCutscene сначала обновляет уже начатые интерполяции и время показа текста, затем обрабатывает паузу и наступившие команды. Несколько команд с одним временем выполняются в порядке хранения: камера, поворот, текст, звук и затемнение могут работать одновременно. WAIT задерживает обработку следующих команд, но время и уже начатые интерполяции продолжаются.
|
||||||
|
|
||||||
Координаты камеры — сырые мировые единицы (64 на клетку). MOVE_CAM_TO использует линейную интерполяцию по длительности из `dataId`; PAN_CAM_TO — кратчайшую дугу угла 0..511 и длительность из `paramB`. Целочисленное умножение с последующим знаковым делением гарантирует точную конечную точку. Нулевая длительность означает мгновенную установку. Новая команда того же типа заменяет незавершённую интерполяцию, начиная с текущего состояния. Конечная клетка должна быть проходимой; сами отрезки не проверяют столкновения со стенами.
|
Координаты камеры — сырые мировые единицы (64 на клетку), отдельные `csCameraX/Y/Angle`, не позиция игрока. Рендер выбирает `viewX/Y/Angle`; фонарик и выборки света следуют камере, как в предпросмотре. MOVE_CAM_TO использует линейную интерполяцию по длительности из `dataId`; PAN_CAM_TO — кратчайшую дугу угла 0..511 и длительность из `paramB`. Целочисленное умножение с последующим знаковым делением гарантирует точную конечную точку. Нулевая длительность означает мгновенную установку. Новая команда того же типа заменяет незавершённую интерполяцию, начиная с текущего состояния. Конечная клетка должна быть проходимой; сами отрезки не проверяют столкновения со стенами.
|
||||||
|
|
||||||
FADE_SCREEN интерполирует темноту от 0 до 16 и масштабирует каждый RGB-канал. Во время сцены HUD заменён чёрными полосами по 52 пикселя (видимая область 320×136, примерно 2.35:1). Субтитры центрируются на полупрозрачной плашке: до 3 строк по 38 глифов 8×8, перенос по ширине или LF. Полный текст ограничен 95 символами CP1251; лишние экранные строки не выводятся. SHOW_SUBTITLE с нулевой длительностью остаётся до HIDE_SUBTITLE или завершения сцены.
|
FADE_SCREEN интерполирует темноту от 0 до 16 и масштабирует каждый RGB-канал. Во время сцены HUD заменён чёрными полосами по 52 пикселя (видимая область 320×136, примерно 2.35:1). Субтитры центрируются на полупрозрачной плашке: до 3 строк по 38 глифов 8×8, перенос по ширине или LF. Полный текст ограничен 95 символами CP1251; лишние экранные строки не выводятся. SHOW_SUBTITLE с нулевой длительностью остаётся до HIDE_SUBTITLE или завершения сцены.
|
||||||
|
|
||||||
PLAY_SOUND использует `PlaySoundA` из уже подключённой WinMM, асинхронно и без системного звука при ошибке. Строка содержит только ASCII-имя WAV длиной до 63 символов, файл лежит в `assets/sounds`. Бит 0 `param8` включает повтор, индекс -1 останавливает звук. Канал один; новый WAV заменяет предыдущий. END, Tab и переход прекращают звук и снимают наложения. Положение камеры сохраняется при возврате управления; защита портала предотвращает немедленный обратный переход из текущей клетки.
|
PLAY_SOUND использует `PlaySoundA` из уже подключённой WinMM, асинхронно и без системного звука при ошибке. Строка содержит только ASCII-имя WAV длиной до 63 символов, файл лежит в `assets/sounds`. Бит 0 `param8` включает повтор, индекс -1 останавливает звук. Канал один; новый WAV заменяет предыдущий. END, Tab и переход прекращают звук и снимают наложения. При END/Tab камера передаёт позицию и угол игроку только если всё collision body свободно; иначе сохраняется прежняя безопасная поза игрока. Дробные остатки сбрасываются; защита портала предотвращает немедленный обратный переход из текущей клетки.
|
||||||
|
|
||||||
CHANGE_SUBLEVEL ссылается на существующий переход по индексу и использует его имя карты, спавн, угол и настройку затемнения. Старый трек останавливается до загрузки новой комнаты. Уже достигнутая темнота сохраняется при начале затемнения перехода, чтобы избежать светлой вспышки. Новая комната может запустить собственную сцену после появления.
|
CHANGE_SUBLEVEL ссылается на существующий переход по индексу и использует его имя карты, спавн, угол и настройку затемнения. Старый трек останавливается до загрузки новой комнаты. Уже достигнутая темнота сохраняется при начале затемнения перехода, чтобы избежать светлой вспышки. Новая комната может запустить собственную сцену после появления.
|
||||||
|
|
||||||
|
### Рестарт текущей комнаты
|
||||||
|
|
||||||
|
`RestartCurrentRoom` используется смертью + R и пунктом 4 паузы. Запоминаются
|
||||||
|
фактический путь комнаты и entry spawn, включая override перехода. Сначала
|
||||||
|
выполняется транзакционная загрузка; лишь после успеха сбрасываются сущности,
|
||||||
|
HP/патроны, ключи/очки, боевые таймеры/вспышки, диалог/toast, затемнение, ожидающий
|
||||||
|
переход и дробные остатки. Данные комнаты и авторский свет восстанавливаются из
|
||||||
|
файла; жанр, настройка оружия/прицела, HUD и выбранное оружие сохраняются. Ошибка
|
||||||
|
не заменяет комнату процедурной и не оживляет игрока; пауза остаётся. Процедурный
|
||||||
|
стартовый fallback перезапускается процедурно.
|
||||||
|
|
||||||
## 7. Таймлайн EblanStudio
|
## 7. Таймлайн EblanStudio
|
||||||
|
|
||||||
F6 или кнопка панели открывает таймлайн (`editor/src/editor_cutscene.inl`). Дорожки камеры, текста, звука и сцены показывают метки времени и перетаскиваемые ромбы. Команды с одинаковым временем можно выбрать через список «Команда». Инспектор редактирует время, длительность, координаты, углы, строки, WAV, повтор и целевой портал. Для камеры кнопка «Указать на карте» позволяет выбрать свободную клетку, не меняя сетку. END добавляется автоматически и переносится за последнее действие. Удаление портала удаляет или перенумеровывает ссылки сцены.
|
F6 или кнопка панели открывает таймлайн (`editor/src/editor_cutscene.inl`). Дорожки камеры, текста, звука и сцены показывают метки времени и перетаскиваемые ромбы. Команды с одинаковым временем можно выбрать через список «Команда». Инспектор редактирует время, длительность, координаты, углы, строки, WAV, повтор и целевой портал. Для камеры кнопка «Указать на карте» позволяет выбрать свободную клетку, не меняя сетку. END добавляется автоматически и переносится за последнее действие. Удаление портала удаляет или перенумеровывает ссылки сцены.
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ Project assets remain byte-for-byte unchanged. The original working directory
|
||||||
still has only its pre-existing `editor/imgui.ini` modification. No separate
|
still has only its pre-existing `editor/imgui.ini` modification. No separate
|
||||||
interactive UI playtest was performed during this preparation.
|
interactive UI playtest was performed during this preparation.
|
||||||
|
|
||||||
### Stage 2 implementation contract (not implemented here)
|
### Stage 2 implementation contract
|
||||||
|
|
||||||
1. Separate message/input polling, fixed simulation ticks and drawing/presentation.
|
1. Separate message/input polling, fixed simulation ticks and drawing/presentation.
|
||||||
Use QPC and a 60 Hz accumulator with bounded catch-up. Consume edge-triggered
|
Use QPC and a 60 Hz accumulator with bounded catch-up. Consume edge-triggered
|
||||||
|
|
@ -106,11 +106,70 @@ no simulation advancement during pause/dialog; no pickups/AI along camera paths;
|
||||||
safe cinematic handoff; restart after a room transition; failed restart rollback;
|
safe cinematic handoff; restart after a room transition; failed restart rollback;
|
||||||
unchanged v1..v5 loading, lighting bake parity and IPC v2 framebuffer parity.
|
unchanged v1..v5 loading, lighting bake parity and IPC v2 framebuffer parity.
|
||||||
|
|
||||||
|
## Stage 2 — predictable simulation
|
||||||
|
|
||||||
|
Implemented without changing EBLN v1..v5, sprite coordinates or preview IPC v2:
|
||||||
|
|
||||||
|
- `include/engine_simulation.inc` owns QPC scheduling and `Engine_Tick`. The
|
||||||
|
accumulator stores elapsed counts multiplied by 60, avoiding rounded tick
|
||||||
|
periods. Each pump runs at most eight ticks; stalls over 250 ms, backwards
|
||||||
|
counter samples, pause/resume, focus changes, modal boundaries and successful
|
||||||
|
restarts discard timing debt. Drawing runs independently, without interpolation.
|
||||||
|
Under overload the engine drops debt rather than fast-forwarding.
|
||||||
|
- `PollInput` records held movement and pending command edges. An edge survives
|
||||||
|
render-only frames and is consumed after one simulation tick, including blocked
|
||||||
|
ticks. On focus regain physical latches are resampled and new edges discarded;
|
||||||
|
movement may resume while held, but actions require release and a fresh press.
|
||||||
|
F12 remains a presentation-only screenshot request.
|
||||||
|
- Movement uses signed 16.16 runtime remainders with truncation toward zero.
|
||||||
|
Opposing axes cancel before rounding; combined forward/strafe uses 46341/65536
|
||||||
|
normalization. Collision checks the axis-aligned body against every overlapped
|
||||||
|
cell, stepping one world unit per axis with wall sliding (maximum 64 substeps
|
||||||
|
per tick). Blocked axes discard their remainder. Portals remain walkable; open
|
||||||
|
doors cannot close over the body. Fractions reset on room load and camera handoff.
|
||||||
|
- State precedence is Unfocused, Paused, Dialog, Transition, Dead, Cutscene,
|
||||||
|
Playing. Playing updates player, entities, combat, `Game_OnTick`, lights and
|
||||||
|
toast lifetime in that order, rechecking the shared gameplay gate between
|
||||||
|
subsystems and entities. Cutscene updates camera/track and lights only;
|
||||||
|
transition advances only its fade/load sequencer. Death accepts R; pause accepts
|
||||||
|
menu commands; dialog accepts E/Space, Enter or attack dismissal. Other world
|
||||||
|
timers freeze in those states. END/skip and transition completion never resume
|
||||||
|
gameplay partway through the same tick.
|
||||||
|
- `Engine_Frame` and HUD/overlay procedures do not update gameplay timers, dismiss
|
||||||
|
dialogs or load rooms. `UpdateScreenFade` is the tick sequencer;
|
||||||
|
`ApplyScreenFade` is only a framebuffer operation.
|
||||||
|
- Cinematics own `csCameraX/Y/Angle`; rendering selects `viewX/Y/Angle` without
|
||||||
|
moving the player's body. Wall samples and camera-attached flashlight use the
|
||||||
|
cinematic view, matching the editor preview. Paths may cross walls. END/Tab
|
||||||
|
transfers position and angle only if the entire body is walkable at the camera
|
||||||
|
pose; otherwise the previous safe player pose remains. No path search or
|
||||||
|
collision correction is attempted. Existing sound/overlay cleanup remains.
|
||||||
|
- `RestartCurrentRoom` reloads the remembered room path with its actual entry
|
||||||
|
spawn (including transition overrides), then resets entities, health/ammo,
|
||||||
|
keys/score, combat flashes/cooldown, dialog/toast, fades, pending transition and
|
||||||
|
movement fractions. Authored lighting and original room data are reloaded;
|
||||||
|
game genre, weapon/HUD configuration and selected weapon remain unchanged.
|
||||||
|
Procedural startup fallback restarts procedurally. Failed file reload leaves
|
||||||
|
the active room and gameplay intact; it does not revive a dead player or resume
|
||||||
|
a paused game. Death R and pause-menu item 4 use this same procedure.
|
||||||
|
|
||||||
|
Verification commands are the same as stage 1. New production ASM checks cover
|
||||||
|
different render schedules, catch-up limits and 64-bit QPC arithmetic, pending
|
||||||
|
edges and focus regain, pause/modal timing, all 512 movement directions, diagonal
|
||||||
|
normalization, sprint, body corners/sliding/doors, high-speed wall collision,
|
||||||
|
wall-crossing cutscene skip/END, lit camera framebuffer parity, both restart entry
|
||||||
|
points and failed-reload rollback. The runner's lighting/cutscene labels now follow
|
||||||
|
the actual ASM call order rather than mislabelling failures. Interactive keyboard
|
||||||
|
and UI playtesting remains separate; no UI redesign is included in this stage.
|
||||||
|
|
||||||
|
Verification (2026-10-31): production engine and editor builds succeeded; editor
|
||||||
|
**137/137**, ASM **288/288**. All 320 projection angles, frame-by-frame C++/ASM
|
||||||
|
cinematic traces and 1024 baked RGB cells agree. Classic/lit IPC v2 frames match
|
||||||
|
direct ASM rendering through the x86/x64/DX11 path. Project assets remain
|
||||||
|
byte-for-byte unchanged. No manual interactive playtest was performed.
|
||||||
|
|
||||||
## Remaining stages
|
## Remaining stages
|
||||||
|
|
||||||
2. Predictable simulation: fixed 60 Hz ticks, fractional movement, normalized
|
|
||||||
input, complete collision shape, current-room restart/reset policy. Extend
|
|
||||||
gameplay gating into explicit states and separate cinematic camera ownership.
|
|
||||||
3. Honest editor/gameplay: complete undo, keys/switches and supported interactions;
|
3. Honest editor/gameplay: complete undo, keys/switches and supported interactions;
|
||||||
UE5-inspired editor/runtime UI redesign.
|
UE5-inspired editor/runtime UI redesign.
|
||||||
4. Project lifecycle and validated, safe standalone export.
|
4. Project lifecycle and validated, safe standalone export.
|
||||||
|
|
@ -118,7 +177,6 @@ unchanged v1..v5 loading, lighting bake parity and IPC v2 framebuffer parity.
|
||||||
6. Finished multi-room demo game and release verification.
|
6. Finished multi-room demo game and release verification.
|
||||||
7. Data-driven extensions and subsequent rendering features.
|
7. Data-driven extensions and subsequent rendering features.
|
||||||
|
|
||||||
Stage 1 intentionally does not claim that all audit findings are fixed: restart
|
Stages 1 and 2 do not claim that all audit findings are fixed: AI visibility/melee
|
||||||
semantics, fixed timing, collision/AI visibility, full undo and exporter safety
|
through walls, full undo and exporter safety remain assigned to subsequent
|
||||||
remain assigned to subsequent stages. Existing map and texture assets are not
|
stages. Existing map and texture assets are not migrated or rewritten.
|
||||||
migrated or rewritten.
|
|
||||||
|
|
|
||||||
Binary file not shown.
BIN
engine.exe
BIN
engine.exe
Binary file not shown.
|
|
@ -231,6 +231,19 @@ csv_invalid:
|
||||||
ValidateStagedCutscene ENDP
|
ValidateStagedCutscene ENDP
|
||||||
|
|
||||||
StopCutscene PROC uses eax ebx
|
StopCutscene PROC uses eax ebx
|
||||||
|
cmp isCutsceneActive, 0
|
||||||
|
je cs_release_done
|
||||||
|
invoke CanOccupyPlayer, csCameraX, csCameraY
|
||||||
|
test eax, eax
|
||||||
|
jz cs_release_done
|
||||||
|
mov eax, csCameraX
|
||||||
|
mov playerX, eax
|
||||||
|
mov eax, csCameraY
|
||||||
|
mov playerY, eax
|
||||||
|
mov eax, csCameraAngle
|
||||||
|
mov playerAngle, eax
|
||||||
|
cs_release_done:
|
||||||
|
call ResetMovementFractions
|
||||||
mov isCutsceneActive, 0
|
mov isCutsceneActive, 0
|
||||||
mov cutscenePending, 0
|
mov cutscenePending, 0
|
||||||
mov cutsceneSubtitle, -1
|
mov cutsceneSubtitle, -1
|
||||||
|
|
@ -256,6 +269,12 @@ StartCutscene PROC
|
||||||
cmp cutsceneCount, 0
|
cmp cutsceneCount, 0
|
||||||
je cs_start_empty
|
je cs_start_empty
|
||||||
call StopCutscene
|
call StopCutscene
|
||||||
|
mov eax, playerX
|
||||||
|
mov csCameraX, eax
|
||||||
|
mov eax, playerY
|
||||||
|
mov csCameraY, eax
|
||||||
|
mov eax, playerAngle
|
||||||
|
mov csCameraAngle, eax
|
||||||
mov cutsceneTick, 0
|
mov cutsceneTick, 0
|
||||||
mov cutsceneIndex, 0
|
mov cutsceneIndex, 0
|
||||||
mov isCutsceneActive, 1
|
mov isCutsceneActive, 1
|
||||||
|
|
@ -278,13 +297,13 @@ UpdateCutscene PROC uses ebx ecx edx esi edi
|
||||||
imul csMoveElapsed
|
imul csMoveElapsed
|
||||||
idiv csMoveDuration
|
idiv csMoveDuration
|
||||||
add eax, csMoveFromX
|
add eax, csMoveFromX
|
||||||
mov playerX, eax
|
mov csCameraX, eax
|
||||||
mov eax, csMoveToY
|
mov eax, csMoveToY
|
||||||
sub eax, csMoveFromY
|
sub eax, csMoveFromY
|
||||||
imul csMoveElapsed
|
imul csMoveElapsed
|
||||||
idiv csMoveDuration
|
idiv csMoveDuration
|
||||||
add eax, csMoveFromY
|
add eax, csMoveFromY
|
||||||
mov playerY, eax
|
mov csCameraY, eax
|
||||||
mov eax, csMoveElapsed
|
mov eax, csMoveElapsed
|
||||||
cmp eax, csMoveDuration
|
cmp eax, csMoveDuration
|
||||||
jb cs_step_angle
|
jb cs_step_angle
|
||||||
|
|
@ -298,7 +317,7 @@ cs_step_angle:
|
||||||
idiv csAngleDuration
|
idiv csAngleDuration
|
||||||
add eax, csAngleFrom
|
add eax, csAngleFrom
|
||||||
and eax, ANGLE_MASK
|
and eax, ANGLE_MASK
|
||||||
mov playerAngle, eax
|
mov csCameraAngle, eax
|
||||||
mov eax, csAngleElapsed
|
mov eax, csAngleElapsed
|
||||||
cmp eax, csAngleDuration
|
cmp eax, csAngleDuration
|
||||||
jb cs_step_fade
|
jb cs_step_fade
|
||||||
|
|
@ -368,9 +387,9 @@ cs_wait:
|
||||||
jnz cs_advance_tick
|
jnz cs_advance_tick
|
||||||
jmp cs_dispatch
|
jmp cs_dispatch
|
||||||
cs_move:
|
cs_move:
|
||||||
mov eax, playerX
|
mov eax, csCameraX
|
||||||
mov csMoveFromX, eax
|
mov csMoveFromX, eax
|
||||||
mov eax, playerY
|
mov eax, csCameraY
|
||||||
mov csMoveFromY, eax
|
mov csMoveFromY, eax
|
||||||
mov eax, [esi].CutsceneEvent.paramA
|
mov eax, [esi].CutsceneEvent.paramA
|
||||||
mov csMoveToX, eax
|
mov csMoveToX, eax
|
||||||
|
|
@ -382,12 +401,12 @@ cs_move:
|
||||||
test eax, eax
|
test eax, eax
|
||||||
jnz cs_dispatch
|
jnz cs_dispatch
|
||||||
mov eax, csMoveToX
|
mov eax, csMoveToX
|
||||||
mov playerX, eax
|
mov csCameraX, eax
|
||||||
mov eax, csMoveToY
|
mov eax, csMoveToY
|
||||||
mov playerY, eax
|
mov csCameraY, eax
|
||||||
jmp cs_dispatch
|
jmp cs_dispatch
|
||||||
cs_pan:
|
cs_pan:
|
||||||
mov eax, playerAngle
|
mov eax, csCameraAngle
|
||||||
mov csAngleFrom, eax
|
mov csAngleFrom, eax
|
||||||
mov ebx, [esi].CutsceneEvent.paramA
|
mov ebx, [esi].CutsceneEvent.paramA
|
||||||
sub ebx, eax
|
sub ebx, eax
|
||||||
|
|
@ -401,7 +420,7 @@ cs_pan:
|
||||||
test eax, eax
|
test eax, eax
|
||||||
jnz cs_dispatch
|
jnz cs_dispatch
|
||||||
mov eax, [esi].CutsceneEvent.paramA
|
mov eax, [esi].CutsceneEvent.paramA
|
||||||
mov playerAngle, eax
|
mov csCameraAngle, eax
|
||||||
jmp cs_dispatch
|
jmp cs_dispatch
|
||||||
cs_text:
|
cs_text:
|
||||||
mov eax, [esi].CutsceneEvent.paramA
|
mov eax, [esi].CutsceneEvent.paramA
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,26 @@ isRunning dd 1
|
||||||
; Latched by UpdatePlayer: scene/end/skip/instant-transition ticks never run AI
|
; Latched by UpdatePlayer: scene/end/skip/instant-transition ticks never run AI
|
||||||
; or combat, even if they return control partway through the tick.
|
; or combat, even if they return control partway through the tick.
|
||||||
gameplayTickBlocked dd 0
|
gameplayTickBlocked dd 0
|
||||||
targetFrameTimeMs dd 16 ; ~60 FPS (16.6 ms)
|
qpcFrequency dq 0
|
||||||
lastTickCount dd 0
|
qpcPrevious dq 0
|
||||||
|
qpcNow dq 0
|
||||||
|
tickAccumulator dq 0 ; QPC counts multiplied by 60
|
||||||
|
clockReset dd 0
|
||||||
|
simulationTicks dd 0
|
||||||
|
simulationState dd 0
|
||||||
|
inputFocused dd 1
|
||||||
|
playerFractionX dd 0 ; signed 16.16 remainder, truncated toward zero
|
||||||
|
playerFractionY dd 0
|
||||||
|
viewX dd 160
|
||||||
|
viewY dd 160
|
||||||
|
viewAngle dd 0
|
||||||
|
csCameraX dd 160
|
||||||
|
csCameraY dd 160
|
||||||
|
csCameraAngle dd 0
|
||||||
|
currentMapPath db MAX_PATH_BYTES dup(0)
|
||||||
|
roomEntryX dd 2
|
||||||
|
roomEntryY dd 2
|
||||||
|
roomEntryAngle dd 0
|
||||||
fpsCounter dd 0
|
fpsCounter dd 0
|
||||||
fpsTimer dd 0
|
fpsTimer dd 0
|
||||||
currentFps dd 60
|
currentFps dd 60
|
||||||
|
|
@ -39,9 +57,9 @@ rngSeed dd 12345678h
|
||||||
playerX dd 160
|
playerX dd 160
|
||||||
playerY dd 160
|
playerY dd 160
|
||||||
playerAngle dd 0 ; 0..511 (0 = East, 128 = South, 256 = West, 384 = North)
|
playerAngle dd 0 ; 0..511 (0 = East, 128 = South, 256 = West, 384 = North)
|
||||||
playerMoveSpeed dd 3 ; raw units per frame (walk)
|
playerMoveSpeed dd 3 ; raw units per 60 Hz tick (walk)
|
||||||
playerRunSpeed dd 6 ; raw units per frame (sprint)
|
playerRunSpeed dd 6 ; raw units per 60 Hz tick (sprint)
|
||||||
playerTurnSpeed dd 4 ; angle units per frame
|
playerTurnSpeed dd 4 ; angle units per 60 Hz tick
|
||||||
playerRadius dd 16 ; collision radius (raw units)
|
playerRadius dd 16 ; collision radius (raw units)
|
||||||
|
|
||||||
; Input State Flags
|
; Input State Flags
|
||||||
|
|
@ -64,6 +82,7 @@ keySceneHeld db 0
|
||||||
keySkipHeld db 0
|
keySkipHeld db 0
|
||||||
keyAttack db 0
|
keyAttack db 0
|
||||||
keyAttackHeld db 0
|
keyAttackHeld db 0
|
||||||
|
keySpaceAttackHeld db 0
|
||||||
keyRestart db 0
|
keyRestart db 0
|
||||||
keyRestartHeld db 0
|
keyRestartHeld db 0
|
||||||
keyWeapon1 db 0
|
keyWeapon1 db 0
|
||||||
|
|
@ -78,6 +97,23 @@ keyFlashlight db 0
|
||||||
keyFlashlightHeld db 0
|
keyFlashlightHeld db 0
|
||||||
keyLightMode db 0
|
keyLightMode db 0
|
||||||
keyLightModeHeld db 0
|
keyLightModeHeld db 0
|
||||||
|
keyMap db 0
|
||||||
|
keyHud db 0
|
||||||
|
keyWeaponHeld1 db 0
|
||||||
|
keyWeaponHeld2 db 0
|
||||||
|
keyWeaponHeld3 db 0
|
||||||
|
keyMenu1 db 0
|
||||||
|
keyMenu2 db 0
|
||||||
|
keyMenu3 db 0
|
||||||
|
keyMenu4 db 0
|
||||||
|
keyMenu5 db 0
|
||||||
|
keyMenuHeld1 db 0
|
||||||
|
keyMenuHeld2 db 0
|
||||||
|
keyMenuHeld3 db 0
|
||||||
|
keyMenuHeld4 db 0
|
||||||
|
keyMenuHeld5 db 0
|
||||||
|
keyDismiss db 0
|
||||||
|
keyDismissHeld db 0
|
||||||
isGamePaused dd 0
|
isGamePaused dd 0
|
||||||
|
|
||||||
; Lighting Configuration & Dynamic State
|
; Lighting Configuration & Dynamic State
|
||||||
|
|
|
||||||
|
|
@ -108,9 +108,17 @@ HUD_STYLE_DEBUG equ 1 ; Debug Mode (FPS counter, Engine watermark, Min
|
||||||
WEAPON_NONE equ -1 ; No weapon (Unarmed / Peaceful / Quest)
|
WEAPON_NONE equ -1 ; No weapon (Unarmed / Peaceful / Quest)
|
||||||
WEAPON_KNIFE equ 0 ; Melee blade (no ammo, fast, 30 damage)
|
WEAPON_KNIFE equ 0 ; Melee blade (no ammo, fast, 30 damage)
|
||||||
WEAPON_PISTOL equ 1 ; Handgun (1 ammo, accurate, 35 damage)
|
WEAPON_PISTOL equ 1 ; Handgun (1 ammo, accurate, 35 damage)
|
||||||
WEAPON_SHOTGUN equ 2 ; Pump Shotgun (2 ammo, 3-ray spread, 70 damage)
|
WEAPON_SHOTGUN equ 2 ; Pump Shotgun (2 ammo, wider hitbox, 70 damage)
|
||||||
WEAPON_COUNT equ 3
|
WEAPON_COUNT equ 3
|
||||||
|
|
||||||
|
SIM_PLAYING equ 0
|
||||||
|
SIM_CUTSCENE equ 1
|
||||||
|
SIM_TRANSITION equ 2
|
||||||
|
SIM_DEAD equ 3
|
||||||
|
SIM_PAUSED equ 4
|
||||||
|
SIM_DIALOG equ 5
|
||||||
|
SIM_UNFOCUSED equ 6
|
||||||
|
|
||||||
; Entity Archetypes & States
|
; Entity Archetypes & States
|
||||||
ENTITY_TYPE_PROP equ 0
|
ENTITY_TYPE_PROP equ 0
|
||||||
ENTITY_TYPE_GUARD equ 1 ; Enemy Guard AI (Patrol, Chase, Attack)
|
ENTITY_TYPE_GUARD equ 1 ; Enemy Guard AI (Patrol, Chase, Attack)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
ShowGameToast PROTO :DWORD, :DWORD, :DWORD
|
ShowGameToast PROTO :DWORD, :DWORD, :DWORD
|
||||||
Game_OnPlayerInteract PROTO :DWORD, :DWORD
|
Game_OnPlayerInteract PROTO :DWORD, :DWORD
|
||||||
|
RestartCurrentRoom PROTO
|
||||||
|
|
||||||
; ------------------------------------------------------------------------------
|
; ------------------------------------------------------------------------------
|
||||||
; InitEntities: Initialize entity runtime state from spriteList flags/defaults
|
; InitEntities: Initialize entity runtime state from spriteList flags/defaults
|
||||||
|
|
@ -140,13 +141,7 @@ UpdateEntities PROC uses ebx ecx edx esi edi
|
||||||
je entities_active
|
je entities_active
|
||||||
cmp keyRestart, 0
|
cmp keyRestart, 0
|
||||||
je entities_done
|
je entities_done
|
||||||
; Respawn player
|
call RestartCurrentRoom
|
||||||
mov playerHealth, 100
|
|
||||||
mov playerAmmo, 50
|
|
||||||
mov isPlayerDead, 0
|
|
||||||
mov playerHitFlash, 0
|
|
||||||
call LoadMapBinary
|
|
||||||
call InitEntities
|
|
||||||
ret
|
ret
|
||||||
|
|
||||||
entities_active:
|
entities_active:
|
||||||
|
|
@ -156,6 +151,9 @@ entities_active:
|
||||||
mov i, 0
|
mov i, 0
|
||||||
|
|
||||||
entity_loop:
|
entity_loop:
|
||||||
|
call IsGameplayBlocked
|
||||||
|
test eax, eax
|
||||||
|
jnz entities_done
|
||||||
mov eax, i
|
mov eax, i
|
||||||
cmp eax, spriteCount
|
cmp eax, spriteCount
|
||||||
jge entities_done
|
jge entities_done
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,6 @@ ShowGameToast ENDP
|
||||||
RenderGameToast PROC uses ebx ecx edx esi edi
|
RenderGameToast PROC uses ebx ecx edx esi edi
|
||||||
cmp gameToastTimer, 0
|
cmp gameToastTimer, 0
|
||||||
jle rt_done
|
jle rt_done
|
||||||
dec gameToastTimer
|
|
||||||
|
|
||||||
; 1. Calculate text character length
|
; 1. Calculate text character length
|
||||||
mov esi, OFFSET gameToastText
|
mov esi, OFFSET gameToastText
|
||||||
|
|
@ -131,6 +130,7 @@ no_text:
|
||||||
|
|
||||||
activate_dlg:
|
activate_dlg:
|
||||||
mov gameDialogActive, 1
|
mov gameDialogActive, 1
|
||||||
|
mov clockReset, 1
|
||||||
ret
|
ret
|
||||||
ShowGameDialog ENDP
|
ShowGameDialog ENDP
|
||||||
|
|
||||||
|
|
@ -139,6 +139,7 @@ ShowGameDialog ENDP
|
||||||
; ------------------------------------------------------------------------------
|
; ------------------------------------------------------------------------------
|
||||||
CloseGameDialog PROC
|
CloseGameDialog PROC
|
||||||
mov gameDialogActive, 0
|
mov gameDialogActive, 0
|
||||||
|
mov clockReset, 1
|
||||||
ret
|
ret
|
||||||
CloseGameDialog ENDP
|
CloseGameDialog ENDP
|
||||||
|
|
||||||
|
|
@ -165,18 +166,6 @@ draw_dlg_body:
|
||||||
; Close hint in bottom-right
|
; Close hint in bottom-right
|
||||||
invoke DrawString8x8, 140, 200, OFFSET szDialogCloseHint, COLOR_GRAY
|
invoke DrawString8x8, 140, 200, OFFSET szDialogCloseHint, COLOR_GRAY
|
||||||
|
|
||||||
; Check if player pressed Action key or Attack to dismiss
|
|
||||||
cmp keyAction, 0
|
|
||||||
jne do_dismiss
|
|
||||||
cmp keyAttack, 0
|
|
||||||
jne do_dismiss
|
|
||||||
jmp rd_done
|
|
||||||
|
|
||||||
do_dismiss:
|
|
||||||
mov keyAction, 0
|
|
||||||
mov keyAttack, 0
|
|
||||||
mov gameDialogActive, 0
|
|
||||||
|
|
||||||
rd_done:
|
rd_done:
|
||||||
ret
|
ret
|
||||||
RenderGameDialog ENDP
|
RenderGameDialog ENDP
|
||||||
|
|
|
||||||
|
|
@ -1,386 +1,70 @@
|
||||||
; ==============================================================================
|
; Keyboard sampling never mutates simulation. Pending edges survive render-only
|
||||||
; EBLAN ENGINE - Input System (Keyboard via GetAsyncKeyState)
|
; frames and are cleared only by ConsumeInputEdges after one simulation tick.
|
||||||
; ==============================================================================
|
|
||||||
|
|
||||||
.code
|
.code
|
||||||
|
SampleHeld PROC uses ebx, first:DWORD, second:DWORD
|
||||||
|
invoke GetAsyncKeyState, first
|
||||||
|
mov ebx, eax
|
||||||
|
cmp second, 0
|
||||||
|
je @F
|
||||||
|
invoke GetAsyncKeyState, second
|
||||||
|
or eax, ebx
|
||||||
|
@@:
|
||||||
|
shr eax, 15
|
||||||
|
and eax, 1
|
||||||
|
ret
|
||||||
|
SampleHeld ENDP
|
||||||
|
|
||||||
; ------------------------------------------------------------------------------
|
SampleEdge PROC uses ebx esi edi, first:DWORD, second:DWORD, held:DWORD, pending:DWORD
|
||||||
; PollInput: Reads keyboard state and updates movement & action flags
|
invoke SampleHeld, first, second
|
||||||
; ------------------------------------------------------------------------------
|
mov esi, held
|
||||||
PollInput PROC uses eax ebx
|
mov edi, pending
|
||||||
mov keyForward, 0
|
test eax, eax
|
||||||
mov keyBackward, 0
|
jz edge_released
|
||||||
mov keyStrafeLeft, 0
|
cmp BYTE PTR [esi], 0
|
||||||
mov keyStrafeRight, 0
|
jne edge_done
|
||||||
mov keyTurnLeft, 0
|
mov BYTE PTR [edi], 1
|
||||||
mov keyTurnRight, 0
|
edge_released:
|
||||||
mov keyRun, 0
|
mov [esi], al
|
||||||
|
edge_done:
|
||||||
|
ret
|
||||||
|
SampleEdge ENDP
|
||||||
|
|
||||||
|
ConsumeInputEdges PROC
|
||||||
mov keyAction, 0
|
mov keyAction, 0
|
||||||
mov keyScreenshot, 0
|
|
||||||
mov keyCutscene, 0
|
mov keyCutscene, 0
|
||||||
mov keySkipScene, 0
|
mov keySkipScene, 0
|
||||||
mov keyAttack, 0
|
mov keyAttack, 0
|
||||||
mov keyRestart, 0
|
mov keyRestart, 0
|
||||||
|
mov keyPause, 0
|
||||||
|
mov keyMap, 0
|
||||||
|
mov keyHud, 0
|
||||||
|
mov keyFlashlight, 0
|
||||||
|
mov keyLightMode, 0
|
||||||
|
mov keyWeapon1, 0
|
||||||
|
mov keyWeapon2, 0
|
||||||
|
mov keyWeapon3, 0
|
||||||
|
mov keyMenu1, 0
|
||||||
|
mov keyMenu2, 0
|
||||||
|
mov keyMenu3, 0
|
||||||
|
mov keyMenu4, 0
|
||||||
|
mov keyMenu5, 0
|
||||||
|
mov keyDismiss, 0
|
||||||
|
ret
|
||||||
|
ConsumeInputEdges ENDP
|
||||||
|
|
||||||
|
PollInput PROC uses eax ebx
|
||||||
|
LOCAL regained:DWORD
|
||||||
|
mov regained, 0
|
||||||
invoke GetForegroundWindow
|
invoke GetForegroundWindow
|
||||||
cmp eax, hEngineWnd
|
cmp eax, hEngineWnd
|
||||||
je input_has_focus
|
je input_has_focus
|
||||||
mov keyActionHeld, 0
|
cmp inputFocused, 0
|
||||||
mov keyMapHeld, 0
|
je @F
|
||||||
mov keyShotHeld, 0
|
mov clockReset, 1
|
||||||
mov keySceneHeld, 0
|
|
||||||
mov keySkipHeld, 0
|
|
||||||
mov keyAttackHeld, 0
|
|
||||||
mov keyRestartHeld, 0
|
|
||||||
ret
|
|
||||||
input_has_focus:
|
|
||||||
|
|
||||||
; 1. Forward (W / Up Arrow)
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_W
|
|
||||||
mov bx, ax
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_UP
|
|
||||||
or bx, ax
|
|
||||||
test bx, 8000h
|
|
||||||
jz @F
|
|
||||||
mov keyForward, 1
|
|
||||||
@@:
|
@@:
|
||||||
|
mov inputFocused, 0
|
||||||
; 2. Backward (S / Down Arrow)
|
call ConsumeInputEdges
|
||||||
invoke GetAsyncKeyState, VK_KEY_S
|
mov keyScreenshot, 0
|
||||||
mov bx, ax
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_DOWN
|
|
||||||
or bx, ax
|
|
||||||
test bx, 8000h
|
|
||||||
jz @F
|
|
||||||
mov keyBackward, 1
|
|
||||||
@@:
|
|
||||||
|
|
||||||
; 3. Strafe Left (A)
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_A
|
|
||||||
test ax, 8000h
|
|
||||||
jz @F
|
|
||||||
mov keyStrafeLeft, 1
|
|
||||||
@@:
|
|
||||||
|
|
||||||
; 4. Strafe Right (D)
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_D
|
|
||||||
test ax, 8000h
|
|
||||||
jz @F
|
|
||||||
mov keyStrafeRight, 1
|
|
||||||
@@:
|
|
||||||
|
|
||||||
; 5. Turn Left (Left Arrow)
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_LEFT
|
|
||||||
test ax, 8000h
|
|
||||||
jz @F
|
|
||||||
mov keyTurnLeft, 1
|
|
||||||
@@:
|
|
||||||
|
|
||||||
; 6. Turn Right (Right Arrow)
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_RIGHT
|
|
||||||
test ax, 8000h
|
|
||||||
jz @F
|
|
||||||
mov keyTurnRight, 1
|
|
||||||
@@:
|
|
||||||
|
|
||||||
; Actions are emitted once per physical press (also for E + Space).
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_E
|
|
||||||
mov bx, ax
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_SPACE
|
|
||||||
or bx, ax
|
|
||||||
test bx, 8000h
|
|
||||||
jz action_released
|
|
||||||
cmp keyActionHeld, 0
|
|
||||||
jne action_sampled
|
|
||||||
mov keyAction, 1
|
|
||||||
mov keyActionHeld, 1
|
|
||||||
jmp action_sampled
|
|
||||||
action_released:
|
|
||||||
mov keyActionHeld, 0
|
|
||||||
action_sampled:
|
|
||||||
|
|
||||||
; 8. Run / Sprint (Shift)
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_SHIFT
|
|
||||||
test ax, 8000h
|
|
||||||
jz @F
|
|
||||||
mov keyRun, 1
|
|
||||||
@@:
|
|
||||||
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_M
|
|
||||||
test ax, 8000h
|
|
||||||
jz map_key_released
|
|
||||||
cmp keyMapHeld, 0
|
|
||||||
jne map_key_sampled
|
|
||||||
xor keyShowMap, 1
|
|
||||||
mov keyMapHeld, 1
|
|
||||||
jmp map_key_sampled
|
|
||||||
map_key_released:
|
|
||||||
mov keyMapHeld, 0
|
|
||||||
map_key_sampled:
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_F12
|
|
||||||
test ax, 8000h
|
|
||||||
jz shot_key_released
|
|
||||||
cmp keyShotHeld, 0
|
|
||||||
jne shot_key_sampled
|
|
||||||
mov keyScreenshot, 1
|
|
||||||
mov keyShotHeld, 1
|
|
||||||
jmp shot_key_sampled
|
|
||||||
shot_key_released:
|
|
||||||
mov keyShotHeld, 0
|
|
||||||
shot_key_sampled:
|
|
||||||
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_C
|
|
||||||
test ax, 8000h
|
|
||||||
jz scene_key_released
|
|
||||||
cmp keySceneHeld, 0
|
|
||||||
jne scene_key_sampled
|
|
||||||
mov keyCutscene, 1
|
|
||||||
mov keySceneHeld, 1
|
|
||||||
jmp scene_key_sampled
|
|
||||||
scene_key_released:
|
|
||||||
mov keySceneHeld, 0
|
|
||||||
scene_key_sampled:
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_TAB
|
|
||||||
test ax, 8000h
|
|
||||||
jz skip_key_released
|
|
||||||
cmp keySkipHeld, 0
|
|
||||||
jne skip_key_sampled
|
|
||||||
mov keySkipScene, 1
|
|
||||||
mov keySkipHeld, 1
|
|
||||||
jmp skip_key_sampled
|
|
||||||
skip_key_released:
|
|
||||||
mov keySkipHeld, 0
|
|
||||||
skip_key_sampled:
|
|
||||||
; Attack Key: Space or Ctrl or Left Mouse Button
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_SPACE
|
|
||||||
mov bx, ax
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_CONTROL
|
|
||||||
or bx, ax
|
|
||||||
invoke GetAsyncKeyState, VK_LBUTTON
|
|
||||||
or bx, ax
|
|
||||||
test bx, 8000h
|
|
||||||
jz attack_released
|
|
||||||
cmp keyAttackHeld, 0
|
|
||||||
jne attack_sampled
|
|
||||||
mov keyAttack, 1
|
|
||||||
mov keyAttackHeld, 1
|
|
||||||
jmp attack_sampled
|
|
||||||
attack_released:
|
|
||||||
mov keyAttackHeld, 0
|
|
||||||
attack_sampled:
|
|
||||||
|
|
||||||
; Restart Key: R
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_R
|
|
||||||
test ax, 8000h
|
|
||||||
jz restart_released
|
|
||||||
cmp keyRestartHeld, 0
|
|
||||||
jne restart_sampled
|
|
||||||
mov keyRestart, 1
|
|
||||||
mov keyRestartHeld, 1
|
|
||||||
jmp restart_sampled
|
|
||||||
restart_released:
|
|
||||||
mov keyRestartHeld, 0
|
|
||||||
restart_sampled:
|
|
||||||
|
|
||||||
; F1 / H: Toggle Clean HUD vs Debug HUD
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_F1
|
|
||||||
mov bx, ax
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_H
|
|
||||||
or bx, ax
|
|
||||||
test bx, 8000h
|
|
||||||
jz hud_key_released
|
|
||||||
cmp keyHudStyleHeld, 0
|
|
||||||
jne hud_key_sampled
|
|
||||||
xor hudStyle, 1
|
|
||||||
mov keyHudStyleHeld, 1
|
|
||||||
cmp hudStyle, HUD_STYLE_CLEAN
|
|
||||||
je toast_hud_clean
|
|
||||||
invoke ShowGameToast, OFFSET szHudDebugToast, 60, COLOR_CYAN
|
|
||||||
jmp hud_key_sampled
|
|
||||||
toast_hud_clean:
|
|
||||||
invoke ShowGameToast, OFFSET szHudCleanToast, 60, COLOR_GREEN
|
|
||||||
jmp hud_key_sampled
|
|
||||||
hud_key_released:
|
|
||||||
mov keyHudStyleHeld, 0
|
|
||||||
hud_key_sampled:
|
|
||||||
|
|
||||||
; F: Toggle Flashlight
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_F
|
|
||||||
test ax, 8000h
|
|
||||||
jz flash_key_released
|
|
||||||
cmp keyFlashlightHeld, 0
|
|
||||||
jne flash_key_sampled
|
|
||||||
xor isFlashlightOn, 1
|
|
||||||
mov keyFlashlightHeld, 1
|
|
||||||
cmp isFlashlightOn, 1
|
|
||||||
je toast_flash_on
|
|
||||||
invoke ShowGameToast, OFFSET szFlashlightOff, 60, 00808080h
|
|
||||||
jmp flash_key_sampled
|
|
||||||
toast_flash_on:
|
|
||||||
invoke ShowGameToast, OFFSET szFlashlightOn, 60, COLOR_CYAN
|
|
||||||
jmp flash_key_sampled
|
|
||||||
flash_key_released:
|
|
||||||
mov keyFlashlightHeld, 0
|
|
||||||
flash_key_sampled:
|
|
||||||
|
|
||||||
; L: Cycle Lighting Mode (0: Off -> 1: Baked -> 2: Dynamic)
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_L
|
|
||||||
test ax, 8000h
|
|
||||||
jz light_key_released
|
|
||||||
cmp keyLightModeHeld, 0
|
|
||||||
jne light_key_sampled
|
|
||||||
inc lightingMode
|
|
||||||
cmp lightingMode, 3
|
|
||||||
jl @F
|
|
||||||
mov lightingMode, 0
|
|
||||||
@@:
|
|
||||||
mov keyLightModeHeld, 1
|
|
||||||
cmp lightingMode, LIGHTING_MODE_NONE
|
|
||||||
je toast_light_none
|
|
||||||
cmp lightingMode, LIGHTING_MODE_BAKED
|
|
||||||
je toast_light_baked
|
|
||||||
invoke ShowGameToast, OFFSET szLightMode2, 60, COLOR_YELLOW
|
|
||||||
jmp light_key_sampled
|
|
||||||
toast_light_none:
|
|
||||||
invoke ShowGameToast, OFFSET szLightMode0, 60, 00808080h
|
|
||||||
jmp light_key_sampled
|
|
||||||
toast_light_baked:
|
|
||||||
invoke ShowGameToast, OFFSET szLightMode1, 60, COLOR_CYAN
|
|
||||||
jmp light_key_sampled
|
|
||||||
light_key_released:
|
|
||||||
mov keyLightModeHeld, 0
|
|
||||||
light_key_sampled:
|
|
||||||
|
|
||||||
; Weapon Selection Keys: 1, 2, 3 (only in Action mode with weapons enabled)
|
|
||||||
cmp gameMode, GAME_MODE_ACTION
|
|
||||||
jne skip_weapon_select
|
|
||||||
cmp enableWeapons, 0
|
|
||||||
je skip_weapon_select
|
|
||||||
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_1
|
|
||||||
test ax, 8000h
|
|
||||||
jz @F
|
|
||||||
mov playerWeapon, WEAPON_KNIFE
|
|
||||||
@@:
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_2
|
|
||||||
test ax, 8000h
|
|
||||||
jz @F
|
|
||||||
mov playerWeapon, WEAPON_PISTOL
|
|
||||||
@@:
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_3
|
|
||||||
test ax, 8000h
|
|
||||||
jz @F
|
|
||||||
mov playerWeapon, WEAPON_SHOTGUN
|
|
||||||
@@:
|
|
||||||
skip_weapon_select:
|
|
||||||
|
|
||||||
; Escape / Pause Key
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_ESCAPE
|
|
||||||
test ax, 8000h
|
|
||||||
jz pause_key_released
|
|
||||||
cmp keyPauseHeld, 0
|
|
||||||
jne pause_key_sampled
|
|
||||||
xor isGamePaused, 1
|
|
||||||
mov keyPauseHeld, 1
|
|
||||||
jmp pause_key_sampled
|
|
||||||
pause_key_released:
|
|
||||||
mov keyPauseHeld, 0
|
|
||||||
pause_key_sampled:
|
|
||||||
|
|
||||||
; If paused, handle pause menu choices
|
|
||||||
cmp isGamePaused, 0
|
|
||||||
je not_in_pause_menu
|
|
||||||
; While paused, mask normal movement & attack
|
|
||||||
mov keyForward, 0
|
|
||||||
mov keyBackward, 0
|
|
||||||
mov keyStrafeLeft, 0
|
|
||||||
mov keyStrafeRight, 0
|
|
||||||
mov keyTurnLeft, 0
|
|
||||||
mov keyTurnRight, 0
|
|
||||||
mov keyAttack, 0
|
|
||||||
mov keyAction, 0
|
|
||||||
|
|
||||||
; Check if any menu key is pressed
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_1
|
|
||||||
mov bx, ax
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_2
|
|
||||||
or bx, ax
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_3
|
|
||||||
or bx, ax
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_4
|
|
||||||
or bx, ax
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_5
|
|
||||||
or bx, ax
|
|
||||||
test bx, 8000h
|
|
||||||
jz menu_keys_released
|
|
||||||
|
|
||||||
; If a key is pressed, check if already held
|
|
||||||
cmp keyMenuHeld, 0
|
|
||||||
jne pause_menu_done
|
|
||||||
mov keyMenuHeld, 1
|
|
||||||
|
|
||||||
; Option 1: Resume
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_1
|
|
||||||
test ax, 8000h
|
|
||||||
jz @F
|
|
||||||
mov isGamePaused, 0
|
|
||||||
jmp pause_menu_done
|
|
||||||
@@:
|
|
||||||
; Option 2: Toggle Weapons / Peaceful Mode
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_2
|
|
||||||
test ax, 8000h
|
|
||||||
jz @F
|
|
||||||
xor enableWeapons, 1
|
|
||||||
cmp enableWeapons, 0
|
|
||||||
je set_peaceful_mode
|
|
||||||
mov gameMode, GAME_MODE_ACTION
|
|
||||||
mov playerWeapon, WEAPON_PISTOL
|
|
||||||
mov showCrosshair, 1
|
|
||||||
invoke ShowGameToast, OFFSET szWeaponsEnabledToast, 80, COLOR_YELLOW
|
|
||||||
jmp pause_menu_done
|
|
||||||
set_peaceful_mode:
|
|
||||||
mov gameMode, GAME_MODE_ADVENTURE
|
|
||||||
mov playerWeapon, WEAPON_NONE
|
|
||||||
mov showCrosshair, 0
|
|
||||||
invoke ShowGameToast, OFFSET szWeaponsDisabledToast, 80, COLOR_GREEN
|
|
||||||
jmp pause_menu_done
|
|
||||||
@@:
|
|
||||||
; Option 3: Toggle HUD (Clean vs Debug)
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_3
|
|
||||||
test ax, 8000h
|
|
||||||
jz @F
|
|
||||||
xor hudStyle, 1
|
|
||||||
cmp hudStyle, HUD_STYLE_CLEAN
|
|
||||||
je toast_pause_clean
|
|
||||||
invoke ShowGameToast, OFFSET szHudDebugToast, 60, COLOR_CYAN
|
|
||||||
jmp pause_menu_done
|
|
||||||
toast_pause_clean:
|
|
||||||
invoke ShowGameToast, OFFSET szHudCleanToast, 60, COLOR_GREEN
|
|
||||||
jmp pause_menu_done
|
|
||||||
@@:
|
|
||||||
; Option 4: Restart level
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_4
|
|
||||||
test ax, 8000h
|
|
||||||
jz @F
|
|
||||||
mov isGamePaused, 0
|
|
||||||
mov keyRestart, 1
|
|
||||||
jmp pause_menu_done
|
|
||||||
@@:
|
|
||||||
; Option 5: Quit to Windows
|
|
||||||
invoke GetAsyncKeyState, VK_KEY_5
|
|
||||||
test ax, 8000h
|
|
||||||
jz pause_menu_done
|
|
||||||
mov isRunning, 0
|
|
||||||
jmp pause_menu_done
|
|
||||||
|
|
||||||
menu_keys_released:
|
|
||||||
mov keyMenuHeld, 0
|
|
||||||
|
|
||||||
pause_menu_done:
|
|
||||||
ret
|
|
||||||
|
|
||||||
not_in_pause_menu:
|
|
||||||
cmp isCutsceneActive, 0
|
|
||||||
je normal_keys_enabled
|
|
||||||
mov keyForward, 0
|
mov keyForward, 0
|
||||||
mov keyBackward, 0
|
mov keyBackward, 0
|
||||||
mov keyStrafeLeft, 0
|
mov keyStrafeLeft, 0
|
||||||
|
|
@ -388,9 +72,55 @@ not_in_pause_menu:
|
||||||
mov keyTurnLeft, 0
|
mov keyTurnLeft, 0
|
||||||
mov keyTurnRight, 0
|
mov keyTurnRight, 0
|
||||||
mov keyRun, 0
|
mov keyRun, 0
|
||||||
mov keyAction, 0
|
; On regain, sample every physical latch but discard the resulting edges.
|
||||||
mov keyAttack, 0
|
ret
|
||||||
normal_keys_enabled:
|
input_has_focus:
|
||||||
|
cmp inputFocused, 0
|
||||||
|
jne @F
|
||||||
|
mov clockReset, 1
|
||||||
|
mov regained, 1
|
||||||
|
@@:
|
||||||
|
mov inputFocused, 1
|
||||||
|
invoke SampleHeld, VK_KEY_W, VK_KEY_UP
|
||||||
|
mov keyForward, al
|
||||||
|
invoke SampleHeld, VK_KEY_S, VK_KEY_DOWN
|
||||||
|
mov keyBackward, al
|
||||||
|
invoke SampleHeld, VK_KEY_A, 0
|
||||||
|
mov keyStrafeLeft, al
|
||||||
|
invoke SampleHeld, VK_KEY_D, 0
|
||||||
|
mov keyStrafeRight, al
|
||||||
|
invoke SampleHeld, VK_KEY_LEFT, 0
|
||||||
|
mov keyTurnLeft, al
|
||||||
|
invoke SampleHeld, VK_KEY_RIGHT, 0
|
||||||
|
mov keyTurnRight, al
|
||||||
|
invoke SampleHeld, VK_KEY_SHIFT, 0
|
||||||
|
mov keyRun, al
|
||||||
|
invoke SampleEdge, VK_KEY_E, VK_KEY_SPACE, OFFSET keyActionHeld, OFFSET keyAction
|
||||||
|
invoke SampleEdge, VK_KEY_CONTROL, VK_LBUTTON, OFFSET keyAttackHeld, OFFSET keyAttack
|
||||||
|
; Separate Space latch prevents a second poll from re-emitting a pending edge.
|
||||||
|
invoke SampleEdge, VK_KEY_SPACE, 0, OFFSET keySpaceAttackHeld, OFFSET keyAttack
|
||||||
|
invoke SampleEdge, VK_KEY_R, 0, OFFSET keyRestartHeld, OFFSET keyRestart
|
||||||
|
invoke SampleEdge, VK_KEY_C, 0, OFFSET keySceneHeld, OFFSET keyCutscene
|
||||||
|
invoke SampleEdge, VK_KEY_TAB, 0, OFFSET keySkipHeld, OFFSET keySkipScene
|
||||||
|
invoke SampleEdge, VK_KEY_M, 0, OFFSET keyMapHeld, OFFSET keyMap
|
||||||
|
invoke SampleEdge, VK_KEY_F12, 0, OFFSET keyShotHeld, OFFSET keyScreenshot
|
||||||
|
invoke SampleEdge, VK_KEY_F1, VK_KEY_H, OFFSET keyHudStyleHeld, OFFSET keyHud
|
||||||
|
invoke SampleEdge, VK_KEY_F, 0, OFFSET keyFlashlightHeld, OFFSET keyFlashlight
|
||||||
|
invoke SampleEdge, VK_KEY_L, 0, OFFSET keyLightModeHeld, OFFSET keyLightMode
|
||||||
|
invoke SampleEdge, VK_KEY_ESCAPE, 0, OFFSET keyPauseHeld, OFFSET keyPause
|
||||||
|
invoke SampleEdge, 13, 0, OFFSET keyDismissHeld, OFFSET keyDismiss
|
||||||
|
invoke SampleEdge, VK_KEY_1, 0, OFFSET keyWeaponHeld1, OFFSET keyWeapon1
|
||||||
|
invoke SampleEdge, VK_KEY_2, 0, OFFSET keyWeaponHeld2, OFFSET keyWeapon2
|
||||||
|
invoke SampleEdge, VK_KEY_3, 0, OFFSET keyWeaponHeld3, OFFSET keyWeapon3
|
||||||
|
invoke SampleEdge, VK_KEY_1, 0, OFFSET keyMenuHeld1, OFFSET keyMenu1
|
||||||
|
invoke SampleEdge, VK_KEY_2, 0, OFFSET keyMenuHeld2, OFFSET keyMenu2
|
||||||
|
invoke SampleEdge, VK_KEY_3, 0, OFFSET keyMenuHeld3, OFFSET keyMenu3
|
||||||
|
invoke SampleEdge, VK_KEY_4, 0, OFFSET keyMenuHeld4, OFFSET keyMenu4
|
||||||
|
invoke SampleEdge, VK_KEY_5, 0, OFFSET keyMenuHeld5, OFFSET keyMenu5
|
||||||
|
cmp regained, 0
|
||||||
|
je input_sample_done
|
||||||
|
call ConsumeInputEdges
|
||||||
|
mov keyScreenshot, 0
|
||||||
|
input_sample_done:
|
||||||
ret
|
ret
|
||||||
PollInput ENDP
|
PollInput ENDP
|
||||||
|
|
|
||||||
|
|
@ -584,6 +584,24 @@ UpdateDynamicLights ENDP
|
||||||
|
|
||||||
SamplePointLighting PROC uses ebx ecx edx esi edi,x:DWORD,y:DWORD,distance:DWORD,side:DWORD
|
SamplePointLighting PROC uses ebx ecx edx esi edi,x:DWORD,y:DWORD,distance:DWORD,side:DWORD
|
||||||
LOCAL forward:DWORD,lateral:DWORD,beam:DWORD,atten:DWORD,fog:DWORD
|
LOCAL forward:DWORD,lateral:DWORD,beam:DWORD,atten:DWORD,fog:DWORD
|
||||||
|
LOCAL originX:DWORD,originY:DWORD,originAngle:DWORD
|
||||||
|
; Camera-attached flashlight. Cinematics never move the gameplay body, but
|
||||||
|
; use the same lighting origin as the editor's camera-only preview packet.
|
||||||
|
mov eax,playerX
|
||||||
|
mov originX,eax
|
||||||
|
mov eax,playerY
|
||||||
|
mov originY,eax
|
||||||
|
mov eax,playerAngle
|
||||||
|
mov originAngle,eax
|
||||||
|
cmp isCutsceneActive,0
|
||||||
|
je spl_origin_ready
|
||||||
|
mov eax,csCameraX
|
||||||
|
mov originX,eax
|
||||||
|
mov eax,csCameraY
|
||||||
|
mov originY,eax
|
||||||
|
mov eax,csCameraAngle
|
||||||
|
mov originAngle,eax
|
||||||
|
spl_origin_ready:
|
||||||
mov eax,256
|
mov eax,256
|
||||||
mov lightRed,eax
|
mov lightRed,eax
|
||||||
mov lightGreen,eax
|
mov lightGreen,eax
|
||||||
|
|
@ -628,9 +646,9 @@ spl_next:
|
||||||
cmp isFlashlightOn,0
|
cmp isFlashlightOn,0
|
||||||
je spl_fog
|
je spl_fog
|
||||||
mov eax,x
|
mov eax,x
|
||||||
sub eax,playerX
|
sub eax,originX
|
||||||
mov ebx,y
|
mov ebx,y
|
||||||
sub ebx,playerY
|
sub ebx,originY
|
||||||
invoke LightDistance,eax,ebx
|
invoke LightDistance,eax,ebx
|
||||||
cmp eax,flashlightRadius
|
cmp eax,flashlightRadius
|
||||||
jae spl_fog
|
jae spl_fog
|
||||||
|
|
@ -641,27 +659,27 @@ spl_next:
|
||||||
xor edx,edx
|
xor edx,edx
|
||||||
div flashlightRadius
|
div flashlightRadius
|
||||||
mov atten,eax
|
mov atten,eax
|
||||||
mov ecx,playerAngle
|
mov ecx,originAngle
|
||||||
and ecx,511
|
and ecx,511
|
||||||
mov eax,x
|
mov eax,x
|
||||||
sub eax,playerX
|
sub eax,originX
|
||||||
imul [tableCos+ecx*4]
|
imul [tableCos+ecx*4]
|
||||||
shrd eax,edx,16
|
shrd eax,edx,16
|
||||||
mov forward,eax
|
mov forward,eax
|
||||||
mov eax,y
|
mov eax,y
|
||||||
sub eax,playerY
|
sub eax,originY
|
||||||
imul [tableSin+ecx*4]
|
imul [tableSin+ecx*4]
|
||||||
shrd eax,edx,16
|
shrd eax,edx,16
|
||||||
add forward,eax
|
add forward,eax
|
||||||
cmp forward,0
|
cmp forward,0
|
||||||
jle spl_fog
|
jle spl_fog
|
||||||
mov eax,y
|
mov eax,y
|
||||||
sub eax,playerY
|
sub eax,originY
|
||||||
imul [tableCos+ecx*4]
|
imul [tableCos+ecx*4]
|
||||||
shrd eax,edx,16
|
shrd eax,edx,16
|
||||||
mov lateral,eax
|
mov lateral,eax
|
||||||
mov eax,x
|
mov eax,x
|
||||||
sub eax,playerX
|
sub eax,originX
|
||||||
imul [tableSin+ecx*4]
|
imul [tableSin+ecx*4]
|
||||||
shrd eax,edx,16
|
shrd eax,edx,16
|
||||||
sub lateral,eax
|
sub lateral,eax
|
||||||
|
|
@ -685,7 +703,7 @@ spl_next:
|
||||||
xor edx,edx
|
xor edx,edx
|
||||||
div beam
|
div beam
|
||||||
mov atten,eax
|
mov atten,eax
|
||||||
invoke CastLightRay,playerX,playerY,x,y
|
invoke CastLightRay,originX,originY,x,y
|
||||||
test eax,eax
|
test eax,eax
|
||||||
jz spl_fog
|
jz spl_fog
|
||||||
invoke AddLightColor,00FFFFFFh,atten
|
invoke AddLightColor,00FFFFFFh,atten
|
||||||
|
|
@ -755,12 +773,12 @@ ComputeWallLightLevel PROC uses ebx ecx edx,screenX:DWORD,mapX:DWORD,mapY:DWORD,
|
||||||
mov eax,rawDist
|
mov eax,rawDist
|
||||||
imul rayDirX
|
imul rayDirX
|
||||||
shrd eax,edx,24
|
shrd eax,edx,24
|
||||||
add eax,playerX
|
add eax,viewX
|
||||||
mov x,eax
|
mov x,eax
|
||||||
mov eax,rawDist
|
mov eax,rawDist
|
||||||
imul rayDirY
|
imul rayDirY
|
||||||
shrd eax,edx,24
|
shrd eax,edx,24
|
||||||
add eax,playerY
|
add eax,viewY
|
||||||
mov y,eax
|
mov y,eax
|
||||||
cmp sideHit,0
|
cmp sideHit,0
|
||||||
jne cwl_y
|
jne cwl_y
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,14 @@ BakeMapLighting PROTO
|
||||||
InitLightingSystem PROTO
|
InitLightingSystem PROTO
|
||||||
ValidateLightingData PROTO :DWORD,:DWORD,:DWORD
|
ValidateLightingData PROTO :DWORD,:DWORD,:DWORD
|
||||||
CommitLightingData PROTO :DWORD,:DWORD,:DWORD,:DWORD
|
CommitLightingData PROTO :DWORD,:DWORD,:DWORD,:DWORD
|
||||||
|
ResetMovementFractions PROTO
|
||||||
|
CanOccupyPlayer PROTO :DWORD,:DWORD
|
||||||
|
|
||||||
; ------------------------------------------------------------------------------
|
; ------------------------------------------------------------------------------
|
||||||
; InitMap: Creates a rich test level with rooms, hallways, pillars & doors
|
; InitMap: Creates a rich test level with rooms, hallways, pillars & doors
|
||||||
; ------------------------------------------------------------------------------
|
; ------------------------------------------------------------------------------
|
||||||
InitMap PROC uses eax ecx edi
|
InitMap PROC uses eax ecx edi
|
||||||
|
call StopCutscene
|
||||||
; Clear map to empty
|
; Clear map to empty
|
||||||
mov edi, OFFSET gameMap
|
mov edi, OFFSET gameMap
|
||||||
mov ecx, MAP_SIZE
|
mov ecx, MAP_SIZE
|
||||||
|
|
@ -95,7 +98,11 @@ loop_wall2:
|
||||||
mov playerX, 160
|
mov playerX, 160
|
||||||
mov playerY, 160
|
mov playerY, 160
|
||||||
mov playerAngle, 0
|
mov playerAngle, 0
|
||||||
call StopCutscene
|
mov currentMapPath, 0
|
||||||
|
mov roomEntryX, 2
|
||||||
|
mov roomEntryY, 2
|
||||||
|
mov roomEntryAngle, 0
|
||||||
|
call ResetMovementFractions
|
||||||
mov cutsceneCount, 0
|
mov cutsceneCount, 0
|
||||||
mov cutsceneStringCount, 0
|
mov cutsceneStringCount, 0
|
||||||
mov cutsceneFlags, 0
|
mov cutsceneFlags, 0
|
||||||
|
|
@ -216,7 +223,28 @@ check_door_close:
|
||||||
cmp eax, CELL_DOOR_OPEN
|
cmp eax, CELL_DOOR_OPEN
|
||||||
jne check_portal_interact
|
jne check_portal_interact
|
||||||
|
|
||||||
; Close the door!
|
; Never close a door over any part of the player's collision body.
|
||||||
|
mov eax, playerX
|
||||||
|
sub eax, playerRadius
|
||||||
|
sar eax, CELL_SHIFT
|
||||||
|
cmp eax, targetX
|
||||||
|
jg close_clear_door
|
||||||
|
mov eax, playerX
|
||||||
|
add eax, playerRadius
|
||||||
|
sar eax, CELL_SHIFT
|
||||||
|
cmp eax, targetX
|
||||||
|
jl close_clear_door
|
||||||
|
mov eax, playerY
|
||||||
|
sub eax, playerRadius
|
||||||
|
sar eax, CELL_SHIFT
|
||||||
|
cmp eax, targetY
|
||||||
|
jg close_clear_door
|
||||||
|
mov eax, playerY
|
||||||
|
add eax, playerRadius
|
||||||
|
sar eax, CELL_SHIFT
|
||||||
|
cmp eax, targetY
|
||||||
|
jge no_door
|
||||||
|
close_clear_door:
|
||||||
invoke SetMapCell, targetX, targetY, CELL_DOOR
|
invoke SetMapCell, targetX, targetY, CELL_DOOR
|
||||||
invoke ShowGameToast, OFFSET szDoorClosedToast, 50, COLOR_YELLOW
|
invoke ShowGameToast, OFFSET szDoorClosedToast, 50, COLOR_YELLOW
|
||||||
ret
|
ret
|
||||||
|
|
@ -410,7 +438,7 @@ next_map_copied:
|
||||||
cmp fadeEffect, 0
|
cmp fadeEffect, 0
|
||||||
je switch_instant
|
je switch_instant
|
||||||
|
|
||||||
; Initiate smooth Fade Out (takes 16 frames ~ 0.26s)
|
; Initiate smooth Fade Out (up to 16 fixed ticks, ~0.26s).
|
||||||
mov fadeState, 1
|
mov fadeState, 1
|
||||||
mov fadeLevel, 1
|
mov fadeLevel, 1
|
||||||
ret
|
ret
|
||||||
|
|
@ -437,11 +465,11 @@ transition_loaded:
|
||||||
DoSublevelTransition ENDP
|
DoSublevelTransition ENDP
|
||||||
|
|
||||||
; ------------------------------------------------------------------------------
|
; ------------------------------------------------------------------------------
|
||||||
; ApplyScreenFade: Screen darkening post-processing and transition sequencer
|
; UpdateScreenFade: fixed-tick transition sequencer, never called from drawing.
|
||||||
; ------------------------------------------------------------------------------
|
; ------------------------------------------------------------------------------
|
||||||
ApplyScreenFade PROC uses ebx ecx edx esi edi
|
UpdateScreenFade PROC
|
||||||
cmp fadeState, 0
|
cmp fadeState, 0
|
||||||
je fade_done
|
je fade_tick_done
|
||||||
|
|
||||||
cmp fadeState, 1
|
cmp fadeState, 1
|
||||||
je do_fade_out
|
je do_fade_out
|
||||||
|
|
@ -449,31 +477,35 @@ ApplyScreenFade PROC uses ebx ecx edx esi edi
|
||||||
je do_switch_state
|
je do_switch_state
|
||||||
cmp fadeState, 3
|
cmp fadeState, 3
|
||||||
je do_fade_in
|
je do_fade_in
|
||||||
jmp fade_done
|
jmp fade_tick_done
|
||||||
|
|
||||||
do_fade_out:
|
do_fade_out:
|
||||||
inc fadeLevel
|
inc fadeLevel
|
||||||
cmp fadeLevel, 16
|
cmp fadeLevel, 16
|
||||||
jl apply_darkening
|
jl fade_tick_done
|
||||||
mov fadeLevel, 16
|
mov fadeLevel, 16
|
||||||
mov fadeState, 2
|
mov fadeState, 2
|
||||||
call DoSublevelTransition
|
call DoSublevelTransition
|
||||||
mov fadeState, 3
|
mov fadeState, 3
|
||||||
jmp apply_darkening
|
jmp fade_tick_done
|
||||||
|
|
||||||
do_switch_state:
|
do_switch_state:
|
||||||
call DoSublevelTransition
|
call DoSublevelTransition
|
||||||
mov fadeState, 3
|
mov fadeState, 3
|
||||||
jmp apply_darkening
|
jmp fade_tick_done
|
||||||
|
|
||||||
do_fade_in:
|
do_fade_in:
|
||||||
dec fadeLevel
|
dec fadeLevel
|
||||||
cmp fadeLevel, 0
|
cmp fadeLevel, 0
|
||||||
jg apply_darkening
|
jg fade_tick_done
|
||||||
mov fadeLevel, 0
|
mov fadeLevel, 0
|
||||||
mov fadeState, 0
|
mov fadeState, 0
|
||||||
jmp fade_done
|
fade_tick_done:
|
||||||
|
ret
|
||||||
|
UpdateScreenFade ENDP
|
||||||
|
|
||||||
|
; Pure framebuffer post-process. Repeated drawing never switches rooms.
|
||||||
|
ApplyScreenFade PROC uses ebx ecx edx esi edi
|
||||||
apply_darkening:
|
apply_darkening:
|
||||||
cmp fadeLevel, 0
|
cmp fadeLevel, 0
|
||||||
jle fade_done
|
jle fade_done
|
||||||
|
|
@ -965,6 +997,20 @@ commit_level:
|
||||||
mov playerY, eax
|
mov playerY, eax
|
||||||
mov eax, newAngle
|
mov eax, newAngle
|
||||||
mov playerAngle, eax
|
mov playerAngle, eax
|
||||||
|
mov eax, newX
|
||||||
|
mov roomEntryX, eax
|
||||||
|
mov eax, newY
|
||||||
|
mov roomEntryY, eax
|
||||||
|
mov eax, newAngle
|
||||||
|
mov roomEntryAngle, eax
|
||||||
|
lea esi, resolvedPath
|
||||||
|
mov edi, OFFSET currentMapPath
|
||||||
|
remember_room_path:
|
||||||
|
lodsb
|
||||||
|
stosb
|
||||||
|
test al, al
|
||||||
|
jnz remember_room_path
|
||||||
|
call ResetMovementFractions
|
||||||
mov mapErrorFrames, 0
|
mov mapErrorFrames, 0
|
||||||
invoke CommitLightingData,OFFSET stagedLightSettings,OFFSET stagedLightSources,numLights,OFFSET stagedLightRGB
|
invoke CommitLightingData,OFFSET stagedLightSettings,OFFSET stagedLightSources,numLights,OFFSET stagedLightRGB
|
||||||
mov eax, 1
|
mov eax, 1
|
||||||
|
|
@ -1040,3 +1086,44 @@ startup_map_ready:
|
||||||
pop eax
|
pop eax
|
||||||
ret
|
ret
|
||||||
LoadMapBinary ENDP
|
LoadMapBinary ENDP
|
||||||
|
|
||||||
|
; Reload the current room and the actual entry spawn, not the startup map.
|
||||||
|
; LoadSublevelBinary commits only on success, so failure preserves all gameplay.
|
||||||
|
RestartCurrentRoom PROC
|
||||||
|
cmp currentMapPath, 0
|
||||||
|
je restart_procedural
|
||||||
|
invoke LoadSublevelBinary, OFFSET currentMapPath, roomEntryX, roomEntryY, roomEntryAngle
|
||||||
|
test eax, eax
|
||||||
|
jz restart_failed
|
||||||
|
jmp restart_committed
|
||||||
|
restart_procedural:
|
||||||
|
call InitMap
|
||||||
|
restart_committed:
|
||||||
|
call InitEntities
|
||||||
|
call ResetMovementFractions
|
||||||
|
mov playerHealth, 100
|
||||||
|
mov playerAmmo, 50
|
||||||
|
mov playerScore, 0
|
||||||
|
mov playerKeys, 0
|
||||||
|
mov isPlayerDead, 0
|
||||||
|
mov isGamePaused, 0
|
||||||
|
mov gameDialogActive, 0
|
||||||
|
mov gameToastTimer, 0
|
||||||
|
mov playerAttackCooldown, 0
|
||||||
|
mov playerFlashTimer, 0
|
||||||
|
mov playerHitFlash, 0
|
||||||
|
mov fadeState, 0
|
||||||
|
mov fadeLevel, 0
|
||||||
|
mov mapErrorFrames, 0
|
||||||
|
mov pendingMapPath, 0
|
||||||
|
mov pendingSpawnX, 0
|
||||||
|
mov pendingSpawnY, 0
|
||||||
|
mov pendingSpawnAngle, 0
|
||||||
|
mov gameplayTickBlocked, 1
|
||||||
|
mov clockReset, 1
|
||||||
|
mov eax, 1
|
||||||
|
ret
|
||||||
|
restart_failed:
|
||||||
|
xor eax, eax
|
||||||
|
ret
|
||||||
|
RestartCurrentRoom ENDP
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
; ==============================================================================
|
; Fixed-tick player simulation. Wire coordinates remain raw world units.
|
||||||
; EBLAN ENGINE - Player Movement, Physics & Collision Handling
|
|
||||||
; ==============================================================================
|
|
||||||
|
|
||||||
.code
|
.code
|
||||||
|
StartCutscene PROTO
|
||||||
|
StopCutscene PROTO
|
||||||
|
UpdateCutscene PROTO
|
||||||
|
|
||||||
; Shared gameplay gate. Returns EAX != 0 when simulation must be frozen.
|
|
||||||
; UpdatePlayer owns the per-tick latch; renderer never changes this decision.
|
|
||||||
IsGameplayBlocked PROC
|
IsGameplayBlocked PROC
|
||||||
mov eax, gameplayTickBlocked
|
mov eax, gameplayTickBlocked
|
||||||
or eax, isPlayerDead
|
or eax, isPlayerDead
|
||||||
|
|
@ -17,232 +15,204 @@ IsGameplayBlocked PROC
|
||||||
ret
|
ret
|
||||||
IsGameplayBlocked ENDP
|
IsGameplayBlocked ENDP
|
||||||
|
|
||||||
; ------------------------------------------------------------------------------
|
ResetMovementFractions PROC
|
||||||
; UpdatePlayer: Updates rotation, computes movement vector & handles collisions
|
mov playerFractionX, 0
|
||||||
; ------------------------------------------------------------------------------
|
mov playerFractionY, 0
|
||||||
UpdatePlayer PROC uses ebx ecx edx esi edi
|
ret
|
||||||
LOCAL moveX:DWORD, moveY:DWORD
|
ResetMovementFractions ENDP
|
||||||
LOCAL speed:DWORD
|
|
||||||
LOCAL nextX:DWORD, nextY:DWORD
|
|
||||||
LOCAL checkGridX:DWORD, checkGridY:DWORD
|
|
||||||
|
|
||||||
|
; Test every overlapped cell, not only the leading edge's center. Portal cells
|
||||||
|
; block rays but deliberately remain walkable for players/camera handoff.
|
||||||
|
CanOccupyPlayer PROC uses ebx ecx edx esi edi, x:DWORD, y:DWORD
|
||||||
|
LOCAL minX:DWORD, maxX:DWORD, maxY:DWORD, row:DWORD
|
||||||
|
mov eax, x
|
||||||
|
sub eax, playerRadius
|
||||||
|
sar eax, CELL_SHIFT
|
||||||
|
mov minX, eax
|
||||||
|
mov eax, x
|
||||||
|
add eax, playerRadius
|
||||||
|
sar eax, CELL_SHIFT
|
||||||
|
mov maxX, eax
|
||||||
|
mov eax, y
|
||||||
|
sub eax, playerRadius
|
||||||
|
sar eax, CELL_SHIFT
|
||||||
|
mov row, eax
|
||||||
|
mov eax, y
|
||||||
|
add eax, playerRadius
|
||||||
|
sar eax, CELL_SHIFT
|
||||||
|
mov maxY, eax
|
||||||
|
body_row:
|
||||||
|
mov ebx, minX
|
||||||
|
body_cell:
|
||||||
|
invoke GetMapCell, ebx, row
|
||||||
|
cmp eax, CELL_PORTAL
|
||||||
|
je body_next
|
||||||
|
invoke IsSolidCell, eax
|
||||||
|
test eax, eax
|
||||||
|
jnz body_blocked
|
||||||
|
body_next:
|
||||||
|
inc ebx
|
||||||
|
cmp ebx, maxX
|
||||||
|
jle body_cell
|
||||||
|
inc row
|
||||||
|
mov eax, row
|
||||||
|
cmp eax, maxY
|
||||||
|
jle body_row
|
||||||
|
mov eax, 1
|
||||||
|
ret
|
||||||
|
body_blocked:
|
||||||
|
xor eax, eax
|
||||||
|
ret
|
||||||
|
CanOccupyPlayer ENDP
|
||||||
|
|
||||||
|
UpdatePlayer PROC uses ebx ecx edx esi edi
|
||||||
|
LOCAL forward:SDWORD, strafe:SDWORD, speed:DWORD
|
||||||
|
LOCAL moveX:SDWORD, moveY:SDWORD, steps:DWORD
|
||||||
mov gameplayTickBlocked, 1
|
mov gameplayTickBlocked, 1
|
||||||
cmp isGamePaused, 0
|
cmp isGamePaused, 0
|
||||||
jne update_player_done
|
jne player_done
|
||||||
cmp gameDialogActive, 0
|
cmp gameDialogActive, 0
|
||||||
jne update_player_done
|
jne player_done
|
||||||
cmp isPlayerDead, 0
|
cmp isPlayerDead, 0
|
||||||
jne update_player_done
|
jne player_done
|
||||||
; Freeze player movement and inputs during level transitions
|
|
||||||
cmp fadeState, 0
|
cmp fadeState, 0
|
||||||
jne update_player_done
|
jne player_done
|
||||||
|
|
||||||
cmp cutscenePending, 0
|
cmp cutscenePending, 0
|
||||||
je scene_manual_start
|
je @F
|
||||||
call StartCutscene
|
call StartCutscene
|
||||||
scene_manual_start:
|
@@:
|
||||||
cmp keyCutscene, 0
|
cmp keyCutscene, 0
|
||||||
je scene_check_active
|
je @F
|
||||||
cmp isCutsceneActive, 0
|
cmp isCutsceneActive, 0
|
||||||
jne scene_check_active
|
jne @F
|
||||||
call StartCutscene
|
call StartCutscene
|
||||||
scene_check_active:
|
@@:
|
||||||
cmp isCutsceneActive, 0
|
cmp isCutsceneActive, 0
|
||||||
je normal_player_update
|
je player_active
|
||||||
cmp keySkipScene, 0
|
cmp keySkipScene, 0
|
||||||
je scene_update_tick
|
je player_scene
|
||||||
call StopCutscene
|
call StopCutscene
|
||||||
ret
|
ret
|
||||||
scene_update_tick:
|
player_scene:
|
||||||
call UpdateCutscene
|
call UpdateCutscene
|
||||||
ret
|
ret
|
||||||
normal_player_update:
|
player_active:
|
||||||
mov gameplayTickBlocked, 0
|
mov gameplayTickBlocked, 0
|
||||||
mov moveX, 0
|
movzx eax, keyTurnRight
|
||||||
mov moveY, 0
|
movzx ebx, keyTurnLeft
|
||||||
|
sub eax, ebx
|
||||||
; 1. Handle Camera Rotation
|
imul eax, playerTurnSpeed
|
||||||
cmp keyTurnLeft, 0
|
add eax, playerAngle
|
||||||
je check_turn_right
|
|
||||||
mov eax, playerAngle
|
|
||||||
sub eax, playerTurnSpeed
|
|
||||||
and eax, ANGLE_MASK
|
and eax, ANGLE_MASK
|
||||||
mov playerAngle, eax
|
mov playerAngle, eax
|
||||||
|
|
||||||
check_turn_right:
|
|
||||||
cmp keyTurnRight, 0
|
|
||||||
je check_action
|
|
||||||
mov eax, playerAngle
|
|
||||||
add eax, playerTurnSpeed
|
|
||||||
and eax, ANGLE_MASK
|
|
||||||
mov playerAngle, eax
|
|
||||||
|
|
||||||
check_action:
|
|
||||||
cmp keyAction, 0
|
cmp keyAction, 0
|
||||||
je check_movement
|
je @F
|
||||||
call InteractDoor
|
call InteractDoor
|
||||||
cmp fadeState, 0
|
call IsGameplayBlocked
|
||||||
jne update_player_done
|
test eax, eax
|
||||||
cmp gameplayTickBlocked, 0
|
jnz player_done
|
||||||
jne update_player_done
|
@@:
|
||||||
|
movzx eax, keyForward
|
||||||
; 2. Determine Movement Speed
|
movzx ebx, keyBackward
|
||||||
check_movement:
|
sub eax, ebx
|
||||||
mov eax, playerMoveSpeed ; Base speed: raw units/frame
|
mov forward, eax
|
||||||
|
movzx eax, keyStrafeRight
|
||||||
|
movzx ebx, keyStrafeLeft
|
||||||
|
sub eax, ebx
|
||||||
|
mov strafe, eax
|
||||||
|
or eax, forward
|
||||||
|
jz player_done
|
||||||
|
mov eax, playerMoveSpeed
|
||||||
cmp keyRun, 0
|
cmp keyRun, 0
|
||||||
je @F
|
je @F
|
||||||
mov eax, playerRunSpeed ; Sprint speed
|
mov eax, playerRunSpeed
|
||||||
@@:
|
@@:
|
||||||
|
shl eax, 16
|
||||||
|
cmp forward, 0
|
||||||
|
je speed_ready
|
||||||
|
cmp strafe, 0
|
||||||
|
je speed_ready
|
||||||
|
; 1/sqrt(2), 16.16. Product may exceed 32 bits.
|
||||||
|
mov ebx, 46341
|
||||||
|
imul ebx
|
||||||
|
shrd eax, edx, 16
|
||||||
|
speed_ready:
|
||||||
mov speed, eax
|
mov speed, eax
|
||||||
|
mov ecx, playerAngle
|
||||||
; 3. Forward / Backward Vector
|
mov eax, [tableCos+ecx*4]
|
||||||
cmp keyForward, 0
|
imul eax, forward
|
||||||
je check_backward
|
mov ebx, [tableSin+ecx*4]
|
||||||
|
imul ebx, strafe
|
||||||
mov eax, playerAngle
|
sub eax, ebx
|
||||||
and eax, ANGLE_MASK
|
imul speed
|
||||||
; dx += cos(angle) * speed
|
mov ebx, FIXED_ONE
|
||||||
mov ebx, [tableCos + eax*4]
|
idiv ebx
|
||||||
imul ebx, speed
|
add eax, playerFractionX
|
||||||
sar ebx, 16
|
cdq
|
||||||
add moveX, ebx
|
mov ebx, FIXED_ONE
|
||||||
|
idiv ebx
|
||||||
; dy += sin(angle) * speed
|
mov moveX, eax
|
||||||
mov ecx, [tableSin + eax*4]
|
mov playerFractionX, edx
|
||||||
imul ecx, speed
|
mov eax, [tableSin+ecx*4]
|
||||||
sar ecx, 16
|
imul eax, forward
|
||||||
add moveY, ecx
|
mov ebx, [tableCos+ecx*4]
|
||||||
|
imul ebx, strafe
|
||||||
check_backward:
|
add eax, ebx
|
||||||
cmp keyBackward, 0
|
imul speed
|
||||||
je check_strafe_left
|
mov ebx, FIXED_ONE
|
||||||
|
idiv ebx
|
||||||
mov eax, playerAngle
|
add eax, playerFractionY
|
||||||
and eax, ANGLE_MASK
|
cdq
|
||||||
mov ebx, [tableCos + eax*4]
|
mov ebx, FIXED_ONE
|
||||||
imul ebx, speed
|
idiv ebx
|
||||||
sar ebx, 16
|
mov moveY, eax
|
||||||
sub moveX, ebx
|
mov playerFractionY, edx
|
||||||
|
; Unit substeps prevent tunneling even when scripts increase speed.
|
||||||
mov ecx, [tableSin + eax*4]
|
mov steps, 0
|
||||||
imul ecx, speed
|
player_step:
|
||||||
sar ecx, 16
|
|
||||||
sub moveY, ecx
|
|
||||||
|
|
||||||
; 4. Strafe Left / Right Vector (Angle +/- 90 degrees)
|
|
||||||
check_strafe_left:
|
|
||||||
cmp keyStrafeLeft, 0
|
|
||||||
je check_strafe_right
|
|
||||||
|
|
||||||
; Left vector is (angle - 128)
|
|
||||||
mov eax, playerAngle
|
|
||||||
sub eax, ANGLE_90
|
|
||||||
and eax, ANGLE_MASK
|
|
||||||
|
|
||||||
mov ebx, [tableCos + eax*4]
|
|
||||||
imul ebx, speed
|
|
||||||
sar ebx, 16
|
|
||||||
add moveX, ebx
|
|
||||||
|
|
||||||
mov ecx, [tableSin + eax*4]
|
|
||||||
imul ecx, speed
|
|
||||||
sar ecx, 16
|
|
||||||
add moveY, ecx
|
|
||||||
|
|
||||||
check_strafe_right:
|
|
||||||
cmp keyStrafeRight, 0
|
|
||||||
je apply_physics
|
|
||||||
|
|
||||||
; Right vector is (angle + 128)
|
|
||||||
mov eax, playerAngle
|
|
||||||
add eax, ANGLE_90
|
|
||||||
and eax, ANGLE_MASK
|
|
||||||
|
|
||||||
mov ebx, [tableCos + eax*4]
|
|
||||||
imul ebx, speed
|
|
||||||
sar ebx, 16
|
|
||||||
add moveX, ebx
|
|
||||||
|
|
||||||
mov ecx, [tableSin + eax*4]
|
|
||||||
imul ecx, speed
|
|
||||||
sar ecx, 16
|
|
||||||
add moveY, ecx
|
|
||||||
|
|
||||||
; 5. Apply Movement with Wall-Sliding Collision Detection
|
|
||||||
apply_physics:
|
|
||||||
mov eax, moveX
|
mov eax, moveX
|
||||||
or eax, moveY
|
or eax, moveY
|
||||||
jz update_player_done ; No movement input
|
jz player_done
|
||||||
|
inc steps
|
||||||
; --- Test X Movement ---
|
cmp steps, 64
|
||||||
mov eax, playerX
|
ja player_done
|
||||||
add eax, moveX
|
|
||||||
mov nextX, eax
|
|
||||||
|
|
||||||
; Buffer check (collision body radius)
|
|
||||||
mov ebx, nextX
|
|
||||||
cmp moveX, 0
|
cmp moveX, 0
|
||||||
jge @F
|
je step_y
|
||||||
mov eax, playerRadius
|
mov esi, 1
|
||||||
sub ebx, eax ; Moving left: check left edge
|
jg @F
|
||||||
jmp check_x_col
|
mov esi, -1
|
||||||
@@:
|
@@:
|
||||||
mov eax, playerRadius
|
|
||||||
add ebx, eax ; Moving right: check right edge
|
|
||||||
check_x_col:
|
|
||||||
sar ebx, 6 ; divide by CELL_SIZE (64)
|
|
||||||
mov checkGridX, ebx
|
|
||||||
|
|
||||||
mov ecx, playerY
|
|
||||||
sar ecx, 6
|
|
||||||
mov checkGridY, ecx
|
|
||||||
|
|
||||||
invoke GetMapCell, checkGridX, checkGridY
|
|
||||||
cmp eax, CELL_PORTAL
|
|
||||||
je allow_portal_x
|
|
||||||
invoke IsSolidCell, eax
|
|
||||||
test eax, eax
|
|
||||||
jnz skip_x_move ; Collision on X: slide along wall!
|
|
||||||
allow_portal_x:
|
|
||||||
|
|
||||||
mov eax, nextX
|
|
||||||
mov playerX, eax
|
|
||||||
|
|
||||||
skip_x_move:
|
|
||||||
; --- Test Y Movement ---
|
|
||||||
mov eax, playerY
|
|
||||||
add eax, moveY
|
|
||||||
mov nextY, eax
|
|
||||||
|
|
||||||
mov ecx, nextY
|
|
||||||
cmp moveY, 0
|
|
||||||
jge @F
|
|
||||||
mov eax, playerRadius
|
|
||||||
sub ecx, eax
|
|
||||||
jmp check_y_col
|
|
||||||
@@:
|
|
||||||
mov eax, playerRadius
|
|
||||||
add ecx, eax
|
|
||||||
check_y_col:
|
|
||||||
sar ecx, 6
|
|
||||||
mov checkGridY, ecx
|
|
||||||
|
|
||||||
mov ebx, playerX
|
mov ebx, playerX
|
||||||
sar ebx, 6
|
add ebx, esi
|
||||||
mov checkGridX, ebx
|
invoke CanOccupyPlayer, ebx, playerY
|
||||||
|
|
||||||
invoke GetMapCell, checkGridX, checkGridY
|
|
||||||
cmp eax, CELL_PORTAL
|
|
||||||
je allow_portal_y
|
|
||||||
invoke IsSolidCell, eax
|
|
||||||
test eax, eax
|
test eax, eax
|
||||||
jnz update_player_done ; Collision on Y
|
jz blocked_x
|
||||||
allow_portal_y:
|
mov playerX, ebx
|
||||||
|
sub moveX, esi
|
||||||
mov eax, nextY
|
jmp step_y
|
||||||
mov playerY, eax
|
blocked_x:
|
||||||
|
mov moveX, 0
|
||||||
update_player_done:
|
mov playerFractionX, 0
|
||||||
|
step_y:
|
||||||
|
cmp moveY, 0
|
||||||
|
je player_step
|
||||||
|
mov esi, 1
|
||||||
|
jg @F
|
||||||
|
mov esi, -1
|
||||||
|
@@:
|
||||||
|
mov ebx, playerY
|
||||||
|
add ebx, esi
|
||||||
|
invoke CanOccupyPlayer, playerX, ebx
|
||||||
|
test eax, eax
|
||||||
|
jz blocked_y
|
||||||
|
mov playerY, ebx
|
||||||
|
sub moveY, esi
|
||||||
|
jmp player_step
|
||||||
|
blocked_y:
|
||||||
|
mov moveY, 0
|
||||||
|
mov playerFractionY, 0
|
||||||
|
jmp player_step
|
||||||
|
player_done:
|
||||||
call IsGameplayBlocked
|
call IsGameplayBlocked
|
||||||
test eax, eax
|
test eax, eax
|
||||||
jnz @F
|
jnz @F
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,25 @@
|
||||||
RenderSprites PROTO :DWORD
|
RenderSprites PROTO :DWORD
|
||||||
RenderGlass PROTO
|
RenderGlass PROTO
|
||||||
|
|
||||||
|
SelectRenderCamera PROC
|
||||||
|
mov eax, playerX
|
||||||
|
mov viewX, eax
|
||||||
|
mov eax, playerY
|
||||||
|
mov viewY, eax
|
||||||
|
mov eax, playerAngle
|
||||||
|
mov viewAngle, eax
|
||||||
|
cmp isCutsceneActive, 0
|
||||||
|
je render_camera_ready
|
||||||
|
mov eax, csCameraX
|
||||||
|
mov viewX, eax
|
||||||
|
mov eax, csCameraY
|
||||||
|
mov viewY, eax
|
||||||
|
mov eax, csCameraAngle
|
||||||
|
mov viewAngle, eax
|
||||||
|
render_camera_ready:
|
||||||
|
ret
|
||||||
|
SelectRenderCamera ENDP
|
||||||
|
|
||||||
; ------------------------------------------------------------------------------
|
; ------------------------------------------------------------------------------
|
||||||
; Render3DScene: Full frame 2.5D raycasting pass (Floor, Ceiling, Walls, Sprites)
|
; Render3DScene: Full frame 2.5D raycasting pass (Floor, Ceiling, Walls, Sprites)
|
||||||
; ------------------------------------------------------------------------------
|
; ------------------------------------------------------------------------------
|
||||||
|
|
@ -41,7 +60,8 @@ Render3DScene PROC uses ebx ecx edx esi edi
|
||||||
; --------------------------------------------------------------------------
|
; --------------------------------------------------------------------------
|
||||||
; 1. Fast Background Clear: Ceiling (Top Half) and Floor (Bottom Half)
|
; 1. Fast Background Clear: Ceiling (Top Half) and Floor (Bottom Half)
|
||||||
; --------------------------------------------------------------------------
|
; --------------------------------------------------------------------------
|
||||||
invoke SamplePointLighting,playerX,playerY,0,0
|
call SelectRenderCamera
|
||||||
|
invoke SamplePointLighting,viewX,viewY,0,0
|
||||||
mov edi, OFFSET screenBuffer
|
mov edi, OFFSET screenBuffer
|
||||||
mov edx,mapCeilingColor
|
mov edx,mapCeilingColor
|
||||||
call ShadeLightPixel
|
call ShadeLightPixel
|
||||||
|
|
@ -78,7 +98,7 @@ ray_column_loop:
|
||||||
; Lookup Tangential Ray Angle Offset (flat plane perspective correction)
|
; Lookup Tangential Ray Angle Offset (flat plane perspective correction)
|
||||||
mov eax, screenX
|
mov eax, screenX
|
||||||
mov edx, [tableRayAngleOffset + eax*4]
|
mov edx, [tableRayAngleOffset + eax*4]
|
||||||
add edx, playerAngle
|
add edx, viewAngle
|
||||||
and edx, ANGLE_MASK
|
and edx, ANGLE_MASK
|
||||||
mov rayAngle, edx
|
mov rayAngle, edx
|
||||||
mov eax, edx
|
mov eax, edx
|
||||||
|
|
@ -90,11 +110,11 @@ ray_column_loop:
|
||||||
mov rayDirY, edx
|
mov rayDirY, edx
|
||||||
|
|
||||||
; Current Grid Cell
|
; Current Grid Cell
|
||||||
mov eax, playerX
|
mov eax, viewX
|
||||||
sar eax, 6
|
sar eax, 6
|
||||||
mov mapX, eax
|
mov mapX, eax
|
||||||
|
|
||||||
mov eax, playerY
|
mov eax, viewY
|
||||||
sar eax, 6
|
sar eax, 6
|
||||||
mov mapY, eax
|
mov mapY, eax
|
||||||
|
|
||||||
|
|
@ -145,7 +165,7 @@ nonzero_step_x:
|
||||||
; sideDistX = (playerX - (mapX * 64)) * deltaDistX / 64
|
; sideDistX = (playerX - (mapX * 64)) * deltaDistX / 64
|
||||||
mov eax, mapX
|
mov eax, mapX
|
||||||
shl eax, 6
|
shl eax, 6
|
||||||
mov edx, playerX
|
mov edx, viewX
|
||||||
sub edx, eax
|
sub edx, eax
|
||||||
mov eax, edx
|
mov eax, edx
|
||||||
imul deltaDistX
|
imul deltaDistX
|
||||||
|
|
@ -159,7 +179,7 @@ ray_dir_x_pos:
|
||||||
mov eax, mapX
|
mov eax, mapX
|
||||||
inc eax
|
inc eax
|
||||||
shl eax, 6
|
shl eax, 6
|
||||||
sub eax, playerX
|
sub eax, viewX
|
||||||
imul deltaDistX
|
imul deltaDistX
|
||||||
shrd eax, edx, 6
|
shrd eax, edx, 6
|
||||||
mov sideDistX, eax
|
mov sideDistX, eax
|
||||||
|
|
@ -176,7 +196,7 @@ nonzero_step_y:
|
||||||
mov stepY, -1
|
mov stepY, -1
|
||||||
mov eax, mapY
|
mov eax, mapY
|
||||||
shl eax, 6
|
shl eax, 6
|
||||||
mov edx, playerY
|
mov edx, viewY
|
||||||
sub edx, eax
|
sub edx, eax
|
||||||
mov eax, edx
|
mov eax, edx
|
||||||
imul deltaDistY
|
imul deltaDistY
|
||||||
|
|
@ -189,7 +209,7 @@ ray_dir_y_pos:
|
||||||
mov eax, mapY
|
mov eax, mapY
|
||||||
inc eax
|
inc eax
|
||||||
shl eax, 6
|
shl eax, 6
|
||||||
sub eax, playerY
|
sub eax, viewY
|
||||||
imul deltaDistY
|
imul deltaDistY
|
||||||
shrd eax, edx, 6
|
shrd eax, edx, 6
|
||||||
mov sideDistY, eax
|
mov sideDistY, eax
|
||||||
|
|
@ -297,7 +317,7 @@ glass_apply_fisheye:
|
||||||
mov ebx, rayDirY
|
mov ebx, rayDirY
|
||||||
imul ebx
|
imul ebx
|
||||||
shrd eax, edx, 8
|
shrd eax, edx, 8
|
||||||
mov ebx, playerY
|
mov ebx, viewY
|
||||||
shl ebx, 16
|
shl ebx, 16
|
||||||
add eax, ebx
|
add eax, ebx
|
||||||
jmp glass_compute_tex_x
|
jmp glass_compute_tex_x
|
||||||
|
|
@ -306,7 +326,7 @@ glass_tex_hit_y:
|
||||||
mov ebx, rayDirX
|
mov ebx, rayDirX
|
||||||
imul ebx
|
imul ebx
|
||||||
shrd eax, edx, 8
|
shrd eax, edx, 8
|
||||||
mov ebx, playerX
|
mov ebx, viewX
|
||||||
shl ebx, 16
|
shl ebx, 16
|
||||||
add eax, ebx
|
add eax, ebx
|
||||||
glass_compute_tex_x:
|
glass_compute_tex_x:
|
||||||
|
|
@ -418,7 +438,7 @@ rounded_distance_ready:
|
||||||
mov ebx, rayDirY
|
mov ebx, rayDirY
|
||||||
imul ebx
|
imul ebx
|
||||||
shrd eax, edx, 8 ; (24.8 * 16.16) -> 16.16
|
shrd eax, edx, 8 ; (24.8 * 16.16) -> 16.16
|
||||||
mov ebx, playerY
|
mov ebx, viewY
|
||||||
shl ebx, 16
|
shl ebx, 16
|
||||||
add eax, ebx
|
add eax, ebx
|
||||||
jmp compute_tex_x
|
jmp compute_tex_x
|
||||||
|
|
@ -429,7 +449,7 @@ tex_hit_y:
|
||||||
mov ebx, rayDirX
|
mov ebx, rayDirX
|
||||||
imul ebx
|
imul ebx
|
||||||
shrd eax, edx, 8
|
shrd eax, edx, 8
|
||||||
mov ebx, playerX
|
mov ebx, viewX
|
||||||
shl ebx, 16
|
shl ebx, 16
|
||||||
add eax, ebx
|
add eax, ebx
|
||||||
|
|
||||||
|
|
@ -629,7 +649,7 @@ RenderSprites PROC uses ebx ecx edx esi edi passFilter:DWORD
|
||||||
|
|
||||||
; Camera basis for -playerAngle (rotate world into camera space)
|
; Camera basis for -playerAngle (rotate world into camera space)
|
||||||
mov eax, ANGLE_512
|
mov eax, ANGLE_512
|
||||||
sub eax, playerAngle
|
sub eax, viewAngle
|
||||||
and eax, ANGLE_MASK
|
and eax, ANGLE_MASK
|
||||||
mov ecx, [tableCos + eax*4]
|
mov ecx, [tableCos + eax*4]
|
||||||
mov camCos, ecx
|
mov camCos, ecx
|
||||||
|
|
@ -650,12 +670,12 @@ dist_loop:
|
||||||
add ebx, OFFSET spriteList
|
add ebx, OFFSET spriteList
|
||||||
|
|
||||||
mov eax, [ebx].SpriteStruct.x
|
mov eax, [ebx].SpriteStruct.x
|
||||||
sub eax, playerX ; dx
|
sub eax, viewX ; dx
|
||||||
imul eax, eax
|
imul eax, eax
|
||||||
mov ecx, eax
|
mov ecx, eax
|
||||||
|
|
||||||
mov eax, [ebx].SpriteStruct.y
|
mov eax, [ebx].SpriteStruct.y
|
||||||
sub eax, playerY ; dy
|
sub eax, viewY ; dy
|
||||||
imul eax, eax
|
imul eax, eax
|
||||||
add eax, ecx
|
add eax, ecx
|
||||||
mov [ebx].SpriteStruct.dist, eax
|
mov [ebx].SpriteStruct.dist, eax
|
||||||
|
|
@ -739,11 +759,11 @@ sprite_loop:
|
||||||
|
|
||||||
; Sprite position relative to player (raw units)
|
; Sprite position relative to player (raw units)
|
||||||
mov eax, [ebx].SpriteStruct.x
|
mov eax, [ebx].SpriteStruct.x
|
||||||
sub eax, playerX
|
sub eax, viewX
|
||||||
mov spriteX, eax
|
mov spriteX, eax
|
||||||
|
|
||||||
mov eax, [ebx].SpriteStruct.y
|
mov eax, [ebx].SpriteStruct.y
|
||||||
sub eax, playerY
|
sub eax, viewY
|
||||||
mov spriteY, eax
|
mov spriteY, eax
|
||||||
|
|
||||||
; Transform to camera space:
|
; Transform to camera space:
|
||||||
|
|
|
||||||
253
include/engine_simulation.inc
Normal file
253
include/engine_simulation.inc
Normal file
|
|
@ -0,0 +1,253 @@
|
||||||
|
; Fixed 60 Hz simulation and QPC scheduler. No presentation or keyboard APIs in
|
||||||
|
; Engine_Tick. AdvanceSimulation accepts elapsed QPC counts for deterministic tests.
|
||||||
|
.code
|
||||||
|
QueryPerformanceCounter PROTO :DWORD
|
||||||
|
QueryPerformanceFrequency PROTO :DWORD
|
||||||
|
|
||||||
|
GetSimulationState PROC
|
||||||
|
mov eax, SIM_UNFOCUSED
|
||||||
|
cmp inputFocused, 0
|
||||||
|
je state_ready
|
||||||
|
mov eax, SIM_PAUSED
|
||||||
|
cmp isGamePaused, 0
|
||||||
|
jne state_ready
|
||||||
|
mov eax, SIM_DIALOG
|
||||||
|
cmp gameDialogActive, 0
|
||||||
|
jne state_ready
|
||||||
|
mov eax, SIM_TRANSITION
|
||||||
|
cmp fadeState, 0
|
||||||
|
jne state_ready
|
||||||
|
mov eax, SIM_DEAD
|
||||||
|
cmp isPlayerDead, 0
|
||||||
|
jne state_ready
|
||||||
|
mov eax, SIM_CUTSCENE
|
||||||
|
cmp isCutsceneActive, 0
|
||||||
|
jne state_ready
|
||||||
|
cmp cutscenePending, 0
|
||||||
|
jne state_ready
|
||||||
|
mov eax, SIM_PLAYING
|
||||||
|
state_ready:
|
||||||
|
ret
|
||||||
|
GetSimulationState ENDP
|
||||||
|
|
||||||
|
; UI edges execute on ticks, never during polling or drawing.
|
||||||
|
ApplyInputCommands PROC
|
||||||
|
cmp keyPause, 0
|
||||||
|
je command_menu
|
||||||
|
xor isGamePaused, 1
|
||||||
|
mov clockReset, 1
|
||||||
|
command_menu:
|
||||||
|
cmp isGamePaused, 0
|
||||||
|
je command_hud
|
||||||
|
cmp keyMenu1, 0
|
||||||
|
je @F
|
||||||
|
mov isGamePaused, 0
|
||||||
|
mov clockReset, 1
|
||||||
|
@@:
|
||||||
|
cmp keyMenu2, 0
|
||||||
|
je @F
|
||||||
|
xor enableWeapons, 1
|
||||||
|
mov eax, enableWeapons
|
||||||
|
mov gameMode, eax
|
||||||
|
mov showCrosshair, eax
|
||||||
|
mov playerWeapon, WEAPON_NONE
|
||||||
|
test eax, eax
|
||||||
|
jz @F
|
||||||
|
mov playerWeapon, WEAPON_PISTOL
|
||||||
|
@@:
|
||||||
|
cmp keyMenu3, 0
|
||||||
|
je @F
|
||||||
|
xor hudStyle, 1
|
||||||
|
@@:
|
||||||
|
cmp keyMenu4, 0
|
||||||
|
je @F
|
||||||
|
call RestartCurrentRoom
|
||||||
|
; A failed reload stays paused with all room/gameplay state intact.
|
||||||
|
@@:
|
||||||
|
cmp keyMenu5, 0
|
||||||
|
je command_done
|
||||||
|
mov isRunning, 0
|
||||||
|
ret
|
||||||
|
command_hud:
|
||||||
|
cmp keyMap, 0
|
||||||
|
je @F
|
||||||
|
xor keyShowMap, 1
|
||||||
|
@@:
|
||||||
|
cmp keyHud, 0
|
||||||
|
je @F
|
||||||
|
xor hudStyle, 1
|
||||||
|
@@:
|
||||||
|
call GetSimulationState
|
||||||
|
cmp eax, SIM_PLAYING
|
||||||
|
jne command_done
|
||||||
|
cmp keyFlashlight, 0
|
||||||
|
je @F
|
||||||
|
xor isFlashlightOn, 1
|
||||||
|
@@:
|
||||||
|
cmp keyLightMode, 0
|
||||||
|
je @F
|
||||||
|
inc lightingMode
|
||||||
|
cmp lightingMode, 3
|
||||||
|
jb @F
|
||||||
|
mov lightingMode, 0
|
||||||
|
@@:
|
||||||
|
cmp enableWeapons, 0
|
||||||
|
je command_done
|
||||||
|
cmp gameMode, GAME_MODE_ACTION
|
||||||
|
jne command_done
|
||||||
|
cmp keyWeapon1, 0
|
||||||
|
je @F
|
||||||
|
mov playerWeapon, WEAPON_KNIFE
|
||||||
|
@@:
|
||||||
|
cmp keyWeapon2, 0
|
||||||
|
je @F
|
||||||
|
mov playerWeapon, WEAPON_PISTOL
|
||||||
|
@@:
|
||||||
|
cmp keyWeapon3, 0
|
||||||
|
je command_done
|
||||||
|
mov playerWeapon, WEAPON_SHOTGUN
|
||||||
|
command_done:
|
||||||
|
ret
|
||||||
|
ApplyInputCommands ENDP
|
||||||
|
|
||||||
|
Engine_Tick PROC
|
||||||
|
inc simulationTicks
|
||||||
|
mov gameplayTickBlocked, 1
|
||||||
|
call GetSimulationState
|
||||||
|
mov simulationState, eax
|
||||||
|
cmp eax, SIM_UNFOCUSED
|
||||||
|
je tick_done
|
||||||
|
call ApplyInputCommands
|
||||||
|
cmp clockReset, 0
|
||||||
|
jne tick_done
|
||||||
|
call GetSimulationState
|
||||||
|
mov simulationState, eax
|
||||||
|
cmp eax, SIM_PAUSED
|
||||||
|
je tick_done
|
||||||
|
cmp eax, SIM_DIALOG
|
||||||
|
je tick_dialog
|
||||||
|
cmp eax, SIM_TRANSITION
|
||||||
|
je tick_transition
|
||||||
|
cmp eax, SIM_DEAD
|
||||||
|
je tick_dead
|
||||||
|
cmp mapErrorFrames, 0
|
||||||
|
je @F
|
||||||
|
dec mapErrorFrames
|
||||||
|
@@:
|
||||||
|
call UpdatePlayer
|
||||||
|
call IsGameplayBlocked
|
||||||
|
test eax, eax
|
||||||
|
jnz tick_cinematic
|
||||||
|
call UpdateEntities
|
||||||
|
call IsGameplayBlocked
|
||||||
|
test eax, eax
|
||||||
|
jnz tick_done
|
||||||
|
call UpdateCombat
|
||||||
|
call IsGameplayBlocked
|
||||||
|
test eax, eax
|
||||||
|
jnz tick_done
|
||||||
|
call Game_OnTick
|
||||||
|
call IsGameplayBlocked
|
||||||
|
test eax, eax
|
||||||
|
jnz tick_done
|
||||||
|
call UpdateDynamicLights
|
||||||
|
cmp gameToastTimer, 0
|
||||||
|
jle tick_done
|
||||||
|
dec gameToastTimer
|
||||||
|
jmp tick_done
|
||||||
|
tick_cinematic:
|
||||||
|
cmp isCutsceneActive, 0
|
||||||
|
je tick_done
|
||||||
|
call UpdateDynamicLights
|
||||||
|
jmp tick_done
|
||||||
|
tick_dialog:
|
||||||
|
mov al, keyAction
|
||||||
|
or al, keyDismiss
|
||||||
|
or al, keyAttack
|
||||||
|
jz tick_done
|
||||||
|
call CloseGameDialog
|
||||||
|
mov clockReset, 1
|
||||||
|
jmp tick_done
|
||||||
|
tick_transition:
|
||||||
|
call UpdateScreenFade
|
||||||
|
jmp tick_done
|
||||||
|
tick_dead:
|
||||||
|
cmp keyRestart, 0
|
||||||
|
je tick_done
|
||||||
|
call RestartCurrentRoom
|
||||||
|
tick_done:
|
||||||
|
call ConsumeInputEdges
|
||||||
|
ret
|
||||||
|
Engine_Tick ENDP
|
||||||
|
|
||||||
|
InitSimulationClock PROC
|
||||||
|
invoke QueryPerformanceFrequency, OFFSET qpcFrequency
|
||||||
|
invoke QueryPerformanceCounter, OFFSET qpcPrevious
|
||||||
|
mov DWORD PTR tickAccumulator, 0
|
||||||
|
mov DWORD PTR tickAccumulator+4, 0
|
||||||
|
mov clockReset, 0
|
||||||
|
ret
|
||||||
|
InitSimulationClock ENDP
|
||||||
|
|
||||||
|
AdvanceSimulation PROC uses ebx esi edi, deltaLow:DWORD, deltaHigh:DWORD
|
||||||
|
; Drop elapsed stalls over 250ms, backwards time, and explicit pause/focus
|
||||||
|
; debt. Bound catch-up to 8 ticks even on an overloaded machine.
|
||||||
|
cmp clockReset, 0
|
||||||
|
jne clock_discard
|
||||||
|
mov eax, DWORD PTR qpcFrequency
|
||||||
|
mov edx, DWORD PTR qpcFrequency+4
|
||||||
|
shrd eax, edx, 2
|
||||||
|
shr edx, 2
|
||||||
|
cmp deltaHigh, edx
|
||||||
|
ja clock_discard
|
||||||
|
jb clock_add
|
||||||
|
cmp deltaLow, eax
|
||||||
|
ja clock_discard
|
||||||
|
clock_add:
|
||||||
|
mov eax, deltaLow
|
||||||
|
mov ebx, 60
|
||||||
|
mul ebx
|
||||||
|
imul ebx, deltaHigh
|
||||||
|
add edx, ebx
|
||||||
|
add DWORD PTR tickAccumulator, eax
|
||||||
|
adc DWORD PTR tickAccumulator+4, edx
|
||||||
|
mov esi, 8
|
||||||
|
clock_tick:
|
||||||
|
mov edx, DWORD PTR tickAccumulator+4
|
||||||
|
cmp edx, DWORD PTR qpcFrequency+4
|
||||||
|
jb clock_done
|
||||||
|
ja clock_step
|
||||||
|
mov eax, DWORD PTR tickAccumulator
|
||||||
|
cmp eax, DWORD PTR qpcFrequency
|
||||||
|
jb clock_done
|
||||||
|
clock_step:
|
||||||
|
mov eax, DWORD PTR qpcFrequency
|
||||||
|
mov edx, DWORD PTR qpcFrequency+4
|
||||||
|
sub DWORD PTR tickAccumulator, eax
|
||||||
|
sbb DWORD PTR tickAccumulator+4, edx
|
||||||
|
call Engine_Tick
|
||||||
|
cmp clockReset, 0
|
||||||
|
jne clock_discard
|
||||||
|
dec esi
|
||||||
|
jnz clock_tick
|
||||||
|
clock_discard:
|
||||||
|
mov DWORD PTR tickAccumulator, 0
|
||||||
|
mov DWORD PTR tickAccumulator+4, 0
|
||||||
|
mov clockReset, 0
|
||||||
|
clock_done:
|
||||||
|
ret
|
||||||
|
AdvanceSimulation ENDP
|
||||||
|
|
||||||
|
PumpSimulationClock PROC uses ebx
|
||||||
|
invoke QueryPerformanceCounter, OFFSET qpcNow
|
||||||
|
mov eax, DWORD PTR qpcNow
|
||||||
|
mov edx, DWORD PTR qpcNow+4
|
||||||
|
sub eax, DWORD PTR qpcPrevious
|
||||||
|
sbb edx, DWORD PTR qpcPrevious+4
|
||||||
|
mov ebx, DWORD PTR qpcNow
|
||||||
|
mov DWORD PTR qpcPrevious, ebx
|
||||||
|
mov ebx, DWORD PTR qpcNow+4
|
||||||
|
mov DWORD PTR qpcPrevious+4, ebx
|
||||||
|
invoke AdvanceSimulation, eax, edx
|
||||||
|
ret
|
||||||
|
PumpSimulationClock ENDP
|
||||||
54
main.asm
54
main.asm
|
|
@ -118,6 +118,7 @@ include include/engine_render2d.inc
|
||||||
include include/engine_combat.inc
|
include include/engine_combat.inc
|
||||||
include include/engine_gameplay.inc
|
include include/engine_gameplay.inc
|
||||||
include include/engine_cutscene.inc
|
include include/engine_cutscene.inc
|
||||||
|
include include/engine_simulation.inc
|
||||||
include include/engine_preview.inc
|
include include/engine_preview.inc
|
||||||
|
|
||||||
.code
|
.code
|
||||||
|
|
@ -207,8 +208,8 @@ Engine_Init PROC uses ebx esi edi
|
||||||
mov hEngineDC, eax
|
mov hEngineDC, eax
|
||||||
|
|
||||||
invoke GetTickCount
|
invoke GetTickCount
|
||||||
mov lastTickCount, eax
|
|
||||||
mov fpsTimer, eax
|
mov fpsTimer, eax
|
||||||
|
call InitSimulationClock
|
||||||
|
|
||||||
ret
|
ret
|
||||||
Engine_Init ENDP
|
Engine_Init ENDP
|
||||||
|
|
@ -275,27 +276,9 @@ hud_no_map_error:
|
||||||
Engine_RenderHUD ENDP
|
Engine_RenderHUD ENDP
|
||||||
|
|
||||||
; ------------------------------------------------------------------------------
|
; ------------------------------------------------------------------------------
|
||||||
; Engine_Frame: Updates single tick and presents frame buffer to window
|
; Engine_Frame: draws/presents only; simulation runs independently at 60 Hz.
|
||||||
; ------------------------------------------------------------------------------
|
; ------------------------------------------------------------------------------
|
||||||
Engine_Frame PROC uses ebx esi edi
|
Engine_Frame PROC uses ebx esi edi
|
||||||
; 1. Input & Physics Update
|
|
||||||
cmp mapErrorFrames, 0
|
|
||||||
je no_error_tick
|
|
||||||
dec mapErrorFrames
|
|
||||||
no_error_tick:
|
|
||||||
call PollInput
|
|
||||||
cmp isGamePaused, 0
|
|
||||||
jne @F
|
|
||||||
cmp gameDialogActive, 0
|
|
||||||
jne skip_movement_in_dialog
|
|
||||||
call UpdatePlayer
|
|
||||||
call UpdateEntities
|
|
||||||
call UpdateCombat
|
|
||||||
call UpdateDynamicLights
|
|
||||||
skip_movement_in_dialog:
|
|
||||||
call Game_OnTick
|
|
||||||
@@:
|
|
||||||
|
|
||||||
; 2. 3D Raycasting & 2D HUD Render
|
; 2. 3D Raycasting & 2D HUD Render
|
||||||
call Render3DScene
|
call Render3DScene
|
||||||
call Engine_RenderHUD
|
call Engine_RenderHUD
|
||||||
|
|
@ -339,16 +322,6 @@ skip_movement_in_dialog:
|
||||||
mov fpsTimer, eax
|
mov fpsTimer, eax
|
||||||
|
|
||||||
frame_done:
|
frame_done:
|
||||||
; Reset one-frame input triggers
|
|
||||||
mov keyForward, 0
|
|
||||||
mov keyBackward, 0
|
|
||||||
mov keyStrafeLeft, 0
|
|
||||||
mov keyStrafeRight, 0
|
|
||||||
mov keyTurnLeft, 0
|
|
||||||
mov keyTurnRight, 0
|
|
||||||
mov keyAction, 0
|
|
||||||
mov keyAttack, 0
|
|
||||||
mov keyRestart, 0
|
|
||||||
ret
|
ret
|
||||||
Engine_Frame ENDP
|
Engine_Frame ENDP
|
||||||
|
|
||||||
|
|
@ -488,7 +461,6 @@ ParseStartupMap ENDP
|
||||||
; ------------------------------------------------------------------------------
|
; ------------------------------------------------------------------------------
|
||||||
main PROC
|
main PROC
|
||||||
LOCAL msg:MSG
|
LOCAL msg:MSG
|
||||||
LOCAL curTick:DWORD, elapsed:DWORD
|
|
||||||
|
|
||||||
call LoadProjectSettings
|
call LoadProjectSettings
|
||||||
call ParseStartupMap
|
call ParseStartupMap
|
||||||
|
|
@ -513,24 +485,10 @@ main_loop:
|
||||||
jmp @B
|
jmp @B
|
||||||
|
|
||||||
process_game_tick:
|
process_game_tick:
|
||||||
; Frame Rate Limiter (~60 FPS)
|
call PollInput
|
||||||
invoke GetTickCount
|
call PumpSimulationClock
|
||||||
mov curTick, eax
|
|
||||||
sub eax, lastTickCount
|
|
||||||
mov elapsed, eax
|
|
||||||
|
|
||||||
mov eax, targetFrameTimeMs
|
|
||||||
cmp elapsed, eax
|
|
||||||
jge do_tick
|
|
||||||
|
|
||||||
invoke Sleep, 1
|
|
||||||
jmp main_loop
|
|
||||||
|
|
||||||
do_tick:
|
|
||||||
mov eax, curTick
|
|
||||||
mov lastTickCount, eax
|
|
||||||
|
|
||||||
call Engine_Frame
|
call Engine_Frame
|
||||||
|
invoke Sleep, 1
|
||||||
jmp main_loop
|
jmp main_loop
|
||||||
|
|
||||||
exit_loop:
|
exit_loop:
|
||||||
|
|
|
||||||
BIN
main.obj
BIN
main.obj
Binary file not shown.
|
|
@ -48,11 +48,11 @@ scene_trace_tick:
|
||||||
mov edi, esi
|
mov edi, esi
|
||||||
imul edi, 24
|
imul edi, 24
|
||||||
add edi, OFFSET sceneTrace
|
add edi, OFFSET sceneTrace
|
||||||
mov eax, playerX
|
mov eax, csCameraX
|
||||||
stosd
|
stosd
|
||||||
mov eax, playerY
|
mov eax, csCameraY
|
||||||
stosd
|
stosd
|
||||||
mov eax, playerAngle
|
mov eax, csCameraAngle
|
||||||
stosd
|
stosd
|
||||||
mov eax, cutsceneFade
|
mov eax, cutsceneFade
|
||||||
stosd
|
stosd
|
||||||
|
|
@ -67,9 +67,9 @@ scene_trace_tick:
|
||||||
scene_trace_not_zero:
|
scene_trace_not_zero:
|
||||||
cmp esi, 2
|
cmp esi, 2
|
||||||
jne scene_trace_not_two
|
jne scene_trace_not_two
|
||||||
CHECK playerX, 224 ; camera reaches exact interpolated X midpoint
|
CHECK csCameraX, 224 ; camera reaches exact interpolated X midpoint
|
||||||
CHECK playerY, 192 ; camera interpolates a negative Y delta
|
CHECK csCameraY, 192 ; camera interpolates a negative Y delta
|
||||||
CHECK playerAngle, 0 ; pan wraps through zero along shortest arc
|
CHECK csCameraAngle, 0 ; pan wraps through zero along shortest arc
|
||||||
CHECK cutsceneFade, 8 ; fade reaches midpoint
|
CHECK cutsceneFade, 8 ; fade reaches midpoint
|
||||||
CHECK cutsceneSubtitle, 0 ; wait blocks a later hide command
|
CHECK cutsceneSubtitle, 0 ; wait blocks a later hide command
|
||||||
scene_trace_not_two:
|
scene_trace_not_two:
|
||||||
|
|
@ -93,7 +93,7 @@ scene_trace_not_two:
|
||||||
CHECK isCutsceneActive, 1 ; player update starts autoplay
|
CHECK isCutsceneActive, 1 ; player update starts autoplay
|
||||||
CHECK playerX, 160 ; movement input does not move the player during the first scene frame
|
CHECK playerX, 160 ; movement input does not move the player during the first scene frame
|
||||||
call UpdatePlayer
|
call UpdatePlayer
|
||||||
CHECK playerX, 192 ; next frame is driven only by cinematic interpolation
|
CHECK csCameraX, 192 ; next frame is driven only by cinematic interpolation
|
||||||
mov keySkipScene, 1
|
mov keySkipScene, 1
|
||||||
call UpdatePlayer
|
call UpdatePlayer
|
||||||
CHECK isCutsceneActive, 0 ; Tab skips and releases camera control
|
CHECK isCutsceneActive, 0 ; Tab skips and releases camera control
|
||||||
|
|
@ -105,11 +105,14 @@ scene_trace_not_two:
|
||||||
mov keyForward, 0
|
mov keyForward, 0
|
||||||
mov testFocus, 1
|
mov testFocus, 1
|
||||||
mov hEngineWnd, 1
|
mov hEngineWnd, 1
|
||||||
|
; First regain focus with no scene command held; subsequent presses are real.
|
||||||
|
call PollInput
|
||||||
|
call ConsumeInputEdges
|
||||||
mov testKeys[VK_KEY_W*2], 8000h
|
mov testKeys[VK_KEY_W*2], 8000h
|
||||||
mov testKeys[VK_KEY_TAB*2], 8000h
|
mov testKeys[VK_KEY_TAB*2], 8000h
|
||||||
call PollInput
|
call PollInput
|
||||||
movzx eax, keyForward
|
movzx eax, keyForward
|
||||||
CHECK eax, 0 ; scene masks normal movement input
|
CHECK eax, 1 ; polling records held movement independently of scene ownership
|
||||||
movzx eax, keySkipScene
|
movzx eax, keySkipScene
|
||||||
CHECK eax, 1 ; skip input remains available in a scene
|
CHECK eax, 1 ; skip input remains available in a scene
|
||||||
call StopCutscene
|
call StopCutscene
|
||||||
|
|
@ -202,7 +205,7 @@ scene_next_pixel:
|
||||||
CHECK fadeLevel, 16 ; transition preserves an existing cinematic fade without a bright flash
|
CHECK fadeLevel, 16 ; transition preserves an existing cinematic fade without a bright flash
|
||||||
mov esi, 32
|
mov esi, 32
|
||||||
scene_transition_fade:
|
scene_transition_fade:
|
||||||
call ApplyScreenFade
|
call UpdateScreenFade
|
||||||
dec esi
|
dec esi
|
||||||
jnz scene_transition_fade
|
jnz scene_transition_fade
|
||||||
CHECK spriteCount, 1 ; scripted transition loads the destination room
|
CHECK spriteCount, 1 ; scripted transition loads the destination room
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,13 @@ include include/engine_render2d.inc
|
||||||
include include/engine_combat.inc
|
include include/engine_combat.inc
|
||||||
include include/engine_gameplay.inc
|
include include/engine_gameplay.inc
|
||||||
include include/engine_cutscene.inc
|
include include/engine_cutscene.inc
|
||||||
|
include include/engine_simulation.inc
|
||||||
include startup_parser.inc
|
include startup_parser.inc
|
||||||
include project_paths.inc
|
include project_paths.inc
|
||||||
|
|
||||||
.data
|
.data
|
||||||
resultCount dd 0
|
resultCount dd 0
|
||||||
results dd 256 dup(0)
|
results dd 512 dup(0)
|
||||||
resultPath db 'engine_results.bin',0
|
resultPath db 'engine_results.bin',0
|
||||||
anglePath db 'angles.bin',0
|
anglePath db 'angles.bin',0
|
||||||
framePath db 'frame.bgra',0
|
framePath db 'frame.bgra',0
|
||||||
|
|
@ -131,6 +132,7 @@ include tests/cutscene_engine_checks.inc
|
||||||
include tests/lighting_engine_checks.inc
|
include tests/lighting_engine_checks.inc
|
||||||
include tests/stability_engine_checks.inc
|
include tests/stability_engine_checks.inc
|
||||||
include tests/integration_engine_checks.inc
|
include tests/integration_engine_checks.inc
|
||||||
|
include tests/simulation_engine_checks.inc
|
||||||
|
|
||||||
AuditMain PROC
|
AuditMain PROC
|
||||||
call InitMathTables
|
call InitMathTables
|
||||||
|
|
@ -177,7 +179,7 @@ dirty_stack:
|
||||||
invoke SwitchSublevel, OFFSET mainPath, 2, 2, 0, 1
|
invoke SwitchSublevel, OFFSET mainPath, 2, 2, 0, 1
|
||||||
mov esi, 32
|
mov esi, 32
|
||||||
fade_frames:
|
fade_frames:
|
||||||
call ApplyScreenFade
|
call UpdateScreenFade
|
||||||
dec esi
|
dec esi
|
||||||
jnz fade_frames
|
jnz fade_frames
|
||||||
CHECK fadeState, 0 ; fade finishes and releases player
|
CHECK fadeState, 0 ; fade finishes and releases player
|
||||||
|
|
@ -260,6 +262,7 @@ copy_missing_startup:
|
||||||
call PollInput
|
call PollInput
|
||||||
movzx eax, keyAction
|
movzx eax, keyAction
|
||||||
CHECK eax, 1 ; action emitted on first key press
|
CHECK eax, 1 ; action emitted on first key press
|
||||||
|
call ConsumeInputEdges
|
||||||
call PollInput
|
call PollInput
|
||||||
movzx eax, keyAction
|
movzx eax, keyAction
|
||||||
CHECK eax, 0 ; held action does not repeat
|
CHECK eax, 0 ; held action does not repeat
|
||||||
|
|
@ -370,6 +373,7 @@ flat_depth_ok:
|
||||||
call RunLightingChecks
|
call RunLightingChecks
|
||||||
call RunCutsceneChecks
|
call RunCutsceneChecks
|
||||||
call RunStabilityChecks
|
call RunStabilityChecks
|
||||||
|
call RunSimulationChecks
|
||||||
mov eax, resultCount
|
mov eax, resultCount
|
||||||
inc eax
|
inc eax
|
||||||
shl eax, 2
|
shl eax, 2
|
||||||
|
|
|
||||||
471
tests/simulation_engine_checks.inc
Normal file
471
tests/simulation_engine_checks.inc
Normal file
|
|
@ -0,0 +1,471 @@
|
||||||
|
.data
|
||||||
|
scheduleSnapshot dd 8 dup(0)
|
||||||
|
symmetryOK dd 1
|
||||||
|
speedOK dd 1
|
||||||
|
COMM cinematicReference:BYTE:FRAME_BUFFER_SIZE
|
||||||
|
.code
|
||||||
|
ResetSimulationFixture PROC
|
||||||
|
call ResetCombatFixture
|
||||||
|
mov spriteCount, 0
|
||||||
|
mov playerX, 800
|
||||||
|
mov playerY, 800
|
||||||
|
mov playerMoveSpeed, 3
|
||||||
|
mov playerRunSpeed, 6
|
||||||
|
mov playerRadius, 16
|
||||||
|
mov keyAttack, 0
|
||||||
|
mov keyForward, 1
|
||||||
|
mov simulationTicks, 0
|
||||||
|
mov lightingTick, 0
|
||||||
|
mov gameToastTimer, 100
|
||||||
|
mov mapErrorFrames, 0
|
||||||
|
mov DWORD PTR qpcFrequency, 60000
|
||||||
|
mov DWORD PTR qpcFrequency+4, 0
|
||||||
|
mov DWORD PTR tickAccumulator, 0
|
||||||
|
mov DWORD PTR tickAccumulator+4, 0
|
||||||
|
ret
|
||||||
|
ResetSimulationFixture ENDP
|
||||||
|
|
||||||
|
MoveSixtyTicks PROC uses esi
|
||||||
|
mov esi, 60
|
||||||
|
move_sixty_loop:
|
||||||
|
call UpdatePlayer
|
||||||
|
dec esi
|
||||||
|
jnz move_sixty_loop
|
||||||
|
ret
|
||||||
|
MoveSixtyTicks ENDP
|
||||||
|
|
||||||
|
RunSimulationChecks PROC uses ebx esi edi
|
||||||
|
call ResetSimulationFixture
|
||||||
|
mov playerAngle, 32
|
||||||
|
mov esi, 60
|
||||||
|
schedule_one:
|
||||||
|
invoke AdvanceSimulation, 1000, 0
|
||||||
|
call Render3DScene
|
||||||
|
dec esi
|
||||||
|
jnz schedule_one
|
||||||
|
mov eax, playerX
|
||||||
|
mov scheduleSnapshot, eax
|
||||||
|
mov eax, playerY
|
||||||
|
mov scheduleSnapshot[4], eax
|
||||||
|
mov eax, playerFractionX
|
||||||
|
mov scheduleSnapshot[8], eax
|
||||||
|
mov eax, playerFractionY
|
||||||
|
mov scheduleSnapshot[12], eax
|
||||||
|
CHECK simulationTicks, 60 ; QPC accumulator executes exactly sixty ticks per second
|
||||||
|
CHECK lightingTick, 60 ; lights advance once per simulation tick
|
||||||
|
CHECK gameToastTimer, 40 ; toast lifetime follows simulation rather than renders
|
||||||
|
call ResetSimulationFixture
|
||||||
|
mov playerAngle, 32
|
||||||
|
mov esi, 20
|
||||||
|
schedule_three:
|
||||||
|
invoke AdvanceSimulation, 3000, 0
|
||||||
|
call Render3DScene
|
||||||
|
call Render3DScene
|
||||||
|
dec esi
|
||||||
|
jnz schedule_three
|
||||||
|
mov ebx, scheduleSnapshot
|
||||||
|
CHECK playerX, ebx ; batched ticks preserve X under a different render schedule
|
||||||
|
mov ebx, scheduleSnapshot[4]
|
||||||
|
CHECK playerY, ebx ; batched ticks preserve Y under a different render schedule
|
||||||
|
mov ebx, scheduleSnapshot[8]
|
||||||
|
CHECK playerFractionX, ebx ; batched ticks preserve fractional X
|
||||||
|
mov ebx, scheduleSnapshot[12]
|
||||||
|
CHECK playerFractionY, ebx ; batched ticks preserve fractional Y
|
||||||
|
CHECK simulationTicks, 60 ; catch-up does not change total simulated time
|
||||||
|
CHECK gameToastTimer, 40 ; repeated drawing does not age toasts
|
||||||
|
invoke AdvanceSimulation, 60000, 0
|
||||||
|
CHECK simulationTicks, 60 ; long stalls discard timing debt instead of fast-forwarding
|
||||||
|
invoke AdvanceSimulation, 15000, 0
|
||||||
|
CHECK simulationTicks, 68 ; catch-up is bounded to eight ticks
|
||||||
|
CHECK DWORD PTR tickAccumulator, 0 ; overload discards excess catch-up debt
|
||||||
|
|
||||||
|
call ResetSimulationFixture
|
||||||
|
mov keyForward, 0
|
||||||
|
mov keyHud, 1
|
||||||
|
mov hudStyle, HUD_STYLE_CLEAN
|
||||||
|
invoke AdvanceSimulation, 100, 0
|
||||||
|
movzx eax, keyHud
|
||||||
|
CHECK eax, 1 ; pending edge survives a frame with no simulation tick
|
||||||
|
invoke AdvanceSimulation, 2900, 0
|
||||||
|
CHECK hudStyle, HUD_STYLE_DEBUG ; edge toggles exactly once across three catch-up ticks
|
||||||
|
movzx eax, keyHud
|
||||||
|
CHECK eax, 0 ; simulation consumes edge after its first tick
|
||||||
|
mov keyPause, 1
|
||||||
|
invoke AdvanceSimulation, 3000, 0
|
||||||
|
CHECK isGamePaused, 1 ; pause edge is applied inside the simulation
|
||||||
|
CHECK simulationTicks, 4 ; pause boundary discards remaining catch-up ticks
|
||||||
|
CHECK DWORD PTR tickAccumulator, 0 ; pause clears timing debt
|
||||||
|
mov ebx, lightingTick
|
||||||
|
mov keyAttack, 1
|
||||||
|
call Engine_Tick
|
||||||
|
CHECK lightingTick, ebx ; pause freezes dynamic lights
|
||||||
|
CHECK playerAmmo, 50 ; paused tick consumes attack without firing
|
||||||
|
mov keyMenu1, 1
|
||||||
|
call Engine_Tick
|
||||||
|
CHECK isGamePaused, 0 ; menu resume returns to gameplay without advancing world
|
||||||
|
mov clockReset, 0
|
||||||
|
mov gameDialogActive, 1
|
||||||
|
mov keyDismiss, 1
|
||||||
|
call Engine_Tick
|
||||||
|
CHECK gameDialogActive, 0 ; Enter dismisses dialogue on a simulation tick
|
||||||
|
CHECK lightingTick, ebx ; dialogue dismissal does not advance world timers
|
||||||
|
CHECK gameToastTimer, 97 ; pause and modal ticks preserve toast lifetime
|
||||||
|
|
||||||
|
call ResetSimulationFixture
|
||||||
|
call MoveSixtyTicks
|
||||||
|
CHECK playerX, 980 ; cardinal speed remains three world units per tick
|
||||||
|
CHECK playerY, 800 ; cardinal movement has no orthogonal drift
|
||||||
|
call ResetSimulationFixture
|
||||||
|
mov keyStrafeRight, 1
|
||||||
|
call MoveSixtyTicks
|
||||||
|
CHECK playerX, 927 ; diagonal input is normalized rather than sqrt-two faster
|
||||||
|
CHECK playerY, 927 ; diagonal normalization is symmetric on both axes
|
||||||
|
call ResetSimulationFixture
|
||||||
|
mov playerAngle, 32
|
||||||
|
call MoveSixtyTicks
|
||||||
|
mov ebx, playerX
|
||||||
|
sub ebx, 800
|
||||||
|
mov scheduleSnapshot, ebx
|
||||||
|
mov ebx, playerY
|
||||||
|
sub ebx, 800
|
||||||
|
mov scheduleSnapshot[4], ebx
|
||||||
|
call ResetSimulationFixture
|
||||||
|
mov playerAngle, 288
|
||||||
|
call MoveSixtyTicks
|
||||||
|
mov ebx, 800
|
||||||
|
sub ebx, scheduleSnapshot
|
||||||
|
CHECK playerX, ebx ; opposite angle has exactly symmetric accumulated X
|
||||||
|
mov ebx, 800
|
||||||
|
sub ebx, scheduleSnapshot[4]
|
||||||
|
CHECK playerY, ebx ; opposite angle has exactly symmetric accumulated Y
|
||||||
|
call ResetSimulationFixture
|
||||||
|
mov keyBackward, 1
|
||||||
|
mov keyStrafeLeft, 1
|
||||||
|
mov keyStrafeRight, 1
|
||||||
|
call MoveSixtyTicks
|
||||||
|
CHECK playerX, 800 ; opposing forward inputs cancel before rounding
|
||||||
|
CHECK playerY, 800 ; opposing strafe inputs cancel before rounding
|
||||||
|
|
||||||
|
call ResetSimulationFixture
|
||||||
|
mov playerX, 175
|
||||||
|
mov playerY, 184
|
||||||
|
mov byte ptr [gameMap+3*MAP_W+3], CELL_BRICK
|
||||||
|
call UpdatePlayer
|
||||||
|
CHECK playerX, 175 ; collision checks leading corners beyond the center cell
|
||||||
|
CHECK playerFractionX, 0 ; blocked axis discards its fractional remainder
|
||||||
|
mov keyStrafeLeft, 1
|
||||||
|
call UpdatePlayer
|
||||||
|
CHECK playerY, 182 ; blocked X still permits sliding on free Y
|
||||||
|
invoke CanOccupyPlayer, 8, 160
|
||||||
|
CHECK eax, 0 ; collision body cannot extend outside the map
|
||||||
|
mov byte ptr [gameMap+3*MAP_W+3], CELL_PORTAL
|
||||||
|
invoke CanOccupyPlayer, 184, 184
|
||||||
|
CHECK eax, 1 ; portal cells remain walkable for the complete collision body
|
||||||
|
mov byte ptr [gameMap+2*MAP_W+3], CELL_DOOR_OPEN
|
||||||
|
mov playerX, 180
|
||||||
|
mov playerY, 160
|
||||||
|
mov playerAngle, 0
|
||||||
|
call InteractDoor
|
||||||
|
movzx eax, byte ptr [gameMap+2*MAP_W+3]
|
||||||
|
CHECK eax, CELL_DOOR_OPEN ; door cannot close over an overlapping player body
|
||||||
|
mov playerX, 160
|
||||||
|
call InteractDoor
|
||||||
|
movzx eax, byte ptr [gameMap+2*MAP_W+3]
|
||||||
|
CHECK eax, CELL_DOOR ; unoccupied door still closes normally
|
||||||
|
|
||||||
|
call ResetSimulationFixture
|
||||||
|
mov isCutsceneActive, 1
|
||||||
|
mov csCameraX, 184
|
||||||
|
mov csCameraY, 184
|
||||||
|
mov csCameraAngle, 128
|
||||||
|
mov byte ptr [gameMap+3*MAP_W+3], CELL_BRICK
|
||||||
|
call SelectRenderCamera
|
||||||
|
CHECK viewX, 184 ; renderer selects cinematic camera without replacing player pose
|
||||||
|
CHECK playerX, 800 ; camera ownership leaves gameplay position untouched
|
||||||
|
call StopCutscene
|
||||||
|
CHECK playerX, 800 ; unsafe cinematic handoff retains last safe gameplay position
|
||||||
|
CHECK playerAngle, 0 ; unsafe cinematic handoff also retains gameplay orientation
|
||||||
|
mov isCutsceneActive, 1
|
||||||
|
mov csCameraX, 864
|
||||||
|
mov csCameraY, 800
|
||||||
|
call StopCutscene
|
||||||
|
CHECK playerX, 864 ; walkable cinematic handoff transfers camera position
|
||||||
|
CHECK playerAngle, 128 ; walkable cinematic handoff transfers orientation
|
||||||
|
|
||||||
|
; Render routines must not dismiss, age or transition anything.
|
||||||
|
mov gameToastTimer, 12
|
||||||
|
mov gameDialogActive, 1
|
||||||
|
mov keyAction, 1
|
||||||
|
mov fadeState, 1
|
||||||
|
mov fadeLevel, 4
|
||||||
|
call RenderGameToast
|
||||||
|
call RenderGameDialog
|
||||||
|
call ApplyScreenFade
|
||||||
|
call ApplyScreenFade
|
||||||
|
CHECK gameToastTimer, 12 ; drawing toast never decrements its timer
|
||||||
|
CHECK gameDialogActive, 1 ; drawing modal never consumes input or closes it
|
||||||
|
CHECK fadeLevel, 4 ; drawing fade never advances transition level
|
||||||
|
CHECK fadeState, 1 ; drawing fade never switches room state
|
||||||
|
|
||||||
|
call ResetSimulationFixture
|
||||||
|
invoke LoadSublevelBinary, OFFSET subPath, 4, 6, 64
|
||||||
|
mov playerX, 600
|
||||||
|
mov playerHealth, 1
|
||||||
|
mov playerAmmo, 2
|
||||||
|
mov playerKeys, 3
|
||||||
|
mov playerScore, 700
|
||||||
|
mov isPlayerDead, 1
|
||||||
|
mov gameDialogActive, 1
|
||||||
|
mov playerFractionX, 12345
|
||||||
|
mov gameMode, GAME_MODE_ADVENTURE
|
||||||
|
mov enableWeapons, 0
|
||||||
|
call RestartCurrentRoom
|
||||||
|
CHECK eax, 1 ; current-room restart succeeds after a sublevel transition
|
||||||
|
CHECK playerX, 288 ; restart retains actual transition entry X rather than file spawn
|
||||||
|
CHECK playerY, 416 ; restart retains actual transition entry Y
|
||||||
|
CHECK playerAngle, 64 ; restart retains actual transition entry angle
|
||||||
|
CHECK spriteCount, 1 ; restart reloads current sublevel rather than startup room
|
||||||
|
CHECK playerHealth, 100 ; restart resets health after successful reload
|
||||||
|
CHECK playerAmmo, 50 ; restart resets ammunition after successful reload
|
||||||
|
CHECK playerKeys, 0 ; restart clears acquired keys
|
||||||
|
CHECK playerScore, 0 ; restart clears room score
|
||||||
|
CHECK playerFractionX, 0 ; restart clears fractional movement
|
||||||
|
CHECK gameDialogActive, 0 ; restart clears old modal dialogue
|
||||||
|
CHECK gameMode, GAME_MODE_ADVENTURE ; restart preserves configured game genre
|
||||||
|
CHECK enableWeapons, 0 ; restart preserves weapon configuration
|
||||||
|
; Replace only remembered path in this isolated fixture to force load failure.
|
||||||
|
mov DWORD PTR currentMapPath, 73696D61h
|
||||||
|
mov DWORD PTR currentMapPath+4, 676E6973h
|
||||||
|
mov DWORD PTR currentMapPath+8, 7461642Eh
|
||||||
|
mov BYTE PTR currentMapPath+12, 0
|
||||||
|
mov playerHealth, 17
|
||||||
|
mov playerAmmo, 9
|
||||||
|
mov playerFractionX, 222
|
||||||
|
mov isPlayerDead, 1
|
||||||
|
call RestartCurrentRoom
|
||||||
|
CHECK eax, 0 ; failed restart reports failure without procedural fallback
|
||||||
|
CHECK playerHealth, 17 ; failed restart preserves health transactionally
|
||||||
|
CHECK playerAmmo, 9 ; failed restart preserves ammunition transactionally
|
||||||
|
CHECK playerFractionX, 222 ; failed restart preserves movement remainder
|
||||||
|
CHECK isPlayerDead, 1 ; failed restart does not revive player in old room
|
||||||
|
CHECK spriteCount, 1 ; failed restart preserves active room entities
|
||||||
|
CHECK playerX, 288 ; failed restart preserves current camera pose
|
||||||
|
|
||||||
|
; Keys pressed while another application owns focus must not become commands.
|
||||||
|
call ResetSimulationFixture
|
||||||
|
mov hEngineWnd, 1
|
||||||
|
mov testFocus, 1
|
||||||
|
mov testKeys[VK_KEY_E*2], 0
|
||||||
|
mov testKeys[VK_KEY_CONTROL*2], 0
|
||||||
|
mov testKeys[VK_KEY_SPACE*2], 0
|
||||||
|
call PollInput
|
||||||
|
call ConsumeInputEdges
|
||||||
|
mov testFocus, 0
|
||||||
|
call PollInput
|
||||||
|
mov testKeys[VK_KEY_E*2], 8000h
|
||||||
|
mov testKeys[VK_KEY_CONTROL*2], 8000h
|
||||||
|
mov testFocus, 1
|
||||||
|
call PollInput
|
||||||
|
movzx eax, keyAction
|
||||||
|
CHECK eax, 0 ; focus regain suppresses action pressed while unfocused
|
||||||
|
movzx eax, keyAttack
|
||||||
|
CHECK eax, 0 ; focus regain suppresses attack pressed while unfocused
|
||||||
|
call ConsumeInputEdges
|
||||||
|
call PollInput
|
||||||
|
movzx eax, keyAttack
|
||||||
|
CHECK eax, 0 ; resynchronized held attack does not fire on the next poll
|
||||||
|
mov testKeys[VK_KEY_CONTROL*2], 0
|
||||||
|
call PollInput
|
||||||
|
mov testKeys[VK_KEY_CONTROL*2], 8000h
|
||||||
|
call PollInput
|
||||||
|
movzx eax, keyAttack
|
||||||
|
CHECK eax, 1 ; releasing and pressing again rearms attack after focus regain
|
||||||
|
|
||||||
|
call ResetSimulationFixture
|
||||||
|
invoke ShowGameDialog, 0, 0
|
||||||
|
CHECK clockReset, 1 ; opening a modal discards outstanding timing debt
|
||||||
|
mov clockReset, 0
|
||||||
|
call CloseGameDialog
|
||||||
|
CHECK clockReset, 1 ; closing a modal through the API discards timing debt
|
||||||
|
|
||||||
|
call ResetSimulationFixture
|
||||||
|
mov lightingMode, LIGHTING_MODE_DYNAMIC
|
||||||
|
mov isFlashlightOn, 1
|
||||||
|
mov isCutsceneActive, 1
|
||||||
|
mov csCameraX, 160
|
||||||
|
mov csCameraY, 160
|
||||||
|
mov csCameraAngle, 0
|
||||||
|
invoke SamplePointLighting, 224, 160, 0, 0
|
||||||
|
mov eax, lightRed
|
||||||
|
mov scheduleSnapshot, eax
|
||||||
|
mov isCutsceneActive, 0
|
||||||
|
mov playerX, 160
|
||||||
|
mov playerY, 160
|
||||||
|
invoke SamplePointLighting, 224, 160, 0, 0
|
||||||
|
mov ebx, scheduleSnapshot
|
||||||
|
CHECK lightRed, ebx ; cinematic flashlight matches preview camera rather than safe player pose
|
||||||
|
|
||||||
|
; Exhaust all half-turn pairs, including signed fractional remainders.
|
||||||
|
mov edi, 0
|
||||||
|
mov symmetryOK, 1
|
||||||
|
mov speedOK, 1
|
||||||
|
symmetry_angle:
|
||||||
|
call ResetSimulationFixture
|
||||||
|
mov playerAngle, edi
|
||||||
|
call MoveSixtyTicks
|
||||||
|
mov eax, playerX
|
||||||
|
sub eax, 800
|
||||||
|
mov scheduleSnapshot, eax
|
||||||
|
imul eax, eax
|
||||||
|
mov ebx, playerY
|
||||||
|
sub ebx, 800
|
||||||
|
mov scheduleSnapshot[4], ebx
|
||||||
|
imul ebx, ebx
|
||||||
|
add eax, ebx
|
||||||
|
cmp eax, 178*178
|
||||||
|
jb speed_mismatch
|
||||||
|
cmp eax, 181*181
|
||||||
|
jbe speed_valid
|
||||||
|
speed_mismatch:
|
||||||
|
mov speedOK, 0
|
||||||
|
speed_valid:
|
||||||
|
mov eax, playerFractionX
|
||||||
|
mov scheduleSnapshot[8], eax
|
||||||
|
mov eax, playerFractionY
|
||||||
|
mov scheduleSnapshot[12], eax
|
||||||
|
call ResetSimulationFixture
|
||||||
|
lea eax, [edi+256]
|
||||||
|
mov playerAngle, eax
|
||||||
|
call MoveSixtyTicks
|
||||||
|
mov eax, 800
|
||||||
|
sub eax, scheduleSnapshot
|
||||||
|
cmp playerX, eax
|
||||||
|
jne symmetry_mismatch
|
||||||
|
mov eax, 800
|
||||||
|
sub eax, scheduleSnapshot[4]
|
||||||
|
cmp playerY, eax
|
||||||
|
jne symmetry_mismatch
|
||||||
|
mov eax, scheduleSnapshot[8]
|
||||||
|
neg eax
|
||||||
|
cmp playerFractionX, eax
|
||||||
|
jne symmetry_mismatch
|
||||||
|
mov eax, scheduleSnapshot[12]
|
||||||
|
neg eax
|
||||||
|
cmp playerFractionY, eax
|
||||||
|
je symmetry_next
|
||||||
|
symmetry_mismatch:
|
||||||
|
mov symmetryOK, 0
|
||||||
|
symmetry_next:
|
||||||
|
inc edi
|
||||||
|
cmp edi, 256
|
||||||
|
jb symmetry_angle
|
||||||
|
CHECK symmetryOK, 1 ; all 512 directions retain opposite-position and fraction symmetry
|
||||||
|
CHECK speedOK, 1 ; all angular speeds remain within integer projection tolerance
|
||||||
|
|
||||||
|
call ResetSimulationFixture
|
||||||
|
mov playerX, 160
|
||||||
|
mov playerY, 160
|
||||||
|
mov byte ptr [gameMap+2*MAP_W+4], CELL_BRICK
|
||||||
|
mov cutsceneCount, 2
|
||||||
|
mov [cutsceneEvents].CutsceneEvent.tickTimestamp, 0
|
||||||
|
mov [cutsceneEvents].CutsceneEvent.opCode, OP_MOVE_CAM_TO
|
||||||
|
mov [cutsceneEvents].CutsceneEvent.paramA, 416
|
||||||
|
mov [cutsceneEvents].CutsceneEvent.paramB, 160
|
||||||
|
mov [cutsceneEvents].CutsceneEvent.dataId, 4
|
||||||
|
mov [cutsceneEvents+16].CutsceneEvent.tickTimestamp, 5
|
||||||
|
mov [cutsceneEvents+16].CutsceneEvent.opCode, OP_END
|
||||||
|
call StartCutscene
|
||||||
|
call UpdatePlayer
|
||||||
|
call UpdatePlayer
|
||||||
|
call UpdatePlayer
|
||||||
|
CHECK csCameraX, 288 ; cinematic path can cross a wall independently of the body
|
||||||
|
CHECK playerX, 160 ; wall-crossing camera path never teleports gameplay body
|
||||||
|
mov keySkipScene, 1
|
||||||
|
call UpdatePlayer
|
||||||
|
CHECK playerX, 160 ; skipping inside a wall retains safe player pose
|
||||||
|
mov keySkipScene, 0
|
||||||
|
call StartCutscene
|
||||||
|
mov esi, 6
|
||||||
|
wall_scene_tick:
|
||||||
|
call UpdatePlayer
|
||||||
|
dec esi
|
||||||
|
jnz wall_scene_tick
|
||||||
|
CHECK isCutsceneActive, 0 ; wall-crossing cinematic reaches END normally
|
||||||
|
CHECK playerX, 416 ; END hands off safe endpoint across the wall
|
||||||
|
|
||||||
|
call ResetSimulationFixture
|
||||||
|
mov playerMoveSpeed, 64
|
||||||
|
mov playerX, 175
|
||||||
|
mov playerY, 160
|
||||||
|
mov byte ptr [gameMap+2*MAP_W+3], CELL_BRICK
|
||||||
|
call UpdatePlayer
|
||||||
|
CHECK playerX, 175 ; high-speed movement cannot tunnel through a wall
|
||||||
|
|
||||||
|
call ResetSimulationFixture
|
||||||
|
mov keyRun, 1
|
||||||
|
call MoveSixtyTicks
|
||||||
|
CHECK playerX, 1160 ; sprint retains six world units per fixed tick
|
||||||
|
mov keyForward, 0
|
||||||
|
mov keyTurnLeft, 1
|
||||||
|
mov keyTurnRight, 1
|
||||||
|
call UpdatePlayer
|
||||||
|
CHECK playerAngle, 0 ; opposing turn inputs cancel
|
||||||
|
|
||||||
|
call ResetSimulationFixture
|
||||||
|
invoke LoadSublevelBinary, OFFSET subPath, 4, 6, 64
|
||||||
|
mov isPlayerDead, 1
|
||||||
|
mov keyRestart, 1
|
||||||
|
call Engine_Tick
|
||||||
|
CHECK isPlayerDead, 0 ; death R uses the shared current-room restart
|
||||||
|
CHECK playerX, 288 ; death restart restores transition entry pose
|
||||||
|
mov clockReset, 0
|
||||||
|
mov isGamePaused, 1
|
||||||
|
mov playerHealth, 7
|
||||||
|
mov keyMenu4, 1
|
||||||
|
call Engine_Tick
|
||||||
|
CHECK isGamePaused, 0 ; pause-menu restart resumes only after successful reload
|
||||||
|
CHECK playerHealth, 100 ; pause-menu restart uses the same transient reset
|
||||||
|
CHECK playerY, 416 ; pause-menu restart keeps the current room entry
|
||||||
|
|
||||||
|
call ResetSimulationFixture
|
||||||
|
; Frequency > 2^32 exercises carries in accumulator and elapsed multiplication.
|
||||||
|
mov DWORD PTR qpcFrequency, 8
|
||||||
|
mov DWORD PTR qpcFrequency+4, 1
|
||||||
|
invoke AdvanceSimulation, 71582788, 0
|
||||||
|
CHECK simulationTicks, 0 ; QPC scheduler keeps a fractional high-frequency tick
|
||||||
|
invoke AdvanceSimulation, 2, 0
|
||||||
|
CHECK simulationTicks, 1 ; QPC scheduler carries elapsed counts across DWORD boundaries
|
||||||
|
CHECK DWORD PTR tickAccumulator, 96 ; QPC scheduler retains exact high-frequency remainder
|
||||||
|
mov clockReset, 1
|
||||||
|
invoke AdvanceSimulation, 1000, 0
|
||||||
|
CHECK DWORD PTR tickAccumulator, 0 ; explicit clock boundary discards existing fractional debt
|
||||||
|
invoke AdvanceSimulation, -1, -1
|
||||||
|
CHECK simulationTicks, 1 ; backwards counter samples cannot fast-forward simulation
|
||||||
|
|
||||||
|
call ResetSimulationFixture
|
||||||
|
call InitLightingSystem
|
||||||
|
mov lightingMode, LIGHTING_MODE_DYNAMIC
|
||||||
|
mov isCutsceneActive, 1
|
||||||
|
mov csCameraX, 160
|
||||||
|
mov csCameraY, 160
|
||||||
|
mov csCameraAngle, 32
|
||||||
|
mov byte ptr [gameMap+3*MAP_W+6], CELL_BRICK
|
||||||
|
call Render3DScene
|
||||||
|
mov esi, OFFSET screenBuffer
|
||||||
|
mov edi, OFFSET cinematicReference
|
||||||
|
mov ecx, FRAME_BUFFER_SIZE/4
|
||||||
|
rep movsd
|
||||||
|
mov isCutsceneActive, 0
|
||||||
|
mov playerX, 160
|
||||||
|
mov playerY, 160
|
||||||
|
mov playerAngle, 32
|
||||||
|
call Render3DScene
|
||||||
|
mov esi, OFFSET screenBuffer
|
||||||
|
mov edi, OFFSET cinematicReference
|
||||||
|
mov ecx, FRAME_BUFFER_SIZE/4
|
||||||
|
repe cmpsd
|
||||||
|
sete al
|
||||||
|
movzx eax, al
|
||||||
|
CHECK eax, 1 ; cinematic lit framebuffer equals camera-only preview with separate gameplay body
|
||||||
|
ret
|
||||||
|
RunSimulationChecks ENDP
|
||||||
|
|
@ -6,9 +6,15 @@ ResetCombatFixture PROC uses eax ecx edi
|
||||||
xor eax, eax
|
xor eax, eax
|
||||||
rep stosb
|
rep stosb
|
||||||
mov fadeState, 0
|
mov fadeState, 0
|
||||||
|
mov fadeLevel, 0
|
||||||
|
call ResetMovementFractions
|
||||||
mov isCutsceneActive, 0
|
mov isCutsceneActive, 0
|
||||||
mov cutscenePending, 0
|
mov cutscenePending, 0
|
||||||
mov isPlayerDead, 0
|
mov isPlayerDead, 0
|
||||||
|
mov clockReset, 0
|
||||||
|
mov inputFocused, 1
|
||||||
|
call ConsumeInputEdges
|
||||||
|
mov keyRun, 0
|
||||||
mov isGamePaused, 0
|
mov isGamePaused, 0
|
||||||
mov gameDialogActive, 0
|
mov gameDialogActive, 0
|
||||||
mov gameMode, GAME_MODE_ACTION
|
mov gameMode, GAME_MODE_ACTION
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ def main():
|
||||||
run([str(work / "engine_tests.exe"), "pace map.dat"], work)
|
run([str(work / "engine_tests.exe"), "pace map.dat"], work)
|
||||||
source = (ROOT / "tests/engine_regressions.asm").read_text() + "".join(
|
source = (ROOT / "tests/engine_regressions.asm").read_text() + "".join(
|
||||||
(ROOT / "tests" / name).read_text() for name in
|
(ROOT / "tests" / name).read_text() for name in
|
||||||
("cutscene_engine_checks.inc", "lighting_engine_checks.inc", "stability_engine_checks.inc", "integration_engine_checks.inc"))
|
("lighting_engine_checks.inc", "cutscene_engine_checks.inc", "stability_engine_checks.inc", "integration_engine_checks.inc", "simulation_engine_checks.inc"))
|
||||||
names = re.findall(r"^\s+CHECK .*?; (.+)$", source, re.M)
|
names = re.findall(r"^\s+CHECK .*?; (.+)$", source, re.M)
|
||||||
raw = (work / "engine_results.bin").read_bytes()
|
raw = (work / "engine_results.bin").read_bytes()
|
||||||
values = struct.unpack(f"<{len(raw)//4}I", raw)
|
values = struct.unpack(f"<{len(raw)//4}I", raw)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue