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 /// Optional checkpoint record (payload + framing). Zero when absent.
42 pub checkpoints: u64,
43 /// Integrity record payload.
44 pub integrity: u64,
45 /// Trailer record payload.
46 pub trailer: u64,
47}
48
49impl CostBreakdown {
50 /// Total serialized size in bytes.
51 pub fn total(&self) -> u64 {
52 self.header
53 + self.universe
54 + self.format
55 + self.record_framing
56 + self.objects
57 + self.external_refs
58 + self.graph
59 + self.models
60 + self.entropy_payload
61 + self.residuals
62 + self.index
63 + self.directory
64 + self.checkpoints
65 + self.integrity
66 + self.trailer
67 }
68
69 /// Stable-order JSON rendering for `inspect` and receipts.
70 pub fn to_json(&self) -> String {
71 let mut s = String::new();
72 s.push('{');
73 let fields: [(&str, u64); 15] = [
74 ("header", self.header),
75 ("universe", self.universe),
76 ("format", self.format),
77 ("record_framing", self.record_framing),
78 ("objects", self.objects),
79 ("external_refs", self.external_refs),
80 ("graph", self.graph),
81 ("models", self.models),
82 ("entropy_payload", self.entropy_payload),
83 ("residuals", self.residuals),
84 ("index", self.index),
85 ("directory", self.directory),
86 ("checkpoints", self.checkpoints),
87 ("integrity", self.integrity),
88 ("trailer", self.trailer),
89 ];
90 for (i, (name, value)) in fields.iter().enumerate() {
91 if i > 0 {
92 s.push(',');
93 }
94 s.push('"');
95 s.push_str(name);
96 s.push_str("\":");
97 s.push_str(&value.to_string());
98 }
99 s.push('}');
100 s
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn total_sums_all_fields() {
110 let c = CostBreakdown {
111 header: 64,
112 objects: 100,
113 record_framing: 24,
114 ..Default::default()
115 };
116 assert_eq!(c.total(), 188);
117 }
118
119 #[test]
120 fn json_has_all_fields() {
121 let j = CostBreakdown::default().to_json();
122 for k in [
123 "header",
124 "universe",
125 "format",
126 "record_framing",
127 "objects",
128 "external_refs",
129 "graph",
130 "models",
131 "entropy_payload",
132 "residuals",
133 "index",
134 "directory",
135 "checkpoints",
136 "integrity",
137 "trailer",
138 ] {
139 assert!(j.contains(k), "missing {k}");
140 }
141 }
142}