Overview
OpenFront’s game loop is built around a deterministic tick-based system managed by theGameRunner class. Each tick represents a discrete unit of game time where all game state modifications are applied in a predictable order.
GameRunner Architecture
TheGameRunner class coordinates the entire game execution cycle, managing turns, executions, and game state updates.
Core Components
The core game state instance containing all players, units, and map data
Manages conversion of player intents into execution objects
Callback function that receives game updates after each tick
Initialization
When a game starts, theGameRunner initializes various game systems:
src/core/GameRunner.ts
Initialization creates execution objects for spawning players, bots, nations, and ongoing game systems like win checking and rail network updates.
Tick Execution Cycle
TheexecuteNextTick() method is the heart of the game loop:
Execution Flow
- Guard Checks: Verify no concurrent execution and turns are available
- Intent Conversion: Convert player intents from the current turn into execution objects
- State Update: Execute all pending executions via
game.executeNextTick() - Performance Tracking: Measure tick execution duration
- View Updates: Update player name positions during spawn phase
- Data Collection: Drain packed tile updates and motion plans
- Callback: Send updates to the client
src/core/GameRunner.ts
Turn Management
Turns are queued and processed sequentially:Adds a turn containing player intents to the execution queue
Returns the number of turns waiting to be executed
Turn Structure
Each turn contains an array ofStampedIntent objects representing player actions:
Determinism
The game loop ensures deterministic execution through:- Fixed Tick Order: All executions run in the same order every time
- Pseudorandom Numbers: Seeded random number generator ensures reproducibility
- No External State: All state changes occur through the execution system
- Turn-Based Input: Player actions are batched into turns and processed atomically
Determinism is critical for multiplayer synchronization. All clients running the same sequence of turns will arrive at identical game states.
View Data Updates
The game loop periodically updates view-specific data for rendering:src/core/GameRunner.ts
Name placement calculations are expensive, so they’re only updated during spawn phase (every 2 ticks) or periodically during gameplay (every 30 ticks).
Performance Monitoring
Each tick’s execution time is measured and included in the game update:Error Handling
The game loop includes comprehensive error handling:src/core/GameRunner.ts
Errors during tick execution are caught, logged, and sent to the client through the error callback mechanism.
Related Systems
- Intent/Execution Pattern - How player actions become game state changes
- Pathfinding - Movement calculations during tick execution
- Alliances - Alliance state managed through execution system