package methods // // GameGround zone payloads — the party/minigame framework (darts / bowling / BBQ / ChineseChess / // Racing / …) that vehicles racing is a *zone* of, not a standalone feature (§59). // Only the Racing branch is modelled here, because that is the only zone the server currently // needs to PUSH (SyncGameGroundZoneInfo 68004609) for the Route-A race-start experiment. // ─────────────────────────────── WIRE FACTS (verified, do not "tidy" the order) ─────────────── // Source of truth = the client's own serializer, RPCSerializeAuto.lua // (function Auto.WriteRacingZoneInfo, line 7474). Field order is copied from it verbatim. // ⇒ The payload is a COMPLEX (non-Inline) contract, so [UxContract(TypeMark = 15)] makes // UxSerializer emit a leading **0x0F (15)** discriminator byte in front of the fields. // The client's polymorphic dispatch table for the zone method (68004609) then routes 0x0F to // its RacingZoneInfo reader. Getting this byte wrong does not throw — the client silently reads // the zone as a plain base zone and nothing happens. // ⇒ Field ORDER is Racing-declared-first, then base — the OPPOSITE of C# inheritance layout // (dump.cs shows CfgId at 0x50, i.e. after the base fields, but the wire is generated from the // lua writer, which emits CfgId first). Therefore this contract MUST stay FLAT: do not refactor // it into a base class, because UxSerializer orders fields by metadata token, which // would put the base fields first and shift every byte. // ⛔ CORRECTION (R203, 2026-09-30): earlier revisions of this header cited // Auto.Dispatch[449] / Concrete[449][15] = Auto.Reader[1403] and // Auto.Dispatch[476] for ParticipantInfos. Those indices are WRONG — Auto.Reader[476] // is the mahjong-hand reader and Auto.Reader[1403] is an unrelated vehicle-target struct. // Do not trust a reader/dispatch index unless the client Lua function it names actually reads the // fields you expect. ParticipantInfos element shape is now settled by byte-level proof against // the client's own writer — see _gomoku_uxcheck R1–R6. // Order reference: Drmk.Proxy/proxy/lua/RPCSerializeAuto.lua Auto.WriteRacingZoneInfo (line 7474). // // // GameGroundZoneCountDownInfo — Auto.Reader[482]. // Framed as a STRUCT (Base.WriteStruct): **no 0xFF marker**, just 4 × uint32. // // // RacingResultData — Auto.Reader[450], one entry per finished participant. // Complex framing (the client reads it through Base.ReadComplex), so each item carries the // default 0xFF marker. Field order copied from Auto.WriteRacingResultData. // // // GameGroundParticipantInfo — the element type of RacingZoneInfo.ParticipantInfos. The client's // own Auto.WriteRacingZoneInfo writes each element via // Base.WriteComplexWrap(Auto.WriteGameGroundParticipantInfo, "GameGroundParticipantInfo", true), // which puts a 0xFF present marker in front of the fields (this type is flat, so 0xFF is what // UxContract emits by default — no TypeMark needed). // Wire order (verified against Auto.WriteGameGroundParticipantInfo, RPCSerializeAuto.lua:3984): // Pid → NpcCultivationId → AIAgentInfo (nullable complex) → AgentInstanceId → // VehicleInstanceId → SeatIndex → IsReady → IsPlayAgain. // AIAgentInfo carries the AI opponent's identity so the client can spawn + nameplate AI cars. // It is nullable: a null value serialises as a single presence byte (0), which is exactly what the // empty grid produced before the AI grid was wired in. The grid itself is built by // GameRouter.BuildRacingAiGrid (RacingZoneProbe.cs). // // // LinkAIAgentInfo — AI opponent identity, nested (nullable) inside GameGroundParticipantInfo. // Wire order verified against Auto.WriteLinkAIAgentInfo (RPCSerializeAuto.lua:5454): // Uid → FightSpiritId → Fashion(nullable LinkAIFashionInfo) → Nickname(nullable string) → // NameId → AvatarImageId → VehicleId. // Fashion is sent null from the server (presence byte 0) — that skips the deep // OtherPlayerSpiritWearFashionsInfo.WearInfo graph entirely and is still enough for the client // to spawn + drive the AI car. WearInfo (in LinkAIFashionInfo) therefore only exists to satisfy // the contract graph and reuses the auto-generated OtherPlayerSpiritWearFashionsInfo (the // wire-flattened, authoritative form). // // // LinkAIFashionInfo — nested (nullable) inside LinkAIAgentInfo. The client only reads it when // Fashion is non-null, which the server never sends, so WearInfo here exists solely to // keep the contract graph resolvable. Type reuses Auto.OtherPlayerSpiritWearFashionsInfo. // Wire order: SuitId → WearInfo(nullable Auto.OtherPlayerSpiritWearFashionsInfo). // // // RacingParticipantInfo — the Racing-specific SUBTYPE of GameGroundParticipantInfo. // ⛔ NOT used by RacingZoneInfo.ParticipantInfos. The client's own zone writer // (Auto.WriteRacingZoneInfo, RPCSerializeAuto.lua:7474) types the list elements as the BASE // GameGroundParticipantInfo via Base.WriteComplexWrap(Auto.WriteGameGroundParticipantInfo, …), // i.e. each element is 0xFF + 8 base fields, with NO TypeMark. Putting this subtype in that // list emitted 0x0E + 13 fields per element and desynced the packet (R203). // This class is kept only because Auto.WriteRacingParticipantInfo exists in the client's // serializer; do not wire it into the zone payload. // ⚠️ Also note the field list below does NOT match Auto.WriteRacingParticipantInfo: the client // writer has 12 fields (AiVehicleCfgId → CheckPointIndex → Lap → RacingTotalTimeMilli → Pid → // NpcCultivationId → AIAgentInfo → AgentInstanceId → VehicleInstanceId → SeatIndex → IsReady → // IsPlayAgain) and has no CurrentVehicleInstanceId; this contract has 13. Anything that // does use this class must first be re-verified field-by-field against that writer. // // // RacingZoneInfo — payload of SyncGameGroundZoneInfo (68004609). // 🔴 TypeMark = 15 is load-bearing (see the file header): it is the discriminator byte the // client's Auto.Dispatch[449] uses to pick Auto.Reader[1403]. // 🔴 Field order = wire order = declaration order. Do not reorder, do not extract a base class. //