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.
third_party/rexglue-sdk now fetches from the migrated copy on
git.mithraic.cloud (ad3laid3/rexglue-skate3, skate3-sdk-clean) instead of
the upstream GitHub fork, so the project no longer depends on the original
author's repo for its rexglue SDK.
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).