Orchestrator Application Layer Plan (GDScript GridBuilding)
Links
- Health:
docs/5.1/gdscript/HEALTH.md - Roadmap:
docs/Roadmap/ROADMAP.md
Goal / Scope
- Goal: Introduce a non-Node “application layer” orchestrator that coordinates
PlacementService,ManipulationService2D,IndicatorService, andTargetingService, soBuildingSystem/ManipulationSystembecome thin adapters (input + scene effects only). - Scope (in):
- Define orchestration responsibilities and the “effects boundary”.
- Define how
GBServiceRegistrycomposes 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 aTargetingSnapshot.PlacementService: compute preview candidate, validate, commit placement.ManipulationService2D: compute/validate/commit manipulation.IndicatorService: buildIndicatorModelfrom 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.
Factory pattern (recommended)
GBServiceRegistry.create_build_orchestrator()→ returns a newBuildWorkflowOrchestratorGBServiceRegistry.create_manipulation_orchestrator()→ returns a newManipulationWorkflowOrchestrator
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
| Component | Lifetime | Owns mutable state? | Created by | Stored in | Notes / leakage risk |
|---|---|---|---|---|---|
GBServiceRegistry | Scene / Grid-root | YES (references only) | Scene root | Node tree | Must not store per-player/session state |
BuildWorkflowOrchestrator | Session (per system, per player) | YES | Registry factory or system | BuildingSystem field | Reset/dispose on cancel/exit mode |
ManipulationWorkflowOrchestrator | Session | YES | Registry factory or system | ManipulationSystem field | Separate to prevent god-workflow |
TargetingService | Scene / Grid-root | MAYBE (cached queries) | Registry | Registry | If it touches physics/camera, keep scene-scoped |
IndicatorService | Stateless preferred | NO | Registry | Registry | Cache read-only resources only |
PlacementService | Scene / Grid-root | NO (ideally) | Registry | Registry | Mutations only via explicit commit |
ManipulationService2D | Scene / Grid-root | NO (ideally) | Registry | Registry | Same pattern |
| Preview/Indicator Nodes | Scene | YES | Systems | Scene tree | Never 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.
- Forbidden:
- Invariant B (Orchestrators don’t pull from registry):
- Forbidden:
orchestrator.registry.get_targeting_service().
- Forbidden:
- 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(orreset_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)
| # | Priority | Milestone | Why | Done when |
|---|---|---|---|---|
| 1 | [O] | Contract + boundaries locked | Prevent “service does everything” regression | Effects boundary + ownership invariants written and agreed |
| 2 | [O] | Registry factories implemented | Composition root stays clean | Registry creates orchestrators; orchestrators have no registry reference |
| 3 | [O] | Build workflow migrated (preview → confirm) | Proves architecture | BuildingSystem uses orchestrator; preview and confirm work |
| 4 | [Y] | Manipulation workflow migrated | Parity + consistency | ManipulationSystem uses orchestrator |
| 5 | [Y] | Leak probes/tests added | Prevent subtle regressions | 2–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:
CancelproducesClearPreviewandClearIndicatoreffects. - 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:
Confirmonly commits when validation is valid.
Reuse-first target suites:
gdscript/grid_building/test/systems/building/unit/placement_service_test.gdgdscript/grid_building/test/systems/building/unit/preview_builder_unit_test.gd
First Move
- Decide whether
GBServiceRegistryis scene-scoped (recommended) vs autoload/global.- If autoload/global is required, introduce an explicit
GridContextpassed into services/orchestrators to prevent cross-scene leakage.
- If autoload/global is required, introduce an explicit