Architecture overview (5.0)

Mental model for GridBuilding 5.0: node-first integration, injector/container era, and state-first wiring.

sort Weight: 10

5.0 is best understood as a node-first plugin with a composition-container / injector integration style.

Boundary (5.0)

  • Godot nodes + scripts
    • Own most runtime behavior.
    • Public API is primarily node exports + signals.
    • Handle keyboard/mouse input through Godot’s input system.
  • Systems
    • Manage all input and functionality except keyboard/mouse input.
    • Process building, manipulation, targeting, and placement logic.
    • Coordinate state changes and workflow orchestration.
  • Composition container + injector
    • Wiring layer that assigns settings/services/state into nodes.
  • State-first coordination
    • All signals are delivered through state objects which also manage the active data.
    • State objects act as both data containers and signal emitters.
    • 5.0 does not separate event payloads from state management.

High-level shape

1
2
3
4
5
6
7
Godot nodes / scripts (keyboard/mouse input)
Systems (building/manipulation/targeting logic)
Composition container + injector
Backend state + (stable) bridge patterns

What 5.0 optimized for

  • Fast adoption: add nodes to a scene, connect signals, configure exports.
  • Direct debuggability in the editor: follow node references in the inspector.
  • Clear input separation: keyboard/mouse handled by Godot, building logic by systems.

Input handling in 5.0

Keyboard/Mouse Input (Godot Nodes)

  • Handled through Godot’s standard input system (_input, _unhandled_input)
  • Processed by scene nodes using Input.is_action_pressed()
  • Converted to system calls (e.g., building_system.start_placement())
  • UI elements handle their own input through Godot controls

Building/Manipulation Input (Systems)

  • BuildingSystem: Processes placement requests, validation, and execution
  • ManipulationSystem: Handles object selection, movement, and deletion
  • GridTargetingSystem: Manages grid targeting and coordinate conversion
  • IndicatorManager: Controls visual feedback and preview indicators

Input Flow Example

1
2
3
4
5
6
7
8
9
User presses "build_confirm" action
Scene node receives _input event
Node calls building_system.confirm_placement()
BuildingSystem validates and executes placement
State updates and signals emitted

This separation allows:

  • Standard Godot input mapping and customization
  • Clean testing of system logic without input handling
  • Reusable building logic across different input schemes
  • Easy integration with different UI frameworks

State Objects in 5.0

How 5.0 State Objects Work

In 5.0, state objects serve as both data containers and signal emitters:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Example 5.0 TargetingState
class_name TargetingState
extends RefCounted
signal target_changed(new_target: Vector2i)
signal target_valid_changed(is_valid: bool)

var current_target: Vector2i = Vector2i.INVALID
var is_target_valid: bool = false

func update_target(new_target: Vector2i):
    current_target = new_target
    is_target_valid = _validate_target(new_target)
    target_changed.emit(current_target)  # Emits signal
    # Listeners can access this.current_target directly

Signal Consumption Pattern

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# UI component listening to 5.0 state
func _ready():
    targeting_state.target_changed.connect(_on_target_changed)
    targeting_state.target_valid_changed.connect(_on_validity_changed)

func _on_target_changed(new_target: Vector2i):
    # Can access live state directly
    var is_valid = targeting_state.is_target_valid
    update_preview(new_target, is_valid)

func _on_validity_changed(is_valid: bool):
    # Can access current target from state object
    var current = targeting_state.current_target
    update_validation_ui(current, is_valid)

Benefits of 5.0 Approach

  • Simple to understand: State and signals in one place
  • Direct access: Listeners can read any state property
  • Immediate consistency: State changes and signals happen together
  • Easy debugging: State objects are inspectable in the debugger

Limitations of 5.0 Approach

  • Tight coupling: Listeners depend on state object structure
  • Testing complexity: Need to mock entire state objects
  • Privacy concerns: All state data is publicly accessible
  • Signal payload ambiguity: Unclear what data the signal carries

What 5.0 did not standardize (later improved in 5.1/6.0)

  • A strict, explicit split between:
    • service-owned state
    • event payloads
    • diagnostics snapshots

Signal Delivery: 5.0 vs 5.1/6.0

5.0 Approach - State Objects as Signal Emitters:

1
2
3
4
5
6
7
8
State Object (e.g., TargetingState)
├── Manages active targeting data
├── Emits signals when data changes
└── Listeners receive state references directly

Example:
targeting_state.target_changed.emit(new_position)
listeners receive both signal AND state object reference

5.1/6.0 Approach - Separated Concerns:

1
2
3
4
5
6
7
8
Internal Service State (private)
├── Manages active data internally
├── Emits EventData payloads via event bus
└── Provides snapshot objects for diagnostics

Example:
event_bus.target_changed.emit(TargetEventData.new(position))
listeners receive lightweight EventData only

Key Differences

5.0 Pattern:

  • State objects are public and emit signals
  • Signals carry references to state objects
  • Listeners access live state directly
  • No separation between data and events

5.1/6.0 Pattern:

  • Internal state stays private to services
  • Event bus delivers EventData payloads
  • Snapshot objects provide diagnostic access
  • Clear separation between state, events, and diagnostics

5.1/6.0 improve this by introducing a clearer boundary where:

  • signals/events carry small payload objects (EventData)
  • snapshots exist for diagnostics/tests
  • internal state stays private to services
  • event bus/presenter patterns coordinate communication

5.0 projects typically wire GridBuilding through a composition container and an injector pattern.

Critical Integration: All 5.0 systems require a properly configured LevelContext to bind grid operations to specific TileMapLayers and object containers. Without LevelContext, targeting and placement systems will not function.

The intent (5.0)

  • Centralize dependency wiring (settings, state, references) so scene nodes do not hand-wire everything.
  • Allow test environments to stand up “the whole stack” by instantiating a known scene.

Common 5.0 wiring pattern

1
2
3
4
5
6
7
8
9
Scene root (keyboard/mouse input handling)
GBInjectorSystem / composition container
Systems (BuildingSystem, ManipulationSystem, GridTargetingSystem, IndicatorManager)
  - Process all building/manipulation/targeting functionality
  - Manage workflow state and orchestration
Shared state objects (mode/targeting/building/manipulation)

Testing implication (5.0)

Some features require a full environment scene to exist (not a minimal unit harness).

Example from the 5.0-era manipulation architecture notes:

  • Minimal collision test scenes are good for geometry/collision math.
  • But manipulation workflow tests require an “all systems” environment so that:
    • ManipulationSystem exists
    • ManipulationParent exists
    • targeting/building/indicator systems are present

This is one of the reasons the later 5.1/6.0 architecture tries to push more logic into testable services and shrink the engine glue surface.