How ujjwalvivek/baremetal works
1. Overall shape
- This repo is a single static Linux/x86-64 executable written entirely in Intel-syntax assembly.
- There is no libc, no dynamic allocation, and no graphics API. The whole application is organized into four runtime subsystems: core game control, terminal/platform I/O, math helpers, and text-mode rendering.
2. Process entry and top-level control
- Execution begins at _start in src/core/entry.asm.
- That module owns bootstrapping, the fixed 60 FPS frame loop, frame timing, and shutdown.
- It coordinates the other subsystems in a strict order: terminal setup → input/render/game initialization → start screen → main loop → restore terminal on exit.
3. Platform boundary: terminal control
- src/platform/terminal.asm is responsible for all terminal state transitions.
- It switches the TTY into raw mode, enters the alternate screen, hides the cursor, clears the display once, and restores everything on exit.
- It also handles terminal resize discovery via TIOCGWINSZ and signal-driven cleanup/resize behavior.
4. Platform boundary: input
- src/platform/input.asm drains keyboard input every frame using nonblocking polling and byte reads.
- It converts raw key bytes into gameplay flags for movement, turn, shoot, use-door, and quit.
- This layer is intentionally small and stateless: the game loop resets and re-derives input state each frame rather than maintaining an input history.
5. Platform boundary: timing
- src/platform/timing.asm abstracts monotonic frame timing and sleep.
- The main loop uses it to enforce the fixed timestep and to keep rendering and simulation synchronized at 60 FPS.
- Any early wakeup from sleep is tolerated; the loop simply continues on the next frame.
6. Game state and simulation core
- src/core/game.asm contains the actual world model and gameplay rules.
- It stores the 32×32 map, player position/angle/health, door state, enemies, and light sources.
- This module is the main simulation boundary: input is translated into movement, combat, door toggles, enemy AI, and per-frame world updates here.
7. World representation and movement
- The map is a fixed grid with encoded cell types for open space, several wall materials, and doors.
- Movement uses collision checks against the grid, with separate X and Y resolution so the player can slide along walls.
- Doors are not just visual; they are active world cells with mutable state that affects both collision and raycasting.
8. Raycasting and visibility
- The rendering view is driven by a DDA raycaster implemented in game.asm.
- For each screen column, it finds the first wall hit, computes perpendicular distance, returns wall type, and provides side information for shading.
- This output feeds the renderer as the primary visibility pipeline and also seeds the Z-buffer used by sprites and hitscan combat.
9. Combat, AI, and lights
- Enemies are simulated in the game core as a small state machine with chase, hurt, and dead states.
- Shooting is hitscan-based: the game uses view-direction math and the render Z-buffer to decide whether an enemy is in front of a wall and within the hit cone.
- Lighting is also game-state driven: static, pulsing, and muzzle-flash point lights influence wall shading during rendering.
10. Math support layer
- src/math/math.asm supplies fixed-point and lookup-table math used throughout the game.
- It replaces floating-point trig with precomputed sin/cos tables and handles integer-to-ASCII conversion for HUD text.
- This module is a shared utility boundary, not a simulation layer; it exists to keep core and render code free of libc-style helpers.
11. Rendering pipeline
- src/render/render.asm owns the frame buffer and all terminal text generation.
- Rendering is multi-pass: raycast columns first, then row-major wall/ceiling/floor output, then sprites, then the gun HUD, minimap, and status line.
- The final output is emitted as one large write(1, ...) per frame, which is the only high-volume I/O path in the program.
12. Rendering data and helpers
- src/render/render_data.asm holds all visual constants: colors, fonts, sprites, HUD strings, and other static art data.
- src/render/render_utils.asm contains low-level buffer and cursor helpers plus drawing primitives for sprites and terminal positioning.
- src/render/render_start.asm is a separate startup-screen renderer, which keeps the intro/title flow isolated from in-game rendering.
13. Data flow across subsystems
- The central runtime flow is: terminal/input state → game update → render state → terminal write.
- The game core produces positions, distances, sprites, enemy states, and lighting; the renderer consumes those values to build the terminal frame.
- The renderer never directly mutates world logic, and the platform layer never interprets game state beyond resize/input/terminal management.
14. Build and deployment shape
- The project is built with nasm and linked statically with ld, producing a Linux-only binary.
- The architecture is designed around predictable memory layout: static .data and .bss storage, no heap, and fixed-size buffers.
- A GitHub Actions release workflow exists, but the runtime architecture itself is entirely local-process and terminal-bound.
Main entry points: entry.asm Entry · render.asm Renderer · game.asm Game · terminal.asm Terminal
Components 10
Core
Process entry, loop control, and game state
Platform
Terminal, input, and timing boundaries
Render
Frame assembly and terminal text output
- Renderer · Builds wall, sprite, HUD, and status output
src/render/render.asm - Art Data · Colors, sprites, fonts, HUD text, and constants
src/render/render_data.asm - Draw Utils · Buffer, cursor, and primitive drawing helpers
src/render/render_utils.asm - Start Screen · Title and startup-screen drawing flow
src/render/render_start.asm
Math
Shared fixed-point and lookup-table helpers
- Math · Trig tables and integer formatting helpers
src/math/math.asm
Connections 14
- Entry → Terminal: setup/restore · Owns terminal lifecycle around execution
- Entry → Input: init/poll · Initializes and refreshes input state each frame
- Entry → Timing: pace frames · Uses monotonic timing and sleep to hold 60 FPS
- Entry → Game: run sim · Coordinates initialization and per-frame game updates
- Entry → Start Screen: show intro · Runs the startup screen before the main loop
- Entry → Renderer: frame output · Drives per-frame rendering and terminal writes
- Input → Game: controls · Converts key bytes into movement and action flags
- Game → Renderer: state feed · Provides positions, visibility, enemies, and lights
- Game → Math: trig/format · Uses lookup tables and numeric formatting helpers
- Renderer → Draw Utils: primitives · Uses low-level buffer and drawing helpers
- Renderer → Art Data: assets · Consumes static art, HUD, and color data
- Renderer → Math: formatting · Uses shared helpers for numeric and HUD text output
- Renderer → Terminal: write tty · Emits the completed frame to the terminal
- Terminal → Entry: resize/cleanup · Feeds terminal state changes back to startup and exit flow