code wiki / (root) / nx_video_client_wasm.nx

nx_video_client_wasm.nx source

↩ module page · 984 lines · 42741 B

1// nx_video_client_wasm.nx -- the Nishi Video BROWSER CLIENT CORE, for the 2// WAT/WASM target. Operator 2026-06-10: "we dont want to use javascript or 3// anything except as a last mile translation for 3rd party browsers." 4// 5// ALL format/protocol/sample logic the video client needs lives HERE, in 6// NishiLang, shipped to the browser as nx_video_client.wasm. The JS that 7// remains in app.js is a named LAST-MILE SHIM: mount this module + bridge 8// the platform primitives the browser gates behind JS APIs (getUserMedia, 9// canvas pixels, AudioContext buffers, WebSocket send/recv). No format 10// byte and no sample arithmetic is computed in JavaScript. 11// 12// Like nx_sha256_wasm.nx: imports NOTHING, works in caller-supplied linear 13// memory regions, no syscalls. Team-side authority for the NV1 container 14// is nx_nv1.nx -- nx_vc_gate cross-checks this module against it natively. 15// 16// Exports (pointers are i64 offsets into the module's linear memory): 17// NV1 container (NLC1 -- see knowledge/specs/2026-06-10-nishilossless-...) 18// vc_nv1_header(out, rate) -> 24 19// vc_nv1_chunk(out, w, kind, t_ms, payload, n) -> new w 20// vc_nv1_end(out, w, dur_ms) -> new w 21// vc_nv1_validate(buf, n, info) -> 0 | negative defect code 22// vc_nv1_next(buf, n, off, info) -> next off | 0 at end | negative 23// info[0]=kind info[1]=len info[2]=t_ms info[3]=payload_off 24// Room wire frames (13-byte kind/id/seq header, relay broadcast) 25// vc_wire_pack(out, kind, id, seq, payload, n) -> total len 26// vc_wire_parse(buf, n, info) -> 0 | -1 27// info[0]=kind info[1]=seq info[2]=payload_off info[3]=payload_len 28// Sample plane (IEEE 754 decoded with INTEGER math -- bit-exact, no 29// float unit on the trust path; matches the JS path it replaces: 30// clamp [-1,1] then TRUNCATE toward zero; NaN->0, +/-Inf->clamp) 31// vc_f32_to_i16(inb, n_samples, outb) -> n_samples (LE f32 bits -> LE i16) 32// vc_i16_to_f32(inb, n_samples, outb) -> n_samples (LE i16 -> LE f32 bits, EXACT) 33// Send-rate governor (the fps/quality ladder decision) 34// vc_ladder_step(idx, n_rungs, rtt_ms, degrade_ms, recover_ms, good) -> idx 35// 36// license_tier: ORIGINAL 37 38// The shared RESOURCE-INTELLIGENCE controller (mobile-first, API-first, 2026-07-03): mb_plan and the 39// mb_* tier tables ride INSIDE this core (pure integer, no syscalls -- wasm-legal by construction), 40// so a phone browser, a laptop, and the native ARM client all call the SAME sovereign API and get 41// the right operating point for their budget. The JS shim only reads device sensors (cores/memory/ 42// battery/connection) and passes them in -- every DECISION lives here, not in the last mile. 43import "nx_media_budget.nx" 44// SOVEREIGN call-UI state machine (task #36): the layout law (2-person = remote full + self PiP), mic/cam, 45// swap, roster live HERE as intents-over-state, exported to wasm so the browser adapter CALLS them instead 46// of duplicating the logic in app.v2.js. Same core compiles native for the NishiOS adapter. No browser terms. 47import "nx_video_ui_core.nx" 48// SOVEREIGN video codec on the wire (task #27): YUV420 wrapper over the gated nx_vcodec (key+P, motion 49// comp). Replaces the browser JPEG encoder -- the last third-party in the media path and the field-call 50// bitrate killer (gate: key 7110B / P 206B at 320x240 vs JPEG ~10KB EVERY frame). Same source runs native 51// on NishiOS -- one codec, every target. 52import "nx_video_codec_wasm.nx" 53// the TRAINED restoration table (neural rung 2) -- vc_nf_load exported so the last-mile shim can fill the 54// nf table region once at mount; the codec then restores per-band ONLY where the encoder measured a win. 55import "nx_vcodec_nf_table.nx" 56const VC_MAGIC_65536: i64 = 65536 57const VC_MAGIC_16777216: i64 = 16777216 58const VC_MAGIC_2147483648: i64 = 2147483648 59const VC_MAGIC_8388608: i64 = 8388608 60const VC_MAGIC_8388607: i64 = 8388607 61const VC_MAGIC_32768: i64 = 32768 62const VC_MAGIC_32767: i64 = 32767 63const VC_MAGIC_2048: i64 = 2048 64const VC_MAGIC_2764: i64 = 2764 65 66const VC_NV1_HDR: i64 = 24 67const VC_NV1_CHDR: i64 = 9 68 69func vc_rd_u32(b: *u8, off: i64) -> i64 { 70 var v: i64 = b[off] & 0xff 71 v = v + ((b[off + 1] & 0xff) * 256) 72 v = v + ((b[off + 2] & 0xff) * VC_MAGIC_65536) 73 v = v + ((b[off + 3] & 0xff) * VC_MAGIC_16777216) 74 return v 75} 76 77func vc_wr_u32(b: *u8, off: i64, v: i64) -> i64 { 78 b[off] = (v & 255) as u8 79 b[off + 1] = ((v / 256) & 255) as u8 80 b[off + 2] = ((v / VC_MAGIC_65536) & 255) as u8 81 b[off + 3] = ((v / VC_MAGIC_16777216) & 255) as u8 82 return 4 83} 84 85// ---- NV1 container ---- 86 87func vc_nv1_header(out: *u8, rate: i64) -> i64 { 88 out[0] = 78; out[1] = 76; out[2] = 67; out[3] = 49 // "NLC1" 89 vc_wr_u32(out, 4, 1) 90 vc_wr_u32(out, 8, rate) 91 vc_wr_u32(out, 12, 1) 92 vc_wr_u32(out, 16, 0) 93 vc_wr_u32(out, 20, 0) 94 return VC_NV1_HDR 95} 96 97func vc_nv1_chunk(out: *u8, w: i64, kind: i64, t_ms: i64, payload: *u8, n: i64) -> i64 { 98 out[w] = kind as u8 99 vc_wr_u32(out, w + 1, n) 100 vc_wr_u32(out, w + 5, t_ms) 101 var i: i64 = 0 102 while i < n { out[w + VC_NV1_CHDR + i] = payload[i]; i = i + 1 } 103 return w + VC_NV1_CHDR + n 104} 105 106func vc_nv1_end(out: *u8, w: i64, dur_ms: i64) -> i64 { 107 out[w] = 69 // 'E' 108 vc_wr_u32(out, w + 1, 0) 109 vc_wr_u32(out, w + 5, dur_ms) 110 return w + VC_NV1_CHDR 111} 112 113// Same defect codes as nx_nv1.nx (the team-side authority). 114func vc_nv1_validate(buf: *u8, n: i64, info: *i64) -> i64 { 115 if n < VC_NV1_HDR + VC_NV1_CHDR { return 0 - 1 } 116 var m: i64 = 1 117 if (buf[0] & 0xff) != 78 { m = 0 } 118 if (buf[1] & 0xff) != 76 { m = 0 } 119 if (buf[2] & 0xff) != 67 { m = 0 } 120 if (buf[3] & 0xff) != 49 { m = 0 } 121 if m == 0 { return 0 - 2 } 122 if vc_rd_u32(buf, 4) != 1 { return 0 - 3 } 123 if vc_rd_u32(buf, 12) != 1 { return 0 - 7 } 124 info[0] = vc_rd_u32(buf, 8) 125 var off: i64 = VC_NV1_HDR 126 var ac: i64 = 0 127 var vc: i64 = 0 128 var samp: i64 = 0 129 var dur: i64 = 0 130 var ended: i64 = 0 131 while ended == 0 { 132 if off + VC_NV1_CHDR > n { return 0 - 6 } 133 let k: i64 = buf[off] & 0xff 134 let ln: i64 = vc_rd_u32(buf, off + 1) 135 let tm: i64 = vc_rd_u32(buf, off + 5) 136 if off + VC_NV1_CHDR + ln > n { return 0 - 5 } 137 var known: i64 = 0 138 if k == 65 { ac = ac + 1; samp = samp + ln / 2; known = 1 } 139 if k == 86 { vc = vc + 1; known = 1 } 140 if k == 69 { 141 if ln != 0 { return 0 - 4 } 142 ended = 1; dur = tm; known = 1 143 } 144 if known == 0 { return 0 - 4 } 145 off = off + VC_NV1_CHDR + ln 146 } 147 if off != n { return 0 - 8 } 148 info[1] = ac 149 info[2] = samp 150 info[3] = vc 151 info[4] = dur 152 return 0 153} 154 155// Walk one chunk at `off` (first call: off = 24). Fills info and returns 156// the NEXT chunk offset; returns 0 when the chunk is the 'E' terminator; 157// negative on malformation. 158func vc_nv1_next(buf: *u8, n: i64, off: i64, info: *i64) -> i64 { 159 if off + VC_NV1_CHDR > n { return 0 - 6 } 160 let k: i64 = buf[off] & 0xff 161 let ln: i64 = vc_rd_u32(buf, off + 1) 162 if off + VC_NV1_CHDR + ln > n { return 0 - 5 } 163 info[0] = k 164 info[1] = ln 165 info[2] = vc_rd_u32(buf, off + 5) 166 info[3] = off + VC_NV1_CHDR 167 if k == 69 { return 0 } 168 return off + VC_NV1_CHDR + ln 169} 170 171// ---- room wire frames: [kind][8-byte id][u32 seq LE][payload] ---- 172 173func vc_wire_pack(out: *u8, kind: i64, id: *u8, seq: i64, payload: *u8, n: i64) -> i64 { 174 out[0] = kind as u8 175 var i: i64 = 0 176 while i < 8 { out[1 + i] = id[i]; i = i + 1 } 177 vc_wr_u32(out, 9, seq) 178 i = 0 179 while i < n { out[13 + i] = payload[i]; i = i + 1 } 180 return 13 + n 181} 182 183func vc_wire_parse(buf: *u8, n: i64, info: *i64) -> i64 { 184 if n < 13 { return 0 - 1 } 185 info[0] = buf[0] & 0xff 186 info[1] = vc_rd_u32(buf, 9) 187 info[2] = 13 188 info[3] = n - 13 189 return 0 190} 191 192// ---- sample plane: IEEE 754 binary32 <-> Int16, integer math only ---- 193 194// One f32 (as raw bits) -> i16: trunc(f * 32768), clamped to [-32768,32767]. 195// SYMMETRIC 32768 scale both directions (the JS this replaces used *32767 196// encode / /32768 decode -- that asymmetry loses 1 LSB per round-trip, 197// which the lossless doctrine forbids; the gate proves the symmetric map 198// round-trips EXACTLY on all 65536 values). NaN -> 0; +/-Inf clamps. 199func vc_f32_bits_to_i16(bits: i64) -> i64 { 200 let s: i64 = (bits / VC_MAGIC_2147483648) & 1 201 let e: i64 = (bits / VC_MAGIC_8388608) & 0xff 202 let mfrac: i64 = bits & VC_MAGIC_8388607 203 if e == 255 { 204 if mfrac != 0 { return 0 } // NaN 205 if s == 1 { return 0 - VC_MAGIC_32768 } // -Inf clamps 206 return VC_MAGIC_32767 207 } 208 if e >= 127 { // |f| >= 1.0 209 if s == 1 { return 0 - VC_MAGIC_32768 } // -1.0 IS representable 210 return VC_MAGIC_32767 // +1.0 clamps to max 211 } 212 var mant: i64 = mfrac 213 var ee: i64 = e 214 if e > 0 { mant = mfrac + VC_MAGIC_8388608 } else { ee = 1 } 215 // |f| = mant * 2^(ee-150); want trunc(|f| * 32768) = mant*2^15 >> (150-ee) 216 var prod: i64 = mant * VC_MAGIC_32768 217 var sh: i64 = 150 - ee // >= 24 here 218 while sh > 0 { 219 prod = prod / 2 220 sh = sh - 1 221 if prod == 0 { sh = 0 } 222 } 223 if s == 1 { return 0 - prod } 224 return prod 225} 226 227// One i16 -> f32 bits. EXACT: v/32768 always fits the 24-bit mantissa. 228func vc_i16_to_f32_bits(v: i64) -> i64 { 229 if v == 0 { return 0 } 230 var s: i64 = 0 231 var a: i64 = v 232 if a < 0 { s = 1; a = 0 - a } // a in 1..VC_MAGIC_32768 233 var p: i64 = 0 234 var t: i64 = a 235 while t > 1 { t = t / 2; p = p + 1 } // msb index, 0..15 236 var pow: i64 = 1 237 var k: i64 = 0 238 while k < p { pow = pow * 2; k = k + 1 } 239 var mant: i64 = a - pow // strip implicit bit 240 var shl: i64 = 23 - p 241 while shl > 0 { mant = mant * 2; shl = shl - 1 } 242 var out: i64 = ((112 + p) * VC_MAGIC_8388608) + mant // exp field = 127 + (p-15) 243 if s == 1 { out = out + VC_MAGIC_2147483648 } 244 return out 245} 246 247// Buffer forms (LE in linear memory). 248func vc_f32_to_i16(inb: *u8, n_samples: i64, outb: *u8) -> i64 { 249 var i: i64 = 0 250 while i < n_samples { 251 let bits: i64 = vc_rd_u32(inb, i * 4) 252 var v: i64 = vc_f32_bits_to_i16(bits) 253 if v < 0 { v = v + VC_MAGIC_65536 } 254 outb[i * 2] = (v & 255) as u8 255 outb[i * 2 + 1] = ((v / 256) & 255) as u8 256 i = i + 1 257 } 258 return n_samples 259} 260 261func vc_i16_to_f32(inb: *u8, n_samples: i64, outb: *u8) -> i64 { 262 var i: i64 = 0 263 while i < n_samples { 264 var v: i64 = (inb[i * 2] & 0xff) + ((inb[i * 2 + 1] & 0xff) * 256) 265 if v >= VC_MAGIC_32768 { v = v - VC_MAGIC_65536 } // sign-extend i16 266 vc_wr_u32(outb, i * 4, vc_i16_to_f32_bits(v)) 267 i = i + 1 268 } 269 return n_samples 270} 271 272// ---- LPC lossless audio: live-call port of the NV1 'L' payload ---- 273// Layout = the nx_nv1_lpc.nx authority byte-for-byte: u8 mode (0..3 fixed 274// predictor order, 255 verbatim escape) | u8 rice_k | u32 nsamples | 275// warmup raw LE i16 | Rice bitstream LSB-first. Live audio chunks ride 276// the room wire as kind 'L' (0x4C) frames: [u32 rate][this payload] -- 277// ~half the bytes of raw PCM with ZERO loss (lossless doctrine). 278// scratch = caller-supplied i64 region, >= 2*nsamples + 8 slots (encode); 279// decode uses slot 0 as its bit cursor. 280 281func vc_lpc_rd_i16(b: *u8, off: i64) -> i64 { 282 var v: i64 = b[off] & 0xff 283 v = v + ((b[off + 1] & 0xff) * 256) 284 if v >= VC_MAGIC_32768 { v = v - VC_MAGIC_65536 } 285 return v 286} 287 288func vc_lpc_wr_i16(b: *u8, idx: i64, v: i64) -> i64 { 289 var u: i64 = v 290 if u < 0 { u = u + VC_MAGIC_65536 } 291 b[idx * 2] = (u & 255) as u8 292 b[idx * 2 + 1] = ((u / 256) & 255) as u8 293 return 0 294} 295 296func vc_lpc_bit_put(b: *u8, pos: i64, bit: i64) -> i64 { 297 if bit == 1 { 298 let by: i64 = pos >> 3 299 b[by] = ((b[by] & 0xff) | (1 << (pos & 7))) as u8 300 } 301 return pos + 1 302} 303 304func vc_lpc_bit_get(b: *u8, cur: *i64, lim: i64) -> i64 { 305 let p: i64 = cur[0] 306 if p >= lim { return 0 - 1 } 307 cur[0] = p + 1 308 return ((b[p >> 3] & 0xff) >> (p & 7)) & 1 309} 310 311func vc_lpc_rice_put(b: *u8, pos: i64, u: i64, k: i64) -> i64 { 312 var p: i64 = pos 313 var q: i64 = u >> k 314 while q > 0 { p = vc_lpc_bit_put(b, p, 1); q = q - 1 } 315 p = vc_lpc_bit_put(b, p, 0) 316 var j: i64 = 0 317 while j < k { 318 p = vc_lpc_bit_put(b, p, (u >> j) & 1) 319 j = j + 1 320 } 321 return p 322} 323 324func vc_lpc_rice_get(b: *u8, cur: *i64, lim: i64, k: i64) -> i64 { 325 var q: i64 = 0 326 var bit: i64 = vc_lpc_bit_get(b, cur, lim) 327 while bit == 1 { 328 q = q + 1 329 bit = vc_lpc_bit_get(b, cur, lim) 330 } 331 if bit < 0 { return 0 - 1 } 332 var v: i64 = q << k 333 var j: i64 = 0 334 while j < k { 335 let x: i64 = vc_lpc_bit_get(b, cur, lim) 336 if x < 0 { return 0 - 1 } 337 v = v + (x << j) 338 j = j + 1 339 } 340 return v 341} 342 343func vc_lpc_residuals(x: *i64, n: i64, o: i64, u: *i64) -> i64 { 344 var i: i64 = o 345 while i < n { 346 var p1: i64 = 0 347 var p2: i64 = 0 348 var p3: i64 = 0 349 if i >= 1 { p1 = x[i - 1] } 350 if i >= 2 { p2 = x[i - 2] } 351 if i >= 3 { p3 = x[i - 3] } 352 var pred: i64 = 0 353 if o == 1 { pred = p1 } 354 if o == 2 { pred = 2 * p1 - p2 } 355 if o == 3 { pred = 3 * p1 - 3 * p2 + p3 } 356 let r: i64 = x[i] - pred 357 var z: i64 = 2 * r 358 if r < 0 { z = 0 - z - 1 } 359 u[i - o] = z 360 i = i + 1 361 } 362 return n - o 363} 364 365func vc_lpc_cost_bits(u: *i64, m: i64, k: i64) -> i64 { 366 var s: i64 = 0 367 var i: i64 = 0 368 while i < m { 369 s = s + (u[i] >> k) + 1 + k 370 i = i + 1 371 } 372 return s 373} 374 375// Encode nsamples LE i16 into the L payload at out. Never larger than the 376// verbatim escape (6 + 2n). Returns payload len; -10 outcap too small. 377func vc_lpc_encode(pcm: *u8, nsamples: i64, out: *u8, outcap: i64, scratch: *i64) -> i64 { 378 if nsamples < 0 { return 0 - 11 } 379 if outcap < 6 + 2 * nsamples { return 0 - 10 } 380 if nsamples == 0 { 381 out[0] = 0 as u8 382 out[1] = 0 as u8 383 vc_wr_u32(out, 2, 0) 384 return 6 385 } 386 let x: *i64 = scratch 387 let u: *i64 = (scratch as i64 + nsamples * 8) as *i64 388 var i: i64 = 0 389 while i < nsamples { x[i] = vc_lpc_rd_i16(pcm, i * 2); i = i + 1 } 390 var bmode: i64 = 255 391 var bk: i64 = 0 392 var bsize: i64 = 6 + 2 * nsamples 393 var o: i64 = 0 394 while o < 4 { 395 if o < nsamples { 396 let m: i64 = vc_lpc_residuals(x, nsamples, o, u) 397 var kk: i64 = 0 398 var bestbits: i64 = vc_lpc_cost_bits(u, m, 0) 399 var bestk: i64 = 0 400 kk = 1 401 while kk < 16 { 402 let c: i64 = vc_lpc_cost_bits(u, m, kk) 403 if c < bestbits { bestbits = c; bestk = kk } 404 kk = kk + 1 405 } 406 let sz: i64 = 6 + 2 * o + (bestbits + 7) / 8 407 if sz < bsize { bsize = sz; bmode = o; bk = bestk } 408 } 409 o = o + 1 410 } 411 out[0] = bmode as u8 412 out[1] = bk as u8 413 vc_wr_u32(out, 2, nsamples) 414 if bmode == 255 { 415 i = 0 416 while i < 2 * nsamples { out[6 + i] = pcm[i]; i = i + 1 } 417 return 6 + 2 * nsamples 418 } 419 i = 0 420 while i < 2 * bmode { out[6 + i] = pcm[i]; i = i + 1 } 421 let m2: i64 = vc_lpc_residuals(x, nsamples, bmode, u) 422 let base: i64 = 6 + 2 * bmode 423 i = base 424 while i < bsize { out[i] = 0 as u8; i = i + 1 } 425 var pos: i64 = base * 8 426 i = 0 427 while i < m2 { pos = vc_lpc_rice_put(out, pos, u[i], bk); i = i + 1 } 428 return bsize 429} 430 431// Decode an L payload into LE i16 at out. Same named negatives as the 432// authority: -1 short -2 mode -3 k -4 outcap -5 bitstream -6 warmup 433// -7 out-of-range reconstruction (tamper-evident). 434func vc_lpc_decode(pl: *u8, plen: i64, out: *u8, outcap: i64, scratch: *i64) -> i64 { 435 if plen < 6 { return 0 - 1 } 436 let mode: i64 = pl[0] & 0xff 437 let k: i64 = pl[1] & 0xff 438 let ns: i64 = vc_rd_u32(pl, 2) 439 var modeok: i64 = 0 440 if mode < 4 { modeok = 1 } 441 if mode == 255 { modeok = 1 } 442 if modeok == 0 { return 0 - 2 } 443 if k > 30 { return 0 - 3 } 444 if ns * 2 > outcap { return 0 - 4 } 445 if mode == 255 { 446 if plen < 6 + 2 * ns { return 0 - 6 } 447 var i: i64 = 0 448 while i < 2 * ns { out[i] = pl[6 + i]; i = i + 1 } 449 return ns 450 } 451 if ns == 0 { return 0 } 452 if mode >= ns { return 0 - 6 } 453 if plen < 6 + 2 * mode { return 0 - 6 } 454 var p1: i64 = 0 455 var p2: i64 = 0 456 var p3: i64 = 0 457 var i: i64 = 0 458 while i < mode { 459 let v: i64 = vc_lpc_rd_i16(pl, 6 + 2 * i) 460 vc_lpc_wr_i16(out, i, v) 461 p3 = p2; p2 = p1; p1 = v 462 i = i + 1 463 } 464 let cur: *i64 = scratch 465 cur[0] = (6 + 2 * mode) * 8 466 let lim: i64 = plen * 8 467 while i < ns { 468 let uv: i64 = vc_lpc_rice_get(pl, cur, lim, k) 469 if uv < 0 { return 0 - 5 } 470 var r: i64 = uv / 2 471 if (uv & 1) == 1 { r = 0 - ((uv + 1) / 2) } 472 var pred: i64 = 0 473 if mode == 1 { pred = p1 } 474 if mode == 2 { pred = 2 * p1 - p2 } 475 if mode == 3 { pred = 3 * p1 - 3 * p2 + p3 } 476 let vv: i64 = pred + r 477 if vv > VC_MAGIC_32767 { return 0 - 7 } 478 if vv < 0 - VC_MAGIC_32768 { return 0 - 7 } 479 vc_lpc_wr_i16(out, i, vv) 480 p3 = p2; p2 = p1; p1 = vv 481 i = i + 1 482 } 483 return ns 484} 485 486// ---- the fps/quality ladder TABLE: the CORE owns the rungs (moved out of app.js 2026-07-02) ---- 487// Research-grounded raise (perf_frame_rate/perf_mjpeg/perf_qoe banked): top rung 10->20fps for small 488// rooms = the direct smoothness win. Offered load stays bounded BY CONSTRUCTION: the RTT governor 489// (vc_ladder_step) degrades on the absolute cliff, the client backpressure gate never queues past 490// BACKPRESSURE_MAX, and vc_ladder_floor below caps big rooms near the edge's MEASURED envelope. 491// 10 rungs (was 7 topping at 20fps -- a self-imposed data ceiling, not SOTA): fps 492// 60/45/30/20/15/12/10/8/5/3. The 60/45/30 rungs are justified by the 2026-07-03 LIVE 493// measurements (nx_video_swarm_probe: 840KB/s fan-out through the edge at p95 7.8ms, ZERO loss; 494// nx_video_qoe_live: 3.6ms p50 single-flow path): 60fps x ~5KB (q=400) = ~300KB/s/stream, well 495// inside the measured-clean envelope. Higher fps rungs carry LOWER per-frame q so bitrate stays 496// bounded (smoothness-for-detail trade the governor can walk). 497const VC_LADDER_RUNGS: i64 = 10 498func vc_ladder_rungs() -> i64 { return VC_LADDER_RUNGS } 499func vc_ladder_fps(idx: i64) -> i64 { 500 if idx <= 0 { return 60 } 501 if idx == 1 { return 45 } 502 if idx == 2 { return 30 } 503 if idx == 3 { return 20 } 504 if idx == 4 { return 15 } 505 if idx == 5 { return 12 } 506 if idx == 6 { return 10 } 507 if idx == 7 { return 8 } 508 if idx == 8 { return 5 } 509 return 3 510} 511func vc_ladder_q_permille(idx: i64) -> i64 { 512 if idx <= 0 { return 400 } 513 if idx == 1 { return 450 } 514 if idx == 2 { return 500 } 515 if idx == 3 { return 550 } 516 if idx == 4 { return 500 } 517 if idx == 5 { return 450 } 518 if idx == 6 { return 400 } 519 if idx == 7 { return 350 } 520 if idx == 8 { return 280 } 521 return 220 522} 523 524// ---- N-aware ladder floor: bigger rooms start lower on the fps ladder ---- 525// Fan-out egress grows with peers; the caps below keep aggregate offered load near the 526// MEASURED envelope (2026-07-03 swarm probe: 1-sender/7-receiver at 20fps x 6KB = 840KB/s 527// fan-out CLEAN, p95 7.8ms, zero loss -- so every room size gets a lifted-but-bounded cap; 528// the RTT governor still degrades under real pressure). Full-mesh aggregate beyond the 529// measured point stays conservative until the mesh swarm measurement lands. 530// n<=2 -> 60fps, 3 -> 30fps, 4 -> 20fps, 5 -> 15fps, 6+ -> 12fps. 531func vc_ladder_floor(n_peers: i64) -> i64 { 532 if n_peers <= 2 { return 0 } 533 if n_peers == 3 { return 2 } 534 if n_peers == 4 { return 3 } 535 if n_peers == 5 { return 4 } 536 return 5 537} 538 539// margin (ms) above the observed baseline RTT that still counts as "calm 540// enough to climb a rung". Picked so a stable intercontinental link recovers 541// while a congested one (queue building over the baseline) does not. 542const VC_RECOVER_DELTA: i64 = 100 543// margin (ms) ABOVE the observed baseline RTT that counts as congestion (queue building) = the DEGRADE trigger. 544// 827 LIVE-FIX (operator call, RTT 419ms baseline pinned the ladder at the 3fps floor with enc=36ms + 6KB/s of 545// data = ZERO real congestion): the old degrade was an ABSOLUTE 400ms cliff, so any far/relay-latency link 546// degraded every ping and could never climb -- latency confused with congestion. Now degrade is baseline- 547// relative like recover (the nx_room_bwe delay-gradient idea): a link is congested when its RTT rises >250ms 548// ABOVE its own floor, not when it exceeds a fixed number a distant relay always sits above. The absolute 549// degrade_ms stays as a HARD-BROKEN ceiling (client raised it 400->900). Throughput congestion is caught 550// separately by the client's bpSkips gate. > VC_RECOVER_DELTA so there is a stable hysteresis band. 551const VC_DEGRADE_DELTA: i64 = 250 552 553// ---- send-rate governor: the fps/quality ladder decision ---- 554// good[0] = caller-persisted 3-good recover counter. 555// good[1] = caller-persisted BASELINE (min RTT seen). An intercontinental 556// link's RTT floor (distance alone ~150-250ms) can exceed an ABSOLUTE 557// recover_ms, which pinned the ladder at the bottom rung FOREVER (it could 558// only ever degrade). Fix (the nx_room_bwe delay-relative-to-baseline idea): 559// recovery is allowed when RTT is back NEAR ITS OWN BASELINE, not only below 560// a fixed threshold a far link never reaches. DEGRADE is unchanged (absolute 561// cliff); the change is additive and local (no wire-format impact). 562func vc_ladder_step(idx: i64, n_rungs: i64, rtt_ms: i64, 563 degrade_ms: i64, recover_ms: i64, good: *i64) -> i64 { 564 if good[1] == 0 { good[1] = rtt_ms } // first sample seeds the baseline 565 if rtt_ms < good[1] { good[1] = rtt_ms } // track the min-RTT floor 566 let recover_floor: i64 = good[1] + VC_RECOVER_DELTA 567 let degrade_floor: i64 = good[1] + VC_DEGRADE_DELTA 568 var do_degrade: i64 = 0 569 if rtt_ms > degrade_floor { do_degrade = 1 } // RTT rose above the link's own baseline = congestion 570 if rtt_ms > degrade_ms { do_degrade = 1 } // OR the absolute hard-broken ceiling (client passes 900) 571 if do_degrade == 1 { 572 good[0] = 0 573 if idx < n_rungs - 1 { return idx + 1 } 574 return idx 575 } 576 var can_recover: i64 = 0 577 if rtt_ms < recover_ms { can_recover = 1 } // absolute low (unchanged path) 578 if rtt_ms < recover_floor { can_recover = 1 } // OR back near the link's own baseline 579 if can_recover == 1 { 580 if idx > 0 { 581 good[0] = good[0] + 1 582 if good[0] >= 3 { good[0] = 0; return idx - 1 } 583 } 584 return idx 585 } 586 return idx 587} 588 589// ============================================================================================================ 590// SOVEREIGN CLIENT-CORE POLICY (821 -- doctrine: JS = LAST-MILE SHIM ONLY; operator 2026-07-12 "make sure we 591// arent building everything into the js ... run it on nishi os and nishi browser as we keep growing"). 592// These exports pull the last two POLICY surfaces out of app.v2.js: the rctx layout+seeding (previously 593// hand-poked slot offsets DUPLICATED in the main thread and the worker template -- the dual-edit hazard that 594// nearly stomped the trained-nf table in the 819 port) and the resolution tier ladder. The JS shim now only 595// bridges browser primitives; NishiOS + the Nishi browser adapters call these SAME exports natively. 596// ============================================================================================================ 597// rctx INTERNAL LAYOUT -- single source of truth; callers pass only the block base + policy inputs. 598// slots(9 i64)@+0 · est@+0x80 · probs@+0x180 · t8c slab@+0x280 (5008B + RD-skip trial scratch @+5008) · 599// rc byte-stream scratch@+0x1800 (the caller guarantees headroom from there to its next region). 600const VV_CTX_EST: i64 = 0x80 601const VV_CTX_PROBS: i64 = 0x180 602const VV_CTX_T8C: i64 = 0x280 603const VV_CTX_RCSCR: i64 = 0x1800 604// vcv-10 MVD MV row-plane carve (F617): inside the rc-scratch region, >46KB past the measured worst rc 605// bytes (~150KB from base+0x1800) and far below the R_HEAT carve; 640B used at the 640-wide ceiling. 606const VV_CTX_MVPLANE: i64 = 0x32000 607// one-time per-mount ctx init (DCT8 matrix + zig-zag tables inside the block's t8c slab) 608func vv_init_ctx(base: i64) -> i64 { vc_t8_init((base + VV_CTX_T8C) as *i64); return 0 } 609// per-call rctx seed. COMPOSES the emode POLICY natively: bit0 range-coder + bit2 t8 RD-auto (820 rung, 610// MEASURED -0.52% avg) + bit12 heat-AQ (819 rung, MEASURED -1.72% avg) all ride the rc capability; bit5 611// sig-map (vcv-6) / bit9 gentle deblock (vcv-8) ride their peer caps. 612// F1115 (2026-07-28, THE MEASURED TRADE — 4-seq MSU BD vs x264, evidence _offc/rung2/bd2593.txt): 613// bit11 RD-SKIP is now ON for rc rooms. The 817 fps law is RETIRED for it: that verdict predated 614// BOTH the 2026-07-14 fast-RD-skip (code-arm-is-the-stream) and F1113 (the trial no longer forces 615// partition-RD + full search on every band MB). Measured same-run: +12% encode for the +8dB curve 616// (gap vs x264 6.7x -> 2.9x). bit11 is ENCODER-ONLY: the skip BIT syntax is unchanged, any decoder 617// reads the stream — safe even in mixed rooms. 618// bit7 P_8x8 PARTITION is now OFF: measured 68% of P-encode for ~1.3% BD (the 825 motion-gating only 619// spares static content). bit7 is SYNTAX (the per-MB part bit) — but both peers derive emode from 620// THIS function in the SAME served binary, so enc+dec always agree; the part capability arg is 621// accepted and deliberately ignored so old callers need no change. 622// Returns the composed emode so callers can log/inspect it. 623func vv_seed_rctx(base: i64, rc: i64, nf: i64, sig: i64, part: i64, deblock: i64, heatp: i64, nftbl: i64, nfscr: i64) -> i64 { 624 let r: *i64 = base as *i64 625 var emode: i64 = 0 626 // 824 LIVE-INCIDENT FIX (field screenshot: 2.2fps + drop-storm corruption on v823): bit12 HEAT-AQ and 627 // bit2 t8-RD are RTC-OFF. On REAL SENSOR NOISE heat is an fps catastrophe MEASURED 2.5x (640x480 steady-P 628 // 134ms->334ms): noise-static blocks build 4-skip streaks, the halved threshold then converts the next 629 // noise wiggle into a FULL encode, streak resets, repeat -- an oscillating code-storm the clean-content 630 // benches could not see (their static blocks are EXACTLY static). The fps collapse backs up the send 631 // queue -> mid-GOP drops -> decode-vs-wrong-prev garbage -> kreq storms = the observed corruption. Both 632 // bits stay in the ARCHIVAL profile (clean stored content, no fps budget). RTC = the 817-proven stream. 633 if rc != 0 { emode = 1 } 634 if sig != 0 { emode = emode | 32 } 635 // bit7 partition: retired by F1115 (68% of encode for ~1%); `part` accepted + ignored, see header 636 if deblock != 0 { emode = emode | 512 } 637 if rc != 0 { emode = emode | VC_MAGIC_2048 } // F1115: RD-skip rides the rc capability (encoder-only bit) 638 r[0] = emode 639 r[1] = base + VV_CTX_EST 640 r[2] = base + VV_CTX_PROBS 641 r[3] = base + VV_CTX_RCSCR 642 r[4] = base + VV_CTX_T8C 643 r[5] = 0 644 r[6] = 0 645 r[7] = 0 646 if nf != 0 { r[6] = nftbl } 647 if nf != 0 { r[7] = nfscr } 648 r[8] = heatp 649 // vcv-10 (F617): rctx[9] = the MVD-median MV row-plane the rct9 frame pair REQUIRES ([6]/[7] stay 650 // the nf table+scratch, [8] the heat plane -- the collision the port caught, twice now) 651 r[9] = base + VV_CTX_MVPLANE 652 return emode 653} 654// resolution TIER policy (Jitsi-class adaptive send). MEASURED tiers (vc_res_bench 2026-07-12: 640x480 = 655// 38.6fps / 480x384 = 58 / 416x320 = 70 single-thread on a cls-9 core; legacy tiers below unchanged). 656// >320x256 gated on all818 (pre-818 peers' RX slots are sized to the legacy ceiling -- overflow otherwise). 657// Returns (w<<16)|h; 0 = caller applies its legacy default (old-peer / unbenchmarked fallback). 658func vc_res_tier(cls: i64, flex_all: i64, all818: i64) -> i64 { 659 if flex_all == 0 { return 0 } 660 if cls < 0 { return 0 } 661 // 825 SPEED-AWARE LADDER: cls now comes from an HONEST bench (vv_enc_rct8 P-frame on noise @320x256, the 662 // real live cost) and the decision-stack early-outs (825) made rct8-on-noise 3-4x faster (MEASURED on this 663 // desktop, proportional head-and-shoulders + sensor noise: 320x256=81fps · 416x320=48 · 480x384=38 · 664 // 640x480=22). Each tier maps to a cls whose extrapolated cost (linear in pixels) clears the 15fps capture 665 // with >=2x margin: cls9(bench<=8ms)->640x480, cls8(<=12)->480x384, cls7(<=18)->416x320, cls6(<=26)->320. 666 // A weaker device only reaches a tier if it MEASURED capable of it -- the overshoot class is closed. 667 if all818 != 0 { 668 if cls >= 9 { return (640 << 16) | 480 } 669 if cls >= 8 { return (480 << 16) | 384 } 670 if cls >= 7 { return (416 << 16) | 320 } 671 if cls >= 6 { return (320 << 16) | 256 } 672 if cls >= 4 { return (256 << 16) | 192 } 673 if cls >= 2 { return (160 << 16) | 128 } 674 return (128 << 16) | 96 675 } 676 // old-peer room (some peer pre-818): ceiling 320x256 (their RX slots are sized to the legacy max) 677 if cls >= 7 { return (320 << 16) | 256 } 678 if cls >= 4 { return (256 << 16) | 192 } 679 if cls >= 2 { return (160 << 16) | 128 } 680 return (128 << 16) | 96 681} 682 683// ---- RS-FEC erasure engine IN THE CORE (2026-07-02, the FEC-LIVE prerequisite) ---- 684// The measured intl h2h (nx_intl_h2h): our TCP path freezes 22-122 frames/30s on international 685// profiles; RS-FEC(k=8,m=2) freezes ZERO. The transport leg (multi-socket striping so one leg's 686// TCP stall = a recoverable erasure) needs the MATH in the core -- JS only moves bytes. This is 687// the proven nx_room_fec.nx GF(256) Cauchy-MDS codec, adapted to the core's rules: NO allocation 688// (the browser stubs syscalls) -- the caller provides every region. k=8 data + m=2 parity shards; 689// any 2 losses of 10 reconstruct byte-exact. tbl layout (i64 entries, caller region ~7KB): 690// [0..511]=exp [512..767]=log [768..847]=G (n*k rows-major). 691const VC_FEC_K: i64 = 8 692const VC_FEC_M: i64 = 2 693const VC_GF_POLY: i64 = 0x11d 694 695func vc_gf_mul(tbl: *i64, a: i64, b: i64) -> i64 { 696 if a == 0 { return 0 } 697 if b == 0 { return 0 } 698 return tbl[tbl[512 + a] + tbl[512 + b]] 699} 700func vc_gf_inv(tbl: *i64, a: i64) -> i64 { return tbl[255 - tbl[512 + a]] } 701 702// build exp/log tables + the [I_k ; Cauchy] generator into tbl. Call once. Returns n (=k+m). 703func vc_fec_init(tbl: *i64) -> i64 { 704 var x: i64 = 1 705 var i: i64 = 0 706 while i < 255 { 707 tbl[i] = x 708 tbl[512 + x] = i 709 x = x << 1 710 if (x & 256) != 0 { x = x ^ VC_GF_POLY } 711 i = i + 1 712 } 713 i = 255 714 while i < 512 { tbl[i] = tbl[i - 255]; i = i + 1 } 715 tbl[512] = 0 716 let k: i64 = VC_FEC_K 717 let m: i64 = VC_FEC_M 718 var r: i64 = 0 719 while r < k { 720 var c: i64 = 0 721 while c < k { if c == r { tbl[768 + r*k + c] = 1 } else { tbl[768 + r*k + c] = 0 } c = c + 1 } 722 r = r + 1 723 } 724 var j: i64 = 0 725 while j < m { 726 var c2: i64 = 0 727 while c2 < k { 728 tbl[768 + (k+j)*k + c2] = vc_gf_inv(tbl, (k + j) ^ c2) 729 c2 = c2 + 1 730 } 731 j = j + 1 732 } 733 return k + m 734} 735 736// encode: data = k*S bytes -> shards = (k+m)*S bytes (systematic: first k*S == data). 737func vc_fec_encode(tbl: *i64, data: *u8, shards: *u8, S: i64) -> i64 { 738 let k: i64 = VC_FEC_K 739 let n: i64 = VC_FEC_K + VC_FEC_M 740 var r: i64 = 0 741 while r < n { 742 var c: i64 = 0 743 while c < S { 744 var acc: i64 = 0 745 var i: i64 = 0 746 while i < k { acc = acc ^ vc_gf_mul(tbl, tbl[768 + r*k + i], data[i*S + c] as i64); i = i + 1 } 747 shards[r*S + c] = acc as u8 748 c = c + 1 749 } 750 r = r + 1 751 } 752 return n 753} 754 755// decode: shards (n*S bytes) + erased flags (n i64: 1=lost) -> out (k*S bytes, the original data). 756// scratchA = caller region for the k*k GF matrix (k*k i64 = 512B). 0 ok; -1 = >m losses (honest bound). 757func vc_fec_decode(tbl: *i64, shards: *u8, erased: *i64, out: *u8, S: i64, scratchA: *i64) -> i64 { 758 let k: i64 = VC_FEC_K 759 let n: i64 = VC_FEC_K + VC_FEC_M 760 var got: i64 = 0 761 var r: i64 = 0 762 while r < n { 763 if got < k { if erased[r] == 0 { 764 var i: i64 = 0 765 while i < k { scratchA[got*k + i] = tbl[768 + r*k + i]; i = i + 1 } 766 var c: i64 = 0 767 while c < S { out[got*S + c] = shards[r*S + c]; c = c + 1 } 768 got = got + 1 769 } } 770 r = r + 1 771 } 772 if got < k { return 0 - 1 } 773 // Gauss-Jordan over GF(256): scratchA (k x k) * X = out (k x S); out becomes X in place. 774 var col: i64 = 0 775 while col < k { 776 var p: i64 = 0 - 1 777 var pr: i64 = col 778 while pr < k { if p < 0 { if scratchA[pr*k + col] != 0 { p = pr } } pr = pr + 1 } 779 if p < 0 { return 0 - 1 } 780 if p != col { 781 var j: i64 = 0 782 while j < k { let t: i64 = scratchA[col*k + j]; scratchA[col*k + j] = scratchA[p*k + j]; scratchA[p*k + j] = t; j = j + 1 } 783 j = 0 784 while j < S { let t2: i64 = out[col*S + j] as i64; out[col*S + j] = out[p*S + j]; out[p*S + j] = t2 as u8; j = j + 1 } 785 } 786 let inv: i64 = vc_gf_inv(tbl, scratchA[col*k + col]) 787 var j2: i64 = 0 788 while j2 < k { scratchA[col*k + j2] = vc_gf_mul(tbl, scratchA[col*k + j2], inv); j2 = j2 + 1 } 789 j2 = 0 790 while j2 < S { out[col*S + j2] = vc_gf_mul(tbl, out[col*S + j2] as i64, inv) as u8; j2 = j2 + 1 } 791 var rr: i64 = 0 792 while rr < k { 793 if rr != col { 794 let f: i64 = scratchA[rr*k + col] 795 if f != 0 { 796 var j3: i64 = 0 797 while j3 < k { scratchA[rr*k + j3] = scratchA[rr*k + j3] ^ vc_gf_mul(tbl, f, scratchA[col*k + j3]); j3 = j3 + 1 } 798 j3 = 0 799 while j3 < S { out[rr*S + j3] = ((out[rr*S + j3] as i64) ^ vc_gf_mul(tbl, f, out[col*S + j3] as i64)) as u8; j3 = j3 + 1 } 800 } 801 } 802 rr = rr + 1 803 } 804 col = col + 1 805 } 806 return 0 807} 808 809// ---- FEC WIRE + REASSEMBLY (TIER-2 substrate): frame <-> shards, out-of-order collect ---- 810// Transport design (zero daemon change, relay stays content-blind): a leg = a shard-lane ROOM 811// ("family#0/1/2"); the sender stripes shard i onto leg i%legs, each shard travels ONE lane, so a 812// receiver never gets duplicates and one lane's TCP stall = a recoverable erasure. Shard payload = 813// the proven 16B fwire header [block_id u32][shard_idx u8][is_parity u8][k u8][m u8][frame_len u32] 814// [rsv u32] + S data bytes, S = ceil(frame_len/8). Collector = ONE block per sender (per-frame FEC: 815// block_id = the video seq; a newer block supersedes -- late shards of a superseded frame drop). 816// State region st (caller-provided, ~21KB): i64[0]=block_id [1]=present_mask [2]=S [3]=flen 817// [4]=count [5]=done; shard bytes at (st as *u8)+64, slot idx*S. NO allocation (core rules). 818 819func vc_fecs_shard_size(flen: i64) -> i64 { return (flen + 7) / 8 } 820 821// pack frame -> 10 wire-ready shard payloads in out (slot r = r*(16+S), each 16+S bytes). 822// scratch_data = k*S bytes, scratch_shards = 10*S bytes (caller regions). Returns 16+S (slot size). 823func vc_fecs_pack(tbl: *i64, frame: *u8, flen: i64, block_id: i64, scratch_data: *u8, scratch_shards: *u8, out: *u8) -> i64 { 824 let k: i64 = VC_FEC_K 825 let m: i64 = VC_FEC_M 826 let n: i64 = k + m 827 let S: i64 = (flen + 7) / 8 828 var i: i64 = 0 829 while i < k * S { if i < flen { scratch_data[i] = frame[i] } else { scratch_data[i] = 0 as u8 } i = i + 1 } 830 vc_fec_encode(tbl, scratch_data, scratch_shards, S) 831 var r: i64 = 0 832 while r < n { 833 let base: i64 = r * (16 + S) 834 var b: i64 = 0 835 while b < 4 { out[base + b] = ((block_id >> (b * 8)) & 0xff) as u8; b = b + 1 } 836 out[base + 4] = r as u8 837 if r < k { out[base + 5] = 0 as u8 } else { out[base + 5] = 1 as u8 } 838 out[base + 6] = k as u8 839 out[base + 7] = m as u8 840 b = 0 841 while b < 4 { out[base + 8 + b] = ((flen >> (b * 8)) & 0xff) as u8; b = b + 1 } 842 out[base + 12] = 0 as u8 843 out[base + 13] = 0 as u8 844 out[base + 14] = 0 as u8 845 out[base + 15] = 0 as u8 846 var c: i64 = 0 847 while c < S { out[base + 16 + c] = scratch_shards[r * S + c]; c = c + 1 } 848 r = r + 1 849 } 850 return 16 + S 851} 852 853func vc_fecrx_reset(st: *i64) -> i64 { 854 st[0] = 0 - 1 855 st[1] = 0 856 st[2] = 0 857 st[3] = 0 858 st[4] = 0 859 st[5] = 0 860 return 0 861} 862 863// feed one shard payload. Returns frame_len when the block completes (frame in out, byte-exact), 864// 0 = pending/duplicate/stale, -1 = malformed. scratchA = k*k i64; scratchSh = 10*S bytes contiguous. 865func vc_fecrx_add(st: *i64, tbl: *i64, payload: *u8, plen: i64, scratchA: *i64, scratchSh: *u8, out: *u8, outcap: i64) -> i64 { 866 let k: i64 = VC_FEC_K 867 let m: i64 = VC_FEC_M 868 let n: i64 = k + m 869 if plen < 17 { return 0 - 1 } 870 var block_id: i64 = 0 871 var b: i64 = 0 872 while b < 4 { block_id = block_id | ((payload[b] as i64) << (b * 8)); b = b + 1 } 873 let idx: i64 = payload[4] as i64 874 if (payload[6] as i64) != k { return 0 - 1 } 875 if (payload[7] as i64) != m { return 0 - 1 } 876 var flen: i64 = 0 877 b = 0 878 while b < 4 { flen = flen | ((payload[8 + b] as i64) << (b * 8)); b = b + 1 } 879 let S: i64 = (flen + 7) / 8 880 if plen != 16 + S { return 0 - 1 } 881 if idx >= n { return 0 - 1 } 882 // SAFE-BY-CONSTRUCTION bound: the LARGEST write is n*S (store[idx*S+c] with idx<n, and scratchSh[r*S+c] 883 // with r<n); out needs only k*S < n*S. Bounding n*S<=outcap makes EVERY write <= outcap, so a caller that 884 // sizes ALL of {st-store-area (slot-192), scratchSh, out} >= outcap CANNOT overflow on any hostile shard. 885 // (Proven exhaustively by nx_vc_fecrx_fuzz_gate: 100k adversarial inputs, 0 guard breaches.) 886 if n * S > outcap { return 0 - 1 } 887 if flen <= 0 { return 0 - 1 } 888 if block_id < st[0] { return 0 } // stale: an older frame's straggler -> drop 889 if block_id == st[0] { if st[5] == 1 { return 0 } } // already delivered this block 890 if block_id > st[0] { // a newer frame supersedes the collector 891 st[0] = block_id 892 st[1] = 0 893 st[2] = S 894 st[3] = flen 895 st[4] = 0 896 st[5] = 0 897 } 898 if st[2] != S { return 0 - 1 } 899 let bit: i64 = 1 << idx 900 if (st[1] & bit) != 0 { return 0 } // duplicate shard -> ignore 901 st[1] = st[1] | bit 902 st[4] = st[4] + 1 903 let store: *u8 = (st as *u8) + 192 // header uses i64[0..17]; bytes from 192 904 var c: i64 = 0 905 while c < S { store[idx * S + c] = payload[16 + c]; c = c + 1 } 906 if st[4] < k { return 0 } 907 // k shards present: erased flags live in the state header (i64[8..17]), decode into out 908 var r: i64 = 0 909 while r < n { 910 if (st[1] & (1 << r)) != 0 { st[8 + r] = 0 } else { st[8 + r] = 1 } 911 var c2: i64 = 0 912 while c2 < S { scratchSh[r * S + c2] = store[r * S + c2]; c2 = c2 + 1 } 913 r = r + 1 914 } 915 let erased: *i64 = (((st as *u8) + 64) as *i64) // = &st[8] via the byte view (8*8=64) 916 if vc_fec_decode(tbl, scratchSh, erased, out, S, scratchA) != 0 { return 0 - 1 } 917 st[5] = 1 918 return st[3] 919} 920 921// ---- chat framing IN THE CORE (C2: off JSON, onto the Nishi wire) ---- 922// Chat rides the same 13B envelope as media, kind 0x43 'C'. Payload = [nlen u8][name][text]. 923// The core owns pack/parse; JS only moves bytes + DOM. Caps: name<=24, text<=500 (the UI caps). 924// ---- REACTIONS (comms rung, wire kind 0x45): the PROTOCOL and the emoji SET live HERE so a browser tab, 925// the Nishi browser, and a native NishiOS video app share ONE reaction capability -- the shim only floats 926// what the core hands it. payload=[idx:1]. vc_react_emoji writes the UTF-8 bytes of reaction idx into out 927// and returns the byte count (0 = invalid idx -> caller treats as idx 0). Set: thumbs-up, heart, laugh, 928// party, clap, fire. 929const VC_NREACTS: i64 = 6 930func vc_react_pack(out: *u8, idx: i64) -> i64 { 931 var i: i64 = idx 932 if i < 0 { i = 0 } 933 if i >= VC_NREACTS { i = 0 } 934 out[0] = i as u8 935 return 1 936} 937func vc_react_parse(pay: *u8, plen: i64) -> i64 { 938 if plen < 1 { return 0 - 1 } 939 let i: i64 = pay[0] & 15 940 if i >= VC_NREACTS { return 0 } 941 return i 942} 943func vc_react_emoji(idx: i64, out: *u8) -> i64 { 944 var i: i64 = idx 945 if i < 0 { i = 0 } 946 if i >= VC_NREACTS { i = 0 } 947 if i == 0 { out[0]=0xF0 as u8; out[1]=0x9F as u8; out[2]=0x91 as u8; out[3]=0x8D as u8; return 4 } // thumbs-up U+1F44D 948 if i == 1 { out[0]=0xE2 as u8; out[1]=0x9D as u8; out[2]=0xA4 as u8; out[3]=0xEF as u8; out[4]=0xB8 as u8; out[5]=0x8F as u8; return 6 } // heart U+VC_MAGIC_2764 FE0F 949 if i == 2 { out[0]=0xF0 as u8; out[1]=0x9F as u8; out[2]=0x98 as u8; out[3]=0x82 as u8; return 4 } // laugh U+1F602 950 if i == 3 { out[0]=0xF0 as u8; out[1]=0x9F as u8; out[2]=0x8E as u8; out[3]=0x89 as u8; return 4 } // party U+1F389 951 if i == 4 { out[0]=0xF0 as u8; out[1]=0x9F as u8; out[2]=0x91 as u8; out[3]=0x8F as u8; return 4 } // clap U+1F44F 952 out[0]=0xF0 as u8; out[1]=0x9F as u8; out[2]=0x94 as u8; out[3]=0xA5 as u8; return 4 // fire U+1F525 953} 954func vc_chat_pack(out: *u8, name: *u8, nlen: i64, text: *u8, tlen: i64) -> i64 { 955 var nl: i64 = nlen 956 if nl > 24 { nl = 24 } 957 var tl: i64 = tlen 958 if tl > 500 { tl = 500 } 959 if nl < 0 { return 0 - 1 } 960 if tl <= 0 { return 0 - 1 } 961 out[0] = nl as u8 962 var i: i64 = 0 963 while i < nl { out[1 + i] = name[i]; i = i + 1 } 964 i = 0 965 while i < tl { out[1 + nl + i] = text[i]; i = i + 1 } 966 return 1 + nl + tl 967} 968// parse -> info[0]=name_off info[1]=name_len info[2]=text_off info[3]=text_len (offsets into pay). 0 ok. 969func vc_chat_parse(pay: *u8, plen: i64, info: *i64) -> i64 { 970 if plen < 2 { return 0 - 1 } 971 let nl: i64 = pay[0] as i64 972 if nl > 24 { return 0 - 1 } 973 if 1 + nl >= plen { return 0 - 1 } 974 var tl: i64 = plen - 1 - nl 975 if tl > 500 { tl = 500 } 976 info[0] = 1 977 info[1] = nl 978 info[2] = 1 + nl 979 info[3] = tl 980 return 0 981} 982 983// No main: the WAT target exports the vc_* functions directly (the LPC 984// wat-module convention) and the native cross-gate provides its own main.