Enhance EvoBot GA gun system #118

Open
opened 2026-08-29 18:56:33 +02:00 by SirStone · 0 comments
Owner

Problem Statement

The EvoBot GA gun system has several architectural improvements identified during a code review. These span performance, correctness, testability, and evolutionary efficiency. Currently the system works but has known limitations in fitness signal quality, long-term exploration, modularity, and computational efficiency that limit peak performance and maintainability.


Solution

Apply six targeted improvements: fix the enemy-motion snapshot error in virtual bullet simulation, add mutation sigma annealing, extract the virtual bullet tracker from the gun module, implement GMM-based warm-starting for the GA, decouple aim computation from GA tick, and clean up redundant input features.


User Stories

Enemy-Motion-Aware Bullet Simulation

  1. As a virtual bullet, I want to be evaluated against the enemy position at arrival time, so that fitness scores reflect actual hit probability.
  2. As a GA candidate, I want my fitness measured against where the enemy will be when my bullet arrives, so that I am not penalized for predicting well but firing at stale data.
  3. As the gun system, I want to thread enemy velocity through to the virtual bullet tracker, so that I can interpolate enemy position at any future tick.
  4. As an observer, I want virtual bullets to use linear enemy motion interpolation, so that fitness scores are accurate for moving targets.

Mutation Sigma Annealing

  1. As a GA operator, I want my mutation sigma to decay over generations, so that I explore widely early and exploit precisely late.
  2. As the GA engine, I want adaptive mutation strength, so that I avoid late-generation destabilization from oversized mutations.
  3. As an experimenter, I want to compare constant vs annealed sigma, so that I can validate the benefit empirically.

Virtual Bullet Tracker Extraction

  1. As a test writer, I want to test the GA evaluation loop in isolation, so that I can verify fitness computation without mocking the entire bot.
  2. As a performance engineer, I want to swap bullet simulators without modifying the gun module, so that I can benchmark against different physics models.
  3. As a module author, I want to understand Gun without reading bullet tracking code, so that the module is maintainable.
  4. As a developer, I want VBulletTracker to have its own type, so that the separation of concerns is explicit in the code.

GA Warm-Starting with GMM Clustering

  1. As a returning battle participant, I want my GA to start from enemy-behavior-aware seeds, so that it converges faster and achieves higher peak fitness.
  2. As an enemy movement analyst, I want past battle data to inform initial populations, so that the GA does not rediscover patterns it has already seen.
  3. As a cold-start handler, I want the system to fall back to random initialization when no history exists, so that functionality is preserved.
  4. As a clustering algorithm, I want to fit a GMM to historical EnemyState distributions, so that I can generate behavior-matched seeds.
  5. As a perturbation operator, I want to add Gaussian noise around GMM centroids, so that initialization seeds have controlled diversity.

Decoupled Aim Computation

  1. As a gun aiming system, I want to compute my input vector once per tick, so that I avoid redundant computations.
  2. As a real-time system, I want aim() to be a pure function with no side effects, so that call sites are easier to reason about.
  3. As a performance optimizer, I want to eliminate the 91 multiplies and additions that aim() currently performs per shot, so that CPU usage drops.

Input Vector Cleanup

  1. As an input designer, I want distance to appear only once in the ANN input, so that the network does not receive conflicting signals.
  2. As a configuration manager, I want feature changes to not break input dimensionality, so that window size changes are safe.

Implementation Decisions

  • Enemy motion interpolation: Add enemyVx, enemyVy and arrivalTicks parameters to stepBullets. Compute enemyXn = enemyX + enemyVx * float64(arrivalTicks) before hit detection. Thread velocity from onScannedBot. Calculate arrivalTicks as ceil(distance / bulletSpeed) in the spawn step.
  • Mutation sigma schedule: Replace constant Sigma = 0.03 with sigma = initialSigma * exp(-generation / annealingHalfLife). Default annealingHalfLife = 50 generations. Keep the current constant as fallback.
  • Tracker extraction: Rename VBulletTracker to VBulletEnvironment. Move to own module. tickGA takes env: var VBulletEnvironment explicitly. Gun owns an instance but not the tracking logic.
  • GMM warm-starting: Store GaussianMixture (means and covariances per cluster) in persistence.nim. On battle start, if history exists, initialize PopSize/4 seeds as centroid + N(0, 0.1 * covariance). Remaining seeds random.
  • Input vector caching: Compute input vector once per tick, pass to tickGA. aim() receives pre-computed vector.
  • Input cleanup: Remove distance from sliding window last feature group. Keep normDistance(currentDist) as sole distance input at index 90. Update toInputVec accordingly.
  • Testing seam: Highest seam is tickGA(inputVec, enemyX, enemyY, gunX, gunY, arenaW, arenaH) -> bool. External behavior: given state, run one generation, return whether new champion installed.
  • Physics seam: Virtual bullet tracking API becomes env.stepBullets(enemyX, enemyY, enemyVx, enemyVy, arenaW, arenaH, tick) -> void. Existing call sites pass enemyVx=0, enemyVy=0 if unavailable.

Testing Decisions

  • Good tests verify external behavior: stepBullets tests verify hit/miss for known trajectories. tickGA tests verify champion installation and population sorting. aim tests verify correct output given input.
  • Modules to test: vbullets (stepBullets with moving enemy, spawnBullet edge cases, hit detection), ga (evolve with mock fitness, population sorting, sigma formula), gun (tickGA with mock tracker, aim with pre-computed vectors), data (toInputVec with partial fill, circular eviction, distance removal).
  • Prior art: All modules have when isMainModule self-checks. GA has toy fitness test. Gun has generation-complete test. Extend these patterns.

Out of Scope

  • CMA-ES integration (deferred, separate issue)
  • NEAT topology evolution (deferred per ADR-0001)
  • Multiple gun types (already exist on disk but not compiled)
  • Real-time enemy prediction models (Kalman filters, particle filters)
  • Cross-battle transfer learning beyond GMM warm-starting
  • Parallelizing the GA loop (currently synchronous in tick loop)
  • GPU-accelerated bullet simulation

Further Notes

  • The sliding window includes distance in its last 3-element group making it appear twice in the 91-dim input. Removing it simplifies input.
  • toInputVec fills zeros for unrecorded window slots. After cleanup, those zeros represent no data rather than zero distance. Make sure this semantic difference is intentional.
  • GMM warm-starting requires persisting clustering state. Start with raw EnemyState snapshots and cluster at runtime.
  • Current GA uses truncation selection with 10% parents and single elite. These are good starting points. If warm-starting causes collapse, add random immigrants.
## Problem Statement The EvoBot GA gun system has several architectural improvements identified during a code review. These span performance, correctness, testability, and evolutionary efficiency. Currently the system works but has known limitations in fitness signal quality, long-term exploration, modularity, and computational efficiency that limit peak performance and maintainability. --- ## Solution Apply six targeted improvements: fix the enemy-motion snapshot error in virtual bullet simulation, add mutation sigma annealing, extract the virtual bullet tracker from the gun module, implement GMM-based warm-starting for the GA, decouple aim computation from GA tick, and clean up redundant input features. --- ## User Stories ### Enemy-Motion-Aware Bullet Simulation 1. As a virtual bullet, I want to be evaluated against the enemy position at arrival time, so that fitness scores reflect actual hit probability. 2. As a GA candidate, I want my fitness measured against where the enemy will be when my bullet arrives, so that I am not penalized for predicting well but firing at stale data. 3. As the gun system, I want to thread enemy velocity through to the virtual bullet tracker, so that I can interpolate enemy position at any future tick. 4. As an observer, I want virtual bullets to use linear enemy motion interpolation, so that fitness scores are accurate for moving targets. ### Mutation Sigma Annealing 5. As a GA operator, I want my mutation sigma to decay over generations, so that I explore widely early and exploit precisely late. 6. As the GA engine, I want adaptive mutation strength, so that I avoid late-generation destabilization from oversized mutations. 7. As an experimenter, I want to compare constant vs annealed sigma, so that I can validate the benefit empirically. ### Virtual Bullet Tracker Extraction 8. As a test writer, I want to test the GA evaluation loop in isolation, so that I can verify fitness computation without mocking the entire bot. 9. As a performance engineer, I want to swap bullet simulators without modifying the gun module, so that I can benchmark against different physics models. 10. As a module author, I want to understand Gun without reading bullet tracking code, so that the module is maintainable. 11. As a developer, I want VBulletTracker to have its own type, so that the separation of concerns is explicit in the code. ### GA Warm-Starting with GMM Clustering 12. As a returning battle participant, I want my GA to start from enemy-behavior-aware seeds, so that it converges faster and achieves higher peak fitness. 13. As an enemy movement analyst, I want past battle data to inform initial populations, so that the GA does not rediscover patterns it has already seen. 14. As a cold-start handler, I want the system to fall back to random initialization when no history exists, so that functionality is preserved. 15. As a clustering algorithm, I want to fit a GMM to historical EnemyState distributions, so that I can generate behavior-matched seeds. 16. As a perturbation operator, I want to add Gaussian noise around GMM centroids, so that initialization seeds have controlled diversity. ### Decoupled Aim Computation 17. As a gun aiming system, I want to compute my input vector once per tick, so that I avoid redundant computations. 18. As a real-time system, I want aim() to be a pure function with no side effects, so that call sites are easier to reason about. 19. As a performance optimizer, I want to eliminate the 91 multiplies and additions that aim() currently performs per shot, so that CPU usage drops. ### Input Vector Cleanup 20. As an input designer, I want distance to appear only once in the ANN input, so that the network does not receive conflicting signals. 21. As a configuration manager, I want feature changes to not break input dimensionality, so that window size changes are safe. --- ## Implementation Decisions - Enemy motion interpolation: Add enemyVx, enemyVy and arrivalTicks parameters to stepBullets. Compute enemyXn = enemyX + enemyVx * float64(arrivalTicks) before hit detection. Thread velocity from onScannedBot. Calculate arrivalTicks as ceil(distance / bulletSpeed) in the spawn step. - Mutation sigma schedule: Replace constant Sigma = 0.03 with sigma = initialSigma * exp(-generation / annealingHalfLife). Default annealingHalfLife = 50 generations. Keep the current constant as fallback. - Tracker extraction: Rename VBulletTracker to VBulletEnvironment. Move to own module. tickGA takes env: var VBulletEnvironment explicitly. Gun owns an instance but not the tracking logic. - GMM warm-starting: Store GaussianMixture (means and covariances per cluster) in persistence.nim. On battle start, if history exists, initialize PopSize/4 seeds as centroid + N(0, 0.1 * covariance). Remaining seeds random. - Input vector caching: Compute input vector once per tick, pass to tickGA. aim() receives pre-computed vector. - Input cleanup: Remove distance from sliding window last feature group. Keep normDistance(currentDist) as sole distance input at index 90. Update toInputVec accordingly. - Testing seam: Highest seam is tickGA(inputVec, enemyX, enemyY, gunX, gunY, arenaW, arenaH) -> bool. External behavior: given state, run one generation, return whether new champion installed. - Physics seam: Virtual bullet tracking API becomes env.stepBullets(enemyX, enemyY, enemyVx, enemyVy, arenaW, arenaH, tick) -> void. Existing call sites pass enemyVx=0, enemyVy=0 if unavailable. ## Testing Decisions - Good tests verify external behavior: stepBullets tests verify hit/miss for known trajectories. tickGA tests verify champion installation and population sorting. aim tests verify correct output given input. - Modules to test: vbullets (stepBullets with moving enemy, spawnBullet edge cases, hit detection), ga (evolve with mock fitness, population sorting, sigma formula), gun (tickGA with mock tracker, aim with pre-computed vectors), data (toInputVec with partial fill, circular eviction, distance removal). - Prior art: All modules have when isMainModule self-checks. GA has toy fitness test. Gun has generation-complete test. Extend these patterns. ## Out of Scope - CMA-ES integration (deferred, separate issue) - NEAT topology evolution (deferred per ADR-0001) - Multiple gun types (already exist on disk but not compiled) - Real-time enemy prediction models (Kalman filters, particle filters) - Cross-battle transfer learning beyond GMM warm-starting - Parallelizing the GA loop (currently synchronous in tick loop) - GPU-accelerated bullet simulation ## Further Notes - The sliding window includes distance in its last 3-element group making it appear twice in the 91-dim input. Removing it simplifies input. - toInputVec fills zeros for unrecorded window slots. After cleanup, those zeros represent no data rather than zero distance. Make sure this semantic difference is intentional. - GMM warm-starting requires persisting clustering state. Start with raw EnemyState snapshots and cluster at runtime. - Current GA uses truncation selection with 10% parents and single elite. These are good starting points. If warm-starting causes collapse, add random immigrants.
SirStone added the wayfinder:map label 2026-08-29 18:56:33 +02:00
SirStone added the ready-for-agent label 2026-08-29 18:59:12 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: SirStone/SirRoboGarage#118