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:
parent
85afaae5fd
commit
bc43fb1d1d
36 changed files with 923 additions and 220 deletions
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue