project-mobile-command/docs/GAMEMODULE_IMPLEMENTATION.md
StillHammer 0953451fea Implement 7 modules: 4 core (game-agnostic) + 3 MC-specific
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>
2025-12-02 16:40:54 +08:00

504 lines
15 KiB
Markdown

# 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()`
- `GameModule` class declaration
- Private methods for state updates and event handlers
- MC-specific state variables (drones, expeditions, etc.)
**Architecture**:
```cpp
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)
```cpp
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:
```cpp
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)
```cpp
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)
```cpp
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)
```cpp
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.
```json
{
"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 testing
- `MockTaskScheduler`: Stub implementation
- Message queue simulation
- Published message tracking
**Example Test**:
```cpp
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.h` to 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**:
```cpp
// ❌ 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`:
```json
{
"previous_state": "Expedition",
"new_state": "Combat",
"game_time": 1234.5
}
```
## Hot-Reload Compatibility
### State Preservation
All state is serialized in `getState()`:
```cpp
state->setFloat("gameTime", m_gameTime);
state->setString("currentState", gameStateToString(m_currentState));
state->setInt("combatsWon", m_combatsWon);
// ... + all MC-specific state
```
### Workflow
```bash
# 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)
```cpp
// 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
```bash
# 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
```bash
cmake -B build -G "MinGW Makefiles"
cmake --build build -j4
```
### Hot-Reload Workflow
```bash
# 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
```bash
# 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
1. **Timeline System**: Track year (2022-2025), apply era-specific rules
2. **Fame System**: Award fame for achievements, unlock features
3. **Reputation System**: Track faction relationships
4. **Commander Skills**: Modify combat/expedition outcomes
### Phase 3: Advanced State Machine
1. **State Stack**: Push/pop states (e.g., pause over any state)
2. **Sub-states**: TrainBuilder.SelectingWagon, TrainBuilder.Upgrading
3. **State Data**: Attach context data to states
4. **Transitions Guards**: Conditions for valid transitions
### Phase 4: AI Director
```cpp
// 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):
1. Implement ResourceModule (game-agnostic)
2. Implement StorageModule (game-agnostic)
3. Validate hot-reload workflow with real module changes
4. 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