code wiki / (root) / nx_pets3d_wasm.nx

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 }