nx_time_quality.nx source
↩ module page · 276 lines · 9987 B
1// nx_time_quality.nx -- four-dimensional time classification primitive.
2//
3// Cardinal: feedback-time-quality-fourth-dimension-triangulation.
4//
5// Time in the substrate is 4-D by construction:
6// (1) t (quantitative) -- wall-ns measured
7// (2) phase (qualitative) -- which racing-crew bucket
8// (3) budget (declared) -- caller's intent at start
9// (4) norm (population) -- avg across history for this phase
10//
11// Three of those (measured + declared + norm) triangulate. When
12// they agree -> confidence. When one disagrees -> signal worth
13// surfacing to the kernel / racing crew / coach.
14//
15// API:
16// nx_tq_record(work_tag, phase, declared_ns, measured_ns) -> 0
17// nx_tq_classify(work_tag) -> phase | -1
18// nx_tq_budget(work_tag) -> declared_ns | -1
19// nx_tq_history_norm(phase) -> avg_ns
20// nx_tq_triangle(work_tag) -> OK | SLOW | FAST | DRIFT | UNKNOWN
21// nx_tq_n_records() -> i64
22//
23// Substrate-native: import syscalls.nx, mmap-backed ledger, sealed
24// enum verdicts, --strip clean.
25
26import "syscalls.nx"
27const NX_MAGIC_4096: i64 = 4096
28const NX_MAGIC_1024: i64 = 1024
29
30// 8 phases (qualitative time-quality).
31const NX_TQ_HOT_PATH: i64 = 1
32const NX_TQ_WARM_PATH: i64 = 2
33const NX_TQ_COLD_PATH: i64 = 3
34const NX_TQ_LEISURE: i64 = 4
35const NX_TQ_SETUP: i64 = 5
36const NX_TQ_TEARDOWN: i64 = 6
37const NX_TQ_DEADLINE: i64 = 7
38const NX_TQ_OPPORTUNISTIC: i64 = 8
39const NX_TQ_PHASE_N: i64 = 9
40
41// Triangle verdicts.
42const NX_TQ_TRIANGLE_OK: i64 = 0
43const NX_TQ_TRIANGLE_SLOW: i64 = 1
44const NX_TQ_TRIANGLE_FAST: i64 = 2
45const NX_TQ_TRIANGLE_DRIFT: i64 = 3
46const NX_TQ_TRIANGLE_UNKNOWN: i64 = 4
47
48// Triangle thresholds (Q10 fraction of declared / norm).
49const NX_TQ_DELTA_OK_BOUND: i64 = 256 // |delta| < 25% = OK
50const NX_TQ_DELTA_BIG_BOUND: i64 = 512 // |delta| > 50% = SLOW/FAST
51
52static NX_TQ_ACTIVE: i64
53static NX_TQ_LEDGER_PTR: i64
54static NX_TQ_N_RECORDS: i64
55static NX_TQ_CAPACITY: i64
56
57// Per-phase rolling stats: total_ns, count. Index by phase tag (1-based).
58static NX_TQ_PHASE_TOTAL_PTR: i64
59static NX_TQ_PHASE_COUNT_PTR: i64
60
61const NX_TQ_REC_SIZE: i64 = 40 // 5 i64: work_tag/phase/decl/meas/seq
62const NX_TQ_CHUNK_BYTES: i64 = 40960 // 1024 records
63
64func nx_tq_init() -> i64 {
65 if NX_TQ_LEDGER_PTR != 0 { return 0 }
66 let raw: *u8 = sys_mmap(NX_TQ_CHUNK_BYTES)
67 if (raw as i64) == 0 { return 1 }
68 if (raw as i64) == 0 - 1 { return 2 }
69 NX_TQ_LEDGER_PTR = raw as i64
70 NX_TQ_N_RECORDS = 0
71 NX_TQ_CAPACITY = NX_TQ_CHUNK_BYTES / NX_TQ_REC_SIZE
72
73 // Per-phase stats: NX_TQ_PHASE_N slots, 16 bytes each = 144 bytes.
74 let st: *u8 = sys_mmap(NX_MAGIC_4096)
75 if (st as i64) == 0 { return 3 }
76 if (st as i64) == 0 - 1 { return 4 }
77 NX_TQ_PHASE_TOTAL_PTR = st as i64
78 NX_TQ_PHASE_COUNT_PTR = (st as i64) + (NX_TQ_PHASE_N * 8)
79 var i: i64 = 0
80 while i < NX_TQ_PHASE_N {
81 let t: *i64 = (NX_TQ_PHASE_TOTAL_PTR + i * 8) as *i64
82 let c: *i64 = (NX_TQ_PHASE_COUNT_PTR + i * 8) as *i64
83 t[0] = 0
84 c[0] = 0
85 i = i + 1
86 }
87
88 NX_TQ_ACTIVE = 1
89 return 0
90}
91
92func nx_tq_enable() -> i64 { NX_TQ_ACTIVE = 1; return 0 }
93func nx_tq_disable() -> i64 { NX_TQ_ACTIVE = 0; return 0 }
94func nx_tq_is_active() -> i64 { return NX_TQ_ACTIVE }
95
96// Record one time-event. Returns 0 OK.
97func nx_tq_record(work_tag: i64, phase: i64,
98 declared_ns: i64, measured_ns: i64) -> i64 {
99 if NX_TQ_ACTIVE == 0 { return 0 }
100 nx_tq_init()
101 if NX_TQ_LEDGER_PTR == 0 { return 0 }
102 if phase < 1 { return 1 }
103 if phase > 8 { return 1 }
104 if declared_ns < 0 { return 2 }
105 if measured_ns < 0 { return 3 }
106 if NX_TQ_N_RECORDS >= NX_TQ_CAPACITY { return 4 }
107
108 let base: i64 = NX_TQ_LEDGER_PTR + (NX_TQ_N_RECORDS * NX_TQ_REC_SIZE)
109 let rec: *i64 = base as *i64
110 rec[0] = work_tag
111 rec[1] = phase
112 rec[2] = declared_ns
113 rec[3] = measured_ns
114 rec[4] = NX_TQ_N_RECORDS
115 NX_TQ_N_RECORDS = NX_TQ_N_RECORDS + 1
116
117 // Update per-phase rolling stats.
118 let t: *i64 = (NX_TQ_PHASE_TOTAL_PTR + phase * 8) as *i64
119 let c: *i64 = (NX_TQ_PHASE_COUNT_PTR + phase * 8) as *i64
120 t[0] = t[0] + measured_ns
121 c[0] = c[0] + 1
122 return 0
123}
124
125// Find the most-recent slot for a work_tag.
126func _nx_tq_latest_slot(work_tag: i64) -> i64 {
127 nx_tq_init()
128 var i: i64 = NX_TQ_N_RECORDS - 1
129 while i >= 0 {
130 let base: i64 = NX_TQ_LEDGER_PTR + (i * NX_TQ_REC_SIZE)
131 let rec: *i64 = base as *i64
132 if rec[0] == work_tag { return i }
133 i = i - 1
134 }
135 return 0 - 1
136}
137
138func nx_tq_classify(work_tag: i64) -> i64 {
139 let slot: i64 = _nx_tq_latest_slot(work_tag)
140 if slot < 0 { return 0 - 1 }
141 let base: i64 = NX_TQ_LEDGER_PTR + (slot * NX_TQ_REC_SIZE)
142 let rec: *i64 = base as *i64
143 return rec[1]
144}
145
146func nx_tq_budget(work_tag: i64) -> i64 {
147 let slot: i64 = _nx_tq_latest_slot(work_tag)
148 if slot < 0 { return 0 - 1 }
149 let base: i64 = NX_TQ_LEDGER_PTR + (slot * NX_TQ_REC_SIZE)
150 let rec: *i64 = base as *i64
151 return rec[2]
152}
153
154// Population-level average measured-ns for a phase.
155func nx_tq_history_norm(phase: i64) -> i64 {
156 if phase < 1 { return 0 }
157 if phase > 8 { return 0 }
158 nx_tq_init()
159 let t: *i64 = (NX_TQ_PHASE_TOTAL_PTR + phase * 8) as *i64
160 let c: *i64 = (NX_TQ_PHASE_COUNT_PTR + phase * 8) as *i64
161 if c[0] == 0 { return 0 }
162 return t[0] / c[0]
163}
164
165// Triangulation verdict for the latest event of a work_tag.
166// abs(measured - declared) / declared <= 25% AND
167// abs(measured - norm) / norm <= 25% -> OK
168// (measured - declared) / declared >= 50% -> SLOW
169// (measured - declared) / declared <= -50% -> FAST
170// (measured - norm) / norm >= 50% -> DRIFT
171//
172// Computed via integer math; thresholds in Q10 of declared/norm.
173func nx_tq_triangle(work_tag: i64) -> i64 {
174 let slot: i64 = _nx_tq_latest_slot(work_tag)
175 if slot < 0 { return NX_TQ_TRIANGLE_UNKNOWN }
176 let base: i64 = NX_TQ_LEDGER_PTR + (slot * NX_TQ_REC_SIZE)
177 let rec: *i64 = base as *i64
178 let phase: i64 = rec[1]
179 let declared: i64 = rec[2]
180 let measured: i64 = rec[3]
181
182 // Compute delta_declared_q10 = (measured - declared) * 1024 / declared.
183 // Guard against zero declared.
184 var delta_d: i64 = 0
185 if declared > 0 {
186 delta_d = (measured - declared) * NX_MAGIC_1024 / declared
187 }
188 if delta_d >= NX_TQ_DELTA_BIG_BOUND { return NX_TQ_TRIANGLE_SLOW }
189 if delta_d <= 0 - NX_TQ_DELTA_BIG_BOUND { return NX_TQ_TRIANGLE_FAST }
190
191 // Compute delta_norm_q10.
192 let norm: i64 = nx_tq_history_norm(phase)
193 var delta_n: i64 = 0
194 if norm > 0 {
195 delta_n = (measured - norm) * NX_MAGIC_1024 / norm
196 }
197 // Absolute value of delta_n for DRIFT check.
198 var abs_n: i64 = delta_n
199 if abs_n < 0 { abs_n = 0 - abs_n }
200 if abs_n >= NX_TQ_DELTA_BIG_BOUND { return NX_TQ_TRIANGLE_DRIFT }
201
202 // Within OK band.
203 var abs_d: i64 = delta_d
204 if abs_d < 0 { abs_d = 0 - abs_d }
205 if abs_d <= NX_TQ_DELTA_OK_BOUND {
206 if abs_n <= NX_TQ_DELTA_OK_BOUND { return NX_TQ_TRIANGLE_OK }
207 }
208 // Mid-band: slight off but not big. Default OK for V0.
209 return NX_TQ_TRIANGLE_OK
210}
211
212func nx_tq_n_records() -> i64 {
213 nx_tq_init()
214 return NX_TQ_N_RECORDS
215}
216
217// ---- self-test ---------------------------------------------------
218// expect_exit: 0
219
220func main() -> i64 {
221 if nx_tq_init() != 0 { return 1 }
222
223 // Disabled-mode short-circuit.
224 nx_tq_disable()
225 nx_tq_record(0xAAAA, NX_TQ_HOT_PATH, 100, 95)
226 if nx_tq_n_records() != 0 { return 10 }
227
228 // Enable + record several hot-path events near declared (OK).
229 nx_tq_enable()
230 if nx_tq_record(0x100, NX_TQ_HOT_PATH, 200, 210) != 0 { return 20 }
231 if nx_tq_record(0x100, NX_TQ_HOT_PATH, 200, 195) != 0 { return 21 }
232 if nx_tq_record(0x100, NX_TQ_HOT_PATH, 200, 205) != 0 { return 22 }
233
234 // Population norm for HOT_PATH should be (210+195+205)/3 = 203.
235 let norm: i64 = nx_tq_history_norm(NX_TQ_HOT_PATH)
236 if norm != 203 { return 30 }
237
238 // Latest event 0x100 was measured=205 vs declared=200 vs norm=203
239 // -> all within 25% bands -> OK triangle.
240 if nx_tq_triangle(0x100) != NX_TQ_TRIANGLE_OK { return 40 }
241
242 // Record a SLOW event for 0x200 -- measured 2x declared.
243 if nx_tq_record(0x200, NX_TQ_WARM_PATH, 100, 250) != 0 { return 50 }
244 if nx_tq_triangle(0x200) != NX_TQ_TRIANGLE_SLOW { return 51 }
245
246 // Record a FAST event for 0x300 -- measured 30% of declared.
247 if nx_tq_record(0x300, NX_TQ_COLD_PATH, 1000, 200) != 0 { return 60 }
248 if nx_tq_triangle(0x300) != NX_TQ_TRIANGLE_FAST { return 61 }
249
250 // Build a LEISURE norm of ~100ns from 3 events, then record one
251 // at 5x norm -> DRIFT.
252 if nx_tq_record(0x400, NX_TQ_LEISURE, 100, 100) != 0 { return 70 }
253 if nx_tq_record(0x400, NX_TQ_LEISURE, 100, 100) != 0 { return 71 }
254 if nx_tq_record(0x400, NX_TQ_LEISURE, 100, 100) != 0 { return 72 }
255 // Last record measured=600 declared=600 (within decl band) but
256 // norm is around (100+100+100+600)/4 = 225; 600 vs 225 = +166% >> 50%.
257 // Skip declared-comparison by recording with declared=measured.
258 if nx_tq_record(0x400, NX_TQ_LEISURE, 600, 600) != 0 { return 73 }
259 if nx_tq_triangle(0x400) != NX_TQ_TRIANGLE_DRIFT { return 74 }
260
261 // Lookups.
262 if nx_tq_classify(0x100) != NX_TQ_HOT_PATH { return 80 }
263 if nx_tq_classify(0x200) != NX_TQ_WARM_PATH { return 81 }
264 if nx_tq_classify(0xDEAD) != 0 - 1 { return 82 }
265 if nx_tq_budget(0x300) != 1000 { return 83 }
266
267 // Invalid phase rejected.
268 if nx_tq_record(0xCAFE, 99, 100, 100) == 0 { return 90 }
269 if nx_tq_record(0xCAFE, NX_TQ_HOT_PATH, 0 - 1, 100) == 0 { return 91 }
270 if nx_tq_record(0xCAFE, NX_TQ_HOT_PATH, 100, 0 - 1) == 0 { return 92 }
271
272 // Triangle for unknown work_tag -> UNKNOWN.
273 if nx_tq_triangle(0xFEED) != NX_TQ_TRIANGLE_UNKNOWN { return 95 }
274
275 return 0
276}