j136 add TR_MODULE_* on/off switches + single [modules] boot inventory
This commit is contained in:
@@ -7,6 +7,7 @@ import env_boot # MUST be first: applies the .env file before any other
|
||||
# module's top-level `let` reads the environment.
|
||||
import std/[math, os, strformat, tables, sets, json, random, strutils]
|
||||
import robocode_tankroyale_botapi
|
||||
import module_switches
|
||||
import radar_harness/radar_interface
|
||||
import radars/radar_lock_module
|
||||
import radars/adaptive_melee_radar
|
||||
@@ -126,6 +127,10 @@ let VBulletAdmitOnly* =
|
||||
## tfil_ring — the new RANGE-WEIGHTED mover (movements/the_floor_is_lava_ring).
|
||||
## UNPROVEN: it must never become the default silently.
|
||||
let MovementName* = getEnv("TR_MOVEMENT", "strafe").strip().toLowerAscii()
|
||||
## The engine the tick ACTUALLY runs after the per-engine on/off switches
|
||||
## (`TR_MODULE_MOVE_*`, default all on) are applied. With every switch unset this
|
||||
## equals `MovementName`, so the dispatch below is byte-identical to before.
|
||||
let EffectiveMovementName* = resolveMoveEngine(MovementName)
|
||||
## TEMPORARY j134 diagnostic (TASK B): when `TR_FIRE_DIAG` is set, print the tick
|
||||
## of every enemy-hit / our-hit EVENT so it can be checked against the energy
|
||||
## reading tick logged by the mover. OFF by default; observability only.
|
||||
@@ -379,13 +384,13 @@ proc printConfig(bot: ModularBot, forceAll: bool = false) =
|
||||
let tc = if bot.currentTargetId != bot.prevTarget or forceAll: CLR_CHANGE else: ""
|
||||
let radarName = if bot.radarMode == 0: "radar_lock" else: "adaptive_melee"
|
||||
let moveName =
|
||||
if MovementName == "strafe":
|
||||
if EffectiveMovementName == "strafe":
|
||||
if bot.isRamming: "strafe(ram)" else: "strafe"
|
||||
elif MovementName == "tfil_ring":
|
||||
elif EffectiveMovementName == "tfil_ring":
|
||||
if bot.isRamming: "tfil_ring(ram)" else: "tfil_ring"
|
||||
elif MovementName == "surf":
|
||||
elif EffectiveMovementName == "surf":
|
||||
if bot.isRamming: "surf(ram)" else: "surf"
|
||||
elif MovementName == "learned":
|
||||
elif EffectiveMovementName == "learned":
|
||||
if bot.isRamming: "learned(ram)" else: "learned"
|
||||
elif bot.isRamming: "rammer"
|
||||
else: "tfil"
|
||||
@@ -1150,8 +1155,8 @@ method run*(bot: ModularBot) =
|
||||
let ramReason = ramTrigger(RamInputs(
|
||||
dist: ramDist, selfEnergy: ws.selfEnergy,
|
||||
enemyEnergy: ws.enemyEnergy, gunHitRate: gunHitRate))
|
||||
let triggerRam = bot.currentTargetId >= 0 and bot.ramCooldownTicks == 0 and
|
||||
ramReason != rrNone
|
||||
let triggerRam = ModuleRam and bot.currentTargetId >= 0 and
|
||||
bot.ramCooldownTicks == 0 and ramReason != rrNone
|
||||
# Abort an IN-PROGRESS ram on sustained incoming fire. The trigger itself is
|
||||
# unaffected, so a firefight before the approach never vetoes the start.
|
||||
let abortRam = shouldAbortRam(bot.isRamming, ramDmgRate)
|
||||
@@ -1195,13 +1200,13 @@ method run*(bot: ModularBot) =
|
||||
# through the single ring engine; the default `tfil` keeps the old
|
||||
# two-engine behaviour (tfil when holding, rammer when ramming).
|
||||
var spd, tr: float
|
||||
if MovementName == "strafe" and not shouldRam:
|
||||
if EffectiveMovementName == "strafe" and not shouldRam:
|
||||
(spd, tr) = bot.strafeMover.computeMove(ws)
|
||||
elif MovementName == "surf" and not shouldRam:
|
||||
elif EffectiveMovementName == "surf" and not shouldRam:
|
||||
(spd, tr) = bot.surfMover.computeMove(ws)
|
||||
elif MovementName == "learned" and not shouldRam:
|
||||
elif EffectiveMovementName == "learned" and not shouldRam:
|
||||
(spd, tr) = bot.learnedMover.computeMove(ws)
|
||||
elif MovementName == "tfil_ring":
|
||||
elif EffectiveMovementName == "tfil_ring":
|
||||
bot.ringMover.band =
|
||||
if shouldRam: (lo: 0.0, hi: 50.0)
|
||||
else: (lo: RangeLo, hi: RangeHi)
|
||||
@@ -1282,7 +1287,8 @@ method run*(bot: ModularBot) =
|
||||
# is 0, so TR_VBULLET_ADMIT_ONLY=0 reproduces the exact pre-change rack.
|
||||
var admit: array[17, bool]
|
||||
for gi in 0..<17:
|
||||
admit[gi] = vBulletAdmitted(gi, bot.rackMode, ActiveRackMembership,
|
||||
admit[gi] = ModuleVBullets and
|
||||
vBulletAdmitted(gi, bot.rackMode, ActiveRackMembership,
|
||||
VBulletAdmitOnly or gi == TmPatternId or
|
||||
gi == TmHorizonId or gi == BitBrainId)
|
||||
for i in 0..<len(PowerBins):
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
import std/[os, strutils, algorithm, times, sets]
|
||||
import env_dotenv
|
||||
import module_switches
|
||||
import gun_harness/virtual_bullets
|
||||
import gun_harness/selector
|
||||
import movements/ram_decision
|
||||
@@ -194,12 +195,10 @@ proc printEffectiveValues(ctx: EnvReportContext) =
|
||||
# ── movement / process-level ModularBot knobs ─────────────────────────────
|
||||
# Only `tfil_ring` and `strafe` select a non-default engine; every other value
|
||||
# runs `tfil`, so report the RESOLVED engine rather than the raw string.
|
||||
let effectiveMovement =
|
||||
if ctx.movementName == "tfil_ring": "tfil_ring"
|
||||
elif ctx.movementName == "strafe": "strafe"
|
||||
elif ctx.movementName == "surf": "surf"
|
||||
elif ctx.movementName == "learned": "learned"
|
||||
else: "tfil"
|
||||
# `resolveMoveEngine` applies the per-engine TR_MODULE_MOVE_* switches, so
|
||||
# this line reports the engine the tick will actually run (identical to the
|
||||
# old name-mapping when every switch is unset).
|
||||
let effectiveMovement = resolveMoveEngine(ctx.movementName)
|
||||
emit("TR_MOVEMENT", effectiveMovement, sourceOf("TR_MOVEMENT"))
|
||||
emit("TR_MOVEMENT_LOG", onOff(MovementLog), sourceOfPresence("TR_MOVEMENT_LOG"))
|
||||
emit("TR_VBULLET_ADMIT_ONLY", onOff(ctx.vBulletAdmitOnly),
|
||||
@@ -422,6 +421,79 @@ proc printEffectiveValues(ctx: EnvReportContext) =
|
||||
emit(EnvReportEnableEnvVar, getEnv(EnvReportEnableEnvVar, "1"),
|
||||
sourceOf(EnvReportEnableEnvVar))
|
||||
|
||||
# ── section C: the module on/off inventory ───────────────────────────────────
|
||||
#
|
||||
# One line per module, always the same shape:
|
||||
# [modules] gun.pattern = ON (source: default)
|
||||
# so `grep '^\[modules\]'` is the complete answer to "what is switched on right
|
||||
# now". The always-on core is listed too, marked `source: always-on`, rather
|
||||
# than silently omitted. Existing off switches (TR_RACK_<GUN>, TR_POWER_POLICY,
|
||||
# TR_FIRE_FIX, TR_RADAR_FORCE_SPIN, TR_VBULLET_DEBUG, TR_GEO_DEBUG,
|
||||
# TR_DEBUG_DRAW, TR_STRAFE_HEAT_GRID, TR_TFIL_HEAT_TIME) are surfaced under
|
||||
# their ONE name — no duplicate knobs are invented.
|
||||
|
||||
proc emitModule(name: string, on: bool, source: string, note = "") =
|
||||
var line = "[modules] " & name
|
||||
while line.len < len("[modules] ") + 20: line.add ' '
|
||||
line.add "= "
|
||||
line.add(if on: "ON" else: "off")
|
||||
line.add " (source: "
|
||||
line.add source
|
||||
if note.len > 0:
|
||||
line.add "; "
|
||||
line.add note
|
||||
line.add ")"
|
||||
echo line
|
||||
|
||||
proc printModules(ctx: EnvReportContext) =
|
||||
echo "[modules] --- C. module on/off inventory (grep '^\\[modules\\]' for all) ---"
|
||||
|
||||
# Guns. Rack membership IS the on/off switch: `off` = disabled, any other
|
||||
# mode = enabled in that rack.
|
||||
for i in 0..<len(RackGunNames):
|
||||
emitModule("gun." & RackGunNames[i].toLowerAscii(),
|
||||
ActiveRackMembership[i] != rmOff,
|
||||
sourceOf(RackEnvPrefix & RackGunNames[i]),
|
||||
"rack: " & rackMembershipName(ActiveRackMembership[i]))
|
||||
|
||||
# Movement engines + the engine the tick actually runs.
|
||||
for e in MoveEngines:
|
||||
emitModule("move." & e.name, e.enabled, sourceOf(e.envVar))
|
||||
echo "[modules] move.effective = ", resolveMoveEngine(ctx.movementName),
|
||||
" (requested: TR_MOVEMENT=", ctx.movementName, ")"
|
||||
|
||||
# New TR_MODULE_* switches (j136).
|
||||
emitModule("vbullets.tracker", ModuleVBullets, sourceOf(ModuleVBulletsEnvVar),
|
||||
"off: no predict/spawn; selector falls back to its floor gun")
|
||||
emitModule("ram.decider", ModuleRam, sourceOf(ModuleRamEnvVar),
|
||||
"off: never ram; the movement engine alone drives")
|
||||
|
||||
# Existing off switches, surfaced as modules under their ONE existing name.
|
||||
emitModule("radar.adaptive", not ctx.radarForceSpin,
|
||||
sourceOfPresence("TR_RADAR_FORCE_SPIN"),
|
||||
"off via TR_RADAR_FORCE_SPIN: full 360 spin")
|
||||
emitModule("radar.lock", true, "always-on",
|
||||
"1v1 radar; without it the bot cannot function")
|
||||
emitModule("power.policy", PowerPolicyEnabled, sourceOf(PowerPolicyEnvVar),
|
||||
"off via TR_POWER_POLICY: uncapped")
|
||||
emitModule("fire.fix", TfilFireFix, sourceOf("TR_FIRE_FIX"),
|
||||
"off: shipped prev-energy detector")
|
||||
emitModule("dbg.debug_draw", ctx.debugDraw, sourceOfPresence(DebugDrawEnv),
|
||||
"off: every mover debugGraphics=false")
|
||||
emitModule("dbg.vbullet", ctx.vBulletDebug, sourceOfPresence(VBulletDebugEnv))
|
||||
emitModule("dbg.geo", ctx.geoDebug, sourceOfPresence(GeoDebugEnv))
|
||||
emitModule("dbg.heat_grid", StrafeHeatGrid, sourceOf("TR_STRAFE_HEAT_GRID"))
|
||||
emitModule("dbg.heat_time", TfilHeatTime, sourceOf("TR_TFIL_HEAT_TIME"),
|
||||
"off: flat (time-independent) bullet heat")
|
||||
|
||||
# Always-on core, listed so the inventory is complete.
|
||||
emitModule("core.enemy_tracker", true, "always-on",
|
||||
"without it the bot has no enemy positions")
|
||||
emitModule("core.target_select", true, "always-on",
|
||||
"picks which enemy to aim at")
|
||||
emitModule("core.gun_selector", true, "always-on",
|
||||
"ranks the rack; empty fitness floor = first admitted gun")
|
||||
|
||||
# ── build identity (which binary is the GUI actually running?) ───────────────
|
||||
|
||||
proc printBuildIdentity() =
|
||||
@@ -466,6 +538,10 @@ proc knownEnvNames*(): seq[string] =
|
||||
# exported constants — the single source of truth (do not re-spell these)
|
||||
MetricEnvVar, SelectorModeEnvVar, TieBreakEnvVar,
|
||||
RackShareEnvVar,
|
||||
# module on/off switches (j136) — the new TR_MODULE_* prefix
|
||||
ModuleVBulletsEnvVar, ModuleRamEnvVar,
|
||||
ModuleMoveTfilEnvVar, ModuleMoveRingEnvVar, ModuleMoveStrafeEnvVar,
|
||||
ModuleMoveSurfEnvVar, ModuleMoveLearnedEnvVar,
|
||||
PowerPolicyEnvVar, PowerFarDistEnvVar, PowerFarCapEnvVar, PowerMidCapEnvVar,
|
||||
PowerRefEnvVar, PowerEnergyHiEnvVar, PowerEnergyLoEnvVar,
|
||||
PowerEnergyMinEnvVar, PowerEnergyMaxEnvVar, PowerFinishKillEnvVar,
|
||||
@@ -661,5 +737,6 @@ proc printEnvReport*(ctx: EnvReportContext) =
|
||||
printRawEnvironment()
|
||||
printMistakeWarnings()
|
||||
printEffectiveValues(ctx)
|
||||
printModules(ctx)
|
||||
printBuildIdentity()
|
||||
echo "[env] === END ENVIRONMENT ==="
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
## Module on/off switches (job j136).
|
||||
##
|
||||
## The owner drives the bot from a `.env` file and wants to flip whole modules
|
||||
## on and off to test combinations. Modules that ALREADY had an off switch keep
|
||||
## it — no duplicate knobs are introduced here:
|
||||
##
|
||||
## adaptive melee radar -> TR_RADAR_FORCE_SPIN (on = force the old full spin)
|
||||
## power policy -> TR_POWER_POLICY
|
||||
## fire fix -> TR_FIRE_FIX
|
||||
## time-indexed heat -> TR_TFIL_HEAT_TIME
|
||||
## virtual-bullet overlay -> TR_VBULLET_DEBUG
|
||||
## geometry overlay -> TR_GEO_DEBUG
|
||||
## global debug draw -> TR_DEBUG_DRAW
|
||||
## strafe heat grid -> TR_STRAFE_HEAT_GRID
|
||||
## every gun -> TR_RACK_<GUN> (membership `off` = disabled)
|
||||
##
|
||||
## Everything NEW below uses one uniform, greppable prefix: `TR_MODULE_<NAME>`.
|
||||
## Every switch defaults to ON, so an unset environment is byte-for-byte the
|
||||
## shipped bot. "OFF" always means the simpler/older fallback, never a broken
|
||||
## bot:
|
||||
##
|
||||
## TR_MODULE_VBULLETS=0 no gun predicts or spawns virtual bullets; the
|
||||
## selector falls back to its floor gun.
|
||||
## TR_MODULE_RAM=0 no ramming at all; the movement engine alone
|
||||
## drives (the finisher/desperation/plan/opportunity
|
||||
## triggers all stop firing).
|
||||
## TR_MODULE_MOVE_<ENGINE>=0 that engine is no longer selectable. The
|
||||
## EFFECTIVE engine falls back to the first enabled
|
||||
## engine, with `tfil` (the long-shipped mover) as
|
||||
## the designated fallback. There is no "no
|
||||
## movement": if every engine is switched off the
|
||||
## bot still runs `tfil`.
|
||||
##
|
||||
## Read once at module init, like every other knob, so one compiled binary can
|
||||
## A/B every arm by environment alone.
|
||||
|
||||
import std/[os, strutils]
|
||||
|
||||
# ── the new switches ─────────────────────────────────────────────────────────
|
||||
|
||||
const
|
||||
ModuleVBulletsEnvVar* = "TR_MODULE_VBULLETS"
|
||||
ModuleRamEnvVar* = "TR_MODULE_RAM"
|
||||
ModuleMoveTfilEnvVar* = "TR_MODULE_MOVE_TFIL"
|
||||
ModuleMoveRingEnvVar* = "TR_MODULE_MOVE_TFIL_RING"
|
||||
ModuleMoveStrafeEnvVar* = "TR_MODULE_MOVE_STRAFE"
|
||||
ModuleMoveSurfEnvVar* = "TR_MODULE_MOVE_SURF"
|
||||
ModuleMoveLearnedEnvVar* = "TR_MODULE_MOVE_LEARNED"
|
||||
|
||||
proc moduleFlagOn*(name: string, default = true): bool =
|
||||
## Value-based, exactly like `TR_POWER_POLICY`: unset/empty -> `default`;
|
||||
## explicit 0/false/no/off -> off; any other value -> on.
|
||||
let v = getEnv(name, "").strip().toLowerAscii()
|
||||
if v.len == 0: return default
|
||||
v notin ["0", "false", "no", "off"]
|
||||
|
||||
let ModuleVBullets* = moduleFlagOn(ModuleVBulletsEnvVar)
|
||||
## OFF: the virtual-bullet tracker stops. The selector's fitness stays empty
|
||||
## and `chooseFromFit` takes its floor path (the first admitted gun), so the
|
||||
## bot keeps aiming and firing — just without the fitness signal.
|
||||
let ModuleRam* = moduleFlagOn(ModuleRamEnvVar)
|
||||
## OFF: `triggerRam` is never true, so `isRamming` stays false and the
|
||||
## movement engine drives every tick (the pre-rammer fallback).
|
||||
|
||||
type
|
||||
MoveEngine* = object
|
||||
name*: string ## the `TR_MOVEMENT` value
|
||||
envVar*: string ## its `TR_MODULE_MOVE_*` switch
|
||||
enabled*: bool
|
||||
|
||||
let MoveEngines*: array[5, MoveEngine] = [
|
||||
MoveEngine(name: "tfil", envVar: ModuleMoveTfilEnvVar,
|
||||
enabled: moduleFlagOn(ModuleMoveTfilEnvVar)),
|
||||
MoveEngine(name: "tfil_ring", envVar: ModuleMoveRingEnvVar,
|
||||
enabled: moduleFlagOn(ModuleMoveRingEnvVar)),
|
||||
MoveEngine(name: "strafe", envVar: ModuleMoveStrafeEnvVar,
|
||||
enabled: moduleFlagOn(ModuleMoveStrafeEnvVar)),
|
||||
MoveEngine(name: "surf", envVar: ModuleMoveSurfEnvVar,
|
||||
enabled: moduleFlagOn(ModuleMoveSurfEnvVar)),
|
||||
MoveEngine(name: "learned", envVar: ModuleMoveLearnedEnvVar,
|
||||
enabled: moduleFlagOn(ModuleMoveLearnedEnvVar)),
|
||||
]
|
||||
|
||||
proc moveEngineEnvVar*(name: string): string =
|
||||
## The switch name for `name`, or "" when it is not a known engine.
|
||||
for e in MoveEngines:
|
||||
if e.name == name: return e.envVar
|
||||
""
|
||||
|
||||
proc moveEngineEnabled*(name: string): bool =
|
||||
for e in MoveEngines:
|
||||
if e.name == name: return e.enabled
|
||||
false
|
||||
|
||||
proc resolveMoveEngine*(requested: string): string =
|
||||
## The EFFECTIVE engine the live tick will run.
|
||||
## * an unknown/empty `requested` means `tfil` (the shipped fallback);
|
||||
## * the requested engine is used when its switch is on;
|
||||
## * otherwise the first enabled engine in `MoveEngines` (tfil first);
|
||||
## * if every engine is off, `tfil` is forced — there is no no-movement.
|
||||
var req = requested.strip().toLowerAscii()
|
||||
if moveEngineEnvVar(req).len == 0: req = "tfil"
|
||||
if moveEngineEnabled(req): return req
|
||||
for e in MoveEngines:
|
||||
if e.enabled: return e.name
|
||||
"tfil"
|
||||
Reference in New Issue
Block a user