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