code wiki / (root) / nx_pets3d_wasm.nx

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 }