fix #416 #415: the retail button state/media machine — roster hover highlight clears; probe wait verbs bind without an artifact dir

#416 (char-select roster highlight never cleared on hover-leave): three
decomp-grounded mechanisms replace the media-keyed _availableStates
approximation.
- UIElement_Button::UpdateState_ @0x00471CF0: the button machine commits
  ONLY states authored on the button's OWN ElementDesc (AccessStateDesc
  gate); unauthored requests no-op, preserving custom semantic states.
- UIElement::SetState @0x00464E70: an unauthored state id is coerced to
  state 0 (the unnamed base state) and committed — ported into
  UiDatElement.TrySetRetailState with the base-descriptor PassToChildren
  cascade arm.
- The SetState media rule @0x004651c0: a committed state replaces the
  playing media ONLY when its media array is non-empty. UiButton now keeps
  per-face-segment media states under that rule (segments model retail's
  PassToChildren children), and LayoutImporter records the raw MediaCount
  including the File=0 draw-nothing images the drawable filter drops —
  the roster bar children's base state is exactly such an image, and it is
  what clears the bar.
The row template truth (probe, installed DAT): the row authors EMPTY
Normal/rollover/Highlight descriptors with PassToChildren; the three bar
children author rollover/Highlight media, NO Normal state, and a File=0
base image. An empty-media Normal_pressed still never blanks a Normal-art
button (the media rule keeps the previous art — the exact behavior the
old gate approximated), and the Appearance spins' property-only Highlight
now genuinely commits: label recolors, arrow art lingers — the retail
split AP-222 approximated with a requested-keyed label hack, now retired.
Live-verified at char select: hover +alex shows the grey bar, moving off
clears it, the selected row keeps its amber bar.

#415 (probe wait world-* verbs dead): the filed snapshot-reset diagnosis
was wrong — the automation bridge simply never bound without
ACDREAM_AUTOMATION_ARTIFACT_DIR. A facts-only
WorldRevealFactsAutomationRuntime now binds whenever the retained UI
exists; checkpoint/screenshot verbs still require the artifact directory
and now report that instead of a generic timeout.

App tests 5568/3 skips, Runtime 1756/0, UI.Abstractions 926/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-17 16:25:36 +02:00
parent 7aa08045d8
commit 91c1962b0d
11 changed files with 469 additions and 86 deletions

View file

@ -164,6 +164,61 @@ internal sealed class WorldLifecycleCheckpointRequest :
}
}
/// <summary>
/// #415: the facts-only automation runtime, bound whenever the full
/// <see cref="WorldLifecycleAutomationController"/> is NOT composed (no
/// <c>ACDREAM_AUTOMATION_ARTIFACT_DIR</c>). The probe script's
/// <c>wait world-ready/world-visible/materialized</c> verbs need only the
/// reveal snapshot — which exists in every launch — yet before this class
/// they silently read <c>false</c> forever through the unbound deferred
/// wrapper, timing out even while the <c>[world-reveal]</c> stream showed
/// the awaited edge. Checkpoint/screenshot verbs still require the artifact
/// directory and now say so instead of failing generically.
/// </summary>
internal sealed class WorldRevealFactsAutomationRuntime
: IRetailUiAutomationRuntime
{
private readonly Func<RuntimePortalSnapshot> _getReveal;
private readonly Func<int> _getPortalMaterializationCount;
public WorldRevealFactsAutomationRuntime(
Func<RuntimePortalSnapshot> getReveal,
Func<int> getPortalMaterializationCount)
{
_getReveal = getReveal
?? throw new ArgumentNullException(nameof(getReveal));
_getPortalMaterializationCount = getPortalMaterializationCount
?? throw new ArgumentNullException(
nameof(getPortalMaterializationCount));
}
public bool IsWorldReady => _getReveal().IsReady;
public bool IsWorldViewportVisible => _getReveal().WorldViewportObserved;
public int PortalMaterializationCount => _getPortalMaterializationCount();
public bool TryRequestCheckpoint(
string name,
out IRetailUiAutomationCheckpoint? checkpoint,
out string error)
{
checkpoint = null;
error = "checkpoints require ACDREAM_AUTOMATION_ARTIFACT_DIR";
return false;
}
public void CancelCheckpoint(IRetailUiAutomationCheckpoint checkpoint)
{
}
public bool TryRequestScreenshot(string name, out string error)
{
error = "screenshots require ACDREAM_AUTOMATION_ARTIFACT_DIR";
return false;
}
public bool IsScreenshotComplete(string name) => false;
}
/// <summary>
/// Diagnostic-only runtime seam used by production retained-UI scripts. It
/// writes one structured checkpoint at explicit script edges and delegates GL