Manipulation: System vs Input Controller (6.0)

GridPlacement 6.0 separates orchestration from input handling for object manipulation (moving, rotating, flipping placed objects). This guide explains why this split exists and how it affects your code.

The Core Split

In 6.0, manipulation is divided into two distinct responsibilities:

  1. ManipulationSystem (via PlacementContext.ManipulationSystem) — The “Brain”. Owns validation, state transitions, and coordinates sub-systems.
  2. PlacementInputController — The “Hands”. Routes confirm/cancel input from cursors to the manipulation bridge.

Why this split exists

The 6.0 architecture intentionally separates orchestration from input routing:

  • Testability: The ManipulationSystem can be tested without needing a complex scene tree
  • Clarity: You know exactly where to look for validation logic (ManipulationSystem) vs. input handling (InputController)
  • Stability: Transform state is managed through ECS components, preventing drift from repeated calculations

Responsibilities (Godot Adapter API)

ComponentRoleOwnsDoes NOT own
ManipulationSystemBusiness LogicLifecycle (start/commit), validation, state transitions, eventsDirect input handling, visual transforms
PlacementInputControllerInput RouterWiring cursor signals to IPlacementInputBridge, confirm/cancel routingValidation rules, grid occupancy checks
PlacementSceneAdapterScene SyncGhost/preview entity creation, ECS-to-Godot syncValidation logic, input handling

Scene Integration

When you pick up an object, the flow looks like this:

1
2
3
4
5
Cursor2D (The cursor/grid snapper)
  └── PlacementInputController (Routes input)
        └── IPlacementInputBridge (via PlacementContext.InputBridge)
              └── ManipulationSystem (Validates & commits)
                    └── PlacementSceneAdapter (Syncs ECS → Godot visuals)

The Cursor2D detects input → PlacementInputController forwards it → ManipulationSystem processes it.

Key 6.0 Behaviors

1. Movable Validation

In 6.0, ManipulationSystem enforces the IsMovable check on the source object via ECS components:

  • Behavior: The system validates against SelectedPlaceableComponent and TransformComponent2D
  • Flow: Input → PlacementInputBridgeManipulationSystem → Validation → Ghost update

2. Transform Preservation

When a move completes, the transform is preserved through ECS state:

  • Flow: Move Start → Store original transform → User rotates/flips → Confirm → Apply final transform
  • Note: Transforms are stored on ECS components, not scene nodes directly

3. Visual Sync

Because the system uses ECS components, visual sync happens through:

  • PlacementSceneAdapter — Syncs ECS state to Godot nodes
  • Cursor2D — Handles cursor visual representation
  • Ghost entities — Managed by MoveManipulationSystem (internal)

Accessing Manipulation in Godot

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Get the manipulation system from PlacementContext
var context = GetNode<PlacementContext>("/root/PlacementContext");
var manipSystem = context?.ManipulationSystem;

if (manipSystem != null)
{
    // Listen to events through IEcsBackendEvents or ManipulationSignalBus
    context.Events.EntityMoved += OnEntityMoved;
    context.ManipulationSignals.MoveConfirmed += OnMoveConfirmed;
}

PlacementInputController Wiring

1
2
3
4
5
6
7
8
// In your scene, PlacementInputController auto-wires to Cursor2D:
// 1. Add PlacementInputController node
// 2. Set its Context property to your PlacementContext
// 3. Optionally set ExplicitCursor if not using the context-registered cursor

// The controller automatically:
// - Listens to Cursor2D.ConfirmPressed → calls InputBridge.ExecutePlacement()
// - Listens to Cursor2D.CancelPressed → calls InputBridge.CancelPlacement()

Common Pitfalls

  • Direct Node Access: Don’t try to access ManipulationSystem as a Godot node — it’s an ECS system accessed via PlacementContext
  • Input Consumption: Input is routed through PlacementInputController, not handled directly by systems
  • Orphaned Ghosts: If the operation is interrupted, PlacementSceneAdapter handles cleanup automatically
  • Transform Access: Access transforms via ECS components, not node.Position directly