nx_icecream_balance_test.nx source
↩ module page · 192 lines · 11360 B
1// nx_icecream_balance_test.nx -- gate for nx_icecream_balance. The core
2// tests are ROUND TRIPS: solve for a mix, hand the answer to nx_icecream,
3// and demand the analyser reports back the targets that were asked for.
4// Solver and analyser are independent code paths, so agreement is real
5// evidence rather than a restatement.
6// expect_exit: 0 license_tier: ORIGINAL
7import "nx_syscalls.nx"
8import "nx_icecream.nx"
9import "nx_icecream_balance.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
14func main() -> i64 {
15 var pass: i64 = 0
16 var total: i64 = 0
17
18 // --- T1 solve a standard 12/10/15 mix ---
19 total = total + 1
20 let s: *NxMixSolution = icb_solve_dairy(1000, 120, 100, 150, 5)
21 t_puts("T1 solve 12% fat /10% MSNF /15% sugar: cream=" as *u8); t_putn(s.cream_g); t_puts(" SMP=" as *u8); t_putn(s.smp_g); t_puts(" sugar=" as *u8); t_putn(s.sugar_g); t_puts(" stab=" as *u8); t_putn(s.stab_g); t_puts(" added-water=" as *u8); t_putn(s.added_water_g); t_puts(" feasible=" as *u8); t_putn(s.feasible); t_puts(": " as *u8)
22 var ok1: i64 = 1
23 if s.feasible != 1 { ok1 = 0 }
24 if s.cream_g != 300 { ok1 = 0 }
25 if s.sugar_g != 150 { ok1 = 0 }
26 let sum1: i64 = s.cream_g + s.smp_g + s.sugar_g + s.stab_g + s.added_water_g
27 if sum1 != 1000 { ok1 = 0 }
28 if ok1 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
29
30 // --- T2 ROUND TRIP: the solved mix analyses back to the targets ---
31 total = total + 1
32 let m: *NxIceMix = icb_solution_to_mix(s, 1000, IC_MW_SUCROSE_Q2, 100, 1100)
33 let f: i64 = ic_fat_permil(m)
34 let ms: i64 = ic_msnf_permil(m)
35 let su: i64 = ic_sugar_permil(m)
36 t_puts("T2 analyser reports fat=" as *u8); t_putn(f); t_puts(" MSNF=" as *u8); t_putn(ms); t_puts(" sugar=" as *u8); t_putn(su); t_puts(" permil, targets 120/100/150 tol 2: " as *u8)
37 var ok2: i64 = 1
38 var df: i64 = f - 120
39 if df < 0 { df = 0 - df }
40 var dm: i64 = ms - 100
41 if dm < 0 { dm = 0 - dm }
42 var ds: i64 = su - 150
43 if ds < 0 { ds = 0 - ds }
44 if df > 2 { ok2 = 0 }
45 if dm > 2 { ok2 = 0 }
46 if ds > 2 { ok2 = 0 }
47 if ok2 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
48
49 // --- T3 ADDED water is not TOTAL water (cream and powder carry their own) ---
50 total = total + 1
51 let total_water: i64 = ic_water_g(m)
52 t_puts("T3 added water=" as *u8); t_putn(s.added_water_g); t_puts(" but mix total water=" as *u8); t_putn(total_water); t_puts(" (must differ, cream/SMP carry water): " as *u8)
53 if total_water > s.added_water_g { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
54
55 // --- T4 the solved mix is legally ice cream and not sandy ---
56 total = total + 1
57 let fda: i64 = ic_fda_is_ice_cream(m, 100)
58 let sandy: i64 = ic_sandy_risk(m)
59 t_puts("T4 solved mix FDA ice cream=" as *u8); t_putn(fda); t_puts(" sandy risk=" as *u8); t_putn(sandy); t_puts(" want 1/0: " as *u8)
60 if fda == 1 { if sandy == 0 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
61
62 // --- T5 REFUSAL: cream alone overshoots MSNF -> no solution ---
63 // 30% fat needs 750 g cream, which drags in 40 g MSNF; ask for 20 g and
64 // the target pair is unreachable.
65 total = total + 1
66 let bad1: *NxMixSolution = icb_solve_dairy(1000, 300, 20, 150, 5)
67 let bad2: *NxMixSolution = icb_solve_dairy(1000, 400, 100, 400, 5)
68 let bad3: *NxMixSolution = icb_solve_dairy(0, 120, 100, 150, 5)
69 t_puts("T5 MSNF-undershoot=" as *u8); t_putn(bad1.feasible); t_puts(" no-water-left=" as *u8); t_putn(bad2.feasible); t_puts(" zero-batch=" as *u8); t_putn(bad3.feasible); t_puts(" (all 0): " as *u8)
70 var ok5: i64 = 1
71 if bad1.feasible != 0 { ok5 = 0 }
72 if bad2.feasible != 0 { ok5 = 0 }
73 if bad3.feasible != 0 { ok5 = 0 }
74 if ok5 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
75
76 // --- T6 sugar blend hits a PAC target, verified by the analyser ---
77 total = total + 1
78 let g_suc: i64 = icb_solve_sugar_blend(150, 1000, 200, IC_MW_SUCROSE_Q2, IC_MW_DEXTROSE_Q2)
79 let g_dex: i64 = 150 - g_suc
80 let pac_s: i64 = icb_pac_index_of(g_suc, 1000, IC_MW_SUCROSE_Q2)
81 let pac_d: i64 = icb_pac_index_of(g_dex, 1000, IC_MW_DEXTROSE_Q2)
82 let pac_tot: i64 = pac_s + pac_d
83 t_puts("T6 for PAC 200: sucrose=" as *u8); t_putn(g_suc); t_puts("g dextrose=" as *u8); t_putn(g_dex); t_puts("g -> achieved PAC=" as *u8); t_putn(pac_tot); t_puts(" tol 3: " as *u8)
84 var d6: i64 = pac_tot - 200
85 if d6 < 0 { d6 = 0 - d6 }
86 if d6 <= 3 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
87
88 // --- T7 blend endpoints: all-sucrose and all-dextrose ---
89 total = total + 1
90 let all_suc: i64 = icb_solve_sugar_blend(150, 1000, 150, IC_MW_SUCROSE_Q2, IC_MW_DEXTROSE_Q2)
91 let all_dex: i64 = icb_solve_sugar_blend(150, 1000, 285, IC_MW_SUCROSE_Q2, IC_MW_DEXTROSE_Q2)
92 t_puts("T7 PAC150 -> sucrose=" as *u8); t_putn(all_suc); t_puts(" (all 150) PAC285 -> sucrose=" as *u8); t_putn(all_dex); t_puts(" (all 0): " as *u8)
93 var ok7: i64 = 1
94 if all_suc != 150 { ok7 = 0 }
95 if all_dex != 0 { ok7 = 0 }
96 if ok7 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
97
98 // --- T8 REFUSAL: a PAC target the pair cannot reach ---
99 total = total + 1
100 let un_lo: i64 = icb_solve_sugar_blend(150, 1000, 100, IC_MW_SUCROSE_Q2, IC_MW_DEXTROSE_Q2)
101 let un_hi: i64 = icb_solve_sugar_blend(150, 1000, 400, IC_MW_SUCROSE_Q2, IC_MW_DEXTROSE_Q2)
102 let un_zero: i64 = icb_solve_sugar_blend(0, 1000, 200, IC_MW_SUCROSE_Q2, IC_MW_DEXTROSE_Q2)
103 t_puts("T8 below-reach=" as *u8); t_putn(un_lo); t_puts(" above-reach=" as *u8); t_putn(un_hi); t_puts(" no-sugar=" as *u8); t_putn(un_zero); t_puts(" (all -1, not clamped): " as *u8)
104 var ok8: i64 = 1
105 if un_lo != ICB_INFEASIBLE { ok8 = 0 }
106 if un_hi != ICB_INFEASIBLE { ok8 = 0 }
107 if un_zero != ICB_INFEASIBLE { ok8 = 0 }
108 if ok8 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
109
110 // --- T9 THE DEXTROSE LEVER, QUANTIFIED: same target, half the sugar ---
111 total = total + 1
112 let need_suc: i64 = icb_sugar_for_serve_temp(100, 600, IC_MW_SUCROSE_Q2, 0 - 12000, 720)
113 let need_dex: i64 = icb_sugar_for_serve_temp(100, 600, IC_MW_DEXTROSE_Q2, 0 - 12000, 720)
114 t_puts("T9 to scoop at -12 C with 72% ice: sucrose=" as *u8); t_putn(need_suc); t_puts("g vs dextrose=" as *u8); t_putn(need_dex); t_puts("g per kg: " as *u8)
115 var ok9: i64 = 1
116 if need_suc < 230 { ok9 = 0 }
117 if need_suc > 265 { ok9 = 0 }
118 if need_dex < 120 { ok9 = 0 }
119 if need_dex > 140 { ok9 = 0 }
120 if need_dex >= need_suc { ok9 = 0 }
121 if ok9 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
122
123 // --- T10 ROUND TRIP through the freezing curve: solve -> analyse ---
124 total = total + 1
125 let rt: *NxIceMix = nx_ice_mix_new()
126 rt.total_g = 1000
127 rt.sugar_mw_q2 = IC_MW_DEXTROSE_Q2
128 rt.sugar_pod = 70
129 rt.msnf_g = 100
130 rt.sugar_g = need_dex
131 let rest: i64 = 1000 - 600 - 100 - need_dex
132 rt.fat_g = rest
133 rt.other_solids_g = 0
134 let got_temp: i64 = ic_serve_temp_for_frozen_permil(rt, 720)
135 t_puts("T10 mix built from the solver serves 72% ice at " as *u8); t_putn(got_temp); t_puts(" milli-C, asked -12000 tol 200: " as *u8)
136 var d10: i64 = got_temp + 12000
137 if d10 < 0 { d10 = 0 - d10 }
138 if d10 <= 200 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
139
140 // --- T11 colder target needs MORE sweetener (monotone, sane direction) ---
141 total = total + 1
142 let n8: i64 = icb_sugar_for_serve_temp(100, 600, IC_MW_SUCROSE_Q2, 0 - 8000, 720)
143 let n12: i64 = icb_sugar_for_serve_temp(100, 600, IC_MW_SUCROSE_Q2, 0 - 12000, 720)
144 let n16: i64 = icb_sugar_for_serve_temp(100, 600, IC_MW_SUCROSE_Q2, 0 - 16000, 720)
145 t_puts("T11 sucrose for -8/-12/-16 C = " as *u8); t_putn(n8); t_puts("/" as *u8); t_putn(n12); t_puts("/" as *u8); t_putn(n16); t_puts(" (increasing): " as *u8)
146 var ok11: i64 = 1
147 if n12 <= n8 { ok11 = 0 }
148 if n16 <= n12 { ok11 = 0 }
149 if ok11 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
150
151 // --- T12 REFUSAL: milk solids alone already overshoot the target ---
152 // A warm target with heavy MSNF needs no sugar at all -- and saying
153 // "0 g" would be a lie, because the mix is already past the target.
154 total = total + 1
155 let over: i64 = icb_sugar_for_serve_temp(200, 300, IC_MW_SUCROSE_Q2, 0 - 3000, 720)
156 let nowater: i64 = icb_sugar_for_serve_temp(100, 0, IC_MW_SUCROSE_Q2, 0 - 12000, 720)
157 let badfrac: i64 = icb_sugar_for_serve_temp(100, 600, IC_MW_SUCROSE_Q2, 0 - 12000, 1000)
158 t_puts("T12 MSNF-overshoot=" as *u8); t_putn(over); t_puts(" no-water=" as *u8); t_putn(nowater); t_puts(" 100%-ice-target=" as *u8); t_putn(badfrac); t_puts(" (all -1): " as *u8)
159 var ok12: i64 = 1
160 if over != ICB_INFEASIBLE { ok12 = 0 }
161 if nowater != ICB_INFEASIBLE { ok12 = 0 }
162 if badfrac != ICB_INFEASIBLE { ok12 = 0 }
163 if ok12 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
164
165 // --- T13 a gelato-style solve also round-trips ---
166 total = total + 1
167 let g: *NxMixSolution = icb_solve_dairy(1000, 70, 100, 180, 4)
168 let gm: *NxIceMix = icb_solution_to_mix(g, 1000, IC_MW_SUCROSE_Q2, 100, 1100)
169 let gf: i64 = ic_fat_permil(gm)
170 let gc: i64 = ic_classify(gm, 30)
171 t_puts("T13 gelato solve fat=" as *u8); t_putn(gf); t_puts(" permil (target 70) class@30% overrun=" as *u8); t_putn(gc); t_puts(" want 2=GELATO: " as *u8)
172 var dg: i64 = gf - 70
173 if dg < 0 { dg = 0 - dg }
174 var ok13: i64 = 1
175 if g.feasible != 1 { ok13 = 0 }
176 if dg > 2 { ok13 = 0 }
177 if gc != IC_STYLE_GELATO { ok13 = 0 }
178 if ok13 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
179
180 // --- T14 an infeasible solution yields a mix with no solids, and the
181 // analyser refuses it rather than reporting a plausible one ---
182 total = total + 1
183 let im: *NxIceMix = icb_solution_to_mix(bad1, 1000, IC_MW_SUCROSE_Q2, 100, 1100)
184 let isolids: i64 = ic_total_solids_g(im)
185 let ifda: i64 = ic_fda_is_ice_cream(im, 100)
186 t_puts("T14 infeasible->mix solids=" as *u8); t_putn(isolids); t_puts(" FDA ice cream=" as *u8); t_putn(ifda); t_puts(" want 0/0: " as *u8)
187 if isolids == 0 { if ifda == 0 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) } } else { t_puts("FAIL\n" as *u8) }
188
189 t_puts("ICECREAM-BALANCE-GATE passed " as *u8); t_putn(pass); t_puts("/" as *u8); t_putn(total)
190 if pass == total { t_puts(" verdict=GREEN\n" as *u8); sys_exit(0); return 0 }
191 t_puts(" verdict=RED\n" as *u8); sys_exit(1); return 1
192}