code wiki / (root) / nx_worldpipe_render.nx

nx_worldpipe_render.nx source

↩ module page · 267 lines · 12653 B

1// nx_worldpipe_render.nx -- THE PRODUCER OF THE PUBLISHED WORLD IMAGE, WITH STAGE 3.5 EROSION APPLIED. 2// 3// WHY THIS ORGAN EXISTS. The picture published at https://nishifamily.com/world/foundation had exactly 4// ONE producer in the whole tree: nx_worldpipe_gate's T8 EVIDENCE emit. Measured, not assumed -- 5// nx_shelltool grep over 23,207 sources returned coverage_complete=1 corpus_complete=1 and named that 6// one write_png call as the only writer of knowledge/nx_worldpipe.png. A gate is a RULER; making it 7// also the FACTORY means the shipped picture only refreshes when somebody runs the test, and it means 8// the picture can never be given a parameter the test does not itself want. The factory is separated 9// out here and the gate is left untouched. 10// 11// WHAT IS NEW IN THE PICTURE. PG20 shipped erosion as a PROCESS on this heightfield (wp_erode_bake / 12// wp_erode, proven by nx_worldpipe_erode_gate 21/21 GREEN over the full 102,400-cell population) and 13// it was INERT IN PRODUCTION BY CONSTRUCTION: WP_ERODE is armed by default, but wp_erode returns 0 14// until a bake has run and NO consumer anywhere called wp_erode_bake. A capability that never reaches 15// a surface is not shipped, however green its gate is. This organ is the consumer that opts in. 16// 17// SCOPING, AND WHY NOBODY ELSE MOVES. The opt-in is the BAKE, not a flag. A consumer that never calls 18// wp_erode_bake still has WP_ERG == 0, so wp_erode returns 0 and wp_height is byte-identical. 19// Opting in HERE therefore cannot move nx_worldpipe_gate, nx_m2d_engine, nx_authorgen or any other 20// importer -- and that is a property of the code, not a promise made in a comment. 21// 22// PARAMETERS -- NAMED OR DERIVED, NONE TASTED. wp_erode_bake_derived takes a tick BUDGET and derives 23// everything else from the terrain it is about to erode (talus from the ground's own mean neighbour 24// drop; rain and flux are the erosion library's unit rate Q). The budget below is NOT a taste 25// constant: it is the exact budget nx_worldpipe_erode_gate exercises and proves -- at that budget 26// material demonstrably MOVES, leaves steep ground for flat ground, conserves mass, and two bakes 27// agree cell-for-cell. Adopting a budget already proven to do work is the declared basis. 28// argv[2] overrides it so an A/B costs no rebuild. 29// 30// THE RENDER COMPOSITION IS COPIED, AND THAT IS DECLARED RATHER THAN HIDDEN. The block below 31// reproduces nx_worldpipe_gate's T8 composition (biome map + spawn cross + 3D hero + biome features) 32// because that composition is inline in the gate's main() and is therefore not callable. This is a 33// second copy of a COMPOSITION, not of a RULER -- no judgement is duplicated -- and the real fix is to 34// extract it into a shared wpv_render(). That is filed as debt, not forgotten. 35// 36// usage: nx_worldpipe_render [out.png] [ticks] 37// exit: 0 written and read-back-verified | 2 usage | 5 png-write-or-readback-fail | 6 bake-refused 38// license_tier: ORIGINAL No hw writes (Rule 26). 39import "nx_syscalls.nx" 40import "nx_png.nx" 41import "nx_trimesh.nx" 42import "nx_worldpipe.nx" 43 44// Frame geometry: the incumbent's, so the published asset keeps its shape and an A/B compares like 45// with like. Left panel is the biome/continent map, right panel the lit 3D hero. 46const WR_MAPW: i64 = 360 47const WR_HERW: i64 = 560 48const WR_GH: i64 = 360 49const WR_SHIFT16: i64 = 65536 50// World seed: nx_worldpipe_gate T8 renders wp_init(1). Same seed = same world = an honest A/B. 51const WR_SEED: i64 = 1 52// Erosion budget. Basis: nx_worldpipe_erode_gate's WG_TICKS, the only budget in the estate that has 53// been PROVEN to move material and to be deterministic at that budget. Not tuned here. 54const WR_TICKS: i64 = 8 55// Camera distance, copied with the composition. 56const WR_CAM_D: i64 = 5900 57// A PNG cannot be smaller than its own structure: 8-byte signature + 25-byte IHDR + 12-byte IEND. 58// At or below this it is not a truncated image, it is not an image. 59const WR_PNG_MIN: i64 = 45 60const WR_OUT_DEFAULT: *u8 = "knowledge/nx_worldpipe_eroded.png" 61 62func wr_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 63func wr_puts(s: *u8) -> i64 { sys_write(1, s, wr_slen(s)); return 0 } 64func wr_pn(v: i64) -> i64 { let b: *u8=sys_mmap(32) as *u8; var x: i64=v; var ng: i64=0; if x<0{ng=1;x=0-x} var i: i64=31; if x==0{b[i]=48 as u8;i=i-1} while x>0{b[i]=(48+x%10) as u8;x=x/10;i=i-1} if ng==1{b[i]=45 as u8;i=i-1} sys_write(1,(b as i64+i+1) as *u8,31-i); return 0 } 65func wr_clearfb(fb: *i64, n: i64, c: i64) -> i64 { var i: i64=0; while i<n { fb[i]=c; i=i+1 } return 0 } 66func wr_int(s: *u8) -> i64 { 67 var v: i64 = 0 68 var i: i64 = 0 69 while s[i] != (0 as u8) { 70 let c: i64 = s[i] as i64 71 if c >= 48 { if c <= 57 { v = v*10 + (c - 48) } } 72 i = i + 1 73 } 74 return v 75} 76 77func main(argc: i64, argv: *i64) -> i64 { 78 var outp: *u8 = WR_OUT_DEFAULT 79 if argc > 1 { outp = argv[1] as *u8 } 80 var ticks: i64 = WR_TICKS 81 if argc > 2 { ticks = wr_int(argv[2] as *u8) } 82 83 wr_puts("=== nx_worldpipe_render -- the published world image, stage-3.5 erosion APPLIED ===\n" as *u8) 84 wp_init(WR_SEED) 85 let nspr: i64 = wp_hydro_bake() 86 wr_puts(" hydrology: springs traced=" as *u8); wr_pn(nspr); wr_puts("\n" as *u8) 87 88 // ---- STAGE 3.5, THE OPT-IN. Before this call wp_erode reads 0 at every point and wp_height is 89 // the un-eroded bed; after it every wp_height sampled below carries the eroded delta. Nothing 90 // else in the estate is affected, because nothing else makes this call. 91 let talus: i64 = wp_erode_bake_derived(ticks) 92 if talus <= 0 { 93 wr_puts("REFUSED bake: derived talus=0, the terrain has no relief -- refusing to report an erosion whose outcome was decided before it ran\n" as *u8) 94 sys_exit(6) 95 return 6 96 } 97 let hi0: i64 = wp_erode_stat(2) 98 let hi1: i64 = wp_erode_stat(3) 99 let moved: i64 = wp_erode_stat(1) 100 wr_puts(" erosion: ticks=" as *u8); wr_pn(wp_erode_stat(0)) 101 wr_puts(" talus_derived=" as *u8); wr_pn(talus) 102 wr_puts(" talus_moved=" as *u8); wr_pn(moved) 103 wr_puts(" hypsometric_permil " as *u8); wr_pn(hi0); wr_puts(" -> " as *u8); wr_pn(hi1) 104 wr_puts("\n" as *u8) 105 106 let sp: *i64 = sys_mmap(24) as *i64 107 let okspawn: i64 = wp_spawn(sp) 108 let sx: i64 = sp[0] 109 let sz: i64 = sp[1] 110 111 // ---- composition (copied from nx_worldpipe_gate T8): biome map (left) + 3D staged world (right) 112 let GW: i64 = WR_MAPW + WR_HERW 113 let gal: *i64 = sys_mmap(GW*WR_GH*8) as *i64 114 wr_clearfb(gal, GW*WR_GH, 20 + 24*256 + 38*WR_SHIFT16) 115 // biome map: world span +-9900 116 var py: i64 = 0 117 while py < WR_MAPW { 118 var px: i64 = 0 119 while px < WR_MAPW { 120 let x: i64 = (px - 180)*55 121 let z: i64 = (py - 180)*55 122 let b: i64 = wp_biome(x, z) 123 var col: i64 = 96 + 150*256 + 60*WR_SHIFT16 124 if b == 0 { col = 24 + 70*256 + 165*WR_SHIFT16 } 125 if b == 1 { col = 216 + 196*256 + 140*WR_SHIFT16 } 126 if b == 2 { col = 52 + 118*256 + 196*WR_SHIFT16 } 127 if b == 3 { col = 228 + 200*256 + 120*WR_SHIFT16 } 128 if b == 4 { col = 232 + 236*256 + 244*WR_SHIFT16 } 129 if b == 5 { col = 130 + 124*256 + 120*WR_SHIFT16 } 130 if b == 6 { col = 52 + 110*256 + 44*WR_SHIFT16 } 131 gal[py*GW + px] = col 132 px = px + 1 133 } 134 py = py + 1 135 } 136 // spawn marker: white cross 137 let mx: i64 = sx/55 + 180 138 let mz: i64 = sz/55 + 180 139 var d: i64 = 0-4 140 while d <= 4 { 141 if mx+d >= 0 { if mx+d < WR_MAPW { gal[mz*GW + mx+d] = 255 + 255*256 + 255*WR_SHIFT16 } } 142 if mz+d >= 0 { if mz+d < WR_MAPW { gal[(mz+d)*GW + mx] = 255 + 255*256 + 255*WR_SHIFT16 } } 143 d = d + 1 144 } 145 // 3D hero: terrain 96x96 spacing 110 + biome features 146 tm_reset() 147 tm_set_spec(0) 148 tm_set_tex(0) 149 tm_set_image(0 as *u8, 0, 0) 150 let TN: i64 = 97 151 let SP: i64 = 110 152 let terrFrom: i64 = tm_nv() 153 var gz: i64 = 0 154 while gz < TN { 155 var gx: i64 = 0 156 while gx < TN { 157 let wx: i64 = (gx - 48)*SP 158 let wz: i64 = (gz - 48)*SP 159 var h: i64 = wp_height(wx, wz) 160 let slope: i64 = wp_iabs(wp_height(wx+SP, wz) - h) + wp_iabs(wp_height(wx, wz+SP) - h) 161 let col: i64 = wp_shade(wx, wz, h, slope) 162 if h < 0-14 { h = 0-14 } 163 let vi: i64 = tm_vert(wx, h, wz) 164 tm_vcol(vi, col) 165 gx = gx + 1 166 } 167 gz = gz + 1 168 } 169 gz = 0 170 while gz < TN-1 { 171 var qx: i64 = 0 172 while qx < TN-1 { 173 let v0: i64 = terrFrom + gz*TN + qx 174 tm_quad(v0, v0+1, v0+TN+1, v0+TN, 0) 175 qx = qx + 1 176 } 177 gz = gz + 1 178 } 179 // features over the same span (cell step 220) 180 var fz: i64 = 0 181 while fz < 48 { 182 var fx: i64 = 0 183 while fx < 48 { 184 let wx: i64 = (fx - 24)*220 185 let wz: i64 = (fz - 24)*220 186 let f: i64 = wp_feature(wx, wz) 187 if f > 0 { 188 let h: i64 = wp_height(wx, wz) 189 let ffrom: i64 = tm_nv() 190 if f == 1 { 191 tm_cube(wx, h+60, wz, 26, 96+72*256+46*WR_SHIFT16) 192 tm_pyramid(wx, h+210, wz, 130, 42+96*256+40*WR_SHIFT16) 193 var pi: i64 = ffrom 194 while pi < ffrom+8 { tm_vcol(pi, 96+72*256+46*WR_SHIFT16); pi = pi + 1 } 195 while pi < tm_nv() { tm_vcol(pi, 42+96*256+40*WR_SHIFT16); pi = pi + 1 } 196 } 197 if f == 2 { 198 tm_cube(wx, h+95, wz, 34, 60+150*256+70*WR_SHIFT16) 199 var pi: i64 = ffrom 200 while pi < tm_nv() { tm_vcol(pi, 60+150*256+70*WR_SHIFT16); pi = pi + 1 } 201 } 202 if f == 3 { 203 tm_cube(wx, h+50, wz, 20, 88+66*256+44*WR_SHIFT16) 204 tm_pyramid(wx, h+230, wz, 150, 210+220*256+230*WR_SHIFT16) 205 var pi: i64 = ffrom 206 while pi < ffrom+8 { tm_vcol(pi, 88+66*256+44*WR_SHIFT16); pi = pi + 1 } 207 while pi < tm_nv() { tm_vcol(pi, 210+220*256+230*WR_SHIFT16); pi = pi + 1 } 208 } 209 if f == 4 { 210 tm_cube(wx, h+42, wz, 44, 128+122*256+116*WR_SHIFT16) 211 var pi: i64 = ffrom 212 while pi < tm_nv() { tm_vcol(pi, 128+122*256+116*WR_SHIFT16); pi = pi + 1 } 213 } 214 } 215 fx = fx + 1 216 } 217 fz = fz + 1 218 } 219 tm_compute_normals() 220 tm_place(0, tm_nv(), 0, 0-480, 0, 1000) 221 tm_set_sun(340, 780, 0-300) 222 tm_shadow_bake() 223 tm_set_tex(64) 224 tm_set_shadow(1) 225 wr_puts(" hero: verts=" as *u8); wr_pn(tm_nv()); wr_puts(" tris=" as *u8); wr_pn(tm_nt()); wr_puts(" ovf=" as *u8); wr_pn(tm_ovf()); wr_puts("\n" as *u8) 226 let npx: i64 = WR_HERW*WR_GH 227 let hero: *i64 = sys_mmap(npx*8) as *i64 228 wr_clearfb(hero, npx, 30 + 40*256 + 58*WR_SHIFT16) 229 trimesh_render_aa(hero, WR_HERW, WR_GH, 620, 0-280, WR_CAM_D, 560, 2) 230 py = 0 231 while py < WR_GH { 232 var px: i64 = 0 233 while px < WR_HERW { gal[py*GW + WR_MAPW + px] = hero[py*WR_HERW + px]; px = px + 1 } 234 py = py + 1 235 } 236 write_png(gal, GW, WR_GH, outp) 237 238 // READ THE ARTIFACT BACK. A write_png that failed silently is indistinguishable from one that 239 // worked unless somebody opens the file, so this reads the signature and the length off DISK. 240 let vfd: i64 = sys_openat_rd(outp) 241 var magic_ok: i64 = 0 242 var bytes: i64 = 0 243 if vfd >= 0 { 244 let hdr: *u8 = sys_mmap(16) 245 let got: i64 = sys_read(vfd, hdr, 8) 246 bytes = sys_lseek(vfd, 0, 2) 247 sys_close(vfd) 248 if got == 8 { if hdr[0] == (137 as u8) { if hdr[1] == (80 as u8) { if hdr[2] == (78 as u8) { if hdr[3] == (71 as u8) { magic_ok = 1 } } } } } 249 } 250 wr_puts("WORLDPIPE-RENDER out=" as *u8); wr_puts(outp) 251 wr_puts(" bytes=" as *u8); wr_pn(bytes) 252 wr_puts(" png_magic=" as *u8); wr_pn(magic_ok) 253 wr_puts(" w=" as *u8); wr_pn(GW) 254 wr_puts(" h=" as *u8); wr_pn(WR_GH) 255 wr_puts(" seed=" as *u8); wr_pn(WR_SEED) 256 wr_puts(" spawn_ok=" as *u8); wr_pn(okspawn) 257 wr_puts(" erode_ticks=" as *u8); wr_pn(wp_erode_stat(0)) 258 wr_puts(" erode_talus=" as *u8); wr_pn(talus) 259 wr_puts(" erode_moved=" as *u8); wr_pn(moved) 260 wr_puts(" hypso_permil_before=" as *u8); wr_pn(hi0) 261 wr_puts(" hypso_permil_after=" as *u8); wr_pn(hi1) 262 wr_puts("\n" as *u8) 263 if magic_ok == 0 { sys_exit(5); return 5 } 264 if bytes <= WR_PNG_MIN { sys_exit(5); return 5 } 265 sys_exit(0) 266 return 0 267}