# 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 40001 # LIST probe # expect a kTypeRooms reply (starts 'S G H T' + type 9 + payload) ``` ## Logging & troubleshooting - **Game logs:** `/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