Skip to main content

vole_document/
accounting.rs

1//! Complete-cost accounting.
2//!
3//! A representation is never credited with an entropy estimate. Every byte
4//! that must be persisted to make a candidate decodable is charged, and only
5//! the complete serialized `.voldoc` size is authoritative for standalone
6//! compression claims.
7
8/// Physical byte attribution for a serialized descriptor.
9///
10/// These categories exist so that `inspect` can answer *where the bytes went*
11/// when a mechanism wins or loses. They sum to the exact `.voldoc` length.
12#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
13pub struct CostBreakdown {
14    /// Fixed header bytes.
15    pub header: u64,
16    /// Universe declaration record (payload + framing).
17    pub universe: u64,
18    /// Format-descriptor record (payload + framing).
19    pub format: u64,
20    /// Record framing overhead (tag/flags/length/crc) for all records.
21    pub record_framing: u64,
22    /// Object record payloads (raw byte objects).
23    pub objects: u64,
24    /// `EXTERNAL_REF` record payloads (Phase 9): 40 bytes per external object.
25    ///
26    /// The record framing is charged to [`CostBreakdown::record_framing`], as for
27    /// `OBJECT` records.
28    pub external_refs: u64,
29    /// Reconstruction-graph record payload.
30    pub graph: u64,
31    /// Entropy model records (0 in the exact core).
32    pub models: u64,
33    /// Entropy channel payloads (0 in the exact core).
34    pub entropy_payload: u64,
35    /// Typed residual records (0 in the exact core).
36    pub residuals: u64,
37    /// Index/checkpoint records.
38    pub index: u64,
39    /// Optional seek-directory record (payload + framing). Zero when absent.
40    pub directory: u64,
41    /// Integrity record payload.
42    pub integrity: u64,
43    /// Trailer record payload.
44    pub trailer: u64,
45}
46
47impl CostBreakdown {
48    /// Total serialized size in bytes.
49    pub fn total(&self) -> u64 {
50        self.header
51            + self.universe
52            + self.format
53            + self.record_framing
54            + self.objects
55            + self.external_refs
56            + self.graph
57            + self.models
58            + self.entropy_payload
59            + self.residuals
60            + self.index
61            + self.directory
62            + self.integrity
63            + self.trailer
64    }
65
66    /// Stable-order JSON rendering for `inspect` and receipts.
67    pub fn to_json(&self) -> String {
68        let mut s = String::new();
69        s.push('{');
70        let fields: [(&str, u64); 14] = [
71            ("header", self.header),
72            ("universe", self.universe),
73            ("format", self.format),
74            ("record_framing", self.record_framing),
75            ("objects", self.objects),
76            ("external_refs", self.external_refs),
77            ("graph", self.graph),
78            ("models", self.models),
79            ("entropy_payload", self.entropy_payload),
80            ("residuals", self.residuals),
81            ("index", self.index),
82            ("directory", self.directory),
83            ("integrity", self.integrity),
84            ("trailer", self.trailer),
85        ];
86        for (i, (name, value)) in fields.iter().enumerate() {
87            if i > 0 {
88                s.push(',');
89            }
90            s.push('"');
91            s.push_str(name);
92            s.push_str("\":");
93            s.push_str(&value.to_string());
94        }
95        s.push('}');
96        s
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn total_sums_all_fields() {
106        let c = CostBreakdown {
107            header: 64,
108            objects: 100,
109            record_framing: 24,
110            ..Default::default()
111        };
112        assert_eq!(c.total(), 188);
113    }
114
115    #[test]
116    fn json_has_all_fields() {
117        let j = CostBreakdown::default().to_json();
118        for k in [
119            "header",
120            "universe",
121            "format",
122            "record_framing",
123            "objects",
124            "external_refs",
125            "graph",
126            "models",
127            "entropy_payload",
128            "residuals",
129            "index",
130            "directory",
131            "integrity",
132            "trailer",
133        ] {
134            assert!(j.contains(k), "missing {k}");
135        }
136    }
137}