nx_worldgen.nx source
↩ module page · 483 lines · 26086 B
1// nx_worldgen.nx -- ★SOVEREIGN INFINIGEN, RUNG 1: procedural NATURE (operator 2026-07-08, sharing princeton-vl/
2// infinigen: "build a WORLD full of living people, animals, environments, weather"). Infinigen = seed->procedural
3// photoreal 3D, NO ML, NO external assets -- EXACTLY our doctrine. This is the nature/"hello world" module: a
4// seed grows a whole LANDSCAPE -- fBm terrain (grass/rock/snow by height+slope) + water (lakes/sea) + sky gradient
5// + sun + procedural clouds + atmospheric fog. All INTEGER, sovereign, our own heightfield ray-marcher. Different
6// seed = different world; a weather param retints sun/sky/fog (clear / sunset / overcast). license_tier: ORIGINAL
7import "nx_syscalls.nx"
8import "nx_itrig.nx"
9const WG_MAGIC_374761393: i64 = 374761393
10const WG_MAGIC_668265263: i64 = 668265263
11const WG_MAGIC_65536: i64 = 65536
12const WG_MAGIC_4000: i64 = 4000
13const WG_MAGIC_1024: i64 = 1024
14const WG_MAGIC_32768: i64 = 32768
15const WG_MAGIC_2900: i64 = 2900
16const WG_MAGIC_1048576: i64 = 1048576
17const WG_MAGIC_3000: i64 = 3000
18const WG_MAGIC_40000: i64 = 40000
19const WG_MAGIC_1050: i64 = 1050
20const WG_MAGIC_4096: i64 = 4096
21const WG_MAGIC_9999: i64 = 9999
22const WG_MAGIC_60000: i64 = 60000
23const WG_MAGIC_2000000000: i64 = 2000000000
24const WG_MAGIC_1800: i64 = 1800
25const WG_MAGIC_1500: i64 = 1500
26const WG_MAGIC_1350: i64 = 1350
27const WG_MAGIC_1100: i64 = 1100
28
29const WG_W: i64 = 1280 // 2x render -> the page downscales it = free anti-aliasing
30const WG_H: i64 = 768
31const WG_FOCAL: i64 = 720
32const WG_HORIZON: i64 = 92 // shifts the horizon down (look slightly over the land)
33const WATER: i64 = 0 // water level (world units)
34const HSCALE: i64 = 4600
35const SNOW: i64 = 2620
36
37func wg_isqrt(v: i64) -> i64 { if v<=0 {return 0} var x: i64=v; var y: i64=(x+1)/2; while y<x {x=y; y=(x+v/x)/2} return x }
38func wg_abs(v: i64) -> i64 { if v<0 {return 0-v} return v }
39func wg_min(a: i64, b: i64) -> i64 { if a<b {return a} return b }
40func wg_max(a: i64, b: i64) -> i64 { if a>b {return a} return b }
41func wg_clamp(v: i64, lo: i64, hi: i64) -> i64 { if v<lo {return lo} if v>hi {return hi} return v }
42
43func wg_hash(ix: i64, iz: i64, seed: i64) -> i64 {
44 var h: i64 = (ix + seed*7)*WG_MAGIC_374761393 + (iz - seed*3)*WG_MAGIC_668265263 + ix*iz*97 + seed*13
45 if h < 0 { h = 0 - h }
46 return h % WG_MAGIC_65536
47}
48func wg_smooth(t: i64) -> i64 { return t*t*(768 - 2*t)/WG_MAGIC_65536 } // smoothstep, t in 0..256 -> 0..256
49func wg_vnoise(x256: i64, z256: i64, seed: i64) -> i64 {
50 let ix: i64 = x256 >> 8; let iz: i64 = z256 >> 8
51 let fx: i64 = x256 & 255; let fz: i64 = z256 & 255
52 let sx: i64 = wg_smooth(fx); let sz: i64 = wg_smooth(fz)
53 let v00: i64 = wg_hash(ix,iz,seed); let v10: i64 = wg_hash(ix+1,iz,seed)
54 let v01: i64 = wg_hash(ix,iz+1,seed); let v11: i64 = wg_hash(ix+1,iz+1,seed)
55 let a: i64 = v00 + (v10-v00)*sx/256
56 let b: i64 = v01 + (v11-v01)*sx/256
57 return a + (b-a)*sz/256
58}
59// fractal Brownian motion -> 0..1024
60func wg_fbm(wx: i64, wz: i64, seed: i64) -> i64 {
61 let cx: i64 = wx*256/WG_MAGIC_4000; let cz: i64 = wz*256/WG_MAGIC_4000
62 var sum: i64 = 0; var amp: i64 = 512; var norm: i64 = 0; var o: i64 = 0
63 while o < 5 {
64 sum = sum + wg_vnoise(cx << o, cz << o, seed + o*131) * amp / WG_MAGIC_65536
65 norm = norm + amp; amp = amp/2; o = o + 1
66 }
67 return sum*WG_MAGIC_1024/norm
68}
69func wg_terrain_h(wx: i64, wz: i64, seed: i64) -> i64 { return (wg_fbm(wx,wz,seed) - 402) * HSCALE / WG_MAGIC_1024 }
70
71// weather -> params array [sundx,sundy,sundz(fx1024 unit), sunr,sung,sunb, skyzr,skyzg,skyzb, skyhr,skyhg,skyhb, fog, cloud]
72func wg_weather(mode: i64, W: *i64) -> i64 {
73 // default CLEAR day: sun upper-right
74 W[0]=520; W[1]=620; W[2]=560; W[3]=255; W[4]=248; W[5]=228
75 W[6]=64; W[7]=120; W[8]=210; W[9]=176; W[10]=205; W[11]=232; W[12]=44; W[13]=42
76 if mode == 1 { // SUNSET: low warm sun, warm sky
77 W[0]=780; W[1]=180; W[2]=560; W[3]=255; W[4]=170; W[5]=96
78 W[6]=40; W[7]=70; W[8]=150; W[9]=250; W[10]=150; W[11]=96; W[12]=60; W[13]=54
79 }
80 if mode == 2 { // OVERCAST: high diffuse, grey sky, heavy fog+cloud
81 W[0]=300; W[1]=760; W[2]=400; W[3]=210; W[4]=212; W[5]=214
82 W[6]=150; W[7]=158; W[8]=168; W[9]=196; W[10]=200; W[11]=205; W[12]=90; W[13]=110
83 }
84 let l: i64 = wg_isqrt(W[0]*W[0]+W[1]*W[1]+W[2]*W[2])
85 if l > 0 { W[0]=W[0]*WG_MAGIC_1024/l; W[1]=W[1]*WG_MAGIC_1024/l; W[2]=W[2]*WG_MAGIC_1024/l }
86 return 0
87}
88
89// sun penumbra at a surface point (the soft-shadow march, shared by the pixel loop AND the GI cache): march
90// toward the sun tracking min(clearance/dist); 0 = fully shadowed .. 1024 = fully lit. Wp = weather (sun dir).
91func wg_sun_pen(seed: i64, hx: i64, hy: i64, hz: i64, Wp: *i64) -> i64 {
92 var pen: i64 = WG_MAGIC_1024
93 var sh2: i64 = 300
94 var ss: i64 = 0
95 while ss < 30 {
96 let qx: i64 = hx + Wp[0]*sh2/WG_MAGIC_1024
97 let qy: i64 = hy + Wp[1]*sh2/WG_MAGIC_1024
98 let qz: i64 = hz + Wp[2]*sh2/WG_MAGIC_1024
99 let clr: i64 = qy - wg_terrain_h(qx, qz, seed) + 10
100 if clr < 0 { pen = 0; ss = 999 }
101 else {
102 let p2: i64 = clr*WG_MAGIC_32768/sh2
103 if p2 < pen { pen = p2 }
104 sh2 = sh2 + 160 + sh2/9
105 if qy > WG_MAGIC_2900 { ss = 999 }
106 }
107 if ss != 999 { ss = ss + 1 }
108 }
109 if pen > WG_MAGIC_1024 { pen = WG_MAGIC_1024 }
110 return pen
111}
112
113// ★R3 GI: a SINGLE-BOUNCE IRRADIANCE CACHE (the classic technique, integer): a coarse world grid stores each
114// cell's SUN-LIT outgoing radiance (albedo x direct light); pixels gather from nearby cells with cosine + falloff
115// weighting -> coloured bounce light (sunlit grass bleeds green into shade). HONEST LABEL: one bounce, no
116// bounce-visibility test, coarse cells -- an approximation, not path tracing. wg_set_gi(0) disables (byte-
117// identical off path).
118const GI_N: i64 = 96 // cells per axis
119const GI_CS: i64 = 500 // world units per cell
120const GI_X0: i64 = 0 - 24000 // grid origin (covers the visible near field)
121const GI_Z0: i64 = 0
122static WG_GION: i64
123static WG_GIC: i64 // cache ptr: GI_N*GI_N cells x 2 i64 {packed rgb radiance, height}
124func wg_set_gi(on: i64) -> i64 { WG_GION = on; return 0 }
125func wg_gi_build(seed: i64, biome: i64, Wp: *i64) -> i64 {
126 if WG_GIC == 0 { WG_GIC = sys_mmap(GI_N*GI_N*2*8) as i64 }
127 let C: *i64 = WG_GIC as *i64
128 var j: i64 = 0
129 while j < GI_N {
130 var i: i64 = 0
131 while i < GI_N {
132 let wx: i64 = GI_X0 + i*GI_CS + GI_CS/2
133 let wz: i64 = GI_Z0 + j*GI_CS + GI_CS/2
134 let hh: i64 = wg_terrain_h(wx, wz, seed)
135 // cell normal (coarse)
136 let e: i64 = 120
137 let hl: i64 = wg_terrain_h(wx-e, wz, seed); let hr2: i64 = wg_terrain_h(wx+e, wz, seed)
138 let hd: i64 = wg_terrain_h(wx, wz-e, seed); let hu: i64 = wg_terrain_h(wx, wz+e, seed)
139 var nx: i64 = (hl-hr2)*WG_MAGIC_1024/(2*e); var ny: i64 = WG_MAGIC_1024; var nz: i64 = (hd-hu)*WG_MAGIC_1024/(2*e)
140 let nl: i64 = wg_isqrt(nx*nx+ny*ny+nz*nz)
141 if nl > 0 { nx=nx*WG_MAGIC_1024/nl; ny=ny*WG_MAGIC_1024/nl; nz=nz*WG_MAGIC_1024/nl }
142 // cell albedo (low-frequency biome palette: grass/rock/sand/snow by height+slope, no speckle)
143 var cr: i64 = 70; var cg: i64 = 104; var cb: i64 = 50
144 if biome == 1 { cr=196; cg=160; cb=110 }
145 if biome == 2 { cr=214; cg=224; cb=236 }
146 if biome == 3 { cr=150; cg=104; cb=44 }
147 var snowline: i64 = SNOW
148 if biome == 2 { snowline = 300 }
149 if ny < 640 { cr=118; cg=104; cb=94 }
150 if biome != 1 { if hh > snowline { if ny > 600 { cr=234; cg=240; cb=248 } } }
151 if hh < WATER { cr=24; cg=74; cb=116 }
152 // direct sun radiance at the cell (Lambert x penumbra), plus a small ambient floor
153 var nd: i64 = (nx*Wp[0]+ny*Wp[1]+nz*Wp[2])/WG_MAGIC_1024
154 if nd < 0 { nd = 0 }
155 var pen: i64 = WG_MAGIC_1024
156 if hh >= WATER { pen = wg_sun_pen(seed, wx, hh+14, wz, Wp) }
157 let lit: i64 = 20 + nd*95*pen/WG_MAGIC_1048576
158 var rr: i64 = cr*lit/100
159 var rg: i64 = cg*lit/100
160 var rb: i64 = cb*lit/100
161 if rr > 255 { rr = 255 }
162 if rg > 255 { rg = 255 }
163 if rb > 255 { rb = 255 }
164 C[(j*GI_N+i)*2] = rr + rg*256 + rb*WG_MAGIC_65536
165 C[(j*GI_N+i)*2+1] = hh
166 i = i + 1
167 }
168 j = j + 1
169 }
170 return 0
171}
172// gather bounce irradiance at a surface point with unit normal (nx,ny,nz fx1024). out[0..2] = rgb irradiance.
173func wg_gi_gather(hx: i64, hy: i64, hz: i64, nx: i64, ny: i64, nz: i64, out: *i64) -> i64 {
174 out[0]=0; out[1]=0; out[2]=0
175 if WG_GION == 0 { return 0 }
176 if WG_GIC == 0 { return 0 }
177 let C: *i64 = WG_GIC as *i64
178 let ci: i64 = (hx - GI_X0) / GI_CS
179 let cj: i64 = (hz - GI_Z0) / GI_CS
180 if ci < 2 { return 0 }
181 if cj < 2 { return 0 }
182 if ci > GI_N-3 { return 0 }
183 if cj > GI_N-3 { return 0 }
184 var sr: i64 = 0
185 var sg: i64 = 0
186 var sb: i64 = 0
187 var wsum: i64 = 0
188 var oj: i64 = 0 - 2
189 while oj <= 2 {
190 var oi: i64 = 0 - 2
191 while oi <= 2 {
192 var ring: i64 = oi
193 if ring < 0 { ring = 0 - ring }
194 var aj: i64 = oj
195 if aj < 0 { aj = 0 - aj }
196 if aj > ring { ring = aj }
197 if ring >= 1 { // exclude the self cell; use rings 1-2
198 let ii: i64 = ci + oi
199 let jj: i64 = cj + oj
200 let crad: i64 = C[(jj*GI_N+ii)*2]
201 let chh: i64 = C[(jj*GI_N+ii)*2+1]
202 let dxg: i64 = (GI_X0 + ii*GI_CS + GI_CS/2) - hx
203 let dyg: i64 = chh - hy
204 let dzg: i64 = (GI_Z0 + jj*GI_CS + GI_CS/2) - hz
205 let dl: i64 = wg_isqrt(dxg*dxg + dyg*dyg + dzg*dzg)
206 if dl > 60 {
207 var cosr: i64 = (nx*dxg + ny*dyg + nz*dzg)/dl // fx1024 cos at the receiver
208 if cosr > 0 {
209 let fall: i64 = GI_CS*WG_MAGIC_1024/(GI_CS + dl) // gentle distance falloff (fx1024)
210 let w: i64 = cosr*fall/WG_MAGIC_1024
211 sr = sr + (crad & 255)*w
212 sg = sg + ((crad >> 8) & 255)*w
213 sb = sb + ((crad >> 16) & 255)*w
214 wsum = wsum + WG_MAGIC_1024
215 }
216 }
217 }
218 oi = oi + 1
219 }
220 oj = oj + 1
221 }
222 if wsum == 0 { return 0 }
223 out[0] = sr/wsum
224 out[1] = sg/wsum
225 out[2] = sb/wsum
226 return 0
227}
228
229func wg_sky(dx: i64, dy: i64, dz: i64, Wp: *i64, oc: *i64) -> i64 {
230 var up: i64 = dy; if up<0 {up=0}
231 let zr: i64 = Wp[6]; let zg: i64 = Wp[7]; let zb: i64 = Wp[8]
232 let hr: i64 = Wp[9]; let hg: i64 = Wp[10]; let hb: i64 = Wp[11]
233 var r: i64 = hr + (zr-hr)*up/WG_MAGIC_1024; var g: i64 = hg + (zg-hg)*up/WG_MAGIC_1024; var b: i64 = hb + (zb-hb)*up/WG_MAGIC_1024
234 // sun glow + disk
235 let sd: i64 = (dx*Wp[0]+dy*Wp[1]+dz*Wp[2])/WG_MAGIC_1024
236 if sd > 800 {
237 let gl: i64 = (sd-800)*100/224
238 r = r + (Wp[3]-r)*gl/100; g = g + (Wp[4]-g)*gl/100; b = b + (Wp[5]-b)*gl/100
239 }
240 if sd > 1012 { r=Wp[3]; g=Wp[4]; b=Wp[5] }
241 // procedural clouds (fBm on the ray projected to a high plane)
242 if dy > 40 {
243 let px: i64 = dx*WG_MAGIC_3000/dy; let pz: i64 = dz*WG_MAGIC_3000/dy
244 let c: i64 = wg_fbm(px+WG_MAGIC_40000, pz+WG_MAGIC_40000, 7)
245 let thr: i64 = WG_MAGIC_1024 - Wp[13]*5
246 if c > thr {
247 let cf: i64 = wg_min((c-thr)*100/200, 100) * up/WG_MAGIC_1024
248 r = r + (238-r)*cf/100; g = g + (240-g)*cf/100; b = b + (242-b)*cf/100
249 }
250 }
251 oc[0]=wg_clamp(r,0,255); oc[1]=wg_clamp(g,0,255); oc[2]=wg_clamp(b,0,255)
252 return 0
253}
254
255// ★R1 DIVERSITY: BIOMES (census critic: "1 fBm biome family"). biome: 0 TEMPERATE (the original) · 1 DESERT
256// (sand/red-rock, no snow) · 2 ARCTIC (snow-dominant, ice water) · 3 AUTUMN (warm foliage). Same terrain
257// machinery; a biome = a palette + snowline row.
258// ★R2 GT ANNOTATIONS: the renderer emits CV ground truth alongside the image (their GroundTruthAnnotations
259// use-case): seg (0 sky / 1 terrain / 2 water), nrm (surface normal packed rgb: 128 + n*127/1024 per axis;
260// 0 for sky), depth (hit distance; BIG for sky). Occlusion boundaries derive from depth discontinuities.
261// ★R4 CAMERA RIG: cam = [dcamx, dcamy, dcamz, yaw] offsets/orientation over the auto terrain-following base
262// (cam 0/absent -> the original fixed +z view, byte-identical). A trajectory is a sequence of cam vectors ->
263// animation; reprojecting a pixel's world point through a second cam -> exact optical-flow GT (see wg_project).
264// render a world. Writes fb (WG_W*WG_H) + depth + seg + nrm.
265func wg_cam_base_y(seed: i64) -> i64 { return wg_max(WATER, wg_terrain_h(0,0,seed)) + WG_MAGIC_1050 }
266func worldgen_render_full(seed: i64, mode: i64, biome: i64, cam: *i64, fb: *i64, depth: *i64, seg: *i64, nrm: *i64) -> i64 {
267 let Wp: *i64 = sys_mmap(32*8) as *i64
268 wg_weather(mode, Wp)
269 if WG_GION == 1 { wg_gi_build(seed, biome, Wp) } // R3: refresh the bounce cache for this frame
270 let gio: *i64 = sys_mmap(32) as *i64
271 let oc: *i64 = sys_mmap(32) as *i64
272 let camx: i64 = cam[0]
273 let camy: i64 = wg_cam_base_y(seed) + cam[1]
274 let camz0: i64 = cam[2]
275 let yaw: i64 = cam[3]
276 let sy4: i64 = it_sin4096(yaw)
277 let cy4: i64 = it_cos4096(yaw)
278 var py: i64 = 0
279 while py < WG_H {
280 var px: i64 = 0
281 while px < WG_W {
282 let sxo: i64 = px - WG_W/2
283 let syo: i64 = (WG_H/2 - py) - WG_HORIZON
284 var dx: i64 = WG_FOCAL*sy4/WG_MAGIC_4096 + sxo*cy4/WG_MAGIC_4096
285 var dy: i64 = syo
286 var dz: i64 = WG_FOCAL*cy4/WG_MAGIC_4096 - sxo*sy4/WG_MAGIC_4096
287 let dl: i64 = wg_isqrt(dx*dx+dy*dy+dz*dz)
288 dx = dx*WG_MAGIC_1024/dl; dy = dy*WG_MAGIC_1024/dl; dz = dz*WG_MAGIC_1024/dl
289 // march the heightfield
290 var t: i64 = 200
291 var hit: i64 = 0
292 var hx: i64 = 0; var hy: i64 = 0; var hz: i64 = 0; var hh: i64 = 0
293 var step: i64 = 0
294 while step < 220 {
295 let wx: i64 = camx + dx*t/WG_MAGIC_1024
296 let wy: i64 = camy + dy*t/WG_MAGIC_1024
297 let wz: i64 = camz0 + dz*t/WG_MAGIC_1024
298 let th: i64 = wg_terrain_h(wx, wz, seed)
299 if wy < th { hit=1; hx=wx; hy=wy; hz=wz; hh=th; step=WG_MAGIC_9999 }
300 else { t = t + 55 + t/48; if t > WG_MAGIC_60000 { step=WG_MAGIC_9999 } }
301 if step != WG_MAGIC_9999 { step = step + 1 }
302 }
303 var r: i64 = 0; var g: i64 = 0; var b: i64 = 0
304 // water intercept (ray crosses y=WATER going down)
305 var twater: i64 = 0
306 if dy < 0 { twater = (WATER - camy)*WG_MAGIC_1024/dy }
307 let water_in_front: i64 = (twater > 0) & ((hit == 0) | (twater < t))
308 // decide surface
309 var surf: i64 = 0 // 0 sky, 1 terrain, 2 water
310 if hit == 1 { if hh >= WATER { surf = 1 } }
311 if (surf == 0) & (water_in_front == 1) { surf = 2 }
312 if (hit == 1) & (hh < WATER) & (water_in_front == 1) { surf = 2 }
313
314 var dpt: i64 = WG_MAGIC_2000000000
315 if surf == 1 { dpt = t }
316 if surf == 2 { dpt = twater }
317 depth[py*WG_W+px] = dpt
318 seg[py*WG_W+px] = surf // GT: 0 sky / 1 terrain / 2 water
319 var nrmv: i64 = 0 // GT normal (sky = 0)
320 if surf == 2 { nrmv = 128 + 255*256 + 128*WG_MAGIC_65536 } // water: straight up
321 nrm[py*WG_W+px] = nrmv
322
323 if surf == 1 {
324 // terrain normal
325 let e: i64 = 24
326 let hl: i64 = wg_terrain_h(hx-e, hz, seed); let hr2: i64 = wg_terrain_h(hx+e, hz, seed)
327 let hd: i64 = wg_terrain_h(hx, hz-e, seed); let hu: i64 = wg_terrain_h(hx, hz+e, seed)
328 var nx: i64 = (hl-hr2)*WG_MAGIC_1024/(2*e); var ny: i64 = WG_MAGIC_1024; var nz: i64 = (hd-hu)*WG_MAGIC_1024/(2*e)
329 // micro-relief bump from fine noise -> surface texture caught by the lighting (not a smooth blob)
330 let bb0: i64 = wg_vnoise((hx+6)*256/55, hz*256/55, seed+31) - wg_vnoise((hx-6)*256/55, hz*256/55, seed+31)
331 let bb1: i64 = wg_vnoise(hx*256/55, (hz+6)*256/55, seed+31) - wg_vnoise(hx*256/55, (hz-6)*256/55, seed+31)
332 nx = nx - bb0*5/100; nz = nz - bb1*5/100
333 let nl: i64 = wg_isqrt(nx*nx+ny*ny+nz*nz)
334 // GT normal (unit, packed rgb) -- written before shading consumes nx/ny/nz
335 if nl > 0 {
336 let ux2: i64 = nx*WG_MAGIC_1024/nl
337 let uy2: i64 = ny*WG_MAGIC_1024/nl
338 let uz2: i64 = nz*WG_MAGIC_1024/nl
339 nrm[py*WG_W+px] = (128 + ux2*127/WG_MAGIC_1024) + (128 + uy2*127/WG_MAGIC_1024)*256 + (128 + uz2*127/WG_MAGIC_1024)*WG_MAGIC_65536
340 }
341 if nl>0 { nx=nx*WG_MAGIC_1024/nl; ny=ny*WG_MAGIC_1024/nl; nz=nz*WG_MAGIC_1024/nl }
342 var nd: i64 = (nx*Wp[0]+ny*Wp[1]+nz*Wp[2])/WG_MAGIC_1024 // fx1024 Lambert (0..WG_MAGIC_1024)
343 if nd<0 {nd=0}
344 // ★P3 SOFT SHADOWS (penumbra): the shared sun-march helper (also feeds the GI cache) --
345 // edges soften with occluder distance; only DIRECT light is occluded (ambient stays).
346 let pen: i64 = wg_sun_pen(seed, hx, hy, hz, Wp)
347 // ★P3 r2 SKY-VISIBILITY ambient (cheap GI): surrounding ridges block skylight -> valleys and
348 // hollows receive less ambient. Horizon slope sampled 4 directions x 3 distances; sv 0..1024.
349 var slmax: i64 = 0
350 var sd2: i64 = 0
351 while sd2 < 4 {
352 var ddx: i64 = 0
353 var ddz: i64 = 0
354 if sd2 == 0 { ddx = 1 }
355 if sd2 == 1 { ddx = 0-1 }
356 if sd2 == 2 { ddz = 1 }
357 if sd2 == 3 { ddz = 0-1 }
358 var dq: i64 = 200
359 while dq <= WG_MAGIC_1800 {
360 let th2: i64 = wg_terrain_h(hx + ddx*dq, hz + ddz*dq, seed)
361 let sl: i64 = (th2 - hy) * WG_MAGIC_1024 / dq
362 if sl > slmax { slmax = sl }
363 dq = dq * 3
364 }
365 sd2 = sd2 + 1
366 }
367 if slmax > WG_MAGIC_1024 { slmax = WG_MAGIC_1024 }
368 var sv: i64 = WG_MAGIC_1024 - slmax*6/10
369 if sv < 420 { sv = 420 }
370 let lit: i64 = 42*sv/WG_MAGIC_1024 + nd*82*pen/WG_MAGIC_1048576 // sky-vis ambient + penumbra-scaled direct
371 // base colour by BIOME, with per-location vegetation variation
372 let nv2: i64 = wg_vnoise(hx*256/WG_MAGIC_1500, hz*256/WG_MAGIC_1500, seed+99)
373 var cr: i64 = 60 + nv2*44/WG_MAGIC_65536; var cg: i64 = 94 + nv2*50/WG_MAGIC_65536; var cb: i64 = 42 + nv2*22/WG_MAGIC_65536
374 var rockr: i64 = 112; var rockg: i64 = 101; var rockb: i64 = 92
375 var snowline: i64 = SNOW
376 var snowon: i64 = 1
377 if biome == 1 { // DESERT: sand base, red rock, no snow
378 cr = 188 + nv2*36/WG_MAGIC_65536; cg = 154 + nv2*30/WG_MAGIC_65536; cb = 104 + nv2*18/WG_MAGIC_65536
379 rockr = 152; rockg = 96; rockb = 66
380 snowon = 0
381 }
382 if biome == 2 { // ARCTIC: snow base, blue-grey rock
383 cr = 212 + nv2*24/WG_MAGIC_65536; cg = 222 + nv2*20/WG_MAGIC_65536; cb = 234 + nv2*14/WG_MAGIC_65536
384 rockr = 96; rockg = 106; rockb = 120
385 snowline = 300
386 }
387 if biome == 3 { // AUTUMN: warm mixed foliage
388 cr = 128 + nv2*70/WG_MAGIC_65536; cg = 84 + nv2*44/WG_MAGIC_65536; cb = 30 + nv2*16/WG_MAGIC_65536
389 rockr = 118; rockg = 100; rockb = 86
390 }
391 // alpine: higher -> browner (vegetation thins out); skip where base is already snow/sand
392 if biome == 0 { if hh > WG_MAGIC_1350 { let m: i64 = wg_min((hh-WG_MAGIC_1350)*100/WG_MAGIC_1100, 100); cr=cr+(124-cr)*m/100; cg=cg+(110-cg)*m/100; cb=cb+(94-cb)*m/100 } }
393 if biome == 3 { if hh > WG_MAGIC_1350 { let m: i64 = wg_min((hh-WG_MAGIC_1350)*100/WG_MAGIC_1100, 100); cr=cr+(124-cr)*m/100; cg=cg+(104-cg)*m/100; cb=cb+(88-cb)*m/100 } }
394 if ny < 640 { cr=rockr; cg=rockg; cb=rockb } // steep -> exposed rock
395 if hh < WATER+130 { cr=198; cg=186; cb=152 } // shoreline sand
396 if snowon == 1 { if (hh > snowline) & (ny > 600) { cr=234; cg=240; cb=248 } } // snow: high AND gentle
397 // fine surface speckle (grass blades / dirt / rock grain)
398 let tex2: i64 = wg_vnoise(hx*256/24, hz*256/24, seed+53)
399 let tv: i64 = (tex2-WG_MAGIC_32768)*26/WG_MAGIC_65536
400 cr = cr + tv; cg = cg + tv*11/10; cb = cb + tv*6/10
401 r = cr*lit/100; g = cg*lit/100; b = cb*lit/100
402 // ★R3 GI: gather single-bounce irradiance from the cache; couple through the receiver albedo
403 // (green light on green grass reinforces; green light on snow shows as a green tint -- physical)
404 wg_gi_gather(hx, hy, hz, nx, ny, nz, gio)
405 r = r + cr*gio[0]*3/512
406 g = g + cg*gio[1]*3/512
407 b = b + cb*gio[2]*3/512
408 b = b + (WG_MAGIC_1024-pen)*9/WG_MAGIC_1024 // shade is sky-lit -> subtle cool tint
409 // atmospheric fog toward horizon-sky by distance
410 let fog: i64 = wg_min(t*Wp[12]/WG_MAGIC_60000, 100)
411 r = r + (Wp[9]-r)*fog/100; g = g + (Wp[10]-g)*fog/100; b = b + (Wp[11]-b)*fog/100
412 }
413 if surf == 2 {
414 // water: deep blue blended with sky reflection + sun glint (arctic = icy pale)
415 wg_sky(dx, 0-dy, dz, Wp, oc) // reflected sky (mirror y)
416 var dwr: i64 = 24; var dwg: i64 = 74; var dwb: i64 = 116
417 if biome == 2 { dwr = 118; dwg = 160; dwb = 190 }
418 if biome == 1 { dwr = 30; dwg = 96; dwb = 104 }
419 r = (dwr + oc[0])/2; g = (dwg + oc[1])/2; b = (dwb + oc[2])/2
420 let sd: i64 = (dx*Wp[0]-dy*Wp[1]+dz*Wp[2])/WG_MAGIC_1024
421 if sd > 990 { r=wg_min(r+120,255); g=wg_min(g+110,255); b=wg_min(b+90,255) }
422 let fog: i64 = wg_min(twater*Wp[12]/WG_MAGIC_60000, 100)
423 r = r + (Wp[9]-r)*fog/100; g = g + (Wp[10]-g)*fog/100; b = b + (Wp[11]-b)*fog/100
424 }
425 if surf == 0 { wg_sky(dx, dy, dz, Wp, oc); r=oc[0]; g=oc[1]; b=oc[2] }
426 fb[py*WG_W+px] = wg_clamp(r,0,255) + wg_clamp(g,0,255)*256 + wg_clamp(b,0,255)*WG_MAGIC_65536
427 px = px + 1
428 }
429 py = py + 1
430 }
431 return 0
432}
433// ★R4 camera helpers (the inverse pair the flow GT reuses, so gate + renderer share exact math):
434// wg_ray = the normalized fx1024 ray direction for a pixel (depends only on pixel + yaw);
435// wg_project = screen coords of a world point through cam=[dx,dy,dz,yaw] (returns -1 in out[2] if behind).
436func wg_ray(px: i64, py: i64, yaw: i64, out: *i64) -> i64 {
437 let sy4: i64 = it_sin4096(yaw)
438 let cy4: i64 = it_cos4096(yaw)
439 let sxo: i64 = px - WG_W/2
440 let syo: i64 = (WG_H/2 - py) - WG_HORIZON
441 var dx: i64 = WG_FOCAL*sy4/WG_MAGIC_4096 + sxo*cy4/WG_MAGIC_4096
442 var dy: i64 = syo
443 var dz: i64 = WG_FOCAL*cy4/WG_MAGIC_4096 - sxo*sy4/WG_MAGIC_4096
444 let dl: i64 = wg_isqrt(dx*dx+dy*dy+dz*dz)
445 if dl > 0 { dx = dx*WG_MAGIC_1024/dl; dy = dy*WG_MAGIC_1024/dl; dz = dz*WG_MAGIC_1024/dl }
446 out[0]=dx; out[1]=dy; out[2]=dz
447 return 0
448}
449func wg_project(seed: i64, cam: *i64, Px: i64, Py: i64, Pz: i64, out: *i64) -> i64 {
450 let camx: i64 = cam[0]
451 let camy: i64 = wg_cam_base_y(seed) + cam[1]
452 let camz0: i64 = cam[2]
453 let yaw: i64 = cam[3]
454 let sy4: i64 = it_sin4096(yaw)
455 let cy4: i64 = it_cos4096(yaw)
456 let rx: i64 = Px - camx
457 let ry: i64 = Py - camy
458 let rz: i64 = Pz - camz0
459 let zc: i64 = (rx*sy4 + rz*cy4)/WG_MAGIC_4096 // forward distance
460 out[2] = 0 - 1
461 if zc <= 16 { return 0 } // behind the camera
462 let xc: i64 = (rx*cy4 - rz*sy4)/WG_MAGIC_4096 // right offset
463 out[0] = WG_W/2 + WG_FOCAL*xc/zc
464 out[1] = WG_H/2 - WG_HORIZON - WG_FOCAL*ry/zc
465 out[2] = zc
466 return 0
467}
468
469// back-compat wrappers: fixed camera / no annotations / temperate biome / no depth buffer
470func worldgen_render_bd(seed: i64, mode: i64, biome: i64, fb: *i64, depth: *i64) -> i64 {
471 let s: *i64 = sys_mmap(WG_W*WG_H*8) as *i64
472 let n: *i64 = sys_mmap(WG_W*WG_H*8) as *i64
473 let cam: *i64 = sys_mmap(4*8) as *i64 // [0,0,0,0] = the original fixed +z camera (identical)
474 return worldgen_render_full(seed, mode, biome, cam, fb, depth, s, n)
475}
476func worldgen_render_d(seed: i64, mode: i64, fb: *i64, depth: *i64) -> i64 { return worldgen_render_bd(seed, mode, 0, fb, depth) }
477func worldgen_render(seed: i64, mode: i64, fb: *i64) -> i64 {
478 let d: *i64 = sys_mmap(WG_W*WG_H*8) as *i64
479 worldgen_render_bd(seed, mode, 0, fb, d)
480 return 0
481}
482func wg_w() -> i64 { return WG_W }
483func wg_h() -> i64 { return WG_H }