From 5bbc8cbda7e727e66a1adb0b62fb778dcebb4306 Mon Sep 17 00:00:00 2001 From: Davide Cappellini Date: Sun, 20 Sep 2026 11:59:00 +0200 Subject: [PATCH] =?UTF-8?q?fix(gun=5Fharness):=20min=2015=20obs=20before?= =?UTF-8?q?=20competing,=20window=2050=E2=86=92100,=20KNN=20min=20k=3D5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- common_libs/gun_harness/virtual_bullets.nim | 35 ++++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) 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)