nx_icecream_bench.nx source
↩ module page · 538 lines · 22866 B
1// nx_icecream_bench.nx -- THE RULER. Score a frozen dessert against Ben &
2// Jerry's Vanilla, the declared SOTA reference for this lane.
3//
4// ===== WHY A RULER COMES FIRST ====================================
5//
6// "Exceed Ben & Jerry's" is not a formulation instruction until something can
7// SCORE both products on the same axes. This organ is that scorer. It holds
8// no ambition and proposes no recipe: it computes what a label implies, what
9// the law actually permits, and where the reference product is structurally
10// weak. The formulator is a separate organ and must be argued WITH this one,
11// never instead of it.
12//
13// ===== THE REFERENCE, FROM THE ACTUAL LABEL =======================
14//
15// Ben & Jerry's Vanilla, US pint, UPC 076840400058. Ingredient declaration
16// in order: Cream, Skim Milk, Liquid Sugar (Sugar, Water), Water, Egg Yolks,
17// Sugar, Guar Gum, Vanilla Extract, Vanilla Beans, Carrageenan.
18//
19// Nutrition panel, 2/3 cup (143 g) serving: 330 kcal, 21 g fat (14 g sat),
20// 29 g carbohydrate (28 g sugars), 6 g protein, 95 mg cholesterol.
21// Pint = 16 fl oz = 473 mL, 3 servings => 429 g net. The net weight is
22// DERIVED (US frozen dessert is sold by volume; weight is not declared), and
23// bench_net_weight_is_derived says so.
24//
25// **TWO FACTS IN THAT DECLARATION DECIDE THE WHOLE COMPETITIVE PICTURE:**
26// 1. The only hydrocolloids are GUAR (primary) and CARRAGEENAN (secondary).
27// There is no locust bean gum -- which matters because this lane's own
28// gap ledger had named LBG as "how industry slows recrystallisation".
29// The actual competitor does not use it.
30// 2. There is NO ADDED EMULSIFIER. Egg yolk is the entire emulsifier
31// system -- no mono- and diglycerides, no polysorbate 80. That is the
32// exploitable weakness, and it is the reason this organ models meltdown.
33//
34// ===== THE LEGAL FINDING THIS ORGAN CARRIES =======================
35//
36// 21 CFR 135.110 imposes FOUR floors on anything labelled "ice cream":
37// >=10% milkfat, >=20% total milk solids, >=4.5 lb/gal finished weight, and
38// >=1.6 lb TOTAL FOOD SOLIDS per gallon. nx_icecream enforces the first
39// three. It declares IC_FDA_MIN_FOOD_SOLIDS_Q2 = 160 for the fourth and
40// then never reads it -- a dead constant, so the fourth floor is documented
41// and unenforced.
42//
43// **THE TWO WEIGHT-SIDE FLOORS BIND IN DIFFERENT REGIMES.** The air ceiling
44// is set by whichever floor is reached first, and which one that is depends
45// on total solids:
46//
47// rho_min_weight = 4.5 lb/gal (constant)
48// rho_min_solids = 1.6 lb/gal / TS_fraction (rises as solids fall)
49//
50// They cross at TS = 355 permil. ABOVE that the weight floor binds and the
51// shipped one-floor law is right by luck. BELOW it the SOLIDS floor binds
52// and the shipped law over-permits -- it licenses air that would put the
53// product outside the standard of identity. bench_shipped_law_error_permil
54// measures the gap rather than asserting it.
55//
56// ★The direction is the unsafe one: it permits MORE air than the law allows,
57// so the failure mode is a mislabelled product, not a conservative one.
58//
59// All INTEGER. _q2 = x100, _q3 = x1000, _permil = x1000 (fraction).
60//
61// Grounding (cited):
62// fda_21cfr135_110_ice_cream_standard_of_identity (four floors)
63// fda_21cfr101_12_racc_frozen_desserts (2/3 cup basis)
64// goff_guelph_overrun_and_mix_density (specific volumes)
65// liu_sala_scholten_2023_food_hydrocolloids_138 (meltdown vs destab)
66// herald_2008_egg_yolk_replacement_french_vanilla (yolk melts fastest)
67// benjerry_vanilla_label_upc_076840400058 (the reference)
68//
69// genealogy_id: frozen_dessert_science + nishi_food_science_suite
70
71import "nx_syscalls.nx"
72const BENCH_MAGIC_10000: i64 = 10000
73const BENCH_MAGIC_1000000: i64 = 1000000
74
75// ===== Verdicts =======================================================
76
77const BENCH_INVALID: i64 = 0 - 1
78
79// Which of the two weight-side floors is binding.
80const BENCH_FLOOR_NONE: i64 = 0
81const BENCH_FLOOR_WEIGHT: i64 = 1
82const BENCH_FLOOR_SOLIDS: i64 = 2
83
84// Emulsifier systems, in ascending order of fat-destabilising power.
85const BENCH_EMUL_NONE: i64 = 0
86const BENCH_EMUL_EGG_YOLK: i64 = 1
87const BENCH_EMUL_MONO_DI: i64 = 2
88const BENCH_EMUL_MONO_DI_PS80: i64 = 3
89
90// ===== Physical + legal constants =====================================
91
92// Partial specific volumes, mL/g x1000 (Guelph mix-density model). Fat is
93// less dense than water; serum solids are much denser.
94const BENCH_SV_FAT_Q3: i64 = 1075
95const BENCH_SV_SNF_Q3: i64 = 630
96const BENCH_SV_WATER_Q3: i64 = 1000
97
98const BENCH_ML_PER_GAL: i64 = 3785
99const BENCH_G_PER_LB: i64 = 454
100
101const BENCH_FDA_MIN_LB_PER_GAL_Q2: i64 = 450 // 4.5 lb/gal finished weight
102const BENCH_FDA_MIN_FOOD_SOLIDS_Q2: i64 = 160 // 1.6 lb food solids/gal
103const BENCH_FDA_MIN_MILKFAT_PERMIL: i64 = 100
104const BENCH_FDA_MIN_MILK_SOL_PERMIL: i64 = 200
105
106// 2/3 cup RACC for frozen desserts, in TENTHS of a mL: 157.725 mL.
107const BENCH_RACC_TENTH_ML: i64 = 1577
108
109// ===== A product under test ==========================================
110//
111// Everything here is readable off a US nutrition panel plus the pack volume.
112// serving_ml_tenth is the RACC in tenths of a mL.
113
114struct BenchProduct {
115 serving_g: i64,
116 serving_ml_tenth: i64,
117 fat_g: i64,
118 carb_g: i64,
119 protein_g: i64,
120 ash_permil: i64, // of the serving; ~8 permil for dairy
121 emulsifier: i64,
122 net_weight_derived: i64,
123}
124
125func bench_product_new() -> *BenchProduct {
126 let p: *BenchProduct = sys_mmap(96) as *BenchProduct
127 p.serving_g = 0
128 p.serving_ml_tenth = BENCH_RACC_TENTH_ML
129 p.fat_g = 0
130 p.carb_g = 0
131 p.protein_g = 0
132 p.ash_permil = 8
133 p.emulsifier = BENCH_EMUL_NONE
134 p.net_weight_derived = 0
135 return p
136}
137
138// Ben & Jerry's Vanilla -- the declared SOTA reference. Every field is off
139// the verified label; net weight is derived, and the flag records that.
140func bench_ref_benjerry() -> *BenchProduct {
141 let p: *BenchProduct = bench_product_new()
142 p.serving_g = 143
143 p.serving_ml_tenth = BENCH_RACC_TENTH_ML
144 p.fat_g = 21
145 p.carb_g = 29
146 p.protein_g = 6
147 p.emulsifier = BENCH_EMUL_EGG_YOLK
148 p.net_weight_derived = 1
149 return p
150}
151
152// Haagen-Dazs Vanilla -- the other super-premium anchor. Ingredients are
153// cream, skim milk, cane sugar, egg yolks, vanilla extract: NO stabiliser
154// and no added emulsifier at all.
155//
156// ⚠⚠ONLY THE SERVING WEIGHT IS VERIFIED FOR THIS PRODUCT. Its nutrition
157// panel was not retrieved, so there is no BenchProduct for it -- encoding a
158// guessed panel would let bench_overrun_of() return a confident number built
159// on invented macros, which is the exact failure this lane exists to refuse.
160// What IS verified is the serving weight at the same 2/3 cup RACC, and that
161// alone supports the only claim worth making here.
162const BENCH_HD_SERVING_G: i64 = 128
163
164func bench_hd_density_q3() -> i64 {
165 return BENCH_HD_SERVING_G * BENCH_MAGIC_10000 / BENCH_RACC_TENTH_ML
166}
167
168func bench_hd_composition_is_known() -> i64 { return 0 }
169
170// ★THE ASSUMPTION-FREE COMPARISON. Both panels declare the same 2/3 cup
171// RACC, so their serving weights are weights of equal volumes: the density
172// ratio needs no mix model, no overrun derivation, and no composition at
173// all. Returns permil by which the reference is denser than Haagen-Dazs.
174func bench_denser_than_hd_permil(p: *BenchProduct) -> i64 {
175 let a: i64 = bench_density_q3(p)
176 let b: i64 = bench_hd_density_q3()
177 if a == BENCH_INVALID { return BENCH_INVALID }
178 if b <= 0 { return BENCH_INVALID }
179 return a * 1000 / b - 1000
180}
181
182// ===== Composition off the panel =====================================
183
184// Finished product density, g/mL x1000. This is the ONE measured quantity
185// the whole overrun derivation rests on, and it needs no model.
186func bench_density_q3(p: *BenchProduct) -> i64 {
187 if p.serving_ml_tenth <= 0 { return BENCH_INVALID }
188 if p.serving_g <= 0 { return BENCH_INVALID }
189 return p.serving_g * BENCH_MAGIC_10000 / p.serving_ml_tenth
190}
191
192func bench_fat_permil(p: *BenchProduct) -> i64 {
193 if p.serving_g <= 0 { return BENCH_INVALID }
194 return p.fat_g * 1000 / p.serving_g
195}
196
197// Total solids = fat + carbohydrate + protein + ash. Water is the rest.
198func bench_total_solids_permil(p: *BenchProduct) -> i64 {
199 if p.serving_g <= 0 { return BENCH_INVALID }
200 let macro: i64 = p.fat_g + p.carb_g + p.protein_g
201 return macro * 1000 / p.serving_g + p.ash_permil
202}
203
204func bench_water_permil(p: *BenchProduct) -> i64 {
205 let ts: i64 = bench_total_solids_permil(p)
206 if ts == BENCH_INVALID { return BENCH_INVALID }
207 return 1000 - ts
208}
209
210// Milk-solids-not-fat, inferred from protein. Milk protein is 36 permil of
211// MSNF, so MSNF = protein / 0.36. INFERRED, not declared.
212func bench_msnf_permil(p: *BenchProduct) -> i64 {
213 if p.serving_g <= 0 { return BENCH_INVALID }
214 let prot: i64 = p.protein_g * 1000 / p.serving_g
215 return prot * 1000 / 360
216}
217
218func bench_total_milk_solids_permil(p: *BenchProduct) -> i64 {
219 let f: i64 = bench_fat_permil(p)
220 let m: i64 = bench_msnf_permil(p)
221 if f == BENCH_INVALID { return BENCH_INVALID }
222 if m == BENCH_INVALID { return BENCH_INVALID }
223 return f + m
224}
225
226// ===== Mix density, and therefore overrun ============================
227//
228// Specific volume is additive over the components, so the UNAERATED mix
229// density follows from composition alone. Guelph's published model.
230func bench_mix_density_q3(fat_permil: i64, ts_permil: i64) -> i64 {
231 if fat_permil < 0 { return BENCH_INVALID }
232 if ts_permil <= fat_permil { return BENCH_INVALID }
233 if ts_permil >= 1000 { return BENCH_INVALID }
234 let snf: i64 = ts_permil - fat_permil
235 let water: i64 = 1000 - ts_permil
236 let vfat: i64 = fat_permil * BENCH_SV_FAT_Q3
237 let vsnf: i64 = snf * BENCH_SV_SNF_Q3
238 let vwat: i64 = water * BENCH_SV_WATER_Q3
239 let v_q3: i64 = (vfat + vsnf + vwat) / 1000
240 if v_q3 <= 0 { return BENCH_INVALID }
241 return BENCH_MAGIC_1000000 / v_q3
242}
243
244func bench_mix_density_of(p: *BenchProduct) -> i64 {
245 let f: i64 = bench_fat_permil(p)
246 let t: i64 = bench_total_solids_permil(p)
247 if f == BENCH_INVALID { return BENCH_INVALID }
248 if t == BENCH_INVALID { return BENCH_INVALID }
249 return bench_mix_density_q3(f, t)
250}
251
252// Overrun in permil. 100 permil = 10% more volume than the mix.
253func bench_overrun_permil(mix_q3: i64, product_q3: i64) -> i64 {
254 if product_q3 <= 0 { return BENCH_INVALID }
255 if mix_q3 <= 0 { return BENCH_INVALID }
256 if mix_q3 < product_q3 { return BENCH_INVALID }
257 return mix_q3 * 1000 / product_q3 - 1000
258}
259
260func bench_overrun_of(p: *BenchProduct) -> i64 {
261 let mix: i64 = bench_mix_density_of(p)
262 let prod: i64 = bench_density_q3(p)
263 if mix == BENCH_INVALID { return BENCH_INVALID }
264 if prod == BENCH_INVALID { return BENCH_INVALID }
265 return bench_overrun_permil(mix, prod)
266}
267
268// ★AIR FRACTION IS NOT OVERRUN, AND CONSUMER PRESS ROUTINELY CONFLATES THEM.
269// Overrun is air as a fraction of the MIX; air fraction is air as a fraction
270// of the FINISHED product. 20% air is 25% overrun. Encoding both, with a
271// function that proves they differ, stops the 25% error being imported.
272func bench_air_permil_from_overrun(overrun_permil: i64) -> i64 {
273 if overrun_permil < 0 { return BENCH_INVALID }
274 return overrun_permil * 1000 / (1000 + overrun_permil)
275}
276
277func bench_air_equals_overrun(overrun_permil: i64) -> i64 {
278 if overrun_permil <= 0 { return 1 }
279 return 0
280}
281
282// The overrun figure is DERIVED from a modelled mix density, never published
283// by either brand. Callers must be able to see that.
284func bench_overrun_is_derived() -> i64 { return 1 }
285func bench_net_weight_is_derived(p: *BenchProduct) -> i64 { return p.net_weight_derived }
286
287// Sensitivity band: the mix-density model is the weak input, so report what
288// the answer becomes across a plausible range of mix densities rather than
289// pretending to a point estimate.
290func bench_overrun_band(product_q3: i64, mix_lo_q3: i64, mix_hi_q3: i64, out: *i64) -> i64 {
291 if product_q3 <= 0 { return BENCH_INVALID }
292 if mix_lo_q3 > mix_hi_q3 { return BENCH_INVALID }
293 out[0] = bench_overrun_permil(mix_lo_q3, product_q3)
294 out[1] = bench_overrun_permil(mix_hi_q3, product_q3)
295 return 0
296}
297
298// ===== THE TWO LEGAL FLOORS ==========================================
299
300// Minimum finished density from the 4.5 lb/gal WEIGHT floor. Constant.
301func bench_min_density_weight_q3() -> i64 {
302 let num: i64 = BENCH_FDA_MIN_LB_PER_GAL_Q2 * BENCH_G_PER_LB * 1000
303 let den: i64 = 100 * BENCH_ML_PER_GAL
304 return num / den
305}
306
307// Minimum finished density from the 1.6 lb-solids/gal FOOD SOLIDS floor.
308// Rises without limit as total solids fall -- which is exactly why it can
309// overtake the weight floor.
310func bench_min_density_solids_q3(ts_permil: i64) -> i64 {
311 if ts_permil <= 0 { return BENCH_INVALID }
312 let num: i64 = BENCH_FDA_MIN_FOOD_SOLIDS_Q2 * BENCH_G_PER_LB * 1000 * 1000
313 let den: i64 = 100 * BENCH_ML_PER_GAL * ts_permil
314 if den <= 0 { return BENCH_INVALID }
315 return num / den
316}
317
318func bench_binding_floor(ts_permil: i64) -> i64 {
319 let w: i64 = bench_min_density_weight_q3()
320 let s: i64 = bench_min_density_solids_q3(ts_permil)
321 if s == BENCH_INVALID { return BENCH_FLOOR_NONE }
322 if s > w { return BENCH_FLOOR_SOLIDS }
323 return BENCH_FLOOR_WEIGHT
324}
325
326func bench_min_density_q3(ts_permil: i64) -> i64 {
327 let w: i64 = bench_min_density_weight_q3()
328 let s: i64 = bench_min_density_solids_q3(ts_permil)
329 if s == BENCH_INVALID { return BENCH_INVALID }
330 if s > w { return s }
331 return w
332}
333
334// The total-solids level at which the two floors cross. Computed from the
335// two constants, so it moves if either does.
336func bench_floor_crossover_permil() -> i64 {
337 let w: i64 = bench_min_density_weight_q3()
338 if w <= 0 { return BENCH_INVALID }
339 let num: i64 = BENCH_FDA_MIN_FOOD_SOLIDS_Q2 * BENCH_G_PER_LB * 1000 * 1000
340 let den: i64 = 100 * BENCH_ML_PER_GAL * w
341 if den <= 0 { return BENCH_INVALID }
342 return num / den
343}
344
345// ★THE CORRECTED LAW: the air ceiling honours BOTH floors.
346// !!ROUNDING ORDER IS A SAFETY PROPERTY HERE, AND THE FIRST VERSION OF THIS
347// FUNCTION GOT IT WRONG. It called bench_min_density_q3 and divided by the
348// result -- but that floor is 539.76 g/L and integer division had already
349// truncated it to 539. Dividing by a floor that has been rounded DOWN
350// permits MORE air, so truncating a legal minimum loosens the very
351// constraint it encodes. The error was ~1 percentage point of overrun, in
352// the direction that licenses a non-compliant product.
353//
354// The fix is to never materialise the floor: fold it into one expression so
355// the only rounding is the final one. The floor functions are retained for
356// reporting, where a tenth of a g/L does not decide compliance.
357//
358// **A ROUNDED-DOWN DIVISOR IS A RELAXED CONSTRAINT WHEN THE DIVISOR IS A
359// LEGAL FLOOR.
360
361// Air ceiling from the 4.5 lb/gal weight floor alone, permil, exact.
362func bench_ceiling_weight_permil(mix_q3: i64) -> i64 {
363 if mix_q3 <= 0 { return BENCH_INVALID }
364 let num: i64 = mix_q3 * 100 * BENCH_ML_PER_GAL
365 let den: i64 = BENCH_FDA_MIN_LB_PER_GAL_Q2 * BENCH_G_PER_LB
366 if den <= 0 { return BENCH_INVALID }
367 return num / den - 1000
368}
369
370// Air ceiling from the 1.6 lb-solids/gal floor alone, permil, exact.
371func bench_ceiling_solids_permil(mix_q3: i64, ts_permil: i64) -> i64 {
372 if mix_q3 <= 0 { return BENCH_INVALID }
373 if ts_permil <= 0 { return BENCH_INVALID }
374 let num: i64 = mix_q3 * 100 * BENCH_ML_PER_GAL * ts_permil
375 let den: i64 = BENCH_FDA_MIN_FOOD_SOLIDS_Q2 * BENCH_G_PER_LB * 1000
376 if den <= 0 { return BENCH_INVALID }
377 return num / den - 1000
378}
379
380// THE CORRECTED LAW: the binding ceiling is the LOWER of the two.
381func bench_max_overrun_permil(mix_q3: i64, ts_permil: i64) -> i64 {
382 let w: i64 = bench_ceiling_weight_permil(mix_q3)
383 let s: i64 = bench_ceiling_solids_permil(mix_q3, ts_permil)
384 if w == BENCH_INVALID { return BENCH_INVALID }
385 if s == BENCH_INVALID { return BENCH_INVALID }
386 if s < w { return s }
387 return w
388}
389
390// The SHIPPED one-floor law, reproduced here so the defect can be measured
391// rather than described. Weight floor only -- the food-solids floor absent.
392func bench_max_overrun_weight_only_permil(mix_q3: i64) -> i64 {
393 return bench_ceiling_weight_permil(mix_q3)
394}
395
396// How much MORE air the one-floor law permits than the law allows. Zero
397// above the crossover; positive below it.
398func bench_shipped_law_error_permil(mix_q3: i64, ts_permil: i64) -> i64 {
399 let correct: i64 = bench_max_overrun_permil(mix_q3, ts_permil)
400 let shipped: i64 = bench_max_overrun_weight_only_permil(mix_q3)
401 if correct == BENCH_INVALID { return BENCH_INVALID }
402 if shipped == BENCH_INVALID { return BENCH_INVALID }
403 return shipped - correct
404}
405
406// ★The error direction is what decides whether this is a bug or a rounding
407// choice. It over-permits, so it can license a mislabelled product.
408func bench_shipped_law_over_permits(mix_q3: i64, ts_permil: i64) -> i64 {
409 let e: i64 = bench_shipped_law_error_permil(mix_q3, ts_permil)
410 if e == BENCH_INVALID { return 0 }
411 if e > 0 { return 1 }
412 return 0
413}
414
415// ===== Full standard-of-identity check, all FOUR floors ==============
416
417func bench_solids_lb_per_gal_q2(product_q3: i64, ts_permil: i64) -> i64 {
418 if product_q3 <= 0 { return BENCH_INVALID }
419 if ts_permil <= 0 { return BENCH_INVALID }
420 let g_per_gal: i64 = product_q3 * BENCH_ML_PER_GAL / 1000
421 let solids_g: i64 = g_per_gal * ts_permil / 1000
422 return solids_g * 100 / BENCH_G_PER_LB
423}
424
425func bench_weight_lb_per_gal_q2(product_q3: i64) -> i64 {
426 if product_q3 <= 0 { return BENCH_INVALID }
427 let g_per_gal: i64 = product_q3 * BENCH_ML_PER_GAL / 1000
428 return g_per_gal * 100 / BENCH_G_PER_LB
429}
430
431// Returns a bitmask of FAILING clauses, so a caller learns WHICH floor was
432// missed instead of a bare no. 0 = compliant.
433func bench_fda_failures(p: *BenchProduct) -> i64 {
434 let prod: i64 = bench_density_q3(p)
435 let ts: i64 = bench_total_solids_permil(p)
436 let fat: i64 = bench_fat_permil(p)
437 let tms: i64 = bench_total_milk_solids_permil(p)
438 if prod == BENCH_INVALID { return BENCH_INVALID }
439 var bad: i64 = 0
440 if fat < BENCH_FDA_MIN_MILKFAT_PERMIL { bad = bad | 1 }
441 if tms < BENCH_FDA_MIN_MILK_SOL_PERMIL { bad = bad | 2 }
442 if bench_weight_lb_per_gal_q2(prod) < BENCH_FDA_MIN_LB_PER_GAL_Q2 { bad = bad | 4 }
443 if bench_solids_lb_per_gal_q2(prod, ts) < BENCH_FDA_MIN_FOOD_SOLIDS_Q2 { bad = bad | 8 }
444 return bad
445}
446
447func bench_is_ice_cream(p: *BenchProduct) -> i64 {
448 let bad: i64 = bench_fda_failures(p)
449 if bad == 0 { return 1 }
450 return 0
451}
452
453// ===== MELTDOWN: where the reference is beatable =====================
454//
455// Liu, Sala & Scholten 2023 (Food Hydrocolloids 138:108466) held fat content
456// and ice fraction CONSTANT and varied only fat destabilisation, so the
457// meltdown difference is cleanly attributable:
458//
459// low destabilisation (0-4% aggregated): first drip 12-18 min,
460// melt rate 21-23 permil/min,
461// 97-99% melted
462// high destabilisation (72-100%): first drip 18-30 min,
463// melt rate 7-12 permil/min,
464// 41-47% melted
465//
466// Herald et al. 2008 measured egg yolk against replacements in a French
467// vanilla and found yolk gave the FASTEST first drip (5 min vs 15 min).
468// Unilever's own US 8,574,654 B2 states that yolk PLASMA destabilises far
469// more powerfully than whole yolk -- i.e. the patent holder documents whole
470// yolk as the weak option.
471//
472// ★So an egg-yolk-only system sits at the LOW-destabilisation end, and the
473// reference product uses egg yolk alone. That is the gap.
474
475const BENCH_DRIP_LOW_DESTAB_MIN: i64 = 12
476const BENCH_DRIP_HIGH_DESTAB_MIN: i64 = 18
477const BENCH_MELTRATE_LOW_DESTAB_PERMIL: i64 = 22
478const BENCH_MELTRATE_HIGH_DESTAB_PERMIL: i64 = 10
479
480// Fat destabilisation band (permil) by emulsifier system. These are class
481// bands read off the cited work, not a continuous dose model.
482func bench_destab_permil(emul: i64) -> i64 {
483 if emul == BENCH_EMUL_NONE { return 20 }
484 if emul == BENCH_EMUL_EGG_YOLK { return 60 }
485 if emul == BENCH_EMUL_MONO_DI { return 400 }
486 if emul == BENCH_EMUL_MONO_DI_PS80 { return 800 }
487 return BENCH_INVALID
488}
489
490func bench_first_drip_min(emul: i64) -> i64 {
491 let d: i64 = bench_destab_permil(emul)
492 if d == BENCH_INVALID { return BENCH_INVALID }
493 if d >= 500 { return BENCH_DRIP_HIGH_DESTAB_MIN }
494 return BENCH_DRIP_LOW_DESTAB_MIN
495}
496
497func bench_melt_rate_permil_min(emul: i64) -> i64 {
498 let d: i64 = bench_destab_permil(emul)
499 if d == BENCH_INVALID { return BENCH_INVALID }
500 if d >= 500 { return BENCH_MELTRATE_HIGH_DESTAB_PERMIL }
501 return BENCH_MELTRATE_LOW_DESTAB_PERMIL
502}
503
504// Is the reference beatable on meltdown, and by how much? Returns the ratio
505// of melt rates x1000: >1000 means the challenger holds shape longer.
506func bench_meltdown_advantage_q3(ref_emul: i64, challenger_emul: i64) -> i64 {
507 let r: i64 = bench_melt_rate_permil_min(ref_emul)
508 let c: i64 = bench_melt_rate_permil_min(challenger_emul)
509 if r == BENCH_INVALID { return BENCH_INVALID }
510 if c == BENCH_INVALID { return BENCH_INVALID }
511 if c <= 0 { return BENCH_INVALID }
512 return r * 1000 / c
513}
514
515// ⚠THE CHAIN IS TWO CITED RESULTS, NOT A MEASUREMENT OF THE REFERENCE.
516// We have not put a Ben & Jerry's pint on a meltdown rig. The claim is:
517// yolk-only implies low destabilisation (Herald; Unilever patent), and low
518// destabilisation implies fast meltdown (Liu, measured). Sound, and still
519// INFERRED -- so the organ says so rather than letting a reader assume it
520// was weighed.
521func bench_meltdown_claim_is_inferred() -> i64 { return 1 }
522func bench_reference_was_physically_measured() -> i64 { return 0 }
523
524// ===== Stabiliser reality check ======================================
525//
526// The reference uses guar + carrageenan. Thaiudom & Goff 2003 rank
527// thermodynamic incompatibility with casein as xanthan > guar > LBG, so guar
528// is the second-worst offender for depletion flocculation -- which is
529// precisely why carrageenan appears on that label at all. Carrageenan is
530// not a thickener here; it is the counter-agent for the primary gum's own
531// side effect.
532func bench_needs_secondary_colloid(primary_gum_permil: i64) -> i64 {
533 if primary_gum_permil <= 0 { return 0 }
534 return 1
535}
536
537func bench_reference_uses_lbg() -> i64 { return 0 }
538func bench_reference_uses_added_emulsifier() -> i64 { return 0 }