1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt::Write as _;
3use std::fs;
4use std::path::{Path, PathBuf};
5use std::process::Command;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8use serde::{Deserialize, Serialize};
9
10use crate::config::DemoConfig;
11use crate::error::{Error, Result};
12use crate::external::{
13 load_bool_buffer_from_reference, load_color_buffer_from_reference,
14 load_scalar_buffer_from_reference, load_vec2_buffer_from_reference,
15 load_vec3_buffer_from_reference, BufferReference, ExternalBufferSet, ExternalCaptureEntry,
16 ExternalCaptureManifest, ExternalCaptureMetadata, ExternalCaptureSource, ExternalNormalization,
17 EXTERNAL_CAPTURE_FORMAT_VERSION,
18};
19use crate::external_validation::{
20 capture_reference_frame_and_metric_source, roi_mask_for_capture, run_external_validation_bundle,
21 CANONICAL_HEADLINE_STATEMENT, ExternalDemoAMetrics, ExternalDemoBMetrics,
22 ExternalGpuMetrics, ExternalScalingMetrics, PURE_DSFB_LIMITATION_STATEMENT,
23 ROI_AGGREGATION_MIN_CAPTURES, ROI_CONTRACT_BASELINE_METHOD_ID, ROI_CONTRACT_SOURCE,
24 ROI_CONTRACT_STATEMENT, ROI_HONESTY_STATEMENT,
25};
26use crate::frame::{save_scalar_field_png, Color, ImageFrame, ScalarField};
27use crate::host::{
28 default_host_realistic_profile, supervise_temporal_reuse, HostSupervisionOutputs,
29};
30use crate::scene::{MotionVector, Normal3};
31
32pub const UNREAL_NATIVE_SCHEMA_VERSION: &str = "dsfb_unreal_native_v1";
33pub const UNREAL_NATIVE_DATASET_KIND: &str = "unreal_native";
34pub const UNREAL_NATIVE_PROVENANCE_LABEL: &str = "unreal_native";
35pub const UNREAL_NATIVE_PDF_FILE_NAME: &str = "artifacts_bundle.pdf";
36pub const UNREAL_NATIVE_ZIP_FILE_NAME: &str = "artifacts_bundle.zip";
37pub const UNREAL_NATIVE_EXECUTIVE_SHEET_FILE_NAME: &str = "executive_evidence_sheet.png";
38pub const UNREAL_NATIVE_EVIDENCE_MANIFEST_FILE_NAME: &str = "evidence_bundle_manifest.json";
39const UNREAL_NATIVE_CANONICAL_METRIC_SHEET_FILE_NAME: &str = "canonical_metric_sheet.md";
40const UNREAL_NATIVE_AGGREGATION_SUMMARY_FILE_NAME: &str = "aggregation_summary.md";
41
42#[derive(Clone, Debug)]
43pub struct UnrealNativeArtifacts {
44 pub run_dir: PathBuf,
45 pub materialized_manifest_path: PathBuf,
46 pub summary_path: PathBuf,
47 pub metrics_csv_path: PathBuf,
48 pub metrics_summary_path: PathBuf,
49 pub comparison_summary_path: PathBuf,
50 pub provenance_path: PathBuf,
51 pub failure_modes_path: PathBuf,
52 pub notebook_manifest_path: PathBuf,
53 pub executive_sheet_path: PathBuf,
54 pub pdf_path: PathBuf,
55 pub zip_path: PathBuf,
56}
57
58#[derive(Clone, Debug, Serialize, Deserialize)]
59pub struct UnrealNativeManifest {
60 pub schema_version: String,
61 pub dataset_kind: String,
62 pub provenance_label: String,
63 pub dataset_id: String,
64 pub description: String,
65 pub engine: UnrealEngineInfo,
66 pub contract: UnrealCaptureContract,
67 pub frames: Vec<UnrealFrameEntry>,
68 #[serde(default)]
69 pub notes: Vec<String>,
70}
71
72#[derive(Clone, Debug, Serialize, Deserialize)]
73pub struct UnrealEngineInfo {
74 pub engine_name: String,
75 pub engine_version: String,
76 pub capture_tool: String,
77 #[serde(default)]
78 pub project_path: Option<String>,
79 #[serde(default)]
80 pub capture_script: Option<String>,
81 #[serde(default)]
82 pub real_engine_capture: bool,
83}
84
85#[derive(Clone, Debug, Serialize, Deserialize)]
86pub struct UnrealCaptureContract {
87 pub color_space: String,
88 pub tonemap: String,
89 pub depth_convention: String,
90 pub normal_space: String,
91 pub motion_vector_convention: String,
92 pub coordinate_space: String,
93 #[serde(default)]
94 pub history_source: String,
95 #[serde(default)]
96 pub notes: Vec<String>,
97}
98
99#[derive(Clone, Debug, Serialize, Deserialize)]
100pub struct UnrealFrameEntry {
101 pub label: String,
102 pub frame_index: usize,
103 pub history_frame_index: usize,
104 pub buffers: UnrealFrameBuffers,
105 #[serde(default)]
106 pub notes: Vec<String>,
107}
108
109#[derive(Clone, Debug, Serialize, Deserialize)]
110pub struct UnrealFrameBuffers {
111 pub current_color: BufferReference,
112 pub previous_color: BufferReference,
113 #[serde(default)]
114 pub history_color: Option<BufferReference>,
115 pub motion_vectors: BufferReference,
116 pub current_depth: BufferReference,
117 pub previous_depth: BufferReference,
118 #[serde(default)]
119 pub history_depth: Option<BufferReference>,
120 pub current_normals: BufferReference,
121 pub previous_normals: BufferReference,
122 #[serde(default)]
123 pub history_normals: Option<BufferReference>,
124 pub metadata: BufferReference,
125 #[serde(default)]
126 pub host_output: Option<BufferReference>,
127 #[serde(default)]
128 pub reference_color: Option<BufferReference>,
129 #[serde(default)]
130 pub roi_mask: Option<BufferReference>,
131 #[serde(default)]
132 pub disocclusion_mask: Option<BufferReference>,
133 #[serde(default)]
134 pub reactive_mask: Option<BufferReference>,
135}
136
137#[derive(Clone, Debug, Serialize, Deserialize)]
138pub struct UnrealFrameMetadata {
139 pub frame_index: usize,
140 pub history_frame_index: usize,
141 pub width: usize,
142 pub height: usize,
143 pub source_kind: String,
144 #[serde(default)]
145 pub externally_validated: bool,
146 #[serde(default)]
147 pub real_external_data: bool,
148 #[serde(default)]
149 pub data_description: Option<String>,
150 #[serde(default)]
151 pub provenance_label: Option<String>,
152 #[serde(default)]
153 pub scene_name: Option<String>,
154 #[serde(default)]
155 pub shot_name: Option<String>,
156 #[serde(default)]
157 pub exposure: Option<String>,
158 #[serde(default)]
159 pub tonemap: Option<String>,
160 #[serde(default)]
161 pub camera: Option<UnrealCameraMetadata>,
162 #[serde(default)]
163 pub notes: Vec<String>,
164}
165
166#[derive(Clone, Debug, Serialize, Deserialize)]
167pub struct UnrealCameraMetadata {
168 #[serde(default)]
169 pub name: Option<String>,
170 #[serde(default)]
171 pub position: Option<[f32; 3]>,
172 #[serde(default)]
173 pub forward: Option<[f32; 3]>,
174 #[serde(default)]
175 pub fov_degrees: Option<f32>,
176 #[serde(default)]
177 pub jitter_pixels: Option<[f32; 2]>,
178}
179
180#[derive(Clone, Debug, Serialize)]
181struct ProvenanceRecord {
182 schema_version: String,
183 dataset_kind: String,
184 provenance_label: String,
185 dataset_id: String,
186 manifest_path: String,
187 materialized_manifest_path: String,
188 run_name: String,
189 run_dir: String,
190 timestamp_epoch_seconds: u64,
191 git_commit: Option<String>,
192 cli_args: Vec<String>,
193}
194
195#[derive(Clone, Debug, Serialize)]
196struct SummaryRecord {
197 schema_version: String,
198 dataset_kind: String,
199 provenance_label: String,
200 dataset_id: String,
201 run_name: String,
202 run_dir: String,
203 capture_count: usize,
204 classification_counts: ClassificationCounts,
205 executive_capture_label: String,
206 pdf_file_name: String,
207 zip_file_name: String,
208 notes: Vec<String>,
209}
210
211#[derive(Clone, Debug, Serialize)]
212struct MetricsSummaryRecord {
213 dataset_id: String,
214 provenance_label: String,
215 capture_count: usize,
216 roi_statement: String,
217 classification_counts: ClassificationCounts,
218 captures: Vec<CaptureSummaryRecord>,
219}
220
221#[derive(Clone, Debug, Default, Serialize)]
222struct ClassificationCounts {
223 dsfb_helpful: usize,
224 dsfb_neutral: usize,
225 heuristic_favorable: usize,
226 richer_cues_required: usize,
227}
228
229#[derive(Clone, Debug, Serialize)]
230struct CaptureSummaryRecord {
231 capture_label: String,
232 scene_name: String,
233 shot_name: String,
234 frame_index: usize,
235 classification: String,
236 roi_source: String,
237 roi_coverage: f32,
238 reference_source: String,
239 metric_source: String,
240 dsfb_full_frame_mae: f32,
241 dsfb_roi_mae: f32,
242 dsfb_max_error: f32,
243 dsfb_plus_heuristic_full_frame_mae: f32,
244 dsfb_plus_heuristic_roi_mae: f32,
245 dsfb_plus_heuristic_max_error: f32,
246 strong_heuristic_full_frame_mae: f32,
247 strong_heuristic_roi_mae: f32,
248 strong_heuristic_max_error: f32,
249 fixed_alpha_full_frame_mae: f32,
250 fixed_alpha_roi_mae: f32,
251 fixed_alpha_max_error: f32,
252 dsfb_mean_trust: f32,
253 dsfb_mean_alpha: f32,
254 dsfb_intervention_rate: f32,
255 roi_residual_mean: f32,
256 instability_fraction: f32,
257 gpu_total_ms: Option<f64>,
258}
259
260#[derive(Clone, Debug, Serialize)]
261struct EvidenceBundleManifest {
262 dataset_id: String,
263 provenance_label: String,
264 run_name: String,
265 pdf_file_name: String,
266 zip_file_name: String,
267 executive_sheet_file_name: String,
268 summary_file_name: String,
269 metrics_summary_file_name: String,
270 comparison_summary_file_name: String,
271 failure_modes_file_name: String,
272 frames: Vec<EvidenceFrameManifest>,
273}
274
275#[derive(Clone, Debug, Serialize)]
276struct EvidenceFrameManifest {
277 label: String,
278 scene_name: String,
279 shot_name: String,
280 frame_index: usize,
281 classification: String,
282 explanation: EvidenceExplanation,
283 key_metrics: Vec<KeyMetric>,
284 current_frame_path: String,
285 baseline_frame_path: String,
286 trust_map_path: String,
287 alpha_map_path: String,
288 intervention_map_path: String,
289 residual_map_path: String,
290 instability_overlay_path: String,
291 roi_overlay_path: String,
292 output_panel_path: String,
293}
294
295#[derive(Clone, Debug, Serialize)]
296struct EvidenceExplanation {
297 what_went_wrong: String,
298 what_dsfb_detected: String,
299 what_dsfb_changed: String,
300 overhead_and_caveat: String,
301}
302
303#[derive(Clone, Debug, Serialize)]
304struct KeyMetric {
305 label: String,
306 value: String,
307}
308
309#[derive(Clone, Debug, Serialize)]
310struct NotebookManifest {
311 dataset_id: String,
312 provenance_label: String,
313 run_name: String,
314 run_dir_name: String,
315 executive_sheet_file_name: String,
316 pdf_bundle_file_name: String,
317 zip_bundle_file_name: String,
318 comparison_summary_file_name: String,
319 primary_panel_file_name: String,
320}
321
322#[derive(Clone, Debug)]
323struct MaterializedRun {
324 materialized_manifest_path: PathBuf,
325 captures: Vec<MaterializedCapture>,
326}
327
328#[derive(Clone, Debug)]
329struct MaterializedCapture {
330 label: String,
331 frame_index: usize,
332 scene_name: String,
333 shot_name: String,
334 host_output: Option<ImageFrame>,
335 roi_mask: Option<Vec<bool>>,
336 disocclusion_mask: Option<Vec<bool>>,
337}
338
339#[derive(Clone, Debug)]
340struct FrameArtifacts {
341 label: String,
342 scene_name: String,
343 shot_name: String,
344 frame_index: usize,
345 classification: String,
346 roi_source: String,
347 roi_pixels: usize,
348 roi_coverage: f32,
349 reference_source: String,
350 current_frame_path: PathBuf,
351 baseline_frame_path: PathBuf,
352 roi_mask_path: PathBuf,
353 trust_map_path: PathBuf,
354 alpha_map_path: PathBuf,
355 intervention_map_path: PathBuf,
356 residual_map_path: PathBuf,
357 instability_overlay_path: PathBuf,
358 roi_overlay_path: PathBuf,
359 output_panel_path: PathBuf,
360 metric_source: String,
361 dsfb_full_frame_mae: f32,
362 dsfb_roi_mae: f32,
363 dsfb_max_error: f32,
364 dsfb_plus_heuristic_full_frame_mae: f32,
365 dsfb_plus_heuristic_roi_mae: f32,
366 dsfb_plus_heuristic_max_error: f32,
367 strong_heuristic_full_frame_mae: f32,
368 strong_heuristic_roi_mae: f32,
369 strong_heuristic_max_error: f32,
370 fixed_alpha_full_frame_mae: f32,
371 fixed_alpha_roi_mae: f32,
372 fixed_alpha_max_error: f32,
373 dsfb_mean_trust: f32,
374 dsfb_mean_alpha: f32,
375 dsfb_intervention_rate: f32,
376 roi_residual_mean: f32,
377 instability_fraction: f32,
378 gpu_total_ms: Option<f64>,
379 explanation: EvidenceExplanation,
380}
381
382#[derive(Clone, Debug)]
383struct DemoAMethodSelection<'a> {
384 metric_source: &'a str,
385 roi_source: &'a str,
386 roi_coverage: f32,
387 reference_source: &'a str,
388 fixed_alpha: &'a crate::external_validation::ExternalDemoAMethodMetrics,
389 strong_heuristic: &'a crate::external_validation::ExternalDemoAMethodMetrics,
390 dsfb: &'a crate::external_validation::ExternalDemoAMethodMetrics,
391 dsfb_plus_heuristic: &'a crate::external_validation::ExternalDemoAMethodMetrics,
392}
393
394pub fn run_unreal_native(
395 config: &DemoConfig,
396 manifest_path: &Path,
397 output_root: &Path,
398 run_name_override: Option<&str>,
399 cli_args: &[String],
400) -> Result<UnrealNativeArtifacts> {
401 let manifest = load_and_validate_manifest(manifest_path)?;
402 let run_name = run_name_override
403 .map(|value| value.to_string())
404 .unwrap_or_else(default_run_name);
405 let run_dir = create_run_dir(output_root, &run_name)?;
406
407 let materialized = materialize_unreal_manifest(&manifest, manifest_path, &run_dir)?;
408 run_external_validation_bundle(config, &materialized.materialized_manifest_path, &run_dir)?;
409
410 let demo_a: ExternalDemoAMetrics =
411 read_json(&run_dir.join("demo_a_external_metrics.json"))?;
412 let demo_b: ExternalDemoBMetrics =
413 read_json(&run_dir.join("demo_b_external_metrics.json"))?;
414 let gpu: ExternalGpuMetrics = read_json(&run_dir.join("gpu_execution_metrics.json"))?;
415 let scaling: ExternalScalingMetrics = read_json(&run_dir.join("scaling_metrics.json"))?;
416
417 let per_frame_dir = run_dir.join("per_frame");
418 fs::create_dir_all(&per_frame_dir)?;
419 let frame_artifacts = generate_per_frame_artifacts(
420 config,
421 &materialized.materialized_manifest_path,
422 &materialized.captures,
423 &demo_a,
424 &gpu,
425 &per_frame_dir,
426 )?;
427
428 let comparison_summary_path = run_dir.join("comparison_summary.md");
429 let canonical_metric_sheet_path =
430 run_dir.join(UNREAL_NATIVE_CANONICAL_METRIC_SHEET_FILE_NAME);
431 let aggregation_summary_path = run_dir.join(UNREAL_NATIVE_AGGREGATION_SUMMARY_FILE_NAME);
432 let metrics_csv_path = run_dir.join("metrics.csv");
433 let metrics_summary_path = run_dir.join("metrics_summary.json");
434 let failure_modes_path = run_dir.join("failure_modes.md");
435 let provenance_path = run_dir.join("provenance.json");
436 let run_manifest_path = run_dir.join("run_manifest.json");
437 let summary_path = run_dir.join("summary.json");
438 let evidence_manifest_path = run_dir.join(UNREAL_NATIVE_EVIDENCE_MANIFEST_FILE_NAME);
439 let notebook_manifest_path = run_dir.join("notebook_manifest.json");
440
441 write_run_manifest(&run_manifest_path, manifest_path, &manifest, &materialized)?;
442
443 let counts = classification_counts(&frame_artifacts);
444 let capture_summaries = frame_artifacts
445 .iter()
446 .map(|frame| CaptureSummaryRecord {
447 capture_label: frame.label.clone(),
448 scene_name: frame.scene_name.clone(),
449 shot_name: frame.shot_name.clone(),
450 frame_index: frame.frame_index,
451 classification: frame.classification.clone(),
452 roi_source: frame.roi_source.clone(),
453 roi_coverage: frame.roi_coverage,
454 reference_source: frame.reference_source.clone(),
455 metric_source: frame.metric_source.clone(),
456 dsfb_full_frame_mae: frame.dsfb_full_frame_mae,
457 dsfb_roi_mae: frame.dsfb_roi_mae,
458 dsfb_max_error: frame.dsfb_max_error,
459 dsfb_plus_heuristic_full_frame_mae: frame.dsfb_plus_heuristic_full_frame_mae,
460 dsfb_plus_heuristic_roi_mae: frame.dsfb_plus_heuristic_roi_mae,
461 dsfb_plus_heuristic_max_error: frame.dsfb_plus_heuristic_max_error,
462 strong_heuristic_full_frame_mae: frame.strong_heuristic_full_frame_mae,
463 strong_heuristic_roi_mae: frame.strong_heuristic_roi_mae,
464 strong_heuristic_max_error: frame.strong_heuristic_max_error,
465 fixed_alpha_full_frame_mae: frame.fixed_alpha_full_frame_mae,
466 fixed_alpha_roi_mae: frame.fixed_alpha_roi_mae,
467 fixed_alpha_max_error: frame.fixed_alpha_max_error,
468 dsfb_mean_trust: frame.dsfb_mean_trust,
469 dsfb_mean_alpha: frame.dsfb_mean_alpha,
470 dsfb_intervention_rate: frame.dsfb_intervention_rate,
471 roi_residual_mean: frame.roi_residual_mean,
472 instability_fraction: frame.instability_fraction,
473 gpu_total_ms: frame.gpu_total_ms,
474 })
475 .collect::<Vec<_>>();
476
477 write_metrics_csv(&metrics_csv_path, &capture_summaries, &demo_b)?;
478 let metrics_summary = MetricsSummaryRecord {
479 dataset_id: manifest.dataset_id.clone(),
480 provenance_label: manifest.provenance_label.clone(),
481 capture_count: capture_summaries.len(),
482 roi_statement: ROI_CONTRACT_STATEMENT.to_string(),
483 classification_counts: counts.clone(),
484 captures: capture_summaries.clone(),
485 };
486 fs::write(
487 &metrics_summary_path,
488 serde_json::to_string_pretty(&metrics_summary)?,
489 )?;
490
491 write_canonical_metric_sheet(&canonical_metric_sheet_path, &capture_summaries)?;
492 write_aggregation_summary(&aggregation_summary_path, &capture_summaries)?;
493 write_comparison_summary(&comparison_summary_path, &manifest, &frame_artifacts, &demo_b)?;
494 write_failure_modes(&failure_modes_path, &manifest, &materialized.captures, &scaling)?;
495
496 let timestamp_epoch_seconds = SystemTime::now()
497 .duration_since(UNIX_EPOCH)
498 .unwrap_or_default()
499 .as_secs();
500 let provenance = ProvenanceRecord {
501 schema_version: manifest.schema_version.clone(),
502 dataset_kind: manifest.dataset_kind.clone(),
503 provenance_label: manifest.provenance_label.clone(),
504 dataset_id: manifest.dataset_id.clone(),
505 manifest_path: manifest_path.display().to_string(),
506 materialized_manifest_path: materialized.materialized_manifest_path.display().to_string(),
507 run_name: run_name.clone(),
508 run_dir: run_dir.display().to_string(),
509 timestamp_epoch_seconds,
510 git_commit: git_commit_hash(),
511 cli_args: cli_args.to_vec(),
512 };
513 fs::write(&provenance_path, serde_json::to_string_pretty(&provenance)?)?;
514
515 let executive_frame = select_executive_frame(&frame_artifacts)?;
516 let evidence_manifest = EvidenceBundleManifest {
517 dataset_id: manifest.dataset_id.clone(),
518 provenance_label: manifest.provenance_label.clone(),
519 run_name: run_name.clone(),
520 pdf_file_name: UNREAL_NATIVE_PDF_FILE_NAME.to_string(),
521 zip_file_name: UNREAL_NATIVE_ZIP_FILE_NAME.to_string(),
522 executive_sheet_file_name: UNREAL_NATIVE_EXECUTIVE_SHEET_FILE_NAME.to_string(),
523 summary_file_name: "summary.json".to_string(),
524 metrics_summary_file_name: "metrics_summary.json".to_string(),
525 comparison_summary_file_name: "comparison_summary.md".to_string(),
526 failure_modes_file_name: "failure_modes.md".to_string(),
527 frames: frame_artifacts
528 .iter()
529 .map(|frame| EvidenceFrameManifest {
530 label: frame.label.clone(),
531 scene_name: frame.scene_name.clone(),
532 shot_name: frame.shot_name.clone(),
533 frame_index: frame.frame_index,
534 classification: frame.classification.clone(),
535 explanation: frame.explanation.clone(),
536 key_metrics: vec![
537 KeyMetric {
538 label: "DSFB ROI MAE".to_string(),
539 value: format!("{:.5}", frame.dsfb_roi_mae),
540 },
541 KeyMetric {
542 label: "Strong heuristic ROI MAE".to_string(),
543 value: format!("{:.5}", frame.strong_heuristic_roi_mae),
544 },
545 KeyMetric {
546 label: "DSFB + heuristic ROI MAE".to_string(),
547 value: format!("{:.5}", frame.dsfb_plus_heuristic_roi_mae),
548 },
549 KeyMetric {
550 label: "Mean trust".to_string(),
551 value: format!("{:.4}", frame.dsfb_mean_trust),
552 },
553 KeyMetric {
554 label: "Intervention rate".to_string(),
555 value: format!("{:.4}", frame.dsfb_intervention_rate),
556 },
557 KeyMetric {
558 label: "GPU total ms".to_string(),
559 value: frame
560 .gpu_total_ms
561 .map(|value| format!("{value:.4}"))
562 .unwrap_or_else(|| "n/a".to_string()),
563 },
564 ],
565 current_frame_path: relative_path_string(&frame.current_frame_path, &run_dir),
566 baseline_frame_path: relative_path_string(&frame.baseline_frame_path, &run_dir),
567 trust_map_path: relative_path_string(&frame.trust_map_path, &run_dir),
568 alpha_map_path: relative_path_string(&frame.alpha_map_path, &run_dir),
569 intervention_map_path: relative_path_string(&frame.intervention_map_path, &run_dir),
570 residual_map_path: relative_path_string(&frame.residual_map_path, &run_dir),
571 instability_overlay_path: relative_path_string(
572 &frame.instability_overlay_path,
573 &run_dir,
574 ),
575 roi_overlay_path: relative_path_string(&frame.roi_overlay_path, &run_dir),
576 output_panel_path: relative_path_string(&frame.output_panel_path, &run_dir),
577 })
578 .collect(),
579 };
580 fs::write(
581 &evidence_manifest_path,
582 serde_json::to_string_pretty(&evidence_manifest)?,
583 )?;
584
585 let summary = SummaryRecord {
586 schema_version: manifest.schema_version.clone(),
587 dataset_kind: manifest.dataset_kind.clone(),
588 provenance_label: manifest.provenance_label.clone(),
589 dataset_id: manifest.dataset_id.clone(),
590 run_name: run_name.clone(),
591 run_dir: run_dir.display().to_string(),
592 capture_count: frame_artifacts.len(),
593 classification_counts: counts,
594 executive_capture_label: executive_frame.label.clone(),
595 pdf_file_name: UNREAL_NATIVE_PDF_FILE_NAME.to_string(),
596 zip_file_name: UNREAL_NATIVE_ZIP_FILE_NAME.to_string(),
597 notes: vec![
598 "Engine-native empirical replay executed on a strict Unreal-native manifest.".to_string(),
599 "No synthetic fallback is available in this mode.".to_string(),
600 "Any missing required Unreal-native buffer is a hard failure.".to_string(),
601 ROI_CONTRACT_STATEMENT.to_string(),
602 format!(
603 "This checked-in manifest provides {} real Unreal-native captures; aggregation {}.",
604 capture_summaries.len(),
605 if capture_summaries.len() >= ROI_AGGREGATION_MIN_CAPTURES {
606 "is emitted in aggregation_summary.md"
607 } else {
608 "remains blocked because fewer than the required real captures are available"
609 }
610 ),
611 ],
612 };
613 fs::write(&summary_path, serde_json::to_string_pretty(&summary)?)?;
614
615 run_bundle_builder(&run_dir)?;
616
617 let notebook_manifest = NotebookManifest {
618 dataset_id: manifest.dataset_id.clone(),
619 provenance_label: manifest.provenance_label.clone(),
620 run_name: run_name.clone(),
621 run_dir_name: run_name.clone(),
622 executive_sheet_file_name: UNREAL_NATIVE_EXECUTIVE_SHEET_FILE_NAME.to_string(),
623 pdf_bundle_file_name: UNREAL_NATIVE_PDF_FILE_NAME.to_string(),
624 zip_bundle_file_name: UNREAL_NATIVE_ZIP_FILE_NAME.to_string(),
625 comparison_summary_file_name: "comparison_summary.md".to_string(),
626 primary_panel_file_name: executive_frame
627 .output_panel_path
628 .file_name()
629 .and_then(|value| value.to_str())
630 .unwrap_or("boardroom_panel.png")
631 .to_string(),
632 };
633 fs::write(
634 ¬ebook_manifest_path,
635 serde_json::to_string_pretty(¬ebook_manifest)?,
636 )?;
637
638 let executive_sheet_path = run_dir.join(UNREAL_NATIVE_EXECUTIVE_SHEET_FILE_NAME);
639 let pdf_path = run_dir.join(UNREAL_NATIVE_PDF_FILE_NAME);
640 let zip_path = run_dir.join(UNREAL_NATIVE_ZIP_FILE_NAME);
641 validate_unreal_native_artifacts(
642 &run_dir,
643 &frame_artifacts,
644 &comparison_summary_path,
645 &canonical_metric_sheet_path,
646 &aggregation_summary_path,
647 )?;
648
649 Ok(UnrealNativeArtifacts {
650 run_dir,
651 materialized_manifest_path: materialized.materialized_manifest_path,
652 summary_path,
653 metrics_csv_path,
654 metrics_summary_path,
655 comparison_summary_path,
656 provenance_path,
657 failure_modes_path,
658 notebook_manifest_path,
659 executive_sheet_path,
660 pdf_path,
661 zip_path,
662 })
663}
664
665fn load_and_validate_manifest(path: &Path) -> Result<UnrealNativeManifest> {
666 let manifest: UnrealNativeManifest = read_json(path)?;
667 if manifest.schema_version != UNREAL_NATIVE_SCHEMA_VERSION {
668 return Err(Error::Message(format!(
669 "unreal-native manifest {} used schema_version `{}` but `{}` is required",
670 path.display(),
671 manifest.schema_version,
672 UNREAL_NATIVE_SCHEMA_VERSION
673 )));
674 }
675 if manifest.dataset_kind != UNREAL_NATIVE_DATASET_KIND {
676 return Err(Error::Message(format!(
677 "unreal-native manifest {} used dataset_kind `{}` but `{}` is required",
678 path.display(),
679 manifest.dataset_kind,
680 UNREAL_NATIVE_DATASET_KIND
681 )));
682 }
683 if manifest.provenance_label != UNREAL_NATIVE_PROVENANCE_LABEL {
684 return Err(Error::Message(format!(
685 "unreal-native manifest {} used provenance_label `{}` but `{}` is required",
686 path.display(),
687 manifest.provenance_label,
688 UNREAL_NATIVE_PROVENANCE_LABEL
689 )));
690 }
691 if manifest.engine.engine_name != "unreal_engine" {
692 return Err(Error::Message(format!(
693 "unreal-native manifest {} declared engine_name `{}`; only `unreal_engine` is accepted",
694 path.display(),
695 manifest.engine.engine_name
696 )));
697 }
698 if !manifest.engine.real_engine_capture {
699 return Err(Error::Message(format!(
700 "unreal-native manifest {} is not marked as a real Unreal capture; this mode refuses pending, proxy, or synthetic provenance",
701 path.display()
702 )));
703 }
704 if manifest.frames.is_empty() {
705 return Err(Error::Message(format!(
706 "unreal-native manifest {} contained no frames",
707 path.display()
708 )));
709 }
710 validate_contract(&manifest.contract)?;
711 validate_unique_frames(&manifest.frames)?;
712 Ok(manifest)
713}
714
715fn validate_contract(contract: &UnrealCaptureContract) -> Result<()> {
716 if !contract.color_space.contains("linear") {
717 return Err(Error::Message(
718 "unreal-native contract must declare a linear color space".to_string(),
719 ));
720 }
721 if !matches!(
722 contract.tonemap.as_str(),
723 "disabled" | "pre_tonemap_capture" | "scene_capture_png_linearized"
724 ) {
725 return Err(Error::Message(format!(
726 "unreal-native contract tonemap `{}` is unsupported; use `disabled`, `pre_tonemap_capture`, or `scene_capture_png_linearized`",
727 contract.tonemap
728 )));
729 }
730 if !matches!(contract.normal_space.as_str(), "view_space_unit" | "world_space_unit") {
731 return Err(Error::Message(format!(
732 "unreal-native contract normal_space `{}` is unsupported; use `view_space_unit` or `world_space_unit`",
733 contract.normal_space
734 )));
735 }
736 if !matches!(
737 contract.depth_convention.as_str(),
738 "monotonic_linear_depth" | "monotonic_visualized_depth"
739 ) {
740 return Err(Error::Message(format!(
741 "unreal-native contract depth_convention `{}` is unsupported; use `monotonic_linear_depth` or `monotonic_visualized_depth`",
742 contract.depth_convention
743 )));
744 }
745 if !matches!(
746 contract.motion_vector_convention.as_str(),
747 "pixel_offset_to_prev" | "ndc_to_prev"
748 ) {
749 return Err(Error::Message(format!(
750 "unreal-native contract motion_vector_convention `{}` is unsupported",
751 contract.motion_vector_convention
752 )));
753 }
754 Ok(())
755}
756
757fn validate_unique_frames(frames: &[UnrealFrameEntry]) -> Result<()> {
758 let mut labels = BTreeSet::new();
759 let mut indices = BTreeSet::new();
760 for frame in frames {
761 if !labels.insert(frame.label.clone()) {
762 return Err(Error::Message(format!(
763 "duplicate unreal-native capture label `{}`",
764 frame.label
765 )));
766 }
767 if !indices.insert(frame.frame_index) {
768 return Err(Error::Message(format!(
769 "duplicate unreal-native frame_index `{}`",
770 frame.frame_index
771 )));
772 }
773 if frame.history_frame_index >= frame.frame_index {
774 return Err(Error::Message(format!(
775 "capture `{}` must provide history_frame_index < frame_index",
776 frame.label
777 )));
778 }
779 }
780 Ok(())
781}
782
783fn create_run_dir(output_root: &Path, run_name: &str) -> Result<PathBuf> {
784 fs::create_dir_all(output_root)?;
785 let run_dir = output_root.join(run_name);
786 if run_dir.exists() {
787 return Err(Error::Message(format!(
788 "refusing to overwrite existing unreal-native run directory {}",
789 run_dir.display()
790 )));
791 }
792 fs::create_dir_all(&run_dir)?;
793 Ok(run_dir)
794}
795
796fn default_run_name() -> String {
797 let seconds = SystemTime::now()
798 .duration_since(UNIX_EPOCH)
799 .unwrap_or_default()
800 .as_secs();
801 format!("unreal_native_{seconds}")
802}
803
804fn materialize_unreal_manifest(
805 manifest: &UnrealNativeManifest,
806 manifest_path: &Path,
807 run_dir: &Path,
808) -> Result<MaterializedRun> {
809 let base_dir = manifest_path.parent().ok_or_else(|| {
810 Error::Message(format!(
811 "unreal-native manifest {} had no parent directory",
812 manifest_path.display()
813 ))
814 })?;
815 let materialized_dir = run_dir.join("materialized_external");
816 fs::create_dir_all(&materialized_dir)?;
817
818 let mut sorted_frames = manifest.frames.clone();
819 sorted_frames.sort_by_key(|frame| frame.frame_index);
820
821 let mut external_entries = Vec::with_capacity(sorted_frames.len());
822 let mut captures = Vec::with_capacity(sorted_frames.len());
823
824 for frame in &sorted_frames {
825 let metadata = load_unreal_frame_metadata(base_dir, &frame.buffers.metadata)?;
826 validate_frame_metadata(frame, &metadata)?;
827
828 let current_color = load_color_buffer_from_reference(
829 base_dir,
830 &frame.buffers.current_color,
831 metadata.width,
832 metadata.height,
833 )?;
834 let previous_color = load_color_buffer_from_reference(
835 base_dir,
836 &frame.buffers.previous_color,
837 metadata.width,
838 metadata.height,
839 )?;
840 let (_, _, mut motion_data) = load_vec2_buffer_from_reference(
841 base_dir,
842 &frame.buffers.motion_vectors,
843 metadata.width,
844 metadata.height,
845 )?;
846 normalize_motion_vectors(
847 &mut motion_data,
848 metadata.width,
849 metadata.height,
850 &manifest.contract.motion_vector_convention,
851 )?;
852 let motion_vectors = motion_data
853 .iter()
854 .map(|value| MotionVector {
855 to_prev_x: value[0],
856 to_prev_y: value[1],
857 })
858 .collect::<Vec<_>>();
859
860 let (_, _, current_depth) = load_scalar_buffer_from_reference(
861 base_dir,
862 &frame.buffers.current_depth,
863 metadata.width,
864 metadata.height,
865 )?;
866 let (_, _, previous_depth) = load_scalar_buffer_from_reference(
867 base_dir,
868 &frame.buffers.previous_depth,
869 metadata.width,
870 metadata.height,
871 )?;
872 let (_, _, current_normal_data) = load_vec3_buffer_from_reference(
873 base_dir,
874 &frame.buffers.current_normals,
875 metadata.width,
876 metadata.height,
877 )?;
878 let (_, _, previous_normal_data) = load_vec3_buffer_from_reference(
879 base_dir,
880 &frame.buffers.previous_normals,
881 metadata.width,
882 metadata.height,
883 )?;
884 let current_normals = current_normal_data
885 .into_iter()
886 .map(|value| Normal3::new(value[0], value[1], value[2]).normalized())
887 .collect::<Vec<_>>();
888 let previous_normals = previous_normal_data
889 .into_iter()
890 .map(|value| Normal3::new(value[0], value[1], value[2]).normalized())
891 .collect::<Vec<_>>();
892
893 let history_color = match &frame.buffers.history_color {
894 Some(reference) => load_color_buffer_from_reference(
895 base_dir,
896 reference,
897 metadata.width,
898 metadata.height,
899 )?,
900 None => reproject_image(&previous_color, &motion_vectors),
901 };
902 let history_depth = match &frame.buffers.history_depth {
903 Some(reference) => load_scalar_buffer_from_reference(
904 base_dir,
905 reference,
906 metadata.width,
907 metadata.height,
908 )?
909 .2,
910 None => reproject_scalar(&previous_depth, metadata.width, metadata.height, &motion_vectors),
911 };
912 let history_normals = match &frame.buffers.history_normals {
913 Some(reference) => load_vec3_buffer_from_reference(
914 base_dir,
915 reference,
916 metadata.width,
917 metadata.height,
918 )?
919 .2
920 .into_iter()
921 .map(|value| Normal3::new(value[0], value[1], value[2]).normalized())
922 .collect(),
923 None => reproject_normals(
924 &previous_normals,
925 metadata.width,
926 metadata.height,
927 &motion_vectors,
928 ),
929 };
930
931 let host_output = frame
932 .buffers
933 .host_output
934 .as_ref()
935 .map(|reference| {
936 load_color_buffer_from_reference(base_dir, reference, metadata.width, metadata.height)
937 })
938 .transpose()?;
939 let reference_color = frame
940 .buffers
941 .reference_color
942 .as_ref()
943 .map(|reference| {
944 load_color_buffer_from_reference(base_dir, reference, metadata.width, metadata.height)
945 })
946 .transpose()?;
947 let roi_mask = frame
948 .buffers
949 .roi_mask
950 .as_ref()
951 .map(|reference| load_mask_any(base_dir, reference, metadata.width, metadata.height))
952 .transpose()?;
953 let disocclusion_mask = frame
954 .buffers
955 .disocclusion_mask
956 .as_ref()
957 .map(|reference| load_mask_any(base_dir, reference, metadata.width, metadata.height))
958 .transpose()?;
959
960 let capture_dir = materialized_dir.join(&frame.label);
961 fs::create_dir_all(&capture_dir)?;
962
963 let current_color_path = capture_dir.join("current_color.png");
964 let history_color_path = capture_dir.join("reprojected_history.png");
965 let motion_vectors_path = capture_dir.join("motion_vectors.json");
966 let current_depth_path = capture_dir.join("current_depth.json");
967 let history_depth_path = capture_dir.join("reprojected_depth.json");
968 let current_normals_path = capture_dir.join("current_normals.json");
969 let history_normals_path = capture_dir.join("reprojected_normals.json");
970 let metadata_path = capture_dir.join("metadata.json");
971 let roi_mask_path = capture_dir.join("roi_mask.json");
972 let reference_path = capture_dir.join("reference_color.png");
973
974 current_color.save_png(¤t_color_path)?;
975 history_color.save_png(&history_color_path)?;
976 write_json(&motion_vectors_path, &Vec2Json {
977 width: metadata.width,
978 height: metadata.height,
979 data: motion_vectors
980 .iter()
981 .map(|motion| [motion.to_prev_x, motion.to_prev_y])
982 .collect(),
983 })?;
984 write_json(¤t_depth_path, &ScalarJson {
985 width: metadata.width,
986 height: metadata.height,
987 data: current_depth.clone(),
988 })?;
989 write_json(&history_depth_path, &ScalarJson {
990 width: metadata.width,
991 height: metadata.height,
992 data: history_depth.clone(),
993 })?;
994 write_json(¤t_normals_path, &Vec3Json {
995 width: metadata.width,
996 height: metadata.height,
997 data: current_normals.iter().map(|normal| [normal.x, normal.y, normal.z]).collect(),
998 })?;
999 write_json(&history_normals_path, &Vec3Json {
1000 width: metadata.width,
1001 height: metadata.height,
1002 data: history_normals.iter().map(|normal| [normal.x, normal.y, normal.z]).collect(),
1003 })?;
1004
1005 let external_metadata = ExternalCaptureMetadata {
1006 scenario_id: metadata.scene_name.clone(),
1007 frame_index: metadata.frame_index,
1008 history_frame_index: metadata.history_frame_index,
1009 width: metadata.width,
1010 height: metadata.height,
1011 source_kind: UNREAL_NATIVE_DATASET_KIND.to_string(),
1012 externally_validated: true,
1013 real_external_data: true,
1014 data_description: Some(
1015 "Unreal Engine exported frame pair materialized into the DSFB external replay contract"
1016 .to_string(),
1017 ),
1018 notes: metadata.notes.clone(),
1019 };
1020 write_json(&metadata_path, &external_metadata)?;
1021
1022 let optional_mask = if let Some(mask) = &roi_mask {
1023 write_json(&roi_mask_path, &BoolJson {
1024 width: metadata.width,
1025 height: metadata.height,
1026 data: mask.clone(),
1027 })?;
1028 Some(BufferReference {
1029 path: relative_path_string(&roi_mask_path, run_dir),
1030 format: "json_mask_bool".to_string(),
1031 semantic: "roi_mask".to_string(),
1032 width: Some(metadata.width),
1033 height: Some(metadata.height),
1034 channels: Some(1),
1035 })
1036 } else {
1037 None
1038 };
1039
1040 let optional_reference = if let Some(reference) = &reference_color {
1041 reference.save_png(&reference_path)?;
1042 Some(BufferReference {
1043 path: relative_path_string(&reference_path, run_dir),
1044 format: "png_rgb8".to_string(),
1045 semantic: "reference_color".to_string(),
1046 width: Some(metadata.width),
1047 height: Some(metadata.height),
1048 channels: Some(3),
1049 })
1050 } else {
1051 None
1052 };
1053
1054 let buffers = ExternalBufferSet {
1055 current_color: BufferReference {
1056 path: relative_path_string(¤t_color_path, run_dir),
1057 format: "png_rgb8".to_string(),
1058 semantic: "current_color".to_string(),
1059 width: Some(metadata.width),
1060 height: Some(metadata.height),
1061 channels: Some(3),
1062 },
1063 reprojected_history: BufferReference {
1064 path: relative_path_string(&history_color_path, run_dir),
1065 format: "png_rgb8".to_string(),
1066 semantic: "reprojected_history".to_string(),
1067 width: Some(metadata.width),
1068 height: Some(metadata.height),
1069 channels: Some(3),
1070 },
1071 motion_vectors: BufferReference {
1072 path: relative_path_string(&motion_vectors_path, run_dir),
1073 format: "json_vec2_f32".to_string(),
1074 semantic: "motion_vectors".to_string(),
1075 width: Some(metadata.width),
1076 height: Some(metadata.height),
1077 channels: Some(2),
1078 },
1079 current_depth: BufferReference {
1080 path: relative_path_string(¤t_depth_path, run_dir),
1081 format: "json_scalar_f32".to_string(),
1082 semantic: "current_depth".to_string(),
1083 width: Some(metadata.width),
1084 height: Some(metadata.height),
1085 channels: Some(1),
1086 },
1087 reprojected_depth: BufferReference {
1088 path: relative_path_string(&history_depth_path, run_dir),
1089 format: "json_scalar_f32".to_string(),
1090 semantic: "reprojected_depth".to_string(),
1091 width: Some(metadata.width),
1092 height: Some(metadata.height),
1093 channels: Some(1),
1094 },
1095 current_normals: BufferReference {
1096 path: relative_path_string(¤t_normals_path, run_dir),
1097 format: "json_vec3_f32".to_string(),
1098 semantic: "current_normals".to_string(),
1099 width: Some(metadata.width),
1100 height: Some(metadata.height),
1101 channels: Some(3),
1102 },
1103 reprojected_normals: BufferReference {
1104 path: relative_path_string(&history_normals_path, run_dir),
1105 format: "json_vec3_f32".to_string(),
1106 semantic: "reprojected_normals".to_string(),
1107 width: Some(metadata.width),
1108 height: Some(metadata.height),
1109 channels: Some(3),
1110 },
1111 metadata: BufferReference {
1112 path: relative_path_string(&metadata_path, run_dir),
1113 format: "json_metadata".to_string(),
1114 semantic: "metadata".to_string(),
1115 width: None,
1116 height: None,
1117 channels: None,
1118 },
1119 optional_mask,
1120 optional_reference,
1121 optional_ground_truth: None,
1122 optional_variance: None,
1123 };
1124
1125 external_entries.push(ExternalCaptureEntry {
1126 label: frame.label.clone(),
1127 buffers,
1128 });
1129
1130 captures.push(MaterializedCapture {
1131 label: frame.label.clone(),
1132 frame_index: frame.frame_index,
1133 scene_name: metadata
1134 .scene_name
1135 .clone()
1136 .unwrap_or_else(|| manifest.dataset_id.clone()),
1137 shot_name: metadata
1138 .shot_name
1139 .clone()
1140 .unwrap_or_else(|| "shot_000".to_string()),
1141 host_output,
1142 roi_mask,
1143 disocclusion_mask,
1144 });
1145 }
1146
1147 let external_manifest = ExternalCaptureManifest {
1148 format_version: EXTERNAL_CAPTURE_FORMAT_VERSION.to_string(),
1149 description: format!(
1150 "Materialized external replay manifest generated from the strict Unreal-native dataset `{}`",
1151 manifest.dataset_id
1152 ),
1153 source: ExternalCaptureSource::EngineNative {
1154 engine_type: "unreal".to_string(),
1155 engine_version: Some(manifest.engine.engine_version.clone()),
1156 capture_tool: Some(manifest.engine.capture_tool.clone()),
1157 capture_note: Some(
1158 "real Unreal capture materialized into reprojected replay inputs with no synthetic fallback"
1159 .to_string(),
1160 ),
1161 },
1162 buffers: None,
1163 captures: external_entries,
1164 normalization: ExternalNormalization {
1165 color: manifest.contract.color_space.clone(),
1166 motion_vectors: format!(
1167 "{}; normalized into pixel offsets to the previous frame",
1168 manifest.contract.motion_vector_convention
1169 ),
1170 depth: manifest.contract.depth_convention.clone(),
1171 normals: manifest.contract.normal_space.clone(),
1172 },
1173 notes: vec![
1174 "provenance_label=unreal_native".to_string(),
1175 "real_engine_capture=true".to_string(),
1176 "no synthetic fallback is implemented in this path".to_string(),
1177 ],
1178 };
1179 let materialized_manifest_path = run_dir.join("materialized_unreal_external_manifest.json");
1180 fs::write(
1181 &materialized_manifest_path,
1182 serde_json::to_string_pretty(&external_manifest)?,
1183 )?;
1184
1185 Ok(MaterializedRun {
1186 materialized_manifest_path,
1187 captures,
1188 })
1189}
1190
1191fn generate_per_frame_artifacts(
1192 config: &DemoConfig,
1193 materialized_manifest_path: &Path,
1194 materialized_captures: &[MaterializedCapture],
1195 demo_a: &ExternalDemoAMetrics,
1196 gpu: &ExternalGpuMetrics,
1197 per_frame_dir: &Path,
1198) -> Result<Vec<FrameArtifacts>> {
1199 let bundle = crate::external::load_external_capture_bundle(
1200 config,
1201 materialized_manifest_path,
1202 per_frame_dir,
1203 )?;
1204 let profile =
1205 default_host_realistic_profile(config.dsfb_alpha_range.min, config.dsfb_alpha_range.max);
1206 let gpu_by_label = gpu
1207 .captures
1208 .iter()
1209 .map(|capture| (capture.capture_label.clone(), capture.total_ms))
1210 .collect::<BTreeMap<_, _>>();
1211
1212 let mut frames = Vec::with_capacity(bundle.captures.len());
1213 for capture in &bundle.captures {
1214 let materialized = materialized_captures
1215 .iter()
1216 .find(|candidate| candidate.label == capture.label)
1217 .ok_or_else(|| {
1218 Error::Message(format!(
1219 "materialized capture `{}` was missing during per-frame artifact generation",
1220 capture.label
1221 ))
1222 })?;
1223 let methods = find_demo_a_methods(demo_a, &capture.label)?;
1224 let outputs = supervise_temporal_reuse(&capture.inputs.borrow(), &profile);
1225 let _dsfb_resolved = resolve_with_alpha(
1226 &capture.inputs.reprojected_history,
1227 &capture.inputs.current_color,
1228 &outputs.alpha,
1229 );
1230 let (_strong_resolved, _, _strong_response) = run_strong_heuristic(config, capture);
1231 let fixed_alpha_field = constant_field(
1232 capture.inputs.width(),
1233 capture.inputs.height(),
1234 config.baseline.fixed_alpha,
1235 );
1236 let fixed_resolved = resolve_with_alpha(
1237 &capture.inputs.reprojected_history,
1238 &capture.inputs.current_color,
1239 &fixed_alpha_field,
1240 );
1241 let (reference_frame, reference_source, _) =
1242 capture_reference_frame_and_metric_source(capture);
1243 let (roi_mask, roi_source, roi_coverage) =
1244 roi_mask_for_capture(capture, &fixed_resolved, reference_frame);
1245 let roi_pixels = roi_mask.iter().filter(|value| **value).count();
1246 let instability_mask = materialized
1247 .disocclusion_mask
1248 .clone()
1249 .unwrap_or_else(|| {
1250 derive_instability_mask(
1251 &outputs,
1252 &capture.inputs.current_color,
1253 &capture.inputs.reprojected_history,
1254 )
1255 });
1256 let baseline = fixed_resolved.clone();
1257
1258 let capture_dir = per_frame_dir.join(&capture.label);
1259 fs::create_dir_all(&capture_dir)?;
1260 let current_frame_path = capture_dir.join("current_frame.png");
1261 let baseline_frame_path = capture_dir.join("baseline_or_host_output.png");
1262 let roi_mask_path = capture_dir.join("roi_mask.json");
1263 let trust_map_path = capture_dir.join("trust_map.png");
1264 let alpha_map_path = capture_dir.join("alpha_map.png");
1265 let intervention_map_path = capture_dir.join("intervention_map.png");
1266 let residual_map_path = capture_dir.join("residual_map.png");
1267 let instability_overlay_path = capture_dir.join("instability_overlay.png");
1268 let roi_overlay_path = capture_dir.join("roi_overlay.png");
1269 let output_panel_path = capture_dir.join(format!("boardroom_panel_{}.png", capture.label));
1270
1271 capture.inputs.current_color.save_png(¤t_frame_path)?;
1272 baseline.save_png(&baseline_frame_path)?;
1273 write_json(
1274 &roi_mask_path,
1275 &BoolJson {
1276 width: capture.inputs.width(),
1277 height: capture.inputs.height(),
1278 data: roi_mask.clone(),
1279 },
1280 )?;
1281 save_scalar_field_png(&outputs.trust, &trust_map_path, heatmap_blue)?;
1282 save_scalar_field_png(&outputs.alpha, &alpha_map_path, heatmap_orange)?;
1283 save_scalar_field_png(
1284 &outputs.intervention,
1285 &intervention_map_path,
1286 heatmap_red,
1287 )?;
1288 let residual_field =
1289 residual_field(&capture.inputs.current_color, &capture.inputs.reprojected_history);
1290 save_scalar_field_png(&residual_field, &residual_map_path, heatmap_residual)?;
1291 overlay_mask(
1292 &capture.inputs.current_color,
1293 &instability_mask,
1294 Color::rgb(1.0, 0.1, 0.1),
1295 0.45,
1296 )
1297 .save_png(&instability_overlay_path)?;
1298 overlay_mask(
1299 &capture.inputs.current_color,
1300 &roi_mask,
1301 Color::rgb(0.12, 1.0, 0.24),
1302 0.45,
1303 )
1304 .save_png(&roi_overlay_path)?;
1305
1306 let classification = classify_capture(&methods);
1307 let roi_residual_mean = residual_field.mean_over_mask(&roi_mask);
1308 let instability_fraction = instability_mask
1309 .iter()
1310 .filter(|value| **value)
1311 .count() as f32
1312 / instability_mask.len().max(1) as f32;
1313 let explanation = build_explanation(
1314 &classification,
1315 materialized,
1316 &methods,
1317 roi_residual_mean,
1318 instability_fraction,
1319 );
1320
1321 frames.push(FrameArtifacts {
1322 label: capture.label.clone(),
1323 scene_name: materialized.scene_name.clone(),
1324 shot_name: materialized.shot_name.clone(),
1325 frame_index: materialized.frame_index,
1326 classification,
1327 roi_source,
1328 roi_pixels,
1329 roi_coverage,
1330 reference_source: reference_source.to_string(),
1331 current_frame_path,
1332 baseline_frame_path,
1333 roi_mask_path,
1334 trust_map_path,
1335 alpha_map_path,
1336 intervention_map_path,
1337 residual_map_path,
1338 instability_overlay_path,
1339 roi_overlay_path,
1340 output_panel_path,
1341 metric_source: methods.metric_source.to_string(),
1342 dsfb_full_frame_mae: methods.dsfb.overall_mae,
1343 dsfb_roi_mae: methods.dsfb.roi_mae,
1344 dsfb_max_error: methods.dsfb.max_error,
1345 dsfb_plus_heuristic_full_frame_mae: methods.dsfb_plus_heuristic.overall_mae,
1346 dsfb_plus_heuristic_roi_mae: methods.dsfb_plus_heuristic.roi_mae,
1347 dsfb_plus_heuristic_max_error: methods.dsfb_plus_heuristic.max_error,
1348 strong_heuristic_full_frame_mae: methods.strong_heuristic.overall_mae,
1349 strong_heuristic_roi_mae: methods.strong_heuristic.roi_mae,
1350 strong_heuristic_max_error: methods.strong_heuristic.max_error,
1351 fixed_alpha_full_frame_mae: methods.fixed_alpha.overall_mae,
1352 fixed_alpha_roi_mae: methods.fixed_alpha.roi_mae,
1353 fixed_alpha_max_error: methods.fixed_alpha.max_error,
1354 dsfb_mean_trust: outputs.trust.mean(),
1355 dsfb_mean_alpha: outputs.alpha.mean(),
1356 dsfb_intervention_rate: outputs.intervention.mean(),
1357 roi_residual_mean,
1358 instability_fraction,
1359 gpu_total_ms: gpu_by_label.get(&capture.label).copied().flatten(),
1360 explanation,
1361 });
1362 }
1363
1364 Ok(frames)
1365}
1366
1367fn write_run_manifest(
1368 path: &Path,
1369 manifest_path: &Path,
1370 manifest: &UnrealNativeManifest,
1371 materialized: &MaterializedRun,
1372) -> Result<()> {
1373 let payload = serde_json::json!({
1374 "schema_version": manifest.schema_version,
1375 "dataset_kind": manifest.dataset_kind,
1376 "provenance_label": manifest.provenance_label,
1377 "dataset_id": manifest.dataset_id,
1378 "manifest_path": manifest_path.display().to_string(),
1379 "materialized_manifest_path": materialized.materialized_manifest_path.display().to_string(),
1380 "capture_count": materialized.captures.len(),
1381 "engine": manifest.engine,
1382 "contract": manifest.contract,
1383 "notes": manifest.notes,
1384 });
1385 fs::write(path, serde_json::to_string_pretty(&payload)?)?;
1386 Ok(())
1387}
1388
1389fn classification_counts(frames: &[FrameArtifacts]) -> ClassificationCounts {
1390 let mut counts = ClassificationCounts::default();
1391 for frame in frames {
1392 match frame.classification.as_str() {
1393 "dsfb_helpful" => counts.dsfb_helpful += 1,
1394 "dsfb_neutral" => counts.dsfb_neutral += 1,
1395 "heuristic_favorable" => counts.heuristic_favorable += 1,
1396 _ => counts.richer_cues_required += 1,
1397 }
1398 }
1399 counts
1400}
1401
1402fn write_metrics_csv(
1403 path: &Path,
1404 capture_summaries: &[CaptureSummaryRecord],
1405 demo_b: &ExternalDemoBMetrics,
1406) -> Result<()> {
1407 let mut csv = String::new();
1408 let _ = writeln!(
1409 csv,
1410 "record_type,capture_label,scene_name,shot_name,frame_index,classification,roi_source,roi_coverage,reference_source,metric_source,dsfb_full_frame_mae,dsfb_roi_mae,dsfb_max_error,dsfb_plus_heuristic_full_frame_mae,dsfb_plus_heuristic_roi_mae,dsfb_plus_heuristic_max_error,strong_heuristic_full_frame_mae,strong_heuristic_roi_mae,strong_heuristic_max_error,fixed_alpha_full_frame_mae,fixed_alpha_roi_mae,fixed_alpha_max_error,dsfb_mean_trust,dsfb_mean_alpha,dsfb_intervention_rate,roi_residual_mean,instability_fraction,gpu_total_ms"
1411 );
1412 for capture in capture_summaries {
1413 let _ = writeln!(
1414 csv,
1415 "demo_a,{capture_label},{scene_name},{shot_name},{frame_index},{classification},{roi_source},{roi_coverage:.5},{reference_source},{metric_source},{dsfb_full_frame_mae:.5},{dsfb_roi_mae:.5},{dsfb_max_error:.5},{dsfb_plus_heuristic_full_frame_mae:.5},{dsfb_plus_heuristic_roi_mae:.5},{dsfb_plus_heuristic_max_error:.5},{strong_heuristic_full_frame_mae:.5},{strong_heuristic_roi_mae:.5},{strong_heuristic_max_error:.5},{fixed_alpha_full_frame_mae:.5},{fixed_alpha_roi_mae:.5},{fixed_alpha_max_error:.5},{dsfb_mean_trust:.5},{dsfb_mean_alpha:.5},{dsfb_intervention_rate:.5},{roi_residual_mean:.5},{instability_fraction:.5},{gpu_total_ms}",
1416 capture_label = capture.capture_label,
1417 scene_name = capture.scene_name,
1418 shot_name = capture.shot_name,
1419 frame_index = capture.frame_index,
1420 classification = capture.classification,
1421 roi_source = capture.roi_source,
1422 roi_coverage = capture.roi_coverage,
1423 reference_source = capture.reference_source,
1424 metric_source = capture.metric_source,
1425 dsfb_full_frame_mae = capture.dsfb_full_frame_mae,
1426 dsfb_roi_mae = capture.dsfb_roi_mae,
1427 dsfb_max_error = capture.dsfb_max_error,
1428 dsfb_plus_heuristic_full_frame_mae = capture.dsfb_plus_heuristic_full_frame_mae,
1429 dsfb_plus_heuristic_roi_mae = capture.dsfb_plus_heuristic_roi_mae,
1430 dsfb_plus_heuristic_max_error = capture.dsfb_plus_heuristic_max_error,
1431 strong_heuristic_full_frame_mae = capture.strong_heuristic_full_frame_mae,
1432 strong_heuristic_roi_mae = capture.strong_heuristic_roi_mae,
1433 strong_heuristic_max_error = capture.strong_heuristic_max_error,
1434 fixed_alpha_full_frame_mae = capture.fixed_alpha_full_frame_mae,
1435 fixed_alpha_roi_mae = capture.fixed_alpha_roi_mae,
1436 fixed_alpha_max_error = capture.fixed_alpha_max_error,
1437 dsfb_mean_trust = capture.dsfb_mean_trust,
1438 dsfb_mean_alpha = capture.dsfb_mean_alpha,
1439 dsfb_intervention_rate = capture.dsfb_intervention_rate,
1440 roi_residual_mean = capture.roi_residual_mean,
1441 instability_fraction = capture.instability_fraction,
1442 gpu_total_ms = capture
1443 .gpu_total_ms
1444 .map(|value| format!("{value:.5}"))
1445 .unwrap_or_else(|| "n/a".to_string()),
1446 );
1447 }
1448 for capture in &demo_b.captures {
1449 for policy in &capture.policies {
1450 let _ = writeln!(
1451 csv,
1452 "demo_b,{capture_label},,,,,{roi_source},{roi_coverage:.5},{reference_source},{metric_source},,{roi_mae:.5},,,,,,,,,,{roi_mean_spp:.5},{non_roi_mean_spp:.5},{overall_mae:.5},,,",
1453 capture_label = capture.capture_label,
1454 roi_source = capture.roi_source,
1455 roi_coverage = capture.roi_coverage,
1456 reference_source = capture.reference_source,
1457 metric_source = capture.metric_source,
1458 roi_mae = policy.roi_mae,
1459 roi_mean_spp = policy.roi_mean_spp,
1460 non_roi_mean_spp = policy.non_roi_mean_spp,
1461 overall_mae = policy.overall_mae,
1462 );
1463 }
1464 }
1465 fs::write(path, csv)?;
1466 Ok(())
1467}
1468
1469fn write_comparison_summary(
1470 path: &Path,
1471 manifest: &UnrealNativeManifest,
1472 frames: &[FrameArtifacts],
1473 demo_b: &ExternalDemoBMetrics,
1474) -> Result<()> {
1475 let mut markdown = String::new();
1476 let roi_coverages = frames
1477 .iter()
1478 .map(|frame| frame.roi_coverage)
1479 .collect::<Vec<_>>();
1480 let fixed_roi = frames
1481 .iter()
1482 .map(|frame| frame.fixed_alpha_roi_mae)
1483 .collect::<Vec<_>>();
1484 let strong_roi = frames
1485 .iter()
1486 .map(|frame| frame.strong_heuristic_roi_mae)
1487 .collect::<Vec<_>>();
1488 let dsfb_roi = frames
1489 .iter()
1490 .map(|frame| frame.dsfb_roi_mae)
1491 .collect::<Vec<_>>();
1492 let hybrid_roi = frames
1493 .iter()
1494 .map(|frame| frame.dsfb_plus_heuristic_roi_mae)
1495 .collect::<Vec<_>>();
1496 let (roi_coverage_mean, roi_coverage_std) = mean_and_std(&roi_coverages);
1497 let (fixed_roi_mean, fixed_roi_std) = mean_and_std(&fixed_roi);
1498 let (strong_roi_mean, strong_roi_std) = mean_and_std(&strong_roi);
1499 let (dsfb_roi_mean, dsfb_roi_std) = mean_and_std(&dsfb_roi);
1500 let (hybrid_roi_mean, hybrid_roi_std) = mean_and_std(&hybrid_roi);
1501 let heuristic_favorable_everywhere =
1502 !frames.is_empty() && frames.iter().all(|frame| frame.classification == "heuristic_favorable");
1503 let hybrid_beats_strong = !frames.is_empty() && hybrid_roi_mean + 1e-6 < strong_roi_mean;
1504 let onset_frame = frames.iter().min_by_key(|frame| frame.frame_index);
1505 let peak_roi_frame = frames
1506 .iter()
1507 .max_by(|left, right| left.roi_coverage.total_cmp(&right.roi_coverage));
1508 let recovery_frame = frames.iter().max_by_key(|frame| frame.frame_index);
1509 let mean_demo_b_policy = |policy_id: &str| -> Option<f32> {
1510 let values = demo_b
1511 .captures
1512 .iter()
1513 .map(|capture| {
1514 capture
1515 .policies
1516 .iter()
1517 .find(|policy| policy.policy_id == policy_id)
1518 .map(|policy| policy.roi_mae)
1519 })
1520 .collect::<Option<Vec<_>>>()?;
1521 Some(values.iter().copied().sum::<f32>() / values.len() as f32)
1522 };
1523 let _ = writeln!(markdown, "# Unreal-Native Comparison Summary");
1524 let _ = writeln!(markdown);
1525 let _ = writeln!(
1526 markdown,
1527 "Dataset `{}` is labeled `{}` and was executed through the strict Unreal-native replay path.",
1528 manifest.dataset_id, manifest.provenance_label
1529 );
1530 let _ = writeln!(markdown);
1531 let _ = writeln!(markdown, "## Frozen Benchmark Contract");
1532 let _ = writeln!(markdown);
1533 let _ = writeln!(markdown, "- {ROI_CONTRACT_STATEMENT}");
1534 let _ = writeln!(
1535 markdown,
1536 "- Canonical baseline ladder: `fixed_alpha`, `strong_heuristic`, `dsfb_host_minimum`, `dsfb_plus_strong_heuristic`."
1537 );
1538 let _ = writeln!(
1539 markdown,
1540 "- Fixed capture count in this run: `{}` real Unreal-native capture(s).",
1541 frames.len()
1542 );
1543 if frames.len() >= 2 {
1544 let _ = writeln!(
1545 markdown,
1546 "- Trust diagnostics generated for the canonical run: `figures/trust_histogram.svg`, `figures/trust_vs_error.svg`, `figures/trust_conditioned_error_map.png`, `figures/trust_temporal_trajectory.svg`."
1547 );
1548 } else {
1549 let _ = writeln!(
1550 markdown,
1551 "- Trust diagnostics generated for the canonical run: `figures/trust_histogram.svg`, `figures/trust_vs_error.svg`, `figures/trust_conditioned_error_map.png`."
1552 );
1553 }
1554 let _ = writeln!(markdown);
1555 let _ = writeln!(markdown, "## Current Result Posture");
1556 let _ = writeln!(markdown);
1557 if hybrid_beats_strong {
1558 let _ = writeln!(markdown, "- {CANONICAL_HEADLINE_STATEMENT}");
1559 }
1560 if heuristic_favorable_everywhere {
1561 let _ = writeln!(markdown, "- {PURE_DSFB_LIMITATION_STATEMENT}");
1562 } else if !frames.is_empty() {
1563 let _ = writeln!(
1564 markdown,
1565 "- Pure DSFB does beat the strong heuristic on at least one capture in this run, so no pure-DSFB limitation claim is emitted for this manifest."
1566 );
1567 }
1568 if (roi_coverage_mean - 0.5).abs() <= 0.1 {
1569 let _ = writeln!(markdown, "- {ROI_HONESTY_STATEMENT}");
1570 } else {
1571 let _ = writeln!(
1572 markdown,
1573 "- The ROI definition captures {:.2}% of the frame under the fixed baseline-relative threshold, so it is not being treated as a tiny artifact-only mask in this run.",
1574 roi_coverage_mean * 100.0
1575 );
1576 }
1577 let _ = writeln!(
1578 markdown,
1579 "- Demo A ROI MAE mean ± std is {:.5} ± {:.5} for `fixed_alpha`, {:.5} ± {:.5} for `strong_heuristic`, {:.5} ± {:.5} for `dsfb_host_minimum`, and {:.5} ± {:.5} for `dsfb_plus_strong_heuristic`.",
1580 fixed_roi_mean,
1581 fixed_roi_std,
1582 strong_roi_mean,
1583 strong_roi_std,
1584 dsfb_roi_mean,
1585 dsfb_roi_std,
1586 hybrid_roi_mean,
1587 hybrid_roi_std
1588 );
1589 let _ = writeln!(
1590 markdown,
1591 "- ROI coverage mean ± std is {:.2}% ± {:.2}% across the fixed capture family.",
1592 roi_coverage_mean * 100.0,
1593 roi_coverage_std * 100.0
1594 );
1595 if let (Some(onset), Some(peak), Some(recovery)) = (onset_frame, peak_roi_frame, recovery_frame) {
1596 let _ = writeln!(
1597 markdown,
1598 "- Trust trajectory facts for this run: onset `{}`, peak ROI `{}`, recovery-side `{}`; mean trust = {:.5} -> {:.5} -> {:.5}; intervention rate = {:.5} -> {:.5} -> {:.5}.",
1599 onset.label,
1600 peak.label,
1601 recovery.label,
1602 onset.dsfb_mean_trust,
1603 peak.dsfb_mean_trust,
1604 recovery.dsfb_mean_trust,
1605 onset.dsfb_intervention_rate,
1606 peak.dsfb_intervention_rate,
1607 recovery.dsfb_intervention_rate
1608 );
1609 }
1610 if let (Some(imported), Some(combined), Some(uniform)) = (
1611 mean_demo_b_policy("imported_trust"),
1612 mean_demo_b_policy("combined_heuristic"),
1613 mean_demo_b_policy("uniform"),
1614 ) {
1615 let _ = writeln!(
1616 markdown,
1617 "- Demo B mean ROI error is {:.5} for imported trust, {:.5} for the combined heuristic, and {:.5} for uniform allocation.",
1618 imported,
1619 combined,
1620 uniform
1621 );
1622 }
1623 let _ = writeln!(markdown);
1624 let _ = writeln!(markdown, "## Capture Classification");
1625 let _ = writeln!(markdown);
1626 for frame in frames {
1627 let _ = writeln!(
1628 markdown,
1629 "- `{}` ({}/{} frame {}): `{}`. ROI pixels = {}, ROI coverage = {:.2}%. DSFB ROI MAE = {:.5}, DSFB + heuristic ROI MAE = {:.5}, strong heuristic ROI MAE = {:.5}, fixed alpha ROI MAE = {:.5}.",
1630 frame.label,
1631 frame.scene_name,
1632 frame.shot_name,
1633 frame.frame_index,
1634 frame.classification,
1635 frame.roi_pixels,
1636 frame.roi_coverage * 100.0,
1637 frame.dsfb_roi_mae,
1638 frame.dsfb_plus_heuristic_roi_mae,
1639 frame.strong_heuristic_roi_mae,
1640 frame.fixed_alpha_roi_mae
1641 );
1642 }
1643 let _ = writeln!(markdown);
1644 let _ = writeln!(markdown, "## Demo B Policy Posture");
1645 let _ = writeln!(markdown);
1646 for capture in &demo_b.captures {
1647 let imported = capture
1648 .policies
1649 .iter()
1650 .find(|policy| policy.policy_id == "imported_trust");
1651 let combined = capture
1652 .policies
1653 .iter()
1654 .find(|policy| policy.policy_id == "combined_heuristic");
1655 let uniform = capture
1656 .policies
1657 .iter()
1658 .find(|policy| policy.policy_id == "uniform");
1659 if let (Some(imported), Some(combined), Some(uniform)) = (imported, combined, uniform) {
1660 let winner = if imported.roi_mae + 1e-4 < combined.roi_mae {
1661 "DSFB-helpful allocation case"
1662 } else if (imported.roi_mae - combined.roi_mae).abs() <= 1e-4 {
1663 "DSFB-neutral allocation case"
1664 } else {
1665 "heuristic-favorable allocation case"
1666 };
1667 let _ = writeln!(
1668 markdown,
1669 "- `{}`: {}. Imported trust ROI error = {:.5}, combined heuristic ROI error = {:.5}, uniform ROI error = {:.5}.",
1670 capture.capture_label,
1671 winner,
1672 imported.roi_mae,
1673 combined.roi_mae,
1674 uniform.roi_mae
1675 );
1676 }
1677 }
1678 let _ = writeln!(markdown);
1679 let _ = writeln!(markdown, "## Boundaries");
1680 let _ = writeln!(markdown);
1681 let _ = writeln!(
1682 markdown,
1683 "- This is evidence consistent with reduced temporal artifact risk in bounded cases, not a claim of universal outperformance."
1684 );
1685 if frames.len() < ROI_AGGREGATION_MIN_CAPTURES {
1686 let _ = writeln!(
1687 markdown,
1688 "- Aggregated mean ± std claims are blocked until at least {} real captures are available under unchanged parameters and code.",
1689 ROI_AGGREGATION_MIN_CAPTURES
1690 );
1691 } else {
1692 let _ = writeln!(
1693 markdown,
1694 "- Aggregated mean ± std claims are emitted in `aggregation_summary.md` because this run contains {} unchanged-code real captures.",
1695 frames.len()
1696 );
1697 }
1698 let _ = writeln!(
1699 markdown,
1700 "- Demo B remains an advisory allocation proxy unless a live renderer budget path is exported."
1701 );
1702 let _ = writeln!(
1703 markdown,
1704 "- The crate is acting as a supervisory trust / admissibility / intervention layer, not a renderer replacement."
1705 );
1706 fs::write(path, markdown)?;
1707 Ok(())
1708}
1709
1710fn write_failure_modes(
1711 path: &Path,
1712 manifest: &UnrealNativeManifest,
1713 captures: &[MaterializedCapture],
1714 scaling: &ExternalScalingMetrics,
1715) -> Result<()> {
1716 let missing_optional = captures
1717 .iter()
1718 .filter(|capture| capture.roi_mask.is_none() || capture.disocclusion_mask.is_none())
1719 .map(|capture| capture.label.clone())
1720 .collect::<Vec<_>>();
1721 let mut markdown = String::new();
1722 let _ = writeln!(markdown, "# Unreal-Native Failure Modes");
1723 let _ = writeln!(markdown);
1724 let _ = writeln!(
1725 markdown,
1726 "This file is first-class evidence for where the Unreal-native replay path should remain bounded or advisory."
1727 );
1728 let _ = writeln!(markdown);
1729 let _ = writeln!(markdown, "## Structural Limits");
1730 let _ = writeln!(markdown);
1731 let _ = writeln!(
1732 markdown,
1733 "- Residual-only evidence weakens when the host output already tracks the current frame closely."
1734 );
1735 let _ = writeln!(
1736 markdown,
1737 "- Canonical ROI is always recomputed from the fixed-alpha baseline and the reference/proxy frame using `{}`; optional manifest ROI masks are audit inputs only.",
1738 ROI_CONTRACT_SOURCE
1739 );
1740 let _ = writeln!(
1741 markdown,
1742 "- Transparency, particles, UI, post effects, and specular-only motion can violate the view-space normal and monotonic-depth assumptions."
1743 );
1744 let _ = writeln!(
1745 markdown,
1746 "- If motion vectors are noisy or encoded in a convention that does not match the manifest, the run fails rather than silently downgrading."
1747 );
1748 let _ = writeln!(
1749 markdown,
1750 "- Where a host heuristic already performs strongly, DSFB should be interpreted as a bounded monitor or advisory layer."
1751 );
1752 let _ = writeln!(markdown);
1753 let _ = writeln!(markdown, "## Export-Specific Notes");
1754 let _ = writeln!(markdown);
1755 let _ = writeln!(
1756 markdown,
1757 "- Dataset `{}` uses motion_vector_convention = `{}` and history_source = `{}`.",
1758 manifest.dataset_id,
1759 manifest.contract.motion_vector_convention,
1760 manifest.contract.history_source
1761 );
1762 if !missing_optional.is_empty() {
1763 let _ = writeln!(
1764 markdown,
1765 "- Optional overlays were missing for: {}.",
1766 missing_optional.join(", ")
1767 );
1768 }
1769 let _ = writeln!(markdown);
1770 let _ = writeln!(markdown, "## Scaling Limits");
1771 let _ = writeln!(markdown);
1772 let _ = writeln!(
1773 markdown,
1774 "- Scaling measurement kind: `{}`. Coverage status: `{}`.",
1775 scaling.measurement_kind, scaling.coverage.coverage_status
1776 );
1777 for entry in &scaling.entries {
1778 if let Some(reason) = &entry.unavailable_reason {
1779 let _ = writeln!(
1780 markdown,
1781 "- `{}` {}x{} unavailable: {}",
1782 entry.label, entry.width, entry.height, reason
1783 );
1784 }
1785 }
1786 fs::write(path, markdown)?;
1787 Ok(())
1788}
1789
1790fn select_executive_frame<'a>(frames: &'a [FrameArtifacts]) -> Result<&'a FrameArtifacts> {
1791 frames
1792 .iter()
1793 .max_by(|left, right| {
1794 let left_gain = left
1795 .strong_heuristic_roi_mae
1796 - left.dsfb_roi_mae.min(left.dsfb_plus_heuristic_roi_mae);
1797 let right_gain = right
1798 .strong_heuristic_roi_mae
1799 - right.dsfb_roi_mae.min(right.dsfb_plus_heuristic_roi_mae);
1800 left_gain.total_cmp(&right_gain)
1801 })
1802 .ok_or_else(|| Error::Message("no per-frame artifacts were generated".to_string()))
1803}
1804
1805fn run_bundle_builder(run_dir: &Path) -> Result<()> {
1806 let script = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
1807 .join("colab")
1808 .join("build_unreal_native_bundle.py");
1809 let status = Command::new("python3")
1810 .arg(&script)
1811 .arg("--run-dir")
1812 .arg(run_dir)
1813 .status()
1814 .map_err(|error| {
1815 Error::Message(format!(
1816 "failed to launch unreal-native bundle builder {}: {error}",
1817 script.display()
1818 ))
1819 })?;
1820 if !status.success() {
1821 return Err(Error::Message(format!(
1822 "unreal-native bundle builder {} exited with status {}",
1823 script.display(),
1824 status
1825 )));
1826 }
1827 Ok(())
1828}
1829
1830fn find_demo_a_methods<'a>(
1831 demo_a: &'a ExternalDemoAMetrics,
1832 capture_label: &str,
1833) -> Result<DemoAMethodSelection<'a>> {
1834 let capture = demo_a
1835 .captures
1836 .iter()
1837 .find(|capture| capture.capture_label == capture_label)
1838 .ok_or_else(|| {
1839 Error::Message(format!(
1840 "Demo A metrics did not contain capture `{capture_label}`"
1841 ))
1842 })?;
1843 let fixed_alpha = capture
1844 .methods
1845 .iter()
1846 .find(|method| method.method_id == "fixed_alpha")
1847 .ok_or_else(|| Error::Message(format!("capture `{capture_label}` missing fixed_alpha")))?;
1848 let strong_heuristic = capture
1849 .methods
1850 .iter()
1851 .find(|method| method.method_id == "strong_heuristic")
1852 .ok_or_else(|| {
1853 Error::Message(format!(
1854 "capture `{capture_label}` missing strong_heuristic"
1855 ))
1856 })?;
1857 let dsfb = capture
1858 .methods
1859 .iter()
1860 .find(|method| method.method_id == "dsfb_host_minimum")
1861 .ok_or_else(|| {
1862 Error::Message(format!(
1863 "capture `{capture_label}` missing dsfb_host_minimum"
1864 ))
1865 })?;
1866 let dsfb_plus_heuristic = capture
1867 .methods
1868 .iter()
1869 .find(|method| method.method_id == "dsfb_plus_strong_heuristic")
1870 .ok_or_else(|| {
1871 Error::Message(format!(
1872 "capture `{capture_label}` missing dsfb_plus_strong_heuristic"
1873 ))
1874 })?;
1875 Ok(DemoAMethodSelection {
1876 metric_source: &capture.metric_source,
1877 roi_source: &capture.roi_source,
1878 roi_coverage: capture.roi_coverage,
1879 reference_source: &capture.reference_source,
1880 fixed_alpha,
1881 strong_heuristic,
1882 dsfb,
1883 dsfb_plus_heuristic,
1884 })
1885}
1886
1887fn classify_capture(methods: &DemoAMethodSelection<'_>) -> String {
1888 let epsilon = 1e-4;
1889 if methods.dsfb.roi_mae + epsilon
1890 < methods
1891 .strong_heuristic
1892 .roi_mae
1893 .min(methods.fixed_alpha.roi_mae)
1894 {
1895 "dsfb_helpful".to_string()
1896 } else if (methods.dsfb.roi_mae - methods.strong_heuristic.roi_mae).abs() <= epsilon {
1897 "dsfb_neutral".to_string()
1898 } else if methods.strong_heuristic.roi_mae + epsilon < methods.dsfb.roi_mae {
1899 "heuristic_favorable".to_string()
1900 } else {
1901 "richer_cues_required".to_string()
1902 }
1903}
1904
1905fn build_explanation(
1906 classification: &str,
1907 materialized: &MaterializedCapture,
1908 methods: &DemoAMethodSelection<'_>,
1909 roi_residual_mean: f32,
1910 instability_fraction: f32,
1911) -> EvidenceExplanation {
1912 let what_went_wrong = format!(
1913 "Temporal reuse risk was concentrated in scene `{}` / shot `{}` frame {} with ROI residual {:.5} and instability coverage {:.3}.",
1914 materialized.scene_name, materialized.shot_name, materialized.frame_index, roi_residual_mean, instability_fraction
1915 );
1916 let what_dsfb_detected = format!(
1917 "DSFB concentrated low trust and intervention in the exported Unreal-native ROI, with ROI MAE {:.5}, strong heuristic ROI MAE {:.5}, and DSFB + heuristic ROI MAE {:.5}.",
1918 methods.dsfb.roi_mae, methods.strong_heuristic.roi_mae, methods.dsfb_plus_heuristic.roi_mae
1919 );
1920 let what_dsfb_changed = match classification {
1921 "dsfb_helpful" => "The supervisory layer would route this region toward higher alpha / intervention and away from blind temporal reuse.".to_string(),
1922 "dsfb_neutral" => "The supervisory layer agreed with the strongest host heuristic closely enough that this should be treated as a bounded monitor result, not a large behavioral delta.".to_string(),
1923 "heuristic_favorable" if methods.dsfb_plus_heuristic.roi_mae + 1e-4 < methods.strong_heuristic.roi_mae =>
1924 "The strongest host heuristic outperformed pure DSFB on this frame, but the explicit DSFB + strong heuristic hybrid recovered that miss and is reported separately rather than relabeled as DSFB.".to_string(),
1925 "heuristic_favorable" => "The strongest host heuristic outperformed DSFB on this frame; the evidence is surfaced directly rather than hidden.".to_string(),
1926 _ => "The current observability is not rich enough to claim a strong DSFB advantage on this frame.".to_string(),
1927 };
1928 let host_output_note = if materialized.host_output.is_some() {
1929 "Exported host output was preserved as an audit-only artifact and is not the canonical benchmark baseline."
1930 } else {
1931 "No exported host output was available; the canonical benchmark baseline remains fixed alpha."
1932 };
1933 let overhead_and_caveat = format!(
1934 "GPU measurement is advisory and environment-dependent; canonical ROI source is `{}` with {:.2}% coverage against reference source `{}` and canonical baseline is `{}`. {} This is not a renderer replacement claim.",
1935 methods.roi_source,
1936 methods.roi_coverage * 100.0,
1937 methods.reference_source,
1938 ROI_CONTRACT_BASELINE_METHOD_ID,
1939 host_output_note
1940 );
1941 EvidenceExplanation {
1942 what_went_wrong,
1943 what_dsfb_detected,
1944 what_dsfb_changed,
1945 overhead_and_caveat,
1946 }
1947}
1948
1949fn load_unreal_frame_metadata(base_dir: &Path, reference: &BufferReference) -> Result<UnrealFrameMetadata> {
1950 if reference.format != "json_metadata" {
1951 return Err(Error::Message(format!(
1952 "unreal-native metadata {} must use json_metadata format",
1953 reference.path
1954 )));
1955 }
1956 let path = resolve_path(base_dir, &reference.path);
1957 read_json(&path)
1958}
1959
1960fn validate_frame_metadata(frame: &UnrealFrameEntry, metadata: &UnrealFrameMetadata) -> Result<()> {
1961 if metadata.width == 0 || metadata.height == 0 {
1962 return Err(Error::Message(format!(
1963 "capture `{}` declared zero-sized extent {}x{}",
1964 frame.label, metadata.width, metadata.height
1965 )));
1966 }
1967 if metadata.frame_index != frame.frame_index {
1968 return Err(Error::Message(format!(
1969 "capture `{}` manifest frame_index {} did not match metadata frame_index {}",
1970 frame.label, frame.frame_index, metadata.frame_index
1971 )));
1972 }
1973 if metadata.history_frame_index != frame.history_frame_index {
1974 return Err(Error::Message(format!(
1975 "capture `{}` manifest history_frame_index {} did not match metadata history_frame_index {}",
1976 frame.label, frame.history_frame_index, metadata.history_frame_index
1977 )));
1978 }
1979 if metadata.source_kind != UNREAL_NATIVE_DATASET_KIND {
1980 return Err(Error::Message(format!(
1981 "capture `{}` metadata source_kind `{}` is invalid; `{}` is required",
1982 frame.label, metadata.source_kind, UNREAL_NATIVE_DATASET_KIND
1983 )));
1984 }
1985 if metadata.provenance_label.as_deref() != Some(UNREAL_NATIVE_PROVENANCE_LABEL) {
1986 return Err(Error::Message(format!(
1987 "capture `{}` metadata provenance_label must be `{}`",
1988 frame.label, UNREAL_NATIVE_PROVENANCE_LABEL
1989 )));
1990 }
1991 if !metadata.real_external_data {
1992 return Err(Error::Message(format!(
1993 "capture `{}` metadata real_external_data=false; unreal-native mode refuses synthetic or proxy provenance",
1994 frame.label
1995 )));
1996 }
1997 Ok(())
1998}
1999
2000fn normalize_motion_vectors(
2001 values: &mut [[f32; 2]],
2002 width: usize,
2003 height: usize,
2004 convention: &str,
2005) -> Result<()> {
2006 match convention {
2007 "pixel_offset_to_prev" => {}
2008 "ndc_to_prev" => {
2009 let scale_x = width as f32 / 2.0;
2010 let scale_y = height as f32 / 2.0;
2011 for value in values {
2012 value[0] *= scale_x;
2013 value[1] *= scale_y;
2014 }
2015 }
2016 other => {
2017 return Err(Error::Message(format!(
2018 "unsupported unreal-native motion vector convention `{other}`"
2019 )))
2020 }
2021 }
2022 Ok(())
2023}
2024
2025fn reproject_image(previous: &ImageFrame, motion_vectors: &[MotionVector]) -> ImageFrame {
2026 let mut output = ImageFrame::new(previous.width(), previous.height());
2027 for y in 0..previous.height() {
2028 for x in 0..previous.width() {
2029 let motion = motion_vectors[y * previous.width() + x];
2030 output.set(
2031 x,
2032 y,
2033 previous.sample_bilinear_clamped(x as f32 + motion.to_prev_x, y as f32 + motion.to_prev_y),
2034 );
2035 }
2036 }
2037 output
2038}
2039
2040fn reproject_scalar(
2041 previous: &[f32],
2042 width: usize,
2043 height: usize,
2044 motion_vectors: &[MotionVector],
2045) -> Vec<f32> {
2046 let mut output = vec![0.0; width * height];
2047 for y in 0..height {
2048 for x in 0..width {
2049 let motion = motion_vectors[y * width + x];
2050 output[y * width + x] = sample_scalar(previous, width, height, x as f32 + motion.to_prev_x, y as f32 + motion.to_prev_y);
2051 }
2052 }
2053 output
2054}
2055
2056fn reproject_normals(
2057 previous: &[Normal3],
2058 width: usize,
2059 height: usize,
2060 motion_vectors: &[MotionVector],
2061) -> Vec<Normal3> {
2062 let mut output = vec![Normal3::new(0.0, 0.0, 1.0); width * height];
2063 for y in 0..height {
2064 for x in 0..width {
2065 let motion = motion_vectors[y * width + x];
2066 output[y * width + x] = sample_normal(previous, width, height, x as f32 + motion.to_prev_x, y as f32 + motion.to_prev_y);
2067 }
2068 }
2069 output
2070}
2071
2072fn sample_scalar(values: &[f32], width: usize, height: usize, x: f32, y: f32) -> f32 {
2073 let x0 = x.floor();
2074 let y0 = y.floor();
2075 let x1 = x0 + 1.0;
2076 let y1 = y0 + 1.0;
2077 let tx = (x - x0).clamp(0.0, 1.0);
2078 let ty = (y - y0).clamp(0.0, 1.0);
2079 let sample = |sx: f32, sy: f32| -> f32 {
2080 let ix = sx.clamp(0.0, width.saturating_sub(1) as f32) as usize;
2081 let iy = sy.clamp(0.0, height.saturating_sub(1) as f32) as usize;
2082 values[iy * width + ix]
2083 };
2084 let top = sample(x0, y0) * (1.0 - tx) + sample(x1, y0) * tx;
2085 let bottom = sample(x0, y1) * (1.0 - tx) + sample(x1, y1) * tx;
2086 top * (1.0 - ty) + bottom * ty
2087}
2088
2089fn sample_normal(values: &[Normal3], width: usize, height: usize, x: f32, y: f32) -> Normal3 {
2090 let x0 = x.floor();
2091 let y0 = y.floor();
2092 let x1 = x0 + 1.0;
2093 let y1 = y0 + 1.0;
2094 let tx = (x - x0).clamp(0.0, 1.0);
2095 let ty = (y - y0).clamp(0.0, 1.0);
2096 let sample = |sx: f32, sy: f32| -> Normal3 {
2097 let ix = sx.clamp(0.0, width.saturating_sub(1) as f32) as usize;
2098 let iy = sy.clamp(0.0, height.saturating_sub(1) as f32) as usize;
2099 values[iy * width + ix]
2100 };
2101 let lerp = |a: Normal3, b: Normal3, t: f32| {
2102 Normal3::new(
2103 a.x + (b.x - a.x) * t,
2104 a.y + (b.y - a.y) * t,
2105 a.z + (b.z - a.z) * t,
2106 )
2107 };
2108 lerp(lerp(sample(x0, y0), sample(x1, y0), tx), lerp(sample(x0, y1), sample(x1, y1), tx), ty).normalized()
2109}
2110
2111fn load_mask_any(
2112 base_dir: &Path,
2113 reference: &BufferReference,
2114 expected_width: usize,
2115 expected_height: usize,
2116) -> Result<Vec<bool>> {
2117 match reference.format.as_str() {
2118 "json_mask_bool" | "raw_mask_u8" => {
2119 Ok(load_bool_buffer_from_reference(base_dir, reference, expected_width, expected_height)?.2)
2120 }
2121 "json_scalar_f32" | "raw_r32f" | "exr_r32f" => {
2122 let values =
2123 load_scalar_buffer_from_reference(base_dir, reference, expected_width, expected_height)?.2;
2124 Ok(values.into_iter().map(|value| value >= 0.5).collect())
2125 }
2126 other => Err(Error::Message(format!(
2127 "unsupported unreal-native mask format `{other}` for {}",
2128 reference.path
2129 ))),
2130 }
2131}
2132
2133fn resolve_with_alpha(
2134 history: &ImageFrame,
2135 current: &ImageFrame,
2136 alpha: &ScalarField,
2137) -> ImageFrame {
2138 let mut output = ImageFrame::new(current.width(), current.height());
2139 for y in 0..current.height() {
2140 for x in 0..current.width() {
2141 output.set(
2142 x,
2143 y,
2144 history.get(x, y).lerp(current.get(x, y), alpha.get(x, y)),
2145 );
2146 }
2147 }
2148 output
2149}
2150
2151fn run_strong_heuristic(
2152 config: &DemoConfig,
2153 capture: &crate::external::ExternalLoadedCapture,
2154) -> (ImageFrame, ScalarField, ScalarField) {
2155 let width = capture.inputs.width();
2156 let height = capture.inputs.height();
2157 let mut resolved = ImageFrame::new(width, height);
2158 let mut alpha = ScalarField::new(width, height);
2159 let mut response = ScalarField::new(width, height);
2160
2161 for y in 0..height {
2162 for x in 0..width {
2163 let index = y * width + x;
2164 let current = capture.inputs.current_color.get(x, y);
2165 let history = capture.inputs.reprojected_history.get(x, y);
2166 let clamped = clamp_to_current_neighborhood(&capture.inputs.current_color, history, x, y);
2167 let clamp_distance = clamped.abs_diff(history);
2168 let residual_gate = smoothstep(
2169 config.baseline.residual_threshold.low,
2170 config.baseline.residual_threshold.high,
2171 current.abs_diff(clamped),
2172 );
2173 let depth_gate = smoothstep(
2174 config.baseline.depth_disagreement.low,
2175 config.baseline.depth_disagreement.high,
2176 (capture.inputs.current_depth[index] - capture.inputs.reprojected_depth[index]).abs(),
2177 );
2178 let normal_gate = smoothstep(
2179 config.baseline.normal_disagreement.low,
2180 config.baseline.normal_disagreement.high,
2181 1.0 - capture.inputs.current_normals[index]
2182 .dot(capture.inputs.reprojected_normals[index])
2183 .clamp(-1.0, 1.0),
2184 );
2185 let neighborhood_gate = smoothstep(
2186 config.baseline.neighborhood_distance.low,
2187 config.baseline.neighborhood_distance.high,
2188 clamp_distance,
2189 );
2190 let trigger = residual_gate
2191 .max(depth_gate)
2192 .max(normal_gate)
2193 .max(neighborhood_gate);
2194 let pixel_alpha = config.baseline.residual_alpha_range.min
2195 + (config.baseline.residual_alpha_range.max
2196 - config.baseline.residual_alpha_range.min)
2197 * trigger;
2198 alpha.set(x, y, pixel_alpha);
2199 response.set(x, y, trigger);
2200 resolved.set(x, y, clamped.lerp(current, pixel_alpha));
2201 }
2202 }
2203
2204 (resolved, alpha, response)
2205}
2206
2207fn clamp_to_current_neighborhood(
2208 current: &ImageFrame,
2209 history: Color,
2210 x: usize,
2211 y: usize,
2212) -> Color {
2213 let mut min_r = f32::INFINITY;
2214 let mut min_g = f32::INFINITY;
2215 let mut min_b = f32::INFINITY;
2216 let mut max_r = f32::NEG_INFINITY;
2217 let mut max_g = f32::NEG_INFINITY;
2218 let mut max_b = f32::NEG_INFINITY;
2219 for dy in -1i32..=1 {
2220 for dx in -1i32..=1 {
2221 let sample = current.sample_clamped(x as i32 + dx, y as i32 + dy);
2222 min_r = min_r.min(sample.r);
2223 min_g = min_g.min(sample.g);
2224 min_b = min_b.min(sample.b);
2225 max_r = max_r.max(sample.r);
2226 max_g = max_g.max(sample.g);
2227 max_b = max_b.max(sample.b);
2228 }
2229 }
2230 Color::rgb(
2231 history.r.clamp(min_r, max_r),
2232 history.g.clamp(min_g, max_g),
2233 history.b.clamp(min_b, max_b),
2234 )
2235}
2236
2237fn smoothstep(low: f32, high: f32, value: f32) -> f32 {
2238 let span = (high - low).max(1e-6);
2239 let t = ((value - low) / span).clamp(0.0, 1.0);
2240 t * t * (3.0 - 2.0 * t)
2241}
2242
2243fn residual_field(current: &ImageFrame, history: &ImageFrame) -> ScalarField {
2244 let mut field = ScalarField::new(current.width(), current.height());
2245 for y in 0..current.height() {
2246 for x in 0..current.width() {
2247 field.set(x, y, (current.get(x, y).abs_diff(history.get(x, y)) / 0.25).clamp(0.0, 1.0));
2248 }
2249 }
2250 field
2251}
2252
2253fn derive_instability_mask(
2254 outputs: &HostSupervisionOutputs,
2255 current: &ImageFrame,
2256 history: &ImageFrame,
2257) -> Vec<bool> {
2258 let residual = residual_field(current, history);
2259 let mut mask = vec![false; current.width() * current.height()];
2260 for y in 0..current.height() {
2261 for x in 0..current.width() {
2262 let index = y * current.width() + x;
2263 mask[index] = outputs.intervention.get(x, y) > 0.45
2264 || outputs.trust.get(x, y) < 0.35
2265 || residual.get(x, y) > 0.35;
2266 }
2267 }
2268 mask
2269}
2270
2271fn overlay_mask(frame: &ImageFrame, mask: &[bool], overlay: Color, strength: f32) -> ImageFrame {
2272 let mut output = frame.clone();
2273 for y in 0..frame.height() {
2274 for x in 0..frame.width() {
2275 let index = y * frame.width() + x;
2276 if mask[index] {
2277 output.set(x, y, frame.get(x, y).lerp(overlay, strength));
2278 }
2279 }
2280 }
2281 output
2282}
2283
2284fn constant_field(width: usize, height: usize, value: f32) -> ScalarField {
2285 ScalarField::from_values(width, height, vec![value; width * height])
2286}
2287
2288fn resolve_path(base_dir: &Path, relative_or_absolute: &str) -> PathBuf {
2289 let candidate = Path::new(relative_or_absolute);
2290 if candidate.is_absolute() {
2291 candidate.to_path_buf()
2292 } else {
2293 base_dir.join(candidate)
2294 }
2295}
2296
2297fn relative_path_string(path: &Path, base_dir: &Path) -> String {
2298 path.strip_prefix(base_dir)
2299 .unwrap_or(path)
2300 .to_string_lossy()
2301 .replace('\\', "/")
2302}
2303
2304fn write_canonical_metric_sheet(
2305 path: &Path,
2306 capture_summaries: &[CaptureSummaryRecord],
2307) -> Result<()> {
2308 let mut markdown = String::new();
2309 let _ = writeln!(markdown, "# Canonical Metric Sheet");
2310 let _ = writeln!(markdown);
2311 let _ = writeln!(markdown, "{ROI_CONTRACT_STATEMENT}");
2312 let _ = writeln!(markdown);
2313 let _ = writeln!(
2314 markdown,
2315 "Strong baseline: `strong_heuristic` (named strong heuristic clamp). Canonical baseline: `{}`.",
2316 ROI_CONTRACT_BASELINE_METHOD_ID
2317 );
2318 let _ = writeln!(markdown);
2319 let _ = writeln!(
2320 markdown,
2321 "| Capture set | Metric | Baseline | Strong heuristic | DSFB | DSFB + heuristic | Winner |"
2322 );
2323 let _ = writeln!(markdown, "| --- | --- | ---: | ---: | ---: | ---: | --- |");
2324 for capture in capture_summaries {
2325 let _ = writeln!(
2326 markdown,
2327 "| {} | ROI MAE | {:.5} | {:.5} | {:.5} | {:.5} | {} |",
2328 capture.capture_label,
2329 capture.fixed_alpha_roi_mae,
2330 capture.strong_heuristic_roi_mae,
2331 capture.dsfb_roi_mae,
2332 capture.dsfb_plus_heuristic_roi_mae,
2333 winner_label(
2334 capture.fixed_alpha_roi_mae,
2335 capture.strong_heuristic_roi_mae,
2336 capture.dsfb_roi_mae,
2337 Some(capture.dsfb_plus_heuristic_roi_mae),
2338 )
2339 );
2340 let _ = writeln!(
2341 markdown,
2342 "| {} | Full-frame MAE | {:.5} | {:.5} | {:.5} | {:.5} | {} |",
2343 capture.capture_label,
2344 capture.fixed_alpha_full_frame_mae,
2345 capture.strong_heuristic_full_frame_mae,
2346 capture.dsfb_full_frame_mae,
2347 capture.dsfb_plus_heuristic_full_frame_mae,
2348 winner_label(
2349 capture.fixed_alpha_full_frame_mae,
2350 capture.strong_heuristic_full_frame_mae,
2351 capture.dsfb_full_frame_mae,
2352 Some(capture.dsfb_plus_heuristic_full_frame_mae),
2353 )
2354 );
2355 let _ = writeln!(
2356 markdown,
2357 "| {} | Max error | {:.5} | {:.5} | {:.5} | {:.5} | {} |",
2358 capture.capture_label,
2359 capture.fixed_alpha_max_error,
2360 capture.strong_heuristic_max_error,
2361 capture.dsfb_max_error,
2362 capture.dsfb_plus_heuristic_max_error,
2363 winner_label(
2364 capture.fixed_alpha_max_error,
2365 capture.strong_heuristic_max_error,
2366 capture.dsfb_max_error,
2367 Some(capture.dsfb_plus_heuristic_max_error),
2368 )
2369 );
2370 let _ = writeln!(
2371 markdown,
2372 "| {} | ROI coverage | {:.2}% | {:.2}% | {:.2}% | {:.2}% | fixed ROI mask |",
2373 capture.capture_label,
2374 capture.roi_coverage * 100.0,
2375 capture.roi_coverage * 100.0,
2376 capture.roi_coverage * 100.0,
2377 capture.roi_coverage * 100.0,
2378 );
2379 }
2380 fs::write(path, markdown)?;
2381 Ok(())
2382}
2383
2384fn write_aggregation_summary(path: &Path, capture_summaries: &[CaptureSummaryRecord]) -> Result<()> {
2385 let mut markdown = String::new();
2386 let _ = writeln!(markdown, "# Aggregation Summary");
2387 let _ = writeln!(markdown);
2388 let _ = writeln!(markdown, "{ROI_CONTRACT_STATEMENT}");
2389 let _ = writeln!(markdown);
2390 let _ = writeln!(
2391 markdown,
2392 "Real capture count in this run: `{}`. Mean ± std claims require at least `{}` real captures under unchanged code and parameters.",
2393 capture_summaries.len(),
2394 ROI_AGGREGATION_MIN_CAPTURES
2395 );
2396 let _ = writeln!(markdown);
2397 if capture_summaries.len() < ROI_AGGREGATION_MIN_CAPTURES {
2398 let _ = writeln!(
2399 markdown,
2400 "Status: blocked by missing real captures. The crate-local canonical path currently has fewer than {} real Unreal-native captures, so no distribution claim is emitted.",
2401 ROI_AGGREGATION_MIN_CAPTURES
2402 );
2403 } else {
2404 let fixed = mean_and_std(
2405 &capture_summaries
2406 .iter()
2407 .map(|capture| capture.fixed_alpha_roi_mae)
2408 .collect::<Vec<_>>(),
2409 );
2410 let strong = mean_and_std(
2411 &capture_summaries
2412 .iter()
2413 .map(|capture| capture.strong_heuristic_roi_mae)
2414 .collect::<Vec<_>>(),
2415 );
2416 let dsfb = mean_and_std(
2417 &capture_summaries
2418 .iter()
2419 .map(|capture| capture.dsfb_roi_mae)
2420 .collect::<Vec<_>>(),
2421 );
2422 let hybrid = mean_and_std(
2423 &capture_summaries
2424 .iter()
2425 .map(|capture| capture.dsfb_plus_heuristic_roi_mae)
2426 .collect::<Vec<_>>(),
2427 );
2428 let _ = writeln!(
2429 markdown,
2430 "| Metric | Baseline mean ± std | Strong heuristic mean ± std | DSFB mean ± std | DSFB + heuristic mean ± std | Winner |"
2431 );
2432 let _ = writeln!(markdown, "| --- | ---: | ---: | ---: | ---: | --- |");
2433 let _ = writeln!(
2434 markdown,
2435 "| ROI MAE | {:.5} ± {:.5} | {:.5} ± {:.5} | {:.5} ± {:.5} | {:.5} ± {:.5} | {} |",
2436 fixed.0,
2437 fixed.1,
2438 strong.0,
2439 strong.1,
2440 dsfb.0,
2441 dsfb.1,
2442 hybrid.0,
2443 hybrid.1,
2444 winner_label(fixed.0, strong.0, dsfb.0, Some(hybrid.0))
2445 );
2446 }
2447 fs::write(path, markdown)?;
2448 Ok(())
2449}
2450
2451fn validate_unreal_native_artifacts(
2452 run_dir: &Path,
2453 frames: &[FrameArtifacts],
2454 comparison_summary_path: &Path,
2455 canonical_metric_sheet_path: &Path,
2456 aggregation_summary_path: &Path,
2457) -> Result<()> {
2458 let current_status_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("CURRENT_STATUS.md");
2459 if !current_status_path.exists() {
2460 return Err(Error::Message(format!(
2461 "required crate status file is missing: {}",
2462 current_status_path.display()
2463 )));
2464 }
2465 let current_status = fs::read_to_string(¤t_status_path)?;
2466 for required in [
2467 CANONICAL_HEADLINE_STATEMENT,
2468 PURE_DSFB_LIMITATION_STATEMENT,
2469 ROI_HONESTY_STATEMENT,
2470 ] {
2471 if !current_status.contains(required) {
2472 return Err(Error::Message(format!(
2473 "crate status file {} is missing required canonical statement: {}",
2474 current_status_path.display(),
2475 required
2476 )));
2477 }
2478 }
2479
2480 let comparison_summary = fs::read_to_string(comparison_summary_path)?;
2481 if !comparison_summary.contains(ROI_CONTRACT_STATEMENT) {
2482 return Err(Error::Message(format!(
2483 "comparison summary {} is missing the ROI contract statement",
2484 comparison_summary_path.display()
2485 )));
2486 }
2487
2488 for report_name in [
2489 "demo_a_external_report.md",
2490 "demo_b_external_report.md",
2491 "external_validation_report.md",
2492 ] {
2493 let report_path = run_dir.join(report_name);
2494 let report = fs::read_to_string(&report_path)?;
2495 if !report.contains(ROI_CONTRACT_STATEMENT) {
2496 return Err(Error::Message(format!(
2497 "report {} is missing the ROI contract statement",
2498 report_path.display()
2499 )));
2500 }
2501 }
2502
2503 let canonical_metric_sheet = fs::read_to_string(canonical_metric_sheet_path)?;
2504 if !canonical_metric_sheet.contains("Strong heuristic") {
2505 return Err(Error::Message(format!(
2506 "canonical metric sheet {} is missing the strong baseline column",
2507 canonical_metric_sheet_path.display()
2508 )));
2509 }
2510 if !canonical_metric_sheet.contains("DSFB + heuristic") {
2511 return Err(Error::Message(format!(
2512 "canonical metric sheet {} is missing the DSFB + heuristic column",
2513 canonical_metric_sheet_path.display()
2514 )));
2515 }
2516 if !canonical_metric_sheet.contains(ROI_CONTRACT_STATEMENT) {
2517 return Err(Error::Message(format!(
2518 "canonical metric sheet {} is missing the ROI contract statement",
2519 canonical_metric_sheet_path.display()
2520 )));
2521 }
2522
2523 let aggregation_summary = fs::read_to_string(aggregation_summary_path)?;
2524 if frames.len() < ROI_AGGREGATION_MIN_CAPTURES
2525 && !aggregation_summary.contains("blocked by missing real captures")
2526 {
2527 return Err(Error::Message(format!(
2528 "aggregation summary {} emitted distribution output without enough real captures",
2529 aggregation_summary_path.display()
2530 )));
2531 }
2532
2533 for relative_path in [
2534 "figures/trust_histogram.svg",
2535 "figures/trust_vs_error.svg",
2536 "figures/trust_conditioned_error_map.png",
2537 ] {
2538 let artifact_path = run_dir.join(relative_path);
2539 if !artifact_path.exists() {
2540 return Err(Error::Message(format!(
2541 "required trust artifact is missing: {}",
2542 artifact_path.display()
2543 )));
2544 }
2545 }
2546 if frames.len() >= 2 {
2547 for relative_path in [
2548 "figures/trust_temporal_trajectory.svg",
2549 "figures/trust_temporal_trajectory.json",
2550 ] {
2551 let artifact_path = run_dir.join(relative_path);
2552 if !artifact_path.exists() {
2553 return Err(Error::Message(format!(
2554 "required temporal trust artifact is missing: {}",
2555 artifact_path.display()
2556 )));
2557 }
2558 }
2559 }
2560
2561 for frame in frames {
2562 if !frame.roi_mask_path.exists() {
2563 return Err(Error::Message(format!(
2564 "required ROI mask artifact is missing: {}",
2565 frame.roi_mask_path.display()
2566 )));
2567 }
2568 }
2569
2570 Ok(())
2571}
2572
2573fn winner_label(baseline: f32, strong: f32, dsfb: f32, hybrid: Option<f32>) -> &'static str {
2574 let best = hybrid
2575 .map(|hybrid| baseline.min(strong).min(dsfb).min(hybrid))
2576 .unwrap_or_else(|| baseline.min(strong).min(dsfb));
2577 if (best - baseline).abs() <= 1e-6 {
2578 "Baseline"
2579 } else if (best - strong).abs() <= 1e-6 {
2580 "Strong heuristic"
2581 } else if hybrid.is_some_and(|value| (best - value).abs() <= 1e-6) {
2582 "DSFB + heuristic"
2583 } else {
2584 "DSFB"
2585 }
2586}
2587
2588fn mean_and_std(values: &[f32]) -> (f32, f32) {
2589 if values.is_empty() {
2590 return (0.0, 0.0);
2591 }
2592 let mean = values.iter().copied().sum::<f32>() / values.len() as f32;
2593 let variance = values
2594 .iter()
2595 .map(|value| {
2596 let delta = *value - mean;
2597 delta * delta
2598 })
2599 .sum::<f32>()
2600 / values.len() as f32;
2601 (mean, variance.sqrt())
2602}
2603
2604fn git_commit_hash() -> Option<String> {
2605 Command::new("git")
2606 .arg("rev-parse")
2607 .arg("HEAD")
2608 .output()
2609 .ok()
2610 .filter(|output| output.status.success())
2611 .and_then(|output| String::from_utf8(output.stdout).ok())
2612 .map(|value| value.trim().to_string())
2613 .filter(|value| !value.is_empty())
2614}
2615
2616fn read_json<T: for<'de> Deserialize<'de>>(path: &Path) -> Result<T> {
2617 let text = fs::read_to_string(path)?;
2618 Ok(serde_json::from_str(&text)?)
2619}
2620
2621fn write_json<T: Serialize>(path: &Path, value: &T) -> Result<()> {
2622 fs::write(path, serde_json::to_string_pretty(value)?)?;
2623 Ok(())
2624}
2625
2626#[derive(Clone, Debug, Serialize)]
2627struct ScalarJson {
2628 width: usize,
2629 height: usize,
2630 data: Vec<f32>,
2631}
2632
2633#[derive(Clone, Debug, Serialize)]
2634struct Vec2Json {
2635 width: usize,
2636 height: usize,
2637 data: Vec<[f32; 2]>,
2638}
2639
2640#[derive(Clone, Debug, Serialize)]
2641struct Vec3Json {
2642 width: usize,
2643 height: usize,
2644 data: Vec<[f32; 3]>,
2645}
2646
2647#[derive(Clone, Debug, Serialize)]
2648struct BoolJson {
2649 width: usize,
2650 height: usize,
2651 data: Vec<bool>,
2652}
2653
2654fn heatmap_blue(value: f32) -> [u8; 4] {
2655 let v = (value.clamp(0.0, 1.0) * 255.0).round() as u8;
2656 [v / 4, v / 2, 255, 255]
2657}
2658
2659fn heatmap_orange(value: f32) -> [u8; 4] {
2660 let v = (value.clamp(0.0, 1.0) * 255.0).round() as u8;
2661 [255, v, 32, 255]
2662}
2663
2664fn heatmap_red(value: f32) -> [u8; 4] {
2665 let v = (value.clamp(0.0, 1.0) * 255.0).round() as u8;
2666 [255, 24, v / 2, 255]
2667}
2668
2669fn heatmap_residual(value: f32) -> [u8; 4] {
2670 let v = (value.clamp(0.0, 1.0) * 255.0).round() as u8;
2671 [255, v / 3, 16, 255]
2672}