nx_video_ui_core.nx source
↩ module page · 77 lines · 4465 B
1// nx_video_ui_core.nx -- SOVEREIGN call-UI state machine. NO browser, NO WebSocket, NO getUserMedia, NO
2// WebAssembly-host, NO navigator, NO addEventListener anywhere. Owns the call's interaction LOGIC as pure
3// INTENTS over a flat state region (operator 2026-07-04: "we need this to work on NishiOS + the Nishi
4// browser without third parties -- build with that in mind"). The browser is ONE throwaway adapter (click
5// -> intent, WebSocket frame -> transport call, camera frame -> media call); NishiOS is ANOTHER adapter
6// over the SAME core. State is a FLAT i64[6] region (not a struct) so it compiles to WASM (browser last
7// mile) AND native (NishiOS) from this one source -- the WAT backend is flat memory + i64. The browser
8// passes a linear-memory offset; the native gate passes an mmap'd buffer. Because the logic is
9// intents-over-state (no host API), it is verified SOVEREIGNLY -- drive intents + assert state/layout
10// (nx_video_ui_core_gate), not by simulating DOM events. That survives the NishiOS transition.
11// license_tier: ORIGINAL
12
13// state slots (i64 each) -- the flat UiState region
14const UST_PEERS: i64 = 0 // remote peer count (self implicit)
15const UST_MIC: i64 = 1
16const UST_CAM: i64 = 2
17const UST_SWAP: i64 = 3 // DUO: is the self tile the full-screen one (tap-to-swap)
18const UST_SHARE: i64 = 4 // screen-share active
19const UST_CHAT: i64 = 5 // chat panel open
20const UST_LEN: i64 = 6
21
22// INTENTS -- abstract user actions. The ADAPTER maps its native input (browser click / NishiOS touch /
23// hardware button) to one of these. The core never knows which adapter fired it.
24const UI_TOGGLE_MIC: i64 = 1
25const UI_TOGGLE_CAM: i64 = 2
26const UI_PEER_JOIN: i64 = 3
27const UI_PEER_LEAVE: i64 = 4
28const UI_SWAP_VIEW: i64 = 5
29const UI_TOGGLE_SHARE: i64 = 6
30const UI_TOGGLE_CHAT: i64 = 7
31
32// LAYOUT modes -- the DERIVED presentation the adapter must render. The "2-person = remote FULL + self in
33// the corner" law the operator asked for lives HERE (sovereign SSOT), not in browser JS.
34const LAYOUT_SOLO: i64 = 1 // just me
35const LAYOUT_DUO: i64 = 2 // exactly 2 -> remote full-screen + self PiP corner (+ tap-to-swap)
36const LAYOUT_GRID: i64 = 3 // 3+ -> equal tiles
37
38func uic_init(s: *i64) -> i64 {
39 s[UST_PEERS]=0; s[UST_MIC]=1; s[UST_CAM]=1; s[UST_SWAP]=0; s[UST_SHARE]=0; s[UST_CHAT]=0
40 return 0
41}
42// the adapter reports the current remote peer count (it owns the roster/transport); the core DERIVES the
43// layout. Clears swap when we drop out of DUO so the UI can't wedge. Returns the derived layout.
44func uic_set_peers(s: *i64, n: i64) -> i64 {
45 var m: i64 = n; if m < 0 { m = 0 }
46 s[UST_PEERS] = m
47 if s[UST_PEERS] != 1 { s[UST_SWAP] = 0 }
48 return uic_layout(s)
49}
50
51// DERIVED layout from peer count -- the sovereign rule any adapter renders.
52func uic_layout(s: *i64) -> i64 {
53 let total: i64 = s[UST_PEERS] + 1
54 if total <= 1 { return LAYOUT_SOLO }
55 if total == 2 { return LAYOUT_DUO }
56 return LAYOUT_GRID
57}
58
59// apply ONE intent. returns 0 ok, 1 = unknown intent (HONEST error, never a silent no-op).
60func uic_intent(s: *i64, intent: i64) -> i64 {
61 if intent == UI_TOGGLE_MIC { if s[UST_MIC]==1 { s[UST_MIC]=0 } else { s[UST_MIC]=1 } return 0 }
62 if intent == UI_TOGGLE_CAM { if s[UST_CAM]==1 { s[UST_CAM]=0 } else { s[UST_CAM]=1 } return 0 }
63 if intent == UI_PEER_JOIN { s[UST_PEERS]=s[UST_PEERS]+1; return 0 }
64 if intent == UI_PEER_LEAVE { if s[UST_PEERS]>0 { s[UST_PEERS]=s[UST_PEERS]-1 } if s[UST_PEERS]!=1 { s[UST_SWAP]=0 } return 0 }
65 if intent == UI_SWAP_VIEW { if uic_layout(s)==LAYOUT_DUO { if s[UST_SWAP]==1 { s[UST_SWAP]=0 } else { s[UST_SWAP]=1 } } return 0 } // swap ONLY applies in DUO -- the core enforces it so no adapter can wedge the UI
66 if intent == UI_TOGGLE_SHARE { if s[UST_SHARE]==1 { s[UST_SHARE]=0 } else { s[UST_SHARE]=1 } return 0 }
67 if intent == UI_TOGGLE_CHAT { if s[UST_CHAT]==1 { s[UST_CHAT]=0 } else { s[UST_CHAT]=1 } return 0 }
68 return 1
69}
70
71// state getters (the adapter reads these to render -- never touches the slots directly)
72func uic_mic(s: *i64) -> i64 { return s[UST_MIC] }
73func uic_cam(s: *i64) -> i64 { return s[UST_CAM] }
74func uic_peers(s: *i64) -> i64 { return s[UST_PEERS] }
75func uic_swapped(s: *i64) -> i64 { if uic_layout(s) == LAYOUT_DUO { return s[UST_SWAP] } return 0 }
76func uic_sharing(s: *i64) -> i64 { return s[UST_SHARE] }
77func uic_chat_open(s: *i64) -> i64 { return s[UST_CHAT] }