diff --git a/docs/superpowers/specs/2026-05-15-phase-b7-target-indicator-design.md b/docs/superpowers/specs/2026-05-15-phase-b7-target-indicator-design.md new file mode 100644 index 0000000..9fd14fe --- /dev/null +++ b/docs/superpowers/specs/2026-05-15-phase-b7-target-indicator-design.md @@ -0,0 +1,128 @@ +# Phase B.7 — Vivid Target Indicator — design + +**Date:** 2026-05-15. +**Status:** DESIGN — implementation starting same session. +**Closes (partially):** [issue #59](../../ISSUES.md) (`WorldPicker` over-pick — by making the wrong pick *visible* so the user can correct before clicking). +**Predecessors:** B.4b (selection), B.5 (pickup), B.6 (auto-walk). +**Retail anchors (decomp `docs/research/named-retail/acclient_2013_pseudo_c.txt`):** +- `VividTargetIndicator::SetSelected` at `0x004f5ce0` — selection setter + filtering. +- `gmRadarUI::GetBlipColor(weenie)` at `0x004d76f0` — per-type colour table. +- `VividTargetIndicator::CopyImage` at `0x004f5dd0` — tints a source bitmap by RGBA, four blit methods supported. +- `VividTargetIndicator::UpdateDisplayState` at `0x004f5fb0` — gated on `PlayerOption_VividTargetingIndicator`. +- Two render surfaces: `m_pOnScreen` (corners around target) + `m_pOffScreen` (edge arrow when target is off-camera). + +--- + +## Problem + +User sees `Tirenia` chat dialogue after intending to click an item. Cause: `WorldPicker.Pick`'s 5 m fixed radius over-picks bigger collision spheres (NPCs > items). Without on-screen feedback, the user can't tell their intended click missed — they only see the consequence (NPC walks toward, dialogue fires). + +Retail solved this with **selection feedback** drawn over the 3D scene: four small corner triangles framing the target's silhouette, colour-coded by entity type (yellow ≈ creature, white ≈ default, red ≈ PK, etc.). The mere existence of the indicator makes the picker bug self-correcting — you click empty space to deselect, then click again on the actual target. + +This is the *missing UX feedback* that's been silently breaking interaction since B.4b shipped. + +--- + +## Scope decisions + +**Included in B.7 MVP:** +1. Per-entity colour lookup, port of `gmRadarUI::GetBlipColor` using our existing `ItemType` and `ObjectDescriptionFlags` (already parsed from `CreateObject`). +2. Screen-space corner-triangle renderer drawn via ImGui's `ImDrawList` (we already have ImGui — no new GL infrastructure). +3. Hook to `_selectedGuid` — updates whenever B.4b's `PickAndStoreSelection` mutates the selection. +4. Hidden when no selection, or when the selected entity is no longer in `_entitiesByServerGuid` (despawned). + +**Deferred to B.7 follow-ups:** +- Off-screen edge arrow (`m_pOffScreen`). Useful for tracking a target you walked past; not MVP-critical. +- Retail-faithful corner-triangle imagery loaded from the DAT. MVP draws procedurally-coloured equilateral triangles via ImGui — looks acceptable, doesn't need a DAT-asset hunt. +- Mesh-tint highlight (`"texture lights up a bit"`). That's a shader-level change on the selected entity's mesh. Requires touching `WbDrawDispatcher` to flag a per-instance highlight uniform. Two-line stub already mentioned in the `mesh_modern.vert` `InstanceData` struct docstring (per CLAUDE.md WB integration cribs) — pick it up if the corner triangles aren't enough. +- Player-option toggle (`PlayerOption_VividTargetingIndicator`). MVP is always-on; add toggle if users complain about screenshots. +- Server selection-relay (`SmartBox::SetTargetObjectID` outbound, `RecvNotice_ServerSaysMoveItem` inbound). Not visible in current ACE behaviour and not blocking M1. + +--- + +## Architecture + +Single new file: `src/AcDream.App/UI/TargetIndicatorPanel.cs` (or similar — fits the existing `AcDream.UI.Abstractions.Panels.*` pattern but lives in `AcDream.App` because it touches `GameWindow`'s camera projection state). + +**Responsibilities:** +- Read `_selectedGuid` (passed in via constructor delegate, like the existing `DebugVM` wiring). +- Look up the entity in `_entitiesByServerGuid` + `_liveEntityInfoByGuid`. +- Compute world-space AABB centre + radius (use cached `EntitySpawn.ItemType` for type bits + a fixed visual radius — 1 m default, or per-itemType later). +- Project the AABB to 4 screen-space corner positions using the active camera's `View × Projection × Viewport`. +- Resolve colour via `RadarBlipColors.For(itemType, objectDescriptionFlags)` — new static helper class. +- Draw 4 small filled triangles via `ImGui.GetBackgroundDrawList().AddTriangleFilled`. + +**Render order:** background-draw-list, AFTER the 3D scene, BEFORE other ImGui panels so other UI can occlude the triangles if needed. + +--- + +## Colour table + +Per `gmRadarUI::GetBlipColor` decomp lines `219913+`, the dispatch order is: + +``` +if pwd._bitfield & 0x40000 → Portal (cyan-ish?) +if pwd._bitfield[1] & 0x02 → Vendor (green?) +if (pwd._bitfield & 0x10) && IsCreature && !IsPlayer → Creature (yellow) +if IsPlayer: + if IsPK → PlayerKiller (red) + elif IsPKLite → PKLite (pink?) + elif pwd._bitfield & 0x200000 → Creature-coloured (hostile player flag?) + else → Default (white) +… (more branches above the read window) +``` + +I'll port the dispatch verbatim. The actual `RGBAColor_Radar*` constants live in retail data (probably in `acclient.h` per CLAUDE.md's named-retail anchors). For MVP I'll use hand-picked colours that visually match retail screenshots; refine later if I find the constants. + +--- + +## Triangle geometry + +Each corner triangle in retail is a small right-angle triangle pointing *into* the centre of the selection rectangle — i.e., the top-left corner has its hypotenuse along the screen-up-then-screen-right diagonals, pointing down-right toward the entity. Same pattern at the other three corners (rotated 90° / 180° / 270°). + +MVP: small filled triangles (~8 px legs) at each corner of the projected AABB, oriented inward. Fine-tune via screenshots vs retail later. + +--- + +## File-level scope sketch + +- **New:** `src/AcDream.Core/Ui/RadarBlipColors.cs` (`AcDream.Core` so it's testable independently of `App`). Static class with `RGBA For(ItemType, ObjectDescriptionFlags)` returning a tagged colour. ~60 LOC. +- **New:** `src/AcDream.App/UI/TargetIndicatorPanel.cs` (~100 LOC). Owns the per-frame projection + ImGui draw. Constructor takes: + - `Func selectedGuidProvider` + - `Func entityResolver` (closure over `_entitiesByServerGuid` / `_liveEntityInfoByGuid`) + - `Func<(Matrix4x4 view, Matrix4x4 projection, Vector2 viewport)> cameraProvider` +- **Modify:** `src/AcDream.App/Rendering/GameWindow.cs` (~15 LOC). Construct the panel after ImGui init, wire delegates, call `Render()` from the ImGui pass. +- **Tests:** `tests/AcDream.Core.Tests/Ui/RadarBlipColorsTests.cs` (~40 LOC, 5-6 cases covering the type-flag → colour matrix). + +Total ~200 LOC + tests. Single phase, no slices needed. + +--- + +## Acceptance criteria + +- [ ] Single-click an NPC at Holtburg → yellow-ish corner triangles appear around it. +- [ ] Single-click a ground item → white-ish triangles. +- [ ] Click empty ground / deselect → triangles disappear. +- [ ] Selected entity moves → triangles track it (project per frame). +- [ ] Selected entity despawns → triangles disappear. +- [ ] No measurable FPS regression at Holtburg radius=4 (it's 4 triangles per frame — should be invisible to perf). +- [ ] Unit tests cover the colour-lookup matrix: NPC, item, player, PK, lifestone, portal, vendor. + +--- + +## Out of scope (deferred) + +- Off-screen edge arrow. +- DAT-loaded triangle sprite (procedural for MVP). +- Mesh-tint highlight on selected entity. +- Player-option toggle. +- Server selection-relay (`SmartBox::SetTargetObjectID`). +- Tab-cycle selection (`SelectClosestCombatTarget` already exists for combat; non-combat cycle is a separate UX phase). + +--- + +## Out-of-band: addressing the picker over-pick (#59) + +This phase deliberately does NOT fix the underlying picker. Once the indicator is shipped, the user can *see* the wrong selection and click empty space to clear it, then retry on the actual target. The picker fix (tighter per-itemType radius, ray-test against actual mesh bounds, or click priority by itemType) is a follow-up that can be informed by which combos most often produce mis-picks once we have the indicator showing them clearly. + +If the indicator + picker need to be revisited together later, both end up in the same UX phase.