diff --git a/common_libs/gun_harness/virtual_bullets.nim b/common_libs/gun_harness/virtual_bullets.nim index 1ce8f34..389224b 100644 --- a/common_libs/gun_harness/virtual_bullets.nim +++ b/common_libs/gun_harness/virtual_bullets.nim @@ -8,9 +8,10 @@ import gun_interface const PowerBins* = [1.0, 1.5, 2.0, 3.0] ## 4 bins; ponytail: fixed array, add runtime config if needed - WindowSize* = 50 ## rolling window ticks for fitness + WindowSize* = 100 ## rolling window ticks for fitness MaxBullets* = 2048 ## hard cap; ponytail: ring buffer, resize if more guns added - MinHitRate* = 0.40 ## 40% threshold for acceptable power selection + MinHitRate* = 0.40 ## 40% threshold for acceptable power selection + MinObsBeforeCompete* = 15 ## min observations before a gunĂ—bin enters competition type GunId* = int ## index into the guns seq @@ -124,12 +125,30 @@ proc bestPower*(t: VirtualTracker, gunId: GunId): (int, float) = proc bestGun*(t: VirtualTracker): GunId = ## Pick gun with highest hit rate across all power bins. + ## Guns with fewer than MinObsBeforeCompete observations across all bins are + ## skipped unless every gun is below threshold (then fall back to best of all). ## ponytail: O(n*bins), fine for small gun counts - var bestRate = -1.0 - result = 0 + proc bestAmong(t: VirtualTracker, requireMin: bool): GunId = + var bestRate = -1.0 + result = 0 + for gunId in 0.. bestRate: + bestRate = r + result = gunId + + # Try gated selection first; fall back to ungated if nothing qualifies + var anyQualifies = false for gunId in 0.. bestRate: - bestRate = r - result = gunId + if t.fitness[gunId].bins[binIdx].count >= MinObsBeforeCompete: + anyQualifies = true + break + if anyQualifies: break + + result = if anyQualifies: t.bestAmong(true) else: t.bestAmong(false)