fix(gun_harness): random tiebreak + drop AntiSurfer + raise MinObsBeforeCompete
- bestGun: replace first-index-wins argmax with random pick among guns within TieMargin (2%) of best rate. HeadOn at index 0 was silently winning every tie, starving Tsetlin/Linear/etc. - MinObsBeforeCompete 15 -> 50 (Pattern entered competition on noise) - add MinHitRateFloor 0.10: if no gun clears it, fall back to HeadOn instead of selecting the best of a bad field - ModularBot: remove AntiSurfer gun (0% virtual hit rate everywhere), 14 -> 13 guns, renumber ids and selection counters
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
import std/math
|
||||
import std/tables
|
||||
import std/random
|
||||
import gun_interface
|
||||
|
||||
const
|
||||
@@ -12,7 +13,9 @@ const
|
||||
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
|
||||
MinObsBeforeCompete* = 15 ## min observations before a gun×bin enters competition
|
||||
MinObsBeforeCompete* = 50 ## min observations before a gun×bin enters competition
|
||||
TieMargin* = 0.02 ## guns within this hit-rate margin of best are tied
|
||||
MinHitRateFloor* = 0.10 ## if best gun < this, fall back to gun 0 (HeadOn)
|
||||
|
||||
type
|
||||
GunId* = int ## index into the guns seq
|
||||
@@ -168,11 +171,11 @@ proc bestGun*(t: VirtualTracker, targetId: int = -1): GunId =
|
||||
## Guns with fewer than MinObsBeforeCompete observations are skipped
|
||||
## unless every gun is below threshold (then fall back to best of all).
|
||||
## Uses per-enemy fitness when targetId >= 0 and data exists; else aggregate.
|
||||
## Ties (within TieMargin) are broken randomly to avoid index-0 bias.
|
||||
## ponytail: O(n*bins), fine for small gun counts
|
||||
let fit = t.fitnessFor(targetId)
|
||||
proc bestAmong(fit: seq[GunFitness], requireMin: bool): GunId =
|
||||
var bestRate = -1.0
|
||||
result = 0
|
||||
for gunId in 0..<fit.len:
|
||||
var maxCount = 0
|
||||
for binIdx in 0..<len(PowerBins):
|
||||
@@ -180,9 +183,22 @@ proc bestGun*(t: VirtualTracker, targetId: int = -1): GunId =
|
||||
if requireMin and maxCount < MinObsBeforeCompete: continue
|
||||
for binIdx in 0..<len(PowerBins):
|
||||
let r = fit[gunId].bins[binIdx].hitRate()
|
||||
if r > bestRate:
|
||||
bestRate = r
|
||||
result = gunId
|
||||
if r > bestRate: bestRate = r
|
||||
# Floor check: if nothing hits well enough, HeadOn is the safe default
|
||||
if bestRate < MinHitRateFloor: return 0
|
||||
var tied: seq[GunId]
|
||||
for gunId in 0..<fit.len:
|
||||
var maxCount = 0
|
||||
for binIdx in 0..<len(PowerBins):
|
||||
maxCount = max(maxCount, fit[gunId].bins[binIdx].count)
|
||||
if requireMin and maxCount < MinObsBeforeCompete: continue
|
||||
for binIdx in 0..<len(PowerBins):
|
||||
let r = fit[gunId].bins[binIdx].hitRate()
|
||||
if r >= bestRate - TieMargin:
|
||||
tied.add(gunId)
|
||||
break
|
||||
if tied.len == 0: return 0
|
||||
return tied[rand(tied.len - 1)]
|
||||
|
||||
var anyQualifies = false
|
||||
for gunId in 0..<fit.len:
|
||||
|
||||
Reference in New Issue
Block a user