nx_worldpipe.nx source
↩ module page · 379 lines · 20700 B
1// nx_worldpipe.nx -- ★R5 STAGED PROCGEN (the banked Minecraft-pipeline curriculum, engine-convergence rung 5):
2// worldgen as an ORDERED STAGED PIPELINE over a MULTI-PARAMETER space -- not one noise call.
3// STAGE 1 PARAMS six independent noise fields at (x,z): temperature / humidity / CONTINENTALNESS / erosion /
4// weirdness / river. (Their 6th, depth, is the 3D-density param -- ours is a heightfield: named residual.)
5// STAGE 2 SPLINE height BASE = piecewise-linear SPLINE over continentalness (deep ocean -> coast -> inland -> highland),
6// the curriculum's core lesson: params map to height THROUGH SPLINES.
7// STAGE 3 RELIEF ridged mountains scaled by WEIRDNESS and flattened by EROSION (high erosion = plains).
8// STAGE 4 CARVER rivers CUT the terrain where the river field crosses its band (geometry change, toggleable).
9// STAGE 5 BIOME classified from the parameter VECTOR (temp x humidity x cont x weirdness) -- NOT from height
10// (the anti-"height-only" lesson: two same-height points can be desert vs snowfield).
11// STAGE 6 SURFACE per-biome + slope + altitude surface colours (sand/grass/snow/rock/water).
12// STAGE 7 FEATURES per-biome placements by seeded hash (trees / cacti / spruce / boulders).
13// STAGE 8 SPAWN a valid spawn point (land, gentle slope) found deterministically.
14// Pure integer, deterministic per seed. Stages 4/7 have toggles so gates can prove CAUSALITY per stage.
15// license_tier: ORIGINAL
16import "nx_syscalls.nx"
17import "nx_trimesh.nx"
18const WP_MAGIC_1400: i64 = 1400
19const WP_MAGIC_3400: i64 = 3400
20const WP_MAGIC_2600: i64 = 2600
21const WP_MAGIC_2100: i64 = 2100
22const WP_MAGIC_1700: i64 = 1700
23const WP_MAGIC_1300: i64 = 1300
24const WP_MAGIC_2400: i64 = 2400
25const WP_MAGIC_9973: i64 = 9973
26const WP_MAGIC_524287: i64 = 524287
27const WP_MAGIC_1024: i64 = 1024
28const WP_MAGIC_9600: i64 = 9600
29const WP_MAGIC_1000000000: i64 = 1000000000
30const WP_MAGIC_10100: i64 = 10100
31const WP_MAGIC_1000000: i64 = 1000000
32const WP_MAGIC_999999: i64 = 999999
33const WP_MAGIC_65536: i64 = 65536
34
35static WP_SEED: i64
36static WP_CARVE: i64 // stage-4 toggle (1 = carve rivers). set via wp_set_carve
37static WP_FEAT: i64 // stage-7 toggle (1 = place features). set via wp_set_feat
38// ★AUTHORING KNOBS (the "describe a world" layer, 2026-07-13): global biases the NL front-end sets. Defaulted in
39// wp_init so every existing gate is byte-unchanged; the authoring path sets them AFTER wp_init.
40static WP_TEMP_BIAS: i64 // added to temperature field (colder = snowier)
41static WP_CONT_BIAS: i64 // added to continentalness (lower = more ocean/islands)
42static WP_MOUNT_SCALE: i64 // relief multiplier (default 1400 = current alpine strength)
43static WP_LIGHT: i64 // stage-6.5 lighting toggle (declared ABOVE all readers -- the AT_FDCWD law)
44func wp_init(seed: i64) -> i64 { WP_SEED = seed; WP_CARVE = 1; WP_FEAT = 1; WP_TEMP_BIAS = 0; WP_CONT_BIAS = 0; WP_MOUNT_SCALE = WP_MAGIC_1400; WP_LIGHT = 1; return 0 }
45func wp_set_carve(v: i64) -> i64 { WP_CARVE = v; return 0 }
46func wp_set_feat(v: i64) -> i64 { WP_FEAT = v; return 0 }
47func wp_set_temp_bias(v: i64) -> i64 { WP_TEMP_BIAS = v; return 0 }
48func wp_set_cont_bias(v: i64) -> i64 { WP_CONT_BIAS = v; return 0 }
49func wp_set_mount_scale(v: i64) -> i64 { WP_MOUNT_SCALE = v; return 0 }
50func wp_iabs(v: i64) -> i64 { if v < 0 { return 0-v } return v }
51func wp_clamp(v: i64, lo: i64, hi: i64) -> i64 { if v<lo {return lo} if v>hi {return hi} return v }
52
53// ---- STAGE 1: the parameter fields. k: 0 continentalness, 1 temperature, 2 humidity, 3 erosion, 4 weirdness, 5 river.
54// each = value noise on its own (seed,param) y-plane at its own frequency, centered to [-512, 511].
55func wp_param(x: i64, z: i64, k: i64) -> i64 {
56 var f: i64 = WP_MAGIC_3400 // continentalness: the longest wavelength (continents)
57 if k == 1 { f = WP_MAGIC_2600 } // temperature
58 if k == 2 { f = WP_MAGIC_2100 } // humidity
59 if k == 3 { f = WP_MAGIC_1700 } // erosion
60 if k == 4 { f = WP_MAGIC_1300 } // weirdness
61 if k == 5 { f = WP_MAGIC_2400 } // river field
62 let yk: i64 = WP_SEED*WP_MAGIC_9973 + k*WP_MAGIC_524287
63 var bias: i64 = 0
64 if k == 0 { bias = WP_CONT_BIAS } // continentalness bias (islands/continents)
65 if k == 1 { bias = WP_TEMP_BIAS } // temperature bias (snowy/hot)
66 return tm_noise3(x, yk, z, f) - 512 + bias
67}
68
69// ---- STAGE 2: the continentalness->height SPLINE (piecewise linear over control points) ----
70func wp_spline(c: i64) -> i64 {
71 // knots: (-512,-430) (-220,-160) (-120,-40) (-40,15) (120,90) (300,190) (512,420)
72 if c <= 0-220 { return 0-430 + (c - (0-512))*((0-160) - (0-430))/((0-220) - (0-512)) }
73 if c <= 0-120 { return 0-160 + (c - (0-220))*((0-40) - (0-160))/((0-120) - (0-220)) }
74 if c <= 0-40 { return 0-40 + (c - (0-120))*(15 - (0-40))/((0-40) - (0-120)) }
75 if c <= 120 { return 15 + (c - (0-40))*(90 - 15)/(120 - (0-40)) }
76 if c <= 300 { return 90 + (c - 120)*(190 - 90)/(300 - 120) }
77 return 190 + (c - 300)*(420 - 190)/(512 - 300)
78}
79
80// ---- STAGES 2+3+4: terrain height from the params (relief = ridged * weirdness * (1-erosion), rivers carve) ----
81// exposed with explicit params so gates can prove each factor's effect in isolation.
82func wp_height_core(cont: i64, ero: i64, weird: i64, x: i64, z: i64) -> i64 {
83 let base: i64 = wp_spline(cont)
84 var ridge: i64 = 512 - wp_iabs(tm_noise3(x, WP_SEED*WP_MAGIC_9973 + 6*WP_MAGIC_524287, z, 900) - 512)*2
85 if ridge < 0 { ridge = 0 }
86 let wamp: i64 = weird + 512 // 0..1023: weirdness scales the mountains
87 let eroF: i64 = 512 - ero // 1..WP_MAGIC_1024: erosion FLATTENS (high ero -> small relief)
88 let mfac: i64 = wp_clamp((cont + 40)*4, 0, WP_MAGIC_1024) // mountains only inland (not in the sea)
89 let mount: i64 = ((ridge*wamp/WP_MAGIC_1024)*eroF/WP_MAGIC_1024)*mfac/WP_MAGIC_1024*WP_MOUNT_SCALE/WP_MAGIC_1024 // relief (authoring-scalable; default WP_MAGIC_1400)
90 return base + mount
91}
92func wp_height_params(cont: i64, ero: i64, weird: i64, x: i64, z: i64) -> i64 {
93 let detail: i64 = (tm_noise3(x, WP_SEED*WP_MAGIC_9973 + 7*WP_MAGIC_524287, z, 300) - 512)/8
94 return wp_height_core(cont, ero, weird, x, z) + detail
95}
96// the FLOW surface: core only -- water descends the VALLEY-scale terrain, not micro-bumps
97func wp_flow_h(x: i64, z: i64) -> i64 {
98 let cont: i64 = wp_param(x, z, 0)
99 let ero: i64 = wp_param(x, z, 3)
100 let weird: i64 = wp_param(x, z, 4)
101 return wp_height_core(cont, ero, weird, x, z)
102}
103// raw terrain (stages 2+3 only -- what the water flows OVER)
104func wp_height_raw(x: i64, z: i64) -> i64 {
105 let cont: i64 = wp_param(x, z, 0)
106 let ero: i64 = wp_param(x, z, 3)
107 let weird: i64 = wp_param(x, z, 4)
108 return wp_height_params(cont, ero, weird, x, z)
109}
110
111// ---- ★Q3 HYDROLOGY (2026-07-12, replaces the noise-band carver -- "the content was random"): rivers are
112// STEEPEST-DESCENT TRACES from high springs down to the sea. Baked once per seed into a coarse bed grid
113// (cell HYCELL over +-HYSPAN); wp_height carves terrain to the traced bed. By construction a river bed is
114// MONOTONE NON-INCREASING along its path (water cannot flow uphill) and terminates at the sea or a lake.
115const HYW: i64 = 320
116const HYCELL: i64 = 64
117const HYSPAN: i64 = 10240
118static WP_HYD: i64 // bed grid: HYW*HYW i64; 0 = no river; else bed height + HYOFF
119static WP_HYSTATS: i64 // per-spring stats: [startBed, endBed, steps, reachedSea] x 8
120static WP_HYN: i64 // springs traced
121const HYOFF: i64 = 5000000
122func wp_hyd_stamp(g: *i64, x: i64, z: i64, bed: i64, rad: i64) -> i64 {
123 var dz: i64 = 0 - rad
124 while dz <= rad {
125 var dx: i64 = 0 - rad
126 while dx <= rad {
127 let cx: i64 = (x + dx*HYCELL + HYSPAN)/HYCELL
128 let cz: i64 = (z + dz*HYCELL + HYSPAN)/HYCELL
129 if cx >= 0 { if cx < HYW { if cz >= 0 { if cz < HYW {
130 let gi: i64 = cz*HYW + cx
131 let v: i64 = bed + HYOFF
132 if g[gi] == 0 { g[gi] = v } else { if v < g[gi] { g[gi] = v } }
133 } } } }
134 dx = dx + 1
135 }
136 dz = dz + 1
137 }
138 return 0
139}
140func wp_hydro_bake() -> i64 {
141 if WP_HYD == 0 { WP_HYD = sys_mmap(HYW*HYW*8) as i64 }
142 if WP_HYSTATS == 0 { WP_HYSTATS = sys_mmap(8*4*8) as i64 }
143 let g: *i64 = WP_HYD as *i64
144 var i: i64 = 0
145 while i < HYW*HYW { g[i] = 0; i = i + 1 }
146 let st: *i64 = WP_HYSTATS as *i64
147 WP_HYN = 0
148 // springs: seeded picks on HIGH ground
149 var sz: i64 = 0-WP_MAGIC_9600
150 while sz <= WP_MAGIC_9600 {
151 var sx: i64 = 0-WP_MAGIC_9600
152 while sx <= WP_MAGIC_9600 {
153 if WP_HYN < 8 {
154 if wp_flow_h(sx, sz) > 140 {
155 if tm_hash3(sx/640, WP_SEED*17 + 3, sz/640) < 70 {
156 // trace steepest descent
157 var px: i64 = sx
158 var pz: i64 = sz
159 var bed: i64 = wp_flow_h(px, pz) - 14
160 let sb: i64 = bed
161 var steps: i64 = 0
162 var alive: i64 = 1
163 var reached: i64 = 0
164 while alive == 1 {
165 wp_hyd_stamp(g, px, pz, bed, 1)
166 var bestH: i64 = WP_MAGIC_1000000000
167 var bx: i64 = px
168 var bz: i64 = pz
169 var d: i64 = 0
170 while d < 16 {
171 var stp: i64 = 110
172 if d >= 8 { stp = 300 } // basin-escape ring
173 var nx: i64 = px
174 var nz: i64 = pz
175 let dd8: i64 = d % 8
176 if dd8 == 0 { nx = px + stp }
177 if dd8 == 1 { nx = px - stp }
178 if dd8 == 2 { nz = pz + stp }
179 if dd8 == 3 { nz = pz - stp }
180 if dd8 == 4 { nx = px + stp*7/10; nz = pz + stp*7/10 }
181 if dd8 == 5 { nx = px - stp*7/10; nz = pz + stp*7/10 }
182 if dd8 == 6 { nx = px + stp*7/10; nz = pz - stp*7/10 }
183 if dd8 == 7 { nx = px - stp*7/10; nz = pz - stp*7/10 }
184 let nh: i64 = wp_flow_h(nx, nz)
185 if nh < bestH { bestH = nh; bx = nx; bz = nz }
186 d = d + 1
187 }
188 let here: i64 = wp_flow_h(px, pz)
189 if bestH >= here {
190 // true basin even at the escape ring -> a LAKE, then stop
191 wp_hyd_stamp(g, px, pz, bed, 2)
192 alive = 0
193 } else {
194 // stamp ALONG the segment (escape jumps are 300 units; a single stamp leaves gaps)
195 wp_hyd_stamp(g, px + (bx-px)/3, pz + (bz-pz)/3, bed, 1)
196 wp_hyd_stamp(g, px + (bx-px)*2/3, pz + (bz-pz)*2/3, bed, 1)
197 px = bx
198 pz = bz
199 var nb: i64 = bestH - 14
200 if nb > bed { nb = bed } // water NEVER flows uphill
201 bed = nb
202 if bestH < 0 { reached = 1; alive = 0 } // reached the sea
203 }
204 steps = steps + 1
205 if steps > 480 { alive = 0 }
206 if px < 0-WP_MAGIC_10100 { alive = 0 }
207 if px > WP_MAGIC_10100 { alive = 0 }
208 if pz < 0-WP_MAGIC_10100 { alive = 0 }
209 if pz > WP_MAGIC_10100 { alive = 0 }
210 }
211 st[WP_HYN*4] = sb
212 st[WP_HYN*4+1] = bed
213 st[WP_HYN*4+2] = steps
214 st[WP_HYN*4+3] = reached
215 WP_HYN = WP_HYN + 1
216 }
217 }
218 }
219 sx = sx + 640
220 }
221 sz = sz + 640
222 }
223 return WP_HYN
224}
225func wp_hyd_n() -> i64 { return WP_HYN }
226func wp_hyd_stat(i: i64, k: i64) -> i64 { let st: *i64 = WP_HYSTATS as *i64; return st[i*4+k] }
227func wp_hyd_bed(x: i64, z: i64) -> i64 {
228 if WP_HYD == 0 { return 0-WP_MAGIC_1000000 }
229 let cx: i64 = (x + HYSPAN)/HYCELL
230 let cz: i64 = (z + HYSPAN)/HYCELL
231 if cx < 0 { return 0-WP_MAGIC_1000000 }
232 if cx >= HYW { return 0-WP_MAGIC_1000000 }
233 if cz < 0 { return 0-WP_MAGIC_1000000 }
234 if cz >= HYW { return 0-WP_MAGIC_1000000 }
235 let g: *i64 = WP_HYD as *i64
236 let v: i64 = g[cz*HYW + cx]
237 if v == 0 { return 0-WP_MAGIC_1000000 }
238 return v - HYOFF
239}
240func wp_height(x: i64, z: i64) -> i64 {
241 var h: i64 = wp_height_raw(x, z)
242 if WP_CARVE == 1 {
243 let bed: i64 = wp_hyd_bed(x, z)
244 if bed > 0-WP_MAGIC_999999 { if bed < h { h = bed } } // STAGE 4: the traced river carves to its bed
245 }
246 return h
247}
248
249// ---- STAGE 5: biome from the parameter VECTOR (not height). ids:
250// 0 OCEAN, 1 BEACH, 2 RIVER, 3 DESERT, 4 SNOWY, 5 PEAKS, 6 FOREST, 7 PLAINS
251func wp_biome(x: i64, z: i64) -> i64 {
252 let cont: i64 = wp_param(x, z, 0)
253 if cont <= 0-120 { return 0 }
254 if cont <= 0-40 { return 1 }
255 let hraw: i64 = wp_height_raw(x, z)
256 if WP_CARVE == 1 {
257 let bed: i64 = wp_hyd_bed(x, z)
258 if bed > 0-WP_MAGIC_999999 { if bed < hraw { return 2 } } // RIVER = the traced watercourse (Q3 hydrology)
259 }
260 // ★Q3 ALTITUDE LAPSE: temperature FALLS with height -- snow tops mountains BECAUSE they are cold,
261 // deserts exist only in hot lowlands. te = temp - lapse(h).
262 var lapse: i64 = 0
263 if hraw > 0 { lapse = hraw*4/5 }
264 let te: i64 = wp_param(x, z, 1) - lapse
265 let humid: i64 = wp_param(x, z, 2)
266 let ero: i64 = wp_param(x, z, 3)
267 let weird: i64 = wp_param(x, z, 4)
268 if te > 170 { if humid < 0-90 { return 3 } }
269 if te < 0-210 { return 4 }
270 if weird > 230 { if ero < 80 { return 5 } }
271 if humid > 70 { return 6 }
272 return 7
273}
274
275// ---- STAGE 6: surface colour from biome + slope + altitude (r + g*256 + b*65536) ----
276func wp_surface(x: i64, z: i64, h: i64, slope: i64) -> i64 {
277 let b: i64 = wp_biome(x, z)
278 if b == 0 { if h < 0-220 { return 14 + 42*256 + 120*WP_MAGIC_65536 } return 24 + 70*256 + 165*WP_MAGIC_65536 }
279 if h < 0 { return 52 + 118*256 + 196*WP_MAGIC_65536 } // carved river water
280 var col: i64 = 96 + 150*256 + 60*WP_MAGIC_65536 // plains grass
281 if b == 1 { col = 216 + 196*256 + 140*WP_MAGIC_65536 } // beach sand
282 if b == 3 { col = 228 + 200*256 + 120*WP_MAGIC_65536 } // desert sand
283 if b == 4 { col = 232 + 236*256 + 244*WP_MAGIC_65536 } // snow
284 if b == 5 { col = 130 + 124*256 + 120*WP_MAGIC_65536 } // peaks rock
285 if b == 6 { col = 52 + 110*256 + 44*WP_MAGIC_65536 } // forest grass
286 if slope > 110 { col = 122 + 112*256 + 96*WP_MAGIC_65536 } // steep -> exposed rock
287 if h > 330 { col = 236 + 239*256 + 246*WP_MAGIC_65536 } // snowcap by altitude
288 return col
289}
290
291// ---- STAGE 6.5: LIGHTING (2026-07-26, F1158 -- the stage the residual list always named).
292// WHY (measured, not taste): every head-to-head frame score names PALETTE as the gap -- the staged
293// world graded 667 RICH with det4 112 ~ Breeders' 125 but palette 227 vs their 1904, because flat
294// per-biome fills emit a handful of distinct colours. Lighting = sun-facing diffuse off the REAL
295// heightfield + per-cell albedo variation, so colour count comes from GEOMETRY, not from noise
296// (the GX-13/14 Goodhart line: a texture overlay that moves one stat is forbidden; shading that
297// follows the terrain is structure). Toggleable like the carver so the gate proves CAUSALITY.
298const WP_L_AMB: i64 = 256 // FLAT GROUND = unity (measured: an ambient floor of 168 made the
299 // stage darken-only -- 183/183 shade-darkened but 3/201 sun-brightened;
300 // centring at 256 gives true brighten/darken symmetry about unlit)
301const WP_L_GAIN: i64 = 22 // diffuse gain per height-step unit
302const WP_L_STEP: i64 = 90 // sample distance for the slope normal
303const WP_L_MAXB: i64 = 340 // brightness ceiling (overbright allowed; channels clamp at 255)
304const WP_L_MINB: i64 = 96 // brightness floor (shadow side never crushes to black)
305const WP_L_JIT: i64 = 14 // per-cell albedo jitter amplitude (small: variation, not noise)
306const WP_L_CELL: i64 = 96 // albedo cell size (matches the feature grid feel)
307func wp_set_light(v: i64) -> i64 { WP_LIGHT = v; return 0 }
308func wp_shade(x: i64, z: i64, h: i64, slope: i64) -> i64 {
309 let base: i64 = wp_surface(x, z, h, slope)
310 if WP_LIGHT == 0 { return base }
311 // sun from the north-west, elevation baked into the gain: faces dropping toward the sun catch it
312 let hw2: i64 = wp_height(x - WP_L_STEP, z - WP_L_STEP)
313 var d: i64 = (hw2 - h)
314 if d > WP_L_STEP { d = WP_L_STEP }
315 if d < 0-WP_L_STEP { d = 0-WP_L_STEP }
316 var bright: i64 = WP_L_AMB + (d * WP_L_GAIN) / 10
317 if bright > WP_L_MAXB { bright = WP_L_MAXB }
318 if bright < WP_L_MINB { bright = WP_L_MINB }
319 // per-cell albedo variation: a deterministic +-JIT on each channel, cell-stable (not per-pixel noise)
320 let cx: i64 = x / WP_L_CELL
321 let cz: i64 = z / WP_L_CELL
322 let jr: i64 = tm_hash3(cx, 91, cz) % (2*WP_L_JIT+1) - WP_L_JIT
323 let jg: i64 = tm_hash3(cx, 92, cz) % (2*WP_L_JIT+1) - WP_L_JIT
324 let jb2: i64 = tm_hash3(cx, 93, cz) % (2*WP_L_JIT+1) - WP_L_JIT
325 var r: i64 = ((base % 256) + jr) * bright / 256
326 var g: i64 = (((base / 256) % 256) + jg) * bright / 256
327 var b: i64 = (((base / WP_MAGIC_65536) % 256) + jb2) * bright / 256
328 r = wp_clamp(r, 0, 255)
329 g = wp_clamp(g, 0, 255)
330 b = wp_clamp(b, 0, 255)
331 return r + g*256 + b*WP_MAGIC_65536
332}
333
334// ---- STAGE 7: feature at a cell (0 none, 1 tree, 2 cactus, 3 spruce, 4 boulder) ----
335func wp_feature(cx: i64, cz: i64) -> i64 {
336 if WP_FEAT == 0 { return 0 }
337 let h: i64 = wp_height(cx, cz)
338 if h < 6 { return 0 } // no features in water/rivers/beach-line
339 let b: i64 = wp_biome(cx, cz)
340 let hsh: i64 = tm_hash3(cx, WP_SEED*31 + 7, cz)
341 if b == 6 { if hsh < 190 { return 1 } } // forest: dense trees
342 if b == 7 { if hsh < 18 { return 1 } } // plains: sparse trees
343 if b == 3 { if hsh < 55 { return 2 } } // desert: cacti
344 if b == 4 { if hsh < 75 { return 3 } } // snowy: spruce
345 if b == 5 { if hsh < 45 { return 4 } } // peaks: boulders
346 return 0
347}
348
349// ---- STAGE 8: deterministic spawn -- first gentle land cell on an outward scan. out = [x, z, h]; 1 if found ----
350func wp_spawn(out: *i64) -> i64 {
351 var r: i64 = 0
352 while r < 40 {
353 var i: i64 = 0
354 while i < 8 {
355 var x: i64 = 0
356 var z: i64 = 0
357 if i == 0 { x = r*130 }
358 if i == 1 { x = 0-r*130 }
359 if i == 2 { z = r*130 }
360 if i == 3 { z = 0-r*130 }
361 if i == 4 { x = r*130; z = r*130 }
362 if i == 5 { x = 0-r*130; z = r*130 }
363 if i == 6 { x = r*130; z = 0-r*130 }
364 if i == 7 { x = 0-r*130; z = 0-r*130 }
365 let h: i64 = wp_height(x, z)
366 if h > 10 {
367 let s1: i64 = wp_iabs(wp_height(x+90, z) - h)
368 let s2: i64 = wp_iabs(wp_height(x, z+90) - h)
369 if s1 + s2 < 80 {
370 let b: i64 = wp_biome(x, z)
371 if b != 5 { out[0] = x; out[1] = z; out[2] = h; return 1 }
372 }
373 }
374 i = i + 1
375 }
376 r = r + 1
377 }
378 return 0
379}