diff --git a/src/AcDream.App/UI/Layout/CharacterOptionsPageController.cs b/src/AcDream.App/UI/Layout/CharacterOptionsPageController.cs
index 7cfe75c5..6e7158f0 100644
--- a/src/AcDream.App/UI/Layout/CharacterOptionsPageController.cs
+++ b/src/AcDream.App/UI/Layout/CharacterOptionsPageController.cs
@@ -111,7 +111,7 @@ public static class CharacterOptionsPageController
/// steps 13-18, written after the code landed):
///
///
- /// - NOT dimmed (19 rows, real acdream-side
+ ///
- NOT dimmed (16 rows, real acdream-side
/// consumer): Group B rows actually bound —
/// ViewCombatTarget/AutoTarget/AutoRepeatAttack
/// (combat: LiveCombatAttackOperations.cs,
@@ -125,14 +125,10 @@ public static class CharacterOptionsPageController
/// InteractionRetainedUiComposition.cs:326), all six
/// ListenTo*Chat ids (TurbineChatMembershipGate.cs:105-136
/// gates every Turbine room join on the matching bit), and — landed at
- /// Campaign FA slice FA4, D7 — IgnoreFellowshipRequests/
- /// FellowshipAutoAcceptRequests (consumed by
- /// RetailUiRuntime.TryAutoRespondToFellowshipInvite's auto-
- /// decline/auto-accept, D6) and FellowshipShareXP/
- /// FellowshipShareLoot (consumed by the fellowship page's own
- /// Create flow / D5 display / second checkbox surface,
- /// SocialFellowshipPageController).
- /// - Dimmed (31 rows, store-only): every
+ /// Campaign FA slice FA4, D7, SURVIVING the fix-round correction below
+ /// — FellowshipShareXP (the Create-flow click genuinely reads
+ /// it as the sent shareXP bit, SocialFellowshipPageController.WireButtons).
+ /// - Dimmed (34 rows, store-only): every
/// remaining Group A row (wire+store only — ACE, not acdream, is the
/// consumer) and every remaining Group B row the OP4 gate script's own
/// step 16 lists as "no consumer surface" (ShowTooltips,
@@ -142,9 +138,53 @@ public static class CharacterOptionsPageController
/// FilterLanguage, MainPackPreferred), plus every Group D
/// deferral (SalvageMultiple,
/// DisableHouseRestrictionEffects,
- /// HearPkDeathMessages) — FellowshipShareXP left Group D
- /// at FA4 (above), it stays a bullet-list resident here only for the
- /// historical count.
+ /// HearPkDeathMessages).
+ ///
+ ///
+ /// FA4 fix-round correction, 2026-08-12 (D6/D7 plan correction +
+ /// mechanism SF-8). FA4 originally un-dimmed FOUR rows here:
+ /// IgnoreFellowshipRequests/FellowshipAutoAcceptRequests
+ /// (claiming a consumer in a client-side invite auto-respond
+ /// interceptor) and FellowshipShareXP/FellowshipShareLoot
+ /// (claiming the fellowship page's own checkbox surface as a
+ /// consumer). Both claims were wrong, for two SEPARATE reasons:
+ ///
+ ///
+ /// - D6's correction (the plan doc, 2026-08-12):
+ /// retail's client reads NEITHER option bit on the invite/confirmation
+ /// path — Handle_Character__ConfirmationRequest @0x005640A0,
+ /// RecvNotice_FellowshipRequest @0x00490880, and
+ /// MakeFellowRequestDialog @0x00490620 (whose ONLY guard is
+ /// m_fellowRequestContext) were read in full; a whole-file sweep
+ /// of both accessors finds zero reads on any confirmation path. ACE
+ /// filters both bits SERVER-SIDE. The client-side interceptor
+ /// (RetailUiRuntime.TryAutoRespondToFellowshipInvite) was
+ /// therefore deleted outright — the fellow-invite dialog always shows,
+ /// exactly like retail. IgnoreFellowshipRequests/
+ /// FellowshipAutoAcceptRequests go back to store-only, exactly
+ /// like the two allegiance bits (IgnoreAllegianceRequests/
+ /// DisplayAllegianceLogonNotifications) they were always meant
+ /// to parallel — both pairs are pure server-side filters with NO client
+ /// consumer.
+ /// - Mechanism SF-8 (the review, same date):
+ /// FellowshipShareLoot's claimed consumer — "a second live
+ /// checkbox surface on the fellowship page" — is not a consumer at
+ /// all: nothing in acdream ever READS the stored value back
+ /// (FormatStatsText uses snapshot.ShareXp only; the
+ /// 0x00A2 Create builder carries shareXP alone), and the
+ /// live-DAT dump confirms its checkbox (0x10000273) is a child
+ /// of the NOT-in-fellowship frame — invisible whenever you actually
+ /// have a fellowship to loot-share within. A second EDITOR of a stored
+ /// value is not a consumer by AD-78's own definition ("drives nothing
+ /// OBSERVABLE client-side"). Only FellowshipShareXP survives —
+ /// its value is genuinely READ by the Create click.
+ ///
+ ///
+ /// Net: 35 (pre-FA4) → FA4 shipped 31 → fix round reverts three of the
+ /// four un-dims (Ignore, AutoAccept, ShareLoot) → 34 of 50 dimmed /
+ /// 16 live, ONE net un-dim from pre-FA4 baseline
+ /// (FellowshipShareXP only).
+ ///
/// - Flagged ambiguity, resolved by code evidence
/// (see final report, not re-litigated here): the research doc's
/// §7.1 lists AcceptLootPermits in BOTH Group A's bullet list AND
@@ -156,12 +196,11 @@ public static class CharacterOptionsPageController
/// IgnoreFellowshipRequests/FellowshipAutoAcceptRequests
/// had a genuine coded mutual-exclusion side effect
/// (RuntimeCharacterState.TrySetOption, MF-2) that the research
- /// doc's Group A classification did not capture, kept dimmed at OP1
- /// pending "a future un-dim is a one-line judgment call, not a
- /// re-investigation" — that future consumer landed at Campaign FA slice
- /// FA4 (see D7's addendum to register row AD-78), so both rows moved to
- /// the NOT-dimmed bullet above, together with the two
- /// FellowshipShare* rows FA4 also gave real consumers.
+ /// doc's Group A classification did not capture — this remains true and
+ /// harmless (the mutual exclusion is enforced regardless of which
+ /// surface's caption is dimmed), but it is NOT itself a client-visible
+ /// CONSUMER of the option's VALUE, so both rows stay dimmed per the
+ /// fix-round correction above.
///
///
private const bool Live = false;
@@ -203,11 +242,11 @@ public static class CharacterOptionsPageController
new("ID_CharacterOption_Grouping_Section", new RowSpec[]
{
new(CharacterOptionId.IgnoreAllegianceRequests, "IgnoreAllegianceRequests", StoreOnly), // Group A — stays dimmed, D7/AD-78 addendum
- new(CharacterOptionId.IgnoreFellowshipRequests, "IgnoreFellowshipRequests", Live), // D7/D6 (Campaign FA slice FA4): consumed by the fellowship-invite auto-decline (RetailUiRuntime.TryAutoRespondToFellowshipInvite)
+ new(CharacterOptionId.IgnoreFellowshipRequests, "IgnoreFellowshipRequests", StoreOnly), // fix-round D6 correction: pure server-side filter, NO client consumer — reverted from FA4's Live (see class doc)
new(CharacterOptionId.DisplayAllegianceLogonNotifications, "DisplayAllegianceLogonNotifications", StoreOnly), // Group A — stays dimmed, D7/AD-78 addendum
- new(CharacterOptionId.FellowshipShareXP, "FellowshipShareXP", Live), // D7 (Campaign FA slice FA4): consumed by the fellowship Create flow (its value IS the sent shareXP bit) and the panel's D5 XP-share display
- new(CharacterOptionId.FellowshipShareLoot, "FellowshipShareLoot", Live), // D7 (Campaign FA slice FA4): now also a SECOND live checkbox surface on the fellowship page itself (SocialFellowshipPageController)
- new(CharacterOptionId.FellowshipAutoAcceptRequests, "FellowshipAutoAcceptRequests", Live), // D7/D6 (Campaign FA slice FA4): consumed by the fellowship-invite auto-accept (suppresses the confirmation dialog)
+ new(CharacterOptionId.FellowshipShareXP, "FellowshipShareXP", Live), // D7 (Campaign FA slice FA4): consumed by the fellowship Create flow (its value IS the sent shareXP bit)
+ new(CharacterOptionId.FellowshipShareLoot, "FellowshipShareLoot", StoreOnly), // fix-round mechanism SF-8: no real acdream-side READER of the stored value — reverted from FA4's Live (see class doc)
+ new(CharacterOptionId.FellowshipAutoAcceptRequests, "FellowshipAutoAcceptRequests", StoreOnly), // fix-round D6 correction: pure server-side filter, NO client consumer — reverted from FA4's Live (see class doc)
}),
new("ID_CharacterOption_OtherPlayers_Section", new RowSpec[]
{
diff --git a/tests/AcDream.App.Tests/UI/Layout/CharacterOptionsPageControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/CharacterOptionsPageControllerTests.cs
index aa9bd75a..10645d1f 100644
--- a/tests/AcDream.App.Tests/UI/Layout/CharacterOptionsPageControllerTests.cs
+++ b/tests/AcDream.App.Tests/UI/Layout/CharacterOptionsPageControllerTests.cs
@@ -698,19 +698,28 @@ public sealed class CharacterOptionsPageControllerTests
// ── AD-78 caption dimming (user-directed, 2026-08-11, gate 2) ───────────
///
- /// The exact 31 values this slice dims
+ /// The exact 34 values this slice dims
/// ( == true), transcribed independently
/// of CharacterOptionsPageController.Groups from the derivation
/// table in that class's own doc comment. Wiring a future consumer
/// for any of these means removing it from this literal set AND
/// flipping its Groups table entry from StoreOnly to
/// Live consciously — leaving either one stale fails this
- /// test. Campaign FA slice FA4, D7 removed four ids from this set —
+ /// test.
+ ///
+ ///
+ /// Campaign FA slice FA4, D7 originally removed four ids from this set.
+ /// The FA4 FIX ROUND (2026-08-12) put THREE of them back:
/// IgnoreFellowshipRequests/FellowshipAutoAcceptRequests
- /// (the fellowship-invite auto-decline/auto-accept, D6) and
- /// FellowshipShareXP/FellowshipShareLoot (the fellowship
- /// page's Create flow / D5 display / second checkbox surface) all
- /// gained real acdream-side consumers.
+ /// (D6's correction — retail's client reads neither bit on the invite
+ /// path; the client-side auto-respond interceptor that was their
+ /// claimed consumer is deleted) and FellowshipShareLoot
+ /// (mechanism SF-8 — its claimed "second checkbox surface" consumer
+ /// never actually READS the stored value back). Only
+ /// FellowshipShareXP survives as a genuine live row (the
+ /// fellowship Create flow reads it as the sent shareXP bit).
+ /// Net: 35 (pre-FA4) → 34 (post-fix-round), one net un-dim.
+ ///
///
private static readonly HashSet ExpectedStoreOnlyIds =
[
@@ -729,9 +738,12 @@ public sealed class CharacterOptionsPageControllerTests
CharacterOptionId.FilterLanguage,
CharacterOptionId.ShowHelm,
CharacterOptionId.ShowCloak,
- // Group 3 (Grouping) — 2 of 6 (FA4, D7: the other four — see class doc)
+ // Group 3 (Grouping) — 5 of 6 (fix round: only FellowshipShareXP stays live — see class doc)
CharacterOptionId.IgnoreAllegianceRequests,
+ CharacterOptionId.IgnoreFellowshipRequests,
CharacterOptionId.DisplayAllegianceLogonNotifications,
+ CharacterOptionId.FellowshipShareLoot,
+ CharacterOptionId.FellowshipAutoAcceptRequests,
// Group 4 (Other Players) — 10 of 11
CharacterOptionId.AcceptLootPermits,
CharacterOptionId.UseDeception,
@@ -762,8 +774,8 @@ public sealed class CharacterOptionsPageControllerTests
.ToHashSet();
Assert.Equal(ExpectedStoreOnlyIds, actualStoreOnly);
- Assert.Equal(31, actualStoreOnly.Count);
- Assert.Equal(19, 50 - actualStoreOnly.Count); // the 19 live rows (FA4, D7: +4)
+ Assert.Equal(34, actualStoreOnly.Count);
+ Assert.Equal(16, 50 - actualStoreOnly.Count); // the 16 live rows (fix round: net +1 from pre-FA4 baseline)
}
[Fact]