gun j123 Task A+B prereg: expose Pattern's TR_PATTERN_LEN/TR_PATTERN_DEPTH (default parity) and pre-register the 6-arm match-parameter sweep
This commit is contained in:
@@ -18,9 +18,20 @@ import gun_harness/gun_interface
|
||||
|
||||
const
|
||||
HistorySize* = 500
|
||||
PatternLen* = 10 # ticks used as search key; ponytail: fixed, expose if tuning needed
|
||||
PatternLen* = 10 # ticks used as search key; shipped default of TR_PATTERN_LEN
|
||||
## Shipped defaults of the two MATCH-SHAPE runtime knobs (Task A, gun j123).
|
||||
## `TR_PATTERN_LEN` is the length of the movement segment compared (the
|
||||
## search key); `TR_PATTERN_DEPTH` is how many recent history entries the
|
||||
## matcher is allowed to scan. Both default to the exact pre-knob constants,
|
||||
## so an unset environment reproduces today's gun byte-for-byte.
|
||||
PatternDepth* = HistorySize # max history entries scanned; default = full buffer
|
||||
PatternRadOffsetEnvVar* = "TR_PATTERN_RAD_OFFSET" ## px; negative = aim short
|
||||
PatternRadScaleEnvVar* = "TR_PATTERN_RAD_SCALE" ## multiplier on aim distance
|
||||
PatternLenEnvVar* = "TR_PATTERN_LEN" ## search-key length (ticks)
|
||||
## History/replay depth: the search never looks further back than this many
|
||||
## entries. The buffer itself is a fixed `array[HistorySize]`, so the useful
|
||||
## range is 1..HistorySize; the ceiling cannot be raised at runtime.
|
||||
PatternDepthEnvVar* = "TR_PATTERN_DEPTH"
|
||||
|
||||
proc patternEnvFloat(name: string, default: float): float =
|
||||
## Read an env knob like every other runtime switch in the harness: unset or
|
||||
@@ -30,6 +41,18 @@ proc patternEnvFloat(name: string, default: float): float =
|
||||
try: parseFloat(v.strip())
|
||||
except ValueError: default
|
||||
|
||||
proc patternEnvInt(name: string, default: int): int =
|
||||
## Integer twin of `patternEnvFloat`: unset or unparsable -> shipped default.
|
||||
let v = getEnv(name, "")
|
||||
if v.len == 0: return default
|
||||
try: parseInt(v.strip())
|
||||
except ValueError: default
|
||||
|
||||
proc clampInt(v, lo, hi: int): int {.inline.} =
|
||||
result = v
|
||||
if result < lo: result = lo
|
||||
if result > hi: result = hi
|
||||
|
||||
type
|
||||
MoveTick = object
|
||||
velocity: float ## signed speed (px/tick)
|
||||
@@ -60,6 +83,10 @@ type
|
||||
pathY: array[HistorySize + 1, float]
|
||||
pathHeading: array[HistorySize + 1, float] ## radians after s steps
|
||||
pathSpeed: array[HistorySize + 1, float] ## speed after s steps
|
||||
# ── match-shape knobs (defaults leave the prediction byte-identical) ──
|
||||
patternLen*: int ## search-key length actually used (default PatternLen)
|
||||
histDepth*: int ## history entries actually scanned (default full)
|
||||
paramConfigured*: bool ## true once the env/setter has populated the two above
|
||||
# ── radial offset knob (defaults leave the prediction byte-identical) ──
|
||||
radScale*: float ## multiplier on the predicted aim distance
|
||||
radOffset*: float ## px added to the predicted aim distance
|
||||
@@ -95,20 +122,30 @@ proc findBestMatch(g: var PatternMatcherGun): int =
|
||||
## Speed-independent history search. Returns the start index of the best
|
||||
## matching pattern, or -1 when there is not enough history. Stores the best
|
||||
## match cost in `g.lastMatchScore` for the confidence readout.
|
||||
##
|
||||
## `g.patternLen` (TR_PATTERN_LEN) is the key length; `g.histDepth`
|
||||
## (TR_PATTERN_DEPTH) bounds how far back the scan may reach. With the shipped
|
||||
## defaults (10, HistorySize) this is the exact pre-knob loop.
|
||||
let plen = g.patternLen
|
||||
g.lastMatchScore = Inf
|
||||
if g.count < PatternLen * 2:
|
||||
if g.count < plen * 2:
|
||||
return -1
|
||||
|
||||
# key = last PatternLen entries
|
||||
let keyStart = g.count - PatternLen
|
||||
# key = last plen entries
|
||||
let keyStart = g.count - plen
|
||||
|
||||
# scan backwards for best match (exclude the key itself)
|
||||
# scan backwards for best match (exclude the key itself), but never further
|
||||
# back than `histDepth` entries; when count <= histDepth this floor is 0 and
|
||||
# the loop is identical to the original.
|
||||
var bestScore = Inf
|
||||
var bestMatch = -1
|
||||
let scanEnd = g.count - PatternLen - 1 # last valid match start
|
||||
for i in countdown(scanEnd, 0):
|
||||
let scanEnd = g.count - plen - 1 # last valid match start
|
||||
let scanFloor = max(0, g.count - g.histDepth)
|
||||
if scanEnd < scanFloor:
|
||||
return -1
|
||||
for i in countdown(scanEnd, scanFloor):
|
||||
var score = 0.0
|
||||
for k in 0 ..< PatternLen:
|
||||
for k in 0 ..< plen:
|
||||
let a = g.readAt(keyStart + k)
|
||||
let b = g.readAt(i + k)
|
||||
let dv = a.velocity - b.velocity
|
||||
@@ -123,7 +160,7 @@ proc findBestMatch(g: var PatternMatcherGun): int =
|
||||
proc buildPath(g: var PatternMatcherGun, state: WorldState, bestMatch: int) =
|
||||
## Precompute the matched pattern replayed forward from the current state.
|
||||
## Only depends on observed movement, so it is valid for every power bin.
|
||||
g.playStart = bestMatch + PatternLen
|
||||
g.playStart = bestMatch + g.patternLen
|
||||
g.playAvail = g.count - 1 - g.playStart # ticks we can replay
|
||||
g.pathX[0] = state.enemyX
|
||||
g.pathY[0] = state.enemyY
|
||||
@@ -173,6 +210,21 @@ proc ensureRadialConfig(g: var PatternMatcherGun) {.inline.} =
|
||||
g.radOffset = patternEnvFloat(PatternRadOffsetEnvVar, 0.0)
|
||||
g.radConfigured = true
|
||||
|
||||
proc ensureParamConfig(g: var PatternMatcherGun) {.inline.} =
|
||||
## Lazily read the match-shape env knobs on first use, mirroring the radial
|
||||
## knobs: the shipped binary reads the env once per gun instance.
|
||||
if g.paramConfigured: return
|
||||
g.patternLen = clampInt(patternEnvInt(PatternLenEnvVar, PatternLen), 1, HistorySize)
|
||||
g.histDepth = clampInt(patternEnvInt(PatternDepthEnvVar, PatternDepth), 1, HistorySize)
|
||||
g.paramConfigured = true
|
||||
|
||||
proc setMatchParams*(g: var PatternMatcherGun, patternLen, histDepth: int) =
|
||||
## Explicit per-gun override used by offline sweeps/tests. Writes the same
|
||||
## fields the env path writes, so the measured code path is identical.
|
||||
g.patternLen = clampInt(patternLen, 1, HistorySize)
|
||||
g.histDepth = clampInt(histDepth, 1, HistorySize)
|
||||
g.paramConfigured = true
|
||||
|
||||
proc setRadialCorrection*(g: var PatternMatcherGun, scale, offsetPx: float) =
|
||||
## Explicit per-gun override used by the offline sweep. Writes the same fields
|
||||
## the env path writes, so the measured code path is identical.
|
||||
@@ -220,6 +272,7 @@ proc predict*(g: var PatternMatcherGun, state: WorldState,
|
||||
g.cacheValid = true
|
||||
g.cacheTick = state.tick
|
||||
|
||||
g.ensureParamConfig()
|
||||
g.bestMatch = g.findBestMatch()
|
||||
if g.bestMatch >= 0:
|
||||
g.buildPath(state, g.bestMatch)
|
||||
|
||||
Reference in New Issue
Block a user