fix(ui): OP6 rework — six range captions, un-invert Sound enabled flags, five font faces
Fixes all three MUST-FIX findings from the OP6 REJECT review (docs/research/2026-08-11-op6-review.md) plus its SHOULD-FIXes and NOTEs. M1 — the "retail ships zero range captions" claim was a Binary Ninja constant-folding artifact (the same class the header-string globals a few lines above already worked around). The six SetSliderLabel call sites byte-decode to reads of runtime-filled ID_Graphics_Value_* globals, not immediate zeros (PE-byte-verified against the PDB-paired acclient.exe, independently re-derived in this session, not just re-asserted from the review). ConfigOptionsPageController.BuildSliderRow gained optional rangeLowKey/rangeHighKey parameters wired for all six idx6 sliders (Camera Stiffness Soft/Hard, Adjustment Speed Slow/Fast, FOV Narrow/Wide, Screen Brightness Dark/Bright, Graphics Performance Speed/Detail, Degrade Distance Close/Far) via the same SetRangeLabel mechanism OP5's Chat opacity sliders already established. Mouse Look Sensitivity (idx3) correctly stays uncaptioned — the one genuine SetSliderLabel omission. Class doc corrected; gate-script lines 535/653-equivalent corrected in place. M2 — the three Sound "Disabled" toggles were semantically inverted: SoundManager::effect_sounds_enabled/ambient_sounds_enabled/ interface_sounds_enabled are all compiled = 1 in .data, and UserPreferences::RegisterPreference binds the checkbox's boolean value DIRECTLY onto those enabled-sense statics — checked-by-default means enabled-by-default, not disabled. AudioSettings.SfxDisabled/AmbientDisabled/ InterfaceDisabled renamed to SfxEnabled/AmbientEnabled/InterfaceEnabled (fresh JSON keys — the rejected slice's keys never shipped in an accepted build); RuntimeSettingsStartupTargets.ApplyAudio now computes effective volume through the extracted, independently-unit-tested pure function ComputeEffectiveCategoryVolumes (enabled ? slider : 0f). This closes the blast radius the review flagged: a missing key in an EXISTING settings.json now falls back to AudioSettings.Default, which is enabled=true, so a fresh launch is audible, not muted. AP-199's wording and gate-script step 6 corrected; the enshrined-inversion test rewritten to assert the correct default and a new SettingsStore test pins the legacy-file fallback path. M3 — UI_ChatFontFace now ships all five of retail's authored choices (Arial, CourierNew, PalatinoLinotype, Tahoma, TimesNewRoman — a fixed compile-time array at gmClient::InitUIPreferences, PE-byte-verified present verbatim in .rdata, not a per-machine runtime enumeration as the rejected slice's comment claimed). Default index 2 (PalatinoLinotype) now indexes a real entry. S1 — Bind() now emits the sixth trailing AddSeperator retail's own InitOptions ends with (0x0049E80D), matching retail's 39-item ListBox (6 headers + 6 separators + 27 option-widget-rows) instead of 38. S2 — Screen Brightness gets its own DisplaySettings.ScreenBrightness field ([-1,1], default 0) instead of overloading Gamma, which has a different unit system (default 1.0, legacy [0.5,2.0] slider) and its own live Settings-panel consumer. S3 — UiScrollbar and UiMenu gained a settable TooltipText surfaced through GetTooltipText (UiButton's existing pattern). Every slider and menu row's own interactive widget (not just toggle/trio rows) now carries retail's "<label>_Help" tooltip, verified as a universal suffix convention across every AttachPreference site touched by this tab. S4 — "800x600" added to DisplaySettings.AvailableResolutions: a genuine retail display mode (Device::ForceDisplayResolution(1,0x320,0x258) at startup) and the Config tab's own byte-verified Resolution row default, not an invented preset. Defaults now lands on a highlighted, re-selectable dropdown entry instead of an orphaned value. S5 — four new/extended tests: ComputeEffectiveCategoryVolumes gets a dedicated pure-function value assertion (Theory + a default-profile-is- audible Fact) in RuntimeSettingsControllerTests, closing the "only event order was asserted" gap that let M2 ship; a label/choice-key conformance table in ConfigOptionsPageControllerTests enumerates every key this tab queries (traced directly from the fixed code paths, not guessed) and fails on an invented OR a dropped key; a per-row DefaultValue pin asserts every row's default against the retail literal directly, independent of the underlying settings-record defaults; and the S1 separator fix gets its own 39-item stacked-ListBox count pin. NOTEs — AP-198's row count was always ten (its own enumeration never said nine); the commit-message inconsistency N1 flagged is reconciled in both the row and the section-summary line, and its Screen Brightness sub-clause now matches S2. N2: Bind() now reads the scrollbar id from UiTemplateListBox.ScrollbarElementId (dat property 0x72) instead of a hardcoded constant. N3 (batch Defaults writes) and N4 (AfterApply on Config-tab entry, needs no action) are left as recorded — out of this rework's scope per the review's own disposition. Full Release suite: 13,125 passed / 4 skipped / 0 failed (baseline 13,117/4/0 — net +8 tests added, 0 regressions, 0 removed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
4996974cfd
commit
472525b99e
11 changed files with 774 additions and 168 deletions
|
|
@ -136,16 +136,34 @@ internal sealed class RuntimeSettingsStartupTargets : IRuntimeSettingsStartupTar
|
|||
if (engine is not { IsAvailable: true })
|
||||
return;
|
||||
engine.MasterVolume = audio.Master;
|
||||
// Campaign OP slice OP6: the Config tab's toggle halves of the
|
||||
// Sound/Ambient volume trios (Sound_SoundDisabled/
|
||||
// Sound_AmbientSoundDisabled) gate the SAME slider value — retail's
|
||||
// UIOption_CheckboxSlider is one row over two preferences, and a
|
||||
// checked "Disable Sound Effects" LED mutes the category regardless
|
||||
// of what the slider itself is set to. No engine-level "disabled"
|
||||
// concept is needed: the effective volume sent IS zero when
|
||||
// disabled, exactly as if the user dragged the slider to zero.
|
||||
engine.SfxVolume = audio.SfxDisabled ? 0f : audio.Sfx;
|
||||
engine.AmbientVolume = audio.AmbientDisabled ? 0f : audio.Ambient;
|
||||
(float sfx, float ambient) = ComputeEffectiveCategoryVolumes(audio);
|
||||
engine.SfxVolume = sfx;
|
||||
engine.AmbientVolume = ambient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign OP slice OP6 (2026-08-11), CORRECTED at the OP6 rework round
|
||||
/// (review <c>docs/research/2026-08-11-op6-review.md</c> finding M2): the
|
||||
/// Config tab's toggle halves of the Sound/Ambient volume trios
|
||||
/// (<see cref="AudioSettings.SfxEnabled"/>/<see cref="AudioSettings.AmbientEnabled"/>
|
||||
/// — retail's own ENABLED-sense <c>SoundManager::effect_sounds_enabled</c>/
|
||||
/// <c>ambient_sounds_enabled</c> statics, see <see cref="AudioSettings"/>'s
|
||||
/// class doc for the byte evidence) gate the SAME slider value: the
|
||||
/// effective volume sent to the engine is the slider value when enabled,
|
||||
/// zero when not — exactly as if the user dragged the slider to zero.
|
||||
/// Extracted as a pure function (no <see cref="OpenAlAudioEngine"/>
|
||||
/// dependency) so the mapping itself — not just that some target was
|
||||
/// called — is unit-testable without OpenAL hardware/mocking (S5: the
|
||||
/// rejected slice's only audio test asserted event ORDER, never the
|
||||
/// VALUE that reached the engine, which is exactly how M2's muted-by-
|
||||
/// default inversion shipped unnoticed).
|
||||
/// </summary>
|
||||
internal static (float Sfx, float Ambient) ComputeEffectiveCategoryVolumes(AudioSettings audio)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(audio);
|
||||
float sfx = audio.SfxEnabled ? audio.Sfx : 0f;
|
||||
float ambient = audio.AmbientEnabled ? audio.Ambient : 0f;
|
||||
return (sfx, ambient);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue