code wiki / (root) / nx_worldpipe_core.nx

nx_worldpipe_core.nx source

↩ module page · 144 lines · 8645 B

1// nx_worldpipe_core.nx -- THE PURE STAGED-TERRAIN CORE. Extracted from nx_worldpipe.nx on 2// 2026-08-28 for gameengine rung GE14a, so the SHIPPING WASM ENGINE and the NAS-side pipeline 3// cannot disagree about the ground they respectively walk on and bake. 4// 5// WHY THE SPLIT EXISTS, MEASURED (the rung's original wording said "the engine imports 6// nx_worldpipe" and that is NOT IMPLEMENTABLE): nx_wasm_craft carries ZERO sys_mmap / 7// sys_read_file / sys_write -- it is allocator-free and syscall-free BY CONSTRUCTION, which is 8// exactly why it compiles to wasm ("wasm has no mmap; every buffer is an arena offset" is written 9// in its own arena map) -- while nx_worldpipe.wp_hydro_bake allocates 819,200 B and pulls 10// nx_trimesh and nx_terrain_erode_lib into the closure. So the pipeline splits along the line the 11// TARGET draws: everything here is PURE ARITHMETIC over (x,z) with no allocation and no syscall, 12// and every allocating stage (hydrology bake, erosion bake, features, lighting) stays in 13// nx_worldpipe. That is the boundary, and it is a property of the code, not a preference. 14// 15// ONE RULER, TWO CONSUMERS: the function bodies below are the SAME BYTES nx_worldpipe carried, 16// moved not copied -- nx_worldpipe now imports this lib and keeps its call sites unchanged, so a 17// copy cannot drift into a second answer for the same ground. The single source change is that the 18// moved code calls rlf_noise3 DIRECTLY instead of through nx_trimesh's one-line delegate 19// (tm_noise3 is literally `return rlf_noise3(...)`), because nx_relief_lib is 7,310 B of pure 20// arithmetic while nx_trimesh is a 45 KB mesh library the wasm target must not carry. 21// That change makes byte-identity of the rebuilt ELF unavailable as the neutrality proof, so the 22// proof is an EXTERNAL CONSTANT instead: nx_worldpipe_core_gate asserts a KAT of heights and 23// parameters harvested from the PRE-EXTRACTION organ. 24// 25// STAGE 1 params: 0 continentalness, 1 temperature, 2 humidity, 3 erosion, 4 weirdness, 5 river. 26// license_tier: ORIGINAL No hw writes (Rule 26). 27import "nx_syscalls.nx" 28import "nx_relief_lib.nx" 29import "nx_itrig.nx" 30import "nx_genp_allow_lib.nx" 31 32const WP_MAGIC_1400: i64 = 1400 33const WP_MAGIC_3400: i64 = 3400 34const WP_MAGIC_2600: i64 = 2600 35const WP_MAGIC_2100: i64 = 2100 36const WP_MAGIC_1700: i64 = 1700 37const WP_MAGIC_1300: i64 = 1300 38const WP_MAGIC_2400: i64 = 2400 39const WP_MAGIC_9973: i64 = 9973 40const WP_MAGIC_524287: i64 = 524287 41const WP_MAGIC_1024: i64 = 1024 42 43static WP_SEED: i64 44// ★AUTHORING KNOBS (the "describe a world" layer, 2026-07-13): global biases the NL front-end sets. Defaulted in 45// wp_init so every existing gate is byte-unchanged; the authoring path sets them AFTER wp_init. 46static WP_TEMP_BIAS: i64 // added to temperature field (colder = snowier) 47static WP_CONT_BIAS: i64 // added to continentalness (lower = more ocean/islands) 48static WP_MOUNT_SCALE: i64 // relief multiplier (default 1400 = current alpine strength) 49 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 rlf_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(rlf_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 = (rlf_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// Dune basis moved from nx_wasm_craft_dunes_t70. Packed recipe is admitted 112// by genp_dune_valid at the public initialization boundary. No allocation. 113// Phase is the engine's existing seed hash in angle units; coordinates are 114// absolute blocks. This is analytic relief, not sediment transport. 115const WP_HEIGHT_Q: i64 = 256 116func wp_dune_wave_q(packed: i64, x: i64, z: i64, phase_seed: i64) -> i64 { 117 if packed == 0 { return 0 } 118 let amplitude: i64 = packed & GA_DUNE_AMP_MASK 119 let wavelength: i64 = (packed >> GA_DUNE_WAVE_SHIFT) & GA_DUNE_WAVE_MASK 120 let heading: i64 = ((packed >> GA_DUNE_HEADING_SHIFT) & GA_DUNE_WAVE_MASK)*2*IT_PI/256 121 let dx: i64 = it_cos4096(heading) 122 let dz: i64 = it_sin4096(heading) 123 let along: i64 = x*dx + z*dz 124 let across: i64 = z*dx - x*dz 125 let bend: i64 = it_sin4096(across*2*IT_PI/(wavelength*4*IT_FX) + phase_seed)*IT_PI2/IT_FX 126 let phase: i64 = along*2*IT_PI/(wavelength*IT_FX) + phase_seed + bend 127 let wave: i64 = 4*it_cos4096(phase) + it_sin4096(2*phase) 128 return amplitude*(5*IT_FX + wave)/(10*IT_FX) 129} 130// Signed inland distance comes from the existing ocean edge owner. Return 131// exactly zero at/on the seaward side; smoothstep fades over one wavelength. 132func wp_dune_coast_q(relief: i64, inland: i64, wavelength: i64) -> i64 { 133 if inland <= 0 { return 0 } 134 if inland >= wavelength { return relief } 135 let f: i64 = inland*WP_HEIGHT_Q/wavelength 136 let fade: i64 = f*f*(3*WP_HEIGHT_Q-2*f)/(WP_HEIGHT_Q*WP_HEIGHT_Q) 137 return relief*fade/WP_HEIGHT_Q 138} 139// Keep the incumbent voxel quantization offset. Only newly crossed integer 140// height boundaries move a column; enabling zero relief cannot re-round it. 141// Both Q heights are already clamped by the existing terrain owner. 142func wp_dune_raw_delta(legacy: i64, base_q: i64, raised_q: i64) -> i64 { 143 return legacy + raised_q/WP_HEIGHT_Q - base_q/WP_HEIGHT_Q 144}