# Plan: Implement Thread-Local Caching in APDCache

## Overview
Move the thread-local caching optimization from scattered locations into the `ActionPointDistancesCache` class itself, using the existing `FullCacheKey` infrastructure. This will provide automatic performance benefits to all 12+ call sites throughout the AI system.

## Implementation Plan

### Phase 1: Enhance APDCache with Thread-Local Caching

#### 1.1 Modify ActionPointDistancesCache.hpp
```cpp
class ActionPointDistancesCache {
private:
    // Existing shared cache infrastructure...
    
    // Thread-local cache using existing FullCacheKey infrastructure
    using TLSCache = std::unordered_map<FullCacheKey, shared_ptr<ActionPointDistances>, FullCacheKeyHash>;
    static thread_local TLSCache tlsCache;
    
    // Helper to build cache key
    static FullCacheKey MakeCacheKey(
        const MapId& mapId,
        const BattalionTypeSPtr& battalionType,
        bool includeBravingWater,
        int braveWaterActionPointCost);

public:
    // Enhanced Get method with thread-local caching
    auto Get(
        const HexMap* map,
        const MapId& mapId,
        const BattalionTypeSPtr& battalionType,
        bool includeBravingWater,
        int braveWaterActionPointCost = -1) -> shared_ptr<ActionPointDistances>;
        
    // Optional: Cache management methods
    static void ClearThreadLocalCache();
    static size_t GetThreadLocalCacheSize();
};
```

#### 1.2 Modify ActionPointDistancesCache.cpp
```cpp
// Thread-local cache definition
thread_local ActionPointDistancesCache::TLSCache ActionPointDistancesCache::tlsCache;

auto ActionPointDistancesCache::MakeCacheKey(
    const MapId& mapId,
    const BattalionTypeSPtr& battalionType,
    bool includeBravingWater,
    int braveWaterActionPointCost) -> FullCacheKey {
    return FullCacheKey{
        mapId,
        static_cast<int>(battalionType->typeId),
        includeBravingWater,
        braveWaterActionPointCost >= 0 ? braveWaterActionPointCost : 0
    };
}

auto ActionPointDistancesCache::Get(
    const HexMap* map,
    const MapId& mapId,
    const BattalionTypeSPtr& battalionType,
    bool includeBravingWater,
    int braveWaterActionPointCost) -> shared_ptr<ActionPointDistances> {
    
    // Create cache key
    auto cacheKey = MakeCacheKey(mapId, battalionType, includeBravingWater, braveWaterActionPointCost);
    
    // Check thread-local cache first
    auto it = tlsCache.find(cacheKey);
    if (it != tlsCache.end()) {
        return it->second;
    }
    
    // Fall back to shared cache (existing implementation)
    auto result = GetFromSharedCache(map, mapId, battalionType, includeBravingWater, braveWaterActionPointCost);
    
    // Cache in thread-local cache
    tlsCache[cacheKey] = result;
    
    return result;
}

void ActionPointDistancesCache::ClearThreadLocalCache() {
    tlsCache.clear();
}

size_t ActionPointDistancesCache::GetThreadLocalCacheSize() {
    return tlsCache.size();
}
```

### Phase 2: Remove Redundant Caching

#### 2.1 Remove PreCachedAPDs from AIScoreCalculator.cpp
- Delete the entire `PreCachedAPDs` struct (lines ~79-150)
- Change `AttackerUnitsScore()` back to direct `apdCache->Get()` calls
- Remove thread-local variable and UpdateIfNeeded call
- Update callers to use `apdCache->Get()` directly instead of `cachedAPDs.GetRegular/GetBraving()`

#### 2.2 Simplify AIAttackGroups.cpp  
- Remove the `apdByBattType` local caching map
- Change the function-local caching loop back to direct `apdCache->Get()` calls per unit
- The new APDCache thread-local caching will handle the optimization automatically

### Phase 3: Testing & Validation

#### 3.1 Performance Testing
- Measure AI performance before/after the change
- Verify thread-local cache hit rates using `GetThreadLocalCacheSize()`
- Confirm that 10+ call sites get automatic optimization
- Profile to ensure no regression in memory usage

#### 3.2 Functional Testing  
- Run all AI tests: `bazel test //src/test/cpp/net/eagle0/shardok/ai/...`
- Test multi-threaded scenarios to ensure thread safety
- Verify cache isolation between threads

#### 3.3 Memory Management Testing
- Monitor thread-local cache growth over time
- Test cache clearing functionality
- Consider automatic cache size limits if needed

### Phase 4: Documentation & Cleanup

#### 4.1 Update Documentation
- Update `AI_PERFORMANCE_FIX_PRECACHED_APDS.md` to reflect architectural change
- Document the new APDCache caching behavior
- Add performance benchmarks

#### 4.2 Code Cleanup
- Remove old performance fix documentation if no longer relevant
- Clean up any remaining direct APDCache optimization attempts

## Expected Benefits

### Performance
- **Automatic optimization for 12+ call sites** throughout AI system
- **Zero code changes required** for existing APDCache::Get() callers
- **Thread-safe** with per-thread cache isolation
- **Consistent caching behavior** across entire codebase

### Architecture  
- **Single responsibility**: APDCache handles its own optimization
- **Eliminates code duplication**: No more scattered caching patterns
- **Uses existing infrastructure**: Leverages FullCacheKey design
- **Clean abstraction**: Consumers just call Get(), caching is transparent

### Maintenance
- **Centralized optimization**: One place to tune caching behavior
- **Easier debugging**: All APD caching logic in one location  
- **Future-proof**: New APDCache callers automatically get optimization

## Implementation Risks & Mitigations

### Risk: Thread-Local Memory Growth
- **Mitigation**: Add cache size monitoring and optional clearing API
- **Monitoring**: Track cache sizes in performance tests

### Risk: Changed Shared Cache Access Patterns
- **Mitigation**: Thorough testing of existing shared cache behavior
- **Validation**: Ensure GetFromSharedCache still works correctly

### Risk: Performance Regression
- **Mitigation**: Benchmark before/after implementation
- **Rollback**: Keep optimization as optional flag initially

## Implementation Order
1. **Phase 1**: Implement enhanced APDCache (non-breaking change)
2. **Phase 3**: Test performance and validate behavior  
3. **Phase 2**: Remove redundant caching (breaking change for our code)
4. **Phase 4**: Documentation and cleanup

This approach ensures we can validate the APDCache enhancement before removing existing optimizations.

## Current State Analysis

### Already Thread-Local Caching:
1. **AIScoreCalculator.cpp** - Our recent `PreCachedAPDs` addition
2. **FixedActionPointDistances.cpp** - Uses thread-local for file I/O buffering (not APDCache results)

### Function-Local Per-Battalion Caching:
1. **AIAttackGroups.cpp** - Uses `apdByBattType` map for function-scoped caching

### No Caching (Direct APDCache::Get calls):
- AIWaterCrossingCalculator.cpp
- AICommandFilter.cpp  
- AIDistanceDebuf.cpp
- AIVictoryConditionScoreCalculator.cpp
- AIAttackerStrategySelector.cpp
- AIDefenderStrategySelector.cpp
- AIVictoryConditionScoreCalculator.cpp
- And 5+ other files

**Impact**: This optimization will automatically benefit 10+ call sites that currently do repeated APDCache::Get calls with no caching optimization.