1use std::fmt::Write as _;
2use std::fs;
3use std::path::{Path, PathBuf};
4use std::process::Command;
5
6use serde::{Deserialize, Serialize};
7
8use crate::config::DemoConfig;
9use crate::error::{Error, Result};
10use crate::external::{
11 load_external_capture_bundle, run_external_import_from_manifest, ExternalCaptureBundle,
12 ExternalHandoffMetrics, ExternalLoadedCapture, OwnedHostTemporalInputs,
13 NO_REAL_EXTERNAL_DATA_PROVIDED,
14};
15use crate::frame::{
16 mean_abs_error, mean_abs_error_over_mask, save_scalar_field_png, Color, ImageFrame, ScalarField,
17};
18use crate::gpu::try_execute_host_minimum_kernel;
19use crate::host::{
20 default_host_realistic_profile, supervise_temporal_reuse,
21};
22use crate::parameters::SmoothstepThreshold;
23use crate::report::EXPERIMENT_SENTENCE;
24use crate::sampling::{
25 build_count_field, combine_fields, gradient_field, guided_allocation, invert_trust,
26 local_contrast_field, mean_count_over_mask, AllocationPolicyId, BudgetCurve, BudgetCurvePoint,
27 DemoBPolicyMetrics,
28};
29use crate::scene::{MotionVector, Normal3};
30pub const ROI_CONTRACT_ALPHA: f32 = 0.15;
31pub const ROI_CONTRACT_BASELINE_METHOD_ID: &str = "fixed_alpha";
32pub const ROI_CONTRACT_SOURCE: &str = "fixed_alpha_local_contrast_0p15";
33pub const ROI_CONTRACT_STATEMENT: &str =
34 "ROI is defined as pixels where baseline error exceeds 15% of local contrast. The mask is computed once from the baseline and held fixed across all methods. DSFB does not influence ROI selection.";
35pub const CANONICAL_HEADLINE_STATEMENT: &str =
36 "DSFB improves strong temporal heuristics via structural supervision.";
37pub const PURE_DSFB_LIMITATION_STATEMENT: &str =
38 "DSFB alone does not outperform strong heuristic baselines in the current evaluation.";
39pub const ROI_HONESTY_STATEMENT: &str =
40 "The ROI definition captures approximately 50% of the frame under the fixed baseline-relative threshold, making the metric closer to a global structural error measure than a sparse artifact mask.";
41pub const ROI_AGGREGATION_MIN_CAPTURES: usize = 3;
42
43#[derive(Clone, Debug)]
44pub struct ExternalValidationArtifacts {
45 pub replay_report_path: PathBuf,
46 pub handoff_report_path: PathBuf,
47 pub validation_report_path: PathBuf,
48 pub gpu_report_path: PathBuf,
49 pub gpu_metrics_path: PathBuf,
50 pub demo_a_report_path: PathBuf,
51 pub demo_b_report_path: PathBuf,
52 pub demo_b_metrics_path: PathBuf,
53 pub scaling_report_path: PathBuf,
54 pub scaling_metrics_path: PathBuf,
55 pub memory_bandwidth_report_path: PathBuf,
56 pub integration_scaling_report_path: PathBuf,
57 pub resolved_manifest_path: PathBuf,
58 pub figures_dir: PathBuf,
59 pub handoff_metrics: ExternalHandoffMetrics,
60}
61
62#[derive(Clone, Debug, Serialize, Deserialize)]
63pub struct ExternalGpuCaptureMetrics {
64 pub capture_label: String,
65 pub measured_gpu: bool,
66 pub adapter: Option<String>,
67 pub backend: Option<String>,
68 pub resolution: [usize; 2],
69 pub kernel: String,
70 pub total_ms: Option<f64>,
71 pub dispatch_ms: Option<f64>,
72 pub readback_ms: Option<f64>,
73 pub mean_abs_trust_delta_vs_cpu: Option<f32>,
74 pub mean_abs_alpha_delta_vs_cpu: Option<f32>,
75 pub mean_abs_intervention_delta_vs_cpu: Option<f32>,
76 pub notes: Vec<String>,
77}
78
79#[derive(Clone, Debug, Serialize, Deserialize)]
80pub struct ExternalGpuMetrics {
81 pub measurement_kind: String,
82 pub measured_gpu: bool,
83 pub actual_real_external_data: bool,
84 pub kernel: String,
85 pub captures: Vec<ExternalGpuCaptureMetrics>,
86 pub notes: Vec<String>,
87}
88
89#[derive(Clone, Debug, Serialize, Deserialize)]
90pub struct ExternalDemoAMethodMetrics {
91 pub method_id: String,
92 pub label: String,
93 pub metric_source: String,
94 pub overall_mae: f32,
95 pub roi_mae: f32,
96 pub non_roi_mae: f32,
97 pub max_error: f32,
98 pub temporal_error_accumulation: f32,
99 pub intervention_rate: f32,
100}
101
102#[derive(Clone, Debug, Serialize, Deserialize)]
103pub struct ExternalDemoACaptureMetrics {
104 pub capture_label: String,
105 pub roi_source: String,
106 pub roi_pixels: usize,
107 pub total_pixels: usize,
108 pub roi_coverage: f32,
109 pub roi_statement: String,
110 pub baseline_method_id: String,
111 pub reference_source: String,
112 pub ground_truth_available: bool,
113 pub metric_source: String,
114 pub methods: Vec<ExternalDemoAMethodMetrics>,
115}
116
117#[derive(Clone, Debug, Serialize, Deserialize)]
118pub struct ExternalDemoAMetrics {
119 pub real_external_data_provided: bool,
120 pub no_real_external_data_provided: bool,
121 pub captures: Vec<ExternalDemoACaptureMetrics>,
122 pub notes: Vec<String>,
123}
124
125#[derive(Clone, Debug, Serialize, Deserialize)]
126pub struct ExternalDemoBCaptureMetrics {
127 pub capture_label: String,
128 pub regime: String,
129 pub metric_source: String,
130 pub roi_source: String,
131 pub roi_pixels: usize,
132 pub total_pixels: usize,
133 pub roi_coverage: f32,
134 pub roi_statement: String,
135 pub baseline_method_id: String,
136 pub reference_source: String,
137 pub ground_truth_available: bool,
138 pub budget_total_samples: usize,
139 pub fixed_budget_equal: bool,
140 pub policies: Vec<DemoBPolicyMetrics>,
141 pub budget_curves: Vec<BudgetCurve>,
142}
143
144#[derive(Clone, Debug, Serialize, Deserialize)]
145pub struct ExternalDemoBMetrics {
146 pub real_external_data_provided: bool,
147 pub no_real_external_data_provided: bool,
148 pub captures: Vec<ExternalDemoBCaptureMetrics>,
149 pub notes: Vec<String>,
150}
151
152#[derive(Clone, Debug, Serialize, Deserialize)]
153pub struct ExternalScalingEntry {
154 pub label: String,
155 pub source: String,
156 pub width: usize,
157 pub height: usize,
158 pub attempted: bool,
159 pub measured_gpu: bool,
160 pub total_ms: Option<f64>,
161 pub dispatch_ms: Option<f64>,
162 pub readback_ms: Option<f64>,
163 pub ms_per_megapixel: Option<f64>,
164 pub scaling_ratio_vs_native: Option<f64>,
165 pub pixel_ratio_vs_native: f64,
166 pub approximately_linear: Option<bool>,
167 pub unavailable_reason: Option<String>,
168}
169
170#[derive(Clone, Debug, Serialize, Deserialize)]
171pub struct ExternalCoverageSummary {
172 pub realism_stress_case: bool,
173 pub larger_roi_case: bool,
174 pub mixed_regime_case: bool,
175 pub coverage_status: String,
176 pub missing: Vec<String>,
177}
178
179#[derive(Clone, Debug, Serialize, Deserialize)]
180pub struct ExternalScalingMetrics {
181 pub measurement_kind: String,
182 pub kernel: String,
183 pub native_capture_label: String,
184 pub native_resolution: [usize; 2],
185 pub attempted_1080p: bool,
186 pub attempted_4k: bool,
187 pub entries: Vec<ExternalScalingEntry>,
188 pub coverage: ExternalCoverageSummary,
189 pub notes: Vec<String>,
190}
191
192#[derive(Clone, Debug, Serialize)]
193struct TemporalTrustTrajectoryPoint {
194 capture_label: String,
195 frame_index: usize,
196 mean_trust: f32,
197 mean_alpha: f32,
198 intervention_rate: f32,
199 roi_coverage: f32,
200 dsfb_roi_mae: f32,
201 hybrid_roi_mae: f32,
202}
203
204#[derive(Clone, Debug, Serialize)]
205struct TemporalTrustTrajectoryReport {
206 onset_capture_label: String,
207 peak_roi_capture_label: String,
208 recovery_capture_label: String,
209 points: Vec<TemporalTrustTrajectoryPoint>,
210}
211
212pub fn run_external_validation_bundle(
213 config: &DemoConfig,
214 manifest_path: &Path,
215 output_dir: &Path,
216) -> Result<ExternalValidationArtifacts> {
217 fs::create_dir_all(output_dir)?;
218
219 let import = run_external_import_from_manifest(config, manifest_path, output_dir)?;
220 let replay_metrics_path = output_dir.join("replay_metrics.json");
221 fs::write(
222 &replay_metrics_path,
223 serde_json::to_string_pretty(&import.metrics)?,
224 )?;
225 let bundle = load_external_capture_bundle(config, manifest_path, output_dir)?;
226 let figures_dir = output_dir.join("figures");
227 fs::create_dir_all(&figures_dir)?;
228
229 let gpu_metrics = run_external_gpu_metrics_safe(config, manifest_path, output_dir, &bundle)?;
230 let gpu_metrics_path = output_dir.join("gpu_execution_metrics.json");
231 fs::write(
232 &gpu_metrics_path,
233 serde_json::to_string_pretty(&gpu_metrics)?,
234 )?;
235 let gpu_report_path = output_dir.join("gpu_execution_report.md");
236 write_gpu_external_report(&gpu_report_path, &gpu_metrics, &bundle)?;
237 fs::copy(
238 &gpu_metrics_path,
239 output_dir.join("gpu_external_metrics.json"),
240 )?;
241 fs::copy(&gpu_report_path, output_dir.join("gpu_external_report.md"))?;
242
243 let demo_a_metrics = run_demo_a_external_metrics(config, &bundle, &figures_dir)?;
244 let demo_a_metrics_path = output_dir.join("demo_a_external_metrics.json");
245 fs::write(
246 &demo_a_metrics_path,
247 serde_json::to_string_pretty(&demo_a_metrics)?,
248 )?;
249 let demo_a_report_path = output_dir.join("demo_a_external_report.md");
250 write_demo_a_external_report(&demo_a_report_path, &demo_a_metrics, &bundle)?;
251
252 let demo_b_metrics = run_demo_b_external_metrics(config, &bundle, &figures_dir)?;
253 let demo_b_metrics_path = output_dir.join("demo_b_external_metrics.json");
254 fs::write(
255 &demo_b_metrics_path,
256 serde_json::to_string_pretty(&demo_b_metrics)?,
257 )?;
258 let demo_b_report_path = output_dir.join("demo_b_external_report.md");
259 write_demo_b_external_report(&demo_b_report_path, &demo_b_metrics, &bundle)?;
260
261 let scaling_metrics = run_external_scaling_study(
262 config,
263 manifest_path,
264 output_dir,
265 &bundle,
266 &gpu_metrics,
267 &demo_a_metrics,
268 &demo_b_metrics,
269 )?;
270 let scaling_metrics_path = output_dir.join("scaling_metrics.json");
271 fs::write(
272 &scaling_metrics_path,
273 serde_json::to_string_pretty(&scaling_metrics)?,
274 )?;
275 let scaling_report_path = output_dir.join("scaling_report.md");
276 write_external_scaling_report(&scaling_report_path, &scaling_metrics, &bundle)?;
277 let memory_bandwidth_report_path = output_dir.join("memory_bandwidth_report.md");
278 write_memory_bandwidth_report(&memory_bandwidth_report_path, &scaling_metrics)?;
279 let integration_scaling_report_path = output_dir.join("integration_scaling_report.md");
280 write_integration_scaling_report(&integration_scaling_report_path, &scaling_metrics, &bundle)?;
281
282 let validation_report_path = output_dir.join("external_validation_report.md");
283 write_external_validation_report(
284 &validation_report_path,
285 &bundle,
286 &import.metrics,
287 &gpu_metrics,
288 &demo_a_metrics,
289 &demo_b_metrics,
290 &scaling_metrics,
291 )?;
292 copy_representative_figure_aliases(output_dir, &figures_dir)?;
293
294 Ok(ExternalValidationArtifacts {
295 replay_report_path: import.report_path,
296 handoff_report_path: output_dir.join("external_handoff_report.md"),
297 validation_report_path,
298 gpu_report_path,
299 gpu_metrics_path,
300 demo_a_report_path,
301 demo_b_report_path,
302 demo_b_metrics_path,
303 scaling_report_path,
304 scaling_metrics_path,
305 memory_bandwidth_report_path,
306 integration_scaling_report_path,
307 resolved_manifest_path: import.resolved_manifest_path,
308 figures_dir,
309 handoff_metrics: import.metrics,
310 })
311}
312
313pub fn probe_external_gpu_only(
314 config: &DemoConfig,
315 manifest_path: &Path,
316 output_dir: &Path,
317 capture_label: Option<&str>,
318 scaled_resolution: Option<(usize, usize)>,
319) -> Result<PathBuf> {
320 fs::create_dir_all(output_dir)?;
321 let bundle = load_external_capture_bundle(config, manifest_path, output_dir)?;
322 let bundle = if capture_label.is_some() || scaled_resolution.is_some() {
323 build_gpu_probe_bundle(bundle, capture_label, scaled_resolution)?
324 } else {
325 bundle
326 };
327 let metrics = run_external_gpu_metrics(config, &bundle)?;
328 let path = output_dir.join("gpu_probe_metrics.json");
329 fs::write(&path, serde_json::to_string_pretty(&metrics)?)?;
330 Ok(path)
331}
332
333fn build_gpu_probe_bundle(
334 bundle: ExternalCaptureBundle,
335 capture_label: Option<&str>,
336 scaled_resolution: Option<(usize, usize)>,
337) -> Result<ExternalCaptureBundle> {
338 let capture = if let Some(label) = capture_label {
339 bundle
340 .captures
341 .iter()
342 .find(|capture| capture.label == label)
343 .cloned()
344 .ok_or_else(|| Error::Message(format!("capture `{label}` was missing from the bundle")))?
345 } else {
346 bundle
347 .captures
348 .first()
349 .cloned()
350 .ok_or_else(|| Error::Message("external capture bundle had no captures".to_string()))?
351 };
352 let capture = if let Some((width, height)) = scaled_resolution {
353 scale_external_capture(&capture, width, height)?
354 } else {
355 capture
356 };
357 Ok(ExternalCaptureBundle {
358 manifest: bundle.manifest,
359 captures: vec![capture],
360 real_external_data_provided: bundle.real_external_data_provided,
361 no_real_external_data_provided: bundle.no_real_external_data_provided,
362 })
363}
364
365fn run_external_gpu_metrics(
366 config: &DemoConfig,
367 bundle: &ExternalCaptureBundle,
368) -> Result<ExternalGpuMetrics> {
369 let profile =
370 default_host_realistic_profile(config.dsfb_alpha_range.min, config.dsfb_alpha_range.max);
371 let mut captures = Vec::with_capacity(bundle.captures.len());
372 let mut measured_gpu = false;
373
374 for capture in &bundle.captures {
375 let cpu = supervise_temporal_reuse(&capture.inputs.borrow(), &profile);
376 match try_execute_host_minimum_kernel(&capture.inputs, profile.parameters)? {
377 Some(gpu) => {
378 measured_gpu = true;
379 captures.push(ExternalGpuCaptureMetrics {
380 capture_label: capture.label.clone(),
381 measured_gpu: true,
382 adapter: Some(gpu.adapter_name),
383 backend: Some(gpu.backend),
384 resolution: [capture.inputs.width(), capture.inputs.height()],
385 kernel: "dsfb_host_minimum".to_string(),
386 total_ms: Some(gpu.total_ms),
387 dispatch_ms: Some(gpu.dispatch_ms),
388 readback_ms: Some(gpu.readback_ms),
389 mean_abs_trust_delta_vs_cpu: Some(mean_abs_delta(cpu.trust.values(), &gpu.trust)),
390 mean_abs_alpha_delta_vs_cpu: Some(mean_abs_delta(cpu.alpha.values(), &gpu.alpha)),
391 mean_abs_intervention_delta_vs_cpu: Some(mean_abs_delta(
392 cpu.intervention.values(),
393 &gpu.intervention,
394 )),
395 notes: vec![
396 "Measured on imported external buffers using the same minimum host-realistic kernel as the synthetic GPU study.".to_string(),
397 ],
398 });
399 }
400 None => {
401 captures.push(ExternalGpuCaptureMetrics {
402 capture_label: capture.label.clone(),
403 measured_gpu: false,
404 adapter: None,
405 backend: None,
406 resolution: [capture.inputs.width(), capture.inputs.height()],
407 kernel: "dsfb_host_minimum".to_string(),
408 total_ms: None,
409 dispatch_ms: None,
410 readback_ms: None,
411 mean_abs_trust_delta_vs_cpu: None,
412 mean_abs_alpha_delta_vs_cpu: None,
413 mean_abs_intervention_delta_vs_cpu: None,
414 notes: vec![
415 "The GPU path is implemented, but no usable adapter was available in the current environment for this imported capture.".to_string(),
416 ],
417 });
418 }
419 }
420 }
421
422 Ok(ExternalGpuMetrics {
423 measurement_kind: if measured_gpu {
424 "measured_gpu".to_string()
425 } else {
426 "gpu_path_unmeasured".to_string()
427 },
428 measured_gpu,
429 actual_real_external_data: bundle.real_external_data_provided,
430 kernel: "dsfb_host_minimum".to_string(),
431 captures,
432 notes: vec![
433 "This report covers imported external buffers rather than the synthetic internal suite.".to_string(),
434 if bundle.no_real_external_data_provided {
435 NO_REAL_EXTERNAL_DATA_PROVIDED.to_string()
436 } else {
437 "Real external data was supplied through the file schema.".to_string()
438 },
439 ],
440 })
441}
442
443fn run_external_gpu_metrics_safe(
444 config: &DemoConfig,
445 manifest_path: &Path,
446 output_dir: &Path,
447 bundle: &ExternalCaptureBundle,
448) -> Result<ExternalGpuMetrics> {
449 if let Some(metrics) = try_gpu_subprocess_probe(manifest_path, output_dir, bundle)? {
450 return Ok(metrics);
451 }
452 run_external_gpu_metrics(config, bundle)
453}
454
455fn try_gpu_subprocess_probe(
456 manifest_path: &Path,
457 output_dir: &Path,
458 bundle: &ExternalCaptureBundle,
459) -> Result<Option<ExternalGpuMetrics>> {
460 let Some(executable) = gpu_probe_executable()? else {
461 return Ok(None);
462 };
463
464 let probe_dir = output_dir.join("gpu_probe");
465 fs::create_dir_all(&probe_dir)?;
466 let status = Command::new(&executable)
467 .arg("probe-external-gpu")
468 .arg("--manifest")
469 .arg(manifest_path)
470 .arg("--output")
471 .arg(&probe_dir)
472 .status()?;
473 let metrics_path = probe_dir.join("gpu_probe_metrics.json");
474 if status.success() && metrics_path.exists() {
475 let metrics: ExternalGpuMetrics = serde_json::from_str(&fs::read_to_string(metrics_path)?)?;
476 return Ok(Some(metrics));
477 }
478
479 let crash_note = if let Some(code) = status.code() {
480 format!("GPU subprocess attempt exited with status {code}")
481 } else {
482 "GPU subprocess attempt terminated by signal".to_string()
483 };
484 Ok(Some(unmeasured_gpu_metrics(bundle, vec![
485 "GPU execution was attempted in a subprocess because some drivers abort the process on shader JIT failure.".to_string(),
486 crash_note,
487 ])))
488}
489
490fn unmeasured_gpu_metrics(bundle: &ExternalCaptureBundle, notes: Vec<String>) -> ExternalGpuMetrics {
491 ExternalGpuMetrics {
492 measurement_kind: "gpu_probe_failed".to_string(),
493 measured_gpu: false,
494 actual_real_external_data: bundle.real_external_data_provided,
495 kernel: "dsfb_host_minimum".to_string(),
496 captures: bundle
497 .captures
498 .iter()
499 .map(|capture| ExternalGpuCaptureMetrics {
500 capture_label: capture.label.clone(),
501 measured_gpu: false,
502 adapter: None,
503 backend: None,
504 resolution: [capture.inputs.width(), capture.inputs.height()],
505 kernel: "dsfb_host_minimum".to_string(),
506 total_ms: None,
507 dispatch_ms: None,
508 readback_ms: None,
509 mean_abs_trust_delta_vs_cpu: None,
510 mean_abs_alpha_delta_vs_cpu: None,
511 mean_abs_intervention_delta_vs_cpu: None,
512 notes: vec![
513 "The GPU path was attempted, but the driver/runtime could not complete the shader execution path safely in-process.".to_string(),
514 ],
515 })
516 .collect(),
517 notes,
518 }
519}
520
521fn run_demo_a_external_metrics(
522 config: &DemoConfig,
523 bundle: &ExternalCaptureBundle,
524 figures_dir: &Path,
525) -> Result<ExternalDemoAMetrics> {
526 let profile =
527 default_host_realistic_profile(config.dsfb_alpha_range.min, config.dsfb_alpha_range.max);
528 let mut captures = Vec::with_capacity(bundle.captures.len());
529 let mut trajectory_points = Vec::with_capacity(bundle.captures.len());
530
531 for (capture_index, capture) in bundle.captures.iter().enumerate() {
532 let outputs = supervise_temporal_reuse(&capture.inputs.borrow(), &profile);
533 let fixed_alpha_field = constant_field(
534 capture.inputs.width(),
535 capture.inputs.height(),
536 config.baseline.fixed_alpha,
537 );
538 let fixed_resolved = resolve_with_alpha(
539 &capture.inputs.reprojected_history,
540 &capture.inputs.current_color,
541 &fixed_alpha_field,
542 );
543 let (reference_frame, reference_source, metric_source) =
544 capture_reference_frame_and_metric_source(capture);
545 let (roi_mask, roi_source, roi_coverage) =
546 roi_mask_for_capture(capture, &fixed_resolved, reference_frame);
547 let dsfb_resolved = resolve_with_alpha(
548 &capture.inputs.reprojected_history,
549 &capture.inputs.current_color,
550 &outputs.alpha,
551 );
552 let (strong_resolved, strong_alpha, strong_response) =
553 run_external_strong_heuristic(config, capture);
554 let (hybrid_resolved, _hybrid_alpha, hybrid_response) =
555 run_external_dsfb_plus_strong_heuristic(
556 capture,
557 &outputs.alpha,
558 &outputs.intervention,
559 &strong_alpha,
560 &strong_response,
561 );
562 let fixed_response = ScalarField::new(capture.inputs.width(), capture.inputs.height());
563
564 let methods = vec![
565 build_demo_a_method_metrics(
566 "fixed_alpha",
567 "Fixed alpha baseline",
568 &fixed_resolved,
569 reference_frame,
570 metric_source,
571 &roi_mask,
572 &fixed_response,
573 ),
574 build_demo_a_method_metrics(
575 "strong_heuristic",
576 "Strong heuristic clamp",
577 &strong_resolved,
578 reference_frame,
579 metric_source,
580 &roi_mask,
581 &strong_response,
582 ),
583 build_demo_a_method_metrics(
584 "dsfb_host_minimum",
585 "DSFB host minimum",
586 &dsfb_resolved,
587 reference_frame,
588 metric_source,
589 &roi_mask,
590 &outputs.intervention,
591 ),
592 build_demo_a_method_metrics(
593 "dsfb_plus_strong_heuristic",
594 "DSFB + strong heuristic",
595 &hybrid_resolved,
596 reference_frame,
597 metric_source,
598 &roi_mask,
599 &hybrid_response,
600 ),
601 ];
602 let dsfb_roi_mae = methods
603 .iter()
604 .find(|method| method.method_id == "dsfb_host_minimum")
605 .map(|method| method.roi_mae)
606 .ok_or_else(|| {
607 Error::Message(format!(
608 "capture `{}` did not retain dsfb_host_minimum metrics",
609 capture.label
610 ))
611 })?;
612 let hybrid_roi_mae = methods
613 .iter()
614 .find(|method| method.method_id == "dsfb_plus_strong_heuristic")
615 .map(|method| method.roi_mae)
616 .ok_or_else(|| {
617 Error::Message(format!(
618 "capture `{}` did not retain dsfb_plus_strong_heuristic metrics",
619 capture.label
620 ))
621 })?;
622
623 if capture_index == 0 {
624 capture
625 .inputs
626 .current_color
627 .save_png(&figures_dir.join("current_color.png"))?;
628 capture
629 .inputs
630 .reprojected_history
631 .save_png(&figures_dir.join("reprojected_history.png"))?;
632 fixed_resolved.save_png(&figures_dir.join("demo_a_fixed_alpha.png"))?;
633 strong_resolved.save_png(&figures_dir.join("demo_a_strong_heuristic.png"))?;
634 dsfb_resolved.save_png(&figures_dir.join("demo_a_dsfb.png"))?;
635 hybrid_resolved.save_png(
636 &figures_dir.join("demo_a_dsfb_plus_strong_heuristic.png"),
637 )?;
638 save_scalar_field_png(
639 &outputs.trust,
640 &figures_dir.join("trust_map.png"),
641 heatmap_blue,
642 )?;
643 save_scalar_field_png(
644 &outputs.alpha,
645 &figures_dir.join("alpha_map.png"),
646 heatmap_orange,
647 )?;
648 save_scalar_field_png(
649 &outputs.intervention,
650 &figures_dir.join("intervention_map.png"),
651 heatmap_red,
652 )?;
653 overlay_roi_mask(&capture.inputs.current_color, &roi_mask)
654 .save_png(&figures_dir.join("roi_overlay.png"))?;
655 save_scalar_field_png(
656 &strong_alpha,
657 &figures_dir.join("strong_alpha_map.png"),
658 heatmap_orange,
659 )?;
660 let trust_error_field = absolute_error_field(&dsfb_resolved, reference_frame);
661 write_trust_histogram_figure(
662 &outputs.trust,
663 &figures_dir.join("trust_histogram.svg"),
664 )?;
665 write_trust_vs_error_figure(
666 &outputs.trust,
667 &trust_error_field,
668 metric_source,
669 &figures_dir.join("trust_vs_error.svg"),
670 )?;
671 save_trust_conditioned_error_map(
672 &outputs.trust,
673 &trust_error_field,
674 &figures_dir.join("trust_conditioned_error_map.png"),
675 )?;
676 }
677
678 trajectory_points.push(TemporalTrustTrajectoryPoint {
679 capture_label: capture.label.clone(),
680 frame_index: capture.metadata.frame_index,
681 mean_trust: outputs.trust.mean(),
682 mean_alpha: outputs.alpha.mean(),
683 intervention_rate: outputs.intervention.mean(),
684 roi_coverage,
685 dsfb_roi_mae,
686 hybrid_roi_mae,
687 });
688
689 captures.push(ExternalDemoACaptureMetrics {
690 capture_label: capture.label.clone(),
691 roi_source,
692 roi_pixels: roi_mask.iter().filter(|value| **value).count(),
693 total_pixels: capture.inputs.width() * capture.inputs.height(),
694 roi_coverage,
695 roi_statement: ROI_CONTRACT_STATEMENT.to_string(),
696 baseline_method_id: ROI_CONTRACT_BASELINE_METHOD_ID.to_string(),
697 reference_source: reference_source.to_string(),
698 ground_truth_available: capture.reference.is_some(),
699 metric_source: metric_source.to_string(),
700 methods,
701 });
702 }
703
704 let all_have_reference = captures.iter().all(|capture| capture.ground_truth_available);
705 let reference_note = if all_have_reference {
706 "The current bundle measures against exported `reference_color` on every capture; that reference is a higher-resolution Unreal proxy rather than a path-traced ground truth.".to_string()
707 } else {
708 "If no optional reference frame is supplied, the current frame is used as the explicit proxy reference for ROI and full-frame error.".to_string()
709 };
710
711 let metrics = ExternalDemoAMetrics {
712 real_external_data_provided: bundle.real_external_data_provided,
713 no_real_external_data_provided: bundle.no_real_external_data_provided,
714 captures,
715 notes: vec![
716 "Demo A external replay uses the same host-minimum supervisory logic as the internal suite.".to_string(),
717 ROI_CONTRACT_STATEMENT.to_string(),
718 reference_note,
719 ],
720 };
721
722 if trajectory_points.len() >= 2 {
723 write_temporal_trust_trajectory_outputs(&trajectory_points, figures_dir)?;
724 }
725
726 Ok(metrics)
727}
728
729fn run_demo_b_external_metrics(
730 config: &DemoConfig,
731 bundle: &ExternalCaptureBundle,
732 figures_dir: &Path,
733) -> Result<ExternalDemoBMetrics> {
734 let profile =
735 default_host_realistic_profile(config.dsfb_alpha_range.min, config.dsfb_alpha_range.max);
736 let mut captures = Vec::with_capacity(bundle.captures.len());
737
738 for (capture_index, capture) in bundle.captures.iter().enumerate() {
739 let outputs = supervise_temporal_reuse(&capture.inputs.borrow(), &profile);
740 let fixed_alpha_field = constant_field(
741 capture.inputs.width(),
742 capture.inputs.height(),
743 config.baseline.fixed_alpha,
744 );
745 let fixed_resolved = resolve_with_alpha(
746 &capture.inputs.reprojected_history,
747 &capture.inputs.current_color,
748 &fixed_alpha_field,
749 );
750 let (reference_frame, reference_source, _) = capture_reference_frame_and_metric_source(capture);
751 let (roi_mask, roi_source, roi_coverage) =
752 roi_mask_for_capture(capture, &fixed_resolved, reference_frame);
753 let width = capture.inputs.width();
754 let height = capture.inputs.height();
755 let total_pixels = width * height;
756 let total_samples = config.demo_b_uniform_spp * total_pixels;
757 let variance_field = capture
758 .variance
759 .clone()
760 .map(normalize_field)
761 .unwrap_or_else(|| {
762 temporal_variance_proxy(
763 &capture.inputs.current_color,
764 &capture.inputs.reprojected_history,
765 )
766 });
767 let gradient = gradient_field(&capture.inputs.current_color);
768 let contrast = local_contrast_field(&capture.inputs.current_color);
769 let imported_trust = invert_trust(&outputs.trust);
770 let combined = combine_fields(
771 &[
772 (&gradient, 0.30),
773 (&contrast, 0.25),
774 (&variance_field, 0.45),
775 ],
776 width,
777 height,
778 );
779 let hybrid = combine_fields(
780 &[(&imported_trust, 0.55), (&variance_field, 0.45)],
781 width,
782 height,
783 );
784 let base_error = combine_fields(
785 &[
786 (&gradient, 0.20),
787 (&contrast, 0.20),
788 (&variance_field, 0.35),
789 (&imported_trust, 0.25),
790 ],
791 width,
792 height,
793 );
794
795 let policies = vec![
796 (
797 AllocationPolicyId::Uniform,
798 "Uniform",
799 vec![config.demo_b_uniform_spp; total_pixels],
800 ),
801 (
802 AllocationPolicyId::EdgeGuided,
803 "Gradient magnitude",
804 guided_allocation(&gradient, total_samples, 1, config.demo_b_max_spp)?,
805 ),
806 (
807 AllocationPolicyId::ContrastGuided,
808 "Contrast-based",
809 guided_allocation(&contrast, total_samples, 1, config.demo_b_max_spp)?,
810 ),
811 (
812 AllocationPolicyId::VarianceGuided,
813 "Variance proxy",
814 guided_allocation(&variance_field, total_samples, 1, config.demo_b_max_spp)?,
815 ),
816 (
817 AllocationPolicyId::CombinedHeuristic,
818 "Combined heuristic",
819 guided_allocation(&combined, total_samples, 1, config.demo_b_max_spp)?,
820 ),
821 (
822 AllocationPolicyId::ImportedTrust,
823 "DSFB imported trust",
824 guided_allocation(&imported_trust, total_samples, 1, config.demo_b_max_spp)?,
825 ),
826 (
827 AllocationPolicyId::HybridTrustVariance,
828 "Hybrid trust + variance",
829 guided_allocation(&hybrid, total_samples, 1, config.demo_b_max_spp)?,
830 ),
831 ];
832
833 let fixed_budget_equal = policies
834 .iter()
835 .all(|(_, _, counts)| counts.iter().sum::<usize>() == total_samples);
836 let uniform_roi_mae =
837 predicted_error_over_mask(&base_error, &policies[0].2, Some(&roi_mask), false);
838 let regime = classify_external_demo_b_regime(&gradient, &variance_field, &contrast);
839
840 let mut policy_metrics = Vec::new();
841 let uniform_roi_mean_spp = mean_count_over_mask(&policies[0].2, &roi_mask);
842 let non_roi_mask = invert_mask(&roi_mask);
843 for (policy_index, (policy_id, label, counts)) in policies.iter().enumerate() {
844 let counts_field = build_count_field(counts, width, height);
845 if capture_index == 0 && policy_index < 4 {
846 save_scalar_field_png(
847 &counts_field,
848 &figures_dir.join(format!("demo_b_allocation_{}.png", policy_id.as_str())),
849 heatmap_blue,
850 )?;
851 }
852 let overall_mae = predicted_error_over_mask(&base_error, counts, None, false);
853 let roi_mae = predicted_error_over_mask(&base_error, counts, Some(&roi_mask), false);
854 let non_roi_mae =
855 predicted_error_over_mask(&base_error, counts, Some(&non_roi_mask), false);
856 let overall_rmse = predicted_error_over_mask(&base_error, counts, None, true);
857 let roi_rmse = predicted_error_over_mask(&base_error, counts, Some(&roi_mask), true);
858 let non_roi_rmse =
859 predicted_error_over_mask(&base_error, counts, Some(&non_roi_mask), true);
860 let roi_mean_spp = mean_count_over_mask(counts, &roi_mask);
861 let non_roi_mean_spp = mean_count_over_mask(counts, &non_roi_mask);
862 let max_spp = counts.iter().copied().max().unwrap_or(0);
863 let allocation_concentration = counts_field.mean();
864 let extra_roi_samples_vs_uniform = roi_mean_spp - uniform_roi_mean_spp;
865 let roi_error_reduction_per_extra_roi_sample =
866 if extra_roi_samples_vs_uniform.abs() <= f32::EPSILON {
867 0.0
868 } else {
869 (uniform_roi_mae - roi_mae) / extra_roi_samples_vs_uniform.max(1e-6)
870 };
871 policy_metrics.push(DemoBPolicyMetrics {
872 policy_id: policy_id.as_str().to_string(),
873 label: label.to_string(),
874 total_samples,
875 overall_mae,
876 overall_rmse,
877 roi_mae,
878 roi_rmse,
879 non_roi_mae,
880 non_roi_rmse,
881 roi_mean_spp,
882 non_roi_mean_spp,
883 max_spp,
884 allocation_concentration,
885 extra_roi_samples_vs_uniform,
886 roi_error_reduction_per_extra_roi_sample,
887 });
888 }
889
890 let mut budget_curves = Vec::new();
891 for (policy_id, field) in [
892 (AllocationPolicyId::Uniform, None),
893 (AllocationPolicyId::CombinedHeuristic, Some(&combined)),
894 (AllocationPolicyId::ImportedTrust, Some(&imported_trust)),
895 (AllocationPolicyId::HybridTrustVariance, Some(&hybrid)),
896 ] {
897 let mut points = Vec::new();
898 for average_spp in [1usize, 2, 4, 8] {
899 let budget = average_spp * total_pixels;
900 let counts = if let Some(difficulty) = field {
901 guided_allocation(difficulty, budget, 1, config.demo_b_max_spp)?
902 } else {
903 vec![average_spp; total_pixels]
904 };
905 points.push(BudgetCurvePoint {
906 average_spp: average_spp as f32,
907 roi_mae: predicted_error_over_mask(
908 &base_error,
909 &counts,
910 Some(&roi_mask),
911 false,
912 ),
913 });
914 }
915 budget_curves.push(BudgetCurve {
916 scenario_id: capture.label.clone(),
917 policy_id: policy_id.as_str().to_string(),
918 points,
919 });
920 }
921
922 captures.push(ExternalDemoBCaptureMetrics {
923 capture_label: capture.label.clone(),
924 regime,
925 metric_source: if capture.reference.is_some() {
926 "allocation_proxy_with_optional_reference".to_string()
927 } else {
928 "allocation_proxy_without_reference".to_string()
929 },
930 roi_source,
931 roi_pixels: roi_mask.iter().filter(|value| **value).count(),
932 total_pixels,
933 roi_coverage,
934 roi_statement: ROI_CONTRACT_STATEMENT.to_string(),
935 baseline_method_id: ROI_CONTRACT_BASELINE_METHOD_ID.to_string(),
936 reference_source: reference_source.to_string(),
937 ground_truth_available: capture.reference.is_some(),
938 budget_total_samples: total_samples,
939 fixed_budget_equal,
940 policies: policy_metrics,
941 budget_curves,
942 });
943 }
944
945 Ok(ExternalDemoBMetrics {
946 real_external_data_provided: bundle.real_external_data_provided,
947 no_real_external_data_provided: bundle.no_real_external_data_provided,
948 captures,
949 notes: vec![
950 "External Demo B is an allocation proxy, not a live renderer replay, because imported captures do not contain per-sample shading or multi-budget re-renders.".to_string(),
951 "The proxy still enforces identical total budgets across all policies and is intended to guide the next engine-side experiment rather than replace it.".to_string(),
952 ROI_CONTRACT_STATEMENT.to_string(),
953 ],
954 })
955}
956
957fn run_external_scaling_study(
958 _config: &DemoConfig,
959 manifest_path: &Path,
960 output_dir: &Path,
961 bundle: &ExternalCaptureBundle,
962 gpu: &ExternalGpuMetrics,
963 demo_a: &ExternalDemoAMetrics,
964 demo_b: &ExternalDemoBMetrics,
965) -> Result<ExternalScalingMetrics> {
966 let capture = bundle.captures.first().ok_or_else(|| {
967 Error::Message("external scaling study requires at least one capture".to_string())
968 })?;
969 let native_width = capture.inputs.width();
970 let native_height = capture.inputs.height();
971 let native_pixels = (native_width * native_height) as f64;
972 let coverage = coverage_summary(bundle, demo_a, demo_b);
973 let native_capture = gpu
974 .captures
975 .iter()
976 .find(|candidate| candidate.capture_label == capture.label)
977 .or_else(|| gpu.captures.first());
978 let native_total_ms = native_capture.and_then(|metrics| metrics.total_ms);
979 let mut entries = vec![ExternalScalingEntry {
980 label: "native_imported".to_string(),
981 source: "native_imported".to_string(),
982 width: native_width,
983 height: native_height,
984 attempted: true,
985 measured_gpu: native_capture.map(|metrics| metrics.measured_gpu).unwrap_or(false),
986 total_ms: native_capture.and_then(|metrics| metrics.total_ms),
987 dispatch_ms: native_capture.and_then(|metrics| metrics.dispatch_ms),
988 readback_ms: native_capture.and_then(|metrics| metrics.readback_ms),
989 ms_per_megapixel: native_capture
990 .and_then(|metrics| metrics.total_ms)
991 .map(|total_ms| total_ms / (native_pixels / 1_000_000.0).max(1e-6)),
992 scaling_ratio_vs_native: native_total_ms.map(|_| 1.0),
993 pixel_ratio_vs_native: 1.0,
994 approximately_linear: native_total_ms.map(|_| true),
995 unavailable_reason: native_capture
996 .filter(|metrics| !metrics.measured_gpu || metrics.total_ms.is_none())
997 .map(|metrics| metrics.notes.join(" ")),
998 }];
999
1000 let executable = gpu_probe_executable()?;
1001 for (label, width, height) in [("scaled_1080p", 1920usize, 1080usize), ("scaled_4k", 3840usize, 2160usize)] {
1002 entries.push(if let Some(executable) = &executable {
1003 probe_scaled_gpu_entry(
1004 executable,
1005 manifest_path,
1006 output_dir,
1007 &capture.label,
1008 label,
1009 width,
1010 height,
1011 native_pixels,
1012 native_total_ms,
1013 )?
1014 } else {
1015 unavailable_scaling_entry(
1016 label,
1017 width,
1018 height,
1019 native_pixels,
1020 false,
1021 "scaled probe requires the standalone dsfb-computer-graphics binary; library/test invocation cannot safely isolate GPU shader-JIT failures"
1022 .to_string(),
1023 )
1024 });
1025 }
1026
1027 let measured_scaled = entries
1028 .iter()
1029 .skip(1)
1030 .any(|entry| entry.measured_gpu && entry.total_ms.is_some());
1031 let measurement_kind = if measured_scaled {
1032 "gpu_scaled_probe_measured".to_string()
1033 } else if entries.iter().any(|entry| entry.measured_gpu) {
1034 "native_gpu_only".to_string()
1035 } else {
1036 "gpu_path_unmeasured".to_string()
1037 };
1038
1039 Ok(ExternalScalingMetrics {
1040 measurement_kind,
1041 kernel: "dsfb_host_minimum".to_string(),
1042 native_capture_label: capture.label.clone(),
1043 native_resolution: [native_width, native_height],
1044 attempted_1080p: true,
1045 attempted_4k: true,
1046 entries,
1047 coverage,
1048 notes: if measured_scaled {
1049 vec![
1050 "Scaled GPU timings were measured in isolated subprocess probes to avoid driver-specific shader-JIT crashes from corrupting the canonical run.".to_string(),
1051 "The native imported timing reuses the same minimum-kernel path as gpu_execution_metrics.json.".to_string(),
1052 ]
1053 } else {
1054 vec![
1055 "Scaled GPU probes were attempted only when the standalone binary was available for isolated subprocess execution.".to_string(),
1056 "When a scaled row is unavailable, the run fails closed rather than guessing a scaling claim.".to_string(),
1057 ]
1058 },
1059 })
1060}
1061
1062fn gpu_probe_executable() -> Result<Option<PathBuf>> {
1063 let executable = std::env::current_exe()?;
1064 let executable_name = executable
1065 .file_name()
1066 .and_then(|name| name.to_str())
1067 .unwrap_or_default();
1068 if executable_name.contains("dsfb-computer-graphics") {
1069 Ok(Some(executable))
1070 } else {
1071 Ok(None)
1072 }
1073}
1074
1075fn probe_scaled_gpu_entry(
1076 executable: &Path,
1077 manifest_path: &Path,
1078 output_dir: &Path,
1079 capture_label: &str,
1080 label: &str,
1081 width: usize,
1082 height: usize,
1083 native_pixels: f64,
1084 native_total_ms: Option<f64>,
1085) -> Result<ExternalScalingEntry> {
1086 let pixel_ratio = (width * height) as f64 / native_pixels.max(1.0);
1087 let probe_dir = output_dir.join("scaling_gpu_probe").join(label);
1088 fs::create_dir_all(&probe_dir)?;
1089 let status = Command::new(executable)
1090 .arg("probe-external-gpu")
1091 .arg("--manifest")
1092 .arg(manifest_path)
1093 .arg("--output")
1094 .arg(&probe_dir)
1095 .arg("--capture-label")
1096 .arg(capture_label)
1097 .arg("--width")
1098 .arg(width.to_string())
1099 .arg("--height")
1100 .arg(height.to_string())
1101 .status()?;
1102 let metrics_path = probe_dir.join("gpu_probe_metrics.json");
1103 if status.success() && metrics_path.exists() {
1104 let metrics: ExternalGpuMetrics = serde_json::from_str(&fs::read_to_string(metrics_path)?)?;
1105 let capture = metrics.captures.first().ok_or_else(|| {
1106 Error::Message(format!(
1107 "scaled GPU probe `{label}` did not emit any capture metrics"
1108 ))
1109 })?;
1110 return Ok(build_scaling_entry(
1111 label,
1112 "scaled_subprocess_probe",
1113 width,
1114 height,
1115 pixel_ratio,
1116 capture,
1117 native_total_ms,
1118 ));
1119 }
1120
1121 let reason = if let Some(code) = status.code() {
1122 format!("scaled GPU subprocess exited with status {code}")
1123 } else {
1124 "scaled GPU subprocess terminated by signal".to_string()
1125 };
1126 Ok(unavailable_scaling_entry(
1127 label,
1128 width,
1129 height,
1130 native_pixels,
1131 true,
1132 reason,
1133 ))
1134}
1135
1136fn build_scaling_entry(
1137 label: &str,
1138 source: &str,
1139 width: usize,
1140 height: usize,
1141 pixel_ratio: f64,
1142 capture: &ExternalGpuCaptureMetrics,
1143 native_total_ms: Option<f64>,
1144) -> ExternalScalingEntry {
1145 let total_ms = capture.total_ms;
1146 let scaling_ratio_vs_native = native_total_ms
1147 .zip(total_ms)
1148 .map(|(native, total)| total / native.max(1e-9));
1149 let approximately_linear = scaling_ratio_vs_native.map(|scaling_ratio| {
1150 let normalized = scaling_ratio / pixel_ratio.max(1e-9);
1151 (normalized - 1.0).abs() <= 0.20
1152 });
1153 ExternalScalingEntry {
1154 label: label.to_string(),
1155 source: source.to_string(),
1156 width,
1157 height,
1158 attempted: true,
1159 measured_gpu: capture.measured_gpu,
1160 total_ms,
1161 dispatch_ms: capture.dispatch_ms,
1162 readback_ms: capture.readback_ms,
1163 ms_per_megapixel: total_ms.map(|value| value / (((width * height) as f64) / 1_000_000.0).max(1e-6)),
1164 scaling_ratio_vs_native,
1165 pixel_ratio_vs_native: pixel_ratio,
1166 approximately_linear,
1167 unavailable_reason: if capture.measured_gpu && total_ms.is_some() {
1168 None
1169 } else if capture.notes.is_empty() {
1170 Some("scaled GPU probe did not return a usable timing".to_string())
1171 } else {
1172 Some(capture.notes.join(" "))
1173 },
1174 }
1175}
1176
1177fn unavailable_scaling_entry(
1178 label: &str,
1179 width: usize,
1180 height: usize,
1181 native_pixels: f64,
1182 attempted: bool,
1183 reason: String,
1184) -> ExternalScalingEntry {
1185 ExternalScalingEntry {
1186 label: label.to_string(),
1187 source: "scaled_subprocess_probe".to_string(),
1188 width,
1189 height,
1190 attempted,
1191 measured_gpu: false,
1192 total_ms: None,
1193 dispatch_ms: None,
1194 readback_ms: None,
1195 ms_per_megapixel: None,
1196 scaling_ratio_vs_native: None,
1197 pixel_ratio_vs_native: (width * height) as f64 / native_pixels.max(1.0),
1198 approximately_linear: None,
1199 unavailable_reason: Some(reason),
1200 }
1201}
1202
1203fn coverage_summary(
1204 bundle: &ExternalCaptureBundle,
1205 demo_a: &ExternalDemoAMetrics,
1206 demo_b: &ExternalDemoBMetrics,
1207) -> ExternalCoverageSummary {
1208 let larger_roi_case = demo_a
1209 .captures
1210 .iter()
1211 .any(|capture| capture.roi_pixels > 50);
1212 let mixed_regime_case = demo_b
1213 .captures
1214 .iter()
1215 .any(|capture| capture.regime == "mixed_regime");
1216 let realism_stress_case = bundle.captures.iter().any(is_realism_stress_capture);
1217 let mut missing = Vec::new();
1218 if !realism_stress_case {
1219 missing.push("realism_stress_case".to_string());
1220 }
1221 if !larger_roi_case {
1222 missing.push("larger_roi_case".to_string());
1223 }
1224 if !mixed_regime_case {
1225 missing.push("mixed_regime_case".to_string());
1226 }
1227 ExternalCoverageSummary {
1228 realism_stress_case,
1229 larger_roi_case,
1230 mixed_regime_case,
1231 coverage_status: if missing.is_empty() {
1232 "complete".to_string()
1233 } else {
1234 "partial".to_string()
1235 },
1236 missing,
1237 }
1238}
1239
1240fn scale_external_capture(
1241 capture: &ExternalLoadedCapture,
1242 width: usize,
1243 height: usize,
1244) -> Result<ExternalLoadedCapture> {
1245 if width == 0 || height == 0 {
1246 return Err(Error::Message(
1247 "scaled GPU probe requires non-zero width and height".to_string(),
1248 ));
1249 }
1250 let source_width = capture.inputs.width();
1251 let source_height = capture.inputs.height();
1252 if width == source_width && height == source_height {
1253 return Ok(capture.clone());
1254 }
1255
1256 let inputs = OwnedHostTemporalInputs {
1257 current_color: scale_image_frame(&capture.inputs.current_color, width, height),
1258 reprojected_history: scale_image_frame(&capture.inputs.reprojected_history, width, height),
1259 motion_vectors: scale_motion_vectors(
1260 &capture.inputs.motion_vectors,
1261 source_width,
1262 source_height,
1263 width,
1264 height,
1265 ),
1266 current_depth: scale_scalar_buffer(
1267 &capture.inputs.current_depth,
1268 source_width,
1269 source_height,
1270 width,
1271 height,
1272 ),
1273 reprojected_depth: scale_scalar_buffer(
1274 &capture.inputs.reprojected_depth,
1275 source_width,
1276 source_height,
1277 width,
1278 height,
1279 ),
1280 current_normals: scale_normal_buffer(
1281 &capture.inputs.current_normals,
1282 source_width,
1283 source_height,
1284 width,
1285 height,
1286 ),
1287 reprojected_normals: scale_normal_buffer(
1288 &capture.inputs.reprojected_normals,
1289 source_width,
1290 source_height,
1291 width,
1292 height,
1293 ),
1294 visibility_hint: capture
1295 .inputs
1296 .visibility_hint
1297 .as_ref()
1298 .map(|mask| scale_bool_buffer(mask, source_width, source_height, width, height)),
1299 thin_hint: capture
1300 .inputs
1301 .thin_hint
1302 .as_ref()
1303 .map(|field| scale_scalar_field(field, width, height)),
1304 };
1305 let mut metadata = capture.metadata.clone();
1306 metadata.width = width;
1307 metadata.height = height;
1308 metadata.notes.push(format!(
1309 "This capture was scaled in-memory from {}x{} to {}x{} for an isolated GPU scaling probe.",
1310 source_width, source_height, width, height
1311 ));
1312
1313 Ok(ExternalLoadedCapture {
1314 label: capture.label.clone(),
1315 inputs,
1316 metadata,
1317 mask: capture
1318 .mask
1319 .as_ref()
1320 .map(|mask| scale_bool_buffer(mask, source_width, source_height, width, height)),
1321 reference: capture
1322 .reference
1323 .as_ref()
1324 .map(|reference| scale_image_frame(reference, width, height)),
1325 variance: capture
1326 .variance
1327 .as_ref()
1328 .map(|variance| scale_scalar_field(variance, width, height)),
1329 })
1330}
1331
1332fn scale_image_frame(frame: &ImageFrame, width: usize, height: usize) -> ImageFrame {
1333 let mut scaled = ImageFrame::new(width, height);
1334 for y in 0..height {
1335 for x in 0..width {
1336 let sample_x = scaled_sample_coordinate(x, frame.width(), width);
1337 let sample_y = scaled_sample_coordinate(y, frame.height(), height);
1338 scaled.set(x, y, frame.sample_bilinear_clamped(sample_x, sample_y));
1339 }
1340 }
1341 scaled
1342}
1343
1344fn scale_scalar_field(field: &ScalarField, width: usize, height: usize) -> ScalarField {
1345 ScalarField::from_values(
1346 width,
1347 height,
1348 scale_scalar_buffer(field.values(), field.width(), field.height(), width, height),
1349 )
1350}
1351
1352fn scale_scalar_buffer(
1353 values: &[f32],
1354 source_width: usize,
1355 source_height: usize,
1356 width: usize,
1357 height: usize,
1358) -> Vec<f32> {
1359 let mut scaled = Vec::with_capacity(width * height);
1360 for y in 0..height {
1361 for x in 0..width {
1362 let sample_x = scaled_sample_coordinate(x, source_width, width);
1363 let sample_y = scaled_sample_coordinate(y, source_height, height);
1364 scaled.push(sample_scalar_bilinear(
1365 values,
1366 source_width,
1367 source_height,
1368 sample_x,
1369 sample_y,
1370 ));
1371 }
1372 }
1373 scaled
1374}
1375
1376fn scale_bool_buffer(
1377 values: &[bool],
1378 source_width: usize,
1379 source_height: usize,
1380 width: usize,
1381 height: usize,
1382) -> Vec<bool> {
1383 let mut scaled = Vec::with_capacity(width * height);
1384 for y in 0..height {
1385 for x in 0..width {
1386 let sample_x = scaled_sample_coordinate(x, source_width, width)
1387 .round()
1388 .clamp(0.0, (source_width.saturating_sub(1)) as f32) as usize;
1389 let sample_y = scaled_sample_coordinate(y, source_height, height)
1390 .round()
1391 .clamp(0.0, (source_height.saturating_sub(1)) as f32) as usize;
1392 scaled.push(values[sample_y * source_width + sample_x]);
1393 }
1394 }
1395 scaled
1396}
1397
1398fn scale_motion_vectors(
1399 values: &[MotionVector],
1400 source_width: usize,
1401 source_height: usize,
1402 width: usize,
1403 height: usize,
1404) -> Vec<MotionVector> {
1405 let scale_x = width as f32 / source_width.max(1) as f32;
1406 let scale_y = height as f32 / source_height.max(1) as f32;
1407 let mut scaled = Vec::with_capacity(width * height);
1408 for y in 0..height {
1409 for x in 0..width {
1410 let sample_x = scaled_sample_coordinate(x, source_width, width);
1411 let sample_y = scaled_sample_coordinate(y, source_height, height);
1412 let motion = sample_motion_bilinear(values, source_width, source_height, sample_x, sample_y);
1413 scaled.push(MotionVector {
1414 to_prev_x: motion.to_prev_x * scale_x,
1415 to_prev_y: motion.to_prev_y * scale_y,
1416 });
1417 }
1418 }
1419 scaled
1420}
1421
1422fn scale_normal_buffer(
1423 values: &[Normal3],
1424 source_width: usize,
1425 source_height: usize,
1426 width: usize,
1427 height: usize,
1428) -> Vec<Normal3> {
1429 let mut scaled = Vec::with_capacity(width * height);
1430 for y in 0..height {
1431 for x in 0..width {
1432 let sample_x = scaled_sample_coordinate(x, source_width, width);
1433 let sample_y = scaled_sample_coordinate(y, source_height, height);
1434 scaled.push(sample_normal_bilinear(
1435 values,
1436 source_width,
1437 source_height,
1438 sample_x,
1439 sample_y,
1440 ));
1441 }
1442 }
1443 scaled
1444}
1445
1446fn scaled_sample_coordinate(index: usize, source_extent: usize, scaled_extent: usize) -> f32 {
1447 ((index as f32 + 0.5) * source_extent as f32 / scaled_extent.max(1) as f32) - 0.5
1448}
1449
1450fn sample_scalar_bilinear(
1451 values: &[f32],
1452 width: usize,
1453 height: usize,
1454 x: f32,
1455 y: f32,
1456) -> f32 {
1457 let x0 = x.floor();
1458 let y0 = y.floor();
1459 let x1 = x0 + 1.0;
1460 let y1 = y0 + 1.0;
1461 let tx = (x - x0).clamp(0.0, 1.0);
1462 let ty = (y - y0).clamp(0.0, 1.0);
1463 let p00 = scalar_at(values, width, height, x0 as i32, y0 as i32);
1464 let p10 = scalar_at(values, width, height, x1 as i32, y0 as i32);
1465 let p01 = scalar_at(values, width, height, x0 as i32, y1 as i32);
1466 let p11 = scalar_at(values, width, height, x1 as i32, y1 as i32);
1467 let top = p00 * (1.0 - tx) + p10 * tx;
1468 let bottom = p01 * (1.0 - tx) + p11 * tx;
1469 top * (1.0 - ty) + bottom * ty
1470}
1471
1472fn scalar_at(values: &[f32], width: usize, height: usize, x: i32, y: i32) -> f32 {
1473 let clamped_x = x.clamp(0, width as i32 - 1) as usize;
1474 let clamped_y = y.clamp(0, height as i32 - 1) as usize;
1475 values[clamped_y * width + clamped_x]
1476}
1477
1478fn sample_motion_bilinear(
1479 values: &[MotionVector],
1480 width: usize,
1481 height: usize,
1482 x: f32,
1483 y: f32,
1484) -> MotionVector {
1485 let x0 = x.floor();
1486 let y0 = y.floor();
1487 let x1 = x0 + 1.0;
1488 let y1 = y0 + 1.0;
1489 let tx = (x - x0).clamp(0.0, 1.0);
1490 let ty = (y - y0).clamp(0.0, 1.0);
1491 let p00 = motion_at(values, width, height, x0 as i32, y0 as i32);
1492 let p10 = motion_at(values, width, height, x1 as i32, y0 as i32);
1493 let p01 = motion_at(values, width, height, x0 as i32, y1 as i32);
1494 let p11 = motion_at(values, width, height, x1 as i32, y1 as i32);
1495 let top_x = p00.to_prev_x * (1.0 - tx) + p10.to_prev_x * tx;
1496 let top_y = p00.to_prev_y * (1.0 - tx) + p10.to_prev_y * tx;
1497 let bottom_x = p01.to_prev_x * (1.0 - tx) + p11.to_prev_x * tx;
1498 let bottom_y = p01.to_prev_y * (1.0 - tx) + p11.to_prev_y * tx;
1499 MotionVector {
1500 to_prev_x: top_x * (1.0 - ty) + bottom_x * ty,
1501 to_prev_y: top_y * (1.0 - ty) + bottom_y * ty,
1502 }
1503}
1504
1505fn motion_at(values: &[MotionVector], width: usize, height: usize, x: i32, y: i32) -> MotionVector {
1506 let clamped_x = x.clamp(0, width as i32 - 1) as usize;
1507 let clamped_y = y.clamp(0, height as i32 - 1) as usize;
1508 values[clamped_y * width + clamped_x]
1509}
1510
1511fn sample_normal_bilinear(
1512 values: &[Normal3],
1513 width: usize,
1514 height: usize,
1515 x: f32,
1516 y: f32,
1517) -> Normal3 {
1518 let x0 = x.floor();
1519 let y0 = y.floor();
1520 let x1 = x0 + 1.0;
1521 let y1 = y0 + 1.0;
1522 let tx = (x - x0).clamp(0.0, 1.0);
1523 let ty = (y - y0).clamp(0.0, 1.0);
1524 let p00 = normal_at(values, width, height, x0 as i32, y0 as i32);
1525 let p10 = normal_at(values, width, height, x1 as i32, y0 as i32);
1526 let p01 = normal_at(values, width, height, x0 as i32, y1 as i32);
1527 let p11 = normal_at(values, width, height, x1 as i32, y1 as i32);
1528 let top = Normal3::new(
1529 p00.x * (1.0 - tx) + p10.x * tx,
1530 p00.y * (1.0 - tx) + p10.y * tx,
1531 p00.z * (1.0 - tx) + p10.z * tx,
1532 );
1533 let bottom = Normal3::new(
1534 p01.x * (1.0 - tx) + p11.x * tx,
1535 p01.y * (1.0 - tx) + p11.y * tx,
1536 p01.z * (1.0 - tx) + p11.z * tx,
1537 );
1538 Normal3::new(
1539 top.x * (1.0 - ty) + bottom.x * ty,
1540 top.y * (1.0 - ty) + bottom.y * ty,
1541 top.z * (1.0 - ty) + bottom.z * ty,
1542 )
1543 .normalized()
1544}
1545
1546fn normal_at(values: &[Normal3], width: usize, height: usize, x: i32, y: i32) -> Normal3 {
1547 let clamped_x = x.clamp(0, width as i32 - 1) as usize;
1548 let clamped_y = y.clamp(0, height as i32 - 1) as usize;
1549 values[clamped_y * width + clamped_x]
1550}
1551
1552fn is_realism_stress_capture(capture: &ExternalLoadedCapture) -> bool {
1553 if let Some(scenario_id) = capture.metadata.scenario_id.as_deref() {
1554 if matches!(
1555 scenario_id,
1556 "motion_bias_band" | "noisy_reprojection" | "fast_pan" | "heuristic_friendly_pan"
1557 ) {
1558 return true;
1559 }
1560 }
1561 let motion_mean = capture
1562 .inputs
1563 .motion_vectors
1564 .iter()
1565 .map(|motion| {
1566 (motion.to_prev_x * motion.to_prev_x + motion.to_prev_y * motion.to_prev_y).sqrt()
1567 })
1568 .sum::<f32>()
1569 / capture.inputs.motion_vectors.len().max(1) as f32;
1570 let depth_disagreement = capture
1571 .inputs
1572 .current_depth
1573 .iter()
1574 .zip(capture.inputs.reprojected_depth.iter())
1575 .map(|(current, history)| (current - history).abs())
1576 .sum::<f32>()
1577 / capture.inputs.current_depth.len().max(1) as f32;
1578 motion_mean > 0.25 || depth_disagreement > 0.02
1579}
1580
1581fn write_external_scaling_report(
1582 path: &Path,
1583 metrics: &ExternalScalingMetrics,
1584 bundle: &ExternalCaptureBundle,
1585) -> Result<()> {
1586 if let Some(parent) = path.parent() {
1587 fs::create_dir_all(parent)?;
1588 }
1589 let mut markdown = String::new();
1590 let _ = writeln!(markdown, "# External Scaling Report");
1591 let _ = writeln!(markdown);
1592 let _ = writeln!(markdown, "{EXPERIMENT_SENTENCE}");
1593 let _ = writeln!(markdown);
1594 if bundle.no_real_external_data_provided {
1595 let _ = writeln!(markdown, "{NO_REAL_EXTERNAL_DATA_PROVIDED}");
1596 let _ = writeln!(markdown);
1597 }
1598 let _ = writeln!(
1599 markdown,
1600 "Native imported capture: `{}` at {}x{}.",
1601 metrics.native_capture_label, metrics.native_resolution[0], metrics.native_resolution[1]
1602 );
1603 let _ = writeln!(markdown);
1604 let _ = writeln!(
1605 markdown,
1606 "| Label | Source | Resolution | Attempted | Measured GPU | total_ms | dispatch_ms | readback_ms | ms/MPixel | scaling ratio vs native | approx linear |"
1607 );
1608 let _ = writeln!(
1609 markdown,
1610 "| --- | --- | --- | --- | --- | ---: | ---: | ---: | ---: | ---: | --- |"
1611 );
1612 for entry in &metrics.entries {
1613 let _ = writeln!(
1614 markdown,
1615 "| {} | {} | {}x{} | {} | {} | {} | {} | {} | {} | {} | {} |",
1616 entry.label,
1617 entry.source,
1618 entry.width,
1619 entry.height,
1620 entry.attempted,
1621 entry.measured_gpu,
1622 format_f64(entry.total_ms),
1623 format_f64(entry.dispatch_ms),
1624 format_f64(entry.readback_ms),
1625 format_f64(entry.ms_per_megapixel),
1626 format_f64(entry.scaling_ratio_vs_native),
1627 entry
1628 .approximately_linear
1629 .map(|value| value.to_string())
1630 .unwrap_or_else(|| "unknown".to_string()),
1631 );
1632 if let Some(reason) = &entry.unavailable_reason {
1633 let _ = writeln!(markdown, " - unavailable: {reason}");
1634 }
1635 }
1636 let approximate_linearity = metrics
1637 .entries
1638 .iter()
1639 .skip(1)
1640 .filter_map(|entry| entry.approximately_linear)
1641 .all(|value| value);
1642 let _ = writeln!(markdown);
1643 let _ = writeln!(
1644 markdown,
1645 "Cost appears approximately linear with resolution: `{}`.",
1646 if metrics
1647 .entries
1648 .iter()
1649 .any(|entry| entry.approximately_linear.is_some())
1650 {
1651 approximate_linearity
1652 } else {
1653 false
1654 }
1655 );
1656 if !metrics
1657 .entries
1658 .iter()
1659 .any(|entry| entry.approximately_linear.is_some())
1660 {
1661 let _ = writeln!(
1662 markdown,
1663 "Approximate linearity could not be classified because no scaled GPU timing was measured in the current environment."
1664 );
1665 }
1666 let _ = writeln!(markdown);
1667 let _ = writeln!(markdown, "## Coverage");
1668 let _ = writeln!(markdown);
1669 let _ = writeln!(
1670 markdown,
1671 "- realism_stress_case: `{}`",
1672 metrics.coverage.realism_stress_case
1673 );
1674 let _ = writeln!(
1675 markdown,
1676 "- larger_roi_case: `{}`",
1677 metrics.coverage.larger_roi_case
1678 );
1679 let _ = writeln!(
1680 markdown,
1681 "- mixed_regime_case: `{}`",
1682 metrics.coverage.mixed_regime_case
1683 );
1684 let _ = writeln!(
1685 markdown,
1686 "- coverage_status: `{}`",
1687 metrics.coverage.coverage_status
1688 );
1689 if !metrics.coverage.missing.is_empty() {
1690 let _ = writeln!(
1691 markdown,
1692 "- missing coverage labels: {}",
1693 metrics.coverage.missing.join(", ")
1694 );
1695 }
1696 let _ = writeln!(markdown);
1697 let _ = writeln!(markdown, "## What Is Not Proven");
1698 let _ = writeln!(markdown);
1699 let _ = writeln!(
1700 markdown,
1701 "- This scaling report does not replace full engine-side profiling on real exported captures."
1702 );
1703 let _ = writeln!(
1704 markdown,
1705 "- When a row is marked unavailable, the corresponding scaling point was attempted but not measured in the current environment."
1706 );
1707 let _ = writeln!(markdown);
1708 let _ = writeln!(markdown, "## Remaining Blockers");
1709 let _ = writeln!(markdown);
1710 let _ = writeln!(
1711 markdown,
1712 "- Imported-buffer scaling does not replace full in-engine profiling on the final evaluator hardware and renderer integration point."
1713 );
1714 fs::write(path, markdown)?;
1715 Ok(())
1716}
1717
1718fn write_memory_bandwidth_report(path: &Path, metrics: &ExternalScalingMetrics) -> Result<()> {
1719 if let Some(parent) = path.parent() {
1720 fs::create_dir_all(parent)?;
1721 }
1722 let current_color_bytes_per_read = 16usize;
1723 let current_color_reads = 19usize;
1724 let bytes_read_per_pixel = current_color_bytes_per_read * current_color_reads + 16 + 8 + 8 + 32;
1725 let bytes_written_per_pixel = 12usize;
1726 let validation_readback_bytes_per_pixel = 12usize;
1727 let reads_per_pixel = 23usize;
1728 let writes_per_pixel = 3usize;
1729
1730 let mut markdown = String::new();
1731 let _ = writeln!(markdown, "# Memory Bandwidth Report");
1732 let _ = writeln!(markdown);
1733 let _ = writeln!(markdown, "{EXPERIMENT_SENTENCE}");
1734 let _ = writeln!(markdown);
1735 let _ = writeln!(
1736 markdown,
1737 "| Label | Resolution | bytes read / px | bytes written / px | validation readback / px | estimated memory traffic MB | reads / px | writes / px | readback required in production |"
1738 );
1739 let _ = writeln!(
1740 markdown,
1741 "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | --- |"
1742 );
1743 for entry in &metrics.entries {
1744 let pixels = entry.width.saturating_mul(entry.height);
1745 let total_bytes = pixels
1746 * (bytes_read_per_pixel
1747 + bytes_written_per_pixel
1748 + validation_readback_bytes_per_pixel);
1749 let _ = writeln!(
1750 markdown,
1751 "| {} | {}x{} | {} | {} | {} | {:.2} | {} | {} | false |",
1752 entry.label,
1753 entry.width,
1754 entry.height,
1755 bytes_read_per_pixel,
1756 bytes_written_per_pixel,
1757 validation_readback_bytes_per_pixel,
1758 total_bytes as f64 / (1024.0 * 1024.0),
1759 reads_per_pixel,
1760 writes_per_pixel,
1761 );
1762 }
1763 let _ = writeln!(markdown);
1764 let _ = writeln!(markdown, "Readback required in production: `false`.");
1765 let _ = writeln!(
1766 markdown,
1767 "Readback was used here only for validation, numerical delta checks, and report generation."
1768 );
1769 let _ = writeln!(markdown);
1770 let _ = writeln!(markdown, "## Memory Access / Coherence Analysis");
1771 let _ = writeln!(markdown);
1772 let _ = writeln!(
1773 markdown,
1774 "- Buffer access pattern: linear per-pixel reads for current color, reprojected history, motion, depth pairs, and normal pairs, plus three scalar output writes."
1775 );
1776 let _ = writeln!(
1777 markdown,
1778 "- Neighborhood reads: the kernel performs two 3x3 neighborhood traversals over current color, one for local contrast and one for neighborhood-hull gating."
1779 );
1780 let _ = writeln!(
1781 markdown,
1782 "- Coherence expectation: adjacent threads in the 8x8 workgroup read strongly overlapping 3x3 neighborhoods, so the access pattern is locally coherent even though current color is revisited many times."
1783 );
1784 let _ = writeln!(
1785 markdown,
1786 "- Cache-friendliness: the minimum kernel avoids scattered history gathers because reprojection is precomputed before dispatch. That keeps the kernel more cache-friendly than a motion-indirected gather path."
1787 );
1788 let _ = writeln!(
1789 markdown,
1790 "- Cache risk: repeated 3x3 reads from a storage buffer still raise bandwidth pressure on current color, so profiling should confirm whether a texture path or shared-memory staging would reduce traffic materially."
1791 );
1792 let _ = writeln!(
1793 markdown,
1794 "- Optional path impact: any future motion-augmented kernel that reintroduces motion-neighborhood disagreement or in-kernel reprojection will increase cache pressure materially and should be treated as non-minimum."
1795 );
1796 let _ = writeln!(markdown);
1797 let _ = writeln!(markdown, "## What Is Not Proven");
1798 let _ = writeln!(markdown);
1799 let _ = writeln!(
1800 markdown,
1801 "- This report is analytical accounting based on the implemented kernel and does not replace external validation with hardware-counter collection."
1802 );
1803 let _ = writeln!(
1804 markdown,
1805 "- Reported traffic is sufficient for reviewer diligence, but not a substitute for per-architecture cache analysis."
1806 );
1807 let _ = writeln!(markdown);
1808 let _ = writeln!(markdown, "## Remaining Blockers");
1809 let _ = writeln!(markdown);
1810 let _ = writeln!(
1811 markdown,
1812 "- External validation still needs hardware counters and vendor-specific bandwidth profiling on imported real captures."
1813 );
1814 fs::write(path, markdown)?;
1815 Ok(())
1816}
1817
1818fn write_integration_scaling_report(
1819 path: &Path,
1820 metrics: &ExternalScalingMetrics,
1821 bundle: &ExternalCaptureBundle,
1822) -> Result<()> {
1823 if let Some(parent) = path.parent() {
1824 fs::create_dir_all(parent)?;
1825 }
1826 let mut markdown = String::new();
1827 let _ = writeln!(markdown, "# Integration Scaling Report");
1828 let _ = writeln!(markdown);
1829 let _ = writeln!(markdown, "{EXPERIMENT_SENTENCE}");
1830 let _ = writeln!(markdown);
1831 if bundle.no_real_external_data_provided {
1832 let _ = writeln!(markdown, "{NO_REAL_EXTERNAL_DATA_PROVIDED}");
1833 let _ = writeln!(markdown);
1834 }
1835 let _ = writeln!(markdown, "## Pipeline Insertion");
1836 let _ = writeln!(markdown);
1837 let _ = writeln!(
1838 markdown,
1839 "- The minimum kernel executes after history reprojection and after motion/depth/normal buffers are available, but before temporal resolve consumes per-pixel alpha or intervention."
1840 );
1841 let _ = writeln!(
1842 markdown,
1843 "- Alpha modulation is consumed by the temporal accumulation pass; trust and intervention are optional debug or allocator-driving side products."
1844 );
1845 let _ = writeln!(
1846 markdown,
1847 "- Production readback is not required. The current reports read buffers back only for validation and CPU/GPU delta checks."
1848 );
1849 let _ = writeln!(markdown);
1850 let _ = writeln!(markdown, "## Async-Compute Feasibility");
1851 let _ = writeln!(markdown);
1852 let _ = writeln!(
1853 markdown,
1854 "- Async execution is feasible if reprojected history, depth, and normals are already materialized and the downstream TAA resolve can wait on a GPU-side signal rather than CPU readback."
1855 );
1856 let _ = writeln!(
1857 markdown,
1858 "- The minimum kernel has no scattered history gather and no CPU dependency, so the main async-compute risk is overlap contention on memory bandwidth rather than synchronization correctness."
1859 );
1860 let _ = writeln!(
1861 markdown,
1862 "- Profiling still needs to confirm that the 3x3 current-color neighborhood reads do not stall other post or denoise passes when overlapped."
1863 );
1864 let _ = writeln!(markdown);
1865 let _ = writeln!(markdown, "## Hazards / Barriers / Transitions");
1866 let _ = writeln!(markdown);
1867 let _ = writeln!(
1868 markdown,
1869 "- Inputs should be transitioned to shader-read / storage-read state before dispatch."
1870 );
1871 let _ = writeln!(
1872 markdown,
1873 "- Trust, alpha, and intervention outputs should be transitioned from UAV/storage-write into the state required by the temporal resolve or any downstream debug visualization."
1874 );
1875 let _ = writeln!(
1876 markdown,
1877 "- A production integration should avoid CPU fences; only GPU barriers and queue synchronization should be required."
1878 );
1879 let _ = writeln!(markdown);
1880 let _ = writeln!(markdown, "## Pipeline Compatibility");
1881 let _ = writeln!(markdown);
1882 let _ = writeln!(
1883 markdown,
1884 "- The minimum kernel is compatible with tiled, deferred, and post-lighting temporal pipelines because it consumes already-aligned per-pixel buffers and writes only local trust/alpha/intervention fields."
1885 );
1886 let _ = writeln!(
1887 markdown,
1888 "- The current design remains compatible with tiled or asynchronous execution because it does not require CPU-side intervention in production."
1889 );
1890 let _ = writeln!(markdown);
1891 let _ = writeln!(markdown, "## Scaling Interpretation");
1892 let _ = writeln!(markdown);
1893 for entry in &metrics.entries {
1894 let _ = writeln!(
1895 markdown,
1896 "- {} {}x{}: measured_gpu = `{}`, ms/MPixel = {}, approx_linear = {}",
1897 entry.label,
1898 entry.width,
1899 entry.height,
1900 entry.measured_gpu,
1901 format_f64(entry.ms_per_megapixel),
1902 entry
1903 .approximately_linear
1904 .map(|value| value.to_string())
1905 .unwrap_or_else(|| "unknown".to_string())
1906 );
1907 }
1908 let _ = writeln!(markdown);
1909 let _ = writeln!(markdown, "## What Is Not Proven");
1910 let _ = writeln!(markdown);
1911 let _ = writeln!(
1912 markdown,
1913 "- This integration note is implementation-specific analysis, not a substitute for engine-side trace profiling."
1914 );
1915 let _ = writeln!(markdown);
1916 let _ = writeln!(markdown, "## Remaining Blockers");
1917 let _ = writeln!(markdown);
1918 let _ = writeln!(
1919 markdown,
1920 "- Async overlap, queue contention, and barrier cost still need confirmation inside a real renderer."
1921 );
1922 fs::write(path, markdown)?;
1923 Ok(())
1924}
1925
1926fn write_gpu_external_report(
1927 path: &Path,
1928 metrics: &ExternalGpuMetrics,
1929 bundle: &ExternalCaptureBundle,
1930) -> Result<()> {
1931 if let Some(parent) = path.parent() {
1932 fs::create_dir_all(parent)?;
1933 }
1934 let mut markdown = String::new();
1935 let _ = writeln!(markdown, "# GPU Execution Report");
1936 let _ = writeln!(markdown);
1937 let _ = writeln!(markdown, "{EXPERIMENT_SENTENCE}");
1938 let _ = writeln!(markdown);
1939 let _ = writeln!(markdown, "measured_gpu: `{}`", metrics.measured_gpu);
1940 let _ = writeln!(markdown, "measurement_kind: `{}`", metrics.measurement_kind);
1941 let _ = writeln!(markdown, "kernel: `{}`", metrics.kernel);
1942 let _ = writeln!(markdown);
1943 if bundle.no_real_external_data_provided {
1944 let _ = writeln!(markdown, "{NO_REAL_EXTERNAL_DATA_PROVIDED}");
1945 let _ = writeln!(markdown);
1946 }
1947 let _ = writeln!(
1948 markdown,
1949 "| Capture | measured_gpu | adapter | backend | resolution | total_ms | dispatch_ms | readback_ms | trust_delta_vs_cpu | alpha_delta_vs_cpu | intervention_delta_vs_cpu |"
1950 );
1951 let _ = writeln!(
1952 markdown,
1953 "| --- | --- | --- | --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |"
1954 );
1955 for capture in &metrics.captures {
1956 let _ = writeln!(
1957 markdown,
1958 "| {} | {} | {} | {} | {}x{} | {} | {} | {} | {} | {} | {} |",
1959 capture.capture_label,
1960 capture.measured_gpu,
1961 capture.adapter.as_deref().unwrap_or("unavailable"),
1962 capture.backend.as_deref().unwrap_or("unavailable"),
1963 capture.resolution[0],
1964 capture.resolution[1],
1965 format_f64(capture.total_ms),
1966 format_f64(capture.dispatch_ms),
1967 format_f64(capture.readback_ms),
1968 format_f32(capture.mean_abs_trust_delta_vs_cpu),
1969 format_f32(capture.mean_abs_alpha_delta_vs_cpu),
1970 format_f32(capture.mean_abs_intervention_delta_vs_cpu),
1971 );
1972 }
1973 let _ = writeln!(markdown);
1974 let _ = writeln!(markdown, "## What Is Proven");
1975 let _ = writeln!(markdown);
1976 let _ = writeln!(
1977 markdown,
1978 "- The imported external buffers can execute through the same minimum host-realistic GPU kernel as the internal study."
1979 );
1980 let _ = writeln!(
1981 markdown,
1982 "- GPU-vs-CPU numerical deltas are recorded whenever a GPU adapter is available."
1983 );
1984 let _ = writeln!(markdown);
1985 let _ = writeln!(markdown, "## What Is Not Proven");
1986 let _ = writeln!(markdown);
1987 let _ = writeln!(
1988 markdown,
1989 "- This file does not prove production renderer integration or full engine-side GPU cost."
1990 );
1991 let _ = writeln!(
1992 markdown,
1993 "- If `measured_gpu` is `false`, the path is implemented but unmeasured in the current environment."
1994 );
1995 let _ = writeln!(markdown);
1996 let _ = writeln!(markdown, "## Remaining Blockers");
1997 let _ = writeln!(markdown);
1998 let _ = writeln!(
1999 markdown,
2000 "- Engine-exported captures on the target evaluation hardware still need GPU-side profiling."
2001 );
2002 fs::write(path, markdown)?;
2003 Ok(())
2004}
2005
2006fn write_demo_a_external_report(
2007 path: &Path,
2008 metrics: &ExternalDemoAMetrics,
2009 bundle: &ExternalCaptureBundle,
2010) -> Result<()> {
2011 if let Some(parent) = path.parent() {
2012 fs::create_dir_all(parent)?;
2013 }
2014 let mut markdown = String::new();
2015 let _ = writeln!(markdown, "# Demo A External Report");
2016 let _ = writeln!(markdown);
2017 let _ = writeln!(markdown, "{EXPERIMENT_SENTENCE}");
2018 let _ = writeln!(markdown);
2019 if bundle.no_real_external_data_provided {
2020 let _ = writeln!(markdown, "{NO_REAL_EXTERNAL_DATA_PROVIDED}");
2021 let _ = writeln!(markdown);
2022 }
2023 let has_sequence = metrics.captures.len() >= 2;
2024 let all_have_reference = metrics.captures.iter().all(|capture| capture.ground_truth_available);
2025 let _ = writeln!(
2026 markdown,
2027 "Point-vs-region disclosure is explicit per capture via the `point_vs_region` line below."
2028 );
2029 let _ = writeln!(markdown);
2030 let _ = writeln!(markdown, "{ROI_CONTRACT_STATEMENT}");
2031 let _ = writeln!(markdown);
2032 for capture in &metrics.captures {
2033 let source_capture = bundle
2034 .captures
2035 .iter()
2036 .find(|candidate| candidate.label == capture.capture_label)
2037 .ok_or_else(|| {
2038 Error::Message(format!(
2039 "Demo A capture {} was missing from the loaded bundle",
2040 capture.capture_label
2041 ))
2042 })?;
2043 let total_pixels = source_capture.inputs.width() * source_capture.inputs.height();
2044 let roi_ratio = capture.roi_pixels as f32 / total_pixels.max(1) as f32;
2045 let _ = writeln!(markdown, "## Capture `{}`", capture.capture_label);
2046 let _ = writeln!(markdown);
2047 let _ = writeln!(markdown, "- ROI source: `{}`", capture.roi_source);
2048 let _ = writeln!(markdown, "- ROI pixels: `{}`", capture.roi_pixels);
2049 let _ = writeln!(markdown, "- ROI coverage: `{:.2}%`", capture.roi_coverage * 100.0);
2050 let _ = writeln!(
2051 markdown,
2052 "- ROI baseline method: `{}`",
2053 capture.baseline_method_id
2054 );
2055 let _ = writeln!(
2056 markdown,
2057 "- reference_source: `{}`",
2058 capture.reference_source
2059 );
2060 let _ = writeln!(
2061 markdown,
2062 "- ground_truth_available: `{}`",
2063 capture.ground_truth_available
2064 );
2065 let _ = writeln!(markdown, "- metric_source: `{}`", capture.metric_source);
2066 let _ = writeln!(
2067 markdown,
2068 "- point_vs_region: `{}`",
2069 if roi_ratio < 0.05 {
2070 "point_like"
2071 } else {
2072 "region_like"
2073 }
2074 );
2075 let _ = writeln!(
2076 markdown,
2077 "- realism_stress_note: `{}`",
2078 if is_realism_stress_capture(source_capture) {
2079 "realism_stress_case"
2080 } else {
2081 "not_classified_as_realism_stress"
2082 }
2083 );
2084 let _ = writeln!(
2085 markdown,
2086 "- larger_roi_note: `{}`",
2087 if roi_ratio > 0.10 {
2088 "larger_roi_case"
2089 } else {
2090 "not_a_larger_roi_case"
2091 }
2092 );
2093 if !capture.ground_truth_available {
2094 let _ = writeln!(markdown, "- ground truth unavailable -> proxy metrics used");
2095 }
2096 let _ = writeln!(markdown);
2097 let _ = writeln!(
2098 markdown,
2099 "| Method | full-frame MAE | ROI MAE | non-ROI MAE | max error | temporal error accumulation | intervention rate |"
2100 );
2101 let _ = writeln!(markdown, "| --- | ---: | ---: | ---: | ---: | ---: | ---: |");
2102 for method in &capture.methods {
2103 let _ = writeln!(
2104 markdown,
2105 "| {} | {:.5} | {:.5} | {:.5} | {:.5} | {:.5} | {:.5} |",
2106 method.label,
2107 method.overall_mae,
2108 method.roi_mae,
2109 method.non_roi_mae,
2110 method.max_error,
2111 method.temporal_error_accumulation,
2112 method.intervention_rate
2113 );
2114 }
2115 let _ = writeln!(markdown);
2116 }
2117 let _ = writeln!(markdown, "## What Is Proven");
2118 let _ = writeln!(markdown);
2119 let _ = writeln!(
2120 markdown,
2121 "- The same DSFB host-minimum supervisory layer runs on imported buffers and can be compared against fixed alpha, a strong heuristic baseline, and an explicit DSFB + strong heuristic hybrid."
2122 );
2123 let _ = writeln!(
2124 markdown,
2125 "- ROI and non-ROI behavior remain separated on imported data."
2126 );
2127 if has_sequence {
2128 let _ = writeln!(
2129 markdown,
2130 "- The current real Unreal-native package also emits `figures/trust_temporal_trajectory.svg` and `figures/trust_temporal_trajectory.json` over the ordered capture sequence."
2131 );
2132 }
2133 let _ = writeln!(markdown);
2134 let _ = writeln!(markdown, "## What Is Not Proven");
2135 let _ = writeln!(markdown);
2136 if all_have_reference {
2137 let _ = writeln!(
2138 markdown,
2139 "- The current bundle measures against `reference_color`, but that reference is a higher-resolution exported Unreal proxy rather than a path-traced ground truth."
2140 );
2141 } else {
2142 let _ = writeln!(
2143 markdown,
2144 "- Without an optional reference frame, error is measured against the current-frame proxy rather than a high-spp reconstruction."
2145 );
2146 }
2147 let _ = writeln!(
2148 markdown,
2149 "- Even with a reference frame, this does not replace longer engine-side sequences."
2150 );
2151 if has_sequence {
2152 let _ = writeln!(
2153 markdown,
2154 "- The current bundle generates `figures/trust_histogram.svg`, `figures/trust_vs_error.svg`, `figures/trust_conditioned_error_map.png`, and `figures/trust_temporal_trajectory.svg`; these are calibration artifacts over a short five-frame sequence, not a broad temporal generalization claim."
2155 );
2156 } else {
2157 let _ = writeln!(
2158 markdown,
2159 "- The current bundle generates `figures/trust_histogram.svg`, `figures/trust_vs_error.svg`, and `figures/trust_conditioned_error_map.png`; for a single frame pair these are calibration diagnostics, not temporal claims."
2160 );
2161 }
2162 let _ = writeln!(markdown);
2163 let _ = writeln!(markdown, "## Remaining Blockers");
2164 let _ = writeln!(markdown);
2165 let _ = writeln!(
2166 markdown,
2167 "- Real engine capture sequences and longer temporal windows still need evaluation."
2168 );
2169 fs::write(path, markdown)?;
2170 Ok(())
2171}
2172
2173fn write_demo_b_external_report(
2174 path: &Path,
2175 metrics: &ExternalDemoBMetrics,
2176 bundle: &ExternalCaptureBundle,
2177) -> Result<()> {
2178 if let Some(parent) = path.parent() {
2179 fs::create_dir_all(parent)?;
2180 }
2181 let mut markdown = String::new();
2182 let _ = writeln!(markdown, "# Demo B External Report");
2183 let _ = writeln!(markdown);
2184 let _ = writeln!(markdown, "{EXPERIMENT_SENTENCE}");
2185 let _ = writeln!(markdown);
2186 if bundle.no_real_external_data_provided {
2187 let _ = writeln!(markdown, "{NO_REAL_EXTERNAL_DATA_PROVIDED}");
2188 let _ = writeln!(markdown);
2189 }
2190 let _ = writeln!(
2191 markdown,
2192 "Regime labels used in this report: `aliasing_limited`, `variance_limited`, `mixed_regime`."
2193 );
2194 let _ = writeln!(markdown);
2195 let _ = writeln!(markdown, "{ROI_CONTRACT_STATEMENT}");
2196 let _ = writeln!(markdown);
2197 for capture in &metrics.captures {
2198 let _ = writeln!(markdown, "## Capture `{}`", capture.capture_label);
2199 let _ = writeln!(markdown);
2200 let _ = writeln!(markdown, "- regime: `{}`", capture.regime);
2201 let _ = writeln!(markdown, "- metric_source: `{}`", capture.metric_source);
2202 let _ = writeln!(
2203 markdown,
2204 "- fixed_budget_equal: `{}`",
2205 capture.fixed_budget_equal
2206 );
2207 let _ = writeln!(markdown, "- ROI source: `{}`", capture.roi_source);
2208 let _ = writeln!(markdown, "- ROI pixels: `{}`", capture.roi_pixels);
2209 let _ = writeln!(markdown, "- ROI coverage: `{:.2}%`", capture.roi_coverage * 100.0);
2210 let _ = writeln!(
2211 markdown,
2212 "- ROI baseline method: `{}`",
2213 capture.baseline_method_id
2214 );
2215 let _ = writeln!(
2216 markdown,
2217 "- reference_source: `{}`",
2218 capture.reference_source
2219 );
2220 let _ = writeln!(markdown);
2221 let _ = writeln!(
2222 markdown,
2223 "| Policy | total samples | ROI error | global error | ROI mean spp | non-ROI mean spp |"
2224 );
2225 let _ = writeln!(markdown, "| --- | ---: | ---: | ---: | ---: | ---: |");
2226 for policy in &capture.policies {
2227 let _ = writeln!(
2228 markdown,
2229 "| {} | {} | {:.5} | {:.5} | {:.3} | {:.3} |",
2230 policy.label,
2231 policy.total_samples,
2232 policy.roi_mae,
2233 policy.overall_mae,
2234 policy.roi_mean_spp,
2235 policy.non_roi_mean_spp
2236 );
2237 }
2238 let uniform = capture
2239 .policies
2240 .iter()
2241 .find(|policy| policy.policy_id == AllocationPolicyId::Uniform.as_str());
2242 let dsfb = capture
2243 .policies
2244 .iter()
2245 .find(|policy| policy.policy_id == AllocationPolicyId::ImportedTrust.as_str());
2246 let combined = capture
2247 .policies
2248 .iter()
2249 .find(|policy| policy.policy_id == AllocationPolicyId::CombinedHeuristic.as_str());
2250 if let (Some(uniform), Some(dsfb), Some(combined)) = (uniform, dsfb, combined) {
2251 let _ = writeln!(markdown);
2252 let _ = writeln!(markdown, "Aliasing vs variance discussion:");
2253 if capture.regime == "aliasing_limited" {
2254 let _ = writeln!(
2255 markdown,
2256 "- This imported capture is primarily aliasing-limited. DSFB imported trust changed ROI proxy error from {:.5} for uniform to {:.5}, while combined heuristic reached {:.5}.",
2257 uniform.roi_mae, dsfb.roi_mae, combined.roi_mae
2258 );
2259 } else if capture.regime == "variance_limited" {
2260 let _ = writeln!(
2261 markdown,
2262 "- This imported capture is primarily variance-limited. Variance-like heuristics should remain competitive, and the imported trust result changed ROI proxy error from {:.5} for uniform to {:.5}, versus {:.5} for the combined heuristic.",
2263 uniform.roi_mae, dsfb.roi_mae, combined.roi_mae
2264 );
2265 } else {
2266 let _ = writeln!(
2267 markdown,
2268 "- This imported capture is mixed-regime. The DSFB imported-trust allocator reached {:.5} ROI proxy error, compared with {:.5} for uniform and {:.5} for the combined heuristic.",
2269 dsfb.roi_mae, uniform.roi_mae, combined.roi_mae
2270 );
2271 }
2272 if (dsfb.roi_mae - combined.roi_mae).abs() <= 1e-4 {
2273 let _ = writeln!(markdown, "- This capture is effectively a tie between DSFB imported trust and the strongest heuristic proxy.");
2274 } else if dsfb.roi_mae < combined.roi_mae {
2275 let _ = writeln!(
2276 markdown,
2277 "- DSFB imported trust wins on this imported capture under equal total budget."
2278 );
2279 } else {
2280 let _ = writeln!(markdown, "- The strongest heuristic proxy wins on this imported capture; that result is surfaced rather than hidden.");
2281 }
2282 }
2283 let _ = writeln!(markdown);
2284 }
2285 let _ = writeln!(markdown, "## What Is Proven");
2286 let _ = writeln!(markdown);
2287 let _ = writeln!(
2288 markdown,
2289 "- Imported captures can drive fixed-budget allocation policies, including DSFB imported trust and stronger heuristic competitors."
2290 );
2291 let _ = writeln!(
2292 markdown,
2293 "- Budget equality is enforced across all compared policies."
2294 );
2295 let _ = writeln!(markdown);
2296 let _ = writeln!(markdown, "## What Is Not Proven");
2297 let _ = writeln!(markdown);
2298 let _ = writeln!(
2299 markdown,
2300 "- This is still an allocation proxy because imported captures do not provide a live renderer or per-sample ground truth."
2301 );
2302 let _ = writeln!(
2303 markdown,
2304 "- It does not replace real engine-side fixed-budget sampling experiments."
2305 );
2306 let _ = writeln!(markdown);
2307 let _ = writeln!(markdown, "## Remaining Blockers");
2308 let _ = writeln!(markdown);
2309 let _ = writeln!(
2310 markdown,
2311 "- A renderer-integrated fixed-budget replay still needs to confirm the allocation story on real per-sample shading."
2312 );
2313 fs::write(path, markdown)?;
2314 Ok(())
2315}
2316
2317fn write_external_validation_report(
2318 path: &Path,
2319 bundle: &ExternalCaptureBundle,
2320 handoff: &ExternalHandoffMetrics,
2321 gpu: &ExternalGpuMetrics,
2322 demo_a: &ExternalDemoAMetrics,
2323 demo_b: &ExternalDemoBMetrics,
2324 scaling: &ExternalScalingMetrics,
2325) -> Result<()> {
2326 if let Some(parent) = path.parent() {
2327 fs::create_dir_all(parent)?;
2328 }
2329 let mut markdown = String::new();
2330 let _ = writeln!(markdown, "# External Validation Report");
2331 let _ = writeln!(markdown);
2332 let _ = writeln!(markdown, "{EXPERIMENT_SENTENCE}");
2333 let _ = writeln!(markdown);
2334 let _ = writeln!(markdown, "## Data Description");
2335 let _ = writeln!(markdown);
2336 let _ = writeln!(markdown, "- source_kind: `{}`", handoff.source_kind);
2337 let _ = writeln!(markdown, "- captures: `{}`", handoff.capture_count);
2338 let _ = writeln!(
2339 markdown,
2340 "- real_external_data_provided: `{}`",
2341 bundle.real_external_data_provided
2342 );
2343 let _ = writeln!(
2344 markdown,
2345 "- synthetic vs real: `{}`",
2346 if bundle.real_external_data_provided {
2347 "real external data"
2348 } else {
2349 "synthetic compatibility export"
2350 }
2351 );
2352 if bundle.no_real_external_data_provided {
2353 let _ = writeln!(markdown);
2354 let _ = writeln!(markdown, "{NO_REAL_EXTERNAL_DATA_PROVIDED}");
2355 }
2356 let _ = writeln!(markdown);
2357 let _ = writeln!(markdown, "## Pipeline Description");
2358 let _ = writeln!(markdown);
2359 let _ = writeln!(
2360 markdown,
2361 "- External replay uses the same DSFB host-minimum supervisory logic and the same minimum GPU kernel as the internal suite."
2362 );
2363 let _ = writeln!(
2364 markdown,
2365 "- Differences: imported buffers replace synthetic scene generation, and Demo B uses an allocation proxy because no live renderer samples are present."
2366 );
2367 let _ = writeln!(markdown, "- ROI contract: {ROI_CONTRACT_STATEMENT}");
2368 let _ = writeln!(markdown);
2369 let _ = writeln!(markdown, "## GPU Execution Summary");
2370 let _ = writeln!(markdown);
2371 let _ = writeln!(markdown, "- measured_gpu: `{}`", gpu.measured_gpu);
2372 let _ = writeln!(markdown, "- kernel: `{}`", gpu.kernel);
2373 for capture in &gpu.captures {
2374 let _ = writeln!(
2375 markdown,
2376 "- capture `{}`: adapter = `{}`, total_ms = {}, dispatch_ms = {}, readback_ms = {}",
2377 capture.capture_label,
2378 capture.adapter.as_deref().unwrap_or("unavailable"),
2379 format_f64(capture.total_ms),
2380 format_f64(capture.dispatch_ms),
2381 format_f64(capture.readback_ms),
2382 );
2383 }
2384 let _ = writeln!(markdown);
2385 let _ = writeln!(markdown, "## Demo A Results");
2386 let _ = writeln!(markdown);
2387 for capture in &demo_a.captures {
2388 let _ = writeln!(
2389 markdown,
2390 "- `{}`: ROI source = `{}`, ROI pixels = {}, ROI coverage = {:.2}%, metric_source = `{}`",
2391 capture.capture_label,
2392 capture.roi_source,
2393 capture.roi_pixels,
2394 capture.roi_coverage * 100.0,
2395 capture.metric_source
2396 );
2397 for method in &capture.methods {
2398 let _ = writeln!(
2399 markdown,
2400 " - {}: full-frame MAE = {:.5}, ROI MAE = {:.5}, non-ROI MAE = {:.5}, max error = {:.5}, temporal accumulation = {:.5}, intervention rate = {:.5}",
2401 method.label,
2402 method.overall_mae,
2403 method.roi_mae,
2404 method.non_roi_mae,
2405 method.max_error,
2406 method.temporal_error_accumulation,
2407 method.intervention_rate
2408 );
2409 }
2410 }
2411 let _ = writeln!(markdown);
2412 let _ = writeln!(markdown, "## Demo B Results");
2413 let _ = writeln!(markdown);
2414 for capture in &demo_b.captures {
2415 let _ = writeln!(
2416 markdown,
2417 "- `{}`: regime = `{}`, fixed_budget_equal = `{}`",
2418 capture.capture_label, capture.regime, capture.fixed_budget_equal
2419 );
2420 for policy in &capture.policies {
2421 let _ = writeln!(
2422 markdown,
2423 " - {}: ROI error = {:.5}, global error = {:.5}, ROI mean spp = {:.3}",
2424 policy.label, policy.roi_mae, policy.overall_mae, policy.roi_mean_spp
2425 );
2426 }
2427 }
2428 let _ = writeln!(markdown);
2429 let _ = writeln!(markdown, "## Scaling / Coverage Summary");
2430 let _ = writeln!(markdown);
2431 let _ = writeln!(markdown, "- attempted_1080p: `{}`", scaling.attempted_1080p);
2432 let _ = writeln!(markdown, "- attempted_4k: `{}`", scaling.attempted_4k);
2433 let _ = writeln!(
2434 markdown,
2435 "- realism_stress_case: `{}`",
2436 scaling.coverage.realism_stress_case
2437 );
2438 let _ = writeln!(
2439 markdown,
2440 "- larger_roi_case: `{}`",
2441 scaling.coverage.larger_roi_case
2442 );
2443 let _ = writeln!(
2444 markdown,
2445 "- mixed_regime_case: `{}`",
2446 scaling.coverage.mixed_regime_case
2447 );
2448 let _ = writeln!(
2449 markdown,
2450 "- coverage_status: `{}`",
2451 scaling.coverage.coverage_status
2452 );
2453 if !scaling.coverage.missing.is_empty() {
2454 let _ = writeln!(
2455 markdown,
2456 "- missing coverage labels: {}",
2457 scaling.coverage.missing.join(", ")
2458 );
2459 }
2460 let _ = writeln!(markdown);
2461 let _ = writeln!(markdown, "## What Is Proven");
2462 let _ = writeln!(markdown);
2463 let _ = writeln!(
2464 markdown,
2465 "- The crate can ingest external buffers through a strict manifest and run the DSFB host-minimum supervisory layer on them."
2466 );
2467 let _ = writeln!(
2468 markdown,
2469 "- The same GPU kernel can execute on imported buffers, with explicit measured-vs-unmeasured disclosure and isolated scaled-resolution probes when the standalone binary is available."
2470 );
2471 let _ = writeln!(
2472 markdown,
2473 "- ROI vs non-ROI reporting survives the external path, Demo B keeps equal budgets across stronger heuristic baselines, and Demo A now includes an explicit DSFB + strong heuristic hybrid."
2474 );
2475 let _ = writeln!(markdown);
2476 let _ = writeln!(markdown, "## What Is Not Proven");
2477 let _ = writeln!(markdown);
2478 let _ = writeln!(
2479 markdown,
2480 "- This report does not prove production-scene generalization."
2481 );
2482 let _ = writeln!(
2483 markdown,
2484 "- It does not prove engine integration unless real exported buffers are supplied."
2485 );
2486 let _ = writeln!(
2487 markdown,
2488 "- Demo B on imported captures remains an allocation proxy, not a renderer-integrated sampling benchmark."
2489 );
2490 if demo_a.captures.len() >= 2 {
2491 let _ = writeln!(
2492 markdown,
2493 "- The trust trajectory is now measured across an ordered five-frame real Unreal-native sequence, but that short sequence is still not enough to claim broad temporal calibration."
2494 );
2495 } else {
2496 let _ = writeln!(
2497 markdown,
2498 "- For the single checked-in Unreal-native sample, trust calibration artifacts are per-frame diagnostics only; no temporal trust trajectory claim is made."
2499 );
2500 }
2501 let _ = writeln!(markdown);
2502 let _ = writeln!(markdown, "## Remaining Blockers");
2503 let _ = writeln!(markdown);
2504 let _ = writeln!(markdown, "- engine-side GPU profiling on imported buffers");
2505 let _ = writeln!(
2506 markdown,
2507 "- renderer-integrated Demo B replay with per-sample budgets"
2508 );
2509 let _ = writeln!(markdown);
2510 let _ = writeln!(markdown, "## Next Required Experiment");
2511 let _ = writeln!(markdown);
2512 let _ = writeln!(
2513 markdown,
2514 "Move from the current five-frame exported Unreal-native sequence to a longer production-representative engine capture, preserve the same fixed ROI contract and baseline ladder, and confirm the trust trajectory plus scaled GPU timings on the target evaluation hardware."
2515 );
2516 fs::write(path, markdown)?;
2517 Ok(())
2518}
2519
2520fn copy_representative_figure_aliases(output_dir: &Path, figures_dir: &Path) -> Result<()> {
2521 for (source_name, target_name) in [
2522 ("current_color.png", "before_current_color.png"),
2523 ("reprojected_history.png", "before_history_color.png"),
2524 ("demo_a_dsfb.png", "after_dsfb_resolved.png"),
2525 ("trust_map.png", "trust_map.png"),
2526 ("intervention_map.png", "intervention_map.png"),
2527 ("roi_overlay.png", "roi_overlay.png"),
2528 ] {
2529 let source = figures_dir.join(source_name);
2530 if source.exists() {
2531 fs::copy(source, output_dir.join(target_name))?;
2532 }
2533 }
2534 Ok(())
2535}
2536
2537fn build_demo_a_method_metrics(
2538 method_id: &str,
2539 label: &str,
2540 resolved: &ImageFrame,
2541 reference: &ImageFrame,
2542 metric_source: &str,
2543 roi_mask: &[bool],
2544 intervention: &ScalarField,
2545) -> ExternalDemoAMethodMetrics {
2546 let error_field = absolute_error_field(resolved, reference);
2547 let non_roi_mask = invert_mask(roi_mask);
2548 ExternalDemoAMethodMetrics {
2549 method_id: method_id.to_string(),
2550 label: label.to_string(),
2551 metric_source: metric_source.to_string(),
2552 overall_mae: mean_abs_error(resolved, reference),
2553 roi_mae: mean_abs_error_over_mask(resolved, reference, roi_mask),
2554 non_roi_mae: mean_abs_error_over_mask(resolved, reference, &non_roi_mask),
2555 max_error: scalar_field_max(&error_field),
2556 temporal_error_accumulation: mean_abs_error(resolved, reference),
2557 intervention_rate: intervention.mean(),
2558 }
2559}
2560
2561pub(crate) fn capture_reference_frame_and_metric_source<'a>(
2562 capture: &'a ExternalLoadedCapture,
2563) -> (&'a ImageFrame, &'static str, &'static str) {
2564 if let Some(reference) = &capture.reference {
2565 (reference, "reference_color", "real_reference")
2566 } else {
2567 (
2568 &capture.inputs.current_color,
2569 "current_color_proxy",
2570 "current_color_proxy",
2571 )
2572 }
2573}
2574
2575pub(crate) fn absolute_error_field(frame_a: &ImageFrame, frame_b: &ImageFrame) -> ScalarField {
2576 let mut field = ScalarField::new(frame_a.width(), frame_a.height());
2577 for y in 0..frame_a.height() {
2578 for x in 0..frame_a.width() {
2579 field.set(x, y, frame_a.get(x, y).abs_diff(frame_b.get(x, y)));
2580 }
2581 }
2582 field
2583}
2584
2585pub(crate) fn scalar_field_max(field: &ScalarField) -> f32 {
2586 field.values().iter().copied().fold(0.0, f32::max)
2587}
2588
2589fn resolve_with_alpha(
2590 history: &ImageFrame,
2591 current: &ImageFrame,
2592 alpha: &ScalarField,
2593) -> ImageFrame {
2594 let mut frame = ImageFrame::new(current.width(), current.height());
2595 for y in 0..current.height() {
2596 for x in 0..current.width() {
2597 let blended = history.get(x, y).lerp(current.get(x, y), alpha.get(x, y));
2598 frame.set(x, y, blended);
2599 }
2600 }
2601 frame
2602}
2603
2604fn run_external_strong_heuristic(
2605 config: &DemoConfig,
2606 capture: &ExternalLoadedCapture,
2607) -> (ImageFrame, ScalarField, ScalarField) {
2608 let width = capture.inputs.width();
2609 let height = capture.inputs.height();
2610 let mut resolved = ImageFrame::new(width, height);
2611 let mut alpha = ScalarField::new(width, height);
2612 let mut response = ScalarField::new(width, height);
2613
2614 for y in 0..height {
2615 for x in 0..width {
2616 let index = y * width + x;
2617 let current = capture.inputs.current_color.get(x, y);
2618 let history = capture.inputs.reprojected_history.get(x, y);
2619 let clamped =
2620 clamp_to_current_neighborhood(&capture.inputs.current_color, history, x, y);
2621 let clamp_distance = clamped.abs_diff(history);
2622 let residual_gate = smoothstep(
2623 config.baseline.residual_threshold,
2624 current.abs_diff(clamped),
2625 );
2626 let depth_gate = smoothstep(
2627 config.baseline.depth_disagreement,
2628 (capture.inputs.current_depth[index] - capture.inputs.reprojected_depth[index])
2629 .abs(),
2630 );
2631 let normal_gate = smoothstep(
2632 config.baseline.normal_disagreement,
2633 1.0 - capture.inputs.current_normals[index]
2634 .dot(capture.inputs.reprojected_normals[index])
2635 .clamp(-1.0, 1.0),
2636 );
2637 let neighborhood_gate =
2638 smoothstep(config.baseline.neighborhood_distance, clamp_distance);
2639 let trigger = residual_gate
2640 .max(depth_gate)
2641 .max(normal_gate)
2642 .max(neighborhood_gate);
2643 let pixel_alpha = config.baseline.residual_alpha_range.min
2644 + (config.baseline.residual_alpha_range.max
2645 - config.baseline.residual_alpha_range.min)
2646 * trigger;
2647 alpha.set(x, y, pixel_alpha);
2648 response.set(x, y, trigger);
2649 resolved.set(x, y, clamped.lerp(current, pixel_alpha));
2650 }
2651 }
2652
2653 (resolved, alpha, response)
2654}
2655
2656fn run_external_dsfb_plus_strong_heuristic(
2657 capture: &ExternalLoadedCapture,
2658 dsfb_alpha: &ScalarField,
2659 dsfb_intervention: &ScalarField,
2660 strong_alpha: &ScalarField,
2661 strong_response: &ScalarField,
2662) -> (ImageFrame, ScalarField, ScalarField) {
2663 let width = capture.inputs.width();
2664 let height = capture.inputs.height();
2665 let mut resolved = ImageFrame::new(width, height);
2666 let mut alpha = ScalarField::new(width, height);
2667 let mut response = ScalarField::new(width, height);
2668
2669 for y in 0..height {
2670 for x in 0..width {
2671 let current = capture.inputs.current_color.get(x, y);
2672 let history = capture.inputs.reprojected_history.get(x, y);
2673 let clamped =
2674 clamp_to_current_neighborhood(&capture.inputs.current_color, history, x, y);
2675 let hybrid_alpha = dsfb_alpha.get(x, y).max(strong_alpha.get(x, y));
2676 let hybrid_response = dsfb_intervention.get(x, y).max(strong_response.get(x, y));
2677 alpha.set(x, y, hybrid_alpha);
2678 response.set(x, y, hybrid_response);
2679 resolved.set(x, y, clamped.lerp(current, hybrid_alpha));
2680 }
2681 }
2682
2683 (resolved, alpha, response)
2684}
2685
2686fn clamp_to_current_neighborhood(
2687 current: &ImageFrame,
2688 history: Color,
2689 x: usize,
2690 y: usize,
2691) -> Color {
2692 let mut min_r = f32::INFINITY;
2693 let mut min_g = f32::INFINITY;
2694 let mut min_b = f32::INFINITY;
2695 let mut max_r = f32::NEG_INFINITY;
2696 let mut max_g = f32::NEG_INFINITY;
2697 let mut max_b = f32::NEG_INFINITY;
2698 for dy in -1i32..=1 {
2699 for dx in -1i32..=1 {
2700 let sample = current.sample_clamped(x as i32 + dx, y as i32 + dy);
2701 min_r = min_r.min(sample.r);
2702 min_g = min_g.min(sample.g);
2703 min_b = min_b.min(sample.b);
2704 max_r = max_r.max(sample.r);
2705 max_g = max_g.max(sample.g);
2706 max_b = max_b.max(sample.b);
2707 }
2708 }
2709 Color::rgb(
2710 history.r.clamp(min_r, max_r),
2711 history.g.clamp(min_g, max_g),
2712 history.b.clamp(min_b, max_b),
2713 )
2714}
2715
2716fn smoothstep(threshold: SmoothstepThreshold, value: f32) -> f32 {
2717 let span = (threshold.high - threshold.low).max(1e-6);
2718 let t = ((value - threshold.low) / span).clamp(0.0, 1.0);
2719 t * t * (3.0 - 2.0 * t)
2720}
2721
2722pub(crate) fn roi_mask_for_capture(
2723 capture: &ExternalLoadedCapture,
2724 baseline: &ImageFrame,
2725 reference: &ImageFrame,
2726) -> (Vec<bool>, String, f32) {
2727 let contrast = local_contrast_field(reference);
2728 let mut mask = vec![false; capture.inputs.width() * capture.inputs.height()];
2729 for y in 0..capture.inputs.height() {
2730 for x in 0..capture.inputs.width() {
2731 let index = y * capture.inputs.width() + x;
2732 let baseline_error = baseline.get(x, y).abs_diff(reference.get(x, y));
2733 let threshold = ROI_CONTRACT_ALPHA * contrast.get(x, y);
2734 mask[index] = baseline_error > threshold;
2735 }
2736 }
2737 let coverage = mask.iter().filter(|value| **value).count() as f32 / mask.len().max(1) as f32;
2738 (mask, ROI_CONTRACT_SOURCE.to_string(), coverage)
2739}
2740
2741fn overlay_roi_mask(frame: &ImageFrame, mask: &[bool]) -> ImageFrame {
2742 let mut output = frame.clone();
2743 for y in 0..frame.height() {
2744 for x in 0..frame.width() {
2745 let index = y * frame.width() + x;
2746 if mask[index] {
2747 let current = frame.get(x, y);
2748 output.set(x, y, current.lerp(Color::rgb(0.12, 1.0, 0.24), 0.45));
2749 }
2750 }
2751 }
2752 output
2753}
2754
2755fn write_trust_histogram_figure(trust: &ScalarField, path: &Path) -> Result<()> {
2756 if let Some(parent) = path.parent() {
2757 fs::create_dir_all(parent)?;
2758 }
2759
2760 let bin_count = 10usize;
2761 let mut counts = vec![0usize; bin_count];
2762 for value in trust.values() {
2763 let bin = ((*value).clamp(0.0, 0.999_999) * bin_count as f32) as usize;
2764 counts[bin.min(bin_count - 1)] += 1;
2765 }
2766 let max_count = counts.iter().copied().max().unwrap_or(1).max(1) as f32;
2767 let mut bars = String::new();
2768 for (index, count) in counts.iter().enumerate() {
2769 let height = 260.0 * (*count as f32 / max_count);
2770 let x = 86.0 + index as f32 * 62.0;
2771 let y = 368.0 - height;
2772 let _ = writeln!(
2773 bars,
2774 r##"<rect x="{x:.1}" y="{y:.1}" width="42" height="{height:.1}" fill="#4cc9f0"/>"##
2775 );
2776 let _ = writeln!(
2777 bars,
2778 r##"<text x="{:.1}" y="392" font-size="13" font-family="Arial, Helvetica, sans-serif" fill="#c6d2dd">{:.1}</text>"##,
2779 x,
2780 index as f32 / bin_count as f32
2781 );
2782 }
2783
2784 let svg = format!(
2785 r##"<svg xmlns="http://www.w3.org/2000/svg" width="900" height="460" viewBox="0 0 900 460">
2786<rect width="900" height="460" fill="#0b1320"/>
2787<text x="36" y="42" font-size="28" font-family="Arial, Helvetica, sans-serif" fill="#f4f7fb">Trust Histogram</text>
2788<text x="36" y="68" font-size="16" font-family="Arial, Helvetica, sans-serif" fill="#c6d2dd">Per-pixel DSFB trust distribution for the canonical imported capture.</text>
2789<line x1="70" y1="96" x2="70" y2="368" stroke="#f4f7fb" stroke-width="2"/>
2790<line x1="70" y1="368" x2="760" y2="368" stroke="#f4f7fb" stroke-width="2"/>
2791{bars}
2792<text x="36" y="106" font-size="14" font-family="Arial, Helvetica, sans-serif" fill="#c6d2dd">count</text>
2793<text x="730" y="420" font-size="14" font-family="Arial, Helvetica, sans-serif" fill="#c6d2dd">trust</text>
2794</svg>"##
2795 );
2796 fs::write(path, svg)?;
2797 Ok(())
2798}
2799
2800fn write_trust_vs_error_figure(
2801 trust: &ScalarField,
2802 error: &ScalarField,
2803 metric_source: &str,
2804 path: &Path,
2805) -> Result<()> {
2806 if let Some(parent) = path.parent() {
2807 fs::create_dir_all(parent)?;
2808 }
2809
2810 let bin_count = 10usize;
2811 let mut counts = vec![0usize; bin_count];
2812 let mut means = vec![0.0f32; bin_count];
2813 for (trust_value, error_value) in trust.values().iter().zip(error.values()) {
2814 let bin = ((*trust_value).clamp(0.0, 0.999_999) * bin_count as f32) as usize;
2815 let index = bin.min(bin_count - 1);
2816 counts[index] += 1;
2817 means[index] += *error_value;
2818 }
2819 for (count, mean) in counts.iter().zip(means.iter_mut()) {
2820 if *count > 0 {
2821 *mean /= *count as f32;
2822 }
2823 }
2824 let max_error = means.iter().copied().fold(0.05, f32::max);
2825 let left = 82.0f32;
2826 let right = 780.0f32;
2827 let top = 92.0f32;
2828 let bottom = 366.0f32;
2829 let x_scale = (right - left) / (bin_count.saturating_sub(1)) as f32;
2830 let mut points = String::new();
2831 for (index, mean) in means.iter().enumerate() {
2832 let x = left + index as f32 * x_scale;
2833 let y = bottom - (mean / max_error.max(1e-6)) * (bottom - top);
2834 let _ = writeln!(points, "{x:.1},{y:.1} ");
2835 }
2836
2837 let svg = format!(
2838 r##"<svg xmlns="http://www.w3.org/2000/svg" width="920" height="460" viewBox="0 0 920 460">
2839<rect width="920" height="460" fill="#0b1320"/>
2840<text x="36" y="42" font-size="28" font-family="Arial, Helvetica, sans-serif" fill="#f4f7fb">Trust vs Error Curve</text>
2841<text x="36" y="68" font-size="16" font-family="Arial, Helvetica, sans-serif" fill="#c6d2dd">Mean per-pixel error by trust bin. Error source: {metric_source}.</text>
2842<line x1="{left}" y1="{top}" x2="{left}" y2="{bottom}" stroke="#f4f7fb" stroke-width="2"/>
2843<line x1="{left}" y1="{bottom}" x2="{right}" y2="{bottom}" stroke="#f4f7fb" stroke-width="2"/>
2844<polyline fill="none" stroke="#8bd450" stroke-width="3.5" points="{points}"/>
2845<text x="36" y="102" font-size="14" font-family="Arial, Helvetica, sans-serif" fill="#c6d2dd">mean error</text>
2846<text x="744" y="420" font-size="14" font-family="Arial, Helvetica, sans-serif" fill="#c6d2dd">trust bin</text>
2847</svg>"##
2848 );
2849 fs::write(path, svg)?;
2850 Ok(())
2851}
2852
2853fn save_trust_conditioned_error_map(
2854 trust: &ScalarField,
2855 error: &ScalarField,
2856 path: &Path,
2857) -> Result<()> {
2858 let mut conditioned = ScalarField::new(trust.width(), trust.height());
2859 for y in 0..trust.height() {
2860 for x in 0..trust.width() {
2861 conditioned.set(x, y, error.get(x, y) * (1.0 - trust.get(x, y)));
2862 }
2863 }
2864 save_scalar_field_png(&conditioned, path, heatmap_red)
2865}
2866
2867fn write_temporal_trust_trajectory_outputs(
2868 points: &[TemporalTrustTrajectoryPoint],
2869 figures_dir: &Path,
2870) -> Result<()> {
2871 let mut ordered = points.to_vec();
2872 ordered.sort_by_key(|point| point.frame_index);
2873 let peak_index = ordered
2874 .iter()
2875 .enumerate()
2876 .max_by(|(_, left), (_, right)| left.roi_coverage.total_cmp(&right.roi_coverage))
2877 .map(|(index, _)| index)
2878 .unwrap_or(0);
2879 let report = TemporalTrustTrajectoryReport {
2880 onset_capture_label: ordered
2881 .first()
2882 .map(|point| point.capture_label.clone())
2883 .unwrap_or_default(),
2884 peak_roi_capture_label: ordered[peak_index].capture_label.clone(),
2885 recovery_capture_label: ordered
2886 .last()
2887 .map(|point| point.capture_label.clone())
2888 .unwrap_or_default(),
2889 points: ordered.clone(),
2890 };
2891 fs::write(
2892 figures_dir.join("trust_temporal_trajectory.json"),
2893 serde_json::to_string_pretty(&report)?,
2894 )?;
2895 write_temporal_trust_trajectory_figure(
2896 &report,
2897 &figures_dir.join("trust_temporal_trajectory.svg"),
2898 )
2899}
2900
2901fn write_temporal_trust_trajectory_figure(
2902 report: &TemporalTrustTrajectoryReport,
2903 path: &Path,
2904) -> Result<()> {
2905 if let Some(parent) = path.parent() {
2906 fs::create_dir_all(parent)?;
2907 }
2908 let point_count = report.points.len().max(2);
2909 let width = 980.0f32;
2910 let height = 520.0f32;
2911 let left = 86.0f32;
2912 let right = 860.0f32;
2913 let top = 88.0f32;
2914 let bottom = 418.0f32;
2915 let inner_width = right - left;
2916 let inner_height = bottom - top;
2917 let x_scale = inner_width / (point_count.saturating_sub(1)) as f32;
2918 let max_roi_error = report
2919 .points
2920 .iter()
2921 .map(|point| point.dsfb_roi_mae.max(point.hybrid_roi_mae))
2922 .fold(1e-6f32, f32::max);
2923
2924 let trust_path = polyline_points(
2925 &report
2926 .points
2927 .iter()
2928 .enumerate()
2929 .map(|(index, point)| {
2930 (
2931 left + index as f32 * x_scale,
2932 bottom - point.mean_trust.clamp(0.0, 1.0) * inner_height,
2933 )
2934 })
2935 .collect::<Vec<_>>(),
2936 );
2937 let intervention_path = polyline_points(
2938 &report
2939 .points
2940 .iter()
2941 .enumerate()
2942 .map(|(index, point)| {
2943 (
2944 left + index as f32 * x_scale,
2945 bottom - point.intervention_rate.clamp(0.0, 1.0) * inner_height,
2946 )
2947 })
2948 .collect::<Vec<_>>(),
2949 );
2950 let roi_coverage_path = polyline_points(
2951 &report
2952 .points
2953 .iter()
2954 .enumerate()
2955 .map(|(index, point)| {
2956 (
2957 left + index as f32 * x_scale,
2958 bottom - point.roi_coverage.clamp(0.0, 1.0) * inner_height,
2959 )
2960 })
2961 .collect::<Vec<_>>(),
2962 );
2963 let dsfb_error_path = polyline_points(
2964 &report
2965 .points
2966 .iter()
2967 .enumerate()
2968 .map(|(index, point)| {
2969 (
2970 left + index as f32 * x_scale,
2971 bottom - (point.dsfb_roi_mae / max_roi_error) * inner_height,
2972 )
2973 })
2974 .collect::<Vec<_>>(),
2975 );
2976 let hybrid_error_path = polyline_points(
2977 &report
2978 .points
2979 .iter()
2980 .enumerate()
2981 .map(|(index, point)| {
2982 (
2983 left + index as f32 * x_scale,
2984 bottom - (point.hybrid_roi_mae / max_roi_error) * inner_height,
2985 )
2986 })
2987 .collect::<Vec<_>>(),
2988 );
2989 let peak_index = report
2990 .points
2991 .iter()
2992 .enumerate()
2993 .max_by(|(_, left), (_, right)| left.roi_coverage.total_cmp(&right.roi_coverage))
2994 .map(|(index, _)| index)
2995 .unwrap_or(0);
2996 let peak_x = left + peak_index as f32 * x_scale;
2997
2998 let mut labels = String::new();
2999 for (index, point) in report.points.iter().enumerate() {
3000 let x = left + index as f32 * x_scale;
3001 let _ = writeln!(
3002 labels,
3003 r##"<text x="{x:.1}" y="446" text-anchor="middle" font-size="13" font-family="Arial, Helvetica, sans-serif" fill="#c6d2dd">{}</text>"##,
3004 point.capture_label
3005 );
3006 }
3007
3008 let svg = format!(
3009 r##"<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}">
3010<rect width="{width}" height="{height}" fill="#0b1320"/>
3011<text x="34" y="42" font-size="28" font-family="Arial, Helvetica, sans-serif" fill="#f4f7fb">Temporal Trust Trajectory</text>
3012<text x="34" y="68" font-size="16" font-family="Arial, Helvetica, sans-serif" fill="#c6d2dd">Ordered real Unreal-native sequence from onset-side frame {onset} through peak ROI frame {peak} to recovery-side frame {recovery}.</text>
3013<line x1="{left}" y1="{top}" x2="{left}" y2="{bottom}" stroke="#f4f7fb" stroke-width="2"/>
3014<line x1="{left}" y1="{bottom}" x2="{right}" y2="{bottom}" stroke="#f4f7fb" stroke-width="2"/>
3015<path d="{trust_path}" fill="none" stroke="#4cc9f0" stroke-width="3.5"/>
3016<path d="{intervention_path}" fill="none" stroke="#ef476f" stroke-width="3.5" stroke-dasharray="10 8"/>
3017<path d="{roi_coverage_path}" fill="none" stroke="#8bd450" stroke-width="3.5"/>
3018<path d="{dsfb_error_path}" fill="none" stroke="#ffd166" stroke-width="3.5"/>
3019<path d="{hybrid_error_path}" fill="none" stroke="#f4978e" stroke-width="3.5" stroke-dasharray="6 6"/>
3020<line x1="{peak_x:.1}" y1="{top}" x2="{peak_x:.1}" y2="{bottom}" stroke="#f4f7fb" stroke-width="1.5" stroke-dasharray="8 6"/>
3021<text x="628" y="110" font-size="15" font-family="Arial, Helvetica, sans-serif" fill="#f4f7fb">Mean trust</text>
3022<line x1="566" y1="104" x2="616" y2="104" stroke="#4cc9f0" stroke-width="3.5"/>
3023<text x="628" y="136" font-size="15" font-family="Arial, Helvetica, sans-serif" fill="#f4f7fb">Intervention rate</text>
3024<line x1="566" y1="130" x2="616" y2="130" stroke="#ef476f" stroke-width="3.5" stroke-dasharray="10 8"/>
3025<text x="628" y="162" font-size="15" font-family="Arial, Helvetica, sans-serif" fill="#f4f7fb">ROI coverage</text>
3026<line x1="566" y1="156" x2="616" y2="156" stroke="#8bd450" stroke-width="3.5"/>
3027<text x="628" y="188" font-size="15" font-family="Arial, Helvetica, sans-serif" fill="#f4f7fb">DSFB ROI MAE / max</text>
3028<line x1="566" y1="182" x2="616" y2="182" stroke="#ffd166" stroke-width="3.5"/>
3029<text x="628" y="214" font-size="15" font-family="Arial, Helvetica, sans-serif" fill="#f4f7fb">Hybrid ROI MAE / max</text>
3030<line x1="566" y1="208" x2="616" y2="208" stroke="#f4978e" stroke-width="3.5" stroke-dasharray="6 6"/>
3031<text x="34" y="102" font-size="14" font-family="Arial, Helvetica, sans-serif" fill="#c6d2dd">normalized value</text>
3032<text x="788" y="470" font-size="14" font-family="Arial, Helvetica, sans-serif" fill="#c6d2dd">ordered capture label</text>
3033{labels}
3034</svg>"##,
3035 onset = report.onset_capture_label,
3036 peak = report.peak_roi_capture_label,
3037 recovery = report.recovery_capture_label,
3038 );
3039 fs::write(path, svg)?;
3040 Ok(())
3041}
3042
3043fn polyline_points(points: &[(f32, f32)]) -> String {
3044 let mut path = String::new();
3045 for (index, (x, y)) in points.iter().enumerate() {
3046 let command = if index == 0 { "M" } else { "L" };
3047 let _ = write!(path, "{command}{x:.1},{y:.1} ");
3048 }
3049 path
3050}
3051
3052fn constant_field(width: usize, height: usize, value: f32) -> ScalarField {
3053 ScalarField::from_values(width, height, vec![value; width * height])
3054}
3055
3056fn invert_mask(mask: &[bool]) -> Vec<bool> {
3057 mask.iter().map(|value| !*value).collect()
3058}
3059
3060fn normalize_field(field: ScalarField) -> ScalarField {
3061 let max_value = field
3062 .values()
3063 .iter()
3064 .copied()
3065 .fold(0.0f32, f32::max)
3066 .max(1e-6);
3067 let values = field
3068 .values()
3069 .iter()
3070 .map(|value| (*value / max_value).clamp(0.0, 1.0))
3071 .collect();
3072 ScalarField::from_values(field.width(), field.height(), values)
3073}
3074
3075fn temporal_variance_proxy(current: &ImageFrame, history: &ImageFrame) -> ScalarField {
3076 let mut field = ScalarField::new(current.width(), current.height());
3077 for y in 0..current.height() {
3078 for x in 0..current.width() {
3079 let diff = current.get(x, y).abs_diff(history.get(x, y));
3080 field.set(x, y, (diff / 0.20).clamp(0.0, 1.0));
3081 }
3082 }
3083 field
3084}
3085
3086fn predicted_error_over_mask(
3087 base_error: &ScalarField,
3088 counts: &[usize],
3089 roi_mask: Option<&[bool]>,
3090 squared: bool,
3091) -> f32 {
3092 let width = base_error.width();
3093 let height = base_error.height();
3094 let mut sum = 0.0;
3095 let mut count = 0usize;
3096 for y in 0..height {
3097 for x in 0..width {
3098 let index = y * width + x;
3099 let include = roi_mask.map(|mask| mask[index]).unwrap_or(true);
3100 if !include {
3101 continue;
3102 }
3103 let predicted = base_error.get(x, y) / (counts[index].max(1) as f32).sqrt();
3104 sum += if squared {
3105 predicted * predicted
3106 } else {
3107 predicted
3108 };
3109 count += 1;
3110 }
3111 }
3112 if count == 0 {
3113 0.0
3114 } else if squared {
3115 (sum / count as f32).sqrt()
3116 } else {
3117 sum / count as f32
3118 }
3119}
3120
3121fn classify_external_demo_b_regime(
3122 gradient: &ScalarField,
3123 variance: &ScalarField,
3124 contrast: &ScalarField,
3125) -> String {
3126 let gradient_mean = gradient.mean();
3127 let variance_mean = variance.mean();
3128 let contrast_mean = contrast.mean();
3129 if gradient_mean + contrast_mean > variance_mean * 1.45 {
3130 "aliasing_limited".to_string()
3131 } else if variance_mean > gradient_mean * 1.20 {
3132 "variance_limited".to_string()
3133 } else {
3134 "mixed_regime".to_string()
3135 }
3136}
3137
3138fn mean_abs_delta(left: &[f32], right: &[f32]) -> f32 {
3139 let count = left.len().min(right.len()).max(1);
3140 left.iter()
3141 .zip(right.iter())
3142 .map(|(lhs, rhs)| (lhs - rhs).abs())
3143 .sum::<f32>()
3144 / count as f32
3145}
3146
3147
3148fn format_f64(value: Option<f64>) -> String {
3149 value
3150 .map(|current| format!("{current:.4}"))
3151 .unwrap_or_else(|| "n/a".to_string())
3152}
3153
3154fn format_f32(value: Option<f32>) -> String {
3155 value
3156 .map(|current| format!("{current:.6}"))
3157 .unwrap_or_else(|| "n/a".to_string())
3158}
3159
3160fn heatmap_blue(value: f32) -> [u8; 4] {
3161 let v = (value.clamp(0.0, 1.0) * 255.0).round() as u8;
3162 [v / 4, v / 2, 255, 255]
3163}
3164
3165fn heatmap_orange(value: f32) -> [u8; 4] {
3166 let v = (value.clamp(0.0, 1.0) * 255.0).round() as u8;
3167 [255, v, 32, 255]
3168}
3169
3170fn heatmap_red(value: f32) -> [u8; 4] {
3171 let v = (value.clamp(0.0, 1.0) * 255.0).round() as u8;
3172 [255, 24, v / 2, 255]
3173}