EBLAN_ENGINE/AGENTS.md

261 lines
20 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.

# 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:
```cmd
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:
```masm
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`):
```masm
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`:
```masm
; 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:
```masm
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): `4` (readers also accept v1 fixed sprite coordinates and v2/v3 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 only)
- `cutsceneStringCount` (uint16): 0..32 strings (offset 30, v4 only)
- `cutsceneFlags` (uint16): bit 0 = autoplay on room entry (offset 32, v4 only)
- `reserved[4]` (bytes): reserved padding
- **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 v4 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:
```cmd
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,357,792-byte shared-memory mailbox: 128-byte header, 1024 cells, 32 sprites, a 96-byte CP1251 subtitle, 16 texture slots and a 320x240 DIB frame. 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.