#!/usr/bin/env python3 """Classify ACDREAM_CAPTURE_RESOLVE JSONL into OK / partial / stuck / airborne-stuck. The A/B measurement instrument for the #182 verbatim player-physics rebuild (docs/superpowers/plans/2026-07-07-player-physics-update-verbatim-rebuild.md). Each JSONL record (PhysicsResolveCapture.ResolveCaptureRecord) has: input.{currentPos,targetPos,cellId,...}, bodyBefore/bodyAfter (PhysicsBodySnapshot incl. velocity, slidingNormal, transientState), result.{position,cellId,isOnGround, collisionNormalValid,collisionNormal}. Vector3 = {x,y,z}. Buckets (move-intent records only, i.e. targetPos != currentPos): OK reached target: dist(result.position, targetPos) <= EPS_REACH partial advanced short: moved > EPS_MOVE and not OK (retail SLID) stuck reverted: moved <= EPS_MOVE (retail COLLIDED-revert) airborne-stuck subset of stuck: bodyBefore airborne w/ jump velocity into a near-horizontal collision normal (the falling-animation wedge) Retail target (tools/cdb/retail-crowd-jump3.cdb): ~78% OK, 12.7% COLLIDED (~stuck), 8.8% SLID (~partial), 0 airborne-stuck. Usage: py tools/analyze_resolve_capture.py [capture.jsonl] """ import sys import json import math EPS_REACH = 0.02 # 2 cm — "reached target" EPS_MOVE = 0.01 # 1 cm — "advanced at all" def dist(a, b): return math.sqrt((a["x"] - b["x"]) ** 2 + (a["y"] - b["y"]) ** 2 + (a["z"] - b["z"]) ** 2) def classify(rec): i = rec["input"] if dist(i["targetPos"], i["currentPos"]) <= EPS_MOVE: return None # zero-motion rest tick — not a move-intent record r = rec["result"] moved = dist(r["position"], i["currentPos"]) reached = dist(r["position"], i["targetPos"]) <= EPS_REACH if reached: return "ok" if moved > EPS_MOVE: return "partial" # reverted / stuck. airborne-stuck = a stuck frame while AIRBORNE (OnWalkable bit clear): # the "stuck in the falling animation" wedge. The near-horizontal creature normal is the # cause and the jump velocity the symptom, but the sliding normal flips frame-to-frame so # neither is reliable every frame — the airborne (not-grounded) revert is the stable marker. bb = rec.get("bodyBefore") or {} on_walkable = bb.get("transientState", 0) & 0x2 # TransientStateFlags.OnWalkable if not on_walkable: return "airborne-stuck" return "stuck" def main(path): counts = {"ok": 0, "partial": 0, "stuck": 0, "airborne-stuck": 0} total_move = 0 total_records = 0 with open(path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue try: rec = json.loads(line) except json.JSONDecodeError: continue total_records += 1 c = classify(rec) if c is None: continue total_move += 1 if c == "airborne-stuck": counts["airborne-stuck"] += 1 counts["stuck"] += 1 # airborne-stuck is a subset of stuck for the % columns else: counts[c] += 1 print(f"total records: {total_records}") if total_move == 0: print("no move-intent records") return print(f"move-intent resolves: {total_move}") for k in ("ok", "partial", "stuck"): print(f" {k:16s} {counts[k]:6d} {100.0 * counts[k] / total_move:5.1f}%") print(f" {'airborne-stuck':16s} {counts['airborne-stuck']:6d} (frames; subset of stuck)") print("retail target: ok ~78% partial ~9% stuck ~13% airborne-stuck 0") if __name__ == "__main__": main(sys.argv[1] if len(sys.argv) > 1 else "acdream-crowd-resolve.jsonl")