docs(B.7): design spec for Vivid Target Indicator (selection feedback)
Retail-anchored design for the missing visual feedback on selection:
four corner triangles + radar-blip colour coding around the selected
entity, drawn via ImGui in screen space.
Retail evidence (named decomp):
* VividTargetIndicator::SetSelected at 0x004f5ce0
* gmRadarUI::GetBlipColor at 0x004d76f0 (Portal / Vendor / Creature /
Player / PK / PKLite / Default colours from pwd._bitfield bits +
IsCreature/IsPlayer/IsPK predicates we already parse)
* VividTargetIndicator::CopyImage at 0x004f5dd0 (tints a source
bitmap by RGBA)
MVP scope:
1. RadarBlipColors helper (Core, with unit tests)
2. TargetIndicatorPanel (App, ImGui draw via background draw list)
3. Wire to existing _selectedGuid from B.4b
4. ~200 LOC + tests
Deferred to follow-ups: off-screen edge arrow, DAT-loaded sprite (MVP
draws procedurally), mesh-tint highlight, player-option toggle, server
selection-relay.
Pairs with #59 (WorldPicker over-pick): the indicator makes the
mis-pick visible, so the user can clear + reselect even before the
underlying picker is tightened.
This commit is contained in:
parent
5612ce718a
commit
37177a418e
1 changed files with 128 additions and 0 deletions
|
|
@ -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<uint?> selectedGuidProvider`
|
||||
- `Func<uint, EntitySpawn?> 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue