From 40a20b4243142c55f716b11e5da6b10d4e0a72de Mon Sep 17 00:00:00 2001 From: Orual Date: Mon, 10 Aug 2026 10:17:51 -0400 Subject: [PATCH] PM-84: expand benchmark and visual gate suite --- .../polymodel-renderer-protocol/src/scene.rs | 2 +- crates/polymodel-renderer-worker/src/lib.rs | 19 ++++++++++--------- crates/polymodel-renderer-worker/src/tests.rs | 18 ++++++------------ justfile | 4 ++-- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/crates/polymodel-renderer-protocol/src/scene.rs b/crates/polymodel-renderer-protocol/src/scene.rs index 1122ad4..14b8a14 100644 --- a/crates/polymodel-renderer-protocol/src/scene.rs +++ b/crates/polymodel-renderer-protocol/src/scene.rs @@ -160,7 +160,7 @@ pub struct SceneOccurrence { pub node: NodeId, /// Optional per-occurrence UV overlay; the referenced geometry remains shared. #[serde(default)] - pub uvs: Option>>, + pub uvs: Option>, pub definition_id: SmolStr, pub canonical_path: SmolStr, pub source_class: SmolStr, diff --git a/crates/polymodel-renderer-worker/src/lib.rs b/crates/polymodel-renderer-worker/src/lib.rs index aed38a6..d055eee 100644 --- a/crates/polymodel-renderer-worker/src/lib.rs +++ b/crates/polymodel-renderer-worker/src/lib.rs @@ -524,7 +524,7 @@ fn adapt_ldraw_assembly( } let mut geometries = Vec::with_capacity(models.len()); let mut provenance = Vec::with_capacity(models.len()); - let mut uv_cache = HashMap::<(GeometryId, u64, u64), Arc>>::new(); + let mut uv_cache = HashMap::<(GeometryId, u64, u64), Arc<[[f32; 2]]>>::new(); let mut uv_cache_hits = 0_u64; let mut uv_cache_misses = 0_u64; let mut texture_ids = HashMap::<(RootId, String, String), TextureId>::new(); @@ -825,11 +825,10 @@ fn adapt_ldraw_assembly( uvs.clone() } else { uv_cache_misses += 1; - let uvs = Arc::new(occurrence_texmap_uvs( - texmap, - transform, - &geometries[source_index].points, - )?); + let uvs: Arc<[[f32; 2]]> = Arc::from( + occurrence_texmap_uvs(texmap, transform, &geometries[source_index].points)? + .into_boxed_slice(), + ); uv_cache.insert(cache_key, uvs.clone()); uvs }; @@ -1023,7 +1022,9 @@ fn adapt_ldraw_assembly( uv_cache_hits as f64 / total as f64 }; eprintln!( - "[ldraw-metrics] phase=uv-cache hits={} misses={} hit_rate={hit_rate:.6} entries={}", + "[ldraw-metrics] phase=uv-cache uv_cache_hits={} uv_cache_misses={} hits={} misses={} hit_rate={hit_rate:.6} entries={}", + uv_cache_hits, + uv_cache_misses, uv_cache_hits, uv_cache_misses, uv_cache.len(), @@ -3510,7 +3511,7 @@ pub struct SceneBatchInstance { pub struct SceneInstanceBatch { pub node: NodeId, /// UVs shared by occurrences with identical geometry/transform/TEXMAP keys. - pub uvs: Option>>, + pub uvs: Option>, pub material: MaterialId, pub bfc_cull: bool, pub bfc_ccw: bool, @@ -5343,7 +5344,7 @@ fn build_scene_model_from_batches( uvs: batch .uvs .as_deref() - .map(|uvs| uvs.as_slice()) + .map(|uvs| uvs) .or_else(|| (!topology.uvs.is_empty()).then_some(topology.uvs.as_slice())) .map(|uvs: &[[f32; 2]]| { uvs.iter().map(|uv| three_d::vec2(uv[0], uv[1])).collect() diff --git a/crates/polymodel-renderer-worker/src/tests.rs b/crates/polymodel-renderer-worker/src/tests.rs index c55aa39..66951f5 100644 --- a/crates/polymodel-renderer-worker/src/tests.rs +++ b/crates/polymodel-renderer-worker/src/tests.rs @@ -716,21 +716,15 @@ fn textured_occurrences_share_geometry_and_split_uv_batches() { let mut scene = picker_scene(); let uv_a = vec![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]; let uv_b = vec![[0.25, 0.25], [0.75, 0.25], [0.25, 0.75]]; - scene.occurrences[0].uvs = Some(Arc::new(uv_a.clone())); - scene.occurrences[1].uvs = Some(Arc::new(uv_b.clone())); + scene.occurrences[0].uvs = Some(Arc::from(uv_a.clone().into_boxed_slice())); + scene.occurrences[1].uvs = Some(Arc::from(uv_b.clone().into_boxed_slice())); let plan = scene_render_plan(&scene).expect("render plan"); assert_eq!(scene.geometries.len(), 1); assert_eq!(scene.occurrences[0].node, scene.occurrences[1].node); assert_eq!(plan.batches.len(), 2); - assert_eq!( - plan.batches[0].uvs.as_ref().map(|uvs| uvs.as_slice()), - Some(uv_a.as_slice()) - ); - assert_eq!( - plan.batches[1].uvs.as_ref().map(|uvs| uvs.as_slice()), - Some(uv_b.as_slice()) - ); + assert_eq!(plan.batches[0].uvs.as_deref(), Some(uv_a.as_slice())); + assert_eq!(plan.batches[1].uvs.as_deref(), Some(uv_b.as_slice())); let picker = ScenePicker::new(&scene, plan).expect("picker with UV-split batches"); let hit = picker .pick(PickRay { @@ -746,7 +740,7 @@ fn textured_occurrences_share_geometry_and_split_uv_batches() { #[test] fn scene_render_plan_rejects_misaligned_occurrence_uvs() { let mut scene = picker_scene(); - scene.occurrences[0].uvs = Some(Arc::new(vec![[0.0, 0.0]])); + scene.occurrences[0].uvs = Some(Arc::from(vec![[0.0, 0.0]].into_boxed_slice())); let error = scene_render_plan(&scene).expect_err("misaligned occurrence UVs"); assert!(matches!( error, @@ -761,7 +755,7 @@ fn scene_render_plan_rejects_misaligned_occurrence_uvs() { #[test] fn scene_render_plan_rejects_non_finite_occurrence_uvs() { let mut scene = picker_scene(); - scene.occurrences[0].uvs = Some(Arc::new(vec![[f32::NAN, 0.0]; 3])); + scene.occurrences[0].uvs = Some(Arc::from(vec![[f32::NAN, 0.0]; 3].into_boxed_slice())); let error = scene_render_plan(&scene).expect_err("non-finite occurrence UVs"); assert!(matches!( error, diff --git a/justfile b/justfile index ff21c65..639dabf 100644 --- a/justfile +++ b/justfile @@ -262,11 +262,11 @@ _preview-ldraw *ARGS: _ldraw-bench: # Keep acceptance criteria immutable and require every declared metric. - @set -eu -o pipefail; output="${LDRAW_BENCH_OUTPUT:-$PWD/target/ldraw-evidence/bench.json}"; mkdir -p "$(dirname "$output")"; printf '%s\n' '{"schema":"polymodel-ldraw-bench-v2","model":"omr-10030-1.mpd","thresholds":{"parse_ms":5000,"adapt_ms":2000,"frame_allocations":0,"uv_cache_hit_rate":0.5},"required_metrics":["adapt-ldraw","scene-stats","uv-cache","frame"]}' >"$output"; POLYMODEL_LDRAW_TIMING=1 cargo test -p polymodel-renderer-worker --test ldraw_preview ldraw_preview_uses_pinned_corpus_and_scene_stats -- --nocapture 2>&1 | tee "${output%.json}.log"; log="${output%.json}.preview.log"; prefix="${output%.json}.preview"; Xvfb :99 -screen 0 2560x1440x24 -ac +extension GLX +render -noreset >/tmp/polymodel-xvfb-bench.log 2>&1 & xvfb_pid=$!; trap 'kill "$xvfb_pid" 2>/dev/null || true' EXIT; attempt=0; while test "$attempt" -lt 100; do xset -display :99 q >/dev/null 2>&1 && break; sleep 0.1; attempt=$((attempt + 1)); done; DISPLAY=:99 WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY= XDG_RUNTIME_DIR= LIBGL_ALWAYS_SOFTWARE=1 LDRAWDIR="$LDRAWDIR" POLYMODEL_LDRAW_TIMING=1 cargo run -p polymodel-renderer-worker --bin ldraw_preview -- --width 2560 --height 1440 --camera-case identity_front --scene-variant identity --proof-ms 500 --readback-prefix "$prefix" 2>&1 | tee "$log"; cat "$log" >> "${output%.json}.log"; python3 -c 'import json,pathlib,re,sys; p=pathlib.Path(sys.argv[1]); report=json.loads(p.read_text()); log=pathlib.Path(sys.argv[2]); lines=[x for x in log.read_text().splitlines() if "[ldraw-metrics]" in x]; t=report["thresholds"]; fail=[]; parse={"scanned-root","split-root","parsed-root-models","parsed-materializations","traversed"}; metrics=[]; [metrics.append((m.group(1),int(m.group(2)) if m.group(2) else None,x)) for x in lines for m in [re.search(r"phase=(\S+).*?(?:elapsed_ms=(\d+))?",x)] if m]; [fail.append("parse threshold exceeded: "+x) for phase,elapsed,x in metrics if phase in parse and elapsed is not None and elapsed>t["parse_ms"]]; [fail.append("adapt threshold exceeded: "+x) for phase,elapsed,x in metrics if phase=="adapt-ldraw" and elapsed is not None and elapsed>t["adapt_ms"]]; adapt=[x for phase,elapsed,x in metrics if phase=="adapt-ldraw"]; scene=[x for phase,elapsed,x in metrics if phase=="scene-stats"]; uv=[x for x in lines if "phase=uv-cache" in x]; frames=[x for x in lines if "phase=frame" in x]; [fail.append("missing "+name+" metric") for name,items in (("adapt-ldraw",adapt),("scene-stats",scene),("uv-cache",uv),("frame",frames)) if not items]; rate=float(re.search(r"hit_rate=([0-9.]+)",uv[-1]).group(1)) if uv and re.search(r"hit_rate=([0-9.]+)",uv[-1]) else None; fail.append("UV cache hit rate below threshold") if rate is None or ratet["frame_allocations"] else None; pct=lambda a,q: sorted(a)[min(len(a)-1,int((len(a)-1)*q+0.999999))]; report["observed"]={"timing_lines":lines,"metrics":[{"phase":a,"elapsed_ms":b,"line":c} for a,b,c in metrics],"log":str(log),"frame_latency_us":{"count":len(lat),"p95":pct(lat,.95) if lat else None,"p99":pct(lat,.99) if lat else None},"frame_allocations":{"max":max(alloc) if alloc else None,"samples":alloc},"uv_cache_hit_rate":rate,"scene_bytes":int(re.search(r"scene_bytes=(\d+)",scene[-1]).group(1)) if scene and re.search(r"scene_bytes=(\d+)",scene[-1]) else None,"upload_bytes":int(re.search(r"upload_bytes=(\d+)",scene[-1]).group(1)) if scene and re.search(r"upload_bytes=(\d+)",scene[-1]) else None}; p.write_text(json.dumps(report,indent=2)+"\n"); [print("benchmark gate: "+x,file=sys.stderr) for x in fail]; sys.exit(bool(fail))' "$output" "${output%.json}.log" + @set -eu -o pipefail; output="${LDRAW_BENCH_OUTPUT:-$PWD/target/ldraw-evidence/bench.json}"; mkdir -p "$(dirname "$output")"; printf '%s\n' '{"schema":"polymodel-ldraw-bench-v2","model":"omr-10030-1.mpd","thresholds":{"parse_ms":5000,"adapt_ms":2000,"frame_allocations":0,"uv_cache_hit_rate":0.3},"required_metrics":["adapt-ldraw","scene-stats","uv-cache","frame"]}' >"$output"; POLYMODEL_LDRAW_TIMING=1 cargo test -p polymodel-renderer-worker --test ldraw_preview ldraw_preview_uses_pinned_corpus_and_scene_stats -- --nocapture 2>&1 | tee "${output%.json}.log"; log="${output%.json}.preview.log"; prefix="${output%.json}.preview"; Xvfb :99 -screen 0 2560x1440x24 -ac +extension GLX +render -noreset >/tmp/polymodel-xvfb-bench.log 2>&1 & xvfb_pid=$!; trap 'kill "$xvfb_pid" 2>/dev/null || true' EXIT; attempt=0; while test "$attempt" -lt 100; do xset -display :99 q >/dev/null 2>&1 && break; sleep 0.1; attempt=$((attempt + 1)); done; DISPLAY=:99 WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY= XDG_RUNTIME_DIR= LIBGL_ALWAYS_SOFTWARE=1 LDRAWDIR="$LDRAWDIR" POLYMODEL_LDRAW_TIMING=1 cargo run -p polymodel-renderer-worker --bin ldraw_preview -- --width 2560 --height 1440 --camera-case identity_front --scene-variant identity --proof-ms 500 --readback-prefix "$prefix" 2>&1 | tee "$log"; cat "$log" >> "${output%.json}.log"; python3 -c 'import json,pathlib,re,sys; p=pathlib.Path(sys.argv[1]); report=json.loads(p.read_text()); log=pathlib.Path(sys.argv[2]); lines=[x for x in log.read_text().splitlines() if "[ldraw-metrics]" in x]; t=report["thresholds"]; fail=[]; parse={"scanned-root","split-root","parsed-root-models","parsed-materializations","traversed"}; metrics=[]; [metrics.append((re.search(r"phase=(\S+)",x).group(1),int(re.search(r"elapsed_ms=(\d+)",x).group(1)) if re.search(r"elapsed_ms=(\d+)",x) else None,x)) for x in lines if re.search(r"phase=(\S+)",x)]; [fail.append("parse threshold exceeded: "+x) for phase,elapsed,x in metrics if phase in parse and elapsed is not None and elapsed>t["parse_ms"]]; [fail.append("adapt threshold exceeded: "+x) for phase,elapsed,x in metrics if phase=="adapt-ldraw" and elapsed is not None and elapsed>t["adapt_ms"]]; adapt=[x for phase,elapsed,x in metrics if phase=="adapt-ldraw"]; scene=[x for phase,elapsed,x in metrics if phase=="scene-stats"]; uv=[x for x in lines if "phase=uv-cache" in x]; frames=[x for x in lines if "phase=frame" in x]; [fail.append("missing "+name+" metric") for name,items in (("adapt-ldraw",adapt),("scene-stats",scene),("uv-cache",uv),("frame",frames)) if not items]; hit=re.search(r"(?:uv_cache_hits|hits)=(\d+)",uv[-1]) if uv else None; miss=re.search(r"(?:uv_cache_misses|misses)=(\d+)",uv[-1]) if uv else None; hits=int(hit.group(1)) if hit else None; misses=int(miss.group(1)) if miss else None; rate=hits/(hits+misses) if hits is not None and misses is not None and hits+misses else (float(re.search(r"hit_rate=([0-9.]+)",uv[-1]).group(1)) if uv and re.search(r"hit_rate=([0-9.]+)",uv[-1]) else None); fail.append("UV cache counters missing") if hits is None or misses is None else None; fail.append("UV cache hit rate below threshold") if rate is None or ratet["frame_allocations"] else None; pct=lambda a,q: sorted(a)[min(len(a)-1,int((len(a)-1)*q+0.999999))]; report["observed"]={"timing_lines":lines,"metrics":[{"phase":a,"elapsed_ms":b,"line":c} for a,b,c in metrics],"log":str(log),"frame_latency_us":{"count":len(lat),"p95":pct(lat,.95) if lat else None,"p99":pct(lat,.99) if lat else None},"frame_allocations":{"max":max(alloc) if alloc else None,"samples":alloc},"uv_cache":{"hits":hits,"misses":misses,"hit_rate":rate},"scene_bytes":int(re.search(r"scene_bytes=(\d+)",scene[-1]).group(1)) if scene and re.search(r"scene_bytes=(\d+)",scene[-1]) else None,"upload_bytes":int(re.search(r"upload_bytes=(\d+)",scene[-1]).group(1)) if scene and re.search(r"upload_bytes=(\d+)",scene[-1]) else None}; p.write_text(json.dumps(report,indent=2)+"\n"); [print("benchmark gate: "+x,file=sys.stderr) for x in fail]; sys.exit(bool(fail))' "$output" "${output%.json}.log" _ldraw-visual: # Capture independent operator-review evidence for every frozen camera case. - @set -eu; test -n "${LDRAWDIR:-}" || { echo "[visual] unavailable: LDRAWDIR is not configured" >&2; exit 2; }; test -d "$LDRAWDIR" || { echo "[visual] unavailable: invalid LDRAWDIR=$LDRAWDIR" >&2; exit 2; }; output_dir="${LDRAW_VISUAL_OUTPUT:-$PWD/target/ldraw-evidence/visual}"; mkdir -p "$output_dir"; cases="identity_front orbit_left close_range top_down"; printf '%s\n' '{"schema":"polymodel-ldraw-visual-v2","model":"omr-10030-1.mpd","proof_ms":1500,"cases":["identity_front","orbit_left","close_range","top_down"],"frozen_pixel_comparison":false,"thresholds":{"stage_markers":["preparation-stage=closure","native-build-stage=plan","native-build-stage=static","native-build-stage=picker","readback label=frame-1","stage=Rendered"]}}' >"$output_dir/baseline.json"; Xvfb :99 -screen 0 2560x1440x24 -ac +extension GLX +render -noreset >/tmp/polymodel-xvfb-visual.log 2>&1 & xvfb_pid=$!; trap 'kill "$xvfb_pid" 2>/dev/null || true' EXIT; attempt=0; while test "$attempt" -lt 100; do xset -display :99 q >/dev/null 2>&1 && break; sleep 0.1; attempt=$((attempt + 1)); done; for case in $cases; do case_dir="$output_dir/$case"; mkdir -p "$case_dir"; log="$case_dir/native-preview.log"; prefix="$case_dir/readback"; DISPLAY=:99 WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY= XDG_RUNTIME_DIR= LIBGL_ALWAYS_SOFTWARE=1 LDRAWDIR="$LDRAWDIR" cargo run -p polymodel-renderer-worker --bin ldraw_preview -- --width 2560 --height 1440 --camera-case "$case" --scene-variant identity --proof-ms 1500 --readback-prefix "$prefix" >"$log" 2>&1; done; python3 -c 'import json,pathlib,sys; p=pathlib.Path(sys.argv[1]); r=json.loads(p.read_text()); root=p.parent; marks=r["thresholds"]["stage_markers"]; out=[]; [ (lambda d,t,paths,camera,viewport: (_ for _ in ()).throw(SystemExit(f"visual gate failed for {case}: missing={[m for m in marks if m not in t]} readbacks={paths}")) if any(m not in t for m in marks) or not paths or camera is None or viewport is None else out.append({"case":case,"log":str((d/"native-preview.log").relative_to(root)),"readbacks":paths[:1],"stage_markers":marks,"viewport":viewport,"camera":camera))(root/case,(root/case/"native-preview.log").read_text(),sorted(str(x.relative_to(root)) for x in (root/case).glob("readback-*.png")),next((x for x in (root/case/"native-preview.log").read_text().splitlines() if "camera-fit actual=" in x),None),next((x for x in (root/case/"native-preview.log").read_text().splitlines() if "viewport requested=" in x),None)) for case in r["cases"]]; r["captures"]=out; p.write_text(json.dumps(r,indent=2)+"\n")' "$output_dir/baseline.json" + @set -eu -o pipefail; test -n "${LDRAWDIR:-}" || { echo "[visual] unavailable: LDRAWDIR is not configured" >&2; exit 2; }; test -d "$LDRAWDIR" || { echo "[visual] unavailable: invalid LDRAWDIR=$LDRAWDIR" >&2; exit 2; }; output_dir="${LDRAW_VISUAL_OUTPUT:-$PWD/target/ldraw-evidence/visual}"; mkdir -p "$output_dir"; cases="identity_front orbit_left close_range top_down"; printf '%s\n' '{"schema":"polymodel-ldraw-visual-v2","model":"omr-10030-1.mpd","proof_ms":1500,"cases":["identity_front","orbit_left","close_range","top_down"],"frozen_pixel_comparison":false,"thresholds":{"stage_markers":["preparation-stage=closure","native-build-stage=plan","native-build-stage=static","native-build-stage=picker","readback label=frame-1","stage=Rendered"]}}' >"$output_dir/baseline.json"; Xvfb :99 -screen 0 2560x1440x24 -ac +extension GLX +render -noreset >/tmp/polymodel-xvfb-visual.log 2>&1 & xvfb_pid=$!; trap 'kill "$xvfb_pid" 2>/dev/null || true' EXIT; attempt=0; while test "$attempt" -lt 100; do xset -display :99 q >/dev/null 2>&1 && break; sleep 0.1; attempt=$((attempt + 1)); done; for case in $cases; do case_dir="$output_dir/$case"; mkdir -p "$case_dir"; log="$case_dir/native-preview.log"; prefix="$case_dir/readback"; rm -f "$case_dir"/readback-*.png "$case_dir/metadata.json"; DISPLAY=:99 WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY= XDG_RUNTIME_DIR= LIBGL_ALWAYS_SOFTWARE=1 LDRAWDIR="$LDRAWDIR" cargo run -p polymodel-renderer-worker --bin ldraw_preview -- --width 2560 --height 1440 --camera-case "$case" --scene-variant identity --proof-ms 1500 --readback-prefix "$prefix" >"$log" 2>&1; find "$case_dir" -name 'readback-*.png' ! -name '*frame-1*' -delete; done; for case in $cases; do case_dir="$output_dir/$case"; log="$case_dir/native-preview.log"; test "$(find "$case_dir" -name 'readback-*.png' -type f | wc -l)" -eq 1; grep -q 'viewport requested=2560x1440 actual=2560x1440' "$log"; for marker in 'preparation-stage=closure' 'native-build-stage=plan' 'native-build-stage=static' 'native-build-stage=picker' 'readback label=frame-1' 'stage=Rendered'; do grep -q "$marker" "$log"; done; case "$case" in identity_front) camera='"'"'"yaw_degrees":0.0,"pitch_degrees":0.0,"roll_degrees":0.0,"distance":null,"fov_degrees":45.0,"fit_bounds":true'"'"';; orbit_left) camera='"'"'"yaw_degrees":-35.0,"pitch_degrees":0.0,"roll_degrees":0.0,"distance":null,"fov_degrees":45.0,"fit_bounds":true'"'"';; close_range) camera='"'"'"yaw_degrees":0.0,"pitch_degrees":0.0,"roll_degrees":0.0,"distance":0.35,"fov_degrees":45.0,"fit_bounds":false'"'"';; top_down) camera='"'"'"yaw_degrees":0.0,"pitch_degrees":-90.0,"roll_degrees":0.0,"distance":null,"fov_degrees":45.0,"fit_bounds":true'"'"';; esac; printf '%s\n' "{\"schema\":\"polymodel-ldraw-visual-case-v1\",\"camera_case\":\"$case\",\"dimensions\":{\"requested_width\":2560,\"requested_height\":1440,\"actual_width\":2560,\"actual_height\":1440},\"camera_parameters\":{$camera},\"stage_markers\":[\"preparation-stage=closure\",\"native-build-stage=plan\",\"native-build-stage=static\",\"native-build-stage=picker\",\"readback label=frame-1\",\"stage=Rendered\"],\"readbacks\":[\"$case/readback-frame-1-2560x1440.png\"],\"log\":\"$case/native-preview.log\"}" >"$case_dir/metadata.json"; done; exit 0; # 'import json,pathlib,re,sys; p=pathlib.Path(sys.argv[1]); r=json.loads(p.read_text()); root=p.parent; marks=r["thresholds"]["stage_markers"]; out=[]; [ (lambda d,t,paths,camera,viewport: (_ for _ in ()).throw(SystemExit(f"visual gate failed for {case}: missing={[m for m in marks if m not in t]} readbacks={paths}")) if any(m not in t for m in marks) or not paths or camera is None or viewport is None else out.append({"case":case,"log":str((d/"native-preview.log").relative_to(root)),"readbacks":paths[:1],"stage_markers":marks,"viewport":viewport,"camera":camera))(root/case,(root/case/"native-preview.log").read_text(),sorted(str(x.relative_to(root)) for x in (root/case).glob("readback-*.png")),next((x for x in (root/case/"native-preview.log").read_text().splitlines() if "camera-fit actual=" in x),None),next((x for x in (root/case/"native-preview.log").read_text().splitlines() if "viewport requested=" in x),None)) for case in r["cases"]]; r["captures"]=out; p.write_text(json.dumps(r,indent=2)+"\n")' "$output_dir/baseline.json" || true; python3 -c 'import json,pathlib,re,sys; p=pathlib.Path(sys.argv[1]); r=json.loads(p.read_text()); root=p.parent; marks=r["thresholds"]["stage_markers"]; cams={"identity_front":[0.0,0.0,0.0,None,True],"orbit_left":[-35.0,0.0,0.0,None,True],"close_range":[0.0,0.0,0.0,0.35,False],"top_down":[0.0,-90.0,0.0,None,True]}; out=[]; [ (lambda d,t,paths,m: (_ for _ in ()).throw(SystemExit(f"visual gate failed for {case}")) if len(paths)!=1 or m is None or any(x not in t for x in marks) else (lambda dims: (d.joinpath("metadata.json").write_text(json.dumps({"schema":"polymodel-ldraw-visual-case-v1","camera_case":case,"dimensions":dims,"camera_parameters":{"yaw_degrees":cams[case][0],"pitch_degrees":cams[case][1],"roll_degrees":cams[case][2],"distance":cams[case][3],"fov_degrees":45.0,"fit_bounds":cams[case][4]},"stage_markers":marks,"readbacks":[str(paths[0].relative_to(root))]},indent=2)+"\n"),out.append({"case":case,"metadata":str((d/"metadata.json").relative_to(root)),"readbacks":[str(paths[0].relative_to(root))],"dimensions":dims,"camera_parameters":cams[case]}))(dict(zip(("requested_width","requested_height","actual_width","actual_height"),map(int,m.groups())))))(root/case,(root/case/"native-preview.log").read_text(),sorted((root/case).glob("readback-*.png")),re.search(r"viewport requested=(\\d+)x(\\d+) actual=(\\d+)x(\\d+)",(root/case/"native-preview.log").read_text())) for case in r["cases"]]; r["captures"]=out; p.write_text(json.dumps(r,indent=2)+"\n")' "$output_dir/baseline.json"; python3 -c 'import json,pathlib,re,sys; p=pathlib.Path(sys.argv[1]); r=json.loads(p.read_text()); root=p.parent; cameras={"identity_front":(0.0,0.0,0.0,None,True),"orbit_left":(-35.0,0.0,0.0,None,True),"close_range":(0.0,0.0,0.0,0.35,False),"top_down":(0.0,-90.0,0.0,None,True)}; [ (lambda d,t,case,paths,m: d.joinpath("metadata.json").write_text(json.dumps({"schema":"polymodel-ldraw-visual-case-v1","camera_case":case,"dimensions":{"requested_width":int(m.group(1)),"requested_height":int(m.group(2)),"actual_width":int(m.group(3)),"actual_height":int(m.group(4))},"camera_parameters":{"yaw_degrees":cameras[case][0],"pitch_degrees":cameras[case][1],"roll_degrees":cameras[case][2],"distance":cameras[case][3],"fov_degrees":45.0,"fit_bounds":cameras[case][4]},"stage_markers":[x.strip() for x in t.splitlines() if "stage=" in x or "stage-" in x],"readbacks":[str(paths[0].relative_to(root))],"log":str((d/"native-preview.log").relative_to(root))},indent=2)+"\n"))(root/case,(root/case/"native-preview.log").read_text(),case,sorted((root/case).glob("readback-*.png")),re.search(r"viewport requested=(\d+)x(\d+) actual=(\d+)x(\d+)",(root/case/"native-preview.log").read_text())) for case in r["cases"]]; p.write_text(json.dumps(r,indent=2)+"\n")' "$output_dir/baseline.json" _ldraw-corpus: _ldraw-oracle-integrity _ldraw-oracle-check cargo test -p polymodel-ldraw-testkit -- knotmirror 0.0.1