208f4a9092
j176 could not attribute the 11.9 deg aim error at 450+ px: the corpus had
no gun id and no bot-side belief, so staleness was an inverse (unidentifiable)
problem and a good gun was indistinguishable from a bad one. Both are cheap to
log and impossible to recover later.
New default-off knob TR_CAPTURE_AIM (presence-only). It appends TWO record
kinds to the EXISTING TR_RECORD_WORLDSTATE file:
aim_scan - one per onScannedBot, written BEFORE the tracker update, so it is
the pre-update belief by construction: tick, raw scanned values
(ex,ey,eh,es,ee), our own state (sx,sy,sh,ss), the gun in force, the
PREVIOUS belief (bx,by,bh,bs,blst), the scan parity age = tick - blst,
and the radar-lock context (rlock, rdir, lbear, boff).
aim_fire - one per real shot: gun, power, the aim angle handed to setFire,
the turret angle and the signed turret error, gunHeat, the predicted
intercept (ax,ay) and implied TOF, and the exact WorldState the predictor
consumed (ex,ey,eh,es,ee,sx,sy) with the tick it came from (lst).
Row builders live in a new pure module src/aim_capture.nim - no bot API, no
env reads - so the offline guard test and the live bot go through the SAME
builders and a field the test proves present is a field the bot writes.
Per-tick world-state rows also gain a `gun` id. offline_range.nim skips
aim_* lines (they carry no `ex`), so the annotations are inert to the replay.
No aim model changed.
Knob registered in env_report.nim (context field, effective-value emit) and
knownEnvNames(); documented in .env.example. Defaults OFF, diagnostic only,
never live-tested.
Verification (no battle, no Java, no server, no GUI):
- default parity: per-gun shots/hits over 20026 ticks of
tr_drussgt_vs_modularbot.jsonl byte-for-byte identical to the golden
generated from the PRE-CHANGE tree (git archive 55e92bc); the golden was
regenerated from that pre-change tree and re-diffed, so it is not
self-referential. Boot [env] block of the pre- and post-change binaries is
identical except pid/cmdline/build line and the new knob's own line.
- test_aim_capture: ALL PASS (every aim_scan/aim_fire key present, plus the
annotation-inertness replay).
- guards: test_tfil_commit_env 159/0, test_env_report 25/0,
test_tfil_ring_weights 24/0, test_vbullet_draw 30/0.
- .env.example round-trip (env_report via the j172 harness): 210 effective
values + 70 [x]/[modules] lines, 0 diffs, 0 dropped keys, 0 warnings.
- clean `git archive HEAD` + nim c -d:release: [SuccessX].
773 lines
52 KiB
Bash
773 lines
52 KiB
Bash
# 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)
|
|
# 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_<GUN> 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_<X>` is the old namespace of TR_LEADGAIN_<X> 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
|