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.
- Core (
- 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 →
CoreVector2IusingCellSizeandOrigin. - Returns
(gridPos, true)— every tile is considered valid for now.
- Converts world →
GetWorldPosition(CoreVector2I gridPosition)- Converts grid → world tile center.
- Registers itself into the 6.0
ServiceRegistryon_Readyso thatTargetingController2Dcan resolveITargetSensorwith 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 toShapeCast2D), or PhysicsDirectSpaceState2D.IntersectShape/IntersectPointunder the hood.
- A
- Export collision-related settings:
CollisionMask(layers to consider).MaxResults/MaxDistance(soft limits for queries).
- Use either:
- Grid mapping
- Continue to convert the hit position →
CoreVector2IviaCellSizeandOrigin.
- Continue to convert the hit position →
- 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.
- Distance /
- From the physics hits, select candidates by:
Interaction With Core Services
TargetSensor2Ddoes not decide game rules.- After computing
(gridPos, candidateNode), it can:- Call into
IGridTargetingServicewhere appropriate (e.g.ValidateTargetAtPosition(gridPos, node)), or - Expose enough information for callers (controller / validators) to ask core services themselves.
- Call into
- The final “is this target allowed?” answer remains a core-service responsibility.
Resulting GetTargetAt shape (conceptual):
- Receive
worldPositionfrom controller. - Perform physics query around that position using collision masks.
- Pick best candidate (or none) based on distance and filters.
- Map candidate position →
CoreVector2I. - Optionally call core service to pre-validate.
- Return
(gridPos, isValid); the controller then updates state and world position usingGetWorldPositionwhen 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
ITargetingFilterandITargetingValidatorwhere appropriate. - Allow
TargetSensor2Dto 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.
GridTargetingControllerTestand future sensor tests):- Stay as thin wiring smokes:
- Verify that:
- The controller resolves
ITargetSensorfrom the registry. - Mouse hover goes through sensor → state → controller position.
- Collision mask / filtering is honored in simple scenes.
- The controller resolves
- Verify that:
- Do not attempt to recreate all core targeting rules — just ensure Godot adapters behave and are correctly wired.
- Stay as thin wiring smokes:
Open Questions / Next Iterations
- Should
TargetSensor2Dinherit fromNode2D(current) orShapeCast2D(to reuse Godot’s built-in casting API)? - Where is the cleanest place to attach
ITargetingFilter/ITargetingValidatorinstances: 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.