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

4.1 KiB
Raw Blame History

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

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
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.