Grid Placement

Input Routing (Option B) — 6.0 C# Canonical, 5.1 Legacy Glue

Decision

  • 6.0: C# Core is canonical for input routing + domain intent emission.
  • 5.1: stays GDScript (legacy), but we keep the same conceptual contract at the engine boundary.

Goals

  • Single Godot-side input capture point per scene/root (one _unhandled_input owner).
  • Core routing policy lives in one place (priority, gating), not scattered across nodes.
  • Domain adapters remain isolated (Positioning vs Placement vs Manipulation).
  • Engine ↔ Core boundary is explicit and small.

Non-goals

  • No requirement to make 5.1 nodes match 6.0 nodes structurally.
  • No multi-node per-domain input capture.

Architecture

Layer 1 — Engine glue (Godot)

Responsibility: capture engine InputEvent and forward to a single handler.

  • 5.1: existing injector/root node calls into GBInputService.handle_unhandled_input(event) (GDScript).
  • 6.0: new/updated root node calls into C# bridge (or C#-exposed service) that ultimately delegates to the C# router.

Layer 2 — Input bridge (6.0)

Responsibility: convert engine event to Core InputEventData.

  • InputEvent (Godot) -> InputEventData (Core)
  • Contains minimal translation logic (mouse move, action press/release, etc.).

Layer 3 — Core router service (6.0)

Responsibility: routing policy + “handled” semantics.

  • Holds interpreters in deterministic order.
  • Applies gating (mode, settings) and stops on first handled.

Layer 4 — Core domain adapters (6.0)

Responsibility: translate InputEventData into domain intents (events/callbacks).

  • PositioningInputInterpreter
  • PlacementInputInterpreter
  • ManipulationInputInterpreter

Contracts

Core adapter contract

  • ICoreInputInterpreter.Handle(InputEventData inputEvent) -> bool
  • Returns true iff the adapter consumed the event.

6.0 engine boundary contract

  • Single call site from Godot node:
    • handled = inputService.HandleUnhandledInput(event) (or equivalent)

Routing policy (initial)

  • Priority order:

    1. Positioning (mouse move, keyboard movement)
    2. Placement (build/off/confirm)
    3. Manipulation (move/demolish/info/off/confirm + rotate/flip)
  • Gating:

    • Placement only active when current mode allows placement.
    • Manipulation only active when current mode allows manipulation.
    • Positioning can be always-on or settings-gated.

Work plan (tracking)

Phase 1 — Core contracts

  • Make PositioningInputInterpreter implement ICoreInputInterpreter.
  • Make PlacementInputInterpreter implement ICoreInputInterpreter.
  • Make ManipulationInputInterpreter implement ICoreInputInterpreter.
  • Ensure adapters remain POCO and unit-testable.

Phase 2 — Core router

  • Add CoreInputService in Core.
  • Router accepts interpreters + optional gating dependencies.
  • Router enforces deterministic ordering.

Phase 3 — 6.0 Godot bridge

  • Implement InputEvent -> InputEventData translator.
  • 6.0 root node forwards _unhandled_input into the bridge/router.

Phase 4 — 5.1 compatibility stance

  • Keep existing GDScript adapters/services as legacy.
  • Ensure documentation clarifies 5.1 is GDScript canonical; 6.0 is C# canonical.

Phase 5 — Verification

  • Add/extend Core xUnit tests to prove:
    • routing order
    • gating behavior
    • first-handler-wins semantics
    • adapter event emission on input

Demo export (canonical Core -> Godot demo addon)

The Godot C# demo addon consumes Core via a prebuilt DLL reference:

  • demos/grid_building_dev/godot_cs/addons/GridPlacement/src/Godot/GridBuilding.Godot.csproj
    • <HintPath>..\..\lib\GridPlacement.Core.dll</HintPath>

To keep the demo aligned with canonical Core, build and export the DLL from the plugin workspace:

  • Script:
    • plugins/gameplay/GridPlacement/scripts/export_core_to_demo.sh
  • Output copied to:
    • demos/grid_building_dev/godot_cs/addons/GridPlacement/lib/GridPlacement.Core.dll

This export step is required whenever Core changes that must be reflected in the demo.

Open questions

  • Which 6.0 node is the single input-capture owner (composition root)?
    • Answer: GBUserScopeRoot (single required per-user/context node).
  • Where does mode live in 6.0 (Core service vs engine state) for gating?
  • Do we need mouse button state aggregation (e.g., drag) at the bridge layer or Core layer?