165 lines
8.1 KiB
Markdown
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.
|