tools+plan(#182): resolve-capture histogram classifier + verbatim player-physics rebuild plan
Slice 0 of the #182 verbatim rebuild. The classifier reproduces the design baseline off acdream-crowd-resolve.jsonl (2883 move-intent resolves: 52.8% OK / 25.1% partial / 22.1% stuck / 107 airborne-stuck) — the A/B 'before' the rebuild measures against (retail target ~78% OK, 0 airborne-stuck). The plan refines the design spec's §7: the airborne-stuck bleed is the frames_stationary_fall counter (validate_transition increments; handle_all_collisions zeros velocity at fsf>1), NOT the cached_velocity field (a separate reporting value). Slices reorder accordingly; calc_friction (retail 0.25 vs acdream 0.0) is an orthogonal L.3c divergence kept out of scope. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
80881ed6f1
commit
8bb8b20411
2 changed files with 1002 additions and 0 deletions
94
tools/analyze_resolve_capture.py
Normal file
94
tools/analyze_resolve_capture.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#!/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")
|
||||
Loading…
Add table
Add a link
Reference in a new issue