a73de13458
The user's plan: "separate racks for melee and 1v1, so the bot switches from those based on the situation, and we can put the guns we want in one or both racks." MECHANISM - `RackMode` (rm1v1/rmMelee) derived from SERVER TRUTH: `rackMode(enemyCount)` = 1v1 when the count is 1, melee otherwise. This is the SAME `getEnemyCount()` value the radar already uses, so there is now ONE definition of the mode. (Using the tracker's known-enemy count was a previous bug in the radar: it read 1 before the second enemy was scanned.) - `RackMembership` per gun: both (default) | 1v1 | melee | off. - The selector ranks only admitted guns - including the floor path and the incumbent-hysteresis path. - Empty filtered set FALLS BACK to the full rack, so the bot can never end up with no gun. - Env-overridable at process start, no rebuild: `TR_RACK_<GUN>` for all 14 guns (TR_RACK_HEADON, TR_RACK_LINEAR, ... TR_RACK_TMSELECT), values both|1v1|melee|off. Empty/unknown -> both + a stderr warning, never fatal. - `[rack] mode=<1v1|melee> active=<guns> overrides=<...>` logged once per mode change, never per tick. DEFAULT IS UNCHANGED: every gun ships `rmBoth`, so behaviour is byte-identical until the user re-racks anything. Verified by the unit test's default-config selection parity (RNG draw for RNG draw) and by `test_gun_harness` 39 and acceptance 12/12. `chooseFromFit` iterates the admitted list in ascending id order, so the random tie-break draws are unchanged. NO TUNING DONE, deliberately: we had no per-gun melee hit-rate data, and an earlier 15-paired-run experiment found pruning neutral-to-negative on hit rate (p=0.57/0.21). So all guns stay `both` and the membership pass waits for data. PER-MODE DATA PLUMBING (this is what unblocks that pass): per-gun real shot accounting is now split by the rack in force at fire time, adding to gun_stats.jsonl: realShots1v1, realHits1v1, realHitRate1v1, realShotsMelee, realHitsMelee, realHitRateMelee. Verification: test_rack_membership 38/38 (new, pure, no battle); test_gun_harness 39, test_vbullet_metric 11, test_power_selection 3, test_adaptive_radar 41, test_tfil_ring_weights 24, test_power_policy 26, test_ram_decision 28; acceptance_offline_vs_online 12/12 VERDICT PASS; ModularBot compiles. The live `[rack]` line was observed switching 1v1 -> melee when the enemy died. The offline range never calls the selector (only spawnBullets/tickBullets/ reportFor), so mode filtering cannot change the offline result and no offline mode parameter was needed - confirmed by reasoning over the source and by 12/12.