env_reference: verify every knob against the code, fix the trap that cost real time
DOCS ONLY. The user set TMH_NSTATES, which is a compile-time {-d:intdefine.}
(-d:TMH_NSTATES=2, tm_horizon.nim:102), not an env var; the runtime var is
TR_TMHORIZON_NSTATES (tm_horizon.nim:139). This rewrites the reference so that
class of confusion cannot recur.
What was WRONG and is now fixed:
- "unparseable warns and falls back" was false for the numeric/bool knobs: only
the enumerated string knobs warn; envInt/envFloat/envBool fall back silently.
- TR_POWER_LOG/TR_RAM_LOG/TR_MOVEMENT_LOG/TR_RECORD_WORLDSTATE/
TR_RADAR_FORCE_SPIN/TR_RADAR_SCANLOG/TR_TRACKER_PROBE are read with existsEnv,
so TR_POWER_LOG=0 turns the log ON. Documented per knob.
- TR_POWER_ENERGY_MIN is a CAP at low energy, not a minimum-power floor.
- TR_TMHORIZON_* knobs are inert unless TR_RACK_TMHORIZON=both; the doc implied
they were live.
- GUN_SELECTOR_WINDOW is clamped 1..100; TR_MOVEMENT silently falls back to tfil
for any value other than tfil_ring.
- The ring file's own header comment (corridor 5 / wall 10) is stale; the code
defaults are 10.0/15.0 (commit 7f6ccfb).
- Compile-time section was incomplete and conflated the two TM modules:
tm_pattern uses TM_NCLAUSES/TM_NSTATES, tsetlin uses TM_N_CLAUSES/TM_N_STATES,
and -d:TM_S_DEF is defined in BOTH.
What was ADDED:
- "COMPILE-TIME vs RUNTIME: the two namespaces": the only define/env pair is
TMH_NSTATES <-> TR_TMHORIZON_NSTATES; everything else is compile-time only.
- "Did my env vars actually reach the bot?": the /proc exec-time check, the note
that grepping only TR_|GUN_ hides a wrongly-named var (grep -i tmh), and the
boot report described as an interface with the two sections + build identity.
- "Measured verdicts" table: window 26.5% vs 49.0% p=0.036 (harmful live),
TMHorizon N=2/8/64 42.9/42.9/53.1% (all p>=0.8), power floor 22/49 vs 21/49
p=1.0, sub-1.0 accuracy 10.80% vs 10.35% p=0.37, shipped bot 49% vs DrussGT.
- "Names that look real but do nothing": TMH_NSTATES (env), TR_VBULLET_METRIC,
TR_POWER_LOW_ENERGY, TR_TRACKER_RECONCILE.
- The adaptive-melee radar's compile-time constants (no env form).
This commit is contained in:
+276
-100
@@ -1,29 +1,101 @@
|
||||
# Environment variable reference
|
||||
|
||||
Every knob the bot reads. **All are read ONCE at process start.**
|
||||
Every knob the bot reads. **Most are read once per process, at module init; a few
|
||||
are read lazily on first use** (`TR_TMHORIZON_SHIFT`/`_BIG_MULT`/`_LOG`/
|
||||
`_RESET_ON_TARGET` in `ensureConfig`, `TR_PATTERN_RAD_*` inside `predict`). Either
|
||||
way, changing one mid-run has no effect.
|
||||
|
||||
> **The bot is spawned by the server (or by the GUI if it starts its own server).**
|
||||
> So the variable must be set on **that** process, not in an unrelated terminal.
|
||||
> Export it in the shell that launches the server/GUI.
|
||||
> It inherits the **server's** environment. Export in the shell that launches the
|
||||
> server/GUI — not in an unrelated terminal. See
|
||||
> [Did my env vars actually reach the bot?](#did-my-env-vars-actually-reach-the-bot).
|
||||
|
||||
An unset (or empty, or unparseable) value means **the shipped default** — a typo
|
||||
can never silently change behaviour, it warns on stderr and falls back.
|
||||
**Read these two traps before the tables — they are the two ways this document has
|
||||
misled people:**
|
||||
|
||||
Every process prints a one-shot, greppable boot report to stdout; see
|
||||
[Did my env vars actually reach the bot?](#did-my-env-vars-actually-reach-the-bot)
|
||||
below. `TR_ENV_REPORT=0` suppresses it.
|
||||
1. **Defaults are silent for numeric/bool knobs.** Unset / empty / unparseable
|
||||
means the shipped default. Only the enumerated *string* knobs
|
||||
(`TR_RACK_*`, `GUN_VBULLET_METRIC`, `GUN_SELECTOR_MODE`, `GUN_SELECTOR_RANK`,
|
||||
`GUN_SELECTOR_TIEBREAK`) **warn on stderr** on a bad value; the
|
||||
`envInt`/`envFloat`/`envBool` helpers fall back to the default with **no
|
||||
warning at all**.
|
||||
2. **Some flags are presence-based, not value-based.** They are read with
|
||||
`existsEnv`, so **`TR_POWER_LOG=0` turns the log ON** (any value does).
|
||||
Presence-based: `TR_POWER_LOG`, `TR_RAM_LOG`, `TR_MOVEMENT_LOG`,
|
||||
`TR_RECORD_WORLDSTATE`, `TR_RADAR_FORCE_SPIN`, `TR_RADAR_SCANLOG`,
|
||||
`TR_TRACKER_PROBE`. Value-based (`0`/`false`/`off` really disable):
|
||||
`TR_ENV_REPORT`, `TR_TMHORIZON_LOG`, `TR_TMHORIZON_ACCURVE`,
|
||||
`TR_TMHORIZON_RESET_ON_TARGET`, `TR_POWER_POLICY`, `TR_POWER_FINISH_KILL`,
|
||||
`TR_RAM_OPPORTUNITY`, `TR_RAM_PLAN`, `GUN_SELECTOR_POOL`,
|
||||
`TR_VBULLET_ADMIT_ONLY`.
|
||||
|
||||
---
|
||||
|
||||
## COMPILE-TIME vs RUNTIME: the two namespaces
|
||||
|
||||
**A `-d:` compile-time define and a runtime environment variable are different
|
||||
namespaces. The naming is only *partly* parallel, so guessing an env name from a
|
||||
define name is unreliable.**
|
||||
|
||||
| compile-time define | runtime env | notes |
|
||||
|---|---|---|
|
||||
| `-d:TMH_NSTATES=N` | `TR_TMHORIZON_NSTATES=N` | **The ONLY pair with both forms.** Env wins at runtime; clamped `2..4096`. `tm_horizon.nim:102,139,381` |
|
||||
| `-d:TMH_NCLAUSES=N` | *(none)* | compile-time ONLY — no env form. `tm_horizon.nim:101` |
|
||||
| `-d:TMH_S_DEF="x"` | *(none)* | compile-time ONLY. `tm_horizon.nim:103` |
|
||||
| `-d:TMH_MIN_OBS=N`, `-d:TMH_STALE_MAX=N` | *(none)* | compile-time ONLY. `tm_horizon.nim:106,108` |
|
||||
| `-d:TM_NCLAUSES=N`, `-d:TM_NSTATES=N`, `-d:TM_S_DEF="x"`, `-d:TM_MIN_OBS=N`, `-d:TM_CLASSES=N` | *(none)* | `tm_pattern.nim` — compile-time ONLY. Note **`TM_NCLAUSES`** (no underscore) here. `tm_pattern.nim:50,53,55,57,59` |
|
||||
| `-d:TM_N_CLAUSES=N`, `-d:TM_N_STATES=N`, `-d:TM_S_DEF="x"`, `-d:TM_T_DEF`, `-d:TM_WINDOW_SIZE=N` | *(none)* | `tsetlin.nim` — compile-time ONLY. Note **`TM_N_CLAUSES`** (underscore) here, and `TM_N_STATES` not `TM_NSTATES`. `tsetlin.nim:29,116,118,119,120` |
|
||||
|
||||
Rules and warnings:
|
||||
|
||||
- The `-d:TMH_X` ↔ `TR_TMHORIZON_X` mapping holds **only for `NSTATES`**.
|
||||
`TMH_NCLAUSES`, `TMH_S_DEF`, `TMH_MIN_OBS`, `TMH_STALE_MAX` have **no env
|
||||
form** — there is no `TR_TMHORIZON_NCLAUSES` etc.
|
||||
- `-d:TM_S_DEF` is defined in **both** `tm_pattern.nim` (default `"3.0"`) and
|
||||
`tsetlin.nim` (default `"1.5"`); the same flag sets both.
|
||||
- **If you set an env var and the bot seems to ignore it, the name is probably
|
||||
wrong — `TMH_NSTATES` is NOT an env var, the env var is
|
||||
`TR_TMHORIZON_NSTATES`.** Likewise `TM_N_CLAUSES`, `TMH_NCLAUSES`, etc. are
|
||||
compile-time only.
|
||||
|
||||
---
|
||||
|
||||
## Did my env vars actually reach the bot?
|
||||
|
||||
**The spawn trap:** the bot is spawned by the **server/GUI**, so it inherits the
|
||||
**server's** environment — exporting a variable in an unrelated terminal does
|
||||
nothing, because that terminal is not the bot's parent.
|
||||
### 1. The spawn trap in one sentence
|
||||
|
||||
### 1. Ask the bot itself (boot-time report)
|
||||
The bot is spawned by the **server/GUI**, so it inherits the **server's**
|
||||
environment — exporting a variable in a separate terminal does **nothing**,
|
||||
because that terminal is not the bot's parent.
|
||||
|
||||
Every process prints a one-shot, greppable report to **stdout**
|
||||
### 2. The no-code check (works today, no boot report needed)
|
||||
|
||||
`/proc/PID/environ` is the environment **at exec time** — authoritative, exactly
|
||||
what the bot started with, before it read anything:
|
||||
|
||||
```sh
|
||||
for p in $(pgrep -f ModularBot); do
|
||||
echo "== pid $p"
|
||||
tr '\0' '\n' < /proc/$p/environ | grep -E '^(TR_|GUN_)' | sort \
|
||||
|| echo ' (none - wrong process!)'
|
||||
done
|
||||
```
|
||||
|
||||
**Warning:** filtering to `TR_|GUN_` **hides a wrongly-named variable** — e.g. a
|
||||
variable called `TMH_NSTATES` will not show up under this filter (and neither will
|
||||
a `TR_TMHORIZON_*` typo). When you are debugging a *missing* var, grep for the
|
||||
distinctive part of the name with **no prefix filter**:
|
||||
|
||||
```sh
|
||||
tr '\0' '\n' < /proc/$p/environ | grep -i tmh
|
||||
```
|
||||
|
||||
### 3. The boot report (`TR_ENV_REPORT`, default `1`)
|
||||
|
||||
> **Available in the build that adds the boot report** (`ModularBot_garage/src/
|
||||
> env_report.nim`, committed as `9bf3005`). If your binary predates it, use §2.
|
||||
|
||||
The bot prints a one-shot `[env]` block to **stdout** at boot
|
||||
(`/tmp/modularbot_stdout.log` when launched by the GUI, or the GUI/server
|
||||
console). Recover just the report with:
|
||||
|
||||
@@ -31,44 +103,35 @@ console). Recover just the report with:
|
||||
grep '^\[env\]' /tmp/modularbot_stdout.log
|
||||
```
|
||||
|
||||
It has three parts, all behind `[env] === ENVIRONMENT ... ===`:
|
||||
It has two sections plus build identity:
|
||||
|
||||
- **A. raw process environment** — every `TR_*`/`GUN_*` this process *actually*
|
||||
received, sorted, followed by
|
||||
`raw: N TR_*/GUN_* of M total environment variables`. It then prints the
|
||||
process identity: `pid`/`ppid`, the cwd, the bot's own command line, and the
|
||||
**parent's command line**. The parent command line is the conclusive proof of
|
||||
which process spawned the bot (the server/GUI — not your shell). If nothing
|
||||
matched it prints a loud
|
||||
- **A. raw process environment** (`[env] --- A. raw process environment ...`):
|
||||
every `TR_*`/`GUN_*` this process *actually* received, sorted, followed by
|
||||
`[env] raw: N TR_*/GUN_* of M total environment variables`. If `N == 0` it
|
||||
prints a loud
|
||||
`[env] WARNING: this process has NO TR_*/GUN_* variables - the env you exported did NOT reach the bot.`
|
||||
- **B. effective values** — for every knob, the value the bot will *actually*
|
||||
use and whether it came from `env` or the shipped `default`, including the
|
||||
effects of parsing, clamping and fallback (e.g. `TR_TMHORIZON_NSTATES` is
|
||||
clamped to `2..4096`; `TR_RACK_*` shows the empty-set fallback). Where a value
|
||||
is only resolvable later, it is labelled `(raw - not resolved here)` rather
|
||||
than guessed.
|
||||
- **build identity** — `NimVersion`, compile date/time, and the binary path +
|
||||
size + mtime, so a stale binary is obvious.
|
||||
It then prints the process identity: `pid`/`ppid`, `cwd`, the bot's own command
|
||||
line (`self_cmd`), and the **parent's command line** (`parent_cmd`). The parent
|
||||
command line is the conclusive proof of who spawned the bot — under the GUI it
|
||||
is the server/GUI, never your interactive shell.
|
||||
- **B. effective values** (`[env] --- B. effective values (resolved at boot) ---`):
|
||||
one line per knob, `[env] NAME = value (source: env|default)`, i.e. the value
|
||||
the bot will *actually* use after parsing, clamping and fallback (e.g.
|
||||
`TR_TMHORIZON_NSTATES` is clamped to `2..4096`; the rack's empty-set fallback is
|
||||
shown). Where a value is only resolvable later it says
|
||||
`(raw - not resolved here)` rather than guessing. It also prints
|
||||
`[env] rack active 1v1 = ...` and `[env] rack active melee = ...`.
|
||||
- **build identity**: `NimVersion`, compile date/time, and the binary path + size
|
||||
+ mtime, so a stale binary is obvious.
|
||||
|
||||
Suppress the report with `TR_ENV_REPORT=0`.
|
||||
`TR_ENV_REPORT=0` suppresses the report (it is value-based: `0`/`false`/`no`/`off`
|
||||
all suppress).
|
||||
|
||||
### 2. The no-code check (authoritative)
|
||||
### 4. Launching the GUI with knobs
|
||||
|
||||
`/proc/PID/environ` is the environment **at exec time** — exactly what the bot
|
||||
started with, before it read anything:
|
||||
|
||||
```sh
|
||||
for p in $(pgrep -f ModularBot); do
|
||||
echo "== $p"
|
||||
tr '\0' '\n' < /proc/$p/environ | grep -E '^(TR_|GUN_)'
|
||||
done
|
||||
```
|
||||
|
||||
### 3. Launching the GUI with knobs
|
||||
|
||||
Export in the **same shell** that launches the GUI/server, then start it from
|
||||
that shell. Because the launcher **appends** to the log, clear it first so you
|
||||
read only the new run:
|
||||
Export in the **same shell** that launches the GUI/server, then start it from that
|
||||
shell. Because the launcher **appends** to the log, clear it **first** so you read
|
||||
only the new run:
|
||||
|
||||
```sh
|
||||
rm -f /tmp/modularbot_stdout.log
|
||||
@@ -77,7 +140,7 @@ export TR_POWER_ENERGY_MIN=1.0 TR_RACK_PATTERN=off
|
||||
grep '^\[env\]' /tmp/modularbot_stdout.log
|
||||
```
|
||||
|
||||
### 4. Prove the trap deliberately
|
||||
### 5. Prove the trap deliberately
|
||||
|
||||
Terminal A — where you *exported*, but never launched anything:
|
||||
|
||||
@@ -99,18 +162,47 @@ server, not terminal A. Fix it by exporting in terminal B before launching.
|
||||
|
||||
---
|
||||
|
||||
## Measured verdicts — read before tuning (MEASURED on the live arena vs real DrussGT)
|
||||
|
||||
These are live battles, not offline replay. Do not re-derive an offline-only win
|
||||
and ship it. (Offline claims are labelled as such.)
|
||||
|
||||
| experiment | result | verdict |
|
||||
|---|---|---|
|
||||
| shipped rack (Pattern only) | **24/49 rounds = 49.0%** (up from 12.5% earlier in the session) | the baseline |
|
||||
| `TR_TMHORIZON_WINDOW=150` (with TMHorizon admitted) | **13/49 = 26.5%** vs 49.0%, **p=0.036** | **MEASURED HARMFUL live.** Looked like an offline +9.3pp late-accuracy win; the arena disagreed. Keep `0`. |
|
||||
| TMHorizon `TR_TMHORIZON_NSTATES=2` | 42.9% | no better than shipped (p>=0.8) |
|
||||
| TMHorizon `TR_TMHORIZON_NSTATES=8` | 42.9% | no better than shipped (p>=0.8) |
|
||||
| TMHorizon `TR_TMHORIZON_NSTATES=64` (default) | 53.1% | not significant vs Pattern (p>=0.8) |
|
||||
| refusing to fire below power 1.0 | 22/49 vs 21/49, **p=1.0** | no help |
|
||||
| sub-1.0 vs >=1.0 bullet accuracy | 10.80% vs 10.35%, **p=0.37** | sub-1.0 bullets are **not** less accurate |
|
||||
| `TR_POWER_POLICY=0` (uncapped) | real hit rate 10.61% → 7.88%, p=0.0012 | policy helps; keep it on |
|
||||
| `TR_VBULLET_ADMIT_ONLY=1` | 87 → 146 ticks/s (+68%) | keep it on |
|
||||
| `TR_MOVEMENT=tfil_ring` | round wins 16/49 → 6/49, p=0.012 | glass cannon; do not ship |
|
||||
| `TR_RAM_OPPORTUNITY=1` | 0/59 opportunity→contact conversions | finisher-only is the default |
|
||||
|
||||
On the TM rows: **no TM configuration beats the shipped rack**, and **low inertia
|
||||
does not help** (contrary to an earlier offline trend, where it was a substitute
|
||||
for forgetting, not a fix). On the power rows: the floor experiment exposed the
|
||||
real tradeoff — power `p` costs a `10+2p` tick reload, so a floor costs ~13% of
|
||||
your shots/round, while the bullets it suppresses were *not* less accurate (they
|
||||
are simply worth less damage per hit, `4p`, which is what makes low-power
|
||||
shooting *look* like missing).
|
||||
|
||||
---
|
||||
|
||||
## The ones you'll actually use live
|
||||
|
||||
| variable | default | what it does |
|
||||
|---|---|---|
|
||||
| `TR_MOVEMENT` | `tfil` | movement engine: `tfil` (shipped) or `tfil_ring` (range-weighted variant) |
|
||||
| `TR_MOVEMENT` | `tfil` | movement engine: `tfil` (shipped) or `tfil_ring` (range-weighted variant; any other value falls back to `tfil`) |
|
||||
| `TR_RACK_<GUN>` | see below | move a gun in/out of the rack per mode |
|
||||
| `TR_POWER_POLICY` | `1` | `0` = uncapped control arm (today's behaviour without the energy policy) |
|
||||
| `TR_POWER_LOG` | off | `1` = log each power decision with its reason |
|
||||
| `TR_RAM_LOG` | off | `1` = log ram on/off with the reason |
|
||||
| `TR_MOVEMENT_LOG` | off | `1` = log movement band/class changes |
|
||||
| `TR_TMHORIZON_LOG` | off | `1` = let the horizon TM gun log its thinking per shot |
|
||||
| `TR_ENV_REPORT` | `1` | print the boot-time `[env]` process-environment report to stdout; `0` suppresses it |
|
||||
| `TR_POWER_LOG` | off | **presence-based**: if the var exists at all (even `=0`) log each power decision |
|
||||
| `TR_RAM_LOG` | off | **presence-based**: log ram on/off with the reason |
|
||||
| `TR_MOVEMENT_LOG` | off | **presence-based**: log movement band/class changes |
|
||||
| `TR_TMHORIZON_LOG` | off | value-based: `1` = let the horizon TM gun log its thinking per shot |
|
||||
| `TR_ENV_REPORT` | `1` | print the boot-time `[env]` report to stdout; `0` suppresses it |
|
||||
|
||||
---
|
||||
|
||||
@@ -118,13 +210,41 @@ server, not terminal A. Fix it by exporting in terminal B before launching.
|
||||
|
||||
| variable | default | what it does |
|
||||
|---|---|---|
|
||||
| `TR_RACK_<GUN>` | `PATTERN=both`, all others `off` | membership: `both` \| `1v1` \| `melee` \| `off` |
|
||||
| `TR_RACK_<GUN>` | `PATTERN=both`, all other 15 guns `off` | membership: `both` \| `1v1` \| `melee` \| `off` |
|
||||
| `GUN_RACK_DISABLE` | empty | comma-separated gun **ids** to remove entirely (older mechanism; the gun never even spawns virtual bullets) |
|
||||
| `GUN_STATS_PATH` | `/tmp/gun_stats.jsonl` | where the per-round per-gun stats are written |
|
||||
| `GUN_SHOTLOG_PATH` | `/tmp/shot_log.jsonl` | where the per-shot JSONL (Task A) is written |
|
||||
| `GUN_SHOTLOG_PATH` | `/tmp/shot_log.jsonl` | where the per-shot JSONL is written |
|
||||
|
||||
`<GUN>` names: `HEADON LINEAR TSETLIN CIRCULAR GUESSFACTOR PATTERN WALLBOUNCE ACCEL
|
||||
STOPSHOT DISPLACE AVGLEAD DECAYGF KNN TMSELECT TMPATTERN TMHORIZON`.
|
||||
`<GUN>` names (ids 0-15, `selector.nim:52`):
|
||||
`HEADON LINEAR TSETLIN CIRCULAR GUESSFACTOR PATTERN WALLBOUNCE ACCEL STOPSHOT
|
||||
DISPLACE AVGLEAD DECAYGF KNN TMSELECT TMPATTERN TMHORIZON`.
|
||||
|
||||
Shipped default membership (`selector.nim:DefaultRackMembership`):
|
||||
|
||||
| gun | default | gun | default |
|
||||
|---|---|---|---|
|
||||
| HEADON | off | STOPSHOT | off |
|
||||
| LINEAR | off | DISPLACE | off |
|
||||
| TSETLIN | off | AVGLEAD | off |
|
||||
| CIRCULAR | off | DECAYGF | off |
|
||||
| GUESSFACTOR | off | KNN | off |
|
||||
| **PATTERN** | **both** | TMSELECT | off |
|
||||
| WALLBOUNCE | off | TMPATTERN | off |
|
||||
| ACCEL | off | TMHORIZON | off |
|
||||
|
||||
`TR_RACK_<GUN>` accepts aliases: `both`/`any`/empty → both;
|
||||
`1v1`/`single`/`lock` → 1v1; `melee`/`multi` → melee;
|
||||
`off`/`none`/`disabled` → off. An **unknown value warns on stderr and falls back
|
||||
to `both`** (`selector.nim:parseRackMembership`).
|
||||
|
||||
The `1v1`/`melee` split is driven by **server truth**, `getEnemyCount()`:
|
||||
`== 1` → 1v1 rack, `> 1` → melee rack (`virtual_bullets.rackMode`).
|
||||
|
||||
**Empty-set fallback:** if every gun is filtered out for a mode, the *selector*
|
||||
falls back to the **full rack** so it can never end up with no gun
|
||||
(`admittedGuns`, `virtual_bullets.nim`). Note this fallback is on *selection*;
|
||||
the spawn gate (`TR_VBULLET_ADMIT_ONLY`) uses `rackAdmitted` and would spawn
|
||||
nothing, so an all-`off` rack is not a useful configuration.
|
||||
|
||||
**The shipped default is Pattern only.** To restore the full old rack:
|
||||
|
||||
@@ -136,7 +256,8 @@ TR_RACK_DECAYGF=both TR_RACK_KNN=both TR_RACK_TMSELECT=both ./out/ModularBot
|
||||
```
|
||||
|
||||
To force one gun alone: `TR_RACK_PATTERN=off TR_RACK_TMHORIZON=both`.
|
||||
If **every** gun is `off`, the rack falls back to admitting all of them.
|
||||
Enabling a gun is `TR_RACK_<GUN>=both`; TMHorizon additionally needs
|
||||
`TR_RACK_TMHORIZON=both` before any `TR_TMHORIZON_*` knob can matter.
|
||||
|
||||
## 2. The selector (virtual-bullet fitness)
|
||||
|
||||
@@ -144,18 +265,20 @@ If **every** gun is `off`, the rack falls back to admitting all of them.
|
||||
|---|---|---|
|
||||
| `GUN_VBULLET_METRIC` | `path` | how a virtual bullet is scored: `path` (swept ray — the better coarse signal, 7.4% vs 4.7% real) or `point` (arrival accuracy) |
|
||||
| `GUN_SELECTOR_MODE` | `relative` | tie-band model: `relative` (scale-aware) or `absolute` (legacy fixed bars) |
|
||||
| `GUN_SELECTOR_WINDOW` | `100` | rolling window (ticks) for the fitness rates |
|
||||
| `GUN_SELECTOR_WINDOW` | `100` | rolling window (ticks) for the fitness rates; **clamped `1..100`** |
|
||||
| `GUN_SELECTOR_TIE` | `0.20` | tie band width: tied if `rate >= bestRate*(1-this)` |
|
||||
| `GUN_SELECTOR_FLOOR` | `0.25` | relative floor: fire HeadOn only if best rate < this × its peak |
|
||||
| `GUN_SELECTOR_MINOBS` | `50` | min observations before a gun×bin may compete (clamped to ≥1) |
|
||||
| `GUN_SELECTOR_POOL` | `on` | pool the gun's power bins when computing its rate |
|
||||
| `GUN_SELECTOR_RANK` | `mean` | window statistic: `mean` \| `wilson` \| `ucb` \| `thompson` \| `shrunk` |
|
||||
| `GUN_SELECTOR_SHRINK` | `20.0` | empirical-Bayes shrink strength (`shrunk` rank only) |
|
||||
| `GUN_SELECTOR_DWELL` | `10` | ticks the incumbent gun is held before it may be displaced |
|
||||
| `GUN_SELECTOR_MARGIN` | `0.05` | a challenger must beat the incumbent by this fraction to displace it |
|
||||
| `GUN_SELECTOR_SHRINK` | `20.0` | empirical-Bayes shrink strength (`shrunk` rank only; clamped ≥0) |
|
||||
| `GUN_SELECTOR_DWELL` | `10` | ticks the incumbent gun is held before it may be displaced (clamped ≥0) |
|
||||
| `GUN_SELECTOR_MARGIN` | `0.05` | a challenger must beat the incumbent by this fraction to displace it (clamped ≥0) |
|
||||
| `GUN_SELECTOR_SEED` | unset | integer seed for the tie-break RNG; unset = time+pid per process |
|
||||
| `GUN_SELECTOR_TIEBREAK` | `off` | `off` (shipped) / `point` / `pointCommit`. Measured **negative** on real hit rate; opt-in only |
|
||||
| `GUN_SELECTOR_POINT_TIE` | `0.5` | margin used by the `point` tie-break |
|
||||
| `GUN_SELECTOR_POINT_TIE` | `0.5` | margin used by the `point` tie-break (clamped `0..1`) |
|
||||
|
||||
Constants live in `virtual_bullets.nim:16,28,31,33,35`.
|
||||
|
||||
**Note:** the per-tick random draw inside the tie band is **load-bearing** —
|
||||
replacing it with commitment to the virtual-best measurably *lowered* real hit
|
||||
@@ -165,22 +288,33 @@ rate (7.02% → 5.10%, p=0.002). Three attempts to "smarten" the band all failed
|
||||
|
||||
| variable | default | what it does |
|
||||
|---|---|---|
|
||||
| `TR_POWER_POLICY` | `1` | `0` = uncapped control arm. **Measured: turning it off drops real hit rate 10.6%→7.9%** |
|
||||
| `TR_POWER_POLICY` | `1` | `0` = uncapped control arm. **MEASURED: turning it off drops real hit rate 10.61%→7.88%, p=0.0012** |
|
||||
| `TR_POWER_FAR_DIST` | `200` px | beyond this = "bad chances zone" |
|
||||
| `TR_POWER_FAR_CAP` | `1.0` | power cap beyond that distance |
|
||||
| `TR_POWER_MID_CAP` | `2.0` | cap when close + healthy but chances are not above average |
|
||||
| `TR_POWER_REF` | `0.0` | reference probability: `0` = the gun's own mean, `>0` = a fixed threshold |
|
||||
| `TR_POWER_ENERGY_HI` | `80` | at/above this energy, no energy-slope cap |
|
||||
| `TR_POWER_ENERGY_LO` | `20` | at/below this energy, cap = `ENERGY_MIN` |
|
||||
| `TR_POWER_ENERGY_MIN` | `0.5` | the cap at/below `ENERGY_LO` |
|
||||
| `TR_POWER_ENERGY_MIN` | `0.5` | the **cap** at/below `ENERGY_LO` — **NOT a minimum-power floor** (see below) |
|
||||
| `TR_POWER_ENERGY_MAX` | `3.0` | the cap at/above `ENERGY_HI` (3.0 = effectively uncapped) |
|
||||
| `TR_POWER_FINISH_KILL` | `1` | cap power at the **smallest bullet that can still kill** the enemy |
|
||||
| `TR_POWER_LOG` | off | `1` = log each decision: `[power] p=… cap=… reason=far\|energySlope\|finishKill\|belowAvg\|full\|ram` |
|
||||
| `TR_POWER_LOG` | off | presence-based; logs each decision: `[power] p=… cap=… reason=… dist=… selfE=… enemyE=… gun=…` |
|
||||
|
||||
Constants live in `virtual_bullets.nim:842-851`; the rule core is
|
||||
`applyPowerPolicy`/`energySlopeCap`/`powerToKill`.
|
||||
|
||||
> **`TR_POWER_ENERGY_MIN` is a CAP, not a floor.** It is the ceiling applied when
|
||||
> *our* energy is at/below `TR_POWER_ENERGY_LO`. Setting it to `1.0` raises the
|
||||
> low-energy ceiling to 1.0; it does **not** forbid shots below 1.0. Sub-1.0
|
||||
> bullets come from the caps (`ENERGY_MIN`, `FAR_CAP`, `FINISH_KILL`). MEASURED:
|
||||
> refusing to fire below power 1.0 does not help (22/49 vs 21/49, p=1.0), and
|
||||
> sub-1.0 bullets are *not* less accurate (10.80% vs 10.35%, p=0.37).
|
||||
|
||||
WHY the slope: bullet speed is `20-3p`, so **lower power = faster bullet** (less
|
||||
lead error, higher hit chance), fires more often (`10+2p` ticks) and drains energy
|
||||
more slowly (`p`/shot). `E[dE] = p(3P-1)`, so break-even is **1/3 regardless of
|
||||
power** — and our measured hit rates are 5–27%, far below it.
|
||||
power** — and our measured hit rates are 5–27%, far below it. A power floor at
|
||||
1.0 costs ~13% of your shots/round (the longer reload) and buys no accuracy.
|
||||
|
||||
WHY the finishing cap: server 1.3.1 caps the damage **score** at the energy
|
||||
actually removed, so **overkill scores nothing**. Damage is `4p` (p≤1) / `6p-2`
|
||||
@@ -190,13 +324,19 @@ actually removed, so **overkill scores nothing**. Damage is `4p` (p≤1) / `6p-2
|
||||
|
||||
| variable | default | what it does |
|
||||
|---|---|---|
|
||||
| `TR_MOVEMENT` | `tfil` | `tfil` (shipped) or `tfil_ring` (the range-weighted variant) |
|
||||
| `TR_MOVEMENT_LOG` | off | `1` = log band/range-class changes for the ring mover |
|
||||
| `TR_MOVEMENT` | `tfil` | `tfil` (shipped) or `tfil_ring` (the range-weighted variant; any other value falls back to `tfil`, no warning) |
|
||||
| `TR_MOVEMENT_LOG` | off | presence-based; `1` = log band/range-class changes for the ring mover |
|
||||
| `TR_TFIL_RANGE_LO` / `_HI` | `100` / `200` px | the target-range band the ring mover prefers |
|
||||
| `TR_TFIL_RANGE_TEMP` | `0.4` | peakiness of the range weighting. `0` = plain uniform draw = exact control |
|
||||
| `TR_TFIL_RANGE_K` | `60` | softness of the falloff outside the band |
|
||||
| `TR_TFIL_CORRIDOR_HEAT` | `10.0` | heat added along a bullet's corridor to the wall |
|
||||
| `TR_TFIL_WALL_HOTNESS` | `15.0` | wall radiance |
|
||||
| `TR_TFIL_WALL_HOTNESS` | `15.0` | peak wall radiance |
|
||||
|
||||
Defaults read in `the_floor_is_lava_ring.nim:116-133`.
|
||||
**Discrepancy to be aware of:** that file's header comment still says
|
||||
`CORRIDOR_HEAT default 5.0` / `WALL_HOTNESS default 10.0` (the pre-retune values);
|
||||
the **code defaults are `10.0` / `15.0`** (commit `7f6ccfb`). The code is the
|
||||
truth.
|
||||
|
||||
**Ring mover is NOT the default and should not be shipped as-is:** it had the best
|
||||
offline hit rate of anything measured, but it halves engagement range and **halves
|
||||
@@ -206,36 +346,45 @@ survival** (round wins 16/49 → 6/49, p=0.012) — a glass cannon.
|
||||
|
||||
| variable | default | what it does |
|
||||
|---|---|---|
|
||||
| `TR_RAM_OPPORTUNITY` | off | enable the proactive ram. **Measured: converts 0/6 times** — a straight-line pursuit cannot catch an equal-speed enemy |
|
||||
| `TR_RAM_OPPORTUNITY` | off | enable the proactive ram. **MEASURED: 0/59 opportunity→contact conversions** — a straight-line pursuit cannot catch an equal-speed enemy |
|
||||
| `TR_RAM_OPP_DIST` | `200` px | opportunity trigger distance |
|
||||
| `TR_RAM_OPP_MARGIN` | `15` | how much MORE energy we must have than the enemy |
|
||||
| `TR_RAM_ABORT_DMG` | `2.0` | abort an in-progress ram above this incoming **energy per turn** (the "bullet rain" abort) |
|
||||
| `TR_RAM_PLAN` | off | the speculative "change of plan" trigger |
|
||||
| `TR_RAM_PLAN_DIST` / `_MARGIN` / `_HITRATE` | `250` / `20` / `0.05` | its thresholds |
|
||||
| `TR_RAM_LOG` | off | `1` = log `[ram] ON/OFF` with reason |
|
||||
| `TR_RAM_LOG` | off | presence-based; `1` = log `[ram] ON/OFF` with reason |
|
||||
|
||||
The **finisher** ram (enemy <20 energy, we are healthier) is always on and is the
|
||||
only path that converts.
|
||||
Defaults in `ram_decision.nim:79-97`. The **finisher** ram (enemy <20 energy, we
|
||||
are healthier) is always on and is the only path that converts.
|
||||
|
||||
## 6. The horizon TM gun (`TMHORIZON`)
|
||||
|
||||
**Only relevant when the rack admits it** (`TR_RACK_TMHORIZON=both`); the shipped
|
||||
rack is Pattern-only, so these knobs are inert by default.
|
||||
|
||||
| variable | default | what it does |
|
||||
|---|---|---|
|
||||
| `TR_TMHORIZON_LOG` | off | `1` = per-shot thinking log + per-round summary |
|
||||
| `TR_TMHORIZON_LOG` | off | value-based; `1` = per-shot thinking log + per-round summary |
|
||||
| `TR_TMHORIZON_SHIFT` | `2.0` deg | how far it may move the aim off Pattern's answer. **`0` = predict but never move the aim** (isolates prediction from application) |
|
||||
| `TR_TMHORIZON_BIG_MULT` | `1.5` | multiplier when the predicted correction is BIG |
|
||||
| `TR_TMHORIZON_NSTATES` | `64` | **automata inertia** — the "mood". Lower = adapts faster, higher = more stubborn (2…4096) |
|
||||
| `TR_TMHORIZON_NSTATES` | `64` | **automata inertia** — the "mood". Lower = adapts faster, higher = more stubborn. Clamped `2..4096`; overrides the `-d:TMH_NSTATES` compile-time value |
|
||||
| `TR_TMHORIZON_RESET_ON_TARGET` | on | wipe the model when the target changes (no-op in 1v1) |
|
||||
| `TR_TMHORIZON_WINDOW` | `0` | sliding window: retrain on only the most recent N samples. `0` = keep everything. **ON is MEASURED HARMFUL live: 26.5% vs 49.0% round wins, p=0.036 — keep it at `0`.** |
|
||||
| `TR_TMHORIZON_WINDOW` | `0` | sliding window: retrain on only the most recent N samples. `0` = keep everything. **ON is MEASURED HARMFUL live: 26.5% vs 49.0% round wins, p=0.036 — keep it at `0`.** (Offline it was +9.3pp late accuracy.) |
|
||||
| `TR_TMHORIZON_RESET_DROP` | `0` | change detection: if rolling accuracy falls this many points below its own peak, treat it as "the enemy changed" and re-learn |
|
||||
| `TR_TMHORIZON_ACCURVE` | off | `1` = log the rolling accuracy periodically |
|
||||
| `TR_TMHORIZON_RETRAIN_EVERY` | `50` | samples between windowed retrains |
|
||||
| `TR_TMHORIZON_EPOCHS` | `1` | epochs per windowed retrain |
|
||||
| `TR_TMHORIZON_ACCURVE` | off | value-based; `1` = log the rolling accuracy periodically |
|
||||
| `TR_TMHORIZON_RETRAIN_EVERY` | `50` | samples between windowed retrains (clamped ≥1) |
|
||||
| `TR_TMHORIZON_EPOCHS` | `1` | epochs per windowed retrain (clamped ≥1) |
|
||||
|
||||
Defaults in `tm_horizon.nim:126-152,381-412`.
|
||||
|
||||
Learning **persists across rounds** of the same battle (so it can overfit the
|
||||
current enemy) and wipes only on a new battle or a target change. **No learning is
|
||||
written to disk — nothing carries between battles.**
|
||||
|
||||
**Measured:** no TM configuration beats the shipped rack — N=2 42.9%, N=8 42.9%,
|
||||
N=64 53.1% (all p>=0.8 vs Pattern), N+window 26.5%. Low-inertia `NSTATES=2` does
|
||||
**not** help (contrary to an earlier offline trend).
|
||||
|
||||
## 7. Other gun knobs
|
||||
|
||||
| variable | default | what it does |
|
||||
@@ -243,42 +392,69 @@ written to disk — nothing carries between battles.**
|
||||
| `TR_PATTERN_RAD_OFFSET` | `0.0` px | shift Pattern's aim along its own bearing (negative = aim short) |
|
||||
| `TR_PATTERN_RAD_SCALE` | `1.0` | multiplier on Pattern's aim distance |
|
||||
|
||||
Both are **structurally incapable of changing the shot** — the live aim is
|
||||
bearing-only, so a purely radial offset is invisible. Measured byte-identical on
|
||||
`bmPath`. Kept for experiments; leave at defaults.
|
||||
(`pattern_matcher.nim:26-27`.) Both are **structurally incapable of changing the
|
||||
shot** — the live aim is bearing-only, so a purely radial offset is invisible.
|
||||
Measured byte-identical on `bmPath`. Kept for experiments; leave at defaults.
|
||||
|
||||
## 8. Measurement / instrumentation (all off by default, all zero-cost when off)
|
||||
|
||||
| variable | default | what it does |
|
||||
|---|---|---|
|
||||
| `TR_VBULLET_ADMIT_ONLY` | `1` | only admitted guns spawn virtual bullets. **Turning it on gave +68% tick rate** (87→146 ticks/s); `0` restores the old behaviour |
|
||||
| `TR_ENV_REPORT` | `1` | print the boot-time `[env]` report to stdout; `0` suppresses it |
|
||||
| `TR_RECORD_WORLDSTATE` | off | append every tick's WorldState to `/tmp/worldstate_record.jsonl` for offline replay |
|
||||
| `TR_RADAR_FORCE_SPIN` | off | force the old stateless full-spin melee radar (for A/B on one binary) |
|
||||
| `TR_RADAR_SCANLOG` | off | append per-round radar scan/coverage JSON |
|
||||
| `TR_VBULLET_ADMIT_ONLY` | `1` | only admitted guns spawn virtual bullets. **MEASURED: +68% tick rate** (87→146 ticks/s); `0` restores the old behaviour |
|
||||
| `TR_ENV_REPORT` | `1` | print the boot-time `[env]` report to stdout; `0` suppresses it (value-based) |
|
||||
| `TR_RECORD_WORLDSTATE` | off | presence-based; append every tick's WorldState to `/tmp/worldstate_record.jsonl` for offline replay |
|
||||
| `TR_RADAR_FORCE_SPIN` | off | presence-based; force the old stateless full-spin melee radar (for A/B on one binary) |
|
||||
| `TR_RADAR_SCANLOG` | off | presence-based; append per-round radar scan/coverage JSON |
|
||||
| `TR_RADAR_SCAN_LOG_PATH` | `/tmp/radar_scan_log.jsonl` | where that goes |
|
||||
| `TR_TRACKER_PROBE` | off | per-tick enemy tracker vs server enemy count |
|
||||
| `TR_TRACKER_PROBE` | off | presence-based; per-tick enemy tracker vs server enemy count |
|
||||
| `TR_TRACKER_PROBE_PATH` | `/tmp/tracker_probe.jsonl` | where that goes |
|
||||
|
||||
The **adaptive-melee radar** has no env knobs. Its tuning lives in compile-time
|
||||
constants in `radars/adaptive_melee_radar.nim:36-50`: `MaxRadarTurnRate=45`,
|
||||
`FreshnessTicks=16`, `FreshStreakTicks=3`, `MarginDeg=20`,
|
||||
`EnterTrackWidthDeg=270`, `ExitTrackWidthDeg=300`. Only
|
||||
`TR_RADAR_FORCE_SPIN`/`TR_RADAR_SCANLOG` are runtime.
|
||||
|
||||
## 9. Test harness only (not read by the bot itself)
|
||||
|
||||
| variable | default | what it does |
|
||||
|---|---|---|
|
||||
| `TR_SERVER_JAR` | the 1.3.1 jar in `~/Downloads/robocode-tankroyale/` | which server the tests launch. Set to the 0.35.5 jar to reproduce old numbers |
|
||||
| `TR_BATTLE_RUNNER` | the 1.0.2 runner jar | which runner the tests launch |
|
||||
| `TR_BATTLE_RUNNER_DIR` | derived | directory form of the runner |
|
||||
| `TR_SERVER_JAR` | `/home/davide/Downloads/robocode-tankroyale/robocode-tankroyale-server.jar` (1.3.1) | which server the tests launch. Set to the 0.35.5 jar (`server_manager.LegacyServerJar`) to reproduce old numbers |
|
||||
| `TR_BATTLE_RUNNER` | `/home/davide/Projects/tank-royale/runner/examples/lib/robocode-tankroyale-runner.jar` | which runner the tests launch (`runner_process.nim`) |
|
||||
| `TR_BATTLE_RUNNER_DIR` | `tools/battle_runner/` | class directory form of the runner |
|
||||
|
||||
(`TR_SAMPLE_BOTS` is read by `SNNBot_garage/tests/test_bullet_economy.nim` only — not
|
||||
by the ModularBot test harness.)
|
||||
|
||||
## 10. Compile-time knobs (`-d:` flags, need a rebuild)
|
||||
|
||||
| flag | default | file |
|
||||
| flag | default | file / effect |
|
||||
|---|---|---|
|
||||
| `-d:TMH_NSTATES=N` | 64 | `guns/tm_horizon.nim` — automata inertia (the runtime env overrides it) |
|
||||
| `-d:TMH_NCLAUSES=N` | 40 | `guns/tm_horizon.nim` |
|
||||
| `-d:TMH_MIN_OBS=N` | 24 | cold gate: below this many samples the TM emits no correction |
|
||||
| `-d:TMH_STALE_MAX=N` | 8 | a sample is dropped if the enemy was not seen this recently |
|
||||
| `-d:TM_S_DEF=x` | `"3.0"` | `guns/tm_pattern.nim` specificity (`s`). **Higher = LONGER clauses**, measured. `s=1.0` is degenerate |
|
||||
| `-d:TM_NSTATES=N` | 64 | `guns/tm_pattern.nim` |
|
||||
| `-d:TM_NCLAUSES=N` | 40 | `guns/tm_pattern.nim` — clauses per class |
|
||||
| `-d:TMH_NSTATES=N` | 64 | `guns/tm_horizon.nim:102` — automata inertia (the runtime `TR_TMHORIZON_NSTATES` overrides it) |
|
||||
| `-d:TMH_NCLAUSES=N` | 40 | `guns/tm_horizon.nim:101` |
|
||||
| `-d:TMH_S_DEF="x"` | `"3.0"` | `guns/tm_horizon.nim:103` specificity (`s`) |
|
||||
| `-d:TMH_MIN_OBS=N` | 24 | `guns/tm_horizon.nim:106` — cold gate: below this many samples the TM emits no correction |
|
||||
| `-d:TMH_STALE_MAX=N` | 8 | `guns/tm_horizon.nim:108` — a sample is dropped if the enemy was not seen this recently |
|
||||
| `-d:TM_CLASSES=N` | 5 | `guns/tm_pattern.nim:50` — GF buckets |
|
||||
| `-d:TM_NCLAUSES=N` | 40 | `guns/tm_pattern.nim:53` — clauses per class |
|
||||
| `-d:TM_NSTATES=N` | 64 | `guns/tm_pattern.nim:55` |
|
||||
| `-d:TM_S_DEF="x"` | `"3.0"` | `guns/tm_pattern.nim:57` specificity (`s`). **Higher = LONGER clauses**, measured. `s=1.0` is degenerate |
|
||||
| `-d:TM_MIN_OBS=N` | 24 | `guns/tm_pattern.nim:59` |
|
||||
| `-d:TM_CONF_MARGIN_DEF` / `TM_SHRINK_DEF` / `TM_GF_MODE` / `TM_SOFT_BETA_DEF` / `TM_RADIAL_RANGE_DEF` / `TM_RAD_MARGIN_DEF` / `TM_REV_TURN_DEG_DEF` / `TM_REV_MARGIN_DEF` / `TM_REV_GAIN_DEF` | see `tm_pattern.nim:62-96` | strdefines, compile-time only |
|
||||
| `-d:TM_WINDOW_SIZE=N` | 10 | `guns/tsetlin.nim:29` |
|
||||
| `-d:TM_N_CLAUSES=N` | 50 | `guns/tsetlin.nim:116` — clauses per output |
|
||||
| `-d:TM_N_STATES=N` | 32 | `guns/tsetlin.nim:118` — automaton range `[-N..N]` |
|
||||
| `-d:TM_S_DEF="x"` | `"1.5"` | `guns/tsetlin.nim:119` — **same flag name as `tm_pattern`; one `-d` sets both** |
|
||||
| `-d:TM_T_DEF` | `""` (→ `float(TM_HALF)`) | `guns/tsetlin.nim:120` |
|
||||
|
||||
## Names that look real but do nothing
|
||||
|
||||
| name | reality |
|
||||
|---|---|
|
||||
| `TMH_NSTATES` (as an env var) | not an env var. The runtime var is `TR_TMHORIZON_NSTATES` |
|
||||
| `TR_VBULLET_METRIC` | does not exist. The metric env var is `GUN_VBULLET_METRIC` |
|
||||
| `TR_POWER_LOW_ENERGY` | removed (replaced by the energy slope). Only referenced in a comment; `getEnv` never reads it |
|
||||
| `TR_TRACKER_RECONCILE` | printed/set by `common_libs/tests/measure_corpse_melee.nim` only; **no bot module reads it** |
|
||||
|
||||
## Adding / finding knobs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user