← Blog
0.7.0 · OS · SRDX · 4-PLAYER

4-Player SRDX — Broadcast Relay + Remote Input + Process Supervisor (sigil-os 585a58e)

srdx multiplayer browser os relay 0.7.0

sigil-os 585a58e ships three cc0 modules that close the Mode 1 SRDX loop for 4-player arcade: proc_super.sg (browser process supervisor with crash detection and auto-restart), srdx_input.sg (remote controller input routing — viewer Pi sends button state to host), and srdx_relay_bcast.sg (N-viewer broadcast relay, up to 15 viewers, encode-once/send-all). Together they enable four players on separate Pis to play TMNT, Simpsons, X-Men arcade — each viewer's frame is broadcast from one encode, and each viewer's controller input flows back to the host.


Section 1: proc_super.sg — browser process supervisor

cap_spawn + 16-slot process table

proc_super_init()
Zeroes the 16-slot process table at 0x800000. Each slot: pid:i32 + tab_id:i32 + alive:i32 (12B/slot).
proc_super_spawn(tab_id, cap)
Calls cap_spawn(cap, 0, 0) (syscall 54) to spawn a renderer process; records returned PID in the slot for tab_id.
proc_super_kill(tab_id)
Finds the PID for tab_id, calls cap_kill(pid), zeroes the slot.

proc_alive polling + auto-restart

proc_alive(pid)
Calls cap_proc_status(pid) (syscall 55); returns 1 if alive, 0 if dead.
proc_super_tick()
Walks all 16 slots. For each slot with alive=1: calls proc_alive(pid). If dead: logs the crash, increments restart counter, calls proc_super_spawn(tab_id, tab_cap[tab_id]) to restart. If the restart counter for a tab exceeds 3, the tab is marked as failed (no further restart attempts). T1-T6 QEMU PASS: init, spawn, kill, crash-detect, restart, no-restart-after-3.

Section 2: srdx_input.sg — remote controller input routing

Wire format: 4B LE u32 button bitmask

srdx_input_send(conn, buttons)
Called on the viewer Pi. Writes a 4-byte LE u32 buttons bitmask to the SRDX connection conn via srdx_wire_raw_send. The bitmask is masked to 15 valid bits (matches the sigilOS normalized gamepad ABI — same as local sys3(44, port, 0, 0) output).
srdx_input_recv(conn)
Called on the host Pi. Reads 4 bytes from conn; returns the masked u32.

srdx_input_recv_all + P2/P3/P4 routing

srdx_input_recv_all(conns, n, inputs)
Polls all n viewer connections per frame. For each active connection i (0-indexed), reads the 4-byte input bitmask and stores in inputs[i]. The first viewer (i=0) maps to P2; second (i=1) to P3; third (i=2) to P4. The host P1 remains the local controller. Up to 15 viewers → up to 15 remote players (the broadcast relay supports up to 15 simultaneous viewers).

Section 3: srdx_relay_bcast.sg — N-viewer broadcast relay

BCAST_BASE=0xA60000: 15-viewer table

srdx_relay_add_viewer(conn)
Finds a free slot in the 15-viewer table at BCAST_BASE (each slot: conn:i32 + active:i32 = 8B); stores conn, marks active.
srdx_relay_remove_viewer(conn)
Clears the slot for conn.
srdx_relay_viewer_count()
Counts active viewer slots.

srdx_relay_broadcast — encode-once, send-all

srdx_relay_broadcast(fb, pitch, fw, fh)
Calls srdx_encode_frame(fb, pitch, fw, fh) once to produce the encoded frame + header. Then walks the 15-viewer table: for each active viewer slot, calls srdx_wire_raw_send(conn, hdr, enc_len). Encode overhead is fixed regardless of viewer count — 1 encode per frame, not N. This is the efficiency property: 15 viewers = same CPU cost as 1 viewer for the encode step.

srdx_relay_recv_inputs + srdx_bcast_tick

srdx_relay_recv_inputs(inputs)
Reads srdx_input_recv(conn) for all active viewer connections; fills inputs[0..n-1] (P2=viewer 0, P3=viewer 1, P4=viewer 2).
srdx_bcast_tick(fb, pitch, fw, fh)
Full per-frame cycle — srdx_relay_broadcast (encode+send) then srdx_relay_recv_inputs (collect P2/P3/P4 inputs). One call per frame from the arcade emulator main loop.

Section 4: 4-player arcade — TMNT / Simpsons / X-Men

What this closes: CPS-1/2 and MAME arcade cores that support 4-player co-op (TMNT, Simpsons, X-Men, Captain Commando, The Punisher) can now run across a LAN with one host Pi and up to 3 viewer Pis. The host runs the emulator core + SRDX relay broadcaster. Each viewer Pi runs the SRDX viewer (viewer.sg IS_SCENE), renders the broadcast frame, and sends its controller input back via srdx_input_send. srdx_bcast_tick gives the host one call per frame that handles everything: encode, broadcast to all viewers, collect P2/P3/P4 inputs.

Frame flow:

  • Host: emulator core renders frame → srdx_bcast_tick(fb,...) → SRDX encode (once) → broadcast to all active viewers → P2/P3/P4 inputs collected
  • Each viewer Pi: receives SRDX frame → srdx_decode_inline → Lumen chrome composite → display; sends button state back via srdx_input_send