nx_ice_recrystal_test.nx source
↩ module page · 191 lines · 10819 B
1// nx_ice_recrystal_test.nx -- gate for ice recrystallisation.
2//
3// THE LIAR-KILL IS THE CUBE-LAW ROUND TRIP (T2/T3): recover k from a pair of
4// observed crystal sizes, then run the model forward and land back on the
5// observation it was recovered from. A wrong exponent or a wrong cube root
6// cannot survive that, because both directions use the full arithmetic.
7//
8// THE FINDING IS T7: two storage histories with the SAME mean temperature
9// give different ice cream. It is measured, with a flat-history control
10// (T8) proving the difference comes from the cycling and not from the model
11// adding damage to everything it touches.
12// expect_exit: 0 license_tier: ORIGINAL
13import "nx_syscalls.nx"
14import "nx_icecream.nx"
15import "nx_ice_recrystal.nx"
16
17func tw(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
18func tn(v: i64) -> i64 { let b: *u8 = sys_mmap(24); var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } let t: *u8 = sys_mmap(24); 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 { b[i] = t[k - 1 - i]; i = i + 1 } sys_write(1, b, k); return 0 }
19func iabs(v: i64) -> i64 { if v < 0 { return 0 - v } return v }
20
21func main() -> i64 {
22 var pass: i64 = 0
23 var total: i64 = 0
24
25 // --- T1 The integer cube root is EXACT where it must be, and it is the
26 // inverse of the cube on real values rather than near them. ---
27 total = total + 1
28 var ok1: i64 = 1
29 if ir_icbrt(0) != 0 { ok1 = 0 }
30 if ir_icbrt(1000) != 10 { ok1 = 0 }
31 if ir_icbrt(15625000) != 250 { ok1 = 0 }
32 if ir_icbrt(125000000) != 500 { ok1 = 0 }
33 if ir_icbrt(ir_cube(371)) != 371 { ok1 = 0 }
34 // and it FLOORS rather than rounding up: one below a perfect cube must
35 // not report the cube's root, or a size just under threshold reads over.
36 if ir_icbrt(ir_cube(371) - 1) != 370 { ok1 = 0 }
37 tw("T1 integer cube root exact on perfect cubes and floors below them: " as *u8)
38 if ok1 == 1 { pass = pass + 1; tw("PASS\n" as *u8) } else { tw("FAIL\n" as *u8) }
39
40 // --- T2 ***THE ROUND TRIP*** Recover k from an observation, run the
41 // model forward over the same interval, land on the observation. ---
42 total = total + 1
43 let k_obs: i64 = ir_k_from_observation(250, 400, 90)
44 let fwd: i64 = ir_size_isothermal(250, k_obs, 90)
45 tw("T2 k recovered from 25um->40um over 90d = " as *u8); tn(k_obs)
46 tw("; model forward 90d returns " as *u8); tn(fwd); tw(" (want 400): " as *u8)
47 if iabs(fwd - 400) <= 1 { pass = pass + 1; tw("PASS\n" as *u8) } else { tw("FAIL\n" as *u8) }
48
49 // --- T3 NON-VACUITY of T2: the cube law is not a straight line. At
50 // HALF the time the diameter must be well ABOVE the halfway
51 // point, because growth in diameter decelerates as D^(1/3).
52 // A linear model would put it at 325. ---
53 total = total + 1
54 let half: i64 = ir_size_isothermal(250, k_obs, 45)
55 tw("T3 at half the time D=" as *u8); tn(half)
56 tw(" -- linear would say 325, cube law says >340 (growth decelerates): " as *u8)
57 var ok3: i64 = 1
58 if half <= 340 { ok3 = 0 }
59 if half >= 400 { ok3 = 0 }
60 if ok3 == 1 { pass = pass + 1; tw("PASS\n" as *u8) } else { tw("FAIL\n" as *u8) }
61
62 // --- T4 k REFUSES an impossible observation. Crystals do not
63 // un-ripen, so a shrinking measurement is an error in the data,
64 // and turning it into a negative rate would produce a shelf life
65 // that grows with time. ---
66 total = total + 1
67 var ok4: i64 = 1
68 if ir_k_from_observation(400, 250, 90) != IR_INVALID { ok4 = 0 }
69 if ir_k_from_observation(250, 250, 90) != IR_INVALID { ok4 = 0 }
70 if ir_k_from_observation(250, 400, 0) != IR_INVALID { ok4 = 0 }
71 if ir_k_is_recovered(k_obs) != 1 { ok4 = 0 }
72 if ir_k_is_recovered(IR_K_DEFAULT) != 0 { ok4 = 0 }
73 tw("T4 shrinking/static/zero-time observations REFUSED, and recovered-vs-default k is distinguishable: " as *u8)
74 if ok4 == 1 { pass = pass + 1; tw("PASS\n" as *u8) } else { tw("FAIL\n" as *u8) }
75
76 // --- T5 ***THE COMPOSITION*** The cycled ice fraction comes out of the
77 // freezing curve this lane already validated, not out of a new
78 // constant. A warm excursion must melt a real, positive share of
79 // the ice, and a deeper excursion must melt more. ---
80 total = total + 1
81 let m: *NxIceMix = nx_ice_mix_new()
82 m.fat_g = 120
83 m.msnf_g = 100
84 m.sugar_g = 150
85 m.total_g = 1000
86 let cyc_small: i64 = ir_cycled_fraction_permil(m, 0 - 20000, 0 - 16000)
87 let cyc_big: i64 = ir_cycled_fraction_permil(m, 0 - 20000, 0 - 10000)
88 tw("T5 cycled ice fraction from the freezing curve: -20/-16C = " as *u8); tn(cyc_small)
89 tw(" permil, -20/-10C = " as *u8); tn(cyc_big); tw(" permil: " as *u8)
90 var ok5: i64 = 1
91 if cyc_small <= 0 { ok5 = 0 }
92 if cyc_big <= cyc_small { ok5 = 0 }
93 if cyc_big >= 1000 { ok5 = 0 }
94 if ok5 == 1 { pass = pass + 1; tw("PASS\n" as *u8) } else { tw("FAIL\n" as *u8) }
95
96 // --- T6 A cycle that does not warm melts nothing. This is the control
97 // that keeps T5 meaningful: the fraction tracks the excursion,
98 // it is not a constant the model applies regardless. ---
99 total = total + 1
100 var ok6: i64 = 1
101 if ir_cycled_fraction_permil(m, 0 - 20000, 0 - 20000) != 0 { ok6 = 0 }
102 if ir_cycled_fraction_permil(m, 0 - 16000, 0 - 20000) != 0 { ok6 = 0 }
103 tw("T6 a non-excursion melts nothing (equal and inverted ends both 0): " as *u8)
104 if ok6 == 1 { pass = pass + 1; tw("PASS\n" as *u8) } else { tw("FAIL\n" as *u8) }
105
106 // --- T7 ***THE FINDING, MEASURED.*** Two 60-day histories with the
107 // SAME mean temperature: one steady at -18 C, one cycling
108 // -20/-16 C twice a week. Chemically they are near-identical.
109 // As ice cream they are not, and the model must say so. ---
110 total = total + 1
111 let steady: i64 = ir_size_isothermal(250, k_obs, 60)
112 let cycled: i64 = ir_size_combined(250, k_obs, 60, cyc_small, 17)
113 tw("T7 60 days, SAME mean temperature: steady -18C -> D=" as *u8); tn(steady)
114 tw(" cycling -20/-16C x17 -> D=" as *u8); tn(cycled)
115 tw(" (0.1um); grades " as *u8); tn(ir_texture_grade(steady))
116 tw(" vs " as *u8); tn(ir_texture_grade(cycled)); tw(": " as *u8)
117 var ok7: i64 = 1
118 if cycled <= steady { ok7 = 0 }
119 // the difference must be large enough to matter, not a rounding artefact
120 if (cycled - steady) * 100 / steady < 5 { ok7 = 0 }
121 if ir_texture_grade(cycled) <= ir_texture_grade(steady) { ok7 = 0 }
122 if ok7 == 1 { pass = pass + 1; tw("PASS\n" as *u8) } else { tw("FAIL\n" as *u8) }
123
124 // --- T8 NEGATIVE CONTROL for T7. Zero-amplitude cycling must reduce
125 // EXACTLY to the isothermal answer. Without this, T7 could be
126 // passing because the model adds damage to any history handed to
127 // it, which would make the finding an artefact. ---
128 total = total + 1
129 let flat: i64 = ir_size_combined(250, k_obs, 60, 0, 17)
130 tw("T8 zero-amplitude cycling reduces to isothermal: " as *u8); tn(flat)
131 tw(" vs " as *u8); tn(steady); tw(": " as *u8)
132 if flat == steady { pass = pass + 1; tw("PASS\n" as *u8) } else { tw("FAIL\n" as *u8) }
133
134 // --- T9 MANY SMALL EXCURSIONS BEAT ONE BIG ONE, because the damage
135 // compounds per cycle. This is the operationally useful claim:
136 // a cabinet that cycles often is worse than one that occasionally
137 // goes warmer, and the model must reproduce that ordering. ---
138 total = total + 1
139 let many_small: i64 = ir_size_after_cycles(250, cyc_small, 20)
140 let one_big: i64 = ir_size_after_cycles(250, cyc_big, 1)
141 tw("T9 twenty small excursions D=" as *u8); tn(many_small)
142 tw(" vs one large excursion D=" as *u8); tn(one_big); tw(" -- many small is worse: " as *u8)
143 if many_small > one_big { pass = pass + 1; tw("PASS\n" as *u8) } else { tw("FAIL\n" as *u8) }
144
145 // --- T10 The sensory verdict is FAIL-CLOSED and the thresholds order
146 // correctly. An unusable diameter must read COARSE: reporting
147 // "smooth" for a state we cannot evaluate is the single answer
148 // that would ship a defective product. ---
149 total = total + 1
150 var ok10: i64 = 1
151 if ir_texture_grade(250) != 0 { ok10 = 0 }
152 if ir_texture_grade(IR_DETECT_D) != 1 { ok10 = 0 }
153 if ir_texture_grade(IR_COARSE_D) != 2 { ok10 = 0 }
154 if ir_texture_grade(IR_INVALID) != 2 { ok10 = 0 }
155 if ir_texture_grade(0) != 2 { ok10 = 0 }
156 tw("T10 texture grade ordered and fail-closed (unknown reads COARSE, never smooth): " as *u8)
157 if ok10 == 1 { pass = pass + 1; tw("PASS\n" as *u8) } else { tw("FAIL\n" as *u8) }
158
159 // --- T11 Shelf life in both currencies -- days of steady storage, and
160 // number of excursions -- and each must agree with the forward
161 // model at its own answer rather than being a separate formula. ---
162 total = total + 1
163 let days_ok: i64 = ir_days_to_detectable(250, k_obs)
164 let cyc_ok: i64 = ir_cycles_to_detectable(250, cyc_small)
165 tw("T11 steady storage lasts " as *u8); tn(days_ok)
166 tw(" days; survives " as *u8); tn(cyc_ok); tw(" excursions: " as *u8)
167 var ok11: i64 = 1
168 if days_ok <= 0 { ok11 = 0 }
169 if cyc_ok <= 0 { ok11 = 0 }
170 if ir_size_isothermal(250, k_obs, days_ok) < IR_DETECT_D - 2 { ok11 = 0 }
171 if ir_size_after_cycles(250, cyc_small, cyc_ok) < IR_DETECT_D - 2 { ok11 = 0 }
172 if ir_size_isothermal(250, k_obs, days_ok - 1) >= IR_DETECT_D { ok11 = 0 }
173 if ok11 == 1 { pass = pass + 1; tw("PASS\n" as *u8) } else { tw("FAIL\n" as *u8) }
174
175 // --- T12 ***THE HONEST BOUNDARY.*** MKT is the right tool for chemical
176 // degradation and the WRONG tool here, because collapsing a
177 // history to one temperature discards the oscillation that IS the
178 // mechanism. Declared as a function so no planner can feed an
179 // MKT in and trust the answer. The asserted coupling is likewise
180 // flagged rather than buried. ---
181 total = total + 1
182 var ok12: i64 = 1
183 if ir_mkt_describes_recrystallisation() != 0 { ok12 = 0 }
184 if ir_coupling_is_asserted() != 1 { ok12 = 0 }
185 tw("T12 MKT declared INAPPLICABLE to recrystallisation, geometric coupling declared ASSERTED: " as *u8)
186 if ok12 == 1 { pass = pass + 1; tw("PASS\n" as *u8) } else { tw("FAIL\n" as *u8) }
187
188 tw("ICE-RECRYSTAL-GATE passed " as *u8); tn(pass); tw("/" as *u8); tn(total)
189 if pass == total { tw(" verdict=GREEN\n" as *u8); sys_exit(0); return 0 }
190 tw(" verdict=RED\n" as *u8); sys_exit(1); return 1
191}