Grid Placement
Development ⚠️ GridPlacement 6.0 documentation is in active development. APIs and content may change, and the site may be temporarily unstable.

Payloads vs Snapshots (Intent)

This guide defines the intended meaning of payloads vs snapshots in GridPlacement. It exists to prevent three common failure modes:

  • Treating an event payload as authoritative internal state.
  • Polluting a payload object with service/session-only diagnostics.
  • Accidentally exposing internal state objects (breaking test isolation and portability).

Boundary

  • Core (C#)
    • Owns authoritative state and domain events.
    • Exposes payloads and snapshots as immutable/read-only views.
  • Engine glue (Godot)
    • Translates engine input into Core service calls.
    • Applies Core outputs (effects, transforms, visuals).
  • UI / diagnostics
    • Should never depend on internal state objects.
    • Should consume snapshots/payloads only.

Border conversion rule (Godot)

When data crosses the engine boundary, it must be converted to the boundary contract:

  • Signals/events should emit small EventData payload objects.
  • Internal state containers remain private to the owning service.
  • Snapshots are pulled explicitly for diagnostics/tests/front-end read requests.

GDScript-first signal signatures

In Godot (GDScript), we intentionally keep signal signatures GDScript-first (often untyped) to avoid load-order parse failures and warnings-as-errors.

Contract rule:

  • Emit an EventData payload instance from the signal.
  • Do not require typed signal annotations to enforce the contract.

Compatibility note (5.1 GDScript track)

The 5.1 GDScript runtime does not have a Core boundary to convert into. It can only emit GDScript-native payload objects. The 6.0 C# track can emit Core payloads/snapshots directly.

Definitions (6.0 intent)

Payload

A payload is the data that is deliberately emitted across a boundary (events/signals/effects).

  • Authoritative for the event that carried it.
  • Not authoritative for the service’s internal state over time.
  • May be mutable internally while the service is processing, but treat it as ephemeral once emitted.

Example (C#): event payload data associated with a manipulation update.

Snapshot

A snapshot is a read-only projection of an internal state container at a point in time.

  • Intended for diagnostics/UI/tests.
  • Must not be used as a state container.
  • If you need additional information later, ask the service again for a new snapshot.

Example (C#): ManipulationSnapshot record struct created from ManipulationState.

Identity (what identifies “a manipulation”)

A manipulation is identified by a manipulation ID, not by object identity of a payload or snapshot.

  • C# canonical identity: ManipulationId (string)
  • Payload identity rule: if a payload contains ManipulationId, treat that as the identity.
  • Snapshot identity rule: snapshots must include ManipulationId so observers can correlate.

Lifetime

ThingLifetimeWho owns itNotes
Authoritative internal statesession / interactionservicemutable, private
Snapshotpoint-in-timecallerdiscard/recreate
Payloadper-eventevent emittershould be treated as immutable by listeners

Testing guidance

  • Prefer Core unit/integration tests to validate snapshot/payload correctness.
  • Use Godot tests only to validate engine glue (signal wiring, input translation).
  • GDScript service signal contract (5.1 track):
    • demos/grid_building_dev/godot/test/grid_building/unit/core/logger_free_services_test.gd

Mapping: C# Core vs GDScript 5.1 (compatibility note)

GridPlacement 6.0 (C#) already models the separation explicitly:

  • C# internal state: GridPlacement.Core.State.Manipulation.ManipulationState
  • C# snapshot: GridPlacement.Core.Services.Manipulation.ManipulationSnapshot
  • C# event payloads: GridPlacement.Core.Services.Manipulation.*Event (carry ManipulationSnapshot)

GDScript 5.1 uses similar concepts but with different constraints:

  • GDScript payload: ManipulationData (status/action/message/results)
  • GDScript service-owned state: ManipulationService2D (authoritative; emits signals)
  • GDScript diagnostics snapshot: ManipulationSnapshot2D (projection from service)

Targeting: 5.1 and 6.0 (this topic)

In the modern architecture (5.1+ and especially 6.0), the targeting boundary follows the same rule:

  • Signals/events emit payload objects, not internal state objects.
  • Snapshots exist for diagnostics/tests, not as “the thing you wire your UI to.”

In the 5.1 (GDScript) track specifically:

  • Event payloads: TargetingEventData2D.* (emitted by TargetingService2D signals)
  • Diagnostics snapshot: TargetingSnapshot2D (projection from TargetingState2D)

Why this exists:

  • Prevents accidentally treating a mutable service state object as public API.
  • Keeps event contracts small and stable (no “bag-of-state” signals).
  • Keeps tests deterministic by snapshotting state at a point in time.

Legacy note (5.0)

GridBuilding 5.0 predates this separation and commonly exposed state directly to listeners.

  • Signals were often wired to public state containers.
  • There was no separate snapshot type for read-only diagnostic/test consumption.

The 5.1 and 6.0 lines improve this by explicitly separating:

  • service-owned state (private)
  • event payloads (public boundary)
  • snapshots (read-only projections)

What we intentionally do NOT do

  • Do not expose internal state objects across boundaries.
  • Do not “just reuse the payload” as the internal state container.
  • Do not attach engine nodes/refs to Core snapshots.