Grid Placement

GridPlacement Migration Guide: 5.1 GDScript → 6.0 C#

Purpose: Help GDScript users understand how 5.1 flows map to 6.0 C# services. Audience: Developers migrating from GDScript 5.1 to C# 6.0. Status: Living document; updated as 6.0 APIs stabilize.

Canonical mapping source

  • The canonical 5.x ↔ 6.0 type/class mapping table is maintained in:
    • docs/6.0/Core/GRIDBUILDING_CLASS_MAPPING.md
  • This document focuses on workflow-level guidance (how to translate common behaviours and usage patterns), not on being the authoritative mapping list.

Canonical parity contract (6.0 ↔ 5.1): docs/Parity/PARITY_CONTRACT_6_0_TO_5_1.md


1. Overview

GridPlacement 6.0 represents a significant architectural shift:

  • 5.1: GDScript-based with node-centric design and injector pattern
  • 6.0: C#-based with service-oriented architecture and dependency injection

This guide maps common 5.1 workflows to their 6.0 equivalents.


2. Core Architectural Changes

2.1 From Nodes to Services

5.1 Pattern: Everything is a Node with signals

1
2
3
4
# 5.1 GDScript
var placement_system = get_node("PlacementSystem")
placement_system.placement_completed.connect(_on_placement_completed)
var result = placement_system.try_place(cell, building_id)

6.0 Pattern: Services with interfaces

1
2
3
4
5
6
7
// 6.0 C#
var placementService = ServiceRegistry.Get<IPlacementService>();
var result = await placementService.TryPlaceAsync(new PlacementRequest 
{
    Cell = cell,
    BuildingId = buildingId
});

2.2 From Global Singletons to Service Registry

5.1 Pattern: Autoload singletons

1
2
3
# 5.1 GDScript
var config = GBGlobals.get_config()
var grid = GBGlobals.get_grid_system()

6.0 Pattern: Service Registry

1
2
3
// 6.0 C#
var config = ServiceRegistry.Get<IGridConfiguration>();
var gridSystem = ServiceRegistry.Get<IGridSystem>();

2.3 From Dictionaries to Typed Classes

5.1 Pattern: Dictionary-based data

1
2
3
4
5
6
7
# 5.1 GDScript
var request = {
    "cell": Vector2i(0, 0),
    "building_id": "house",
    "rotation": 0,
    "user_id": 1
}

6.0 Pattern: Strongly-typed classes

1
2
3
4
5
6
7
8
// 6.0 C#
var request = new PlacementRequest
{
    Cell = new Vector2I(0, 0),
    BuildingId = "house",
    Rotation = 0,
    UserId = 1
};

3. Common Workflow Mappings

3.1 Building Placement

5.1 Workflow

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Get targeting system
var targeting = $GridTargetingSystem
var target_cell = targeting.get_target_cell()

# Validate placement
var placement = $PlacementSystem
if placement.can_place(target_cell, "house"):
    var result = placement.try_place(target_cell, "house")
    if result.status == "ok":
        print("Placed successfully at ", result.cell)

6.0 Workflow

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Get services
var targetingService = ServiceRegistry.Get<IGridTargetingService>();
var placementService = ServiceRegistry.Get<IPlacementService>();

// Get target
var targetCell = targetingService.GetTargetCell();

// Validate and place
var validation = await placementService.ValidatePlacementAsync("house", targetCell);
if (validation.IsValid)
{
    var result = await placementService.TryPlaceAsync(new PlacementRequest
    {
        Cell = targetCell,
        BuildingId = "house"
    });
    
    if (result.Success)
    {
        GD.Print($"Placed successfully at {result.Cell}");
    }
}

3.2 Building Manipulation (Move)

5.1 Workflow

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Start move
var manipulation = $ManipulationSystem
manipulation.start_move(source_cell)

# ... user selects target ...

# Confirm move
var result = manipulation.confirm_manipulation()
if result.status == "ok":
    print("Moved successfully")

6.0 Workflow

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Get service
var ManipulationService2D = ServiceRegistry.Get<IManipulationService>();

// Start move
var moveHandle = await ManipulationService2D.BeginMoveAsync(sourceCell);

// ... user selects target ...

// Confirm move
var result = await moveHandle.ConfirmAsync(targetCell);
if (result.Success)
{
    GD.Print("Moved successfully");
}

3.3 Grid Targeting

5.1 Workflow

1
2
3
4
5
6
7
# Connect to targeting signals
var targeting = $GridTargetingSystem
targeting.target_changed.connect(_on_target_changed)
targeting.enabled = true

func _on_target_changed(cell: Vector2i):
    print("Target changed to ", cell)

6.0 Workflow

1
2
3
4
5
6
7
8
9
// Subscribe to events
var targetingService = ServiceRegistry.Get<IGridTargetingService>();
targetingService.TargetChanged += OnTargetChanged;
targetingService.Enable();

void OnTargetChanged(Vector2I cell)
{
    GD.Print($"Target changed to {cell}");
}

3.4 Configuration Access

5.1 Workflow

1
2
3
4
# Access configuration
var config = GBGlobals.get_config()
var grid_size = config.grid_size
var cell_size = config.cell_size

6.0 Workflow

1
2
3
4
// Access configuration service
var config = ServiceRegistry.Get<IGridConfiguration>();
var gridSize = config.GridSize;
var cellSize = config.CellSize;

3.5 Grid Positioner / Targeting Controller

5.1 Workflow (Refactored)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 5.1 GridPositioner2D (The canonical node)
# acts as a Thin Controller using a pure Evaluator
var positioner = $GridPositioner2D

# Connect to cursor state signals (Indicator Sink pattern)
positioner.cursor_moved.connect(_on_cursor_moved)
positioner.cursor_visibility_changed.connect(_on_cursor_vis_changed)

func _on_cursor_moved(tile_pos: Vector2i):
    print("Cursor at: ", tile_pos)

Naming parity note (5.1 ↔ 6.0):

  • The pure, engine-agnostic logic behind the positioner should be treated as a service.
  • Canonical name: PositionerService (matches C# 6.0 naming).
  • Godot Nodes remain adapters/controllers (e.g. GridPositioner2D, TargetingController2D).

6.0 Workflow

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 6.0 TargetingController2D + TargetSensor2D
var controller = ServiceRegistry.Get<ICursorController>();

// Subscribe to events
controller.CursorMoved += OnCursorMoved;

void OnCursorMoved(CoreVector2I tilePos)
{
    GD.Print($"Cursor at: {tilePos}");
}

6.0 → 5.1 Mapping (Reverse Lookup)

If you are reading 6.0 C# Godot integration code and want the closest 5.1 GDScript equivalent for behaviour comparison:

  • 6.0 GridBuilding.Godot.Targeting.TargetingController2D maps to 5.1 GridPositioner2D (GDScript, Node2D).

Rules:

  • This mapping is for human understanding and migration guidance.
  • Parity is NOT required for Nodes and Resources:
    • Node/Resource names can drift without blocking parity.
    • Parity tooling should focus on Core/service/domain behaviour and typed APIs.

Key Architectural Mapping:

  • 5.1 GridPositioner2D6.0 TargetingController2D (Controller Node)
  • 5.1 GridTargetingControllerEvaluator6.0 Controller Logic (Pure Decision Logic)
  • 5.1 TargetingService6.0 GridTargetingService + TargetSensor2D (State + Sensing)

For 6.0 implementations that need a dedicated positioner logic layer, prefer:

  • PositionerService (pure logic/state; service pattern)
  • A thin Node/controller that delegates to it

4. Signal to Event Mapping

5.1 Signal6.0 Event
target_changed(cell)TargetChanged(Vector2I cell)
placement_requested(data)PlacementRequested(PlacementRequest)
placement_completed(result)PlacementCompleted(PlacementResult)
manipulation_started(data)ManipulationStarted(ManipulationData)
manipulation_completed(result)ManipulationCompleted(ManipulationResult)
cursor_moved(tile_pos)CursorMoved(CoreVector2I tilePos)
cursor_visibility_changed(visible)CursorVisibilityChanged(bool visible)

5. API Type Mapping

5.1 Type6.0 Type
Dictionary (placement request)PlacementRequest
Dictionary (placement result)PlacementResult
Dictionary (manipulation data)ManipulationData
Vector2iVector2I
Node (systems)IService interface
Autoload singletonService in ServiceRegistry

6. Service Interface Reference

6.1 IPlacementService

Purpose: Handle building placement operations

Methods:

  • ValidatePlacementAsync(string buildingId, Vector2I cell)ValidationResult
  • TryPlaceAsync(PlacementRequest request)PlacementResult
  • RemoveBuildingAsync(Vector2I cell)bool

Events:

  • PlacementRequested
  • PlacementCompleted
  • PlacementFailed

6.2 IGridTargetingService

Purpose: Handle grid cell targeting and cursor

Methods:

  • GetTargetCell()Vector2I?
  • SetTargetCell(Vector2I cell)void
  • ClearTarget()void
  • Enable() / Disable()void

Events:

  • TargetChanged(Vector2I cell)
  • TargetCleared()

6.3 IManipulationService

Purpose: Handle building manipulation operations

Methods:

  • BeginMoveAsync(Vector2I sourceCell)IManipulationHandle
  • BeginRotateAsync(Vector2I cell)IManipulationHandle
  • CancelManipulation()void

Events:

  • ManipulationStarted
  • ManipulationCompleted
  • ManipulationCancelled

6.4 IGridSystem

Purpose: Core grid management and queries

Methods:

  • GetBuildingAt(Vector2I cell)IBuilding?
  • GetOccupiedCells()IEnumerable<Vector2I>
  • IsValidCell(Vector2I cell)bool
  • WorldToGrid(Vector2 worldPos)Vector2I
  • GridToWorld(Vector2I cell)Vector2

7. Adapter Pattern (Bridging GDScript to C#)

For gradual migration, you can use adapters:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// GDScript-compatible adapter node
public partial class PlacementSystemAdapter : Node
{
    [Signal]
    public delegate void PlacementCompletedEventHandler(Dictionary result);
    
    private IPlacementService _service;
    
    public override void _Ready()
    {
        _service = ServiceRegistry.Get<IPlacementService>();
        _service.PlacementCompleted += OnServicePlacementCompleted;
    }
    
    private void OnServicePlacementCompleted(PlacementResult result)
    {
        // Convert C# result to GDScript Dictionary
        var dict = new Dictionary
        {
            ["status"] = "ok",
            ["cell"] = result.Cell,
            ["building_id"] = result.BuildingId
        };
        EmitSignal(SignalName.PlacementCompleted, dict);
    }
    
    public Dictionary TryPlace(Vector2I cell, string buildingId)
    {
        var request = new PlacementRequest 
        { 
            Cell = cell, 
            BuildingId = buildingId 
        };
        var result = _service.TryPlaceAsync(request).Result;
        
        return new Dictionary
        {
            ["status"] = result.Success ? "ok" : "error",
            ["cell"] = result.Cell,
            ["error"] = result.ErrorMessage
        };
    }
}

This allows GDScript code to continue working while gradually transitioning to C#.


8. Migration Strategy

  1. Understand the Service Model

    • Read 6.0 architecture documentation
    • Review service interfaces and responsibilities
  2. Identify Touch Points

    • List all places your code interacts with 5.1 systems
    • Map each interaction to corresponding 6.0 service
  3. Use Adapters Initially

    • Create adapter nodes that bridge GDScript to C# services
    • Keep existing GDScript UI/gameplay code working
  4. Gradually Migrate

    • Convert one system at a time (e.g., placement, then targeting)
    • Test thoroughly after each conversion
    • Remove adapters once fully migrated to C#
  5. Leverage Type Safety

    • Replace Dictionary-based data with typed classes
    • Use compile-time checking to catch errors early

9. Common Pitfalls

9.1 Async/Await

Problem: 6.0 uses async methods; GDScript has no async/await

Solution: Use adapters that handle awaiting for you, or use .Result carefully

1
2
3
4
5
6
7
// In adapter for GDScript
public Dictionary TryPlace(Vector2I cell, string buildingId)
{
    // Block until complete (acceptable in adapters)
    var result = _service.TryPlaceAsync(request).Result;
    return ConvertToDict(result);
}

9.2 Null Handling

Problem: C# nullable reference types vs GDScript null

Solution: Check nullability explicitly

1
2
3
4
5
var building = gridSystem.GetBuildingAt(cell);
if (building != null)
{
    // Safe to use building
}

9.3 Signal vs Event Lifecycle

Problem: GDScript signals auto-disconnect; C# events require manual unsubscription

Solution: Always unsubscribe in _ExitTree or disposal

1
2
3
4
5
6
7
public override void _ExitTree()
{
    if (_service != null)
    {
        _service.PlacementCompleted -= OnPlacementCompleted;
    }
}

10. Testing Strategy

Unit Tests

5.1 GdUnit tests can be replaced with C# xUnit tests:

1
2
3
4
5
# 5.1 GdUnit
func test_placement_validation():
    var placement = PlacementSystem.new()
    var result = placement.can_place(Vector2i.ZERO, "house")
    assert_true(result)
1
2
3
4
5
6
7
8
// 6.0 xUnit
[Fact]
public async Task PlacementValidation_ValidCell_ReturnsTrue()
{
    var placementService = CreateService();
    var result = await placementService.ValidatePlacementAsync("house", Vector2I.Zero);
    Assert.True(result.IsValid);
}

Integration Tests

Use the new IntegrationHarness pattern with C# services.


11. Resources


12. Support Timeline

  • 5.1: Maintenance mode; critical bugs only
  • 6.0: Active development; new features
  • Migration support: Available through 2025

Last Updated: 2025-12-11
Version: 5.1 → 6.0
Status: Draft (updates as 6.0 stabilizes)