#!/usr/bin/env python3 """Summarize an ACDREAM_CAPTURE_PLAYER_QUANTA JSONL for issue #269.""" from __future__ import annotations import argparse import json import math from pathlib import Path from typing import Any def vector(value: dict[str, Any]) -> tuple[float, float, float]: return float(value["x"]), float(value["y"]), float(value["z"]) def length(value: tuple[float, float, float]) -> float: return math.sqrt(sum(component * component for component in value)) def horizontal(value: tuple[float, float, float]) -> float: return math.hypot(value[0], value[1]) def subtract( left: tuple[float, float, float], right: tuple[float, float, float], ) -> tuple[float, float, float]: return tuple(a - b for a, b in zip(left, right, strict=True)) def scale( value: tuple[float, float, float], scalar: float, ) -> tuple[float, float, float]: return tuple(component * scalar for component in value) def input_active(record: dict[str, Any]) -> bool: state = record["input"] return any( bool(state[key]) for key in ( "forward", "backward", "strafeLeft", "strafeRight", "turnLeft", "turnRight", "jump", ) ) def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("capture", type=Path) parser.add_argument( "--tail", type=int, default=45, help="quanta printed after the final directional-input release", ) args = parser.parse_args() with args.capture.open("r", encoding="utf-8") as stream: records = [json.loads(line) for line in stream if line.strip()] if not records: print("capture contains no physics quanta") return 2 release_indices = [ index for index in range(1, len(records)) if input_active(records[index - 1]) and not input_active(records[index]) ] start = release_indices[-1] if release_indices else max(0, len(records) - args.tail) stop = min(len(records), start + args.tail) print( "seq dt walk(pre/post) |v|pre |v|fric |v|commit " "horizCommit rootDelta collision fsf" ) for record in records[start:stop]: dt = float(record["dt"]) pre = record["preIntegration"] post_integration = record["postIntegration"] post_commit = record["postCommit"] pre_velocity = vector(pre["velocity"]) integrated_velocity = vector(post_integration["velocity"]) acceleration = vector(pre["acceleration"]) friction_velocity = subtract( integrated_velocity, scale(acceleration, dt), ) commit_velocity = vector(post_commit["velocity"]) transient_pre = int(pre["transientState"]) transient_post = int(post_commit["transientState"]) pre_walk = (transient_pre & 0x2) != 0 post_walk = (transient_post & 0x2) != 0 root_delta = length(vector(record["rootAndManagerDelta"])) resolve = record["resolve"] collision = "yes" if resolve["collisionNormalValid"] else "no" print( f'{record["sequence"]:5d} {dt:0.5f} ' f"{int(pre_walk)}/{int(post_walk)} " f"{length(pre_velocity):8.4f} " f"{length(friction_velocity):8.4f} " f"{length(commit_velocity):8.4f} " f"{horizontal(commit_velocity):8.4f} " f"{root_delta:8.4f} {collision:>3s} " f'{post_commit["framesStationaryFall"]:d}' ) if release_indices: print(f"\nlast directional-input release: sequence {records[start]['sequence']}") else: print("\nno directional-input release edge found; showing capture tail") print(f"records: {len(records)}, displayed: {stop - start}") return 0 if __name__ == "__main__": raise SystemExit(main())