Grid Placement

TargetSensor2D (6.0) — Target Detection Plan

Goals

  • Replace the old TargetingShapeCast2D* nodes with a smaller, clearer responsibility split:
    • Core (IGridTargetingService, state, rules): owns behaviour, pathfinding, and placement validation.
    • Godot Sensor (TargetSensor2D): owns physics queries and world↔grid conversion.
    • Controller (TargetingController2D): owns input + cursor presentation, and uses the sensor & core services.
  • Keep Godot-side code as a thin adapter: physics + coordinate math, delegating all “is this tile/placeable allowed” rules to core services.

Current State (Phase 1 — Minimal Adapter)

Today, TargetSensor2D only implements a minimal contract:

  • Implements ITargetSensor:
    • GetTargetAt(Vector2 worldPosition)
      • Converts world → CoreVector2I using CellSize and Origin.
      • Returns (gridPos, true) — every tile is considered valid for now.
    • GetWorldPosition(CoreVector2I gridPosition)
      • Converts grid → world tile center.
  • Registers itself into the 6.0 ServiceRegistry on _Ready so that TargetingController2D can resolve ITargetSensor with no manual wiring in each scene.

This is enough to:

  • Drive the cursor via the controller.
  • Write tests that treat the sensor as a pure coordinate adapter.

It does not yet perform physics casting, collision-mask filtering, or metadata-based target checks.


Planned Behaviour (Phase 2 — Physics + Filtering)

We want TargetSensor2D to eventually take over the sensor part of what TargetingShapeCast2D did, without re-introducing monolithic logic.

Responsibilities for Phase 2

  • Physics detection
    • Use either:
      • A ShapeCast2D-based node (change base to ShapeCast2D), or
      • PhysicsDirectSpaceState2D.IntersectShape / IntersectPoint under the hood.
    • Export collision-related settings:
      • CollisionMask (layers to consider).
      • MaxResults / MaxDistance (soft limits for queries).
  • Grid mapping
    • Continue to convert the hit position → CoreVector2I via CellSize and Origin.
  • Candidate filtering
    • From the physics hits, select candidates by:
      • Distance / MaxDistance.
      • Layer mask (CollisionMask).
      • Optional metadata / components:
        • In legacy code this was often “does this node represent a manipulable target or a relevant tile?”
        • In 6.0 we want to express that as filters/validators rather than ad-hoc checks baked into the node.

Interaction With Core Services

  • TargetSensor2D does not decide game rules.
  • After computing (gridPos, candidateNode), it can:
    • Call into IGridTargetingService where appropriate (e.g. ValidateTargetAtPosition(gridPos, node)), or
    • Expose enough information for callers (controller / validators) to ask core services themselves.
  • The final “is this target allowed?” answer remains a core-service responsibility.

Resulting GetTargetAt shape (conceptual):

  1. Receive worldPosition from controller.
  2. Perform physics query around that position using collision masks.
  3. Pick best candidate (or none) based on distance and filters.
  4. Map candidate position → CoreVector2I.
  5. Optionally call core service to pre-validate.
  6. Return (gridPos, isValid); the controller then updates state and world position using GetWorldPosition when appropriate.

Metadata & “Manipulatable” Objects

Legacy TargetingShapeCast2D logic inspected colliders to decide if they were manipulatable or should be ignored. In 6.0, we plan to express this via filters and validators instead of hard-coding types:

  • Reuse / adapt existing interfaces from the legacy refactor like ITargetingFilter and ITargetingValidator where appropriate.
  • Allow TargetSensor2D to host a small list of validators that can be configured per scene or via code, e.g.:
    • “Only accept nodes with a specific script or metadata key.”
    • “Ignore decorative tiles / background layers.”
  • Keep the sensor focused on wiring these validators + core services rather than knowing about specific gameplay features.

Test Strategy

  • Core tests (e.g. GridTargetingServiceTests):
    • Own grid graph, path shape, valid/invalid tile rules, and distances.
  • Godot tests (e.g. GridTargetingControllerTest and future sensor tests):
    • Stay as thin wiring smokes:
      • Verify that:
        • The controller resolves ITargetSensor from the registry.
        • Mouse hover goes through sensor → state → controller position.
        • Collision mask / filtering is honored in simple scenes.
    • Do not attempt to recreate all core targeting rules — just ensure Godot adapters behave and are correctly wired.

Open Questions / Next Iterations

  • Should TargetSensor2D inherit from Node2D (current) or ShapeCast2D (to reuse Godot’s built-in casting API)?
  • Where is the cleanest place to attach ITargetingFilter / ITargetingValidator instances: directly on the sensor node, or via a separate Godot component that also talks to core services?
  • How much of the legacy debug visualization (drawn shapes, labels) should be reintroduced, and where?

These are left intentionally open so we can evolve the design as we port more targeting behaviour into the 6.0 architecture.