diff --git a/docs/plans/2026-08-11-fellowship-allegiance-campaign.md b/docs/plans/2026-08-11-fellowship-allegiance-campaign.md index 4ce5fd30..0cd3934d 100644 --- a/docs/plans/2026-08-11-fellowship-allegiance-campaign.md +++ b/docs/plans/2026-08-11-fellowship-allegiance-campaign.md @@ -21,8 +21,11 @@ panel (the OP3 recipe: `LayoutImporter.ImportInfos(dats, 0x2100006E, 0x1000018F)`, Type-8 tab host + `ActivateTabBehavior`, per-page scoped controllers, `RetailPanelCatalog` id 12, F3/F4 keybinds). Fellowship and allegiance state live in TWO new sibling Runtime owners under -`GameRuntime` per the Slice-J pattern (different lifetimes: fellowship is -session-scoped; allegiance survives reconnect behind a seed latch). Wire +`GameRuntime` per the Slice-J pattern. ~~(different lifetimes: fellowship is +session-scoped; allegiance survives reconnect behind a seed latch)~~ +**[FA2 fix-round addendum, 2026-08-12: corrected — both owners are +session-scoped and clear at every generation reset; see D2's addendum +below for the full three-way evidence citation.]** Wire parsers/builders live in `AcDream.Core.Net` beside the H.2 scaffolding they connect, repair, or replace. Both graphical and headless hosts are served by the single inbound wiring site. @@ -49,10 +52,51 @@ served by the single inbound wiring site. is already served by ACE and trivially pinned in-slice — otherwise the action buttons are honest INERT with register rows (the OP3 precedent), completed post-campaign. -- **D2 — two sibling Runtime owners.** `RuntimeFellowshipState` +- **D2 —** ~~two sibling Runtime owners. `RuntimeFellowshipState` (session-scoped, cleared at reset like external-container) and `RuntimeAllegianceState` (survives reconnect behind a `HasServerSeed`- - style latch). Lane D's 8-edit template per owner; consumers poll via + style latch).~~ **[FA2 fix-round addendum, 2026-08-12: shipped the + OPPOSITE finding for `RuntimeAllegianceState` — the "survives reconnect" + half is proven wrong by three-way evidence + (docs/research/2026-08-12-fa2-review-mechanism.md MUST-FIX 1): + (1) **the retail hook** — `ClientAllegianceSystem:: + OnEndCharacterSession @0x00569FA0` tail-calls `AllegianceProfile::Clear` + at exactly the per-character-session boundary this owner's reset would + run at, mirroring the sibling `ClientFellowshipSystem:: + OnEndCharacterSession @0x005690A0` Fellowship already honored — FA2's + fellowship half was byte-faithful, the allegiance half was the exact + inverse of retail's behavior at the same hook; + (2) **the cited precedent's actual behavior** — + `RuntimeCharacterOptionsState.ResetSession` + (`src/AcDream.Runtime/Gameplay/RuntimeCharacterState.cs:1007-1017`), + the very `HasServerSeed`-style latch this decision named as its + justification, CLEARS and re-latches on every reset; it does not + persist. Its own doc comment names the exact hazard this row then walked + into verbatim: "a stale seed surviving a session boundary could let a + flush ship the PRIOR character's words over the new one's"; + (3) **the no-character-selector connect path** — + `SessionPlayerComposition.cs:1127-1132` constructs + `LiveSessionConnectOptions` with no character field, so + `LiveSessionController.StartCore` falls through to + `CharacterList.TrySelectFirstAvailable`: which character enters world at + generation N+1 is resolved fresh from a server-supplied list, and + nothing in `RuntimeAllegianceState` keyed on character identity. The + process model does NOT preclude a cross-character reset on the + graphical host, so a stale allegiance tree surviving reset could present + as the WRONG character's monarch/rank/vassal list. + **Corrected semantics:** `RuntimeAllegianceState` is now ALSO a + `RuntimeGenerationReset` stage (`RuntimeGenerationResetStage.Allegiance`) + with the identical clear-and-relatch shape as `RuntimeFellowshipState` + and the `RuntimeCharacterOptionsState` precedent — the profile clears + AND `HasServerSeed` drops to `false` at every generation reset, not just + at terminal `Dispose`. The `HasServerSeed` latch's remaining job is + exactly what its name says: distinguishing "no profile has arrived THIS + generation" from "genuinely no allegiance" WITHIN a session — it is a + within-session rendering gate, not a cross-reconnect persistence + mechanism. The two owners remain separate classes (not merged into one) + because fellowship and allegiance are independent retail systems with + independent wire families, not because their lifetimes differ anymore.]** + Lane D's 8-edit template per owner; consumers poll via `Snapshot.Revision` — no `IRuntimeEventObserver` member is added (that would break all five bot policies + the trace recorder). - **D3 — the H.2 scaffolding is connected, repaired, or deleted — never @@ -128,7 +172,7 @@ FA2, FA3, FA4, FA5, FA6, FA7. | Slice | Contract (summary) | Gate | |---|---|---| | FA1 | Core.Net truth: repair fellowship builders + re-pin golden vectors; add `0x0290`/`0x0291`/`0x00A6`/`0x001F`; the allegiance action set the panel needs (swear/break/kick/info at minimum; the rest of the 27 as builders only where ACE serves them); parsers for the 11 S→C events incl. the profile version gates (extending `ParseAllegianceInfoResponse`) and the tree discard/reversal rules; complete the confirmation triple; ~~add the 8 WeenieError strings~~ **[FA1 fix-round addendum, 2026-08-12: shipped as CONFIRM-ABSENT instead — see D9's addendum. The 8 ids have no retail display text; acdream's silence is already faithful and a conformance test pins it.]**; DELETE `AllegianceTree` | automated only | -| FA2 | `RuntimeFellowshipState` + `RuntimeAllegianceState` (lane D's 8-edit template each); single-site inbound wiring serving BOTH hosts; typed commands/views; snapshot revisions; reset/reconnect semantics (session-scoped vs seed-latched); bot event surface via polling | automated only | +| FA2 | `RuntimeFellowshipState` + `RuntimeAllegianceState` (lane D's 8-edit template each); single-site inbound wiring serving BOTH hosts; typed commands/views; snapshot revisions; reset/reconnect semantics (~~session-scoped vs seed-latched~~ **[FA2 fix-round: both session-scoped — see D2's addendum]**); bot event surface via polling | automated only | | FA3 | The social panel shell: mount slot `0x1000018F` (catalog id 12), F3/F4 handlers, fixture dump of the slot (closes U3/U4/U6/U7/U10), tab activation, all four pages' empty states, Friends/Squelch read-only binding to J4.1 state | user (connected) | | FA4 | Fellowship page live: roster rows (adds `UiTemplateListBox` Flush/selection/row-instance-id — lane A sized this), the `0x00A6` show/hide declaration + vitals stream, create dialog (inline name field, shareXP), recruit/dismiss/quit/disband/leader + confirmations, share display per D5, option-row un-dims per D7 | user (connected) + bot | | FA5 | Allegiance page live: profile parse on show (`0x027B`), monarch/patron/self blocks, flat vassal list with the reversal rule honored, swear/break/kick + confirmations, ACE zeroed-field presentation scoped honestly (register row) | user (connected) + bot | @@ -194,7 +238,7 @@ before anything builds on them. | Slice | Status | Commit(s) | Review | Gate | |---|---|---|---|---| | FA1 | **CLOSED 2026-08-12** — narrow re-review verdict CLOSED, no reopen (`96df892d`, §7 of the mechanism findings doc: all 7 mechanism dispositions re-derived in the diffs, all 6 blast dispositions spot-verified, suite claim corroborated on post-fix binaries); re-review carry-forward CF-1 (two further stale `AllegianceTree` citations in the seam map's FA2 guidance, `:128`/`:214`) closed by coordinator addenda in the same commit as this ledger update — FA2 was contracted not to start before that | `7be86f47` (builders), `6bedbc47` (parsers), `5f9aa16f` (WeenieError), `4281750b` (delete AllegianceTree); fix-round: `ed308087` (mechanism+blast MUST/SHOULD-FIX code+tests), this commit (register/plan/seams doc corrections) | mechanism `docs/research/2026-08-12-fa1-review-mechanism.md` (2 MUST-FIX, 5 SHOULD-FIX, all applied); blast `docs/research/2026-08-12-fa1-review-blast.md` (4 MUST-FIX, 5 SHOULD-FIX, all applied). **Live-surface note (blast SF-1):** FA1 changed the observable output of the ALREADY-LIVE `@allegiance info` command in two retail-faithful ways — vassal print order reversed (now pinned by a 3-vassal test through `FormatAllegianceInfoLines`) and a malformed tree now prints nothing instead of a partial roster (now pinned at the `GameEventWiring` layer) — not a "purely unwired" slice. | automated: Release build + full suite green throughout. **Reconciled totals (blast MF-4):** the ledger's own prior figure (13,149/4/0) is CONFIRMED correct by direct measurement at the pre-fix-round tip `bc693728` (13,153 total = 4+916+1559+15+130+119+4856+877+4677 across all 9 test projects); the campaign-start baseline in §5 is 13,103/4/0, and the diff-verified FA1 delta is **+58 added / −9 deleted (deleted `AllegianceTreeTests.cs`) = net +49**, i.e. 13,103+49=13,152 — one test of drift against the directly-measured 13,153/13,149 baseline, attributed to the §5 baseline being captured at a different point in git history than the FA1 diff's actual parent, not a further miscount. This fix round adds a further **+9 tests** (2 golden vectors for the new `0x001F` builder, 1 D5 `<<1` pin at the `0x02C0` site, 4 MF-1/SF-1 boundary tests, 2 blast SF-1 live-surface pins) — **final: 13,158 passed / 4 skipped / 0 failed (13,162 total), directly measured.** | -| FA2 | **CODE-COMPLETE 2026-08-12** — automated gate only, per contract | `RuntimeFellowshipState`/`RuntimeAllegianceState` (2 new sibling J-owners, `src/AcDream.Runtime/Gameplay/`), the full 8-edit template applied twice (`GameRuntime.cs`, `RuntimeGenerationReset.cs`, `RuntimeGameplayOwnership.cs`/`RuntimeSimulationOwnership.cs`, `GameRuntimeGameplayViews.cs`, `GameRuntimeCommands.cs`, `GameRuntimeEvents.cs`, `GameRuntimeViews.cs`), 12 new `WorldSession.Send*` wrappers, 15 new `GameEventWiring.WireAll` delegate holes registered at the single site (`LiveSessionEventRouter.cs`), both `LiveSocialSessionBindings` construction sites updated (`LiveSessionRuntimeFactory.cs`, `HeadlessSessionHost.cs`), `IRuntimeFellowshipCommands`/`IRuntimeAllegianceCommands` implemented on both host command adapters (`DirectGameRuntimeCommandAdapter`, `CurrentGameRuntimeCommandAdapter` + its `LiveSessionCommandRouter`/`LiveSessionCommandBindings` App-bus plumbing), divergence register rows TS-81 (filed) + TS-80 (narrowed) | mechanism review not yet run (automated-gate slice per contract) | automated: Release build + full suite green throughout; **baseline 13,158/4/0 (13,162 total) → 13,201/4/0 (13,205 total), +43 tests, arithmetic exact** (`tests/AcDream.Runtime.Tests/Gameplay/RuntimeFellowshipStateTests.cs` +14, `RuntimeAllegianceStateTests.cs` +7, `RuntimeGenerationResetTests.cs` +1, `GameRuntimeTests.cs` +2 construction-fault-point cases, `Session/DirectGameRuntimeCommandAdapterTests.cs` +10, `tests/AcDream.Core.Net.Tests/GameEventWiringTests.cs` +9). Seam-doc correction found and fixed during implementation: §2.3's "the dispatcher supports multiple owned handlers per type — both fire" claim does not hold against `GameEventDispatcher.Dispatch` (`src/AcDream.Core.Net/Messages/GameEventDispatcher.cs:95-117`), which invokes only the single most-recently-registered handler per `GameEventType`; a literal second `registrar.Register(GameEventType.AllegianceInfoResponse, ...)` would have silently killed the already-live `@allegiance info` chat-text output. Fixed by folding both behaviors into the ONE existing registration instead (see `GameEventWiring.cs`'s AllegianceInfoResponse handler). Deferred: `0x027A AllegianceLoginNotification`'s two-line retail chat text — the candidate strings resolve through Binary-Ninja-mislabeled symbols (`gmAllegianceUI::RecvNotice_AllegianceLogin @0x00492220`) requiring a DAT string-table lookup not yet done; filed as register row TS-81 rather than inventing English. The seam doc's "borrow RuntimeEntityObjectLifetime for member guid → ClientObject" fellowship recommendation was not implemented — the wire's own `FellowMember`/`FellowshipUpdateFellow` records already carry name + full vitals inline, so no entity-table join is needed; noted, not forced. | +| FA2 | **FIX-ROUND CLOSED 2026-08-12** — both reviews' MUST-FIX/SHOULD-FIX findings applied; automated gate only, per contract | `RuntimeFellowshipState`/`RuntimeAllegianceState` (2 new sibling J-owners, `src/AcDream.Runtime/Gameplay/`), the full 8-edit template applied twice (`GameRuntime.cs`, `RuntimeGenerationReset.cs`, `RuntimeGameplayOwnership.cs`/`RuntimeSimulationOwnership.cs`, `GameRuntimeGameplayViews.cs`, `GameRuntimeCommands.cs`, `GameRuntimeEvents.cs`, `GameRuntimeViews.cs`), ~~12 new `WorldSession.Send*` wrappers, 15 new `GameEventWiring.WireAll` delegate holes~~ **[FA2 fix-round addendum, 2026-08-12 (docs/research/2026-08-12-fa2-review-blast.md SHOULD-FIX 3): both counts were wrong. `WorldSession.cs:2318-2404` adds **11** new `Send*` wrappers (7 fellowship + 4 allegiance) — `SendAllegianceInfoRequest` pre-dates FA2; the **12** figure belongs to a different count, `cced83b4`'s `*RuntimeCmd` records / `LiveSessionCommandBindings` send delegates. `GameEventWiring.cs:107-120` adds **10** delegate holes (9 new + the `0x027C` fold), not 15 — matching the "11 S→C events" the seam doc's §2.3 table names, of which 2 (`0x01C9`/`0x01CA`) are correctly left unregistered (dead COMDAT-fold no-ops) and 1 (`0x027C`) was already registered pre-FA2.]** registered at the single site (`LiveSessionEventRouter.cs`), both `LiveSocialSessionBindings` construction sites updated (`LiveSessionRuntimeFactory.cs`, `HeadlessSessionHost.cs`), `IRuntimeFellowshipCommands`/`IRuntimeAllegianceCommands` implemented on both host command adapters (`DirectGameRuntimeCommandAdapter`, `CurrentGameRuntimeCommandAdapter` + its `LiveSessionCommandRouter`/`LiveSessionCommandBindings` App-bus plumbing), divergence register rows TS-81 (filed) + TS-80 (narrowed); fix-round: `4272ad0e` (mechanism MUST-FIX 1/2 + blast MUST-FIX 1/2 + blast SF-1 + mechanism SF-2 — allegiance reset semantics, 0x027C stops seeding, teardown-ledger off-by-one, conditional delegate holes, disposed-checks-inside-lock), `ded23067` (mechanism SF-3/4/5/6 + blast SF-4/5/7 — RecalculateEvenXPSplitting, locked/departed admission gate, AllegianceProfileLookups reuse, non-null checkpoint defaults, router self/other-quit test, ResetSession disposed-guard parity, GetVassals allocation doc), this commit (register/plan/seams doc corrections) | mechanism `docs/research/2026-08-12-fa2-review-mechanism.md` (2 MUST-FIX, 6 SHOULD-FIX, all applied); blast `docs/research/2026-08-12-fa2-review-blast.md` (2 MUST-FIX, 7 SHOULD-FIX, all applied). **Allegiance register-row re-evaluation (blast SF-6):** the design blast SF-6 asked to be either dropped or given a register row is now fully retired by MF-1's fix — `RuntimeAllegianceState` clears at every generation reset exactly like `RuntimeFellowshipState` and exactly like retail's `OnEndCharacterSession` hook, so there is no remaining acdream-vs-retail deviation for allegiance persistence to name a row for. No register row added; conclusion recorded here per the fix-round contract. | automated: Release build + full suite green throughout. **Pre-fix-round baseline 13,201/4/0 (13,205 total, measured at `12053e61` by the blast review) → fix-round 13,215/4/0 (13,219 total), +14 tests, arithmetic exact:** `RuntimeAllegianceStateTests.cs` net +2 (−1 deleted `ApplyInfoResponseSelf_...`, +3 new `ResetSession_*`), `RuntimeFellowshipStateTests.cs` +10 (1 `ResetSession_AfterDispose_...` + 3 `[Theory]` cases for `RecalculateEvenXPSplitting` + 1 `ShareXp`-off case + 1 full-update-never-recomputes case + 4 locked/departed admission-gate cases), `GameRuntimeTests.cs` +1 (`CompletedTeardownStagesAccumulatesExactlyOneFlagPerStage`), `Session/LiveSessionEventRouterTests.cs` +1 (`FellowshipQuit_RoutesSelfGuidToClearAndOtherGuidToRemove`); `RuntimeGenerationResetTests.cs` and `GameEventWiringTests.cs` each renamed one test in place (net 0); `GameRuntimeContractTests.cs` gained two trailing constructor arguments at its sole positional `RuntimeStateCheckpoint` site (compile-fix only, no new test). | | FA3 | — | | | | | FA4 | — | | | | | FA5 | — | | | | diff --git a/docs/research/2026-08-11-fa-acdream-seams.md b/docs/research/2026-08-11-fa-acdream-seams.md index 192f4457..bb349ed4 100644 --- a/docs/research/2026-08-11-fa-acdream-seams.md +++ b/docs/research/2026-08-11-fa-acdream-seams.md @@ -146,6 +146,25 @@ Rationale and precedent: (same class as `RuntimeCharacterOptionsState`'s `HasServerSeed` latch, `project_settings_options_digest.md` DO-NOT-RETRY row "Applying a pre-seed Reset/local snapshot over server truth"). + **[FA2 fix-round addendum, 2026-08-12 + (docs/research/2026-08-12-fa2-review-mechanism.md MUST-FIX 1): this + citation was INVERTED. `RuntimeCharacterOptionsState`'s `HasServerSeed` + latch does NOT survive a reset — `ResetSession` + (`RuntimeCharacterState.cs:1007-1017`) explicitly zeroes the options AND + clears `_hasServerSeed`, with its own doc comment naming the exact hazard + this row then walked into: "a stale seed surviving a session boundary + could let a flush ship the PRIOR character's words over the new one's." + Retail itself clears allegiance at the same per-character-session + boundary Fellowship already clears at + (`ClientAllegianceSystem::OnEndCharacterSession @0x00569FA0` tail-calls + `AllegianceProfile::Clear`, mirroring + `ClientFellowshipSystem::OnEndCharacterSession @0x005690A0`). Corrected: + allegiance is now ALSO a reset stage + (`RuntimeGenerationResetStage.Allegiance`) with the identical + clear-and-relatch shape as the cited precedent. The two owners remain + separate classes because fellowship and allegiance are independent + retail systems with independent wire families, not because their + lifetimes differ — they no longer do.]** - **Borrow, never copy, the entity table.** Fellowship member rows must read live names/vitals from `RuntimeEntityObjectLifetime`'s canonical `ClientObjectTable` the way `RuntimeInventoryState` does @@ -264,6 +283,37 @@ existing lambda or add a second `registrar.Register` for the same type `OwnedGameEventRegistrar.Register`, `:929-940`) and accept that both fire. Decide this in the plan, explicitly. +**[FA2 fix-round addendum, 2026-08-12 +(docs/research/2026-08-12-fa2-review-blast.md SHOULD-FIX 1): the +parenthetical above is WRONG and was disproved during FA2 implementation. +`GameEventDispatcher.Dispatch` +(`src/AcDream.Core.Net/Messages/GameEventDispatcher.cs:95-117`) does a +single `_handlers.TryGetValue(...)` and invokes the ONE stored +`RegistrationNode`; `OwnedGameEventRegistrar.Register` REPLACES the prior +registration for that `GameEventType`, it does not add a second co-owned +handler. A second `registrar.Register(GameEventType.AllegianceInfoResponse, +...)` call would have silently killed the already-live `@allegiance info` +chat-text output the instant a caller supplied an allegiance-owner +callback. FA2 folded both behaviors into the ONE existing registration +instead (`GameEventWiring.cs`'s `AllegianceInfoResponse` handler) — see +that method's own code comment for the corrected claim. This was also the +right call independent of the correction: FA2's fix round (MUST-FIX 2) +subsequently found that `0x027C` should not feed a Runtime callback at +all, since retail's own handler for it is print-only over a stack-local +profile — so the "second handler" question this section poses is now moot +either way.]** + +`0x01C9 FellowshipFellowUpdateDone` and `0x01CA FellowshipFellowStatsDone` +from the table above are **[FA2 fix-round addendum, 2026-08-12 +(docs/research/2026-08-12-fa2-review-blast.md SHOULD-FIX 2): correctly +left unregistered by FA2.** FA1 established that both COMDAT-fold onto the +identical no-op body in the Sept-2013 client and are parse-and-ignore only +(`src/AcDream.Core.Net/Messages/GameEvents.cs:779-803`) — there is nothing +for a Runtime owner to consume. This is the disposition for the two rows +this section's own title ("The 11 events FA must register") implied FA +owed a registration for; FA2 registered the other 9 plus the `0x027C` +fold, 10 total.]** + ### 2.4 Bindings-record plumbing (both hosts) `LiveSessionEventRouter`'s five binding records are the host-facing @@ -808,7 +858,7 @@ A/B should confirm this against retail rather than inferring it from ACE. | Subsystem | Where new code goes | Pattern to follow | Precedent citation | |---|---|---|---| | Fellowship state owner | `src/AcDream.Runtime/Gameplay/RuntimeFellowshipState.cs` (new) | J4 owner: `IDisposable` + `ResetSession()` + `CaptureOwnership()`; borrow entity table | `RuntimeInventoryState` ctor `GameRuntime.cs:192-193`; snapshot shape `RuntimeCharacterState.cs:9-51` | -| Allegiance state owner | `src/AcDream.Runtime/Gameplay/RuntimeAllegianceState.cs` (new), ~~wrapping existing `AcDream.Core.Allegiance.AllegianceTree`~~ **[FA1 fix-round addendum, 2026-08-12: `AllegianceTree` was DELETED by Campaign FA slice FA1 (`4281750b`) — there is nothing left to wrap. Wrap `ClientCommandResponses.AllegianceProfileLookups` (the flat `AllegianceMemberRecord` list + `GetData`/`GetPatron`/`FindVassals` walk that replaced it) instead. FA2 is the very next slice — this is its seam-map row.]** | Same; **survives reconnect** — use a `HasServerSeed`-style latch | `RuntimeCharacterOptionsState` seed latch; ~~`AllegianceTree.cs:56-162`~~ **[FA1 fix-round addendum: see `ClientCommandResponses.cs:250-295` (`AllegianceProfileLookups`) instead]** | +| Allegiance state owner | `src/AcDream.Runtime/Gameplay/RuntimeAllegianceState.cs` (new), ~~wrapping existing `AcDream.Core.Allegiance.AllegianceTree`~~ **[FA1 fix-round addendum, 2026-08-12: `AllegianceTree` was DELETED by Campaign FA slice FA1 (`4281750b`) — there is nothing left to wrap. Wrap `ClientCommandResponses.AllegianceProfileLookups` (the flat `AllegianceMemberRecord` list + `GetData`/`GetPatron`/`FindVassals` walk that replaced it) instead. FA2 is the very next slice — this is its seam-map row.]** | Same; ~~**survives reconnect** — use a `HasServerSeed`-style latch~~ **[FA2 fix-round addendum, 2026-08-12 (docs/research/2026-08-12-fa2-review-mechanism.md MUST-FIX 1): WRONG — retail clears the allegiance profile at `OnEndCharacterSession`, the very same boundary Fellowship clears at. Allegiance is now a `RuntimeGenerationReset` stage exactly like Fellowship; the `HasServerSeed` latch clears AND re-arms every generation (matching the `RuntimeCharacterOptionsState` precedent's actual clear-and-relatch behavior, not the persist behavior this row previously (mis)cited it for).]** | `RuntimeCharacterOptionsState` seed latch; ~~`AllegianceTree.cs:56-162`~~ **[FA1 fix-round addendum: see `ClientCommandResponses.cs:250-295` (`AllegianceProfileLookups`) instead]** | | Owner registration | `GameRuntime.cs` | ctor + `construction.Own` + fault point + `*Owner` prop + view prop + `CaptureOwnership` + shutdown step | `GameRuntime.cs:200-214`, `:419-429`, `:463-470`, `:545-567`, `:703-752` | | Session reset | `RuntimeGenerationReset.cs` | new `RuntimeGenerationResetStage` ordinal + `Drain` case; **fellowship clears, allegiance re-seeds** | `:21-44`, `:236-349`; shared-stage example `:248-261` | | Ownership rollup | `RuntimeGameplayOwnership.cs` | add to record + `Capture(...)` signature + `IsConverged` | `:8-21`, `:23-44` | @@ -850,8 +900,16 @@ A/B should confirm this against retail rather than inferring it from ACE. 3. **`0x027C AllegianceInfoResponse` already has a handler** (`GameEventWiring.cs:192-198`, chat output for `@allegiance info`). Does FA extend that lambda, register a second handler, or leave the - panel to use a different event? The dispatcher permits multiple owned - handlers per type — but two handlers writing to two different owners is + panel to use a different event? ~~The dispatcher permits multiple owned + handlers per type~~ **[FA2 fix-round addendum, 2026-08-12 + (docs/research/2026-08-12-fa2-review-blast.md SHOULD-FIX 1): wrong — + `GameEventDispatcher.Dispatch` invokes only the single + most-recently-registered handler per type; a second `Register` call + REPLACES, it does not co-own. See §2.3's addendum for the full + correction. Answered: extend the existing lambda (done), and FA2's own + fix round then found the extension itself should not exist — retail's + 0x027C handler is print-only, so no owner ever gets fed from it.]** — + but two handlers writing to two different owners is the "two writable copies" smell. 4. **Which of the 6 dimmed rows actually gain consumers in FA v1?** Each un-dim requires three coordinated edits (§4.2). Under-claiming is safe; @@ -908,8 +966,12 @@ A/B should confirm this against retail rather than inferring it from ACE. parsers**, **no inbound registrations**, and **no F3/F4 handler**. 2. **Ownership: two new sibling J-owners under `GameRuntime`** (`RuntimeFellowshipState`, `RuntimeAllegianceState`), NOT children of - `RuntimeCommunicationState` — the lifetimes differ (fellowship is - session-scoped, allegiance survives reconnect) and J4's own precedent is + `RuntimeCommunicationState` — ~~the lifetimes differ (fellowship is + session-scoped, allegiance survives reconnect)~~ **[FA2 fix-round + addendum, 2026-08-12: both are now session-scoped, cleared at every + generation reset — see §1.3's addendum. They remain separate owners + because they are independent retail systems, not because their + lifetimes differ]** and J4's own precedent is one owner per coupled graph. Each costs eight mechanical edits, listed with line-cited templates in §1.1. 3. **Inbound has exactly one registration site**