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