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.