Skip to main content

launchbound_report/
render.rs

1//! Text rendering. Legible to someone who has never used reconverge: rule
2//! IDs come with the source span and a plain-language reason.
3
4use crate::Report;
5use std::fmt::Write;
6
7/// Kept in the report crate so rendering cannot compile without it.
8pub fn launchbound_metal_notice() -> &'static str {
9    "NO convergence gate exists on the Metal path: the same bug class is NOT checked"
10}
11
12pub fn render_text(report: &Report) -> String {
13    let mut out = String::new();
14    let device = report
15        .device
16        .as_ref()
17        .map(|d| format!("{} (cc {}, driver {})", d.name, d.cc, d.driver_version))
18        .unwrap_or_else(|| "no device — nothing measured yet".into());
19    let _ = writeln!(
20        out,
21        "launchbound report — {} · gate cc {} · {} · {}",
22        report.kernel, report.gate_cc, report.measurement_kind, device
23    );
24    // Unconditional whenever the gate did not run: the Metal asymmetry is
25    // published, never buried (docs/LIMITATIONS.md). Do not add a way to skip this.
26    if report.convergence_gate == "none" {
27        let _ = writeln!(out, "\n*** {} ***", launchbound_metal_notice());
28    }
29
30    match &report.chosen {
31        Some(chosen) => {
32            let s = &chosen.summary;
33            let _ = writeln!(
34                out,
35                "\nCHOSEN: {}  {}\n  {:.4} ms  [{:.4}, {:.4}]  (n={}, {} outliers rejected)",
36                chosen.id,
37                chosen.config,
38                s.median_ms,
39                s.ci95_lo_ms,
40                s.ci95_hi_ms,
41                s.n,
42                s.outliers_rejected
43            );
44            if !report.indistinguishable_from_chosen.is_empty() {
45                let _ = writeln!(
46                    out,
47                    "  statistically indistinguishable from: {}",
48                    report.indistinguishable_from_chosen.join(", ")
49                );
50            }
51        }
52        None => {
53            let _ = writeln!(
54                out,
55                "\nCHOSEN: none — no admitted candidate has a measurement"
56            );
57        }
58    }
59
60    if !report.rejected_faster.is_empty() {
61        let _ = writeln!(
62            out,
63            "\nREFUSED BUT FASTER — an autotuner without a convergence gate would have\nhanded you one of these:"
64        );
65        for r in &report.rejected_faster {
66            let s = &r.summary;
67            let _ = writeln!(
68                out,
69                "  {}  {}\n    {:.4} ms  [{:.4}, {:.4}]  — {:.2}x faster than the chosen config",
70                r.id, r.config, s.median_ms, s.ci95_lo_ms, s.ci95_hi_ms, r.speedup_vs_chosen
71            );
72            for rule in &r.rules {
73                let _ = writeln!(
74                    out,
75                    "    REFUSED {} at {}: {}",
76                    rule.rule,
77                    rule.span.as_deref().unwrap_or("<no span>"),
78                    rule.reason
79                );
80            }
81        }
82        if let Some(reason) = &report.allow_unsafe_reason {
83            let _ = writeln!(
84                out,
85                "    (measured under --allow-unsafe; recorded reason: {reason:?})"
86            );
87        }
88    }
89
90    let _ = writeln!(out, "\nALL CANDIDATES:");
91    for c in &report.candidates {
92        let timing = match (&c.summary, c.measurement_status.as_str()) {
93            (Some(s), "ok") => format!(
94                "{:.4} ms [{:.4}, {:.4}]",
95                s.median_ms, s.ci95_lo_ms, s.ci95_hi_ms
96            ),
97            (_, "timeout") => "TIMED OUT (presumed hung — the failure the gate predicts)".into(),
98            (_, "error") => format!("error: {}", c.measurement_error.as_deref().unwrap_or("?")),
99            _ => "unmeasured".into(),
100        };
101        let mark = match c.verdict.as_str() {
102            "clean" => " ",
103            "admitted_with_caveats" => "~",
104            "disqualified" => "x",
105            "ungated" => "u",
106            _ => "!",
107        };
108        let _ = writeln!(out, "  {mark} {}  {}  {timing}", c.id, c.config);
109        for rule in &c.rules {
110            let _ = writeln!(
111                out,
112                "      {} at {}: {}",
113                rule.rule,
114                rule.span.as_deref().unwrap_or("<no span>"),
115                rule.reason
116            );
117        }
118    }
119
120    let t = &report.totals;
121    let _ = writeln!(
122        out,
123        "\n{} candidates: {} admitted, {} refused; {} measured ok; {:.1} GPU-seconds consumed",
124        t.candidates, t.admitted, t.refused, t.measured_ok, t.gpu_seconds
125    );
126    out
127}