feat(ui): House tab ownership text — DisplayPurchaseTimeText + RuntimeHouseState

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>
This commit is contained in:
Erik 2026-08-17 03:38:16 +02:00
parent eb6f3bd8c8
commit 06512f0957
13 changed files with 601 additions and 99 deletions

View file

@ -109,6 +109,7 @@ internal enum GameRuntimeConstructionPoint
FellowshipCreated,
AllegianceCreated,
TradeCreated,
HouseCreated,
MovementCreated,
ActionsCreated,
EnvironmentCreated,
@ -127,6 +128,7 @@ internal sealed class GameRuntimeConstructionContext
public RuntimeFellowshipState? Fellowship { get; set; }
public RuntimeAllegianceState? Allegiance { get; set; }
public RuntimeTradeState? Trade { get; set; }
public RuntimeHouseState? House { get; set; }
public RuntimeLocalPlayerMovementState? Movement { get; set; }
public RuntimeActionState? Actions { get; set; }
public GameRuntimeEventHub? Events { get; set; }
@ -282,6 +284,17 @@ public sealed class GameRuntime
context,
faultInjection);
// House tab (Batch C, Map/House toolbar panel, 2026-08-17):
// deliberately minimal owner (ISSUES #413's own sizing note) —
// no live-object side effects, nothing to dispose, so no
// construction.Own() (unlike Trade above, which owns staged
// items' TradeState flags on live objects).
context.House = new RuntimeHouseState(context.EntityObjects.Objects);
Fault(
GameRuntimeConstructionPoint.HouseCreated,
context,
faultInjection);
context.Movement = new RuntimeLocalPlayerMovementState();
// Campaign CH slice CH2: local jump refusals (CommenceJump/
// DoJump's WeenieError family — research doc §4.2/§6.4) reach
@ -337,7 +350,8 @@ public sealed class GameRuntime
context.PlayerIdentity,
context.Fellowship,
context.Allegiance,
context.Trade);
context.Trade,
context.House);
context.Movement.AttachPhysicsPublication(
new RuntimeLocalPlayerPhysicsPublicationState(
@ -393,6 +407,7 @@ public sealed class GameRuntime
FellowshipOwner = context.Fellowship;
AllegianceOwner = context.Allegiance;
TradeOwner = context.Trade;
HouseOwner = context.House;
MovementOwner = context.Movement;
ActionOwner = context.Actions;
EnvironmentOwner = environment;
@ -501,6 +516,11 @@ public sealed class GameRuntime
/// <summary>Secure trade (2026-08-14): third sibling J-owner.</summary>
public RuntimeTradeState TradeOwner { get; }
/// <summary>Batch C (2026-08-17): House tab minimal owner — see
/// <see cref="RuntimeHouseState"/>'s own class doc for the sizing
/// rationale.</summary>
public RuntimeHouseState HouseOwner { get; }
public RuntimeActionState ActionOwner { get; }
public RuntimeLocalPlayerMovementState MovementOwner { get; }
internal RuntimeLocalPlayerPhysicsPublicationState