nx_food_chem_react.nx source
↩ module page · 131 lines · 5232 B
1// nx_food_chem_react.nx -- FOOD-SCIENCE SUITE / COOKING CHEMISTRY rung.
2// Predicts which browning/transformation reaction occurs from the actual
3// conditions (temperature, pH, reactants, oxygen) -- Maillard vs
4// caramelization vs enzymatic browning -- and gives the real thresholds,
5// where a recipe just says "cook until golden".
6//
7// All INTEGER threshold rules (temp in C, pH x1000). Grounded chemistry:
8// - Maillard: amino acids (protein) + REDUCING sugars, dry heat >= ~140 C
9// (below that little browning); alkaline pH accelerates it.
10// - Caramelization: sugar pyrolysis, NO protein needed; per-sugar onset
11// (fructose ~110, glucose/sucrose ~160, maltose ~180 C).
12// - Enzymatic browning: polyphenol oxidase (PPO) on cut produce + O2;
13// stopped by acid (pH<~3), heat (>=70 C denatures PPO), antioxidant
14// (vitamin C), or excluding O2 (submersion).
15//
16// THE exceed: it CLASSIFIES the browning correctly from conditions (a
17// seared steak = Maillard, caramel = caramelization, a cut apple = enzymatic)
18// and prescribes prevention -- generalising beyond a fixed recipe step.
19//
20// Grounding (cited; researcher-groundable):
21// maillard_reaction_amino_reducing_sugar_140C
22// caramelization_onset_temperatures_by_sugar
23// polyphenol_oxidase_enzymatic_browning_control
24//
25// genealogy_id: food_chemistry_reactions + nishi_food_science_suite
26
27import "nx_syscalls.nx"
28const FCR_MAGIC_7000: i64 = 7000
29
30// ===== Thresholds (grounded) ======================================
31
32const FCR_MAILLARD_TEMP_C: i64 = 140 // dry-heat onset
33const FCR_PPO_INHIBIT_PH: i64 = 3000 // pH < 3.0 (acid) stops PPO
34const FCR_PPO_DENATURE_C: i64 = 70 // >= 70 C denatures PPO (blanch)
35const FCR_ALKALINE_PH: i64 = 7000 // pH > 7 accelerates Maillard
36
37// ===== Sealed enums ===============================================
38
39const FCR_NONE: i64 = 0
40const FCR_MAILLARD: i64 = 1
41const FCR_CARAMELIZATION: i64 = 2
42const FCR_ENZYMATIC_BROWNING: i64 = 3
43
44const FCR_SUGAR_FRUCTOSE: i64 = 0
45const FCR_SUGAR_GLUCOSE: i64 = 1
46const FCR_SUGAR_SUCROSE: i64 = 2
47const FCR_SUGAR_MALTOSE: i64 = 3
48
49// ===== Atomic reaction predicates =================================
50
51// Caramelization onset temperature for a sugar (C); unknown -> unreachable.
52func fcr_caramelization_temp(sugar_id: i64) -> i64 {
53 if sugar_id == FCR_SUGAR_FRUCTOSE { return 110 }
54 if sugar_id == FCR_SUGAR_GLUCOSE { return 160 }
55 if sugar_id == FCR_SUGAR_SUCROSE { return 160 }
56 if sugar_id == FCR_SUGAR_MALTOSE { return 180 }
57 return 999
58}
59
60func fcr_maillard(has_protein: i64, has_reducing_sugar: i64, temp_c: i64) -> i64 {
61 if has_protein != 1 { return 0 }
62 if has_reducing_sugar != 1 { return 0 }
63 if temp_c < FCR_MAILLARD_TEMP_C { return 0 }
64 return 1
65}
66
67// Relative Maillard rate: rises with temperature above onset, boosted by
68// alkaline pH (integer, unitless relative score).
69func fcr_maillard_rate(temp_c: i64, ph_milli: i64) -> i64 {
70 if temp_c < FCR_MAILLARD_TEMP_C { return 0 }
71 var rate: i64 = temp_c - FCR_MAILLARD_TEMP_C
72 if ph_milli > FCR_ALKALINE_PH { rate = rate * 2 }
73 return rate
74}
75
76func fcr_caramelizes(sugar_id: i64, temp_c: i64) -> i64 {
77 if temp_c >= fcr_caramelization_temp(sugar_id) { return 1 }
78 return 0
79}
80
81func fcr_enzymatic_browning(has_ppo: i64, o2_present: i64, ph_milli: i64,
82 temp_c: i64, has_antioxidant: i64) -> i64 {
83 if has_ppo != 1 { return 0 }
84 if o2_present != 1 { return 0 }
85 if has_antioxidant == 1 { return 0 }
86 if ph_milli < FCR_PPO_INHIBIT_PH { return 0 } // acid inhibits (lemon)
87 if temp_c >= FCR_PPO_DENATURE_C { return 0 } // heat denatures (blanch)
88 return 1
89}
90
91// ===== Scenario classifier ========================================
92
93// Bundled scenario (>6 loose args would risk the return-register clobber).
94struct NxCookScenario {
95 has_protein: i64,
96 has_reducing_sugar: i64,
97 has_sugar: i64, // any sugar (for caramelization)
98 has_ppo: i64,
99 o2_present: i64,
100 has_antioxidant: i64,
101 ph_milli: i64,
102 temp_c: i64,
103 sugar_id: i64,
104}
105
106func nx_cook_scenario_new() -> *NxCookScenario {
107 let s: *NxCookScenario = (sys_mmap(80)) as *NxCookScenario
108 s.has_protein = 0
109 s.has_reducing_sugar = 0
110 s.has_sugar = 0
111 s.has_ppo = 0
112 s.o2_present = 0
113 s.has_antioxidant = 0
114 s.ph_milli = FCR_MAGIC_7000
115 s.temp_c = 20
116 s.sugar_id = FCR_SUGAR_SUCROSE
117 return s
118}
119
120// The DOMINANT browning reaction for a scenario.
121func fcr_predict_browning(s: *NxCookScenario) -> i64 {
122 // Maillard dominates under dry high heat with protein + reducing sugar.
123 if fcr_maillard(s.has_protein, s.has_reducing_sugar, s.temp_c) == 1 { return FCR_MAILLARD }
124 // Otherwise sugar pyrolysis if hot enough.
125 if s.has_sugar == 1 {
126 if s.temp_c >= fcr_caramelization_temp(s.sugar_id) { return FCR_CARAMELIZATION }
127 }
128 // Otherwise low-temperature enzymatic browning on cut PPO produce.
129 if fcr_enzymatic_browning(s.has_ppo, s.o2_present, s.ph_milli, s.temp_c, s.has_antioxidant) == 1 { return FCR_ENZYMATIC_BROWNING }
130 return FCR_NONE
131}