code wiki / (root) / nx_video_client_wasm.nx

nx_video_client_wasm.nx source

↩ module page · 1125 lines · 53834 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). 497// 865 (operator 2026-09-02: capability is EMITTED from NishiLang; the browser shim is glue) -- two client DECISIONS moved out of 498// app.v2.js into the core: (1) the RESOLUTION-UPLIFT CAP: the highest cls the band uplift may reach. MEASURED same-instrument on 499// the h2h card: cap 11 sent 1152x640 over the TCP relay (+2.6 dB, 16 s stalls, no fps gain); cap 9 (640x480) gave 14.1/15.6 fps 500// +3.2 dB; cap 7 (416x320) gave 18.1/17.3 fps, 601/671 permil delivered, 48/52 ms glass-to-glass -- fps first until a 501// delivered-fps governor exists. (2) the READY-WORKER PICK: round-robin from a sender's slot over a bitmask of ready decode 502// workers (the shim used slot mod n, which pinned senders to a worker whose init had failed -- measured 3 of 4 ready). 503const VC_RES_UPLIFT_CLS_CAP: i64 = 7 504func vc_res_uplift_cap() -> i64 { return VC_RES_UPLIFT_CLS_CAP } 505func vc_pick_ready(slot: i64, mask: i64, n: i64) -> i64 { 506 if n <= 0 { return 0 - 1 } 507 var k: i64 = 0 508 while k < n { 509 let i: i64 = (slot + k) % n 510 if ((mask >> i) & 1) != 0 { return i } 511 k = k + 1 512 } 513 return 0 - 1 514} 515const VC_LADDER_RUNGS: i64 = 10 516func vc_ladder_rungs() -> i64 { return VC_LADDER_RUNGS } 517func vc_ladder_fps(idx: i64) -> i64 { 518 if idx <= 0 { return 60 } 519 if idx == 1 { return 45 } 520 if idx == 2 { return 30 } 521 if idx == 3 { return 20 } 522 if idx == 4 { return 15 } 523 if idx == 5 { return 12 } 524 if idx == 6 { return 10 } 525 if idx == 7 { return 8 } 526 if idx == 8 { return 5 } 527 return 3 528} 529func vc_ladder_q_permille(idx: i64) -> i64 { 530 if idx <= 0 { return 400 } 531 if idx == 1 { return 450 } 532 if idx == 2 { return 500 } 533 if idx == 3 { return 550 } 534 if idx == 4 { return 500 } 535 if idx == 5 { return 450 } 536 if idx == 6 { return 400 } 537 if idx == 7 { return 350 } 538 if idx == 8 { return 280 } 539 return 220 540} 541 542// ---- N-aware ladder floor: bigger rooms start lower on the fps ladder ---- 543// Fan-out egress grows with peers; the caps below keep aggregate offered load near the 544// MEASURED envelope (2026-07-03 swarm probe: 1-sender/7-receiver at 20fps x 6KB = 840KB/s 545// fan-out CLEAN, p95 7.8ms, zero loss -- so every room size gets a lifted-but-bounded cap; 546// the RTT governor still degrades under real pressure). Full-mesh aggregate beyond the 547// measured point stays conservative until the mesh swarm measurement lands. 548// n<=2 -> 60fps, 3 -> 30fps, 4 -> 20fps, 5 -> 15fps, 6+ -> 12fps. 549func vc_ladder_floor(n_peers: i64) -> i64 { 550 if n_peers <= 2 { return 0 } 551 if n_peers == 3 { return 2 } 552 if n_peers == 4 { return 3 } 553 if n_peers == 5 { return 4 } 554 return 5 555} 556 557// margin (ms) above the observed baseline RTT that still counts as "calm 558// enough to climb a rung". Picked so a stable intercontinental link recovers 559// while a congested one (queue building over the baseline) does not. 560const VC_RECOVER_DELTA: i64 = 100 561// margin (ms) ABOVE the observed baseline RTT that counts as congestion (queue building) = the DEGRADE trigger. 562// 827 LIVE-FIX (operator call, RTT 419ms baseline pinned the ladder at the 3fps floor with enc=36ms + 6KB/s of 563// data = ZERO real congestion): the old degrade was an ABSOLUTE 400ms cliff, so any far/relay-latency link 564// degraded every ping and could never climb -- latency confused with congestion. Now degrade is baseline- 565// relative like recover (the nx_room_bwe delay-gradient idea): a link is congested when its RTT rises >250ms 566// ABOVE its own floor, not when it exceeds a fixed number a distant relay always sits above. The absolute 567// degrade_ms stays as a HARD-BROKEN ceiling (client raised it 400->900). Throughput congestion is caught 568// separately by the client's bpSkips gate. > VC_RECOVER_DELTA so there is a stable hysteresis band. 569const VC_DEGRADE_DELTA: i64 = 250 570 571// ---- send-rate governor: the fps/quality ladder decision ---- 572// good[0] = caller-persisted 3-good recover counter. 573// good[1] = caller-persisted BASELINE (min RTT seen). An intercontinental 574// link's RTT floor (distance alone ~150-250ms) can exceed an ABSOLUTE 575// recover_ms, which pinned the ladder at the bottom rung FOREVER (it could 576// only ever degrade). Fix (the nx_room_bwe delay-relative-to-baseline idea): 577// recovery is allowed when RTT is back NEAR ITS OWN BASELINE, not only below 578// a fixed threshold a far link never reaches. DEGRADE is unchanged (absolute 579// cliff); the change is additive and local (no wire-format impact). 580func vc_ladder_step(idx: i64, n_rungs: i64, rtt_ms: i64, 581 degrade_ms: i64, recover_ms: i64, good: *i64) -> i64 { 582 if good[1] == 0 { good[1] = rtt_ms } // first sample seeds the baseline 583 if rtt_ms < good[1] { good[1] = rtt_ms } // track the min-RTT floor 584 let recover_floor: i64 = good[1] + VC_RECOVER_DELTA 585 let degrade_floor: i64 = good[1] + VC_DEGRADE_DELTA 586 var do_degrade: i64 = 0 587 if rtt_ms > degrade_floor { do_degrade = 1 } // RTT rose above the link's own baseline = congestion 588 if rtt_ms > degrade_ms { do_degrade = 1 } // OR the absolute hard-broken ceiling (client passes 900) 589 if do_degrade == 1 { 590 good[0] = 0 591 if idx < n_rungs - 1 { return idx + 1 } 592 return idx 593 } 594 var can_recover: i64 = 0 595 if rtt_ms < recover_ms { can_recover = 1 } // absolute low (unchanged path) 596 if rtt_ms < recover_floor { can_recover = 1 } // OR back near the link's own baseline 597 if can_recover == 1 { 598 if idx > 0 { 599 good[0] = good[0] + 1 600 if good[0] >= 3 { good[0] = 0; return idx - 1 } 601 } 602 return idx 603 } 604 return idx 605} 606 607// ---- fast congestion controller on the WSS send queue (task #34; 862: moved OUT of app.v2.js INTO the core 608// so NishiOS + the Nishi browser share it and it is gate-provable -- JS is a last-mile shim only). ---- 609// The socket's `bufferedAmount` (unsent-byte queue depth) is the browser-native, sovereign back-pressure 610// signal. AIMD: MULTIPLICATIVE decrease when the queue overflows the back-pressure line, ADDITIVE increase 611// (one rung) only after the queue has drained clearly for a full recovery window. Decrease is fast (every 612// tick) and increase slow (a whole window) -- the AIMD asymmetry that fits a suddenly-narrowed pipe in ONE 613// tick instead of the old +1-rung/~9-tick ladder that froze ~25s under a sudden 600 kbps cap. 614// 615// The caller runs this ONCE PER FIXED-PERIOD TICK, so there is no in-function cooldown to tune. Every 616// constant is DERIVED, not hand-tuned: 617// severity k = 1 + floor(log2(buf / bp_max)), capped at VC_BWE_MAX_STEP -- each DOUBLING of the overflow 618// drops one more rung: a multiplicative response to a multiplicative signal, with NO magic multiplier. 619// drain line = bp_max >> VC_BWE_DRAIN_SHIFT (a quarter) -- a hysteresis gap below the bp_max decrease 620// line, so a decrease and the next increase cannot oscillate around bp_max. 621// recovery window = VC_BWE_RECOVER_TICKS clean ticks -- at the caller's tick period this IS the wall-clock 622// recovery cadence (the JS version encoded the same ~1.5s as BOTH a 5-tick streak AND a 1500 ms timer: 623// one duplicate ruler, now a single tick count). 624// eff_idx = the caller's current EFFECTIVE index (raw reactive idx floored by room-size/slow-start): a 625// decrease drops from THERE, so a congested big room degrades below its floor. raw_idx = the reactive idx 626// the caller persists; recovery decrements THAT so the floors still bound the effective rate. st = the 627// caller-persisted clean-tick streak (i64[1]). A link that never congests holds at its floor (raw stays, 628// recovery only fires after an overflow lifted raw above 0) -- it never drifts up. 629const VC_BWE_MAX_STEP: i64 = 3 // per-tick decrease cap: 3 rungs ~= a 3x fps cut, enough for one tick 630const VC_BWE_DRAIN_SHIFT: i64 = 2 // recover only below bp_max>>2 (a quarter of the back-pressure line) 631const VC_BWE_RECOVER_TICKS: i64 = 5 // clean ticks before one +1 recovery (at a 300ms tick ~= 1.5s cadence) 632func vc_ilog2(v: i64) -> i64 { var n: i64 = 0; var x: i64 = v; while x > 1 { x = x >> 1; n = n + 1 } return n } 633func vc_bwe_step(eff_idx: i64, raw_idx: i64, n_rungs: i64, buf: i64, bp_max: i64, st: *i64) -> i64 { 634 if bp_max <= 0 { return raw_idx } 635 if buf > bp_max { // queue overflow -> multiplicative decrease 636 st[0] = 0 637 var k: i64 = 1 + vc_ilog2(buf / bp_max) 638 if k > VC_BWE_MAX_STEP { k = VC_BWE_MAX_STEP } 639 var ni: i64 = eff_idx + k 640 if ni > n_rungs - 1 { ni = n_rungs - 1 } 641 return ni 642 } 643 if buf < (bp_max >> VC_BWE_DRAIN_SHIFT) { // queue drained -> additive increase, slow 644 st[0] = st[0] + 1 645 if st[0] >= VC_BWE_RECOVER_TICKS { 646 st[0] = 0 647 if raw_idx > 0 { return raw_idx - 1 } 648 } 649 return raw_idx 650 } 651 st[0] = 0 // hysteresis band -> hold, reset the streak 652 return raw_idx 653} 654 655// ============================================================================================================ 656// SOVEREIGN CLIENT-CORE POLICY (821 -- doctrine: JS = LAST-MILE SHIM ONLY; operator 2026-07-12 "make sure we 657// arent building everything into the js ... run it on nishi os and nishi browser as we keep growing"). 658// These exports pull the last two POLICY surfaces out of app.v2.js: the rctx layout+seeding (previously 659// hand-poked slot offsets DUPLICATED in the main thread and the worker template -- the dual-edit hazard that 660// nearly stomped the trained-nf table in the 819 port) and the resolution tier ladder. The JS shim now only 661// bridges browser primitives; NishiOS + the Nishi browser adapters call these SAME exports natively. 662// ============================================================================================================ 663// rctx INTERNAL LAYOUT -- single source of truth; callers pass only the block base + policy inputs. 664// slots(9 i64)@+0 · est@+0x80 · probs@+0x180 · t8c slab@+0x280 (5008B + RD-skip trial scratch @+5008) · 665// rc byte-stream scratch@+0x1800 (the caller guarantees headroom from there to its next region). 666const VV_CTX_EST: i64 = 0x80 667const VV_CTX_PROBS: i64 = 0x180 668const VV_CTX_T8C: i64 = 0x280 669const VV_CTX_RCSCR: i64 = 0x1800 670// ============================================================================================================ 671// GEOMETRY CEILING + DERIVED REGION SIZES -- THE CORE OWNS THEM (862, V1 of /compare/video). The shim used 672// to carry hand-rounded slot constants (0x80000, 0x130000, 0x80000 ...) beside the ceiling they were derived 673// from; two copies of one shape drift (the 818 rule-11 lesson, and the MV-plane carve below was a third 674// copy: 0x32000 = "past the measured worst rc bytes at VGA", a number that stops being true at HD). Every 675// size below is DERIVED from VV_MAX_W x VV_MAX_H and page-tidy (64KiB) rounded; the shim reads them at mount 676// and lays its regions out by SUMMING them in declared order. The ceiling is the shipped product decision 677// (1280x720 = the majors' default HD send), not a buffer guess; raising it is one edit here and a gate run. 678// 862: the ceiling = the codec's TOP ADAPTIVE TIER (1152x640), not a round HD number. 1280x720 cannot 679// be rich-banded (45 luma-MB rows split into no even K) and its 8 decode slots would cost ~52 MB of RX 680// buffer; 1152x640 (40 rows -> K=4 x 10) is the highest tier that both bands AND fits a ~40 MB 8-slot RX, 681// so it is the real shippable HD ceiling. The 1280x704 K=2 tier is the next rung once a 2-band budget clears. 682const VV_MAX_W: i64 = 1152 683const VV_MAX_H: i64 = 640 684const VV_WASM_PAGE: i64 = 65536 685const VV_PEER_SLOTS: i64 = 8 // RX slots = NX_SIG2_MAX_PEERS (the relay's room cap) 686const VV_MB: i64 = 16 687const VV_NF_TABLE_BYTES: i64 = 4096 // trained restoration table (vc_nf_load) -- its own declared size 688const VV_DRIFT_SLOT_BYTES: i64 = 1024 // one i64[128] drift ledger per peer slot 689func vv_page_tidy(n: i64) -> i64 { return ((n + VV_WASM_PAGE - 1) / VV_WASM_PAGE) * VV_WASM_PAGE } 690func vc_max_w() -> i64 { return VV_MAX_W } 691func vc_max_h() -> i64 { return VV_MAX_H } 692func vc_peer_slots() -> i64 { return VV_PEER_SLOTS } 693// yuv420 plane set for the ceiling (1.5 B/px), page-tidy 694func vc_slot_yuv() -> i64 { return vv_page_tidy(VV_MAX_W * VV_MAX_H * 3 / 2) } 695// rgba plane for the ceiling (4 B/px), page-tidy 696func vc_slot_rgba() -> i64 { return vv_page_tidy(VV_MAX_W * VV_MAX_H * 4) } 697// wire-stream staging: a useful compressed frame never exceeds the raw yuv it codes (the encoder REFUSES at 698// out_cap, it never overruns) -- so the raw plane set is the only non-guessed bound 699func vc_wirebuf() -> i64 { return vc_slot_yuv() } 700// heat-AQ plane: one i64 per luma MB of the ceiling grid 701func vc_heat_bytes() -> i64 { return vv_page_tidy(((VV_MAX_W + VV_MB - 1) / VV_MB) * ((VV_MAX_H + VV_MB - 1) / VV_MB) * 8) } 702// nf full-plane scratch: one luma plane of the ceiling 703func vc_nfscr_bytes() -> i64 { return vv_page_tidy(VV_MAX_W * VV_MAX_H) } 704func vc_nftbl_bytes() -> i64 { return VV_NF_TABLE_BYTES } 705func vc_drift_bytes() -> i64 { return vv_page_tidy(VV_PEER_SLOTS * VV_DRIFT_SLOT_BYTES) } 706// vcv-10 MVD MV row-plane carve (F617): one 16B cell per MB COLUMN, placed past the rc byte-stream scratch. 707// 862: the carve is DERIVED -- rc scratch worst = the raw yuv plane set of the ceiling (the same bound as 708// the wire buffer), so the plane can never be overrun by the coder at any geometry up to the ceiling. The 709// shim must give the rctx block vc_ctx_bytes() of headroom (it sums that in, never guesses it). 710func vv_ctx_mvplane_off() -> i64 { return VV_CTX_RCSCR + VV_MAX_W * VV_MAX_H * 3 / 2 } 711func vc_ctx_bytes() -> i64 { return vv_page_tidy(vv_ctx_mvplane_off() + ((VV_MAX_W + VV_MB - 1) / VV_MB) * 16) } 712// one-time per-mount ctx init (DCT8 matrix + zig-zag tables inside the block's t8c slab) 713func vv_init_ctx(base: i64) -> i64 { vc_t8_init((base + VV_CTX_T8C) as *i64); return 0 } 714// per-call rctx seed. COMPOSES the emode POLICY natively: bit0 range-coder + bit2 t8 RD-auto (820 rung, 715// MEASURED -0.52% avg) + bit12 heat-AQ (819 rung, MEASURED -1.72% avg) all ride the rc capability; bit5 716// sig-map (vcv-6) / bit9 gentle deblock (vcv-8) ride their peer caps. 717// F1115 (2026-07-28, THE MEASURED TRADE — 4-seq MSU BD vs x264, evidence _offc/rung2/bd2593.txt): 718// bit11 RD-SKIP is now ON for rc rooms. The 817 fps law is RETIRED for it: that verdict predated 719// BOTH the 2026-07-14 fast-RD-skip (code-arm-is-the-stream) and F1113 (the trial no longer forces 720// partition-RD + full search on every band MB). Measured same-run: +12% encode for the +8dB curve 721// (gap vs x264 6.7x -> 2.9x). bit11 is ENCODER-ONLY: the skip BIT syntax is unchanged, any decoder 722// reads the stream — safe even in mixed rooms. 723// bit7 P_8x8 PARTITION is now OFF: measured 68% of P-encode for ~1.3% BD (the 825 motion-gating only 724// spares static content). bit7 is SYNTAX (the per-MB part bit) — but both peers derive emode from 725// THIS function in the SAME served binary, so enc+dec always agree; the part capability arg is 726// accepted and deliberately ignored so old callers need no change. 727// Returns the composed emode so callers can log/inspect it. 728func vv_seed_rctx(base: i64, rc: i64, nf: i64, sig: i64, part: i64, deblock: i64, heatp: i64, nftbl: i64, nfscr: i64) -> i64 { 729 let r: *i64 = base as *i64 730 var emode: i64 = 0 731 // 824 LIVE-INCIDENT FIX (field screenshot: 2.2fps + drop-storm corruption on v823): bit12 HEAT-AQ and 732 // bit2 t8-RD are RTC-OFF. On REAL SENSOR NOISE heat is an fps catastrophe MEASURED 2.5x (640x480 steady-P 733 // 134ms->334ms): noise-static blocks build 4-skip streaks, the halved threshold then converts the next 734 // noise wiggle into a FULL encode, streak resets, repeat -- an oscillating code-storm the clean-content 735 // benches could not see (their static blocks are EXACTLY static). The fps collapse backs up the send 736 // queue -> mid-GOP drops -> decode-vs-wrong-prev garbage -> kreq storms = the observed corruption. Both 737 // bits stay in the ARCHIVAL profile (clean stored content, no fps budget). RTC = the 817-proven stream. 738 if rc != 0 { emode = 1 } 739 if sig != 0 { emode = emode | 32 } 740 // bit7 partition: retired by F1115 (68% of encode for ~1%); `part` accepted + ignored, see header 741 if deblock != 0 { emode = emode | 512 } 742 if rc != 0 { emode = emode | VC_MAGIC_2048 } // F1115: RD-skip rides the rc capability (encoder-only bit) 743 // 2026-09-02 (nx_vcodec_stage_bench on the live h2h card): the RD-skip trial band's lower edge sat below the recon 744 // quantisation-noise floor, so 88 percent of a static frame paid the code-arm trial and re-skipped (23.7 -> 11.0 ms per 745 // CIF frame at the live point once gated). bit14 = quiet-quadrant early skip (VC_EMODE_QQSKIP): trial a lower-band MB 746 // only when an 8x8 quadrant moves. Encoder-only; BD-rate on akiyo/foreman/bus/mobile +0.1/+0.9/-0.7/0.0 percent. 747 if rc != 0 { emode = emode | VC_EMODE_QQSKIP } 748 r[0] = emode 749 r[1] = base + VV_CTX_EST 750 r[2] = base + VV_CTX_PROBS 751 r[3] = base + VV_CTX_RCSCR 752 r[4] = base + VV_CTX_T8C 753 r[5] = 0 754 r[6] = 0 755 r[7] = 0 756 if nf != 0 { r[6] = nftbl } 757 if nf != 0 { r[7] = nfscr } 758 r[8] = heatp 759 // vcv-10 (F617): rctx[9] = the MVD-median MV row-plane the rct9 frame pair REQUIRES ([6]/[7] stay 760 // the nf table+scratch, [8] the heat plane -- the collision the port caught, twice now) 761 r[9] = base + vv_ctx_mvplane_off() 762 return emode 763} 764// resolution TIER policy (Jitsi-class adaptive send). MEASURED tiers (vc_res_bench 2026-07-12: 640x480 = 765// 38.6fps / 480x384 = 58 / 416x320 = 70 single-thread on a cls-9 core; legacy tiers below unchanged). 766// >320x256 gated on all818 (pre-818 peers' RX slots are sized to the legacy ceiling -- overflow otherwise). 767// Returns (w<<16)|h; 0 = caller applies its legacy default (old-peer / unbenchmarked fallback). 768// 862 HD TIERS (V1 of /compare/video): two tiers ABOVE 640x480, reachable only through the banded-aware 769// cls uplift (the shim adds +2 cls when the rich K-band pool is armed -- the MEASURED K=4 wall is 3.3x the 770// single-thread cost). Geometries are chosen by the band CONSTRAINT, not by taste: a rich band covers an 771// EVEN number of luma MB rows (the chroma planes are coded as half-res MBs, so a band edge must sit on a 772// chroma MB row), hence H/16 must split into K even parts. 1280x720 (45 MB rows) cannot band at all and 773// 960x544 (34) cannot either; 1024x576 (36 rows -> K=3 x 12) and 1152x640 (40 rows -> K=4 x 10) can. 774// Extrapolated from the 825 bench (640x480 = 22 fps single-thread on a cls-9 core, linear in pixels): 775// 1024x576 = 1.9x px -> ~11.5 fps single / ~30 fps at K=3 (2x the 15 fps capture target); 776// 1152x640 = 2.4x px -> ~9 fps single / ~30 fps at K=4 (2x). Both tiers are ARMED BY BUDGET at run time 777// (the shim's encMsLive vs the rung budget + resPenalty walk a tier back within seconds), so a device 778// that measured capable but cannot hold it never freezes -- the 818 rule, kept. all_hd = every peer's RX 779// slots are sized to the 862 ceiling (a pre-862 peer would overflow); without it the ladder is byte- 780// identical to 861. 1280x704 (44 rows, K=2 only) is the next rung once a 2-band split clears the budget. 781func vc_res_tier(cls: i64, flex_all: i64, all818: i64) -> i64 { 782 if flex_all == 0 { return 0 } 783 if cls < 0 { return 0 } 784 // 825 SPEED-AWARE LADDER: cls now comes from an HONEST bench (vv_enc_rct8 P-frame on noise @320x256, the 785 // real live cost) and the decision-stack early-outs (825) made rct8-on-noise 3-4x faster (MEASURED on this 786 // desktop, proportional head-and-shoulders + sensor noise: 320x256=81fps · 416x320=48 · 480x384=38 · 787 // 640x480=22). Each tier maps to a cls whose extrapolated cost (linear in pixels) clears the 15fps capture 788 // with >=2x margin: cls9(bench<=8ms)->640x480, cls8(<=12)->480x384, cls7(<=18)->416x320, cls6(<=26)->320. 789 // A weaker device only reaches a tier if it MEASURED capable of it -- the overshoot class is closed. 790 if all818 != 0 { 791 if cls >= 9 { return (640 << 16) | 480 } 792 if cls >= 8 { return (480 << 16) | 384 } 793 if cls >= 7 { return (416 << 16) | 320 } 794 if cls >= 6 { return (320 << 16) | 256 } 795 if cls >= 4 { return (256 << 16) | 192 } 796 if cls >= 2 { return (160 << 16) | 128 } 797 return (128 << 16) | 96 798 } 799 // old-peer room (some peer pre-818): ceiling 320x256 (their RX slots are sized to the legacy max) 800 if cls >= 7 { return (320 << 16) | 256 } 801 if cls >= 4 { return (256 << 16) | 192 } 802 if cls >= 2 { return (160 << 16) | 128 } 803 return (128 << 16) | 96 804} 805// 862 HD LADDER (V1 of /compare/video). A SEPARATE export so the 3-arg vc_res_tier is byte-for-byte 806// unchanged -- the new wasm is a drop-in for any client that has not yet learned the HD path. Two rungs 807// ABOVE 640x480, reachable only when EVERY peer is 862-capable (all_hd: their RX slots are sized to the 808// 862 ceiling; a pre-862 peer would overflow) AND the banded-aware cls uplift put the sender at cls>=10. 809// Geometries are chosen by the BAND CONSTRAINT, not taste: a rich band spans an even number of luma-MB 810// rows (chroma is coded half-res, so a band edge sits on a chroma-MB row), so H/16 must split into K even 811// parts. 1152x640 -> 40 rows -> K=4 x 10; 1024x576 -> 36 rows -> K=3 x 12. 1280x720 (45 rows) cannot band 812// at all, which is why the ceiling stops at 1152x640. Extrapolated from the 825 bench (640x480 = 22 fps 813// single-thread, linear in pixels): 1024x576 ~11.5 fps single / ~30 at K=3; 1152x640 ~9 / ~30 at K=4 -- 814// both cleared to 2x the 15 fps capture target only WITH the band pool, which is exactly the cls>=10 gate. 815// Below the HD rungs it DELEGATES to vc_res_tier, so an all_hd room on a mid device is identical to today. 816func vc_res_tier_hd(cls: i64, flex_all: i64, all818: i64, all_hd: i64) -> i64 { 817 if all_hd != 0 { if all818 != 0 { if flex_all != 0 { 818 if cls >= 11 { return (1152 << 16) | 640 } 819 if cls >= 10 { return (1024 << 16) | 576 } 820 } } } 821 return vc_res_tier(cls, flex_all, all818) 822} 823 824// ---- RS-FEC erasure engine IN THE CORE (2026-07-02, the FEC-LIVE prerequisite) ---- 825// The measured intl h2h (nx_intl_h2h): our TCP path freezes 22-122 frames/30s on international 826// profiles; RS-FEC(k=8,m=2) freezes ZERO. The transport leg (multi-socket striping so one leg's 827// TCP stall = a recoverable erasure) needs the MATH in the core -- JS only moves bytes. This is 828// the proven nx_room_fec.nx GF(256) Cauchy-MDS codec, adapted to the core's rules: NO allocation 829// (the browser stubs syscalls) -- the caller provides every region. k=8 data + m=2 parity shards; 830// any 2 losses of 10 reconstruct byte-exact. tbl layout (i64 entries, caller region ~7KB): 831// [0..511]=exp [512..767]=log [768..847]=G (n*k rows-major). 832const VC_FEC_K: i64 = 8 833const VC_FEC_M: i64 = 2 834const VC_GF_POLY: i64 = 0x11d 835 836func vc_gf_mul(tbl: *i64, a: i64, b: i64) -> i64 { 837 if a == 0 { return 0 } 838 if b == 0 { return 0 } 839 return tbl[tbl[512 + a] + tbl[512 + b]] 840} 841func vc_gf_inv(tbl: *i64, a: i64) -> i64 { return tbl[255 - tbl[512 + a]] } 842 843// build exp/log tables + the [I_k ; Cauchy] generator into tbl. Call once. Returns n (=k+m). 844func vc_fec_init(tbl: *i64) -> i64 { 845 var x: i64 = 1 846 var i: i64 = 0 847 while i < 255 { 848 tbl[i] = x 849 tbl[512 + x] = i 850 x = x << 1 851 if (x & 256) != 0 { x = x ^ VC_GF_POLY } 852 i = i + 1 853 } 854 i = 255 855 while i < 512 { tbl[i] = tbl[i - 255]; i = i + 1 } 856 tbl[512] = 0 857 let k: i64 = VC_FEC_K 858 let m: i64 = VC_FEC_M 859 var r: i64 = 0 860 while r < k { 861 var c: i64 = 0 862 while c < k { if c == r { tbl[768 + r*k + c] = 1 } else { tbl[768 + r*k + c] = 0 } c = c + 1 } 863 r = r + 1 864 } 865 var j: i64 = 0 866 while j < m { 867 var c2: i64 = 0 868 while c2 < k { 869 tbl[768 + (k+j)*k + c2] = vc_gf_inv(tbl, (k + j) ^ c2) 870 c2 = c2 + 1 871 } 872 j = j + 1 873 } 874 return k + m 875} 876 877// encode: data = k*S bytes -> shards = (k+m)*S bytes (systematic: first k*S == data). 878func vc_fec_encode(tbl: *i64, data: *u8, shards: *u8, S: i64) -> i64 { 879 let k: i64 = VC_FEC_K 880 let n: i64 = VC_FEC_K + VC_FEC_M 881 var r: i64 = 0 882 while r < n { 883 var c: i64 = 0 884 while c < S { 885 var acc: i64 = 0 886 var i: i64 = 0 887 while i < k { acc = acc ^ vc_gf_mul(tbl, tbl[768 + r*k + i], data[i*S + c] as i64); i = i + 1 } 888 shards[r*S + c] = acc as u8 889 c = c + 1 890 } 891 r = r + 1 892 } 893 return n 894} 895 896// decode: shards (n*S bytes) + erased flags (n i64: 1=lost) -> out (k*S bytes, the original data). 897// scratchA = caller region for the k*k GF matrix (k*k i64 = 512B). 0 ok; -1 = >m losses (honest bound). 898func vc_fec_decode(tbl: *i64, shards: *u8, erased: *i64, out: *u8, S: i64, scratchA: *i64) -> i64 { 899 let k: i64 = VC_FEC_K 900 let n: i64 = VC_FEC_K + VC_FEC_M 901 var got: i64 = 0 902 var r: i64 = 0 903 while r < n { 904 if got < k { if erased[r] == 0 { 905 var i: i64 = 0 906 while i < k { scratchA[got*k + i] = tbl[768 + r*k + i]; i = i + 1 } 907 var c: i64 = 0 908 while c < S { out[got*S + c] = shards[r*S + c]; c = c + 1 } 909 got = got + 1 910 } } 911 r = r + 1 912 } 913 if got < k { return 0 - 1 } 914 // Gauss-Jordan over GF(256): scratchA (k x k) * X = out (k x S); out becomes X in place. 915 var col: i64 = 0 916 while col < k { 917 var p: i64 = 0 - 1 918 var pr: i64 = col 919 while pr < k { if p < 0 { if scratchA[pr*k + col] != 0 { p = pr } } pr = pr + 1 } 920 if p < 0 { return 0 - 1 } 921 if p != col { 922 var j: i64 = 0 923 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 } 924 j = 0 925 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 } 926 } 927 let inv: i64 = vc_gf_inv(tbl, scratchA[col*k + col]) 928 var j2: i64 = 0 929 while j2 < k { scratchA[col*k + j2] = vc_gf_mul(tbl, scratchA[col*k + j2], inv); j2 = j2 + 1 } 930 j2 = 0 931 while j2 < S { out[col*S + j2] = vc_gf_mul(tbl, out[col*S + j2] as i64, inv) as u8; j2 = j2 + 1 } 932 var rr: i64 = 0 933 while rr < k { 934 if rr != col { 935 let f: i64 = scratchA[rr*k + col] 936 if f != 0 { 937 var j3: i64 = 0 938 while j3 < k { scratchA[rr*k + j3] = scratchA[rr*k + j3] ^ vc_gf_mul(tbl, f, scratchA[col*k + j3]); j3 = j3 + 1 } 939 j3 = 0 940 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 } 941 } 942 } 943 rr = rr + 1 944 } 945 col = col + 1 946 } 947 return 0 948} 949 950// ---- FEC WIRE + REASSEMBLY (TIER-2 substrate): frame <-> shards, out-of-order collect ---- 951// Transport design (zero daemon change, relay stays content-blind): a leg = a shard-lane ROOM 952// ("family#0/1/2"); the sender stripes shard i onto leg i%legs, each shard travels ONE lane, so a 953// receiver never gets duplicates and one lane's TCP stall = a recoverable erasure. Shard payload = 954// the proven 16B fwire header [block_id u32][shard_idx u8][is_parity u8][k u8][m u8][frame_len u32] 955// [rsv u32] + S data bytes, S = ceil(frame_len/8). Collector = ONE block per sender (per-frame FEC: 956// block_id = the video seq; a newer block supersedes -- late shards of a superseded frame drop). 957// State region st (caller-provided, ~21KB): i64[0]=block_id [1]=present_mask [2]=S [3]=flen 958// [4]=count [5]=done; shard bytes at (st as *u8)+64, slot idx*S. NO allocation (core rules). 959 960func vc_fecs_shard_size(flen: i64) -> i64 { return (flen + 7) / 8 } 961 962// pack frame -> 10 wire-ready shard payloads in out (slot r = r*(16+S), each 16+S bytes). 963// scratch_data = k*S bytes, scratch_shards = 10*S bytes (caller regions). Returns 16+S (slot size). 964func vc_fecs_pack(tbl: *i64, frame: *u8, flen: i64, block_id: i64, scratch_data: *u8, scratch_shards: *u8, out: *u8) -> i64 { 965 let k: i64 = VC_FEC_K 966 let m: i64 = VC_FEC_M 967 let n: i64 = k + m 968 let S: i64 = (flen + 7) / 8 969 var i: i64 = 0 970 while i < k * S { if i < flen { scratch_data[i] = frame[i] } else { scratch_data[i] = 0 as u8 } i = i + 1 } 971 vc_fec_encode(tbl, scratch_data, scratch_shards, S) 972 var r: i64 = 0 973 while r < n { 974 let base: i64 = r * (16 + S) 975 var b: i64 = 0 976 while b < 4 { out[base + b] = ((block_id >> (b * 8)) & 0xff) as u8; b = b + 1 } 977 out[base + 4] = r as u8 978 if r < k { out[base + 5] = 0 as u8 } else { out[base + 5] = 1 as u8 } 979 out[base + 6] = k as u8 980 out[base + 7] = m as u8 981 b = 0 982 while b < 4 { out[base + 8 + b] = ((flen >> (b * 8)) & 0xff) as u8; b = b + 1 } 983 out[base + 12] = 0 as u8 984 out[base + 13] = 0 as u8 985 out[base + 14] = 0 as u8 986 out[base + 15] = 0 as u8 987 var c: i64 = 0 988 while c < S { out[base + 16 + c] = scratch_shards[r * S + c]; c = c + 1 } 989 r = r + 1 990 } 991 return 16 + S 992} 993 994func vc_fecrx_reset(st: *i64) -> i64 { 995 st[0] = 0 - 1 996 st[1] = 0 997 st[2] = 0 998 st[3] = 0 999 st[4] = 0 1000 st[5] = 0 1001 return 0 1002} 1003 1004// feed one shard payload. Returns frame_len when the block completes (frame in out, byte-exact), 1005// 0 = pending/duplicate/stale, -1 = malformed. scratchA = k*k i64; scratchSh = 10*S bytes contiguous. 1006func vc_fecrx_add(st: *i64, tbl: *i64, payload: *u8, plen: i64, scratchA: *i64, scratchSh: *u8, out: *u8, outcap: i64) -> i64 { 1007 let k: i64 = VC_FEC_K 1008 let m: i64 = VC_FEC_M 1009 let n: i64 = k + m 1010 if plen < 17 { return 0 - 1 } 1011 var block_id: i64 = 0 1012 var b: i64 = 0 1013 while b < 4 { block_id = block_id | ((payload[b] as i64) << (b * 8)); b = b + 1 } 1014 let idx: i64 = payload[4] as i64 1015 if (payload[6] as i64) != k { return 0 - 1 } 1016 if (payload[7] as i64) != m { return 0 - 1 } 1017 var flen: i64 = 0 1018 b = 0 1019 while b < 4 { flen = flen | ((payload[8 + b] as i64) << (b * 8)); b = b + 1 } 1020 let S: i64 = (flen + 7) / 8 1021 if plen != 16 + S { return 0 - 1 } 1022 if idx >= n { return 0 - 1 } 1023 // SAFE-BY-CONSTRUCTION bound: the LARGEST write is n*S (store[idx*S+c] with idx<n, and scratchSh[r*S+c] 1024 // with r<n); out needs only k*S < n*S. Bounding n*S<=outcap makes EVERY write <= outcap, so a caller that 1025 // sizes ALL of {st-store-area (slot-192), scratchSh, out} >= outcap CANNOT overflow on any hostile shard. 1026 // (Proven exhaustively by nx_vc_fecrx_fuzz_gate: 100k adversarial inputs, 0 guard breaches.) 1027 if n * S > outcap { return 0 - 1 } 1028 if flen <= 0 { return 0 - 1 } 1029 if block_id < st[0] { return 0 } // stale: an older frame's straggler -> drop 1030 if block_id == st[0] { if st[5] == 1 { return 0 } } // already delivered this block 1031 if block_id > st[0] { // a newer frame supersedes the collector 1032 st[0] = block_id 1033 st[1] = 0 1034 st[2] = S 1035 st[3] = flen 1036 st[4] = 0 1037 st[5] = 0 1038 } 1039 if st[2] != S { return 0 - 1 } 1040 let bit: i64 = 1 << idx 1041 if (st[1] & bit) != 0 { return 0 } // duplicate shard -> ignore 1042 st[1] = st[1] | bit 1043 st[4] = st[4] + 1 1044 let store: *u8 = (st as *u8) + 192 // header uses i64[0..17]; bytes from 192 1045 var c: i64 = 0 1046 while c < S { store[idx * S + c] = payload[16 + c]; c = c + 1 } 1047 if st[4] < k { return 0 } 1048 // k shards present: erased flags live in the state header (i64[8..17]), decode into out 1049 var r: i64 = 0 1050 while r < n { 1051 if (st[1] & (1 << r)) != 0 { st[8 + r] = 0 } else { st[8 + r] = 1 } 1052 var c2: i64 = 0 1053 while c2 < S { scratchSh[r * S + c2] = store[r * S + c2]; c2 = c2 + 1 } 1054 r = r + 1 1055 } 1056 let erased: *i64 = (((st as *u8) + 64) as *i64) // = &st[8] via the byte view (8*8=64) 1057 if vc_fec_decode(tbl, scratchSh, erased, out, S, scratchA) != 0 { return 0 - 1 } 1058 st[5] = 1 1059 return st[3] 1060} 1061 1062// ---- chat framing IN THE CORE (C2: off JSON, onto the Nishi wire) ---- 1063// Chat rides the same 13B envelope as media, kind 0x43 'C'. Payload = [nlen u8][name][text]. 1064// The core owns pack/parse; JS only moves bytes + DOM. Caps: name<=24, text<=500 (the UI caps). 1065// ---- REACTIONS (comms rung, wire kind 0x45): the PROTOCOL and the emoji SET live HERE so a browser tab, 1066// the Nishi browser, and a native NishiOS video app share ONE reaction capability -- the shim only floats 1067// what the core hands it. payload=[idx:1]. vc_react_emoji writes the UTF-8 bytes of reaction idx into out 1068// and returns the byte count (0 = invalid idx -> caller treats as idx 0). Set: thumbs-up, heart, laugh, 1069// party, clap, fire. 1070const VC_NREACTS: i64 = 6 1071func vc_react_pack(out: *u8, idx: i64) -> i64 { 1072 var i: i64 = idx 1073 if i < 0 { i = 0 } 1074 if i >= VC_NREACTS { i = 0 } 1075 out[0] = i as u8 1076 return 1 1077} 1078func vc_react_parse(pay: *u8, plen: i64) -> i64 { 1079 if plen < 1 { return 0 - 1 } 1080 let i: i64 = pay[0] & 15 1081 if i >= VC_NREACTS { return 0 } 1082 return i 1083} 1084func vc_react_emoji(idx: i64, out: *u8) -> i64 { 1085 var i: i64 = idx 1086 if i < 0 { i = 0 } 1087 if i >= VC_NREACTS { i = 0 } 1088 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 1089 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 1090 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 1091 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 1092 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 1093 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 1094} 1095func vc_chat_pack(out: *u8, name: *u8, nlen: i64, text: *u8, tlen: i64) -> i64 { 1096 var nl: i64 = nlen 1097 if nl > 24 { nl = 24 } 1098 var tl: i64 = tlen 1099 if tl > 500 { tl = 500 } 1100 if nl < 0 { return 0 - 1 } 1101 if tl <= 0 { return 0 - 1 } 1102 out[0] = nl as u8 1103 var i: i64 = 0 1104 while i < nl { out[1 + i] = name[i]; i = i + 1 } 1105 i = 0 1106 while i < tl { out[1 + nl + i] = text[i]; i = i + 1 } 1107 return 1 + nl + tl 1108} 1109// parse -> info[0]=name_off info[1]=name_len info[2]=text_off info[3]=text_len (offsets into pay). 0 ok. 1110func vc_chat_parse(pay: *u8, plen: i64, info: *i64) -> i64 { 1111 if plen < 2 { return 0 - 1 } 1112 let nl: i64 = pay[0] as i64 1113 if nl > 24 { return 0 - 1 } 1114 if 1 + nl >= plen { return 0 - 1 } 1115 var tl: i64 = plen - 1 - nl 1116 if tl > 500 { tl = 500 } 1117 info[0] = 1 1118 info[1] = nl 1119 info[2] = 1 + nl 1120 info[3] = tl 1121 return 0 1122} 1123 1124// No main: the WAT target exports the vc_* functions directly (the LPC 1125// wat-module convention) and the native cross-gate provides its own main.