Phase 4:
- Cloth: remote ghosts now wear this player's cloth garments (tee/pants).
The local player's rigid character.cloth_ropa items (CPU-sim deformed
root-local VB) are cloned per ghost and drawn with the REMOTE entity world
from the pose, so each ghost is draped in its own root frame. No mesh or
cloth data is transmitted; the shader already applies item.world to the
rigid cloth path (zero blend weights). Sim-inactive (skinned) garments and
garments not near the local body are skipped.
- Nameplates: display names ride the wire. JOIN carries a display name
(skate3_ghost_name cvar, default the OS user name); the relay stores it
per client and echoes {session_id, name} pairs in the HELLO roster
(RosterEntry). Clients re-JOIN every 5 s so the roster/names stay current
as members join and leave. A GhostNameplateOverlay (always registered,
draws only while enabled) projects each ghost's entity origin +1.75 m
through the published camera and draws a colored name label above it.
Uses skate3::native_scene::GetPublishedCamera (new thread-safe snapshot of
the published view-proj/cam) so the UI thread can project world points.
- GhostDialog gains a display-name field. Protocol version bumped to 3.
Phase 3: two robustness fixes for full-model ghosts.
- Wire: a full skeleton pose is ~3.1 KB, above the 1500-byte path MTU, so it
now travels as up to 3 MTU-safe kTypePoseChunk datagrams (<=1.2 KB each,
~1.2 KB total with headers). The relay forwards each chunk verbatim
(re-stamping only the chunk header session id), so its code path is
shared with the legacy single-datagram POSE and needs no pose-body
parsing. GhostPoseBuildChunks / GhostPoseReassembler (inline in the proto
header, standalone-testable) tolerate out-of-order and duplicate chunks,
drop incomplete sequences (the next frame resends), discard a partial on
a newer seq, and special-case a header-only pose as a single chunk.
Receive identity comes from the chunk header (the relay re-stamps it),
not the blob, which the relay never touches.
- Smoothing: each remote ghost now holds a target skeleton (latest received
pose) and a display skeleton chased per render query with exponential
catch-up (time constant ~33 ms), so the 15 Hz bone stream renders as
smooth motion instead of stepping. New/reshaped skeletons snap instead of
blending between layouts.
Reassembly state is per-session on the network thread, bounded (64) and
cleared on prune/disable. Protocol version bumped to 2.
Phase 2: replace the placeholder-cube ghost rendering with real character
models driven by the remote player's skeleton.
- Wire: POSE now carries the sender's skater skeleton (up to 64 bones, 3
float4 rows each, index-ordered exactly like DrawItem::bones), padded with
identity rows so every mesh's referenced bones stay in range. The relay
forwards POSE payloads verbatim (re-stamping only the sender session id),
so the protocol can grow without a relay update.
- Capture: OnFrameSceneBuilt (called from BuildFrameScene pre-publish)
snapshots the local player's body item bone palette (highest-bone-count
skinned non-ropa non-LivingWorld char item near the camera) as the
broadcast pose; defaults the broadcast rate to 15 Hz.
- Render: in RenderScene, each remote ghost is drawn by cloning this
player's own character pieces (body + nearby skinned char items) and
substituting the remote skeleton into the bone ring - same meshes,
textures and cached GPU decodes, no mesh transfer. Ghosts without a pose
(older peers / capture not ready) fall back to the solid-color cube, which
skate3_ghost_draw_local still uses for the self-check.
Known Phase-2 limitations (documented in code): ghosts render with this
player's own character model/outfit, cloth (ropa) garments are skipped (no
remote cloth sim), and hair/alpha pieces render opaque. All confined to
src/native and the hand-written scene hooks.
Bolt-on multiplayer layer confined to src/native and hand-written code (no
codegen output touched):
- GhostRelay core (src/native/skate3_native_ghost_relay.*): small UDP relay
matching clients into rooms by a shareable code; forwards each client's
pose to the other room members. Used by both the standalone binary and
the in-game LAN host fallback.
- Standalone server (tools/ghost_relay_main.cpp, ghost_relay target): runs
on a public VPS or behind a free tunnel; clients connect OUTBOUND, so no
port forwarding is needed on any client.
- In-game module (skate3_native_ghost.*): captures the local player's world
transform at Sk8::UpdateBoneTransforms exit (pack-owner entity +416,
verified layout) and broadcasts it at a fixed rate; auto-connects to the
default public relay, falls back to LAN auto-host + UDP broadcast
discovery when unreachable.
- Renderer: draws a solid-color cube per remote player in RenderScene via
the existing scene PSO tint.a>0 branch (no new shader/SPIR-V).
- UI: GhostDialog (M key) with enable toggle, room code, relay override,
status readout. Off by default.
Wire protocol in skate3_native_ghost_proto.h (JOIN/HELLO/POSE/PING/LEAVE).