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
|
const
|
||||||
PowerBins* = [1.0, 1.5, 2.0, 3.0] ## 4 bins; ponytail: fixed array, add runtime config if needed
|
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
|
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
|
type
|
||||||
GunId* = int ## index into the guns seq
|
GunId* = int ## index into the guns seq
|
||||||
@@ -124,12 +125,30 @@ proc bestPower*(t: VirtualTracker, gunId: GunId): (int, float) =
|
|||||||
|
|
||||||
proc bestGun*(t: VirtualTracker): GunId =
|
proc bestGun*(t: VirtualTracker): GunId =
|
||||||
## Pick gun with highest hit rate across all power bins.
|
## 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
|
## ponytail: O(n*bins), fine for small gun counts
|
||||||
var bestRate = -1.0
|
proc bestAmong(t: VirtualTracker, requireMin: bool): GunId =
|
||||||
result = 0
|
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 gunId in 0..<t.fitness.len:
|
||||||
for binIdx in 0..<len(PowerBins):
|
for binIdx in 0..<len(PowerBins):
|
||||||
let r = t.fitness[gunId].bins[binIdx].hitRate()
|
if t.fitness[gunId].bins[binIdx].count >= MinObsBeforeCompete:
|
||||||
if r > bestRate:
|
anyQualifies = true
|
||||||
bestRate = r
|
break
|
||||||
result = gunId
|
if anyQualifies: break
|
||||||
|
|
||||||
|
result = if anyQualifies: t.bestAmong(true) else: t.bestAmong(false)
|
||||||
|
|||||||
Reference in New Issue
Block a user