GameServer: RadarContactUpdateEvent is serialized without any fields — radar contacts never reach clients #911

Closed
opened 2026-08-05 03:53:10 +00:00 by spikerj · 3 comments
Owner

SpaceZone.BroadcastRadarUpdates() queues RadarContactUpdateEvent (contacts, currentMode, currentRange, radarPowerUsage, activeScanCooldown) every radar interval, but MessagePackSerializer.EventToDict has no case for RadarContactUpdateEvent — the event goes over the wire as just {type: "RadarContactUpdate", serverTick} (the type name comes from the GetEventTypeName fallback; the field switch silently drops everything else).

Impact:

  • The Angular client (game-server.service.ts:1083, normalizeRadarContactUpdateEvent) receives empty events — the radar HUD has presumably never shown a contact via the MessagePack path.
  • The Godot client (spikersoft-games-godot PR #2) hit the same wall; it now synthesizes radar locally from tracked ships and will automatically prefer server contacts once this is fixed.

Fix: add a RadarContactUpdateEvent e case to EventToDict serializing contacts (list of dicts: entityId, contactType, x/y/z → note Angular expects lastKnownX/Y/Z naming — pick one and align the clients), timeSinceUpdate, isGhost, isStealthed, signalStrength, distance, name, healthPercent, shieldPercent, isHostile, isFriendly, plus currentMode, currentRange, radarPowerUsage, activeScanCooldown.

Refs: SpikerSoft.GameServer/Network/MessagePackSerializer.cs (EventToDict switch), SpikerSoft.GameServer/Zones/SpaceZone.cs ~L881 (BroadcastRadarUpdates), SpikerSoft.Common/Models/GameServer/RadarEvents.cs.

`SpaceZone.BroadcastRadarUpdates()` queues `RadarContactUpdateEvent` (contacts, currentMode, currentRange, radarPowerUsage, activeScanCooldown) every radar interval, but `MessagePackSerializer.EventToDict` has **no case for `RadarContactUpdateEvent`** — the event goes over the wire as just `{type: "RadarContactUpdate", serverTick}` (the type name comes from the `GetEventTypeName` fallback; the field switch silently drops everything else). Impact: - The Angular client (`game-server.service.ts:1083`, `normalizeRadarContactUpdateEvent`) receives empty events — the radar HUD has presumably never shown a contact via the MessagePack path. - The Godot client (spikersoft-games-godot PR #2) hit the same wall; it now synthesizes radar locally from tracked ships and will automatically prefer server contacts once this is fixed. Fix: add a `RadarContactUpdateEvent e` case to `EventToDict` serializing `contacts` (list of dicts: `entityId`, `contactType`, `x/y/z` → note Angular expects `lastKnownX/Y/Z` naming — pick one and align the clients), `timeSinceUpdate`, `isGhost`, `isStealthed`, `signalStrength`, `distance`, `name`, `healthPercent`, `shieldPercent`, `isHostile`, `isFriendly`, plus `currentMode`, `currentRange`, `radarPowerUsage`, `activeScanCooldown`. Refs: `SpikerSoft.GameServer/Network/MessagePackSerializer.cs` (EventToDict switch), `SpikerSoft.GameServer/Zones/SpaceZone.cs` ~L881 (`BroadcastRadarUpdates`), `SpikerSoft.Common/Models/GameServer/RadarEvents.cs`.
Author
Owner

Scope update (found while reviewing spikersoft-games-godot PR #3): the same missing-EventToDict-case gap also affects two more Space events, both queued owner-only by SpaceZone:

  • RadarModeChangedEvent (entityId, newMode, newRange, powerUsage) — queued on every successful SetRadarMode (SpaceZone.cs:1307/:1338) and after ActiveScan (:1477).
  • StealthStateChangedEvent (entityId, isStealthActive, isFullyEngaged, activationDelayRemaining, reactivationCooldown, powerUsage, heatGeneration) — queued on stealth activate (:1411) and deactivate (:866).

Both currently arrive as {type, serverTick} only. The Godot client (PR #3) treats its radar/stealth UI state as optimistic and applies these events defensively when fields are present, so it will pick the fields up automatically once the EventToDict cases land — please add cases for these two alongside RadarContactUpdateEvent.

Scope update (found while reviewing spikersoft-games-godot PR #3): the same missing-`EventToDict`-case gap also affects two more Space events, both queued owner-only by `SpaceZone`: - **`RadarModeChangedEvent`** (`entityId`, `newMode`, `newRange`, `powerUsage`) — queued on every successful `SetRadarMode` (SpaceZone.cs:1307/:1338) and after `ActiveScan` (:1477). - **`StealthStateChangedEvent`** (`entityId`, `isStealthActive`, `isFullyEngaged`, `activationDelayRemaining`, `reactivationCooldown`, `powerUsage`, `heatGeneration`) — queued on stealth activate (:1411) and deactivate (:866). Both currently arrive as `{type, serverTick}` only. The Godot client (PR #3) treats its radar/stealth UI state as optimistic and applies these events defensively when fields are present, so it will pick the fields up automatically once the `EventToDict` cases land — please add cases for these two alongside `RadarContactUpdateEvent`.
Author
Owner

Fix is ready in spikersoft-backend PR #517 (CI fully green: unit tests, both arch builds, game-server pipeline). Adds EventToDict cases for RadarContactUpdateEvent, RadarModeChangedEvent, StealthStateChangedEvent, ContactLostEvent and ContactGhostEvent. Contact positions serialize as lastKnownX/Y/Z with type/contactType and detectedThroughStealth aliases — matching what the Angular normalizer and the Godot client already parse, so no client changes needed. Closing once the PR merges (merge is awaiting a human click — the agent's merge action was permission-blocked).

Fix is ready in spikersoft-backend PR #517 (CI fully green: unit tests, both arch builds, game-server pipeline). Adds `EventToDict` cases for `RadarContactUpdateEvent`, `RadarModeChangedEvent`, `StealthStateChangedEvent`, `ContactLostEvent` and `ContactGhostEvent`. Contact positions serialize as `lastKnownX/Y/Z` with `type`/`contactType` and `detectedThroughStealth` aliases — matching what the Angular normalizer and the Godot client already parse, so no client changes needed. Closing once the PR merges (merge is awaiting a human click — the agent's merge action was permission-blocked).
Author
Owner

Resolved in spikersoft-backend PR #517 (merged to master). EventToDict now serializes RadarContactUpdate, RadarModeChanged, StealthStateChanged, ContactLost and ContactGhost with full fields (contact positions as lastKnownX/Y/Z, matching both the Angular and Godot clients as-is). The master deploy pipeline ships it to the gameserver. Closing.

Resolved in spikersoft-backend PR #517 (merged to master). `EventToDict` now serializes `RadarContactUpdate`, `RadarModeChanged`, `StealthStateChanged`, `ContactLost` and `ContactGhost` with full fields (contact positions as `lastKnownX/Y/Z`, matching both the Angular and Godot clients as-is). The master deploy pipeline ships it to the gameserver. Closing.
Sign in to join this conversation.