fix #430 #440: character panel — tooltips can mount, and rows refresh on the authoritative record
Some checks failed
CI / linux-portable (push) Failing after 3m25s
CI / windows-gate (push) Successful in 6m12s
CI / release (push) Has been skipped

Two defects the owner found at the CA5 drive, one shared theme: the data
was right and the presentation seam was dead.

#430 (tooltips): the TS-85 Batch-B port set runtime TooltipText on the
runtime-built attribute/vital/skill rows but never gave them a popup
locator, and RetailTooltipPresenter.OnTooltipShow refuses any widget with
AuthoredTooltipRootElementId == 0 — the tooltip could never mount, on any
row, ever. (The register's 'live-verified on the Character tab' was the
OPTIONS panel's Character tab — authored elements with authored locators;
a different surface.) Rows now carry the shared popup skin
0x10000395/0x21000041 — live-DAT probed as the ONLY locator pair the
character layout references, and the same inference UiItemSlot already
ships for runtime-built widgets. TS-85's row carries the dated correction.

#440 (train row stuck): training a skill debited credits on screen but
left the row in the untrained section until the NEXT click — because the
sheet-changed subscription only refreshed the captured sheet, and row
STRUCTURE rebuilt exclusively in click handlers (the raise 'completed'
callback runs after SEND, before the server answers; the owner's second
click was simply the first rebuild after the record landed, and ACE's
rejection of that second train — 'Failed to train', no credit change —
matches the owner's report exactly). The same gap kept CA4's
awaiting-ghost from visually releasing. CharacterStatController.Bind now
returns the data-changed refresh and MountCharacter invokes it on every
authoritative sheet change, mirroring retail's quality-change broadcast
(InfoRegion::OnQualityChanged @ 0x004F0EB0).

Pinned by DataChangedRefresh_MovesATrainedSkillToItsSection_WithoutAClick
and Rows_CarryTheSharedTooltipPopupLocatorAndDescriptionText. Owner
visual re-check owed next session (hover-dwell a row; train a skill and
watch it move immediately). Full hermetic suite 15,329 passed / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-24 16:51:58 +02:00
parent bd943849b1
commit bce17b3cfb
5 changed files with 177 additions and 7 deletions

View file

@ -247,7 +247,17 @@ public static class CharacterStatController
/// icon rendering is not asserted.
/// </para>
/// </summary>
public static void Bind(
/// <returns>
/// #431-CA5 gate fix (2026-08-24): the data-changed refresh. Retail's
/// panel refreshes rows from qualities on every authoritative
/// quality-change element message (InfoRegion::OnQualityChanged @
/// 0x004F0EB0 → the 0x10000004 broadcast); before this, rows only
/// rebuilt on CLICKS, so a trained skill stayed in the untrained
/// section (and CA4's awaiting-ghost never visually released) until the
/// next click. The caller invokes this from the sheet-changed
/// subscription.
/// </returns>
public static Action Bind(
ImportedLayout layout,
Func<CharacterSheet> data,
UiDatFont? datFont = null,
@ -601,6 +611,8 @@ public static class CharacterStatController
RefreshActiveRaiseButtons();
}
return () => RefreshAfterRaise(null);
}
private static UiScrollbar? PrepareSkillScrollbar(
@ -1502,6 +1514,16 @@ public static class CharacterStatController
{
var row = new UiClickablePanel
{
// #430: runtime-built rows author no P0x47/P0x48 of their own, and
// OnTooltipShow refuses a widget without a popup locator — so the
// Batch-B TooltipText never mounted. Use the shared popup skin,
// the ONLY locator pair the character layout (0x2100002E) itself
// references (live-DAT probed 2026-08-24) and the same inference
// UiItemSlot already ships for runtime-built widgets.
AuthoredTooltipRootElementId =
RetailTooltipPresenter.SharedPopupSkinRootElementId,
AuthoredTooltipLayoutDid =
RetailTooltipPresenter.SharedPopupSkinLayoutDid,
Left = left,
Top = top,
Width = width,

View file

@ -4017,9 +4017,7 @@ public sealed class RetailUiRuntime : IDisposable
}
CharacterSheetProvider provider = _bindings.Character.Provider;
CharacterSheet currentSheet = provider.BuildSheet();
_characterSheetSubscription = provider.SubscribeChanged(
() => currentSheet = provider.BuildSheet());
CharacterStatController.Bind(
Action refreshRows = CharacterStatController.Bind(
layout,
() => currentSheet,
_bindings.Assets.DefaultFont,
@ -4027,6 +4025,16 @@ public sealed class RetailUiRuntime : IDisposable
_bindings.Assets.ResolveSprite,
(request, completed) => HandleCharacterRaise(provider, request, completed),
() => CloseWindow(WindowNames.Character));
// #431-CA5 gate fix: rebuild the ROWS on every authoritative sheet
// change, not only on clicks — a train's skill record must move the
// row to the trained section (and un-ghost the raise controls) the
// moment it lands, mirroring retail's quality-change broadcast
// (InfoRegion::OnQualityChanged @ 0x004F0EB0).
_characterSheetSubscription = provider.SubscribeChanged(() =>
{
currentSheet = provider.BuildSheet();
refreshRows();
});
RetailWindowHandle handle = RetailWindowFrame.Mount(
Host.Root,
layout.Root,