Core Modules (game-agnostic, reusable for WarFactory): - ResourceModule: Inventory, crafting system (465 lines) - StorageModule: Save/load with pub/sub state collection (424 lines) - CombatModule: Combat resolver, damage/armor/morale (580 lines) - EventModule: JSON event scripting with choices/outcomes (651 lines) MC-Specific Modules: - GameModule v2: State machine + event subscriptions (updated) - TrainBuilderModule: 3 wagons, 2-axis balance, performance malus (530 lines) - ExpeditionModule: A→B expeditions, team management, events integration (641 lines) Features: - All modules hot-reload compatible (state preservation) - Pure pub/sub architecture (zero direct coupling) - 7 config files (resources, storage, combat, events, train, expeditions) - 7 test suites (GameModuleTest: 12/12 PASSED) - CMakeLists.txt updated for all modules + tests Total: ~3,500 lines of production code + comprehensive tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
15 KiB
GameModule Implementation - State Machine & Event Subscriptions
Date: December 2, 2025 Version: 0.1.0 Status: Implementation Complete
Overview
This document describes the implementation of the GameModule state machine and event subscription system for Mobile Command. The implementation follows the game-agnostic architecture principles defined in ARCHITECTURE.md, where core modules remain generic and GameModule applies Mobile Command-specific logic via pub/sub.
Files Modified/Created
1. src/modules/GameModule.h (NEW)
Purpose: Header file defining the GameModule class, state machine, and MC-specific game state.
Key Components:
enum class GameState- 6 states (MainMenu, TrainBuilder, Expedition, Combat, Event, Pause)- Helper functions:
gameStateToString(),stringToGameState() GameModuleclass declaration- Private methods for state updates and event handlers
- MC-specific state variables (drones, expeditions, etc.)
Architecture:
namespace mc {
enum class GameState { MainMenu, TrainBuilder, Expedition, Combat, Event, Pause };
class GameModule : public grove::IModule {
// State machine
GameState m_currentState;
void updateMainMenu(float deltaTime);
void updateTrainBuilder(float deltaTime);
// ... etc
// Event handlers - MC-SPECIFIC LOGIC HERE
void onResourceCraftComplete(const grove::IDataNode& data);
void onCombatStarted(const grove::IDataNode& data);
// ... etc
// MC-specific game state
std::unordered_map<std::string, int> m_availableDrones;
int m_expeditionsCompleted;
int m_combatsWon;
};
}
2. src/modules/GameModule.cpp (UPDATED)
Purpose: Full implementation of GameModule with state machine and event subscriptions.
Key Features:
State Machine (Lines 70-110)
void GameModule::process(const grove::IDataNode& input) {
// Update game time
m_gameTime += deltaTime;
// Process incoming messages from core modules
processMessages();
// State machine update
switch (m_currentState) {
case GameState::MainMenu: updateMainMenu(deltaTime); break;
case GameState::TrainBuilder: updateTrainBuilder(deltaTime); break;
case GameState::Expedition: updateExpedition(deltaTime); break;
case GameState::Combat: updateCombat(deltaTime); break;
case GameState::Event: updateEvent(deltaTime); break;
case GameState::Pause: updatePause(deltaTime); break;
}
}
Event Subscriptions Setup (Lines 39-68)
Subscribes to all core module events:
- ResourceModule:
resource:craft_complete,resource:inventory_low,resource:inventory_changed - StorageModule:
storage:save_complete,storage:load_complete,storage:save_failed - CombatModule:
combat:started,combat:round_complete,combat:ended - EventModule:
event:triggered,event:choice_made,event:outcome
Message Processing (Lines 112-155)
Pull-based message consumption from IIO:
void GameModule::processMessages() {
while (m_io->hasMessages() > 0) {
auto msg = m_io->pullMessage();
// Route to appropriate handler
if (msg.topic == "resource:craft_complete") {
onResourceCraftComplete(*msg.data);
}
// ... etc
}
}
MC-Specific Event Handlers (Lines 213-355)
Example 1: Drone Crafting (Lines 308-327)
void GameModule::handleDroneCrafted(const std::string& droneType) {
// MC-SPECIFIC: Track available drones for expeditions
m_availableDrones[droneType]++;
// MC-SPECIFIC: Publish to expedition system
auto droneData = std::make_unique<grove::JsonDataNode>("drone_available");
droneData->setString("drone_type", droneType);
droneData->setInt("total_available", m_availableDrones[droneType]);
m_io->publish("expedition:drone_available", std::move(droneData));
// MC-SPECIFIC: Fame bonus (future)
// if (m_timeline.year >= 2024) { publishFameGain("drone_crafted", 5); }
}
Example 2: Low Fuel Warning (Lines 329-346)
void GameModule::handleLowSupplies(const std::string& resourceId) {
// MC-SPECIFIC: Critical resource checks
if (resourceId == "fuel_diesel" && !m_lowFuelWarningShown) {
spdlog::warn("[GameModule] MC: CRITICAL - Low on fuel! Return to train recommended.");
m_lowFuelWarningShown = true;
}
if (resourceId == "ammunition_9mm") {
spdlog::warn("[GameModule] MC: Low on ammo - avoid combat or resupply!");
}
}
Example 3: Combat Victory (Lines 283-296)
void GameModule::onCombatEnded(const grove::IDataNode& data) {
bool victory = data.getBool("victory", false);
if (victory) {
m_combatsWon++; // MC-SPECIFIC: Track victories
handleCombatVictory(data);
}
// MC-SPECIFIC: Transition back to expedition
transitionToState(GameState::Expedition);
}
Hot-Reload Support (Lines 365-419)
Preserves all state including:
- Game time, frame count, state time
- Current and previous states
- MC-specific counters (expeditions, combats won)
- Available drones map
- Warning flags
3. config/game.json (UPDATED)
Purpose: GameModule configuration.
{
"version": "0.1.0",
"game": {
"name": "Mobile Command",
"targetFrameRate": 10
},
"debug": {
"logLevel": "debug",
"showFrameCount": true
},
"initialState": "MainMenu",
"tickRate": 10,
"debugMode": true
}
New Fields:
initialState: Starting game state (MainMenu/TrainBuilder/etc.)tickRate: Game loop frequency (10 Hz)debugMode: Enable debug logging
4. tests/GameModuleTest.cpp (NEW)
Purpose: Comprehensive unit tests for GameModule.
Test Coverage (12 tests):
| Test # | Name | Purpose |
|---|---|---|
| 1 | InitialStateIsMainMenu | Verify initial state configuration |
| 2 | StateTransitionsWork | Test state machine transitions |
| 3 | EventSubscriptionsSetup | Verify all topics subscribed |
| 4 | GameTimeAdvances | Test time progression |
| 5 | HotReloadPreservesState | Test state serialization |
| 6 | DroneCraftedTriggersCorrectLogic | Test MC-specific drone logic |
| 7 | LowFuelWarningTriggered | Test MC-specific fuel warning |
| 8 | CombatVictoryIncrementsCounter | Test combat tracking |
| 9 | EventTriggersStateTransition | Test event state changes |
| 10 | ModuleTypeIsCorrect | Test module identification |
| 11 | ModuleIdleInMainMenu | Test idle state detection |
| 12 | MultipleMessagesProcessedInSingleFrame | Test batch processing |
Mock Infrastructure:
MockIIO: Full IIO implementation for testingMockTaskScheduler: Stub implementation- Message queue simulation
- Published message tracking
Example Test:
TEST_F(GameModuleTest, DroneCraftedTriggersCorrectLogic) {
// Simulate drone craft completion
auto craftData = std::make_unique<JsonDataNode>("craft_complete");
craftData->setString("recipe", "drone_recon");
craftData->setInt("quantity", 1);
mockIO->pushMessage("resource:craft_complete", std::move(craftData));
// Process the message
auto input = std::make_unique<JsonDataNode>("input");
input->setFloat("deltaTime", 0.1f);
module->process(*input);
// Check that expedition:drone_available was published
EXPECT_TRUE(mockIO->hasPublished("expedition:drone_available"));
}
5. CMakeLists.txt (UPDATED)
Changes:
- Added
GameModule.hto GameModule target - Added include directories for GameModule
- Added Google Test via FetchContent
- Added GameModuleTest executable
- Added test targets:
test_game,test_all
Architecture Compliance
Game-Agnostic Core Modules ✅
The implementation strictly follows the architecture principle:
Core modules (ResourceModule, CombatModule, etc.):
- ❌ NO knowledge of "train", "drone", "expedition" (MC concepts)
- ✅ Publish generic events:
resource:craft_complete,combat:ended - ✅ Pure functionality: inventory, crafting, combat formulas
- ✅ Configured via JSON
- ✅ Reusable for WarFactory or other games
GameModule (MC-SPECIFIC):
- ✅ CAN reference "train", "drone", "expedition" (MC concepts)
- ✅ Subscribes to core module events
- ✅ Applies MC-specific interpretations
- ✅ Contains game flow orchestration
- ✅ Fully decoupled via pub/sub
Example Flow: Drone Crafting
1. ResourceModule (game-agnostic):
- Completes craft of "drone_recon"
- Publishes: resource:craft_complete { recipe: "drone_recon", quantity: 1 }
- Has NO idea what a "drone" is or why it matters
2. GameModule (MC-specific):
- Receives resource:craft_complete
- Recognizes recipe starts with "drone_"
- MC LOGIC: Adds to available drones for expeditions
- MC LOGIC: Publishes expedition:drone_available
- MC LOGIC: Could award fame if year >= 2024
Result: Core module stays generic, MC logic in GameModule
Pub/Sub Decoupling ✅
No Direct Coupling:
// ❌ BAD (direct coupling)
ResourceModule* resources = getModule<ResourceModule>();
resources->craft("drone_recon");
// ✅ GOOD (pub/sub)
auto craftRequest = std::make_unique<JsonDataNode>("request");
craftRequest->setString("recipe", "drone_recon");
m_io->publish("resource:craft_request", std::move(craftRequest));
Benefits:
- Modules can be hot-reloaded independently
- ResourceModule can be reused in WarFactory unchanged
- Easy to mock for testing
- Clear event contracts
State Machine Design
States
| State | Purpose | MC-Specific Behavior |
|---|---|---|
| MainMenu | Initial menu, load/new game | No game time progression |
| TrainBuilder | Configure train wagons | Balance calculations, wagon upgrades |
| Expedition | Team out scavenging | Track progress, trigger events |
| Combat | Battle in progress | Monitor combat, apply MC rules |
| Event | Player making choice | Wait for choice, apply MC consequences |
| Pause | Game paused | Freeze all progression |
State Transitions
MainMenu → TrainBuilder (new game/continue)
TrainBuilder → Expedition (launch expedition)
Expedition → Combat (encounter enemies)
Expedition → Event (trigger event)
Combat → Expedition (combat ends)
Event → Expedition (choice made)
Any → Pause (player pauses)
Pause → Previous State (resume)
Transition Events
All transitions publish game:state_changed:
{
"previous_state": "Expedition",
"new_state": "Combat",
"game_time": 1234.5
}
Hot-Reload Compatibility
State Preservation
All state is serialized in getState():
state->setFloat("gameTime", m_gameTime);
state->setString("currentState", gameStateToString(m_currentState));
state->setInt("combatsWon", m_combatsWon);
// ... + all MC-specific state
Workflow
# 1. Game running
./build/mobilecommand.exe
# 2. Edit GameModule.cpp (add feature, fix bug)
# 3. Rebuild module only (fast)
cmake --build build --target GameModule
# 4. Module auto-reloads with state preserved
# Player continues playing without interruption
Test Coverage
Test #5 HotReloadPreservesState validates:
- ✅ Game time preserved
- ✅ Frame count preserved
- ✅ State machine state preserved
- ✅ MC-specific counters preserved
Testing Strategy
Unit Tests (tests/GameModuleTest.cpp)
- Mock IIO: Simulate core module events
- Mock TaskScheduler: Stub implementation
- Isolated Tests: No dependencies on other modules
- Fast Execution: All tests run in < 1 second
Integration Tests (Future)
// Example: Full game flow test
TEST(Integration, CompleteGameLoop) {
// 1. Start in MainMenu
// 2. Transition to TrainBuilder
// 3. Launch expedition
// 4. Trigger combat
// 5. Win combat
// 6. Return to train
// 7. Save game
// 8. Load game
// 9. Verify state restored
}
Manual Testing
# Build and run
cmake -B build -G "MinGW Makefiles"
cmake --build build -j4
cd build && ./mobilecommand.exe
# Run tests
cmake --build build --target test_game
cmake --build build --target test_all
Build Instructions
Initial Build
cmake -B build -G "MinGW Makefiles"
cmake --build build -j4
Hot-Reload Workflow
# Terminal 1: Run game
cd build && ./mobilecommand.exe
# Terminal 2: Edit and rebuild
# Edit src/modules/GameModule.cpp
cmake --build build --target GameModule
# Module reloads automatically in Terminal 1
Run Tests
# GameModule tests only
cmake --build build --target test_game
# All tests
cmake --build build --target test_all
# Or using CTest
cd build && ctest --output-on-failure
Future Enhancements
Phase 2: Full Gameplay
- Timeline System: Track year (2022-2025), apply era-specific rules
- Fame System: Award fame for achievements, unlock features
- Reputation System: Track faction relationships
- Commander Skills: Modify combat/expedition outcomes
Phase 3: Advanced State Machine
- State Stack: Push/pop states (e.g., pause over any state)
- Sub-states: TrainBuilder.SelectingWagon, TrainBuilder.Upgrading
- State Data: Attach context data to states
- Transitions Guards: Conditions for valid transitions
Phase 4: AI Director
// GameModule orchestrates AI director events
void GameModule::updateExpedition(float deltaTime) {
// MC-SPECIFIC: Check conditions for AI director events
if (m_gameTime - m_lastEventTime > 600.0f) { // 10 minutes
float dangerLevel = calculateDangerLevel();
triggerAIDirectorEvent(dangerLevel);
}
}
Validation Checklist
Based on the requirements from the task:
- ✅ State machine functional (6 states)
- ✅ Subscribes to core module topics (12 topics)
- ✅ MC-specific logic in subscriptions (drone, fuel, combat)
- ✅ Hot-reload works (getState/setState implemented)
- ✅ Tests pass (12 tests, all passing)
- ✅ Existing functionality preserved (backward compatible)
- ✅ Files created/updated:
- ✅ GameModule.h (new)
- ✅ GameModule.cpp (updated)
- ✅ game.json (updated)
- ✅ GameModuleTest.cpp (new)
- ✅ CMakeLists.txt (updated)
Key Takeaways
1. Clear Separation of Concerns
Core Modules: Generic, reusable, data-driven GameModule: MC-specific, orchestration, game flow
2. Pub/Sub Decoupling
All communication via IIO topics, no direct module references.
3. MC Logic Centralized
All Mobile Command-specific interpretations live in GameModule event handlers.
4. Hot-Reload Ready
Full state serialization enables seamless development workflow.
5. Testable
Mock IIO allows isolated unit testing without dependencies.
Conclusion
The GameModule implementation successfully adds a state machine and event subscription system while maintaining strict adherence to the game-agnostic architecture. Core modules remain reusable, and all Mobile Command-specific logic is centralized in GameModule through pub/sub event handlers.
Next Steps (from PROTOTYPE_PLAN.md Phase 1):
- Implement ResourceModule (game-agnostic)
- Implement StorageModule (game-agnostic)
- Validate hot-reload workflow with real module changes
- Add UI layer for state visualization
Implementation Date: December 2, 2025 Status: ✅ Complete and Ready for Testing Files: 5 files created/modified Lines of Code: ~750 lines (including tests) Test Coverage: 12 unit tests, all passing