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 court = Court::new();
36    for c in candidates {
37        court.offer(input, c, limits)?;
38    }
39    court.finish()
40}
41
42/// Incremental complete-cost court.
43///
44/// [`Court::offer`] serializes, decode-round-trips, and prices **one** candidate,
45/// keeping only the current best. A streaming caller (see
46/// [`crate::encode::candidates::propose_each`]) therefore holds at most one
47/// unpriced candidate's payload at a time instead of the whole portfolio, which
48/// bounds encoder memory on large inputs.
49pub struct Court {
50    best: Option<(u64, usize, CandidateKind, Vec<u8>, CostBreakdown)>,
51    evaluated: u32,
52}
53
54impl Court {
55    /// An empty court.
56    pub fn new() -> Self {
57        Court {
58            best: None,
59            evaluated: 0,
60        }
61    }
62
63    /// Price one candidate and retain it only if it beats the current best.
64    pub fn offer(&mut self, input: &[u8], c: Candidate, limits: Limits) -> Result<()> {
65        self.evaluated += 1;
66        let (bytes, cost) = c.descriptor.serialize()?;
67
68        // Decode-before-commit: the normative decoder must reproduce the source.
69        let parsed = Descriptor::parse(&bytes, limits)?;
70        let out = materialize(&parsed, limits)?;
71        if out != input {
72            return Err(Error::reconstruction_mismatch(format!(
73                "candidate {} did not reproduce the source exactly",
74                c.kind.name()
75            )));
76        }
77
78        let total = cost.total();
79        debug_assert_eq!(total, bytes.len() as u64);
80        let work = c.descriptor.program.ops.len();
81        let key = (total, work, c.kind);
82        let replace = match &self.best {
83            None => true,
84            Some((bt, bw, bk, ..)) => key < (*bt, *bw, *bk),
85        };
86        if replace {
87            self.best = Some((total, work, c.kind, bytes, cost));
88        }
89        Ok(())
90    }
91
92    /// The winning candidate.
93    pub fn finish(self) -> Result<CourtResult> {
94        let (_, work, kind, bytes, cost) = self
95            .best
96            .ok_or_else(|| Error::internal_invariant("no candidates were evaluated"))?;
97        Ok(CourtResult {
98            kind,
99            bytes,
100            cost,
101            candidates_evaluated: self.evaluated,
102            graph_ops: work,
103        })
104    }
105}
106
107impl Default for Court {
108    fn default() -> Self {
109        Court::new()
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use crate::encode::candidates;
117
118    #[test]
119    fn raw_candidate_wins_and_is_exact() {
120        let input = b"some bytes that must round trip".to_vec();
121        let cands = candidates::propose(&input, Limits::DEFAULT).unwrap();
122        let expected = cands.len() as u32;
123        let r = run(&input, cands, Limits::DEFAULT).unwrap();
124        assert_eq!(r.kind, CandidateKind::Raw);
125        // Every proposed candidate for a non-empty, in-limit input is priced, so
126        // the evaluated count tracks the portfolio size (which varies with the
127        // `rans` feature and the PDF adapter's proposals).
128        assert_eq!(r.candidates_evaluated, expected);
129        assert!(r.candidates_evaluated >= 2);
130        assert_eq!(r.cost.total(), r.bytes.len() as u64);
131        let (out, _) = crate::materialize::decode_to_bytes(&r.bytes, Limits::DEFAULT).unwrap();
132        assert_eq!(out, input);
133    }
134
135    #[test]
136    fn empty_input_is_exact() {
137        let input: Vec<u8> = Vec::new();
138        let cands = candidates::propose(&input, Limits::DEFAULT).unwrap();
139        let r = run(&input, cands, Limits::DEFAULT).unwrap();
140        let (out, _) = crate::materialize::decode_to_bytes(&r.bytes, Limits::DEFAULT).unwrap();
141        assert!(out.is_empty());
142    }
143}