Commit graph

3559 commits

Author SHA1 Message Date
Erik
eda8729c92 docs: FA4 blast-radius review -- APPROVE-WITH-FIXES (1 SHOULD-FIX)
All nine blast axes verified clean at the code level. Single fix:
the AD-78 register row still reads "35 of 50 rows dimmed" after FA4's
D7 flipped four rows to Live (now 31 of 50) -- the class doc and
conformance test were updated, the binding register row was not.
Plus one minor non-blocking observation on GetMembers' per-vitals-tick
allocation profile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 04:57:11 +02:00
Erik
38f08314c7 docs(fa4): register rows AD-80/AD-81, AD-78 addendum, gate script section, ledger
Register: AD-80 files the D5 XP-share display divergence between
retail's byte-decoded table (acdream renders it verbatim) and the
currently-targeted ACE server's slightly different actual grant (.3 vs
.3111111 at 9 fellows, no 10-fellow row, wrong out-of-range default) --
an ACE-vs-retail gap, not an acdream-vs-retail one, filed because it is
directly user-visible through this panel. AD-81 files the two unported
retail text-composition primitives the fellowship page's mechanism
needs (StringInfo variable substitution, ACCharGenData::FormatName) and
what acdream renders instead (plain numeric composites, the raw typed
name). AD-78's derivation table gains its D7 addendum: 4 of the 35
store-only rows (IgnoreFellowshipRequests/FellowshipAutoAcceptRequests/
FellowshipShareXP/FellowshipShareLoot) moved to the Live bullet with
their new consumers named.

Gate script: new §FA4 section covering create (name + shareXP), the
open/close caption swap, button-enable rules, and the D5 display -- all
solo-testable -- plus roster/recruit/dismiss/leader-handoff/invite-
dialog steps marked [TWO-CLIENT] with an honest note that they defer to
FA6's bot-vs-ACE gate if a second account isn't available for this
connected gate. Corrects FA3's now-stale "these six buttons/four
checkboxes are INERT" claims in steps 11-12 to point at the new
section instead of leaving a wrong claim in place.

Ledger: FA4 row CODE-COMPLETE with both commit SHAs, the reconciled
13,238->13,272 (+34) test-count arithmetic, the live-DAT verification
summary (ACDREAM_PROBE_LIVE_MOUNT=1 against real installed DATs,
including the structural finding that retail's own frame-visibility
swap already gates the Create-flow controls away from the roster view
with no extra code needed), and the four scoped
deferrals/simplifications this slice made (the StringInfo/FormatName
gap, the proportional-share omission, the Recruit button's
superset-of-retail enable rule).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 04:41:53 +02:00
Erik
5bdd0528f1 feat(ui): FA4 -- fellowship page fully live
Roster: SocialFellowshipPageController now builds one row per fellow
from the authored template (0x21000030/0x10000281, live-DAT verified),
diffing the member GUID set on each revision-gated Tick -- an unchanged
set updates every row's bound widgets in place (no ListBox mutation, so
scroll position is untouched by construction); only a real join/leave/
disband triggers a rebuild, via UiTemplateListBox.FlushPreservingScroll
(FA3 carry-forward 1, both the widget-level fix and the controller-level
diff). Health/stamina/mana meters bind Fill+Label; the leader's name
tints gold (lane A's row template has no dedicated leader marker, so
this is a flagged adaptation, not a ported mechanism). Row-click
selection (SelectFellow) drives Dismiss/Leader targeting and the world
selection (SelectionChangeSource.Social).

D4: SocialPanelController now tracks "is the social window shown AND is
Fellowship the active tab" via UiTabPanel.ActivePageChanged +
OnShown/OnHidden, and calls SetPageVisible on every transition, which
sends 0x00A6 (idempotent, no-op while disconnected) -- the prerequisite
ACE gates its 0x02C0 vitals stream on.

Create flow: the inline name field (0x1000026F, an authored Editable
UiField -- live-DAT verified) gates the Create button's enabled state
exactly like retail (empty name = disabled = the whole refusal
mechanism, no separate error text); FellowshipShareXP's live value is
read at click time.

Actions + confirmations: Recruit/Dismiss/Quit/Disband/AssignLeader/
SetOpen all route through DeferredGameRuntimeStateCommands (new
Fellowship* methods) rather than a raw WorldSession send, so Quit
correctly picks up RuntimeFellowshipState's leader hand-off rule.
Button enable states port gmFellowshipUI::UpdateButtons verbatim. The
Open/Close button's caption swaps between the two DAT-resolved strings
cached once at Bind (never per-tick -- DatCollection is not safe to
touch unprotected from the render loop). RetailUiRuntime intercepts a
type-4 confirmation request before it reaches the generic
GameplayConfirmationController: IgnoreFellowshipRequests auto-declines,
FellowshipAutoAcceptRequests auto-accepts, neither set falls through to
the existing dialog machinery unchanged (D6).

D5 display: the per-fellow stats line uses retail's byte-decoded
even-split percentage table verbatim (1.0/.../.3111111/.28, default
0.0); the proportional branch omits the percentage rather than
inventing a formula (no acdream ExperienceToRaiseLevel table exists
yet). Both StringInfo variable substitution (row/stats/vitals text) and
ACCharGenData::FormatName (create-flow name canonicalization) are
unported prerequisites, so row text renders as plain numeric composites
-- register rows AD-80/AD-81 (docs commit).

D7: un-dims IgnoreFellowshipRequests/FellowshipAutoAcceptRequests
(consumed by the D6 auto-decline/accept) and FellowshipShareXP/
FellowshipShareLoot (consumed by Create + the page's own second
checkbox surface) on the Character tab -- 4 of 35 store-only rows
promoted to Live (31 remain dimmed).

Carry-forwards from the FA3 re-review, folded into this slice's
contract:
- UiTemplateListBox.FlushPreservingScroll -- preserves scroll offset
  across a rebuild instead of resetting to 0 (Flush's existing
  contract, unchanged, for Friends/Squelch).
- RowTemplateResolver -- the FA3 caching row-template resolver
  extracted from a MountSocialPanel local function into its own
  hermetically-testable class; now shared by Friends/Squelch/
  Fellowship's row families.
- Friends/Squelch scrollbars now resolve via the built
  UiTemplateListBox.ScrollbarElementId (DAT property 0x72) instead of
  a hardcoded literal, matching ConfigOptionsPageController's own OP6
  precedent.
- The Fellowship roster path never advances its revision latch on a
  partial resolver failure until the NEXT real membership change --
  never a per-frame retry loop.

Live-DAT verified (ACDREAM_PROBE_LIVE_MOUNT=1, extended
SocialPanelLiveMountProbeTests): the name field builds as UiField, all
11 buttons/checkboxes resolve, the row template's 5 checked fields
resolve to the right widget types, every checkbox label/tooltip and the
Open/Close captions resolve to real retail strings ("Open"/"Close"),
and a full production-path Bind() against live DATs produces zero
"not found" warnings.

App tests: +30 (7 UiTemplateListBox/RowTemplateResolver unit tests, 23
SocialFellowshipPageControllerTests covering roster diff/rebuild,
button enable rules, checkbox wiring, create-flow gating, D4
idempotency, and D5 formatting) plus 2 CharacterOptionsPageController
counts updated for the D7 un-dim (35->31 dimmed, 15->19 live).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 04:40:53 +02:00
Erik
357d203202 feat(runtime): FA4 -- fellowship roster enumeration + Social selection source
IRuntimeFellowshipView.GetMembers() gives the fellowship panel a way to
enumerate the whole roster (TryGetMember alone needs the guid first,
which a UI roster build doesn't have yet). Implemented on
RuntimeFellowshipState.FellowshipView as a materialized snapshot under
the same lock every other read there uses.

SelectionChangeSource.Social covers a fellowship-roster row click
(gmFellowshipUI's list-selection arm calls the same
ACCWeenieObject::SetSelectedObject primitive every other selection
origin uses -- lane B docs/research/2026-08-11-fa-panel-structure.md
§6.2/§2.8).

Runtime tests: +4 (RuntimeFellowshipStateTests.GetMembers_*).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 04:40:19 +02:00
Erik
afb3223c9b docs: FA3 CODE-CLOSED (re-review bf07b70e) — carry-forwards folded into FA4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 03:53:07 +02:00
Erik
bf07b70ef1 docs: FA3 mechanism re-review -- CLOSED, no reopen (4 carry-forwards)
Narrow re-review of the FA3 fix round (9afa05b5/35c40a9b/ae772709/
a5553904/c5f73744) against this doc's 2 MUST-FIX and 9 SHOULD-FIX. Every
disposition re-derived from the diffs, not from the commit messages. All
11 correctly applied; nothing reopened; no new MUST-FIX or SHOULD-FIX.

Dispositions worth naming: MF-1/MF-2 were fixed as script REWRITES that
state the verified truth (Allegiance is both the default and the
left-most tab; @allegiance info prints chat data AND the blocks stay
hidden, report neither) rather than deletions, and MF-2's report-bullet
is correctly inverted to "blocks becoming VISIBLE is the anomaly".
SF-3's assertions pin more than asked (which page is visible, not just
that one is). SF-7 landed as a binding FA5 acceptance line plus the
forward hazard about Tick()'s unconditional LinesProvider reassignment,
the right altitude for a shell slice.

Claims verified rather than accepted: SF-1's no-register-row precedent
(grepped -- no row exists for any Toggle*Panel close-on-second-press, so
the precedent is real); the #383 timestamp correction (b4edee97
2026-08-11 09:19, e71e5a96 06:25, 74c3d85d 2026-08-12 02:58 = ~17h39m
and ~20h33m, previous day); OnShown/OnHidden really are driven by
RetailWindowHandle.NotifyVisibility and correctly do NOT fire for a
window mounted Visible=false; UiTemplateListBox.Scroll forces the
extent-seeded viewport so pre-row scrollbar wiring is sound; and the new
template cache is safe because Build is the pure builder -- only
BuildFromInfos (tests-only) mutates the ElementInfo it is handed.

Gates re-run on the post-fix Release binaries: live-mount probe passes
against the installed DATs with the promoted assertions showing
Allegiance Visible=True (other three False) and 0x10000492 count = 2;
AcDream.App.Tests 4,876 passed / 3 skipped / 0 failed, exactly +5 over
the pre-fix 4,871 and exactly the ledger's App figure, so the 13,238/4/0
+5 reconciles at the only project this round touched. Blast radius is 14
files, all FA3's own plus doc-only edits to UiTemplateListBox and
MountSocialPanel.

Carry-forwards (non-blocking): the Flush scroll-position reset will bite
harder in FA4's per-vitals-tick roster rebuild; the production template
cache has no test (both long-roster tests use the fake resolver); the
scrollbar element ids are literals where ScrollbarElementId carries the
authored value (cohort-wide nit); and the allRowsResolved retry rebuilds
per frame on a permanently unresolvable template (bounded and cheap).

FA3's remaining obligation is unchanged: the user's connected gate,
against a script that no longer contains two instructions guaranteed to
produce false defect reports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 03:52:08 +02:00
Erik
c5f73744f0 docs(fa3): update plan ledger with fix-round SHAs and final totals
Records the FA3 fix-round closure: both dual-lens reviews' findings
(mechanism 2 MUST-FIX/9 SHOULD-FIX, blast 1 MUST-FIX/6 SHOULD-FIX/1 NIT)
applied across four commits (9afa05b5/35c40a9b/ae772709/a5553904).
Baseline 13,233/4/0 (13,237 total) -> fix-round 13,238/4/0 (13,242
total), +5 tests, arithmetic verified directly against all 9 test
project totals. FA3 still owes the user's connected gate against the
now-corrected script.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 03:46:10 +02:00
Erik
a555390483 docs(fa3): fix gate script tab order + false-defect route, add scroll/restore-open steps, correct #383 timing, fix bold markers + add U11
Mechanism MUST-FIX 1: the connected-gate script's steps 1/9/10 carried
the REFUTED x-order guess forward — claiming Friends was drawn left-most
and that the authored default (Allegiance) was somehow NOT the left-most
tab. The real authored order (fixture + live-mount probe, corroborated
by each page's own P0x57 action-map id) is Allegiance (x=0, DEFAULT),
Fellowship, Friends, Squelch — the default tab IS the left-most tab.
Fixed steps 1, 9, 10, and the "What to report" bullet that repeated the
wrong claim.

Mechanism MUST-FIX 2: step 14 sent the user to `@allegiance info` as a
trigger that would supposedly reveal the monarch/patron blocks, and told
them to report it if it didn't — the trigger CANNOT fire post-FA2
(0x0020 AllegianceUpdate is the only inbound writer of this panel's
data; 0x027C, the @allegiance info response, stopped seeding it in
4272ad0e) and FA3 sends no 0x001F subscription at all (FA5 scope). The
script primed the user to file a false defect. Rewritten to state the
true FA3 expectation: @allegiance info prints real data to chat, the
panel blocks stay hidden regardless, for the whole gate — report
NEITHER half as a bug; the actual anomaly to watch for is the blocks
becoming visible at all.

Mechanism SHOULD-FIX 4: step 11's Fellowship checkbox count hedge
("three... a fourth may also be present") replaced with the settled
count (four).

Blast NIT 8 / gate note: added two steps the original script never
exercised — a long-roster Friends/Squelch scroll check (exactly where
blast MF-1's scrollbar-wiring fix bites, and a short test roster would
never surface it) and an honest restore-open-across-relaunch
observation step (the social panel follows the SAME restore-open
convention every sibling main panel already has — Options/Spellbook/
Character/Inventory/Vitae — stated up front so it isn't mistaken for a
bug mid-gate).

Blast SHOULD-FIX 6: docs/ISSUES.md #383 said the two drifted fixtures
were committed "days ago" — git says otherwise: ~18h and ~21h before the
FA3 regeneration run, the previous day. Corrected, and added the
mechanism reviewer's no-drift finding for the NEW social-panel fixture
(cross-checked against the live probe on every axis, zero drift) —
narrows the issue to exactly the two pre-existing OP-era fixtures.

Blast SHOULD-FIX 7: the §10 addendum in fa-panel-structure.md had five
`**` bold markers (odd count) — an orphaned trailing marker bled bold
formatting into the following section. Dropped the orphan; the addendum
now bolds only its lead sentence and the inline "Allegiance" callout,
both balanced pairs.

Mechanism SHOULD-FIX 1 (research-doc half): filed unknown U11 in §8 —
what a repeat F3/F4 press does when the panel is open on the OTHER tab
is not established from retail decomp (no OnAction consumer exists for
either action in the binary); acdream's own OpenSpellbook-precedent
choice is not a retail port.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 03:44:26 +02:00
Erik
ae77270939 fix(ui): fellowship checkbox count, row-text + ListBox Flush doc corrections, promote probe findings to assertions
Mechanism SHOULD-FIX 4: SocialFellowshipPageController's class doc said
0x1000026B holds "all three visible option checkboxes" — the fixture and
the decomp both authors FOUR (Ignore Fellowship Requests / Auto-Accept
Requests / Share XP / Share Loot, 0x10000270-0x10000273). Corrected.

Mechanism SHOULD-FIX 9: SocialPanelRowText.FindDeepest's doc promised
"the deepest UiText descendant" but the implementation returns the LAST
match in pre-order traversal order, which only equals the deepest when
the subtree is a single chain. Both real row templates ARE single
chains today, so behavior is unaffected — the doc now describes what the
code actually does instead of a stronger guarantee it doesn't implement.

Blast SHOULD-FIX 5: UiTemplateListBox.Flush()'s doc only mentioned the
ContentHeight reset; UiScrollablePanel.ClearContent() also resets scroll
position to 0, which the sibling UiItemList.Flush() (same method name,
different semantics) does NOT do. Documented explicitly, including the
UX cost this creates for a scrolled-in Friends/Squelch roster once its
scrollbar is wired (this fix round's blast MF-1) — flagged for whoever
revisits Friends/Squelch scrolling next rather than silently fixed as an
unasked behavior change.

Mechanism SHOULD-FIX 3: SocialPanelLiveMountProbeTests printed two
headline findings (the 0x10000492-authored-twice count, page exclusivity
after ActivateTabBehavior) without ever asserting them — a future
importer regression collapsing/dropping an instance, or breaking
exclusivity, could not fail this test. Both are now real assertions
(Assert.Equal(2, passupCount); exactly one page Visible and it is
Allegiance).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 03:44:05 +02:00
Erik
35c40a9b56 fix(ui): allegiance page — hoist per-frame LinesProvider allocation, document the coarser empty-state gate
Mechanism SHOULD-FIX 2 / blast SHOULD-FIX 4 (same finding, both reviews):
SocialAllegiancePageController.Tick() closed over a local `lines` on
every call — `() => lines` allocated a display class plus a delegate on
EVERY frame, unconditionally, at ~2,000 allocations/second at the
profile's measured FPS, in a hot loop the Modern Runtime slices spent
whole commits driving to 0 B/frame. Hoisted two static readonly
Func<IReadOnlyList<UiText.Line>> providers (BlankLineProvider/
NoLinesProvider); Tick() now assigns the cached delegate reference —
zero allocation while idle or active.

Mechanism SHOULD-FIX 7: FA3's empty-state gate is coarser than the
retail mechanism it is contracted against — gmAllegianceUI::UpdateMonarchData
@0x00491B40 hides the monarch/patron blocks per-relationship (monarch
block also hides when the monarch IS the viewer; patron block on the
analogous test), while this shell gates both blocks on the single
HasProfile flag. Not a MUST-FIX for FA3 (MF-2 in the same review means
HasProfile is effectively always false for the whole FA3 gate, so
nothing wrong is visible during this slice's own gate) — recorded
instead as an explicit FA5 acceptance line in the plan's FA5 row so the
gap cannot be silently lost, plus a class-doc note that FA5's real
monarch/patron population must also change Tick()'s unconditional
LinesProvider reassignment in the same commit or its content will be
overwritten the next frame.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 03:43:48 +02:00
Erik
9afa05b563 fix(ui): FA3 fix-round — wire Friends/Squelch scrollbars, gate rebuild on visibility, fix revision-latch ordering
Campaign FA slice FA3 dual-lens review fix round.

Blast MUST-FIX 1: SocialFriendsPageController/SocialSquelchPageController
never wired their ListBox's own sibling scrollbar (Friends
0x10000517->0x10000518, Squelch 0x1000053E->0x10000543) — the two lists
had NO scroll driver at all (no wheel fallback exists on
UiScrollablePanel), making any roster past the visible extent completely
unreachable, not just awkward to scroll. Both controllers now wire
`scrollbar.Model = listBox.Scroll` scoped to their own page root, exactly
like the four existing UiTemplateListBox consumers
(Character/Chat/Config/KeyboardConfig). New tests prove the wiring AND
that a >panel-height roster is actually reachable through it.

Blast SHOULD-FIX 2/3: the Friends/Squelch resolver re-ran
LayoutImporter.ImportInfos (a full DAT tree walk) under the shared DAT
lock on EVERY row, every revision, even while the panel was closed —
RetailUiRuntime.MountSocialPanel's TemplateResolver now caches each row
template's ElementInfo the first time it is resolved and never
re-Imports for that template id again. SocialPanelController additionally
gates the Friends/Squelch Tick-driven rebuild on the panel's own
visibility via the (previously unused) IRetainedPanelController
OnShown/OnHidden hooks, so no DAT-locked rebuild work runs at all while
the panel is closed. SocialFriendsPageController/SocialSquelchPageController
also now only advance _lastRevision after every row resolves — a
transient resolver miss no longer latches an empty roster until the next
server-side change; it retries on the next Tick instead.

Mechanism SHOULD-FIX 8: SocialPanelController.Tick() now returns early
once disposed, matching every other J-slice teardown discipline (it was
being ticked unconditionally forever since RetailUiRuntime never nulls
the field).

Mechanism SHOULD-FIX 1: IsShowingAllegiance's doc claimed the F3/F4
close-on-second-press semantics were "retail's Toggle-action semantics" —
re-derived and confirmed NO retail OnAction consumer exists for either
action anywhere in the binary. Relabeled as acdream's own
OpenSpellbook-precedent convention; no register row added, following the
same no-row precedent OpenSpellbook and every other non-toolbar Toggle
panel already sets. Unknown filed as U11 in the panel-structure research
doc's §8 table.

Tests: 5 new (2 scrollbar-wiring pins, 2 long-roster-reachable-via-
scrollbar, 1 hidden-panel-does-not-rebuild/shown-panel-catches-up); 2
existing tests updated for the new visibility gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 03:43:35 +02:00
Erik
9e622f565e docs: FA3 mechanism review -- APPROVE-WITH-FIXES (2 MUST-FIX, 9 SHOULD-FIX)
Campaign FA slice FA3 mechanism-faithfulness review of 0a9ca2f1 /
74c3d85d / b6a25110 / d7e1cffd.

The load-bearing tab-table correction VERIFIED CORRECT three independent
ways: the authored 0x2E array re-parsed straight out of the committed
fixture (Allegiance 0x1000028C -> 0x10000291 is the sole IsDefault entry),
each button's caption resolved from the installed DATs via the live-mount
probe, and each page's own P0x57 + RegisterElementClass type re-read in
the pseudo-C. A fourth corroboration the addendum missed: under the
corrected pairing the default tab is also the LEFT-MOST tab (x=0).

MUST-FIX (both in the user's connected-gate script, neither code):
1. The script still carries the REFUTED x-order -- step 9 states the strip
   as "Friends, Allegiance, Fellowship, Squelch" and step 1 primes the user
   to expect Friends left-most. Real geometry: Allegiance x=0, Fellowship
   x=72, Friends x=144, Squelch x=206.
2. Step 14 tells the user `@allegiance info` should reveal the
   monarch/patron blocks and to "report if they do not" -- FA2's own fix
   round deliberately stopped 0x027C from seeding RuntimeAllegianceState,
   ApplyUpdate (0x0020) is the only writer of _hasProfile, and FA3 sends
   no 0x001F. The script steers the user into a false defect report.

SHOULD-FIX: unsupported "retail's Toggle-action semantics" claim on F3/F4
(no P0x57 read site, no OnAction handler, folded gmPanelUI global-message
stub -- the rule is acdream's OpenSpellbook precedent, not retail);
per-frame closure allocation in SocialAllegiancePageController.Tick; two
probe findings printed but never asserted (0x10000492 count, page
exclusivity); "three checkboxes" is four; AD-79 enumerates seven controls
but its cited test pins six; the two page controllers do not name AD-79;
the allegiance empty state is gated on HasProfile rather than retail's
per-relationship rule (TryGetMonarch/TryGetPatron already exist);
Tick() ignores _disposed; FindDeepest's doc overstates its guarantee.

Verified clean: every §6 Campaign-OP lesson (string resolver on both
Builds, scoped lookups -- I enumerated ALL duplicate ids and found three
previously-uncalled-out cross-page repeats, tab activation, no 0x0-extent
lazy children, cross-layout templates so the same-layout skip cannot
apply, no hand-rolled viewport); U6 genuinely closed (page 0x10000292 has
exactly two children); Flush/ClearContent resets ContentHeight; the J4.1
owners are borrowed by reference and clear in place; catalog id 12
byte-verified and the toolbar seam tolerates it via the same path four
existing non-toolbar panels take; window-frame policy byte-identical to
Options. data_794358 BYTE-VERIFIED in the PDB-paired binary as UTF-16LE
" " (one space, not empty) -- lane A's L" " reading and FA3's BlankLine
both correct. Live-mount probe passes against the installed DATs with no
fixture drift; 22 FA3 tests and 4,871 App tests / 3 skips / 0 failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 03:23:16 +02:00
Erik
e9eb756480 docs: FA3 blast-radius review -- APPROVE-WITH-FIXES (1 MUST-FIX, 6 SHOULD-FIX)
MUST-FIX 1: the Friends/Squelch lists have NO scroll driver. Both
controllers set TemplateResolver but never wire the authored scrollbar's
Model (0x10000518 / 0x10000543, both direct siblings of their ListBox
under the page root per FA3's own fixture), unlike all four existing
UiTemplateListBox consumers. There is no wheel fallback -- wheel scroll
lives only on UiText, not UiScrollablePanel -- so Scroll has no driver at
all. Visible at authored size: the 400/430-tall ListBoxes sit at y=40 in
a 362-tall panel, so rows past ~322px are off-panel AND unreachable.
AD-79 covers the inert BUTTONS, not a dead scrollbar.

SHOULD-FIX: the revision-driven rebuild does N live DAT imports under the
shared DatLock while the panel is CLOSED (first repeating consumer of a
resolver every other caller invokes once at Bind); Refresh() consumes the
revision before building so one resolver miss latches an empty list;
per-frame closure allocation in the hidden allegiance page; Flush()'s doc
omits its scroll reset and diverges from the sibling UiItemList.Flush()
it shares a name with; #383's "days ago" is really ~18-21h per git;
the SS10 addendum's trailing ** is orphaned.

Verified clean: catalog/window-name consumers all degrade safely
(SetPanelOpen(12) is a no-op, opacity controller correctly scoped by
#379); persistence omission is the documented cohort behavior; F3/F4
plumbing predates FA3 entirely (UI.Abstractions untouched) and the
apparent bare-F3 duplicate is in the non-production AcdreamCurrentDefaults;
mount order respects the DialogFactory constraint; SocialRuntimeBindings
is required-positional with one construction site; the generator is
env-gated and only the new fixture landed; +18 reconciles exactly
(23 targeted + 289 blast-radius regression tests pass on the FA3 binary);
AD-79 well-formed with the count bumped 58->59; and the corrected tab
table was independently re-read from the fixture's own TabTable
(0x1000028C -> 0x10000291, IsDefault=true -- Allegiance IS the default).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 03:14:42 +02:00
Erik
3e6dc62b0e docs: file #383 — installed-DAT vs committed-fixture drift found at FA3
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 03:01:36 +02:00
Erik
d7e1cffddc docs: FA3 addendum -- correct lane-A's x-order tab-table guess
docs/research/2026-08-11-fa-panel-structure.md §10's coordinator
addendum inferred the social panel's button-to-page pairing from
authored x-order, landing on Friends as the implied default tab. The
FA3 fixture dump read the real authored 0x2E tab table: Allegiance
(button 0x1000028C) is the actual default, corroborated independently
by each page's own P0x57 lining up with the real F3/F4 ActionMap ids.
Same convention as the FA1/FA2 fix-round addenda already in this
campaign's docs -- correct in place, keep the original text visible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 03:00:16 +02:00
Erik
b6a25110f3 docs: FA3 -- connected-gate test script + plan ledger update
- docs/research/2026-08-12-campaign-fa-test-script.md: the user's
  connected-gate script for FA3 -- open paths (F3/F4 + tab switch),
  gmPanelUI exclusivity vs sibling panels, tab switching, all four
  pages' expected shells/empty states, Friends/Squelch read-only
  expectations + the D1 INERT buttons, what to report, what's
  explicitly out of scope (FA4/FA5/FA6/D1's deferred wire).
- Plan ledger: FA3 row filled in with commit SHAs, totals (13,233/4/0,
  13,237 total, +18 over FA2's close), the tab-table correction finding,
  and the reverted unrelated fixture drift note. Campaign status line
  updated from "FA3 in flight" to implementation-complete pending
  review + gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 02:59:10 +02:00
Erik
74c3d85d37 test(ui): FA3 -- social panel fixture dump, live-mount probe, conformance tests
- RetailLayoutFixtureGenerator dumps LayoutDesc 0x2100006E slot
  0x1000018F to the committed social_panel_2100006E_1000018F.json
  fixture, closing lane-A unknowns U3/U4/U6/U7/U10 with real DAT data
  (geometry/media/fonts/base refs, the fellowship empty/full frame
  containment, the panel/page P0x57 properties, the row-template arrays).
- SocialPanelLiveMountProbeTests exercises the PRODUCTION mount path
  against the live DATs (ACDREAM_PROBE_LIVE_MOUNT=1): the tab host
  resolves as UiTabPanel with its 4-entry table, all four pages resolve,
  the fellowship frame pair and allegiance signature elements resolve,
  0x10000492 is confirmed authored twice under the allegiance page, and
  every tab button caption is non-empty (the #375 resolver class).
- SocialPanelControllerTests pins the fixture-driven conformance: the
  real (coordinator-addendum-correcting) tab table and default entry,
  the Fellowship/Allegiance empty-state gates, Friends/Squelch row
  population and revision-driven rebuilds, and the D1 INERT-button
  contract (AD-79) via
  FriendsAndSquelchActionButtons_AreClickable_ButHaveNoHandler.
- RetailPanelCatalogTests gains the SocialPanel id/window-name/
  Mounted-not-Toolbar pins (lane A §6.1: no toolbar button).

Note: the ACDREAM_REGENERATE_UI_FIXTURES=1 run used to produce the new
fixture also touched keyboard_config_21000009.json and
options_2100002B.json on this machine (unrelated installed-DAT drift,
likely from local DAT-editing tooling) -- both were reverted to HEAD
before this commit; only the new social panel fixture is included.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 02:58:28 +02:00
Erik
0a9ca2f1f9 feat(ui): FA3 -- mount the social panel shell (Friends/Allegiance/Fellowship/Squelch)
Campaign FA slice FA3: retail's four-tab social panel (LayoutDesc
0x2100006E slot 0x1000018F, RetailPanelCatalog id 12), built on the OP3
OptionsPanelController recipe -- Type-8 tab host, ActivateTabBehavior,
per-page scoped controllers.

- SocialPanelController mounts the tab host and wires the close button;
  F3 (ToggleAllegiancePanel) and F4 (ToggleFellowshipPanel) open the
  panel and switch to their own tab, sharing the same gmPanelUI
  one-active-panel exclusivity every sibling main panel already has.
- The live-DAT tab table CORRECTS the coordinator addendum's x-order
  guess: button 0x1000028C ("Allegiance") pairs with page 0x10000291 and
  is the authored DEFAULT entry, not Friends -- each button's own page
  id and its own P0x57 (matching the F3/F4 ActionMap ids on the
  Allegiance/Fellowship pages specifically) both corroborate the real
  pairing. See SocialPanelController's class doc for the full table.
- SocialFellowshipPageController swaps the two authored empty/full
  frames (0x1000026B/0x10000275) on RuntimeFellowshipState's
  IsInFellowship -- both frames' full containment (name box, create
  button, checkboxes vs. roster list, six buttons) was confirmed by the
  live-mount probe, so a single Visible toggle per frame is the whole
  swap (closes lane-A unknown U6).
- SocialAllegiancePageController hides the monarch/patron blocks and
  blanks their name text to a literal space when
  RuntimeAllegianceState.Snapshot.HasProfile is false, using SCOPED
  FindDescendant lookups (the panel authors 0x10000492 twice, once per
  block).
- SocialFriendsPageController/SocialSquelchPageController bind their
  ListBoxes read-only to RuntimeCommunicationState's existing J4.1
  Friends/Squelch owners (names only), rebuilding on revision change.
  Their action buttons are honest INERT (D1) -- register row AD-79.
- UiTemplateListBox gains Flush() (lane A/D's "Gap found" prerequisite)
  so a poll-and-rebuild list can shrink between refreshes.
- RetailPanelCatalog.SocialPanel = 12, byte-verified from the live slot's
  own P0x10000029; listed in Mounted only (no toolbar button -- lane A
  §6.1: the open path is keyboard-only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 02:58:09 +02:00
Erik
b560f415cd docs: FA2 CLOSED — re-review verdict + CF-1 correction to the FA5 slice row
The narrow re-review (cc1a319c) closed FA2 with no reopen; its
carry-forward corrected the FA5 row: the allegiance page's data
subscription is 0x001F AllegianceUpdateRequest(on) at retail's three
arming points, not the text-only 0x027B info request.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 02:31:27 +02:00
Erik
cc1a319c1f docs: FA2 mechanism re-review -- CLOSED, no reopen (1 carry-forward)
All 2 MUST-FIX and all 6 SHOULD-FIX dispositions re-derived in the actual
fix-round diffs (4272ad0e, ded23067, ed8b3ec9); every blast disposition
spot-verified. Nothing skipped, no fix introduced a new mechanism defect.

MF-1: RuntimeGenerationResetStage.Allegiance added and drained, the owner
clears the profile AND drops HasServerSeed, the inverted test flipped. Stage
enumeration verified consistent everywhere -- every reference outside the
enum's own file is by NAME, nothing serializes the ordinal, so the +1 shift
is inert. MF-2: ApplyInfoResponseSelf, the delegate hole and the self-gate
are all gone (0 whole-tree hits); the test was rewritten to pin text-only
output for self and other guids alike.

SF-3's RecalculateEvenXPSplitting port checked line-for-line against lane B
2.10, including the deliberate leaderless-table departure -- lane B 7.4 says
verbatim "treat a leaderless table as leave _even_xp_split at 1", so the
citation is accurate. SF-4's 900s gate confirmed to have real data (FA1 does
parse 0x02BE field 8) and to gate only the new-guid branch. SF-1/2/5/6 all
land as specified.

Blast: the teardown table re-derived for every N in 0..13 (case 9 was
genuinely one flag over); the new reflection walk pins every intermediate
stage; seam-doc and plan addenda are dated and accurate; the corrected 11/10
counts are right. Audited blast SF-6's no-register-row conclusion and AGREE
-- clear-at-reset plus 0x0020-only seeding means acdream now matches retail,
so no deviation remains for a row to name.

Suite claim 13,201/4/0 -> 13,215/4/0 (+14) reproduced exactly by counting
discovered cases per file. Targeted post-fix Release runs: 95/95 Runtime,
67/67 Core.Net.

CF-1 (FA5, not a reopen): nothing re-subscribes 0x001F now that the reset
clears the owner, and the plan's FA5 row cites 0x027B for the panel-show
path -- which after MF-2 is text-only and feeds nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 02:30:28 +02:00
Erik
ed8b3ec96d docs: FA2 fix-round -- ledger update, D2 correction, seam-map addenda
Plan (docs/plans/2026-08-11-fellowship-allegiance-campaign.md):
- D2 gets the D9-style dated strike/addendum recording the corrected
  allegiance reset semantics (clears at every generation reset; the
  HasServerSeed latch only gates pre-seed rendering WITHIN a session) with
  the three-way evidence citation: the retail OnEndCharacterSession hook,
  the RuntimeCharacterOptionsState precedent's actual clear-and-relatch
  behavior, and the no-character-selector connect path
  (SessionPlayerComposition.cs:1127).
- The architecture blurb and FA2's slice-map contract row get matching
  strike/addendum corrections so the "fellowship session-scoped,
  allegiance survives reconnect" claim does not survive uncorrected
  anywhere in the plan.
- FA2's ledger row: fix-round commit SHAs, corrected delegate-hole/
  wrapper counts (blast SHOULD-FIX 3: 10 not 15, 11 not 12), the
  allegiance register-row re-evaluation conclusion (blast SHOULD-FIX 6 --
  no row needed, MF-1's fix retires the deviation entirely), and the
  reconciled fix-round test totals (13,201/4/0 -> 13,215/4/0, +14,
  arithmetic exact per file).

Seam map (docs/research/2026-08-11-fa-acdream-seams.md), per the FA1
fix-round's established in-place-correction convention:
- SS1.3 and SS9's dispatcher-replaces-not-chains correction is now dated
  and cites the actual GameEventDispatcher.Dispatch behavior, matching the
  code comment already landed in GameEventWiring.cs.
- SS2.3 gets the 0x01C9/0x01CA disposition it was missing (correctly
  left unregistered -- dead COMDAT-fold no-ops per FA1) so FA3 does not
  have to re-derive it or "fix" the gap.
- The SS8 seam-map table's Allegiance-owner row and the executive-summary
  ownership bullet both get the "survives reconnect" claim struck with a
  dated correction to "session-scoped, clears at every generation reset".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 02:18:59 +02:00
Erik
ded23067aa fix(net,runtime): FA2 fix-round SHOULD-FIX -- fellowship mechanism parity, lookup reuse, router test, checkpoint defaults
Remaining SHOULD-FIX findings from the FA2 mechanism/blast reviews:

Mechanism SF-3/SF-4 -- RuntimeFellowshipState.ApplyUpdateFellow now ports
Fellowship::RecalculateEvenXPSplitting @0x005B92E0 (called from retail's
AddFellow/UpdateFellow/RemoveFellow on every upsert/removal, but never
from a full update -- that carries the server's own authoritative flag
verbatim, lane B 6.2) and Fellowship::AddFellow @0x005B9480's
locked/departed admission gate (a brand-new guid is refused while
_locked unless it appears in the 0x02BE field-8 _fellows_departed table
within 900s, @0x005B94A5). ApplyFullUpdate now stores update.Departed
instead of discarding it. A TimeProvider dependency (defaulting to
TimeProvider.System, matching the RuntimeCharacterOptionsState precedent)
makes the 900s grace window testable.

Mechanism SF-5 -- RuntimeAllegianceState's TryGetMember/TryGetPatron/
GetVassals now reuse ClientCommandResponses.AllegianceProfileLookups
(promoted private -> internal, AcDream.Runtime added to Core.Net's
InternalsVisibleTo) instead of re-implementing the retail walk a second
time.

Mechanism SF-6 -- RuntimeStateCheckpoint's Fellowship/Allegiance
parameters are no longer trailing-optional. `default(RuntimeFellowshipSnapshot)`/
`default(RuntimeAllegianceSnapshot)` zero-init Name/AllegianceName to
null, and C# does not allow a non-constant `new(...)` as an optional
parameter's default value (CS1736) even when the struct declares an
explicit parameterless constructor -- so the only way to guarantee a
non-null default was to make the parameters required. Both snapshot types
still gained an explicit parameterless constructor for callers that want
an empty-but-safe `new()`.

Blast SF-4 -- LiveSessionEventRouterTests gains
FellowshipQuit_RoutesSelfGuidToClearAndOtherGuidToRemove, wiring real
RuntimeFellowshipState/RuntimeAllegianceState owners through the one
production registration site and dispatching a real 0x00A3 envelope for
both a self-quit and an other-quit -- the one non-trivial lambda in the
slice (the self-guid source that decides "remove one member" vs "clear
the whole snapshot") was previously untested; every other router test
defaults Fellowship/Allegiance to null.

Blast SF-5 -- RuntimeFellowshipState.ResetSession dropped its disposed
guard to match the precedent its own doc comment names
(RuntimeInventoryState.ResetExternalContainer,
RuntimeCommunicationState.ResetNegotiatedChannels -- both bare delegations
with no disposal guard); the reset transaction is retryable and disposal
is terminal, so a throwing guard could never converge on retry.
RuntimeAllegianceState.ResetSession (new this fix round) matches the same
shape from the start.

Blast SF-7 -- IRuntimeAllegianceView.GetVassals' per-call List<> allocation
is now documented as an intentional exception to the file's "Snapshot +
TryGet*, no allocation" view convention (C# cannot yield-return from
inside a lock).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 02:17:56 +02:00
Erik
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>
2026-08-12 02:17:04 +02:00
Erik
63649c8053 docs: FA2 mechanism review -- APPROVE-WITH-FIXES (2 MUST-FIX, 6 SHOULD-FIX)
Both MUST-FIX findings are on RuntimeAllegianceState and they compound.

MF-1: the owner participates in no reset stage. Retail does the
opposite at the same boundary -- ClientAllegianceSystem::
OnEndCharacterSession @0x00569FA0 tail-calls AllegianceProfile::Clear,
while its sibling ClientFellowshipSystem::OnEndCharacterSession
@0x005690A0 deletes m_pFellowship (so FA2's fellowship half IS
faithful). The precedent the code and lane D §1.3 both cite,
RuntimeCharacterOptionsState.HasServerSeed, CLEARS at ResetSession and
its own doc names this hazard. The graphical host passes no character
selector, so TrySelectFirstAvailable re-resolves the character from a
fresh server list every generation -- a cross-character reset is not
precluded, and nothing in the owner keys on identity. Already pinned
by a passing test.

MF-2: ApplyInfoResponseSelf seeds from 0x027C. Retail's dispatcher
@0x006A7470 unpacks into a stack-local profile and its handler
@0x0056A1D0 only prints; 0x0020's handler @0x0056A120 is the single
inbound writer of the cached profile. Carries a stale-Rank
second-order defect (0x027C has no rank field).

Verified clean and re-derived from the decomp: all six fellowship
lifecycle rules, the exact leader hand-off condition (case 8 vs case
0xC at @0x0049034B/@0x004903EF), the dispatcher-folding correction and
byte-identical @allegiance info output, D4's 0x00A6 present but never
fired, both bindings sites symmetric, IRuntimeEventObserver untouched,
TS-81 honest, and the 8-edit J-owner template incl. teardown masks.
43/43 targeted Runtime tests pass on the committed Release binaries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 01:50:35 +02:00
Erik
2e97924801 docs: FA2 blast review -- APPROVE-WITH-FIXES (2 MUST-FIX, 7 SHOULD-FIX)
Blast-radius review of FA2 (1c401048, 369729f0, cced83b4, 12053e61)
along the axes the implementer did not traverse.

MF-1: GameRuntime.cs:716-725 -- CompletedTeardownStages case 9 claims
FellowshipDisposed one stage early (10 flags where the pre-FA2 case had
exactly 9, ending at CommunicationDisposed). Only observable on the
teardown failure path, which is precisely when the ledger must be
honest. No test pins intermediate stages, so nothing caught it.

MF-2: seeding RuntimeAllegianceState from 0x027C AllegianceInfoResponse
is a retail divergence with no register row. Retail's
CM_Allegiance::DispatchUI_AllegianceInfoResponseEvent @0x006a7470
unpacks into a STACK-LOCAL CAllegianceProfile and destroys it on return;
Handle_Allegiance__AllegianceInfoResponseEvent @0x0056a1d0 uses it only
as a read source for AddTextToScroll. 0x027C is text-only in retail; the
panel is fed exclusively by 0x0020. Concrete risk: 0x027C carries no
rank, so an @allegiance info before the first 0x0020 leaves the owner at
HasProfile=true with a fabricated Rank=0 for FA3's panel to render.

SHOULD-FIX: the seam doc still carries the false dispatcher claim FA2
disproved (only the plan ledger and a code comment were corrected);
no disposition recorded for the two deliberately-skipped dead events;
three count claims wrong (15 delegate holes -> 10; 12 Send wrappers ->
11; 11 S->C events -> 10); no test covers the router->owner plumb
including the one non-trivial lambda; ResetSession's disposal guard
diverges from the precedent it cites; allegiance reconnect-survival has
no register row; GetVassals allocates against the stated view contract.

Verified clean and enumerated exhaustively: every WireAll site (one
production, shared by both hosts), both bindings sites, every
IGameRuntimeCommands/IGameRuntimeView implementer (no bot-reachable
stub), zero auto-fire on all 11 new Send wrappers incl. 0x00A6/0x001F,
reset- and teardown-stage renumbering at every enumeration point, the
central accepting gate, host-adapter self-guid and owner-borrow
equivalence, the K-slice bot policies + trace recorder (21/21), the
@allegiance info live path (79/79), and the suite accounting -- measured
13,201/4/0 (13,205 total) with the +43 reconciled per test file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 01:46:57 +02:00
Erik
12053e61ea docs: FA2 -- divergence register TS-81/TS-80 + campaign ledger update
TS-81 filed: 0x027A AllegianceLoginNotification's retail two-line chat
text is not emitted -- the candidate strings resolve through Binary-
Ninja-mislabeled symbols at gmAllegianceUI::RecvNotice_AllegianceLogin
(0x00492220) that need a DAT string-table lookup, not a guess.
TS-80 partially narrowed: the fellowship-create shareXp wire mechanism
now exists end-to-end (IRuntimeFellowshipCommands.Create takes and
sends it), but no caller reads the option bit yet -- that's FA4's
create-dialog scope.

Updates the campaign plan's FA2 ledger row: code-complete, full test
totals (13,158/4/0 -> 13,201/4/0, +43 exact), the seam-doc dispatcher
correction, and the entity-table-borrow recommendation that wasn't
needed (the wire's own FellowMember record already carries full vitals
inline).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 01:32:39 +02:00
Erik
cced83b483 feat(app): FA2 -- fellowship/allegiance command routing for graphical + headless hosts
Both LiveSocialSessionBindings construction sites updated together
(LiveSessionRuntimeFactory.cs, HeadlessSessionHost.cs) so the single
GameEventWiring.WireAll registration site serves both hosts identically
(the K-slice unification). CurrentGameRuntimeCommandAdapter implements
IRuntimeFellowshipCommands/IRuntimeAllegianceCommands over the App
command bus (LiveSessionCommandRouter gains 12 new *RuntimeCmd records
+ registrations + LiveSessionCommandBindings send delegates), mirroring
DirectGameRuntimeCommandAdapter's direct-session shape including the
identical Quit leader-hand-off rule read from RuntimeFellowshipState.
CurrentGameRuntimeAdapter (the graphical IGameRuntimeView/
IGameRuntimeCommands composite) exposes the two new views/command
groups. Headless's DirectGameRuntimeCommandAdapter needed no changes --
it already implements both new interfaces from the Runtime-layer
commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 01:32:18 +02:00
Erik
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>
2026-08-12 01:31:54 +02:00
Erik
1c40104896 feat(net): FA2 -- fellowship/allegiance outbound wrappers + inbound wiring
Adds the missing WorldSession.Send* link for every FA1 fellowship/
allegiance builder (SendFellowshipCreate/Quit/Dismiss/Recruit/
UpdateRequest/AssignNewLeader/ChangeOpenness, SendAllegianceSwear/
Break/Kick/UpdateRequest) and 15 new GameEventWiring.WireAll delegate
holes covering the 11 S->C fellowship/allegiance events. Delegate holes
(not state-object params) because Core.Net cannot reference
AcDream.Runtime, matching the onCharacterOptions/onConfirmationRequest
precedent.

Fixes a real bug found during implementation: GameEventDispatcher.
Dispatch invokes only the single most-recently-registered handler per
GameEventType (RegisterOwned REPLACES, it does not chain-invoke) --
contradicts the seam doc's "the dispatcher supports multiple owned
handlers per type" claim. A literal second registrar.Register call for
AllegianceInfoResponse would have silently killed the already-live
`@allegiance info` chat-text output the moment a caller supplied the
new self-gated Runtime callback. Both behaviors are folded into the
ONE existing registration instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 01:31:18 +02:00
Erik
1bb707e248 docs: FA1 CLOSED — re-review CF-1 seam-map addenda + ledger verdict
The narrow re-review (96df892d) closed FA1 with no reopen and one
carry-forward: two further forward-looking seam-map rows (:128 owner
diagram, :214 state-parameter pattern) still cited the deleted
AllegianceTree as FA2 design guidance. Both now carry dated strike/
addendum notes pointing FA2 at the parsed profile records instead.
FA2 unblocked per the re-review's precondition.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 00:56:18 +02:00
Erik
96df892d21 docs: FA1 mechanism re-review -- CLOSED, all 7 findings verified fixed
Narrow re-review of the fix round (ed308087 code+tests, 511ba6e5 docs)
against the mechanism findings doc. Verdict CLOSED, no REOPEN.

All 2 MUST-FIX and 5 SHOULD-FIX verified in the actual diffs, each
re-derived rather than taken on the commit message's word:

- MF-1: zero-id rejection on BOTH the monarch and child paths, which is
  also what makes treeParent == 0 provably fatal (knownIds can never
  contain 0); three boundary tests.
- MF-2: the 0x001F builder re-verified against primary source --
  CM_Allegiance::Event_UpdateRequest @0x006A7260 allocates 0x10, stores
  0x1f at 006a72ba, writes the arg as a full u32 at 006a72cb. Both golden
  vectors correct; all five new anchors resolve; the ACE claim
  (GameActionAllegianceUpdateRequest.cs:12 reads and ignores the value)
  is accurate.
- SF-1: monarch clear placed at retail's own position/guard; the fixture
  relocation onto a vassal is not just correct but necessary, since the
  clear would otherwise mask the legacy-compat fallback.
- SF-2/SF-3/SF-5 all closed; SF-5 resolved better than asked, renumbering
  to the real AllegianceVersion enum values (verified against
  acclient.h:2979-2994) and naming gate 5 as real-but-gating-nothing.

Spot-verified all six blast dispositions: AP-90 re-pointed without being
wrongly retired; four seam-map corrections applied as dated strikes (its
open-question-8 answer independently re-verified against PackString16L
and ACE's ReadString16L pad skip); D9 + slice row struck and annotated;
ledger arithmetic now closes (13,153 total sums correctly, -4 skips =
13,149).

Suite claim corroborated: the fix diff adds exactly +9 [Fact]/[Theory]
and removes 0, and the post-fix Release binaries (stamped after
ed308087, so --no-build is legitimate here) measure AcDream.Core.Net.Tests
at 886/0/0 -- exactly the blast doc's 877 pre-fix anchor plus 9, with all
9 new tests in that project.

One carry-forward, NOT a reopen: blast MF-2's enumeration stopped at four
rows; lane D still names the deleted AllegianceTree at :128 and :214,
both forward-looking FA2 design guidance of the same danger class as the
:791 row that was corrected. Two more dated addenda close it; FA2 should
not start before that.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 00:55:06 +02:00
Erik
511ba6e5d4 docs: FA1 review round -- register repoint, seams corrections, plan reconciliation
Applies the documentation-only MUST-FIX items from the blast review
(docs/research/2026-08-12-fa1-review-blast.md), plus mechanism SF-4:

Blast MF-1 / mechanism SF-4: register row AP-90
(retail-divergence-register.md) cited the deleted AllegianceTree class as
its evidence. Re-pointed to ClientCommandResponses.AllegianceProfileLookups
and the fellowship parsers FA1 added -- the deviation itself (radar
relationship state undelivered at runtime) is unchanged and NOT retired,
since FA2 hasn't wired a live owner yet.

Blast MF-2: corrected four falsified statements in the lane D research doc
(fa-acdream-seams.md), each marked with a dated, clearly-struck FA1
fix-round addendum rather than silently rewritten (it is a committed
research record):
  - :791 "wrapping existing AllegianceTree" -- class deleted; re-pointed to
    AllegianceProfileLookups.
  - :666/:672 `commands.Fellowship.SetOpen -> BuildFellowshipUpdate` -- that
    builder no longer exists; its renamed successor is panel visibility,
    not openness, and using it here would re-introduce the exact semantic
    bug FA1 fixed. Re-pointed to BuildFellowshipChangeOpenness (0x0291).
  - :429/:668 `BuildFellowshipCreate(seq, name, openness, shareXp)` -- the
    builder is now 3-arg; there is no wire openness field.
  - :854 open question 8 (trailing-pad rule) -- ANSWERED by FA1 (VC-3),
    closed with the answer instead of left open for re-derivation.

Blast MF-3: plan decision D9 and the FA1 slice-map row both asserted "the 8
missing fellowship WeenieError strings are added in FA1" -- FA1 shipped the
opposite, verified finding (no retail display text exists for any of the
8 ids). Both struck and annotated with the actual outcome.

Blast MF-4: reconciled the ledger's internally-inconsistent test-total row.
Direct measurement at the pre-fix-round tip (bc693728, stashed/restored
during this session to isolate it) confirms 13,149 passed / 4 skipped / 0
failed (13,153 total) -- the ledger's own prior number was actually
correct; the "baseline 13,103" and "net +50" framing next to it did not
reconcile with each other or with the diff-verified delta (+58 added / -9
deleted = net +49, one test of drift attributed to a different baseline
commit, not a further miscount). Also records this session's own +9 tests
and the blast SF-1 live-surface note (FA1 changed observable @allegiance
info output, not a purely-unwired slice).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 00:47:09 +02:00
Erik
ed30808720 fix(net): FA1 review round -- zero-id tree rejection, monarch clear, 0x001F builder
Applies both MUST-FIX items and the code-facing SHOULD-FIX items from the
dual-lens FA1 review (docs/research/2026-08-12-fa1-review-mechanism.md,
docs/research/2026-08-12-fa1-review-blast.md):

Mechanism MF-1 / blast SF-2: AllegianceHierarchy::Add @0x005B6E90 wraps its
entire body in `if (_id != 0)` -- a record whose own id is zero discards
the WHOLE message, for both the monarch and a child record, and this is
also what makes treeParent == 0 unconditionally fatal for a non-monarch
record. ReadAllegianceProfileBody now rejects CharacterId == 0 on both
paths; four new boundary tests in AllegianceProfileVersionGateTests.cs
(zero-id monarch, zero-id child, zero treeParent, plus the existing
orphan/self-parent/duplicate trio).

Mechanism MF-2: added the missing 0x001F AllegianceUpdateRequest builder --
the structural twin of the fellowship 0x00A6 this slice already repaired --
with golden-vector tests for both on/off.

Mechanism SF-1 / blast SF-3: UnPack's last act before returning success
forces the monarch's MayPassupExperience to false regardless of the wire
bit or the HasPackedLevel-absent legacy-compat fallback. Ported at the end
of the record loop; the pre-existing HasPackedLevel-absent test moved off
the monarch record (which the new clear makes indistinguishable from "the
fallback never fired") onto a vassal record, and a new test proves the
monarch clear fires even when the wire bit explicitly asks for true.

Mechanism SF-2: removed ParseFellowshipDisband's invented body-length
validation -- retail's DispatchUI_Disband reads only the opcode and never
inspects a trailing body. The parser now always succeeds; the matching
test flips from asserting rejection to asserting acceptance.

Mechanism SF-3: added the D5 `<<1` shareLoot-shape test at the 0x02C0
FellowshipUpdateFellow site -- previously only pinned at 0x02BE, so a
future split of the shared ReadFellow helper could silently reintroduce a
bool read on this leg undetected.

Mechanism SF-5: renumbered the version-gate comments in
ReadAllegianceProfileBody to the true AllegianceVersion enum values
(1-11, matching acclient.h's SpokespersonAdded..ApprovedVassal) instead of
wire-appearance order, which only reached 10 and silently dropped gate 5
(BannedCharactersAdded, which is real but gates nothing in UnPack -- now
called out explicitly). Fixed the stale "lane B §12" citation in
SocialActions.cs to the actual master-table row.

Blast SF-1: pinned the two retail-faithful but user-visible behavior
changes FA1 made to the ALREADY-LIVE `@allegiance info` command --
reversed vassal print order (3-vassal test through
FormatAllegianceInfoLines) and malformed-tree silent-drop (test at the
GameEventWiring registration layer, which is `if (info is null) return;`).

Blast SF-4: fixed a doc comment citing a nonexistent `ConfirmationResponseTests`
class; the actual class is `ConfirmationTripleTests`.

Blast SF-5: cross-referenced the confirmation-triple discriminator's split
representation (ConfirmationType on the response leg only; bare uint on
the two inbound legs production actually reads) at both sites, so FA4
inherits a stated decision rather than an unexplained inconsistency.

Full Release suite: 13,158 passed / 4 skipped / 0 failed (13,162 total),
up from the pre-fix-round 13,149/4/0 (+9 tests this round).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 00:46:42 +02:00
Erik
bc693728a6 docs: FA1 mechanism review — APPROVE-WITH-FIXES (2 MUST-FIX, 5 SHOULD-FIX)
Mechanism-faithfulness lens on 7be86f47/6bedbc47/5f9aa16f/4281750b.

Every wire layout re-derived independently from the retail decomp rather
than taken on the lane docs' word: Fellow::UnPack, Fellowship::UnPack,
PackableHashTable::UnPack's count/buckets split, DispatchUI_UpdateFellow's
guid-first read, Event_Create's single trailing u32 shareXP,
AllegianceHierarchy::UnPack's eleven gates + their non-monotonic wire
order, AllegianceHierarchy::Add, AllegianceProfile::UnPack, the 0x20
dispatch case. All six golden byte vectors re-computed field by field --
no encoding, padding, or endianness slip found.

Both premise-contradiction calls VERIFIED CORRECT from primary source:
the 8 fellowship WeenieError ids genuinely have no case label, no else-if
comparison, no decimal form and no default fallthrough in
HandleFailureEvent (D9's premise was wrong, the refusal to invent English
was right); and 0x0275 is client-authored, so the typed ConfirmationType
enum -- not a receive parser -- was the real gap, and D6's FA4/FA5 flows
are buildable on what landed.

MUST-FIX: (1) AllegianceHierarchy::Add's fourth rejection rule (_id == 0,
which also makes treeParent == 0 unconditionally fatal) is unmodeled;
(2) the 0x001F AllegianceUpdateRequest builder -- the allegiance twin of
the 0x00A6 this slice repaired, and lane C's #3 minimum-viable message --
is missing entirely.

SHOULD-FIX: monarch MayPassupExperience is not force-cleared;
ParseFellowshipDisband validates a body length retail never inspects;
D5's <<1 shape is unpinned at the 0x02C0 site; AP-90's register row still
cites the deleted AllegianceTree; the gate comment numbering stops at ten.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 00:30:28 +02:00
Erik
c4b7247484 docs: FA1 blast review -- APPROVE-WITH-FIXES (4 MUST-FIX, 5 SHOULD-FIX)
Blast-radius lens over Campaign FA slice FA1 (7be86f47, 6bedbc47,
5f9aa16f, 4281750b, ee1124ca). The wire work is right -- both repaired
builders re-derived against ACE's own handlers, all eleven allegiance
version gates against AllegianceHierarchy::UnPack @0x005B7520, and both
tree-assembly rules against Add @0x005B6E90. No handler-lane collision,
no double registration, no cross-host source touched.

The bookkeeping is not. MUST-FIX: register row AP-90 still cites the
deleted AllegianceTree; lane D's seam map -- FA2's own contract -- is
falsified in four places including a row that would make FA6 re-introduce
the exact openness/panel-visibility bug FA1 fixed; plan decision D9 still
says the 8 WeenieError strings were added when FA1 shipped the opposite
finding; and the ledger's test totals state three mutually exclusive
numbers (+46 implied, +50 stated, +49 measured from the diffs).

SHOULD-FIX: the "UNWIRED" framing is untrue of ParseAllegianceInfoResponse
(live behind @allegiance info -- vassal print order now reverses and a
malformed tree now silences the command outright; both retail-faithful,
neither pinned by an order-sensitive test); the id != 0 discard rule is
missing; retail's monarch MayPassupExperience zeroing is missing; one new
doc comment cites a nonexistent test class; and the confirmation triple
now carries its discriminator two ways.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 00:26:59 +02:00
Erik
ee1124ca5b docs: FA1 ledger entry -- Core.Net truth slice code-complete
Records the FA1 commit SHAs and automated-gate test totals in Campaign
FA's ledger (§9). Owes its dual-lens review per the campaign's §7
protocol before FA2 (the RuntimeFellowshipState/RuntimeAllegianceState
owners) begins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 00:14:09 +02:00
Erik
4281750b4d fix(core): FA1 -- delete dead AllegianceTree scaffold
Campaign FA slice FA1, lane C §0/§7.3 DELETE verdict. Verified zero
production references (a repo-wide grep for AllegianceTree/
AllegianceNode/AllegianceMath outside the class's own file and its own
test file returns nothing but docs and an unrelated test-name string
coincidence in ChatChannelInfoTests.cs).

Two defects made this safer to delete than fix:

- AllegianceMath.ComputePassup transcribed retail's passup formula wrong
  by roughly 1000x: it computed (50 + 22.5*loyalty) / 291 instead of
  50 + 22.5*(loyalty/291) AS A PERCENTAGE (missing the trailing / 100),
  and its own unit test locked the wrong value in as correct.
- AllegianceTree's UpsertNode(guid, name, patronGuid, rank) modeled a
  patron edge the wire does not carry -- the wire names each record's
  TREE PARENT (§4.4), which for ACE's own writer is not always the real
  patron (ACE hangs a non-monarch patron directly off the monarch). The
  parsed record list plus its treeParent tags already IS the tree
  (see ClientCommandResponses.AllegianceProfileLookups, landed in the
  companion feat(net) commit this session) -- no separate tree class is
  needed. The client also never needs the passup number at all:
  _cp_tithed arrives pre-computed from the server.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 00:13:39 +02:00
Erik
5f9aa16f43 fix(chat): FA1 -- 8 fellowship WeenieError ids confirmed absent from retail, not missing
The FA1 contract asked to "add the 8 missing fellowship entries lane B
lists" (0x0417-0x041C, 0x04DB, 0x04DC). Re-ran this table's own binary-
sweep methodology specifically for these 8 ids rather than inventing
text for them: a full-text grep of the 1.4M-line
acclient_2013_pseudo_c.txt found ZERO comparisons/case-labels against any
of the 8 anywhere in the retail client, and a manual walk of
HandleFailureEvent's own case-label sequence confirmed the switch goes
straight from case 0x416/0x41d (skipping 0x417-0x41c) and from case
0x4da/0x4dd (skipping 0x4db/0x4dc).

Conclusion: retail's Sept-2013 client has no display text for any of
these 8 ids -- they are intentionally absent from this table, not
overlooked. This contradicts the FA1 contract's premise but not lane B's
own text, which only claimed the ids were "missing" from the table (true)
and that two of them (0x0417, 0x04DB) are on ACE's live send paths (also
true) -- it never claimed retail has text for them. Two of the ids are
therefore live-but-silent gaps against a real ACE server, and acdream's
current no-display behavior for them is ALREADY retail-faithful. Adding
invented English would be exactly the class of mistake SHOULD-FIX 4
(the no-default-case rule this table's Resolve() already implements)
exists to prevent.

Documents the finding at both table gaps and adds a conformance test
(Resolve_FellowshipIdsAbsentFromHandleFailureEvent_ReturnsNoText) proving
all 8 resolve to null text, matching the existing
Format_0x051D_ReturnsNull_NoRetailCaseExists precedent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 00:13:25 +02:00
Erik
6bedbc4772 feat(net): FA1 -- S->C parsers for fellowship/allegiance, confirmation triple, allegiance version gates
Campaign FA slice FA1: pure parse functions + typed records only,
UNWIRED (FA2 registers them against the new RuntimeFellowshipState/
RuntimeAllegianceState owners -- see docs/research/2026-08-11-fa-acdream-seams.md
§2).

Fellowship family (GameEvents.cs), field orders from lane B §3.8-§3.13,
guid-first on 0x02C0 per the resolved Chorizite disagreement:
FellowshipFullUpdate (0x02BE), FellowshipUpdateFellow (0x02C0),
FellowshipQuitNotice/FellowshipDismissNotice (S->C 0x00A3/0x00A4),
FellowshipDisband (0x02BF, empty body), and the dead
FellowshipFellowUpdateDone/FellowshipFellowStatsDone (0x01C9/0x01CA,
parse-and-ignore, must never fail per lane B §2.7). ShareLoot is modeled
as a raw uint (D5) -- ACE encodes it two incompatible ways (0x10 in full
updates, <<1 incremental), so `!= 0` is the only safe read, never `== 1`.

Confirmation triple (D6): grepping the tree showed 0x0274/0x0276 already
had typed parsers in Core.Net; 0x0275 (client-authored) already had a
byte-correct builder but no typed representation. Added the
ConfirmationType enum (1 SwearAllegiance, 4 Fellowship, matching retail's
Handle_Character__ConfirmationRequest switch and ACE's enum verbatim) and
ParseConfirmationResponse, completing Core.Net's typed coverage of all
three legs and round-tripping against the existing
ClientCommandRequests.BuildConfirmationResponse byte-for-byte.

Allegiance small events (GameEvents.cs): AllegianceLoginNotification
(0x027A), AllegianceUpdateDone (0x01C8), AllegianceUpdateAborted (0x0003,
declared but never sent by ACE).

The heavyweight AllegianceUpdate (0x0020) extends
ClientCommandResponses.ParseAllegianceInfoResponse (0x027C) rather than a
second parser, per lane C §7.2's explicit reuse verdict -- both messages
now share ReadAllegianceProfileBody, which the discriminating leading u32
(targetGuid vs rank) is read around. That shared reader implements:

- The ELEVEN AllegianceHierarchy::UnPack version gates (lane C §4.2) --
  officers/spokesperson-skip, officer titles, the four broadcast
  counters, motd/motdSetBy, chatRoomId, bind point, allegianceName,
  isLocked, approvedVassal, each behind its own oldVersion threshold.
  AllegianceProfileVersionGateTests.cs pins all eleven with a
  boundary-crossing pair per gate (N-1 OFF vs N ON), including the
  negative proof that version 5 (BannedCharactersAdded) gates nothing
  in UnPack.
- The §4.4 tree-assembly rules: a record whose treeParent is not already
  in the tree (orphan), equals its own id (self-parent), or duplicates an
  id already seen makes AllegianceHierarchy::Add fail, which the whole
  parse now mirrors by returning null for the ENTIRE message -- not a
  partial tree. Sibling order REVERSES on assembly (each new record is
  prepended to its parent's vassal list), so FindVassals now walks
  records in reverse wire order; both rules have dedicated tests.
- AllegianceMemberRecord gained the panel-needed columns lane C §7.2
  names (rank, level, loyalty, leadership, cpCached, cpTithed, gender,
  heritage, MayPassupExperience) with defaulted trailing parameters so
  existing 4-arg positional construction sites keep compiling. Officers/
  officer titles/bind point are read (so every later field lands at the
  right offset) but deliberately left unsurfaced -- ACE always zeroes/
  empties them anyway (lane C §5.1), and bind point is a 32-byte Position
  the retail chat renderer never uses either; a future panel slice can
  extend the record without re-deriving the parse.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 00:13:10 +02:00
Erik
7be86f47f6 fix(net): FA1 -- repair fellowship builders, add 0x0290/0x0291, allegiance Kick
Campaign FA slice FA1 (lane B field-order sections §3.1-§3.7, lane C §3.2,
§1.4). Two latent acdream builder defects repaired, both cited in lane B
§5.2:

- BuildFellowshipCreate (0x00A2) invented a nonexistent "openness" byte
  and silently sent it as the low byte of shareXP -- ACE would read an
  INVERTED shareXP value. Corrected to retail's real shape: [str16L
  name][u32 shareXP]. shareXP is the FellowshipShareXP character option,
  not a dialog checkbox.
- BuildFellowshipUpdate(open:) mislabeled 0x00A6 as fellowship openness;
  it is FellowshipUpdateRequest -- panel VISIBILITY. Renamed to
  BuildFellowshipUpdateRequest(panelOpen:); the wire bytes were already
  correct, only the name/doc were wrong. ACE gates the whole 0x02C0
  member-vitals stream on this message (lane B §4.5) -- a prerequisite
  for live vitals once FA4 wires the panel.

Two builders added that acdream never had at all:

- BuildFellowshipAssignNewLeader (0x0290) -- retail's leader-Quit path
  sends this before 0x00A3 disband=0 (lane B §2.5).
- BuildFellowshipChangeOpenness (0x0291) -- the REAL openness toggle.
- AllegianceRequests.BuildKick -- wire-identical to BuildBreak (both are
  Event_BreakAllegiance 0x001E); named separately so FA2's panel command
  surface can distinguish "break from patron" from "kick a vassal" (lane
  C §1.4). AllegianceInfoRequest (0x027B) was already live via
  ClientCommandRequests.BuildAllegianceInfoRequest -- not duplicated.

Wrong-shape tests at SocialActionsTests.cs:53-105 re-pinned with
hand-computed golden byte vectors deriving each field from the cited
lane-B sections (not generated by calling the builder under test, per the
OP1 convention this file already follows for BuildSetCharacterOptions).
AllegianceRequestsTests.cs gained golden vectors for the existing
Swear/Break builders plus the new Kick alias.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 00:12:47 +02:00
Erik
bb48d2c89d plan: Campaign FA — the retail social panel (Fellowship & Allegiance)
Seven slices over the four committed research lanes + the U2 slot-table
closure: Core.Net truth (repair/complete/delete the H.2 scaffolding),
two sibling J-owners with different lifetimes, the OP3-recipe mount of
the ONE four-tab social panel (id 12: Friends/Allegiance/Fellowship/
Squelch), the two live pages, bot-vs-ACE gates (second ACE account is
the user prerequisite at FA6), and closeout. Nine design decisions
stated (D1-D9); Campaign OP's gate lessons imported as binding rules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 23:44:21 +02:00
Erik
1226f289f3 research: Campaign FA lanes A-D + the U2 slot-table probe (social panel found)
Four Opus research lanes for the Fellowship & Allegiance campaign:
panel structure, fellowship wire (two BN zero-folds broken by byte
decode: IsFull >= 9, the x87 XP-share table capping at 2.8x), allegiance
wire (27+5 messages binary-verified; tree assembly discard/reversal
rules; ACE zeroed-field caveats), and the acdream seams audit (H.2
scaffolding inventory, J-owner recommendation, AD-78 dimmed-row
inventory, bot-gate requirements).

Coordinator U2 closure (FaPanelSlotProbeTests, live DATs): Fellowship
and Allegiance are two of FOUR pages of ONE tabbed social panel — slot
0x1000018F, panel id 12, Type-8 host — alongside gmFriendsUI and
gmSquelchUI; lane A's separate-siblings mounting call is corrected in
its addendum, and the full 16-slot dump closes every unidentified
RetailPanelCatalog entry (Abuse/Book/LinkStatus/MiniGame/UA/Vitae/
Map+House/Journal).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 23:42:19 +02:00
Erik
5eca35b706 docs: park Campaign OP — gate-4 fixes committed, user re-check owed at resume
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 23:11:43 +02:00
Erik
d1c60df946 fix #382: chat-window indicator buttons invisible until first hovered
Root cause (found via reference-identity-verified live-DAT probing, not
a guess): the four main-chat-window indicator buttons (0x10000522-
0x10000525) resolve their own correct ActiveState="Normal" at
construction, then get blanked to "" moments later in the SAME
LayoutImporter.Build call. The indicator column's backing panel
(0x10000600) authors PassToChildren=true on its own empty DirectState
(confirmed live: States[0xFFFFFFFF].PassToChildren == true); when
LayoutImporter.BuildWidget's post-attach state reapply runs for that
panel, UiDatElement.TrySetRetailState cascades its DirectStateId to
every IUiDatStateful child, including the already-correctly-resolved
buttons. UiButton.TrySetRetailState's DirectStateId branch used to
accept that cascade because every button structurally carries a
DirectStateId entry in its States dict as a property bag (ToggleBehavior/
RolloverEnabled/etc), independent of whether it authors any blank
sprite, so TryFindState(DirectStateId) found that entry and blanked
ActiveState even with no "" media. A hover "fixed" it only because
UiButtonStateMachine.RequestedState resolves to the same canonical
Normal id regardless of PointerOver when RolloverEnabled is false.

Retail's own decompiled UIElement::SetState @0x00464e70 does the exact
same unconditional-commit-plus-cascade; retail avoids this specific bug
purely through construction timing (UIElement::Initialize's SetState
call precedes child-tree construction, so a cascade fired during import
always iterates zero children). Our port's LayoutImporter.BuildWidget
deliberately reapplies in the opposite order to give retained
PassToChildren tabs their authored child media, so this literal
state-machine port needed a compensating guard.

Fix: UiButton.TrySetRetailState's DirectStateId branch now requires
REAL "" media (HasStateMedia("")) before accepting the transition.
Scoped to UiButton only; UiDatElement's parallel branch and the cascade
mechanism are unchanged, so CharacterStatController's own
PassToChildren-driven chrome children are unaffected. Register row
AP-206 records the divergence from retail's literal unconditional-
commit semantics. Regressed by two fast unit tests in UiButtonTests.cs
(DirectStateCascade_WithoutRealMedia_DoesNotBlankAnAlreadyResolvedState,
DirectStateTransition_WithRealMedia_StillSucceeds) plus a live-mount
probe confirming all four buttons resolve ActiveState="Normal"
immediately after import against the real installed DAT.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 23:03:31 +02:00
Erik
a31fd631ad fix #381: Options-panel footer needs an opaque backing field
Root cause: a live-DAT probe found retail authors NO backing element
behind the Character/Chat/Config tabs' Apply/Reset/Defaults footer —
each page root has exactly five children (the row ListBox, its
scrollbar, and the three buttons) with zero direct-state media on the
root itself. Scrolled row content therefore bled through visibly
between/behind the three buttons; the bleed-through is a rendering gap
in our own composition, not a missing import.

Fix: new minimal widget UiSolidSpriteFill tiles
RetailChromeSprites.CenterFill (the SAME panel-background sprite the
Options window's own chrome already draws behind everything, not an
invented color) across the footer strip's rect, derived from the three
buttons' own resolved Top/Height and z-ordered strictly behind every
other child so it can never intercept input or occlude the buttons.
Register row AP-205 records the synthesis. Regressed by
OptionsPanelControllerTests.
Bind_SynthesizesOneOpaqueFooterBacking_PerPageWithApplyResetDefaults,
which pins exactly one backing field per page, sized from the live
button rects, z-ordered behind every sibling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 23:01:43 +02:00
Erik
2a248c0d48 fix #380: Chat tab opacity sliders were missing their retail row captions
Root cause: PlayerOptionPage::AddSliderOption never sets a row's own
name-label text — the "Inactive Opacity"/"Active Opacity" caption comes
from a SEPARATE DAT-resident runtime catalog (DID 0x78000000, resolved
via the same two-level DBCache::GetDIDFromEnumStatic master-map/submap
lookup ChatOptionsDatDefaults already uses for enum 0x16/category 2,
here for enum 0x15/category 2) that nothing in the codebase ever
queried, so both slider rows rendered with no caption at all.

Fix: new ChatOptionsDatCaptions.TryRead resolves the DID-0x78000000
catalog's per-property name/tooltip entries (matched by the same
owning-property enum ChatOptionsDatDefaults already keys its defaults
by) and ChatOptionsPageController.BuildOpacitySliders stamps each
slider's own row caption/tooltip from it — falling back to no text
(never invented English) if resolution fails. Regressed by
ChatOptionsPageControllerTests.
Bind_WiresEachSlidersOwnRowCaption_FromTheResolvedDatCatalog and the
companion Bind_MissingCaption_RendersNoText_NeverInventsEnglish case,
plus a live-mount probe (OptionsPanelLiveMountProbeTests.
ProbeChatOpacityCaptions) confirming the production TryRead call
resolves "Inactive Opacity"/"Active Opacity" against the real DAT.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 23:01:22 +02:00
Erik
c0b3d8f233 fix #379: chat opacity fade was scoped to every window, not just chat
Root cause: RetailWindowOpacityController applied the Default/Active
opacity fade to EVERY registered UiRoot window (vitals, toolbar,
inventory, spellbook, radar, even the Options panel itself), but
retail's ChatInterface::SetDefaultOpacity/SetActiveOpacity are only
ever called by gmMainChatUI/gmFloatyChatUI — the mechanism is chat-only
in retail, not a global window-opacity feature.

Fix: scope the controller's catch-up loop, OnWindowRegistered,
ReapplyAll, and Dispose to WindowNames.Chat/ChatWindow1-4 only; every
other registered window now stays fully opaque regardless of slider
position, matching retail's own scope. Regressed by
RetailWindowOpacityControllerTests.
OpacityFade_AppliesOnlyToChatWindows_NeverOtherPanels (registers
vitals/toolbar/chat/a floating chat window and asserts the non-chat
windows never move off 1.0 while chat windows still track Default/
Active correctly).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 23:00:57 +02:00
Erik
c121842664 fix #378: Config-tab dropdown menus render bare with no popup chrome
Root cause: DatWidgetFactory builds Config-tab Type-0x10000038 menu
leaves as bare UiMenu instances (matching the vendor/chat channel menu
pattern), but unlike those two controllers, ConfigOptionsPageController
never wired the menu's sprite/font/geometry properties after Bind — so
every dropdown rendered as plain text with no button well, no arrow
cap, and opened no popup on click (#374's fix only corrected click
ROUTING, not the missing chrome).

Fix: ConfigOptionsPageController.ApplyMenuChrome wires every Config-tab
menu row with the SAME retail sprite ids VendorUiController/
ChatWindowController's channel menu already use for this shared popup
catalog (LayoutDesc 0x21000043), verified against the live DAT via
OptionsPanelLiveMountProbeTests' ProbeConfigMenuChrome/
ProbeConfigMenuPopupChrome probes. Regressed by
ConfigOptionsPageControllerTests.MenuRow_SoundFeatures_OpensAndSelects
ThroughRealHitPath_UsingAuthoredPopupGeometry, which drives the real
click-to-open + item-pick path through the authored popup geometry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 23:00:29 +02:00
Erik
28bef4e03b docs: file #378-#382 — Campaign OP gate-4 findings (user screenshots)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 19:25:17 +02:00
Erik
a59e077a66 fix #371: straddling rows clip at the viewport edge instead of vanishing whole
Gate-3 screenshot review (user): 'the chat tab looks like it is missing
per window config' — Chat Window 1's header rendered over a void at the
DEFAULT scroll offset because its 260px self-sized filter block
straddled the viewport's bottom edge and UiScrollablePanel hid
straddling rows WHOLE (AP-201's predicted symptom, now user-observed at
scroll position zero, upgrading it from polish to blocking).

By fix time the UI renderer HAD everything needed: UiRenderContext's
clip stack (PushClip/PopClip with rect intersection + per-draw quad
clipping) and UiElement's ClipsChildren hook, already honored by both
the generic draw walk and hit-testing. The fix is therefore exactly the
shape the filing asked for, in the panel itself:
- ClipsChildren => true: children draw and hit-test clipped to the
  viewport rect.
- The layout cull keeps any INTERSECTING row Visible (was: fully-inside
  only), with a half-pixel margin excluding zero-overlap edge rows;
  fully-outside rows stay hidden as the cheap skip.

AP-201 retired in this commit (AP actives 142 -> 141); #371 closed; the
gate script's Chat-tab steps re-written to expect clean edge clipping
and to treat any whole-block vanish as a regression. Pinned by
StraddlingRow_StaysVisible_AndClipsInsteadOfVanishing (the exact gate-3
geometry: header + 260px straddler in a 430px viewport) and
ViewportClipsChildDrawingAndHitTesting (the clipped slice is not
clickable).

Full Release suite: 13,089 passed / 4 skipped / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 17:08:21 +02:00