From dc3fe32bb38b0255e71199ef72e4cb84ea3fd491 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 31 May 2026 23:40:49 +0100 Subject: [PATCH] Report articulated sweep overlaps --- state/evidence.jsonl | 1 + tools/crunch_sync_sweep.py | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 14136eb..31d6a94 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -296,3 +296,4 @@ {"ts": "2026-05-31T23:14:03+01:00", "type": "implementation", "status": "ok", "note": "Changed music_keyed_move_chirp_sync.py visual pulses from square on/off flashes into sampled Move visual words. The default mimir.move_visual_contour_word.v1 uses identity color bias, attack/release envelope, symbol-dependent hue glide, tremolo articulation, emphasized Move lane, and an explicit visual_gesture_hz rate knob. Synthetic dry smoke wrote contour samples; a short non-dry Nightwing HID test at 80 Hz completed without writer failure. The camera-decodable rate still needs offline recovery scoring from captured frames."} {"ts": "2026-05-31T23:31:00+01:00", "type": "implementation", "status": "ok", "note": "sync_sweep.py now accepts a mimir.music_keyed_move_chirp_plan.v1 as the shared gesture authority for articulated chirps and chirp-flashes. The sweep copies/renders the plan WAV, sends exact Move visual contour samples over HID, attaches RGB visual_contour decoder scores to video receipts, and runs Mimir perfect-machine live receipts for local Leap/Kiyos plus Raven/Eve configs unless disabled. This fixes the earlier DirectShow/Nightwing-only sweep omission path: Leap/Eve are explicit receipt surfaces instead of assumed present."} {"ts": "2026-05-31T23:48:00+01:00", "type": "verification", "status": "partial", "note": "Ran an articulated all-sensor sweep from a live Scarlett-derived music plan at artifacts/runtime/all-sensor-articulated-20260531-232349. ASIO loopback channels 2/3 matched 77 chirp events with 0.083/0.292 ms median residuals; Nightwing builtin cam and nw-eye-1 each emitted 100/100 Move observations; Kiyo Pro/Kiyo captured RGB video and offline crunch extracted 47/51 marker observations. tools/crunch_sync_sweep.py wrote 298 total marker observations to calibration-observations.json/csv. Missing witnesses are explicit: nw-eye-0 timed out, Nightwing mic only matched one chirp, Focusrite DShow was unavailable while ASIO succeeded, LeapUVC KS open failed, and Raven/Eve receiver runtime saw no camera textures."} +{"ts": "2026-05-31T23:54:00+01:00", "type": "verification", "status": "ok", "note": "The articulated sweep crunch now reports pairwise shared marker events for calibration solve selection. In the 20260531-232349 run: Nightwing builtin cam and nw-eye-1 share 100 events; Kiyo shares 51 with each Nightwing view; Kiyo Pro shares 47 with each Nightwing view; Kiyo and Kiyo Pro share 28. This is enough correspondence substrate for initial multi-view frustum fitting on the sensors that actually saw the Move spheres."} diff --git a/tools/crunch_sync_sweep.py b/tools/crunch_sync_sweep.py index b516be5..074bee4 100644 --- a/tools/crunch_sync_sweep.py +++ b/tools/crunch_sync_sweep.py @@ -208,12 +208,16 @@ def main() -> int: for name in ("kiyo-pro", "kiyo"): sensors.append(crunch_video(sweep_dir, name, events, args)) observations = [obs for sensor in sensors for obs in sensor.get("observations", [])] + pair_overlaps = compute_pair_overlaps(observations) + event_coverage = compute_event_coverage(observations) document = { "kind": "mimir.articulated_sync_observation_crunch.v1", "sweep_dir": str(sweep_dir), "event_count": len(events), "sensor_count": len(sensors), "observation_count": len(observations), + "pair_overlaps": pair_overlaps, + "event_coverage": event_coverage, "sensors": sensors, } output_json = Path(args.output_json) if args.output_json else sweep_dir / "calibration-observations.json" @@ -224,9 +228,41 @@ def main() -> int: writer = csv.DictWriter(handle, fieldnames=fields, extrasaction="ignore") writer.writeheader() writer.writerows(observations) - print(json.dumps({"output_json": str(output_json), "output_csv": str(output_csv), "observation_count": len(observations)}, indent=2)) + print(json.dumps({ + "output_json": str(output_json), + "output_csv": str(output_csv), + "observation_count": len(observations), + "pair_overlaps": pair_overlaps, + }, indent=2)) return 0 if observations else 1 +def compute_pair_overlaps(observations: list[dict]) -> list[dict]: + by_event: dict[int, dict[str, dict]] = {} + for obs in observations: + event_index = int(obs["event_index"]) + by_event.setdefault(event_index, {})[str(obs["source_id"])] = obs + pairs: dict[tuple[str, str], int] = {} + for sensors in by_event.values(): + names = sorted(sensors) + for left_index, left in enumerate(names): + for right in names[left_index + 1:]: + pairs[(left, right)] = pairs.get((left, right), 0) + 1 + return [ + {"left": left, "right": right, "shared_events": count} + for (left, right), count in sorted(pairs.items(), key=lambda item: (-item[1], item[0])) + ] + + +def compute_event_coverage(observations: list[dict]) -> list[dict]: + by_event: dict[int, set[str]] = {} + for obs in observations: + by_event.setdefault(int(obs["event_index"]), set()).add(str(obs["source_id"])) + return [ + {"event_index": event_index, "sensor_count": len(sources), "sources": sorted(sources)} + for event_index, sources in sorted(by_event.items()) + ] + + if __name__ == "__main__": raise SystemExit(main())