User Scope Roots (C#)
User Scope Roots (C#)
This guide defines the C# foundation for integrating GridPlacement 6.0 into a game that may already have its own “user scope” or “user scope” orchestration.
Problem statement
GridPlacement needs:
- A per-user service scope (services and state that belong to one user/user).
- A per-user input provider (the single place that consumes raw engine input and routes it into Core).
Final games often already have:
- One
UserScopeRoot(or similar) that owns service wiring for all systems.
So GridPlacement must support:
- A standalone scope root for quick-start demos.
- An adapter mode that plugs into a game’s existing scope root.
Canonical concepts
User identity policy (6.0)
- Canonical
UserIdfor 6.0 integration isGameUserSessions.Core.UserId. - Any internal GridPlacement/Core-only identity wrapper (e.g.
GBUserId) is an internal implementation detail and must not leak into public APIs or engine adapters.
GBUserScopeRoot (plugin-provided)
A Godot C# node that acts as the scope origin for GridPlacement.
This is the single required node per user/context for 6.0 C# integration.
Responsibilities:
- Create or receive a
ServiceRegistryfor this user. - Register GridPlacement services into that registry.
- Own the single Godot input capture point (
_UnhandledInput) for this user. - Ensure exactly one Core input pipeline exists for this user.
Tested behavior contract (enforced by GoDotTest in the 6.0 demo harness):
Configure(ServiceRegistry registry)- must throw when
registryisnull. - must throw if called more than once.
- when configured, must use the injected registry instance as
GBUserScopeRoot.Registry.
- must throw when
IUserScoperegistration- if no
IUserScopeis registered, must register exactly one. - if an
IUserScopeis already registered, must not overwrite it.
- if no
IUserScopeProfileProviderusage- if present, must use it to select
UserScopeProfile. - if
UserScopeProfile.UserIdis empty, must generate a new non-empty user id. - must prefer
UserScopeProfile.Namewhen provided (fall back toUserScopeName).
- if present, must use it to select
Non-responsibilities:
- Owning the entire game’s service graph.
- Acting as a global singleton across players.
GBUserScopeRootAdapter (plugin-provided adapter)
A small adapter surface intended to sit under a game’s existing UserScopeRoot.
Responsibilities:
- Bridge between a game’s scope root and GridPlacement’s expectations.
- Provide a stable integration surface for:
- service registry access
- per-user input routing
- lifecycle events
Minimal contract:
GBUserScopeRoot.Configure(ServiceRegistry registry)
Non-responsibilities:
- Defining the game’s overall scope model.
Game-owned UserScopeRoot (game-provided)
A game’s primary scope root node that orchestrates multiple systems:
- Inventory
- Abilities
- UI
- GridPlacement
- etc.
GridPlacement should integrate into this root rather than forcing a new one.
Integration options (recommended ordering)
Option A — Standalone plugin scope (fastest)
Use GBUserScopeRoot directly.
Best for:
- demos
- prototypes
- single-user setups
- validating GridPlacement in isolation
Done when:
GBUserScopeRootregisters services and owns the per-user input capture.
Option B — Adapter into game scope (recommended for production)
Game owns UserScopeRoot. GridPlacement provides GBUserScopeRootAdapter that is attached as a child.
Best for:
- production games
- multiplayer
- teams with existing DI/service orchestration
Done when:
- Game root calls an adapter method such as:
GBUserScopeRoot.Configure(ServiceRegistry registry)
and GridPlacement registers its services into the provided registry.
Option C — Game implements the interface directly (most integrated)
Game scope root implements an interface that GridPlacement can consume.
Example interface shape:
IUserScopeRootServiceRegistry Registry { get; }Node ScopeNode { get; }int? PlayerId { get; }
Best for:
- larger projects that want to avoid extra adapter nodes
Input routing per user
Rule: one input provider per user
Within a single user scope, there should be exactly one component that:
- consumes raw
InputEvent - translates to Core
InputEventData - calls
CoreInputService.Handle(...)
In the minimal-node setup, that component is GBUserScopeRoot.
Where the input provider lives
For 6.0 C#-heavy architecture:
- Input provider is a per-scope node created/owned by the scope origin.
- The provider resolves the registry-owned Core pipeline:
CoreInputService- interpreters (
PositioningInputInterpreter,PlacementInputInterpreter,ManipulationInputInterpreter)
Consumers (e.g. targeting/cursor controller, placement UI) subscribe to interpreter events.
Practical wiring checklist
- Ensure a scope origin exists per user/user.
- Ensure that scope origin creates/owns a
ServiceRegistry. - Register GridPlacement services into that registry.
- Ensure exactly one input provider node per scope.
- Ensure gameplay nodes are consumers (subscribe), not raw input owners.
Notes on parity with GDScript
- GDScript parity for cursor/positioning should use
GridPositioner2Das the consumer of positioning intents. - C# parity uses the equivalent targeting/cursor controller node as the consumer.
Where the contract is tested
The integration contract above is enforced in the demo C# test harness:
demos/grid_building_dev/godot_cs/InputPipelineSmokeGoDotTests.csdemos/grid_building_dev/godot_cs/UserScopeRootIntegrationBehaviorTests.cs