feat(SNNBot): offset-based exemplar learning for fast lead generalization
Exemplars now store lead correction offsets instead of absolute angles. The system learns 'how much lead to apply' independently of bearing, so one learned lead pattern generalizes to all positions on the battlefield. Converges in a few ticks for constant-velocity targets.
This commit is contained in:
@@ -13,7 +13,7 @@ type
|
||||
|
||||
Exemplar* = object
|
||||
pattern*: BitVec80
|
||||
angle*: float # degrees 0-360
|
||||
offset*: float # lead correction in degrees, typically [-30, +30]
|
||||
active*: bool
|
||||
|
||||
BinaryAimer* = object
|
||||
@@ -27,7 +27,7 @@ proc initBinaryAimer*(): BinaryAimer =
|
||||
proc forward*(aimer: var BinaryAimer, input: BitVec80): float =
|
||||
## Kernel regression: weighted circular mean over active exemplars.
|
||||
## Weights by similarity^2 and exponential recency decay (0.95^age).
|
||||
## Returns angle in [0,360) degrees, or -1.0 sentinel when cold (no weight).
|
||||
## Returns offset (lead correction) in degrees, or -999.0 sentinel when cold (no weight).
|
||||
var sinSum = 0.0
|
||||
var cosSum = 0.0
|
||||
var totalW = 0.0
|
||||
@@ -41,18 +41,16 @@ proc forward*(aimer: var BinaryAimer, input: BitVec80): float =
|
||||
let age = (aimer.nextSlot - 1 - i + MAX_K) mod MAX_K
|
||||
let recency = pow(0.95, age.float)
|
||||
let weight = float(excess * excess) * recency
|
||||
let rad = degToRad(aimer.exemplars[i].angle)
|
||||
let rad = degToRad(aimer.exemplars[i].offset)
|
||||
sinSum += weight * sin(rad)
|
||||
cosSum += weight * cos(rad)
|
||||
totalW += weight
|
||||
if totalW == 0.0:
|
||||
return -1.0
|
||||
var angle = radToDeg(arctan2(sinSum, cosSum))
|
||||
if angle < 0.0: angle += 360.0
|
||||
result = angle
|
||||
return -999.0
|
||||
result = radToDeg(arctan2(sinSum, cosSum))
|
||||
|
||||
proc learn*(aimer: var BinaryAimer, input: BitVec80, correctAngle: float) =
|
||||
proc learn*(aimer: var BinaryAimer, input: BitVec80, offset: float) =
|
||||
## Ring-buffer store: write exemplar at nextSlot, advance mod MAX_K.
|
||||
aimer.exemplars[aimer.nextSlot] = Exemplar(pattern: input, angle: correctAngle, active: true)
|
||||
aimer.exemplars[aimer.nextSlot] = Exemplar(pattern: input, offset: offset, active: true)
|
||||
aimer.nextSlot = (aimer.nextSlot + 1) mod MAX_K
|
||||
if aimer.count < MAX_K: inc aimer.count
|
||||
|
||||
Reference in New Issue
Block a user