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:
parent
06512f0957
commit
6ee3d88863
3 changed files with 148 additions and 17 deletions
|
|
@ -103,6 +103,66 @@ public sealed class MapPageControllerTests
|
|||
}
|
||||
}
|
||||
|
||||
// ── Marker placement math (GOLDEN PIXEL values, byte-decoded formula) ──
|
||||
//
|
||||
// gmMapUI::PlaceMarkerOnMap @0x004a18b0. Binary Ninja elides the entire
|
||||
// FPU chain to bare, operand-less _ftol2() calls; the formula below was
|
||||
// recovered by disassembling the raw bytes of the PDB-paired
|
||||
// acclient.exe directly (capstone) — see MapPageController.ComputeMarkerPosition's
|
||||
// doc comment and docs/research/2026-08-17-map-house-recon.md's
|
||||
// corrected PlaceMarkerOnMap entry. Every expected value here is a
|
||||
// LITERAL computed independently from the formula (by hand / an
|
||||
// external script), never by calling the port itself — that is the
|
||||
// whole point of a golden-value test.
|
||||
//
|
||||
// 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))
|
||||
//
|
||||
// Marker area used throughout: (6,8)-(247,258) — the live-fixture value
|
||||
// (MapHousePanelSlotProbeTests). Icon: 10x10 (matches the town hotspot
|
||||
// template's "plain 10x10 hotspot dot" and this test file's own fixture
|
||||
// resolver).
|
||||
|
||||
[Theory]
|
||||
// Canonical case: dead center of Dereth (0.0N/0.0E) -> the marker
|
||||
// area's own true center pixel.
|
||||
[InlineData(0.0, 0.0, 122, 128)]
|
||||
// Far west (x very negative): pixel X moves toward the marker area's
|
||||
// left edge (m_x0=6), well below the center-case 122.
|
||||
[InlineData(-100.0, 0.0, 3, 128)]
|
||||
// Far north (y very positive): pixel Y moves toward the marker area's
|
||||
// top edge (m_y0=8) — the FSUBR north-up flip means +Y in-game means
|
||||
// SMALLER pixel Y, not larger.
|
||||
[InlineData(0.0, 100.0, 122, 5)]
|
||||
// A real town-table entry: Arwic's landblock cell 0x11CE0001 fed
|
||||
// through the ALREADY-VERIFIED RadarCoordinates.TryFromCell (a
|
||||
// different, independently-tested subsystem) to get x=-88.3/y=62.9,
|
||||
// then through the formula above to get the expected pixel.
|
||||
[InlineData(-88.30000000000001, 62.900000000000006, 17, 51)]
|
||||
public void ComputeMarkerPosition_MatchesByteDecodedFormula_GoldenPixels(
|
||||
double x, double y, int expectedLeft, int expectedTop)
|
||||
{
|
||||
(float left, float top) = MapPageController.ComputeMarkerPosition(
|
||||
markerX0: 6, markerX1: 247, markerY0: 8, markerY1: 258,
|
||||
iconWidth: 10, iconHeight: 10, x: x, y: y);
|
||||
|
||||
Assert.Equal(expectedLeft, left);
|
||||
Assert.Equal(expectedTop, top);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeMarkerPosition_ArwicCell_MatchesRadarCoordinates()
|
||||
{
|
||||
// Cross-check that the golden (x,y) literal used above for the
|
||||
// "town-table entry" case really is what RadarCoordinates.TryFromCell
|
||||
// produces for Arwic's landblock, so the golden test above can't
|
||||
// silently drift from the coordinate subsystem it's chained to.
|
||||
const uint cellId = 0x11CE0001u;
|
||||
Assert.True(RadarCoordinates.TryFromCell(cellId, out RadarCoordinates coords));
|
||||
Assert.Equal(-88.30000000000001, coords.X, precision: 12);
|
||||
Assert.Equal(62.900000000000006, coords.Y, precision: 12);
|
||||
}
|
||||
|
||||
// ── Marker placement wiring (real fixture, no re-derivation) ────────────
|
||||
|
||||
[Fact]
|
||||
|
|
@ -111,9 +171,10 @@ public sealed class MapPageControllerTests
|
|||
// Arwic's landblock cell id (0x11CE0001 — an arbitrary real outdoor
|
||||
// cell, picked only because RadarCoordinates.TryFromCell already
|
||||
// proves gid-to-lcoord conformance elsewhere; this test proves the
|
||||
// WIRING, not the formula).
|
||||
// WIRING, not the formula — the formula itself is golden-tested
|
||||
// above).
|
||||
const uint cellId = 0x11CE0001u;
|
||||
Assert.True(RadarCoordinates.TryFromCell(cellId, out RadarCoordinates expected));
|
||||
Assert.True(RadarCoordinates.TryFromCell(cellId, out _));
|
||||
|
||||
ElementInfo rootInfo = FixtureLoader.LoadMapHouseHostInfos();
|
||||
ImportedLayout layout = FixtureLoader.LoadMapHouseHost();
|
||||
|
|
@ -134,10 +195,11 @@ public sealed class MapPageControllerTests
|
|||
Assert.True(playerIcon!.Visible);
|
||||
|
||||
// markerArea from the live fixture (MapHousePanelSlotProbeTests):
|
||||
// (6,8)-(247,258) -> m_x0=6, m_y0=8.
|
||||
const int markerX0 = 6, markerY0 = 8;
|
||||
Assert.Equal(markerX0 + (float)expected.X - playerIcon.Width / 2f, playerIcon.Left, precision: 3);
|
||||
Assert.Equal(markerY0 + (float)expected.Y - playerIcon.Height / 2f, playerIcon.Top, precision: 3);
|
||||
// (6,8)-(247,258). GOLDEN pixel value computed independently above
|
||||
// (ComputeMarkerPosition_MatchesByteDecodedFormula_GoldenPixels'
|
||||
// Arwic case) — (17,51) for this exact (x,y).
|
||||
Assert.Equal(17f, playerIcon.Left);
|
||||
Assert.Equal(51f, playerIcon.Top);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue