# ModularBot environment — EVERY knob the bot reads, already set to its default. # # cp .env.example .env # # The bot reads `.env` from its own folder automatically when you start it from # there. To use a different file: ./out/ModularBot --env-file /path/to/my.env # The FILE WINS over variables you exported in the shell: if a shell value # differs, the bot prints one override line at boot so you know it happened. # # Every value below IS the built-in default, so running the bot with this file # is identical to a clean run with no file at all. Delete a line (or comment it # out with #) and that knob falls back to the built-in default. An inline # `# comment` after a value is fine — the loader strips it (a `#` that follows # a space starts the comment; a `#` glued to the value, like `x#y`, is data). # # These values mirror the current defaults, not a frozen snapshot of one commit. # Regenerate this file whenever a default changes, or it will start lying. # ═════════════════════════════════════════════════════════════════════════════ # 1. ONE EXPERIMENT, END TO END # ═════════════════════════════════════════════════════════════════════════════ # # Pick ONE knob. Here the example is TR_TFIL_ARRIVE_TICKS, but the shape is the # same for every knob in this file. # # # 1. write the arm. In ModularBot_garage/.env, change ONE line: # # TR_TFIL_ARRIVE_TICKS=15.0 # # A per-run file is better than editing .env, because it is how you # # GUARANTEE the arm: whatever else is in .env or in your shell, this # # file is the one that is applied. # cat > /tmp/arm_arrive15.env <<'EOF' # TR_MOVEMENT=tfil # TR_TFIL_ARRIVE_TICKS=15.0 # EOF # # # 2. RESTART THE BOT. Env is read ONCE, at boot (module init). Editing # # .env while the bot runs changes nothing. There is no live reload. # # (Two exceptions read lazily on first use: TR_PATTERN_RAD_* and a few # # TR_TMHORIZON_* — do not rely on either.) # cd ModularBot_garage && ./ModularBot.sh # or restart the GUI # # # 3. CONFIRM IT TOOK EFFECT, before you read a single result line. # # `source: .env` = your file was applied. `source: default` = it was not. # grep '^\[env\]' /tmp/modularbot_stdout.log | grep -E 'env file|ARRIVE_TICKS' # # [env] env file: /tmp/arm_arrive15.env (source: TR_ENV_FILE) # # [env] TR_TFIL_ARRIVE_TICKS = 15.0 (source: .env) # # A value showing `(source: default)` means YOUR FILE NEVER REACHED THE BOT. # # # 4. point at the file instead of copying it into .env: # TR_ENV_FILE=/tmp/arm_arrive15.env ./out/ModularBot # ./out/ModularBot --env-file /tmp/arm_arrive15.env # # This is what an A/B run does: one frozen binary, one env file per arm. # # A file you ASKED for and that does not exist stops the bot with an error # # (it never silently falls back); a missing default .env is silent. # # # 5. the module inventory, when you want to know what is on at all: # grep '^\[modules\]' /tmp/modularbot_stdout.log # # ═════════════════════════════════════════════════════════════════════════════ # 2. SAFE TO EXPERIMENT WITH RIGHT NOW # ═════════════════════════════════════════════════════════════════════════════ # # The honest list is SHORT. After the recent campaign most experimental knobs # are either never live-tested or already measured null/harmful, and this file # says so on every one of them. These four are safe in the sense that they # either cannot change a decision, or are the ones a measurement actually # supports. # # TR_GEO_DEBUG=on Draw-only: the candidate-tile geometry overlay. # Watch: the circle on the two tanks and each # heading line. Good: you can SEE the tile the # picker chose. Cannot change any decision. # TR_VBULLET_DEBUG=1 Draw-only: each admitted gun's virtual bullets. # TR_VBULLET_DEBUG_GUN=all # Watch: travelled path, aim ring, miss vector. # Good: you can see the signal the selector ranks # on. Also draw-only. Needs a gun in the rack. # TR_TFIL_DIAG=on Observability only, on the tfil mover. Fills the # per-pick LOSS HISTOGRAM. Watch: the tfil pick log # line. Good: the sReach/sCool/sSafe/sCand counts # tell you where tiles are lost. Provably does not # move a single command (guard-tested). # TR_MOVEMENT=tfil The long-shipped mover, as an explicit override. # Watch: nothing to compare against — it is the # same engine you had before j119. Good: you are # reproducing an older, documented behaviour. Only # do this together with the tfil knobs below. # # Anything else on this list is a MEASUREMENT, not a free change: read its # STATUS line before you type it. # # ═════════════════════════════════════════════════════════════════════════════ # 3. ALREADY REJECTED OR MEASURED NULL — WITH THE NUMBER # ═════════════════════════════════════════════════════════════════════════════ # # TR_TFIL_GEO_MODE=both-rej REJECTED live, 420 battles, 15-opponent panel. # TR_TFIL_GEO_TAU=60 damage/run -8.83, p(sign-flip) = 0.0061, # Wilcoxon p = 0.011. Round wins null. # Docs: docs/tfil_geo_ab.md. DO NOT re-run it. # TR_RAM_FLOOR_ENERGY=5 CLEAN NULL, 900 battles, 450 runs/arm. # -0.018 wins/run, p(sign-flip) = 0.7676, under # a 0.1420 wins/run MDE. Docs: # docs/ram_floor_exhaustion_ab.md. DO NOT re-run # it — the mechanism fired on 0.04% of ticks, so # more runs buy resolution on an effect that is # not there. # TR_RAM_FLOOR_ENERGY=10/20 MEASURED COSTLY OFFLINE (24.7% of ticks blocked # at 20) and the live zone they guard is almost # empty: only 4.8% of shots are ever taken below # 10 energy. Do not go above 5. # TR_TMHORIZON_WINDOW=150 MEASURED HARMFUL live: 26.5% round wins vs 49.0% # for the shipped rack, p = 0.036. Keep 0. # TR_POWER_POLICY=0 MEASURED HARMFUL live: real hit rate 10.61% -> # 7.88%, p = 0.0012. Keep it on. # TR_TFIL_HEAT_TIME=1 MEASURED HARMFUL live at every tau tried # (3/5/9/15): tau15 alone is -22 damage/run, # p = 0.046. Keep it off. # TR_MOVEMENT=tfil_ring MEASURED: round wins 16/49 -> 6/49, p = 0.012. A # glass cannon — best live hit rate of anything # measured, half the survival. Do not ship. # TR_RACK_* (the full rack) MEASURED NEGATIVE VALUE: Pattern ALONE beats # the full 13-gun rack, p = 0.0012. Adding guns # costs rounds. # TR_TFIL_TURN_BIAS=9 LIVE NULL: +0.15 wins/run, p(sign) = 0.244, under # TR_TFIL_TURN_REF_DEG=0 a 0.30 MDE; 300 battles. Docs: # docs/movement_campaign.md (j145). # TR_RAM_OPPORTUNITY=on MEASURED not to convert: 0/59 # opportunity -> contact. The finisher ram is the # only path that converts, and it is always on. # # READ THIS BEFORE YOU TRUST ANY "null" ABOVE. A null only excludes an # effect at or above the MDE that run resolved. The 15-opponent panel at # 14 runs/arm resolves ~0.17 wins/run and ~7.65 damage/run; the j163 run # resolved 0.1420 wins/run. So "clean null" here means "no effect >= that # size", NOT "no effect". # # ═════════════════════════════════════════════════════════════════════════════ # 4. PRESENCE-GATED KNOBS — FOR THESE, `NAME=0` TURNS THE FEATURE ON # ═════════════════════════════════════════════════════════════════════════════ # # grep -rn 'existsEnv' ModularBot_garage/src common_libs | grep -v /tests/ # # The knobs below are read with `existsEnv`, not by value. OFF means THE LINE # IS ABSENT. Writing `TR_POWER_LOG=0` does not disable the power log — it # ENABLES it, because 0 is a perfectly good value for a knob nobody reads. # That is why they are all shown COMMENTED OUT in this file: there is no # "off" spelling for them, only absence. To disable one, DELETE its line. # # TR_POWER_LOG one line per power-decision CHANGE # TR_RAM_LOG one line per ram start/stop, with the reason # TR_MOVEMENT_LOG movement band / range-class changes # TR_STRAFE_LOG one line per strafe tile pick # TR_SURF_LOG one line per wave-surfing decision # TR_FIRE_DIAG per-reading fire-detection tick/raw/correction # TR_RECORD_WORLDSTATE dump every observed world state to JSONL # TR_CAPTURE_AIM aim_scan/aim_fire records (gun id + bot belief). # KNOWN LIMIT: only shots that PASSED setFire are # recorded, so a gap is ambiguous - see docs/env_reference.md # TR_RADAR_SCANLOG log every radar scan tick # TR_RADAR_FORCE_SPIN force the old full-360 spin radar # TR_TRACKER_PROBE dump the enemy-tracker internals # # The VALUE-based switches are the opposite: 0 / false / no / off really # disable them, and anything else enables them. Those are TR_RESULT_LOG, # TR_TMHORIZON_LOG, TR_TMHORIZON_ACCURVE, TR_TMHORIZON_RESET_ON_TARGET, # TR_LEADGAIN_LOG, TR_LEARNED_LOG, TR_LEARNED_GLOBAL, TR_LEARNED_REAL_EVENTS, # TR_POWER_POLICY, TR_POWER_FINISH_KILL, TR_RAM_OPPORTUNITY, TR_RAM_PLAN, # TR_FIRE_FIX, TR_STRAFE_FIRE_FIX, TR_STRAFE_ESCAPE, TR_STRAFE_HEAT_GRID, # TR_TFIL_HEAT_TIME, TR_TFIL_PILLAR_ON, TR_TFIL_DIAG, TR_TFIL_NO_REV, # TR_TFIL_HOLD_WHEN_TRAPPED, TR_TFIL_COMMIT_ARRIVAL, # TR_TFIL_RING_COMMIT_ARRIVAL, GUN_SELECTOR_POOL, GUN_VBULLET_ADMIT_ONLY, # TR_VBULLET_DEBUG, TR_GEO_DEBUG, TR_DEBUG_DRAW, TR_ENV_REPORT. # (TR_TFIL_DIAG / _NO_REV / _HOLD_WHEN_TRAPPED / _COMMIT_ARRIVAL / # _RING_COMMIT_ARRIVAL are read by value in the source; the boot report # labels them by presence, which only affects the "(source: ...)" line, never # the value.) # ── movement ───────────────────────────────────────────────────────────────── # Which dodging engine runs. VALUE: strafe (default) | tfil | tfil_ring | # surf | learned. Any unrecognised value silently runs `tfil`, with no warning. # WHAT: the engine that picks the dodge tile every tick. # STATUS: DEFAULT = strafe, the measured champion — 300 fresh battles, # +0.30 wins/run over tfil, 95% CI [+0.02, +0.58], sign-flip p = 0.045 # (docs/movement_campaign.md, "Fresh-data confirmation (gate v2)"). # tfil_ring is measured harmful (round wins 16/49 -> 6/49, p = 0.012). # surf and learned are wired and measured, and neither beats strafe. # GOTCHA: a typo does not warn. `TR_MOVEMENT=straf` runs tfil. # TRY: TR_MOVEMENT=tfil -> the long-shipped engine; the `[env]` block then # reads `move.effective = tfil`. Pairs with the TR_TFIL_* knobs below. TR_MOVEMENT=strafe # Whole-engine on/off switches. VALUE: on | 0/false/no/off. 0 removes an engine # from the TR_MOVEMENT choices; the effective engine then falls back to the # first still-enabled one, and tfil if all are off (there is no "no movement"). TR_MODULE_MOVE_TFIL=on # the long-shipped "floor is lava" engine TR_MODULE_MOVE_TFIL_RING=on # the same, re-weighted toward a target range TR_MODULE_MOVE_STRAFE=on # perpendicular strafe with sign-flip reversals TR_MODULE_MOVE_SURF=on # wave surfing, steered by the GuessFactor TR_MODULE_MOVE_LEARNED=on # learned per-state danger field # ── gun rack: which guns the bot may choose (off | 1v1 | melee | both) ─────── # WHAT: which guns the selector is allowed to fire. The shipped rack is # PATTERN only; every other gun is `off`. # VALUES: off | 1v1 | melee | both. Aliases: any/empty->both, single/lock->1v1, # only1v1->1v1, multi/onlymelee->melee, none/disabled/disable->off. # STATUS: MEASURED — the full 13-gun rack is WORSE than Pattern alone, # p = 0.0012. Do not re-enable guns to "improve" the bot. # GOTCHA: an UNRECOGNISED value warns on stderr and falls back to `both`, i.e. # a typo ADDS the gun back into the rack. It never turns one off. # TRY: TR_RACK_PATTERN=off -> nothing admitted in 1v1; the selector falls # back to the full rack, so do not ship this. Real use is a PAIR: # TR_RACK_PATTERN=off + TR_RACK_HEADON=both -> exactly one gun fires. TR_RACK_PATTERN=both # the only admitted gun; both = usable in 1v1 and melee TR_RACK_HEADON=off # aim straight at the target, no lead TR_RACK_LINEAR=off # constant-angle linear aim TR_RACK_TSETLIN=off # Tsetlin automaton gun TR_RACK_CIRCULAR=off # assumes the enemy circles TR_RACK_GUESSFACTOR=off # learns the wave-surfing GF of the enemy TR_RACK_WALLBOUNCE=off # predicts a rebound off the arena wall TR_RACK_ACCEL=off # leads a bullet that is still accelerating TR_RACK_STOPSHOT=off # fires only when it expects a full stop TR_RACK_DISPLACE=off # leads a displaced enemy TR_RACK_AVGLEAD=off # averaged lead over recent shots TR_RACK_DECAYGF=off # GuessFactor with a forgetting decay TR_RACK_KNN=off # nearest-neighbour over past gun states TR_RACK_TMSELECT=off # Tsetlin machine used as the shot selector TR_RACK_TMPATTERN=off # Tsetlin machine used as a pattern matcher TR_RACK_TMHORIZON=off # horizon Tsetlin automata gun TR_RACK_LEADGAIN=off # per-range-band learned lead-gain corrector # Give every named gun a fixed share of the turns instead of ranking them. # e.g. TR_RACK_SHARE=PATTERN:60%,HEADON:40% (GUN:weight, comma separated, # the % sign is optional). Empty = the ranking selector. GOTCHA: every gun you # name must ALSO be admitted by its own TR_RACK_ line, or the share is # refused with a loud `[gun_harness] ERROR` and the ranking selector is used # instead. In the shipped rack that means PATTERN and nothing else. TR_RACK_SHARE= # Drop whole guns by rack id (comma separated, e.g. 16). Empty = keep them all. # Ids: 0 HEADON 1 LINEAR 2 TSETLIN 3 CIRCULAR 4 GUESSFACTOR 5 PATTERN # 6 WALLBOUNCE 7 ACCEL 8 STOPSHOT 9 DISPLACE 10 AVGLEAD 11 DECAYGF 12 KNN # 13 TMSELECT 14 TMPATTERN 15 TMHORIZON 16 LEADGAIN. A disabled gun never even # spawns a virtual bullet, so its fitness stays empty and it cannot be picked. GUN_RACK_DISABLE= # ── gun selector (which admitted gun fires this tick) ─────────────────────── GUN_VBULLET_METRIC=path # fitness measure: path (time-to-collision) or point GUN_SELECTOR_MODE=relative # rank guns against the incumbent (absolute = vs a fixed bar) GUN_SELECTOR_WINDOW=100 # ticks of virtual-bullet history behind the fitness (clamped 1..100) GUN_SELECTOR_MINOBS=50 # observations a gun needs before it may compete GUN_SELECTOR_TIE=0.2 # relative margin two guns must differ by to count as separated GUN_SELECTOR_FLOOR=0.25 # fitness fraction of the peak below which a band is unsafe GUN_SELECTOR_POOL=on # pool the per-tick samples over the window instead of replacing GUN_SELECTOR_RANK=mean # how to rank guns: mean, wilson, ucb, thompson or shrunk GUN_SELECTOR_SHRINK=20.0 # pseudo-count the shrunk rank adds per prior observation GUN_SELECTOR_DWELL=10 # ticks a gun must lead before it can be switched away from GUN_SELECTOR_MARGIN=0.05 # fraction the challenger must beat the incumbent by GUN_SELECTOR_TIEBREAK=off # off, point or pointCommit: break ties on arrival accuracy GUN_SELECTOR_POINT_TIE=0.5 # relative width of the point band used by the tie-break # Seed for the selector's tie-breaks. Empty = seed from the clock + pid. GUN_SELECTOR_SEED= # ── power / energy policy ──────────────────────────────────────────────────── # Every rule below only ever CAPS power; the gun's own preference is the ceiling. # The measured case for keeping it on: TR_POWER_POLICY=0 drops the real hit rate # from 10.61% to 7.88%, p = 0.0012. TR_POWER_POLICY=on # 0 = no cap at all (the control arm) TR_POWER_FAR_DIST=200.0 # px; past this the enemy is in the bad-chances zone TR_POWER_FAR_CAP=1.0 # cap applied past TR_POWER_FAR_DIST TR_POWER_MID_CAP=2.0 # cap when close and healthy but not above its average TR_POWER_REF=0.0 # 0 = the gun's own mean; >0 = that fixed reference power TR_POWER_ENERGY_HI=80.0 # our energy at/above which the energy slope stops capping TR_POWER_ENERGY_LO=20.0 # our energy at/below which the cap is TR_POWER_ENERGY_MIN TR_POWER_ENERGY_MIN=0.5 # NOTE: this is a CEILING, not a floor — low energy, low power TR_POWER_ENERGY_MAX=3.0 # the cap at/above ENERGY_HI; 3.0 means effectively uncapped TR_POWER_FINISH_KILL=on # cap to the smallest bullet that still kills a low-energy enemy # ── radar ──────────────────────────────────────────────────────────────────── #TR_RADAR_FORCE_SPIN=1 # PRESENCE-only: force the old full 360 spin instead of 1v1 lock TR_RADAR_SCAN_LOG_PATH=/tmp/radar_scan_log.jsonl # where the per-tick scan log is written # ── ram ────────────────────────────────────────────────────────────────────── TR_MODULE_RAM=on # 0 = never ram; the movement engine alone drives TR_RAM_OPPORTUNITY=off # the proactive "close the distance" gate (measured not to convert) TR_RAM_OPP_DIST=200.0 # px; max range at which that gate may fire TR_RAM_OPP_MARGIN=15.0 # energy advantage the gate needs before it starts TR_RAM_ABORT_DMG=2.0 # incoming damage per turn that aborts a ram in progress TR_RAM_PLAN=off # the change-of-plan trigger (enemy outguns us while we ram) TR_RAM_PLAN_DIST=250.0 # px; max range at which the plan trigger may fire TR_RAM_PLAN_MARGIN=20.0 # energy advantage the plan trigger needs TR_RAM_PLAN_HITRATE=0.05 # pooled virtual hit rate below which the gun duel counts as failing # WHAT: at or below this much SELF energy we start no new shot, keeping a # reserve for the ram. Units: energy points. # VALUES: energy, 0.0 = off (today's behaviour). Any float parses. # STATUS: =5 is a CLEAN NULL and must not be re-run: -0.018 wins/run, # p(sign-flip) = 0.7676, under a 0.1420 wins/run MDE, 900 battles. The # mechanism fired on 0.04% of ticks, ~200x less than the offline ruler # predicted. Docs: docs/ram_floor_exhaustion_ab.md. # GOTCHA: 0.0 genuinely disables it, but any POSITIVE value arms it, and the # higher it goes the more of the low-energy zone it blocks (20 blocked 24.7% # of all ticks offline). # TRY: TR_RAM_FLOOR_ENERGY=5 -> already measured, do not re-run. If you # want to see it at all, that is the only defensible value; anything # higher is worse by the offline ruler and by the live null. TR_RAM_FLOOR_ENERGY=0.0 # WHAT: the last-scanned enemy energy at or below this switches the ram decider # into exhaustion mode (they are out of ammo, we are not). Units: energy. # VALUES: energy, 0.0 = off. Any float parses. # STATUS: DEFAULT-OFF, never live-tested. It is the second half of the j160 pair; # the other half (FLOOR_ENERGY) measured null, so the pair is not a win. # GOTCHA: 0.0 is the only safe "off". The always-on finisher (enemy < 20 energy # and we are healthier, within 300 px) already covers most of this; setting # this to 20 makes the two overlap. # TRY: TR_RAM_ENEMY_ENERGY=10 -> ram once a scan shows them at <= 10. # Watch: the `[ram] ON rrExhausted` line. Good: the reason field says # `exhausted` rather than `finisher`. Needs TR_RAM_LOG=1 to see it. TR_RAM_ENEMY_ENERGY=0.0 # ── movement internals: tfil (the floor-is-lava field) ────────────────────── TR_TFIL_RANGE_LO=100.0 # px; lower edge of the range band the ring mover prefers TR_TFIL_RANGE_HI=200.0 # px; upper edge of that band TR_TFIL_RANGE_TEMP=0.4 # sharpness of the ring mover's weighted random draw TR_TFIL_RANGE_K=60.0 # px; how fast the weight falls off outside the band # WHAT (tfil_ring only): hold the committed dodge tile until we are actually ON # it, instead of the fixed dwell. VALUE: on | 0/off. # STATUS: DEFAULT-OFF, never live-tested. Ported to the ring fork in j165; the # tfil original (TR_TFIL_COMMIT_ARRIVAL) has a real but under-powered live # result — see that line. Neither is a proven win. # GOTCHA: the ring mover is NOT the default and is not shippable (round wins # 16/49 -> 6/49, p = 0.012), so this only matters while you are measuring it. # TRY: TR_MOVEMENT=tfil_ring + TR_TFIL_RING_COMMIT_ARRIVAL=1 # -> the `[env]` block reads TR_TFIL_RING_COMMIT_ARRIVAL = on. TR_TFIL_RING_COMMIT_ARRIVAL=off # WHAT (tfil_ring only): below this self speed (px/tick), a mid-flight switch # may not turn the bot around. UNITS: px/tick (top speed is 8). # STATUS: DEFAULT-OFF (0.0), never live-tested. The tfil original is in the # j144 recommendation below. # TRY: TR_TFIL_RING_NOREV_SPEED=4 -> 4 px/tick is half of top speed, the # value j144 used on tfil. Accepts any float >= 0. TR_TFIL_RING_NOREV_SPEED=0.0 TR_TFIL_CORRIDOR_HEAT=10.0 # lava painted per corridor-overlapping tile # WHAT: cap the bullet-danger corridor at `bullet speed x this many ticks`, # instead of running it all the way to the arena wall. UNITS: ticks. # VALUES: ticks, 0.0 = off (corridor reaches the wall, today's behaviour). # Any float parses; negatives are treated as 0. # STATUS: DEFAULT-OFF, never live-tested, and NOT recommended. Commit 5e213df # (j148) shipped it with no measurement at all: no battle, no offline ruler. # The neighbouring j146 field-shape sweep, which is the closest evidence, # says halving the corridor restores a safe tile set offline (filter-broken # 63.5% -> 30.4%) and is a LIVE NULL on outcome. # GOTCHA: value knob - 0.0 genuinely disables it. It is NOT a presence knob. # TRY: TR_TFIL_CORRIDOR_TICKS=20 -> a 20-tick look-ahead. Bullet speed is # 20 - 3*power, so over the shipped power bins 1.0..3.0 that is # 17.0..11.0 px/tick = 340..220 px of corridor, instead of the whole # wall. Watch: in the debug overlay the corridor stops short of the # wall. Only bind it to tfil; strafe has its own knob below. TR_TFIL_CORRIDOR_TICKS=0.0 TR_TFIL_WALL_HOTNESS=15.0 # peak heat painted on tiles next to a wall TR_TFIL_WALL_RADIANCE=10.0 # how fast wall heat falls off with distance # WHAT: how hot a bullet paints the tile it is sitting on. UNITS: lava points # on the picker's heat scale. # VALUES: any float >= 0. 10.0 is exactly the danger threshold, so a bullet is # never dangerous on its own; 20.0 puts one bullet's own tile over it. # STATUS: live-tested ONLY as part of the j146 five-shape batch, 375 battles: # core 10 -> 20 was a live NULL on both primaries, and the arm that combined # it with no corridor/wall field LOST 13.45 damage/run, p(sign-flip) = # 0.0095. The middle shape (corridor 10 / wall 15/5 / core 20 / aura 10) is # also a null. Do not retune the shape on one axis. # GOTCHA: changing CORE alone with the shipped corridor (10) and wall (15) is # the one combination j146 did NOT isolate. # TRY: TR_TFIL_BULLET_CORE=20 + TR_TFIL_BULLET_AURA=10 -> the j146 # "middle" bullet heat. Only meaningful as part of the whole middle # shape; on its own it is a null at best. TR_TFIL_BULLET_CORE=10.0 # WHAT: the heat painted on the bullet's AURA ring (the tiles around it), as # opposed to the core tile. UNITS: lava points. # VALUES: any float >= 0; 5.0 is half the core's 10.0. # STATUS: live-tested only inside the j146 batch — null, see BULLET_CORE. # TRY: TR_TFIL_BULLET_AURA=10 -> doubles the aura heat; pairs with # TR_TFIL_BULLET_CORE=20 in the j146 "middle" shape. TR_TFIL_BULLET_AURA=5.0 # WHAT: when a dodge commitment is cancelled, replan from whose state? # self = today's behaviour. VALUE: self | enemy | off. # STATUS: `self` is the shipped default; `off` is the pre-j144 behaviour and is # exactly what an earlier A/B (cc11ede arm D) tried and could not measure. # TRY: TR_TFIL_TILE_REPLAN=off -> the tile-crossing cancel stops firing. # Accepts self, enemy, off, none, never, 0, false (case-insensitive); # anything else falls back to self with no warning. TR_TFIL_TILE_REPLAN=self TR_TFIL_COMMIT_TICKS=15 # ticks to commit to a dodge point before replanning (min 1) # WHAT: hold the committed dodge tile until we are actually ON it, instead of # letting our own tile-boundary crossing cancel it. VALUE: on | 0/off. # STATUS: LIVE, 600 battles in two blocks (j144). Mechanism is real and # confirmed: incoming hit rate 18.07% -> 14.92%, sign-flip p = 0.0013, damage # taken -24.57/run. Outcome is NOT distinguishable: +0.28 wins/run, # p(sign) = 0.0574 against a 0.31 MDE. The ledger's answer for your own .env # is YES to this knob, and NO to COMMIT_MARGIN. # GOTCHA: only meaningful with TR_MOVEMENT=tfil; the default strafe mover has # no tfil commitment. With TR_TFIL_COMMIT_ARRIVAL on, COMMIT_TICKS becomes a # MINIMUM dwell, not a maximum. # TRY: TR_MOVEMENT=tfil + TR_TFIL_COMMIT_ARRIVAL=1 # -> `[env] TR_TFIL_COMMIT_ARRIVAL = on (source: .env)`. TR_TFIL_COMMIT_ARRIVAL=off # WHAT: leave the committed tile only if the best alternative is at least this # much COOLER on the same pathMaxHeat scale the picker uses. UNITS: lava # points; 10.0 is exactly one PathDangerThreshold level. # VALUES: any float >= 0; 0.0 = off = today's behaviour (any improvement ends # the commitment). # STATUS: LIVE, j144, 600 battles, as the `arrive_hyst` arm. It is the WEAKEST # of the three j144 arms on both primaries (+0.19 wins/run, p = 0.092) and # the ledger's explicit answer is: YES to COMMIT_ARRIVAL, NO to this. # GOTCHA: it is a hysteresis, so it makes the bot commit harder; a large value # with a busy field means it holds a tile that is no longer the best one. # TRY: TR_TFIL_COMMIT_MARGIN=10 -> the j144 value. Already measured, and # the answer was no. Use it only to isolate COMMIT_ARRIVAL, not as an # improvement. TR_TFIL_COMMIT_MARGIN=0.0 # WHAT: refuse a candidate tile we cannot REACH inside this many ticks. The # bot's top speed is 8 px/tick, so 1 tick = 8 px. UNITS: ticks. # VALUES: ticks, 0.0 = off = today's uniform draw over every safe tile. # STATUS: DEFAULT-OFF, never live-tested. Found by the j151 offline ruler: 65% # of tfil picks outran the 15-tick commitment and the chosen tile was reached # only 6.5% of the time. # GOTCHA: it is a HARD bound, not a preference. If every safe tile is out of # range the pool empties and the code falls back to today's full pool, so it # can never starve the draw — it can also silently do nothing. # TRY: TR_TFIL_ARRIVE_TICKS=15 -> refuse anything farther than 15*8 = 120 # px. That is deliberately the same length as TR_TFIL_COMMIT_TICKS, # i.e. "only pick a tile you can still reach while you hold it". TR_TFIL_ARRIVE_TICKS=0.0 # WHAT: when the safe-tile set is EMPTY (no tile under the heat threshold), # hold position for one tick instead of promoting the 2 least-hot blocked # tiles. VALUE: on | 0/off. # STATUS: DEFAULT-OFF, never live-tested. j153 wrote the proposal and the A/B # design; it was NOT run (docs/tfil_hold_when_trapped_ab.md is titled # "NOT RUN"). Four mechanism-positive / outcome-null results preceded it, so # a null was always the likely answer. # GOTCHA: it can never latch — the pick site only runs on a replan tick, so the # next tick re-reads the field from scratch. The gun is untouched: a held # tick still fires exactly like every other tick. # TRY: TR_TFIL_HOLD_WHEN_TRAPPED=on -> the tfil pick log shows `hold` # instead of `promote` on a trapped tick. Needs TR_TFIL_COMMIT_LOG set. TR_TFIL_HOLD_WHEN_TRAPPED=off # WHAT: the BOUNDED version of the knob above: while the safe set stays empty, # hold for at most this many ticks per empty streak. UNITS: ticks (integer). # VALUES: integer >= 0; 0 = off = today's promote-the-2 fallback. # STATUS: DEFAULT-OFF, never live-tested (j154, no battle). # GOTCHA: the budget is DERIVED, not guessed: the enemy fires two 3.0-power # shots 16 ticks apart, so 16 is the first window that admits its second # shot. The hold is released the tick a safe tile exists, the counter resets # when one is taken, and a tracked bullet reaching us within min(N,16) ticks # overrides the hold outright (panic release). # TRY: TR_TFIL_HOLD_MAX_TICKS=16 -> the derived budget. Any integer parses; # a junk value degrades to 0 (off). TR_TFIL_HOLD_MAX_TICKS=0 # WHAT: the hard heat filter on a candidate's path. UNITS: lava points. # VALUES: any float >= 0. Lava is QUANTISED to 5, so the only values that # change anything are 10, 15 and 20: 10-14 admits exactly what 10 admits. # STATUS: 10.0 is the shipped const; j150 made it sweepable so the offline # ruler could move it. Never live-tested as a knob. # GOTCHA: raising it does not make the bot braver, it makes the safe set # smaller and more of the picks forced. j146's `nofield` arm is the warning: # -13.45 damage/run, p = 0.0095. # TRY: TR_TFIL_DANGER_THRESHOLD=15 -> one quantisation step stricter. The # picker log then reports fewer safe candidates per pick. TR_TFIL_DANGER_THRESHOLD=10.0 # WHAT: fill the per-pick LOSS HISTOGRAM (TfilLoss*): how many tiles die at # each picker stage. VALUE: on | 0/off (read by value, not by presence). # STATUS: DIAGNOSTIC ONLY, j150. Provably does not move a single move command # (byte-for-byte, guard-tested). Never measured on outcome, by design. # GOTCHA: nothing. It is the safest tfil knob in this file. # TRY: TR_TFIL_DIAG=on -> the tfil pick log gains the per-stage counts. TR_TFIL_DIAG=off # WHAT: shape the tile DRAW over the heat-filtered pool by geometry (how far # the tile sits from where we are already going) as well as by heat. # VALUES: off | turn | dist | both, each optionally suffixed -soft (default), # -topk or -rej. `distance`=dist, `rejection`=rej are also accepted. # Anything unrecognised, and `off`, means OFF — today's uniform draw. # The form is NOT printed by the boot report, only the dim. # STATUS: DEFAULT-OFF. THE ONE LIVE-TESTED ARM IS `both-rej` + TAU=60 AND IT # WAS REJECTED: -8.83 damage/run, p(sign-flip) = 0.0061, Wilcoxon p = 0.011, # 420 battles, 15 opponents. Round wins null. Offline it did exactly what was # predicted (arrivals 4.5% -> 29.4%) and that is WHY it is bad: the bot ends # up 26 px further out on 15/15 opponents and deals less. See # docs/tfil_geo_ab.md. DO NOT re-run both-rej. # GOTCHA: this knob ALONE is inert — TR_TFIL_GEO_TAU=0.0 means the weighting is # off whatever the mode says. And it needs TR_MOVEMENT=tfil. # TRY: TR_TFIL_GEO_MODE=both-soft + TR_TFIL_GEO_TAU=45 # -> the j152 offline headline (arrivals 4.5% -> 16.9%, top-tile share # only 7.0% -> 8.6%). Still never live-tested, and the family has one # measured loss, so treat it as a hypothesis. TR_TFIL_GEO_MODE=off # WHAT: the geometric cost scale, in degrees. 0.0 = off, i.e. exactly today's # uniform draw. UNITS: degrees. VALUES: any float >= 0. # STATUS: never live-tested with a mode other than off. The one live arm used # TAU=60 and lost (see GEO_MODE). # GOTCHA: TAU is IGNORED unless GEO_MODE is not off. A big TAU with mode=off # looks like it is doing something and is not. # TRY: TR_TFIL_GEO_TAU=45 -> 45 degrees of turn cost. Pairs with # TR_TFIL_GEO_MODE=both-soft; on its own it changes nothing. TR_TFIL_GEO_TAU=0.0 # WHAT: inside a corridor, never pick a tile in the reverse direction. VALUE: # on | 0/false/no/off (read by value, so `=off` really disables it). # STATUS: never live-tested as a standalone arm. The j144/j145 arms relied on # the NOREV_SPEED knob instead, which is stricter. # GOTCHA: soft only — every weight is floored, so the pool can never empty; and # it overlaps TR_TFIL_NOREV_SPEED, which is the knob that was actually run. # TRY: TR_TFIL_NO_REV=on -> a 3:1 forward:rearward draw weight inside a # corridor. Accepts on/1/true/yes to enable, 0/false/no/off to disable. TR_TFIL_NO_REV=off TR_TFIL_COMMIT_LOG= # path for the per-commit log; empty = no log # WHAT: while our own speed is below this, a mid-flight target switch may not # take a tile more than 90 degrees off the travel direction. UNITS: px/tick # (top speed 8). VALUES: any float >= 0; 0.0 = off = shipped. # STATUS: LIVE, j144/j145, 600 battles. Mechanism confirmed: slow opposite-way # mid-flight switches fell 394 -> 64 offline. Outcome not distinguishable on # its own (+0.12 wins/run, p = 0.39); the ledger recommends 4 in your .env # together with COMMIT_ARRIVAL, not alone. # GOTCHA: the pool can never be emptied — with every candidate behind us it # takes the least-bad turn. 0.0 genuinely disables it. # TRY: TR_TFIL_NOREV_SPEED=4 -> 4 px/tick = half of top speed, the j144 # value. Watch: fewer >90 deg switches in the tfil pick log. TR_TFIL_NOREV_SPEED=0.0 # WHAT: turn TIEBREAK odds ratio among the SAFE tiles: a straight-ahead safe # tile is drawn `1 + bias` times as often as a 180-degree one. # w = max(1, round(1 + BIAS * (1 - max(0,|turn| - REF)/180))) # UNITS: dimensionless. VALUES: any float >= 0; 0.0 = uniform draw as today. # STATUS: LIVE NULL, j145, 300 battles: +0.15 wins/run, p(sign) = 0.244, under # a 0.30 MDE. Docs: docs/movement_campaign.md. A real but small mechanism # with an under-powered outcome. # GOTCHA: the turn cost is NEVER folded into the heat score — the filter stays # hard, and every weight is floored at 1, so the pool can never empty. # TRY: TR_TFIL_TURN_BIAS=9 + TR_TFIL_TURN_REF_DEG=0 # -> the j145 arm value (the knee of the offline bias curve: mean # |turn| -11%, opposite picks -22%). Already measured null. TR_TFIL_TURN_BIAS=0.0 # WHAT: the turn below which the tiebreak above applies NO penalty. UNITS: # degrees. VALUES: any float >= 0; 45.0 = today's default. # STATUS: inert unless TR_TFIL_TURN_BIAS > 0. j145 used 0 with bias 9. # GOTCHA: on its own this knob does nothing at all. # TRY: TR_TFIL_TURN_REF_DEG=0 -> penalise every turn, not just the sharp # ones. Pairs with TR_TFIL_TURN_BIAS=9; alone it is a no-op. TR_TFIL_TURN_REF_DEG=45.0 # WHAT: paint heat on the virtual centre pillar. VALUE: on | 0/off. The shipped # field has NO pillar: PillarHotness/PillarRadiance are 0.0. # STATUS: live-tested, and the OWNER OVERRULED the recommendation to turn it # back on: `old` (pillar on) was best on damage/run (287) and round wins # (35/70) but the contrast is INSIDE the MDE (33 damage/run, 1.22 wins/run # at n=10) and damage taken was 30.8/run higher with the pillar off # (p = 0.040, not corrected for multiple arms). Decision: pillar stays # removed. Docs: docs/tfil_heat_pillar_ab.md. # GOTCHA: this restores an INVENTED hazard with no physical object behind it. # Reversing that decision needs its own pre-registered A/B. # TRY: TR_TFIL_PILLAR_ON=1 -> the pre-change field (30/10), for a fair # A/B against the shipped one. Needs TR_MOVEMENT=tfil. TR_TFIL_PILLAR_ON=off TR_TFIL_HEAT_TIME=off # on = index bullet heat by time (flat field when off) TR_TFIL_HEAT_TAU=9.0 # ticks a tracked bullet's heat lives for TR_TFIL_HEAT_POWER_GAIN=1.0 # scale of the heat a bullet paints, per firepower # ── movement internals: strafe ─────────────────────────────────────────────── TR_STRAFE_BAND=20.0 # degrees the heading may sit off the perpendicular TR_STRAFE_SPREAD=1 # tiles of sideways jitter added to each candidate TR_STRAFE_REACH=144.0 # px the candidate line reaches in each direction TR_STRAFE_DWELL_MIN=6 # min ticks on a target tile before it may be re-picked TR_STRAFE_DWELL_MAX=20 # max ticks on a target tile before it is re-picked anyway TR_STRAFE_RANGE=325.0 # px; the enemy distance the line is tilted toward TR_STRAFE_RANGE_TOL=25.0 # px dead band around it, where the tilt is exactly 0 TR_STRAFE_TILT_MAX=15.0 # degrees; the hard cap on that tilt TR_STRAFE_TILT_GAIN=0.1 # degrees of tilt per px of range error beyond the band TR_STRAFE_KAPPA=0.0025 # 1/px; how hard the candidate wing bends near a wall TR_STRAFE_WALL_MARGIN=108.0 # px range over which the wing starts to bend TR_STRAFE_WING_MAX=30.0 # degrees; cap on the wing's own tilt toward the interior TR_STRAFE_WALL_BIAS=0.35 # how strongly a tile farther from the wall is preferred TR_STRAFE_WALL_SAFE=24.0 # px; a wing point never lands nearer than this to a wall TR_STRAFE_ESCAPE=on # the guaranteed wall escape when every candidate is hot TR_STRAFE_FIRE_FIX=on # strafe's share of the shared TR_FIRE_FIX switch # WHAT: the shared enemy-fire detector. VALUE: on | 0/off. One switch, read by # every mover; each mover may AND it with its own (TR_STRAFE_FIRE_FIX). # STATUS: j134 propagated it to all five movers; the per-mover catch table went # 98.888% -> 100% of enemy fires on a 70-battle corpus. # GOTCHA: TR_STRAFE_FIRE_FIX is an AND, so turning TR_FIRE_FIX off is enough; # turning only TR_STRAFE_FIRE_FIX off does not disable strafe's detector. # TRY: TR_FIRE_FIX=0 -> the shipped previous-energy detector everywhere # (the control arm for any fire-detector A/B). TR_FIRE_FIX=on #TR_FIRE_DIAG=1 # PRESENCE-only: per-reading tick/raw/correction trace # WHAT: back-date every detected enemy fire by this many ticks when the ghost # is spawned. UNITS: ticks (integer, clamped at 0). 0 = shipped. # STATUS: LIVE, j147, 180 battles. The 1-tick detection lag is OURS and was # measured on 1777 matched ghost spawns (displacement 19.1 px mean on tfil, # 16.1 on strafe; the arrival deadline the mover reads was 0.99 / 0.77 ticks # late). With =1 it falls to 5.4 / 9.0 px and 0.06 ticks. The live OUTCOME is # null on both movers (+2.08 / +3.77 damage/run, every p > 0.6), so it stays 0. # GOTCHA: a junk or negative value degrades to 0, never a negative back-date. # TRY: TR_FIRE_LAG=1 -> one bullet step at power 1.0 is 17 px; the ghost is # born that far downrange. Watch the tfil/strafe pick log's arrival # deadline, which is what the knob actually corrects. TR_FIRE_LAG=0 TR_STRAFE_HEAT_GRID=on # draw the whole heat grid; 0 leaves only the chosen tile TR_STRAFE_BULLET_CORE=20.0 # strafe's own retune: lava per bullet-overlapping tile TR_STRAFE_BULLET_AURA=10.0 # strafe's own retune: lava for the bullet aura ring TR_STRAFE_CORRIDOR_HEAT=10.0 # strafe's own retune: lava per corridor tile # WHAT: the same corridor LENGTH bound as TR_TFIL_CORRIDOR_TICKS, for the # strafe mover. UNITS: ticks. VALUES: ticks, 0.0 = to the wall (shipped). # STATUS: DEFAULT-OFF, never live-tested (commit 5e213df, j148). Same standing # as the tfil one: no battle, no offline ruler, not recommended. # GOTCHA: this is the DEFAULT engine's knob. Setting only the tfil one does # nothing at all, because the shipped movement is strafe. # TRY: TR_STRAFE_CORRIDOR_TICKS=20 -> 20-tick look-ahead, 220..340 px over # the shipped power bins, instead of the whole wall. Watch: the strafe # heat grid's corridor stops before the wall. TR_STRAFE_CORRIDOR_TICKS=0.0 TR_STRAFE_WALL_HOTNESS=15.0 # strafe's own retune: peak wall heat TR_STRAFE_WALL_RADIANCE=5.0 # strafe's own retune: wall heat falloff # ── movement internals: surf (wave surfing) ───────────────────────────────── TR_SURF_PREF_DIST=400.0 # px; the wave distance the mover tries to sit at TR_SURF_DIST_BAND=50.0 # px dead band around it TR_SURF_WALL_MARGIN=48.0 # px kept from the wall when picking a wave point TR_SURF_RADIAL_FRAC=0.35 # how much of the remaining weight goes to the radial blend #TR_SURF_LOG=1 # PRESENCE-only: one line per wave-surfing decision # ── movement internals: learned (per-state learned danger) ────────────────── TR_LEARNED_DECAY_EVERY=128 # learns between forgetting passes; 0 never forgets TR_LEARNED_DECAY_SHIFT=1 # forgetting is c -= c shr shift; 0 disables it TR_LEARNED_ALPHA=5.0 # weight of the uniform prior against the learned counts TR_LEARNED_TRAVEL=0.01 # danger cost of crossing a wave TR_LEARNED_REVERSAL=0.02 # danger cost of flipping the strafe side TR_LEARNED_PREF_DIST=400.0 # px; the enemy distance this mover prefers TR_LEARNED_DIST_BAND=50.0 # px dead band around it TR_LEARNED_RADIAL_FRAC=0.35 # radial blend used outside the band TR_LEARNED_WALL_MARGIN=48.0 # px kept from the wall TR_LEARNED_GLOBAL=off # on = ignore the learned state (ablation arm) TR_LEARNED_LABEL=histogram # histogram (default) or outcome: what a wave is labelled with TR_LEARNED_REAL_EVENTS=off # on = resolve a wave on the real bullet event, not on energy #TR_LEARNED_LOG=1 # VALUE-based (1/on/yes to enable, 0/off to disable) # ── guns ───────────────────────────────────────────────────────────────────── # Virtual bullets: the prediction the whole gun selector is built on. TR_MODULE_VBULLETS=on # 0 = no gun predicts or spawns; the selector falls back to its floor gun # TMH — the horizon Tsetlin automata gun. Inert unless TR_RACK_TMHORIZON=both. TR_TMHORIZON_SHIFT=2.0 # degrees added to the aim; 0 disables the correction arm TR_TMHORIZON_BIG_MULT=1.5 # extra scale applied when the error magnitude is big TR_TMHORIZON_RESET_ON_TARGET=on # wipe the automata when the target changes TR_TMHORIZON_NSTATES=64 # automata state count (the inertia it can hold) TR_TMHORIZON_WINDOW=0 # samples kept in the sliding window; 0 keeps everything # e.g. TR_TMHORIZON_WINDOW=30 -> retrain on the 30 most recent samples only. # MEASURED HARMFUL LIVE at 150: 26.5% round wins vs 49.0%, p = 0.036. Keep 0. TR_TMHORIZON_RESET_DROP=0.0 # rolling accuracy drop, in points, that forces a retrain TR_TMHORIZON_ACCURVE=off # log the accuracy curve even without the thinking log TR_TMHORIZON_RETRAIN_EVERY=50 # samples between full retrains in sliding mode TR_TMHORIZON_EPOCHS=1 # epochs each full retrain runs # LEADGAIN (rack id 16) — the per-range-band learned lead-gain corrector. TR_LEADGAIN_MEM=perRound # perRound | retained | decay: how corrections are kept TR_LEADGAIN_GAINS=0.0,0.25,0.5,0.75,1.0 # the candidate lead gains it picks among TR_LEADGAIN_MIN_OBS=8 # samples a band needs before its gain is trusted TR_LEADGAIN_DECAY=250 # samples between count-decay passes TR_LEADGAIN_DECAY_FRAC=0.02 # fraction each decay pass takes off every count TR_LEADGAIN_RESET_ON_TARGET=on # wipe the learned gains when the target changes TR_LEADGAIN_LOG=off # on = one line per gain change (value-based: 0 disables) # Kept only so a pre-rename .env does not warn. The gun does not read them. TR_LEADGAIN_N=32 # NO-OP: the old SBC geometry, no longer used TR_LEADGAIN_NADE=256 # NO-OP: the old ADE count, no longer used TR_LEADGAIN_RANGE=40.0 # NO-OP: the old class half-range, no longer used TR_LEADGAIN_WARMUP=400 # NO-OP: the old warm-up count, no longer used TR_LEADGAIN_ADAPT=32 # NO-OP: the old adapt interval, no longer used TR_LEADGAIN_CALIB=512 # NO-OP: the old calibration interval, no longer used TR_LEADGAIN_SEED=20240921 # NO-OP: the old seed, no longer used # PATTERN — the shipped gun, the only one the rack admits. TR_PATTERN_LEN=10 # ticks of movement history used as the search key TR_PATTERN_DEPTH=500 # how far back the history scan may reach TR_PATTERN_RAD_OFFSET=0.0 # px added to the aim distance; negative aims short # Both RAD_* knobs are read LAZILY, inside predict() — the one place a # mid-run change can matter, and it still does not. The live aim is bearing # only, so a purely radial offset is structurally invisible: MEASURED # byte-identical on bmPath. Docs: docs/env_reference.md §7. TR_PATTERN_RAD_SCALE=1.0 # multiplier on the whole aim distance # The SBC library (common_libs/bitbrain), not a gun knob. Registered so a config # that sets them is not reported as a typo; the shipped bot path never reads them. TR_BITBRAIN_MODE=bitset # bitset (default) or counted: the memory's storage TR_BITBRAIN_DECAY_EVERY=1024 # learns between forgetting passes, counted mode only TR_BITBRAIN_DECAY_SHIFT=1 # forgetting strength, counted mode only; 0 disables it # Legacy names. `TR_BITBRAIN_` is the old namespace of TR_LEADGAIN_ and # `TR_RACK_BITBRAIN` the old name of TR_RACK_LEADGAIN; they are still honoured, # but the new name always wins and setting an old one prints a [depr] line, so # they stay commented out here. #TR_RACK_BITBRAIN=off # LEGACY: old name of TR_RACK_LEADGAIN #TR_BITBRAIN_MEM=perRound # LEGACY: old name of TR_LEADGAIN_MEM #TR_BITBRAIN_GAINS=0.0,0.25,0.5,0.75,1.0 # LEGACY: old name of TR_LEADGAIN_GAINS #TR_BITBRAIN_N=32 # LEGACY: old name of TR_LEADGAIN_N #TR_BITBRAIN_NADE=256 # LEGACY: old name of TR_LEADGAIN_NADE #TR_BITBRAIN_RANGE=40.0 # LEGACY: old name of TR_LEADGAIN_RANGE #TR_BITBRAIN_LOG=off # LEGACY: old name of TR_LEADGAIN_LOG #TR_BITBRAIN_MIN_OBS=8 # LEGACY: old name of TR_LEADGAIN_MIN_OBS #TR_BITBRAIN_WARMUP=400 # LEGACY: old name of TR_LEADGAIN_WARMUP #TR_BITBRAIN_ADAPT=32 # LEGACY: old name of TR_LEADGAIN_ADAPT #TR_BITBRAIN_CALIB=512 # LEGACY: old name of TR_LEADGAIN_CALIB #TR_BITBRAIN_DECAY=250 # LEGACY: old name of TR_LEADGAIN_DECAY #TR_BITBRAIN_DECAY_FRAC=0.02 # LEGACY: old name of TR_LEADGAIN_DECAY_FRAC #TR_BITBRAIN_SEED=20240921 # LEGACY: old name of TR_LEADGAIN_SEED #TR_BITBRAIN_RESET_ON_TARGET=on # LEGACY: old name of TR_LEADGAIN_RESET_ON_TARGET # ── debug overlays (all on top of the gun; they never change a decision) ───── # WHAT: master switch for every mover's debugGraphics. VALUE: on | 0/off. TR_DEBUG_DRAW=on # WHAT: draw the shared candidate-tile geometry overlay. VALUE: on | 0/off. # DRAW ONLY — it cannot change a decision, so it is the safest knob here. # GOTCHA: independent of TR_DEBUG_DRAW: the overlay is drawn either way, and # TR_DEBUG_DRAW=0 is what suppresses the movers' own graphics. # TRY: TR_GEO_DEBUG=on -> the geometry circles appear over the arena. TR_GEO_DEBUG=off # WHAT: draw each admitted gun's virtual-bullet paths. VALUE: 1/on/yes to # enable, 0/off/no/false to disable, unset = off. DRAW ONLY. # GOTCHA: needs a gun in the rack to be legible; the shipped rack admits # Pattern only, so set TR_VBULLET_DEBUG_GUN too. # TRY: TR_VBULLET_DEBUG=1 + TR_VBULLET_DEBUG_GUN=all # -> travelled path, aim ring and miss vector for every admitted gun. TR_VBULLET_DEBUG=off # WHICH gun the overlay draws. VALUES: empty = the currently selected gun; # `all` or `*` = every gun; otherwise a gun name, e.g. `Pattern`. # TRY: TR_VBULLET_DEBUG_GUN=all -> every admitted gun at once. TR_VBULLET_DEBUG_GUN= TR_VBULLET_DEBUG_MAX=32 # max virtual bullets drawn per gun (clamped to >= 1) TR_VBULLET_ADMIT_ONLY=on # on = a gun the rack does not admit is not even predicted # ── logs (set the value to 1; PRESENCE alone turns these on) ───────────────── # WHAT: one line per round result. VALUE-based (unlike the block below): 0/off # really disables it, which is why this one is written out uncommented. # TRY: TR_RESULT_LOG=off -> no [result] lines at all. TR_RESULT_LOG=on #TR_POWER_LOG=1 # PRESENCE-only: one line per power decision (0 would ENABLE it) #TR_RAM_LOG=1 # PRESENCE-only: one line per ram start/stop and why #TR_MOVEMENT_LOG=1 # PRESENCE-only: movement band/class changes #TR_STRAFE_LOG=1 # PRESENCE-only: one line per strafe tile pick #TR_TMHORIZON_LOG=1 # VALUE-based: the per-shot thinking of the TM horizon gun GUN_STATS_PATH=/tmp/gun_stats.jsonl # where the per-round gun stats are written GUN_SHOTLOG_PATH=/tmp/shot_log.jsonl # where the per-shot log is written # ── measurement helpers (leave off unless you are measuring) ───────────────── # WHAT: aim capture. Adds TWO record kinds to the TR_RECORD_WORLDSTATE file: # aim_scan — one per radar scan: the raw reading, our own state, the gun # that fired, the PREVIOUS tracker belief, and the scan parity # (age = tick - previous lastSeenTick); # aim_fire — one per real shot: gun, power, the aim angle, the turret error, # gun heat, the predicted intercept/TOF, and the exact WorldState the # predictor consumed (with the tick it came from). It also adds the `gun` # id to the per-tick world-state rows. # VALUES: presence-only, like the other keys in this block. Unset = off. # STATUS: default-off, diagnostic only, never live-tested. j176 could not # attribute the 11.9 deg aim error because the corpus had no gun id and no # bot-side belief; this knob makes both a lookup instead of an inverse # problem. No aim model changed with it. # GOTCHA: it only writes when TR_RECORD_WORLDSTATE is on as well, and it makes # the capture file bigger, not different: the extra lines are annotations and # the offline replay skips them. # TRY: TR_RECORD_WORLDSTATE=1 TR_CAPTURE_AIM=1 ./out/ModularBot #TR_CAPTURE_AIM=1 # PRESENCE-only: aim_scan / aim_fire records (needs TR_RECORD_WORLDSTATE) #TR_RECORD_WORLDSTATE=1 # PRESENCE-only: dump every observed world state #TR_RADAR_SCANLOG=1 # PRESENCE-only: log every radar scan tick #TR_TRACKER_PROBE=1 # PRESENCE-only: dump the enemy-tracker's internal state TR_TRACKER_PROBE_PATH=/tmp/tracker_probe.jsonl # where that dump is written # ── dotenv / boot report ───────────────────────────────────────────────────── # Name of the env file to load. Must be set in the REAL environment, not in the # file it points at. Empty = use ./.env, else .env next to the binary. # GOTCHA: the line below sets it to the EMPTY string, which is the correct # "use the default" spelling; putting a real path here would make this file # load itself, recursively, at every start. TR_ENV_FILE= # 1 = print the [env] report on startup (default). 0 = do not print it. TR_ENV_REPORT=1