skate3recomp-multiplayer/docs/netcode-revival.md

82 lines
No EOL
4.5 KiB
Markdown

# Netcode revival (branch `netcode-revival`)
Goal: revive Skate 3's **original** Xbox 360 multiplayer — the game's own
RakNet-based peer-to-peer netcode — instead of only the ghost co-presence
layer. `main` keeps the working ghost multiplayer + releases; this branch
carries the netcode work and stays clearly separate.
The transport is **RakNet over XNet/XSocket** (confirmed: rexglue's XNet
comments reference RakNet, and the game's `socket → setsockopt → bind →
sendto` sequence matches `RakPeer::Startup`). The game speaks its own
protocol; we only need the **platform netcode layer to work** (implement
rexglue's stubbed XNet session APIs + route the game's UDP through the
relay). We do not decode the game protocol.
## Architecture
- **Virtual addressing**: each machine gets a stable `XNADDR` (virtual IP
`10.80.0.x` + unique 6-byte `abEnet` → RakNet GUID). `XNetXnAddrToInAddr` /
`XNetServerToInAddr` resolve those into the socket layer's address space.
- **Relay = P2P stabilizer**: session UDP is forwarded outbound through the
relay (tagged `game` vs `ghost`) to the destination member's real socket;
on a LAN the transport can go direct. The server stabilizes NAT/remote
play but is not required on a LAN.
- **Session registry in the relay**: `XNetQosListen` advertises a session;
`XNetQosLookup` returns discovered sessions; the lobby/room-code UI stays
as the manual join path.
- **Ghost layer is kept** as the guaranteed fallback (co-presence that works
for everyone even if the netcode path fails).
## Phase 0 — XNet session/address layer (done, rexglue `767bd6e`)
Implemented in `rexglue-sdk/src/kernel/xam/xam_net.cpp`:
- `XNetGetTitleXnAddr`: stable virtual identity (was loopback + `0xCC` MAC).
- `XNetXnAddrToInAddr` / `XNetInAddrToXnAddr` / `XNetServerToInAddr` /
`XNetXnAddrToMachineId` / `XNetInAddrToString`: resolve virtual addresses
(were failure / `666.666.666.666`).
- `XNetQosListen`: accept advertisement (was `X_ERROR_FUNCTION_FAILED`).
- `XNetQosLookup`: success + empty `XNQOS` + completion signal (Phase 1
fills it from the relay registry).
- `XNetCreateKey` / `XNetRegisterKey` / `XNetUnregisterKey`: session keys.
- `XNetConnect` / `XNetGetConnectStatus`: report established.
- `getsockopt`: real basic reads off the native socket.
- `XNetSetSystemLinkPort`: accept.
## Phase 1 — relay game-transport + session registry (done, built)
- v5 relay: `kTypeGame` (opaque game UDP, unicast or room-broadcast) +
`kTypeSessionQuery`/`Reply` (discovery). JoinPayload carries the virtual IP.
- rexglue `XNetTransport`: joins the same relay + room as the ghost client,
routes `sendto` to `10.80.0.x` peers through the relay, injects inbound
game datagrams into the game's sockets, and serves session discovery to
`XNetQosLookup`.
- Wire structs byte-verified against the game's proto (sizes 7/31/1410/25/801)
with `static_assert`s on both sides.
## Test recipe (two instances on one machine, local relay)
1. Build: `cmake --build out/build/linux-relwithdebinfo --target ghost_relay skate3 -j$(nproc)`
2. Start the local relay: `./out/build/linux-relwithdebinfo/ghost_relay --port 40001`
3. Launch instance A: `./out/build/linux-relwithdebinfo/skate3`
Launch instance B (same machine; the relay loops them via 127.0.0.1).
4. In each instance's M menu: set `skate3_ghost_server = 127.0.0.1`, enable
multiplayer, join the SAME room code. The relay then routes both the ghost
poses and the game's own netcode traffic.
5. Verify in the log (stdout): `xnet transport: connecting to relay ...`,
`xnet transport: relay joined`, and on session search
`xnet transport: session reply with N session(s)`. Enable the game's
online flow (invite/join) and check whether RakNet establishes a session.
6. Two instances bind the same UDP data port → the second may fail to bind.
If so, the game's socket bind must become relay-aware (bind to an
ephemeral port in relay mode) — a known Phase 3 item.
## Phase 2 — invite UI
- `XamShowGameInviteUI` / `XamShowFriendsUI` as ImGui `XamDialog`s backed by
the relay lobby (real player list). Receive side (`XamNotifyCreateListener`
/ `XamSessionRefObjByHandle`) already works.
## Phase 3 — iterate to playable
Whatever the game needs past discovery (RakNet handshake, ports, privilege
fakes). Ghost stays as fallback throughout.
## Notes
- Testing is done with **two instances on one machine** (no second device);
the relay loops them via `127.0.0.1`. A headless harness may be added.
- Build: `clang-22` override, preset `linux-relwithdebinfo`, per
`docs/build.md` — same as `main`.