Overview
OpenFront uses an Intent/Execution pattern to separate player actions from game state modifications. This pattern ensures deterministic gameplay, enables replay functionality, and maintains clean separation between client input and server logic.Architecture
Intent Layer
Intents represent player actions in a serializable, validated format. They are:- Immutable: Once created, intents cannot be modified
- Serializable: Can be sent over network or saved to disk
- Validated: Schema-validated using Zod before processing
- Timestamped: Tagged with client ID and turn information
Execution Layer
Executions are stateful objects that modify game state over multiple ticks. They:- Process Incrementally: Can take multiple ticks to complete
- Maintain State: Track progress of ongoing operations
- Can Be Cancelled: Support interruption and cleanup
- Modify Game State: Direct access to game objects
Intent Types
OpenFront supports various intent types defined insrc/core/Schemas.ts:
Executor: Intent to Execution Conversion
TheExecutor class (src/core/execution/ExecutionManager.ts:32) converts intents into execution objects:
src/core/execution/ExecutionManager.ts
Invalid intents (e.g., from disconnected players) are converted to
NoOpExecution to maintain deterministic execution order.Execution Interface
All executions implement theExecution interface:
Called once when the execution is added to the game. Performs validation and setup.
Called every game tick while the execution is active. Performs incremental state updates.
Returns whether the execution should continue processing. Inactive executions are removed.
Returns whether this execution runs during the spawn phase or only during normal gameplay.
Example: Attack Execution
TheAttackExecution class demonstrates the pattern in action:
src/core/execution/AttackExecution.ts
Key Features
- Validation in Init: Checks target validity and alliance status before starting
- Incremental Progress: Conquers tiles over multiple ticks based on troop count
- State Management: Tracks attack progress, border tiles, and troop losses
- Dynamic Cancellation: Can be cancelled if alliance forms during attack
- Performance Optimization: Uses heap data structure for efficient tile selection
The attack execution demonstrates how complex, multi-tick operations are managed through the execution pattern.
Execution Lifecycle
- Creation: Intent converted to execution by
Executor - Registration: Added to game’s execution queue
- Initialization:
init()called once, performs validation - Processing:
tick()called every frame while active - Completion: Sets
active = falsewhen done - Removal: Game removes inactive executions
Benefits of the Pattern
Determinism
All state changes go through executions, ensuring reproducible game state:- Same intents → Same executions → Same results
- Critical for multiplayer synchronization
- Enables replay and time-travel debugging
Separation of Concerns
- Client: Creates intents, doesn’t know game rules
- Network: Serializes intents, doesn’t execute logic
- Server: Converts intents to executions
- Game: Executes changes, doesn’t know network
Extensibility
Adding new actions requires:- Define new intent type in schemas
- Add case to
Executor.createExec() - Implement new execution class
- No changes to core game loop
The Intent/Execution pattern is the foundation of OpenFront’s architecture. Understanding it is essential for implementing new features.
Common Execution Types
Instant Executions
Complete in a single tick:AllianceRequestExecution- Sends alliance requestEmojiExecution- Sends emoji messageDonateGoldExecution- Transfers gold
Ongoing Executions
Run every tick:WinCheckExecution- Checks win conditionsRecomputeRailClusterExecution- Updates rail network
Multi-tick Executions
Process over many ticks:AttackExecution- Conquers tiles incrementallyTransportShipExecution- Moves units across waterNationExecution- AI player behavior
Related Systems
- Game Loop - How executions are processed each tick
- Pathfinding - Used by movement executions
- Alliances - Alliance-related executions