91 lines
7.1 KiB
Markdown
91 lines
7.1 KiB
Markdown
# NEO-20 — Implementation plan
|
|
|
|
## Story reference
|
|
|
|
| Field | Value |
|
|
|--------|--------|
|
|
| **Key** | NEO-20 |
|
|
| **Title** | E1.M2: Click-through input — occluder geometry must not block ground-pick raycasts |
|
|
| **Linear** | [NEO-20](https://linear.app/neon-sprawl/issue/NEO-20) |
|
|
| **Parent** | [Epic 1 — Core Player Runtime](https://linear.app/neon-sprawl/project/epic-1-core-player-runtime-client-controls-character-loop-66bd590cd016) |
|
|
| **Module** | [E1.M2 — IsometricCameraController](../decomposition/modules/E1_M2_IsometricCameraController.md); umbrella [E1.M2](https://linear.app/neon-sprawl/project/e1m2-isometriccameracontroller-deac8ef10395) |
|
|
|
|
## Goal, scope, and out-of-scope
|
|
|
|
**Goal:** Ensure geometry tagged `"occluder"` never intercepts click-to-move ground picks, so clicking the ground behind faded or opaque occluders still yields the intended world target.
|
|
|
|
**In scope**
|
|
|
|
- Inspect `client/scripts/ground_pick.gd` and preserve its current walkable-surface rules while making occluders transparent to picking.
|
|
- Make the exclusion unconditional: the click ray must ignore occluders whether or not NEO-17's `OcclusionPolicy` is currently fading them.
|
|
- Keep non-occluder click targeting behavior unchanged on walkable surfaces.
|
|
- Document the chosen approach in this implementation plan without introducing unexplained collision-layer constants.
|
|
|
|
**Out of scope** (per Linear)
|
|
|
|
- Changing the movement or navigation systems.
|
|
- Server-side awareness of occluder state.
|
|
- Input pass-through for geometry that is not tagged `"occluder"`.
|
|
|
|
## Acceptance criteria checklist
|
|
|
|
- [x] Clicking on the ground directly behind an occluder (faded or opaque) issues a valid move command to that position.
|
|
- [x] Clicking on a non-occluder surface behaves as before.
|
|
- [x] No new collision-layer magic numbers introduced; the approach is documented in the implementation plan.
|
|
|
|
## Technical approach
|
|
|
|
### Preferred approach: explicit occluder skip in `ground_pick.gd`
|
|
|
|
Keep the existing stepped raycast loop in `ground_pick.gd`, but treat `"occluder"` hits as a first-class skip case before the walkable-surface check. When the ray hits an occluder, advance the ray origin a small distance past the hit point along the ray direction and continue the loop, similar to the existing steep-surface pass-through behavior. This keeps the fix local to input picking, preserves NEO-17's scene convention (`"occluder"` group), and avoids changing the collision-layer contract that the occlusion system already uses.
|
|
|
|
### Why not a dedicated occluder collision layer first
|
|
|
|
Moving occluders to a new layer would also require updating the occlusion raycast mask in `isometric_follow_camera.gd`, plus scene data changes for every occluder. That is a broader contract change than this story needs and would add a new physics-layer convention that the acceptance criteria explicitly ask us to document carefully. Group-based exclusion in the pick loop is smaller, easier to verify, and reuses the convention already established by NEO-17.
|
|
|
|
### Step-by-step implementation
|
|
|
|
1. Add a small occluder helper path in `ground_pick.gd` (for example `_collider_is_occluder`) that walks collider ancestry the same way `_collider_is_walkable` does.
|
|
2. Introduce a named pass-through distance constant for occluder skips rather than an inline float literal, mirroring the existing `STEEP_PICK_THROUGH` style so the code remains self-documenting.
|
|
3. In the ray loop, if the hit collider is an occluder, move `from` slightly forward along the ray and continue without selecting or rejecting the click.
|
|
4. Leave the current walkable / wall / steep-surface handling intact after the occluder check so non-occluder behavior stays stable.
|
|
5. Extend the unit tests around collider ancestry helpers and document the remaining full click-through behavior as manual verification.
|
|
|
|
## Decisions
|
|
|
|
| Topic | Choice | Rationale |
|
|
|-------|--------|-----------|
|
|
| **Occluder exclusion mechanism** | Skip `"occluder"` hits in the ground-pick loop | Smallest change set; preserves NEO-17's established group tag; no new collision-layer convention needed. |
|
|
| **Collision-layer strategy** | Do not add or repurpose layers for this story | Avoids extra scene churn and occlusion-ray regressions; satisfies the AC against unexplained magic numbers. |
|
|
| **Scope boundary** | Fix only click picking, not movement or nav | Matches the Linear issue scope and keeps this story focused on input selection correctness. |
|
|
| **Debug warning on occluder skip** | No `push_warning` — silent pass-through | An occluder skip is intended, non-exceptional behavior; a warning would flood logs during normal play. Unlike the occlusion policy's skip of non-`StandardMaterial3D` surfaces (which signals a content mismatch), advancing past an occluder is the correct outcome. |
|
|
|
|
## Files to add
|
|
|
|
None. The expected change is a focused update to existing pick logic, tests, and supporting docs rather than a new script or scene resource.
|
|
|
|
## Files to modify
|
|
|
|
| Path | Rationale |
|
|
|------|-----------|
|
|
| `client/scripts/ground_pick.gd` | Add unconditional occluder-skip handling to the stepped raycast loop without changing existing walkable filtering semantics. |
|
|
| `client/test/ground_pick_test.gd` | Add focused tests for occluder ancestry detection and any extracted helper logic that can be verified headlessly. |
|
|
| `client/README.md` | Refresh the input-picking documentation so the current click-through behavior and its limits are recorded near other client runtime notes. |
|
|
| `docs/decomposition/modules/E1_M2_IsometricCameraController.md` | Update the module backlog / implementation snapshot if the final implementation adds a lasting input contract around occluders for this camera slice. |
|
|
|
|
## Tests
|
|
|
|
| File | Coverage |
|
|
|------|----------|
|
|
| `client/test/ground_pick_test.gd` | **Change.** Add helper-level tests confirming that a collider under an `"occluder"` ancestor is recognized as skippable, while non-occluder colliders are not. If logic is extracted into a pure helper, cover the helper directly. |
|
|
|
|
**Manual verification:** Run the client and click the ground behind the `Obstacle` in `client/scenes/main.tscn` while it is opaque and while it is faded by the camera occlusion logic. Confirm the player receives a valid move target behind the obstacle, and confirm clicks on normal non-occluder walkable surfaces still behave as before.
|
|
|
|
## Open questions / risks
|
|
|
|
**Resolved during implementation:**
|
|
|
|
- **Segment cap:** `MAX_PICK_SEGMENTS = 24` leaves ample headroom in the prototype with a single `Obstacle`. Each occluder skip consumes one iteration; the remaining segments are still available for steep-surface pass-through. No cap change needed.
|
|
- **Pass-through epsilon:** Named constant `OCCLUDER_PICK_THROUGH = 0.14` introduced (same value as `STEEP_PICK_THROUGH`; separate name for self-documentation since the two cases are conceptually distinct). Chosen to match the already-validated steep-surface nudge distance.
|
|
- **Module doc update:** E1_M2_IsometricCameraController.md updated with the NEO-20 click-through contract (group-based exclusion, no new collision layer) to record the shared `"occluder"` group convention for future readers.
|