EBLAN_ENGINE/docs/STABILIZATION.md
Игорь Федосеев 7a56403d65 feat: add authored history and door interactions
Group level edits across inspector and canvas gestures, preserve legacy maps, and expose opt-in switch/key door bindings without changing wire layouts.

Refresh studio and pause UI with UE5-inspired surfaces and content drawer. Verify 162 editor and 307 ASM checks plus renderer parity.
2026-10-31 22:51:08 +03:00

236 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

# Stabilization roadmap
Stages are delivered separately. Each completed stage is tested, committed and
pushed before work stops for confirmation. Editor and runtime UI redesign will
use an Unreal Engine 5-inspired visual style; stage 1 does not redesign UI.
## Stage 1 — correctness and data safety
Implemented:
- `DrawRectOutline` keeps loop counters in a callee-saved register, not the
volatile registers overwritten by `DrawPixel`. Nonpositive sizes draw nothing.
- `TraceShotWall` traverses current map cells independently of rendering. Distances
are 24.8 world units; closed doors, glass, portals and out-of-map cells block
shots. Open doors do not. Corner-adjacent walls block diagonal seam shots.
- Hitscan chooses the nearest live guard intersecting the forward billboard
hitbox. Half-width is 16 world units times authored scale (zero scale means
legacy 1.0), not a constant number of screen pixels.
- `IsGameplayBlocked` centralizes gameplay gating. `UpdatePlayer` latches blocked
ticks before processing cinematic state. AI, pickups and weapon timers freeze
during active/pending cutscenes and room fades. END/skip and instant transitions
cannot resume AI or fire stale input halfway through a tick. Death blocks player
movement, turning and firing; lethal damage stops the remaining entity pass.
- Packed entity metadata remains binary-compatible. Only `flags == 0` enables
legacy texture-based archetypes; any nonzero flags explicitly encode the low
type byte, including Prop. Health uses one byte: 1..255, zero = legacy 100 HP.
The C++ setter clamps before narrowing, and the inspector exposes 1..255.
- IDE source writes use a unique sibling temporary file, checked full write,
flush, close and atomic replacement. Failure preserves disk contents and dirty
buffer. Loading publishes a new document only after a complete valid text read.
File switches and builds stop on failed autosave. Saves target the file actually
opened, not a newly resolved fallback path.
- Compiler output is drained even with the IDE hidden. Process startup uses the
system command processor with quoting for project paths containing spaces.
### Verification
Windows commands:
```cmd
build.bat
editor\build_editor.bat
python tools\test_regressions.py
```
Results: editor **112/112**, ASM **148/148**. The suite also checks live x86/x64
preview IPC, DX11 pixel equality, cinematic trace equality and unchanged project
assets. New tests exercise production gameplay and IDE procedures, including
failed atomic replacement under a Windows sharing lock and hidden compiler output
exceeding pipe capacity. Interactive UI playtesting remains separate.
## Preparation for stage 2 — integration with current main
Integrated server history through `2bac213` with stage-1 commit `f42679f` in an
isolated worktree, preserving both histories and the original working directory.
Retained map v5, preview IPC v2, authored lighting, three weapons, explosive
barrels, modal dialogues, peaceful mode and the updated studio UI.
Compatibility adjustments:
- Pause and modal dialogue join the shared gameplay gate, including direct calls
to gameplay procedures. Blocked player updates cannot activate portal triggers.
- Knife/pistol/shotgun keep their range, damage, ammunition and reserved light
behavior. World-space half-widths are respectively 24/16/32 times sprite scale;
the shotgun remains a single-target wider hit test, not three separate rays.
- Barrel texture fallback is enabled only with zero flags in both editor and ASM.
Explicit Prop overrides barrel artwork and cannot explode.
- A new explosion regression reproduced access violation `0xC0000005`: the AOE
loop kept a sprite pointer in EDX and destroyed it with CDQ. The pointer now
stays in EBX. A nearby guard receives blast damage without crashing.
- IDE tab navigation, API catalog and docked view retain stage-1 safe writes.
Verification: both production builds succeeded; editor **137/137**, ASM
**191/191**. All 1024 baked RGB cells match between C++ and ASM; classic and lit
preview frames match direct ASM rendering, including the x86/x64/DX11 path.
Project assets remain byte-for-byte unchanged. The original working directory
still has only its pre-existing `editor/imgui.ini` modification. No separate
interactive UI playtest was performed during this preparation.
### Stage 2 implementation contract
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
commands once, not once per catch-up tick. Reset timing debt after pause/focus
stalls; advance world timers/lights only on the appropriate simulation tick.
2. Keep movement fractions in runtime state without changing serialized raw-unit
coordinates. Cancel opposing inputs, normalize combined forward/strafe input,
and use symmetric signed accumulation. Clear fractions on load/teleport/restart.
3. Check the full collision body on each axis and preserve wall sliding; prevent
corner clipping and occupied-door closure. Keep portals intentionally walkable.
4. Centralize Playing/Cutscene/Transition/Dead/Paused/Dialog update rules, including
`Game_OnTick`, dynamic lights, fade, toasts and modal dismissal. Render routines
must not advance those timers or perform input-driven state changes.
5. Separate cinematic camera pose from gameplay/player position. Define END/skip
handoff explicitly and validate the restored gameplay collision body, including
paths crossing walls. Keep C++ preview and ASM cinematic traces aligned.
6. Track the current room path and its loaded entry spawn separately from startup
map. Restart restores that room and entry pose, resets gameplay transients and
fractional movement, and preserves project mode/settings. Failed reload must
not partially reset the current room. Make death-R and pause restart share it.
Acceptance checks: identical simulation after 60 ticks under different render
schedules; single consumption of edge inputs during catch-up; symmetric movement
for all quadrants and equal cardinal/diagonal speed; collision corner/slide cases;
no simulation advancement during pause/dialog; no pickups/AI along camera paths;
safe cinematic handoff; restart after a room transition; failed restart rollback;
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.
## Stage 3 — authored history, interactions and studio UI
Implemented:
- Nested `LevelEdit` scopes compare complete level snapshots around production UI
edits. Inspector transforms, sprite texture/type/AI parameters, spawn/angle,
palette/presets, portal filenames/destinations/fade, light settings/sources and
cinematic events/strings participate in Undo/Redo. Existing explicit `PushUndo`
calls are suppressed inside these scopes, avoiding duplicate entries. A stable
active widget or canvas stroke groups continuous edits; release-time baking and
timeline normalization belong to the same gesture. Selection/view changes and
clicks without authored changes create no history. New edits clear Redo; history
is limited to 64 snapshots. Successful loads clear history, failed loads preserve
it; creating a default level is undoable. Undo/Redo clear active widgets, drag
state and selection, restore RGB bake/signature and rescrub cinematic preview.
- Sprite drags clamp to map bounds. Inspector duplication returns immediately,
avoiding references invalidated by vector growth. Releasing a light drag outside
the canvas clears its radius/position drag state.
- A switch can opt into one authored door target without changing EBLN v5 or IPC
v2 layouts. Flags low byte is 5; byte 1 is `0xA8` (switch-only door) or `0xA9`
(also unlockable with a key), bytes 2/3 are door cell X/Y. Other switch flags
retain legacy hook/toggle behavior. Each door has at most one bound switch;
C++ and ASM loaders reject non-door targets, out-of-bounds targets and duplicate
bindings before replacing the room. Painting away a bound door leaves a visible
inspector error and blocks save/F5 until the target is repaired or unbound.
- Ordinary doors remain free. Bound doors require their switch, or one collected
key if explicitly enabled. First direct unlock consumes one key and persists
until room reinitialization; subsequent opens/closes are free. Switch use itself
never consumes a key. `ToggleDoorCell` shares full-player-body closure protection
between direct and remote interactions. An occupied/invalid target does not
change switch state. `E` prioritizes the facing door/portal, then the first nearby
switch in authored order, consuming the action once. Custom switch hooks run
before default door behavior and may handle the action themselves. This stage
does not add generic proximity dialogs or switch line-of-sight tests.
- Removed fake per-entity dialogue fields and the nonfunctional insertion promise.
UI explicitly identifies dialogue as an `engine_gameplay.inc` code hook, not map
data. Texture/audio import and project settings remain external file operations,
not part of level Undo; IDE text history is separate.
- UE5-inspired studio uses charcoal surfaces, compact square panels, Outliner and
Details headers, a two-row horizontally scrollable toolbar, resizable views and
a bottom Content Drawer (`Ctrl+Space`). Minimum desktop size is 1000x680; narrow
mode shortens viewport tabs. No docking framework or alternative renderer was
introduced. Workspace geometry ignores stale ImGui layout entries; no existing
`imgui.ini` is rewritten by the implementation. F5/F6/F7 remain; F focuses, G
fills, resolving the previous F conflict. Runtime pause menu uses the same
restrained charcoal/blue visual language with five clearly separated rows.
Verification (2026-10-31): production builds succeed; editor **162/162**, ASM
**307/307**. New checks exercise actual ImGui spawn/sprite/portal inputs and palette
preset buttons, history grouping/branching/capacity, workspace/content drawer at
1000/1360/1920 widths, real C++/ASM binding validation, key consumption, legacy
compatibility, single-action priority and remotely occupied doors. All 320 ray
angles, cinematic traces, 1024 RGB bake cells and classic/lit IPC frames still
agree. Project assets remain byte-for-byte unchanged. No manual visual/keyboard
playtest was performed; automated UI checks do not replace it.
## Remaining stages
4. Project lifecycle and validated, safe standalone export.
5. Measurements and targeted renderer/editor optimizations.
6. Finished multi-room demo game and release verification.
7. Data-driven extensions and subsequent rendering features.
Stages 1–3 do not claim that all audit findings are fixed: AI visibility/melee
through walls and exporter safety remain open. Existing map and texture assets
are not migrated or rewritten.