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

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()
  • GameModule class 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 testing
  • MockTaskScheduler: 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.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:

// ❌ 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

  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

// 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