nx_thermal_profile_test.nx source
↩ module page · 176 lines · 9667 B
1// nx_thermal_profile_test.nx -- gate for nx_thermal_profile. The headline is
2// T4: on a realistic retort curve the come-up and cool-down together deliver
3// MORE lethality than the hold itself, so a hold-only calculation does not
4// merely round down -- it discards the majority of the process.
5// expect_exit: 0 license_tier: ORIGINAL
6import "nx_syscalls.nx"
7import "nx_pow10.nx"
8import "nx_thermal_process.nx"
9import "nx_thermal_profile.nx"
10
11func t_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
12func t_putn(v: i64) -> i64 { let bb: *u8 = sys_mmap(28); var m: i64 = v; if m < 0 { m = 0 - m; sys_write(1, "-" as *u8, 1) } let t: *u8 = sys_mmap(28); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } var i: i64 = 0; while i < k { bb[i] = t[k - 1 - i]; i = i + 1 } sys_write(1, bb, k); return 0 }
13
14// A realistic retort: 10 min come-up 100->121.1 C, 2.52 min hold (the 12D
15// cook), 10 min cool-down back to 100 C.
16func build_retort() -> *NxThermalProfile {
17 let p: *NxThermalProfile = nx_thermal_profile_new(64)
18 tpr_add_ramp(p, 100000, 121100, 600000, 10)
19 tpr_add_segment(p, TP_REF_STERIL_MILLI_C, 151200)
20 tpr_add_ramp(p, 121100, 100000, 600000, 10)
21 return p
22}
23
24func main() -> i64 {
25 var pass: i64 = 0
26 var total: i64 = 0
27
28 // --- T1 a single-segment profile reproduces tp_f0_ms exactly ---
29 total = total + 1
30 let one: *NxThermalProfile = nx_thermal_profile_new(4)
31 tpr_add_segment(one, TP_REF_STERIL_MILLI_C, 151200)
32 let f_prof: i64 = tpr_f0_ms(one)
33 let f_direct: i64 = tp_f0_ms(151200, TP_REF_STERIL_MILLI_C)
34 t_puts("T1 one segment: profile F0=" as *u8); t_putn(f_prof); t_puts(" direct F0=" as *u8); t_putn(f_direct); t_puts(" (identical): " as *u8)
35 if f_prof == f_direct { if f_prof == 151200 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
36
37 // --- T2 the retort profile is assembled as expected ---
38 total = total + 1
39 let r: *NxThermalProfile = build_retort()
40 let nseg: i64 = r.n
41 let ttime: i64 = tpr_total_time_ms(r)
42 let peak: i64 = tpr_peak_temp_milli(r)
43 t_puts("T2 segments=" as *u8); t_putn(nseg); t_puts(" total time=" as *u8); t_putn(ttime); t_puts(" ms peak=" as *u8); t_putn(peak); t_puts(" milli-C: " as *u8)
44 var ok2: i64 = 1
45 if nseg != 21 { ok2 = 0 }
46 if ttime != 1351200 { ok2 = 0 }
47 if peak != TP_REF_STERIL_MILLI_C { ok2 = 0 }
48 if ok2 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
49
50 // --- T3 the whole profile clears the 12D cook comfortably ---
51 total = total + 1
52 let f0: i64 = tpr_f0_ms(r)
53 let adeq: i64 = tpr_bot_cook_adequate(r)
54 let need: i64 = tp_bot_12d_ms()
55 t_puts("T3 profile F0=" as *u8); t_putn(f0); t_puts(" ms vs 12D floor " as *u8); t_putn(need); t_puts(" adequate=" as *u8); t_putn(adeq); t_puts(": " as *u8)
56 var ok3: i64 = 1
57 if f0 < 350000 { ok3 = 0 }
58 if f0 > 450000 { ok3 = 0 }
59 if adeq != 1 { ok3 = 0 }
60 if ok3 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
61
62 // --- T4 THE FINDING: the ramps out-deliver the hold ---
63 // Hold-only credits 151200 ms. The ramps add more than that again, so a
64 // hold-only process authority is not conservative-by-a-little: it throws
65 // away most of the lethality and prescribes over-processing.
66 total = total + 1
67 let credit: i64 = tpr_ramp_credit_ms(r, 151200)
68 t_puts("T4 hold-only credit=151200 ms, ramps add " as *u8); t_putn(credit); t_puts(" ms MORE than the hold itself: " as *u8)
69 var ok4: i64 = 1
70 if credit <= 151200 { ok4 = 0 }
71 if credit < 190000 { ok4 = 0 }
72 if credit > 300000 { ok4 = 0 }
73 if ok4 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
74
75 // --- T5 a come-up ALONE already approaches a 12D cook ---
76 total = total + 1
77 let up: *NxThermalProfile = nx_thermal_profile_new(16)
78 tpr_add_ramp(up, 100000, 121100, 600000, 10)
79 let f_up: i64 = tpr_f0_ms(up)
80 t_puts("T5 10-min come-up alone F0=" as *u8); t_putn(f_up); t_puts(" ms vs 12D " as *u8); t_putn(need); t_puts(" (same order of magnitude): " as *u8)
81 var ok5: i64 = 1
82 if f_up < 90000 { ok5 = 0 }
83 if f_up > 160000 { ok5 = 0 }
84 if ok5 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
85
86 // --- T6 temperature dominates time: the SAME 22.5 minutes held 15 C
87 // cooler delivers almost nothing ---
88 total = total + 1
89 let cool: *NxThermalProfile = nx_thermal_profile_new(4)
90 tpr_add_segment(cool, 106000, 1351200)
91 let f_cool: i64 = tpr_f0_ms(cool)
92 t_puts("T6 same 1351200 ms held at 106 C: F0=" as *u8); t_putn(f_cool); t_puts(" ms vs the retort's " as *u8); t_putn(f0); t_puts(" (far less, and inadequate): " as *u8)
93 var ok6: i64 = 1
94 if f_cool >= f0 { ok6 = 0 }
95 if tp_bot_cook_adequate(f_cool) != 0 { ok6 = 0 }
96 if ok6 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
97
98 // --- T7 lethality is ADDITIVE over segments ---
99 total = total + 1
100 let a1: *NxThermalProfile = nx_thermal_profile_new(4)
101 tpr_add_segment(a1, 118000, 300000)
102 let a2: *NxThermalProfile = nx_thermal_profile_new(4)
103 tpr_add_segment(a2, 115000, 200000)
104 let both: *NxThermalProfile = nx_thermal_profile_new(4)
105 tpr_add_segment(both, 118000, 300000)
106 tpr_add_segment(both, 115000, 200000)
107 let sum: i64 = tpr_f0_ms(a1) + tpr_f0_ms(a2)
108 let joint: i64 = tpr_f0_ms(both)
109 t_puts("T7 F0(A)+F0(B)=" as *u8); t_putn(sum); t_puts(" F0(A then B)=" as *u8); t_putn(joint); t_puts(" (equal): " as *u8)
110 if sum == joint { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
111
112 // --- T8 more ramp steps converge (the approximation is stable) ---
113 total = total + 1
114 let c5: *NxThermalProfile = nx_thermal_profile_new(128)
115 tpr_add_ramp(c5, 100000, 121100, 600000, 5)
116 let c20: *NxThermalProfile = nx_thermal_profile_new(128)
117 tpr_add_ramp(c20, 100000, 121100, 600000, 20)
118 let f5: i64 = tpr_f0_ms(c5)
119 let f20: i64 = tpr_f0_ms(c20)
120 var spread: i64 = f20 - f5
121 if spread < 0 { spread = 0 - spread }
122 let pct: i64 = spread * 100 / f20
123 t_puts("T8 ramp with 5 steps=" as *u8); t_putn(f5); t_puts(" with 20 steps=" as *u8); t_putn(f20); t_puts(" spread=" as *u8); t_putn(pct); t_puts("% (<15): " as *u8)
124 if pct < 15 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
125
126 // --- T9 FAIL-CLOSED: capacity overflow REFUSES, never truncates ---
127 // A silently truncated profile reports LESS lethality than the process
128 // delivers, and the operator would respond by cooking longer.
129 total = total + 1
130 let small: *NxThermalProfile = nx_thermal_profile_new(2)
131 tpr_add_segment(small, 120000, 1000)
132 tpr_add_segment(small, 120000, 1000)
133 let third: i64 = tpr_add_segment(small, 120000, 1000)
134 let s_valid: i64 = small.valid
135 let s_f0: i64 = tpr_f0_ms(small)
136 t_puts("T9 third segment into cap-2: rc=" as *u8); t_putn(third); t_puts(" valid=" as *u8); t_putn(s_valid); t_puts(" F0=" as *u8); t_putn(s_f0); t_puts(" want -1/0/-1: " as *u8)
137 var ok9: i64 = 1
138 if third != TPR_REFUSED { ok9 = 0 }
139 if s_valid != 0 { ok9 = 0 }
140 if s_f0 != TPR_REFUSED { ok9 = 0 }
141 if ok9 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
142
143 // --- T10 malformed segments and empty profiles refuse ---
144 total = total + 1
145 let z: *NxThermalProfile = nx_thermal_profile_new(4)
146 let rc_zero: i64 = tpr_add_segment(z, 120000, 0)
147 let empty: *NxThermalProfile = nx_thermal_profile_new(4)
148 let e_f0: i64 = tpr_f0_ms(empty)
149 let ramp0: *NxThermalProfile = nx_thermal_profile_new(4)
150 let rc_ramp: i64 = tpr_add_ramp(ramp0, 100000, 121100, 600000, 0)
151 t_puts("T10 zero-duration=" as *u8); t_putn(rc_zero); t_puts(" empty-profile F0=" as *u8); t_putn(e_f0); t_puts(" zero-step ramp=" as *u8); t_putn(rc_ramp); t_puts(" (all -1): " as *u8)
152 var ok10: i64 = 1
153 if rc_zero != TPR_REFUSED { ok10 = 0 }
154 if e_f0 != TPR_REFUSED { ok10 = 0 }
155 if rc_ramp != TPR_REFUSED { ok10 = 0 }
156 if ok10 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
157
158 // --- T11 log reductions of the whole profile against C. botulinum ---
159 total = total + 1
160 let logs: i64 = tpr_log_reductions_q3(r, TP_BOT_D_REF_MS, TP_REF_STERIL_MILLI_C, TP_BOT_Z_MILLI)
161 t_puts("T11 retort delivers logs x1000 = " as *u8); t_putn(logs); t_puts(" vs the 12000 that 12D means (>12000): " as *u8)
162 if logs > 12000 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
163
164 // --- T12 a pasteurisation profile integrates on its own z ---
165 total = total + 1
166 let zp: i64 = tp_pasteurization_z_milli()
167 let ht: *NxThermalProfile = nx_thermal_profile_new(8)
168 tpr_add_segment(ht, tp_sched_temp_milli(TP_SCHED_MIX_HTST), 15000)
169 let leth: i64 = tpr_lethality_ms(ht, tp_sched_temp_milli(TP_SCHED_MIX_HTST), zp)
170 t_puts("T12 ice-cream-mix HTST leg integrated at its own reference = " as *u8); t_putn(leth); t_puts(" ms, want 15000: " as *u8)
171 if leth == 15000 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
172
173 t_puts("THERMAL-PROFILE-GATE passed " as *u8); t_putn(pass); t_puts("/" as *u8); t_putn(total)
174 if pass == total { t_puts(" verdict=GREEN\n" as *u8); sys_exit(0); return 0 }
175 t_puts(" verdict=RED\n" as *u8); sys_exit(1); return 1
176}