fix(ui,runtime): OP4 re-review residuals R1-R4 (coordinator pass) — OP4 CLOSED

R1: the timestamp prefix moves from ChatLog.Append (which stamped the
stored BODY, rendering 'Alice says, "13:05:09 hi"') to ChatVM's display
composition — FormatTimestampPrefix(entry.Received) prepends the COMPOSED
line, matching retail's separate-leading-string model (fprintf("%ls%ls",
ts, text) @0x00563e5b; AddTextToScroll receives composed lines). The
prefix renders entry.Received in LOCAL time (retail strftime), invariant
literal colons. The ten defect-pinning test cases across
ChatLogTests/RuntimeCommunicationStateTests are rewritten to pin the
corrected contract (stored bodies stay clean; the composed line carries
the stamp outside the quotes — ChatVMTests).

R2: open option-bearing panels converge on every PlayerDescription seed:
OptionPage.ReloadFromLive (per-row live re-read + gating re-eval, NO
AfterApply flush — the seed just cleared the dirty module),
OptionsPanelController.OnServerOptionsSeeded (active page),
CombatUiController.OnServerOptionsSeeded (SyncControls), wired through
RuntimeSettingsController.ServerOptionsSeeded from the same factory hook
LockUI already uses. Retail cannot reach this state (its panels close
across login); the adaptation exists because retained panels survive the
session boundary — documented at the seam.

R3: tests drive the refresh widget push (model AND checkbox converge) and
ReloadFromLive's no-flush contract. R4: AP-196 addendum names the
headless AutoRepeatAttack false->true effective-default flip and the
characterOptions escape hatch.

Full Release suite: 13,083 passed / 4 skipped / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-11 06:36:14 +02:00
parent e71e5a9614
commit ac0304dcf0
13 changed files with 216 additions and 114 deletions

View file

@ -200,6 +200,12 @@ public sealed class CombatUiController : IRetainedPanelController
private void OnAttackStateChanged() => SyncControls();
/// <summary>OP4 re-review R2: a fresh PlayerDescription seed landed —
/// the three option LEDs re-read the live bits so an OPEN combat panel
/// converges with the Character tab instead of waiting for the next
/// show/mode/attack-state change.</summary>
public void OnServerOptionsSeeded() => SyncControls();
private void SyncControls()
{
_powerControl.SetScalarPosition(_attacks.DesiredPower);

View file

@ -494,6 +494,25 @@ public sealed class OptionPage
/// (re)opening): applies + commits, same as <see cref="Apply"/>.</summary>
public void OnShown() => Apply();
/// <summary>
/// OP4 re-review R2 (2026-08-11): a fresh <c>PlayerDescription</c> seed
/// replaced the live option words while this page may be VISIBLE —
/// re-read every row's (current, saved) from the live source WITHOUT
/// <see cref="Apply"/>'s flush (the seed just cleared the dirty module;
/// there is nothing to flush, and an <see cref="AfterApply"/> publication
/// here would be spurious). Retail cannot reach this state — its panels
/// are closed during login/reconnect — so this adaptation exists only
/// because acdream's retained panels survive the session boundary; the
/// stale (current, saved) it clears would otherwise let Reset restore
/// pre-reconnect values over the new character's server truth.
/// </summary>
public void ReloadFromLive()
{
foreach (IOptionRow row in _rows)
row.SaveCurrentValue();
OnOptionChanged?.Invoke();
}
/// <summary><c>PlayerOptionPage::OnVisibilityChanged(false)</c> — the page
/// became hidden (a tab switch away, or the window closing): reverts
/// uncommitted edits, same as <see cref="Reset"/>.</summary>

View file

@ -310,6 +310,16 @@ public sealed class OptionsPanelController : IRetainedPanelController
page.OnShown();
}
/// <summary>OP4 re-review R2: a fresh PlayerDescription seed landed —
/// re-read the ACTIVE page's rows from live state (flush-free; see
/// <see cref="OptionPage.ReloadFromLive"/>). Hidden pages re-read on
/// their next <see cref="OptionPage.OnShown"/> as always.</summary>
public void OnServerOptionsSeeded()
{
if (_pages.TryGetValue(_tabPanel.ActivePageElementId, out OptionPage? page))
page.ReloadFromLive();
}
public void Dispose()
{
if (_disposed) return;