This file provides technical context for working on the Goliath game engine codebase.
Goliath is a handmade game engine following platform abstraction principles. The codebase demonstrates separation between platform-specific code and platform-independent game logic.
┌─────────────────────────────────────┐
│ Platform Layer (SDL/macOS) │
│ - Event handling │
│ - Window/rendering setup │
│ - Audio callbacks │
│ - Controller enumeration │
└────────────┬────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Platform-Independent API │
│ - game_input │
│ - game_memory │
│ - game_offscreen_buffer │
│ - game_sound_output_buffer │
└────────────┬────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Game Logic (goliath.cpp) │
│ - GameUpdateAndRender() │
│ - RenderWeirdGradient() │
│ - GameOutputSound() │
└─────────────────────────────────────┘
src/goliath.h
- Defines all platform-independent data structures
- Core types:
game_input,game_memory,game_state,game_controller_input - Platform services API:
DEBUGPlatformReadEntireFile(),DEBUGPlatformWriteEntireFile() - Memory macros:
Kilobytes(),Megabytes(),Gigabytes()
src/goliath.cpp
- Main game logic entry point:
GameUpdateAndRender() - Graphics:
RenderWeirdGradient()- software rendering demo - Audio:
GameOutputSound()- sine wave tone generator - Game state: Color offsets controlled by input
src/sdl_goliath.cpp (SDL2 cross-platform)
- Window and renderer setup with VSync
- Keyboard event handling →
KeyboardStateglobal - Game controller polling (SDL_GameController API)
- Audio ring buffer and callback (
SDLAudioCallback()) - Input double-buffering with pointer swapping
- Keyboard mapped to Controllers[0], physical controllers to Controllers[1-5]
src/macos/osx_main.mm (macOS native)
- Cocoa/AppKit window and event loop
- Core Audio output queue
- IOKit HID for game controllers
- Keyboard also mapped as virtual controller
game_input Input[2] = {};
game_input *NewInput = &Input[0];
game_input *OldInput = &Input[1];
// Every frame:
// 1. Poll input → write to NewInput
// 2. Call GameUpdateAndRender(NewInput)
// 3. Swap pointers
game_input *Temp = NewInput;
NewInput = OldInput;
OldInput = Temp;Why: Allows game logic to see previous frame's state for edge detection (button pressed/released).
struct sdl_audio_ring_buffer {
int Size;
int WriteCursor; // Where game writes new samples
int PlayCursor; // Where audio callback reads from
void *Data; // Circular buffer
};Flow:
- Audio callback runs in background thread, reads from
PlayCursor - Main loop calculates how much to write based on latency
- Game generates samples via
GameOutputSound() - Platform copies samples to ring buffer at
WriteCursor - Cursors chase each other around the circle
Keyboard input is mapped to Controllers[0] as if it were a game controller:
- WASD → MoveUp/Down/Left/Right
- Arrows → ActionUp/Down/Left/Right
- Q/E → LeftShoulder/RightShoulder
- Space/ESC → Start/Back
IsAnalog = false(keyboard is digital)IsConnected = true(always available)
Physical controllers use indices 1-5.
Game code calls platform services via function pointers:
// Defined by platform, called by game
debug_read_file_result DEBUGPlatformReadEntireFile(char *Filename);
void DEBUGPlatformFreeFileMemory(void *Memory);
bool32 DEBUGPlatformWriteEntireFile(char *Filename, uint32 MemorySize, void *Memory);This keeps game code portable - platform layer provides implementations.
struct game_controller_input {
bool32 IsAnalog; // True if using analog stick
bool32 IsConnected; // Controller is attached
real32 StickAverageX; // Analog stick X (-1.0 to 1.0)
real32 StickAverageY; // Analog stick Y (-1.0 to 1.0)
union {
game_button_state Buttons[13];
struct {
game_button_state MoveUp/Down/Left/Right;
game_button_state ActionUp/Down/Left/Right;
game_button_state LeftShoulder/RightShoulder;
game_button_state Back/Start;
game_button_state Terminator;
};
};
};struct game_button_state {
int HalfTransitionCount; // Number of times state changed this frame
bool32 EndedDown; // Current state (pressed = true)
};Usage: Check EndedDown for current state, HalfTransitionCount for edge detection.
struct game_memory {
bool32 IsInitialized;
uint64 PermanentStorageSize; // Long-lived game state
void *PermanentStorage;
uint64 TransientStorageSize; // Temporary/per-level data
void *TransientStorage;
};Typical allocation:
- Permanent: 64MB
- Transient: 4GB
clang++ \
-DGOLIATH_INTERNAL=1 \
-DGOLIATH_SLOW=1 \
$(sdl2-config --cflags) \
src/goliath.cpp \
src/sdl_goliath.cpp \
$(sdl2-config --libs) \
-o build/goliathFlags:
GOLIATH_INTERNAL: Enables debug file I/O and assertsGOLIATH_SLOW: Enables runtime checks and validation
Creates macOS.app bundle with:
- Main executable
- Info.plist
- Icon resources
- Linked against: Cocoa, IOKit, CoreAudio, AudioToolbox
internal_usage=static(internal linkage)local_persist=static(persistent local variable)global_variable=static(global variable)- Type aliases:
int32,uint32,real32,real64,bool32 Assert()macro: Enabled inGOLIATH_SLOWmodeArrayCount(arr)macro: Returns array element count
- Add to
game_controller_inputunion ingoliath.h - Expand
Buttons[]array size - Map in platform layer (SDL/macOS event handlers)
- Update keyboard mapping if needed
- Use in
GameUpdateAndRender()
- Implement window/rendering setup
- Implement input polling (keyboard + controllers)
- Implement audio output (ring buffer pattern)
- Implement debug file I/O functions
- Allocate
game_memory(mmap/VirtualAlloc) - Call
GameUpdateAndRender()each frame
- Build with
-DGOLIATH_SLOW=1for asserts - Check
printf()output for debug messages - Use
DEBUGPlatformWriteEntireFile()to dump state - Monitor frame timing (MS/frame, FPS, megacycles/frame)
Last commit: 53f7064 - "feat(input): add keyboard support and refactor input system"
- Added keyboard as virtual controller (Controllers[0])
- Refactored button naming (Move*/Action* pattern)
- Added IsConnected flag
- Expanded controller array to 6 slots
- Added GetController() helper function
- Implemented analog-to-digital threshold conversion
- Added D-pad to analog stick mapping
- No hot-plug support (controllers must be connected at startup)
- Single-threaded (except audio callback)
- No asset loading system
- No entity/component system
- Simple test graphics (gradient only)
- Simple test audio (sine wave only)
When extending this codebase:
- Maintain platform abstraction separation
- Keep game code portable (no platform-specific APIs)
- Use platform services pattern for OS features
- Consider memory arena allocators for game state
- Plan for hot-reloading game DLL (handmade pattern)