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}