Grid Placement

GridPlacement Parity Contract — 6.0 C# Core ↔ 5.1 GDScript

Purpose

This document is the canonical source of truth for what “parity” means between:

  • GridPlacement 6.0 C# (canonical)
  • GridBuilding/GridPlacement 5.1 GDScript (legacy-stable)

It defines:

  • Scope (what parity is required for)
  • Non-goals (what parity is explicitly not required for)
  • Vertical slices (how we implement parity incrementally)
  • Acceptance tests (how we prove parity)
  • Update rules (where to record mappings when things move)

Canonical Principles

  • 6.0 C# Core is canonical for behavior and tests.
  • 5.1 is legacy-stable: we keep it loading/working and we backport only small, high-value behavior when it clearly reduces risk.
  • No plugin-agnostic composition framework in 5.1.
    • GBInjectorSystem is the canonical 5.1 composition root and remains so.
  • 5.1 keeps GBEnums (GDScript) as a stable public API surface.
    • This reduces class_name proliferation and avoids churn in legacy demos/tests.
  • Parity is expressed at the contract level; enum/value names may differ by track.
  • 6.0 uses GridMode (C#) as the canonical mode enum.
    • Canonical type: GridBuilding.Core.Types.GridMode (cs/Core/Types/Mode.cs).
    • See cs/Core/Types/GridModeExtensions.cs, cs/Core/Services/Mode/IModeService.cs, cs/Core/Services/Mode/ModeService.cs.
  • Breaking change (6.0): no C# GBEnums.
    • GBEnums is a 5.1 GDScript-only API surface.
    • All 6.0 C# code should reference GridMode (and other explicit types) directly.
  • Parity is contract-level:
    • Parity is required for Core/service/domain behavior.
    • Parity is not required for Godot Node/Resource names, scene graphs, or editor conveniences.
  • No cross-language runtime bridge:
    • 5.1 parity is achieved via GDScript translation into pure RefCounted logic modules (deterministic IO), not by calling 6.0 C# at runtime.

Mode naming parity (5.1 ↔ 6.0)

5.1 (GDScript) uses GBEnums.Mode while 6.0 (C#) uses GridMode. Preferred conceptual mapping:

  • GBEnums.Mode.OFFGridMode.Off
  • GBEnums.Mode.INFOGridMode.Info
  • GBEnums.Mode.BUILDGridMode.Place
  • GBEnums.Mode.MOVEGridMode.Move
  • GBEnums.Mode.DEMOLISHGridMode.Remove

Input wiring acceptance tests (composition chain)

These are integration wiring contracts (not full gameplay parity): they prove that each track can boot its composition root and route a representative input event through the configured adapter/interpreter pipeline.

5.1 GDScript (Godot) chain

Contract:

GBInjectorSystem → GBServiceFactory → GBServiceRegistry → GBInputService → PositioningInputAdapter

Evidence (GdUnit integration test):

  • demos/grid_building_dev/godot/test/grid_building/systems/placement/integration/node_adapter_service_wiring_integration_test.gd

What it asserts:

  • The test environment boots and provides a GBCompositionContainer.
  • The container exposes a non-null GBServiceRegistry.
  • The registry exposes a non-null GBInputService.
  • A forwarded engine input event triggers a positioning-domain signal (adapter/interpreter path is live).

6.0 C# (Godot) chain

Contract:

GBUserScopeRoot → ServiceRegistry (C#) → CoreInputService → CoreInputRoutingService → interpreters/adapters

Evidence (GoDotTest):

  • demos/grid_building_dev/godot_cs/InputPipelineSmokeGoDotTests.cs

What it asserts:

  • GBUserScopeRoot boots standalone and provides a ServiceRegistry.
  • CoreInputService is registered and contains a non-null internal CoreInputRoutingService router.
  • An InputEventAction translated through the bridge is routed and handled by the interpreter pipeline.

What parity IS (required)

Parity is required for:

  • Placement validation semantics
    • same “valid/invalid” decision outcomes for the same inputs
    • same key failure categories (bounds, collisions, rule failures)
  • Indicator decision semantics
    • given a placement validation outcome, the same indicator state decision is produced (valid/invalid/mixed/etc.)
  • Placement execution semantics (minimal)
    • the same high-level result categories (success/failure) and core invariants

In practice, parity is expressed as:

  • a small set of deterministic acceptance tests per vertical slice
  • a stable data contract for inputs/outputs (even if 5.1 uses Dictionaries internally)

What parity is NOT (explicitly not required)

Parity is not required for:

  • Godot Node names, scene layout, exported property names (unless they are part of the locked 5.1 public API)
  • Demo scene UX parity
  • Full test-suite parity (5.1 keeps a curated, small test slice)
  • DI/composition parity (5.1 stays on InjectorSystem)

Plugin boundary: JSON & resource loading

Parity does not require both tracks to share the same JSON loading architecture.

5.1 GDScript (grid_building) boundary

  • 5.1 uses Godot-native resource loading as the canonical path (res:// resources, .tres/.tscn), plus any existing plugin-specific configuration loaders.
  • 5.1 does not need a generalized “safe JSON loader” framework.
  • 5.1 does not need to support headless/server JSON workflows.
  • If a game wants JSON-driven configuration, JSON parsing belongs in game/host code using Godot’s JSON APIs.
  • Parity requirement (5.1): it is sufficient that the game can convert JSON into the plugin’s expected Resources (e.g., Placeable resources) and then call into the plugin using those resources.

6.0 C# (GridPlacement) boundary

  • 6.0 Core should remain engine-agnostic and should not own Godot Resource loading.
  • 6.0 should not provide a generic JSON file IO subsystem as part of the plugin boundary.
  • If JSON is involved, the expected boundary is:
    • Host/engine layer owns IO + asset resolution (e.g., AssetLoader / safe loader service)
    • Core owns schema/contract interpretation and validation (pure types in/pure results out)

In-scope vertical slices (incremental parity plan)

We implement parity one vertical slice at a time.

Slice A — Placement Validate → Indicator Decision → Place (core)

Goal: a user can attempt to place an item; the validation and indicator decisions match 6.0 behavior.

Minimal contract (Slice A)

Inputs (conceptual):

  • User identity: UserId (6.0 canonical type; 5.1 uses owner-style identity but conceptually maps 1:1)
  • Target: grid position or world position that can be converted to grid
  • Placeable definition: placeable id + footprint/variant + any required metadata
  • Rotation: canonical rotation representation for the placement workflow
  • Grid bounds: region/limits used to validate in-bounds placement
  • Occupancy/collision representation: current occupied cells and collision rules

Outputs (contract-level):

  • Validation outcome: valid/invalid + reason category (bounds, collision/overlap, rule failure)
  • Indicator decision: the canonical indicator state decision for the validation outcome
  • Execution result category: success/failure (minimal), plus stable identifiers/coordinates where available

Minimal service graph expectations

6.0 C# (canonical):

  • Core services own the Slice A behavior under the service + adapter architecture.
  • Loading/asset resolution is out-of-scope (host/engine responsibilities).

5.1 GDScript (legacy):

  • GBInjectorSystem remains the canonical composition root.
  • Slice A parity is implemented by:
    • pure RefCounted logic (validation and indicator decision) plus
    • thin Node/scene adapters that keep the 5.1 public API stable.
  • JSON parsing (if any) remains game-level code; the plugin consumes Resources.

Done when (Slice A)

  • 6.0 has a deterministic Core test set covering the Slice A contract.
  • 5.1 has a small deterministic GDScript test set that mirrors the same cases.
  • A short doc example shows the same inputs produce equivalent outcomes.

Slice B — Targeting / cursor state transitions (optional)

Parity applies to:

  • cursor → grid snapping rules
  • target cell transitions

Parity does not require node-level APIs to match.

Slice C — Manipulation (move/rotate/demolish) (optional)

Larger slice; defer until slices A/B are stable.

Acceptance tests (how we prove parity)

Rules:

  • Tests must be deterministic.
  • Prefer contract-level tests over heavy scenes.
  • 6.0 tests are canonical; 5.1 tests mirror only the chosen slice’s contract.

Done when (per slice):

  • a) 6.0 Core tests exist and pass
  • b) a mirrored 5.1 GDScript test set exists and passes
  • c) documented examples show the same inputs produce equivalent outcomes

Canonical tracker map (where to look)

Use these documents as the “map,” but treat this file as the canonical contract:

  • 6.0 overall execution plan: docs/Roadmap/ROADMAP.md
  • 5.1 legacy lane plan: docs/5.1/gdscript/ROADMAP.md
  • Canonical type mapping: docs/6.0/Core/GRIDBUILDING_CLASS_MAPPING.md
  • Legacy Godot (_Legacy/) port scoring: docs/Migration/MIGRATION_CLASS_PRIORITY.md
  • Workflow guidance for users migrating: docs/5.1/gdscript/GDSCRIPT_5_1_TO_6_0_MIGRATION.md
  • Architecture/migration narrative: docs/6.0/Core/SERVICE_PATTERN_MIGRATION.md

Update rules (to prevent drift)

When parity decisions change:

  • Update this file first.
  • If types move, update:
    • docs/6.0/Core/GRIDBUILDING_CLASS_MAPPING.md
    • any impacted migration docs referenced above
  • Keep duplication low: other docs should link here rather than restating the contract.