Grid Placement separates runtime logic from scene/game context. This is the canonical guide for host/session composition beyond the starter setup.
You usually interact with three layers:
GridPlacementHost— routes the active interaction and composes runtime services.PlacementSession— stores per-player/controller state and configuration references.- Context/provider adapters — connect the current level, player, and game facts to placement validation/services.
Default path
For new 6.0 scenes, use the host/session path directly:
- Add a
GridPlacementHostto the scene root or systems scene. - Assign
GridPlacementHost.grid_placement_bundle. - Assign each
GridPositioner2D.sessionto thePlacementSessionowned by that player/controller. - Author
PlacementLevelContextandPlacementOwnerin the scene.
When GridPositioner2D.session is assigned, the positioner configures itself from that session and registers it with the nearest GridPlacementHost at runtime. The controller configures the host with the same session and binds scene consumers.
A normal project also does not need to instantiate every placement service manually. The host composes the configured service group from the bundle/session.
Explicit host configuration
Use explicit configuration when code owns the session lifecycle rather than a scene-authored positioner:
host.configure(session)For independent controllers:
host.register_session(controller_root, session, device_id)Unregister the controller/session when its owning scene or controller leaves. Keep one session per independent interaction; players that need separate selection, targeting, coordinate mode, or pending manipulation state should not share a session.
Consumers should use supported host/service entry points instead of depending on internal scene paths.
Binding scene consumers
GridPlacementHost.bind_scene_consumers(root, session) walks a scene subtree and
calls each consumer's existing resolve_placement_dependencies and
resolve_placement_runtime hooks. Controllers call it after configure() so UI,
previews, and helpers receive their dependencies without per-node scene exports.
Positioners self-register from their session export and are skipped.
Context injection
Use contexts/providers for facts that come from the current game scene, for example:
- target
TileMapLayerorGridMapsurface; - placed-object parent;
- placement owner/controller;
- game-owned reserved/occupied zones;
- inventory, cost, or world-state facts used by custom rules.
Do not copy those facts into long-lived plugin globals when their lifetime belongs to a level, session, or game system.
Shared placement world
Dimension-specific services still need to agree on the same committed placement world.
This matters for:
- GRID vs SMOOTH conflicts;
- CELL/EDGE/FACE/CORNER/TOP mount occupancy;
- SMOOTH socket occupancy;
- multiple sessions;
- save/restore.
Do not build a second occupancy registry that can commit conflicting placement state.
When to inject a custom service/provider
Add custom composition only when the project needs a real policy or integration point. Examples:
- a game-owned world-facts provider;
- custom placement validation rule;
- inventory/refund bridge;
- custom input/controller/session setup.
PlacementInjectable receives a PlacementSession directly. Do not write new integration code around removed PlacementContainer compatibility types.
Avoid replacing plugin services only to change naming or add logging.
Troubleshooting composition
Check these before changing plugin code:
GridPlacementHostexists in the relevant scene/systems tree.GridPositioner2D.sessionis assigned for scene-authored controller wiring.- The host and session use the intended
GridPlacementBundle. PlacementLevelContextandPlacementOwnerbelong to the active level/controller lifetime.- Scene consumers: verify
host.bind_scene_consumers(root, session)ran for the active controller scope.