nx_wasm_mineworld.nx source
↩ module page · 1194 lines · 56246 B
1// nx_wasm_mineworld.nx -- MINECRAFT-LEVEL PROC GEN: an INFINITE deterministic voxel world (the noise function
2// IS the world -- no fixed array). Multi-octave integer value-noise terrain + CONTINUOUS biome blend (plains/
3// forest/desert/snow: colors, tree density, hill amplitude -- amplitude blended so biome borders have no cliff),
4// 3D-noise CAVES with unbreakable bedrock at y=0, water, trees. World state = a SLIDING 32x32 height/biome cache
5// window around the camera (refreshed per frame; terrain is a pure fn of (x,z,seed) so roaming is unbounded) +
6// a SPARSE GLOBAL EDIT TABLE (open-address hash, global coords) so dig/place edits PERSIST when you roam away
7// and return (the fixed 48x48 grid of nx_wasmvox3d could not leave its box). Same base-relative pattern + export
8// surface as nx_wasmvox3d (render/terrain_at/break_at/place_at/ww/hh/fb_off) -> same JS shim contract.
9// Scoped 192x144 / VDIST 14 for software-raster speed (the proven envelope). license_tier: ORIGINAL
10import "nx_f32_hw.nx"
11import "nx_pets_netstate.nx"
12const PI2_MAGIC_4096: i64 = 4096
13const PI2_MAGIC_374761393: i64 = 374761393
14const PI2_MAGIC_668265263: i64 = 668265263
15const PI2_MAGIC_1442695041: i64 = 1442695041
16const PI2_MAGIC_1274126177: i64 = 1274126177
17const PI2_MAGIC_65536: i64 = 65536
18const PI2_MAGIC_2246822519: i64 = 2246822519
19const PI2_MAGIC_16777216: i64 = 16777216
20const PI2_MAGIC_2654435761: i64 = 2654435761
21const PI2_MAGIC_3900: i64 = 3900
22const PI2_MAGIC_9999: i64 = 9999
23const PI2_MAGIC_5734: i64 = 5734
24
25const W: i64 = 256
26const H: i64 = 192
27const HW: i64 = 128
28const HH: i64 = 96
29const ZFAR: i64 = 1073741824
30const FOCAL: i64 = 200
31const YMAX: i64 = 28
32const WSEED: i64 = 7
33const WWATER: i64 = 4
34const VDIST: i64 = 18
35const WWIN: i64 = 40
36const BIAS: i64 = 1048576
37const EBIAS: i64 = 67108864
38const ECAP: i64 = 4096
39const PI4096: i64 = 12868 // pi in 4096-scale radians (integer trig -- NO JS Math on the game path)
40const PI2_4096: i64 = 6434
41const FOG0: i64 = 12 // fog starts (blocks); full at FOG0+8 -> the horizon fades into sky
42
43const O_FB: i64 = 0
44const O_ZB: i64 = 393216 // W*H*8 (256x192)
45const O_R: i64 = 786432
46const O_RY: i64 = 786560
47const O_RX: i64 = 786688
48const O_V: i64 = 786816
49const O_RV: i64 = 786848
50const O_SX: i64 = 786880
51const O_SY: i64 = 786944
52const O_SD: i64 = 787008
53const O_SOK: i64 = 787072
54const O_PJ: i64 = 787136
55const O_HCORG: i64 = 787200 // sliding-window origin: [0]=orgx [1]=orgz [2]=valid
56const O_HC: i64 = 787224 // height cache, WWIN*WWIN i64 (40x40 covers VDIST 18 + neighbors)
57const O_HB: i64 = 800024 // biome cache, WWIN*WWIN i64
58const O_HT: i64 = 812824 // tree-presence cache, WWIN*WWIN i64 (trees are SOLID blocks now)
59const O_EDIT: i64 = 825624 // sparse edit table: ECAP * (key,val) i64 pairs
60const O_ECNT: i64 = 891160 // live edit count
61// --- multiplayer (GTA-online rung 1): peer table + netstate buffers; protocol logic ALL in-wasm ---
62const MAXPEERS: i64 = 8
63const O_PEERS: i64 = 891224 // MAXPEERS x {active,x256,y256,z256,yaw1000} i64
64const O_NETF: i64 = 891544 // ps_pack/ps_unpack scratch fields f[10]
65const O_NETOUT: i64 = 891624 // packed self frame (28B) -- JS memcpy's this to the /pstate POST body
66const O_NETIN: i64 = 891656 // roster body in ([len:1][frame] records) -- JS memcpy's /roster here
67// --- SOVEREIGN TICK: the ENTIRE player sim lives here (camera, physics, input) -- JS sends only raw input ---
68const O_CAM: i64 = 895752 // [0]x256 [1]y256(eye) [2]z256 [3]yaw4096rad [4]pitch4096rad [5]vy256 [6]fly [7]onground [8]prevkeys [9]invY [10]invX [11]sens
69const MW_SENS: i64 = 13 // default mouse gain (rad*4096 per pixel); the client owns it as data (c[11])
70// ---- BLOCK DATA REGISTRY: blocks are DATA, not const (operator: games as data-io + logic, loosely coupled).
71// A block record = 4 i64 {top_rgb, side_rgb, style, flags}. A DATA PACK (or a MOD overlay) loads records via
72// pack_block() at RUNTIME -- no recompile. tex() READS this table -> a mod that changes a color/style re-skins
73// the world live. blk[0*4+0] = the "pack loaded" marker (0 = load the built-in default pack lazily). ----
74const O_BLK: i64 = 895848 // MAXBLK * 4 i64 block records (starts right after O_CAM's 96 bytes)
75const O_STAT: i64 = 896872 // [0] = terrain pixels DRAWN this frame (ground truth for gates; reset per render)
76const MAXBLK: i64 = 32
77const ST_PLAIN: i64 = 0
78const ST_GRASS: i64 = 1
79const ST_STONE: i64 = 2
80const ST_WOOD: i64 = 3
81const ST_LEAF: i64 = 4
82const ST_WATER: i64 = 5
83const ST_ORE: i64 = 6 // stone body (side color) with mineral flecks (top color) -- the harvested ores
84// --- block kinds (per-face PROCEDURAL textures, integer hash, zero assets) ---
85const K_GRASS: i64 = 1
86const K_DIRT: i64 = 2
87const K_STONE: i64 = 3
88const K_SAND: i64 = 4
89const K_SNOW: i64 = 5
90const K_ROCK: i64 = 6
91const K_WATER: i64 = 7
92const K_WOOD: i64 = 8
93const K_LEAF: i64 = 9
94const K_PLANK: i64 = 10
95// HARVESTED from the minecraftclone block registry (nishi-engine packages/voxel-blocks/src/blocks.nx --
96// already NishiLang data): the ore/cobble vocabulary, as data-pack records + deterministic underground veins.
97const K_COBBLE: i64 = 11
98const K_COAL: i64 = 12
99const K_IRON: i64 = 13
100const K_GOLD: i64 = 14
101const K_EMER: i64 = 15
102const K_DARKWOOD: i64 = 16
103
104func imin(a: i64, b: i64) -> i64 { if a < b { return a } return b }
105func imax(a: i64, b: i64) -> i64 { if a > b { return a } return b }
106func min3(a: i64, b: i64, c: i64) -> i64 { return imin(a, imin(b, c)) }
107func max3(a: i64, b: i64, c: i64) -> i64 { return imax(a, imax(b, c)) }
108func clamp255(v: i64) -> i64 { if v < 0 { return 0 } if v > 255 { return 255 } return v }
109func fratio(num: i64, den: i64) -> i64 { return f32_div(f32_of(num), f32_of(den)) }
110// floor-division by 256 (correct for negative world coords; plain / truncates toward zero)
111func mw_fd256(v: i64) -> i64 { var q: i64 = v / 256; if v < 0 { if q * 256 != v { q = q - 1 } } return q }
112// integer sine: x in 4096-scale radians -> 4096-scale result. Range-reduce to [0,pi/2] by symmetry, then a
113// 5th-order Taylor (max err ~0.5% at the quarter-turn edge). Deterministic, no float, no JS Math.
114func mw_sin4096(x0: i64) -> i64 {
115 var x: i64 = x0 % (2 * PI4096)
116 if x > PI4096 { x = x - 2 * PI4096 }
117 if x < 0 - PI4096 { x = x + 2 * PI4096 }
118 var s: i64 = 1
119 if x < 0 { s = 0 - 1; x = 0 - x }
120 if x > PI2_4096 { x = PI4096 - x }
121 let x2: i64 = x * x / PI2_MAGIC_4096
122 let t3: i64 = x * x2 / PI2_MAGIC_4096
123 let t5: i64 = t3 * x2 / PI2_MAGIC_4096
124 return s * (x - t3 / 6 + t5 / 120)
125}
126func mw_cos4096(x: i64) -> i64 { return mw_sin4096(x + PI2_4096) }
127
128// ---- deterministic integer noise (pure fn of coords + seed = the infinite world) ----
129func mw_h2(ix: i64, iz: i64, s: i64) -> i64 {
130 var n: i64 = ix * PI2_MAGIC_374761393 + iz * PI2_MAGIC_668265263 + ix * iz * 31 + s * PI2_MAGIC_1442695041
131 n = (n >> 13) ^ n
132 n = n * PI2_MAGIC_1274126177
133 n = (n >> 16) ^ n
134 var v: i64 = n % 256
135 if v < 0 { v = v + 256 }
136 return v
137}
138// bilinear value-noise octave, coords pre-biased positive; returns 0..255
139func mw_o2(bx: i64, bz: i64, lat: i64, s: i64) -> i64 {
140 let ix: i64 = bx / lat
141 let iz: i64 = bz / lat
142 let fx: i64 = (bx % lat) * 256 / lat
143 let fz: i64 = (bz % lat) * 256 / lat
144 let h00: i64 = mw_h2(ix, iz, s)
145 let h10: i64 = mw_h2(ix + 1, iz, s)
146 let h01: i64 = mw_h2(ix, iz + 1, s)
147 let h11: i64 = mw_h2(ix + 1, iz + 1, s)
148 let a: i64 = h00 * 256 + (h10 - h00) * fx
149 let b: i64 = h01 * 256 + (h11 - h01) * fx
150 return (a * 256 + (b - a) * fz) / PI2_MAGIC_65536
151}
152// biome class 0=plains 1=forest 2=desert 3=snowy (large patches, lattice 96)
153func biome_of(gx: i64, gz: i64) -> i64 {
154 let v: i64 = mw_o2(gx + BIAS, gz + BIAS, 96, WSEED * 7 + 5)
155 if v < 88 { return 0 }
156 if v < 152 { return 1 }
157 if v < 200 { return 2 }
158 return 3
159}
160// natural surface height: continental + biome-amplitude-blended hills + roughness (1..20, sea=WWATER)
161func terrain_h0(gx: i64, gz: i64) -> i64 {
162 let bx: i64 = gx + BIAS
163 let bz: i64 = gz + BIAS
164 let cont: i64 = mw_o2(bx, bz, 48, WSEED)
165 let hill: i64 = mw_o2(bx, bz, 12, WSEED + 11)
166 let rough: i64 = mw_o2(bx, bz, 5, WSEED + 23)
167 let bv: i64 = mw_o2(bx, bz, 96, WSEED * 7 + 5) // same channel as biome_of -> amplitude is CONTINUOUS
168 let hmul: i64 = 3 + bv * 7 / 256 // desert-ish low -> snowy mountains high, no border cliff
169 var h: i64 = 1 + cont * 9 / 256 + hill * hmul / 256 + rough * 2 / 256
170 if h < 1 { h = 1 }
171 if h > 20 { h = 20 }
172 return h
173}
174// 3D cave noise: trilinear value-noise, carved where dense (1 = air pocket)
175func mw_h3(ix: i64, iy: i64, iz: i64, s: i64) -> i64 {
176 var n: i64 = ix * PI2_MAGIC_374761393 + iy * PI2_MAGIC_2246822519 + iz * PI2_MAGIC_668265263 + ix * iz * 31 + iy * ix * 17 + s * PI2_MAGIC_1442695041
177 n = (n >> 13) ^ n
178 n = n * PI2_MAGIC_1274126177
179 n = (n >> 16) ^ n
180 var v: i64 = n % 256
181 if v < 0 { v = v + 256 }
182 return v
183}
184func cave3(gx: i64, gy: i64, gz: i64) -> i64 {
185 let bx: i64 = gx + BIAS
186 let bz: i64 = gz + BIAS
187 let ix: i64 = bx / 9
188 let iz: i64 = bz / 9
189 let iy: i64 = gy / 6
190 let fx: i64 = (bx % 9) * 256 / 9
191 let fz: i64 = (bz % 9) * 256 / 9
192 let fy: i64 = (gy % 6) * 256 / 6
193 let c000: i64 = mw_h3(ix, iy, iz, WSEED + 31)
194 let c100: i64 = mw_h3(ix + 1, iy, iz, WSEED + 31)
195 let c010: i64 = mw_h3(ix, iy + 1, iz, WSEED + 31)
196 let c110: i64 = mw_h3(ix + 1, iy + 1, iz, WSEED + 31)
197 let c001: i64 = mw_h3(ix, iy, iz + 1, WSEED + 31)
198 let c101: i64 = mw_h3(ix + 1, iy, iz + 1, WSEED + 31)
199 let c011: i64 = mw_h3(ix, iy + 1, iz + 1, WSEED + 31)
200 let c111: i64 = mw_h3(ix + 1, iy + 1, iz + 1, WSEED + 31)
201 let x00: i64 = c000 * 256 + (c100 - c000) * fx
202 let x10: i64 = c010 * 256 + (c110 - c010) * fx
203 let x01: i64 = c001 * 256 + (c101 - c001) * fx
204 let x11: i64 = c011 * 256 + (c111 - c011) * fx
205 let z0: i64 = x00 * 256 + (x01 - x00) * fz
206 let z1: i64 = x10 * 256 + (x11 - x10) * fz
207 let v: i64 = (z0 * 256 + (z1 - z0) * fy) / PI2_MAGIC_16777216
208 if v > 197 { return 1 }
209 return 0
210}
211
212// ---- sparse global edit table (0=untouched 1=placed-solid 2=dug-air), persists across roaming ----
213func mw_key(gx: i64, gy: i64, gz: i64) -> i64 { return ((gx + EBIAS) << 33) | ((gz + EBIAS) << 6) | gy }
214func mw_ehash(k: i64) -> i64 {
215 var n: i64 = k ^ (k >> 29)
216 n = n * PI2_MAGIC_2654435761
217 n = n ^ (n >> 17)
218 n = n % ECAP
219 if n < 0 { n = n + ECAP }
220 return n
221}
222func mw_eget(base: i64, gx: i64, gy: i64, gz: i64) -> i64 {
223 let t: *i64 = (base + O_EDIT) as *i64
224 let k: i64 = mw_key(gx, gy, gz)
225 var i: i64 = mw_ehash(k)
226 var probes: i64 = 0
227 while probes < ECAP {
228 let sk: i64 = t[i * 2]
229 if sk == 0 { return 0 }
230 if sk == k { return t[i * 2 + 1] }
231 i = i + 1
232 if i >= ECAP { i = 0 }
233 probes = probes + 1
234 }
235 return 0
236}
237func mw_eset(base: i64, gx: i64, gy: i64, gz: i64, v: i64) -> i64 {
238 let cnt: *i64 = (base + O_ECNT) as *i64
239 let t: *i64 = (base + O_EDIT) as *i64
240 let k: i64 = mw_key(gx, gy, gz)
241 var i: i64 = mw_ehash(k)
242 var probes: i64 = 0
243 while probes < ECAP {
244 let sk: i64 = t[i * 2]
245 if sk == k { t[i * 2 + 1] = v; return 0 }
246 if sk == 0 {
247 if cnt[0] >= PI2_MAGIC_3900 { return 0 - 1 } // honest capacity bound, never corrupt
248 t[i * 2] = k
249 t[i * 2 + 1] = v
250 cnt[0] = cnt[0] + 1
251 return 0
252 }
253 i = i + 1
254 if i >= ECAP { i = 0 }
255 probes = probes + 1
256 }
257 return 0 - 1
258}
259
260// is (gx,gy,gz) solid? hcol = natural column height (pass -1 to compute fresh). Edits override nature;
261// bedrock y=0 always solid; caves carve the interior [1, h-2] so mouths show on hillsides + digs break through.
262func solid_h(base: i64, gx: i64, gy: i64, gz: i64, hcol: i64) -> i64 {
263 if gy < 0 { return 1 }
264 if gy >= YMAX { return 0 }
265 let e: i64 = mw_eget(base, gx, gy, gz)
266 if e == 1 { return 1 }
267 if e == 2 { return 0 }
268 var h: i64 = hcol
269 if h < 0 { h = terrain_h0(gx, gz) }
270 if gy >= h { return 0 }
271 if gy == 0 { return 1 }
272 if gy <= h - 2 { if cave3(gx, gy, gz) == 1 { return 0 } }
273 return 1
274}
275// column info packed into ONE i64 (no scratch buffers on the hot path): h | bio<<6 | tree<<8.
276// Window-cached when the camera window covers the column; fresh (pure noise fns) otherwise.
277func col_pack(base: i64, gx: i64, gz: i64) -> i64 {
278 let orgp: *i64 = (base + O_HCORG) as *i64
279 if orgp[2] == 1 {
280 let wx: i64 = gx - orgp[0]
281 let wz: i64 = gz - orgp[1]
282 if wx >= 0 { if wx < WWIN { if wz >= 0 { if wz < WWIN {
283 let hc: *i64 = (base + O_HC) as *i64
284 let hb: *i64 = (base + O_HB) as *i64
285 let ht: *i64 = (base + O_HT) as *i64
286 return hc[wz * WWIN + wx] | (hb[wz * WWIN + wx] << 6) | (ht[wz * WWIN + wx] << 8)
287 } } } }
288 }
289 let h: i64 = terrain_h0(gx, gz)
290 let bio: i64 = biome_of(gx, gz)
291 return h | (bio << 6) | (has_tree(gx, gz, h, bio) << 8)
292}
293// the BLOCK KIND at a cell (0 = air): terrain body w/ caves + bedrock, sea filled to WWATER, SOLID trees
294// (trunk + plus-shaped canopy), and player edits (dug = air, placed = planks). solid() = kind != 0.
295func kind_c(base: i64, gx: i64, gy: i64, gz: i64) -> i64 {
296 if gy < 0 { return K_STONE }
297 if gy >= YMAX { return 0 }
298 let e: i64 = mw_eget(base, gx, gy, gz)
299 if e == 1 { return K_PLANK }
300 if e == 2 { return 0 }
301 let cp: i64 = col_pack(base, gx, gz)
302 let h: i64 = cp & 63
303 let bio: i64 = (cp >> 6) & 3
304 let tr: i64 = (cp >> 8) & 1
305 if gy < h {
306 if gy == 0 { return K_STONE }
307 if gy <= h - 2 { if cave3(gx, gy, gz) == 1 { return 0 } }
308 if gy == h - 1 {
309 if h <= WWATER + 1 { return K_SAND }
310 if gy >= 16 { return K_SNOW }
311 if bio == 3 { return K_SNOW }
312 if bio == 2 { return K_SAND }
313 return K_GRASS
314 }
315 if gy >= 16 { return K_ROCK }
316 if bio == 2 { return K_SAND }
317 if gy >= h - 4 { return K_DIRT } // topsoil band under the surface block
318 // STONE body with deterministic ORE VEINS (harvested from the minecraftclone vocabulary; deeper = rarer)
319 let ov: i64 = mw_h3(gx, gy, gz, WSEED + 47)
320 if ov == 0 { if gy <= 4 { return K_EMER } }
321 if ov >= 1 { if ov <= 3 { if gy <= 6 { return K_GOLD } } }
322 if ov >= 4 { if ov <= 9 { if gy <= 9 { return K_IRON } } }
323 if ov >= 10 { if ov <= 21 { if gy <= 12 { return K_COAL } } }
324 return K_STONE
325 }
326 if h <= WWATER { if gy <= WWATER { return K_WATER } }
327 if tr == 1 { if h > WWATER {
328 if gy >= h { if gy <= h + 3 { return K_WOOD } }
329 if gy >= h + 4 { if gy <= h + 5 { return K_LEAF } }
330 } }
331 // cardinal neighbors' canopies (plus-shaped crown around each trunk top)
332 var k: i64 = 0
333 while k < 4 {
334 var dx: i64 = 0
335 var dz: i64 = 0
336 if k == 0 { dx = 1 }
337 if k == 1 { dx = 0 - 1 }
338 if k == 2 { dz = 1 }
339 if k == 3 { dz = 0 - 1 }
340 let np: i64 = col_pack(base, gx + dx, gz + dz)
341 let ntr: i64 = (np >> 8) & 1
342 if ntr == 1 {
343 let nh: i64 = np & 63
344 if nh > WWATER { if gy >= nh + 3 { if gy <= nh + 4 { return K_LEAF } } }
345 }
346 k = k + 1
347 }
348 return 0
349}
350func solid(base: i64, gx: i64, gy: i64, gz: i64) -> i64 { if kind_c(base, gx, gy, gz) != 0 { return 1 } return 0 }
351// PHYSICS solidity: water is render-solid but permeable to the body (swimmable); everything else matches render
352func phys_solid(base: i64, gx: i64, gy: i64, gz: i64) -> i64 {
353 let k: i64 = kind_c(base, gx, gy, gz)
354 if k == 0 { return 0 }
355 if k == K_WATER { return 0 }
356 return 1
357}
358// the highest phys-solid cell top AT/BELOW start_gy in this column -- the true FLOOR (cave/overhang correct)
359func floor_y(base: i64, gx: i64, gz: i64, start_gy: i64) -> i64 {
360 var gy: i64 = start_gy
361 if gy > YMAX - 1 { gy = YMAX - 1 }
362 while gy >= 0 {
363 if phys_solid(base, gx, gy, gz) == 1 { return gy + 1 }
364 gy = gy - 1
365 }
366 return 0
367}
368func rgb(r: i64, g: i64, b: i64) -> i64 { return r + g * 256 + b * PI2_MAGIC_65536 }
369// ---- block-data-registry access ----
370func blk_ptr(base: i64) -> *i64 { return (base + O_BLK) as *i64 }
371func blk_set(base: i64, id: i64, top: i64, side: i64, style: i64) -> i64 {
372 if id < 1 { return 0 - 1 }
373 if id >= MAXBLK { return 0 - 1 }
374 let b: *i64 = blk_ptr(base)
375 b[id * 4 + 0] = top; b[id * 4 + 1] = side; b[id * 4 + 2] = style; b[id * 4 + 3] = 1
376 return 0
377}
378// the built-in BASE data pack (10 blocks). A MOD calls pack_begin then pack_block to override/extend at runtime.
379func pack_default_at(base: i64) -> i64 {
380 let b: *i64 = blk_ptr(base)
381 var i: i64 = 0
382 while i < MAXBLK * 4 { b[i] = 0; i = i + 1 }
383 blk_set(base, K_GRASS, rgb(92, 168, 74), rgb(126, 92, 54), ST_GRASS)
384 blk_set(base, K_DIRT, rgb(126, 92, 54), rgb(126, 92, 54), ST_PLAIN)
385 blk_set(base, K_STONE, rgb(120, 118, 112), rgb(120, 118, 112), ST_STONE)
386 blk_set(base, K_SAND, rgb(215, 196, 123), rgb(215, 196, 123), ST_PLAIN)
387 blk_set(base, K_SNOW, rgb(231, 235, 244), rgb(231, 235, 244), ST_PLAIN)
388 blk_set(base, K_ROCK, rgb(145, 141, 135), rgb(145, 141, 135), ST_STONE)
389 blk_set(base, K_WATER, rgb(74, 127, 211), rgb(74, 127, 211), ST_WATER)
390 blk_set(base, K_WOOD, rgb(113, 81, 46), rgb(94, 66, 37), ST_WOOD)
391 blk_set(base, K_LEAF, rgb(45, 115, 49), rgb(45, 115, 49), ST_LEAF)
392 blk_set(base, K_PLANK, rgb(163, 128, 81), rgb(163, 128, 81), ST_PLAIN)
393 // harvested minecraftclone vocabulary: cobble + the ore family (fleck color in top, stone base in side)
394 blk_set(base, K_COBBLE, rgb(104, 102, 98), rgb(104, 102, 98), ST_STONE)
395 blk_set(base, K_COAL, rgb(38, 38, 40), rgb(116, 114, 108), ST_ORE)
396 blk_set(base, K_IRON, rgb(206, 166, 128), rgb(116, 114, 108), ST_ORE)
397 blk_set(base, K_GOLD, rgb(244, 204, 62), rgb(116, 114, 108), ST_ORE)
398 blk_set(base, K_EMER, rgb(52, 210, 106), rgb(116, 114, 108), ST_ORE)
399 blk_set(base, K_DARKWOOD, rgb(74, 52, 30), rgb(62, 43, 25), ST_WOOD)
400 b[0] = 1 // marker: a pack is loaded (0 => render lazily loads the default)
401 return 0
402}
403func ensure_pack(base: i64) -> i64 { let b: *i64 = blk_ptr(base); if b[0] == 0 { pack_default_at(base) } return 0 }
404// clamp helper to keep brightness modulation from overflowing a channel into the next
405func tbr(v: i64) -> i64 { if v > 255 { return 255 } if v < 0 { return 0 } return v }
406// DATA-DRIVEN block texture: read the block record (color + style) and apply the style's procedural detail as
407// a brightness modulation of the record's base color. Same integer-hash patterns as before, but the COLORS are
408// DATA -> a mod re-skins by changing records. base carries the block table.
409func tex(base: i64, kind: i64, fk: i64, tu: i64, tv: i64) -> i64 {
410 let b: *i64 = blk_ptr(base)
411 if kind < 1 { return rgb(200, 60, 200) }
412 if kind >= MAXBLK { return rgb(200, 60, 200) }
413 if b[kind * 4 + 3] == 0 { return rgb(200, 60, 200) } // undefined block = loud magenta (never silent)
414 let top: i64 = b[kind * 4 + 0]
415 let side: i64 = b[kind * 4 + 1]
416 let style: i64 = b[kind * 4 + 2]
417 var c: i64 = top
418 if fk != 0 { c = side }
419 let sp: i64 = mw_h2(tu * 7 + 1, tv * 13 + 3, kind * 57 + fk) % 4
420 if style == ST_GRASS {
421 if fk == 0 { return packc(c, tbr(232 + sp * 7)) }
422 if tv <= 1 { return packc(top, 236) } // green lip: the TOP color bleeds onto the side rim
423 return packc(c, tbr(230 + sp * 7))
424 }
425 if style == ST_STONE {
426 let bl: i64 = mw_h2(tu / 2, tv / 2, 91) % 3
427 return packc(c, tbr(224 + bl * 14))
428 }
429 if style == ST_WOOD {
430 if fk == 0 { if ((tu & 2) ^ (tv & 2)) != 0 { return packc(c, 250) } return packc(c, 222) }
431 if (tu & 3) == 0 { return packc(c, 208) }
432 return packc(c, 246)
433 }
434 if style == ST_LEAF { return packc(c, tbr(214 + sp * 12)) }
435 if style == ST_WATER { if ((tv + tu / 3) & 3) == 0 { return packc(c, 255) } return packc(c, 230) }
436 if style == ST_ORE {
437 // mineral flecks (the TOP color) embedded in a stone body (the SIDE color)
438 let fl: i64 = mw_h2(tu, tv, kind * 13 + 5) % 8
439 if fl <= 1 { return packc(top, 250) }
440 let bl2: i64 = mw_h2(tu / 2, tv / 2, 91) % 3
441 return packc(side, tbr(224 + bl2 * 14))
442 }
443 return packc(c, tbr(234 + sp * 6)) // ST_PLAIN: gentle speckle
444}
445func shade(lnx: i64, lny: i64, lnz: i64) -> i64 {
446 var d: i64 = lnx * 2 + lny * 3 + lnz * 1
447 if d < 0 { d = 0 }
448 return clamp255(110 + d * 38)
449}
450func packc(col: i64, bright: i64) -> i64 {
451 let r: i64 = (col & 0xff) * bright / 255
452 let g: i64 = ((col >> 8) & 0xff) * bright / 255
453 let b: i64 = ((col >> 16) & 0xff) * bright / 255
454 return r + g * 256 + b * PI2_MAGIC_65536
455}
456func fill_z(base: i64, x0: i64, y0: i64, d0: i64, x1: i64, y1: i64, d1: i64, x2: i64, y2: i64, d2: i64, col: i64) -> i64 {
457 let fb: *i64 = (base + O_FB) as *i64
458 let zb: *i64 = (base + O_ZB) as *i64
459 let area: i64 = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0)
460 if area == 0 { return 0 }
461 let minx: i64 = imax(0, min3(x0, x1, x2))
462 let maxx: i64 = imin(W - 1, max3(x0, x1, x2))
463 let miny: i64 = imax(0, min3(y0, y1, y2))
464 let maxy: i64 = imin(H - 1, max3(y0, y1, y2))
465 var py: i64 = miny
466 while py <= maxy {
467 var px: i64 = minx
468 while px <= maxx {
469 let e0: i64 = (x2 - x1) * (py - y1) - (y2 - y1) * (px - x1)
470 let e1: i64 = (x0 - x2) * (py - y2) - (y0 - y2) * (px - x2)
471 let e2: i64 = (x1 - x0) * (py - y0) - (y1 - y0) * (px - x0)
472 var ins: i64 = 0
473 if area > 0 { if e0 >= 0 { if e1 >= 0 { if e2 >= 0 { ins = 1 } } } }
474 if area < 0 { if e0 <= 0 { if e1 <= 0 { if e2 <= 0 { ins = 1 } } } }
475 if ins == 1 {
476 let d: i64 = (d0 * e0 + d1 * e1 + d2 * e2) / area
477 let idx: i64 = py * W + px
478 if d < zb[idx] {
479 zb[idx] = d
480 let stz: *i64 = (base + O_STAT) as *i64
481 stz[0] = stz[0] + 1
482 // distance fog: beyond FOG0 blocks, blend toward the sky color (the horizon fades out)
483 var fc: i64 = col
484 let fd: i64 = d / 64
485 if fd > FOG0 {
486 var f: i64 = (fd - FOG0) * 255 / 8
487 if f > 255 { f = 255 }
488 let r: i64 = col & 0xff
489 let g: i64 = (col >> 8) & 0xff
490 let b: i64 = (col >> 16) & 0xff
491 fc = (r + (142 - r) * f / 255) + (g + (186 - g) * f / 255) * 256 + (b + (228 - b) * f / 255) * PI2_MAGIC_65536
492 }
493 fb[idx] = fc
494 }
495 }
496 px = px + 1
497 }
498 py = py + 1
499 }
500 return 0
501}
502// TEXTURED z-buffered triangle: barycentric-interpolated UVs (affine -- faces are small on screen) sample the
503// procedural block texture per pixel; per-VERTEX LIGHT (smooth ambient occlusion, harvested pattern from the
504// minecraftclone's baked-AO mesher) interpolates the same way; then face brightness + distance fog apply.
505// ⚠ 16-PARAM LIMIT: the WAT emitter silently DROPS call arguments beyond 16 (found 2026-07-02 -- the browser
506// rejects the module at validation). Each vertex packs (light<<16 | u<<8 | v) into ONE arg = still 16 params.
507func fill_zt(base: i64, x0: i64, y0: i64, d0: i64, uv0: i64, x1: i64, y1: i64, d1: i64, uv1: i64, x2: i64, y2: i64, d2: i64, uv2: i64, kind: i64, fk: i64, bright: i64) -> i64 {
508 let u0: i64 = (uv0 >> 8) & 255
509 let v0: i64 = uv0 & 255
510 let l0: i64 = (uv0 >> 16) & 255
511 let u1: i64 = (uv1 >> 8) & 255
512 let v1: i64 = uv1 & 255
513 let l1: i64 = (uv1 >> 16) & 255
514 let u2: i64 = (uv2 >> 8) & 255
515 let v2: i64 = uv2 & 255
516 let l2: i64 = (uv2 >> 16) & 255
517 let fb: *i64 = (base + O_FB) as *i64
518 let zb: *i64 = (base + O_ZB) as *i64
519 let area: i64 = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0)
520 if area == 0 { return 0 }
521 let minx: i64 = imax(0, min3(x0, x1, x2))
522 let maxx: i64 = imin(W - 1, max3(x0, x1, x2))
523 let miny: i64 = imax(0, min3(y0, y1, y2))
524 let maxy: i64 = imin(H - 1, max3(y0, y1, y2))
525 var py: i64 = miny
526 while py <= maxy {
527 var px: i64 = minx
528 while px <= maxx {
529 let e0: i64 = (x2 - x1) * (py - y1) - (y2 - y1) * (px - x1)
530 let e1: i64 = (x0 - x2) * (py - y2) - (y0 - y2) * (px - x2)
531 let e2: i64 = (x1 - x0) * (py - y0) - (y1 - y0) * (px - x0)
532 var ins: i64 = 0
533 if area > 0 { if e0 >= 0 { if e1 >= 0 { if e2 >= 0 { ins = 1 } } } }
534 if area < 0 { if e0 <= 0 { if e1 <= 0 { if e2 <= 0 { ins = 1 } } } }
535 if ins == 1 {
536 let d: i64 = (d0 * e0 + d1 * e1 + d2 * e2) / area
537 let idx: i64 = py * W + px
538 if d < zb[idx] {
539 zb[idx] = d
540 let stz: *i64 = (base + O_STAT) as *i64
541 stz[0] = stz[0] + 1
542 var tu: i64 = (u0 * e0 + u1 * e1 + u2 * e2) / area
543 var tv: i64 = (v0 * e0 + v1 * e1 + v2 * e2) / area
544 var tl: i64 = (l0 * e0 + l1 * e1 + l2 * e2) / area
545 if tu < 0 { tu = 0 }
546 if tu > 255 { tu = 255 }
547 if tv < 0 { tv = 0 }
548 if tv > 255 { tv = 255 }
549 if tl < 100 { tl = 100 }
550 if tl > 255 { tl = 255 }
551 let col: i64 = packc(tex(base, kind, fk, tu >> 5, tv >> 5), bright * tl / 255)
552 var fc: i64 = col
553 let fd: i64 = d / 64
554 if fd > FOG0 {
555 var f: i64 = (fd - FOG0) * 255 / 8
556 if f > 255 { f = 255 }
557 let r: i64 = col & 0xff
558 let g: i64 = (col >> 8) & 0xff
559 let b: i64 = (col >> 16) & 0xff
560 fc = (r + (142 - r) * f / 255) + (g + (186 - g) * f / 255) * 256 + (b + (228 - b) * f / 255) * PI2_MAGIC_65536
561 }
562 fb[idx] = fc
563 }
564 }
565 px = px + 1
566 }
567 py = py + 1
568 }
569 return 0
570}
571func project(base: i64, camx: i64, camy: i64, camz: i64, iwx: i64, iwy: i64, iwz: i64) -> i64 {
572 let v: *i64 = (base + O_V) as *i64
573 let r: *i64 = (base + O_RV) as *i64
574 let R: *i64 = (base + O_R) as *i64
575 let pj: *i64 = (base + O_PJ) as *i64
576 v[0] = f32_sub(f32_of(iwx), camx)
577 v[1] = f32_sub(f32_of(iwy), camy)
578 v[2] = f32_sub(f32_of(iwz), camz)
579 v[3] = f32_of(0)
580 m4_vec4(R, v, r)
581 let pz: i64 = r[2]
582 if (pz & 0x80000000) != 0 { pj[3] = 0; return 0 }
583 let zi: i64 = f32_int(f32_mul(pz, f32_of(64)))
584 if zi <= 0 { pj[3] = 0; return 0 }
585 pj[0] = HW + f32_int(f32_div(f32_mul(r[0], f32_of(FOCAL)), pz))
586 pj[1] = HH - f32_int(f32_div(f32_mul(r[1], f32_of(FOCAL)), pz))
587 pj[2] = zi
588 pj[3] = 1
589 return 0
590}
591func corners(base: i64, camx: i64, camy: i64, camz: i64, x0: i64, y0: i64, z0: i64) -> i64 {
592 let SX: *i64 = (base + O_SX) as *i64
593 let SY: *i64 = (base + O_SY) as *i64
594 let SD: *i64 = (base + O_SD) as *i64
595 let SOK: *i64 = (base + O_SOK) as *i64
596 let pj: *i64 = (base + O_PJ) as *i64
597 var k: i64 = 0
598 while k < 8 {
599 project(base, camx, camy, camz, x0 + (k & 1), y0 + ((k >> 1) & 1), z0 + ((k >> 2) & 1))
600 SX[k] = pj[0]; SY[k] = pj[1]; SD[k] = pj[2]; SOK[k] = pj[3]
601 k = k + 1
602 }
603 return 0
604}
605func face(base: i64, a: i64, b: i64, c: i64, dd: i64, lnx: i64, lny: i64, lnz: i64, col: i64) -> i64 {
606 let SX: *i64 = (base + O_SX) as *i64
607 let SY: *i64 = (base + O_SY) as *i64
608 let SD: *i64 = (base + O_SD) as *i64
609 let SOK: *i64 = (base + O_SOK) as *i64
610 if SOK[a] == 0 { return 0 }
611 if SOK[b] == 0 { return 0 }
612 if SOK[c] == 0 { return 0 }
613 if SOK[dd] == 0 { return 0 }
614 let fc: i64 = packc(col, shade(lnx, lny, lnz))
615 fill_z(base, SX[a], SY[a], SD[a], SX[b], SY[b], SD[b], SX[c], SY[c], SD[c], fc)
616 fill_z(base, SX[a], SY[a], SD[a], SX[c], SY[c], SD[c], SX[dd], SY[dd], SD[dd], fc)
617 return 0
618}
619// smooth-AO corner light from the classic 3-sample rule (two edge neighbors + the diagonal, all in the face's
620// air plane): both edges solid = full occlusion regardless of the diagonal. 255/215/175/135 steps.
621func ao3(s1: i64, s2: i64, sc: i64) -> i64 {
622 var ao: i64 = s1 + s2 + sc
623 if s1 == 1 { if s2 == 1 { ao = 3 } }
624 return 255 - ao * 40
625}
626// TEXTURED quad with PER-VERTEX LIGHT: corner UV convention a=(0,0) b=(255,0) c=(255,255) dd=(0,255);
627// la..ld = smooth-AO corner lights (packed light<<16|u<<8|v per vertex); bright = sun-normal x voxel hash.
628func face_t(base: i64, a: i64, b: i64, c: i64, dd: i64, lnx: i64, lny: i64, lnz: i64, kind: i64, fk: i64, vbr: i64, la: i64, lb: i64, lc: i64, ld: i64) -> i64 {
629 let SX: *i64 = (base + O_SX) as *i64
630 let SY: *i64 = (base + O_SY) as *i64
631 let SD: *i64 = (base + O_SD) as *i64
632 let SOK: *i64 = (base + O_SOK) as *i64
633 if SOK[a] == 0 { return 0 }
634 if SOK[b] == 0 { return 0 }
635 if SOK[c] == 0 { return 0 }
636 if SOK[dd] == 0 { return 0 }
637 let br: i64 = shade(lnx, lny, lnz) * vbr / 255
638 let pa: i64 = (la << 16)
639 let pb: i64 = (lb << 16) | (255 << 8)
640 let pc: i64 = (lc << 16) | (255 << 8) | 255
641 let pd: i64 = (ld << 16) | 255
642 fill_zt(base, SX[a], SY[a], SD[a], pa, SX[b], SY[b], SD[b], pb, SX[c], SY[c], SD[c], pc, kind, fk, br)
643 fill_zt(base, SX[a], SY[a], SD[a], pa, SX[c], SY[c], SD[c], pc, SX[dd], SY[dd], SD[dd], pd, kind, fk, br)
644 return 0
645}
646func draw_voxel(base: i64, camx: i64, camy: i64, camz: i64, gx: i64, gy: i64, gz: i64, kind: i64) -> i64 {
647 let up: i64 = solid(base, gx, gy + 1, gz)
648 // per-voxel brightness hash: block-to-block variation on top of the per-face texture
649 let vh: i64 = mw_h2(gx * 3 + gy * 7, gz * 5 + gy * 11, 999) % 21
650 let vbr: i64 = 234 + vh
651 corners(base, camx, camy, camz, gx, gy, gz)
652 if up == 0 {
653 var tb: i64 = vbr
654 // water SHORE: a water top touching land brightens (shallow/foam edge)
655 if kind == K_WATER {
656 var shore: i64 = 0
657 let e1: i64 = kind_c(base, gx + 1, gy, gz)
658 let e2: i64 = kind_c(base, gx - 1, gy, gz)
659 let e3: i64 = kind_c(base, gx, gy, gz + 1)
660 let e4: i64 = kind_c(base, gx, gy, gz - 1)
661 if e1 != 0 { if e1 != K_WATER { shore = 1 } }
662 if e2 != 0 { if e2 != K_WATER { shore = 1 } }
663 if e3 != 0 { if e3 != K_WATER { shore = 1 } }
664 if e4 != 0 { if e4 != K_WATER { shore = 1 } }
665 if shore == 1 { tb = tb + 24 }
666 }
667 if tb > 255 { tb = 255 }
668 // SMOOTH per-vertex AO, top face quad (2,3,7,6) -> corners (cx,cz) = (0,0),(1,0),(1,1),(0,1)
669 let txm: i64 = solid(base, gx - 1, gy + 1, gz)
670 let txp: i64 = solid(base, gx + 1, gy + 1, gz)
671 let tzm: i64 = solid(base, gx, gy + 1, gz - 1)
672 let tzp: i64 = solid(base, gx, gy + 1, gz + 1)
673 let la: i64 = ao3(txm, tzm, solid(base, gx - 1, gy + 1, gz - 1))
674 let lb: i64 = ao3(txp, tzm, solid(base, gx + 1, gy + 1, gz - 1))
675 let lc: i64 = ao3(txp, tzp, solid(base, gx + 1, gy + 1, gz + 1))
676 let ld: i64 = ao3(txm, tzp, solid(base, gx - 1, gy + 1, gz + 1))
677 face_t(base, 2, 3, 7, 6, 0, 1, 0, kind, 0, tb, la, lb, lc, ld)
678 }
679 // +x face quad (1,5,7,3) -> corners (cy,cz) = (0,0),(0,1),(1,1),(1,0); air plane x = gx+1
680 if solid(base, gx + 1, gy, gz) == 0 {
681 let xym: i64 = solid(base, gx + 1, gy - 1, gz)
682 let xyp: i64 = solid(base, gx + 1, gy + 1, gz)
683 let xzm: i64 = solid(base, gx + 1, gy, gz - 1)
684 let xzp: i64 = solid(base, gx + 1, gy, gz + 1)
685 let xa: i64 = ao3(xym, xzm, solid(base, gx + 1, gy - 1, gz - 1))
686 let xb: i64 = ao3(xym, xzp, solid(base, gx + 1, gy - 1, gz + 1))
687 let xc: i64 = ao3(xyp, xzp, solid(base, gx + 1, gy + 1, gz + 1))
688 let xd: i64 = ao3(xyp, xzm, solid(base, gx + 1, gy + 1, gz - 1))
689 face_t(base, 1, 5, 7, 3, 1, 0, 0, kind, 1, vbr, xa, xb, xc, xd)
690 }
691 // -x face quad (0,2,6,4) -> corners (cy,cz) = (0,0),(1,0),(1,1),(0,1); air plane x = gx-1
692 if solid(base, gx - 1, gy, gz) == 0 {
693 let mym: i64 = solid(base, gx - 1, gy - 1, gz)
694 let myp: i64 = solid(base, gx - 1, gy + 1, gz)
695 let mzm: i64 = solid(base, gx - 1, gy, gz - 1)
696 let mzp: i64 = solid(base, gx - 1, gy, gz + 1)
697 let ma: i64 = ao3(mym, mzm, solid(base, gx - 1, gy - 1, gz - 1))
698 let mb: i64 = ao3(myp, mzm, solid(base, gx - 1, gy + 1, gz - 1))
699 let mc: i64 = ao3(myp, mzp, solid(base, gx - 1, gy + 1, gz + 1))
700 let md: i64 = ao3(mym, mzp, solid(base, gx - 1, gy - 1, gz + 1))
701 face_t(base, 0, 2, 6, 4, 0 - 1, 0, 0, kind, 1, vbr, ma, mb, mc, md)
702 }
703 // +z face quad (4,5,7,6) -> corners (cx,cy) = (0,0),(1,0),(1,1),(0,1); air plane z = gz+1
704 if solid(base, gx, gy, gz + 1) == 0 {
705 let zxm: i64 = solid(base, gx - 1, gy, gz + 1)
706 let zxp: i64 = solid(base, gx + 1, gy, gz + 1)
707 let zym: i64 = solid(base, gx, gy - 1, gz + 1)
708 let zyp: i64 = solid(base, gx, gy + 1, gz + 1)
709 let za: i64 = ao3(zxm, zym, solid(base, gx - 1, gy - 1, gz + 1))
710 let zb2: i64 = ao3(zxp, zym, solid(base, gx + 1, gy - 1, gz + 1))
711 let zc: i64 = ao3(zxp, zyp, solid(base, gx + 1, gy + 1, gz + 1))
712 let zd: i64 = ao3(zxm, zyp, solid(base, gx - 1, gy + 1, gz + 1))
713 face_t(base, 4, 5, 7, 6, 0, 0, 1, kind, 1, vbr, za, zb2, zc, zd)
714 }
715 // -z face quad (0,1,3,2) -> corners (cx,cy) = (0,0),(1,0),(1,1),(0,1); air plane z = gz-1
716 if solid(base, gx, gy, gz - 1) == 0 {
717 let wxm: i64 = solid(base, gx - 1, gy, gz - 1)
718 let wxp: i64 = solid(base, gx + 1, gy, gz - 1)
719 let wym: i64 = solid(base, gx, gy - 1, gz - 1)
720 let wyp: i64 = solid(base, gx, gy + 1, gz - 1)
721 let wa: i64 = ao3(wxm, wym, solid(base, gx - 1, gy - 1, gz - 1))
722 let wb: i64 = ao3(wxp, wym, solid(base, gx + 1, gy - 1, gz - 1))
723 let wc: i64 = ao3(wxp, wyp, solid(base, gx + 1, gy + 1, gz - 1))
724 let wd: i64 = ao3(wxm, wyp, solid(base, gx - 1, gy + 1, gz - 1))
725 face_t(base, 0, 1, 3, 2, 0, 0, 0 - 1, kind, 1, vbr, wa, wb, wc, wd)
726 }
727 return 0
728}
729// tree presence from hash, density by biome (desert none, forest dense); trees are SOLID blocks via kind_c
730func has_tree(gx: i64, gz: i64, h: i64, bio: i64) -> i64 {
731 if h <= WWATER { return 0 }
732 if h >= 16 { return 0 }
733 if bio == 2 { return 0 }
734 var n: i64 = (gx * 131 + gz * 197 + gx * gz * 7 + 13) % 1021
735 if n < 0 { n = n + 1021 }
736 var den: i64 = 41
737 if bio == 1 { den = 19 }
738 if bio == 3 { den = 31 }
739 if n % den == 0 { return 1 }
740 return 0
741}
742func render_at(base: i64, camx: i64, camy: i64, camz: i64, cgx: i64, cgz: i64) -> i64 {
743 ensure_pack(base) // lazy-load the built-in block DATA pack (a mod may already have overridden records)
744 let fb: *i64 = (base + O_FB) as *i64
745 let zb: *i64 = (base + O_ZB) as *i64
746 let stp: *i64 = (base + O_STAT) as *i64
747 stp[0] = 0
748 // ---- SKY: vertical gradient + the SUN projected from the actual light direction (2,3,1) + procedural
749 // clouds (value-noise band anchored to the sun's screen position -> pans with the view). Integer per-pixel.
750 let sv: *i64 = (base + O_V) as *i64
751 let rv: *i64 = (base + O_RV) as *i64
752 let R: *i64 = (base + O_R) as *i64
753 sv[0] = f32_of(2); sv[1] = f32_of(3); sv[2] = f32_of(1); sv[3] = f32_of(0)
754 m4_vec4(R, sv, rv)
755 var sunx: i64 = 0 - PI2_MAGIC_9999
756 var suny: i64 = 0 - PI2_MAGIC_9999
757 if (rv[2] & 0x80000000) == 0 { if f32_int(f32_mul(rv[2], f32_of(64))) > 0 {
758 sunx = HW + f32_int(f32_div(f32_mul(rv[0], f32_of(FOCAL)), rv[2]))
759 suny = HH - f32_int(f32_div(f32_mul(rv[1], f32_of(FOCAL)), rv[2]))
760 } }
761 var py: i64 = 0
762 var i: i64 = 0
763 while py < H {
764 let t: i64 = py * 256 / H
765 var px: i64 = 0
766 while px < W {
767 var r: i64 = 88 + 92 * t / 256
768 var g: i64 = 140 + 72 * t / 256
769 var b: i64 = 230 + 10 * t / 256
770 let dx: i64 = px - sunx
771 let dy: i64 = py - suny
772 let d2: i64 = dx * dx + dy * dy
773 if d2 < 36 { r = 255; g = 246; b = 205 }
774 else { if d2 < 620 {
775 let hb2: i64 = (620 - d2) * 44 / 620
776 r = r + hb2
777 g = g + hb2 * 3 / 4
778 b = b + hb2 / 4
779 } }
780 if py < H * 2 / 5 {
781 let cn: i64 = mw_o2(px + sunx * 2 + PI2_MAGIC_65536, py * 3 + PI2_MAGIC_65536, 24, 77)
782 if cn > 150 {
783 var wgt: i64 = (cn - 150) * 2
784 if wgt > 110 { wgt = 110 }
785 r = r + (255 - r) * wgt / 255
786 g = g + (255 - g) * wgt / 255
787 b = b + (255 - b) * wgt / 255
788 }
789 }
790 if r > 255 { r = 255 }
791 if g > 255 { g = 255 }
792 if b > 255 { b = 255 }
793 fb[i] = r + g * 256 + b * PI2_MAGIC_65536
794 zb[i] = ZFAR
795 px = px + 1
796 i = i + 1
797 }
798 py = py + 1
799 }
800 // slide the height/biome cache window to the camera (world is infinite; the window is just a cache)
801 let orgp: *i64 = (base + O_HCORG) as *i64
802 let hc: *i64 = (base + O_HC) as *i64
803 let hb: *i64 = (base + O_HB) as *i64
804 let ox: i64 = cgx - 19
805 let oz: i64 = cgz - 19
806 orgp[0] = ox
807 orgp[1] = oz
808 orgp[2] = 1
809 let ht: *i64 = (base + O_HT) as *i64
810 var wz: i64 = 0
811 while wz < WWIN {
812 var wx: i64 = 0
813 while wx < WWIN {
814 let ch: i64 = terrain_h0(ox + wx, oz + wz)
815 let cb: i64 = biome_of(ox + wx, oz + wz)
816 hc[wz * WWIN + wx] = ch
817 hb[wz * WWIN + wx] = cb
818 ht[wz * WWIN + wx] = has_tree(ox + wx, oz + wz, ch, cb)
819 wx = wx + 1
820 }
821 wz = wz + 1
822 }
823 var gz: i64 = cgz - VDIST
824 while gz <= cgz + VDIST {
825 var gx: i64 = cgx - VDIST
826 while gx <= cgx + VDIST {
827 var gy: i64 = 0
828 while gy < YMAX {
829 let kk: i64 = kind_c(base, gx, gy, gz)
830 if kk != 0 { draw_voxel(base, camx, camy, camz, gx, gy, gz, kk) }
831 gy = gy + 1
832 }
833 gx = gx + 1
834 }
835 gz = gz + 1
836 }
837 // (decorative tree pass removed -- trees are SOLID textured voxels via kind_c now)
838 // peer avatars (multiplayer): z-buffered against the world like everything else
839 var pi: i64 = 0
840 while pi < MAXPEERS {
841 let pp: *i64 = peer_ptr(base, pi)
842 if pp[0] == 1 { draw_peer(base, camx, camy, camz, pp[1], pp[2], pp[3], pi) }
843 pi = pi + 1
844 }
845 return 0
846}
847func build_R(base: i64, ycos: i64, ysin: i64, pcos: i64, psin: i64) -> i64 {
848 let Ry: *i64 = (base + O_RY) as *i64
849 let Rx: *i64 = (base + O_RX) as *i64
850 let R: *i64 = (base + O_R) as *i64
851 // NEGATED ysin: the VIEW transform is the INVERSE of the camera orientation. With raw +ysin the view
852 // rotated OPPOSITE the walk heading -> mouse-right turned the view LEFT while movement went right = the
853 // operator's "left is right" mirror (proof: heading (sin yaw, cos yaw) through m4_roty(c, +s) maps to
854 // 2*yaw off-center; through m4_roty(c, -s) it maps to screen center). Look/move/aim now agree.
855 m4_roty(fratio(ycos, 1000), f32_neg(fratio(ysin, 1000)), Ry)
856 // RAW psin (NO f32_neg): in this m4_rotx convention r2 = s*vy + c*vz, so negative psin = pitch DOWN
857 // (found 2026-07-02 via the edit-visibility gate + nx_mw_f32_probe).
858 m4_rotx(fratio(pcos, 1000), fratio(psin, 1000), Rx)
859 m4_mul(Rx, Ry, R)
860 return 0
861}
862func mem_bytes() -> i64 { return O_STAT + 64 + 64 }
863func drawn_px_at(base: i64) -> i64 { let s: *i64 = (base + O_STAT) as *i64; return s[0] }
864func drawn_px() -> i64 { return drawn_px_at(0) }
865// ---- SOVEREIGN TICK: camera + physics + input decoding + aim rays ALL in-wasm. The JS shim may only
866// capture raw input (key bitmask, mouse deltas), memcpy net bytes, and blit -- zero game logic outside. ----
867// keys bitmask: 1=W 2=S 4=A 8=D 16=Space/Up 32=Shift/Down 64=F(fly toggle, edge-detected here)
868func cam_ptr(base: i64) -> *i64 { return (base + O_CAM) as *i64 }
869func start_at(base: i64) -> i64 {
870 let c: *i64 = cam_ptr(base)
871 c[0] = 30 * 256 + 128
872 c[2] = 30 * 256 + 128
873 c[1] = surface_y(base, 30, 30) * 256 + 410
874 c[3] = 0
875 c[4] = 0 - 532 // -0.13 rad: gaze slightly down at spawn
876 c[5] = 0
877 c[6] = 0
878 c[7] = 0
879 c[8] = 0
880 c[9] = 0 // invert-Y off (default: mouse-up looks up); client toggles with 'I' -- controls are DATA
881 c[10] = 0 // invert-X off; client toggles with 'O'
882 c[11] = MW_SENS // mouse sensitivity (client-owned data)
883 return 0
884}
885func tick_at(base: i64, keys: i64, mdx: i64, mdy: i64) -> i64 {
886 let c: *i64 = cam_ptr(base)
887 // mouse look -- sign is CONFIG (invert-X c[10], invert-Y c[9]) so the client owns feel; sens is c[11].
888 // STANDARD default (invY=0): mouse-forward (movementY<0) looks UP, mouse-back looks DOWN => pitch -= mdy.
889 // (History: the original -mdy was CORRECT; the first "inverted" report was the YAW mirror [fixed in
890 // build_R] and pitch got wrongly flipped in overcorrection -- restored 2026-07-03. 'I' toggles feel.)
891 var sens: i64 = c[11]
892 if sens <= 0 { sens = MW_SENS }
893 var sx: i64 = 1
894 if c[10] == 1 { sx = 0 - 1 }
895 var sy: i64 = 1
896 if c[9] == 1 { sy = 0 - 1 }
897 c[3] = c[3] + sx * mdx * sens
898 c[4] = c[4] - sy * mdy * sens
899 if c[4] > PI2_MAGIC_5734 { c[4] = PI2_MAGIC_5734 }
900 if c[4] < 0 - PI2_MAGIC_5734 { c[4] = 0 - PI2_MAGIC_5734 }
901 // F edge-toggles fly; I edge-toggles invert-Y; O edge-toggles invert-X (edges vs prevkeys c[8])
902 if (keys & 64) != 0 { if (c[8] & 64) == 0 { c[6] = 1 - c[6] } }
903 if (keys & 128) != 0 { if (c[8] & 128) == 0 { c[9] = 1 - c[9] } }
904 if (keys & 256) != 0 { if (c[8] & 256) == 0 { c[10] = 1 - c[10] } }
905 c[8] = keys
906 // walk vector from the integer-trig heading (parity with the old shim: diagonals unnormalized)
907 let sy: i64 = mw_sin4096(c[3])
908 let cy: i64 = mw_cos4096(c[3])
909 var dx: i64 = 0
910 var dz: i64 = 0
911 if (keys & 1) != 0 { dx = dx + sy; dz = dz + cy }
912 if (keys & 2) != 0 { dx = dx - sy; dz = dz - cy }
913 if (keys & 8) != 0 { dx = dx + cy; dz = dz - sy }
914 if (keys & 4) != 0 { dx = dx - cy; dz = dz + sy }
915 let tx: i64 = c[0] + dx * 67 / PI2_MAGIC_4096
916 let tz: i64 = c[2] + dz * 67 / PI2_MAGIC_4096
917 if c[6] == 1 {
918 c[0] = tx
919 c[2] = tz
920 if (keys & 16) != 0 { c[1] = c[1] + 67 }
921 if (keys & 32) != 0 { c[1] = c[1] - 67 }
922 c[5] = 0
923 return 0
924 }
925 // ---- VOXEL PHYSICS (rework 2026-07-03: the old column-top surface физics broke caves [the "floor"
926 // under a ceiling was the ceiling top], glided up 1.3-block steps, and WALKED ON water) ----
927 // body = the two cells at feet and head height; water is render-solid but PHYS-permeable (swimmable)
928 let feet0: i64 = c[1] - 410
929 let fgy: i64 = mw_fd256(feet0)
930 // horizontal, axis-separated: blocked if either body cell at the target column is phys-solid (JUMP to climb)
931 let txg: i64 = mw_fd256(tx)
932 let czg: i64 = mw_fd256(c[2])
933 if phys_solid(base, txg, fgy, czg) == 0 { if phys_solid(base, txg, fgy + 1, czg) == 0 { c[0] = tx } }
934 let cxg: i64 = mw_fd256(c[0])
935 let tzg: i64 = mw_fd256(tz)
936 if phys_solid(base, cxg, fgy, tzg) == 0 { if phys_solid(base, cxg, fgy + 1, tzg) == 0 { c[2] = tz } }
937 // in water? (feet cell) -> buoyant sink + Space swims up; else gravity with terminal velocity
938 let pgx: i64 = mw_fd256(c[0])
939 let pgz: i64 = mw_fd256(c[2])
940 let fk2: i64 = kind_c(base, pgx, mw_fd256(c[1] - 410), pgz)
941 if fk2 == K_WATER {
942 c[5] = c[5] - 4
943 if c[5] < 0 - 40 { c[5] = 0 - 40 }
944 if (keys & 16) != 0 { c[5] = 55 }
945 c[7] = 0
946 } else {
947 c[5] = c[5] - 12
948 if c[5] < 0 - 200 { c[5] = 0 - 200 }
949 }
950 c[1] = c[1] + c[5]
951 // FLOOR: the highest phys-solid BELOW the feet in this column (caves/overhangs correct, not the column top)
952 let g: i64 = floor_y(base, pgx, pgz, mw_fd256(c[1] - 410)) * 256 + 410
953 if c[1] <= g { c[1] = g; c[5] = 0; c[7] = 1 } else { if fk2 != K_WATER { c[7] = 0 } }
954 // CEILING: rising into a solid cell stops the head
955 if c[5] > 0 {
956 let hgy: i64 = mw_fd256(c[1] + 60)
957 if phys_solid(base, pgx, hgy, pgz) == 1 { c[1] = hgy * 256 - 61; c[5] = 0 }
958 }
959 if (keys & 16) != 0 { if c[7] == 1 { c[5] = 179 } }
960 return 0
961}
962// aim direction in 1000-scale from the camera's yaw/pitch (for dig/put rays and future projectiles)
963func aim_at(base: i64, out: *i64) -> i64 {
964 let c: *i64 = cam_ptr(base)
965 let sy: i64 = mw_sin4096(c[3])
966 let cy: i64 = mw_cos4096(c[3])
967 let sp: i64 = mw_sin4096(c[4])
968 let cp: i64 = mw_cos4096(c[4])
969 out[0] = sy * cp * 1000 / PI2_MAGIC_16777216
970 out[1] = sp * 1000 / PI2_MAGIC_4096
971 out[2] = cy * cp * 1000 / PI2_MAGIC_16777216
972 return 0
973}
974func dig_at(base: i64) -> i64 {
975 let c: *i64 = cam_ptr(base)
976 let a: *i64 = (base + O_V) as *i64 // scratch (rebuilt every projection anyway)
977 aim_at(base, a)
978 let o: *i64 = (base + O_PJ) as *i64
979 raycast(base, c[0], c[1], c[2], a[0], a[1], a[2], o)
980 if o[0] == 1 { if o[2] >= 1 { if o[2] < YMAX { mw_eset(base, o[1], o[2], o[3], 2) } } }
981 return 0
982}
983func put_at(base: i64) -> i64 {
984 let c: *i64 = cam_ptr(base)
985 let a: *i64 = (base + O_V) as *i64
986 aim_at(base, a)
987 let o: *i64 = (base + O_PJ) as *i64
988 raycast(base, c[0], c[1], c[2], a[0], a[1], a[2], o)
989 if o[0] == 1 { if o[5] >= 0 { if o[5] < YMAX { mw_eset(base, o[4], o[5], o[6], 1) } } }
990 return 0
991}
992func render_cam_at(base: i64) -> i64 {
993 let c: *i64 = cam_ptr(base)
994 build_R(base, mw_cos4096(c[3]) * 1000 / PI2_MAGIC_4096, mw_sin4096(c[3]) * 1000 / PI2_MAGIC_4096, mw_cos4096(c[4]) * 1000 / PI2_MAGIC_4096, mw_sin4096(c[4]) * 1000 / PI2_MAGIC_4096)
995 let camx: i64 = f32_div(f32_of(c[0]), f32_of(256))
996 let camy: i64 = f32_div(f32_of(c[1]), f32_of(256))
997 let camz: i64 = f32_div(f32_of(c[2]), f32_of(256))
998 render_at(base, camx, camy, camz, mw_fd256(c[0]), mw_fd256(c[2]))
999 return 0
1000}
1001func net_push_at(base: i64) -> i64 {
1002 let c: *i64 = cam_ptr(base)
1003 return net_pack_self_at(base, c[0], c[1], c[2], c[3] * 1000 / PI2_MAGIC_4096, c[4] * 1000 / PI2_MAGIC_4096)
1004}
1005// ---- multiplayer: peer table + fractional-position avatar boxes + in-wasm netstate ----
1006func peer_ptr(base: i64, i: i64) -> *i64 { return (base + O_PEERS + i * 40) as *i64 }
1007func peers_clear_at(base: i64) -> i64 {
1008 var i: i64 = 0
1009 while i < MAXPEERS { let p: *i64 = peer_ptr(base, i); p[0] = 0; i = i + 1 }
1010 return 0
1011}
1012func peer_set_at(base: i64, i: i64, x256: i64, y256: i64, z256: i64, yaw1000: i64) -> i64 {
1013 if i < 0 { return 0 - 1 }
1014 if i >= MAXPEERS { return 0 - 1 }
1015 let p: *i64 = peer_ptr(base, i)
1016 p[0] = 1; p[1] = x256; p[2] = y256; p[3] = z256; p[4] = yaw1000
1017 return 0
1018}
1019// project a FIXED-POINT (x256) world position (peers move smoothly between cells)
1020func projectp(base: i64, camx: i64, camy: i64, camz: i64, wx256: i64, wy256: i64, wz256: i64) -> i64 {
1021 let v: *i64 = (base + O_V) as *i64
1022 let r: *i64 = (base + O_RV) as *i64
1023 let R: *i64 = (base + O_R) as *i64
1024 let pj: *i64 = (base + O_PJ) as *i64
1025 v[0] = f32_sub(f32_div(f32_of(wx256), f32_of(256)), camx)
1026 v[1] = f32_sub(f32_div(f32_of(wy256), f32_of(256)), camy)
1027 v[2] = f32_sub(f32_div(f32_of(wz256), f32_of(256)), camz)
1028 v[3] = f32_of(0)
1029 m4_vec4(R, v, r)
1030 let pz: i64 = r[2]
1031 if (pz & 0x80000000) != 0 { pj[3] = 0; return 0 }
1032 let zi: i64 = f32_int(f32_mul(pz, f32_of(64)))
1033 if zi <= 0 { pj[3] = 0; return 0 }
1034 pj[0] = HW + f32_int(f32_div(f32_mul(r[0], f32_of(FOCAL)), pz))
1035 pj[1] = HH - f32_int(f32_div(f32_mul(r[1], f32_of(FOCAL)), pz))
1036 pj[2] = zi
1037 pj[3] = 1
1038 return 0
1039}
1040func corners_p(base: i64, camx: i64, camy: i64, camz: i64, x256: i64, y256: i64, z256: i64, w256: i64, h256: i64, l256: i64) -> i64 {
1041 let SX: *i64 = (base + O_SX) as *i64
1042 let SY: *i64 = (base + O_SY) as *i64
1043 let SD: *i64 = (base + O_SD) as *i64
1044 let SOK: *i64 = (base + O_SOK) as *i64
1045 let pj: *i64 = (base + O_PJ) as *i64
1046 var k: i64 = 0
1047 while k < 8 {
1048 projectp(base, camx, camy, camz, x256 + (k & 1) * w256, y256 + ((k >> 1) & 1) * h256, z256 + ((k >> 2) & 1) * l256)
1049 SX[k] = pj[0]; SY[k] = pj[1]; SD[k] = pj[2]; SOK[k] = pj[3]
1050 k = k + 1
1051 }
1052 return 0
1053}
1054func draw_boxp(base: i64, camx: i64, camy: i64, camz: i64, x256: i64, y256: i64, z256: i64, w256: i64, h256: i64, l256: i64, col: i64) -> i64 {
1055 corners_p(base, camx, camy, camz, x256, y256, z256, w256, h256, l256)
1056 face(base, 2, 3, 7, 6, 0, 1, 0, col)
1057 face(base, 1, 5, 7, 3, 1, 0, 0, col)
1058 face(base, 0, 2, 6, 4, 0 - 1, 0, 0, col)
1059 face(base, 4, 5, 7, 6, 0, 0, 1, col)
1060 face(base, 0, 1, 3, 2, 0, 0, 0 - 1, col)
1061 return 0
1062}
1063func peer_body_col(idx: i64) -> i64 {
1064 let m: i64 = idx % 4
1065 if m == 0 { return 210 + 60 * 256 + 50 * PI2_MAGIC_65536 } // red
1066 if m == 1 { return 70 + 110 * 256 + 220 * PI2_MAGIC_65536 } // blue
1067 if m == 2 { return 230 + 150 * 256 + 40 * PI2_MAGIC_65536 } // orange
1068 return 170 + 80 * 256 + 200 * PI2_MAGIC_65536 // purple
1069}
1070// avatar: body box (0.6 x 1.2 x 0.6) from the feet + head cube; y256 = the peer's EYE height (shim cam.y)
1071func draw_peer(base: i64, camx: i64, camy: i64, camz: i64, x256: i64, y256: i64, z256: i64, idx: i64) -> i64 {
1072 let feet: i64 = y256 - 410
1073 draw_boxp(base, camx, camy, camz, x256 - 77, feet, z256 - 77, 154, 307, 154, peer_body_col(idx))
1074 draw_boxp(base, camx, camy, camz, x256 - 64, feet + 307, z256 - 64, 128, 128, 128, 235 + 205 * 256 + 170 * PI2_MAGIC_65536)
1075 return 0
1076}
1077// pack own state into the 28B netstate frame at O_NETOUT (nx_pets_netstate layout; ALL protocol in-wasm)
1078func net_pack_self_at(base: i64, x256: i64, y256: i64, z256: i64, yaw1000: i64, pitch1000: i64) -> i64 {
1079 let f: *i64 = (base + O_NETF) as *i64
1080 f[0] = x256; f[1] = y256; f[2] = z256; f[3] = yaw1000; f[4] = pitch1000
1081 f[5] = 0; f[6] = 0; f[7] = 0; f[8] = 0; f[9] = 0
1082 return ps_pack((base + O_NETOUT) as *u8, f)
1083}
1084// ingest a /roster body ([len:1][frame] records) from O_NETIN -> peer table; returns peers set.
1085// bad-magic frames are REJECTED (ps_unpack 0) and do not occupy a slot.
1086func net_ingest_roster_at(base: i64, total0: i64) -> i64 {
1087 var total: i64 = total0
1088 if total > PI2_MAGIC_4096 { total = PI2_MAGIC_4096 }
1089 peers_clear_at(base)
1090 let inb: *u8 = (base + O_NETIN) as *u8
1091 let f: *i64 = (base + O_NETF) as *i64
1092 var o: i64 = 0
1093 var slot: i64 = 0
1094 var go: i64 = 1
1095 while go == 1 {
1096 if o >= total { go = 0 } else { if slot >= MAXPEERS { go = 0 } else {
1097 let fl: i64 = inb[o] as i64
1098 o = o + 1
1099 if fl <= 0 { go = 0 } else { if o + fl > total { go = 0 } else {
1100 if ps_unpack((inb as i64 + o) as *u8, fl, f) == 1 {
1101 peer_set_at(base, slot, f[0], f[1], f[2], f[3])
1102 slot = slot + 1
1103 }
1104 o = o + fl
1105 } }
1106 } }
1107 }
1108 return slot
1109}
1110// surface height (highest solid voxel +1) at any global column -- includes trees, water surface, and edits
1111func surface_y(base: i64, gx: i64, gz: i64) -> i64 {
1112 var gy: i64 = YMAX - 1
1113 while gy >= 0 {
1114 if kind_c(base, gx, gy, gz) != 0 { return gy + 1 }
1115 gy = gy - 1
1116 }
1117 return 0
1118}
1119// 3D-DDA raycast (floor-division cell stepping so negative coords aim correctly)
1120func raycast(base: i64, camx256: i64, camy256: i64, camz256: i64, dx1000: i64, dy1000: i64, dz1000: i64, out: *i64) -> i64 {
1121 var px: i64 = camx256
1122 var py: i64 = camy256
1123 var pz: i64 = camz256
1124 var ax: i64 = mw_fd256(px)
1125 var ay: i64 = mw_fd256(py)
1126 var az: i64 = mw_fd256(pz)
1127 var i: i64 = 0
1128 while i < 140 {
1129 let gx: i64 = mw_fd256(px)
1130 let gy: i64 = mw_fd256(py)
1131 let gz: i64 = mw_fd256(pz)
1132 if solid(base, gx, gy, gz) == 1 {
1133 out[0] = 1; out[1] = gx; out[2] = gy; out[3] = gz; out[4] = ax; out[5] = ay; out[6] = az
1134 return 0
1135 }
1136 ax = gx; ay = gy; az = gz
1137 px = px + dx1000 * 38 / 1000
1138 py = py + dy1000 * 38 / 1000
1139 pz = pz + dz1000 * 38 / 1000
1140 i = i + 1
1141 }
1142 out[0] = 0
1143 return 0
1144}
1145// WASM exports (same surface as nx_wasmvox3d -> same shim contract)
1146func render(camx256: i64, camy256: i64, camz256: i64, ycos: i64, ysin: i64, pcos: i64, psin: i64) -> i64 {
1147 build_R(0, ycos, ysin, pcos, psin)
1148 let camx: i64 = f32_div(f32_of(camx256), f32_of(256))
1149 let camy: i64 = f32_div(f32_of(camy256), f32_of(256))
1150 let camz: i64 = f32_div(f32_of(camz256), f32_of(256))
1151 render_at(0, camx, camy, camz, mw_fd256(camx256), mw_fd256(camz256))
1152 return 0
1153}
1154func terrain_at(gx: i64, gz: i64) -> i64 { return surface_y(0, gx, gz) }
1155// break the aimed voxel (bedrock y=0 unbreakable)
1156func break_at(camx256: i64, camy256: i64, camz256: i64, dx1000: i64, dy1000: i64, dz1000: i64) -> i64 {
1157 let o: *i64 = (0 + O_PJ) as *i64
1158 raycast(0, camx256, camy256, camz256, dx1000, dy1000, dz1000, o)
1159 if o[0] == 1 { if o[2] >= 1 { if o[2] < YMAX { mw_eset(0, o[1], o[2], o[3], 2) } } }
1160 return 0
1161}
1162// place a solid voxel in the air cell just before the hit
1163func place_at(camx256: i64, camy256: i64, camz256: i64, dx1000: i64, dy1000: i64, dz1000: i64) -> i64 {
1164 let o: *i64 = (0 + O_PJ) as *i64
1165 raycast(0, camx256, camy256, camz256, dx1000, dy1000, dz1000, o)
1166 if o[0] == 1 { if o[5] >= 0 { if o[5] < YMAX { mw_eset(0, o[4], o[5], o[6], 1) } } }
1167 return 0
1168}
1169func edit_count_at(base: i64) -> i64 { let c: *i64 = (base + O_ECNT) as *i64; return c[0] }
1170func edit_count() -> i64 { return edit_count_at(0) }
1171// multiplayer exports (base=0 wrappers; the JS shim only memcpy's bytes + calls these)
1172func peers_clear() -> i64 { return peers_clear_at(0) }
1173func peer_set(i: i64, x256: i64, y256: i64, z256: i64, yaw1000: i64) -> i64 { return peer_set_at(0, i, x256, y256, z256, yaw1000) }
1174func net_pack_self(x256: i64, y256: i64, z256: i64, yaw1000: i64, pitch1000: i64) -> i64 { return net_pack_self_at(0, x256, y256, z256, yaw1000, pitch1000) }
1175func net_ingest_roster(total: i64) -> i64 { return net_ingest_roster_at(0, total) }
1176func net_out_off() -> i64 { return O_NETOUT }
1177func net_in_off() -> i64 { return O_NETIN }
1178// sovereign-tick exports: the WHOLE game loop body is in-wasm; JS calls these with raw input only
1179func start() -> i64 { return start_at(0) }
1180func tick(keys: i64, mdx: i64, mdy: i64) -> i64 { return tick_at(0, keys, mdx, mdy) }
1181func render_cam() -> i64 { return render_cam_at(0) }
1182func dig() -> i64 { return dig_at(0) }
1183func put() -> i64 { return put_at(0) }
1184func net_push() -> i64 { return net_push_at(0) }
1185// controls-as-data readouts for the HUD (client displays; logic stays in-wasm): invY*10 + invX
1186func invert_state() -> i64 { let c: *i64 = cam_ptr(0); return c[9] * 10 + c[10] }
1187// ---- MOD API (blocks-as-DATA): a mod is a sequence of pack_block records fed at RUNTIME (no recompile).
1188// pack_reset -> reload the built-in base pack; pack_block(id, top_rgb, side_rgb, style) -> override/extend.
1189func pack_reset() -> i64 { return pack_default_at(0) }
1190func pack_block(id: i64, top: i64, side: i64, style: i64) -> i64 { ensure_pack(0); return blk_set(0, id, top, side, style) }
1191func fb_off() -> i64 { return O_FB }
1192func ww() -> i64 { return W }
1193func hh() -> i64 { return H }
1194func main() -> i64 { return 0 }