Back to Supersonic RC Revive
SOURCE / PINNED RELEASE

Made of little things.

Supersonic RC Revive

Release
1ba42f1ca1d6…
Author-recorded commit
baecad10b1cd…
License
LICENSE
Author’s source reference
nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.napplet.soy%2F/n-143146b0d6f

Archive hash verified: 90d22b206672eba4…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

tools/sim/compare.py
#!/usr/bin/env python3
"""Compare an original-game log with a port replay, frame by frame.
    python3 tools/sim/compare.py original.txt port.txt [every]
"""
import math, sys

def load(p):
    rows = []
    for line in open(p, newline=''):
        for part in line.replace('\r', '\n').split('\n'):
            r = part.split()
            if len(r) >= 33:
                rows.append([float(x) for x in r[:33]])
    return rows


def pair_by_sim(orig, port, tol=0.02):
    """Pair each original row with the port row nearest in simTime (within tol)."""
    out, j = [], 0
    for o in orig:
        while j + 1 < len(port) and abs(port[j + 1][1] - o[1]) <= abs(port[j][1] - o[1]):
            j += 1
        if port and abs(port[j][1] - o[1]) <= tol:
            out.append((o, port[j]))
    return out

orig, port = load(sys.argv[1]), load(sys.argv[2])
pairs = pair_by_sim(orig, port)
every = int(sys.argv[3]) if len(sys.argv) > 3 else 10
print(f"{'sim':>6} | {'orig pos':>22} {'spd':>5} {'tilt':>5} {'spr':>5} | {'port pos':>22} {'spd':>5} {'tilt':>5} {'spr':>5} | {'dpos':>6} keys")
def tilt(r):  # angle of the body's up axis from world up, from axis-angle
    ax, ay, az, ang = r[5], r[6], r[7], math.radians(r[8])
    n = math.sqrt(ax*ax + ay*ay + az*az) or 1
    ax, ay, az = ax/n, ay/n, az/n
    c, s = math.cos(ang), math.sin(ang)
    upz = c + az*az*(1-c)
    return math.degrees(math.acos(max(-1, min(1, upz))))
for o, p in pairs[::every]:
    d = math.dist(o[2:5], p[2:5])
    so, sp = math.hypot(o[9], o[10]), math.hypot(p[9], p[10])
    keys = ''.join(str(int(k)) for k in o[27:32])
    print(f"{o[1]:6.2f} | {o[2]:7.0f}{o[3]:8.0f}{o[4]:7.1f} {so:5.0f} {tilt(o):5.1f} {o[16]:5.2f} | {p[2]:7.0f}{p[3]:8.0f}{p[4]:7.1f} {sp:5.0f} {tilt(p):5.1f} {p[16]:5.2f} | {d:6.1f} {keys}")

# Summary: position error and heading error over the run.
def heading(r):
    ax, ay, az, ang = r[5], r[6], r[7], math.radians(r[8])
    n = math.sqrt(ax*ax + ay*ay + az*az) or 1
    ax, ay, az = ax/n, ay/n, az/n
    c, s = math.cos(ang), math.sin(ang)
    fx = ax*ay*(1-c) - az*s      # rotated (0,1,0)
    fy = c + ay*ay*(1-c)
    return math.degrees(math.atan2(-fx, fy))
errs = [math.dist(o[2:5], p[2:5]) for o, p in pairs]
herr = [abs((heading(o) - heading(p) + 180) % 360 - 180) for o, p in pairs]
first_div = next((o[1] for (o, p), e in zip(pairs, errs) if e > 50), None)
print(f"SUMMARY frames={len(pairs)} meanPosErr={sum(errs)/len(errs):.1f} maxPosErr={max(errs):.1f} meanHdgErr={sum(herr)/len(herr):.1f} firstDiverge(>50u)@sim={first_div}")