acdream/docs/research/2026-07-10-retail-radar-pseudocode.md

103 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Retail radar/compass pseudocode — 2026-07-10
## Oracle and scope
This note records the primary retail behavior used by acdream's retained
radar port. The oracle is the September 2013 named client:
- `gmRadarUI` fields: `named-retail/acclient.h:54506-54548`
- color/shape: `gmRadarUI::GetBlipColor` `0x004D76F0`,
`gmRadarUI::GetBlipShape` `0x004D7B60`
- coordinates/compass/draw: `UpdateCoordinates` `0x004D8C80`,
`UpdateCompassTokens` `0x004D9060`, `DrawObjects` `0x004D9380`,
`DrawChildren` `0x004D9720`
- cadence/list maintenance: `UseTime` `0x004D98A0`, `AddObject`
`0x004D9A00`, `Init` `0x004D9B90`, `PostInit` `0x004D9D30`
- production DAT layout: `LayoutDesc 0x21000074` in
`2026-06-25-retail-ui-layout-dump.json`
The older AC2D-derived `1.18 × range`, rotating player arrow, and horizontal
scrolling compass strip are rejected by the named retail implementation.
## Retained UI ownership
The production root is a 120×140 `gmRadarUI` element of class
`0x10000010`. Custom properties set center `(60,60)` (`0x1000002E`,
nested `0x1000002F/30`) and pixel radius `50` (`0x1000002D`).
`LayoutImporter` owns the static DAT children and media.
A dedicated `UiRadar` owns the dynamic draw/tick/input behavior. Core owns
the pure projection, classification, marker geometry, compass, and coordinate
algorithms. `GameWindow` supplies live snapshots and selection/settings
actions only.
This is a screen-space retained panel. It is not an ImGui panel and does not
use the raw world-space TextRenderer overlay path.
## Per-tick pseudocode
```text
every 0.025 seconds:
range = player is outdoors ? 75 : 25
heading = physical player heading in degrees
move N/E/S/W child tokens around radar center
if CoordinatesOnRadar and LandDefs.gid_to_lcoord(playerCell):
ns = (lcoordY - 1024) * 0.1 + 0.5
ew = (lcoordX - 1024) * 0.1 + 0.5
coordinateText = abs(ns) + N/S + "," + abs(ew) + E/W
show footer/text
else:
hide footer/text
```
```text
for each maintained object except the player:
require a physics/world entity
require RadarBehavior in { ShowMovement=2, ShowAttacking=3, ShowAlways=4 }
relative = convert object position into player's oriented frame
if relative.X² + relative.Y² >= (range - 1)²:
continue
scale = radarPixelRadius / range
x = trunc(centerX + relative.X * scale)
y = trunc(centerY - relative.Y * scale)
rgbBrightness = abs(relative.Z) < 5 ? 1.0 : 0.65
color = exact _blipColor override 1..10, otherwise retail classification
shape = fellowship/allegiance/PK relationship shape, otherwise Plus
draw marker pixels
if selected: draw the open selection outline at offsets ±3
```
After all object blips, draw the player's fixed bright-green center plus.
Hover selects the nearest projected blip within squared distance 36; its
name is the tooltip. A left click selects that object.
The lock button drives the existing retained-window drag gate; the drag
affordance is hidden while locked. Completed moves are clamped inside the UI
root and persisted per character through `SettingsStore`.
## Production assets
| Role | Element | Rect | RenderSurface |
|---|---:|---:|---:|
| Face | `0x1000003F` | `(0,0) 120×120` | `0x06004CC1` |
| Coordinate footer | `0x1000003E` | `(0,120) 120×18` | `0x06004CC0` |
| N / E / S / W | `0x10000040..43` | four 10×9 tokens | `0x060011FB`, `0x06001938`, `0x0600193A`, `0x0600193C` |
| Lock | `0x10000619` | `(6,6) 27×27` | `0x060074B7` / `0x060074B8` |
| Drag | `0x100006A3` | `(87,6) 27×27` | `0x060074C9` |
The child token orbit uses `center + (sin(angle), cos(angle)) * magnitude`
with angles N=`heading+π`, E=`heading+π/2`, S=`heading`, W=`heading+3π/2`.
## Wire contract
PublicWeenieDesc flag `0x00100000` carries nullable byte
`RadarBlipColor`; flag `0x00800000` carries nullable byte `RadarBehavior`.
Absent is distinct from an explicitly transmitted zero. Both values flow
through `CreateObject.Parsed`, `WorldSession.EntitySpawn`, and
`ClientObjectTable`.
Do not infer ShowMovement/ShowAttacking from local motion. Retail's
`InqShowableOnRadar` accepts values 2, 3, and 4 directly; server/quality
changes are authoritative.