8.1 KiB
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:
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:
PollInput: queries keyboard state (keyForward,keyBackward,keyStrafeLeft,keyStrafeRight,keyTurnLeft,keyTurnRight,keyAction).UpdatePlayer: applies camera turning, calculates forward/strafe movement vectors, runs wall-sliding collision againstgameMap, checks door interaction.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
spriteListwith depth testing.
- Clears ceiling (
Engine_RenderHUD: draws 2D overlay (title, FPS counter, minimap radar, controls).StretchDIBits: copiesscreenBuffer(320x240 BGRA) to window DC (1280x960).
How to Extend This Engine (Guidelines for AI Agents)
A. How to Add a New Wall Texture
- Open
include/engine_defs.incand define a new cell ID:CELL_MYWALL equ 8 - Open
include/engine_textures.incand generate pixels intotextureBuffer + (8 * TEX_SIZE_BYTES). Each pixel is a 32-bit DWORD in00RRGGBBhformat. - Place
CELL_MYWALLintogameMapininclude/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 withand 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 withsar 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):2(v1 legacy: sprite coords are 16.16 fixed, converted on load)mapWidth(uint16):32mapHeight(uint16):32playerStartX(uint16): cell X coordinate (e.g. 2)playerStartY(uint16): cell Y coordinate (e.g. 2)playerStartAngle(uint16): 0..511 anglespriteCount(uint16): count of sprite entriesceilingColor(uint32): 00RRGGBBhfloorColor(uint32): 00RRGGBBhreserved[12](bytes): reserved padding
- Grid Data:
32 * 32 = 1024bytes (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 * 24bytes (SpriteStruct:x,yin raw world units,textureId,scale16.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 * 65536bytes — 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.