Chris' Tutorials
Docs/Grid Placement

Grid Placement v6.0

Session Ownership

Who owns what in the host architecture — controllers own sessions, host owns services.

StatusDraft
Versionv6.0
UpdatedDevelopment docs generated from GDScript source

This is unreleased documentation in active development. APIs, class names, and behavior may change before the final release.

Draft — Unreleased:This page is in active development. APIs, class names, and behavior may change before the release is finalized. Use it as a preview of what's coming — not as a stable integration target.

Grid Placement 6.0.0 separates what the game supports, what each player is doing, and how input is routed.

Version note: This guide is for Grid Placement 6.0.0. For first-time setup, start with Getting Started.


The Three Scopes

Scope Main type Owns
Game-scoped GridPlacementBundle / GridPlacementSettings Shared configuration: settings, actions, templates, rules, dimensions.
Player-scoped PlacementSession Mutable state for one player/controller: targeting, placement, manipulation, mode, selected terrain/placeable, logger, contexts.
Runtime/input-scoped GridPlacementHost Input dispatch, session registry, service group, and host-owned infrastructure.

Settings determine which domains exist. Sessions own player/controller state. The host resolves the active session and dispatches work.


Ownership Split

Controller / Player
└── owns PlacementSession
    ├── PlacementStates
    │   ├── ModeState
    │   ├── GridTargetingState
    │   ├── PlacementState
    │   └── ManipulationState
    ├── PlacementContexts
    └── PlacementLogger

GridPlacementHost
├── registers controller sessions
├── owns PlacementServiceGroup
│   ├── terrain service
│   ├── manipulation service
│   ├── targeting service
│   ├── brush coordinator
│   ├── refund service
│   └── drag service
└── owns input/mode dispatch infrastructure

Scene adapters such as GridPositioner2D, TargetingShapeCast2D, TerrainPreview, and UI nodes live in the scene tree and resolve into the active session. The host does not need hard-coded scene paths to those nodes.


PlacementSession

A PlacementSession is a resource that holds the mutable runtime state for one controller.

var session := PlacementSession.new()
session.grid_placement_bundle = preload("res://config/grid_placement_bundle.tres")
session.catalog = preload("res://catalog.tres")

Each session should be treated as isolated. Do not share one session between two active players unless you intentionally want them to share selection, targeting, mode, and action history.

PlacementStates structure

Field Type What it tracks
states.mode ModeState Current placement/manipulation mode.
states.targeting GridTargetingState Cursor cell, target map, maps array, positioner, target object.
states.building PlacementState Preview, placed parent, placement signals, terrain signals.
states.manipulation ManipulationState Active manipulatable, manipulation parent, refunder registry.

How Services Work

Services are RefCounted logic objects held by PlacementServiceGroup. They operate on the active session's state objects.

When the host dispatches input for another controller, it activates that controller's session and swaps service state pointers to that session's PlacementStates.

The important rule:

Services do logic. Sessions own state. Host routes input.

Single-Player Convenience

For a single-player project, the setup can be simple:

@onready var host: GridPlacementHost = $GridPlacementHost
var session := PlacementSession.new()

func _ready() -> void:
    session.grid_placement_bundle = preload("res://config/grid_placement_bundle.tres")
    host.configure(session)

Editor-composed template scenes can use PlacementInjectorSystem to configure and register a session automatically.


Scoped / Controller-Owned Sessions

For split-screen or per-controller setup, each controller creates and registers its own session.

@export var host: GridPlacementHost
@export var device_id: int = -1

var session := PlacementSession.new()

func _ready() -> void:
    session.grid_placement_bundle = preload("res://config/grid_placement_bundle.tres")
    host.register_session(self, session, device_id)

func _exit_tree() -> void:
    if host != null:
        host.unregister_session(self)

Use PlacementInjectorSystem.set_session_scope(controller_root, session) when you want injection limited to one controller subtree.


Pre-6.0 vs 6.0 Mental Model

Concern Older mental model 6.0 mental model
State ownership Individual system nodes PlacementSession per controller
Input entry point Multiple system nodes GridPlacementHost dispatch
Logic organization Node lifecycle mixed with logic Host/session/services split
Multi-controller support Global-ish state Session registry + device routing
Scene wiring Scene paths and system references Injection/context/session resolution

Key Rule

Settings determine which placement domains exist. Sessions own who is doing what. The host owns routing and dispatch. Scene adapters and UI should resolve dependencies through the session/context path rather than finding private nodes by hard-coded paths.


Related Guides

Source

docs/v6-0/guides/session-ownership.md

Plugin docs root:gdscript/plugins/grid_placement_dev/docs