STRAFE movement: body pinned perpendicular to the threat, reversals by sign flip
New engine movements/strafe.nim, selected by TR_MOVEMENT=strafe (default stays tfil, byte-identical — test_tfil_commit_env.nim's 30 checks still pass). Design (the owner's): - AXIS = incoming bullet's direction when a bullet is in flight, else the perpendicular of the enemy bearing. The body heading is kept inside a band (TR_STRAFE_BAND, default 20 deg) around the perpendicular LINE; it turns only when outside the band, and never turns to face a movement target. - Candidate tiles on the perpendicular line through our position, both forward and backward, within TR_STRAFE_REACH px, with a perpendicular jitter of +/- TR_STRAFE_SPREAD tiles. A tile is acceptable when its path max heat is <= PathDangerThreshold, the SAME safety rule TFIL uses. - Move by SIGN only: setForward(+/-MaxSpeed>). Dwell is re-picked after a random number of ticks in [TR_STRAFE_DWELL_MIN, TR_STRAFE_DWELL_MAX], on arrival, or on a serious threat spike. - Heat machinery is REUSED from the shipped mover, not re-implemented: the exported heatDecay()/bulletMagScale() (j105 time-indexed model) and the PillarHotness/PillarRadiance globals (j106 pillar-free default). The heat shape is overridable via TR_STRAFE_CORRIDOR_HEAT/WALL_HOTNESS/WALL_RADIANCE (defaults = the shipped TFIL field). - GUI overlay: strafe line, threat axis, candidate tiles (safe/unsafe), chosen target, sign-coloured movement ray, and the heading band. Gates (offline, recorded DrussGT fixture, 20026 ticks): - A TILE AVAILABILITY: shipped heat field -> a safe tile exists on only 36.6% of picks (63.4% fall back to the least-hot tile); the ring retune (corridor 5, wall 10/5) raises it to 91.9%. - B PREDICTABILITY: reversal-interval entropy 5.84 bits vs TFIL 5.09; direction entropy 1.00 both; long-lag autocorrelation ~0 for both (no periodic component). Fewer reversals (710 vs 1453) and more full-speed ticks. measurements: common_libs/tests/measure_strafe_gates.nim Also registers TR_STRAFE_* in the boot env report (ModularBot_garage/src/ env_report.nim) and wires the engine into ModularBot.nim (hold -> strafe, ram trigger -> rammer).
This commit is contained in:
@@ -33,6 +33,7 @@ import movements/rammer
|
||||
import movements/ram_decision
|
||||
import movements/the_floor_is_lava
|
||||
import movements/the_floor_is_lava_ring
|
||||
import movements/strafe
|
||||
import movement_harness/virtual_bodies as mvb
|
||||
import movement_harness/bullet_shadows
|
||||
import targeting/enemy_tracker
|
||||
@@ -193,6 +194,7 @@ type
|
||||
bitbrain: BitBrainGun
|
||||
mover: TFILModule
|
||||
ringMover: TFILRingModule
|
||||
strafeMover: StrafeModule
|
||||
rammer: RammerModule
|
||||
isRamming: bool
|
||||
ramDurationTicks: int
|
||||
@@ -317,7 +319,9 @@ 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 == "tfil_ring":
|
||||
if MovementName == "strafe":
|
||||
if bot.isRamming: "strafe(ram)" else: "strafe"
|
||||
elif MovementName == "tfil_ring":
|
||||
if bot.isRamming: "tfil_ring(ram)" else: "tfil_ring"
|
||||
elif bot.isRamming: "rammer"
|
||||
else: "tfil"
|
||||
@@ -567,6 +571,7 @@ method onBulletHitWall*(bot: ModularBot, e: BulletHitWallEvent) =
|
||||
bot.moveTracker.shadows.removeBulletNear(e.bullet.x, e.bullet.y)
|
||||
bot.mover.removeBulletNear(e.bullet.x, e.bullet.y)
|
||||
bot.ringMover.removeBulletNear(e.bullet.x, e.bullet.y)
|
||||
bot.strafeMover.removeBulletNear(e.bullet.x, e.bullet.y)
|
||||
|
||||
method onBulletHitBullet*(bot: ModularBot, e: BulletHitBulletEvent) =
|
||||
discard bot.resolveOwnBullet(e.bullet.bulletId) # bullet-vs-bullet: free the slot
|
||||
@@ -574,6 +579,7 @@ method onBulletHitBullet*(bot: ModularBot, e: BulletHitBulletEvent) =
|
||||
bot.moveTracker.shadows.removeBulletNear(e.bullet.x, e.bullet.y)
|
||||
bot.mover.removeBulletNear(e.bullet.x, e.bullet.y)
|
||||
bot.ringMover.removeBulletNear(e.bullet.x, e.bullet.y)
|
||||
bot.strafeMover.removeBulletNear(e.bullet.x, e.bullet.y)
|
||||
|
||||
method onHitByBullet*(bot: ModularBot, e: HitByBulletEvent) =
|
||||
# Feeds the ram bullet-rain abort window. Accumulate REAL ENERGY (the server's
|
||||
@@ -735,6 +741,7 @@ method onRoundStarted*(bot: ModularBot, e: RoundStartedEvent) =
|
||||
bot.meleeRadar.init()
|
||||
bot.mover.resetRound()
|
||||
bot.ringMover.resetRound()
|
||||
bot.strafeMover.resetRound()
|
||||
bot.enemyTracker.resetRound()
|
||||
# The TM pattern gun is cold every round (no cross-battle persistence): wipe
|
||||
# its clause teams and motion history explicitly, so a same-id opponent in the
|
||||
@@ -971,12 +978,15 @@ method run*(bot: ModularBot) =
|
||||
if bot.isRamming:
|
||||
bot.logRamChange(true, ramReason, ramDist, ws, ramDmgRate)
|
||||
|
||||
# Movement dispatch. `TR_MOVEMENT=tfil_ring` routes BOTH holding and
|
||||
# ramming through the single ring engine (ram is just band [0, 50]);
|
||||
# `shouldRam` already accounts for the bullet-rain abort. The default `tfil`
|
||||
# keeps the old two-engine behaviour (tfil when holding, rammer when ramming).
|
||||
# Movement dispatch. `TR_MOVEMENT=strafe` (this module) routes holding
|
||||
# through the perpendicular-strafe engine and still lets the rammer take
|
||||
# over on a ram trigger; `tfil_ring` routes BOTH holding and ramming
|
||||
# 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 == "tfil_ring":
|
||||
if MovementName == "strafe" and not shouldRam:
|
||||
(spd, tr) = bot.strafeMover.computeMove(ws)
|
||||
elif MovementName == "tfil_ring":
|
||||
bot.ringMover.band =
|
||||
if shouldRam: (lo: 0.0, hi: 50.0)
|
||||
else: (lo: RangeLo, hi: RangeHi)
|
||||
@@ -1280,6 +1290,7 @@ when isMainModule:
|
||||
meleeRadar: initAdaptiveMeleeRadar(),
|
||||
mover: TFILModule(debugGraphics: true),
|
||||
ringMover: TFILRingModule(debugGraphics: true),
|
||||
strafeMover: StrafeModule(debugGraphics: true),
|
||||
rammer: initRammer(),
|
||||
moveTracker: mvb.initVirtualBodyTracker(1),
|
||||
currentGun: -1,
|
||||
|
||||
@@ -25,6 +25,7 @@ import gun_harness/selector
|
||||
import movements/ram_decision
|
||||
import movements/the_floor_is_lava
|
||||
import movements/the_floor_is_lava_ring
|
||||
import movements/strafe
|
||||
import guns/tm_horizon
|
||||
import guns/bitbrain_gun
|
||||
import guns/pattern_matcher
|
||||
@@ -174,9 +175,12 @@ proc printEffectiveValues(ctx: EnvReportContext) =
|
||||
echo "[env] --- B. effective values (resolved at boot) ---"
|
||||
|
||||
# ── movement / process-level ModularBot knobs ─────────────────────────────
|
||||
# Only `tfil_ring` selects the ring 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" else: "tfil"
|
||||
# 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"
|
||||
else: "tfil"
|
||||
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),
|
||||
@@ -259,6 +263,19 @@ proc printEffectiveValues(ctx: EnvReportContext) =
|
||||
emit("TR_TFIL_PILLAR_ON", onOff(PillarHotness > 0.0),
|
||||
sourceOf("TR_TFIL_PILLAR_ON"))
|
||||
|
||||
# ── movement (STRAFE) ─────────────────────────────────────────────────────
|
||||
emit("TR_STRAFE_BAND", $StrafeBand, sourceOf("TR_STRAFE_BAND"))
|
||||
emit("TR_STRAFE_SPREAD", $StrafeSpread, sourceOf("TR_STRAFE_SPREAD"))
|
||||
emit("TR_STRAFE_REACH", $StrafeReach, sourceOf("TR_STRAFE_REACH"))
|
||||
emit("TR_STRAFE_DWELL_MIN", $StrafeDwellMin, sourceOf("TR_STRAFE_DWELL_MIN"))
|
||||
emit("TR_STRAFE_DWELL_MAX", $StrafeDwellMax, sourceOf("TR_STRAFE_DWELL_MAX"))
|
||||
emit("TR_STRAFE_LOG", onOff(StrafeLog), sourceOfPresence("TR_STRAFE_LOG"))
|
||||
# STRAFE's heat shape is override-able (default = the shipped TFIL field) so
|
||||
# the shipped field and the ring retune can be A/B'd on one binary at boot.
|
||||
emit("TR_STRAFE_CORRIDOR_HEAT", $StrafeCorridorHeat, sourceOf("TR_STRAFE_CORRIDOR_HEAT"))
|
||||
emit("TR_STRAFE_WALL_HOTNESS", $StrafeWallHotness, sourceOf("TR_STRAFE_WALL_HOTNESS"))
|
||||
emit("TR_STRAFE_WALL_RADIANCE", $StrafeWallRadiance, sourceOf("TR_STRAFE_WALL_RADIANCE"))
|
||||
|
||||
# ── ramming ───────────────────────────────────────────────────────────────
|
||||
emit("TR_RAM_OPPORTUNITY", onOff(RamOppEnabled), sourceOf("TR_RAM_OPPORTUNITY"))
|
||||
emit("TR_RAM_OPP_DIST", $RamOppDist, sourceOf("TR_RAM_OPP_DIST"))
|
||||
@@ -409,6 +426,10 @@ proc knownEnvNames*(): seq[string] =
|
||||
"TR_TFIL_COMMIT_LOG",
|
||||
"TR_TFIL_HEAT_TIME", "TR_TFIL_HEAT_TAU", "TR_TFIL_HEAT_POWER_GAIN",
|
||||
"TR_TFIL_PILLAR_ON",
|
||||
"TR_STRAFE_BAND", "TR_STRAFE_SPREAD", "TR_STRAFE_REACH",
|
||||
"TR_STRAFE_DWELL_MIN", "TR_STRAFE_DWELL_MAX", "TR_STRAFE_LOG",
|
||||
"TR_STRAFE_CORRIDOR_HEAT", "TR_STRAFE_WALL_HOTNESS",
|
||||
"TR_STRAFE_WALL_RADIANCE",
|
||||
# harness vars (read by the test framework, inherited by the bot, so they
|
||||
# must NOT be reported as typos)
|
||||
"TR_SERVER_JAR", "TR_BATTLE_RUNNER", "TR_BATTLE_RUNNER_DIR",
|
||||
|
||||
Reference in New Issue
Block a user