fix(ui): OP3 review fixes — byte-verified Magic chat lines, Gameplay/OptionPage shape, mid-air tri-state, shared geometry
Consolidated fix round for the two OP3 dual-lens reviews
(docs/research/2026-08-11-op3-review-{mechanism,blast}.md), both
APPROVE-WITH-FIXES.
MUST-FIX:
- The six "Use Mouse Turning Settings" chat lines were typed
RetailLogTextType.ClientLocal (0x1A); retail types them 0x07 (Magic).
BYTE-VERIFIED against the PDB-paired binary at all six
gmConfigUI::SetMouseTurningDefaults call sites (0x0049E972/E9E2/EA52/
EAA4/EAF6/EB48): every site pushes `6a 07` (type=7) immediately before
the text-pointer push and the AddTextToScroll call. Added a dedicated
OptionsRuntimeBindings.DisplayMouseTurningMacroLine seam routed at
Magic (scrolling chat transcript, light blue, timestamped) instead of
the 4-slot SpewBox ClientLocal uses; the mid-air refusal and UA/RA
keep ClientLocal (both independently confirmed correct).
- Filed AD-77: the client-wide floating-only gmPanelUI host divergence
(retail also exposes a docked 0x21000017 host) the plan §5 delegated
to this review, scoped to every main panel, not just Options.
SHOULD-FIX:
- gmGameplayOptionsUI is not an OptionPage in retail (acclient.h:55857,
UIElement_Field). OptionsPanelController now constructs the Gameplay
slot's OptionPage with AfterApply deliberately null, so entering/
leaving that tab never publishes SaveCharacterOptionsRuntimeCmd.
Corrected OptionPageModel's doc comment and rewrote the two tests
that pinned the wrong (Gameplay-flushes) shape.
- Added the OptionPage.OnOptionChanged seam (PlayerOptionPage::
OnOptionChanged @0x004F27D0) — fires as the last step of Apply/
Reset/Defaults, plus once per live LED edit via a new
IOptionRow.AttachPageNotify hook (BoolOptionRow wires it into
SetCurrentValue only, matching retail's Apply(1)-only
HandleDialogAndNotices path). OP4-6 will bind Apply/Reset enable
state to this.
- Exit to Character Selection's mid-air refusal is now tri-state
(Func<bool?> IsGrounded): retail's UseTime only reaches the airborne
test inside `else if (smartbox->player)`, so outside player mode (or
with no live controller) the button is a SILENT no-op, not a
refusal. Fixed the inverted comment at both call sites.
- Options panel geometry now matches its nine gmPanelUI siblings
sharing RetailPanelUiController's one main-panel rectangle
(ResizeX=false, bottom-edge-only resize, no invented Min/MaxWidth/
Height) instead of being the only all-four-edge/horizontal-resize
outlier whose width silently reverted whenever a sibling was shown.
- Added the three missing test pins: Options/Character mutual
exclusion through a REAL RetailPanelUiController registration,
RetailDialogFactory.MakeConfirmation's omitted-queueKey overload
sharing DefaultQueueKey, and UiTabPanel.ActivePageChanged never
firing on a dormant (non-activated) host.
- TS-74's What/Where now names the five store-only CameraTurning
preferences explicitly instead of only mentioning them in Risk.
- Test script gains the toolbar-button ghosted->enabled+highlight
check, UseMouseTurning-survives-relogin and the five prefs-survive-
relaunch steps, a UA/RA legibility eye-item, and the corrected
bottom-edge-only geometry description for step 5.
One-liners fixed in files already touched: symmetric close-button
resolve-failure logging in OptionsPanelController.Bind (blast NOTE 8).
Full Release suite: 12,947 passed / 4 skipped / 0 failed (baseline
12,935/4/0 post-OP7 — 12 net new tests; the two OptionPageModelTests
"wrong-shape" tests were renamed/rewritten in place, not removed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
64898f1301
commit
386076af0f
11 changed files with 505 additions and 72 deletions
|
|
@ -60,8 +60,14 @@ public sealed class OptionsPanelControllerTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void ActivateTabs_SelectsGameplayAsDefault_AndAppliesItsPage()
|
||||
public void ActivateTabs_SelectsGameplayAsDefault_ButNeverFlushesIt()
|
||||
{
|
||||
// Mechanism review S1 (2026-08-11 fix round): gmGameplayOptionsUI is
|
||||
// NOT an OptionPage in retail (acclient.h:55857) — its own OptionPage
|
||||
// instance is constructed with AfterApply deliberately left null
|
||||
// (OptionsPanelController's constructor), regardless of what the
|
||||
// controller's OWN AfterApply callback is, so the initial default-
|
||||
// tab activation (OnShown -> Apply) never publishes a flush.
|
||||
ImportedLayout layout = FixtureLoader.LoadOptionsPanelHost();
|
||||
var calls = new List<string>();
|
||||
int gameplayFlushCount = 0;
|
||||
|
|
@ -73,12 +79,11 @@ public sealed class OptionsPanelControllerTests
|
|||
|
||||
Assert.True(controller.TabPanel.BehaviorActive);
|
||||
Assert.Equal(0x10000212u, controller.TabPanel.ActivePageElementId); // Gameplay slot
|
||||
// OnShown() fired for the initial default tab -> Apply() -> AfterApply.
|
||||
Assert.Equal(1, gameplayFlushCount);
|
||||
Assert.Equal(0, gameplayFlushCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TabSwitch_RevertsLeavingPage_AndAppliesEnteringPage()
|
||||
public void TabSwitch_RevertsLeavingGameplayPage_AndAppliesEnteringCharacterPage()
|
||||
{
|
||||
ImportedLayout layout = FixtureLoader.LoadOptionsPanelHost();
|
||||
var calls = new List<string>();
|
||||
|
|
@ -86,12 +91,13 @@ public sealed class OptionsPanelControllerTests
|
|||
OptionsPanelController controller = OptionsPanelController.Bind(
|
||||
layout, MakeCallbacks(calls) with { AfterApply = () => flushes.Add("flush") })!;
|
||||
controller.ActivateTabs();
|
||||
flushes.Clear(); // drop the initial-activation flush
|
||||
flushes.Clear(); // drop the initial-activation flush (Gameplay never flushes anyway)
|
||||
|
||||
controller.TabPanel.SwitchTo(0x10000211u); // Character page slot
|
||||
|
||||
Assert.Equal(0x10000211u, controller.TabPanel.ActivePageElementId);
|
||||
// Both OnHidden (Gameplay, Reset — no flush) and OnShown (Character, Apply — flush).
|
||||
// OnHidden (Gameplay, Reset — no flush regardless) and OnShown
|
||||
// (Character, a REAL AfterApply-wired page — Apply flushes).
|
||||
Assert.Equal(["flush"], flushes);
|
||||
}
|
||||
|
||||
|
|
@ -116,12 +122,17 @@ public sealed class OptionsPanelControllerTests
|
|||
[Fact]
|
||||
public void WholeWindowShow_AppliesCurrentlyActivePage()
|
||||
{
|
||||
// Switches off the Gameplay default onto Character FIRST — Gameplay
|
||||
// never flushes (S1), so testing "OnShown applies the currently
|
||||
// active page" needs a REAL AfterApply-wired page active, exactly
|
||||
// like a returning user re-opening the window on a non-default tab.
|
||||
ImportedLayout layout = FixtureLoader.LoadOptionsPanelHost();
|
||||
var calls = new List<string>();
|
||||
int flushCount = 0;
|
||||
OptionsPanelController controller = OptionsPanelController.Bind(
|
||||
layout, MakeCallbacks(calls) with { AfterApply = () => flushCount++ })!;
|
||||
controller.ActivateTabs();
|
||||
controller.TabPanel.SwitchTo(0x10000211u); // Character page slot
|
||||
flushCount = 0;
|
||||
|
||||
controller.OnShown();
|
||||
|
|
@ -129,6 +140,24 @@ public sealed class OptionsPanelControllerTests
|
|||
Assert.Equal(1, flushCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GameplayPage_OnShownAndOnHidden_NeverFlush_EvenWhenControllerAfterApplyIsWired()
|
||||
{
|
||||
// Direct pin of S1's fix: cycling Gameplay's own OnShown/OnHidden
|
||||
// (the page-model hooks OnActivePageChanged drives) never publishes
|
||||
// a flush, independent of TabSwitch/ActivateTabs framing.
|
||||
ImportedLayout layout = FixtureLoader.LoadOptionsPanelHost();
|
||||
var calls = new List<string>();
|
||||
int flushCount = 0;
|
||||
OptionsPanelController controller = OptionsPanelController.Bind(
|
||||
layout, MakeCallbacks(calls) with { AfterApply = () => flushCount++ })!;
|
||||
|
||||
controller.GameplayPage.OnShown();
|
||||
controller.GameplayPage.OnHidden();
|
||||
|
||||
Assert.Equal(0, flushCount);
|
||||
}
|
||||
|
||||
// ── Close button ─────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue