skate3recomp-multiplayer/docs/ghost-multiplayer.md
MiTHRAL 1763b39eca ghost multiplayer: deployment docs + systemd unit + install script
- docs/ghost-multiplayer.md: the full beta guide - player usage, cvars,
  relay deployment (VPS build/install, free-tunnel option, verification),
  logging/troubleshooting and security notes.
- tools/ghost_relay.service: hardened systemd unit for the public relay
  (Restart=on-failure, NoNewPrivileges, PrivateTmp, ProtectSystem).
- tools/ghost_relay_deploy.sh: one-shot build + install + enable script for
  the VPS (GHOST_RELAY_PORT / GHOST_RELAY_VERBOSE overrides; opens the UDP
  port via ufw). Verified: the exact build line compiles and runs.
- README: multiplayer pointer under 'How Do I Play?'.
2026-08-26 22:21:08 -04:00

178 lines
No EOL
6.7 KiB
Markdown

# Ghost multiplayer (beta)
A from-scratch "ghost player" layer for the Skate 3 recomp native renderer:
every player's skater skeleton is relayed through a small UDP server so other
players see a real character model (driven by the remote skeleton) plus a
nameplate. Nothing touches the game's own netcode, animation or physics.
## How it works
- **Topology:** every client connects **outbound** (UDP) to a relay. No port
forwarding on any client. The relay forwards each player's pose stream to
the other members of the same **room** (a 4-8 character code).
- **Relay:** either the public `ghost_relay` server (below) or a player's
game in LAN host mode. When the configured relay is unreachable, the game
automatically starts an in-process relay on the LAN (UDP broadcast
discovery) so same-network play still works.
- **Protocol:** versioned (`kGhostVersion`). A mismatched client/server is
refused with a clear message rather than silently misbehaving.
- **Rendering:** ghosts are drawn with **this player's own character model**
driven by the remote skeleton (bone palettes, no mesh transfer). Cloth
garments drape in the remote root frame; nameplates label each ghost with
the owner's display name.
- **Logs:** everything important is logged to the game's log file (next to
the executable in `logs/`). The relay logs refusals/caps to its own output.
## Player usage
1. Launch the game, press **M** to open the multiplayer menu.
2. Tick **Enable ghost multiplayer**.
3. Enter a **room code** (or leave empty to get an auto-assigned one, shown
in the menu — share it with friends). Players sharing a code see each
other.
4. Use **Browse rooms (lobby)** to see who is on the configured relay and
click a room to join it.
5. Set a **display name** for your nameplate (default: your OS user name).
Settings (server, room, name, port, rate) persist across restarts.
### Configuration cvars
| cvar | default | meaning |
|---|---|---|
| `skate3_ghost_enable` | false | master switch (also the menu toggle) |
| `skate3_ghost_server` | `""` | relay `host[:port]`; empty = the compiled-in default |
| `skate3_ghost_port` | `40001` | UDP port (outbound + LAN discovery/host) |
| `skate3_ghost_room` | `""` | room code; empty = server assigns one |
| `skate3_ghost_name` | `""` | nameplate name; empty = OS user name |
| `skate3_ghost_rate` | `15` | pose broadcast rate (Hz) |
| `skate3_ghost_lan` | `true` | LAN auto-discovery + auto-host fallback |
| `skate3_ghost_draw_local` | `false` | debug: show your own capture cube |
| `skate3_ghost_debug` | `false` | verbose ghost logging |
The compiled-in default relay address is `kGhostDefaultServer` in
`src/native/skate3_native_ghost_proto.h` — set it to your relay before
shipping a build.
## Deploying the relay (public server)
The relay is a single dependency-free binary (plain sockets, no rexglue).
Run it on a public VPS (any size; the workload is a few UDP forwards, a
small VPS is plenty) or on your own machine behind a free tunnel
(playit.gg / ngrok / Tailscale Funnel).
### Build
```sh
# from the repo root
cmake --preset linux-relwithdebinfo -DSKATE3_GAME_DATA_ROOT="$PWD/game"
cmake --build --preset linux-relwithdebinfo --target ghost_relay --parallel
# -> out/build/linux-relwithdebinfo/ghost_relay
```
Or compile in place (no CMake/rexglue needed, just a C++23 compiler):
```sh
g++ -std=c++23 -O2 -DSKATE3_RELAY_STANDALONE \
-Isrc/native \
tools/ghost_relay_main.cpp src/native/skate3_native_ghost_relay.cpp \
-o ghost_relay
```
### Install on the VPS
`tools/ghost_relay_deploy.sh` builds, installs and registers the service:
```sh
# copy the repo (or just tools/ + the relay sources) to the VPS, then:
./tools/ghost_relay_deploy.sh # uses UDP 40001 by default
GHOST_RELAY_PORT=40002 ./tools/ghost_relay_deploy.sh
```
Manual:
```sh
install -m 0755 ghost_relay /usr/local/bin/ghost_relay
install -m 0644 tools/ghost_relay.service /etc/systemd/system/ghost-relay.service
systemctl daemon-reload && systemctl enable --now ghost-relay
ufw allow 40001/udp # open the port; nothing else is needed
```
The `ghost_relay.service` unit is:
```ini
[Unit]
Description=Skate 3 recomp ghost multiplayer relay
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/ghost_relay --port 40001 --max-clients 64
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
[Install]
WantedBy=multi-user.target
```
### Free-tunnel option (no VPS)
The relay only needs a public UDP endpoint. Run it on any machine and expose
it with a free tunnel that supports UDP (playit.gg is the most
straightforward for games): it gives you a public `host:port` that forwards
to your local relay. Point clients at it with `skate3_ghost_server` (or set
it as the compiled-in default). No client-side port forwarding either way.
### Verify
```sh
systemctl status ghost-relay
journalctl -u ghost-relay -f # join/leave lines (--verbose)
ss -ulnp | grep 40001 # listening on UDP
```
From a second machine:
```sh
printf '\x53\x47\x48\x54\x08\x00\x00' | nc -u <relay-ip> 40001 # LIST probe
# expect a kTypeRooms reply (starts 'S G H T' + type 9 + payload)
```
## Logging & troubleshooting
- **Game logs:** `<exe dir>/logs/` (SDK file logging; also where crash
dumps land). Set `skate3_ghost_debug 1` (F12 menu) for verbose ghost
lines.
- **Relay logs:** `journalctl -u ghost-relay` (add `--verbose` to
`ExecStart` for per-join/leave lines).
- **"Refused by relay (protocol version mismatch)":** the game build and the
relay must agree on `kGhostVersion` (both from the same build). Rebuild
both.
- **"Cannot resolve the relay"**: the default address is a placeholder until
you set `skate3_ghost_server`; the game then falls back to LAN auto-host.
- **Ghosts show as cubes**: the sender had no skeleton capture ready (menus,
loading) — the cube is the graceful fallback.
## Security notes
- The relay is an anonymous UDP relay: any client that knows the address can
join a room. It is intended for friends / a community, not untrusted
internet strangers. Keep `--max-clients` sane and do not expose it beyond
your intended audience.
- Room codes are not passwords. Do not put personal information in the room
code or display name.
## Repository layout
- `src/native/skate3_native_ghost_proto.h` — wire protocol
- `src/native/skate3_native_ghost_net.h` — POSIX/Winsock UDP shim
- `src/native/skate3_native_ghost_relay.{h,cpp}` — relay core (in-game host
mode + standalone binary)
- `src/native/skate3_native_ghost.{h,cpp}` — in-game client/capture
- `src/skate3_native_ghost_dialog.{h,cpp}` — multiplayer menu + nameplates
- `tools/ghost_relay_main.cpp`, `tools/ghost_relay.service`,
`tools/ghost_relay_deploy.sh` — the standalone server