The Steam-multiplayer prerequisite from the design doc: L4NET keeps hosts, message queues, and the deterministic mesh ordering, while connect/listen/accept/close, send/receive, startup/cleanup, the local interface list, and ip[:port] parsing move behind NetTransport (L4NETTRANSPORT.h). WinsockNetTransport carries the existing TCP behavior over verbatim - including the connect retry-while-refused loop the egg-ACK ordering relies on - and is the process default; NetTransport_Set installs a replacement before the network manager comes up. L4STEAMTRANSPORT.h documents the ISteamNetworkingSockets mapping per method (FakeIP keeps [pilots] entries as IPv4 strings) behind RP412_STEAM until the Steamworks SDK lands. Also fixed in passing: the ExclusiveBroadcast path sent sizeof(network_packet) - four bytes of pointer - instead of the message size, which would have sheared the stream framing had it ever fired. Verified: single-player race cycle (menu, race, results, menu, race) green; -net 8000 boots, listens for a console through the transport, and idles stable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6.8 KiB
RP412 Front End & Steam Multiplayer — Design Notes
Findings from reading TeslaConsole's Red Planet control code
(TeslaSuite/Console/TeslaConsole.RedPlanet/) against the game side
(MUNGA_L4/L4NET.CPP, MUNGA/NETWORK.cpp), and how a Steam front end maps
onto it. Written 2026-07-12 for the implement-decision.
1. What the console actually does
TeslaConsole is four things, and RP412's front end must absorb all four:
a) Pod control channel (Munga.Net, TCP console port)
Per-pod state machine (RPGame.cs): take ownership of a pod, watch its
app state, and drive the lifecycle:
WaitingForEgg --egg chunks--> (ACK, 5s retry)
WaitingForLaunch --RunMissionMessage-->
RunningMission --telemetry--> EndMission (final scores)
--AbortMissionMessage--> reset
The egg is sent as EggFileMessage chunks of 1000 bytes with an
AcknowledgeEggFileMessage reply from the pod.
b) The mission egg (the ENTIRE mission definition)
A NotationFile-format text file (RPMission.ToEggString()), newline→NUL:
[mission] adventure=Red Planet, scenario, map, time, weather,
temperature, compression, length (seconds)
[pilots] ordered pilot=<host address> list (players, then cameras)
[<address>] per participant: hostType, dropzone, name, bitmapindex,
loadzones, vehicle, color, badge (+ team/position in football)
[largebitmap] player names PRE-RENDERED as 1bpp bitmaps for the plasma
[smallbitmap] glass (128x32 and 64x16), generated by the console
[ordinals] 1st-4th place plasma graphics (hardcoded art)
The game already loads a local egg with -egg <file> (standalone mode) —
the same text format. Building an egg locally = the whole single-player
front end problem.
c) The catalog (RedPlanet/RPConfig.xml)
Two scenarios — Martian Death Race and Martian Football (2-color teams, crusher/blocker/runner positions) — 11 maps, ~30 vehicles (sharing a handful of models), 9 colors, 11 badges, day/night, 3 weather levels, with per-scenario exclusion lists. Clean data file; reusable as-is.
d) Results & telemetry
During the mission the pods stream VTVBooster/Damaged/Killed/Scored/ ScoreUpdate and EndMission(finalScore) to the console host, which records
them (RPMissionRecorder) into RPMissionResults and prints score sheets.
The consumer version wants this as a post-race results screen (and, later,
Steam leaderboards/achievements fed from the same events).
2. How the pods network with each other (the key finding)
The console does NOT relay gameplay. Every pod receives the same egg, and
L4NetworkManager::StartConnecting builds a deterministic full TCP mesh
from the ordered [pilots] list:
- Resolve each address (
ip[:port], default = game port). - Entries before your own: open a TCP connection to them.
- Entries after your own: listen for their incoming connection.
- No entry matching a local address → single-user mode, immediate load.
- Egg-from-network (
SlaveMode) → ACK the egg, wait for mesh completion.
So the "lobby protocol" is simply: agree on an ordered participant list, then everyone meshes. This is an exceptionally good fit for Steam.
3. Steam mapping (plan)
| Arcade concept | Steam concept |
|---|---|
| TeslaConsole operator | Lobby owner (host player) |
| Pod list / Site Management | ISteamMatchmaking lobby members |
| Pilot config dialog | In-lobby cockpit UI; member data (name/vehicle/color/badge) via lobby member data |
| Egg delivery + ACK | Owner builds the canonical egg, distributes via lobby data or reliable P2P channel; same ACK semantics |
[pilots] host addresses |
Ordered SteamID list — same list, different address type |
TCP mesh (StartConnecting) |
ISteamNetworkingSockets P2P mesh over SDR: ConnectP2P to earlier entries, accept later ones — the connect/listen ordering ports 1:1 |
| Console telemetry sink | Lobby owner doubles as the console host (results collection); results screen replaces the printout |
| Site network / fixed IPs | NAT traversal + IP privacy for free via Steam Datagram Relay |
Transport seam: mirror the RIOBase pattern at the L4NET layer — a
NetTransport interface with the existing WinSock TCP implementation
(LAN/dev, keeps working today) and a Steam-sockets implementation (retail).
L4Host keeps its identity/queue role; only connect/listen/send/recv move
behind the interface.
Status (2026-07-12): the seam is IN. MUNGA_L4/L4NETTRANSPORT.h/.cpp
defines NetTransport (startup/cleanup, local-address list, ip[:port]
resolve, connect/listen/accept/close, send/receive, remote-address) with
WinsockNetTransport as the process default; L4NET.CPP contains no raw
Winsock calls anymore. MUNGA_L4/L4STEAMTRANSPORT.h documents the
per-method ISteamNetworkingSockets mapping (FakeIP keeps the [pilots]
list as IPv4 strings) and stays behind RP412_STEAM until the Steamworks
SDK is dropped at extern\steamworks (partner-login download). Remaining
Steam work: SDK drop → implement SteamNetTransport → lobby UI in the
front end (owner collects FakeIPs/loadouts via lobby data, builds and
distributes the canonical egg, runs the RPL4CONSOLE marshal) → install
with NetTransport_Set at WinMain when launched under Steam.
4. Implementation options (decide here)
A. In-engine front end (recommended). Port the egg builder (~300 lines:
RPMission/RPPlayer.ToEggString + the plasma name-bitmap generator) to C++
inside RP_L4. New application state before WaitingForEgg: a menu that
renders on the cockpit displays we just built — scenario/map/weather/length
on the viewscreen, selections driven by the MFD buttons and map presets
(the cockpit IS the menu — maximally pod-authentic). Single player works
immediately: build egg → hand to the existing egg-load path. Multiplayer
then layers the Steam lobby under the same UI (owner builds the egg,
distributes, mesh over Steam sockets).
B. Standalone launcher app (C#, reuse TeslaConsole code nearly verbatim,
drive the game via -net + console port like the arcade). Fastest to stand
up, but a second process, not cockpit-feel, and awkward under Steam (overlay,
launch flow, no in-game rejoin). Useful as a dev tool, not the product.
C. Hybrid: in-engine UI, but mission/egg logic in a small shared C++ library so a dev console tool and the game share one egg builder.
Open decisions
- Front end location — Option A (in-engine, cockpit-rendered) confirmed?
- v1 scenario scope — Death Race only, or Football (needs teams UI) too?
- Results screen — post-race on the cockpit displays (replaces printout)?
- Steam model — lobby-owner-as-console confirmed? (Implies owner migration handling later; the egg re-issue path makes host migration plausible.)
- Pilot identity — Steam persona as pilot name; vehicle/color/badge persisted per player (Steam Cloud later)?