7 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
06512f0957 |
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> |
||
|
|
067cbea8a5 |
feat: secure trade with other players - wire, RuntimeTradeState, the
authored gmSecureTradeUI window, and both retail open paths
Three-lane research first (docs/research/2026-08-14-trade-lane{A,B,C}):
retail gmSecureTradeUI decode, the byte-exact ACE/decomp/holtburger
three-way wire agreement, and the acdream seam map (which found both
open paths ALREADY classified by the ported policy - OpenSecureTrade on
Use-a-player, StartSecureTrade on drag-item-onto-player with the
DragItemOnPlayerOpensSecureTrade option - dead-ending at a stub toast).
- Core.Net: TradeRequests builders (0x1F6-0x204, retail's CM_Trade
senders byte-checked against ACE's readers; the ACE-discarded
AcceptTrade echo carries zero-count item lists - AD-94), corrected +
completed inbound parsers (0x1FD-0x208; the old AddToTrade parser
missed the SIDE dword, TradeFailure missed the reason), delegate-hole
registrars, six WorldSession sends. 10 golden-byte tests.
- Runtime: RuntimeTradeState, the third sibling J-owner (fellowship/
allegiance shape): session-scoped, clears at generation reset (new
stage Trade=14), staged teardown stage 11 (Identity/EntityObjects
shift 12/13, TeardownStageCount 14 - the FA2-era per-stage-flag test
caught the mapping exactly as designed), combined ownership ledger,
event routing with ACE's wrong-initiator RegisterTrade landmine
honored (partner = whichever guid is not mine). 7 conformance tests.
- App: SecureTradeUiController binds the dedicated authored LayoutDesc
0x2100000D (root 0x1000007A - gmSecureTradeUI::PostInit's exact ids):
partner name/status/count/grid, the authored 'Trade' accept toggle
(accept <-> decline withdraw), 'Clear All' (ACE clears BOTH sides -
surfaced honestly), the X close, drop-on-your-grid staging, per-mode
accept cues (partner icon's authored Highlight state + Trade button
Selected latch). Mounted via the vendor recipe (nine-slice chrome,
hidden until RegisterTrade). ItemInteractionController's two policy
arms now raise SecureTradeRequested instead of the stub toast; the
drag path queues the dragged item until the window registers
(ClientTradeSystem::AttemptToTradeItem @0x0056DF80's shape).
Register: AD-94 (accept-echo zero-count lists), AD-95 (numeric-only
count texts pending template verification).
Suites: App 4,990/3, Core.Net 905, Runtime 1,626 - all green. The
panel itself is user-gate acceptance (two-client connected trade), the
#372-class lesson: fixture-green alone is not acceptance for a mount.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
4272ad0ea4 |
fix(net,runtime): FA2 fix-round MUST-FIX -- allegiance clears at reset, 0x027C stops seeding
Two MUST-FIX findings from the FA2 mechanism/blast reviews (docs/research/2026-08-12-fa2-review-mechanism.md, docs/research/2026-08-12-fa2-review-blast.md): MF-1 (mechanism) -- RuntimeAllegianceState survived a generation reset, contradicting retail (ClientAllegianceSystem::OnEndCharacterSession @0x00569FA0 tail-calls AllegianceProfile::Clear at the same boundary Fellowship already clears at), contradicting the precedent it cited (RuntimeCharacterOptionsState.ResetSession clears-and-relatches, it does not persist), and pinned by a test asserting the wrong behavior. Fixed: RuntimeAllegianceState.ResetSession() clears the profile and drops HasServerSeed; a new RuntimeGenerationResetStage.Allegiance stage runs it on every generation reset, mirroring RuntimeFellowshipState exactly. RuntimeGenerationResetTests' FellowshipClearsAtResetButAllegianceSurvivesReconnect inverted to FellowshipAndAllegianceBothClearAtGenerationReset. MF-2 (mechanism) / blast MF-2 -- 0x027C AllegianceInfoResponse fed the Runtime allegiance owner (self-gated). Retail's own handler for 0x027C (CM_Allegiance::DispatchUI_AllegianceInfoResponseEvent @0x006a7470) unpacks into a stack-local profile destroyed on return; the consumer (Handle_Allegiance__AllegianceInfoResponseEvent @0x0056a1d0) only prints AddTextToScroll lines. Retail's panel is fed exclusively by 0x0020 AllegianceUpdate. The removed seeding also fabricated RuntimeAllegianceSnapshot.Rank (0x027C carries no rank field) on any client whose first allegiance message was a self @allegiance info query. Fixed: dropped ApplyInfoResponseSelf, the onAllegianceInfoResponseSelf delegate hole, and the self-gate; 0x027C is text-only again, matching retail and the pre-FA2 shape. Also covers blast SHOULD-FIX 1 in the same edit to LiveSessionEventRouter.cs: the fellowship/allegiance delegate holes are now passed conditionally on the owner being supplied, so GameEventDispatcher.GetUnhandledCount reads correctly for callers without an owner (bare-ChatLog tests, a future partial host) instead of silently reading 0 for 9 event types whose parse result was discarded. RuntimeAllegianceState.cs and the two owners' Apply* mutators also move their ObjectDisposedException.ThrowIf checks inside the lock they already take (mechanism SHOULD-FIX 2) -- the prior check-then-lock shape let an inbound event on the decode thread race Dispose on the host thread and repopulate state after _disposed = true, permanently falsifying CaptureOwnership().IsConverged at teardown. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
369729f06a |
feat(runtime): FA2 -- RuntimeFellowshipState + RuntimeAllegianceState sibling J-owners
Two new sibling Runtime owners under GameRuntime per the Slice-J pattern (D2): RuntimeFellowshipState is session-scoped (a new RuntimeGenerationResetStage.Fellowship clears it at every generation reset, matching the ExternalContainer precedent); RuntimeAllegianceState survives reconnect behind a HasServerSeed-style one-way latch and participates in NO reset stage (its data persists like a real disconnect does not sever allegiance membership). Fellowship: full-update REPLACE, incremental-fellow UPSERT, self-vs- other quit/dismiss removal (self clears the whole snapshot), disband clear, and retail's leader hand-off rule for the Quit button (RequiresLeaderHandoffBeforeQuit -- the current leader quitting WITHOUT disbanding must send 0x0290 AssignNewLeader before 0x00A3, lane B §2.5/§3.6). Allegiance: seeded by AllegianceUpdate (0x0020, always self) and, self-gated on TargetGuid == playerGuid(), by AllegianceInfoResponse (0x027C); wraps the FA1-assembled flat AllegianceMemberRecord list directly (AllegianceTree was deleted at FA1 -- nothing left to wrap). Both apply the full 8-edit J-owner template: construction + fault points + Owner/View properties + CaptureOwnership + a new GameRuntimeTeardownStage pair (FellowshipDisposed/AllegianceDisposed, stage count 11->13) + RuntimeGameplayOwnershipSnapshot inclusion + RuntimeStateCheckpoint/trace fields. IRuntimeFellowshipCommands/ IRuntimeAllegianceCommands added to IGameRuntimeCommands and implemented on DirectGameRuntimeCommandAdapter. No IRuntimeEventObserver member added (D2) -- consumers poll Snapshot.Revision. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e0e7888308 |
fix(chat): CH2 rework — SpewBox tick-driven visibility + binary-derived error table
Reworks Campaign CH slice CH2 per the REJECT-review findings doc (docs/research/2026-08-09-ch2-review-findings.md). BLOCKER 1 — SpewBoxController never rendered a line and leaked its pending queue. LinesProvider only ran through UiText.OnDraw, which gates on Visible — and the box started invisible, so the provider (the sole caller of SpewBoxState.Tick) never ran. Gave the controller an explicit per-frame Tick(now) driven by UiRoot's global-message-3 broadcast (a zero-size GlobalTimeSink child, the same pattern VendorUiController.DragOverGlobalTimeSink already uses), matching retail's gmSpewBoxUI::Update. LinesProvider now only returns the cache. Tests rewritten to drive root.Tick(...) instead of calling the provider directly, plus new coverage for visibility-without-a-draw, queue-drain-without-a-draw, and bounded-queue-across-many-ticks. BLOCKER 2 — re-derived the HandleFailureEvent routing table from the PDB-paired binary instead of the pseudo-C's ~33-char string previews. tools/pdb-extract/sweep_weenie_strings.py sweeps every push imm32 in VA 0x571990-0x575480, dereferences into .rdata/.data, and decodes the full UTF-16LE literal. Added the 5 ids dispatched via else-if (missed by case-label enumeration), resolved 0x4F8 (previously excluded), fixed 18 wrong strings (16 the review flagged + 2 more — 0x4E9 and 0x518 — an automated diff between every swept literal and the landed table found). Every changed row cross-checked against ACE's WeenieError/WeenieErrorWithString enum doc comments; both oracles agreed on every row, including a case where the review's own proposed text for the new 0x4E8 row was itself wrong (it was 0x4E9's text) — corrected via the else-if block's own instruction address plus the ACE cross-check. Pinned table count: 344 (338 + 5 + 0x4F8). SHOULD-FIX 1 — RuntimeCommunicationState.ResetSpewBox was dead code; folded into the ChatIdentity generation-reset stage (same lifetime boundary), with a reset assertion added to the existing populated-reset test. SHOULD-FIX 2 — AddText trimmed only the trailing end and invented an empty-string early return; retail's AddTextToScroll trims both ends (trim(&str, 1, 1, ws)) and has no empty guard. Both retired. SHOULD-FIX 3 — ShowWeenieError bypassed the AddText chokepoint via ChatLog.OnWeenieError (hardcoded LogTextType 0x00); routed through Communication.AddText(Resolve(code, param)) instead, and ChatLog.OnWeenieError is deleted — GameEventWiring's legacy no-router fallback now resolves + calls OnSystemMessage directly. SHOULD-FIX 4 — retail's HandleFailureEvent switch has no default case; an unmapped id now resolves to a null Text (silence toward the player) instead of the invented "WeenieError 0xNNNN" hex fallback, with a diagnostics-only console log line for the id. NITs — AP-TBD placeholders corrected to their real register rows (AP-178, not the unrelated AP-177 lifetime row); filed AP-180 for the windowId dual-destination gap and corrected three stale "lands with CH2" comments; extended SpewBoxLayoutDumpDiagnostic from dats.Portal to dats.Local and found the SpewBox element for real — LayoutDesc 0x21000011, element 0x10000048, size 450x72, MaxConcurrentItems (ListBox property 0x10000028) = 4, not retail's code default of 1. AP-178 narrowed accordingly; SpewBoxState.MaxConcurrentItems and SpewBoxController's extent/anchor/OneLine are now authored rather than placeholder (absolute screen position and colour remain open); fixed the "19 ids... lists 18" miscount by retiring the stale paragraph in the class doc rewrite; aligned the UseDone handler's silent-status check with the other two WeenieError handlers. Full Release suite: 11,914 passed / 4 skipped / 0 failed (build 0 errors). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9796d71522 |
feat(runtime): Slice 5.3 — RuntimeInventoryState owns the vendor browse session
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
The sole VendorState joins the J4.2 inventory owners: populated by the new 0x0062 ApproachVendor route (parse via VendorApproach, wire-to- domain mapping at the routing seam, silent-drop on malformed like every sibling), borrowed by both graphical and headless hosts, and torn down through the EXISTING ExternalContainer reset stage — session reset, portal-out, and logout all funnel through the one mechanism. Close is client-local per retail (nothing on the wire): a range watcher rides the existing per-advanced-frame publishMovement callback, using the vendor's own authored UseRadius (ACE's 0.6 m fallback when absent). The dormant ItemInteractionController ActiveVendorId seam is finally wired as a live delegate — real id while open, 0 the moment the session clears. AP-160 filed in this same commit: the watcher measures plain 3D center distance rather than retail's cylinder-gap, because Runtime has no per-NPC collision radius/height source; bounded sub-meter, client- local UI only. Twelve Runtime tests: populate/field mapping, vendor replacement, range clear + within-range retention, all three generation teardowns, the ActiveVendorId seam, malformed-event drop. Clean-room complete solution: 11,302 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> |
||
|
|
a9a822f206 |
refactor(runtime): unify generation reset for direct hosts
Move canonical per-session teardown into one retryable Runtime transaction, reduce App reset to projection acknowledgements, and prove the same GameRuntime graph through deterministic no-window lifecycle, gameplay, portal, fault, reconnect, and isolation gates.\n\nCo-authored-by: Codex <noreply@openai.com> |