CM_House::Event_QueryHouse @0x006aaa00 (opcode 0x21e) tail-calls
unconditionally from the end of CPlayerSystem::InitializePlayer
@0x00563570 — the same once-per-session function
AttemptSendLoginCompleteNotification lives in (both guarded by the
player_initialized flag), right after that notification. Retail never
sends it from gmHouseUI::PostInit or gmMapUI::PostInit on House-tab
activation.
Moved WorldSession.SendHouseQuery() to the direct (non-portal)
first-entry completion edges — the same places acdream already sends
the analogous "initial session bootstrap" LoginComplete:
- graphical: LiveSessionRuntimeFactory's RuntimeFirstEntryDriveController
localPlayerCompleted callback
- headless: HeadlessSessionHost's equivalent callback
- headless content-less direct host: RuntimeLiveEntitySessionController.OnSpawned
Portal-space re-entries (LocalPlayerTeleportController's F751 path,
RuntimeLiveEntitySessionController.TryAdvancePortalCompletion) do NOT
resend it, matching retail's single-shot guard.
Removed the invented House-tab-open -> SendHouseQuery trigger
(InteractionRetainedUiComposition's HouseShown binding) and retired
register row AD-107, which had documented that adaptation.
Updated RuntimeLiveEntitySessionControllerTests' exact game-action
assertions for the content-less path, which now also captures the
HouseQuery send alongside LoginComplete.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Derived the mechanism from the decomp before writing code: neither
gmHouseUI::PostInit @0x004a2710 nor gmMapUI::PostInit @0x004a1c70 sends a
HouseQuery, and six of gmHouseUI's seven Display* builders early-return on
m_pHouseData == 0. The only text a houseless character's House tab shows is
gmHouseUI::DisplayPurchaseTimeText @0x004a3110's expired branch (it doesn't
gate on m_pHouseData) — the local player's PropertyInt.HousePurchaseTimestamp
plus HouseSystem::HasPurchaseWaitPeriodExpired renders exactly "You may buy
another house immediately." for a fresh character. Exhaustive search of the
2013 EoR decomp, ACE, and the live DAT found zero support for a second
"You do not currently own a house." line the task brief described — this
commit ports what the decomp actually shows.
Ships:
- RuntimeHouseState: a minimal (no disposal, no construction-transaction
Fault() point) Runtime owner per ISSUES #413's own sizing note, wired
through GameEventWiring's existing HouseData/HouseStatus delegate holes,
LiveSessionEventRouter, and GameRuntime.HouseOwner. Participates in
RuntimeGenerationReset (new House stage) since a fresh login must not
show a stale character's house state.
- HousePageController.Bindings.Lines/OnShown wired to real data; OnShown
fires WorldSession.SendHouseQuery() on tab-open (AD-107: an acdream
trigger, not a ported retail call site — filed in the divergence
register).
- Fixed a real bug found along the way: HousePageController.Bind never
wired UiTemplateListBox.TemplateResolver, so no row could ever render
regardless of Lines content. Now reuses the Map tab's generic hotspot
resolver.
Live-verified against a real local ACE server and the +Acdream character
(--session-config auto-select + a UI automation script): screenshot and
structural UI-tree dump both confirm the House tab renders exactly "You may
buy another house immediately." Graceful logout confirmed both launches.
ISSUES #413 narrowed to its one remaining piece: the six owned-house-only
Display* builders (DisplayBuyPayment/RentPayment/BuyTime/RentTimes/
Location/WarningText), unexercisable without a test character that owns a
house.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The class doc referenced RuntimeHouseState.PurchaseAvailabilityText as
already wired this session; it isn't (deferred to #413, the
RuntimeHouseState owner integration). Corrected to accurately describe
what shipped (mount + wire parsing groundwork) vs what's still open.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mounts host 0x2100006E slot 0x1000018C (RetailPanelCatalog.MapHouse = 16)
as a two-tab UiTabPanel (Map default, House second) through the OP3/FA3
recipe (LayoutImporter.Build -> Bind -> ActivateTabBehavior). Toolbar
button 0x1000019A un-ghosts (added to both RetailPanelCatalog.Mounted and
.Toolbar). Combined into one commit because MapHousePanelController.Bind
depends on both MapPageController and HousePageController existing —
splitting them would mean landing dead code first.
Map tab (gmMapUI, MapPageController):
- Calendar formatter matching gmMapUI::Update's "Date: %s\nTime: %s"
shape, reusing WorldTimeService.CurrentCalendar (new
Func<DerethDateTime.Calendar> dependency threaded through
InteractionRetainedUiDependencies/GameWindow — a stable long-lived
service, not routed through the deferred-binding machinery Radar's
per-session state needs). MonthName enum values already match retail
display text; HourName's "AndHalf" suffix is rewritten to "-and-Half".
- Coordinate math + marker placement reuse RadarCoordinates/
LandDefs.GidToLcoord verbatim (both already byte-exact ports of
CPlayerSystem::InqPlayerCoords/LandDefs::gid_to_lcoord) — no re-port.
PlaceMarkerOnMap's centering math (m_x0 + x - w/2) ported from
gmMapUI::PlaceMarkerOnMap @0x004a18b0. Indoor gating clears the
coordinate text and hides the player marker, matching
gmMapUI::Update's else branch.
- 53-town s_rgLocations table ported verbatim into MapLocations.cs.
Markers built once at bind time via the panel's own RowTemplateResolver
against m_pMap's authored hotspot-template attrs (0x47/0x48), with
literal-string tooltips through AuthoredTooltipText/Enabled
(RetailTooltipPresenter) — closes divergence-register row TS-85's last
item, gmMapUI::AddMapNote @0x004A1C51.
- Structural finding: m_pMap (0x100001EC) is itself authored as a Type-1
BUTTON (the GM click-to-teleport hook at
gmMapUI::ListenToElementMessage), and the player/house icons
(0x100001ED/EE) are its own NESTED children, not siblings —
UiButton.ConsumesDatChildren swallows them from the normally-built
tree. Both are re-resolved standalone through the same template
resolver the town hotspots use and reattached under m_pMap.
House tab (gmHouseUI, HousePageController): mounts the ListBox
(0x100001E6) with its authored row template, wired to an empty Lines()
source by default — genuinely empty until Slice 4's wire lands, matching
retail's own PostInit (no Update call, no static content).
21 new tests (7 MapHousePanelControllerTests, 14 MapPageControllerTests):
tab table pairing, close button, town-hotspot count/tooltips, calendar
formatter golden values (Frostfell 27/119 P.Y., every HourName incl.
AndHalf), player/house marker placement and indoor-gating reproduced
against the real fixture via already-tested RadarCoordinates (no
re-derivation). Fixture map_house_2100006E_1000018C.json captured via
the shared RetailLayoutFixtureGenerator (other 34 fixtures deliberately
NOT regenerated — out of scope for this batch, would touch unrelated
panels' schema drift).
Full solution builds clean; App suite 5391/0 failed/71 skipped (non-live;
one earlier flaky streaming failure unrelated to this change, confirmed
pre-existing on the branch before these commits).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>