Saves the overnight-round recon (embedded findings + this session's desk
verification) for auditability before implementation starts. Corrects two
handoff claims: the panel id is 16 (already resolved by the existing FA
campaign's full 16-slot gmPanelUI::SetupChildren dump, not a guess from
{1,2,6,14}), and GameEventType already defines all four House opcodes
(0x0225-0x0228) — what's missing is routing, not the enum. Identifies that
LandDefs.GidToLcoord/LcoordToGid (src/AcDream.Core/Physics/LandDefs.cs) is
an existing tested port of LandDefs::gid_to_lcoord, reusable for both the
Map tab's coordinate math and the House location display — no re-port
needed. Cites the toolbar button (0x1000019A, panel id 16), the 53-entry
s_rgLocations marker table verbatim, the ServerPosition wire struct reuse
for HouseData.Position, and the AuthoredTooltipText/RetailTooltipPresenter
seam that will close register row TS-85's last item (gmMapUI::AddMapNote).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
16 KiB
Map/House toolbar panel — recon (Batch C, overnight hover/UI round)
Combines the recon handed to this session (address-level findings, verified) with additional desk verification done before implementation: several facts the handoff marked "UNKNOWN" or "likely" were already resolved elsewhere in the repo, and are corrected here.
Panel identity — RESOLVED (corrects the handoff's guess)
The handoff guessed the panel id was "likely one of RetailPanelCatalog's
unused ids {1,2,6,14}". That guess is wrong — the answer was already on
disk from the FA campaign's own full 16-slot gmPanelUI::SetupChildren
dump (docs/research/2026-08-11-fa-panel-structure.md:927-933, itself
byte-verified against the live installed DATs at FA3):
0x1000018C = 16 gmMapUI+gmHouseUI pages
0x10000559 = 25 gmJournalUI/gmPageListUI/gmContractsUI (NOT this batch)
So: host 0x2100006E, slot 0x1000018C, RetailPanelCatalog id 16.
gmPanelUI::SetupChildren @0x004bc9e0 (pc:195832) confirms the full 16-slot
enumeration is exhaustive — 0x10000186 inside the contiguous id run
resolves to nothing (IMPORT NULL), consistent with the FA doc's flag.
Toolbar button: cross-referenced the committed fixture
tests/AcDream.App.Tests/UI/Layout/fixtures/toolbar_21000016.json (last
regenerated 2026-08-11, Campaign OP slice OP5 — same DAT install this
session uses) for each of ToolbarController.PanelButtonIds' own
P0x10000029 value:
| Button element | P0x10000029 |
Panel |
|---|---|---|
0x1000055A |
25 | Journal (ghosted, out of scope) |
0x10000197 |
12 | Social — currently ghosted despite FA docs claiming "no toolbar button authors this id"; flagged, not chased (out of scope for this batch) |
0x10000198 |
13 | Magic (registered) |
0x10000199 |
11 | Character (registered) |
0x100001B1 |
7 | Inventory (registered) |
0x1000019A |
16 | Map/House — THE button this batch un-ghosts |
0x1000019B |
10 | Options (registered) |
Slice 1's live probe re-confirms both facts (slot table + button) against the live DAT rather than trusting the committed fixture at face value.
gmMapUI — decompiled, byte-exact
Retail source: docs/research/named-retail/acclient_2013_pseudo_c.txt.
gmMapUI::PostInit @0x004a1c70(pc:171993): resolvesm_pDateTimeText=0x100001eb(UIElement_Text, DynamicCast 0xc),m_pCoordinateText=0x100001ef(UIElement_Text),m_pPlayerLocationIcon=0x100001ed,m_pHouseLocationIcon=0x100001ee,m_pMap=0x100001ec. Readsm_pMap's own int attrs0x1000004e/4f/50/51intom_boxMapMarkerArea(x0,x1,y0,y1) — the marker-area rect. Readsm_pMap's enum attr0x47(literal small property id, NOT0x10000047) and DataID attr0x48into aQualifiedDataID(id, 0x23)(category0x23= LayoutDesc) →DBObj::Get. If that resolves, loops the 53-entrys_rgLocationstable callingAddMapNote(this, m_pMap, var_c /*=attr 0x47, the template ELEMENT id*/, eax_10 /*=the resolved LayoutDesc*/, &s_rgLocations[i])for each. So0x47/0x48onm_pMaptogether name a template (LayoutDesc, element) pair for the per-town hotspot widget — the exact same "authored template" patternLayout.RowTemplateResolveralready serves for Friends/Squelch/ Fellowship rows. Slice 1's probe reads the live values.gmMapUI::AddMapNote @0x004a1bb0(pc:171967):CreateChildElement(mgr, m_pMap, layoutDesc, templateElementId)→MoveTo(info.X, info.Y)→ResizeTo(info.Width, info.Height)→SetTooltip(child, info.Name)(a literal wide string,StringInfo::SetLiteralValue— not a DAT string table lookup). acdream'sUiElement.AuthoredTooltipText+AuthoredTooltipEnabled, served byRetailTooltipPresenter(src/AcDream.App/UI/Layout/RetailTooltipPresenter.cs), is the exact seam — set both fields on the built child and the existing tooltip pipeline does the rest. This closes the one remaining item of register row TS-85 (docs/architecture/retail-divergence-register.md), which explicitly namedgmMapUI::AddMapNote @0x004A1C51as the last unportedSetTooltipcall 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 actualarg3/arg4reads) — 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.
- width/2, m_y0 + (int)y - height/2)
gmMapUI::Update @0x004a1eb0(pc:172084): re-armsm_nextUpdate = Timer::cur_time + 5.0every call (5 s cadence, driven byListenToGlobalMessage'sarg2==3tick case). Date/time block: builds"Date: %s\nTime: %s"fromGameTime::GetDateTimeString, only callsSetTextwhen the string actually differs (a change-detect, not a re-stamp every 5 s). Coordinate block, gated onCPlayerSystem::IsOutside():- outside:
CPlayerSystem::InqPlayerCoords→ sign-based N/S/E/W selection (heavily FPU-mangled — BN elides the actual printf format string behind aFormatted/vtable-slot placeholder it cannot resolve; accepted as genuinely unrecoverable from this dump, matching the handoff's own UNKNOWN #3) →SetText(change-detected) →PlaceMarkerOnMap(m_pPlayerLocationIcon, x, y). - inside:
SetTextto a fixed narrow-string constant (also BN-mangled/unrecovered — treated as "empty/blank", matching retail's known behavior of clearing the readout) →m_pPlayerLocationIcon-> SetVisible(0). - House marker (independent of the outside/inside branch, gated on
m_pHouseLocationIcon != 0):Position::IsValid(&m_HousePosition)→ if invalid,SetVisible(0); if valid,Position::get_outside_cell_id(&m_HousePosition)→LandDefs::gid_to_lcoord→ the identical(v - 0x400) * 0.1 + 0.5transform on both axes →PlaceMarkerOnMap(m_pHouseLocationIcon, x, y).
- outside:
CPlayerSystem::InqPlayerCoords @0x00560090(pc:364615): confirms the(lcoord - 0x400) * 0.1 + 0.5display transform per axis, fed byCPhysicsObj::get_landscape_coord. Which of that function's two raw outputs maps to which InqPlayerCoords axis is ambiguous in the BN decomp (aesp+0x10/esp+0x24swap that can't be resolved without disassembly); not chased — see "Precision decision" below.LandDefs::gid_to_lcoord @0x00497a90(pc:163500): clean, no FPU noise. Already ported atsrc/AcDream.Core/Physics/LandDefs.cs:73(LandDefs.GidToLcoord, issue #106, cross-checked against ACE) — theedx_2 < 0x100low-word check that looked suspicious in the raw BN text is exactly the existing port's own documented finding (low = cellId & 0xFFFF; if (low >= 0x100) return false— a 16-bit sub-register access BN renders as a full-width compare). No re-port needed, per the WorldBuilder-inventory doctrine ("read the inventory FIRST... re-porting when we already have a tested port is how bugs slip in") extended here to the equivalent Core-physics precedent.- Also present but out of scope:
gmMapUI::ListenToElementMessage @0x004a2350idMessage0x1chandles a GM-only ("PlayerDesc:: PlayerIsPSR") click-to-teleport on the map (lcoord_to_gidfrom the click pixel →Position→ presumably a teleport notice). Not part of any assigned slice; flagged for a future issue if wanted, not filed given no immediate need.
Precision decision (player marker)
InqPlayerCoords' raw inputs come from CPhysicsObj::get_landscape_coord,
which is not itself ported and whose BN decomp is FPU-mangled beyond safe
recovery tonight. At this map's scale (53-entry pixel-rect town table
covering the FULL Dereth landmass, lcoord range [0, 0x7F8)=2040 mapped
to ~0.1 map-units/cell, i.e. ~0.8 map-units per landblock, well under a
pixel) integer landcell precision is visually indistinguishable from
sub-cell precision. The port therefore computes the player marker exactly
like the (byte-exact, unambiguous) house marker: current outdoor cell id →
LandDefs.GidToLcoord → the same (v-0x400)*0.1+0.5 transform. This is a
reasoned substitution of an ALREADY-VERIFIED equivalent primitive, not a
guess — flagged here and in a divergence-register row for the one case
where it could matter (crossing a cell boundary at the exact map-rendering
threshold), not for the sub-pixel precision itself.
Wire byte layout — Position (used by HouseData.Position)
references/ACE/Source/ACE.Server/Network/Structure/AllegianceHierarchy.cs:192-212
writes Cell(uint32) + Pos.XYZ(float×3) + Rotation.WXYZ(float×4) = 32
bytes. acdream already has this exact shape as
AcDream.Core.Net.Messages.CreateObject.ServerPosition (CreateObject.cs:479,
parsed at CreateObject.cs:611-619) — reused rather than re-defined.
House — gmHouseUI, decompiled
gmHouseUI::PostInit @0x004a2710(pc:172581): resolvesm_pTextBox=0x100001e6(UIElement_ListBox, DynamicCast 5). Registers FOUR notice handlers:0x4dd225(HouseData),0x4dd226(HouseStatus),0x4dd227(UpdateRentTime),0x4dd228(UpdateRentPayment) — matching wire opcodes0x0225-0x0228.gmHouseUI::GetHouseLocation @0x004a27b0: readsm_pHouseDataat offset0x74(an enum,== 4is a short-circuit "no location" case) or falls through toPosition::IsValid(&m_pHouseData->m_pos /*+0x2c*/)→LandDefs::gid_to_lcoord(Position::get_outside_cell_id(...)).- Seven
Display*line builders (DisplayBuyPayment,DisplayRentPayment,DisplayBuyTime,DisplayRentTimes,DisplayLocation,DisplayWarningText,DisplayPurchaseTimeText), all called in sequence fromDisplayHouseData @0x004a3380and from bothUpdateoverloads. Each is dozens-to-a-few-hundred lines of heavily FPU/string-mangled BN pseudo-C (PStringBase sprintf chains, HousePaymentList iteration,IsPaidInFull/ConstructRentWarningMessage-style formatting). Sized as genuinely disproportionate for tonight's batch — this matches the task brief's own pre-authorized fallback ("if the whole owned-house wire half balloons beyond reach tonight, land the default-content tab + the enum/parser groundwork, and file the remainder as a precise ISSUES entry"). Decision: land the mount (default authored content, zero wire) and the wire groundwork (enum route registration + parsers +RuntimeHouseStateraw-field owner), and file the seven line builders as an ISSUES entry rather than porting them tonight.
Wire — GameEventType already has all four ids (corrects the handoff)
The handoff claimed "0x0227/0x0228 absent from the enum". Checked
src/AcDream.Core.Net/Messages/GameEventType.cs:73-76 directly — all
four are already defined:
HouseData = 0x0225
HouseStatus = 0x0226
UpdateRentTime = 0x0227
UpdateRentPayment= 0x0228
What's actually missing (confirmed by grepping src/ for every
GameEventType.House*/UpdateRent* reference: zero hits) is routing —
no parser, no GameEventWiring registration, no consumer. ACE's own
writers for two of the four are themselves stubs worth knowing about
before treating any live capture as ground truth:
GameEventHouseUpdateRentTime.cs always writes a hardcoded rentTime = 0u; GameEventHouseUpdateRentPayment.cs always writes an empty
List<HousePayment>. GameEventHouseData/GameEventHouseStatus write
real data (HouseData/(uint)WeenieError).
Marker table — s_rgLocations[0x35] (53 entries), verbatim
docs/research/named-retail/acclient_2013_pseudo_c.txt:977225-977651.
Struct gmMapUI::LocationRolloverInfo { uint X,Y,Width,Height; wchar_t* Name; } (acclient.h:55686). Values are direct pixel rects passed to
MoveTo/ResizeTo on m_pMap — no coordinate transform (unlike the
player/house markers). Ported verbatim into a static C# array (see
MapLocations.cs below) — town list cross-checked as the complete classic
Dereth town set (Holtburg, Arwic, Yaraq, Shoushi, Rithwic, Samsur, Zaikhal,
Xarabydun, Yanshi, Nanto, Kara, Lin, Mayoi, Baishi, Sawato, Tou-Tou,
Al-Jalima, Al-Arqas, Qalaba'r, Silyun, Bandit Castle, Fort Tethana, Glenden
Wood, Cragstone, Dryreach, Eastham, Lytelthorpe, MacNiall's Freehold,
Linvak Tukal, Uziz, Wai Jhou, Timaru, Sanamar, Stonehold, Redspire,
Bluespire, Greenspire, Neydisa, Mt Esper-Crater Village, Plateau Village,
Fiun Outpost, Danby's Outpost, Candeth Keep, Khayyaban, Kryst, Hebian-to,
Oolutanga's Refuge, Ulgrim's Island, Ayan Baqur, Aerlinthe Island,
Singularity Caul Island).
Seams reused (no new infrastructure needed)
- Panel mount recipe:
RetailUiRuntime.MountSocialPanel(src/AcDream.App/UI/RetailUiRuntime.cs:3033-3257) andLayout.SocialPanelController.cs— copied for a 2-tabMapHousePanelController. UiTabPanel(src/AcDream.App/UI/UiTabPanel.cs) — same tab-table/ActivateTabBehaviormechanism.ToolbarController+RetailPanelCatalog— add panel id 16 to bothMountedandToolbararrays; button un-ghosts automatically once its panel id resolves viaTryGetWindowName.RetailTooltipPresenterviaUiElement.AuthoredTooltipText/AuthoredTooltipEnabled— town marker tooltips, closing TS-85's last item.Layout.RowTemplateResolverpattern — the per-town hotspot child is a template-instantiated element exactly like Friends/Squelch/Fellowship rows (template LayoutDesc/element resolved fromm_pMap's own0x48/0x47attrs).LandDefs.GidToLcoord/LcoordToGid(src/AcDream.Core/Physics/LandDefs.cs) — coordinate math, already ported and tested (issue #106).ServerPosition(src/AcDream.Core.Net/Messages/CreateObject.cs:479) — House wire'sPositionfield, already parsed elsewhere.WorldTimeService.CurrentCalendar/DerethDateTime— calendar data; only a formatter matching retail's"Date: %s\nTime: %s"shape is new.RuntimeTradeState(src/AcDream.Runtime/Gameplay/RuntimeTradeState.cs) — J4.x-style session-scoped owner pattern, template forRuntimeHouseStateif the wire groundwork lands.
Open items carried into slice reports
- Live probe (slice 1) must re-confirm the desk-verified slot/panelId/ button facts above against the ACTUAL live DAT install, not just trust the committed fixture + FA doc (which are consistent with each other but both need the live cross-check the task mandates).
m_pMap's attrs0x47/0x48(hotspot template element/LayoutDesc) need a live read — the decomp explains their MEANING but not their VALUE.- House ListBox
0x100001e6's authored default content (the "You do not currently own a house" text) needs a live read to confirm it's baked into the LayoutDesc rather than something the client synthesizes. - Coordinate/N-S-E-W format string for the outside coordinate readout is
unrecoverable from this decomp dump (BN elides it behind an
unresolved vtable-slot placeholder) — implemented per the task's
pre-authorized fallback (sign-based N/S/E/W, one-decimal magnitude,
"42.1N, 33.6E"shape), marked inferred-pending-verification in code. - Seven House
Display*line builders deferred to an ISSUES entry (see above) — ownership of the wire groundwork (parsers,RuntimeHouseStateraw fields) still lands this session so a future session can pick up the text formatting without re-doing the wire. - Toolbar button
0x10000197(panel id 12 = Social) appears to be a real, currently-ghosted toolbar entry for the Social panel — contradictsSocialPanelController's class doc ("No toolbar button authors this id"). Flagged, not chased — outside Batch C's scope.