code wiki / _hdl_build / nx_garden.nx
nx_garden.nx source
↩ module page · 206 lines · 12417 B
1// nx_garden.nx -- LIB: the GROW-YOUR-OWN affordability engine (operator: "garden-to-table ... affordability
2// improvement"). For each food ingredient, models whether you can grow it at home, the season, and the cost to
3// BUY vs the cost to GROW (cents, integer -- no float). The savings = buy - grow is the affordability win:
4// grow-your-own turns a Michelin ingredient into a budget one. Each crop CITES a real permaculture research
5// source (knowledge/fetched/perma_*.raw) -- research, not bro science. Crops live in the food store keyed
6// garden:crop:<food-ingredient-id>. Only growable ingredients have a record (proteins/processed sauces don't).
7// Schema: garden:crop:<id> -> difficulty(1-5) <t> season <t> buy_cents <t> grow_cents <t> yield <t> cite
8// license_tier: ORIGINAL
9import "nx_food_science.nx"
10import "nx_seg_store.nx"
11import "nx_syscalls.nx"
12
13func gd_money(dst: *u8, off: i64, cents: i64) -> i64 {
14 var o: i64 = off
15 dst[o] = 36 as u8; o = o + 1
16 o = fd_apnum(dst, o, cents / 100)
17 dst[o] = 46 as u8; o = o + 1
18 let r: i64 = cents % 100
19 dst[o] = (48 + (r / 10)) as u8; o = o + 1
20 dst[o] = (48 + (r % 10)) as u8; o = o + 1
21 return o
22}
23func gd_crop_key(id: *u8, out: *u8) -> i64 {
24 var o: i64 = 0
25 o = as_append(out, o, "garden:crop:" as *u8)
26 o = as_append(out, o, id)
27 out[o] = 0 as u8
28 return o
29}
30func gd_get(prefix: *u8, id: *u8, ptr: *i64, len: *i64) -> i64 {
31 let key: *u8 = sys_mmap(80)
32 gd_crop_key(id, key)
33 return ss_get(prefix, key, ptr, len)
34}
35func gd_growable(prefix: *u8, id: *u8) -> i64 {
36 let pq: *i64 = sys_mmap(16) as *i64
37 let lq: *i64 = sys_mmap(16) as *i64
38 if gd_get(prefix, id, pq, lq) == 1 { return 1 }
39 return 0
40}
41func gd_field_int(prefix: *u8, id: *u8, f: i64) -> i64 {
42 let pq: *i64 = sys_mmap(16) as *i64
43 let lq: *i64 = sys_mmap(16) as *i64
44 if gd_get(prefix, id, pq, lq) != 1 { return 0 - 1 }
45 let buf: *u8 = sys_mmap(32)
46 let l: i64 = fd_field(pq[0] as *u8, lq[0], f, buf)
47 return fd_atoi(buf, l)
48}
49func gd_field_str(prefix: *u8, id: *u8, f: i64, out: *u8) -> i64 {
50 let pq: *i64 = sys_mmap(16) as *i64
51 let lq: *i64 = sys_mmap(16) as *i64
52 if gd_get(prefix, id, pq, lq) != 1 { out[0] = 0 as u8; return 0 }
53 return fd_field(pq[0] as *u8, lq[0], f, out)
54}
55func gd_buy(prefix: *u8, id: *u8) -> i64 { return gd_field_int(prefix, id, 2) }
56func gd_grow(prefix: *u8, id: *u8) -> i64 { return gd_field_int(prefix, id, 3) }
57// savings (cents) of growing vs buying one unit; 0 if not growable
58func gd_savings(prefix: *u8, id: *u8) -> i64 {
59 if gd_growable(prefix, id) == 0 { return 0 }
60 let b: i64 = gd_buy(prefix, id)
61 let g: i64 = gd_grow(prefix, id)
62 if b < 0 { return 0 }
63 if g < 0 { return 0 }
64 return b - g
65}
66
67// ---- companion planting (closes the loop: the perma SYNTHESIS garden:companion:<a>:<b> feeds affordability) ----
68// read the companion co-occurrence between two crop ids (tries both orders); 0 if not companions.
69func gd_companion(prefix: *u8, a: *u8, b: *u8) -> i64 {
70 let key: *u8 = sys_mmap(96)
71 let pq: *i64 = sys_mmap(16) as *i64
72 let lq: *i64 = sys_mmap(16) as *i64
73 var o: i64 = 0
74 o = as_append(key, o, "garden:companion:" as *u8); o = as_append(key, o, a); key[o] = 58 as u8; o = o + 1; o = as_append(key, o, b); key[o] = 0 as u8
75 if ss_get(prefix, key, pq, lq) == 1 { return fd_atoi(pq[0] as *u8, lq[0]) }
76 o = 0
77 o = as_append(key, o, "garden:companion:" as *u8); o = as_append(key, o, b); key[o] = 58 as u8; o = o + 1; o = as_append(key, o, a); key[o] = 0 as u8
78 if ss_get(prefix, key, pq, lq) == 1 { return fd_atoi(pq[0] as *u8, lq[0]) }
79 return 0
80}
81// seed the mined companion facts (idempotent; mirrors what nx_perma_mine_gate extracted from the corpus).
82func gd_seed_companions(prefix: *u8) -> i64 {
83 let keys: *i64 = sys_mmap(8 * 8) as *i64
84 let vals: *i64 = sys_mmap(8 * 8) as *i64
85 var n: i64 = 0
86 keys[n] = "garden:companion:onion:carrot" as *u8 as i64; vals[n] = "6" as *u8 as i64; n = n + 1
87 keys[n] = "garden:companion:onion:garlic" as *u8 as i64; vals[n] = "2" as *u8 as i64; n = n + 1
88 keys[n] = "garden:companion:onion:mushroom" as *u8 as i64; vals[n] = "1" as *u8 as i64; n = n + 1
89 keys[n] = "garden:companion:onion:lime" as *u8 as i64; vals[n] = "1" as *u8 as i64; n = n + 1
90 let w: *i64 = ss_begin()
91 var tw: i64 = 0
92 var i: i64 = 0
93 while i < n {
94 let k: *u8 = keys[i] as *u8
95 let v: *u8 = vals[i] as *u8
96 let vl: i64 = as_len(v)
97 if fd_streq_store(prefix, k, v, vl) == 0 { ss_add(w, 1, k, v, vl); tw = tw + 1 }
98 i = i + 1
99 }
100 if tw > 0 { let seg: i64 = fd_seg_next(prefix); ss_commit(prefix, w, seg) }
101 return tw
102}
103// companion savings bonus (cents) for a set of growable crop world-indices: each interplanted companion pair
104// boosts yield, worth 5c per companion-strength point. (The synthesis number drives a real affordability gain.)
105func gd_companion_bonus(prefix: *u8, world: *i64, idxs: *i64, nidx: i64) -> i64 {
106 let ids: *i64 = world[1] as *i64
107 var bonus: i64 = 0
108 var i: i64 = 0
109 while i < nidx {
110 var j: i64 = i + 1
111 while j < nidx {
112 let c: i64 = gd_companion(prefix, ids[idxs[i]] as *u8, ids[idxs[j]] as *u8)
113 if c > 0 { bonus = bonus + c * 5 }
114 j = j + 1
115 }
116 i = i + 1
117 }
118 return bonus
119}
120
121// author the growable-crop catalogue (idempotent). Costs in cents. cite -> a fetched permaculture source.
122func gd_seed(prefix: *u8) -> i64 {
123 let keys: *i64 = sys_mmap(8 * 32) as *i64
124 let vals: *i64 = sys_mmap(8 * 32) as *i64
125 var n: i64 = 0
126 keys[n] = "garden:cropids" as *u8 as i64
127 vals[n] = "garlic\tonion\tscallion\tginger\tchili\tbroccoli\tbellpepper\tcarrot\tsnowpea\tbokchoy\tbeansprout\tmushroom\ttofu\tlime\tpeanut" as *u8 as i64; n = n + 1
128 keys[n] = "garden:crop:garlic" as *u8 as i64; vals[n] = "1\tFALL\t100\t12\t8\tperma_a6_companion_planting" as *u8 as i64; n = n + 1
129 keys[n] = "garden:crop:onion" as *u8 as i64; vals[n] = "1\tSPRING\t100\t15\t8\tperma_b11_crop_rotation" as *u8 as i64; n = n + 1
130 keys[n] = "garden:crop:scallion" as *u8 as i64; vals[n] = "1\tPERENNIAL\t100\t8\t15\tperma_c6_seed_saving" as *u8 as i64; n = n + 1
131 keys[n] = "garden:crop:ginger" as *u8 as i64; vals[n] = "3\tSUMMER\t350\t60\t4\tperma_c3_container_garden" as *u8 as i64; n = n + 1
132 keys[n] = "garden:crop:chili" as *u8 as i64; vals[n] = "2\tSUMMER\t200\t20\t12\tperma_c4_hydroponics" as *u8 as i64; n = n + 1
133 keys[n] = "garden:crop:broccoli" as *u8 as i64; vals[n] = "2\tFALL\t200\t30\t4\tperma_a6_companion_planting" as *u8 as i64; n = n + 1
134 keys[n] = "garden:crop:bellpepper" as *u8 as i64; vals[n] = "3\tSUMMER\t150\t25\t6\tperma_c1_raised_bed" as *u8 as i64; n = n + 1
135 keys[n] = "garden:crop:carrot" as *u8 as i64; vals[n] = "2\tSPRING\t120\t20\t10\tperma_b4_soil_structure" as *u8 as i64; n = n + 1
136 keys[n] = "garden:crop:snowpea" as *u8 as i64; vals[n] = "2\tSPRING\t250\t30\t5\tperma_b12_nitrogen_fixation" as *u8 as i64; n = n + 1
137 keys[n] = "garden:crop:bokchoy" as *u8 as i64; vals[n] = "2\tFALL\t180\t25\t4\tperma_a5_polyculture" as *u8 as i64; n = n + 1
138 keys[n] = "garden:crop:beansprout" as *u8 as i64; vals[n] = "1\tPERENNIAL\t150\t10\t20\tperma_c6_seed_saving" as *u8 as i64; n = n + 1
139 keys[n] = "garden:crop:mushroom" as *u8 as i64; vals[n] = "4\tPERENNIAL\t300\t80\t6\tperma_b6_humus" as *u8 as i64; n = n + 1
140 keys[n] = "garden:crop:tofu" as *u8 as i64; vals[n] = "4\tSUMMER\t200\t50\t6\tperma_b12_nitrogen_fixation" as *u8 as i64; n = n + 1
141 keys[n] = "garden:crop:lime" as *u8 as i64; vals[n] = "4\tPERENNIAL\t50\t30\t50\tperma_a3_agroforestry" as *u8 as i64; n = n + 1
142 keys[n] = "garden:crop:peanut" as *u8 as i64; vals[n] = "3\tSUMMER\t300\t40\t8\tperma_b12_nitrogen_fixation" as *u8 as i64; n = n + 1
143
144 let w: *i64 = ss_begin()
145 var towrite: i64 = 0
146 var i: i64 = 0
147 while i < n {
148 let key: *u8 = keys[i] as *u8
149 let val: *u8 = vals[i] as *u8
150 let vl: i64 = as_len(val)
151 if fd_streq_store(prefix, key, val, vl) == 0 { ss_add(w, 1, key, val, vl); towrite = towrite + 1 }
152 i = i + 1
153 }
154 if towrite > 0 { let seg: i64 = fd_seg_next(prefix); ss_commit(prefix, w, seg) }
155 return towrite
156}
157
158// total per-unit savings of growing the growable ingredients in a build; out_grow = how many are growable
159func gd_meal_savings(prefix: *u8, food_world: *i64, build: *i64, nb: i64, out_grow: *i64) -> i64 {
160 let ids: *i64 = food_world[1] as *i64
161 var sav: i64 = 0
162 var g: i64 = 0
163 var i: i64 = 0
164 while i < nb {
165 let id: *u8 = ids[build[i]] as *u8
166 if gd_growable(prefix, id) == 1 { sav = sav + gd_savings(prefix, id); g = g + 1 }
167 i = i + 1
168 }
169 out_grow[0] = g
170 return sav
171}
172
173// render the sovereign grow-your-own affordability plan for a build (NUL-terminated); returns length
174func gd_render_plan(prefix: *u8, food_world: *i64, build: *i64, nb: i64, meals_per_week: i64, out: *u8) -> i64 {
175 let ids: *i64 = food_world[1] as *i64
176 let names: *i64 = food_world[4] as *i64
177 var o: i64 = 0
178 o = as_append(out, o, "<!doctype html><html lang='en'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>Grow it yourself</title><style>body{margin:0;font-family:system-ui,sans-serif;color:#1d2a1f;background:#f2f7ee;line-height:1.5}.card{max-width:620px;margin:0 auto;padding:24px 22px}header{background:#2e6b34;color:#fff;padding:24px;border-radius:0 0 12px 12px}header h1{margin:0 0 4px;font-size:1.4rem}header p{margin:0;color:#d6ebcf}h2{font-size:1.05rem;color:#2e6b34;margin:20px 0 8px}table{border-collapse:collapse;width:100%;background:#fff;border-radius:10px;overflow:hidden}th{text-align:left;background:#e3efdc;padding:8px 10px;color:#33503f;font-size:.9rem}td{padding:8px 10px;border-top:1px solid #eef3ea}td.n{text-align:right;font-variant-numeric:tabular-nums}.buy td{color:#999;text-decoration:line-through}.big{background:#fff;border-left:4px solid #2e6b34;border-radius:8px;padding:14px 16px;margin-top:16px;font-size:1.05rem}.ftr{margin-top:20px;color:#7a8a72;font-size:.82rem;text-align:center}</style></head><body><header><h1>Grow it yourself</h1><p>Tonight's stir-fry, from your own garden — the cheapest premium ingredient is the one you grew.</p></header><div class='card'>" as *u8)
179 o = as_append(out, o, "<h2>What you can grow</h2><table><tr><th>Ingredient</th><th>Season</th><th>Buy</th><th>Grow</th><th>You save</th></tr>" as *u8)
180 let og: *i64 = sys_mmap(16) as *i64
181 let persav: i64 = gd_meal_savings(prefix, food_world, build, nb, og)
182 var i: i64 = 0
183 var notgrow: i64 = 0
184 while i < nb {
185 let id: *u8 = ids[build[i]] as *u8
186 if gd_growable(prefix, id) == 1 {
187 o = as_append(out, o, "<tr><td>" as *u8)
188 o = as_append_escaped(out, o, names[build[i]] as *u8, as_len(names[build[i]] as *u8))
189 o = as_append(out, o, "</td><td>" as *u8)
190 let se: *u8 = sys_mmap(24); gd_field_str(prefix, id, 1, se); o = as_append(out, o, se)
191 o = as_append(out, o, "</td><td class='n'>" as *u8); o = gd_money(out, o, gd_buy(prefix, id))
192 o = as_append(out, o, "</td><td class='n'>" as *u8); o = gd_money(out, o, gd_grow(prefix, id))
193 o = as_append(out, o, "</td><td class='n'>" as *u8); o = gd_money(out, o, gd_savings(prefix, id))
194 o = as_append(out, o, "</td></tr>" as *u8)
195 } else { notgrow = notgrow + 1 }
196 i = i + 1
197 }
198 o = as_append(out, o, "</table>" as *u8)
199 o = as_append(out, o, "<div class='big'>Per meal you save <b>" as *u8); o = gd_money(out, o, persav)
200 o = as_append(out, o, "</b> by growing " as *u8); o = fd_apnum(out, o, og[0]); o = as_append(out, o, " of the ingredients. Cooked " as *u8)
201 o = fd_apnum(out, o, meals_per_week); o = as_append(out, o, "x a week, that is <b>" as *u8)
202 o = gd_money(out, o, persav * meals_per_week * 52); o = as_append(out, o, "</b> a year — before counting the quality you cannot buy.</div>" as *u8)
203 o = as_append(out, o, "<p class='ftr'>Grounded in the Nishi permaculture corpus — sovereign, no tracking. Costs are planning estimates.</p></div></body></html>" as *u8)
204 out[o] = 0 as u8
205 return o
206}