Files
arcattackandClaude Fable 5 1ace2e3cb4 Gyro: BT_GYRO_TRACE per-frame integrator trace -- oscillation verified vs authored constants (task #56)
25-hit MP autofire trace: kick -> damped oscillation -> settle, peaks well
inside clamps, no NaN/drift; measured Y:X frequency ratio 7.75x matches
sqrt(springK.y/springK.x) exactly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:56:55 -05:00

57 lines
2.8 KiB
Python

import re, sys
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
frames, eyes, bodies = [], [], []
hits = [] # (frame_at_hit, t, p, y, v)
last_f = None
for line in open("../content/gtrace_a.log", encoding="utf-8", errors="replace"):
m = re.match(r"\[gtrace\] g=\S+ f=(\d+) eye=(\S+) (\S+) (\S+) body=(\S+) (\S+) (\S+)", line)
if m:
last_f = int(m.group(1))
frames.append(last_f)
eyes.append(tuple(float(m.group(i)) for i in (2,3,4)))
bodies.append(tuple(float(m.group(i)) for i in (5,6,7)))
continue
m = re.match(r"\[gtrace\] g=\S+ HIT t=(\S+) p=(\S+) y=(\S+) v=(\S+)", line)
if m:
hits.append((last_f, *[float(m.group(i)) for i in (1,2,3,4)]))
print(f"trace frames: {len(frames)}, hits: {len(hits)}")
print(f"frame range: {frames[0]}..{frames[-1]}")
# stats per volley: peak |eye|, settle
import math
mag = [math.sqrt(e[0]**2+e[1]**2+e[2]**2) for e in eyes]
peak = max(mag); print(f"peak |eyePos| = {peak:.4f} u (clamp legal range +-0.1/0.15 per axis)")
peakY = max(abs(e[1]) for e in eyes); peakX = max(abs(e[0]) for e in eyes); peakZ = max(abs(e[2]) for e in eyes)
print(f"peak per-axis |x|={peakX:.4f} |y|={peakY:.4f} |z|={peakZ:.4f}")
bad = [e for e in eyes if any(x != x for x in e)]
print(f"NaN frames: {len(bad)}")
# oscillation period of Y (zero crossings) in the first big volley
ys = [e[1] for e in eyes]
cross = [frames[i] for i in range(1,len(ys)) if ys[i-1]*ys[i] < 0 and frames[i]-frames[i-1] == 1]
if len(cross) > 3:
gaps = [b-a for a,b in zip(cross, cross[1:]) if b-a < 30]
if gaps: print(f"Y half-period ~{sum(gaps)/len(gaps):.1f} frames (full period ~{2*sum(gaps)/len(gaps):.1f})")
# settle check: gap in frames = eye at rest (below 1e-9 magnitude threshold not logged)
gapct = sum(1 for a,b in zip(frames, frames[1:]) if b-a > 5)
print(f"rest gaps (>5 frames unlogged = settled to ~0): {gapct}")
fig, ax = plt.subplots(2, 1, figsize=(16, 8), sharex=True)
ax[0].plot(frames, [e[0] for e in eyes], '.', ms=2, label='eye X (lateral)')
ax[0].plot(frames, [e[1] for e in eyes], '.', ms=2, label='eye Y (vert)')
ax[0].plot(frames, [e[2] for e in eyes], '.', ms=2, label='eye Z (fore/aft)')
for h in [h for h in hits if h[0] is not None]: ax[0].axvline(h[0], color='r', alpha=.25, lw=.8)
ax[0].legend(); ax[0].set_ylabel('eyePosition (u)'); ax[0].set_title('Gyro eye spring — red lines = hits received')
ax[1].plot(frames, [b[0] for b in bodies], '.', ms=2, label='body X (pitch)')
ax[1].plot(frames, [b[1] for b in bodies], '.', ms=2, label='body Y')
ax[1].plot(frames, [b[2] for b in bodies], '.', ms=2, label='body Z (roll)')
for h in [h for h in hits if h[0] is not None]: ax[1].axvline(h[0], color='r', alpha=.25, lw=.8)
ax[1].legend(); ax[1].set_ylabel('bodyOrientation (rad)'); ax[1].set_xlabel('frame')
plt.tight_layout(); plt.savefig("GYRO_TRACE.png", dpi=90)
print("wrote GYRO_TRACE.png")