nx_worldgen.nx source
↩ module page · 574 lines · 32155 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"
9import "nx_vecmath.nx"
10const WG_MAGIC_374761393: i64 = 374761393
11const WG_MAGIC_668265263: i64 = 668265263
12const WG_MAGIC_65536: i64 = 65536
13const WG_MAGIC_4000: i64 = 4000
14const WG_MAGIC_1024: i64 = 1024
15const WG_MAGIC_32768: i64 = 32768
16const WG_MAGIC_2900: i64 = 2900
17const WG_MAGIC_1048576: i64 = 1048576
18const WG_MAGIC_3000: i64 = 3000
19const WG_MAGIC_40000: i64 = 40000
20const WG_MAGIC_1050: i64 = 1050
21const WG_MAGIC_4096: i64 = 4096
22const WG_MAGIC_9999: i64 = 9999
23const WG_MAGIC_60000: i64 = 60000
24const WG_MAGIC_2000000000: i64 = 2000000000
25const WG_MAGIC_1800: i64 = 1800
26const WG_MAGIC_1500: i64 = 1500
27const WG_MAGIC_1350: i64 = 1350
28const WG_MAGIC_1100: i64 = 1100
29
30const WG_W: i64 = 1280 // 2x render -> the page downscales it = free anti-aliasing
31const WG_H: i64 = 768
32const WG_FOCAL: i64 = 720
33const WG_HORIZON: i64 = 92 // shifts the horizon down (look slightly over the land)
34const WATER: i64 = 0 // water level (world units)
35const HSCALE: i64 = 4600
36const SNOW: i64 = 2620
37
38// RETIRED ONTO THE SHARED OWNER 2026-08-24 (was a private Newton floor-sqrt; vm_isqrt is gate-proven exact).
39func wg_isqrt(v: i64) -> i64 { return vm_isqrt(v) }
40func wg_abs(v: i64) -> i64 { if v<0 {return 0-v} return v }
41func wg_min(a: i64, b: i64) -> i64 { if a<b {return a} return b }
42func wg_max(a: i64, b: i64) -> i64 { if a>b {return a} return b }
43func wg_clamp(v: i64, lo: i64, hi: i64) -> i64 { if v<lo {return lo} if v>hi {return hi} return v }
44
45func wg_hash(ix: i64, iz: i64, seed: i64) -> i64 {
46 var h: i64 = (ix + seed*7)*WG_MAGIC_374761393 + (iz - seed*3)*WG_MAGIC_668265263 + ix*iz*97 + seed*13
47 if h < 0 { h = 0 - h }
48 return h % WG_MAGIC_65536
49}
50func wg_smooth(t: i64) -> i64 { return t*t*(768 - 2*t)/WG_MAGIC_65536 } // smoothstep, t in 0..256 -> 0..256
51func wg_vnoise(x256: i64, z256: i64, seed: i64) -> i64 {
52 let ix: i64 = x256 >> 8; let iz: i64 = z256 >> 8
53 let fx: i64 = x256 & 255; let fz: i64 = z256 & 255
54 let sx: i64 = wg_smooth(fx); let sz: i64 = wg_smooth(fz)
55 let v00: i64 = wg_hash(ix,iz,seed); let v10: i64 = wg_hash(ix+1,iz,seed)
56 let v01: i64 = wg_hash(ix,iz+1,seed); let v11: i64 = wg_hash(ix+1,iz+1,seed)
57 let a: i64 = v00 + (v10-v00)*sx/256
58 let b: i64 = v01 + (v11-v01)*sx/256
59 return a + (b-a)*sz/256
60}
61// fractal Brownian motion -> 0..1024
62func wg_fbm(wx: i64, wz: i64, seed: i64) -> i64 {
63 let cx: i64 = wx*256/WG_MAGIC_4000; let cz: i64 = wz*256/WG_MAGIC_4000
64 var sum: i64 = 0; var amp: i64 = 512; var norm: i64 = 0; var o: i64 = 0
65 while o < 5 {
66 sum = sum + wg_vnoise(cx << o, cz << o, seed + o*131) * amp / WG_MAGIC_65536
67 norm = norm + amp; amp = amp/2; o = o + 1
68 }
69 return sum*WG_MAGIC_1024/norm
70}
71func wg_terrain_h(wx: i64, wz: i64, seed: i64) -> i64 { return (wg_fbm(wx,wz,seed) - 402) * HSCALE / WG_MAGIC_1024 }
72
73// ---- PG22 (2026-09-06): THE VOLUMETRIC WEATHER FIELD -- a cloud is a 3D density, never a sheet ---------------------
74// wg_cloud_volume(wx, wy, wz, t, cover_q10, wind_dx_q10, wind_dz_q10, seed) -> density Q10 in [0, 1024]. The volume is
75// DRIVEN BY THE WEATHER FIELD THROUGH ITS OUTPUTS: cover_q10 comes from wg_cover_from_weather over the field's precip,
76// visibility and fog params (the gate composes nx_weather_pattern and proves CLEAR < OVERCAST < RAIN), and the field's
77// wind vector advects the volume in time -- no wind, no motion, which the gate holds as a neg-control. Shape is 3D
78// value-noise fBm over WG_CLOUD_CELL-unit cells, gated by a vertical envelope between WG_CLOUD_BASE and WG_CLOUD_TOP
79// and by the coverage threshold thr = 1024 - cover: density = clamp((fbm - thr) * WG_CLOUD_GAIN) * envelope. The
80// 2D sheet the CPU tier keeps drawing is wg_cloud_sheet, the incumbent sky expression BY NAME (the renderer below
81// calls it), so the fallback and the volume are one coverage law. Integer, seeded, bit-exact. This file's closure is
82// deliberately unchanged (no import): the weather field is an INPUT, so the volume compiles on every tree the field
83// does not, and a world without a weather field can still pass a constant cover.
84const WG_CLOUD_BASE: i64 = 5200 // world units; terrain tops out near HSCALE, the deck floats above it
85const WG_CLOUD_TOP: i64 = 8400
86const WG_CLOUD_RAMP_LO: i64 = 400 // density rises over this many units above the base
87const WG_CLOUD_RAMP_HI: i64 = 1200 // and falls over this many units below the top (anvils thin out)
88const WG_CLOUD_CELL: i64 = 640 // one noise cell in world units at octave 0
89const WG_CLOUD_OCTAVES: i64 = 4
90const WG_CLOUD_GAIN: i64 = 6 // slope of the coverage threshold
91const WG_CLOUD_SEED_SALT: i64 = 7 // the sheet's seed, shared so volume and sheet agree on which sky
92const WG_CLOUD_ADVECT: i64 = 12 // world units per time unit at wind Q10 = 1024
93const WG_CLOUD_ORIGIN: i64 = 1048576 // positive offset so >> and & see positive coordinates
94const WG_COVER_LOSTVIS_W: i64 = 4 // coverage weights over the Q14 field params: lost visibility dominates
95const WG_COVER_PRECIP_W: i64 = 1
96const WG_COVER_FOG_W: i64 = 1
97const WG_SHEET_THR_GAIN: i64 = 5 // the incumbent sheet threshold: 1024 - cloud*5
98func wg_hash3(ix: i64, iy: i64, iz: i64, seed: i64) -> i64 {
99 var h: i64 = (ix + seed*7)*WG_MAGIC_374761393 + (iy + seed*11)*WG_MAGIC_668265263 + (iz - seed*3)*WG_MAGIC_374761393 + ix*iz*97 + iy*iz*89 + ix*iy*83 + seed*13
100 if h < 0 { h = 0 - h }
101 return h % WG_MAGIC_65536
102}
103func wg_vnoise3(x256: i64, y256: i64, z256: i64, seed: i64) -> i64 {
104 let ix: i64 = x256 >> 8; let iy: i64 = y256 >> 8; let iz: i64 = z256 >> 8
105 let sx: i64 = wg_smooth(x256 & 255); let sy: i64 = wg_smooth(y256 & 255); let sz: i64 = wg_smooth(z256 & 255)
106 let v000: i64 = wg_hash3(ix,iy,iz,seed); let v100: i64 = wg_hash3(ix+1,iy,iz,seed)
107 let v010: i64 = wg_hash3(ix,iy+1,iz,seed); let v110: i64 = wg_hash3(ix+1,iy+1,iz,seed)
108 let v001: i64 = wg_hash3(ix,iy,iz+1,seed); let v101: i64 = wg_hash3(ix+1,iy,iz+1,seed)
109 let v011: i64 = wg_hash3(ix,iy+1,iz+1,seed); let v111: i64 = wg_hash3(ix+1,iy+1,iz+1,seed)
110 let a0: i64 = v000 + (v100-v000)*sx/256
111 let a1: i64 = v010 + (v110-v010)*sx/256
112 let b0: i64 = v001 + (v101-v001)*sx/256
113 let b1: i64 = v011 + (v111-v011)*sx/256
114 let a: i64 = a0 + (a1-a0)*sy/256
115 let b: i64 = b0 + (b1-b0)*sy/256
116 return a + (b-a)*sz/256
117}
118// 3D fractal Brownian motion -> 0..1024, the shape of wg_fbm lifted one dimension
119func wg_fbm3(x256: i64, y256: i64, z256: i64, seed: i64) -> i64 {
120 var sum: i64 = 0; var amp: i64 = 512; var norm: i64 = 0; var o: i64 = 0
121 while o < WG_CLOUD_OCTAVES {
122 sum = sum + wg_vnoise3(x256 << o, y256 << o, z256 << o, seed + o*131) * amp / WG_MAGIC_65536
123 norm = norm + amp; amp = amp/2; o = o + 1
124 }
125 return sum*WG_MAGIC_1024/norm
126}
127// the weather field's params (Q14, q = the field's own Q) -> cloud cover Q10: lost visibility, precipitation and fog
128func wg_cover_from_weather(precip_q14: i64, visibility_q14: i64, fog_q14: i64, q: i64) -> i64 {
129 if q <= 0 { return 0 }
130 var lost: i64 = q - visibility_q14
131 if lost < 0 { lost = 0 }
132 let c14: i64 = lost*WG_COVER_LOSTVIS_W + precip_q14*WG_COVER_PRECIP_W + fog_q14*WG_COVER_FOG_W
133 return wg_clamp(c14*WG_MAGIC_1024/q, 0, WG_MAGIC_1024)
134}
135// the vertical envelope Q10: 0 below the base and above the top, ramping in over RAMP_LO and out over RAMP_HI
136func wg_cloud_envelope(wy: i64) -> i64 {
137 if wy <= WG_CLOUD_BASE { return 0 }
138 if wy >= WG_CLOUD_TOP { return 0 }
139 let up: i64 = wg_min((wy - WG_CLOUD_BASE)*WG_MAGIC_1024/WG_CLOUD_RAMP_LO, WG_MAGIC_1024)
140 let dn: i64 = wg_min((WG_CLOUD_TOP - wy)*WG_MAGIC_1024/WG_CLOUD_RAMP_HI, WG_MAGIC_1024)
141 return wg_min(up, dn)
142}
143func wg_cloud_volume(wx: i64, wy: i64, wz: i64, t: i64, cover_q10: i64, wind_dx_q10: i64, wind_dz_q10: i64, seed: i64) -> i64 {
144 if cover_q10 <= 0 { return 0 }
145 let env: i64 = wg_cloud_envelope(wy)
146 if env <= 0 { return 0 }
147 let ax: i64 = wx - wind_dx_q10*t*WG_CLOUD_ADVECT/WG_MAGIC_1024
148 let az: i64 = wz - wind_dz_q10*t*WG_CLOUD_ADVECT/WG_MAGIC_1024
149 let x256: i64 = (ax + WG_CLOUD_ORIGIN)*256/WG_CLOUD_CELL
150 let y256: i64 = (wy + WG_CLOUD_ORIGIN)*256/WG_CLOUD_CELL
151 let z256: i64 = (az + WG_CLOUD_ORIGIN)*256/WG_CLOUD_CELL
152 let shape: i64 = wg_fbm3(x256, y256, z256, seed + WG_CLOUD_SEED_SALT)
153 let thr: i64 = WG_MAGIC_1024 - cover_q10
154 let d: i64 = wg_clamp((shape - thr)*WG_CLOUD_GAIN, 0, WG_MAGIC_1024)
155 return d*env/WG_MAGIC_1024
156}
157// the CPU tier's sheet: the incumbent sky cloud, positive where the sheet is cloud (fbm minus the coverage threshold)
158func wg_cloud_sheet(px: i64, pz: i64, cloud: i64) -> i64 {
159 let c: i64 = wg_fbm(px+WG_MAGIC_40000, pz+WG_MAGIC_40000, WG_CLOUD_SEED_SALT)
160 return c - (WG_MAGIC_1024 - cloud*WG_SHEET_THR_GAIN)
161}
162
163// weather -> params array [sundx,sundy,sundz(fx1024 unit), sunr,sung,sunb, skyzr,skyzg,skyzb, skyhr,skyhg,skyhb, fog, cloud]
164func wg_weather(mode: i64, W: *i64) -> i64 {
165 // default CLEAR day: sun upper-right
166 W[0]=520; W[1]=620; W[2]=560; W[3]=255; W[4]=248; W[5]=228
167 W[6]=64; W[7]=120; W[8]=210; W[9]=176; W[10]=205; W[11]=232; W[12]=44; W[13]=42
168 if mode == 1 { // SUNSET: low warm sun, warm sky
169 W[0]=780; W[1]=180; W[2]=560; W[3]=255; W[4]=170; W[5]=96
170 W[6]=40; W[7]=70; W[8]=150; W[9]=250; W[10]=150; W[11]=96; W[12]=60; W[13]=54
171 }
172 if mode == 2 { // OVERCAST: high diffuse, grey sky, heavy fog+cloud
173 W[0]=300; W[1]=760; W[2]=400; W[3]=210; W[4]=212; W[5]=214
174 W[6]=150; W[7]=158; W[8]=168; W[9]=196; W[10]=200; W[11]=205; W[12]=90; W[13]=110
175 }
176 let l: i64 = wg_isqrt(W[0]*W[0]+W[1]*W[1]+W[2]*W[2])
177 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 }
178 return 0
179}
180
181// sun penumbra at a surface point (the soft-shadow march, shared by the pixel loop AND the GI cache): march
182// toward the sun tracking min(clearance/dist); 0 = fully shadowed .. 1024 = fully lit. Wp = weather (sun dir).
183func wg_sun_pen(seed: i64, hx: i64, hy: i64, hz: i64, Wp: *i64) -> i64 {
184 var pen: i64 = WG_MAGIC_1024
185 var sh2: i64 = 300
186 var ss: i64 = 0
187 while ss < 30 {
188 let qx: i64 = hx + Wp[0]*sh2/WG_MAGIC_1024
189 let qy: i64 = hy + Wp[1]*sh2/WG_MAGIC_1024
190 let qz: i64 = hz + Wp[2]*sh2/WG_MAGIC_1024
191 let clr: i64 = qy - wg_terrain_h(qx, qz, seed) + 10
192 if clr < 0 { pen = 0; ss = 999 }
193 else {
194 let p2: i64 = clr*WG_MAGIC_32768/sh2
195 if p2 < pen { pen = p2 }
196 sh2 = sh2 + 160 + sh2/9
197 if qy > WG_MAGIC_2900 { ss = 999 }
198 }
199 if ss != 999 { ss = ss + 1 }
200 }
201 if pen > WG_MAGIC_1024 { pen = WG_MAGIC_1024 }
202 return pen
203}
204
205// ★R3 GI: a SINGLE-BOUNCE IRRADIANCE CACHE (the classic technique, integer): a coarse world grid stores each
206// cell's SUN-LIT outgoing radiance (albedo x direct light); pixels gather from nearby cells with cosine + falloff
207// weighting -> coloured bounce light (sunlit grass bleeds green into shade). HONEST LABEL: one bounce, no
208// bounce-visibility test, coarse cells -- an approximation, not path tracing. wg_set_gi(0) disables (byte-
209// identical off path).
210const GI_N: i64 = 96 // cells per axis
211const GI_CS: i64 = 500 // world units per cell
212const GI_X0: i64 = 0 - 24000 // grid origin (covers the visible near field)
213const GI_Z0: i64 = 0
214static WG_GION: i64
215static WG_GIC: i64 // cache ptr: GI_N*GI_N cells x 2 i64 {packed rgb radiance, height}
216func wg_set_gi(on: i64) -> i64 { WG_GION = on; return 0 }
217func wg_gi_build(seed: i64, biome: i64, Wp: *i64) -> i64 {
218 if WG_GIC == 0 { WG_GIC = sys_mmap(GI_N*GI_N*2*8) as i64 }
219 let C: *i64 = WG_GIC as *i64
220 var j: i64 = 0
221 while j < GI_N {
222 var i: i64 = 0
223 while i < GI_N {
224 let wx: i64 = GI_X0 + i*GI_CS + GI_CS/2
225 let wz: i64 = GI_Z0 + j*GI_CS + GI_CS/2
226 let hh: i64 = wg_terrain_h(wx, wz, seed)
227 // cell normal (coarse)
228 let e: i64 = 120
229 let hl: i64 = wg_terrain_h(wx-e, wz, seed); let hr2: i64 = wg_terrain_h(wx+e, wz, seed)
230 let hd: i64 = wg_terrain_h(wx, wz-e, seed); let hu: i64 = wg_terrain_h(wx, wz+e, seed)
231 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)
232 let nl: i64 = wg_isqrt(nx*nx+ny*ny+nz*nz)
233 if nl > 0 { nx=nx*WG_MAGIC_1024/nl; ny=ny*WG_MAGIC_1024/nl; nz=nz*WG_MAGIC_1024/nl }
234 // cell albedo (low-frequency biome palette: grass/rock/sand/snow by height+slope, no speckle)
235 var cr: i64 = 70; var cg: i64 = 104; var cb: i64 = 50
236 if biome == 1 { cr=196; cg=160; cb=110 }
237 if biome == 2 { cr=214; cg=224; cb=236 }
238 if biome == 3 { cr=150; cg=104; cb=44 }
239 var snowline: i64 = SNOW
240 if biome == 2 { snowline = 300 }
241 if ny < 640 { cr=118; cg=104; cb=94 }
242 if biome != 1 { if hh > snowline { if ny > 600 { cr=234; cg=240; cb=248 } } }
243 if hh < WATER { cr=24; cg=74; cb=116 }
244 // direct sun radiance at the cell (Lambert x penumbra), plus a small ambient floor
245 var nd: i64 = (nx*Wp[0]+ny*Wp[1]+nz*Wp[2])/WG_MAGIC_1024
246 if nd < 0 { nd = 0 }
247 var pen: i64 = WG_MAGIC_1024
248 if hh >= WATER { pen = wg_sun_pen(seed, wx, hh+14, wz, Wp) }
249 let lit: i64 = 20 + nd*95*pen/WG_MAGIC_1048576
250 var rr: i64 = cr*lit/100
251 var rg: i64 = cg*lit/100
252 var rb: i64 = cb*lit/100
253 if rr > 255 { rr = 255 }
254 if rg > 255 { rg = 255 }
255 if rb > 255 { rb = 255 }
256 C[(j*GI_N+i)*2] = rr + rg*256 + rb*WG_MAGIC_65536
257 C[(j*GI_N+i)*2+1] = hh
258 i = i + 1
259 }
260 j = j + 1
261 }
262 return 0
263}
264// gather bounce irradiance at a surface point with unit normal (nx,ny,nz fx1024). out[0..2] = rgb irradiance.
265func wg_gi_gather(hx: i64, hy: i64, hz: i64, nx: i64, ny: i64, nz: i64, out: *i64) -> i64 {
266 out[0]=0; out[1]=0; out[2]=0
267 if WG_GION == 0 { return 0 }
268 if WG_GIC == 0 { return 0 }
269 let C: *i64 = WG_GIC as *i64
270 let ci: i64 = (hx - GI_X0) / GI_CS
271 let cj: i64 = (hz - GI_Z0) / GI_CS
272 if ci < 2 { return 0 }
273 if cj < 2 { return 0 }
274 if ci > GI_N-3 { return 0 }
275 if cj > GI_N-3 { return 0 }
276 var sr: i64 = 0
277 var sg: i64 = 0
278 var sb: i64 = 0
279 var wsum: i64 = 0
280 var oj: i64 = 0 - 2
281 while oj <= 2 {
282 var oi: i64 = 0 - 2
283 while oi <= 2 {
284 var ring: i64 = oi
285 if ring < 0 { ring = 0 - ring }
286 var aj: i64 = oj
287 if aj < 0 { aj = 0 - aj }
288 if aj > ring { ring = aj }
289 if ring >= 1 { // exclude the self cell; use rings 1-2
290 let ii: i64 = ci + oi
291 let jj: i64 = cj + oj
292 let crad: i64 = C[(jj*GI_N+ii)*2]
293 let chh: i64 = C[(jj*GI_N+ii)*2+1]
294 let dxg: i64 = (GI_X0 + ii*GI_CS + GI_CS/2) - hx
295 let dyg: i64 = chh - hy
296 let dzg: i64 = (GI_Z0 + jj*GI_CS + GI_CS/2) - hz
297 let dl: i64 = wg_isqrt(dxg*dxg + dyg*dyg + dzg*dzg)
298 if dl > 60 {
299 var cosr: i64 = (nx*dxg + ny*dyg + nz*dzg)/dl // fx1024 cos at the receiver
300 if cosr > 0 {
301 let fall: i64 = GI_CS*WG_MAGIC_1024/(GI_CS + dl) // gentle distance falloff (fx1024)
302 let w: i64 = cosr*fall/WG_MAGIC_1024
303 sr = sr + (crad & 255)*w
304 sg = sg + ((crad >> 8) & 255)*w
305 sb = sb + ((crad >> 16) & 255)*w
306 wsum = wsum + WG_MAGIC_1024
307 }
308 }
309 }
310 oi = oi + 1
311 }
312 oj = oj + 1
313 }
314 if wsum == 0 { return 0 }
315 out[0] = sr/wsum
316 out[1] = sg/wsum
317 out[2] = sb/wsum
318 return 0
319}
320
321func wg_sky(dx: i64, dy: i64, dz: i64, Wp: *i64, oc: *i64) -> i64 {
322 var up: i64 = dy; if up<0 {up=0}
323 let zr: i64 = Wp[6]; let zg: i64 = Wp[7]; let zb: i64 = Wp[8]
324 let hr: i64 = Wp[9]; let hg: i64 = Wp[10]; let hb: i64 = Wp[11]
325 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
326 // sun glow + disk
327 let sd: i64 = (dx*Wp[0]+dy*Wp[1]+dz*Wp[2])/WG_MAGIC_1024
328 if sd > 800 {
329 let gl: i64 = (sd-800)*100/224
330 r = r + (Wp[3]-r)*gl/100; g = g + (Wp[4]-g)*gl/100; b = b + (Wp[5]-b)*gl/100
331 }
332 if sd > 1012 { r=Wp[3]; g=Wp[4]; b=Wp[5] }
333 // procedural clouds (fBm on the ray projected to a high plane)
334 if dy > 40 {
335 let px: i64 = dx*WG_MAGIC_3000/dy; let pz: i64 = dz*WG_MAGIC_3000/dy
336 let cs: i64 = wg_cloud_sheet(px, pz, Wp[13]) // PG22: the sheet by name, one coverage law with the volume
337 if cs > 0 {
338 let cf: i64 = wg_min(cs*100/200, 100) * up/WG_MAGIC_1024
339 r = r + (238-r)*cf/100; g = g + (240-g)*cf/100; b = b + (242-b)*cf/100
340 }
341 }
342 oc[0]=wg_clamp(r,0,255); oc[1]=wg_clamp(g,0,255); oc[2]=wg_clamp(b,0,255)
343 return 0
344}
345
346// ★R1 DIVERSITY: BIOMES (census critic: "1 fBm biome family"). biome: 0 TEMPERATE (the original) · 1 DESERT
347// (sand/red-rock, no snow) · 2 ARCTIC (snow-dominant, ice water) · 3 AUTUMN (warm foliage). Same terrain
348// machinery; a biome = a palette + snowline row.
349// ★R2 GT ANNOTATIONS: the renderer emits CV ground truth alongside the image (their GroundTruthAnnotations
350// use-case): seg (0 sky / 1 terrain / 2 water), nrm (surface normal packed rgb: 128 + n*127/1024 per axis;
351// 0 for sky), depth (hit distance; BIG for sky). Occlusion boundaries derive from depth discontinuities.
352// ★R4 CAMERA RIG: cam = [dcamx, dcamy, dcamz, yaw] offsets/orientation over the auto terrain-following base
353// (cam 0/absent -> the original fixed +z view, byte-identical). A trajectory is a sequence of cam vectors ->
354// animation; reprojecting a pixel's world point through a second cam -> exact optical-flow GT (see wg_project).
355// render a world. Writes fb (WG_W*WG_H) + depth + seg + nrm.
356func wg_cam_base_y(seed: i64) -> i64 { return wg_max(WATER, wg_terrain_h(0,0,seed)) + WG_MAGIC_1050 }
357func worldgen_render_full(seed: i64, mode: i64, biome: i64, cam: *i64, fb: *i64, depth: *i64, seg: *i64, nrm: *i64) -> i64 {
358 let Wp: *i64 = sys_mmap(32*8) as *i64
359 wg_weather(mode, Wp)
360 if WG_GION == 1 { wg_gi_build(seed, biome, Wp) } // R3: refresh the bounce cache for this frame
361 let gio: *i64 = sys_mmap(32) as *i64
362 let oc: *i64 = sys_mmap(32) as *i64
363 let camx: i64 = cam[0]
364 let camy: i64 = wg_cam_base_y(seed) + cam[1]
365 let camz0: i64 = cam[2]
366 let yaw: i64 = cam[3]
367 let sy4: i64 = it_sin4096(yaw)
368 let cy4: i64 = it_cos4096(yaw)
369 var py: i64 = 0
370 while py < WG_H {
371 var px: i64 = 0
372 while px < WG_W {
373 let sxo: i64 = px - WG_W/2
374 let syo: i64 = (WG_H/2 - py) - WG_HORIZON
375 var dx: i64 = WG_FOCAL*sy4/WG_MAGIC_4096 + sxo*cy4/WG_MAGIC_4096
376 var dy: i64 = syo
377 var dz: i64 = WG_FOCAL*cy4/WG_MAGIC_4096 - sxo*sy4/WG_MAGIC_4096
378 let dl: i64 = wg_isqrt(dx*dx+dy*dy+dz*dz)
379 dx = dx*WG_MAGIC_1024/dl; dy = dy*WG_MAGIC_1024/dl; dz = dz*WG_MAGIC_1024/dl
380 // march the heightfield
381 var t: i64 = 200
382 var hit: i64 = 0
383 var hx: i64 = 0; var hy: i64 = 0; var hz: i64 = 0; var hh: i64 = 0
384 var step: i64 = 0
385 while step < 220 {
386 let wx: i64 = camx + dx*t/WG_MAGIC_1024
387 let wy: i64 = camy + dy*t/WG_MAGIC_1024
388 let wz: i64 = camz0 + dz*t/WG_MAGIC_1024
389 let th: i64 = wg_terrain_h(wx, wz, seed)
390 if wy < th { hit=1; hx=wx; hy=wy; hz=wz; hh=th; step=WG_MAGIC_9999 }
391 else { t = t + 55 + t/48; if t > WG_MAGIC_60000 { step=WG_MAGIC_9999 } }
392 if step != WG_MAGIC_9999 { step = step + 1 }
393 }
394 var r: i64 = 0; var g: i64 = 0; var b: i64 = 0
395 // water intercept (ray crosses y=WATER going down)
396 var twater: i64 = 0
397 if dy < 0 { twater = (WATER - camy)*WG_MAGIC_1024/dy }
398 let water_in_front: i64 = (twater > 0) & ((hit == 0) | (twater < t))
399 // decide surface
400 var surf: i64 = 0 // 0 sky, 1 terrain, 2 water
401 if hit == 1 { if hh >= WATER { surf = 1 } }
402 if (surf == 0) & (water_in_front == 1) { surf = 2 }
403 if (hit == 1) & (hh < WATER) & (water_in_front == 1) { surf = 2 }
404
405 var dpt: i64 = WG_MAGIC_2000000000
406 if surf == 1 { dpt = t }
407 if surf == 2 { dpt = twater }
408 depth[py*WG_W+px] = dpt
409 seg[py*WG_W+px] = surf // GT: 0 sky / 1 terrain / 2 water
410 var nrmv: i64 = 0 // GT normal (sky = 0)
411 if surf == 2 { nrmv = 128 + 255*256 + 128*WG_MAGIC_65536 } // water: straight up
412 nrm[py*WG_W+px] = nrmv
413
414 if surf == 1 {
415 // terrain normal
416 let e: i64 = 24
417 let hl: i64 = wg_terrain_h(hx-e, hz, seed); let hr2: i64 = wg_terrain_h(hx+e, hz, seed)
418 let hd: i64 = wg_terrain_h(hx, hz-e, seed); let hu: i64 = wg_terrain_h(hx, hz+e, seed)
419 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)
420 // micro-relief bump from fine noise -> surface texture caught by the lighting (not a smooth blob)
421 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)
422 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)
423 nx = nx - bb0*5/100; nz = nz - bb1*5/100
424 let nl: i64 = wg_isqrt(nx*nx+ny*ny+nz*nz)
425 // GT normal (unit, packed rgb) -- written before shading consumes nx/ny/nz
426 if nl > 0 {
427 let ux2: i64 = nx*WG_MAGIC_1024/nl
428 let uy2: i64 = ny*WG_MAGIC_1024/nl
429 let uz2: i64 = nz*WG_MAGIC_1024/nl
430 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
431 }
432 if nl>0 { nx=nx*WG_MAGIC_1024/nl; ny=ny*WG_MAGIC_1024/nl; nz=nz*WG_MAGIC_1024/nl }
433 var nd: i64 = (nx*Wp[0]+ny*Wp[1]+nz*Wp[2])/WG_MAGIC_1024 // fx1024 Lambert (0..WG_MAGIC_1024)
434 if nd<0 {nd=0}
435 // ★P3 SOFT SHADOWS (penumbra): the shared sun-march helper (also feeds the GI cache) --
436 // edges soften with occluder distance; only DIRECT light is occluded (ambient stays).
437 let pen: i64 = wg_sun_pen(seed, hx, hy, hz, Wp)
438 // ★P3 r2 SKY-VISIBILITY ambient (cheap GI): surrounding ridges block skylight -> valleys and
439 // hollows receive less ambient. Horizon slope sampled 4 directions x 3 distances; sv 0..1024.
440 var slmax: i64 = 0
441 var sd2: i64 = 0
442 while sd2 < 4 {
443 var ddx: i64 = 0
444 var ddz: i64 = 0
445 if sd2 == 0 { ddx = 1 }
446 if sd2 == 1 { ddx = 0-1 }
447 if sd2 == 2 { ddz = 1 }
448 if sd2 == 3 { ddz = 0-1 }
449 var dq: i64 = 200
450 while dq <= WG_MAGIC_1800 {
451 let th2: i64 = wg_terrain_h(hx + ddx*dq, hz + ddz*dq, seed)
452 let sl: i64 = (th2 - hy) * WG_MAGIC_1024 / dq
453 if sl > slmax { slmax = sl }
454 dq = dq * 3
455 }
456 sd2 = sd2 + 1
457 }
458 if slmax > WG_MAGIC_1024 { slmax = WG_MAGIC_1024 }
459 var sv: i64 = WG_MAGIC_1024 - slmax*6/10
460 if sv < 420 { sv = 420 }
461 let lit: i64 = 42*sv/WG_MAGIC_1024 + nd*82*pen/WG_MAGIC_1048576 // sky-vis ambient + penumbra-scaled direct
462 // base colour by BIOME, with per-location vegetation variation
463 let nv2: i64 = wg_vnoise(hx*256/WG_MAGIC_1500, hz*256/WG_MAGIC_1500, seed+99)
464 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
465 var rockr: i64 = 112; var rockg: i64 = 101; var rockb: i64 = 92
466 var snowline: i64 = SNOW
467 var snowon: i64 = 1
468 if biome == 1 { // DESERT: sand base, red rock, no snow
469 cr = 188 + nv2*36/WG_MAGIC_65536; cg = 154 + nv2*30/WG_MAGIC_65536; cb = 104 + nv2*18/WG_MAGIC_65536
470 rockr = 152; rockg = 96; rockb = 66
471 snowon = 0
472 }
473 if biome == 2 { // ARCTIC: snow base, blue-grey rock
474 cr = 212 + nv2*24/WG_MAGIC_65536; cg = 222 + nv2*20/WG_MAGIC_65536; cb = 234 + nv2*14/WG_MAGIC_65536
475 rockr = 96; rockg = 106; rockb = 120
476 snowline = 300
477 }
478 if biome == 3 { // AUTUMN: warm mixed foliage
479 cr = 128 + nv2*70/WG_MAGIC_65536; cg = 84 + nv2*44/WG_MAGIC_65536; cb = 30 + nv2*16/WG_MAGIC_65536
480 rockr = 118; rockg = 100; rockb = 86
481 }
482 // alpine: higher -> browner (vegetation thins out); skip where base is already snow/sand
483 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 } }
484 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 } }
485 if ny < 640 { cr=rockr; cg=rockg; cb=rockb } // steep -> exposed rock
486 if hh < WATER+130 { cr=198; cg=186; cb=152 } // shoreline sand
487 if snowon == 1 { if (hh > snowline) & (ny > 600) { cr=234; cg=240; cb=248 } } // snow: high AND gentle
488 // fine surface speckle (grass blades / dirt / rock grain)
489 let tex2: i64 = wg_vnoise(hx*256/24, hz*256/24, seed+53)
490 let tv: i64 = (tex2-WG_MAGIC_32768)*26/WG_MAGIC_65536
491 cr = cr + tv; cg = cg + tv*11/10; cb = cb + tv*6/10
492 r = cr*lit/100; g = cg*lit/100; b = cb*lit/100
493 // ★R3 GI: gather single-bounce irradiance from the cache; couple through the receiver albedo
494 // (green light on green grass reinforces; green light on snow shows as a green tint -- physical)
495 wg_gi_gather(hx, hy, hz, nx, ny, nz, gio)
496 r = r + cr*gio[0]*3/512
497 g = g + cg*gio[1]*3/512
498 b = b + cb*gio[2]*3/512
499 b = b + (WG_MAGIC_1024-pen)*9/WG_MAGIC_1024 // shade is sky-lit -> subtle cool tint
500 // atmospheric fog toward horizon-sky by distance
501 let fog: i64 = wg_min(t*Wp[12]/WG_MAGIC_60000, 100)
502 r = r + (Wp[9]-r)*fog/100; g = g + (Wp[10]-g)*fog/100; b = b + (Wp[11]-b)*fog/100
503 }
504 if surf == 2 {
505 // water: deep blue blended with sky reflection + sun glint (arctic = icy pale)
506 wg_sky(dx, 0-dy, dz, Wp, oc) // reflected sky (mirror y)
507 var dwr: i64 = 24; var dwg: i64 = 74; var dwb: i64 = 116
508 if biome == 2 { dwr = 118; dwg = 160; dwb = 190 }
509 if biome == 1 { dwr = 30; dwg = 96; dwb = 104 }
510 r = (dwr + oc[0])/2; g = (dwg + oc[1])/2; b = (dwb + oc[2])/2
511 let sd: i64 = (dx*Wp[0]-dy*Wp[1]+dz*Wp[2])/WG_MAGIC_1024
512 if sd > 990 { r=wg_min(r+120,255); g=wg_min(g+110,255); b=wg_min(b+90,255) }
513 let fog: i64 = wg_min(twater*Wp[12]/WG_MAGIC_60000, 100)
514 r = r + (Wp[9]-r)*fog/100; g = g + (Wp[10]-g)*fog/100; b = b + (Wp[11]-b)*fog/100
515 }
516 if surf == 0 { wg_sky(dx, dy, dz, Wp, oc); r=oc[0]; g=oc[1]; b=oc[2] }
517 fb[py*WG_W+px] = wg_clamp(r,0,255) + wg_clamp(g,0,255)*256 + wg_clamp(b,0,255)*WG_MAGIC_65536
518 px = px + 1
519 }
520 py = py + 1
521 }
522 return 0
523}
524// ★R4 camera helpers (the inverse pair the flow GT reuses, so gate + renderer share exact math):
525// wg_ray = the normalized fx1024 ray direction for a pixel (depends only on pixel + yaw);
526// wg_project = screen coords of a world point through cam=[dx,dy,dz,yaw] (returns -1 in out[2] if behind).
527func wg_ray(px: i64, py: i64, yaw: i64, out: *i64) -> i64 {
528 let sy4: i64 = it_sin4096(yaw)
529 let cy4: i64 = it_cos4096(yaw)
530 let sxo: i64 = px - WG_W/2
531 let syo: i64 = (WG_H/2 - py) - WG_HORIZON
532 var dx: i64 = WG_FOCAL*sy4/WG_MAGIC_4096 + sxo*cy4/WG_MAGIC_4096
533 var dy: i64 = syo
534 var dz: i64 = WG_FOCAL*cy4/WG_MAGIC_4096 - sxo*sy4/WG_MAGIC_4096
535 let dl: i64 = wg_isqrt(dx*dx+dy*dy+dz*dz)
536 if dl > 0 { dx = dx*WG_MAGIC_1024/dl; dy = dy*WG_MAGIC_1024/dl; dz = dz*WG_MAGIC_1024/dl }
537 out[0]=dx; out[1]=dy; out[2]=dz
538 return 0
539}
540func wg_project(seed: i64, cam: *i64, Px: i64, Py: i64, Pz: i64, out: *i64) -> i64 {
541 let camx: i64 = cam[0]
542 let camy: i64 = wg_cam_base_y(seed) + cam[1]
543 let camz0: i64 = cam[2]
544 let yaw: i64 = cam[3]
545 let sy4: i64 = it_sin4096(yaw)
546 let cy4: i64 = it_cos4096(yaw)
547 let rx: i64 = Px - camx
548 let ry: i64 = Py - camy
549 let rz: i64 = Pz - camz0
550 let zc: i64 = (rx*sy4 + rz*cy4)/WG_MAGIC_4096 // forward distance
551 out[2] = 0 - 1
552 if zc <= 16 { return 0 } // behind the camera
553 let xc: i64 = (rx*cy4 - rz*sy4)/WG_MAGIC_4096 // right offset
554 out[0] = WG_W/2 + WG_FOCAL*xc/zc
555 out[1] = WG_H/2 - WG_HORIZON - WG_FOCAL*ry/zc
556 out[2] = zc
557 return 0
558}
559
560// back-compat wrappers: fixed camera / no annotations / temperate biome / no depth buffer
561func worldgen_render_bd(seed: i64, mode: i64, biome: i64, fb: *i64, depth: *i64) -> i64 {
562 let s: *i64 = sys_mmap(WG_W*WG_H*8) as *i64
563 let n: *i64 = sys_mmap(WG_W*WG_H*8) as *i64
564 let cam: *i64 = sys_mmap(4*8) as *i64 // [0,0,0,0] = the original fixed +z camera (identical)
565 return worldgen_render_full(seed, mode, biome, cam, fb, depth, s, n)
566}
567func worldgen_render_d(seed: i64, mode: i64, fb: *i64, depth: *i64) -> i64 { return worldgen_render_bd(seed, mode, 0, fb, depth) }
568func worldgen_render(seed: i64, mode: i64, fb: *i64) -> i64 {
569 let d: *i64 = sys_mmap(WG_W*WG_H*8) as *i64
570 worldgen_render_bd(seed, mode, 0, fb, d)
571 return 0
572}
573func wg_w() -> i64 { return WG_W }
574func wg_h() -> i64 { return WG_H }