EBLAN_ENGINE/AGENTS.md

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:

  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): 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:
python tools\make_texturepack.py

If the pack is missing or invalid, the engine silently keeps procedural textures.