Skip to main content

vole_document/encode/
court.rs

1//! The complete-cost court.
2//!
3//! Every candidate is fully serialized, then round-tripped through the
4//! normative decoder and byte-compared against the source, then priced from its
5//! *actual* serialized bytes. No candidate is admitted on an entropy estimate
6//! or on the plausibility of its logic.
7
8use crate::accounting::CostBreakdown;
9use crate::container::Descriptor;
10use crate::encode::candidates::{Candidate, CandidateKind};
11use crate::error::{Error, Result};
12use crate::limits::Limits;
13use crate::materialize::materialize;
14
15/// The winning candidate after the court.
16#[derive(Debug, Clone)]
17pub struct CourtResult {
18    /// Winning family.
19    pub kind: CandidateKind,
20    /// The exact serialized `.voldoc` bytes that passed the court.
21    pub bytes: Vec<u8>,
22    /// Physical byte attribution of `bytes`.
23    pub cost: CostBreakdown,
24    /// How many candidates were evaluated.
25    pub candidates_evaluated: u32,
26    /// Reconstruction work (DRA instruction count) of the winner.
27    pub graph_ops: usize,
28}
29
30/// Run the court and return the strictly smallest exact candidate.
31///
32/// Deterministic tie-breaking: lower complete cost, then lower reconstruction
33/// work, then lower [`CandidateKind`] ordinal.
34pub fn run(input: &[u8], candidates: Vec<Candidate>, limits: Limits) -> Result<CourtResult> {
35    let mut best: Option<(u64, usize, CandidateKind, Vec<u8>, CostBreakdown)> = None;
36    let mut evaluated: u32 = 0;
37
38    for c in candidates {
39        evaluated += 1;
40        let (bytes, cost) = c.descriptor.serialize()?;
41
42        // Decode-before-commit: the normative decoder must reproduce the source.
43        let parsed = Descriptor::parse(&bytes, limits)?;
44        let out = materialize(&parsed, limits)?;
45        if out != input {
46            return Err(Error::reconstruction_mismatch(format!(
47                "candidate {} did not reproduce the source exactly",
48                c.kind.name()
49            )));
50        }
51
52        let total = cost.total();
53        debug_assert_eq!(total, bytes.len() as u64);
54        let work = c.descriptor.program.ops.len();
55        let key = (total, work, c.kind);
56        let replace = match &best {
57            None => true,
58            Some((bt, bw, bk, ..)) => key < (*bt, *bw, *bk),
59        };
60        if replace {
61            best = Some((total, work, c.kind, bytes, cost));
62        }
63    }
64
65    let (_, work, kind, bytes, cost) =
66        best.ok_or_else(|| Error::internal_invariant("no candidates were evaluated"))?;
67    Ok(CourtResult {
68        kind,
69        bytes,
70        cost,
71        candidates_evaluated: evaluated,
72        graph_ops: work,
73    })
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::encode::candidates;
80
81    #[test]
82    fn raw_candidate_wins_and_is_exact() {
83        let input = b"some bytes that must round trip".to_vec();
84        let cands = candidates::propose(&input, Limits::DEFAULT).unwrap();
85        let expected = cands.len() as u32;
86        let r = run(&input, cands, Limits::DEFAULT).unwrap();
87        assert_eq!(r.kind, CandidateKind::Raw);
88        // Every proposed candidate for a non-empty, in-limit input is priced, so
89        // the evaluated count tracks the portfolio size (which varies with the
90        // `rans` feature and the PDF adapter's proposals).
91        assert_eq!(r.candidates_evaluated, expected);
92        assert!(r.candidates_evaluated >= 2);
93        assert_eq!(r.cost.total(), r.bytes.len() as u64);
94        let (out, _) = crate::materialize::decode_to_bytes(&r.bytes, Limits::DEFAULT).unwrap();
95        assert_eq!(out, input);
96    }
97
98    #[test]
99    fn empty_input_is_exact() {
100        let input: Vec<u8> = Vec::new();
101        let cands = candidates::propose(&input, Limits::DEFAULT).unwrap();
102        let r = run(&input, cands, Limits::DEFAULT).unwrap();
103        let (out, _) = crate::materialize::decode_to_bytes(&r.bytes, Limits::DEFAULT).unwrap();
104        assert!(out.is_empty());
105    }
106}