Grid Placement

Orchestrator Application Layer Plan (GDScript GridBuilding)

Goal / Scope

  • Goal: Introduce a non-Node “application layer” orchestrator that coordinates PlacementService, ManipulationService2D, IndicatorService, and TargetingService, so BuildingSystem / ManipulationSystem become thin adapters (input + scene effects only).
  • Scope (in):
    • Define orchestration responsibilities and the “effects boundary”.
    • Define how GBServiceRegistry composes services and creates orchestrators.
    • Define lifetime/state/leakage rules as first-class acceptance criteria.
    • Define minimal regression probes/tests to prevent regressions.
  • Scope (out):
    • Rewriting validator logic or grid math.
    • Large scene/harness redesign.

Constraints

  • Strong typing: keep runtime code strongly typed; don’t loosen typing to make tests pass.
  • Canonical source first: implement in canonical plugin paths (not demo addon copies).
  • No test exclusion hacks: do not hide failing tests via build file exclusions.
  • Godot execution: run Godot/GdUnit via C# toolkit/orchestrator wrappers (not direct godot --headless).

Architecture Summary

New layer: Orchestrators (non-Node)

  • Build workflow: BuildWorkflowOrchestrator
  • Manipulation workflow: ManipulationWorkflowOrchestrator

Orchestrators:

  • Own session state (selection, rotation, cached targeting snapshot).
  • Coordinate focused services.
  • Return data-only models + effects, never mutate Nodes.

Existing services (focused collaborators)

  • TargetingService: convert raw input/physics info into a TargetingSnapshot.
  • PlacementService: compute preview candidate, validate, commit placement.
  • ManipulationService2D: compute/validate/commit manipulation.
  • IndicatorService: build IndicatorModel from preview + targeting + validation.

Node systems become “thin adapters”

  • BuildingSystem:
    • translate input into orchestrator actions
    • apply returned effects (spawn/move/delete preview nodes, indicator visuals, SFX)
  • ManipulationSystem:
    • same pattern for manipulation

Effects Boundary (authoritative)

Rule: Orchestrators and services return effects (instructions). Only Nodes apply effects.

Minimal effect set (example shape; final naming TBD):

  • EnsurePreviewExists(preview_id, resource_path)
  • UpdatePreview(preview_id, preview_model)
  • ShowIndicator(indicator_model)
  • ClearPreview()
  • ClearIndicator()
  • PlayFeedback(kind)

GBServiceRegistry Integration

Role of GBServiceRegistry

GBServiceRegistry is the composition root for scene/grid scope.

  • Builds/wires scene-scoped services.
  • Provides factories for creating session-scoped orchestrators.
  • Does not store session state.
  • GBServiceRegistry.create_build_orchestrator() → returns a new BuildWorkflowOrchestrator
  • GBServiceRegistry.create_manipulation_orchestrator() → returns a new ManipulationWorkflowOrchestrator

Hard rule: Orchestrators must not “reach back” into the registry. Dependencies are injected once.

Lifetime / State / Leakage Contract

This section is an explicit acceptance gate for the architecture.

Lifetime matrix

ComponentLifetimeOwns mutable state?Created byStored inNotes / leakage risk
GBServiceRegistryScene / Grid-rootYES (references only)Scene rootNode treeMust not store per-player/session state
BuildWorkflowOrchestratorSession (per system, per player)YESRegistry factory or systemBuildingSystem fieldReset/dispose on cancel/exit mode
ManipulationWorkflowOrchestratorSessionYESRegistry factory or systemManipulationSystem fieldSeparate to prevent god-workflow
TargetingServiceScene / Grid-rootMAYBE (cached queries)RegistryRegistryIf it touches physics/camera, keep scene-scoped
IndicatorServiceStateless preferredNORegistryRegistryCache read-only resources only
PlacementServiceScene / Grid-rootNO (ideally)RegistryRegistryMutations only via explicit commit
ManipulationService2DScene / Grid-rootNO (ideally)RegistryRegistrySame pattern
Preview/Indicator NodesSceneYESSystemsScene treeNever created by services/orchestrators

Ownership invariants

  • Invariant A (Registry is not a state bucket):
    • Forbidden: registry.current_rotation, registry.selected_blueprint, registry.active_preview_node.
  • Invariant B (Orchestrators don’t pull from registry):
    • Forbidden: orchestrator.registry.get_targeting_service().
  • Invariant C (Services don’t mutate Nodes):
    • Services return models/results; systems apply effects.
  • Invariant D (No Node refs in services/orchestrators):
    • Services/orchestrators must not keep references to preview nodes, indicator nodes, or UI nodes.

Anti-leak guardrails

  • Orchestrators support Cancel (or reset_session) that clears:
    • selection
    • cached targeting snapshot
    • cached preview/indicator models
  • Systems treat effects[] as the only source of truth for visuals (no side-channel scene mutations).

Work Plan (milestones)

#PriorityMilestoneWhyDone when
1[O]Contract + boundaries lockedPrevent “service does everything” regressionEffects boundary + ownership invariants written and agreed
2[O]Registry factories implementedComposition root stays cleanRegistry creates orchestrators; orchestrators have no registry reference
3[O]Build workflow migrated (preview → confirm)Proves architectureBuildingSystem uses orchestrator; preview and confirm work
4[Y]Manipulation workflow migratedParity + consistencyManipulationSystem uses orchestrator
5[Y]Leak probes/tests addedPrevent subtle regressions2–4 deterministic probes pass in existing suites

Targeted Regression Probes (tests)

These are small, high-value probes to prevent lifetime/state/leakage regressions.

  • Cancel clears effects: Cancel produces ClearPreview and ClearIndicator effects.
  • No cross-session leakage: two orchestrator instances do not share selection/rotation.
  • Registry creates distinct sessions: registry factory produces distinct orchestrators with independent state.
  • Invalid placement does not commit: Confirm only commits when validation is valid.

Reuse-first target suites:

  • gdscript/grid_building/test/systems/building/unit/placement_service_test.gd
  • gdscript/grid_building/test/systems/building/unit/preview_builder_unit_test.gd

First Move

  • Decide whether GBServiceRegistry is scene-scoped (recommended) vs autoload/global.
    • If autoload/global is required, introduce an explicit GridContext passed into services/orchestrators to prevent cross-scene leakage.