feat(ui): vitals — Side By Side Vitals swaps retail's two vitals windows
Port of retail's SideBySideVitals character option, derived end-to-end: retail authors TWO complete vitals windows and swaps their VISIBILITY on the option bit — nothing is rearranged in place. - gmFloatySideVitalsUI (0x10000056, Register @0x004D0490) is a second full vitals window from LayoutDesc 0x21000075: 460x26, the same three meters (0x100000E6/EC/EE), cur/max labels (0x100000EB/ED/EF), and detail-icon overlays authored id-for-id with the stacked window — so the same VitalsController.Bind and the inherited UiVitalsRoot click toggle apply unchanged. Authored constraints ride the root's 0x3C..0x3F (fixed 26 height, width 360..3000) through DatConstraintSource. - Visibility ownership: gmFloatyVitalsUI::UpdateFromPlayerModule @0x004CF140 shows the stacked window iff PlayerModule::SideBySideVitals == 0; gmFloatySideVitalsUI::UpdateFromPlayerModule @0x004D0810 shows the side row iff set; gmGamePlayUI::RecvNotice_PlayerOptionChanged @0x004E9DA0 flips both live on option id 0x13. - The bit: PlayerModule::SideBySideVitals @0x005D3070 = (options_ >> 0x15) & 1 — CharacterOptions1 0x00200000, ACE-confirmed; CharacterOptionTable already carried the exact row (PlayerModule-blob group, not a 0x0005 auto-save id). acdream shape: MountSideVitals mounts the second window hidden; VitalsSideBySideController polls the borrowed J4 option bit once per frame from RetailUiRuntime.Tick and applies BOTH windows' visibility on the edge — covering the mount default, the PlayerModule blob arriving after mount, and the Character tab's live checkbox with one mechanism. Both window names join stateManagedVisibilityWindows so the saved layout never restores a visibility the option owns. The Character tab's SideBySideVitals row un-dims (StoreOnly → Live) with a real reader — 33 dimmed / 17 live. 4 new controller tests (initial apply both directions, live edge swap both directions, steady-bit non-reassertion). App suite Release live-DAT 5499 passed / 3 skips; Runtime 1744/0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
306a1670d3
commit
db8fa328dc
6 changed files with 300 additions and 22 deletions
|
|
@ -111,7 +111,7 @@ public static class CharacterOptionsPageController
|
|||
/// steps 13-18, written after the code landed):
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <item><description><b>NOT dimmed (16 rows, real acdream-side
|
||||
/// <item><description><b>NOT dimmed (17 rows, real acdream-side
|
||||
/// consumer):</b> Group B rows actually bound —
|
||||
/// <c>ViewCombatTarget</c>/<c>AutoTarget</c>/<c>AutoRepeatAttack</c>
|
||||
/// (combat: <c>LiveCombatAttackOperations.cs</c>,
|
||||
|
|
@ -124,15 +124,20 @@ public static class CharacterOptionsPageController
|
|||
/// <c>DragItemOnPlayerOpensSecureTrade</c> (TS-48,
|
||||
/// <c>InteractionRetainedUiComposition.cs:326</c>), all six
|
||||
/// <c>ListenTo*Chat</c> ids (<c>TurbineChatMembershipGate.cs:105-136</c>
|
||||
/// gates every Turbine room join on the matching bit), and — landed at
|
||||
/// gates every Turbine room join on the matching bit), — landed at
|
||||
/// Campaign FA slice FA4, D7, SURVIVING the fix-round correction below
|
||||
/// — <c>FellowshipShareXP</c> (the Create-flow click genuinely reads
|
||||
/// it as the sent <c>shareXP</c> bit, <c>SocialFellowshipPageController.WireButtons</c>).</description></item>
|
||||
/// <item><description><b>Dimmed (34 rows, store-only):</b> every
|
||||
/// it as the sent <c>shareXP</c> bit, <c>SocialFellowshipPageController.WireButtons</c>),
|
||||
/// and — the vitals retail-modes round, 2026-08-17 —
|
||||
/// <c>SideBySideVitals</c> (<c>VitalsSideBySideController</c> swaps the
|
||||
/// stacked 0x2100006C / side-by-side 0x21000075 vitals windows on the
|
||||
/// bit, retail's <c>gmGamePlayUI::RecvNotice_PlayerOptionChanged
|
||||
/// @0x004E9DA0</c>).</description></item>
|
||||
/// <item><description><b>Dimmed (33 rows, store-only):</b> every
|
||||
/// remaining Group A row (wire+store only — ACE, not acdream, is the
|
||||
/// consumer) and every remaining Group B row the OP4 gate script's own
|
||||
/// step 16 lists as "no consumer surface" (<c>ShowTooltips</c>,
|
||||
/// <c>SideBySideVitals</c>, <c>SpellDuration</c>,
|
||||
/// <c>SpellDuration</c>,
|
||||
/// <c>AdvancedCombatUI</c>, <c>StayInChatMode</c>,
|
||||
/// <c>DisableMostWeatherEffects</c>, <c>PersistentAtDay</c>,
|
||||
/// <c>FilterLanguage</c>, <c>MainPackPreferred</c>), plus every Group D
|
||||
|
|
@ -181,9 +186,9 @@ public static class CharacterOptionsPageController
|
|||
/// </list>
|
||||
/// <para>
|
||||
/// Net: 35 (pre-FA4) → FA4 shipped 31 → fix round reverts three of the
|
||||
/// four un-dims (Ignore, AutoAccept, ShareLoot) → <b>34 of 50 dimmed /
|
||||
/// 16 live</b>, ONE net un-dim from pre-FA4 baseline
|
||||
/// (<c>FellowshipShareXP</c> only).
|
||||
/// four un-dims (Ignore, AutoAccept, ShareLoot) → 34 dimmed / 16 live →
|
||||
/// the vitals retail-modes round (2026-08-17) un-dims
|
||||
/// <c>SideBySideVitals</c> → <b>33 of 50 dimmed / 17 live</b>.
|
||||
/// </para></description></item>
|
||||
/// <item><description><b>Flagged ambiguity, resolved by code evidence
|
||||
/// (see final report, not re-litigated here):</b> the research doc's
|
||||
|
|
@ -226,7 +231,7 @@ public static class CharacterOptionsPageController
|
|||
new(CharacterOptionId.VividTargetingIndicator, "VividTargetingIndicator", Live), // Group C
|
||||
new(CharacterOptionId.ShowTooltips, "ShowTooltips", StoreOnly), // Group B, unbound
|
||||
new(CharacterOptionId.CoordinatesOnRadar, "CoordinatesOnRadar", Live), // Group C
|
||||
new(CharacterOptionId.SideBySideVitals, "SideBySideVitals", StoreOnly), // Group B, unbound
|
||||
new(CharacterOptionId.SideBySideVitals, "SideBySideVitals", Live), // vitals retail-modes round (2026-08-17): consumed by VitalsSideBySideController — the bit swaps the stacked (0x2100006C) and side-by-side (0x21000075) vitals windows live, retail's gmGamePlayUI::RecvNotice_PlayerOptionChanged @0x004E9DA0
|
||||
new(CharacterOptionId.SpellDuration, "SpellDuration", StoreOnly), // Group B, unbound
|
||||
new(CharacterOptionId.DisableMostWeatherEffects, "DisableMostWeatherEffects", StoreOnly), // Group B, unbound
|
||||
new(CharacterOptionId.DisableDistanceFog, "DisableDistanceFog", Live), // Group B, bound (GameWindow.cs:657)
|
||||
|
|
|
|||
84
src/AcDream.App/UI/Layout/VitalsSideBySideController.cs
Normal file
84
src/AcDream.App/UI/Layout/VitalsSideBySideController.cs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
|
||||
namespace AcDream.App.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// Owns which of the two vitals windows is visible — retail's
|
||||
/// "Side By Side Vitals" character option (Options → Character tab).
|
||||
///
|
||||
/// <para>
|
||||
/// Retail authors TWO complete vitals windows and swaps their visibility on
|
||||
/// the option bit; nothing is rearranged in place:
|
||||
/// </para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>gmFloatyVitalsUI</c> (LayoutDesc 0x2100006C, the
|
||||
/// stacked 160x58 window): <c>UpdateFromPlayerModule @0x004CF140</c> —
|
||||
/// visible iff <c>PlayerModule::SideBySideVitals() == 0</c>.</description></item>
|
||||
/// <item><description><c>gmFloatySideVitalsUI</c> (LayoutDesc 0x21000075, the
|
||||
/// 460x26 single-row window): <c>UpdateFromPlayerModule @0x004D0810</c> —
|
||||
/// visible iff the bit is set.</description></item>
|
||||
/// <item><description>Live apply: <c>gmGamePlayUI::RecvNotice_PlayerOptionChanged
|
||||
/// @0x004E9DA0</c> flips both windows the moment the
|
||||
/// <c>SideBySideVitals_PlayerOption</c> (0x13) changes; both
|
||||
/// <c>UpdateFromPlayerModule</c> bodies re-apply on PlayerDescReceived.</description></item>
|
||||
/// <item><description>The bit is <c>PlayerModule::SideBySideVitals @0x005D3070</c>
|
||||
/// = <c>(options_ >> 0x15) & 1</c> — CharacterOptions1 0x00200000,
|
||||
/// option id 0x13, riding the 0x01A1 PlayerModule blob (not a 0x0005
|
||||
/// auto-save id) — <c>CharacterOptionTable</c> row confirmed against ACE.</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>
|
||||
/// acdream shape: the option lives in the J4 Runtime owner
|
||||
/// (<c>RuntimeCharacterState.Options</c>); this controller polls the borrowed
|
||||
/// bit once per frame from <c>RetailUiRuntime.Tick</c> (the OP campaign's
|
||||
/// established live-apply seam) and applies BOTH windows' visibility on the
|
||||
/// edge — covering the mount-time default, the PlayerModule blob arriving
|
||||
/// after mount, and the Character tab's live toggle with one mechanism.
|
||||
/// Both window names ride <c>stateManagedVisibilityWindows</c> so the saved
|
||||
/// window layout never restores a visibility this option owns.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class VitalsSideBySideController
|
||||
{
|
||||
private readonly UiRoot _root;
|
||||
private readonly Func<bool> _sideBySideVitals;
|
||||
private readonly string _stackedWindow;
|
||||
private readonly string _sideWindow;
|
||||
private bool? _applied;
|
||||
|
||||
public VitalsSideBySideController(
|
||||
UiRoot root,
|
||||
Func<bool> sideBySideVitals,
|
||||
string stackedWindow,
|
||||
string sideWindow)
|
||||
{
|
||||
_root = root ?? throw new ArgumentNullException(nameof(root));
|
||||
_sideBySideVitals = sideBySideVitals
|
||||
?? throw new ArgumentNullException(nameof(sideBySideVitals));
|
||||
_stackedWindow = stackedWindow;
|
||||
_sideWindow = sideWindow;
|
||||
}
|
||||
|
||||
/// <summary>The last applied bit (null before the first apply). Exposed for tests.</summary>
|
||||
public bool? Applied => _applied;
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
bool sideBySide = _sideBySideVitals();
|
||||
if (_applied == sideBySide) return;
|
||||
_applied = sideBySide;
|
||||
|
||||
// Retail's exact swap (RecvNotice_PlayerOptionChanged @0x004E9DBE):
|
||||
// option ON → stacked hidden, side shown; OFF → the opposite.
|
||||
if (sideBySide)
|
||||
{
|
||||
_root.HideWindow(_stackedWindow);
|
||||
_root.ShowWindow(_sideWindow);
|
||||
}
|
||||
else
|
||||
{
|
||||
_root.ShowWindow(_stackedWindow);
|
||||
_root.HideWindow(_sideWindow);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue