fix(gun_harness): min 15 obs before competing, window 50→100, KNN min k=5
Gun selector now gates competition: guns with <15 total observations across all power bins sit out until at least one gun qualifies. Falls back to ungated selection if no gun reaches threshold, preventing cold-start stalls. Sliding window increased from 50 to 100 ticks to reduce switching noise. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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..<t.fitness.len:
|
||||
var maxCount = 0
|
||||
for binIdx in 0..<len(PowerBins):
|
||||
maxCount = max(maxCount, t.fitness[gunId].bins[binIdx].count)
|
||||
if requireMin and maxCount < MinObsBeforeCompete: continue
|
||||
for binIdx in 0..<len(PowerBins):
|
||||
let r = t.fitness[gunId].bins[binIdx].hitRate()
|
||||
if r > bestRate:
|
||||
bestRate = r
|
||||
result = gunId
|
||||
|
||||
# Try gated selection first; fall back to ungated if nothing qualifies
|
||||
var anyQualifies = false
|
||||
for gunId in 0..<t.fitness.len:
|
||||
for binIdx in 0..<len(PowerBins):
|
||||
let r = t.fitness[gunId].bins[binIdx].hitRate()
|
||||
if r > 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)
|
||||
|
||||
Reference in New Issue
Block a user