fix #380: Chat tab opacity sliders were missing their retail row captions
Root cause: PlayerOptionPage::AddSliderOption never sets a row's own name-label text — the "Inactive Opacity"/"Active Opacity" caption comes from a SEPARATE DAT-resident runtime catalog (DID 0x78000000, resolved via the same two-level DBCache::GetDIDFromEnumStatic master-map/submap lookup ChatOptionsDatDefaults already uses for enum 0x16/category 2, here for enum 0x15/category 2) that nothing in the codebase ever queried, so both slider rows rendered with no caption at all. Fix: new ChatOptionsDatCaptions.TryRead resolves the DID-0x78000000 catalog's per-property name/tooltip entries (matched by the same owning-property enum ChatOptionsDatDefaults already keys its defaults by) and ChatOptionsPageController.BuildOpacitySliders stamps each slider's own row caption/tooltip from it — falling back to no text (never invented English) if resolution fails. Regressed by ChatOptionsPageControllerTests. Bind_WiresEachSlidersOwnRowCaption_FromTheResolvedDatCatalog and the companion Bind_MissingCaption_RendersNoText_NeverInventsEnglish case, plus a live-mount probe (OptionsPanelLiveMountProbeTests. ProbeChatOpacityCaptions) confirming the production TryRead call resolves "Inactive Opacity"/"Active Opacity" against the real DAT. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
c0b3d8f233
commit
2a248c0d48
8 changed files with 415 additions and 10 deletions
|
|
@ -152,6 +152,13 @@ public sealed class ChatOptionsPageControllerTests
|
|||
public float ActiveOpacity = 1.0f;
|
||||
public float DefaultOpacityDatDefault = 0.5f;
|
||||
public float ActiveOpacityDatDefault = 1.0f;
|
||||
// #380: realistic test defaults matching the live-DAT-verified
|
||||
// strings (docs/research live-mount probe, 2026-08-11) so existing
|
||||
// tests exercise the same shape production sees.
|
||||
public ChatOptionsDatCaptions.Caption DefaultOpacityCaption =
|
||||
new("Inactive Opacity", "Adjusts the opacity of the chat window when it is inactive");
|
||||
public ChatOptionsDatCaptions.Caption ActiveOpacityCaption =
|
||||
new("Active Opacity", "Adjusts the opacity of the chat window when it is active");
|
||||
public List<float> DefaultOpacitySets { get; } = new();
|
||||
public List<float> ActiveOpacitySets { get; } = new();
|
||||
public int OpacityFlushes { get; private set; }
|
||||
|
|
@ -182,6 +189,8 @@ public sealed class ChatOptionsPageControllerTests
|
|||
FlushOpacity: () => OpacityFlushes++,
|
||||
DefaultOpacityDatDefault: DefaultOpacityDatDefault,
|
||||
ActiveOpacityDatDefault: ActiveOpacityDatDefault,
|
||||
DefaultOpacityCaption: DefaultOpacityCaption,
|
||||
ActiveOpacityCaption: ActiveOpacityCaption,
|
||||
CurrentFilter: windowId => Filters[windowId],
|
||||
SetFilter: (windowId, value) =>
|
||||
{
|
||||
|
|
@ -388,6 +397,111 @@ public sealed class ChatOptionsPageControllerTests
|
|||
Assert.Empty(fakeBindings.ActiveOpacitySets);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #380 regression (2026-08-11, gate 4): before this fix, NOTHING wired
|
||||
/// element <c>0x1000021B</c> on either slider row, so both rows rendered
|
||||
/// with no caption at all — the user's literal complaint ("I also miss
|
||||
/// the text next to the bars for Inactive and Active Opacity"). Drives
|
||||
/// the row-caption text down from <see cref="ChatOptionsPageController.Bindings.DefaultOpacityCaption"/>/
|
||||
/// <c>ActiveOpacityCaption</c> the same way production resolves it
|
||||
/// (<see cref="ChatOptionsDatCaptions"/> against the live DAT) and reads
|
||||
/// back the ACTUAL built <see cref="UiText"/> next to each slider — not
|
||||
/// just that the field was set somewhere, but that IT rendered on the
|
||||
/// correct row (Default → row 1's own caption, Active → row 2's own,
|
||||
/// never swapped or bled onto the wrong slider).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Bind_WiresEachSlidersOwnRowCaption_FromTheResolvedDatCatalog()
|
||||
{
|
||||
var fakeBindings = new FakeBindings
|
||||
{
|
||||
DefaultOpacityCaption = new ChatOptionsDatCaptions.Caption("Inactive Opacity", "inactive tip"),
|
||||
ActiveOpacityCaption = new ChatOptionsDatCaptions.Caption("Active Opacity", "active tip"),
|
||||
};
|
||||
ImportedLayout layout = FixtureLoader.LoadOptionsPanelHost();
|
||||
OptionsPanelController controller = OptionsPanelController.Bind(
|
||||
layout,
|
||||
new OptionsPanelController.Callbacks(
|
||||
Toggle: () => { },
|
||||
RequestExitToCharacterSelection: () => { },
|
||||
ExitGame: () => { },
|
||||
UseMouseTurningSettings: () => { },
|
||||
DisplaySystemMessage: _ => { }))!;
|
||||
bool bound = ChatOptionsPageController.Bind(
|
||||
layout, controller.ChatPage, MakeTemplateResolver(), (_, _) => null,
|
||||
fakeBindings.ToBindings());
|
||||
Assert.True(bound);
|
||||
|
||||
var listBox = Assert.IsType<UiTemplateListBox>(
|
||||
layout.FindElement(ChatOptionsPageController.ListBoxElementId));
|
||||
List<UiScrollbar> sliders = CollectScalarSliders(listBox);
|
||||
Assert.Equal(2, sliders.Count); // build order: [0]=Default, [1]=Active
|
||||
|
||||
UiElement defaultRowRoot = FindRowRoot(sliders[0]);
|
||||
UiElement activeRowRoot = FindRowRoot(sliders[1]);
|
||||
|
||||
var defaultCaption = Assert.IsType<UiText>(
|
||||
UiElement.FindDescendant(defaultRowRoot, 0x1000021Bu));
|
||||
var activeCaption = Assert.IsType<UiText>(
|
||||
UiElement.FindDescendant(activeRowRoot, 0x1000021Bu));
|
||||
|
||||
Assert.Equal("Inactive Opacity", Assert.Single(defaultCaption.LinesProvider()).Text);
|
||||
Assert.Equal("Active Opacity", Assert.Single(activeCaption.LinesProvider()).Text);
|
||||
|
||||
// The tooltip attaches to the interactive slider itself, per-row.
|
||||
Assert.Equal("inactive tip", sliders[0].TooltipText);
|
||||
Assert.Equal("active tip", sliders[1].TooltipText);
|
||||
}
|
||||
|
||||
/// <summary>A DAT read failure (or a missing catalog entry) must never
|
||||
/// invent English — the row renders with NO caption, exactly like every
|
||||
/// other resolve-miss path already established in this controller
|
||||
/// (filter labels, section headers, range captions).</summary>
|
||||
[Fact]
|
||||
public void Bind_MissingCaption_RendersNoText_NeverInventsEnglish()
|
||||
{
|
||||
var fakeBindings = new FakeBindings
|
||||
{
|
||||
DefaultOpacityCaption = default, // both Name/Tooltip null
|
||||
ActiveOpacityCaption = default,
|
||||
};
|
||||
ImportedLayout layout = FixtureLoader.LoadOptionsPanelHost();
|
||||
OptionsPanelController controller = OptionsPanelController.Bind(
|
||||
layout,
|
||||
new OptionsPanelController.Callbacks(
|
||||
Toggle: () => { },
|
||||
RequestExitToCharacterSelection: () => { },
|
||||
ExitGame: () => { },
|
||||
UseMouseTurningSettings: () => { },
|
||||
DisplaySystemMessage: _ => { }))!;
|
||||
bool bound = ChatOptionsPageController.Bind(
|
||||
layout, controller.ChatPage, MakeTemplateResolver(), (_, _) => null,
|
||||
fakeBindings.ToBindings());
|
||||
Assert.True(bound);
|
||||
|
||||
var listBox = Assert.IsType<UiTemplateListBox>(
|
||||
layout.FindElement(ChatOptionsPageController.ListBoxElementId));
|
||||
List<UiScrollbar> sliders = CollectScalarSliders(listBox);
|
||||
UiElement defaultRowRoot = FindRowRoot(sliders[0]);
|
||||
|
||||
var defaultCaption = Assert.IsType<UiText>(
|
||||
UiElement.FindDescendant(defaultRowRoot, 0x1000021Bu));
|
||||
Assert.Empty(defaultCaption.LinesProvider());
|
||||
Assert.Null(sliders[0].TooltipText);
|
||||
}
|
||||
|
||||
/// <summary>Walks up from a built slider leaf to the row root
|
||||
/// <see cref="UiTemplateListBox.AddPrebuiltRow"/> stacked directly under
|
||||
/// the viewport — the same subtree <see cref="ChatOptionsPageController.BuildOpacitySliders"/>
|
||||
/// itself scopes its <c>FindDescendant</c> caption lookup to.</summary>
|
||||
private static UiElement FindRowRoot(UiElement leaf)
|
||||
{
|
||||
UiElement node = leaf;
|
||||
while (node.Parent is { } parent && parent is not UiScrollablePanel)
|
||||
node = parent;
|
||||
return node;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DraggingDefaultSlider_AppliesLive_AndDragsActiveUp_NeverClamping()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue