nx_icecream_test.nx source
↩ module page · 256 lines · 14116 B
1// nx_icecream_test.nx -- gate for nx_icecream. The freezing-curve tests
2// are checked against PUBLISHED freezing curves for a typical mix, the DE
3// polymer formula is checked by REDUCTION to two molecules whose masses are
4// known independently, and the legal-overrun ceiling is checked by finding
5// the actual crossing point rather than trusting the algebra.
6// expect_exit: 0 license_tier: ORIGINAL
7import "nx_syscalls.nx"
8import "nx_icecream.nx"
9
10func 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 }
11func 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 }
12
13// Reference mix: 12% fat, 10% MSNF, 15% sucrose, 3% other, per 1000 g.
14func ref_mix() -> *NxIceMix {
15 let m: *NxIceMix = nx_ice_mix_new()
16 m.fat_g = 120
17 m.msnf_g = 100
18 m.sugar_g = 150
19 m.sugar_mw_q2 = IC_MW_SUCROSE_Q2
20 m.sugar_pod = 100
21 m.other_solids_g = 30
22 m.total_g = 1000
23 m.density_q3 = 1100
24 return m
25}
26
27func main() -> i64 {
28 var pass: i64 = 0
29 var total: i64 = 0
30
31 let m: *NxIceMix = ref_mix()
32
33 // --- T1 composition bookkeeping: solids + water == batch ---
34 total = total + 1
35 let sol: i64 = ic_total_solids_g(m)
36 let wat: i64 = ic_water_g(m)
37 t_puts("T1 solids=" as *u8); t_putn(sol); t_puts(" water=" as *u8); t_putn(wat); t_puts(" sum==1000: " as *u8)
38 if sol == 400 { if wat == 600 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
39
40 // --- T2 initial freezing point lands where real mixes do (~-2.5 C) ---
41 total = total + 1
42 let fp: i64 = ic_freezing_point_milli_c(m)
43 t_puts("T2 freezing point milli-C=" as *u8); t_putn(fp); t_puts(" band -2200..-2800: " as *u8)
44 if fp <= 0 - 2200 { if fp >= 0 - 2800 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
45
46 // --- T3 freezing curve vs PUBLISHED points: ~50% at -5, ~80% at -13 ---
47 total = total + 1
48 let f5: i64 = ic_frozen_water_permil(m, 0 - 5000)
49 let f13: i64 = ic_frozen_water_permil(m, 0 - 13000)
50 let f18: i64 = ic_frozen_water_permil(m, 0 - 18000)
51 t_puts("T3 frozen permil at -5/-13/-18 C = " as *u8); t_putn(f5); t_puts("/" as *u8); t_putn(f13); t_puts("/" as *u8); t_putn(f18); t_puts(" (lit 500/800/900): " as *u8)
52 var ok3: i64 = 1
53 if f5 < 470 { ok3 = 0 }
54 if f5 > 530 { ok3 = 0 }
55 if f13 < 770 { ok3 = 0 }
56 if f13 > 830 { ok3 = 0 }
57 if f18 <= f13 { ok3 = 0 }
58 if ok3 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
59
60 // --- T4 FAIL-CLOSED above the freezing point: no negative ice ---
61 total = total + 1
62 let fwarm: i64 = ic_frozen_water_permil(m, 0 - 2000)
63 let fzero: i64 = ic_frozen_water_permil(m, 0)
64 t_puts("T4 frozen at -2 C=" as *u8); t_putn(fwarm); t_puts(" at 0 C=" as *u8); t_putn(fzero); t_puts(" (both 0): " as *u8)
65 if fwarm == 0 { if fzero == 0 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
66
67 // --- T5 the curve INVERTS exactly: solve for T, then evaluate back ---
68 total = total + 1
69 let ts: i64 = ic_scoop_temp_milli_c(m)
70 let back: i64 = ic_frozen_water_permil(m, ts)
71 t_puts("T5 scoop temp milli-C=" as *u8); t_putn(ts); t_puts(" round-trips to permil=" as *u8); t_putn(back); t_puts(" want 720: " as *u8)
72 if back == IC_SCOOPABLE_FROZEN_PERMIL { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
73
74 // --- T6 PAC reproduces the trade table for crystalline sugars ---
75 // Salt is 586 here, not the table's 585: 342.30/58.44 = 585.73, and the
76 // trade figure comes from rounding NaCl to 58.5 g/mol first. We keep
77 // the exact molar mass, so the discrepancy is in the TABLE, not here.
78 total = total + 1
79 let pac_suc: i64 = ic_pac_from_mw(IC_MW_SUCROSE_Q2)
80 let pac_dex: i64 = ic_pac_from_mw(IC_MW_DEXTROSE_Q2)
81 let pac_fru: i64 = ic_pac_from_mw(IC_MW_FRUCTOSE_Q2)
82 let pac_lac: i64 = ic_pac_from_mw(IC_MW_LACTOSE_Q2)
83 let pac_salt: i64 = ic_pac_from_mw(IC_MW_NACL_Q2)
84 t_puts("T6 PAC sucrose/dextrose/fructose/lactose/salt = " as *u8); t_putn(pac_suc); t_puts("/" as *u8); t_putn(pac_dex); t_puts("/" as *u8); t_putn(pac_fru); t_puts("/" as *u8); t_putn(pac_lac); t_puts("/" as *u8); t_putn(pac_salt); t_puts(" want 100/190/190/100/586: " as *u8)
85 var ok6: i64 = 1
86 if pac_suc != 100 { ok6 = 0 }
87 if pac_dex != 190 { ok6 = 0 }
88 if pac_fru != 190 { ok6 = 0 }
89 if pac_lac != 100 { ok6 = 0 }
90 if pac_salt != 586 { ok6 = 0 }
91 if ok6 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
92
93 // --- T7 DE polymer formula REDUCES to known molecules ---
94 // DE 100 must give glucose (180.16); DE 50 must give a disaccharide
95 // (342.30). Two independent anchors on the same equation.
96 total = total + 1
97 let de100: i64 = ic_syrup_mw_q2(100)
98 let de50: i64 = ic_syrup_mw_q2(50)
99 let de42: i64 = ic_syrup_mw_q2(42)
100 t_puts("T7 syrup MW at DE100/DE50/DE42 = " as *u8); t_putn(de100); t_puts("/" as *u8); t_putn(de50); t_puts("/" as *u8); t_putn(de42); t_puts(" want 18016/34230/~40406: " as *u8)
101 var ok7: i64 = 1
102 if de100 != IC_MW_DEXTROSE_Q2 { ok7 = 0 }
103 if de50 != IC_MW_SUCROSE_Q2 { ok7 = 0 }
104 if de42 <= de50 { ok7 = 0 }
105 if ok7 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
106
107 // --- T8 blend MW is mass-over-moles, and sits between its parts ---
108 total = total + 1
109 let bmw: i64 = ic_blend_mw_q2(100, IC_MW_SUCROSE_Q2, 100, IC_MW_DEXTROSE_Q2)
110 let bpod: i64 = ic_blend_pod(100, 100, 100, 70)
111 t_puts("T8 50/50 sucrose+dextrose blend MW=" as *u8); t_putn(bmw); t_puts(" POD=" as *u8); t_putn(bpod); t_puts(" want ~23607/85: " as *u8)
112 var ok8: i64 = 1
113 if bmw >= IC_MW_SUCROSE_Q2 { ok8 = 0 }
114 if bmw <= IC_MW_DEXTROSE_Q2 { ok8 = 0 }
115 if bpod != 85 { ok8 = 0 }
116 if ok8 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
117
118 // --- T9 CROSS-VALIDATION: the trade index and the physics agree ---
119 // Swap sucrose for dextrose at equal mass. PAC must rise, POD must
120 // FALL, and the colligative freezing point must drop further -- the
121 // whole reason dextrose is the softness lever.
122 total = total + 1
123 let d: *NxIceMix = ref_mix()
124 d.sugar_mw_q2 = IC_MW_DEXTROSE_Q2
125 d.sugar_pod = 70
126 let pac_a: i64 = ic_pac_index(m)
127 let pac_b: i64 = ic_pac_index(d)
128 let pod_a: i64 = ic_pod_index(m)
129 let pod_b: i64 = ic_pod_index(d)
130 let fpd_a: i64 = ic_fpd_milli_c(m)
131 let fpd_b: i64 = ic_fpd_milli_c(d)
132 t_puts("T9 sucrose PAC=" as *u8); t_putn(pac_a); t_puts(" POD=" as *u8); t_putn(pod_a); t_puts(" FPD=" as *u8); t_putn(fpd_a); t_puts(" | dextrose PAC=" as *u8); t_putn(pac_b); t_puts(" POD=" as *u8); t_putn(pod_b); t_puts(" FPD=" as *u8); t_putn(fpd_b); t_puts(": " as *u8)
133 var ok9: i64 = 1
134 if pac_b <= pac_a { ok9 = 0 }
135 if pod_b >= pod_a { ok9 = 0 }
136 if fpd_b <= fpd_a { ok9 = 0 }
137 if ok9 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
138
139 // --- T10 and it shows up as a SOFTER product at the same temperature ---
140 total = total + 1
141 let h_a: i64 = ic_hardness_index(m, 0 - 13000)
142 let h_b: i64 = ic_hardness_index(d, 0 - 13000)
143 t_puts("T10 ice at -13 C sucrose=" as *u8); t_putn(h_a); t_puts(" dextrose=" as *u8); t_putn(h_b); t_puts(" (dextrose softer): " as *u8)
144 if h_b < h_a { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
145
146 // --- T11 sandiness: reference is safe, high-MSNF mix is not ---
147 total = total + 1
148 let c_ref: i64 = ic_lactose_per_100_water_q1(m)
149 let r_ref: i64 = ic_sandy_risk(m)
150 let s: *NxIceMix = ref_mix()
151 s.msnf_g = 120
152 let c_hi: i64 = ic_lactose_per_100_water_q1(s)
153 let r_hi: i64 = ic_sandy_risk(s)
154 t_puts("T11 lactose g/100g-water x10 ref=" as *u8); t_putn(c_ref); t_puts(" risk=" as *u8); t_putn(r_ref); t_puts(" | 12%MSNF=" as *u8); t_putn(c_hi); t_puts(" risk=" as *u8); t_putn(r_hi); t_puts(": " as *u8)
155 if r_ref == 0 { if r_hi == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
156
157 // --- T12 the DERIVED MSNF ceiling reproduces the "1/6 of water" rule ---
158 total = total + 1
159 let cap: i64 = ic_max_msnf_g(600)
160 let sixth: i64 = 600 / 6
161 t_puts("T12 max MSNF for 600 g water=" as *u8); t_putn(cap); t_puts(" g, one sixth=" as *u8); t_putn(sixth); t_puts(" (within 5 g): " as *u8)
162 var dd: i64 = cap - sixth
163 if dd < 0 { dd = 0 - dd }
164 if dd <= 5 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
165
166 // --- T13 overrun by volume and by weight agree on a doubled batch ---
167 total = total + 1
168 let ov_v: i64 = ic_overrun_pct(1000, 2000)
169 let ov_w: i64 = ic_overrun_pct_by_weight(1000, 500)
170 t_puts("T13 overrun by volume=" as *u8); t_putn(ov_v); t_puts("% by weight=" as *u8); t_putn(ov_w); t_puts("% (both 100): " as *u8)
171 if ov_v == 100 { if ov_w == 100 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
172
173 // --- T14 legal weight: 100% overrun passes, 120% fails ---
174 total = total + 1
175 let w100: i64 = ic_weight_lb_per_gal_q2(1100, 100)
176 let w120: i64 = ic_weight_lb_per_gal_q2(1100, 120)
177 t_puts("T14 lb/gal x100 at 100% ov=" as *u8); t_putn(w100); t_puts(" at 120% ov=" as *u8); t_putn(w120); t_puts(" min 450: " as *u8)
178 if w100 >= IC_FDA_MIN_LB_PER_GAL_Q2 { if w120 < IC_FDA_MIN_LB_PER_GAL_Q2 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
179
180 // --- T15 the derived legal ceiling IS the real crossing point ---
181 // Not "the algebra looks right": evaluate the weight rule at the
182 // ceiling and one point past it, and demand the sign actually flips.
183 total = total + 1
184 let capov: i64 = ic_max_legal_overrun_pct(1100)
185 let at_cap: i64 = ic_weight_lb_per_gal_q2(1100, capov)
186 let past: i64 = ic_weight_lb_per_gal_q2(1100, capov + 1)
187 t_puts("T15 max legal overrun=" as *u8); t_putn(capov); t_puts("% weight at cap=" as *u8); t_putn(at_cap); t_puts(" one past=" as *u8); t_putn(past); t_puts(": " as *u8)
188 if at_cap >= IC_FDA_MIN_LB_PER_GAL_Q2 { if past < IC_FDA_MIN_LB_PER_GAL_Q2 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
189
190 // --- T16 FDA standard of identity, all three conditions ---
191 total = total + 1
192 let is_ic: i64 = ic_fda_is_ice_cream(m, 100)
193 let lean: *NxIceMix = ref_mix()
194 lean.fat_g = 60
195 let is_lean: i64 = ic_fda_is_ice_cream(lean, 100)
196 let airy: i64 = ic_fda_is_ice_cream(m, 130)
197 t_puts("T16 FDA ice cream: ref=" as *u8); t_putn(is_ic); t_puts(" 6%-fat=" as *u8); t_putn(is_lean); t_puts(" over-aerated=" as *u8); t_putn(airy); t_puts(" want 1/0/0: " as *u8)
198 var ok16: i64 = 1
199 if is_ic != 1 { ok16 = 0 }
200 if is_lean != 0 { ok16 = 0 }
201 if airy != 0 { ok16 = 0 }
202 if ok16 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
203
204 // --- T17 style classification ---
205 total = total + 1
206 let cls_ic: i64 = ic_classify(m, 100)
207 let cls_gel: i64 = ic_classify(m, 30)
208 let sorb: *NxIceMix = nx_ice_mix_new()
209 sorb.sugar_g = 250
210 sorb.other_solids_g = 10
211 let cls_sorb: i64 = ic_classify(sorb, 30)
212 t_puts("T17 class ref@100%=" as *u8); t_putn(cls_ic); t_puts(" ref@30%=" as *u8); t_putn(cls_gel); t_puts(" sorbet=" as *u8); t_putn(cls_sorb); t_puts(": " as *u8)
213 var ok17: i64 = 1
214 if cls_ic != IC_STYLE_ICE_CREAM { ok17 = 0 }
215 if cls_gel != IC_STYLE_GELATO { ok17 = 0 }
216 if cls_sorb != IC_STYLE_SORBET { ok17 = 0 }
217 if ok17 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
218
219 // --- T18 INCOHERENT mix fails closed (solids exceed the batch) ---
220 total = total + 1
221 let badm: *NxIceMix = ref_mix()
222 badm.sugar_g = 900
223 let bw: i64 = ic_water_g(badm)
224 let bf: i64 = ic_fpd_milli_c(badm)
225 let bfr: i64 = ic_frozen_water_permil(badm, 0 - 13000)
226 t_puts("T18 over-full mix water=" as *u8); t_putn(bw); t_puts(" FPD=" as *u8); t_putn(bf); t_puts(" frozen=" as *u8); t_putn(bfr); t_puts(" (all 0, no bogus answer): " as *u8)
227 var ok18: i64 = 1
228 if bw != 0 { ok18 = 0 }
229 if bf != 0 { ok18 = 0 }
230 if bfr != 0 { ok18 = 0 }
231 if ok18 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
232
233 // --- T19 monotonicity: colder is never less frozen ---
234 total = total + 1
235 var tt: i64 = 0 - 3000
236 var prev: i64 = 0 - 1
237 var ok19: i64 = 1
238 while tt >= 0 - 30000 {
239 let fr: i64 = ic_frozen_water_permil(m, tt)
240 if fr < prev { ok19 = 0 }
241 prev = fr
242 tt = tt - 1000
243 }
244 t_puts("T19 frozen fraction monotone non-decreasing from -3 to -30 C: " as *u8)
245 if ok19 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
246
247 // --- T20 ice fraction never reaches 100% (water is never all frozen) ---
248 total = total + 1
249 let deep: i64 = ic_frozen_water_permil(m, 0 - 40000)
250 t_puts("T20 frozen at -40 C=" as *u8); t_putn(deep); t_puts(" permil, must stay under 1000: " as *u8)
251 if deep < 1000 { if deep > 900 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
252
253 t_puts("ICECREAM-GATE passed " as *u8); t_putn(pass); t_puts("/" as *u8); t_putn(total)
254 if pass == total { t_puts(" verdict=GREEN\n" as *u8); sys_exit(0); return 0 }
255 t_puts(" verdict=RED\n" as *u8); sys_exit(1); return 1
256}