From 32c5e438eda8abf286c2bd444225751576013e66 Mon Sep 17 00:00:00 2001 From: MiTHRAL Date: Wed, 26 Aug 2026 23:17:08 -0400 Subject: [PATCH] ghost multiplayer: fix wire-struct packing; set default relay to relay.mithraic.lol - #pragma pack(1) on all protocol structs: alignas(1) set the struct alignment but still let the compiler pad members to their natural alignment - the uint16_t payload_size landed on an even offset, so PacketHeader was 8 bytes instead of 7 and the relay's guard dropped every real 7-byte packet (n=7 < 8). The in-process tests passed only because BuildPacket/PacketSize used the same inflated size on both sides. With true packing the struct sizes now equal the wire sizes exactly (PacketHeader=7, PosePayload=3140, HelloPayload=687, RoomsPayload=1266, ...), verified. - kGhostDefaultServer -> relay.mithraic.lol (the production beta relay on the OVH VPS; IPv6 PTR set, IPv4 PTR still pending at OVH). --- src/native/skate3_native_ghost_proto.h | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/native/skate3_native_ghost_proto.h b/src/native/skate3_native_ghost_proto.h index c63bad0..b9f61b8 100644 --- a/src/native/skate3_native_ghost_proto.h +++ b/src/native/skate3_native_ghost_proto.h @@ -35,8 +35,10 @@ constexpr uint32_t kGhostMaxRoomMembers = 4; // member names shown per room // captured count are identity-padded so every mesh's referenced bones stay // in range on the receiving side. constexpr uint32_t kGhostMaxBones = 64; -// Placeholder default public relay: replace in ONE place (skate3_native_ghost.cpp). -constexpr const char* kGhostDefaultServer = "ghost.skate3.example"; +// Default public relay (the production beta relay on the OVH VPS). Clients +// connect OUTBOUND to this; no port forwarding on any client. Override at +// runtime with skate3_ghost_server. +constexpr const char* kGhostDefaultServer = "relay.mithraic.lol"; constexpr uint16_t kGhostDefaultServerPort = 40001; // A LAN host with no room advertised is implicitly in this room. constexpr const char* kGhostLobbyRoom = "LOBBY"; @@ -53,7 +55,15 @@ enum PacketType : uint8_t { kTypeRooms = 9, // relay -> client: { RoomsPayload } (the lobby browser) }; -struct alignas(1) PacketHeader { +// True byte-packing: the struct layouts below must match the wire format +// exactly (the receiver bounds-checks every packet against these sizes, and +// the relay rejects undersized datagrams with `n < sizeof(PacketHeader)`). +// alignas(1) alone is NOT enough - it sets the struct alignment but the +// compiler still pads MEMBERS to their natural alignment (a uint16_t lands +// on an even offset, inflating PacketHeader to 8 bytes and dropping every +// real 7-byte packet). +#pragma pack(push, 1) +struct PacketHeader { uint32_t magic; uint8_t type; uint16_t payload_size; // bytes following this header @@ -201,10 +211,11 @@ struct GhostPoseReassembler { // Largest payload: PoseChunkPayload (1214 bytes). constexpr uint16_t kGhostMaxPayload = 3200; -struct alignas(1) PacketBuf { +struct PacketBuf { PacketHeader h; uint8_t payload[kGhostMaxPayload]; }; +#pragma pack(pop) inline uint16_t PacketSize(const PacketBuf& p) { return uint16_t(sizeof(PacketHeader) + p.h.payload_size);