EBLAN_ENGINE/AGENTS.md

165 lines
8.1 KiB
Markdown

# 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**: 16.16 signed fixed-point math (`FixedMul`, `FixedDiv`). Trigonometry via 512-entry precomputed lookup tables (`tableSin`, `tableCos`).
---
## 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): `2` (v1 legacy: sprite coords are 16.16 fixed, converted on load)
- `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
- `reserved[12]` (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`).
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.
### 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.