fix(selection): port SmartBox click lighting pulse

Port the retail high/low material-lighting cadence for successful world clicks, keep it instance-scoped in the modern renderer, and restore authored lighting after 0.8 seconds. Correct the selection oracle and pin timing plus per-frame buffer lifecycle with tests.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-17 23:00:04 +02:00
parent 146a963aeb
commit ea4f52ec51
11 changed files with 338 additions and 16 deletions

View file

@ -524,7 +524,7 @@ behavior. Estimated 1726 days focused work, 35 weeks calendar.
- **✓ SHIPPED — UI Studio** (2026-06-26, branch `claude/hopeful-maxwell-214a12`, ~33693c6→HEAD). Standalone `AcDream.App ui-studio <datdir> [--layout 0xNNNN | --dump <slug>] [--screenshot <png>]` Silk tool that previews any panel through the **production renderer** (`RenderBootstrap.cs` + GameWindow untouched). Sources: 26-window retail dump (`docs/research/2026-06-25-retail-ui-layout-dump.json`) via `--dump`, or live dat import + fixtures via `--layout`. Interactive canvas (click-routing to `UiHost`, Interact/Inspect ImGui toggle), headless `--screenshot`. Architecture: `src/AcDream.App/Studio/` (`StudioWindow`, `PanelFbo`, `FixtureProvider`, `LayoutSource`) + `src/AcDream.App/Rendering/RenderBootstrap.cs`. ISSUES #156 (inventory all-black in studio) and #157 (GameWindow font resolver) filed.
- **✓ SHIPPED — Importer dat-fidelity** (Fix A/B/C, 2026-06-26, same branch). **Boundary established: look = importer (font/justification/color from dat); state/behavior = runtime controller logic.** Fix A: justification property `0x14`/`0x15``UiText` Centered/RightAligned/VerticalJustify. Fix B: FontColor property `0x1B``UiText.DefaultColor`; character colors proved RUNTIME (no importer fix). Fix C: FontDid → per-element dat font via a resolver in `DatWidgetFactory`**STUDIO path only**; GameWindow passes `null` (Issue #157). Fix 4 (UIState-group activation): INVESTIGATED — no importer fix exists; the dat has no Visible encoding; runtime `gm*UI` is correct.
- **✓ SHIPPED — Wave 3.3 unified primary-click router.** `ItemInteractionController.OfferPrimaryClick` returns `NotActive` / `ConsumedSuccess` / `ConsumedRejected`; inventory, bags, main pack, paperdoll, toolbar, radar, and world clicks all offer target mode before local fallback. Rejected targets cannot drift selection or open/use the clicked object. Issue #197 and AP-107 retired; retained matrix and live ACE-provisioned healing gate user-confirmed. The 3-D doll routes through authored hit mask `0x100001D6` (resolved `UiButton` above viewport `0x100001D5`), and local health prefers authoritative private-vital state.
- **✓ SHIPPED — retail world selection + vivid target indicator (2026-07-17).** The projected Setup-sphere rectangle and independent physics-wall ray are deleted. The normal visible-mesh traversal now publishes server-object parts through their exact drawing-BSP spheres; `RetailWorldPicker` ports the camera-eye ray, per-part local transform, double-precision retail sphere solve, first visual-polygon hit in DAT order, one-sided/convex-edge rules, and global polygon-over-sphere arbitration. The retained gameplay UI resolves client-enum category `0x10000009` values `1..4` to the four installed 12×12 corner surfaces, colorizes them with the radar-blip color, and ports the exact Setup-sphere rectangle plus separate-edge 8-pixel viewport clamp. Issues #70/#71 closed; AP-29/TS-16 retired. Research: `docs/research/2026-07-17-retail-world-selection-pseudocode.md`.
- **✓ SHIPPED — retail world selection + complete SmartBox feedback (2026-07-17).** The projected Setup-sphere rectangle and independent physics-wall ray are deleted. The normal visible-mesh traversal now publishes server-object parts through their exact drawing-BSP spheres; `RetailWorldPicker` ports the camera-eye ray, per-part local transform, double-precision retail sphere solve, first visual-polygon hit in DAT order, one-sided/convex-edge rules, and global polygon-over-sphere arbitration. The retained gameplay UI resolves client-enum category `0x10000009` values `1..4` to the four installed 12×12 corner surfaces, colorizes them with the radar-blip color, and ports the exact Setup-sphere rectangle plus separate-edge 8-pixel viewport clamp. Successful select/use/targeted-use/drop clicks also run SmartBox's separate 0.8-second HIGH/LOW/HIGH/LOW material-lighting pulse with the exact retail luminosity/diffuse replacements before restoring authored rendering. Issues #70/#71 closed; AP-29/TS-16 retired. Research: `docs/research/2026-07-17-retail-world-selection-pseudocode.md`.
- **✓ SHIPPED — Wave 4.1 retail quick-slot input.** Bare `1..9` use toolbar slots 0..8, Ctrl+`1..9` selects them, Alt+`5..9` uses slots 13..17, and `0` creates a shortcut to the selected owned/eligible object in retail's first empty slot. `ToolbarInputController` is the focused semantic adapter; `ToolbarController` owns `UseShortcut` target-before-use/select ordering and auto-slot eligibility. Keybinding schema v2 migrates only the exact old default Ctrl+number chords, so custom bindings retain their chosen action. Full automated conformance and live user gate passed 2026-07-11.
- **✓ SHIPPED — Wave 4.2 exact shortcut records.** One Core-owned `ShortcutEntry` preserves retail's signed `index_`, unsigned `objectID_`, and raw 32-bit `spellID_` through PlayerDescription parse, the nullable 18-slot session store, drag snapshots/reindexing, WorldSession, and exact 12-byte AddShortcut packing. The visible `gmToolbarUI` remains object-only; `favorite_spells_[8]` remains a separate future spell-bar source. AP-103 retired. Full automated conformance and live mutate→clean-relog persistence gate passed 2026-07-11.
- **✓ SHIPPED — Wave 4.3a shortcut-alias boundary.** Drag feedback now preserves retail's neutral/accept/reject distinction. Inventory and paperdoll apply the `(DropItemFlags & 0xE) == 0` physical-item gate, so a toolbar alias can reorder within the toolbar but can never move, unwield, wield, or drop the object it references. Live equipped-helmet gate passed 2026-07-11: inventory drop removed only the shortcut.

View file

@ -139,7 +139,64 @@ else:
show the on-screen root and hide the off-screen root
```
There is no selected-world-mesh tint or luminosity mutation in
`CPhysicsPart::Draw`, `ACCWeenieObject::SetSelected`, or the world mesh draw
path. The visible notification is the colorized four-corner DAT indicator. A
mesh shader tint would therefore be a new effect, not a retail port.
The indicator is persistent selection presentation. Retail also has a separate,
short-lived click confirmation owned by SmartBox; it is not implemented in
`CPhysicsPart::Draw` or `ACCWeenieObject::SetSelected`.
## SmartBox click lighting pulse
`UIElement_SmartBoxWrapper::RecvNotice_SmartBoxObjectFound @ 0x004E5AD0`
```text
when FindObject succeeds for Select, Examine, Use, Drop, Drag, or TargetedUse:
if another object is currently pulsing:
RestoreLighting(previous object)
SetLighting(found object, luminosity=0.99, diffuse=1.0) // HIGH
flipCount = 1
nextFlip = Timer::cur_time + 0.2 seconds
continue with the reason-specific select/examine/use/drop behavior
```
`UIElement_SmartBoxWrapper::Global_Loop @ 0x004E5620`
```text
if flipCount != 0 and Timer::cur_time >= nextFlip:
flipCount += 1
if flipCount < 5:
lightingMode = ((flipCount & 1) != 0) + 1
nextFlip = Timer::cur_time + 0.2 seconds
else:
flipCount = 0
lightingMode = RESTORE
ApplyLighting(pulsing object, lightingMode)
```
`UIElement_SmartBoxWrapper::ApplyLighting @ 0x004E5320`
```text
RESTORE: CPhysicsObj::RestoreLighting(object)
LOW: CPhysicsObj::SetLighting(object, luminosity=0.0, diffuse=0.35)
HIGH: CPhysicsObj::SetLighting(object, luminosity=0.99, diffuse=1.0)
```
The complete cadence is therefore HIGH immediately, LOW at 0.2 seconds, HIGH
at 0.4, LOW at 0.6, and authored material lighting restored at 0.8. A delayed
frame advances only one step because retail schedules the next flip from the
current `Timer::cur_time`; it does not drain missed intervals.
`CPhysicsObj::SetLighting @ 0x00511A80` forwards to every physics part through
`CPartArray::SetLightingInternal @ 0x00518490`. `CPhysicsPart::SetLighting @
0x0050E400` copies the material when needed, then calls
`CMaterial::SetLuminositySimple` followed by `CMaterial::SetDiffuseSimple`.
The modern renderer's equivalent is an instance-scoped material replacement:
```text
lit = luminosity + diffuse * sceneLighting
rgb = sampledTexture.rgb * clamp(lit, 0, 1)
```
Normal/restored rendering uses `(luminosity=0, diffuse=1)` in the current object
shader. The pulse is distinct from the persistent four-corner indicator and
must restart on every successfully resolved SmartBox click, even when the click
is consumed by targeted use instead of changing the selected object.