Closes the mechanism lens's REOPEN (one MUST-FIX) and both lenses' small
residuals on the OP2 rework (b236a442); the blast lens re-review was
CLOSED outright. Fable-direct per the two-failure escalation rule.
- AP-195 filed: UIOption_CheckboxBitfield64 ports HALF of Refresh
@0x004859C0 — the ANY-set checkbox predicate is exact, but the ALL-set
LED media swap (P0x10000082=0x06004D17 / P0x10000083=0x06004D19) and
the ListBox self-sizing tail (ResizeTo/CalculatePaperSize — the block
IS a UIElement_ListBox in retail) are unported, and the block's row
stacking is a second divergent implementation beside UiTemplateListBox.
All due at OP5 before the Chat tab's connected gate; the IsSet doc
comment now names both halves instead of quoting only the ported one.
- Row tooltips: UiButton gains settable TooltipText surfaced through the
shared GetTooltipText hover pipeline (UiCatalogSlot's pattern);
UiCheckboxBitfield64.AddChild applies the row tooltip retail stamps in
CreateChildren @0x00485DF0, and documents that the 0x10000084 row-index
attribute stamp is deliberately replaced by the typed mask closure.
- AD-73 addendum: UiTemplateListBox.ConsumesDatChildren=true is inert
only while no authored Type-5 element carries children — that premise
is now conformance-PINNED across all 32 fixtures (a future DAT
regeneration surfacing an authored child fails the build instead of
silently dropping it).
- Plan doc: OP2's contract names UiTabPanel.cs (retail UIElement_Panel),
not the fictional-class-named UiTabControl.cs; ledger records OP1 and
OP2 both CLOSED.
Full Release suite: 12,871 passed / 4 skipped / 0 failed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
27 KiB
OP2 rework re-review — regression / blast-radius lens
Rework under review: b236a442 "fix(ui): OP2 rework — dormant UiDatElement
subclasses, fixed Panel/CheckboxBitfield64 mechanism"
Original (REJECTed): df9c7a35; round-1 findings
docs/research/2026-08-11-op2-review-blast.md
Reviewer lens: what OP2 breaks OUTSIDE its own scope (Options panel).
Date: 2026-08-11
Verdict: CLOSED — all three MUST-FIXes and all three SHOULD-FIXes are
discharged, verified against the committed data rather than against the commit
message. Four residuals, none blocking: 1 SHOULD-FIX (an unpinned premise), 3
NOTEs.
0. Executive summary
The rework replaces "widen the factory arm and hope" with a dormancy model:
UiDatElement is unsealed (src/AcDream.App/UI/Layout/UiDatElement.cs:43), and
both behavioral widgets now derive from it — UiTabPanel : UiDatElement
(src/AcDream.App/UI/UiTabPanel.cs:79) and UiTemplateListBox : UiDatElement
(src/AcDream.App/UI/UiTemplateListBox.cs:81). The Type-8 and Type-5 factory
arms are unconditional again
(src/AcDream.App/UI/Layout/DatWidgetFactory.cs:106, :119), which is now
safe rather than lucky, because an un-activated instance inherits the exact
UiDatElement media draw, ClickThrough = true default, and IUiDatStateful
propagation the pre-OP2 fallback provided, and performs zero import-time work
(UiTabPanel.cs:126-128, the empty OnChildrenAttached).
That is the right shape. It removes the class of defect instead of narrowing a guard, and it does so without the "gate on the Options LayoutDesc ids" workaround round 1 offered as one option.
All three MUST-FIXes verified closed against primary data, not against the commit message:
- The five Type-8 elements are behaviorally identical to pre-OP2, and the six new
pins assert the failure modes (media file id non-zero,
ClickThroughtrue,ActivePageElementId == 0, child count 16, all childrenVisible), not just widget class names. - The ten Type-5 elements gain no viewport child. Verified independently: all
16 Type-5 elements across all 32 fixtures have zero children, so the one
genuine remaining delta (
ConsumesDatChildren => true) drops nothing today. - All 32 fixtures were regenerated and committed. Verified by structural diff: all 1,102 nodes across all 32 files carry the five new fields, and the regeneration changed no pre-existing field value in any of the 27 previously stale fixtures — zero drift, in all 27, not just the 5 spot-checks requested.
Two of round 1's own findings were factually wrong and the rework absorbed one
of them without correcting the record. See §5. This matters because SHOULD-FIX
2's stated rationale (base-vs-incorporated id mismatch) is refuted by the raw dat
data, and MUST-FIX 1(d)'s "mitigating: it is luck, three of four panels don't
resolve" was the inverse of the truth — every tab-table id resolves as a
direct child, so df9c7a35 was strictly worse than round 1 assessed. The
rework's dormancy fix is correct either way; only the reasoning under
SHOULD-FIX 2 needs correcting.
1. Method
- Parsed all 32 committed fixtures under
tests/AcDream.App.Tests/UI/Layout/fixtures/atb236a442, walked everyElementInfonode (1,102 total), and recorded Type, child ids, raw dat properties0x2E/0x64/0x72,StateMedia, and new-field presence. - Structural deep-diff of every one of the 27 pre-existing fixtures,
df9c7a35→b236a442, with the five new field names stripped, to isolate "did regeneration change anything OLD?" - Cross-checked the raw
0x2Estruct members (0x30/0x31/0x32) against the projectedTabTablefield, per element, to settle the base-vs-incorporated-id question with data. - Read the widget/factory/importer sources and every new or changed test.
- No build, no test run, no launch (read-only mandate).
2. MUST-FIX closure
MUST-FIX 1 — five Type-8 elements re-classed — CLOSED
Every one of the five is now behaviorally identical to pre-OP2. The mechanism is
inheritance, not per-element special-casing: UiTabPanel adds no OnDraw
override, no ClickThrough write, no ConsumesDatChildren override, and its
IUiChildrenAttachedListener.OnChildrenAttached body is empty
(UiTabPanel.cs:126-128).
| Element | Round-1 defect | Status | Evidence |
|---|---|---|---|
vendor 0x1000008D |
lost DirectState backdrop fill | fixed | fixture StateMedia[""] = (100687632, DrawMode 1); UiDatElement ctor picks "" (no DefaultStateName, no Normal) → ActiveMedia() returns it → UiDatElement.OnDraw (UiDatElement.cs:208) draws it |
character 0x10000227 |
ClickThrough true→false; lost state propagation |
fixed | ClickThrough = true set in UiDatElement.cs:126, never overwritten; TrySetRetailState is non-virtual (UiDatElement.cs:86) so a subclass cannot alter propagation |
spellbook 0x100002A8 |
same | fixed | same |
vendor 0x100000B8 |
same | fixed | same |
combat 0x100000A2 |
import-time takeover (8-entry table, no controller re-binder) | fixed | OnChildrenAttached is empty; ActivateTabBehavior has no caller anywhere in src/ |
Do the new pins assert failure modes rather than class names? Yes, on three of the four axes, with one weaker:
- Media drawn —
OP2ReworkBlastRadiusConformanceTests.cs:48-49:(uint file, int _) = backdrop.ActiveMedia(); Assert.NotEqual(0u, file);This is the actual draw input, not a class name. ✅ - Click-through —
:47,:67,:90,:106,:155:Assert.True(...ClickThrough)on all five Type-8 elements plus the representative Type-5. This is the exact fieldUiElement.HitTestgates on. ✅ - No visibility flip / no takeover —
:129-131:Assert.Equal(0u, host.ActivePageElementId),Assert.Equal(16, host.Children.Count),Assert.All(host.Children, child => Assert.True(child.Visible)). That is the observable failure mode (a hidden page), not a class name. ✅ - State propagation —
:91,:107:Assert.IsAssignableFrom<IUiDatStateful>(root). This is a capability assertion, not a behavior assertion: it does not build aPassToChildrenstate and check a child received it. Acceptable-as-is becauseTrySetRetailState/ActiveRetailStateIdare non-virtual onUiDatElement(:86,:69), so a subclass structurally cannot diverge — but it is the one pin of the four that would still pass if the mechanism broke in the base class. Recorded as NOTE 1, not a residual defect.
Combat's specific claim ("no controller rebind") re-verified: grep of
src/AcDream.App/UI/Layout/CombatUiController.cs for 0x100000A2 / 0x100000A3
returns nothing.
MUST-FIX 2 — ten Type-5 elements — CLOSED
The viewport is now created lazily inside the Viewport property
(UiTemplateListBox.cs:147-162), reached only from Scroll and from a
successful AddItemFromTemplateList (:182). DatWidgetFactory leaves
TemplateResolver null (:106 passes no resolver), and AddItemFromTemplateList
returns before touching Viewport when the resolver is null (:175-176) or the
index is out of range (:174). So no pre-existing Type-5 element can allocate or
attach the viewport.
Child structure verified identical to the old UiDatElement build — with one
qualifier the class doc names and I confirmed:
old UiDatElement |
new dormant UiTemplateListBox |
|
|---|---|---|
| viewport child | none | none (lazy) ✅ |
| authored dat children | built and attached (LayoutImporter.cs:129-136) |
skipped — ConsumesDatChildren => true (UiTemplateListBox.cs:145) |
That second row is the only place the "behaviorally identical" claim is not
literally true. It is harmless today, and I verified it directly rather than
taking the doc's word: across all 32 fixtures, all 16 Type-5 elements have
zero Children. Incorporation (LayoutImporter.Resolve → IncorporateChildren,
LayoutImporter.cs:352) happens in the ImportInfos phase, so the committed
fixture child tables are the final production child tables — this is not a
fixture-vs-production gap. Nothing is dropped, and no id disappears from byId.
The premise being unpinned by any test is SHOULD-FIX A below.
EffectsUiControllerTests' updated assertion is correct, not loosened. The
diff changes Assert.IsType<UiDatElement> → Assert.IsType<UiTemplateListBox>
(exact-type, xUnit IsType is not assignability), and the line immediately after
is unchanged and is the load-bearing one:
UiItemList list = Assert.IsType<UiItemList>(Assert.Single(listHost.Children));
Assert.Single is exactly the assertion that fails if a viewport is injected
alongside the controller's own UiItemList. The pin got stronger in effect,
not weaker.
MUST-FIX 3 — harness blindness — CLOSED
Three independent checks, all measured:
-
All 32 fixtures regenerated and committed.
git show --stat b236a442lists all 32 fixture files as modified; 27 of them carry substantial line counts (e.g.character_2100002E.json+1001,combat_21000073.json+741). -
All 27 previously-stale fixtures now carry the five new fields. Verified node-by-node, not file-by-file: 1,102 / 1,102 nodes across all 32 files carry
TabTable,TemplateList,ScrollbarElementId,Outline, andOutlineColor. Zero nodes missing any of the five. -
The regeneration introduced ONLY the expected field additions. The task asked for a spot-diff of at least 5 of the 27; I ran the structural deep-diff on all 27, stripping only the five new field names and comparing every remaining key/value recursively:
character_2100002E.json : 0 non-new-field differences CLEAN combat_21000073.json : 0 non-new-field differences CLEAN spellbook_21000034.json : 0 non-new-field differences CLEAN vendor_21000012_100000B7.json : 0 non-new-field differences CLEAN effects_positive_2100001B.json : 0 non-new-field differences CLEAN … and the other 22, all CLEAN (0 differences each)No pre-existing value drifted. The hazard the task named — "a changed pre-existing value would mean the generator's output changed for OLD fields" — did not occur anywhere. (
chat_2100006f.json, the one fixture that already carriedOutline/OutlineColorfrom CH6a, is included in that sweep with the same result on its other fields.) -
The suite genuinely exercises the new-shape fixtures.
ElementInfo's three new members are public fields (ElementReader.cs:192,:199,:207) andFixtureLoaderdeserializes withIncludeFields = true(FixtureLoader.cs:14-17), so the fixture path now reads real projected values. Concretely:OP2ReworkBlastRadiusConformanceTests.cs:64assertshost.Tabs.Count == 3and:127asserts== 8— values that were empty under the stale fixtures. The structural false negative is gone. -
The reader itself is now covered independently of the fixtures. The remaining structural gap round 1 named —
ApplyCanonicalLegacyProjectionis never called on the fixture path (LayoutImporter.cs:465,ElementReader.cs:410are still its only call sites) — is addressed by five new tests inElementReaderTests.csthat driveApplyCanonicalLegacyProjectiondirectly from synthetic raw property bags. A reader regression now fails a test even without regeneration. That is the right seam.
3. SHOULD-FIX closure
SHOULD-FIX 1 — unconditional viewport injection — CLOSED
Lazy Viewport property (UiTemplateListBox.cs:147-162), pinned in both
directions: OptionsPanelLayoutConformanceTests asserts Assert.Empty(listBox.Children)
after a failed/never-attempted add, and Assert.Single(listBox.Children) after the
one successful add. ConsumesDatChildren => true also resolves round 1's
"authored children become siblings of the viewport" trap for OP3+ — authored
children are now never built for a Type-5 element, so the sibling-vs-row ambiguity
cannot arise.
SHOULD-FIX 2 — base-vs-incorporated tab-table ids — CLOSED, on a corrected premise
FindDirectChild → FindDescendant, a pre-order DFS of the whole subtree
(UiTabPanel.cs:208-219), correctly cited to retail GetChildRecursive.
Round 1's stated rationale was factually wrong. I read the raw 0x2E struct
members straight out of the fixtures and compared them to the projected TabTable
and to the actual built child ids:
| Host | raw 0x2E page ids |
projected TabTable |
actual direct children |
|---|---|---|---|
vendor 0x100000B8 |
0x100000BC, 0x100000C4, 0x100000CD |
identical | contains all three |
combat 0x100000A2 |
0x100000AA … 0x100005C3 |
identical | contains all eight |
character 0x10000227 |
0x1000022B, 0x1000022C, 0x10000539 |
identical | contains all three |
spellbook 0x100002A8 |
0x100002AC, 0x100002AD |
identical | contains both |
Round 1 reported vendor's table as naming 0x1000007C/0x10000084/0x1000008D and
character's third entry as 0x100003F8→0x100003F9. Neither appears anywhere in
the dat property. Since the deep-diff proves the raw properties are byte-identical
between df9c7a35 and b236a442, this was a round-1 mis-read, not a data change.
Measured consequence: every tab-table id occurs exactly once, at depth 1, in
every one of the five Type-8 hosts — so FindDescendant returns the same
element FindDirectChild did. No behavior change today.
Does the recursion introduce NEW ambiguity? Yes, latently — and the codebase
already documents the duplicate-id hazard the task asked about
(UiDatElement.cs:60-62: "footer state groups that appear once per tab page but
share the same dat id"). Two facts:
- Duplicate
DatElementIds are rampant inside these very subtrees: character0x10000227's 133-node subtree has 39 duplicated ids (0x10000244×6,0x1000023D×2, …); combat has four ids at ×8; spellbook has0x10000328×13; options has0x100002CE/CF/D0×16. FindDescendantis pre-order: it fully recurses into child n's subtree before testing child n+1. So a deep match under an earlier sibling beats a direct-child match on a later sibling — a strictly larger ambiguity surface thanFindDirectChildhad.
Safe today only because no tab-table id collides with a duplicated id. Recorded as NOTE 2, with the OP3+ trigger named.
SHOULD-FIX 3 — silent no-resolve — CLOSED
UnresolvedEntries (UiTabPanel.cs:118) plus a per-miss Console.WriteLine
carrying the host id, both entry ids, and which side missed
(:154-157). Detectable enough for this lens: it is a public, testable
collection, not only a log line, and OptionsPanelLayoutConformanceTests pins
Assert.Empty(tabControl.UnresolvedEntries) after activation. A future
regression that breaks resolution is now an assertable state change rather than a
silent no-op.
4. New blast radius introduced by the rework
Scanned the rework diff for what it adds to the exposure surface.
(a) Unsealing UiDatElement — no invariant becomes mutable by subclasses.
Info is protected readonly (UiDatElement.cs:57), so the reference cannot be
reseated. The class's identity-carrying members are non-virtual:
ElementId, ActiveRetailStateId (:69), TrySetRetailState (:86),
ActiveMedia (:150). The only overridable surface a subclass inherits is
UiElement's own (OnDraw, OnHitTest, ConsumesDatChildren, OnEvent,
HandlesClick) — and of those, exactly one is exercised
(UiTemplateListBox.ConsumesDatChildren, §2). OnDraw is
protected override and not sealed, so a future subclass could silently kill
authored-media drawing; neither current subclass does. NOTE 3.
(b) Unsealing widens every is/as UiDatElement site in the tree. Nine
production sites now match Type-5/Type-8 widgets where they previously fell
through: AppraisalUiController.cs:176, :224;
CharacterStatController.cs:1582, :1585, :1799, :1839;
SelectedObjectController.cs:152; CreatureAppraisalRows.cs:312;
RetailTabBinding.cs:21; SpellcastingUiController.cs:658;
WindowChromeController.cs:41. Every one of them is additionally gated on a
specific ElementId/role, and I checked the ids involved
(InscriptionBackgroundId = 0x10000137, SpellIconId = 0x1000015F,
FooterStateAId/BId = 0x10000240/41, OverlayId = 0x100001A0) against the
complete Type-5/Type-8 id set — no overlap. The widening is also benign in
direction: the behavior these sites reach for (OnClick, MediaVisible,
ClickThrough) is inherited unchanged. Clean.
The inverse — tests using exact-type Assert.IsType<UiDatElement> — is the
sharper edge, since xUnit's IsType rejects subclasses. Ten such sites exist;
EffectsUiControllerTests.cs:136 was the one that needed updating and was
updated. The remaining nine target ids outside the Type-5/8 set.
(c) The factory arms being unconditional again. Correct now, for the reason
the comments give. Verified there is no arm collision: the switch is a single
info.Type switch (DatWidgetFactory.cs:76), and the four UIOption_* class ids
(0x10000036/37/38/44) are distinct Type values from 5 and 8 — the
0x10000044 template element 0x10000520 is Type 0x10000044, not Type 5, so it
cannot be captured by the Type-5 arm.
(d) BuildCheckboxBitfield64 deleted; 0x10000044u => new UiCheckboxBitfield64(info.TemplateList)
(DatWidgetFactory.cs:162) no longer seeds SpriteResolve/LabelFont. Zero
blast radius — class 0x10000044 occurs in exactly one fixture
(options_2100002B.json, element 0x10000520), in a panel that is not yet
shipped. Rows now carry their own art via the resolved template. Noted for
completeness only.
(e) Fixture regeneration's effect on other conformance suites. No test pins a fixture value that regeneration changed, because regeneration changed no pre-existing value (§2, MUST-FIX 3.3, all 27 files). The only way a suite could have broken is by exact-type assertion on a re-classed widget — one site, updated.
(f) The RuntimeCollisionReportingStateTests allocation flake — not caused by
this rework, and it is exactly ISSUES #250's class.
tests/AcDream.Runtime.Tests/Physics/RuntimeCollisionReportingStateTests.cs:2359-2384
(WarmedSteadyContactRefreshDoesNotAllocate) has the precise shape #250 named as
its root cause: one warmup call, then a 10,000-iteration loop written inline
inside the measured window, asserted Assert.Equal(0, allocated). #250's own
table lists that shape (UiDatFontTests: "1 call" warmup / "a 10,000-iteration
loop written inline") and diagnoses it as on-stack replacement compiling on the
measuring thread inside the window.
The #250 fix moved every strict-zero site in AcDream.App.Tests onto
tests/AcDream.App.Tests/ZeroAllocationProbe.cs. It never swept the other test
assemblies: AcDream.Runtime.Tests still has three files with raw
GC.GetAllocatedBytesForCurrentThread() sites
(Physics/RuntimeCollisionReportingStateTests.cs,
Physics/RuntimePhysicsStateTests.cs, Physics/RuntimeSetPositionStateTests.cs,
plus Gameplay/RuntimeLocalPlayerMovementStateTests.cs), as do
AcDream.Headless.Tests and AcDream.Core.Tests. #250's own closing lesson
applies verbatim: "'Not observed failing' only ever meant 'not yet observed'."
Could the rework have affected it? No causal path. git show --stat b236a442
touches only docs/, src/AcDream.App/UI/**, and tests/AcDream.App.Tests/UI/**.
Zero files under src/AcDream.Runtime/ or tests/AcDream.Runtime.Tests/. Test
assemblies run in separate processes, so the larger App fixtures cannot raise GC
pressure inside the Runtime test process.
5. Corrections to the round-1 report (record-keeping)
Two round-1 claims are refuted by the committed data. Both are recorded here so the next reader does not re-derive them:
- "On 3 of the 4 panels the tab table's ids do not match the resolved child
ids, so
FindDirectChildmisses andSwitchTois inert — mitigating, and it is luck." False. All 16 tab-table ids across the four pre-existing hosts resolve as direct children (§3, SHOULD-FIX 2 table).df9c7a35was therefore worse than round 1 assessed: all four panels would have had a live import-time switcher, and combat — with no controller re-binder — would have hidden seven of its eight stance pages on import. This strengthens, not weakens, the case for the dormancy fix that landed. - SHOULD-FIX 2's premise, "property
0x2Ecarries base-layout ids thatLayoutImporter.IncorporateChildrenremaps." False; the raw struct members equal the incorporated child ids exactly. TheFindDescendantchange is still defensible on its own merits (retail usesGetChildRecursive, and OP3+ cross-layout page mounts genuinely nest), but it was justified by a diagnosis that does not hold, and it carries the real cost measured in §3.
This is the docs/research/2026-08-05-c4-closeout-handoff.md pattern in
miniature — inferring a fact you can observe. The observation cost one script.
6. Residuals
SHOULD-FIX A — ConsumesDatChildren => true rests on a measured-but-unpinned premise
UiTemplateListBox.cs:145 is the single remaining behavioral delta versus the
pre-OP2 UiDatElement fallback: authored dat children of a Type-5 element are no
longer built. The class doc states the premise correctly
(UiTemplateListBox.cs:28-32: "None of the ten pre-existing elements author real
dat children of their own"), and I verified it — all 16 Type-5 elements, all 32
fixtures, zero children.
No test pins it. Worse, the closest pin cannot distinguish the two outcomes:
OP2ReworkBlastRadiusConformanceTests.cs:153 asserts
Assert.Empty(host.Children) on effects 0x10000123, which passes both when no
viewport was injected (intended) and when authored children were silently
consumed (the failure). A future dat/importer change that gives any of the ten a
child would be dropped with the whole suite green.
Suggested pin (cheap): assert info.Children.Count == 0 on the ElementInfo side
for the ten ids, in the same test file, so the premise is asserted where it is
relied upon. Contrast the Type-8 side, which does have the right pin —
Assert.Equal(16, host.Children.Count) at :130.
Also, only one of the ten Type-5 elements is pinned at all; the other nine
(0x1000023D, 0x10000532, 0x10000123 negative, 0x10000149, 0x1000032D,
0x10000335, 0x10000174, 0x10000464) rely on class-level reasoning. That is
defensible for the dormancy properties, which are class-level; it is not
defensible for the child-count premise, which is per-element.
NOTE 1 — the state-propagation pin is a capability assertion
OP2ReworkBlastRadiusConformanceTests.cs:91, :107 assert
IsAssignableFrom<IUiDatStateful>, not that a PassToChildren state actually
reached a child. Sound in practice because TrySetRetailState is non-virtual
(UiDatElement.cs:86), so no subclass can diverge — but it is the one MUST-FIX-1
axis whose pin would survive the mechanism breaking in the base class.
NOTE 2 — FindDescendant pre-order first-match in a duplicate-id tree
UiTabPanel.cs:208-219 recurses into an earlier sibling's entire subtree before
testing the next sibling, in trees where duplicate DatElementIds are common
(up to ×16 in options_2100002B.json). Harmless today — every tab id is unique
and at depth 1 — but the OP3+ trigger is concrete: the Options page slots
(0x10000211/0x10000212/0x10000213/0x1000050C) mount cross-layout content
that already re-uses ids present elsewhere in the same tree. If it bites, prefer
breadth-first, or search direct children first and fall back to descendants.
Two smaller items in the same class, both unreachable today (nothing calls
ActivateTabBehavior):
UiTabPanel.cs:161binds the click even when only the page failed to resolve, so the button would switch to a page that does not exist — andSwitchTo(:184-199) would then hide every page and close every tab, i.e. a blank panel.- Conversely, the new
ReadTabTablezero-id filter (ElementReader.cs:520-527) usefully protectsSwitchTo'sActivePageElementId == 0sentinel from ever being a real page id. Good interaction, worth keeping in mind if that filter is ever relaxed.
NOTE 3 — unsealing leaves OnDraw overridable
UiDatElement.OnDraw (:208) is protected override, not sealed. A future
UiDatElement subclass that overrides it silently loses authored-media drawing —
which is precisely the df9c7a35 vendor-backdrop defect, reachable again by a
different route. Sealing OnDraw, TrySetRetailState's surface is already safe
(non-virtual), would close it. Also: UiTabPanel still implements
IUiChildrenAttachedListener with an empty body (:126-128) purely to document
"deliberately does nothing" — harmless, but it is a live interface registration
that a reader may mistake for an active hook.
NOTE 4 — blast-radius sweeps remain bounded by the 32 fixtures
Both round 1 and this re-review measure the 27 layouts the fixture generator
covers (RetailLayoutFixtureGenerator.cs:19-37, :95-184). Production imports at
least a few LayoutDescs with no fixture — 0x21000008
(ExternalContainerController, mounted at RetailUiRuntime.cs:2177/:2223),
0x21000037 (shared UIItem catalog), 0x21000043 (vendor popup catalog),
0x21000011 (SpewBox). With dormancy in place the only unmeasured risk in those
is narrow and specific: a Type-5 element carrying authored dat children
(Type-8 is now fully identical to the old fallback, with no ConsumesDatChildren
override, so it carries no residual risk at all). 0x21000008's two lists build
as UiItemList (ExternalContainerController.cs:80-81 uses a RequiredList
helper), i.e. class 0x10000031, not Type 5 — so the highest-traffic unfixtured
layout is clear. Recorded so the boundary of the claim is explicit, not because a
defect is suspected.
7. Verdict
CLOSED.
The rework fixes the class rather than the instances: dormancy makes an
unactivated Type-8/Type-5 widget indistinguishable from the pre-OP2
UiDatElement fallback by construction, so the unconditional factory arms are
now correct instead of accidentally survivable. All three MUST-FIXes are
discharged and — critically for a lens whose round-1 complaint was that the
harness could not see anything — the closure is verifiable from committed data:
1,102/1,102 fixture nodes carry the new fields, 27/27 regenerated fixtures show
zero drift in pre-existing values, all 16 Type-5 elements carry zero children,
and the new pins assert media file ids, ClickThrough, child counts, and page
visibility rather than widget class names.
Residuals are one SHOULD-FIX (pin the ConsumesDatChildren premise where it is
relied upon) and four NOTEs, none of which changes shipped behavior. The
RuntimeCollisionReportingStateTests flake is ISSUES #250's class in an assembly
#250's fix never swept, with no causal path to this commit.
The one thing worth carrying forward beyond OP2: round 1 got two facts wrong that one fixture-parsing script settles, and one of them became the stated rationale for a code change that shipped. §5 corrects the record.