fix(ui,runtime): OP4 review fixes — live re-seed, enable-gating, Combat panel re-point, universal timestamps

Both OP4 reviews converged on one headline bug (Character-tab rows never
re-read live server truth after their pre-login constructor-word seed) plus
overlapping MUST-FIXes. All ten converged/consolidated findings land here:

MUST-FIX:
- BoolOptionRow.SaveCurrentValue now re-reads its live binding (retail's
  GetValue()-into-SaveCurrentValue) on every OnShown — panel open, tab
  switch in, initial activation — instead of trusting the pre-login
  constructor word it was built with. Reset/tab-switch can now only
  restore values that were actually live at the last show. LockUI's
  host.Root.UiLocked one-shot mount seed now also converges on every
  PlayerDescription via the existing OnCharacterOptionsChanged hook.
- Apply/Reset are wired to OptionPage.OnOptionChanged in production
  (Ghosted when nothing changed, Normal when dirty, run once at bind so
  both start disabled per retail's PostInit); Defaults stays ungated.
- The Combat panel's three LEDs (Repeat Attacks/Auto Target/Keep in View)
  now read/write the same RuntimeCharacterOptionsState seam the Character
  tab uses instead of a disconnected client-local GameplaySettings copy —
  closes the "two writable copies" divergence. The three now-orphaned
  GameplaySettings fields and RuntimeSettingsController's mirror
  properties/SetCombatGameplay are deleted outright; the headless host's
  hardcoded AutoRepeatAttack/AutoTarget now read the live option bit.
- RuntimeSettingsController.SetUiLocked's convergence guard now compares
  against the last value actually applied to the runtime target instead
  of the persisted GameplaySettings.LockUI snapshot, which could already
  match a server-derived request without ever having been pushed.

SHOULD-FIX:
- DisplayTimeStamps now prefixes every chat producer (ChatLog.Append is
  the one seam all of them funnel through), not just AddText's own
  callers — heard speech, emotes, Turbine channels, and combat text were
  previously missed. The prefix format escapes its colons and forces
  InvariantCulture instead of the culture-dependent TimeSeparator
  placeholder.
- sky.frag now honors uFogParams.w (fog mode) like the mesh/terrain
  shaders, so Disable Distance Fog stops the sky dome's horizon band from
  blending toward fog color too.
- Corrected the "byte-verified" overclaim on the timestamp format string
  doc comment (BN-sourced, wire doc U6) and the AP-194 anchor-column
  class-name typo; the RunAsDefaultMovement doc comments now cite retail's
  actual acclient.h enumerator name.
- Added: DispatcherMovementInputSource's option x modifier truth table
  (incl. || AutoRunActive with the option off), the per-page Apply/Reset
  enable-gate tests, a real checkbox.OnClick/ToggleBehavior-driven click
  test, and hash-pins for the six header string keys.
- Gate script step 8 corrected for the logout-flush false-failure
  (closing the panel before relogging is load-bearing); a new step
  documents the enable-gate sequence and the Combat-panel/Character-tab
  cross-check.

Register: AP-196 (the Group-C default-source change + GameplaySettings
retirement) and AP-197 (the ignored per-character timestamp format
override) filed in this commit.

Full Release suite: 13,044 passed / 4 skipped / 0 failed (was 13,008/4/0;
net +36 tests from new coverage and legitimate assertion updates from the
GameplaySettings retirement).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-11 05:30:26 +02:00
parent 85afaae5fd
commit bc43fb1d1d
36 changed files with 923 additions and 220 deletions

View file

@ -23,12 +23,6 @@ public sealed class RuntimeSettingsControllerTests
VSync = false,
Quality = QualityPreset.Ultra,
},
GameplayValue = GameplaySettings.Default with
{
AutoTarget = true,
AutoRepeatAttack = true,
ViewCombatTarget = true,
},
};
var resolved = new QualitySettings(7, 18, 8, 16, true, 9);
int resolveCount = 0;
@ -57,9 +51,6 @@ public sealed class RuntimeSettingsControllerTests
Assert.Same(storage.DefaultCharacterValue, controller.Startup.Character);
Assert.Equal(resolved, controller.Startup.Quality);
Assert.Equal(resolved, controller.ResolvedQuality);
Assert.True(controller.AutoTarget);
Assert.True(controller.AutoRepeatAttack);
Assert.True(controller.ViewCombatTarget);
Assert.Equal("default", controller.ActiveToonKey);
}
@ -549,7 +540,7 @@ public sealed class RuntimeSettingsControllerTests
ParticleRange = ParticleRange.Retail,
});
viewModel.SetAudio(viewModel.AudioDraft with { Sfx = 0.33f });
viewModel.SetGameplay(viewModel.GameplayDraft with { AutoTarget = true });
viewModel.SetGameplay(viewModel.GameplayDraft with { ShowTooltips = false });
Assert.True(controller.HasDraftPreview);
Assert.Equal(91f, controller.DisplayPreview.FieldOfView);
@ -565,7 +556,7 @@ public sealed class RuntimeSettingsControllerTests
Assert.Equal(91f, controller.DisplayPreview.FieldOfView);
Assert.True(controller.Gameplay.LockUI);
Assert.True(controller.Gameplay.AcceptLootPermits);
Assert.True(viewModel.GameplayDraft.AutoTarget);
Assert.False(viewModel.GameplayDraft.ShowTooltips);
Assert.True(viewModel.GameplayDraft.LockUI);
Assert.True(viewModel.GameplayDraft.AcceptLootPermits);
Assert.Contains("target-ui-lock:True", events);
@ -590,12 +581,9 @@ public sealed class RuntimeSettingsControllerTests
CoordinatesOnRadar = false,
});
controller.SetCombatGameplay(controller.Gameplay with { AutoTarget = false });
controller.SetUiLocked(true);
controller.SetAcceptLootPermits(true);
Assert.False(controller.Gameplay.AutoTarget);
Assert.False(viewModel.GameplayDraft.AutoTarget);
Assert.False(viewModel.GameplayDraft.ShowTooltips);
Assert.False(viewModel.GameplayDraft.CoordinatesOnRadar);
Assert.True(viewModel.GameplayDraft.LockUI);
@ -609,14 +597,12 @@ public sealed class RuntimeSettingsControllerTests
Assert.Equal(
controller.Gameplay.CoordinatesOnRadar,
viewModel.GameplayDraft.CoordinatesOnRadar);
Assert.False(viewModel.GameplayDraft.AutoTarget);
Assert.True(viewModel.GameplayDraft.LockUI);
Assert.True(viewModel.GameplayDraft.AcceptLootPermits);
viewModel.SetGameplay(viewModel.GameplayDraft with { ShowHelm = false });
viewModel.Save();
Assert.False(storage.GameplayValue.AutoTarget);
Assert.True(storage.GameplayValue.LockUI);
Assert.True(storage.GameplayValue.AcceptLootPermits);
Assert.False(storage.GameplayValue.ShowHelm);
@ -638,26 +624,19 @@ public sealed class RuntimeSettingsControllerTests
static _ => { });
controller.SetUiLocked(true);
controller.SetCombatGameplay(controller.Gameplay with { AutoTarget = false });
Assert.Throws<IOException>(() => controller.SetAcceptLootPermits(true));
Assert.True(viewModel.GameplayDraft.LockUI);
Assert.False(viewModel.GameplayDraft.AutoTarget);
Assert.True(viewModel.GameplayDraft.AcceptLootPermits);
viewModel.Cancel();
Assert.Equal(GameplaySettings.Default.LockUI, viewModel.GameplayDraft.LockUI);
Assert.Equal(
GameplaySettings.Default.AutoTarget,
viewModel.GameplayDraft.AutoTarget);
Assert.Equal(
GameplaySettings.Default.AcceptLootPermits,
viewModel.GameplayDraft.AcceptLootPermits);
Assert.Contains(logs, line =>
line.Contains("radar lock save failed", StringComparison.Ordinal));
Assert.Contains(logs, line =>
line.Contains("combat option save failed", StringComparison.Ordinal));
}
[Fact]
@ -928,6 +907,42 @@ public sealed class RuntimeSettingsControllerTests
Assert.Equal(["target-quality"], events);
}
[Fact]
public void SetUiLocked_AppliesEvenWhenGameplayLockUIAlreadyMatches_IfNeverActuallyApplied()
{
// MUST-FIX 4 (OP4 review-fix round, 2026-08-11, blast M3): the
// guard used to compare the requested value against
// `Gameplay.LockUI` — valid only while `ToggleUiLock` derived
// `locked` AS `!Gameplay.LockUI`. OP4 re-pointed `ToggleUiLock` to
// read the SERVER bit (RuntimeCharacterOptionsState) instead, a
// DIFFERENT store that can already equal a value this controller
// never actually pushed to `_runtimeTargets`. A pre-existing save
// seeds Gameplay.LockUI = true; the runtime target has never seen
// `true` — the FIRST SetUiLocked(true) call must still apply.
var events = new List<string>();
var storage = new FakeStorage(events)
{
GameplayValue = GameplaySettings.Default with { LockUI = true },
};
var controller = new RuntimeSettingsController(
storage,
static preset => QualitySettings.From(preset),
static _ => { });
Assert.True(controller.Gameplay.LockUI); // already true, but never applied
var targets = new FakeRuntimeTargets(events);
controller.BindRuntimeTargets(targets);
controller.SetUiLocked(true);
Assert.Equal(1, targets.UiLockCalls);
// A second call with the SAME value now correctly no-ops — this
// time it really was applied.
controller.SetUiLocked(true);
Assert.Equal(1, targets.UiLockCalls);
}
[Fact]
public void UiLockTargetFailureCanRetryTheSameRequestedValue()
{