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}