nx_pets3d_wasm.nx source
↩ module page · 910 lines · 43464 B
1// EXPORTED-ENTRYPOINTS: a wasm module. Its exports are invoked by the browser host, not by any .nx
2// call site, so the reachability census cannot see their caller. Declared so invisibly-wired entry
3// points are bucketed rather than ratcheted as debt -- deleting one would break the live page.
4// nx_pets3d_wasm.nx -- KEYSTONE sub-rung B4: a REAL orbitable voxel TERRAIN in-browser wasm (not a toy
5// cube). Composes the HAL-free nx_render_core to render a 12x12 procedural voxel world: per-column
6// height, height-coloured (water/grass/rock/snow), directional face light, solid columns drawn as boxes,
7// z-buffered. gi_render(frame) orbits the camera around the world centre on a 16-step ring (the native
8// renderer's proven turntable framing), so JS can drive it from input -> interactive. NO syscalls: i64
9// packed-u32-RGBA fb + zb at fixed offsets; sovereign Q14 sin/cos angle table (no JS trig). license_tier: ORIGINAL
10import "nx_render_core.nx"
11import "nx_netquant.nx" // MP-R1: sovereign bit-packed player-state codec (host only moves bytes)
12import "nx_lerp_smooth.nx" // MP-R1b: peer render smoothing (interp prev->cur + bounded extrapolation)
13const O_MAGIC_60000: i64 = 60000
14const O_MAGIC_65536: i64 = 65536
15const O_MAGIC_16777216: i64 = 16777216
16const O_MAGIC_4096: i64 = 4096
17const O_MAGIC_80000: i64 = 80000
18const O_MAGIC_13418: i64 = 13418
19const O_MAGIC_9402: i64 = 9402
20const O_MAGIC_16000000: i64 = 16000000
21const O_MAGIC_6000: i64 = 6000
22const O_MAGIC_16384: i64 = 16384
23const O_MAGIC_8192: i64 = 8192
24const O_MAGIC_1200: i64 = 1200
25const O_MAGIC_1047: i64 = 1047
26const O_MAGIC_1320: i64 = 1320
27const O_MAGIC_12345: i64 = 12345
28const O_MAGIC_6554: i64 = 6554
29const O_MAGIC_1103515: i64 = 1103515
30const O_MAGIC_2147483647: i64 = 2147483647
31
32const VW: i64 = 192 // internal render res (4:3, ~2.25x sharper than the old 128x96)
33const VH: i64 = 144
34const HW: i64 = 96
35const HH: i64 = 72
36const VQ: i64 = 16384
37const GN: i64 = 24 // world is GN x GN columns
38const FBOFF: i64 = 240000 // i64 framebuffer (192*144*8 = 221184 B -> ends 461184), above the save region
39const ZBOFF: i64 = 461184 // i64 z-buffer (-> ends 682368); both inside the 16-page (1 MB) default
40const O_MVP: i64 = 1024
41const O_T: i64 = 1168
42const O_P: i64 = 1312
43const O_RY: i64 = 1456
44const O_RX: i64 = 1600
45const O_T1: i64 = 1744
46const O_V: i64 = 1888
47const O_VS: i64 = 2048
48const O_CS: i64 = 2080
49const O_TRI: i64 = 2112 // triangle scratch (15 i64 for textured verts -> ends 2232)
50const O_CORN: i64 = 2240 // 8 corners * 4 i64 = 256 -> ends 2496
51const TN: i64 = 8 // texels per face edge (UV runs 0..TN across a face)
52const O_DELTA: i64 = 2496 // GN*GN i64 per-column height edits (24*24*8=4608 -> ends 7104, < FBOFF)
53const RENDER_R: i64 = 12 // render a (2R+1)^2 window of columns AROUND the camera (infinite streaming)
54const O_CAM: i64 = 212992 // camera state in wasm: [camxq, camyq, camzq, yaw, pitch] (above the z-buffer)
55const O_KEYS: i64 = 213056 // held-key flags by keyCode 0..127 (128 i64)
56const NC: i64 = 8 // number of creatures in the world
57const O_CREAT: i64 = 214080 // NC creatures, stride 4 i64: [x, z, species, captured]
58const O_GAME: i64 = 214336 // game state: [encounter_idx (-1 none), captured_count]
59const ENC_R2: i64 = 9 // encounter radius^2 in voxels (walk within 3 of a creature)
60const O_AUDIO: i64 = 220000 // u8 PCM SFX buffer (8-bit, 128=silence) synthesized in wasm
61const ARATE: i64 = 8000 // audio sample rate
62const O_CHP: i64 = 228000 // per-creature battle HP (NC i64)
63const MAXHP: i64 = 5 // creature starting HP (weaken it to raise catch chance)
64const O_SAVE: i64 = 230000 // save record scratch (versioned, i64 fields)
65const SAVE_MAGIC: i64 = 1346458963 // "PETS" -- rejects non-Nishi saves
66const SAVE_VER: i64 = 1 // bump when the layout grows; gi_load stays forward-compatible
67// ===== MULTIPLAYER (MP-R1): peer presence over the sovereign relay =====
68// net buffers + peer table live ABOVE the z-buffer (ZB ends 461184+221184=682368), inside the 16-page (1 MB) memory.
69const O_NET_OUT: i64 = 688128 // 64 B: local snapshot bytes (host reads -> POSTs to relay)
70const O_NET_IN: i64 = 688192 // 256 B: the WHOLE /roster body (host writes <- daemon, then gi_net_ingest_roster)
71const O_NET_F: i64 = 688448 // 10 i64 field scratch for pack/unpack
72const O_NET_CNT: i64 = 688528 // live peer count
73const O_NET_SELF: i64 = 688536 // this player's avatar tag (species/colour)
74const O_PEERS: i64 = 688576 // MAXP peers, stride 128 B -> ends 690624 (< 1 MB / 16 pages)
75const MAXP: i64 = 16
76const PEER_STRIDE: i64 = 128
77
78// (free camera now uses the render core's sovereign rc_sin_q14/rc_cos_q14 for arbitrary angles.)
79// coarse lattice height (1..6) -- the value-noise control points.
80func b_lat(ix: i64, iz: i64) -> i64 {
81 var n: i64 = (ix * 71 + iz * 191 + ix * iz * 7) % 6
82 if n < 0 { n = n + 6 }
83 return 1 + n
84}
85// SMOOTH base terrain height 1..6 via bilinear value noise (lattice spacing 6 -> coherent hills, not the
86// old per-cell hash chaos). Out-of-world columns = height 0 (world-edge faces draw; neighbour queries safe).
87func b_terrain_base(x: i64, z: i64) -> i64 {
88 let LAT: i64 = 6
89 let bx: i64 = x + O_MAGIC_60000 // bias keeps lattice coords positive -> the world generates for ANY (x,z)
90 let bz: i64 = z + O_MAGIC_60000 // = infinite procedural terrain (no fixed 0..GN bound)
91 let ix: i64 = bx / LAT
92 let iz: i64 = bz / LAT
93 let fx: i64 = (bx % LAT) * 256 / LAT
94 let fz: i64 = (bz % LAT) * 256 / LAT
95 let h00: i64 = b_lat(ix, iz)
96 let h10: i64 = b_lat(ix + 1, iz)
97 let h01: i64 = b_lat(ix, iz + 1)
98 let h11: i64 = b_lat(ix + 1, iz + 1)
99 let a: i64 = h00 * 256 + (h10 - h00) * fx
100 let b: i64 = h01 * 256 + (h11 - h01) * fx
101 var h: i64 = (a * 256 + (b - a) * fz) / O_MAGIC_65536
102 if h < 1 { h = 1 }
103 if h > 6 { h = 6 }
104 return h
105}
106// mutable per-column height EDITS (place/break). wasm linear memory is zero-initialised so deltas start 0.
107func b_delta_ptr(gx: i64, gz: i64) -> *i64 { return ((O_DELTA + (gz * GN + gx) * 8) as i64) as *i64 }
108func b_get_delta(gx: i64, gz: i64) -> i64 { let p: *i64 = b_delta_ptr(gx, gz); return p[0] }
109func b_set_delta(gx: i64, gz: i64, v: i64) -> i64 { let p: *i64 = b_delta_ptr(gx, gz); p[0] = v; return 0 }
110// EFFECTIVE terrain height = base + player edits, clamped 1..12. out-of-world = 0. (defined early so the
111// render loop, culling, and raycast all see edits.)
112func gi_height(x: i64, z: i64) -> i64 {
113 var h: i64 = b_terrain_base(x, z) // infinite base terrain (any x,z)
114 if x >= 0 { if z >= 0 { if x < GN { if z < GN { h = h + b_get_delta(x, z) } } } } // edits live in the home GNxGN region
115 if h < 1 { h = 1 }
116 if h > 12 { h = 12 }
117 return h
118}
119func b_pack(r: i64, g: i64, b: i64, a: i64) -> i64 { return r + g * 256 + b * O_MAGIC_65536 + a * O_MAGIC_16777216 }
120func b_fb() -> *i64 { return (FBOFF as i64) as *i64 }
121func b_zb() -> *i64 { return (ZBOFF as i64) as *i64 }
122func b_corner_ptr(idx: i64) -> *i64 { return ((O_CORN + idx * 32) as i64) as *i64 }
123
124func b_project(mvp: *i64, wx: i64, wy: i64, wz: i64, scr: *i64) -> i64 {
125 let v: *i64 = (O_VS as i64) as *i64
126 let clip: *i64 = (O_CS as i64) as *i64
127 v[0] = wx * VQ
128 v[1] = wy * VQ
129 v[2] = wz * VQ
130 v[3] = VQ
131 rc_mat4_vec4(mvp, v, clip)
132 let w: i64 = clip[3]
133 if w < O_MAGIC_4096 { scr[3] = 0; return 0 }
134 scr[0] = HW + (clip[0] * HW) / w
135 scr[1] = HH - (clip[1] * HH) / w
136 scr[2] = w
137 scr[3] = 1
138 return 1
139}
140// project the 8 corners of the BOX with min-corner (x0,y0,z0) and size (hx,hy,hz) into corners[0..7].
141func b_project_box(mvp: *i64, x0: i64, y0: i64, z0: i64, hx: i64, hy: i64, hz: i64) -> i64 {
142 var i: i64 = 0
143 while i < 8 {
144 var dx: i64 = 0
145 if (i % 2) == 1 { dx = hx }
146 var dy: i64 = 0
147 if ((i / 2) % 2) == 1 { dy = hy }
148 var dz: i64 = 0
149 if ((i / 4) % 2) == 1 { dz = hz }
150 b_project(mvp, x0 + dx, y0 + dy, z0 + dz, b_corner_ptr(i))
151 i = i + 1
152 }
153 return 0
154}
155func b_tri(a: *i64, b: *i64, c: *i64, col: i64) -> i64 {
156 if a[3] == 0 { return 0 }
157 if b[3] == 0 { return 0 }
158 if c[3] == 0 { return 0 }
159 let tri: *i64 = (O_TRI as i64) as *i64
160 tri[0] = a[0]
161 tri[1] = a[1]
162 tri[2] = a[2]
163 tri[3] = col
164 tri[4] = b[0]
165 tri[5] = b[1]
166 tri[6] = b[2]
167 tri[7] = col
168 tri[8] = c[0]
169 tri[9] = c[1]
170 tri[10] = c[2]
171 tri[11] = col
172 rc_triangle(b_fb(), b_zb(), VW, VH, tri)
173 return 0
174}
175func b_quad(ia: i64, ib: i64, ic: i64, id: i64, col: i64) -> i64 {
176 b_tri(b_corner_ptr(ia), b_corner_ptr(ib), b_corner_ptr(ic), col)
177 b_tri(b_corner_ptr(ia), b_corner_ptr(ic), b_corner_ptr(id), col)
178 return 0
179}
180// draw a lit solid box (a voxel column): faces shaded by a fixed directional light via per-vertex alpha.
181func b_draw_box(mvp: *i64, x0: i64, y0: i64, z0: i64, hx: i64, hy: i64, hz: i64, r: i64, g: i64, b: i64) -> i64 {
182 b_project_box(mvp, x0, y0, z0, hx, hy, hz)
183 b_quad(4, 5, 7, 6, b_pack(r, g, b, 175)) // +z
184 b_quad(0, 1, 3, 2, b_pack(r, g, b, 110)) // -z
185 b_quad(1, 5, 7, 3, b_pack(r, g, b, 205)) // +x
186 b_quad(0, 4, 6, 2, b_pack(r, g, b, 125)) // -x
187 b_quad(2, 3, 7, 6, b_pack(r, g, b, 255)) // +y top (brightest)
188 b_quad(0, 1, 5, 4, b_pack(r, g, b, 70)) // -y bottom
189 return 0
190}
191// TEXTURED face: per-vertex UV (0..TN), procedural per-texel detail + directional brightness via rc_triangle_tex.
192func b_tri_tex(a: *i64, b: *i64, c: *i64, ua: i64, va: i64, ub: i64, vb: i64, uc: i64, vc: i64, br: i64, bg: i64, bb: i64, bright: i64) -> i64 {
193 if a[3] == 0 { return 0 }
194 if b[3] == 0 { return 0 }
195 if c[3] == 0 { return 0 }
196 let tri: *i64 = (O_TRI as i64) as *i64
197 tri[0] = a[0]
198 tri[1] = a[1]
199 tri[2] = a[2]
200 tri[3] = ua
201 tri[4] = va
202 tri[5] = b[0]
203 tri[6] = b[1]
204 tri[7] = b[2]
205 tri[8] = ub
206 tri[9] = vb
207 tri[10] = c[0]
208 tri[11] = c[1]
209 tri[12] = c[2]
210 tri[13] = uc
211 tri[14] = vc
212 rc_triangle_tex(b_fb(), b_zb(), VW, VH, tri, br, bg, bb, bright)
213 return 0
214}
215func b_quad_tex(ia: i64, ib: i64, ic: i64, id: i64, br: i64, bg: i64, bb: i64, bright: i64) -> i64 {
216 b_tri_tex(b_corner_ptr(ia), b_corner_ptr(ib), b_corner_ptr(ic), 0, 0, TN, 0, TN, TN, br, bg, bb, bright)
217 b_tri_tex(b_corner_ptr(ia), b_corner_ptr(ic), b_corner_ptr(id), 0, 0, TN, TN, 0, TN, br, bg, bb, bright)
218 return 0
219}
220func b_draw_box_tex(mvp: *i64, x0: i64, y0: i64, z0: i64, hx: i64, hy: i64, hz: i64, r: i64, g: i64, b: i64) -> i64 {
221 b_project_box(mvp, x0, y0, z0, hx, hy, hz)
222 b_quad_tex(4, 5, 7, 6, r, g, b, 175)
223 b_quad_tex(0, 1, 3, 2, r, g, b, 110)
224 b_quad_tex(1, 5, 7, 3, r, g, b, 205)
225 b_quad_tex(0, 4, 6, 2, r, g, b, 125)
226 b_quad_tex(2, 3, 7, 6, r, g, b, 255)
227 b_quad_tex(0, 1, 5, 4, r, g, b, 70)
228 return 0
229}
230// ===== 3D caves (voxel occupancy): a 3D-sheared value-noise band carves tunnels out of the solid columns =====
231func b_lat255(ix: i64, iz: i64) -> i64 { var n: i64 = (ix * 73 + iz * 179 + ix * iz * 13) % 256; if n < 0 { n = n + 256 } return n }
232// smooth 2D value noise 0..255 (bilinear interp of the 255-lattice, spacing 8; infinite-safe).
233func b_vn2(ax: i64, az: i64) -> i64 {
234 let L: i64 = 8
235 let bx: i64 = ax + O_MAGIC_80000
236 let bz: i64 = az + O_MAGIC_80000
237 let ix: i64 = bx / L
238 let iz: i64 = bz / L
239 let fx: i64 = (bx % L) * 256 / L
240 let fz: i64 = (bz % L) * 256 / L
241 let h00: i64 = b_lat255(ix, iz)
242 let h10: i64 = b_lat255(ix + 1, iz)
243 let h01: i64 = b_lat255(ix, iz + 1)
244 let h11: i64 = b_lat255(ix + 1, iz + 1)
245 let a: i64 = h00 * 256 + (h10 - h00) * fx
246 let b: i64 = h01 * 256 + (h11 - h01) * fx
247 return (a * 256 + (b - a) * fz) / O_MAGIC_65536
248}
249// a voxel is carved (air) where a 3D-SHEARED field (y couples both samples -> genuinely 3D) crosses a narrow
250// band = winding connected tunnels. Bedrock (y<1) never carved.
251func b_carved(x: i64, y: i64, z: i64) -> i64 {
252 if y < 1 { return 0 }
253 let f: i64 = b_vn2(x * 3 + y * 5, z * 3) + b_vn2(z * 3 + y * 5, x * 3)
254 if f > 250 { if f < 270 { return 1 } }
255 return 0
256}
257func b_column_has_cave(gx: i64, gz: i64, h: i64) -> i64 {
258 var y: i64 = 1
259 while y < h { if b_carved(gx, y, gz) == 1 { return 1 } y = y + 1 }
260 return 0
261}
262// solid voxel test (voxel occupancy): within the column [0,h) and not carved.
263func gi_solid(x: i64, y: i64, z: i64) -> i64 {
264 if y < 0 { return 0 }
265 if y >= gi_height(x, z) { return 0 }
266 if b_carved(x, y, z) == 1 { return 0 }
267 return 1
268}
269// draw one terrain column. If un-caved: the fast FACE-CULLED single box (top + sides exposed by shorter
270// neighbours). If caved: render each contiguous SOLID RUN as its own box, so the carved gaps show as caves.
271func b_draw_column(mvp: *i64, gx: i64, gz: i64, h: i64, r: i64, g: i64, b: i64) -> i64 {
272 if b_column_has_cave(gx, gz, h) == 1 {
273 var y: i64 = 0
274 while y < h {
275 if b_carved(gx, y, gz) == 1 {
276 y = y + 1
277 } else {
278 let rs: i64 = y
279 var re: i64 = y
280 var ext: i64 = 1
281 while ext == 1 {
282 if re < h { if b_carved(gx, re, gz) == 0 { re = re + 1 } else { ext = 0 } } else { ext = 0 }
283 }
284 b_draw_box_tex(mvp, gx, rs, gz, 1, re - rs, 1, r, g, b)
285 y = re
286 }
287 }
288 return 0
289 }
290 b_project_box(mvp, gx, 0, gz, 1, h, 1)
291 b_quad_tex(2, 3, 7, 6, r, g, b, 255) // +y top (always exposed)
292 if gi_height(gx + 1, gz) < h { b_quad_tex(1, 5, 7, 3, r, g, b, 205) } // +x
293 if gi_height(gx - 1, gz) < h { b_quad_tex(0, 4, 6, 2, r, g, b, 125) } // -x
294 if gi_height(gx, gz + 1) < h { b_quad_tex(4, 5, 7, 6, r, g, b, 175) } // +z
295 if gi_height(gx, gz - 1) < h { b_quad_tex(0, 1, 3, 2, r, g, b, 110) } // -z
296 return 0
297}
298// FREE CAMERA: MVP = P * Rx(pitch) * Ry(yaw) * T(-cam). camera position is Q14, yaw/pitch are integer
299// degrees; rotations built from the core's sovereign rc_sin_q14/rc_cos_q14 (arbitrary angles, no JS trig).
300func b_build_mvp(camxq: i64, camyq: i64, camzq: i64, yaw: i64, pitch: i64) -> i64 {
301 let T: *i64 = (O_T as i64) as *i64
302 rc_translation_4x4(0 - camxq, 0 - camyq, 0 - camzq, T)
303 let cyw: i64 = rc_cos_q14(yaw)
304 let syw: i64 = rc_sin_q14(yaw)
305 let Ry: *i64 = (O_RY as i64) as *i64
306 rc_identity_4x4(Ry)
307 Ry[0] = cyw
308 Ry[2] = syw
309 Ry[8] = 0 - syw
310 Ry[10] = cyw
311 let cpt: i64 = rc_cos_q14(pitch)
312 let spt: i64 = rc_sin_q14(pitch)
313 let Rx: *i64 = (O_RX as i64) as *i64
314 rc_identity_4x4(Rx)
315 Rx[5] = cpt
316 Rx[6] = 0 - spt
317 Rx[9] = spt
318 Rx[10] = cpt
319 let t1: *i64 = (O_T1 as i64) as *i64
320 rc_mat4_mul(Ry, T, t1) // Ry * T
321 let V: *i64 = (O_V as i64) as *i64
322 rc_mat4_mul(Rx, t1, V) // Rx * (Ry * T)
323 let P: *i64 = (O_P as i64) as *i64
324 let aspect: i64 = (VQ * VW) / VH
325 rc_perspective_cs(O_MAGIC_13418, O_MAGIC_9402, aspect, VQ, 1000 * VQ, P)
326 let mvp: *i64 = (O_MVP as i64) as *i64
327 rc_mat4_mul(P, V, mvp) // P * V
328 return 0
329}
330// ===== creatures (the PETS collect loop) =====
331func b_creat(i: i64) -> *i64 { return ((O_CREAT + i * 32) as i64) as *i64 }
332func b_game() -> *i64 { return (O_GAME as i64) as *i64 }
333func gi_creatures() -> i64 { return NC }
334func gi_creature_x(i: i64) -> i64 { let p: *i64 = b_creat(i); return p[0] }
335func gi_creature_z(i: i64) -> i64 { let p: *i64 = b_creat(i); return p[1] }
336func gi_creature_species(i: i64) -> i64 { let p: *i64 = b_creat(i); return p[2] }
337func gi_creature_captured(i: i64) -> i64 { let p: *i64 = b_creat(i); return p[3] }
338func gi_captured() -> i64 { let g: *i64 = b_game(); return g[1] }
339func gi_encounter() -> i64 { let g: *i64 = b_game(); if g[0] < 0 { return 0 - 1 } let p: *i64 = b_creat(g[0]); return p[2] }
340func gi_throws() -> i64 { let g: *i64 = b_game(); return g[3] }
341func gi_last() -> i64 { let g: *i64 = b_game(); return g[5] }
342func gi_won() -> i64 { let g: *i64 = b_game(); if g[1] >= NC { return 1 } return 0 }
343func b_chp(i: i64) -> i64 { let p: *i64 = ((O_CHP + i * 8) as i64) as *i64; return p[0] }
344func b_chp_set(i: i64, v: i64) -> i64 { let p: *i64 = ((O_CHP + i * 8) as i64) as *i64; p[0] = v; return 0 }
345func gi_enc_hp() -> i64 { let g: *i64 = b_game(); if g[0] < 0 { return 0 - 1 } return b_chp(g[0]) }
346func b_creature_color(sp: i64) -> i64 {
347 if sp == 0 { return b_pack(230, 70, 70, 255) } // red
348 if sp == 1 { return b_pack(180, 80, 230, 255) } // purple
349 if sp == 2 { return b_pack(240, 160, 50, 255) } // orange
350 return b_pack(240, 120, 190, 255) // pink
351}
352// draw a creature as a small z-tested billboard square at its projected screen position.
353func b_marker(sx: i64, sy: i64, depth: i64, col: i64) -> i64 {
354 let fb: *i64 = b_fb()
355 let zb: *i64 = b_zb()
356 var dy: i64 = 0 - 4
357 while dy <= 4 {
358 var dx: i64 = 0 - 3
359 while dx <= 3 {
360 let x: i64 = sx + dx
361 let y: i64 = sy + dy
362 if x >= 0 { if y >= 0 { if x < VW { if y < VH {
363 let idx: i64 = y * VW + x
364 if depth < zb[idx] { zb[idx] = depth; fb[idx] = col } // in front of terrain
365 } } } }
366 dx = dx + 1
367 }
368 dy = dy + 1
369 }
370 return 0
371}
372// draw every un-captured creature as a small 3D VOXEL FIGURE (body + brighter head cube), species-coloured,
373// z-buffered like terrain (so it's truly 3D, occluded correctly), with a gentle idle bob.
374func b_draw_creatures(mvp: *i64) -> i64 {
375 let g: *i64 = b_game()
376 var bob: i64 = 0
377 if (g[6] / 12) % 2 == 1 { bob = 1 } // slow idle hop
378 var i: i64 = 0
379 while i < NC {
380 let cp: *i64 = b_creat(i)
381 if cp[3] == 0 {
382 let cx: i64 = cp[0]
383 let cz: i64 = cp[1]
384 let base: i64 = gi_height(cx, cz) + 1 + bob
385 let sp: i64 = cp[2]
386 var br: i64 = 220
387 var bg: i64 = 70
388 var bl: i64 = 70 // sp0 red
389 if sp == 1 { br = 170; bg = 90; bl = 220 } // purple
390 if sp == 2 { br = 235; bg = 150; bl = 50 } // orange
391 if sp == 3 { br = 235; bg = 110; bl = 180 } // pink
392 b_draw_box_tex(mvp, cx, base, cz, 1, 1, 1, br, bg, bl) // body
393 b_draw_box_tex(mvp, cx, base + 1, cz, 1, 1, 1, br + 20, bg + 20, bl + 20) // head (brighter)
394 }
395 i = i + 1
396 }
397 return 0
398}
399// biome id 0..3 (plains / desert / snow / rocky) over coarse ~12-voxel regions (infinite-safe). Discrete
400// regions (blocky boundaries) so the world has desert patches, snowfields, plains, etc.
401func gi_biome(gx: i64, gz: i64) -> i64 {
402 let bx: i64 = (gx + O_MAGIC_60000) / 12
403 let bz: i64 = (gz + O_MAGIC_60000) / 12
404 var n: i64 = (bx * 53 + bz * 97 + bx * bz * 3) % 4
405 if n < 0 { n = n + 4 }
406 return n
407}
408// deterministic sparse trees: ~1/23 of LAND columns (height 2..6) IN THE PLAINS BIOME (0) grow a tree.
409func gi_has_tree(gx: i64, gz: i64) -> i64 {
410 let h: i64 = gi_height(gx, gz)
411 if h < 2 { return 0 }
412 if h > 6 { return 0 }
413 if gi_biome(gx, gz) != 0 { return 0 } // forests only in plains
414 var n: i64 = (gx * 131 + gz * 197 + gx * gz) % 23
415 if n < 0 { n = n + 23 }
416 if n == 0 { return 1 }
417 return 0
418}
419// draw a tree on column (gx,gz): a brown trunk + a green leaf blob, textured + lit (composes b_draw_box_tex).
420func b_draw_tree(mvp: *i64, gx: i64, gz: i64, h: i64) -> i64 {
421 b_draw_box_tex(mvp, gx, h, gz, 1, 3, 1, 120, 80, 40) // trunk (3 tall)
422 b_draw_box_tex(mvp, gx - 1, h + 2, gz - 1, 3, 2, 3, 45, 140, 45) // leaf canopy (3x2x3)
423 return 0
424}
425// ===== MP-R1: draw other players as voxel avatars (codec = nx_netquant; smoothing = NET interp, host-side) =====
426func b_peer(i: i64) -> *i64 { return ((O_PEERS + i * PEER_STRIDE) as i64) as *i64 }
427// each live peer: a body box + brighter head, player-coloured (blue/teal/yellow/green) -- distinct from creatures.
428func b_draw_peers(mvp: *i64) -> i64 {
429 var i: i64 = 0
430 while i < MAXP {
431 let s: *i64 = b_peer(i)
432 if s[0] == 1 {
433 // MP-R1b smoothing: render at the INTERPOLATED pose (prev -> cur by alpha), the nx_interp mechanism.
434 // alpha 0=prev, 256=cur, up to 320 = bounded EXTRAPOLATION so a dropped sync keeps moving (no freeze).
435 let a: i64 = s[14]
436 let px: i64 = ls_interp(s[11], s[1], a)
437 let py: i64 = ls_interp(s[12], s[2], a)
438 let pz: i64 = ls_interp(s[13], s[3], a)
439 let sp: i64 = s[6] % 4
440 var r: i64 = 60
441 var g: i64 = 120
442 var bl: i64 = 230 // sp0 blue
443 if sp == 1 { r = 60; g = 205; bl = 200 } // teal
444 if sp == 2 { r = 230; g = 205; bl = 60 } // yellow
445 if sp == 3 { r = 90; g = 205; bl = 90 } // green
446 let base: i64 = py - 2
447 b_draw_box_tex(mvp, px, base, pz, 1, 2, 1, r, g, bl) // body (2 tall)
448 b_draw_box_tex(mvp, px, base + 2, pz, 1, 1, 1, r + 20, g + 20, bl + 20) // head (brighter)
449 }
450 i = i + 1
451 }
452 return 0
453}
454// advance each peer's interpolation alpha one frame (called once per gi_tick, before gi_render). reaches cur in
455// ~5 frames then holds at the bounded cap = glide-not-teleport + no-freeze-on-dropped-sync. (defined before gi_tick.)
456func gi_net_smooth() -> i64 {
457 var i: i64 = 0
458 while i < MAXP {
459 let s: *i64 = b_peer(i)
460 if s[0] == 1 { s[14] = ls_advance(s[14]) }
461 i = i + 1
462 }
463 return 0
464}
465// render the whole voxel terrain + trees + creatures from an arbitrary camera (pos Q14, yaw/pitch degrees).
466func gi_render(camxq: i64, camyq: i64, camzq: i64, yaw: i64, pitch: i64) -> i64 {
467 let fb: *i64 = b_fb()
468 let zb: *i64 = b_zb()
469 rc_clear(fb, VW, VH, b_pack(120, 165, 220, 255)) // sky
470 rc_zclear(zb, VW, VH)
471 b_build_mvp(camxq, camyq, camzq, yaw, pitch)
472 let mvp: *i64 = (O_MVP as i64) as *i64
473 let camgx: i64 = camxq / VQ // the camera's column -> window follows it (infinite streaming)
474 let camgz: i64 = camzq / VQ
475 var gz: i64 = camgz - RENDER_R
476 while gz <= camgz + RENDER_R {
477 var gx: i64 = camgx - RENDER_R
478 while gx <= camgx + RENDER_R {
479 let h: i64 = gi_height(gx, gz)
480 let bm: i64 = gi_biome(gx, gz)
481 var rr: i64 = 80
482 var gg: i64 = 160
483 var bb: i64 = 70 // biome 0 plains = grass
484 if bm == 1 { rr = 210; gg = 190; bb = 120 } // desert sand
485 if bm == 2 { rr = 220; gg = 225; bb = 235 } // snowfield
486 if bm == 3 { rr = 120; gg = 115; bb = 110 } // rocky
487 if h <= 1 { rr = 60; gg = 90; bb = 200 } // water in the lows (any biome)
488 if h >= 6 { rr = 235; gg = 235; bb = 245 } // snow caps on peaks (any biome)
489 b_draw_column(mvp, gx, gz, h, rr, gg, bb)
490 if gi_has_tree(gx, gz) == 1 { b_draw_tree(mvp, gx, gz, h) }
491 gx = gx + 1
492 }
493 gz = gz + 1
494 }
495 b_draw_creatures(mvp) // billboards on top of the terrain
496 b_draw_peers(mvp) // MP-R1: other players' voxel avatars (z-tested like everything else)
497 return 0
498}
499// sovereign SSAO post-process: darken a pixel when neighbours are significantly CLOSER (a depth edge /
500// concave crease) -> contact-shadow ambient occlusion. Reads the z-buffer, modifies the framebuffer only.
501func gi_ssao() -> i64 {
502 let fb: *i64 = b_fb()
503 let zb: *i64 = b_zb()
504 var y: i64 = 1
505 while y < VH - 1 {
506 var x: i64 = 1
507 while x < VW - 1 {
508 let idx: i64 = y * VW + x
509 let d: i64 = zb[idx]
510 if d < O_MAGIC_16000000 { // geometry (not sky)
511 var occ: i64 = 0
512 if zb[idx - 1] < d - O_MAGIC_6000 { occ = occ + 1 }
513 if zb[idx + 1] < d - O_MAGIC_6000 { occ = occ + 1 }
514 if zb[idx - VW] < d - O_MAGIC_6000 { occ = occ + 1 }
515 if zb[idx + VW] < d - O_MAGIC_6000 { occ = occ + 1 }
516 if occ > 0 {
517 let c: i64 = fb[idx]
518 let r: i64 = c % 256
519 let g: i64 = (c / 256) % 256
520 let b: i64 = (c / O_MAGIC_65536) % 256
521 let f: i64 = 100 - occ * 14 // darken up to ~56% in deep creases
522 fb[idx] = (r * f) / 100 + ((g * f) / 100) * 256 + ((b * f) / 100) * O_MAGIC_65536 + 255 * O_MAGIC_16777216
523 }
524 }
525 x = x + 1
526 }
527 y = y + 1
528 }
529 return 0
530}
531// sovereign HUD drawn INTO the framebuffer (no JS UI logic): caught-count digits, an HP pip-bar during an
532// encounter, and a win banner. Composes the reusable rc_ UI primitives.
533func gi_hud() -> i64 {
534 let fb: *i64 = b_fb()
535 let g: *i64 = b_game()
536 rc_fillrect(fb, VW, VH, 2, 2, 54, 14, b_pack(18, 22, 30, 255)) // count panel
537 rc_draw_number(fb, VW, VH, 5, 4, g[1], b_pack(225, 235, 130, 255), 2) // caught count
538 if g[0] >= 0 { // encounter: HP pip bar
539 let hp: i64 = b_chp(g[0])
540 rc_fillrect(fb, VW, VH, 2, VH - 14, MAXHP * 9 + 4, 10, b_pack(18, 22, 30, 255))
541 var i: i64 = 0
542 while i < hp { rc_fillrect(fb, VW, VH, 5 + i * 9, VH - 12, 7, 6, b_pack(230, 80, 80, 255)); i = i + 1 }
543 }
544 if g[1] >= NC { rc_fillrect(fb, VW, VH, VW / 2 - 36, VH / 2 - 10, 72, 20, b_pack(40, 170, 70, 255)) } // win banner
545 return 0
546}
547func gi_want_save() -> i64 { let g: *i64 = b_game(); let s: i64 = g[9]; g[9] = 0; return s }
548func gi_w() -> i64 { return VW }
549func gi_h() -> i64 { return VH }
550func gi_fb_offset() -> i64 { return FBOFF }
551func gi_world() -> i64 { return GN }
552// raycast from the camera forward and place(mode=1)/break(mode=-1) the first terrain column hit. forward =
553// (sin(yaw)cos(pitch), -sin(pitch), -cos(yaw)cos(pitch)), derived from the P*Rx*Ry*T camera. returns the
554// edited column encoded gx*1000+gz, or -1 if the ray hit nothing.
555func gi_edit(camxq: i64, camyq: i64, camzq: i64, yaw: i64, pitch: i64, mode: i64) -> i64 {
556 let cyw: i64 = rc_cos_q14(yaw)
557 let syw: i64 = rc_sin_q14(yaw)
558 let cpt: i64 = rc_cos_q14(pitch)
559 let spt: i64 = rc_sin_q14(pitch)
560 let fx: i64 = (syw * cpt) / O_MAGIC_16384
561 let fy: i64 = 0 - spt
562 let fz: i64 = (0 - cyw * cpt) / O_MAGIC_16384
563 var px: i64 = camxq
564 var py: i64 = camyq
565 var pz: i64 = camzq
566 var i: i64 = 0
567 while i < 160 {
568 px = px + (fx * O_MAGIC_8192) / O_MAGIC_16384 // step 0.5 voxel along the look ray
569 py = py + (fy * O_MAGIC_8192) / O_MAGIC_16384
570 pz = pz + (fz * O_MAGIC_8192) / O_MAGIC_16384
571 let gx: i64 = px / O_MAGIC_16384
572 let gz: i64 = pz / O_MAGIC_16384
573 if gx >= 0 { if gz >= 0 { if gx < GN { if gz < GN {
574 if py / O_MAGIC_16384 < gi_height(gx, gz) {
575 b_set_delta(gx, gz, b_get_delta(gx, gz) + mode) // place raises / break lowers this column
576 return gx * 1000 + gz
577 }
578 } } } }
579 i = i + 1
580 }
581 return 0 - 1
582}
583// ===== sovereign game state + input + loop (JS only forwards raw events + blits the framebuffer) =====
584func b_cam() -> *i64 { return (O_CAM as i64) as *i64 }
585func b_keys() -> *i64 { return (O_KEYS as i64) as *i64 }
586func b_key(code: i64) -> i64 { if code < 0 { return 0 } if code > 127 { return 0 } let k: *i64 = b_keys(); return k[code] }
587// ===== sovereign SFX: wasm SYNTHESIZES 8-bit PCM; JS is a pure Web Audio DAC sink (no synthesis in JS) =====
588func b_audio() -> *u8 { return (O_AUDIO as i64) as *u8 }
589func gi_audio_offset() -> i64 { return O_AUDIO }
590func gi_audio_rate() -> i64 { return ARATE }
591// write a square-wave tone with linear-decay envelope into the PCM buffer [start..start+len); 128=silence.
592func b_tone(start: i64, len: i64, freq: i64, amp0: i64) -> i64 {
593 let buf: *u8 = b_audio()
594 var period: i64 = 0
595 if freq > 0 { period = ARATE / freq }
596 var i: i64 = 0
597 while i < len {
598 var s: i64 = 128
599 if period > 0 {
600 let amp: i64 = amp0 * (len - i) / len
601 if (i % period) * 2 < period { s = 128 + amp } else { s = 128 - amp }
602 }
603 buf[start + i] = s as u8
604 i = i + 1
605 }
606 return start + len
607}
608// synthesize SFX `id` into the PCM buffer; returns total sample count. JS reads + plays it.
609func gi_synth(id: i64) -> i64 {
610 var n: i64 = 0
611 if id == 1 { n = b_tone(0, 520, 523, 80); n = b_tone(n, 520, 659, 80); n = b_tone(n, 900, 784, 86) } // catch (rising)
612 if id == 2 { n = b_tone(0, 700, 300, 70); n = b_tone(n, 520, 180, 60) } // broke free (falling)
613 if id == 3 { n = b_tone(0, 420, 523, 84); n = b_tone(n, 420, 659, 84); n = b_tone(n, 420, 784, 84); n = b_tone(n, O_MAGIC_1200, O_MAGIC_1047, 90) } // WIN fanfare
614 if id == 4 { n = b_tone(0, 180, 880, 58) } // place blip
615 if id == 5 { n = b_tone(0, 240, 220, 66) } // break thunk
616 if id == 6 { n = b_tone(0, 120, O_MAGIC_1320, 48) } // encounter chirp
617 if id == 7 { n = b_tone(0, 150, 440, 70); n = b_tone(n, 150, 330, 60) } // hit (weaken)
618 return n
619}
620// take the pending SFX id (and clear it); -1 if none. JS polls this each frame.
621func gi_take_sfx() -> i64 { let g: *i64 = b_game(); let s: i64 = g[7]; g[7] = 0 - 1; return s }
622// gi_init: set the starting camera (wasm memory is zero-init, but the camera needs non-zero defaults).
623func gi_init() -> i64 {
624 let c: *i64 = b_cam()
625 c[0] = 12 * VQ
626 c[1] = 9 * VQ
627 c[2] = 23 * VQ
628 c[3] = 0
629 c[4] = 12
630 var i: i64 = 0 // spawn creatures across the home region
631 while i < NC {
632 let p: *i64 = b_creat(i)
633 p[0] = 3 + (i * 5) % 19 // x in ~[3,21]
634 p[1] = 4 + (i * 7) % 17 // z in ~[4,20]
635 p[2] = i % 4 // species
636 p[3] = 0 // not captured
637 b_chp_set(i, MAXHP) // full battle HP
638 i = i + 1
639 }
640 let g: *i64 = b_game()
641 g[0] = 0 - 1 // no encounter
642 g[1] = 0 // captured count
643 g[2] = O_MAGIC_12345 // catch RNG seed
644 g[3] = 0 // throws this encounter
645 g[4] = 0 // prev C-key state (edge detect)
646 g[5] = 0 // last throw result: 0 none, 1 caught, 2 broke free
647 g[6] = 0 // frame counter (creature idle animation)
648 g[7] = 0 - 1 // pending SFX id (-1 none)
649 g[8] = 0 // prev X-key state (weaken edge detect)
650 g[9] = 0 // save-dirty flag (wasm-owned autosave policy)
651 return 0
652}
653// ===== S-CLASS SAVE: versioned, forward-compatible serialize/restore. A save survives a MODULE SWAP
654// (load an old-module save into a new module = hot-update with no data loss) and tolerates shorter/older
655// saves (missing fields keep defaults) -- so a save made today still loads in future versions. JS only
656// shuttles these bytes to/from localStorage (persistence) or across a hot-swapped module. =====
657func gi_save_offset() -> i64 { return O_SAVE }
658func b_sv() -> *i64 { return (O_SAVE as i64) as *i64 }
659// serialize state (STABLE field order) into the save buffer; returns byte length.
660func gi_save() -> i64 {
661 let sv: *i64 = b_sv()
662 let c: *i64 = b_cam()
663 let g: *i64 = b_game()
664 sv[0] = SAVE_MAGIC
665 sv[1] = SAVE_VER
666 sv[2] = c[0]
667 sv[3] = c[1]
668 sv[4] = c[2]
669 sv[5] = c[3]
670 sv[6] = c[4]
671 sv[7] = g[1] // captured_count
672 var k: i64 = 8
673 var i: i64 = 0
674 while i < NC { let cp: *i64 = b_creat(i); sv[k] = cp[3]; k = k + 1; i = i + 1 } // captured flags
675 i = 0
676 while i < NC { sv[k] = b_chp(i); k = k + 1; i = i + 1 } // battle HP
677 i = 0
678 while i < GN * GN { let p: *i64 = ((O_DELTA + i * 8) as i64) as *i64; sv[k] = p[0]; k = k + 1; i = i + 1 } // world edits
679 return k * 8
680}
681// read field k if present (k < n) else keep deflt -- this is what makes load FORWARD-COMPATIBLE.
682func b_svget(sv: *i64, k: i64, n: i64, deflt: i64) -> i64 { if k < n { return sv[k] } return deflt }
683// restore from the save buffer (JS wrote `bytelen` bytes). returns 1 if accepted, 0 if rejected (bad magic
684// => not our save => keep current state, NO data loss). Length-bounded => older/shorter saves load with
685// defaults for fields they predate.
686func gi_load(bytelen: i64) -> i64 {
687 let sv: *i64 = b_sv()
688 let n: i64 = bytelen / 8
689 if n < 2 { return 0 }
690 if sv[0] != SAVE_MAGIC { return 0 }
691 let c: *i64 = b_cam()
692 let g: *i64 = b_game()
693 c[0] = b_svget(sv, 2, n, c[0])
694 c[1] = b_svget(sv, 3, n, c[1])
695 c[2] = b_svget(sv, 4, n, c[2])
696 c[3] = b_svget(sv, 5, n, c[3])
697 c[4] = b_svget(sv, 6, n, c[4])
698 g[1] = b_svget(sv, 7, n, g[1])
699 var k: i64 = 8
700 var i: i64 = 0
701 while i < NC { let cp: *i64 = b_creat(i); cp[3] = b_svget(sv, k, n, cp[3]); k = k + 1; i = i + 1 }
702 i = 0
703 while i < NC { b_chp_set(i, b_svget(sv, k, n, b_chp(i))); k = k + 1; i = i + 1 }
704 i = 0
705 while i < GN * GN { let p: *i64 = ((O_DELTA + i * 8) as i64) as *i64; p[0] = b_svget(sv, k, n, p[0]); k = k + 1; i = i + 1 }
706 return 1
707}
708// gi_key: record a held key by its raw browser keyCode (JS forwards the code; wasm owns the meaning).
709func gi_key(code: i64, down: i64) -> i64 {
710 if code < 0 { return 0 }
711 if code > 127 { return 0 }
712 let k: *i64 = b_keys()
713 k[code] = down
714 return 0
715}
716// gi_click: place(left)/break(right) at the current camera (raycast edit lives in wasm).
717func gi_click(button: i64) -> i64 {
718 let c: *i64 = b_cam()
719 var mode: i64 = 1
720 if button == 2 { mode = 0 - 1 }
721 let hit: i64 = gi_edit(c[0], c[1], c[2], c[3], c[4], mode)
722 if hit >= 0 {
723 let g: *i64 = b_game()
724 if mode == 1 { g[7] = 4 } else { g[7] = 5 } // SFX: place / break
725 }
726 return 0
727}
728// gi_tick: ONE frame of the game -- process held input (view-relative move via sovereign sin/cos, look),
729// update camera, then render. JS calls this each rAF and blits the framebuffer. ALL logic is here.
730func gi_tick() -> i64 {
731 let c: *i64 = b_cam()
732 let MV: i64 = O_MAGIC_6554 // 0.4 voxel/frame in Q14
733 let LK: i64 = 3 // 3 deg/frame look
734 let yaw: i64 = c[3]
735 let sy: i64 = rc_sin_q14(yaw)
736 let cyw: i64 = rc_cos_q14(yaw)
737 var dx: i64 = 0
738 var dz: i64 = 0
739 if b_key(87) == 1 { dx = dx + sy; dz = dz - cyw } // W forward (look direction, horizontal)
740 if b_key(83) == 1 { dx = dx - sy; dz = dz + cyw } // S back
741 if b_key(68) == 1 { dx = dx + cyw; dz = dz + sy } // D strafe right
742 if b_key(65) == 1 { dx = dx - cyw; dz = dz - sy } // A strafe left
743 c[0] = c[0] + (dx * MV) / O_MAGIC_16384
744 c[2] = c[2] + (dz * MV) / O_MAGIC_16384
745 if b_key(81) == 1 { c[1] = c[1] + MV } // Q up
746 if b_key(69) == 1 { c[1] = c[1] - MV } // E down
747 if b_key(37) == 1 { c[3] = c[3] - LK } // arrow left
748 if b_key(39) == 1 { c[3] = c[3] + LK } // arrow right
749 if b_key(38) == 1 { c[4] = c[4] + LK } // arrow up
750 if b_key(40) == 1 { c[4] = c[4] - LK } // arrow down
751 if c[4] > 85 { c[4] = 85 }
752 if c[4] < 0 - 85 { c[4] = 0 - 85 }
753 // creature encounter: nearest un-captured creature within radius -> encounter; 'C' (67) captures it.
754 let g: *i64 = b_game()
755 let px: i64 = c[0] / VQ
756 let pz: i64 = c[2] / VQ
757 var enc: i64 = 0 - 1
758 var ci: i64 = 0
759 while ci < NC {
760 let cp: *i64 = b_creat(ci)
761 if cp[3] == 0 {
762 let ex: i64 = px - cp[0]
763 let ez: i64 = pz - cp[1]
764 if ex * ex + ez * ez <= ENC_R2 { enc = ci }
765 }
766 ci = ci + 1
767 }
768 g[6] = g[6] + 1 // advance frame (creature idle animation)
769 g[0] = enc
770 let cdown: i64 = b_key(67)
771 let xdown: i64 = b_key(88)
772 if enc >= 0 {
773 if xdown == 1 { if g[8] == 0 { // 'X' edge = WEAKEN the encountered creature (lowers its HP)
774 let hp: i64 = b_chp(enc)
775 if hp > 1 { b_chp_set(enc, hp - 1) }
776 g[7] = 7 // SFX: hit
777 } }
778 if cdown == 1 { if g[4] == 0 { // 'C' edge = one throw
779 g[2] = (g[2] * O_MAGIC_1103515 + O_MAGIC_12345) % O_MAGIC_2147483647 // advance catch RNG
780 let roll: i64 = g[2] % 100
781 let chance: i64 = 30 + (MAXHP - b_chp(enc)) * 15 + g[3] * 20 // weaker creature + more throws -> easier
782 if roll < chance {
783 let cap: *i64 = b_creat(enc)
784 cap[3] = 1
785 g[1] = g[1] + 1
786 g[0] = 0 - 1
787 g[3] = 0
788 g[5] = 1 // caught!
789 g[7] = 1 // SFX: catch
790 if g[1] >= NC { g[7] = 3 } // SFX: win fanfare (caught them all)
791 } else {
792 g[3] = g[3] + 1
793 g[5] = 2 // it broke free
794 g[7] = 2 // SFX: broke free
795 }
796 } }
797 } else {
798 g[3] = 0
799 g[5] = 0
800 }
801 g[8] = xdown
802 g[4] = cdown
803 gi_net_smooth() // MP-R1b: advance peer interpolation one frame (smooth on bad nets)
804 gi_render(c[0], c[1], c[2], c[3], c[4])
805 gi_ssao() // ambient-occlusion crease shadows (depth-based)
806 gi_hud() // sovereign HUD drawn into the framebuffer
807 if g[6] % 600 == 0 { g[9] = 1 } // wasm-owned autosave policy (~every 10s)
808 if g[1] >= NC { g[9] = 1 } // and on win
809 return 0
810}
811// ===== MP-R1: sovereign multiplayer peer API. ALL logic in wasm; the host (thin JS in Chrome / native on
812// Nishi browser) only shuttles opaque bytes to/from the relay -- the network last-mile, twin of the canvas blit. =====
813func gi_net_out_ptr() -> i64 { return O_NET_OUT } // host reads packed snapshot bytes here -> POST
814func gi_net_in_ptr() -> i64 { return O_NET_IN } // host writes a peer's bytes here <- roster, then ingest
815func gi_net_set_self(sp: i64) -> i64 { let p: *i64 = (O_NET_SELF as i64) as *i64; p[0] = sp; return 0 }
816func gi_net_count() -> i64 { let p: *i64 = (O_NET_CNT as i64) as *i64; return p[0] }
817// pack the local player into O_NET_OUT (bit-packed keyframe, ~11 bytes); returns the byte length
818func gi_net_snapshot() -> i64 {
819 let c: *i64 = b_cam()
820 let g: *i64 = b_game()
821 let self: *i64 = (O_NET_SELF as i64) as *i64
822 let f: *i64 = (O_NET_F as i64) as *i64
823 f[0] = c[0] / VQ
824 f[1] = c[1] / VQ
825 f[2] = c[2] / VQ
826 f[3] = c[3]
827 f[4] = c[4]
828 f[5] = self[0]
829 f[6] = g[1]
830 var act: i64 = 0
831 if g[0] >= 0 { act = 4 }
832 f[7] = act
833 f[8] = g[6] & 63
834 f[9] = 0
835 let bits: i64 = nq_pack_full((O_NET_OUT as i64) as *u8, f)
836 return nx_bits_bytes(bits)
837}
838// reset the roster (host calls once per sync round, then re-ingests each live peer)
839func gi_net_clear() -> i64 {
840 var i: i64 = 0
841 while i < MAXP { let s: *i64 = b_peer(i); s[0] = 0; i = i + 1 }
842 let cnt: *i64 = (O_NET_CNT as i64) as *i64; cnt[0] = 0
843 return 0
844}
845// defensive boundary clamp on a peer slot: a garbage frame can't fling an avatar across the world.
846func b_peer_clamp(slot: i64) -> i64 {
847 let s: *i64 = b_peer(slot)
848 let c: *i64 = b_cam()
849 let cx: i64 = c[0] / VQ
850 let cz: i64 = c[2] / VQ
851 if s[1] < cx - O_MAGIC_4096 { s[1] = cx - O_MAGIC_4096 }
852 if s[1] > cx + O_MAGIC_4096 { s[1] = cx + O_MAGIC_4096 }
853 if s[3] < cz - O_MAGIC_4096 { s[3] = cz - O_MAGIC_4096 }
854 if s[3] > cz + O_MAGIC_4096 { s[3] = cz + O_MAGIC_4096 }
855 if s[2] < 0 { s[2] = 0 }
856 if s[2] > 200 { s[2] = 200 }
857 return 0
858}
859// ingest one frame into a SPECIFIC slot, maintaining the prev->cur interpolation pair + alpha (smoothing).
860// slot layout adds: [11..13]=prev pos, [14]=alpha(Q8: 0=prev,256=cur), [15]=inited.
861func b_ingest_frame_at(slot: i64, src: *u8) -> i64 {
862 let s: *i64 = b_peer(slot)
863 let fp: *i64 = ((O_PEERS + slot * PEER_STRIDE + 8) as i64) as *i64 // &slot[1] = the 10 decoded fields
864 if s[0] == 1 { if s[15] == 1 { // already tracked -> shift cur into prev, then take the new cur
865 s[11] = s[1]; s[12] = s[2]; s[13] = s[3]
866 nq_unpack_full(src, fp)
867 b_peer_clamp(slot)
868 s[14] = 0 // restart interpolation prev -> cur
869 s[0] = 1
870 return slot
871 } }
872 nq_unpack_full(src, fp) // first sight -> snap (prev=cur, alpha=full), no slide from origin
873 b_peer_clamp(slot)
874 s[11] = s[1]; s[12] = s[2]; s[13] = s[3]
875 s[14] = 256
876 s[15] = 1
877 s[0] = 1
878 return slot
879}
880// ingest one peer frame already at O_NET_IN (host wrote it) into slot 0. returns slot.
881func gi_net_ingest(len: i64) -> i64 { let r: i64 = b_ingest_frame_at(0, (O_NET_IN as i64) as *u8); let cnt: *i64 = (O_NET_CNT as i64) as *i64; cnt[0] = 1; return r }
882// ingest the WHOLE roster body the daemon serialized -- records [len:1][frame bytes]. Updates slots IN PLACE
883// (slot index = roster position, stable while the peer set is) so the prev/alpha smoothing survives. The host
884// does ONLY a memcpy of the /roster response into O_NET_IN + ONE call; ALL parsing is sovereign. returns count.
885func gi_net_ingest_roster(total: i64) -> i64 {
886 let inb: *u8 = (O_NET_IN as i64) as *u8
887 var o: i64 = 0
888 var slot: i64 = 0
889 var go: i64 = 1
890 while go == 1 {
891 if o >= total { go = 0 } else { if slot >= MAXP { go = 0 } else {
892 let flen: i64 = inb[o] as i64
893 o = o + 1
894 b_ingest_frame_at(slot, ((O_NET_IN + o) as i64) as *u8)
895 o = o + flen
896 slot = slot + 1
897 } }
898 }
899 var i: i64 = slot
900 while i < MAXP { let sd: *i64 = b_peer(i); sd[0] = 0; i = i + 1 } // peers that left this round -> not live
901 let cnt: *i64 = (O_NET_CNT as i64) as *i64; cnt[0] = slot
902 return slot
903}
904func gi_peer_x(i: i64) -> i64 { let s: *i64 = b_peer(i); return s[1] }
905func gi_peer_y(i: i64) -> i64 { let s: *i64 = b_peer(i); return s[2] }
906func gi_peer_z(i: i64) -> i64 { let s: *i64 = b_peer(i); return s[3] }
907func gi_peer_yaw(i: i64) -> i64 { let s: *i64 = b_peer(i); return s[4] }
908func gi_peer_species(i: i64) -> i64 { let s: *i64 = b_peer(i); return s[6] }
909
910func main() -> i64 { return 0 }