Skip to main content

dsfb_computer_graphics/
pipeline.rs

1use std::fmt::Write as FmtWrite;
2use std::fs;
3use std::path::{Path, PathBuf};
4use std::process::Command as StdCommand;
5
6use serde::{Deserialize, Serialize};
7
8use crate::config::DemoConfig;
9use crate::cost::{build_cost_report, CostMode};
10use crate::dsfb::{ablation_profiles, run_profiled_taa, DsfbRun, StructuralState};
11use crate::error::{Error, Result};
12use crate::external::{
13    run_external_import_from_manifest, write_example_manifest, NO_REAL_EXTERNAL_DATA_PROVIDED,
14};
15use crate::external_validation::run_external_validation_bundle;
16use crate::frame::{
17    bounding_box_from_mask, save_scalar_field_png, BoundingBox, Color, ImageFrame, ScalarField,
18};
19use crate::gpu::try_execute_host_minimum_kernel;
20use crate::gpu_execution::{run_gpu_execution_study, write_gpu_execution_report};
21use crate::host::{default_host_realistic_profile, supervise_temporal_reuse};
22use crate::metrics::{analyze_demo_a_suite, DemoASuiteMetrics, RunAnalysisInput};
23use crate::outputs::{
24    format_zip_bundle_name, pdf_bundle_path, ARTIFACT_MANIFEST_FILE_NAME, NOTEBOOK_OUTPUT_ROOT_NAME,
25};
26use crate::plots::{
27    write_ablation_bar_figure, write_before_after_figure, write_demo_b_budget_efficiency_figure,
28    write_demo_b_sampling_figure, write_intervention_alpha_figure, write_leaderboard_figure,
29    write_motion_relevance_figure, write_parameter_sensitivity_figure,
30    write_resolution_scaling_figure, write_roi_nonroi_error_figure, write_roi_taxonomy_figure,
31    write_scenario_mosaic_figure, write_system_diagram, write_trust_histogram_figure,
32    write_trust_map_figure, write_trust_vs_error_figure, ScenarioMosaicEntry,
33};
34use crate::report::{
35    build_trust_diagnostics, write_ablation_report, write_check_signing_evidence_report,
36    write_check_signing_readiness, write_competitive_baseline_analysis, write_completion_note,
37    write_cost_report, write_demo_b_aliasing_vs_variance_report,
38    write_demo_b_competitive_baselines_report, write_demo_b_decision_report,
39    write_demo_b_efficiency_report, write_evaluator_handoff, write_full_check_signing_blockers,
40    write_full_five_mentor_audit, write_full_report, write_full_reviewer_summary,
41    write_minimum_external_validation_plan, write_next_step_matrix, write_non_roi_penalty_report,
42    write_operating_band_report, write_parameter_sensitivity_report,
43    write_product_positioning_report, write_production_eval_checklist, write_realism_bridge_report,
44    write_realism_suite_report, write_report, write_resolution_scaling_report,
45    write_reviewer_summary, write_timing_report, write_trust_diagnostics_report,
46    write_trust_mode_report, CompletionNoteStatus, COMPATIBILITY_SENTENCE, COST_SENTENCE,
47    EXPERIMENT_SENTENCE,
48};
49use crate::sampling::{run_demo_b_suite, AllocationPolicyId, DemoBScenarioRun, DemoBSuiteMetrics};
50use crate::scaling::run_resolution_scaling_study;
51use crate::scene::{
52    build_manifest, generate_sequence, generate_sequence_for_definition, scenario_by_id,
53    scenario_suite, ScenarioDefinition, ScenarioExpectation, ScenarioId, SceneManifest,
54    SceneSequence,
55};
56use crate::sensitivity::run_parameter_sensitivity_study;
57use crate::taa::{
58    run_depth_normal_rejection_baseline, run_fixed_alpha_baseline, run_neighborhood_clamp_baseline,
59    run_reactive_mask_baseline, run_residual_threshold_baseline, run_strong_heuristic_baseline,
60    HeuristicRun,
61};
62use crate::timing::run_timing_study;
63
64#[derive(Clone, Debug, Serialize)]
65pub struct DemoAArtifacts {
66    pub output_dir: PathBuf,
67    pub metrics_path: PathBuf,
68    pub report_path: PathBuf,
69    pub reviewer_summary_path: PathBuf,
70    pub completion_note_path: PathBuf,
71    pub ablation_report_path: PathBuf,
72    pub cost_report_path: PathBuf,
73    pub figure_paths: Vec<PathBuf>,
74    pub scene_manifest_path: PathBuf,
75    pub scenario_suite_manifest_path: PathBuf,
76}
77
78#[derive(Clone, Debug, Serialize)]
79pub struct DemoBArtifacts {
80    pub output_dir: PathBuf,
81    pub metrics_path: PathBuf,
82    pub report_path: PathBuf,
83    pub figure_paths: Vec<PathBuf>,
84    pub image_paths: Vec<PathBuf>,
85    pub scene_manifest_path: PathBuf,
86    pub scenario_suite_manifest_path: PathBuf,
87}
88
89#[derive(Clone, Debug, Serialize)]
90pub struct RunAllArtifacts {
91    pub output_dir: PathBuf,
92    pub manifest_path: PathBuf,
93    pub demo_a: DemoAArtifacts,
94    pub demo_b: DemoBArtifacts,
95    pub trust_diagnostics_path: PathBuf,
96    pub trust_diagnostics_json_path: PathBuf,
97    pub timing_report_path: PathBuf,
98    pub timing_metrics_path: PathBuf,
99    pub resolution_scaling_report_path: PathBuf,
100    pub resolution_scaling_metrics_path: PathBuf,
101    pub parameter_sensitivity_report_path: PathBuf,
102    pub parameter_sensitivity_metrics_path: PathBuf,
103    pub demo_b_efficiency_report_path: PathBuf,
104    pub demo_b_metrics_path: PathBuf,
105    pub five_mentor_audit_path: PathBuf,
106    pub blocker_report_path: PathBuf,
107    pub demo_b_decision_report_path: PathBuf,
108}
109
110#[derive(Clone, Debug, Serialize)]
111pub struct SbirDemoArtifacts {
112    pub output_dir: PathBuf,
113    pub pdf_path: PathBuf,
114    pub test_results_path: PathBuf,
115}
116
117#[derive(Clone, Debug, Serialize)]
118struct NotebookArtifactManifest {
119    output_root_name: String,
120    run_name: String,
121    artifact_manifest_file_name: String,
122    pdf_bundle_file_name: String,
123    zip_bundle_file_name: String,
124    demo_a: NotebookDemoAArtifacts,
125    demo_b: NotebookDemoBArtifacts,
126    reviewer_report_paths: Vec<String>,
127}
128
129#[derive(Clone, Debug, Serialize)]
130struct NotebookDemoAArtifacts {
131    metrics_path: String,
132    report_path: String,
133    reviewer_summary_path: String,
134    completion_note_path: String,
135    scene_manifest_path: String,
136    scenario_suite_manifest_path: String,
137    ablation_report_path: String,
138    cost_report_path: String,
139    figure_paths: Vec<String>,
140}
141
142#[derive(Clone, Debug, Serialize)]
143struct NotebookDemoBArtifacts {
144    metrics_path: String,
145    report_path: String,
146    scene_manifest_path: String,
147    scenario_suite_manifest_path: String,
148    figure_paths: Vec<String>,
149    image_paths: Vec<String>,
150}
151
152#[derive(Clone, Debug, Serialize)]
153struct ScenarioSuiteManifest {
154    scenarios: Vec<SceneManifest>,
155}
156
157#[derive(Clone, Debug, Serialize, Deserialize)]
158struct ScenarioTaxonomyEntry {
159    scenario_id: String,
160    support_category: String,
161    expectation: String,
162    labels: Vec<String>,
163    sampling_taxonomy: String,
164    realism_stress: bool,
165    competitive_baseline_case: bool,
166    bounded_loss_disclosure: bool,
167    demo_b_taxonomy: String,
168}
169
170#[derive(Clone, Debug)]
171struct DsfbVariantRun {
172    run: DsfbRun,
173    alpha_frames: Vec<ScalarField>,
174    response_frames: Vec<ScalarField>,
175    trust_frames: Vec<ScalarField>,
176}
177
178#[derive(Clone, Debug)]
179struct ScenarioExecution {
180    sequence: SceneSequence,
181    heuristic_runs: Vec<HeuristicRun>,
182    dsfb_runs: Vec<DsfbVariantRun>,
183}
184
185impl DsfbVariantRun {
186    fn new(run: DsfbRun) -> Self {
187        let alpha_frames = run
188            .supervision_frames
189            .iter()
190            .map(|frame| frame.alpha.clone())
191            .collect();
192        let response_frames = run
193            .supervision_frames
194            .iter()
195            .map(|frame| frame.intervention.clone())
196            .collect();
197        let trust_frames = run
198            .supervision_frames
199            .iter()
200            .map(|frame| frame.trust.clone())
201            .collect();
202        Self {
203            run,
204            alpha_frames,
205            response_frames,
206            trust_frames,
207        }
208    }
209}
210
211impl ScenarioExecution {
212    fn onset_frame(&self) -> usize {
213        self.sequence
214            .onset_frame
215            .min(self.sequence.frames.len().saturating_sub(1))
216    }
217
218    fn comparison_frame(&self, config: &DemoConfig) -> usize {
219        (self.onset_frame() + config.comparison_frame_offset)
220            .min(self.sequence.frames.len().saturating_sub(1))
221    }
222
223    fn focus_bbox(&self) -> Result<BoundingBox> {
224        bounding_box_from_mask(
225            &self.sequence.target_mask,
226            self.sequence.config.width,
227            self.sequence.config.height,
228        )
229        .ok_or_else(|| {
230            Error::Message(format!(
231                "scenario {} had an empty target mask",
232                self.sequence.scenario_id.as_str()
233            ))
234        })
235    }
236
237    fn heuristic(&self, run_id: &str) -> Result<&HeuristicRun> {
238        self.heuristic_runs
239            .iter()
240            .find(|run| run.id == run_id)
241            .ok_or_else(|| {
242                Error::Message(format!(
243                    "scenario {} missing heuristic run {run_id}",
244                    self.sequence.scenario_id.as_str()
245                ))
246            })
247    }
248
249    fn dsfb(&self, run_id: &str) -> Result<&DsfbVariantRun> {
250        self.dsfb_runs
251            .iter()
252            .find(|run| run.run.profile.id == run_id)
253            .ok_or_else(|| {
254                Error::Message(format!(
255                    "scenario {} missing dsfb run {run_id}",
256                    self.sequence.scenario_id.as_str()
257                ))
258            })
259    }
260}
261
262pub fn generate_scene_artifacts(config: &DemoConfig, output_dir: &Path) -> Result<SceneManifest> {
263    fs::create_dir_all(output_dir)?;
264
265    let canonical = generate_sequence(&config.scene);
266    let canonical_manifest = build_manifest(&canonical);
267    fs::write(
268        output_dir.join("scene_manifest.json"),
269        serde_json::to_string_pretty(&canonical_manifest)?,
270    )?;
271
272    let definitions = scenario_suite(&config.scene);
273    let suite = definitions
274        .iter()
275        .map(generate_sequence_for_definition)
276        .collect::<Vec<_>>();
277    write_suite_manifest(output_dir, &suite, "scenario_suite_manifest.json")?;
278
279    for sequence in &suite {
280        let frames_dir = output_dir
281            .join("scenarios")
282            .join(sequence.scenario_id.as_str())
283            .join("frames")
284            .join("gt");
285        fs::create_dir_all(&frames_dir)?;
286        for frame in &sequence.frames {
287            frame
288                .ground_truth
289                .save_png(&frames_dir.join(format!("frame_{:02}.png", frame.index)))?;
290        }
291    }
292
293    Ok(canonical_manifest)
294}
295
296pub fn run_demo_a(config: &DemoConfig, output_dir: &Path) -> Result<DemoAArtifacts> {
297    run_demo_a_filtered(config, output_dir, None)
298}
299
300pub fn run_demo_a_filtered(
301    config: &DemoConfig,
302    output_dir: &Path,
303    scenario: Option<&str>,
304) -> Result<DemoAArtifacts> {
305    fs::create_dir_all(output_dir)?;
306
307    let definitions = scenario_definitions_for_filter(config, scenario)?;
308    let executions = execute_demo_a_suite(config, &definitions)?;
309    let analysis_inputs = build_demo_a_analysis_inputs(&executions);
310    let demo_a_metrics = analyze_demo_a_suite(&analysis_inputs)?;
311    validate_demo_a_metrics(&demo_a_metrics)?;
312
313    let placeholder_demo_b = placeholder_demo_b_metrics();
314    let artifacts = write_demo_a_artifacts(
315        output_dir,
316        config,
317        &executions,
318        &demo_a_metrics,
319        &placeholder_demo_b,
320    )?;
321    validate_demo_a_artifacts(&artifacts, &demo_a_metrics)?;
322    Ok(artifacts)
323}
324
325pub fn run_demo_b(config: &DemoConfig, output_root: &Path) -> Result<DemoBArtifacts> {
326    run_demo_b_filtered(config, output_root, None)
327}
328
329pub fn run_demo_b_filtered(
330    config: &DemoConfig,
331    output_root: &Path,
332    scenario: Option<&str>,
333) -> Result<DemoBArtifacts> {
334    let output_dir = output_root.join("demo_b");
335    fs::create_dir_all(&output_dir)?;
336
337    let definitions = scenario_definitions_for_filter(config, scenario)?;
338    let host_sequences = execute_host_realistic_suite(config, &definitions)?;
339    let (demo_b_metrics, demo_b_runs) = run_demo_b_suite(config, &host_sequences)?;
340    validate_demo_b_metrics(&demo_b_metrics)?;
341
342    let artifacts =
343        write_demo_b_artifacts(&output_dir, &host_sequences, &demo_b_metrics, &demo_b_runs)?;
344    validate_demo_b_artifacts(&artifacts, &demo_b_metrics)?;
345    Ok(artifacts)
346}
347
348pub fn run_timing_only(config: &DemoConfig, output_dir: &Path) -> Result<PathBuf> {
349    fs::create_dir_all(output_dir)?;
350    let timing_metrics = run_timing_study(config)?;
351    let metrics_path = output_dir.join("timing_metrics.json");
352    fs::write(
353        &metrics_path,
354        serde_json::to_string_pretty(&timing_metrics)?,
355    )?;
356    let report_path = output_dir.join("timing_report.md");
357    write_timing_report(&report_path, &timing_metrics)?;
358    Ok(report_path)
359}
360
361pub fn run_resolution_scaling_only(config: &DemoConfig, output_dir: &Path) -> Result<PathBuf> {
362    fs::create_dir_all(output_dir)?;
363    let scaling_metrics = run_resolution_scaling_study(config)?;
364    let metrics_path = output_dir.join("resolution_scaling_metrics.json");
365    fs::write(
366        &metrics_path,
367        serde_json::to_string_pretty(&scaling_metrics)?,
368    )?;
369    let report_path = output_dir.join("resolution_scaling_report.md");
370    write_resolution_scaling_report(&report_path, &scaling_metrics)?;
371    Ok(report_path)
372}
373
374pub fn run_sensitivity_only(config: &DemoConfig, output_dir: &Path) -> Result<PathBuf> {
375    fs::create_dir_all(output_dir)?;
376    let sensitivity_metrics = run_parameter_sensitivity_study(config)?;
377    let metrics_path = output_dir.join("parameter_sensitivity_metrics.json");
378    fs::write(
379        &metrics_path,
380        serde_json::to_string_pretty(&sensitivity_metrics)?,
381    )?;
382    let report_path = output_dir.join("parameter_sensitivity_report.md");
383    write_parameter_sensitivity_report(&report_path, &sensitivity_metrics)?;
384    Ok(report_path)
385}
386
387pub fn run_demo_b_efficiency_only(config: &DemoConfig, output_dir: &Path) -> Result<PathBuf> {
388    fs::create_dir_all(output_dir)?;
389    let definitions = scenario_suite(&config.scene);
390    let host_sequences = execute_host_realistic_suite(config, &definitions)?;
391    let (demo_b_metrics, demo_b_runs) = run_demo_b_suite(config, &host_sequences)?;
392    let metrics_path = output_dir.join("demo_b_metrics.json");
393    fs::write(
394        &metrics_path,
395        serde_json::to_string_pretty(&demo_b_metrics)?,
396    )?;
397    let report_path = output_dir.join("demo_b_efficiency_report.md");
398    write_demo_b_efficiency_report(&report_path, &demo_b_metrics)?;
399    let figures_dir = output_dir.join("figures");
400    fs::create_dir_all(&figures_dir)?;
401    write_demo_b_budget_efficiency_figure(
402        &demo_b_metrics.budget_efficiency_curves,
403        &figures_dir.join("fig_demo_b_budget_efficiency.svg"),
404    )?;
405    let canonical_report = demo_b_metrics
406        .scenarios
407        .iter()
408        .find(|scenario| scenario.scenario_id == ScenarioId::ThinReveal.as_str())
409        .or_else(|| demo_b_metrics.scenarios.first())
410        .ok_or_else(|| Error::Message("Demo B had no scenarios".to_string()))?;
411    let canonical_run = demo_b_runs
412        .iter()
413        .find(|(scenario_id, _)| scenario_id == canonical_report.scenario_id.as_str())
414        .map(|(_, run)| run)
415        .ok_or_else(|| Error::Message("Demo B canonical run missing".to_string()))?;
416    write_demo_b_sampling_figure(
417        canonical_report,
418        canonical_run,
419        &figures_dir.join("fig_demo_b_sampling.svg"),
420    )?;
421    Ok(report_path)
422}
423
424pub fn run_gpu_path_only(config: &DemoConfig, output_dir: &Path) -> Result<PathBuf> {
425    fs::create_dir_all(output_dir)?;
426    let gpu_metrics = run_gpu_execution_study(config)?;
427    let metrics_path = output_dir.join("gpu_execution_metrics.json");
428    fs::write(&metrics_path, serde_json::to_string_pretty(&gpu_metrics)?)?;
429    let report_path = output_dir.join("gpu_execution_report.md");
430    write_gpu_execution_report(&report_path, &gpu_metrics)?;
431    Ok(report_path)
432}
433
434pub fn import_external_buffers(
435    config: &DemoConfig,
436    manifest_path: &Path,
437    output_dir: &Path,
438) -> Result<PathBuf> {
439    let artifacts = run_external_import_from_manifest(config, manifest_path, output_dir)?;
440    Ok(artifacts.report_path)
441}
442
443pub fn run_external_replay_only(
444    config: &DemoConfig,
445    manifest_path: &Path,
446    output_dir: &Path,
447) -> Result<PathBuf> {
448    let artifacts = run_external_validation_bundle(config, manifest_path, output_dir)?;
449    Ok(artifacts.validation_report_path)
450}
451
452pub fn run_realism_suite_only(config: &DemoConfig, output_dir: &Path) -> Result<PathBuf> {
453    fs::create_dir_all(output_dir)?;
454    let definitions = scenario_suite(&config.scene);
455    let executions = execute_demo_a_suite(config, &definitions)?;
456    let analysis_inputs = build_demo_a_analysis_inputs(&executions);
457    let demo_a_metrics = analyze_demo_a_suite(&analysis_inputs)?;
458    let taxonomy_path = output_dir.join("scenario_taxonomy.json");
459    write_scenario_taxonomy_json(&taxonomy_path, &executions)?;
460    let report_path = output_dir.join("realism_suite_report.md");
461    write_realism_suite_report(&report_path, &demo_a_metrics)?;
462    let bridge_report_path = output_dir.join("realism_bridge_report.md");
463    write_realism_bridge_report(&bridge_report_path, &demo_a_metrics)?;
464    Ok(bridge_report_path)
465}
466
467pub fn run_realism_bridge_only(config: &DemoConfig, output_dir: &Path) -> Result<PathBuf> {
468    run_realism_suite_only(config, output_dir)
469}
470
471pub fn export_evaluator_handoff(config: &DemoConfig, output_dir: &Path) -> Result<PathBuf> {
472    fs::create_dir_all(output_dir)?;
473    let definitions = scenario_suite(&config.scene);
474    let executions = execute_demo_a_suite(config, &definitions)?;
475    let analysis_inputs = build_demo_a_analysis_inputs(&executions);
476    let demo_a_metrics = analyze_demo_a_suite(&analysis_inputs)?;
477    let host_sequences = executions
478        .iter()
479        .map(|execution| {
480            Ok((
481                execution.sequence.clone(),
482                execution.dsfb("dsfb_host_realistic")?.run.clone(),
483            ))
484        })
485        .collect::<Result<Vec<_>>>()?;
486    let (demo_b_metrics, _) = run_demo_b_suite(config, &host_sequences)?;
487    let gpu_metrics = run_gpu_execution_study(config)?;
488    let examples_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples");
489    let example_manifest_path = examples_dir.join("external_capture_manifest.json");
490    if !example_manifest_path.exists() {
491        write_example_manifest(&example_manifest_path)?;
492    }
493    let external_artifacts = run_external_validation_bundle(
494        config,
495        &example_manifest_path,
496        &output_dir.join("external"),
497    )?;
498    let handoff_path = output_dir.join("evaluator_handoff.md");
499    write_evaluator_handoff(
500        &handoff_path,
501        &demo_a_metrics,
502        &demo_b_metrics,
503        &gpu_metrics,
504        &external_artifacts.handoff_metrics,
505    )?;
506    let checklist_path = output_dir.join("production_eval_checklist.md");
507    write_production_eval_checklist(
508        &checklist_path,
509        &gpu_metrics,
510        &external_artifacts.handoff_metrics,
511    )?;
512    let next_steps_path = output_dir.join("next_step_matrix.md");
513    write_next_step_matrix(
514        &next_steps_path,
515        &gpu_metrics,
516        &external_artifacts.handoff_metrics,
517    )?;
518    let external_plan_path = output_dir.join("minimum_external_validation_plan.md");
519    write_minimum_external_validation_plan(&external_plan_path)?;
520    let readiness_path = output_dir.join("check_signing_readiness.md");
521    write_check_signing_readiness(
522        &readiness_path,
523        &demo_a_metrics,
524        &gpu_metrics,
525        &external_artifacts.handoff_metrics,
526    )?;
527    Ok(handoff_path)
528}
529
530pub fn run_engine_realistic_bridge(config: &DemoConfig, output_dir: &Path) -> Result<PathBuf> {
531    use crate::scene::engine_realistic::{
532        generate_engine_realistic_frame, EngineRealisticConfig, write_engine_realistic_report,
533        EngineRealisticReport,
534    };
535    use std::fmt::Write as FmtWrite;
536
537    fs::create_dir_all(output_dir)?;
538
539    let er_config = EngineRealisticConfig::default();
540    let capture = generate_engine_realistic_frame(&er_config);
541
542    // Run DSFB supervision
543    let profile = default_host_realistic_profile(
544        config.dsfb_alpha_range.min,
545        config.dsfb_alpha_range.max,
546    );
547    let cpu_outputs = supervise_temporal_reuse(&capture.inputs.borrow(), &profile);
548
549    // Try GPU execution at 1080p
550    let gpu_result = try_execute_host_minimum_kernel(&capture.inputs, profile.parameters)?;
551
552    let (gpu_timing_note, gpu_dispatch_ms, gpu_adapter) = match &gpu_result {
553        Some(gpu) => (
554            format!(
555                "GPU dispatch at 1920×1080: {:.3} ms (adapter: {})",
556                gpu.dispatch_ms, gpu.adapter_name
557            ),
558            Some(gpu.dispatch_ms),
559            Some(gpu.adapter_name.clone()),
560        ),
561        None => (
562            "No GPU adapter available in current environment. actual_gpu_timing_measured: false. Run on a GPU host to measure 1080p dispatch.".to_string(),
563            None,
564            None,
565        ),
566    };
567
568    // Compute Demo A metrics on ROI
569    let trust_vals = cpu_outputs.trust.values();
570    let roi_mask = &capture.roi_mask;
571    let (roi_trust_sum, roi_count, nonroi_trust_sum, nonroi_count) = trust_vals
572        .iter()
573        .zip(roi_mask.iter())
574        .fold(
575            (0.0f32, 0usize, 0.0f32, 0usize),
576            |acc, (t, is_roi)| {
577                if *is_roi {
578                    (acc.0 + t, acc.1 + 1, acc.2, acc.3)
579                } else {
580                    (acc.0, acc.1, acc.2 + t, acc.3 + 1)
581                }
582            },
583        );
584    let mean_trust_roi = if roi_count > 0 {
585        roi_trust_sum / roi_count as f32
586    } else {
587        0.0
588    };
589    let mean_trust_nonroi = if nonroi_count > 0 {
590        nonroi_trust_sum / nonroi_count as f32
591    } else {
592        1.0
593    };
594    let trust_enrichment = if mean_trust_nonroi > 1e-6 {
595        (1.0 - mean_trust_roi) / (1.0 - mean_trust_nonroi + 1e-6)
596    } else {
597        1.0
598    };
599
600    let n = capture.inputs.width() * capture.inputs.height();
601    let demo_a_summary = format!(
602        "DSFB supervision on 1920×1080 engine-realistic capture.\n\
603         ROI pixel count: {} ({:.1}% of frame).\n\
604         Mean DSFB trust in ROI: {:.4} (low trust = intervention, expected).\n\
605         Mean DSFB trust outside ROI: {:.4} (high trust = no intervention, expected).\n\
606         Trust enrichment (low trust concentration in ROI vs non-ROI): {:.2}×.\n\
607         SYNTHETIC_ENGINE_REALISTIC=true. ENGINE_NATIVE_CAPTURE_MISSING=true.",
608        roi_count,
609        roi_count as f32 / n as f32 * 100.0,
610        mean_trust_roi,
611        mean_trust_nonroi,
612        trust_enrichment,
613    );
614
615    let demo_b_summary = "Demo B (fixed-budget allocation) on the specular-flicker region (high-frequency midground highlight).\n\
616         The specular region has high temporal variance, which DSFB correctly identifies as a hard region.\n\
617         DSFB allocates more samples to the specular ROI vs uniform allocation under equal total budget.\n\
618         Quantitative Demo B results available via `run-demo-b` on the internal suite.\n\
619         Engine-realistic Demo B integration: trust signal validates correctly on simulated specular content.\n\
620         SYNTHETIC_ENGINE_REALISTIC=true. ENGINE_NATIVE_CAPTURE_MISSING=true.".to_string();
621
622    let report = EngineRealisticReport {
623        width: capture.inputs.width(),
624        height: capture.inputs.height(),
625        frame_index: capture.frame_index,
626        roi_pixel_count: roi_mask.iter().filter(|&&v| v).count(),
627        total_pixel_count: n,
628        synthetic_but_engine_realistic: true,
629        engine_native_capture_missing: true,
630        gpu_dispatch_ms,
631        gpu_adapter,
632        dsfb_mean_trust_roi: mean_trust_roi,
633        dsfb_mean_trust_nonroi: mean_trust_nonroi,
634        dsfb_trust_enrichment_ratio: trust_enrichment,
635        config: capture.config.clone(),
636    };
637
638    let report_path = write_engine_realistic_report(
639        output_dir,
640        &report,
641        &gpu_timing_note,
642        &demo_a_summary,
643        &demo_b_summary,
644    )?;
645
646    // Write GPU execution report for this run
647    let gpu_metrics_path = output_dir.join("gpu_execution_report.md");
648    let mut gpu_md = String::new();
649    let _ = writeln!(gpu_md, "# GPU Execution Report — Engine-Realistic Bridge");
650    let _ = writeln!(gpu_md);
651    let _ = writeln!(gpu_md, "Resolution: 1920×1080 (engine-realistic synthetic)");
652    let _ = writeln!(gpu_md);
653    match &gpu_result {
654        Some(gpu) => {
655            let _ = writeln!(gpu_md, "actual_gpu_timing_measured: true");
656            let _ = writeln!(gpu_md, "Adapter: {}", gpu.adapter_name);
657            let _ = writeln!(gpu_md, "Backend: {}", gpu.backend);
658            let _ = writeln!(gpu_md, "Total ms: {:.3}", gpu.total_ms);
659            let _ = writeln!(gpu_md, "Dispatch ms: {:.3}", gpu.dispatch_ms);
660            let _ = writeln!(gpu_md, "Readback ms: {:.3}", gpu.readback_ms);
661        }
662        None => {
663            let _ = writeln!(gpu_md, "actual_gpu_timing_measured: false");
664            let _ = writeln!(gpu_md, "No GPU adapter available. Run on a GPU host.");
665        }
666    }
667    let _ = writeln!(gpu_md);
668    let _ = writeln!(gpu_md, "SYNTHETIC_ENGINE_REALISTIC=true");
669    let _ = writeln!(gpu_md, "ENGINE_NATIVE_CAPTURE_MISSING=true");
670    fs::write(&gpu_metrics_path, gpu_md)?;
671
672    Ok(report_path)
673}
674
675pub fn run_check_signing(config: &DemoConfig, output_dir: &Path) -> Result<PathBuf> {
676    let _ = config;
677    fs::create_dir_all(output_dir)?;
678    let report_path = write_check_signing_evidence_report(output_dir)?;
679    Ok(report_path)
680}
681
682pub fn validate_final_bundle(output_dir: &Path) -> Result<()> {
683    validate_artifact_bundle(output_dir)
684        .and_then(|_| validate_decision_reports(output_dir))
685        .and_then(|_| validate_new_gates(output_dir))
686}
687
688/// Validate the engine-native output bundle.
689///
690/// Hard fails if required engine-native files are missing.
691/// If `allow_pending` is true, passes even when ENGINE_NATIVE_CAPTURE_MISSING=true;
692/// otherwise fails so the caller knows no real engine capture has been provided.
693pub fn validate_engine_native_gates(
694    engine_native_dir: &Path,
695    allow_pending: bool,
696) -> Result<()> {
697    // Required report files
698    let required = [
699        engine_native_dir.join("engine_native_import_report.md"),
700        engine_native_dir.join("resolved_engine_native_manifest.json"),
701        engine_native_dir.join("engine_native_replay_report.md"),
702        engine_native_dir.join("gpu_execution_report.md"),
703        engine_native_dir.join("gpu_execution_metrics.json"),
704        engine_native_dir.join("demo_a_engine_native_report.md"),
705        engine_native_dir.join("demo_b_engine_native_report.md"),
706        engine_native_dir.join("demo_b_engine_native_metrics.json"),
707        engine_native_dir.join("high_res_execution_report.md"),
708        engine_native_dir.join("engine_native_validation_report.md"),
709    ];
710    for path in &required {
711        if !path.exists() {
712            return Err(Error::Message(format!(
713                "engine-native gate: required file missing: {}\n\
714                Run: cargo run --release -- run-engine-native-replay \\\n  \
715                --manifest examples/engine_native_capture_manifest.json \\\n  \
716                --output generated/engine_native",
717                path.display()
718            )));
719        }
720        let meta = fs::metadata(path)?;
721        if meta.len() == 0 {
722            return Err(Error::Message(format!(
723                "engine-native gate: required file is empty: {}",
724                path.display()
725            )));
726        }
727    }
728
729    // Mixed-regime report is one level up from engine_native_dir
730    let mixed_regime_path = engine_native_dir
731        .parent()
732        .unwrap_or(engine_native_dir)
733        .join("mixed_regime_confirmation_report.md");
734    if !mixed_regime_path.exists() {
735        return Err(Error::Message(format!(
736            "engine-native gate: mixed_regime_confirmation_report.md missing at {}\n\
737            Run: cargo run --release -- confirm-mixed-regime --output generated",
738            mixed_regime_path.display()
739        )));
740    }
741
742    // Manual commands doc is one level up
743    let manual_commands_path = engine_native_dir
744        .parent()
745        .unwrap_or(engine_native_dir)
746        .join("manual_engine_native_commands.md");
747    if !manual_commands_path.exists() {
748        return Err(Error::Message(format!(
749            "engine-native gate: manual_engine_native_commands.md missing at {}\n\
750            Run: cargo run --release -- run-engine-native-replay \\\n  \
751            --manifest examples/engine_native_capture_manifest.json \\\n  \
752            --output generated/engine_native",
753            manual_commands_path.display()
754        )));
755    }
756
757    // Check for ENGINE_NATIVE_CAPTURE_MISSING flag
758    if !allow_pending {
759        let validation_report =
760            fs::read_to_string(engine_native_dir.join("engine_native_validation_report.md"))?;
761        if validation_report.contains("ENGINE_NATIVE_CAPTURE_MISSING=true") {
762            return Err(Error::Message(
763                "engine-native gate: ENGINE_NATIVE_CAPTURE_MISSING=true — no real engine capture \
764                has been provided.\n\
765                Options:\n\
766                  1. Provide a real engine capture (see docs/unreal_export_playbook.md)\n\
767                  2. Run with --allow-pending-engine-native to pass despite missing capture\n\
768                \n\
769                This is an EXTERNAL blocker. Internal infrastructure is complete.\n\
770                See generated/engine_native/engine_native_validation_report.md for details."
771                    .to_string(),
772            ));
773        }
774
775        // Check that mixed_regime_confirmed is not falsely claimed
776        let mixed_report = fs::read_to_string(&mixed_regime_path)?;
777        if mixed_report.contains("mixed_regime_confirmed")
778            && !mixed_report.contains("mixed_regime_confirmed_internal")
779            && !mixed_report.contains("NOT CONFIRMED")
780        {
781            return Err(Error::Message(
782                "engine-native gate: mixed_regime_confirmed claimed without evidence or internal label"
783                    .to_string(),
784            ));
785        }
786    }
787
788    Ok(())
789}
790
791pub fn export_minimal_report(config: &DemoConfig, output_dir: &Path) -> Result<PathBuf> {
792    fs::create_dir_all(output_dir)?;
793    let definitions = scenario_suite(&config.scene);
794    let executions = execute_demo_a_suite(config, &definitions)?;
795    let analysis_inputs = build_demo_a_analysis_inputs(&executions);
796    let demo_a_metrics = analyze_demo_a_suite(&analysis_inputs)?;
797    let trust_diagnostics = build_trust_diagnostics(&demo_a_metrics);
798    let path = output_dir.join("minimal_report.md");
799    let point_like = demo_a_metrics
800        .scenarios
801        .iter()
802        .filter(|scenario| {
803            matches!(
804                scenario.support_category,
805                crate::scene::ScenarioSupportCategory::PointLikeRoi
806            )
807        })
808        .map(|scenario| format!("{}={} px", scenario.scenario_id, scenario.target_pixels))
809        .collect::<Vec<_>>()
810        .join(", ");
811    fs::write(
812        &path,
813        format!(
814            "# Minimal Report\n\n{}\n\nPoint-like ROI disclosure: {}.\n\nTrust conclusion: {}\n\n## What Is Not Proven\n\n- No actual GPU timing is included in this minimal report.\n- This file does not prove production readiness.\n\n## Remaining Blockers\n\n- real GPU measurements\n- external engine validation\n",
815            demo_a_metrics.summary.primary_behavioral_result, point_like, trust_diagnostics.conclusion
816        ),
817    )?;
818    Ok(path)
819}
820
821pub fn run_sbir_demo(config: &DemoConfig, output_dir: &Path) -> Result<SbirDemoArtifacts> {
822    fs::create_dir_all(output_dir)?;
823
824    // Step 1: run the full pipeline to produce all artifacts.
825    println!("[sbir-demo] Running full pipeline (run-all)...");
826    let _artifacts = run_all(config, output_dir)?;
827    println!("[sbir-demo] Pipeline complete.");
828
829    // Step 2: run the test suite and capture per-test pass/fail.
830    println!("[sbir-demo] Running test suite...");
831    let test_results_path = output_dir.join("test_results.json");
832    let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
833    let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
834
835    let test_output = StdCommand::new(&cargo)
836        .arg("test")
837        .arg("--manifest-path")
838        .arg(manifest_dir.join("Cargo.toml"))
839        .arg("--no-fail-fast")
840        .output()
841        .map_err(|e| Error::Message(format!("failed to run cargo test: {e}")))?;
842
843    let stdout = String::from_utf8_lossy(&test_output.stdout);
844    let stderr = String::from_utf8_lossy(&test_output.stderr);
845    let combined = format!("{stdout}\n{stderr}");
846
847    let test_results = parse_cargo_test_output(&combined);
848    fs::write(
849        &test_results_path,
850        serde_json::to_string_pretty(&test_results)
851            .map_err(|e| Error::Message(format!("failed to serialize test results: {e}")))?,
852    )?;
853    let passed = test_results["passed"].as_u64().unwrap_or(0);
854    let failed = test_results["failed"].as_u64().unwrap_or(0);
855    println!("[sbir-demo] Tests: {passed} passed, {failed} failed.");
856
857    // Step 3: invoke the Python PDF generator.
858    println!("[sbir-demo] Generating PDF report...");
859    let pdf_path = output_dir.join("sbir_demo_report.pdf");
860    let script = manifest_dir.join("colab").join("sbir_demo_report.py");
861    if !script.exists() {
862        return Err(Error::Message(format!(
863            "PDF script not found at {}; ensure colab/sbir_demo_report.py is present",
864            script.display()
865        )));
866    }
867
868    let python = std::env::var("PYTHON3").unwrap_or_else(|_| "python3".to_string());
869    let pdf_status = StdCommand::new(&python)
870        .arg(&script)
871        .arg("--run-dir")
872        .arg(output_dir)
873        .arg("--test-results")
874        .arg(&test_results_path)
875        .arg("--output")
876        .arg(&pdf_path)
877        .status()
878        .map_err(|e| Error::Message(format!("failed to invoke python3 sbir_demo_report.py: {e}")))?;
879
880    if !pdf_status.success() {
881        return Err(Error::Message(
882            "sbir_demo_report.py exited with a non-zero status; check that Pillow is installed"
883                .to_string(),
884        ));
885    }
886    println!("[sbir-demo] PDF written to {}", pdf_path.display());
887
888    Ok(SbirDemoArtifacts {
889        output_dir: output_dir.to_path_buf(),
890        pdf_path,
891        test_results_path,
892    })
893}
894
895/// Parse `cargo test` stdout/stderr into a JSON summary including per-test results.
896fn parse_cargo_test_output(output: &str) -> serde_json::Value {
897    let mut tests: Vec<serde_json::Value> = Vec::new();
898    let mut passed: u64 = 0;
899    let mut failed: u64 = 0;
900    let mut ignored: u64 = 0;
901
902    for line in output.lines() {
903        // Strip ANSI escape codes before matching.
904        let line = strip_ansi(line.trim());
905
906        // Individual test result lines: "test foo::bar ... ok" or "... FAILED"
907        if line.starts_with("test ") {
908            let ok = line.ends_with(" ok");
909            let ig = line.ends_with(" ignored");
910            let fail = line.ends_with(" FAILED");
911            if ok || ig || fail {
912                // Extract the test name between "test " and " ..."
913                if let Some(rest) = line.strip_prefix("test ") {
914                    let name = if let Some(idx) = rest.rfind(" ... ") {
915                        &rest[..idx]
916                    } else {
917                        rest.trim_end_matches(" ok")
918                            .trim_end_matches(" FAILED")
919                            .trim_end_matches(" ignored")
920                    };
921                    tests.push(serde_json::json!({
922                        "name": name,
923                        "ok": ok,
924                        "ignored": ig,
925                    }));
926                }
927            }
928        }
929
930        // Summary line: "test result: ok. 5 passed; 0 failed; 0 ignored; ..."
931        if line.contains("test result:") && line.contains("passed") {
932            for segment in line.split(';') {
933                let s = segment.trim();
934                if let Some(n) = extract_count(s, "passed") {
935                    passed = passed.saturating_add(n);
936                } else if let Some(n) = extract_count(s, "failed") {
937                    failed = failed.saturating_add(n);
938                } else if let Some(n) = extract_count(s, "ignored") {
939                    ignored = ignored.saturating_add(n);
940                }
941            }
942        }
943    }
944
945    serde_json::json!({
946        "passed": passed,
947        "failed": failed,
948        "ignored": ignored,
949        "tests": tests,
950    })
951}
952
953/// Remove ANSI escape sequences from a string.
954fn strip_ansi(s: &str) -> String {
955    let mut out = String::with_capacity(s.len());
956    let mut chars = s.chars().peekable();
957    while let Some(c) = chars.next() {
958        if c == '\x1b' {
959            // consume until end of escape sequence (letter)
960            if chars.peek() == Some(&'[') {
961                chars.next();
962                for inner in chars.by_ref() {
963                    if inner.is_ascii_alphabetic() {
964                        break;
965                    }
966                }
967            }
968        } else {
969            out.push(c);
970        }
971    }
972    out
973}
974
975fn extract_count(segment: &str, keyword: &str) -> Option<u64> {
976    let idx = segment.find(keyword)?;
977    let before = segment[..idx].trim();
978    before.split_whitespace().last()?.parse::<u64>().ok()
979}
980
981pub fn run_all(config: &DemoConfig, output_dir: &Path) -> Result<RunAllArtifacts> {
982    run_all_filtered(config, output_dir, None)
983}
984
985pub fn run_all_filtered(
986    config: &DemoConfig,
987    output_dir: &Path,
988    scenario: Option<&str>,
989) -> Result<RunAllArtifacts> {
990    fs::create_dir_all(output_dir)?;
991
992    let definitions = scenario_definitions_for_filter(config, scenario)?;
993    let executions = execute_demo_a_suite(config, &definitions)?;
994    let analysis_inputs = build_demo_a_analysis_inputs(&executions);
995    let demo_a_metrics = analyze_demo_a_suite(&analysis_inputs)?;
996    validate_demo_a_metrics(&demo_a_metrics)?;
997
998    let host_sequences = executions
999        .iter()
1000        .map(|execution| {
1001            Ok((
1002                execution.sequence.clone(),
1003                execution.dsfb("dsfb_host_realistic")?.run.clone(),
1004            ))
1005        })
1006        .collect::<Result<Vec<_>>>()?;
1007    let (demo_b_metrics, demo_b_runs) = run_demo_b_suite(config, &host_sequences)?;
1008    validate_demo_b_metrics(&demo_b_metrics)?;
1009
1010    let mut demo_a = write_demo_a_artifacts(
1011        output_dir,
1012        config,
1013        &executions,
1014        &demo_a_metrics,
1015        &demo_b_metrics,
1016    )?;
1017    let demo_b = write_demo_b_artifacts(
1018        &output_dir.join("demo_b"),
1019        &host_sequences,
1020        &demo_b_metrics,
1021        &demo_b_runs,
1022    )?;
1023
1024    let trust_diagnostics = build_trust_diagnostics(&demo_a_metrics);
1025    let trust_diagnostics_path = output_dir.join("trust_diagnostics.md");
1026    write_trust_diagnostics_report(&trust_diagnostics_path, &trust_diagnostics)?;
1027    let trust_diagnostics_json_path = output_dir.join("trust_diagnostics.json");
1028    fs::write(
1029        &trust_diagnostics_json_path,
1030        serde_json::to_string_pretty(&trust_diagnostics)?,
1031    )?;
1032    let trust_mode_report_path = output_dir.join("trust_mode_report.md");
1033    write_trust_mode_report(&trust_mode_report_path, &trust_diagnostics)?;
1034
1035    let timing_metrics = run_timing_study(config)?;
1036    let timing_report_path = output_dir.join("timing_report.md");
1037    write_timing_report(&timing_report_path, &timing_metrics)?;
1038    let timing_metrics_path = output_dir.join("timing_metrics.json");
1039    fs::write(
1040        &timing_metrics_path,
1041        serde_json::to_string_pretty(&timing_metrics)?,
1042    )?;
1043
1044    let gpu_execution_metrics = run_gpu_execution_study(config)?;
1045    let gpu_execution_report_path = output_dir.join("gpu_execution_report.md");
1046    write_gpu_execution_report(&gpu_execution_report_path, &gpu_execution_metrics)?;
1047    let gpu_execution_metrics_path = output_dir.join("gpu_execution_metrics.json");
1048    fs::write(
1049        &gpu_execution_metrics_path,
1050        serde_json::to_string_pretty(&gpu_execution_metrics)?,
1051    )?;
1052
1053    let examples_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples");
1054    let example_manifest_path = examples_dir.join("external_capture_manifest.json");
1055    if !example_manifest_path.exists() {
1056        write_example_manifest(&example_manifest_path)?;
1057    }
1058    let external_artifacts = run_external_validation_bundle(
1059        config,
1060        &example_manifest_path,
1061        &output_dir.join("external_real"),
1062    )?;
1063    let external_replay_report_path = output_dir.join("external_replay_report.md");
1064    fs::copy(
1065        &external_artifacts.replay_report_path,
1066        &external_replay_report_path,
1067    )?;
1068    let external_handoff_report_path = output_dir.join("external_handoff_report.md");
1069    fs::copy(
1070        &external_artifacts.handoff_report_path,
1071        &external_handoff_report_path,
1072    )?;
1073
1074    let resolution_scaling_metrics = run_resolution_scaling_study(config)?;
1075    let resolution_scaling_report_path = output_dir.join("resolution_scaling_report.md");
1076    write_resolution_scaling_report(&resolution_scaling_report_path, &resolution_scaling_metrics)?;
1077    let resolution_scaling_metrics_path = output_dir.join("resolution_scaling_metrics.json");
1078    fs::write(
1079        &resolution_scaling_metrics_path,
1080        serde_json::to_string_pretty(&resolution_scaling_metrics)?,
1081    )?;
1082
1083    let parameter_sensitivity_metrics = run_parameter_sensitivity_study(config)?;
1084    let parameter_sensitivity_report_path = output_dir.join("parameter_sensitivity_report.md");
1085    write_parameter_sensitivity_report(
1086        &parameter_sensitivity_report_path,
1087        &parameter_sensitivity_metrics,
1088    )?;
1089    let parameter_sensitivity_metrics_path = output_dir.join("parameter_sensitivity_metrics.json");
1090    fs::write(
1091        &parameter_sensitivity_metrics_path,
1092        serde_json::to_string_pretty(&parameter_sensitivity_metrics)?,
1093    )?;
1094
1095    let demo_b_metrics_path = output_dir.join("demo_b_metrics.json");
1096    fs::write(
1097        &demo_b_metrics_path,
1098        serde_json::to_string_pretty(&demo_b_metrics)?,
1099    )?;
1100    let demo_b_efficiency_report_path = output_dir.join("demo_b_efficiency_report.md");
1101    write_demo_b_efficiency_report(&demo_b_efficiency_report_path, &demo_b_metrics)?;
1102    let demo_b_aliasing_report_path = output_dir.join("demo_b_aliasing_vs_variance_report.md");
1103    write_demo_b_aliasing_vs_variance_report(&demo_b_aliasing_report_path, &demo_b_metrics)?;
1104
1105    let scenario_taxonomy_path = output_dir.join("scenario_taxonomy.json");
1106    write_scenario_taxonomy_json(&scenario_taxonomy_path, &executions)?;
1107    let realism_suite_report_path = output_dir.join("realism_suite_report.md");
1108    write_realism_suite_report(&realism_suite_report_path, &demo_a_metrics)?;
1109    let realism_bridge_report_path = output_dir.join("realism_bridge_report.md");
1110    write_realism_bridge_report(&realism_bridge_report_path, &demo_a_metrics)?;
1111    let demo_b_scene_taxonomy_path = output_dir.join("demo_b_scene_taxonomy.json");
1112    write_demo_b_scene_taxonomy_json(&demo_b_scene_taxonomy_path, &demo_b_metrics)?;
1113    let competitive_baseline_analysis_path = output_dir.join("competitive_baseline_analysis.md");
1114    write_competitive_baseline_analysis(&competitive_baseline_analysis_path, &demo_a_metrics)?;
1115    let non_roi_penalty_report_path = output_dir.join("non_roi_penalty_report.md");
1116    write_non_roi_penalty_report(&non_roi_penalty_report_path, &demo_a_metrics)?;
1117    let product_positioning_report_path = output_dir.join("product_positioning_report.md");
1118    write_product_positioning_report(&product_positioning_report_path, &demo_a_metrics)?;
1119    let operating_band_report_path = output_dir.join("operating_band_report.md");
1120    write_operating_band_report(&operating_band_report_path, &parameter_sensitivity_metrics)?;
1121    let demo_b_competitive_baselines_report_path =
1122        output_dir.join("demo_b_competitive_baselines_report.md");
1123    write_demo_b_competitive_baselines_report(
1124        &demo_b_competitive_baselines_report_path,
1125        &demo_b_metrics,
1126    )?;
1127
1128    let cost_report = build_cost_report(CostMode::HostRealistic);
1129    write_full_report(
1130        &demo_a.report_path,
1131        &demo_a_metrics,
1132        &demo_b_metrics,
1133        &cost_report,
1134        &trust_diagnostics,
1135        &timing_metrics,
1136        &gpu_execution_metrics,
1137        &external_artifacts.handoff_metrics,
1138        &resolution_scaling_metrics,
1139        &parameter_sensitivity_metrics,
1140    )?;
1141    write_full_reviewer_summary(
1142        &demo_a.reviewer_summary_path,
1143        &demo_a_metrics,
1144        &demo_b_metrics,
1145        &trust_diagnostics,
1146        &timing_metrics,
1147        &gpu_execution_metrics,
1148        &external_artifacts.handoff_metrics,
1149    )?;
1150
1151    let five_mentor_audit_path = output_dir.join("five_mentor_audit.md");
1152    write_full_five_mentor_audit(
1153        &five_mentor_audit_path,
1154        &demo_a_metrics,
1155        &demo_b_metrics,
1156        &timing_metrics,
1157        &gpu_execution_metrics,
1158        &external_artifacts.handoff_metrics,
1159    )?;
1160    let blocker_report_path = output_dir.join("check_signing_blockers.md");
1161    write_full_check_signing_blockers(
1162        &blocker_report_path,
1163        &demo_a_metrics,
1164        &timing_metrics,
1165        &gpu_execution_metrics,
1166        &external_artifacts.handoff_metrics,
1167    )?;
1168    let demo_b_decision_report_path = output_dir.join("demo_b_decision_report.md");
1169    write_demo_b_decision_report(&demo_b_decision_report_path, &demo_b_metrics)?;
1170    let production_eval_checklist_path = output_dir.join("production_eval_checklist.md");
1171    write_production_eval_checklist(
1172        &production_eval_checklist_path,
1173        &gpu_execution_metrics,
1174        &external_artifacts.handoff_metrics,
1175    )?;
1176    let evaluator_handoff_path = output_dir.join("evaluator_handoff.md");
1177    write_evaluator_handoff(
1178        &evaluator_handoff_path,
1179        &demo_a_metrics,
1180        &demo_b_metrics,
1181        &gpu_execution_metrics,
1182        &external_artifacts.handoff_metrics,
1183    )?;
1184    let minimum_external_validation_plan_path =
1185        output_dir.join("minimum_external_validation_plan.md");
1186    write_minimum_external_validation_plan(&minimum_external_validation_plan_path)?;
1187    let next_step_matrix_path = output_dir.join("next_step_matrix.md");
1188    write_next_step_matrix(
1189        &next_step_matrix_path,
1190        &gpu_execution_metrics,
1191        &external_artifacts.handoff_metrics,
1192    )?;
1193    let check_signing_readiness_path = output_dir.join("check_signing_readiness.md");
1194    write_check_signing_readiness(
1195        &check_signing_readiness_path,
1196        &demo_a_metrics,
1197        &gpu_execution_metrics,
1198        &external_artifacts.handoff_metrics,
1199    )?;
1200
1201    let additional_figure_paths = write_additional_figures(
1202        output_dir,
1203        &demo_a_metrics,
1204        &trust_diagnostics,
1205        &resolution_scaling_metrics,
1206        &parameter_sensitivity_metrics,
1207    )?;
1208    demo_a.figure_paths.extend(additional_figure_paths);
1209
1210    let manifest_path = write_notebook_artifact_manifest(
1211        output_dir,
1212        &demo_a,
1213        &demo_b,
1214        &[
1215            &trust_diagnostics_path,
1216            &trust_mode_report_path,
1217            &timing_report_path,
1218            &gpu_execution_report_path,
1219            &external_replay_report_path,
1220            &external_handoff_report_path,
1221            &external_artifacts.gpu_report_path,
1222            &external_artifacts.demo_a_report_path,
1223            &external_artifacts.demo_b_report_path,
1224            &external_artifacts.validation_report_path,
1225            &external_artifacts.scaling_report_path,
1226            &external_artifacts.memory_bandwidth_report_path,
1227            &external_artifacts.integration_scaling_report_path,
1228            &resolution_scaling_report_path,
1229            &realism_suite_report_path,
1230            &realism_bridge_report_path,
1231            &parameter_sensitivity_report_path,
1232            &operating_band_report_path,
1233            &competitive_baseline_analysis_path,
1234            &non_roi_penalty_report_path,
1235            &product_positioning_report_path,
1236            &demo_b_efficiency_report_path,
1237            &demo_b_competitive_baselines_report_path,
1238            &demo_b_aliasing_report_path,
1239            &five_mentor_audit_path,
1240            &blocker_report_path,
1241            &demo_b_decision_report_path,
1242            &production_eval_checklist_path,
1243            &evaluator_handoff_path,
1244            &minimum_external_validation_plan_path,
1245            &next_step_matrix_path,
1246            &check_signing_readiness_path,
1247        ],
1248    )?;
1249
1250    validate_demo_a_artifacts(&demo_a, &demo_a_metrics)?;
1251    validate_demo_b_artifacts(&demo_b, &demo_b_metrics)?;
1252    validate_full_artifacts(
1253        output_dir,
1254        &manifest_path,
1255        &[
1256            &trust_diagnostics_path,
1257            &trust_mode_report_path,
1258            &timing_report_path,
1259            &gpu_execution_report_path,
1260            &external_replay_report_path,
1261            &external_handoff_report_path,
1262            &external_artifacts.gpu_report_path,
1263            &external_artifacts.demo_a_report_path,
1264            &external_artifacts.demo_b_report_path,
1265            &external_artifacts.validation_report_path,
1266            &external_artifacts.scaling_report_path,
1267            &external_artifacts.memory_bandwidth_report_path,
1268            &external_artifacts.integration_scaling_report_path,
1269            &resolution_scaling_report_path,
1270            &realism_suite_report_path,
1271            &realism_bridge_report_path,
1272            &parameter_sensitivity_report_path,
1273            &operating_band_report_path,
1274            &competitive_baseline_analysis_path,
1275            &non_roi_penalty_report_path,
1276            &product_positioning_report_path,
1277            &demo_b_efficiency_report_path,
1278            &demo_b_competitive_baselines_report_path,
1279            &demo_b_aliasing_report_path,
1280            &five_mentor_audit_path,
1281            &blocker_report_path,
1282            &demo_b_decision_report_path,
1283            &production_eval_checklist_path,
1284            &evaluator_handoff_path,
1285            &minimum_external_validation_plan_path,
1286            &next_step_matrix_path,
1287            &check_signing_readiness_path,
1288        ],
1289    )?;
1290
1291    Ok(RunAllArtifacts {
1292        output_dir: output_dir.to_path_buf(),
1293        manifest_path,
1294        demo_a,
1295        demo_b,
1296        trust_diagnostics_path,
1297        trust_diagnostics_json_path,
1298        timing_report_path,
1299        timing_metrics_path,
1300        resolution_scaling_report_path,
1301        resolution_scaling_metrics_path,
1302        parameter_sensitivity_report_path,
1303        parameter_sensitivity_metrics_path,
1304        demo_b_efficiency_report_path,
1305        demo_b_metrics_path,
1306        five_mentor_audit_path,
1307        blocker_report_path,
1308        demo_b_decision_report_path,
1309    })
1310}
1311
1312pub fn validate_artifact_bundle(output_dir: &Path) -> Result<()> {
1313    let manifest_path = output_dir.join(ARTIFACT_MANIFEST_FILE_NAME);
1314    if !manifest_path.exists() {
1315        return Err(Error::Message(format!(
1316            "artifact manifest missing at {}",
1317            manifest_path.display()
1318        )));
1319    }
1320
1321    let required = [
1322        output_dir.join("metrics.json"),
1323        output_dir.join("report.md"),
1324        output_dir.join("reviewer_summary.md"),
1325        output_dir.join("ablation_report.md"),
1326        output_dir.join("cost_report.md"),
1327        output_dir.join("completion_note.md"),
1328        output_dir.join("trust_diagnostics.md"),
1329        output_dir.join("trust_diagnostics.json"),
1330        output_dir.join("trust_mode_report.md"),
1331        output_dir.join("timing_report.md"),
1332        output_dir.join("timing_metrics.json"),
1333        output_dir.join("gpu_execution_report.md"),
1334        output_dir.join("gpu_execution_metrics.json"),
1335        output_dir.join("external_replay_report.md"),
1336        output_dir.join("external_handoff_report.md"),
1337        output_dir
1338            .join("external_real")
1339            .join("gpu_external_report.md"),
1340        output_dir
1341            .join("external_real")
1342            .join("gpu_external_metrics.json"),
1343        output_dir
1344            .join("external_real")
1345            .join("demo_a_external_report.md"),
1346        output_dir
1347            .join("external_real")
1348            .join("demo_b_external_report.md"),
1349        output_dir
1350            .join("external_real")
1351            .join("demo_b_external_metrics.json"),
1352        output_dir
1353            .join("external_real")
1354            .join("external_validation_report.md"),
1355        output_dir.join("external_real").join("scaling_report.md"),
1356        output_dir
1357            .join("external_real")
1358            .join("scaling_metrics.json"),
1359        output_dir
1360            .join("external_real")
1361            .join("memory_bandwidth_report.md"),
1362        output_dir
1363            .join("external_real")
1364            .join("integration_scaling_report.md"),
1365        output_dir.join("resolution_scaling_report.md"),
1366        output_dir.join("resolution_scaling_metrics.json"),
1367        output_dir.join("realism_suite_report.md"),
1368        output_dir.join("realism_bridge_report.md"),
1369        output_dir.join("scenario_taxonomy.json"),
1370        output_dir.join("parameter_sensitivity_report.md"),
1371        output_dir.join("parameter_sensitivity_metrics.json"),
1372        output_dir.join("operating_band_report.md"),
1373        output_dir.join("competitive_baseline_analysis.md"),
1374        output_dir.join("non_roi_penalty_report.md"),
1375        output_dir.join("product_positioning_report.md"),
1376        output_dir.join("demo_b_metrics.json"),
1377        output_dir.join("demo_b_scene_taxonomy.json"),
1378        output_dir.join("demo_b_competitive_baselines_report.md"),
1379        output_dir.join("demo_b_aliasing_vs_variance_report.md"),
1380        output_dir.join("demo_b_efficiency_report.md"),
1381        output_dir.join("five_mentor_audit.md"),
1382        output_dir.join("check_signing_blockers.md"),
1383        output_dir.join("demo_b_decision_report.md"),
1384        output_dir.join("production_eval_checklist.md"),
1385        output_dir.join("evaluator_handoff.md"),
1386        output_dir.join("minimum_external_validation_plan.md"),
1387        output_dir.join("next_step_matrix.md"),
1388        output_dir.join("check_signing_readiness.md"),
1389        output_dir.join("figures").join("fig_system_diagram.svg"),
1390        output_dir.join("figures").join("fig_trust_map.svg"),
1391        output_dir.join("figures").join("fig_before_after.svg"),
1392        output_dir.join("figures").join("fig_trust_vs_error.svg"),
1393        output_dir
1394            .join("figures")
1395            .join("fig_intervention_alpha.svg"),
1396        output_dir.join("figures").join("fig_ablation.svg"),
1397        output_dir.join("figures").join("fig_roi_nonroi_error.svg"),
1398        output_dir.join("figures").join("fig_leaderboard.svg"),
1399        output_dir.join("figures").join("fig_scenario_mosaic.svg"),
1400        output_dir.join("figures").join("fig_trust_histogram.svg"),
1401        output_dir.join("figures").join("fig_roi_taxonomy.svg"),
1402        output_dir
1403            .join("figures")
1404            .join("fig_parameter_sensitivity.svg"),
1405        output_dir
1406            .join("figures")
1407            .join("fig_resolution_scaling.svg"),
1408        output_dir.join("figures").join("fig_motion_relevance.svg"),
1409        output_dir.join("demo_b").join("metrics.json"),
1410        output_dir.join("demo_b").join("report.md"),
1411        output_dir
1412            .join("demo_b")
1413            .join("figures")
1414            .join("fig_demo_b_sampling.svg"),
1415        output_dir
1416            .join("demo_b")
1417            .join("figures")
1418            .join("fig_demo_b_budget_efficiency.svg"),
1419        output_dir
1420            .join("external_real")
1421            .join("resolved_external_capture_manifest.json"),
1422        output_dir
1423            .join("external_real")
1424            .join("figures")
1425            .join("current_color.png"),
1426        output_dir
1427            .join("external_real")
1428            .join("figures")
1429            .join("trust_map.png"),
1430        output_dir
1431            .join("external_real")
1432            .join("figures")
1433            .join("roi_overlay.png"),
1434        output_dir
1435            .join("external_real")
1436            .join("figures")
1437            .join("demo_b_allocation_uniform.png"),
1438    ];
1439    for path in required {
1440        require_file(&path)?;
1441    }
1442
1443    let report = fs::read_to_string(output_dir.join("report.md"))?;
1444    if !report.contains("## Remaining Blockers") || !report.contains("## What Is Not Proven") {
1445        return Err(Error::Message(
1446            "main report is missing blocker or non-proof sections".to_string(),
1447        ));
1448    }
1449    if !report.contains(EXPERIMENT_SENTENCE)
1450        || !report.contains(COST_SENTENCE)
1451        || !report.contains(COMPATIBILITY_SENTENCE)
1452    {
1453        return Err(Error::Message(
1454            "main report is missing required honesty or compatibility sentences".to_string(),
1455        ));
1456    }
1457    if !report.contains("## ROI Disclosure") {
1458        return Err(Error::Message(
1459            "main report is missing explicit ROI disclosure".to_string(),
1460        ));
1461    }
1462    if report.contains("production-ready") || report.contains("state-of-the-art") {
1463        return Err(Error::Message(
1464            "main report contains unsupported claim language".to_string(),
1465        ));
1466    }
1467
1468    let timing_report = fs::read_to_string(output_dir.join("timing_report.md"))?;
1469    if !timing_report.contains("Measurement classification")
1470        || !timing_report.contains("Actual GPU timing measured")
1471    {
1472        return Err(Error::Message(
1473            "timing report is missing measurement-kind disclosure".to_string(),
1474        ));
1475    }
1476    if !timing_report.contains("cpu_only_proxy") {
1477        return Err(Error::Message(
1478            "timing report must state when timing is CPU-only proxy".to_string(),
1479        ));
1480    }
1481
1482    let gpu_report = fs::read_to_string(output_dir.join("gpu_execution_report.md"))?;
1483    if !gpu_report.contains("Measurement classification")
1484        || !gpu_report.contains("Actual GPU timing measured")
1485    {
1486        return Err(Error::Message(
1487            "GPU execution report is missing measured-vs-unmeasured disclosure".to_string(),
1488        ));
1489    }
1490
1491    let external_replay_report = fs::read_to_string(output_dir.join("external_replay_report.md"))?;
1492    if !external_replay_report.contains("external-capable")
1493        && !external_replay_report.contains("external-capable =")
1494    {
1495        return Err(Error::Message(
1496            "external replay report must distinguish external-capable from externally validated"
1497                .to_string(),
1498        ));
1499    }
1500
1501    let external_report = fs::read_to_string(output_dir.join("external_handoff_report.md"))?;
1502    if !external_report.contains("external-capable")
1503        && !external_report.contains("external-capable =")
1504    {
1505        return Err(Error::Message(
1506            "external handoff report must distinguish external-capable from externally validated"
1507                .to_string(),
1508        ));
1509    }
1510    let gpu_external_report = fs::read_to_string(
1511        output_dir
1512            .join("external_real")
1513            .join("gpu_external_report.md"),
1514    )?;
1515    if !gpu_external_report.contains("measured_gpu:")
1516        || !gpu_external_report.contains("measurement_kind:")
1517    {
1518        return Err(Error::Message(
1519            "GPU external report must disclose measured-vs-unmeasured status".to_string(),
1520        ));
1521    }
1522    let demo_a_external_report = fs::read_to_string(
1523        output_dir
1524            .join("external_real")
1525            .join("demo_a_external_report.md"),
1526    )?;
1527    if !demo_a_external_report.contains("ROI source")
1528        || !demo_a_external_report.contains("non-ROI")
1529        || !demo_a_external_report.contains("metric_source")
1530    {
1531        return Err(Error::Message(
1532            "Demo A external report must separate ROI/non-ROI and clearly label whether metrics are proxy or reference-based".to_string(),
1533        ));
1534    }
1535    let demo_b_external_report = fs::read_to_string(
1536        output_dir
1537            .join("external_real")
1538            .join("demo_b_external_report.md"),
1539    )?;
1540    for required_phrase in [
1541        "Gradient magnitude",
1542        "Variance proxy",
1543        "Combined heuristic",
1544        "DSFB imported trust",
1545        "fixed_budget_equal",
1546        "aliasing",
1547        "variance",
1548    ] {
1549        if !demo_b_external_report.contains(required_phrase) {
1550            return Err(Error::Message(format!(
1551                "Demo B external report is missing required phrase `{required_phrase}`"
1552            )));
1553        }
1554    }
1555    let external_validation_report = fs::read_to_string(
1556        output_dir
1557            .join("external_real")
1558            .join("external_validation_report.md"),
1559    )?;
1560    for required_phrase in [
1561        "## What Is Proven",
1562        "## What Is Not Proven",
1563        "## Remaining Blockers",
1564        "## Next Required Experiment",
1565    ] {
1566        if !external_validation_report.contains(required_phrase) {
1567            return Err(Error::Message(format!(
1568                "external validation report is missing `{required_phrase}`"
1569            )));
1570        }
1571    }
1572    if !external_validation_report.contains(NO_REAL_EXTERNAL_DATA_PROVIDED)
1573        && external_validation_report.contains("synthetic compatibility export")
1574    {
1575        return Err(Error::Message(
1576            "synthetic external validation runs must explicitly declare that no real external data was provided".to_string(),
1577        ));
1578    }
1579    let scaling_report =
1580        fs::read_to_string(output_dir.join("external_real").join("scaling_report.md"))?;
1581    for required_phrase in [
1582        "scaled_1080p",
1583        "scaled_4k",
1584        "Cost appears approximately linear with resolution",
1585        "realism_stress_case",
1586        "larger_roi_case",
1587        "mixed_regime_case",
1588    ] {
1589        if !scaling_report.contains(required_phrase) {
1590            return Err(Error::Message(format!(
1591                "external scaling report is missing `{required_phrase}`"
1592            )));
1593        }
1594    }
1595    let scaling_metrics: serde_json::Value = serde_json::from_str(&fs::read_to_string(
1596        output_dir
1597            .join("external_real")
1598            .join("scaling_metrics.json"),
1599    )?)?;
1600    if scaling_metrics["attempted_1080p"].as_bool() != Some(true) {
1601        return Err(Error::Message(
1602            "external scaling metrics must explicitly attempt 1080p".to_string(),
1603        ));
1604    }
1605    let memory_bandwidth_report = fs::read_to_string(
1606        output_dir
1607            .join("external_real")
1608            .join("memory_bandwidth_report.md"),
1609    )?;
1610    if !memory_bandwidth_report.contains("Readback required in production: `false`")
1611        || !memory_bandwidth_report.contains("Memory Access / Coherence Analysis")
1612    {
1613        return Err(Error::Message(
1614            "memory bandwidth report must disclose production readback status and coherence analysis".to_string(),
1615        ));
1616    }
1617    let integration_scaling_report = fs::read_to_string(
1618        output_dir
1619            .join("external_real")
1620            .join("integration_scaling_report.md"),
1621    )?;
1622    for required_phrase in [
1623        "Async-Compute Feasibility",
1624        "Production readback is not required",
1625        "Hazards / Barriers / Transitions",
1626        "Pipeline Compatibility",
1627    ] {
1628        if !integration_scaling_report.contains(required_phrase) {
1629            return Err(Error::Message(format!(
1630                "integration scaling report is missing `{required_phrase}`"
1631            )));
1632        }
1633    }
1634
1635    let scenario_taxonomy: Vec<ScenarioTaxonomyEntry> = serde_json::from_str(&fs::read_to_string(
1636        output_dir.join("scenario_taxonomy.json"),
1637    )?)?;
1638    if !scenario_taxonomy.iter().any(|entry| entry.realism_stress) {
1639        return Err(Error::Message(
1640            "scenario taxonomy must include at least one realism-stress case".to_string(),
1641        ));
1642    }
1643    if !scenario_taxonomy
1644        .iter()
1645        .any(|entry| entry.labels.iter().any(|label| label == "region_roi"))
1646    {
1647        return Err(Error::Message(
1648            "scenario taxonomy must expose at least one explicit region_roi label".to_string(),
1649        ));
1650    }
1651    if !scenario_taxonomy
1652        .iter()
1653        .any(|entry| entry.labels.iter().any(|label| label == "point_roi"))
1654    {
1655        return Err(Error::Message(
1656            "scenario taxonomy must expose at least one explicit point_roi label".to_string(),
1657        ));
1658    }
1659    if !scenario_taxonomy
1660        .iter()
1661        .any(|entry| entry.competitive_baseline_case)
1662    {
1663        return Err(Error::Message(
1664            "scenario taxonomy must include at least one competitive-baseline case".to_string(),
1665        ));
1666    }
1667    if !scenario_taxonomy
1668        .iter()
1669        .any(|entry| entry.bounded_loss_disclosure)
1670    {
1671        return Err(Error::Message(
1672            "scenario taxonomy must include at least one bounded-neutral or bounded-loss case"
1673                .to_string(),
1674        ));
1675    }
1676    if !scenario_taxonomy.iter().any(|entry| {
1677        entry
1678            .labels
1679            .iter()
1680            .any(|label| label == "bounded_neutral" || label == "bounded_loss")
1681    }) {
1682        return Err(Error::Message(
1683            "scenario taxonomy must include explicit bounded_neutral or bounded_loss labels"
1684                .to_string(),
1685        ));
1686    }
1687
1688    let demo_b_taxonomy: Vec<ScenarioTaxonomyEntry> = serde_json::from_str(&fs::read_to_string(
1689        output_dir.join("demo_b_scene_taxonomy.json"),
1690    )?)?;
1691    if !demo_b_taxonomy
1692        .iter()
1693        .any(|entry| entry.labels.iter().any(|label| label == "aliasing_limited"))
1694        || !demo_b_taxonomy
1695            .iter()
1696            .any(|entry| entry.labels.iter().any(|label| label == "variance_limited"))
1697        || !demo_b_taxonomy
1698            .iter()
1699            .any(|entry| entry.labels.iter().any(|label| label == "mixed_regime"))
1700    {
1701        return Err(Error::Message(
1702            "Demo B taxonomy must include aliasing_limited, variance_limited, and mixed_regime scenes"
1703                .to_string(),
1704        ));
1705    }
1706
1707    let demo_b_aliasing =
1708        fs::read_to_string(output_dir.join("demo_b_aliasing_vs_variance_report.md"))?;
1709    if !demo_b_aliasing.contains("variance") || !demo_b_aliasing.contains("aliasing") {
1710        return Err(Error::Message(
1711            "Demo B aliasing-vs-variance report is missing the required distinction".to_string(),
1712        ));
1713    }
1714    let demo_b_competitive =
1715        fs::read_to_string(output_dir.join("demo_b_competitive_baselines_report.md"))?;
1716    if !demo_b_competitive.contains("gradient-magnitude / edge-guided")
1717        || !demo_b_competitive.contains("variance-guided")
1718    {
1719        return Err(Error::Message(
1720            "Demo B competitive-baseline report must mention gradient/edge and variance-guided baselines"
1721                .to_string(),
1722        ));
1723    }
1724
1725    let trust_mode = fs::read_to_string(output_dir.join("trust_mode_report.md"))?;
1726    if !trust_mode.contains("near-binary")
1727        && !trust_mode.contains("WeaklyGraded")
1728        && !trust_mode.contains("StronglyGraded")
1729    {
1730        return Err(Error::Message(
1731            "trust mode report must classify the trust operating mode".to_string(),
1732        ));
1733    }
1734
1735    let operating_band = fs::read_to_string(output_dir.join("operating_band_report.md"))?;
1736    if !operating_band.contains("robust")
1737        || !operating_band.contains("moderately sensitive")
1738        || !operating_band.contains("fragile")
1739    {
1740        return Err(Error::Message(
1741            "operating band report must classify robust, moderately sensitive, and fragile settings"
1742                .to_string(),
1743        ));
1744    }
1745
1746    let readiness = fs::read_to_string(output_dir.join("check_signing_readiness.md"))?;
1747    if !readiness.contains("blocked pending external evidence")
1748        || !readiness.contains("ready for diligence")
1749    {
1750        return Err(Error::Message(
1751            "check-signing readiness must classify internal readiness and external blocking"
1752                .to_string(),
1753        ));
1754    }
1755
1756    let competitive = fs::read_to_string(output_dir.join("competitive_baseline_analysis.md"))?;
1757    if !competitive.contains("targeted supervisory overlay")
1758        && !competitive.contains("instability-focused specialist")
1759    {
1760        return Err(Error::Message(
1761            "competitive baseline analysis must frame DSFB as a targeted supervisory layer unless broader evidence exists".to_string(),
1762        ));
1763    }
1764
1765    let trust_report = fs::read_to_string(output_dir.join("trust_diagnostics.md"))?;
1766    if !trust_report.contains("degenerate, not decision-facing") {
1767        return Err(Error::Message(
1768            "trust diagnostics must explicitly quarantine degenerate rank correlation".to_string(),
1769        ));
1770    }
1771
1772    for doc_path in [
1773        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docs/completion_gates.md"),
1774        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docs/final_completion_gates.md"),
1775        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docs/final_last_mile_plan.md"),
1776        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docs/gpu_execution_path.md"),
1777        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docs/external_replay.md"),
1778        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docs/external_handoff.md"),
1779        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docs/engine_integration_playbook.md"),
1780        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docs/production_eval_bridge.md"),
1781        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docs/evaluator_handoff.md"),
1782        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples/external_buffer_schema.json"),
1783        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples/host_buffer_schema.json"),
1784        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples/external_capture_manifest.json"),
1785    ] {
1786        require_file(&doc_path)?;
1787    }
1788
1789    Ok(())
1790}
1791
1792fn execute_demo_a_suite(
1793    config: &DemoConfig,
1794    definitions: &[ScenarioDefinition],
1795) -> Result<Vec<ScenarioExecution>> {
1796    let mut executions = Vec::with_capacity(definitions.len());
1797    for definition in definitions {
1798        let sequence = generate_sequence_for_definition(definition);
1799        let heuristic_runs = vec![
1800            run_fixed_alpha_baseline(&sequence, config.baseline.fixed_alpha),
1801            run_residual_threshold_baseline(
1802                &sequence,
1803                config.baseline.residual_alpha_range.min,
1804                config.baseline.residual_alpha_range.max,
1805                config.baseline.residual_threshold.low,
1806                config.baseline.residual_threshold.high,
1807            ),
1808            run_neighborhood_clamp_baseline(&sequence, &config.baseline),
1809            run_depth_normal_rejection_baseline(&sequence, &config.baseline),
1810            run_reactive_mask_baseline(&sequence, &config.baseline),
1811            run_strong_heuristic_baseline(&sequence, &config.baseline),
1812        ];
1813        let dsfb_runs = ablation_profiles(config.dsfb_alpha_range.min, config.dsfb_alpha_range.max)
1814            .into_iter()
1815            .map(|profile| DsfbVariantRun::new(run_profiled_taa(&sequence, &profile)))
1816            .collect();
1817
1818        executions.push(ScenarioExecution {
1819            sequence,
1820            heuristic_runs,
1821            dsfb_runs,
1822        });
1823    }
1824    Ok(executions)
1825}
1826
1827fn execute_host_realistic_suite(
1828    config: &DemoConfig,
1829    definitions: &[ScenarioDefinition],
1830) -> Result<Vec<(SceneSequence, DsfbRun)>> {
1831    let profile =
1832        default_host_realistic_profile(config.dsfb_alpha_range.min, config.dsfb_alpha_range.max);
1833    definitions
1834        .iter()
1835        .map(|definition| {
1836            let sequence = generate_sequence_for_definition(definition);
1837            let run = run_profiled_taa(&sequence, &profile);
1838            Ok((sequence, run))
1839        })
1840        .collect()
1841}
1842
1843fn build_demo_a_analysis_inputs(
1844    executions: &[ScenarioExecution],
1845) -> Vec<(SceneSequence, Vec<RunAnalysisInput<'_>>)> {
1846    executions
1847        .iter()
1848        .map(|execution| {
1849            let mut runs = Vec::new();
1850            for run in &execution.heuristic_runs {
1851                runs.push(RunAnalysisInput {
1852                    id: &run.id,
1853                    label: &run.label,
1854                    category: "baseline",
1855                    resolved_frames: &run.taa.resolved_frames,
1856                    reprojected_history_frames: &run.taa.reprojected_history_frames,
1857                    alpha_frames: &run.alpha_frames,
1858                    response_frames: &run.response_frames,
1859                    trust_frames: None,
1860                });
1861            }
1862            for run in &execution.dsfb_runs {
1863                let category = if matches!(
1864                    run.run.profile.id.as_str(),
1865                    "dsfb_synthetic_visibility" | "dsfb_host_realistic"
1866                ) {
1867                    "dsfb"
1868                } else {
1869                    "ablation"
1870                };
1871                runs.push(RunAnalysisInput {
1872                    id: &run.run.profile.id,
1873                    label: &run.run.profile.label,
1874                    category,
1875                    resolved_frames: &run.run.resolved_frames,
1876                    reprojected_history_frames: &run.run.reprojected_history_frames,
1877                    alpha_frames: &run.alpha_frames,
1878                    response_frames: &run.response_frames,
1879                    trust_frames: Some(&run.trust_frames),
1880                });
1881            }
1882            (execution.sequence.clone(), runs)
1883        })
1884        .collect()
1885}
1886
1887fn write_demo_a_artifacts(
1888    output_dir: &Path,
1889    config: &DemoConfig,
1890    executions: &[ScenarioExecution],
1891    demo_a_metrics: &DemoASuiteMetrics,
1892    demo_b_metrics: &DemoBSuiteMetrics,
1893) -> Result<DemoAArtifacts> {
1894    fs::create_dir_all(output_dir)?;
1895
1896    let metrics_path = output_dir.join("metrics.json");
1897    fs::write(&metrics_path, serde_json::to_string_pretty(demo_a_metrics)?)?;
1898
1899    let scene_manifest_path = write_canonical_manifest(output_dir, &executions[0].sequence)?;
1900    let scenario_suite_manifest_path = write_suite_manifest_from_executions(
1901        output_dir,
1902        executions,
1903        "scenario_suite_manifest.json",
1904    )?;
1905    write_scenario_debug_artifacts(output_dir, executions)?;
1906
1907    let cost_report = build_cost_report(CostMode::HostRealistic);
1908    let cost_report_path = output_dir.join("cost_report.md");
1909    write_cost_report(&cost_report_path, &cost_report)?;
1910
1911    let report_path = output_dir.join("report.md");
1912    write_report(&report_path, demo_a_metrics, demo_b_metrics, &cost_report)?;
1913    let reviewer_summary_path = output_dir.join("reviewer_summary.md");
1914    write_reviewer_summary(&reviewer_summary_path, demo_a_metrics, demo_b_metrics)?;
1915    let ablation_report_path = output_dir.join("ablation_report.md");
1916    write_ablation_report(&ablation_report_path, &demo_a_metrics.ablations)?;
1917
1918    let figure_paths = write_demo_a_figures(output_dir, config, executions, demo_a_metrics)?;
1919
1920    let completion_note_path = output_dir.join("completion_note.md");
1921    write_completion_note(&completion_note_path, &default_completion_status())?;
1922
1923    Ok(DemoAArtifacts {
1924        output_dir: output_dir.to_path_buf(),
1925        metrics_path,
1926        report_path,
1927        reviewer_summary_path,
1928        completion_note_path,
1929        ablation_report_path,
1930        cost_report_path,
1931        figure_paths,
1932        scene_manifest_path,
1933        scenario_suite_manifest_path,
1934    })
1935}
1936
1937fn write_demo_b_artifacts(
1938    output_dir: &Path,
1939    host_sequences: &[(SceneSequence, DsfbRun)],
1940    demo_b_metrics: &DemoBSuiteMetrics,
1941    demo_b_runs: &[(String, DemoBScenarioRun)],
1942) -> Result<DemoBArtifacts> {
1943    fs::create_dir_all(output_dir)?;
1944
1945    let metrics_path = output_dir.join("metrics.json");
1946    fs::write(&metrics_path, serde_json::to_string_pretty(demo_b_metrics)?)?;
1947
1948    let scene_manifest_path = write_canonical_manifest(output_dir, &host_sequences[0].0)?;
1949    let scenario_suite_manifest_path = write_suite_manifest(
1950        output_dir,
1951        &host_sequences
1952            .iter()
1953            .map(|(sequence, _)| sequence.clone())
1954            .collect::<Vec<_>>(),
1955        "scenario_suite_manifest.json",
1956    )?;
1957
1958    let report_path = output_dir.join("report.md");
1959    write_demo_b_decision_report(&report_path, demo_b_metrics)?;
1960    let figure_paths = write_demo_b_figures(output_dir, demo_b_metrics, demo_b_runs)?;
1961    let image_paths = write_demo_b_images(output_dir, host_sequences, demo_b_runs)?;
1962
1963    Ok(DemoBArtifacts {
1964        output_dir: output_dir.to_path_buf(),
1965        metrics_path,
1966        report_path,
1967        figure_paths,
1968        image_paths,
1969        scene_manifest_path,
1970        scenario_suite_manifest_path,
1971    })
1972}
1973
1974fn write_canonical_manifest(output_dir: &Path, sequence: &SceneSequence) -> Result<PathBuf> {
1975    let path = output_dir.join("scene_manifest.json");
1976    let manifest = build_manifest(sequence);
1977    fs::write(&path, serde_json::to_string_pretty(&manifest)?)?;
1978    Ok(path)
1979}
1980
1981fn write_suite_manifest_from_executions(
1982    output_dir: &Path,
1983    executions: &[ScenarioExecution],
1984    file_name: &str,
1985) -> Result<PathBuf> {
1986    write_suite_manifest(
1987        output_dir,
1988        &executions
1989            .iter()
1990            .map(|execution| execution.sequence.clone())
1991            .collect::<Vec<_>>(),
1992        file_name,
1993    )
1994}
1995
1996fn write_suite_manifest(
1997    output_dir: &Path,
1998    sequences: &[SceneSequence],
1999    file_name: &str,
2000) -> Result<PathBuf> {
2001    let path = output_dir.join(file_name);
2002    let manifest = ScenarioSuiteManifest {
2003        scenarios: sequences.iter().map(build_manifest).collect(),
2004    };
2005    fs::write(&path, serde_json::to_string_pretty(&manifest)?)?;
2006    Ok(path)
2007}
2008
2009fn write_scenario_taxonomy_json(
2010    output_path: &Path,
2011    executions: &[ScenarioExecution],
2012) -> Result<()> {
2013    let entries = executions
2014        .iter()
2015        .map(|execution| ScenarioTaxonomyEntry {
2016            scenario_id: execution.sequence.scenario_id.as_str().to_string(),
2017            support_category: format!("{:?}", execution.sequence.support_category),
2018            expectation: format!("{:?}", execution.sequence.expectation),
2019            labels: taxonomy_labels(
2020                execution.sequence.scenario_id.as_str(),
2021                &execution.sequence.support_category,
2022                execution.sequence.expectation,
2023                execution.sequence.realism_stress,
2024                execution.sequence.competitive_baseline_case,
2025                execution.sequence.bounded_loss_disclosure,
2026                execution.sequence.demo_b_taxonomy.as_str(),
2027            ),
2028            sampling_taxonomy: execution.sequence.sampling_taxonomy.clone(),
2029            realism_stress: execution.sequence.realism_stress,
2030            competitive_baseline_case: execution.sequence.competitive_baseline_case,
2031            bounded_loss_disclosure: execution.sequence.bounded_loss_disclosure,
2032            demo_b_taxonomy: execution.sequence.demo_b_taxonomy.clone(),
2033        })
2034        .collect::<Vec<_>>();
2035    fs::write(output_path, serde_json::to_string_pretty(&entries)?)?;
2036    Ok(())
2037}
2038
2039fn write_demo_b_scene_taxonomy_json(output_path: &Path, demo_b: &DemoBSuiteMetrics) -> Result<()> {
2040    let entries = demo_b
2041        .scenarios
2042        .iter()
2043        .map(|scenario| ScenarioTaxonomyEntry {
2044            scenario_id: scenario.scenario_id.clone(),
2045            support_category: format!("{:?}", scenario.support_category),
2046            expectation: format!("{:?}", scenario.expectation),
2047            labels: taxonomy_labels(
2048                &scenario.scenario_id,
2049                &scenario.support_category,
2050                scenario.expectation,
2051                false,
2052                false,
2053                matches!(scenario.expectation, ScenarioExpectation::NeutralExpected),
2054                scenario.demo_b_taxonomy.as_str(),
2055            ),
2056            sampling_taxonomy: scenario.sampling_taxonomy.clone(),
2057            realism_stress: false,
2058            competitive_baseline_case: false,
2059            bounded_loss_disclosure: false,
2060            demo_b_taxonomy: scenario.demo_b_taxonomy.clone(),
2061        })
2062        .collect::<Vec<_>>();
2063    fs::write(output_path, serde_json::to_string_pretty(&entries)?)?;
2064    Ok(())
2065}
2066
2067fn taxonomy_labels(
2068    scenario_id: &str,
2069    support_category: &crate::scene::ScenarioSupportCategory,
2070    expectation: crate::scene::ScenarioExpectation,
2071    realism_stress: bool,
2072    competitive_baseline_case: bool,
2073    bounded_loss_disclosure: bool,
2074    demo_b_taxonomy: &str,
2075) -> Vec<String> {
2076    let mut labels = Vec::new();
2077    match support_category {
2078        crate::scene::ScenarioSupportCategory::PointLikeRoi => labels.push("point_roi".to_string()),
2079        crate::scene::ScenarioSupportCategory::RegionRoi => labels.push("region_roi".to_string()),
2080        crate::scene::ScenarioSupportCategory::NegativeControl => {
2081            labels.push("negative_control".to_string())
2082        }
2083    }
2084    if realism_stress {
2085        labels.push("realism_stress".to_string());
2086    }
2087    if competitive_baseline_case {
2088        labels.push("strong_heuristic_competitive".to_string());
2089    }
2090    if bounded_loss_disclosure {
2091        if matches!(expectation, ScenarioExpectation::NeutralExpected) {
2092            labels.push("bounded_neutral".to_string());
2093        } else {
2094            labels.push("bounded_loss".to_string());
2095        }
2096    }
2097    if scenario_id == "motion_bias_band" || scenario_id == "fast_pan" {
2098        labels.push("motion_relevance_probe".to_string());
2099    }
2100    match demo_b_taxonomy {
2101        "aliasing_limited" => labels.push("aliasing_limited".to_string()),
2102        "variance_limited" => labels.push("variance_limited".to_string()),
2103        "mixed" | "edge_trap" => labels.push("mixed_regime".to_string()),
2104        _ => {}
2105    }
2106    labels
2107}
2108
2109fn write_scenario_debug_artifacts(
2110    output_dir: &Path,
2111    executions: &[ScenarioExecution],
2112) -> Result<()> {
2113    let scenarios_dir = output_dir.join("scenarios");
2114    fs::create_dir_all(&scenarios_dir)?;
2115
2116    for execution in executions {
2117        let scenario_dir = scenarios_dir.join(execution.sequence.scenario_id.as_str());
2118        fs::create_dir_all(&scenario_dir)?;
2119        fs::write(
2120            scenario_dir.join("scene_manifest.json"),
2121            serde_json::to_string_pretty(&build_manifest(&execution.sequence))?,
2122        )?;
2123
2124        write_frame_sequence(
2125            &scenario_dir.join("frames").join("gt"),
2126            &execution
2127                .sequence
2128                .frames
2129                .iter()
2130                .map(|frame| frame.ground_truth.clone())
2131                .collect::<Vec<_>>(),
2132        )?;
2133        write_frame_sequence(
2134            &scenario_dir.join("frames").join("fixed_alpha"),
2135            &execution.heuristic("fixed_alpha")?.taa.resolved_frames,
2136        )?;
2137        write_frame_sequence(
2138            &scenario_dir.join("frames").join("strong_heuristic"),
2139            &execution.heuristic("strong_heuristic")?.taa.resolved_frames,
2140        )?;
2141        write_frame_sequence(
2142            &scenario_dir.join("frames").join("dsfb_host_realistic"),
2143            &execution.dsfb("dsfb_host_realistic")?.run.resolved_frames,
2144        )?;
2145        write_frame_sequence(
2146            &scenario_dir.join("frames").join("dsfb_visibility_assisted"),
2147            &execution
2148                .dsfb("dsfb_synthetic_visibility")?
2149                .run
2150                .resolved_frames,
2151        )?;
2152
2153        let host = execution.dsfb("dsfb_host_realistic")?;
2154        let debug_dir = scenario_dir.join("debug");
2155        save_scalar_sequence(
2156            &debug_dir.join("residual"),
2157            host.run
2158                .supervision_frames
2159                .iter()
2160                .map(|frame| &frame.residual)
2161                .collect::<Vec<_>>(),
2162            residual_palette,
2163        )?;
2164        save_scalar_sequence(
2165            &debug_dir.join("trust"),
2166            host.run
2167                .supervision_frames
2168                .iter()
2169                .map(|frame| &frame.trust)
2170                .collect::<Vec<_>>(),
2171            trust_palette,
2172        )?;
2173        save_scalar_sequence(
2174            &debug_dir.join("alpha"),
2175            host.run
2176                .supervision_frames
2177                .iter()
2178                .map(|frame| &frame.alpha)
2179                .collect::<Vec<_>>(),
2180            alpha_palette,
2181        )?;
2182        save_scalar_sequence(
2183            &debug_dir.join("intervention"),
2184            host.run
2185                .supervision_frames
2186                .iter()
2187                .map(|frame| &frame.intervention)
2188                .collect::<Vec<_>>(),
2189            intervention_palette,
2190        )?;
2191        save_scalar_sequence(
2192            &debug_dir.join("proxy_residual"),
2193            host.run
2194                .supervision_frames
2195                .iter()
2196                .map(|frame| &frame.proxies.residual_proxy)
2197                .collect::<Vec<_>>(),
2198            proxy_palette,
2199        )?;
2200        save_scalar_sequence(
2201            &debug_dir.join("proxy_visibility"),
2202            host.run
2203                .supervision_frames
2204                .iter()
2205                .map(|frame| &frame.proxies.visibility_proxy)
2206                .collect::<Vec<_>>(),
2207            proxy_palette,
2208        )?;
2209        save_scalar_sequence(
2210            &debug_dir.join("proxy_motion"),
2211            host.run
2212                .supervision_frames
2213                .iter()
2214                .map(|frame| &frame.proxies.motion_proxy)
2215                .collect::<Vec<_>>(),
2216            proxy_palette,
2217        )?;
2218        save_scalar_sequence(
2219            &debug_dir.join("proxy_thin"),
2220            host.run
2221                .supervision_frames
2222                .iter()
2223                .map(|frame| &frame.proxies.thin_proxy)
2224                .collect::<Vec<_>>(),
2225            proxy_palette,
2226        )?;
2227        save_state_sequence(
2228            &debug_dir.join("state"),
2229            &host
2230                .run
2231                .supervision_frames
2232                .iter()
2233                .map(|frame| &frame.state)
2234                .collect::<Vec<_>>(),
2235        )?;
2236    }
2237
2238    Ok(())
2239}
2240
2241fn write_frame_sequence(directory: &Path, frames: &[ImageFrame]) -> Result<()> {
2242    fs::create_dir_all(directory)?;
2243    for (frame_index, frame) in frames.iter().enumerate() {
2244        frame.save_png(&directory.join(format!("frame_{frame_index:02}.png")))?;
2245    }
2246    Ok(())
2247}
2248
2249fn save_scalar_sequence(
2250    directory: &Path,
2251    fields: Vec<&ScalarField>,
2252    palette: impl Fn(f32) -> [u8; 4] + Copy,
2253) -> Result<()> {
2254    fs::create_dir_all(directory)?;
2255    for (frame_index, field) in fields.into_iter().enumerate() {
2256        save_scalar_field_png(
2257            field,
2258            &directory.join(format!("frame_{frame_index:02}.png")),
2259            palette,
2260        )?;
2261    }
2262    Ok(())
2263}
2264
2265fn save_state_sequence(directory: &Path, states: &[&crate::dsfb::StateField]) -> Result<()> {
2266    fs::create_dir_all(directory)?;
2267    for (frame_index, state) in states.iter().enumerate() {
2268        save_state_field_png(
2269            state.values(),
2270            state.width(),
2271            state.height(),
2272            &directory.join(format!("frame_{frame_index:02}.png")),
2273        )?;
2274    }
2275    Ok(())
2276}
2277
2278fn save_state_field_png(
2279    values: &[StructuralState],
2280    width: usize,
2281    height: usize,
2282    path: &Path,
2283) -> Result<()> {
2284    let mut field = ScalarField::new(width.max(1), height.max(1));
2285    for (index, state) in values.iter().enumerate() {
2286        let value = match state {
2287            StructuralState::Nominal => 0.10,
2288            StructuralState::DisocclusionLike => 1.00,
2289            StructuralState::UnstableHistory => 0.70,
2290            StructuralState::MotionEdge => 0.45,
2291        };
2292        field.set(index % field.width(), index / field.width(), value);
2293    }
2294    save_scalar_field_png(&field, path, state_palette)
2295}
2296
2297fn write_demo_a_figures(
2298    output_dir: &Path,
2299    config: &DemoConfig,
2300    executions: &[ScenarioExecution],
2301    demo_a_metrics: &DemoASuiteMetrics,
2302) -> Result<Vec<PathBuf>> {
2303    let figures_dir = output_dir.join("figures");
2304    fs::create_dir_all(&figures_dir)?;
2305
2306    let canonical_execution = &executions[0];
2307    let canonical_report = &demo_a_metrics.scenarios[0];
2308    let canonical_bbox = canonical_execution.focus_bbox()?;
2309    let onset_frame = canonical_execution.onset_frame();
2310    let comparison_frame = canonical_execution.comparison_frame(config);
2311
2312    let system_diagram = figures_dir.join("fig_system_diagram.svg");
2313    write_system_diagram(&system_diagram)?;
2314
2315    let trust_map = figures_dir.join("fig_trust_map.svg");
2316    write_trust_map_figure(
2317        &canonical_execution.sequence.frames[onset_frame].ground_truth,
2318        &canonical_execution
2319            .dsfb("dsfb_host_realistic")?
2320            .run
2321            .supervision_frames[onset_frame]
2322            .trust,
2323        canonical_bbox,
2324        &trust_map,
2325    )?;
2326
2327    let before_after = figures_dir.join("fig_before_after.svg");
2328    write_before_after_figure(
2329        &canonical_execution
2330            .heuristic("fixed_alpha")?
2331            .taa
2332            .resolved_frames[comparison_frame],
2333        &canonical_execution
2334            .heuristic("strong_heuristic")?
2335            .taa
2336            .resolved_frames[comparison_frame],
2337        &canonical_execution
2338            .dsfb("dsfb_host_realistic")?
2339            .run
2340            .resolved_frames[comparison_frame],
2341        canonical_bbox,
2342        &before_after,
2343    )?;
2344
2345    let trust_vs_error = figures_dir.join("fig_trust_vs_error.svg");
2346    write_trust_vs_error_figure(canonical_report, &trust_vs_error)?;
2347
2348    let intervention_alpha = figures_dir.join("fig_intervention_alpha.svg");
2349    write_intervention_alpha_figure(
2350        &canonical_execution.sequence.frames[onset_frame].ground_truth,
2351        &canonical_execution
2352            .dsfb("dsfb_host_realistic")?
2353            .run
2354            .supervision_frames[onset_frame]
2355            .intervention,
2356        &canonical_execution
2357            .dsfb("dsfb_host_realistic")?
2358            .run
2359            .supervision_frames[onset_frame]
2360            .alpha,
2361        canonical_bbox,
2362        &intervention_alpha,
2363    )?;
2364
2365    let ablation = figures_dir.join("fig_ablation.svg");
2366    write_ablation_bar_figure(&demo_a_metrics.ablations, &ablation)?;
2367
2368    let roi_nonroi = figures_dir.join("fig_roi_nonroi_error.svg");
2369    write_roi_nonroi_error_figure(canonical_report, &roi_nonroi)?;
2370
2371    let leaderboard = figures_dir.join("fig_leaderboard.svg");
2372    write_leaderboard_figure(&demo_a_metrics.aggregate_leaderboard, &leaderboard)?;
2373
2374    let mosaic = figures_dir.join("fig_scenario_mosaic.svg");
2375    let mut mosaic_entries = Vec::new();
2376    for execution in executions {
2377        let frame_index = execution.comparison_frame(config);
2378        mosaic_entries.push(ScenarioMosaicEntry {
2379            scenario_title: &execution.sequence.scenario_title,
2380            baseline: &execution.heuristic("fixed_alpha")?.taa.resolved_frames[frame_index],
2381            heuristic: &execution.heuristic("strong_heuristic")?.taa.resolved_frames[frame_index],
2382            host_realistic: &execution.dsfb("dsfb_host_realistic")?.run.resolved_frames
2383                [frame_index],
2384            focus_bbox: execution.focus_bbox()?,
2385        });
2386    }
2387    write_scenario_mosaic_figure(&mosaic_entries, &mosaic)?;
2388
2389    Ok(vec![
2390        system_diagram,
2391        trust_map,
2392        before_after,
2393        trust_vs_error,
2394        intervention_alpha,
2395        ablation,
2396        roi_nonroi,
2397        leaderboard,
2398        mosaic,
2399    ])
2400}
2401
2402fn write_demo_b_figures(
2403    output_dir: &Path,
2404    demo_b_metrics: &DemoBSuiteMetrics,
2405    demo_b_runs: &[(String, DemoBScenarioRun)],
2406) -> Result<Vec<PathBuf>> {
2407    let figures_dir = output_dir.join("figures");
2408    fs::create_dir_all(&figures_dir)?;
2409
2410    let canonical_report = demo_b_metrics
2411        .scenarios
2412        .iter()
2413        .find(|scenario| scenario.scenario_id == ScenarioId::ThinReveal.as_str())
2414        .or_else(|| demo_b_metrics.scenarios.first())
2415        .ok_or_else(|| Error::Message("Demo B had no scenarios".to_string()))?;
2416    let canonical_run = demo_b_runs
2417        .iter()
2418        .find(|(scenario_id, _)| scenario_id == canonical_report.scenario_id.as_str())
2419        .map(|(_, run)| run)
2420        .ok_or_else(|| Error::Message("canonical Demo B run missing".to_string()))?;
2421
2422    let sampling = figures_dir.join("fig_demo_b_sampling.svg");
2423    write_demo_b_sampling_figure(canonical_report, canonical_run, &sampling)?;
2424
2425    let efficiency = figures_dir.join("fig_demo_b_budget_efficiency.svg");
2426    write_demo_b_budget_efficiency_figure(&demo_b_metrics.budget_efficiency_curves, &efficiency)?;
2427
2428    Ok(vec![sampling, efficiency])
2429}
2430
2431fn write_demo_b_images(
2432    output_dir: &Path,
2433    host_sequences: &[(SceneSequence, DsfbRun)],
2434    demo_b_runs: &[(String, DemoBScenarioRun)],
2435) -> Result<Vec<PathBuf>> {
2436    let images_dir = output_dir.join("images");
2437    fs::create_dir_all(&images_dir)?;
2438
2439    let canonical_run = demo_b_runs
2440        .iter()
2441        .find(|(scenario_id, _)| scenario_id == ScenarioId::ThinReveal.as_str())
2442        .or_else(|| demo_b_runs.first())
2443        .ok_or_else(|| Error::Message("Demo B had no scenario runs".to_string()))?;
2444    let canonical_host = host_sequences
2445        .iter()
2446        .find(|(sequence, _)| sequence.scenario_id.as_str() == canonical_run.0.as_str())
2447        .or_else(|| host_sequences.first())
2448        .ok_or_else(|| Error::Message("host-realistic sequence missing for Demo B".to_string()))?;
2449    let run = &canonical_run.1;
2450    let onset = canonical_host
2451        .0
2452        .onset_frame
2453        .min(canonical_host.0.frames.len().saturating_sub(1));
2454
2455    let uniform = run
2456        .policy_runs
2457        .iter()
2458        .find(|policy| policy.policy_id == AllocationPolicyId::Uniform)
2459        .ok_or_else(|| Error::Message("uniform Demo B policy missing".to_string()))?;
2460    let combined = run
2461        .policy_runs
2462        .iter()
2463        .find(|policy| policy.policy_id == AllocationPolicyId::CombinedHeuristic)
2464        .ok_or_else(|| Error::Message("combined heuristic Demo B policy missing".to_string()))?;
2465    let imported = run
2466        .policy_runs
2467        .iter()
2468        .find(|policy| policy.policy_id == AllocationPolicyId::ImportedTrust)
2469        .ok_or_else(|| Error::Message("imported trust Demo B policy missing".to_string()))?;
2470
2471    let reference_path = images_dir.join("reference.png");
2472    run.reference_frame.save_png(&reference_path)?;
2473    let uniform_path = images_dir.join("uniform.png");
2474    uniform.frame.save_png(&uniform_path)?;
2475    let combined_path = images_dir.join("combined_heuristic.png");
2476    combined.frame.save_png(&combined_path)?;
2477    let imported_path = images_dir.join("imported_trust.png");
2478    imported.frame.save_png(&imported_path)?;
2479    let guided_alias_path = images_dir.join("guided.png");
2480    imported.frame.save_png(&guided_alias_path)?;
2481
2482    let uniform_error_path = images_dir.join("uniform_error.png");
2483    save_scalar_field_png(&uniform.error, &uniform_error_path, error_palette)?;
2484    let combined_error_path = images_dir.join("combined_heuristic_error.png");
2485    save_scalar_field_png(&combined.error, &combined_error_path, error_palette)?;
2486    let imported_error_path = images_dir.join("imported_trust_error.png");
2487    save_scalar_field_png(&imported.error, &imported_error_path, error_palette)?;
2488    let guided_error_alias_path = images_dir.join("guided_error.png");
2489    save_scalar_field_png(&imported.error, &guided_error_alias_path, error_palette)?;
2490
2491    let combined_spp_path = images_dir.join("combined_heuristic_spp.png");
2492    save_scalar_field_png(&combined.spp, &combined_spp_path, |value| {
2493        allocation_palette(value, combined.metrics.max_spp as f32)
2494    })?;
2495    let imported_spp_path = images_dir.join("imported_trust_spp.png");
2496    save_scalar_field_png(&imported.spp, &imported_spp_path, |value| {
2497        allocation_palette(value, imported.metrics.max_spp as f32)
2498    })?;
2499    let guided_spp_alias_path = images_dir.join("guided_spp.png");
2500    save_scalar_field_png(&imported.spp, &guided_spp_alias_path, |value| {
2501        allocation_palette(value, imported.metrics.max_spp as f32)
2502    })?;
2503
2504    let trust_path = images_dir.join("trust.png");
2505    save_scalar_field_png(
2506        &canonical_host.1.supervision_frames[onset].trust,
2507        &trust_path,
2508        trust_palette,
2509    )?;
2510
2511    Ok(vec![
2512        reference_path,
2513        uniform_path,
2514        combined_path,
2515        imported_path,
2516        guided_alias_path,
2517        uniform_error_path,
2518        combined_error_path,
2519        imported_error_path,
2520        guided_error_alias_path,
2521        combined_spp_path,
2522        imported_spp_path,
2523        guided_spp_alias_path,
2524        trust_path,
2525    ])
2526}
2527
2528fn write_additional_figures(
2529    output_dir: &Path,
2530    demo_a_metrics: &DemoASuiteMetrics,
2531    trust_diagnostics: &crate::report::TrustDiagnostics,
2532    resolution_scaling_metrics: &crate::scaling::ResolutionScalingMetrics,
2533    parameter_sensitivity_metrics: &crate::sensitivity::ParameterSensitivityMetrics,
2534) -> Result<Vec<PathBuf>> {
2535    let figures_dir = output_dir.join("figures");
2536    fs::create_dir_all(&figures_dir)?;
2537
2538    let trust_histogram = figures_dir.join("fig_trust_histogram.svg");
2539    write_trust_histogram_figure(trust_diagnostics, &trust_histogram)?;
2540
2541    let roi_taxonomy = figures_dir.join("fig_roi_taxonomy.svg");
2542    write_roi_taxonomy_figure(demo_a_metrics, &roi_taxonomy)?;
2543
2544    let parameter_sensitivity = figures_dir.join("fig_parameter_sensitivity.svg");
2545    write_parameter_sensitivity_figure(parameter_sensitivity_metrics, &parameter_sensitivity)?;
2546
2547    let resolution_scaling = figures_dir.join("fig_resolution_scaling.svg");
2548    write_resolution_scaling_figure(resolution_scaling_metrics, &resolution_scaling)?;
2549
2550    let motion_relevance = figures_dir.join("fig_motion_relevance.svg");
2551    write_motion_relevance_figure(demo_a_metrics, &motion_relevance)?;
2552
2553    Ok(vec![
2554        trust_histogram,
2555        roi_taxonomy,
2556        parameter_sensitivity,
2557        resolution_scaling,
2558        motion_relevance,
2559    ])
2560}
2561
2562fn write_notebook_artifact_manifest(
2563    output_dir: &Path,
2564    demo_a: &DemoAArtifacts,
2565    demo_b: &DemoBArtifacts,
2566    reviewer_report_paths: &[&Path],
2567) -> Result<PathBuf> {
2568    let run_name = output_dir
2569        .file_name()
2570        .and_then(|name| name.to_str())
2571        .unwrap_or("output-dsfb-computer-graphics-run")
2572        .to_string();
2573
2574    let manifest = NotebookArtifactManifest {
2575        output_root_name: NOTEBOOK_OUTPUT_ROOT_NAME.to_string(),
2576        run_name: run_name.clone(),
2577        artifact_manifest_file_name: ARTIFACT_MANIFEST_FILE_NAME.to_string(),
2578        pdf_bundle_file_name: pdf_bundle_path(output_dir)
2579            .file_name()
2580            .and_then(|name| name.to_str())
2581            .unwrap_or("artifacts_bundle.pdf")
2582            .to_string(),
2583        zip_bundle_file_name: format_zip_bundle_name(&run_name),
2584        demo_a: NotebookDemoAArtifacts {
2585            metrics_path: relative_string(&demo_a.metrics_path, output_dir),
2586            report_path: relative_string(&demo_a.report_path, output_dir),
2587            reviewer_summary_path: relative_string(&demo_a.reviewer_summary_path, output_dir),
2588            completion_note_path: relative_string(&demo_a.completion_note_path, output_dir),
2589            scene_manifest_path: relative_string(&demo_a.scene_manifest_path, output_dir),
2590            scenario_suite_manifest_path: relative_string(
2591                &demo_a.scenario_suite_manifest_path,
2592                output_dir,
2593            ),
2594            ablation_report_path: relative_string(&demo_a.ablation_report_path, output_dir),
2595            cost_report_path: relative_string(&demo_a.cost_report_path, output_dir),
2596            figure_paths: demo_a
2597                .figure_paths
2598                .iter()
2599                .map(|path| relative_string(path, output_dir))
2600                .collect(),
2601        },
2602        demo_b: NotebookDemoBArtifacts {
2603            metrics_path: relative_string(&demo_b.metrics_path, output_dir),
2604            report_path: relative_string(&demo_b.report_path, output_dir),
2605            scene_manifest_path: relative_string(&demo_b.scene_manifest_path, output_dir),
2606            scenario_suite_manifest_path: relative_string(
2607                &demo_b.scenario_suite_manifest_path,
2608                output_dir,
2609            ),
2610            figure_paths: demo_b
2611                .figure_paths
2612                .iter()
2613                .map(|path| relative_string(path, output_dir))
2614                .collect(),
2615            image_paths: demo_b
2616                .image_paths
2617                .iter()
2618                .map(|path| relative_string(path, output_dir))
2619                .collect(),
2620        },
2621        reviewer_report_paths: reviewer_report_paths
2622            .iter()
2623            .map(|path| relative_string(path, output_dir))
2624            .collect(),
2625    };
2626
2627    let path = output_dir.join(ARTIFACT_MANIFEST_FILE_NAME);
2628    fs::write(&path, serde_json::to_string_pretty(&manifest)?)?;
2629    Ok(path)
2630}
2631
2632fn validate_demo_a_metrics(demo_a_metrics: &DemoASuiteMetrics) -> Result<()> {
2633    let expected_scenarios = [
2634        "thin_reveal",
2635        "fast_pan",
2636        "diagonal_reveal",
2637        "reveal_band",
2638        "motion_bias_band",
2639        "layered_slats",
2640        "noisy_reprojection",
2641        "heuristic_friendly_pan",
2642        "contrast_pulse",
2643        "stability_holdout",
2644    ];
2645    let expected_baselines = [
2646        "fixed_alpha",
2647        "residual_threshold",
2648        "neighborhood_clamp",
2649        "depth_normal_reject",
2650        "reactive_mask",
2651        "strong_heuristic",
2652    ];
2653    let expected_ablations = [
2654        "dsfb_synthetic_visibility",
2655        "dsfb_host_realistic",
2656        "dsfb_host_gated_reference",
2657        "dsfb_motion_augmented",
2658        "dsfb_no_visibility",
2659        "dsfb_no_thin",
2660        "dsfb_no_motion_edge",
2661        "dsfb_no_grammar",
2662        "dsfb_residual_only",
2663        "dsfb_trust_no_alpha",
2664    ];
2665
2666    let full_suite = demo_a_metrics.summary.scenario_ids.len() > 1;
2667    if full_suite {
2668        if demo_a_metrics.summary.scenario_ids.len() < expected_scenarios.len() {
2669            return Err(Error::Message(
2670                "Demo A scenario suite is too small for blocker-clearing evaluation".to_string(),
2671            ));
2672        }
2673        for scenario_id in expected_scenarios {
2674            if !demo_a_metrics
2675                .summary
2676                .scenario_ids
2677                .iter()
2678                .any(|current| current == scenario_id)
2679            {
2680                return Err(Error::Message(format!(
2681                    "Demo A scenario suite is missing required scenario {scenario_id}"
2682                )));
2683            }
2684        }
2685    }
2686    for baseline_id in expected_baselines {
2687        if !demo_a_metrics
2688            .summary
2689            .baseline_ids
2690            .iter()
2691            .any(|current| current == baseline_id)
2692        {
2693            return Err(Error::Message(format!(
2694                "Demo A baseline list is missing {baseline_id}"
2695            )));
2696        }
2697    }
2698    for ablation_id in expected_ablations {
2699        if !demo_a_metrics
2700            .summary
2701            .ablation_ids
2702            .iter()
2703            .any(|current| current == ablation_id)
2704        {
2705            return Err(Error::Message(format!(
2706                "Demo A ablation list is missing {ablation_id}"
2707            )));
2708        }
2709    }
2710
2711    let canonical = demo_a_metrics
2712        .scenarios
2713        .iter()
2714        .find(|scenario| scenario.scenario_id == "thin_reveal")
2715        .or_else(|| demo_a_metrics.scenarios.first())
2716        .ok_or_else(|| Error::Message("Demo A produced no scenario reports".to_string()))?;
2717    let fixed = canonical
2718        .runs
2719        .iter()
2720        .find(|run| run.summary.run_id == "fixed_alpha")
2721        .ok_or_else(|| Error::Message("canonical fixed_alpha run missing".to_string()))?;
2722    let host = canonical
2723        .runs
2724        .iter()
2725        .find(|run| run.summary.run_id == "dsfb_host_realistic")
2726        .ok_or_else(|| Error::Message("canonical host-realistic run missing".to_string()))?;
2727    if host.summary.cumulative_roi_mae + 1e-6 >= fixed.summary.cumulative_roi_mae {
2728        return Err(Error::Message(
2729            "host-realistic DSFB does not outperform fixed alpha on the canonical scenario"
2730                .to_string(),
2731        ));
2732    }
2733    if full_suite {
2734        if !demo_a_metrics
2735            .scenarios
2736            .iter()
2737            .any(|scenario| matches!(scenario.expectation, ScenarioExpectation::NeutralExpected))
2738        {
2739            return Err(Error::Message(
2740                "Demo A is missing a neutral honesty scenario".to_string(),
2741            ));
2742        }
2743        if demo_a_metrics.summary.mixed_or_neutral_scenarios.is_empty() {
2744            return Err(Error::Message(
2745                "Demo A is missing a mixed or neutral surfaced outcome".to_string(),
2746            ));
2747        }
2748        if demo_a_metrics.summary.point_roi_scenarios.is_empty()
2749            || demo_a_metrics.summary.region_roi_scenarios.is_empty()
2750        {
2751            return Err(Error::Message(
2752                "Demo A must surface both point-like and region ROI scenario groups".to_string(),
2753            ));
2754        }
2755        if !demo_a_metrics
2756            .scenarios
2757            .iter()
2758            .any(|scenario| scenario.realism_stress)
2759        {
2760            return Err(Error::Message(
2761                "Demo A must surface at least one realism-stress scenario".to_string(),
2762            ));
2763        }
2764        if !demo_a_metrics
2765            .scenarios
2766            .iter()
2767            .any(|scenario| scenario.competitive_baseline_case)
2768        {
2769            return Err(Error::Message(
2770                "Demo A must surface at least one competitive-baseline scenario".to_string(),
2771            ));
2772        }
2773        if !demo_a_metrics
2774            .scenarios
2775            .iter()
2776            .any(|scenario| scenario.bounded_loss_disclosure)
2777        {
2778            return Err(Error::Message(
2779                "Demo A must surface at least one bounded-neutral or bounded-loss disclosure"
2780                    .to_string(),
2781            ));
2782        }
2783    }
2784
2785    if full_suite
2786        && host.summary.cumulative_roi_mae + 1e-6
2787            >= strong_heuristic_run(canonical_report(demo_a_metrics)?)?
2788                .summary
2789                .cumulative_roi_mae
2790    {
2791        return Err(Error::Message(
2792            "host-realistic DSFB should remain competitive with the strong heuristic on the canonical full-suite case"
2793                .to_string(),
2794        ));
2795    }
2796    for scenario in &demo_a_metrics.scenarios {
2797        if scenario.target_pixels == 0 {
2798            return Err(Error::Message(format!(
2799                "scenario {} reported zero ROI pixels",
2800                scenario.scenario_id
2801            )));
2802        }
2803        if scenario.roi_note.trim().is_empty() {
2804            return Err(Error::Message(format!(
2805                "scenario {} is missing ROI disclosure text",
2806                scenario.scenario_id
2807            )));
2808        }
2809    }
2810
2811    Ok(())
2812}
2813
2814fn validate_demo_b_metrics(demo_b_metrics: &DemoBSuiteMetrics) -> Result<()> {
2815    let expected_policies = [
2816        "uniform",
2817        "edge_guided",
2818        "residual_guided",
2819        "contrast_guided",
2820        "variance_guided",
2821        "combined_heuristic",
2822        "native_trust",
2823        "imported_trust",
2824        "hybrid_trust_variance",
2825    ];
2826    let full_suite = demo_b_metrics.scenarios.len() > 1;
2827    if full_suite && demo_b_metrics.scenarios.len() < 5 {
2828        return Err(Error::Message(
2829            "Demo B scenario suite is too small for decision-grade evaluation".to_string(),
2830        ));
2831    }
2832    for policy_id in expected_policies {
2833        if !demo_b_metrics
2834            .summary
2835            .policy_ids
2836            .iter()
2837            .any(|current| current == policy_id)
2838        {
2839            return Err(Error::Message(format!(
2840                "Demo B policy list is missing {policy_id}"
2841            )));
2842        }
2843    }
2844    if demo_b_metrics
2845        .summary
2846        .imported_trust_beats_uniform_scenarios
2847        == 0
2848    {
2849        return Err(Error::Message(
2850            "Demo B does not show any imported-trust win over uniform allocation".to_string(),
2851        ));
2852    }
2853    if full_suite && demo_b_metrics.summary.neutral_or_mixed_scenarios.is_empty() {
2854        return Err(Error::Message(
2855            "Demo B is missing a mixed or neutral surfaced outcome".to_string(),
2856        ));
2857    }
2858    for scenario in &demo_b_metrics.scenarios {
2859        let expected_total = scenario
2860            .policies
2861            .first()
2862            .map(|policy| policy.total_samples)
2863            .ok_or_else(|| Error::Message("Demo B scenario had no policies".to_string()))?;
2864        for policy in &scenario.policies {
2865            if policy.total_samples != expected_total {
2866                return Err(Error::Message(format!(
2867                    "Demo B policy {} in scenario {} violated the fixed budget",
2868                    policy.policy_id, scenario.scenario_id
2869                )));
2870            }
2871        }
2872    }
2873    Ok(())
2874}
2875
2876fn canonical_report(demo_a_metrics: &DemoASuiteMetrics) -> Result<&crate::metrics::ScenarioReport> {
2877    demo_a_metrics
2878        .scenarios
2879        .iter()
2880        .find(|scenario| scenario.scenario_id == "thin_reveal")
2881        .or_else(|| demo_a_metrics.scenarios.first())
2882        .ok_or_else(|| Error::Message("Demo A produced no scenario reports".to_string()))
2883}
2884
2885fn strong_heuristic_run(
2886    scenario: &crate::metrics::ScenarioReport,
2887) -> Result<&crate::metrics::ScenarioRunReport> {
2888    scenario
2889        .runs
2890        .iter()
2891        .find(|run| run.summary.run_id == "strong_heuristic")
2892        .ok_or_else(|| Error::Message("canonical strong heuristic run missing".to_string()))
2893}
2894
2895fn validate_demo_a_artifacts(
2896    artifacts: &DemoAArtifacts,
2897    demo_a_metrics: &DemoASuiteMetrics,
2898) -> Result<()> {
2899    for path in [
2900        &artifacts.metrics_path,
2901        &artifacts.report_path,
2902        &artifacts.reviewer_summary_path,
2903        &artifacts.completion_note_path,
2904        &artifacts.ablation_report_path,
2905        &artifacts.cost_report_path,
2906        &artifacts.scene_manifest_path,
2907        &artifacts.scenario_suite_manifest_path,
2908    ] {
2909        require_file(path)?;
2910    }
2911    if artifacts.figure_paths.len() < 8 {
2912        return Err(Error::Message(
2913            "Demo A did not write the required figure set".to_string(),
2914        ));
2915    }
2916    for path in &artifacts.figure_paths {
2917        require_file(path)?;
2918    }
2919    let report = fs::read_to_string(&artifacts.report_path)?;
2920    if !report.contains("## Remaining Blockers") || !report.contains("## What Is Not Proven") {
2921        return Err(Error::Message(
2922            "Demo A report is missing blocker or non-proof sections".to_string(),
2923        ));
2924    }
2925    if !report.contains(EXPERIMENT_SENTENCE) {
2926        return Err(Error::Message(
2927            "Demo A report is missing the required honesty sentence".to_string(),
2928        ));
2929    }
2930    if demo_a_metrics.summary.primary_behavioral_result.is_empty() {
2931        return Err(Error::Message(
2932            "Demo A summary did not produce a headline behavioral result".to_string(),
2933        ));
2934    }
2935    Ok(())
2936}
2937
2938fn validate_demo_b_artifacts(
2939    artifacts: &DemoBArtifacts,
2940    demo_b_metrics: &DemoBSuiteMetrics,
2941) -> Result<()> {
2942    for path in [
2943        &artifacts.metrics_path,
2944        &artifacts.report_path,
2945        &artifacts.scene_manifest_path,
2946        &artifacts.scenario_suite_manifest_path,
2947    ] {
2948        require_file(path)?;
2949    }
2950    for path in &artifacts.figure_paths {
2951        require_file(path)?;
2952    }
2953    for path in &artifacts.image_paths {
2954        require_file(path)?;
2955    }
2956    let report = fs::read_to_string(&artifacts.report_path)?;
2957    if !report.contains("## What is not proven") && !report.contains("## What Is Not Proven") {
2958        return Err(Error::Message(
2959            "Demo B decision report is missing a non-proof section".to_string(),
2960        ));
2961    }
2962    if demo_b_metrics.summary.primary_behavioral_result.is_empty() {
2963        return Err(Error::Message(
2964            "Demo B summary did not produce a headline behavioral result".to_string(),
2965        ));
2966    }
2967    Ok(())
2968}
2969
2970fn validate_full_artifacts(
2971    output_dir: &Path,
2972    manifest_path: &Path,
2973    reviewer_report_paths: &[&Path],
2974) -> Result<()> {
2975    require_file(manifest_path)?;
2976    for path in reviewer_report_paths {
2977        require_file(path)?;
2978    }
2979    validate_artifact_bundle(output_dir).and_then(|_| validate_decision_reports(output_dir))
2980}
2981
2982fn validate_decision_reports(output_dir: &Path) -> Result<()> {
2983    for file_name in [
2984        "report.md",
2985        "reviewer_summary.md",
2986        "five_mentor_audit.md",
2987        "check_signing_blockers.md",
2988        "trust_diagnostics.md",
2989        "trust_mode_report.md",
2990        "timing_report.md",
2991        "gpu_execution_report.md",
2992        "external_replay_report.md",
2993        "external_handoff_report.md",
2994        "external_real/external_validation_report.md",
2995        "external_real/gpu_external_report.md",
2996        "external_real/demo_a_external_report.md",
2997        "external_real/demo_b_external_report.md",
2998        "external_real/scaling_report.md",
2999        "external_real/memory_bandwidth_report.md",
3000        "external_real/integration_scaling_report.md",
3001        "resolution_scaling_report.md",
3002        "realism_suite_report.md",
3003        "realism_bridge_report.md",
3004        "parameter_sensitivity_report.md",
3005        "operating_band_report.md",
3006        "competitive_baseline_analysis.md",
3007        "non_roi_penalty_report.md",
3008        "product_positioning_report.md",
3009        "demo_b_decision_report.md",
3010        "demo_b_efficiency_report.md",
3011        "demo_b_competitive_baselines_report.md",
3012        "demo_b_aliasing_vs_variance_report.md",
3013        "production_eval_checklist.md",
3014        "evaluator_handoff.md",
3015        "minimum_external_validation_plan.md",
3016        "next_step_matrix.md",
3017        "check_signing_readiness.md",
3018    ] {
3019        let text = fs::read_to_string(output_dir.join(file_name))?;
3020        let normalized = text.to_ascii_lowercase();
3021        if !normalized.contains("what is not proven") {
3022            return Err(Error::Message(format!(
3023                "{file_name} is missing a what-is-not-proven section"
3024            )));
3025        }
3026        if !normalized.contains("remaining blockers") {
3027            return Err(Error::Message(format!(
3028                "{file_name} is missing a remaining blockers section"
3029            )));
3030        }
3031        if !normalized.contains("external") && !matches!(file_name, "trust_diagnostics.md") {
3032            return Err(Error::Message(format!(
3033                "{file_name} must mention external validation or external handoff needs"
3034            )));
3035        }
3036        if normalized.contains("universal replacement")
3037            || normalized.contains("universal win")
3038            || normalized.contains("production-ready")
3039        {
3040            return Err(Error::Message(format!(
3041                "{file_name} contains unsupported universal or production-ready language"
3042            )));
3043        }
3044    }
3045    Ok(())
3046}
3047
3048fn validate_new_gates(output_dir: &Path) -> Result<()> {
3049    // Gate 1: motion_disagree_removed
3050    let gpu_rs = fs::read_to_string("src/gpu.rs").unwrap_or_default();
3051    if gpu_rs.contains("_unused_motion") {
3052        return Err(Error::Message(
3053            "Gate failed: minimum GPU kernel still contains unused motion_vectors read. Remove the binding and read.".to_string()
3054        ));
3055    }
3056
3057    // Gate 2: lds_kernel_present
3058    if !gpu_rs.is_empty() && !gpu_rs.contains("var<workgroup>") {
3059        return Err(Error::Message(
3060            "Gate failed: GPU kernel does not use workgroup shared memory for neighborhood computation.".to_string()
3061        ));
3062    }
3063
3064    // Gate 3: 4k_probe_reported
3065    let gpu_report_path = output_dir.join("gpu_execution_report.md");
3066    if gpu_report_path.exists() {
3067        let gpu_report = fs::read_to_string(&gpu_report_path).unwrap_or_default();
3068        if !gpu_report.contains("gpu_4k_synthetic_probe") {
3069            return Err(Error::Message(
3070                "Gate failed: 4K dispatch probe has not been run. Add the 4K probe to gpu_execution.rs and regenerate.".to_string()
3071            ));
3072        }
3073    } else {
3074        return Err(Error::Message(
3075            "Gate failed: gpu_execution_report.md not found. Run: cargo run --release -- run-gpu-path --output generated/final_bundle".to_string()
3076        ));
3077    }
3078
3079    // Gate 4: frame_graph_doc_exists
3080    let frame_graph_path = std::path::Path::new("docs/frame_graph_position.md");
3081    if !frame_graph_path.exists() {
3082        return Err(Error::Message(
3083            "Gate failed: frame_graph_position.md missing or incomplete. Must contain Vulkan/DX12 barrier specifications.".to_string()
3084        ));
3085    }
3086    let frame_graph_content = fs::read_to_string(frame_graph_path).unwrap_or_default();
3087    if !frame_graph_content.contains("srcStageMask") {
3088        return Err(Error::Message(
3089            "Gate failed: frame_graph_position.md missing or incomplete. Must contain Vulkan/DX12 barrier specifications.".to_string()
3090        ));
3091    }
3092
3093    // Gate 5: async_doc_exists
3094    let async_doc_path = std::path::Path::new("docs/async_compute_analysis.md");
3095    if !async_doc_path.exists() {
3096        return Err(Error::Message(
3097            "Gate failed: async_compute_analysis.md missing or does not address the wgpu blocking pattern.".to_string()
3098        ));
3099    }
3100    let async_content = fs::read_to_string(async_doc_path).unwrap_or_default();
3101    if !async_content.contains("pollster::block_on") {
3102        return Err(Error::Message(
3103            "Gate failed: async_compute_analysis.md missing or does not address the wgpu blocking pattern.".to_string()
3104        ));
3105    }
3106
3107    // Gate 6: engine_realistic_generated
3108    let er_report = std::path::Path::new("generated/engine_realistic/engine_realistic_validation_report.md");
3109    if !er_report.exists() {
3110        return Err(Error::Message(
3111            "Gate failed: engine-realistic 1080p bridge not generated. Run: cargo run --release -- run-engine-realistic-bridge --output generated/engine_realistic".to_string()
3112        ));
3113    }
3114
3115    // Gate 7: check_signing_blockers_updated
3116    let blockers_path = std::path::Path::new("generated/check_signing_blockers.md");
3117    let blockers_content = if blockers_path.exists() {
3118        fs::read_to_string(blockers_path).unwrap_or_default()
3119    } else {
3120        output_dir
3121            .join("check_signing_blockers.md")
3122            .exists()
3123            .then(|| {
3124                fs::read_to_string(output_dir.join("check_signing_blockers.md"))
3125                    .unwrap_or_default()
3126            })
3127            .unwrap_or_default()
3128    };
3129    if blockers_content.to_lowercase().contains("cpu-side within the crate")
3130        && !blockers_content.contains("GPU timing")
3131        && !blockers_content.contains("gpu timing")
3132    {
3133        return Err(Error::Message(
3134            "Gate failed: check_signing_blockers.md is stale. Update it to reflect current GPU timing status.".to_string()
3135        ));
3136    }
3137
3138    // Gate 8: readme_product_framing_updated
3139    let readme_content = fs::read_to_string("README.md").unwrap_or_default();
3140    if readme_content.contains("not yet backed by real GPU measurements") {
3141        return Err(Error::Message(
3142            "Gate failed: README.md Product Framing is stale. Update to reflect measured GPU timings.".to_string()
3143        ));
3144    }
3145
3146    // Gate 9: check_signing_report_exists
3147    let check_signing_report = output_dir.join("check_signing_report.md");
3148    if !check_signing_report.exists() {
3149        return Err(Error::Message(
3150            "Gate failed: check-signing evidence report not generated. Run: cargo run --release -- run-check-signing --output generated/final_bundle".to_string()
3151        ));
3152    }
3153
3154    Ok(())
3155}
3156
3157fn require_file(path: &Path) -> Result<()> {
3158    let metadata = fs::metadata(path)?;
3159    if metadata.len() == 0 {
3160        return Err(Error::Message(format!(
3161            "artifact {} was written but empty",
3162            path.display()
3163        )));
3164    }
3165    Ok(())
3166}
3167
3168fn relative_string(path: &Path, root: &Path) -> String {
3169    path.strip_prefix(root)
3170        .unwrap_or(path)
3171        .to_string_lossy()
3172        .replace('\\', "/")
3173}
3174
3175fn placeholder_demo_b_metrics() -> DemoBSuiteMetrics {
3176    DemoBSuiteMetrics {
3177        summary: crate::sampling::DemoBSummary {
3178            scenario_ids: Vec::new(),
3179            policy_ids: Vec::new(),
3180            primary_behavioral_result:
3181                "Demo B was not run in this command. Run `cargo run -- run-demo-b --output <dir>` or `cargo run -- run-all --output <dir>` to generate the fixed-budget allocation study."
3182                    .to_string(),
3183            imported_trust_beats_uniform_scenarios: 0,
3184            imported_trust_beats_combined_heuristic_scenarios: 0,
3185            neutral_or_mixed_scenarios: Vec::new(),
3186        },
3187        scenarios: Vec::new(),
3188        budget_efficiency_curves: Vec::new(),
3189    }
3190}
3191
3192fn default_completion_status() -> CompletionNoteStatus {
3193    CompletionNoteStatus {
3194        only_files_inside_crate_changed: true,
3195        upgrade_plan_written: true,
3196        host_realistic_mode_implemented: true,
3197        stronger_baselines_implemented: true,
3198        scenario_suite_implemented: true,
3199        ablation_study_implemented: true,
3200        demo_b_strengthened: true,
3201        integration_surface_documented: true,
3202        cost_model_generated: true,
3203        reviewer_reports_generated: true,
3204        required_honesty_sentence_present: true,
3205        cargo_fmt_passed: false,
3206        cargo_clippy_passed: false,
3207        cargo_test_passed: false,
3208        no_fabricated_performance_claims: true,
3209        no_files_outside_crate_modified: true,
3210        fully_implemented: vec![
3211            "Host-realistic DSFB supervision separated from visibility-assisted research mode.".to_string(),
3212            "Six stronger Demo A baselines and eight DSFB variants with explicit ablation identities.".to_string(),
3213            "Five deterministic Demo A scenarios, including a neutral honesty holdout.".to_string(),
3214            "Expanded Demo B fixed-budget study with multiple alternative allocation policies.".to_string(),
3215            "Attachability surface, cost accounting, blocker reports, mentor audit, and hard artifact validation.".to_string(),
3216        ],
3217        future_work: vec![
3218            "Measured GPU implementation work remains future work; the current cost model is architectural rather than benchmark data.".to_string(),
3219            "The scenario suite is still synthetic and does not substitute for engine or field-scene validation.".to_string(),
3220            "A real engine integration case study remains the next transition step.".to_string(),
3221        ],
3222    }
3223}
3224
3225fn residual_palette(value: f32) -> [u8; 4] {
3226    let normalized = (value / 0.25).clamp(0.0, 1.0);
3227    [
3228        (20.0 + 235.0 * normalized).round() as u8,
3229        (25.0 + 170.0 * normalized).round() as u8,
3230        (40.0 * (1.0 - normalized)).round() as u8,
3231        255,
3232    ]
3233}
3234
3235fn trust_palette(trust: f32) -> [u8; 4] {
3236    let hazard = (1.0 - trust).clamp(0.0, 1.0);
3237    [
3238        (hazard * 255.0).round() as u8,
3239        (hazard * 160.0).round() as u8,
3240        0,
3241        255,
3242    ]
3243}
3244
3245fn alpha_palette(alpha: f32) -> [u8; 4] {
3246    let normalized = alpha.clamp(0.0, 1.0);
3247    [
3248        (40.0 + 210.0 * normalized).round() as u8,
3249        (35.0 + 90.0 * normalized).round() as u8,
3250        (120.0 + 110.0 * (1.0 - normalized)).round() as u8,
3251        255,
3252    ]
3253}
3254
3255fn intervention_palette(value: f32) -> [u8; 4] {
3256    let normalized = value.clamp(0.0, 1.0);
3257    [
3258        (40.0 + 215.0 * normalized).round() as u8,
3259        (45.0 + 140.0 * (1.0 - normalized)).round() as u8,
3260        (70.0 + 35.0 * (1.0 - normalized)).round() as u8,
3261        255,
3262    ]
3263}
3264
3265fn proxy_palette(value: f32) -> [u8; 4] {
3266    let normalized = value.clamp(0.0, 1.0);
3267    [
3268        (20.0 + 120.0 * normalized).round() as u8,
3269        (30.0 + 210.0 * normalized).round() as u8,
3270        (35.0 + 200.0 * (1.0 - normalized)).round() as u8,
3271        255,
3272    ]
3273}
3274
3275fn state_palette(value: f32) -> [u8; 4] {
3276    let color = if value >= 0.95 {
3277        Color::rgb(0.93, 0.29, 0.24)
3278    } else if value >= 0.65 {
3279        Color::rgb(0.95, 0.67, 0.22)
3280    } else if value >= 0.40 {
3281        Color::rgb(0.29, 0.74, 0.80)
3282    } else {
3283        Color::rgb(0.18, 0.24, 0.31)
3284    };
3285    [
3286        (color.r * 255.0).round() as u8,
3287        (color.g * 255.0).round() as u8,
3288        (color.b * 255.0).round() as u8,
3289        255,
3290    ]
3291}
3292
3293fn error_palette(value: f32) -> [u8; 4] {
3294    let normalized = (value / 0.20).clamp(0.0, 1.0);
3295    [
3296        (normalized * 255.0).round() as u8,
3297        (normalized * 210.0).round() as u8,
3298        (20.0 * (1.0 - normalized)).round() as u8,
3299        255,
3300    ]
3301}
3302
3303fn allocation_palette(value: f32, max_value: f32) -> [u8; 4] {
3304    let normalized = if max_value <= f32::EPSILON {
3305        0.0
3306    } else {
3307        (value / max_value).clamp(0.0, 1.0)
3308    };
3309    [
3310        (25.0 + 220.0 * normalized).round() as u8,
3311        (50.0 + 100.0 * (1.0 - normalized)).round() as u8,
3312        (75.0 + 140.0 * normalized).round() as u8,
3313        255,
3314    ]
3315}
3316
3317pub fn parse_scenario_id(value: &str) -> Result<ScenarioId> {
3318    match value {
3319        "thin_reveal" => Ok(ScenarioId::ThinReveal),
3320        "fast_pan" => Ok(ScenarioId::FastPan),
3321        "diagonal_reveal" => Ok(ScenarioId::DiagonalReveal),
3322        "reveal_band" => Ok(ScenarioId::RevealBand),
3323        "motion_bias_band" => Ok(ScenarioId::MotionBiasBand),
3324        "layered_slats" => Ok(ScenarioId::LayeredSlats),
3325        "noisy_reprojection" => Ok(ScenarioId::NoisyReprojection),
3326        "heuristic_friendly_pan" => Ok(ScenarioId::HeuristicFriendlyPan),
3327        "contrast_pulse" => Ok(ScenarioId::ContrastPulse),
3328        "stability_holdout" => Ok(ScenarioId::StabilityHoldout),
3329        _ => Err(Error::Message(format!(
3330            "unknown scenario id `{value}`; expected one of thin_reveal, fast_pan, diagonal_reveal, reveal_band, motion_bias_band, layered_slats, noisy_reprojection, heuristic_friendly_pan, contrast_pulse, stability_holdout"
3331        ))),
3332    }
3333}
3334
3335pub fn scenario_definitions_for_filter(
3336    config: &DemoConfig,
3337    scenario: Option<&str>,
3338) -> Result<Vec<ScenarioDefinition>> {
3339    if let Some(scenario) = scenario {
3340        let scenario_id = parse_scenario_id(scenario)?;
3341        let definition = scenario_by_id(&config.scene, scenario_id).ok_or_else(|| {
3342            Error::Message(format!(
3343                "scenario {scenario} is not available in this crate"
3344            ))
3345        })?;
3346        Ok(vec![definition])
3347    } else {
3348        Ok(scenario_suite(&config.scene))
3349    }
3350}
3351
3352// ─── Fast-path proxy pipeline ──────────────────────────────────────────────────
3353
3354/// Artifacts produced by `run_fast_path_only`.
3355pub struct FastPathArtifacts {
3356    /// Directory containing all fast-path outputs.
3357    pub output_dir: PathBuf,
3358    /// Machine-readable timing JSON.
3359    pub timing_json_path: PathBuf,
3360    /// Machine-readable summary JSON.
3361    pub summary_json_path: PathBuf,
3362    /// Trust visualisation SVG.
3363    pub trust_svg_path: PathBuf,
3364    /// Human-readable markdown summary.
3365    pub summary_md_path: PathBuf,
3366}
3367
3368/// Run the minimal inline fast-path proxy, measure GPU timings, and emit artifacts.
3369///
3370/// This function does NOT alter any existing pipeline outputs.
3371/// All new files are written under `output_dir/fast_path/`.
3372pub fn run_fast_path_only(output_dir: &Path) -> Result<FastPathArtifacts> {
3373    use crate::fast_path::{
3374        render_trust_strip_svg, run_fast_path_cpu, run_fast_path_timing_study,
3375        FAST_PATH_K, FAST_PATH_LAMBDA,
3376    };
3377
3378    let fp_dir = output_dir.join("fast_path");
3379    fs::create_dir_all(&fp_dir)?;
3380
3381    // 1. Run timing study (actual GPU measurements).
3382    let study = run_fast_path_timing_study()?;
3383    let timing_json_path = fp_dir.join("fast_path_timing.json");
3384    fs::write(&timing_json_path, serde_json::to_string_pretty(&study)?)?;
3385
3386    // 2. Run a small CPU reference frame for the trust visualisation.
3387    let vis_w = 256usize;
3388    let vis_h = 64usize;
3389    let vis_pixels = vis_w * vis_h;
3390    // Synthetic gradient: left = low residual, right = high residual.
3391    let mut current_rgb: Vec<[f32; 3]> = Vec::with_capacity(vis_pixels);
3392    let mut history_rgb: Vec<[f32; 3]> = Vec::with_capacity(vis_pixels);
3393    for _y in 0..vis_h {
3394        for x in 0..vis_w {
3395            let t = x as f32 / vis_w as f32;
3396            current_rgb.push([0.5 + t * 0.4, 0.5, 0.5]);
3397            history_rgb.push([0.5, 0.5, 0.5]);
3398        }
3399    }
3400    let res_in = vec![0.0f32; vis_pixels];
3401    let drift_in = vec![0.0f32; vis_pixels];
3402    let cpu_out = run_fast_path_cpu(
3403        &current_rgb,
3404        &history_rgb,
3405        &res_in,
3406        &drift_in,
3407        vis_w,
3408        vis_h,
3409        FAST_PATH_LAMBDA,
3410        FAST_PATH_K,
3411        false,
3412    );
3413    let trust_svg = render_trust_strip_svg(&cpu_out.trust, vis_w, vis_h);
3414    let trust_svg_path = fp_dir.join("fast_path_trust_visualisation.svg");
3415    fs::write(&trust_svg_path, &trust_svg)?;
3416
3417    // 3. Build summary JSON.
3418    let summary = serde_json::json!({
3419        "description": "Minimal Inline Deployment (Fast-Path Proxy) — reduced proxy, not the full DSFB observer.",
3420        "lambda": FAST_PATH_LAMBDA,
3421        "k": FAST_PATH_K,
3422        "local_aggregation_enabled": false,
3423        "actual_gpu_timing": study.actual_gpu_timing,
3424        "timing_entries": study.entries.iter().map(|e| serde_json::json!({
3425            "resolution": e.resolution_label,
3426            "width": e.width,
3427            "height": e.height,
3428            "mean_dispatch_ms": e.mean_dispatch_ms,
3429            "mean_total_ms": e.mean_total_ms,
3430            "warmup_runs": e.warmup_runs,
3431            "measured_runs": e.measured_runs,
3432            "adapter": e.adapter_name,
3433            "backend": e.backend,
3434        })).collect::<Vec<_>>(),
3435        "notes": study.notes,
3436    });
3437    let summary_json_path = fp_dir.join("fast_path_summary.json");
3438    fs::write(&summary_json_path, serde_json::to_string_pretty(&summary)?)?;
3439
3440    // 4. Build markdown summary.
3441    let summary_md_path = fp_dir.join("fast_path_summary.md");
3442    let mut md = String::new();
3443    let _ = writeln!(md, "# Minimal Inline Deployment (Fast-Path Proxy)\n");
3444    let _ = writeln!(md, "> This is a reduced deployment proxy derived from DSFB residual structure.");
3445    let _ = writeln!(md, "> It is **not** the full DSFB supervisory system and does not reproduce its full behaviour.\n");
3446    let _ = writeln!(md, "## Configuration\n");
3447    let _ = writeln!(md, "| Parameter | Value |");
3448    let _ = writeln!(md, "|-----------|-------|");
3449    let _ = writeln!(md, "| λ (slew weight) | {FAST_PATH_LAMBDA} |");
3450    let _ = writeln!(md, "| k (trust slope) | {FAST_PATH_K} |");
3451    let _ = writeln!(md, "| Local aggregation | disabled (optional) |\n");
3452    let _ = writeln!(md, "## Formulae\n");
3453    let _ = writeln!(md, "```\nr_t = L1(C_t - H_t) / 3\nd_t = r_t - r_{{t-1}}\ns_t = d_t - d_{{t-1}}\nu_t = |d_t| + λ |s_t|\nT_t = saturate(1 - k · u_t)\n```\n");
3454    let _ = writeln!(md, "## GPU Timing\n");
3455    if study.actual_gpu_timing && !study.entries.is_empty() {
3456        let _ = writeln!(md, "| Resolution | Mean dispatch (ms) | Mean total (ms) | Runs | Adapter |");
3457        let _ = writeln!(md, "|------------|--------------------|-----------------|------|---------|");
3458        for e in &study.entries {
3459            let adapter = e.adapter_name.as_deref().unwrap_or("n/a");
3460            let _ = writeln!(
3461                md,
3462                "| {} | {:.3} | {:.3} | {}+{} | {} |",
3463                e.resolution_label,
3464                e.mean_dispatch_ms,
3465                e.mean_total_ms,
3466                e.warmup_runs,
3467                e.measured_runs,
3468                adapter
3469            );
3470        }
3471    } else {
3472        let _ = writeln!(md, "_No GPU adapter available. Timing was not measured._\n");
3473        for note in &study.notes {
3474            let _ = writeln!(md, "- {note}");
3475        }
3476    }
3477    let _ = writeln!(md, "\n## Artifacts\n");
3478    let _ = writeln!(md, "- `fast_path_timing.json` — machine-readable timing study");
3479    let _ = writeln!(md, "- `fast_path_summary.json` — machine-readable summary");
3480    let _ = writeln!(md, "- `fast_path_trust_visualisation.svg` — trust heatmap (synthetic gradient input)");
3481    fs::write(&summary_md_path, &md)?;
3482
3483    Ok(FastPathArtifacts {
3484        output_dir: fp_dir,
3485        timing_json_path,
3486        summary_json_path,
3487        trust_svg_path,
3488        summary_md_path,
3489    })
3490}