fix(ui): night-round review — F1 real PlaceMarkerOnMap formula

The prior PlaceMarker() reading ("center at markerX0+x") was wrong.
Binary Ninja elides gmMapUI::PlaceMarkerOnMap @0x004a18b0's entire FPU
chain to bare, operand-less _ftol2() calls, so the pseudo-C
under-specifies the function. A capstone disassembly of the raw bytes
in the PDB-paired acclient.exe recovers the real formula: retail
projects the AC display coordinate (range ~-102.4..102.4) onto the
marker-area rect via a fixed-point-style transform, not a raw pixel
add:

  X = m_x0 - w/2 - (int)((m_x1-m_x0+1) * (x*10+1024)        * (-1/2048))
  Y = m_y0 - h/2 - (int)((m_y1-m_y0+1) * (2047-(y*10+1024)) * (-1/2048))

Constants read directly from .rdata: 0x79bac8=10.0, 0x7aac78=1024.0,
0x7aac70=-1/2048, 0x7aac68=2047.0. The Y axis's FSUBR is retail's
north-up flip. w/h halve with truncating integer division (matching
retail's cdq;sub;sar idiom), not float division.

Extracted the pure math into MapPageController.ComputeMarkerPosition
so it's directly testable, and retargeted MapPageControllerTests to
GOLDEN PIXEL values computed independently from the formula (never
from the port's own output): the reviewer's canonical (0,0)->(122,128)
case, a far-west and far-north case, and a real town-table entry
(Arwic's landblock, cross-checked against RadarCoordinates). Applies
to the green ring, house pin, and all 53 static town hotspots, which
all resolve through the same PlaceMarker call.

Corrected the recon doc's "accepted as-is" note, which had mistaken
"the FPU argument-passing is BN-mangled" for a narrow issue instead of
the whole-formula elision it actually was.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-17 04:19:29 +02:00
parent 06512f0957
commit 6ee3d88863
3 changed files with 148 additions and 17 deletions

View file

@ -74,13 +74,34 @@ Retail source: `docs/research/named-retail/acclient_2013_pseudo_c.txt`.
row TS-85** (`docs/architecture/retail-divergence-register.md`), which
explicitly named `gmMapUI::AddMapNote @0x004A1C51` as the last unported
`SetTooltip` call site.
- `gmMapUI::PlaceMarkerOnMap @0x004a18b0` (pc:171827): `MoveTo(m_x0 + (int)x
- width/2, m_y0 + (int)y - height/2)`, `SetVisible(1)`. The x87/FPU
argument-passing is BN-mangled in the raw decomp (the `_ftol2()`
placeholder swallows the actual `arg3`/`arg4` reads) — this formula is
the handoff's own already-verified reading and is accepted as-is; the
underlying `+x-w/2` / `+y-h/2` centering pattern is unambiguous from the
surrounding integer math.
- `gmMapUI::PlaceMarkerOnMap @0x004a18b0` (pc:171827): **CORRECTED
2026-08-17 (night-round review, finding F1) — the "accepted as-is"
reading below was WRONG.** The BN pseudo-C's operand-less `_ftol2()`
calls are not just "argument-passing mangled" — they swallow the
ENTIRE FPU chain (constants, multiplies, the Y-axis FSUBR flip), not
merely the `arg3`/`arg4` reads. A direct capstone disassembly of the
raw bytes at `0x004a18b0` in the PDB-paired `acclient.exe` recovers the
true formula: retail projects the AC display coordinate (`x`/`y`,
range ≈ ±102.4) onto the marker-area rect via a fixed-point-style
transform, not a raw pixel offset:
`X = m_x0 - w/2 - (int)((m_x1-m_x0+1) * (x*10+1024) * (-1/2048))`,
`Y = m_y0 - h/2 - (int)((m_y1-m_y0+1) * (2047-(y*10+1024)) * (-1/2048))`,
then `SetVisible(1)`. Constants read from `.rdata`: `0x79bac8`=10.0,
`0x7aac78`=1024.0, `0x7aac70`=-1/2048, `0x7aac68`=2047.0. `w`/`h` are
`UIRegion::GetWidth`/`GetHeight` halved by INTEGER (truncating)
division, matching retail's `cdq;sub;sar` idiom. Golden case: marker
area (6,8)-(247,258), 10x10 icon, position 0.0N/0.0E → (122,128)
center — reproduced exactly. Ported at
`src/AcDream.App/UI/Layout/MapPageController.cs`'s `PlaceMarker`. The
ORIGINAL (wrong) note, kept for the historical record of how the
mistake happened: "`MoveTo(m_x0 + (int)x - width/2, m_y0 + (int)y -
height/2)`... the x87/FPU argument-passing is BN-mangled in the raw
decomp (the `_ftol2()` placeholder swallows the actual `arg3`/`arg4`
reads) — this formula is the handoff's own already-verified reading
and is accepted as-is; the underlying `+x-w/2` / `+y-h/2` centering
pattern is unambiguous from the surrounding integer math." It was not
unambiguous — the BN elision hid a whole coordinate-projection
transform behind what looked like a plain pixel add.
- `gmMapUI::Update @0x004a1eb0` (pc:172084): re-arms `m_nextUpdate =
Timer::cur_time + 5.0` every call (5 s cadence, driven by
`ListenToGlobalMessage`'s `arg2==3` tick case). Date/time block: builds