EBLAN_ENGINE/AGENTS.md

25 KiB
Raw Blame History

AGENTS.md - EBLAN ENGINE 3D

Project Identity

EBLAN ENGINE 3D is a lightweight, modular, pure x86 assembly (MASM32) 2.5D retro raycasting game engine with software rendering to a 320x240 framebuffer stretched to a 1280x960 Windows GDI window.

  • Language: Pure x86 Assembly (MASM32 / MSVC ml.exe).
  • Dependencies: Windows API only (kernel32.lib, user32.lib, gdi32.lib, winmm.lib). No external DLLs, DirectX, OpenGL, or C runtimes.
  • Rendering: Software DIBits raycaster (Wolfenstein 3D style DDA grid stepping, texture mapping, distance shading, billboard sprites, 2D UI canvas with 8x8 bitmap font).
  • Coordinate Space: Player/entity positions are raw world units (64 per cell). Trigonometry and sprite scale use 16.16; DDA distances retain 8 fractional bits (24.8) until projection. Trigonometry uses 512-entry lookup tables.

Directory Structure

EBLAN_ENGINE/
│
├── main.asm                    # Entry point, Win32 window, message loop, 60 FPS frame ticker
├── build.bat                   # 1-click build script (supports MASM32 and MSVC BuildTools)
├── engine.exe                  # Compiled game executable
│
├── include/
│   ├── engine_defs.inc         # Screen resolution, colors, key codes, constants, structs
│   ├── engine_data.inc         # Framebuffer, depthbuffer, player state, map array, textures
│   ├── engine_math.inc         # FPU Sin/Cos table generator, RNG, fixed-point math
│   ├── engine_map.inc          # Grid map accessors, wall collision queries, door interaction
│   ├── engine_textures.inc     # Procedural textures + external texturepack.bin loader
│   ├── engine_input.inc        # Keyboard polling (WASD, arrows, actions)
│   ├── engine_physics.inc      # Player movement vector, strafe, wall-sliding collision
│   ├── engine_raycaster.inc    # 320-ray DDA raycasting, fog, textured sorted sprites
│   ├── engine_render2d.inc     # Pixel, Rect, Outline, 8x8 Font (CP1251), Minimap radar
│   └── font8x8.inc             # 8x8 font bitmap glyphs (256 chars, CP1251)
│
├── tools/
│   └── make_texturepack.py     # Builds assets/textures/texturepack.bin (from KAZIK pack or procedural)
├── assets/                     # map.dat (level), textures/texturepack.bin (optional texture override)
└── docs/
    └── ENGINE_ARCHITECTURE.md  # Detailed deep-dive into engine math and rendering pipeline

Build System

Run build.bat from command line or IDE:

build.bat
  • Assembler: ml.exe /c /coff /Cp /nologo /I. /Iinclude main.asm
  • Linker: link.exe /NOLOGO /SUBSYSTEM:WINDOWS /OUT:engine.exe main.obj kernel32.lib winmm.lib user32.lib gdi32.lib

Core Engine Architecture

1. Frame Lifecycle

Each tick (~60 times per second), main.asm executes:

  1. PollInput: queries keyboard state (keyForward, keyBackward, keyStrafeLeft, keyStrafeRight, keyTurnLeft, keyTurnRight, keyAction).
  2. UpdatePlayer: applies camera turning, calculates forward/strafe movement vectors, runs wall-sliding collision against gameMap, checks door interaction.
  3. Render3DScene:
    • Clears ceiling (COLOR_CEILING) and floor (COLOR_FLOOR).
    • Casts 320 rays across FOV (~67.5°).
    • Traverses grid using DDA until a solid cell is hit.
    • Computes perpendicular wall distance (fish-eye corrected).
    • Samples 128x128 texture stripe, applies side shading (Y-walls darkened for 3D depth) and distance fog.
    • Populates depthBuffer[screenX].
    • Renders billboard sprites from spriteList with depth testing.
  4. Engine_RenderHUD: draws 2D overlay (title, FPS counter, minimap radar, controls).
  5. StretchDIBits: copies screenBuffer (320x240 BGRA) to window DC (1280x960).

How to Extend This Engine (Guidelines for AI Agents)

A. How to Add a New Wall Texture

  1. Open include/engine_defs.inc and define a new cell ID:
    CELL_MYWALL equ 8
    
  2. Open include/engine_textures.inc and generate pixels into textureBuffer + (8 * TEX_SIZE_BYTES). Each pixel is a 32-bit DWORD in 00RRGGBBh format.
  3. Place CELL_MYWALL into gameMap in include/engine_map.inc.

B. How to Add a New Sprite / Entity (e.g. Enemy, Coin, Prop)

Sprite positions use raw world units (1 cell = 64 units), the same scale as playerX/playerY. In your initialization code (e.g. Engine_Init):

mov eax, spriteCount
cmp eax, MAX_SPRITES
jge @F
mov ebx, eax
imul ebx, sizeof SpriteStruct
add ebx, OFFSET spriteList

mov [ebx].SpriteStruct.x, (15 * 64 + 32) ; X in raw world units (cell 15.5)
mov [ebx].SpriteStruct.y, (10 * 64 + 32) ; Y in raw world units (cell 10.5)
mov [ebx].SpriteStruct.textureId, 11     ; texture slot (11 = barrel)
mov [ebx].SpriteStruct.scale, 65536      ; 1.0 scale (16.16 fixed)
inc spriteCount
@@:

Sprites are rendered as textured billboards: back-to-front sorted by distance, per-column depth-tested against depthBuffer, floor-anchored, with distance fog. Texel value 00FF00FFh (magenta, COLOR_CHROMA_KEY) is transparent. textureId 0 samples slot 0 (empty/black) — use it as a debug marker. scale multiplies the projected size (65536 = 1.0, 131072 = 2.0).

C. How to Draw Custom 2D HUD (Health Bar, Text, Dialogs)

Inside Engine_RenderHUD in main.asm:

; Draw Health Bar background (Red)
invoke DrawRect, 10, 40, 100, 10, 00000088h
; Draw Health Bar fill (Green)
invoke DrawRect, 10, 40, playerHealth, 10, 0000FF00h
; Draw Outline
invoke DrawRectOutline, 10, 40, 100, 10, COLOR_WHITE

; Print String
invoke DrawString8x8, 10, 55, OFFSET myMessageStr, COLOR_YELLOW

; Print Number
invoke DrawNumber8x8, 120, 42, playerHealth, COLOR_WHITE

D. Math Rules

  • All angles are integers in range 0..511 (ANGLE_512). Always mask with and eax, ANGLE_MASK (511).
  • Trigonometry: [tableCos + eax*4] and [tableSin + eax*4] return 16.16 signed fixed point (65536 = 1.0).
  • Player and sprite positions (playerX, playerY, SpriteStruct.x/y) are raw world units: 1 cell = 64 units. Convert to cell index with sar eax, 6.
  • Multiplying a raw distance by cos/sin:
    mov eax, playerAngle
    and eax, ANGLE_MASK
    mov ebx, [tableCos + eax*4]
    imul ebx, distance
    sar ebx, 16                  ; result in ebx (raw units)
    

E. Binary Level Format (assets/map.dat) & EblanStudio

The engine supports both procedural default generation and binary level loading. When assets/map.dat exists, LoadMapBinary in include/engine_map.inc automatically loads it:

  • Header (MapHeader, 38 bytes, packed):
    • magic (uint32): 0x4E4C4245 ("EBLN")
    • version (uint16): 5 (readers also accept v1 fixed sprite coordinates and v2/v3/v4 raw-coordinate maps, including early v2 maps with transitions)
    • mapWidth (uint16): 32
    • mapHeight (uint16): 32
    • playerStartX (uint16): cell X coordinate (e.g. 2)
    • playerStartY (uint16): cell Y coordinate (e.g. 2)
    • playerStartAngle (uint16): 0..511 angle
    • spriteCount (uint16): count of sprite entries
    • ceilingColor (uint32): 00RRGGBBh
    • floorColor (uint32): 00RRGGBBh
    • transitionCount (uint16): 0..16 (v1 ignores this formerly reserved field)
    • cutsceneCount (uint16): 0..128 commands (offset 28, v4+)
    • cutsceneStringCount (uint16): 0..32 strings (offset 30, v4+)
    • cutsceneFlags (uint16): bit 0 = autoplay on room entry (offset 32, v4+)
    • lightCount (uint16): 0..13 authored sources (offset 34, v5 only)
    • lightingReserved (uint16): zero (offset 36, v5 only)
  • Grid Data: 32 * 32 = 1024 bytes (row-major order). Cell value = texture slot (1 brick, 2 wood, 3 stone, 4 metal, 5 door, 7 pillar, 8 glass, 9 marble).
  • Sprite Array: spriteCount * 24 bytes (SpriteStruct: x, y in raw world units, textureId, scale 16.16, dist, flags).
  • Transition Array: transitionCount * 42 bytes (LevelTransition in both engine_defs.inc and editor/src/level_format.h). Target names are ASCII basenames, 5..31 bytes including .dat, with a terminating NUL inside targetMap[32]. Put linked maps in the same assets directory. Triggers must be unique and occupy empty, open-door or portal cells; every portal cell needs a transition.
  • Files are validated completely before replacing the active room. Counts above capacity, truncated/extra payload, invalid coordinates/scales, blocked spawns and invalid transition names are rejected. Black ceiling/floor colors are valid. A failed runtime transition keeps the current room; a failed startup load creates the procedural map.
  • Cutscene Array (v4): after transitions, cutsceneCount * 16 bytes; followed by cutsceneStringCount * 96 bytes of NUL-terminated CP1251 strings. No serialized pointers. Earlier versions ignore the new header fields and load an empty track.
  • New saves use v5 without rewriting existing map files until explicitly saved. Sprite positions must remain inside the map; scales accepted by loaders are 0..16.0 (0 keeps the legacy 1.0 behavior).

Human users can design levels visually using run_editor.bat (editor/editor.exe, written in C++17 + ImGui + DirectX 11), with 1-click Playtest (F5). AI agents can directly read or write assets/map.dat following this specification.

F5 copies neighboring maps and resources into a dedicated temporary directory, writes the current unsaved map there under its original filename, validates all reachable destinations, and launches engine.exe "assets\selected-map.dat". It never replaces the source assets/map.dat or changes the editor's current file path. Starting the engine without an argument still loads assets/map.dat.

F. Texture Pack (assets/textures/texturepack.bin)

At startup the engine always generates procedural textures first, then LoadTexturePack tries to override them from assets/textures/texturepack.bin:

  • Header (8 bytes): magic (uint32) = 0x58544245 ("EBTX"), version (uint16) = 1, slotCount (uint16) ≤ 16.
  • Pixels: slotCount * 65536 bytes — slot 0 first, each slot is 128x128 BGRA32. Rebuild the pack from the KAZIK 48-slot atlas (casino3d-hd.bin) or fully procedurally:
python tools\make_texturepack.py

If the pack is missing or invalid, the engine silently keeps procedural textures. The loader stages and validates the complete pack before replacing pixels. Slots 10..15 are sprite slots; CELL_PORTAL uses a separate portalTextureBuffer, so importing slot 10 never changes portals. First import creates missing directories and seeds untouched slots with procedural defaults. Import/reset writes are atomic; an invalid existing pack is preserved and reported to the user.

G. Regression Checks

Run python tools\test_regressions.py on Windows with the build toolchains installed. The scenarios compile the real editor logic and ASM procedures, exercise file I/O, playtest isolation, transitions, keyboard edges, renderer geometry, cutscene validation and editor undo in TEMP, and verify that project assets remain unchanged. C++ preview and ASM camera/angle/fade/subtitle states are compared frame by frame. Use --keep to retain generated framebuffers and fixtures. These checks do not replace a manual UI playtest.

H. Cutscenes (v4)

include/engine_cutscene.inc owns validation, playback and overlays. CutsceneEvent is packed: uint16 tickTimestamp, uint8 opCode, uint8 param8, int32 paramA, int32 paramB, int32 dataId. Time uses 60 Hz ticks, timestamps 0..65535, individual durations 0..3600 ticks. Commands are sorted by timestamp; a nonempty track ends with a single final END.

Opcode paramA paramB dataId / param8
0 END ignored ignored Returns control, retains camera pose
1 WAIT duration ignored Delays dispatch; existing tweens continue
2 MOVE_CAM_TO raw world X raw world Y dataId = duration (0 snaps)
3 PAN_CAM_TO target angle 0..511 duration Shortest arc
4 SHOW_SUBTITLE string index duration (0 persistent) CP1251, up to 95 characters
5 HIDE_SUBTITLE ignored ignored Clears subtitle
6 PLAY_SOUND string index, or -1 to stop ignored param8 bit 0 = loop
7 FADE_SCREEN darkness 0..16 duration 0 clear, 16 black
8 CHANGE_SUBLEVEL transition index ignored Uses existing transition destination/spawn/fade

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.

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.

examples/cutscene_demo/run_demo.bat runs an isolated two-room example with camera waypoints, Russian subtitles, WAV, fades and a scripted transition. Regenerate it with python tools/make_cutscene_demo.py.

I. Embedded Game View

The x64 editor uses a windowless x86 engine.exe --preview:Local\EblanPreview-... process to execute the actual Render3DScene, cinematic overlay and fade procedures. No duplicate raycaster. editor/src/preview_protocol.h and include/engine_preview.inc define a versioned 1,362,408-byte shared-memory mailbox (protocol v2): 128-byte header, 1024 cells, 32 sprites, a 96-byte CP1251 subtitle, 16 texture slots, a 320x240 DIB frame, 13 authored light records and 1024 baked RGB cells. Header state is 0 idle, 1 engine owns payload, 2 editor owns result, 3 shutdown. Publish with InterlockedExchange/xchg; never modify payload while state is 1. All fields are fixed-width; do not put pointers or size_t into the wire layout.

The renderer validates camera and sprite bounds, uploads textures only when their revision changes, and exits if its parent process ends. The editor has process-exit/timeout checks and a retry button, converts DIB padding to opaque alpha, uploads BGRA to DX11 and displays with point sampling at 4:3. Map/inspector changes appear without saving. Timeline playback/scrubbing drives the viewport camera, subtitles and fade. Free-camera inspection affects no authored data. The viewport shows the scene and cinematic overlays; normal game HUD, door interactions and room switching are tested in F5.

tools/test_preview_ipc.py exercises the actual renderer without a visible window. The full regression suite also checks the x86-to-x64-to-DX11 path on a WARP device and compares the live viewport's pixels with a direct ASM rendering. Build engine.exe before running these tests.

J. Game Studio IDE, Entity Archetypes & Combat Subsystems

EblanStudio has evolved from a pure level editor into a Full Game Engine Studio / IDE for retro x86 Assembly (MASM32) games:

  1. Entity Archetypes & AI (include/engine_entity.inc):

    • 0: Prop: static decoration / obstacles.
    • 1: Guard AI: patrolling enemy guard that detects the player within an aggro radius, chases, and deals melee damage.
    • 2: Medkit: collectible healing item (+25 HP up to 100).
    • 3: Ammo Box: collectible ammunition pickup (+20 rounds up to 100).
    • 4: Key: key card for unlocking locked doors.
    • 5: Switch: interactive trigger/lever toggled by proximity action ([E]).
    • 6: Explosive Barrel: destructible barrel that explodes on weapon hit with 60 AOE damage to surrounding entities and fiery screen flash.
    • Packed into SpriteStruct.flags without altering binary map compatibility (sizeof(SpriteEntry) == 24).
  2. Player Combat & Multi-Weapon Arsenal (include/engine_combat.inc):

    • Weapon switching via keys [1], [2], [3]:
      • [1] Боевой нож (Combat Knife): fast melee attack (cooldown 8, 30 damage, no ammo required).
      • [2] Пистолет (Pistol): standard handgun (cooldown 12, 35 damage, 1 ammo).
      • [3] Дробовик (Pump Shotgun): heavy scatter weapon (cooldown 22, 70 damage, 2 ammo, wide spread).
    • Dynamic Muzzle Flash Room Illumination: shooting dynamically brightens ceiling and floor shading across the whole 3D environment for realistic muzzle flare lighting.
    • Center crosshair and hitscan raycasting against active enemy guards and barrels.
    • Damage screen flash (red overlay), death state screen, and instant level restart with [R].
    • In-Game Retro Pause Menu via [ESC]: sleek semi-transparent pause menu with [1] ПРОДОЛЖИТЬ, [2] ЗАНОВО, [3] МИНИ-КАРТА, [4] ВЫХОД В WINDOWS.
    • Comprehensive combat HUD: Active weapon badge with slot indicators [1] [2] [3], Health bar, Ammo counter, Score counter, and Key indicator.
  3. In-Editor MASM32 IDE (F7 / "Код / IDE"):

    • Integrated in-engine code editor (editor/src/editor_code.inl) accessible via F7, Ctrl+B, or the main menu.
    • MASM syntax highlighting (instructions, registers, directives, comments, strings, numbers).
    • Project file navigator (engine_entity.inc, engine_combat.inc, main.asm, etc.).
    • Engine API Reference Catalog: built-in searchable catalog of all engine procedures with 1-click "Вставить" snippet insertion.
    • Real-time search bar (Поиск в коде...) with instant match counter.
    • Script templates (Custom Guard AI, Mega-Medkit, Secret Door Trigger, Explosive Barrel, Custom HUD).
    • Asynchronous 1-click compiler runner (build.bat / ml.exe) with interactive output console and clickable error lines.
  4. Editor UI, 2D Canvas & Project Exporter (editor/src/editor_app.cpp):

    • Modern Game Studio dark theme with Electric Cyan and Neon Amber accents.
    • 2D Canvas Guard Aggro Circles: visualizes guard detection and chase radius on the 2D map.
    • Quick Entity Spawner Palette: 1-click spawn buttons (+ Враг, + Аптечка, + Патроны, + Ключ, + Рычаг, + Бочка, + Проп).
    • Canvas Zoom buttons ([-], [100%], [+]) and pan controls.
    • Project configuration file (project.eblanproj) defining title, author, version, window title, and startup map.
    • "Экспорт автономной игры (.exe)" builds a standalone release folder containing the renamed game executable, packaged assets (maps, textures, sounds), launcher batch file, and readme.
  5. Gameplay Scripting Subsystem & Interactive Modals (include/engine_gameplay.inc):

    • ShowGameToast(pszText, durationTicks, color): floating framed notification banners (auto-centered, custom CP1251 string, colored outline, auto-dismiss).
    • ShowGameDialog(pszSpeaker, pszText): modal dialogue & lore note reader freezing player movement until dismissed with [E] / [ENTER].
    • Extensible engine lifecycle hooks:
      • Game_OnTick: 60 Hz frame tick hook for custom game logic, timers, and puzzle triggers.
      • Game_OnPlayerInteract(entityIdx, entityType): intercepts [E] interactions before default behavior.
      • Game_OnEntityKilled(entityIdx, entityType): triggers on guard death or barrel explosion for boss encounters.
      • Game_OnCustomHUD: custom 2D canvas drawing (health bars, gauges, quest markers).
    • PlayGameSFX(pszRelativePath): safe asynchronous WinMM audio player.
    • Built-in automatic gameplay toasts for keys, medkits, ammo, switches, doors, and explosions.
  6. Game Genres, Peaceful Adventure Mode & Immersive Clean HUD:

    • GAME_MODE_ADVENTURE (0): pure exploration / quest / horror / puzzle mode without weapons, ammo, crosshair, or combat overlays.
    • GAME_MODE_ACTION (1): full combat shooter mode with weapon models, crosshair, health bar, and ammo counter.
    • HUD_STYLE_CLEAN (0): 100% immersive full-screen view (no debug banners, no text collisions, no bottom controls clutter).
    • HUD_STYLE_DEBUG (1): developer mode displaying FPS counter at top-right (X=250), engine title, score, and controls hints.
    • Instant in-game switching:
      • [ESC] -> [2]: toggle weapons / peaceful mode on the fly with toast notifications.
      • [ESC] -> [3]: toggle clean vs debug HUD.
      • [F1] or [H]: quick hotkey to toggle clean HUD vs debug HUD.
    • Editor toolbar 1-click genre switcher: ☮ КВЕСТ / БЕЗ ОРУЖИЯ vs ⚔ ЭКШЕН / С ОРУЖИЕМ.
    • Project settings (project.eblanproj) persist GameMode, ShowWeapons, and ShowCrosshair.
  7. Static Lightmap Baking & Realtime Dynamic Lighting (include/engine_light.inc):

    • BakeMapLighting: precomputes 1024 RGB cells in lightGridRGB from active STATIC sources only, with exact supercover cell-boundary occlusion, approximate distance attenuation and an out-of-place, wall-aware cross filter. lightGrid is a derived grayscale view. Realtime sources are never baked or counted twice.
    • Dynamic point lights (LIGHT_TYPE_POINT) with customizable color, radius, intensity, and organic torch flame flicker (LIGHT_FLAG_FLICKER).
    • Dynamic burst lights (LIGHT_TYPE_BURST): weapon muzzle flare on pistol/shotgun shots and high-intensity fiery explosion blast on explosive barrel detonation.
    • Player directional spotlight flashlight [F]: conical falloff (flashlightConeAngle), distance attenuation, and wall occlusion physics.
    • Dynamic lighting mode cycle [L]:
      • LIGHTING_MODE_NONE (0): classic flat Wolf3D retro fullbright.
      • LIGHTING_MODE_BAKED (1): static baked lightmap shadows and ambient shading.
      • LIGHTING_MODE_DYNAMIC (2): full dynamic physics (point lights + player flashlight + muzzle flares + barrel blasts + lightmap).
    • EblanStudio Studio & Editor Lighting Tools:
      • 1-Click Toolbar Button: 💡 ЗАПЕЧЬ СВЕТ (Bake Lightmap) for instant baking.
      • Toolbar Lighting Mode Switcher: [☀ Свет: ВЫКЛ], [🕯 Свет: ЗАПЕЧЁН], [⚡ Свет: ДИНАМИКА].
      • Toolbar Flashlight Toggle: [🔦 Фонарик: ВКЛ/ВЫКЛ].
      • 2D Canvas Lightmap Heatmap Overlay: [🗺 Карта: ВКЛ/ВЫКЛ] visualizing light and shadow gradients across the level grid.
      • Light Source Entities on 2D Canvas: interactive light markers with RGB tint, glow halo, radius circle, click-to-select, and real-time mouse dragging.
      • Quick Spawner Palette: + Источник света button.
      • Scene Hierarchy: dedicated 💡 Источники света (Lights) tree with selection and "+ Добавить свет".
      • Property Inspector: position X/Y sliders, radius slider, intensity slider, RGB color picker, light type dropdown (Static, Point, Torch flicker, Pulse), wall shadow-casting toggle, and torch flicker toggle.
      • Real-Time 3D Viewport synchronization via EnginePreviewPacket shared-memory protocol.

K. Authored Lighting Format (EBLN v5)

After the v4 cutscene strings, v5 appends LightingSettings (32 bytes), lightCount * 40 bytes of LightSource, and 1024 DWORD baked RGB cells (00RRGGBB, 4096 bytes). Settings are eight DWORDs: mode (0..2), ambient (0..255), flashlight (0/1), flashlight radius (32..2048), intensity (0..255), half cone angle (1..96 angle units), baked-valid (1), reserved (0). Source layouts match EditorLightSource and MASM LightSource. Counts/settings/coordinates/radii/intensities/flags/colors and exact file size are validated before committing a room. v1..v4 load no authored lights and mode NONE; the old reserved header bytes are ignored.

Sources and baked data are map-local and travel with save/load, undo/redo, F5 and standalone exports. Static lighting is baked in the editor with BakeLightingRGB and in ASM with BakeMapLighting; all 1024 RGB cells are regression-compared. Realtime point/pulse/flicker lights use only the live source array on top of the persisted static map. Shadow flags are respected. Slot 0 belongs to the player flashlight, 1..13 to authored sources, 14 to muzzle flare, 15 to explosions; combat never overwrites authored lights. Both wall stripes and sprites receive RGB coefficients. Flat ceiling/floor colors use a camera-cell light sample, not per-pixel floor projection.

Editor light settings are in the property inspector and toolbar. A stale-bake indicator tracks static sources, geometry and ambient, excluding realtime movement. Saving or F5 automatically rebuilds a stale static bake; runtime and preview consume persisted RGB without baking each frame. The IPC v2 header includes flashlight parameters and a deterministic light tick; sources begin at offset 1,357,792 and baked RGB at 1,358,312. Update the C++, ASM and Python layouts together.

Lighting verification (24.10.2026): 117 editor checks and 131 ASM checks passed; all 1024 RGB bake cells match, and both lit and classic viewport framebuffers match direct ASM rendering. Project assets were verified unchanged.