Owner request: plugin panels (Decal/VirindiViewService-class, per the
MosswartMassacre reference usage) need to embed real DAT icons the way
FlagTrackerView.SafeSetListImage does — spell/skill art, raw portal
indices, and a window icon. This is Slice B of
docs/plans/2026-09-06-plugin-shelf-and-dat-icons.md (Slice A, the
movable/collapsible shelf, landed in 01b98ca30/4fada238e/718005b21).
What shipped:
- AcDream.Plugin.Abstractions.PluginIcons.Normalize: the one Decal-style
bare-index -> 0x06xxxxxx RenderSurface DID grammar, applied at every
icon SINK (descriptor IconSurfaceId in PluginShelfButton, and markup
<icon did>/<button icon>/<list icons> did-kind ids) rather than on the
plugin-facing records, which already carry real DIDs read straight
from the client's tables.
- PluginSpellInfo.IconId / PluginSkillInfo.IconId /
PluginInventoryItem.IconId / PluginWorldObject.IconId: additive init
properties (default 0), filled in AppAutomationSurface from
SpellMetadata.IconId (already projected from SpellBase.Icon by
RetailSpellMetadataProjector — no gap there), a new BindSkillIcons
parallel to BindSkillNames (GameWindow reads
DatReaderWriter.Types.SkillBase.IconId — confirmed via reflection over
the installed Chorizite.DatReaderWriter package, since its XML docs
don't cover Pack/Unpack-generated public fields: Description, Name,
IconId (uint), TrainedCost, SpecializedCost, Category, ChargenUse,
MinLevel, Formula, UpperBound, LowerBound, LearnMod), and
ClientObject.IconId in CaptureOwnedItems/ProjectWorldObject.
- IMarkupIconResolver (AcDream.App.UI): ResolveDid/ResolveSpell/
ResolveItem. MarkupDocument.Build gains an optional parameter (null by
default -> every icon sink resolves to nothing rather than throwing,
so pre-Slice-B callers/tests are unaffected). RetailUiRuntime.
MountPlugins builds ONE RetailMarkupIconResolver per pass from
RetailUiAssets.ResolveSprite + RetailUiAssets.Icons (the shared
IconComposer) + Toolbar.Objects (the SAME ClientObjectTable
Magic/Toolbar bindings already borrow for their own icon resolution —
no second object lookup introduced).
- New UiMarkupIcon widget (<icon x y w h did|spell|item tooltip>):
exactly one source required (FormatException at Build otherwise,
matching every other malformed-attribute rule), aspect-preserved,
centered, click-through unless a tooltip makes it a real hit-test
target.
- UiSimpleButton.IconSource and UiMarkupList.IconIdsSource/IconResolve:
additive, default null/no-op, so every existing button/list caller
(including the plugin shelf's own toggle/minimize buttons) is
unaffected. Button icon draws flush left and shifts the caption's
centering region right; list icons reserve a leading RowHeight-2
column (Decal's IconColumn) and skip rows whose id is 0 or
unresolvable.
- MarkupDocument centralizes the did/spell/item dispatch (including
PluginIcons.Normalize for did) in two small helpers (BuildIconSource
for <icon>/<button>, BuildRowIconResolve for <list>) so all three
markup surfaces share one resolver call path.
- AcDream.Plugins.Smoke ships a RegisterPanelContent (in-memory KSML,
no plugin-side .xml file) proof panel exercising every new surface:
a bare-index <icon>, a literal-hex <icon>, a composited <icon
spell=...>, a <button icon=...>, and a <list icons=... iconkind=
spell> of the first five known self-buffs with their IconId printed
alongside. Descriptor IconSurfaceId reuses the same bare index to
prove the shelf button and the panel's own icon normalize identically.
- docs/plugin-ui-markup.md is the new SSOT for the full markup
vocabulary + icon grammar + the Slice A shelf; linked from
docs/README.md and docs/plans/2026-04-24-ui-framework.md.
Design decisions where the plan left room:
- Normalize runs inside the resolver dispatch (BuildIconSource/
BuildRowIconResolve), not scattered at each markup call site, so
every did-kind sink shares one choke point.
- did/spell/item all accept either a literal (decimal or 0x-hex) or a
{Binding}, via one BindUintLiteralOrBinding helper, for symmetry —
the plan only showed spell/item as bindings but didn't forbid a
literal.
- <icon> requires exactly one source INCLUDING zero (not just two);
an icon with no source is not a coherent element.
- The button/list icon draw math (icon column extent, padding) lives
in the widgets themselves (UiSimpleButton/UiMarkupList), not in
MarkupDocument, keeping the parser only responsible for wiring
Func<(tex,w,h)> sources.
Tests: PluginIconsTests (Normalize table), MarkupIconTests (icon/button/
list resolver dispatch via a fake IMarkupIconResolver, plus draw-level
pins via the RecordingGpuDevice/TextRenderer apparatus already used by
UiAncestorClipTests/UiRenderContextDrawStringDatOutlineTests — "draws
nothing when unresolvable" and "button/list icon shifts the text"),
and AppAutomationSurfaceIconInstalledDatTests (Lane=InstalledDat: a
known spell's IconId matches the real installed SpellTable's own Icon
field exactly). Verified every new test fails to COMPILE without this
change (git-stashed the src/ changes, rebuilt the test project: CS0246
on IMarkupIconResolver) before restoring. Full App suite: 7331 passed /
97 skipped / 36 failed (identical pre-existing failure set/count to the
7306/97/36 baseline; the +25 passes are exactly the new tests).
AcDream.Plugins.MossTank.Tests (the main consumer of the touched
Plugin.Abstractions records) passes 337/337 unchanged, confirming
API-v1 binary/source compatibility. Full solution builds green.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Opus re-review of 4fada238e (Slice A review-fix round) left seven residuals.
All seven addressed here, in one commit per the residual-round contract:
NEW-1 (test-coverage): no test pinned the Draggable:true->false change
itself. Added Drag_StartingOnShelfPadding_DoesNotMoveTheShelf, pressing the
shelf's own PADDING (left of the first button's Left=4, below the 12px grip)
and dragging 100px through the real UiRoot press/move/release path. Proven
to fail against the pre-4fada238e shelf: temporarily setting Draggable=true
in the ctor and re-running this test moved the shelf from Left=760 to 764
(clamped to parent.Width-Width) instead of leaving it in place — reverted
after confirming.
NEW-2 (correctness): RetailWindowLayoutPersistence.ClampAllToScreen clamps
generically to screen.Width-handle.Width, 4px (OuterPadding) off what the
shelf's own dock formula (parent.Width-Width-OuterPadding) would produce for
the same new parent size. A screen shrink that triggers that clamp used to
flip _userPositioned permanently even though nothing a user would call
"dragging" happened. PluginSidePanel.OnHandleMoved now re-derives "still
docked" at comparison time as a function of the CURRENT parent size: either
the live dock formula's own result, or what ClampAllToScreen's clamp would
produce from the PREVIOUS docked position — only a position matching
NEITHER flips _userPositioned. New test
ClampAllToScreen_AfterShrinkingTheRoot_DoesNotFlipAnchoring_ButARealDragStillDoes
shrinks the root, runs ClampAllToScreen, confirms the shelf still anchors
top-right through a later reflow and a collapse, then confirms a genuine
grip drag afterward still flips anchoring.
NEW-3 (decision, documented): KeepWindowReachable's per-tick clamp and
ClampAllToScreen's screen-resize sweep both touch plugin windows. Evidence
gathered and recorded as a doc comment on KeepWindowReachable:
RetailWindowManager.MoveTo (line 178) already short-circuits an unchanged
position before ever raising Moved; persistence's ScreenSize and Host.Root's
size derive from the same d.Window.Size and are reconciled every frame
(UiHost.Draw sets Root.Width/Height from screenSize); and because
RetainedGameplayUiFrame.Render ticks BEFORE it draws in the same frame,
ClampAllToScreen always resolves a screen-resize's clamp before
KeepWindowReachable ever observes the new size next tick, making
KeepWindowReachable's own pass a structural no-op for that case (never a
second write). KeepWindowReachable is NOT dropped, though: it is the only
reachability guarantee for (a) a plugin window whose geometry is mutated
directly rather than through MoveTo — pinned by the pre-existing
FullWidthPluginWindowStartsAndStaysReachableAtMinimumCanvas test, which
exercises exactly that with no screen resize at all — and (b) a host wired
with no RetailWindowLayoutPersistence at all. Decision: keep both; they do
not race.
NEW-4 (robustness): RetailWindowLayoutPersistence._attached is mutated
mid-session by WindowRegistered/WindowUnregistered (a callback invoked from
inside one of these loops — e.g. Apply -> Show()/Hide() -> a controller
unregistering another window — could otherwise mutate the list being
enumerated). All five bare `foreach (... in _attached)` loops
(RestoreAllCore, ClampAllToScreen, SaveAll, SaveNamed, RestoreNamed) now
snapshot with .ToArray(), matching Dispose's existing pattern.
NEW-5 (retail-faithfulness): the plugin-shelf hide message hard-coded
"Shift+Ctrl+F1" even though InputAction.TogglePluginManager is rebindable
through Configure Keyboard. RetailUiRuntime.PluginShelfHiddenMessage now
looks up the CURRENT binding via the live InputDispatcher and formats it
through Layout.RetailKeyNames.Describe — the exact formatter Configure
Keyboard's own row captions already use
(Layout/KeyboardConfigController.cs:284) — falling back to an honest
"bind it in Configure Keyboard" message when the action is unbound or no
dispatcher is wired.
NEW-6 (bookkeeping): added divergence-register row IA-27 for the plugin
shelf's repurposing of retail's plugin-manager chord (Shift+Ctrl+F1),
its ASCII </> toggle glyphs, its two acdream-authored system messages, and
its Draggable=false grip-only drag model — citing PluginSidePanel.cs and
the RetailUiRuntime.cs TogglePluginManager case. Header count bumped
23->24 active IA rows.
NEW-7 (correctness): the one-time dock wrote Left directly, so the
first-run docked position went unsaved until some later, unrelated event
happened to trigger a save. PluginSidePanel.OnTick now routes the one-time
dock through the retained-window handle's MoveTo when registered (falling
back to the direct field write when unregistered, as before) —
_dockLeft/_dockTop are set to the TARGET position BEFORE calling MoveTo so
the synchronous OnHandleMoved re-entry it triggers (NEW-2's logic) reads the
new dock position and does not flip _userPositioned. New test
FreshShelf_OneTick_SavesTheDockedPositionImmediately confirms the store has
the docked X/Y after exactly one tick and that a subsequent collapse still
preserves the right (not left) edge.
Verification: dotnet build src/AcDream.App (Release) green; targeted filter
(PluginSidePanel|RetailWindowLayout|Markup|UiRootInput|KeyboardConfig) 145
passed / 2 pre-existing Lane=Manual failures (unrelated, gated on
ACDREAM_PROBE_LIVE_MOUNT=1); full tests/AcDream.App.Tests suite: 7306
passed / 97 skipped / 36 failed — same 36 pre-existing failures by name
(installed-DAT/Cathedral/Linux/Lane=Manual probe tests), +3 passed vs the
7303 baseline (exactly the three new tests this round added).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two independent Opus reviews of 01b98ca30 (Slice A - movable, collapsible
plugin shelf) found blocking design and behavior issues plus several
should-fix gaps. All addressed in this commit:
1. Grip/toggle are now REAL children instead of a drawn band + a
cursor-position-dependent HandlesClick override + an OnEvent toggle
hack: ShelfGripPanel (WindowMoveHandle=true) spans the top band minus
the toggle width; a UiSimpleButton toggle sits beside it (HandlesClick
already wins at UiRoot.OnMouseDown before the Draggable-window
fallback). The shelf's own Draggable is now FALSE - verified against
UiRoot.FindDragHandleWindow, which never reads a window's own
Draggable flag at all (it walks for a WindowMoveHandle ancestor-or-self
then climbs to the nearest child of UiRoot), so Draggable=true was
never required for the grip to work and only armed the whole-window-
drag fallback for clicks on the shelf's own padding - exactly the
behavior the review asked NOT to have. The two Assert.Single(shelf.
Children) test sites now filter by the (now internal) PluginShelfButton
type instead of asserting child count.
2. Deleted the per-tick "always highest ZOrder" raise in OnTick. It fought
RetailDialogFactory.Tick's own per-frame dialog re-raise, so a dialog
opened while the shelf was visible could never end up on top of it.
Registration's ordinary press-to-raise (a grip press calls
BringToFront before the drag starts) remains.
3. Collapse and hide/show intent are now persisted through their own
channel. RetainedWindowState gained a nullable RequestedVisible;
RetailWindowHandle gained an internal StateChanged event that
RetailWindowLayoutPersistence subscribes to (alongside Moved/Resized/
Shown/Hidden) and that the shelf raises after a collapse toggle or
Show/Hide. Capture() now persists state.RequestedVisible (the panel's
own intent) instead of the derived IsVisible, so an availability hide
(last plugin window unregistered) is never mistaken for a user hide.
WindowNames.PluginShelf is now one of RetailUiRuntime's state-managed
visibility windows, so Apply() restores the intent through
RestoreWindowState directly rather than via Show()/Hide().
4. RetailWindowLayoutPersistence now subscribes to
RetailWindowManager.WindowRegistered/WindowUnregistered so a window
(a plugin window loaded after startup, or the shelf on any path that
constructs persistence first) attaches even when it registers after
persistence already exists.
5. Reflow()'s default height argument is now nullable and falls back to
the last height OnTick actually measured (or unbounded if none yet),
instead of always forcing a single-column layout - every call site
OTHER than OnTick's own row-wrap (Add, unregister, Show/Hide, the
collapse toggle, RestoreWindowState) used to collapse a wrapped
multi-column layout to one column for a frame.
6. _userPositioned is now flipped only when the handle's position differs
from the recorded dock placement, not on every RetailWindowHandle.
Moved (which fires unconditionally on any completed window-drag
release, including a zero-movement grip click, and on any
ClampAllToScreen reachable-clamp).
7. New tests cover: a press+drag starting on an entry button does not
move the shelf; the removed per-tick raise (a sibling with higher
ZOrder keeps it after a tick); TogglePluginManager's hidden-and-
collapsed -> shown-and-expanded / visible -> hidden transitions at the
shelf API (no RetailUiRuntime construction harness exists in this test
suite to exercise the action-routing switch itself - the "no plugin
windows registered" message branch is therefore not covered here).
8. The hide branch of TogglePluginManager now displays "Plugin shelf
hidden. Press Shift+Ctrl+F1 to show it again."; the show branch stays
silent.
9. The collapse toggle now draws ASCII '<'/'>' instead of the DAT-font-
dependent '«'/'»' glyphs (the only use of those code points in the App
UI, silently dropped by UiDatFont when absent), and gets the same
DatFont + bitmap fallback the shelf's entry buttons already have
through UiSimpleButton. A new installed-DAT test pins that the default
font actually carries both ASCII glyphs.
10. ResizeX/ResizeY are false on the shelf so a restored layout's saved
dimensions can never stomp the derived Width/Height via ResizeTo.
11. WindowNames.PluginShelf replaces the "plugin-shelf" literal at every
site (RetailUiRuntime, docs comments, tests).
12. The grip dims to half opacity while RetailWindowManager.IsLocked, the
same visual cue every other retail window gets (the shelf's grip has
no DatElementId, so RetailWindowLockPresentationController's authored-
chrome dimming does not reach it on its own).
Every new test was verified to fail against the pre-fix source: reverting
src/AcDream.App/UI/PluginSidePanel.cs, IRetainedWindowStateController.cs,
RetailWindowHandle.cs, WindowNames.cs, and RetailUiRuntime.cs to their
01b98ca30 state makes the whole PluginSidePanelTests.cs file fail to even
compile (missing WindowNames.PluginShelf, the now-internal
PluginShelfButton type, and RetainedWindowState.RequestedVisible);
reverting RetailWindowLayoutPersistence.cs alone (fixed source elsewhere)
makes WindowRegisteredAfterConstruction_StillRoundTrips fail at runtime
with a null saved layout, confirming finding 4 in isolation.
Verified: dotnet build src/AcDream.App (Release) green; dotnet test
tests/AcDream.App.Tests (Release) targeted filter
(PluginSidePanel|RetailWindowLayout|Markup|UiRootInput) 105/105 green;
full suite 7303 passed / 97 skipped / 36 failed (identical failure set to
the pre-fix-round baseline - installed-DAT live-mount probes, Linux-only
pacing/credential tests, and alpha-flush COUNT-only conformance
divergences, none touching plugin UI; +9 tests, all passing, over the
prior 7294/97/36 baseline).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Owner request (docs/plans/2026-09-06-plugin-shelf-and-dat-icons.md, Slice A):
the plugin shelf was pinned to the right screen edge every tick, had no drag
or hide affordance, and never persisted position. It is now a registered
retained window ("plugin-shelf") that gets drag, the UI lock, and
RetailWindowLayoutPersistence position/visibility/collapsed persistence for
free, the same way every other retained window does.
Design decisions where the plan left room:
- Grip + collapse toggle are drawn, not child elements. A real child for the
toggle would have to live outside any WindowMoveHandle grip subtree (nesting
it inside lets UiRoot's drag-handle promotion swallow the press before the
button ever sees a click - `handleWindow is not null` outranks
`HandlesClick` in UiRoot.OnMouseDown), and a grip element as a plain sibling
changes UiElement.Children's shape, which the pre-existing single-button
shelf tests assert directly (`Assert.Single(shelf.Children)`). Keeping the
whole shelf Draggable=true and excluding just the toggle's pixel rect from
an overridden HandlesClick (computed live from UiRoot.MouseX/MouseY, the
only call site) gets grip-drags/buttons-and-toggle-don't without adding any
child or touching the existing tests' shape assumptions.
- Availability (has plugin windows) vs the user's requested-visible intent
are split the same way PluginWindowVisibilityController already splits it
for individual plugin windows, but applied SYNCHRONOUSLY (not via
VisibleSource) so Visible updates immediately after Add()/unregister with
no dependency on a Tick ever running - required to keep the pre-Slice-A
unregistered-shelf test (ShelfAndMinimizeButtonsHideAndRestoreWithoutUnregisteringWindow)
green, since it never calls root.Tick().
- "The shelf was moved" (drag or a differing restored layout) is tracked via
the shelf's own RetailWindowHandle.Moved event, captured through
WindowManager.WindowRegistered the moment MountPlugins registers it - so
unregistered/legacy use (the two other pre-existing tests) never sets this
and behaves exactly as before.
- The one-time right-edge dock (no saved layout) fires on the first OnTick
with a real parent width, replacing the old per-tick pin; Reflow's
anchor math then preserves the top-right corner while still docked or the
top-left corner once positioned, on any width change (entry add/remove,
collapse, or a parent-height-driven column rewrap).
Caption finding: the Configure Keyboard row for InputAction.TogglePluginManager
resolves its label live from the installed DAT's action-map string table
(KeyboardConfigController.BuildActionRow, RetailActionMapRow.LabelHash) -
there is no "Plugin Manager" string literal anywhere in our code to rename to
"Plugin Shelf". The row keeps showing retail's own authored name; only the
acdream-side action semantics changed.
Tests added to PluginSidePanelTests.cs (all 7 fail to even compile against
the pre-Slice-A PluginSidePanel, verified by temporarily reverting the source
files and re-running): default right-edge dock; top-right corner preserved
across a Reflow-driven width change while docked; grip drag moves the shelf
and top-left survives the next reflow once positioned; drag refused under
UiLocked; collapse via the real toggle click round-trips through
CaptureWindowState/RestoreWindowState; Show/Hide toggle sequence and a hidden
shelf staying hidden when a new plugin window registers; a full
RetailWindowLayoutPersistence round trip of X/Y/Visible/Collapsed onto a
fresh shelf instance. All 3 pre-existing tests remain green unmodified.
Verified: dotnet build src/AcDream.App (Release) green; the full App test
suite passes 7294/97 skipped/36 pre-existing unrelated failures (identical
failure set confirmed present on HEAD before this change - installed-DAT
live-mount probes, Linux-only pacing/credential tests, and known alpha-flush
COUNT-only conformance divergences, none touching plugin UI).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Project the original Run failure with loaded build, cached GPU and cell context before unwind. Preserve crash status and rethrow; contain report failures and omit arbitrary messages, source paths and session data.
Focused 19/19, narrow production/privacy review and default Release 17044/0/0 pass. Preserve the wrong-package smoke failure and real emitted report; corrected recipe10 smoke exits gracefully with two provisional PNG passes. AMD initiating cause and G4 remain open; FPS deferred. No new retail behavior deviation.
Native-boundary injection: old code 9 pass / 6 expected Assert.Throws failures. Separate acquire and present sabotage each fail their 3 fatal cases; restored focused gate 82/82. One independent API/production review PASS. Lead locked Release 0W0E; literal-hermetic 17051/0/0, manifest32/32. No retirement, recovery, normal result policy or retail behavior changes. This is first-failure evidence preservation, not an AMD reset fix; extended reproduction and G4 remain open.
Retain building and object collision for available neighboring landcells when the terrain polygon does not cover the sphere center. Verified against named and paired retail dispatch. Add prepared-flat regressions and installed cathedral repro with sabotage proof. Full Release gate 17036/0/0 and graphical wall blocking, overlap correction and escape pass provisionally. Owner accepts functional collision; exact retail settle coordinates are not claimed. Retire UN-10; keep AP159, AMD stability and final G4 open.
Scan both instance and static declared fields so a second static WalkPView or RetailFrameWalk owner cannot evade the final-graph architecture proof. Preserve the exact owner/count assertions.
Mutation evidence (both restored):
1. Added static WalkPView field to RetailPViewRenderer -> WalkFrameOwners_AreUnique first failed Assert.All: expected RetailFrameWalk owner, actual RetailPViewRenderer (3 fields).
2. Added static RetailFrameWalk field to RetailFrameWalk -> WalkFrameOwners_AreUnique first failed Assert.Single with _frameWalk and MutatedStaticRetailFrameWalk.
Delete the callerless portal-BFS research graph and spent renderer probe families while retaining the production RetailFrameWalk path, terrain diagnostics, membership invariant, and walk transcript.
Mutation evidence (all restored):
1. Restored PortalVisibilityBuilder type -> AppAssembly_ContainsNoSupersededPortalGraphTypes first failed Assert.Empty with AcDream.App.Rendering.PortalVisibilityBuilder.
2. Restored ACDREAM_PROBE_FACILITY_STAIRS -> ProductionSource_ContainsNoDeletedRendererProbe_AndRetainsWalkTranscriptProof first failed Assert.Empty on RenderingDiagnostics.cs.
3. Added a second RetailFrameWalk field -> WalkFrameOwners_AreUnique first failed Assert.Single with _frameWalk and _mutatedSecondFrameWalk.
4. Added OrderBy to OrderedStream -> OrderedWalkStream_HasNoCrossStreamReorder first failed Assert.DoesNotContain on OrderBy(.
5. Added IDatReaderWriter parameter -> FrameTimeWalkOwners_HaveNoRawDatDependency first failed Assert.Empty on RetailFrameWalk.MutatedRawDatParameter.
Keep both atmospheric vertex receivers on retail's exact authored, unnormalized uLights direction in every shadow-gate state while preserving the selected celestial direction for fragment shadow projection and volumetrics. Regenerate the affected SPIR-V and amend IA-24.
Proof adds committed-SPIR-V dataflow checks, parent plain/pipeline source pins, and a real Vulkan mesh+terrain pixel witness owned by Lane=Vulkan.
Mutation first failures (all restored):
1. Restoring the mesh shadow/celestial branch failed CommittedProductionAtmosphericReceivers_KeepAuthoredLightAcrossShadowGate at line 27: expected Pixel 128/128/128/255, actual 0/0/0/255.
2. Restoring the terrain shadow/celestial branch failed the same witness at line 28: expected Pixel 128/128/128/255, actual 0/0/0/255.
3. Normalizing the authored mesh direction failed AssertAuthoredHalfIntensity at line 277: expected 126..129, actual 255.
4. Removing atmospheric_volumetric.frag's celestial xyz use failed ProductionShadowAndVolumetricModules_StillNormalizeTheCelestialProjectionDirection at line 61: Assert.Single found no matching member-5 access.
5. Restoring IA-24's old celestial-base-light claim failed BuiltInAndDeclaredShadowGraphsUseTheSameTypedPriorVisibilitySelector at line 1482: the authored unnormalized uLights sole-base-light assertion was absent.
Record the exact sync2 COPY/TRANSFER_WRITE to HOST/HOST_READ buffer dependency over the copied readback range before ending and submitting the existing command buffer. Preserve coherent mapping and queue-idle completion.
Add a portable guard that inspects the descriptor factory used by the live ReadBack path and pins copy, descriptor, barrier, end, and submit order without initializing Vulkan.
Mutations performed and restored:
1. Removed the live post-copy barrier: the guard first failed with Expected copy -> descriptor -> barrier -> end -> submit, got 4260, -1, -1, -1, 4386, 4900.
2. Changed source access to TransferReadBit: the guard first failed Assert.Equal, expected Access2TransferWriteBit, actual Access2TransferReadBit.
3. Moved the correct barrier before the copy: the guard first failed with Expected copy -> descriptor -> barrier -> end -> submit, got 4657, 4260, 4525, 4594, 4783, 5297.
Tag the production offscreen witness as Lane=Vulkan, exclude capability-owned tests from the portable Release filter, and give lavapipe an explicit trait-only invocation after the portable Vulkan contracts.
Document the lane and pin witness, portable-filter, and workflow ownership without initializing Vulkan.
Mutations performed and restored:
1. Removed the witness trait: VulkanLaneOwnershipContractTests first failed Assert.Matches because the Lane=Vulkan/Fact/method pattern was absent.
2. Removed Lane!=Vulkan from the default filter: the contract first failed Assert.Contains, not found Lane!=Vulkan.
3. Weakened the dedicated invocation to Lane!=Vulkan: the contract first failed Assert.Contains, not found --filter Lane=Vulkan in the hardware step.
4. Narrowed the dedicated invocation with FullyQualifiedName: the contract first failed Assert.DoesNotContain because FullyQualifiedName was present at position 231.
Use the shared absolute base-instance domain only for mesh transforms and subtract the published transform prefix for every live ordinary sidecar. Regenerate the production module and pin its hash.
Add real ordered-recording, committed-SPIR-V structure, and headless production-Vulkan pixel witnesses at nonzero prefixes, including receiver active/inactive invariance.
Mutations performed and restored:
1. Transform lookup -> local instanceIndex: MeshModernSharedIndexSpirvTests first failed Assert.Contains, item 368 not found in [27,377,27] (and the pixel witness found 0 dark pixels).
2. Selection sidecar -> absolute transformIndex: MeshModernSharedIndexOffscreenTests first failed: Expected a dark local-sidecar instance, found 0 matching pixels.
3. Published TextureIndexB -> 0: SharedTransformPrefix recording first failed Assert.Equal, expected 3, actual 0.
4. Receiver choice inverted: inactive first failed because expected mesh_modern was absent and only mesh_atmospheric was recorded; active failed conversely.
5. Offscreen shader directory -> copied test output: witness first failed the exact-path Assert.Equal (expected repo src/AcDream.App/Rendering/Shaders/spv, actual tests/AcDream.App.Tests/bin/Release/net10.0/Rendering/Shaders/spv).
Restore the pre-c4 classic immediate path for transparent commands without active building detail: always use the shared alpha pipeline and coalesce adjacent same-cull commands across TranslucencyKind boundaries. Keep detail-active material selection, detail arm/reset, and inner cull-run splitting unchanged.
Required mutations and first discriminating failures (all restored):
1. Non-detail arm restored to PipelineForBlend: ClassicImmediate_NonDetailUsesAlphaBlendWhenDetailIsDisabledOrUnavailable first reported InvAlpha/detail-disabled at line 1258; expected wb-mesh-alpha-1x, actual wb-mesh-inverse-1x.
2. Translucency equality split restored with the alpha arm intact: ClassicImmediate_NonDetailCrossBlendRunCoalescesIntoOnePhysicalDraw failed at line 1279; Assert.Single saw 3 physical MDI records.
3. Detail-active command forced onto AlphaBlend: ClassicImmediate_DetailActiveUsesExactMaterialPipelineAndArm failed at line 1303; expected wb-mesh-raw-additive-depth-write-1x, actual wb-mesh-alpha-1x.
Add paired depth-write variants for all five blended SetSurface families in ordinary and atmospheric Wb pipeline sets at both sample counts and in EnvCell. Select them only from the carried AlphaTestEnabled state, retain pure-Clip and Translucent override behavior, and cover disposal plus partial-construction rollback.
Correct the three stale depth oracles and the AP register count/temporary AP-232 overclaim.
Required restored mutations and first discriminating failures:
1. Wb StraightAlpha+Clip collapsed to depth-off: WalkStaticStreamPopulatorTests.ImmediateBuildingDetail_UsesExactResolvedSetSurfaceState failed at line 1278; expected wb-mesh-alpha-depth-write-1x, binds were wb-mesh-alpha-1x.
2. EnvCell raw Additive+Clip collapsed to depth-off: EnvCellAlphaDrawSourceTests.DetailOn_EveryEnvCellFamilyDrawsOnceInPlaceWithAuthoredOpacity failed at line 132; expected envcell-raw-additive-depth-write, actual envcell-raw-additive.
3. EnvCell non-Clip raw Additive forced depth-on: the same production transcript failed at line 132; first row expected envcell-raw-additive, actual envcell-raw-additive-depth-write.
4. Late Translucent|Clip override forced alpha-test/depth-on: the same production transcript failed at line 132; expected envcell-alpha, actual envcell-alpha-depth-write.
Resolve exact SetSurface blend, alpha-test, and fog state once during extraction and preserve it through recipe-10 prepared payloads, Wb/EnvCell command data, Vulkan pipelines, push constants, and both ordinary/atmospheric one-pass shaders. Preserve AP-240 Wb pure-Clip immediate opaque/A2C while EnvCell uses retail premultiplied Clip; detail-off routing remains unchanged. Correct AP-232 and register the remaining detail-off state divergence.
Mutation first failures (all restored):
- raw Add->SRCALPHA/ONE: WalkStaticStreamPopulatorTests.ImmediateBuildingDetail_UsesExactResolvedSetSurfaceState line 1278, expected wb-mesh-raw-additive-1x.
- inverse-add->alpha-add: same test line 1278, expected wb-mesh-inverse-additive-1x.
- remove Env inverse: EnvCellAlphaDrawSourceTests.DetailOn_EveryEnvCellFamilyDrawsOnceInPlaceWithAuthoredOpacity line 132, expected envcell-inverse.
- raw IsAdditive precedence: same test line 132, Translucent|Clip|Additive expected envcell-alpha.
- Wb paletted ParamB=0: OrderPreservingSubmitterTests line 333, expected 0.392156869.
- Wb DDS ParamB=0.05: same test line 333, expected 0.784313738.
- disable Alpha+Clip test: WalkStaticStreamPopulatorTests line 1286, expected 0.784313738.
- always fog raw Add: same test line 1287, expected no-fog true.
- disable fog non-Add: same test line 1287, expected no-fog false.
- X=a*qA: RetailDetailTextureContractTests line 261, shared squared-alpha substring absent.
- X includes base alpha: same test line 262, forbidden baseTexel.a present.
- CLIP uses 0.05: EnvCellAlphaDrawSourceTests.ClipShaders_UseGreaterEqualForThePerRangeReference line 367.
- second detail draw: EnvCell detail-on line 131, collection contained 2 draws.
- straight-alpha substitute: Wb immediate line 1278, expected wb-mesh-additive-1x.
- omit ordered detail arm: OrderPreservingSubmitterTests line 318, expected (77,3.5), got (0,0).
- omit atmospheric combine: RetailDetailTextureContractTests line 260, shared include absent.
- drop serialized opacity: ObjectMeshDataSerializerTests line 292, expected opacity bits, got 1.0.
- stale detail arm: atmospheric adjacency line 402, expected slot 0, got 77.
- per-frame surface map: EnvCell warmed allocation line 285, expected 0 B, got 204800 B.
Replace the building and EnvCell detail replay with retail's exact single-pass stage result, including authored surface opacity, squared detail alpha, final-alpha clipping, and the original subset pipeline/order. Arm the ordered walk command in place to close#471, delete the replay pipelines/shaders, and advance prepared content to recipe 9.
Mutation witnesses (each restored before commit):
- X=a*qA: RetailDetailTextureContractTests.BothShaderFamiliesUseTheSharedOnePassSourceAndDebugPrecedesDetailSample line 174, missing materialAlpha * detail.a * detail.a.
- X*=base alpha: same test line 175, forbidden baseTexel.a found.
- CLIP against base alpha: EnvCellAlphaDrawSourceTests.ClipShaders_UseGreaterEqualForThePerRangeReference line 260, final-X conditional missing.
- second detail draw: EnvCellAlphaDrawSourceTests.DetailOn_EveryEnvCellFamilyDrawsOnceInPlaceWithAuthoredOpacity line 105, Assert.Single saw 2 MDI calls.
- straight-alpha substitution: WalkStaticStreamPopulatorTests.ImmediateBuildingDetail_RetainsOriginalFramebufferFamily line 1244, Additive first failed (only wb-mesh-alpha-1x recorded; InvAlpha also failed).
- omit ordered arm: OrderPreservingSubmitterTests.PrepareThenDraw_OrdinaryBuildingClipBuildingOrdinary_ArmsOnePassInPlace line 305, expected (77,3.5), got (0,0).
- omit atmospheric combine: RetailDetailTextureContractTests.BothShaderFamiliesUseTheSharedOnePassSourceAndDebugPrecedesDetailSample line 173, atmospheric shared include missing.
- drop serialized opacity: ObjectMeshDataSerializerTests.SurfaceOpacity_RoundTripsBitExactlyAndDeterministically line 288, first reported 0.5 bits 1056964608 vs 1065353216.
- stale detail arm: ordered adjacency test line 307, expected following ordinary (0,0), got (77,3.5).
- per-frame surface map: EnvCellAlphaDrawSourceTests.ProductionWholeLeaf_WarmedScanSubmitRhiAndFilteredReplayDoNotAllocate line 178, expected 0 B, got 147456 B.
Verification before commit: shader compiler 23/23; focused App 213/213; Content 75/75; Core Wb 10/10; launcher migration 6/6; Release solution build 0 warnings / 0 errors; git diff --check clean.
Borrow the exact prior-completed landscape visibility transaction and S2 CELLARRAY owner for the opt-in IA-24 directional-shadow pack. Select terrain by authored 1..64 cells, ordinary casters by CELLARRAY, and buildings by outdoor EffectCellId with no fallback.
Keep retained caster/material/terrain topology stable across visibility-only frames. Publish exact arbitrary active instance runs and bounded terrain commands through separate selection sequences; preserve transform-journal, fade/retry, deferral, shader/RHI, ordinary world, and pack-off behavior. Amend IA-24 and the S5 ledger.
Pre-commit gates: Release solution build 0 warnings/0 errors; focused visibility/frame/caster/prepared/GPU/terrain/pack lane 137/137; warmed caster/prepared/terrain selectors 0 B; git diff --check clean. Official hermetic and InstalledDat evidence intentionally run post-commit from this exact clean tree.
Mutation evidence (each restored exactly): (1) CELLARRAY->Parent first failed PriorLandscapeSelection expected [201,202,203,205], actual [204,205]. (2) all resident terrain first failed Assert.Single with 3 commands. (3) building EffectCell->anchor first failed expected trailing 205, actual 206. (4) admit missing membership first failed with extra 204. (5) completed->building scratch first failed completed-view Assert.True, expected true/actual false. (6) selection advanced BuildSequence first failed expected 1/actual 2. (7) alternating->prefix first failed active command count expected 3/actual 1.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Publish the exact completed walk landscape set as the typed retained particle-view product. Apply retail CLandCell membership versus constant-true non-null CEnvCell eligibility while preserving the x87 distance and AP-116 behavior.
Delete the null-root terrain visibility reconstruction and the dead drawable-cell point-light feedback chain. Retire AP-117, correct AP-85 and AD-21, and keep directional shadows, building degrade, AP-232, probes, RHI, shaders, and DAT outside this chunk.
Automated return: Release 0 warnings/0 errors; Core VFX 111/111; App particle/frame/renderer/terrain 146/146; warmed production allocation 1/1 at 0 B. First official hermetic artifact s5-c1-hermetic-20260904 is preserved at 16759/16760 with only the stale 162-row assertion; after the bounded 161 correction, exact pin 1/1 and fresh s5-c1-hermetic-corrected-20260904 16760/16760. InstalledDat 385 pass, 10 documented failures, 1 documented skip, no new identity.
Mutation 1: making EnvCell eligibility set-dependent first failed ParticleSystemTests.cs:553 Assert.True, expected true actual false. Mutation 2: making outdoor eligibility constant true first failed ParticleSystemTests.cs:559 Assert.False, expected false actual true.
Mutation 3: feeding the diagnostic union first failed WorldSceneRendererTests.cs:279 HashSet equality, expected [16842755], actual [16843008, 16842755]. Mutation 4: restoring CollectVisibleCells first failed TerrainParticleCellVisibilityTests.cs:37 and named TerrainModernRenderer.cs.
Mutation 5: restoring ObserveDrawableCells first failed TerrainParticleCellVisibilityTests.cs:37 and named WorldRenderFrameBuilder.cs. Mutation 6: changing the inclusive boundary from <= to < first failed ParticleSystemTests.cs:519 Assert.True, expected true actual false.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Remove the dead classic-group CachedBatch/InstanceGroup sort-center sidecar and its cache, append, reset, and diagnostic-digest plumbing. Remove the discarded camera parameter chain from grouped alpha deferral and dispatcher submission diagnostics while preserving walk/particle CYpt keys, FIFO submission ordinals, and opaque camera distance.
Add production-facing reflection/source pins for the deleted shapes and camera chain, retained per-cell and opaque owners, the exact three AlphaFlushCounts reasons, and deleted alpha-order symbols. Update directly affected cache/digest/group lifecycle tests and record the complete section 22 result.
Gates: Release 0W/0E; focused App 132/132 plus Core 29/29; new pins 4/4; real allocation pins 2/2 at 0 B; shaders 32/32; hermetic 16755/16755; InstalledDat exactly 385 pass, 10 documented fail, 1 skip; diff-check clean.
Mutations: InstanceGroup and CachedBatch sidecars fail their Assert.Null pins; Defer and digest camera parameters fail method-shape pins; false count prose fails the exact-reason pin; RetailAlphaOrdering resurrection fails the deletion pin. Each was restored independently before the final matrix.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Retain the GfxObj-authored SortCenter independently from the rendered AABB center and use its scaled, oriented world point for billboard/mode-2-5 CYpt. Keep the visual center unchanged.
Reserve prepared source payload only at Append, roll back the exact tail once on false or exception, cap retained preparation at 3000 CLIP plus 3000 ALPHA, preserve immediate duplicates, account the bounded scratch, and correct the static-record ordering comment. The lead-authorized plan edit records candidate 44e2bc227b's retail FAIL and this section 19 result.
Gates: Release solution build 0 warnings/0 errors; focused particle/driver/queue/order/bounds 95/95; register boundary/count 1/1; real allocations 2/2 at 0 B; shader/manifest 32/32; one-shot hermetic 16751/16751; InstalledDat 385 pass/10 documented fail/1 skip; diff-check pass. No graphical client.
Mutations: rendered pos first failed distance expected 40 actual 116; AABB center expected 40 actual 116; dropped size expected 40 actual 65; dropped orientation expected 40 actual 160; eager reservation first failed Assert.Empty with one payload; omitted false rollback first failed Assert.Single with two payloads; omitted exception rollback first failed Assert.Empty with one payload; removed caps first failed prepared count expected 6000 actual 6002.
Evidence note: the first ordinary InstalledDat filter produced 385 pass/2 expected fail/1 skip but excluded the eight required KnownFailure rows; the separate inclusive no-retry artifact produced the exact documented 10-failure set. Initial test fixture compilation/analyzer setup failures are recorded in packet section 19.5.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Reconstruct one combined static/dynamic object-part stream for each ordinary outdoor or interior cell, compute authored SortCenter CYpt keys, and stable-sort far to near before projecting opaque and delayed subsets. Prepare real cell-particle records at the leaf, preserve every S4-c2 router outcome, and merge object and particle delayed records by retained key before either source appends to the unchanged CLIP/ALPHA FIFO lists. Cell turns remain cell-major; equal cross-source ties are deterministically object-first.
File AP-241 and AP-242 for the remaining separate opaque/row-5 channels and unrepresented equal-key common ordinal. File AP-243 for the paired-binary correction: retail shares the cell CYpt/heading beyond 50 m while this bounded port always uses the more exact authored per-part center. Pin 162 active AP rows and correct world-alpha and AlphaFlushCounts prose.
Lead-approved scope clarification: RetailPViewPassExecutor.WalkLeaf.cs and RetailPViewPassExecutor.cs are the minimum existing production leaf adapter and thin particle-prepare forwarder omitted by the literal Walk/Wb file list. They contain no router, queue, mask, state, depth, or flush behavior; relocating them would create an artificial seam.
Gates: Release solution build 0W/0E; shader/manifest 32/32; focused production 210/210; real allocation 3/3 at 0 B; one-shot hermetic 16743/0/0 across 14 assemblies; InstalledDat 385 pass/10 documented fail/1 skip with all six AlphaFlushSites passing; git diff --check PASS. Initial no-restore solution build failed NETSDK1004 for 42 missing scratch assets; one solution restore preceded the official build.
Mutation proof, each restored before final gates:
1. Reverse comparator: authored-center order expected [202,101], actual [101,202].
2. Move ties left: multipart/subset order expected [11,12,21,22], actual [22,21,12,11].
3. Restore static/dynamic blocks: expected [2,3,1], actual [3,1,2].
4. Use entity origin: authored-center order expected [202,101], actual [101,202].
5. Restore particle tail: expected [Wb,Particle,Wb,Particle], actual [Wb,Wb,Particle,Particle].
6. Scope-global sort: first cell model X expected 5, actual 50.
7. Restore dead camera parameter: SubmitWalkAlphaInstance parameter count expected 2, actual 3.
8. Restore stale global-queue prose: exact Assert.DoesNotContain failure on distance-sorts one shared queue.
9. Remove AP-241 identity: Assert.Single found no matching row.
10. Allocate in real merge: expected 0 B, actual 3072 B.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Exclude deferred EnvCell subsets from the opaque turn, preserve exact CLIP/ALPHA fixed-state groups through leaf replay, and use retail's row-3 override, blend, depth, and texture-class alpha references. Keep the existing building-detail sentinel distinct from the two CLIP references.
Bound rejected source payload, replace no-op allocation proofs with actual EnvCell and particle RHI paths, rebuild checked-in SPIR-V, and correct AP-238/AP-240 plus the S4-c2 evidence record.
Gates: Release 0W/0E; hermetic 16735/0/0; InstalledDat 255 pass/10 known fail/1 skip; shaders 32/32; focused 239/239; allocation 2/2 at 0 B. The evidence/comment repair re-ran Release 0W/0E, shaders 32/32, affected 37/37, and allocation 2/2; mesh_detail.vert.spv remained byte-identical at SHA-256 5346247ab7d606046943e19b28888c814e08dc6cb27cd9750096ac055457eb57.
Mutation proof, with each mutation restored after its named first failure:
1. Restoring the opaque predicate to !IsAdditive fails WholeLeaf_MixedCellDrawsOpaqueAtTurnThenClipAndAlphaAtDrain first at draw count: expected 1, actual 3.
2. Selecting _alphaPipeline for CLIP fails WholeLeaf_ClipDrainBindsExactStateAndTextureClassReference first at the bind sequence: expected [envcell-clip], actual [envcell-alpha].
3. Disabling CLIP depth write fails that production Theory first at Assert.True(clipPipeline.Depth.Write): expected true, actual false.
4. Swapping palette/DDS references fails the DDS row first: expected 0.784313738, actual 0.392156869; the palette row reports the inverse.
5. Mutating mesh_modern.frag from < to <= fails ClipShaders_UseGreaterEqualForThePerRangeReference first at Assert.Contains("if (color.a < alphaCutoff) discard;"): the required source spelling is absent.
6. Restoring row-3 OverrideClipmap=true fails the real-interface clip Theory first at Assert.False: expected false, actual true.
7. Deleting failed-append rollback fails the flush/end/abort rejection-storm rows first at the bounded pending-count assertion: expected 0, actual 9000.
8. Selecting _transparentDetailPipeline for a ClipMap detail contribution fails the leaf detail pin first at the bind sequence: expected second bind envcell-retail-detail-clip, actual envcell-retail-detail-alpha.
9. Resetting detail ParamB to zero fails the same detail pin first at the second pushed reference: expected 0.784313738, actual 0.
10. Classifying CLIP with exact mask equality excludes legal 0x09 and fails WholeLeaf_PositiveStippleClipMaskUsesClipPipelineAndDdsReference first at pipeline: expected envcell-clip, actual envcell-alpha.
11. Mapping the new blend to SRC_ALPHA/INVSRCALPHA fails AllRetailBlendModesAreRepresentable first at the tuple: expected (One, OneMinusSrcAlpha), actual (SrcAlpha, OneMinusSrcAlpha).
12. Restoring mesh_detail.vert's uParamB > 0.5 category predicate fails ClipShaders_UseGreaterEqualForThePerRangeReference first because vDetailCategory = uParamB == 1.0 is absent.
13. Treating every positive detail uParamB as a cutoff fails that source pin first because isRetailClipReference(uParamB) ? uParamB : 0.05 is absent.
14. Adding arbitrary 0.5 as an accepted reference to either mesh_modern.frag or mesh_detail.frag fails that source pin first at Assert.DoesNotContain("value - 0.5"); both mutations were run and reversed independently.
Retail: D3DPolyRender::SetSurface @ 0x0059c4d0; paired binary @ 0x0059c72a, 0x0059c747/0x0059c74f, 0x0059c821, 0x0059c838, 0x0059c866.
Final allowed fix round for S4-c2 on cc8e5677a. This lands every item in
the campaign packet section 12 without adding a flush site, shader, distance,
overflow recovery draw, or graphical-client run.
R2-1 particle row 5: ParticleRenderer now constructs and owns the actual
particle-mesh-opaque pipeline using the existing particle_mesh shaders and
layout, Blend=None, depth test/write enabled with WorldCompare, dynamic
per-batch cull, clockwise front face, and no alpha-to-coverage. The production
dispatch selects it for an opaque-classified mesh particle whose clamped
material alpha is 1.0. Nonopaque mesh pipelines remain depth-write-off. The
production route keeps cached reserve/immediate delegates and the warmed
append/immediate paths allocate 0 B.
R2-2 EnvCell exact per-subset routing: ObjectMeshManager carries Content's
TextureBatchData.RetailSurfaceMask onto ObjectRenderBatch at the real upload
boundary. EnvCellRenderer scans the active prepared cell snapshot and feeds
each real transparent batch's exact mask through RetailAlphaMeshRouter. Pure
0x08 Base1ClipMap reaches CLIP, 0x02 alpha-family reaches ALPHA, and table
Immediate subsets draw at the cell turn. Separate fixed-route draw sources
coalesce to at most one token per (cell,list) and filtered replay draws only
that list's subsets; a mixed cell contributes to both lists without duplicate
replay. Detail-on routes eligible subsets immediately with the detail pass.
The warmed dispatch/source allocation pins measure 0 B; the production scan/filter is covered behaviorally and uses only retained scratch/enumerators (static allocation audit).
R2-3 capacity cleanup: RetailAlphaQueue registers a source before the 3,000
entry capacity return. A source whose first append is rejected is therefore
reset by flush, EndFrame, or abort, but its rejected payload is never prepared
or drawn.
R2-4 production proof and prose: both actual Wb submit sites are exercised;
the particle tests call the production dispatcher and inspect the constructed
owner's production pipeline/selector; EnvCell tests upload real Content batch
masks through ObjectMeshManager and drain real filtered MDI calls. The A1
positive proof executes WorldSceneRenderer's real outdoor frame owner through
RetailPViewRenderer.DrawInside and RetailAlphaQueue.EndFrame and observes
[DrawBuilding x N, RenderNormalMode] with no LandscapeFlush. The packet and
register now state the varying retail first-for-list truth and the exact
per-subset EnvCell/AP-238, visible AP-239 compositing, and AP-240 feeder scope.
Physical active register counts remain AP=159 and AD=92.
Final clean-state gates (actual output):
- Release solution build: Build succeeded; 0 Warning(s); 0 Error(s).
- Hermetic solution filter: every project green, 16,728 passed / 0 failed /
0 skipped total; AcDream.App.Tests 6,875/6,875.
- InstalledDat: 255 passed / 10 failed / 1 skipped / 266 total, exactly the
allowed identities: TowerAscent_StaircaseStaysConeVisible_EveryStep;
MainGameUiAndChatInput_MediaBearingChildrenNowBuildAsRealWidgets (#383);
EveryAuthoredInvisibleWidget_StartsHiddenAcrossAllLayouts (#383);
Oh_doorway_still_first_frame_diff (#458); and the six
AlphaFlushCounts_{CathedralArrival,CathedralLeak,CathedralStairArch,
FoundryDeep,HoltburgDoorwayStill,TerraceEdge}_MatchesRetailFrame2. All six
AlphaFlushSites_* pass in the same lane.
- VulkanShaderDescriptorContractTests + VulkanShaderManifestTests +
RenderPackSpirvValidatorTests: 32 passed / 0 failed / 0 skipped.
- Corrected queue/router/walk/driver/particle/Wb/EnvCell/PView production
filter: 211 passed / 0 failed / 0 skipped.
- Explicit warmed production allocation pins: 2 passed; both measure 0 B.
- Register: physical AP-238/AP-239/AP-240/AD-120/~~AP-34~~ rows each count
exactly 1; active physical rows AP=159 and AD=92.
- git diff --check: PASS.
Production mutation checks (each applied, run to the named first failure, and
exactly reversed before the final gates):
1. Restoring the particle row-5 throw fails
OpaqueClassifiedMeshBatch_WithNoMaterialAlpha_DrawsImmediateOnOpaqueDepthState
first with InvalidOperationException: mutation: row 5 unreachable.
2. Deleting row 5's immediate callback fails that same production-dispatch
test's first collection assertion: expected [(Mesh, 11, True)], actual [].
3. Constructing the actual owner pipeline with depthWrite:false fails
ImmediateOpaqueMesh_UsesProductionParticleMeshOpaquePipelineDescription
first at Assert.True(description.Depth.Write): expected true, actual false.
4. Mapping the production selector back to _meshAlphaPipeline fails that same
test first at Assert.Same: expected particle-mesh-opaque, actual
particle-mesh-alpha.
5. Dropping RetailSurfaceMask at the real ObjectMeshManager upload boundary
fails the mixed-cell production scan first: expected Clip | Alpha, actual
Immediate.
6. Hardcoding the uploaded EnvCell scan to MaskAlphaFamily fails the mixed-cell
production scan first: expected Clip | Alpha, actual Alpha; the pure-mask
pin also reports expected Clip, actual Alpha.
7. Inverting the production detail predicate fails the detail-on production
pin first: expected Immediate, actual Clip.
8. Removing both EnvCell replay filters fails the mixed production drain's real
MDI assertions: each call expected DrawCount 1, actual DrawCount 2.
9. Moving RegisterSource below the full-capacity return fails every
RejectedFirstUseSource_IsCleanedWithoutPrepareOrDraw row (flush, EndFrame,
abort) at the first ResetCount assertion: expected 1, actual 0.
10. Hardcoding detailSurfaceActive=false at the real Wb dispatch fails both
production submit-site tests at their first queue-count assertion:
expected 0, actual 1.
11. Restoring RetailPViewRenderer's removed outdoor LandscapeFlush call fails
OutdoorProductionPView_DrainsBuildingThenRenderNormalModeWithoutLandscapeFlush
first at the real drain sequence: expected [DrawBuilding,
RenderNormalMode], actual [DrawBuilding, LandscapeFlush,
RenderNormalMode].
12. Deleting WorldSceneRenderer's final EndFrame owner drain fails that same
full-path A1 test first: expected [DrawBuilding, RenderNormalMode], actual
[DrawBuilding].
Not done/deferred: none. No retail conflict or infeasible contract item was
found. No graphical client was launched.
Co-Authored-By: Codex <noreply@openai.com>
Fix round 1 of the two the plan allows for S4-c2 (048d5b12f), addressing
every item M1-M8/A1-A8 from the campaign branch's packet section 11.
M1 (BLOCKING): ParticleRenderer.DeferToRetailAlphaQueue threw whenever a
mesh-particle batch's TranslucencyKind.Opaque classification (mask 0x00)
reached a non-Append DrawMesh row - a real, reachable outcome since
TryAppendMeshDraws applies no IsOpaque-style pre-filter unlike
WbDrawDispatcher's path. Deleted both throws (ParticleRenderer.cs,
WbDrawDispatcher.cs SubmitToAlphaQueue), extracted the router-input
derivation into internal static ParticleRenderer.RouteParticleSubmission
(directly testable), and implemented rows 1/5's immediate draw by reusing
the existing per-instance billboard/mesh draw code (new
DrawImmediateParticleSubmissionRhi, no new renderer, no sort per A8).
materialHasAlpha (row 4) is derived from CMaterial::CheckAlphaValues
@0x005396a0 (Ghidra-verified 2026-09-04): has_alpha=0 only when
Ambient.a==Diffuse.a==Specular.a==Emissive.a==1.0f, else 1 - and
CMaterial::SetTranslucencySimple @0x005396f0 (reached from
CPhysicsPart::SetTranslucency <- Particle::Update @0x0051c290's per-frame
alpha-fade) sets all four channels to (1f - translucency) uniformly, so
has_alpha == "this particle's current alpha isn't 1.0" - already carried in
ColorArgb's top byte (Lerp(StartAlpha,EndAlpha,tLife), ParticleSystem.cs
1039-1041/1453).
M3: SubmitToAlphaQueue now takes isBuildingShell + viewProjection and feeds
Row 1's detailSurfaceActive gate for translucent building-shell instances
(retail DrawBuilding @0x0059f2a0 installs building detail before its own
shell subsets reach DrawMesh). Row 1 (detail on) draws the instance
immediately at its own walk-stream AlphaSubmitMark via new
DrawImmediateAlphaInstance/DrawImmediateAlphaInstanceRhi (reusing
WriteDeferredAlphaEntrySlot + the drain's pipeline/detail code for slot 0);
never touches _deferredAlpha/the queue for pure-Immediate submissions (fixes
a latent leak: previously reserving a _deferredAlpha slot for every
candidate meant a frame with ALL-immediate content never registered a
source, so ResetAlphaSubmissions never ran). M3c: DrawPreparedAlphaBatchRhi's
own per-command hasDetail/DrawBuildingDetailRangeRhi branch is deleted -
detail-eligible content no longer reaches the drain (spec: delayed replay
is always detailEnabled=0). New AD-120 register row for the "in place -> at
the entity's own stream mark" granularity adaptation this introduces.
M6: RetailPViewPassExecutor.SubmitOrDrawTransparentCellShell routes an
EnvCell's transparent shell through the SAME RetailAlphaMeshRouter table
(mask 0x02, detailSurfaceActive = EnvCellRenderer.TransparentDetailEnabled)
instead of a hand-coded branch. Detail on: unchanged immediate draw. Detail
off: appends ONE token per CELL via new EnvCellAlphaDrawSource (internal,
delegate-based so it's unit-testable without a GPU harness), replayed
through EnvCellRenderer.RenderTransparentOrdered at the next flush -
RenderTransparentOrdered already replayed a cell's batch on demand, so no
new deferred-replay abstraction was needed (the original landing's claim
that one was required is corrected in the packet).
M4: RetailAlphaEntry.IsFirstForList deleted. Retail's per-DrawMesh-
invocation "first for this list" flag is trivially true for every subset
retail ever appends (DrawMesh @0x0059d4a0 sets both flags true at entry,
0059d4cc/0059d4d0, clearing each independently after ITS OWN first append
within that call, 0059d5ef) - the captures confirm it (new=1 on 9,685/10,556
AM lines, impossible under "first since last drain"). acdream's deleted
isFirstForList = target.Count == 0 computed the wrong quantifier. One
acdream append already IS one DrawMesh call, so the field carried no
information and its own pin was vacuous (feedback_every_new_pin_must_be_
shown_to_fail) - deleted rather than kept as an always-true no-op.
M5: split all six AlphaFlushTranscript_* KnownFailure Facts into live
AlphaFlushSites_* (site+threshold sequence only, content-independent,
unconditional pass) and AlphaFlushCounts_* (KnownFailure, full tuple incl.
drained counts) sharing one RunAlphaFlushTranscriptReplay helper. Rewrote
the KnownFailure reason honestly: THREE independent causes, not "harness
content only" - (1) zero live mesh content, (2) acdream appends one entry
per INSTANCE vs retail's one per SUBSET per DrawMesh call, (3) acdream's
CLIP list is structurally empty for ordinary content (M2). Added ONE live
valve pin (WalkFrameDriverTests.SortCellExit_ValveDrainsThroughReplayAtThe
ExactBoundary, a Theory over 2250/2249) that goes through WalkFrameDriver.
Replay's own SortCellExit arm, not RetailAlphaQueue.Flush called directly.
M2/A2/M8c (register): ~~AP-34~~ restored as struck-through RETIRED (it had
been deleted outright, not struck, breaking the register's own convention)
citing residuals AP-238/AP-239/AP-240. The chunk-2 commit's "AP-236"/
"AP-237" collided with an id ALREADY filed-and-retired on main by an
unrelated #132 fix (docs/ISSUES.md:19976) - ids are never reused, so both
are renumbered: AP-236->AP-238 (EnvCell; rewritten per M6 - granularity-only
residual now, "never through the shared queue" is retired), AP-237->AP-239
(mask-reconstruction gap; the dangling "AP-236b" citation in
RetailAlphaMeshRouter.cs never matched either filed id and is corrected to
AP-239). New AP-240 filed for M2: WbDrawDispatcher.IsOpaque filters
ClipMap out upstream, so ordinary clip-mapped content never reaches
retail's CLIP FIFO (draws immediately on the pre-existing
OpaqueAlphaToCoverage pipeline instead) - no pipeline change this round,
per the contract. AP section header recounted honestly: 159 active (was
stale at 161); AD section 92 (was 91, +AD-120).
M7: AP-239's only cited example (cloud GfxObj 0x01004C35, surface
0x08000023) is DISPROVEN - decoded Type=0x10114 has alpha-family bits
that win in BOTH retail's real priority and acdream's reconstruction, so it
never actually diverges. A 2026-09-04 scan of all 6,152 Portal-dat Surfaces
found 27 real Translucent+Base1ClipMap-no-alpha-family instances
(Type=0x14); cross-referencing GfxObjs found surface 0x08000015 on GfxObj
0x010001EC - cited in place of the disproven cloud example.
M8: (a) RenderDeviceD3D::DrawBlock is @0x005a17c0 per symbols.json, not
@0x005a18d0 (that is its per-land-cell loop head) - fixed across
RetailAlphaQueue.cs, RetailPViewPassExecutor.cs, WalkEvents.cs,
WalkFrameDriver.cs (x3), RetailFrameWalk.cs. (b) RetailAlphaQueue.cs's four
FlushAlphaList addresses were labelled "return site" but are the CALL
instruction's own address (x86 CALL rel32 is 5 bytes) - relabelled "call at
X (returns to X+5)". (d) worldbuilder-inventory.md:420's AP-34 citation
updated.
A1: RetailPViewRenderer.cs's outdoor-root passes.FlushLandscapeAlpha() call
(labelled LandscapeFlush) is deleted - retail's real LandscapeFlush
(PView::DrawCells @0x005a4840's own FlushAlphaList(0f) @0x005a4872) sits
strictly inside if (outside_view.view_count > 0) on the INTERIOR PView; an
outdoor root never reaches it (terrace-edge capture: zero 005a4877 FL
lines). Verified no opaque draw intervenes between this deletion point and
RetailAlphaQueue.EndFrame()'s own RenderNormalMode pass-end flush
(WorldSceneRenderer.cs: DrawPostWorldParticles's PView branch is an
explicit no-op; only diagnostic wireframes follow) - #132's "drain after
the finished opaque world" invariant is kept by that flush alone.
A4: RetailAlphaQueue's ApplyScratchRetention call sites passed the ENTRY
count as the SOURCE-count argument too (observedClip + observedAlpha
twice), so _sources/_sourceDrawOffsets never shrank toward the real source
count. Fixed to capture _sources.Count before Clear().
A5/A6/A7: WalkStaticStreamPopulatorTests.cs's stale "same viewer distance"
banner corrected; two off-by-one entry counts in the packet's section 10
sequences corrected (foundry-deep x10->x11, holtburg-doorway-still x5->x6);
WalkAlphaDepthTrace.cs's "new=" comment explains why IsNew is retained but
never compared (M4's finding makes a per-subset comparison meaningless at
acdream's per-instance granularity); RetailAlphaMeshRouterTests.cs's
IndependentReferenceRoute (never actually independently-shaped) renamed to
RestatedBranchTableRoute with an honest doc comment, and the unverified
"flipped in turn and failed every time" claim deleted.
A3 was resolved as a side effect of M1's SubmitToAlphaQueue doc-comment
rewrite (the wrong "0x02/0x03, 0x04/0x05, 0x08/0x09" enumeration no longer
exists); RetailAlphaMeshRouter.cs's own MaskFromTranslucencyKind comment
updated in the same spirit (M1's particle-site Opaque-reachability finding).
Mutation checks (each applied to a scratch-restored copy, confirmed
failing, then byte-exact reverted - diffed against a /tmp backup after
restore):
- A1 (RetailPViewRendererTests.DrawInside_NeverCallsFlushLandscapeAlphaDirectly):
restoring the deleted "if (ctx.RootCell.IsOutdoorNode)
passes.FlushLandscapeAlpha();" line reintroduces a direct
RetailPViewPassExecutor.FlushLandscapeAlpha call in DrawInside's compiled
body. Actual failure: "Assert.DoesNotContain() Failure: Filter matched in
collection ... CompiledCall { ... Target = Void FlushLandscapeAlpha() }".
- A4 (RetailAlphaQueueTests.RetainedSourceCapacity_ConvergesToTheReal
SourceCountNotTheEntryCount): reverting both ApplyScratchRetention calls
to (observedClip + observedAlpha, observedClip + observedAlpha) - with
100 distinct sources in the spike frame (so _sources.Capacity grows to
128, past its initial 4) followed by three low-demand frames of 10
entries via ONE repeated source - makes sourceTarget compute from 10
(Math.Max(4,10*2)=20) instead of 1 (Math.Max(4,1*2)=4). Actual failure:
"Expected the retained source-array capacity to converge toward the real
source count (1), but it stayed at 20". (A single-source-only spike does
NOT discriminate this bug - Math.Min(sourceTarget, _sources.Capacity)
clamps both formulas to the same unchanged initial 4 - hence the
100-source setup.)
- M5 valve (WalkFrameDriverTests.SortCellExit_ValveDrainsThroughReplayAt
TheExactBoundary, 2250 case): flipping RetailAlphaQueue.Flush's no-op
comparison from "<" to "<=" (S4-c1/c2's own boundary mutation) makes the
2250-entries case a no-op instead of draining. Actual failure:
"Assert.Equal() Failure: Values differ Expected: 0 Actual: 2250".
- M6 (EnvCellAlphaDrawSourceTests, both Facts): adding an immediate
renderTransparentOrdered(new[]{cellId}) call inside
EnvCellAlphaDrawSource.AddPendingCellId (simulating the M1/M3 "drew
immediately instead of deferring" bug class) makes
PendingCellToken_IsDrainedAtTheFlushAndNeverBeforeIt fail with
"Assert.Empty() Failure: Collection was not empty Collection: [[4660]]"
and ParticleAppendedBetweenTwoCellTokens_KeepsItsPositionInTheCombined
Drain fail with 'Expected: string[] ["cell:256", "particle:7",
"cell:512"] Actual: List<string> ["cell:256", "cell:512", "cell:256",
"particle:7", "cell:512"]'.
- M1 (ParticleRendererRouteTests.OpaqueClassifiedMeshBatch_WithMaterialAlpha
_RoutesToAlphaAppend): hardcoding materialHasAlpha to false inside
RouteParticleSubmission makes the Opaque-mask+has-alpha case route
Immediate instead of Append. Actual failure: "Assert.Equal() Failure:
Values differ Expected: Append Actual: Immediate".
Gates (real output lines):
- dotnet build AcDream.slnx -c Release -> "Build succeeded. 0 Warning(s)
0 Error(s)".
- Hermetic lane (--filter "Lane!=InstalledDat&Lane!=PreparedPackage&
Lane!=Live&Lane!=Manual&Lane!=Timing&Lane!=Windows&Lane!=Linux&
Lane!=SystemFont&Purpose!=Diagnostic&Status!=KnownFailure") ->
"Passed! - Failed: 0, Passed: 6864, Skipped: 0, Total: 6864".
- InstalledDat lane (ACDREAM_RUN_INSTALLED_DAT_TESTS=1,
ACDREAM_DAT_DIR="C:/Users/erikn/Documents/Asheron's Call",
--filter "Lane=InstalledDat") -> "Failed: 10, Passed: 255, Skipped: 1,
Total: 266" - the exact 4 pre-existing failures
(TowerAscent_StaircaseStaysConeVisible_EveryStep,
LayoutImporterMediaBearingChildSweepTests, LayoutImporterInvisibleSweep
Tests, Oh_doorway_still_first_frame_diff) plus the six
AlphaFlushCounts_* KnownFailure Facts, nothing else; all six
AlphaFlushSites_* Facts pass inside this same lane (the whole
WalkTraceConformanceTests partial class carries Lane=InstalledDat, so
they cannot run hermetically).
- Shader classes (VulkanShaderDescriptorContractTests,
VulkanShaderManifestTests, RenderPackSpirvValidatorTests) -> "Passed! -
Failed: 0, Passed: 32, Skipped: 0, Total: 32".
- RetailAlphaQueueTests, RetailAlphaMeshRouterTests, RetailFrameWalkTests,
WalkFrameDriverTests, ParticleRenderer*Tests, EnvCellRenderer*Tests
(combined filter) -> "Passed! - Failed: 0, Passed: 120, Skipped: 0,
Total: 120".
- Register collision grep: AP-238/AP-239/AP-240/AD-120 each count exactly
1; ~~AP-34~~ count exactly 1; live totals AP=159, AD=92.
Not done / deferred: none - all M1-M8/A1-A8 items landed; A8 was a
no-action note.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replaces the single scope-global distance-sorted RetailAlphaQueue with
retail's own two independent FIFO lists (CLIP/ALPHA, capacity 3000 each,
D3DPolyRender::AddMeshToAlphaList's exact append-only/capacity-drop
behavior — Ghidra-verified 2026-09-04), routed by a new
RetailAlphaMeshRouter porting DrawMesh's five-row immediate/delayed
branch table and ConstructMesh's subset-mask formula as pure functions,
and drained at retail's four normal-world FlushAlphaList sites
(DrawBuilding/DrawBlock/PView::DrawCells/RenderNormalMode) under the
exact Ghidra-verified no-op predicate (both counts strictly below
threshold*3000). A new WalkFrameEventKind.SortCellExit /
IWalkEventSink.OnSortCellExit / IWalkFrameLeafRenderer.FlushSortCellExit
fires once per admitted land-block cell for DrawBlock's 0.75f valve,
pinned by a dedicated far/near ordering test in RetailFrameWalkTests.cs.
WbDrawDispatcher's two submit sites and ParticleRenderer's one route
through the router; since none of the three ever draws during the Sky
leaf, installs a detail surface, or sets MultiPassAlpha, rows 1/2/4/5
are provably unreachable there and the call sites assert loudly rather
than building unexercisable immediate-draw plumbing. FlushFartherThan,
RetailAlphaOrdering.ComputeViewerDistance, and every viewerDistance
argument on the submit path are deleted.
Scope note (packet s4-depth-alpha-packet.md §10): C4 (routing EnvCell's
transparent shell batches through the shared queue) was not attempted —
EnvCell draws one per-cell MultiDrawIndexedIndirect call with no
per-subset deferred-replay abstraction, and building one without visual
verification (no graphical client in this worktree) was judged out of
this bounded chunk's scope. AP-34 is therefore retired and replaced by
two narrower rows rather than deleted outright: AP-236 (the carried-
forward EnvCell-immediate residual) and AP-237 (a newly identified gap:
TranslucencyKind.AlphaBlend can arise from either retail's Alpha/
Translucent bits, mask 0x02/ALPHA, or the Translucent+ClipMap "cloud"
override, mask 0x08/CLIP — GroupKey doesn't retain the raw bit to tell
them apart, so the router always picks ALPHA; only known example is
cloud GfxObj 0x01004C35). Both are compositing-order-only divergences,
never blend/visual ones.
Mutation checks (each applied, confirmed failing, then reverted):
- FIFO drain order reversed -> 5 RetailAlphaQueueTests fail (order).
- FlushAlphaList `<` -> `<=` -> boundary/scratch tests fail (2250 case
reads drained=0 instead of 2250).
- Capacity check loosened (3000 -> 6000) -> overflow-drop test fails
(TryAppend returns true, PendingCount reads 3001).
- IsFirstForList forced true -> flag test fails once inspected on the
pre-flush two-entry snapshot (the post-flush single-survivor version
of this test was vacuous and rewritten).
- Router row 3 condition inverted -> both the hand-traced Theory (6
cases) and the 160-cell independent-truth-table brute force fail (20
mismatches).
- SortCellExit emitted before OnLandscapeCellTurn instead of after ->
RetailFrameWalkTests ordering pin fails ("SCX must immediately follow
its own cell's SC").
- Prepare-per-list instead of prepare-once-combined -> the CLIP/ALPHA
boundary batching test throws (index out of range).
Gates: Release build 0 warnings/0 errors. Hermetic lane (Lane!=Installed
Dat&...&Status!=KnownFailure) 6855/6855 passed. InstalledDat lane 249
passed / 10 failed — exactly the 4 pre-existing failures (#383 x2
LayoutImporter, TowerAscent KnownFailure, #458 Oh_doorway_still
KnownFailure) plus 6 NEW KnownFailure Facts
(AlphaFlushTranscript_*_MatchesRetailFrame2, one per capture) extending
this gate from PM/PC to AM/FL: the flush SITE+THRESHOLD sequence matches
the capture exactly for all six poses (including zero SortCellExit
drains in every capture, confirming the 0.75 valve is inert at these
scene complexities in both retail and this replay); the drained-COUNT-
per-list dimension diverges because this hermetic harness (matching the
existing PM/PC gate's own EmptyAlphaDepthWorldData design) carries no
live GfxObj/particle content, so every observed count reads (0,0)
against retail's real per-frame volume — both sequences quoted in full
per pose in the packet's new §10. Shader classes (VulkanShaderDescriptor
ContractTests/VulkanShaderManifestTests/RenderPackSpirvValidatorTests)
32/32 passed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Round 2 (9ba8f013e) passed the retail and production lenses and every gate
and mutation was reproduced by all three; the gate-honesty lens failed it
on comment truth only: the terrace-edge Fact repeated round 1's false
'PC already matched' sentence, the sixth Fact claimed a 12/4 guard split
and a preamble seed the gate does not evaluate, and WalkVisibilityMath's
scan note cited a pin that does not exist and a callers argument that
was not the real justification. A third implementer round is barred by
the campaign rule, so the lead corrected the sentences at landing.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Round 1 (2bd353234) was three-lens reviewed: retail-faithful PASS,
production PASS, gate-honesty FAIL. Every finding landed on the
terrace-edge KnownFailure row plus one pin gap. This is the last round.
R2-1 - gate honesty. WalkTraceConformanceTests.AlphaDepthTranscript.cs's
own KnownFailure doc comment claimed "the PC sequence matches exactly";
it never did - Assert.Equal(expected.PmEvents, actualPm) threw first, so
the PC Assert.Equal line never ran. RunAlphaDepthTranscriptGate now
computes BOTH diffs (SequenceEqual, not Assert.Equal) before asserting
either, and Assert.Fail's message prints both sequences together
whenever either diverges - a written claim about one sequence is now
only ever one this method actually evaluated. Re-running the OLD
(unseeded, cold PortalsDrawnCount=0) code path through this new combined
assertion shows terrace-edge's real divergence was WORSE than round 1
believed: both PM and PC diverged (PC expected counter=2, actual
counter=0) - round 1's "PC matches" claim was not just untested, it was
false.
R2-2 - the harness fix. Retail's portalsDrawnCount (wo(008719b4)) is a
PERSISTENT session global; each capture's own FIRST PM or PC line
(cathedral-arrival 0, cathedral-leak 0, foundry-deep 1,
holtburg-doorway-still 2, terrace-edge 2, cathedral-stair-arch 8) already
carries the pre-capture value - terrace-edge's own sample sits in the
"F 1" preamble text a cdb session prints before the first parsed frame
marker (line 1209, ahead of "F 1" at line 1298), so no amount of
replaying the CAPTURED frames from a cold start can ever reach it (this
fixture's own turns never touch the counter - every PC line reads
ov=0). New WalkAlphaDepthTrace.LoadInitialCounter(root, fixtureName)
scans a fixture's raw lines, ignoring frame boundaries, for the first
PM/PC match and returns its counter. RunAlphaDepthTranscriptGate seeds
WalkFrameDriver.PortalsDrawnCount (already an internal, test-assembly-
settable field) from this value, runs ONE Collect+Replay pass, and
deletes the two-pass "priming" replay outright. Result: terrace-edge
turns GREEN with no KnownFailure tag; every other pose stays exact
(a fixture with real steady-state seal activity computes the identical
value a priming pass used to converge to, since a steady state is by
definition unchanged by one more pass). No pose diverges after seeding,
so the packet's "write the divergence into section 9" fallback never
triggers - nothing needed there this round.
R2-3 - the SOME-vs-SAME-plane discriminator. T2's existing five cases
never separated retail's real quantifier ("every vertex lies on the SAME
one of the four +/-12 planes") from a weaker "every vertex lies on SOME
plane" (per-vertex OR across the four planes, ANDed across vertices) -
the existing split-plane case's third vertex sits on NO plane at all, so
both readings admit it for the same reason. New case in
WalkVisibilityMathTests.cs
(Boundary_guard_admits_a_polygon_whose_every_vertex_is_on_SOME_plane_but_not_the_SAME_one)
and a fourth sink.OnPunchGeometry call in WalkFrameDriverTests.cs's
OnPunchGeometry_RejectsOnlyWhenEveryVertexSharesOnePlane_ButPunchesAnyOtherShape
pin close the gap: [(12,0,3),(0,12,3),(12,5,3)] - every vertex touches
SOME plane (vertex1/3 on x=+12, vertex2 on y=+12) but no SINGLE plane
holds all three (x=+12 cleared by vertex2's x=0, y=+12 cleared by
vertex1's y=0) - retail's real four-accumulator guard admits it; the
weaker per-vertex-OR form rejects it.
R2-4 - the sixth pose. Cherry-picked 4f12cd06b (walk+alphadepth capture)
and 3a6aa7eef (pmverts capture) from claude/campaign-w-retail-frame-walk
onto 2bd353234 - both applied clean, no conflicts (README.md merged
without a manual row). New
AlphaDepthTranscript_CathedralStairArch_MatchesRetailFrame2 Fact: root
0xF4180114, seven outside views, sixteen mode=1 building punches (all
counterBefore=0008 - far-Z punches never touch the counter) of which
twelve are guard-rejected on local y=12.000, then eight mode=0 exit
seals with counterBefore running 0..7 against the root's own ov=7
read-then-zero, settling this fixture's own steady-state counter at 8 -
exactly the value LoadInitialCounter reads back out of its own preamble.
PASS.
R2-5 - comment truth. (a) RetailPViewPassExecutor.cs:
DrawPortalDepthWrite's and DrawExitPortalMask's summaries corrected -
neither the retired ">=3 vertices" pre-filter nor "always matches what
reached the GPU" describe F2's actual count-before-clip behavior any
more. (b) The F2 pin's doc comment
(RetailPViewPassExecutorTests.DrawExitPortalMask_CountsAnUnclippableTwoVertexPolygon_ButDrawsNothing)
named TWO alternative mutations as if both failed; verified only one
does (see mutations below) - the doc comment now says so explicitly and
keeps only the pre-filter mutation. (c) WalkVisibilityMath.cs's doc
comment stated DAT-scan figures as bare facts; independently re-verified
this round with a freshly-written scan (own code, walking the same
production EnvCell.CellPortals/CellStruct.Polygons shape
WalkCellFactory itself uses, over the full 0x0000-0xFFFF landblock-prefix
range) - every cited figure reproduced bit-exact (3,405 landblocks with
cells; 1,854,237 portal polygons; 2,889 any-vertex; 2,163 all-on-plane,
all exit, 0 interior; 16,939 exit portals total, 1,837,298 interior) -
so the doc comment now states the counting rule (id range, what's
counted, how unresolvable polygons/cells are skipped) alongside every
figure instead of leaving them unqualified; landblocksWithCells=3,405
also independently matches CellStructSurfaceConstructionInstalledDatTests'
own OH2 walk of the identical range (that test's own golden-count
comment). The scratch scan itself is not part of this diff (deleted
after use, same as F1's own datScan). (d) This commit body does not
cite "register tests" as evidence - no test reads the register.
Register truth is the human read: no row changes, AD-119 (the C2
blend-vs-write-mask row) unchanged.
Mutations run this round (all verified against production code, then
reverted):
- R2-1/R2-2 (disable seeding, driver.PortalsDrawnCount = 0 instead of
initialCounter): AlphaDepthTranscript_TerraceEdge_MatchesRetailFrame2
fails - "terrace-edge.alphadepth: PM DIVERGES - expected
[(mode=1,counterBefore=2), (mode=1,counterBefore=2)], actual
[(mode=1,counterBefore=0), (mode=1,counterBefore=0)]; PC DIVERGES -
expected [(ov=0,counter=2,fc=0), (ov=0,counter=2,fc=0)], actual
[(ov=0,counter=0,fc=0), (ov=0,counter=0,fc=0)]" - proving both the
seeding fix (R2-2) and the honest dual-sequence message (R2-1) at
once.
- R2-3 (replace the four per-plane accumulators with a single
per-vertex "on ANY plane" test ANDed across vertices):
Boundary_guard_admits_a_polygon_whose_every_vertex_is_on_SOME_plane_but_not_the_SAME_one
fails, Assert.False() Expected:False Actual:True;
OnPunchGeometry_RejectsOnlyWhenEveryVertexSharesOnePlane_ButPunchesAnyOtherShape
fails, Assert.Equal() Expected:3 Actual:2.
- R2-5b, mutation ONE (restore the old `localVertices.Length < 3`
pre-filter ahead of the guard):
DrawExitPortalMask_CountsAnUnclippableTwoVertexPolygon_ButDrawsNothing
fails, Assert.Equal() Expected:1 Actual:0 - kept as the pin's cited
mutation.
- R2-5b, mutation TWO (move `submitted++` to AFTER
`_portalDepthMask.DrawDepthFan(...)` instead of before): same test
STILL PASSES - DrawDepthFan has no effect on the local `submitted`
counter, so the final returned value is identical regardless of
which side of that call the increment sits on. Confirmed unobservable
exactly as R2-5b required; the doc comment no longer names this as a
failing mutation.
Gates: dotnet build tests/AcDream.App.Tests -c Release -> 0 warnings/0
errors. dotnet build src/AcDream.App -c Release -> 0 warnings/0 errors.
Hermetic lane (Lane!=InstalledDat&Lane!=PreparedPackage&Lane!=Live&
Lane!=Manual&Lane!=Timing&Lane!=Windows&Lane!=Linux&Lane!=SystemFont&
Purpose!=Diagnostic&Status!=KnownFailure): 6830/6830 passed (+1 over
round 1's 6829, the new T2 case). InstalledDat lane
(ACDREAM_RUN_INSTALLED_DAT_TESTS=1, --filter Lane=InstalledDat): 254
total (+1 over round 1's 253, the new sixth Fact), 249 passed, 4 failed -
exactly the pre-existing four (LayoutImporterMediaBearingChildSweepTests
and LayoutImporterInvisibleSweepTests, both #383;
TowerAscentReplayTests' KnownFailure; Oh_doorway_still_first_frame_diff,
#458) plus the pre-existing WalkLookInGateSweepTests skip - terrace-edge
no longer fails, no new divergence anywhere else. All six
AlphaDepthTranscript_*_MatchesRetailFrame2 Facts: 6/6 passed standalone.
Shader classes (VulkanShaderDescriptorContractTests +
VulkanShaderManifestTests + RenderPackSpirvValidatorTests +
SkyVertexLayoutTests): 35/35. RetailPViewPassExecutorTests +
WalkFrameDriverTests (full classes, regression check on the R2-3/R2-5b
edits): 57/57.
Commits: two cherry-picks (4f12cd06b, 3a6aa7eef) from
claude/campaign-w-retail-frame-walk, both clean, plus this one fix
commit on top of 2bd353234.
Register: no new AD row; AD-119 unchanged (human read, not a test -
see R2-5d above).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Three-lens review of c7ab5b6d8 found the round-0 port of
DrawPortalPolyInternal's degenerate-input guard inverted retail's
quantifier: it rejected a polygon when ANY vertex sat on ANY of the
four +/-12 planes. The PDB-paired bytes (VA 0x59BCD6-0x59BD66: four
per-plane fld/fcomp 12.0/fnstsw/test ah,0x44/jnp sequences, each
accumulating a "every vertex on this plane" boolean, cleared by the
first vertex NOT on it) and the named decomp's own nested-if reading
of the post-loop branch (0x59bd42-0x59bd6c) agree: retail rejects only
when EVERY local vertex lies on the SAME one of the four planes — a
polygon degenerate onto a plane, not one that merely touches a plane
at one vertex.
F1 — the quantifier. WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard
now tracks four booleans (everyVertexOnPlusX/MinusX/PlusY/MinusY), each
starting true and cleared by the first vertex that fails its own plane
test; the guard rejects iff any of the four survives the whole vertex
loop. T2 (WalkVisibilityMathTests.cs) is rewritten to the five §7
cases: every vertex on x=+12 -> rejected; every vertex on y=-12 ->
rejected; one vertex on x=+12, rest inside -> KEPT; every vertex at
11.999 -> kept; vertices split across x=+12 and y=+12 (no common
plane) -> kept. WalkFrameDriverTests' punch-side pin
(OnPunchGeometry_RejectsOnlyWhenEveryVertexSharesOnePlane_ButPunchesAnyOtherShape)
carries the same correction — its old "one vertex on the plane"
polygon was wrongly rejected under round 0; it punches now, and a new
all-on-plane polygon is added as the actual rejected case. The doc
comment's "essentially never" sentence is deleted and replaced with a
fresh DAT-scan citation (see datScan below).
MUTATION (verified): restore the any-vertex form -> the one-vertex and
split-plane admit cases fail (Assert.False Expected:False Actual:True,
x2; the WalkFrameDriverTests punch pin's Assert.Equal Expected:2
Actual:1). Delete the guard (always return false) -> the all-on-plane
reject cases fail (Assert.True Expected:True Actual:False, x3 —
plusX-plane, minusY-plane, and the vacuous-empty-polygon pin; the
punch pin's Assert.Equal Expected:2 Actual:3).
datScan (DAT counts backing F1's doc comment): a throwaway xunit test
(deleted after this commit — not part of the diff) walked the full
0x0000-0xFFFF landblock-prefix space (LandBlockInfo/EnvCell carry no
usable DatCollection.GetAllIdsOfType range — both are landblock-
relative — matching the method CellStructSurfaceConstructionInstalledDatTests
already established), resolved each EnvCell's Environment/CellStruct,
and for every CellPortal's polygon tested each vertex's local x/y
against +/-12. Against the installed client_cell_1.dat: 3,405
landblocks with cells, 1,854,237 total portal polygons. 2,889 polygons
have at least one vertex on a +/-12 plane; 2,163 have EVERY vertex on
the SAME plane — all 2,163 are EXIT portals (OtherCellId==0xFFFF, out
of 16,939 exit portals total); 0 interior (non-exit) portals ever
qualify (interior: 1,837,298 total, 340 any-vertex, 0 all-on-plane).
Duration ~15s. This exactly reproduces the review's cited numbers.
F2 — seal-path count order. RetailPViewPassExecutor's exit-seal loop
dropped the `localVertices.Length < 3` pre-filter that ran BEFORE both
the boundary guard and the `submitted` increment (round 0's order:
length check -> guard -> transform -> draw -> count). Retail's real
order is guard -> transform -> clip -> count, with the counter
incremented BEFORE polyClipFinish runs (0x59BD70-0x59BD74 precedes
0x59BDB0) — the counter records accepted ATTEMPTS, not successful GPU
fans. `submitted++` now runs immediately after the guard-gated
transform+EmitSeamMask, before DrawDepthFan (whose own `< 3` guard is
this port's stand-in for retail's post-clip `var_4 >= 3` check). A new
pin (RetailPViewPassExecutorTests.DrawExitPortalMask_CountsAnUnclippableTwoVertexPolygon_ButDrawsNothing)
constructs a real PortalDepthMaskRenderer (RecordingGpuDevice-backed)
and a constructor-bypassed executor (RuntimeHelpers.GetUninitializedObject,
the same pattern WalkOutsideViewReassemblyTests already uses) with a
2-vertex ordinary-coordinate portal polygon: DrawExitPortalMask returns
submitted=1 while zero draw calls reach the device.
MUTATION (verified): restore the `< 3` pre-filter ahead of the guard
-> the pin's Assert.Equal fails, Expected:1 Actual:0.
F3 — the depth-event transcript gate, automated. New
WalkAlphaDepthTrace.cs (beside WalkOracleTrace.cs) parses the four
`PM poly=<ptr> mode=<0|1> counterBefore=<hex>` / `PC ov=<n>
counter=<hex> fc=<0|1>` lines per oh-capture/*.alphadepth.log frame,
using the same F-marker frame delimiting as WalkOracleTrace. New
WalkTraceConformanceTests.AlphaDepthTranscript.cs (beside
WalkTraceConformanceTests.cs, which is now `partial`) replays each
pose's frame 2 through the real WalkFrameDriver/RetailFrameWalk over
WalkLandscapeDatBuilder-built installed-DAT world data, with:
- AlphaDepthCollectSink: an IWalkEventSink decorator forwarding every
hook to the real driver unchanged, while recording mode=1 punch
events (OnPunchGeometry, counterBefore = driver.PortalsDrawnCount
at COLLECT time — Collect finishes entirely before Replay starts,
so reading the counter at Replay time would already see the SAME
turn's post-reset value; holtburg-doorway-still's own capture
proves this: its punches show counterBefore=2 immediately followed
by its seals' counterBefore=0/1 in the identical turn) and every
PC tuple (Emit, WalkEventKind.DrawCells — fires for the root's own
turn AND every building look-in's re-entrant call, matching
retail's breakpoint not distinguishing them).
- AlphaDepthLeaf: an IWalkFrameLeafRenderer whose DrawExitSeals
reproduces DrawPortalDepthWrite's real per-portal loop (F1's guard,
F2's count-before-clip order) directly over
driver.InteriorFloodCells / WalkCell.Portals / .PortalPolygons —
the identical data the render-side production path reads — since
the interface gives DrawExitSeals no cell/portal parameter at all.
- Two Collect+Replay passes per pose (a fresh driver reaches the SAME
steady-state a running session already carries by the time
retail's capture began — holtburg-doorway-still and foundry-deep
both show a stable per-frame accepted-seal count that regenerates
identically every captured frame): the first pass's own output is
discarded, only the second is compared.
Five Facts (one per pose — the spec's "four" undercounts the five
*.alphadepth.log files actually present in oh-capture/):
AlphaDepthTranscript_CathedralArrival_MatchesRetailFrame2 — PASS
AlphaDepthTranscript_CathedralLeak_MatchesRetailFrame2 — PASS
AlphaDepthTranscript_FoundryDeep_MatchesRetailFrame2 — PASS
AlphaDepthTranscript_HoltburgDoorwayStill_MatchesRetailFrame2 — PASS
(proves the incrementing 0/1/2 accepted-seal sequence AND the
pre-reset counterBefore=2 punches, end to end, against real DAT
portal data)
AlphaDepthTranscript_TerraceEdge_MatchesRetailFrame2 — KnownFailure
(tagged [Trait("Status","KnownFailure")]): terrace-edge never runs
a qualifying interior turn in its own capture (every PC line reads
ov=0) — its punches' counterBefore=2 is a session value carried
over from BEFORE the capture even started (the file's own pre-"F 1"
content already reads 2; nothing inside the captured frames ever
touches the counter). A fresh two-pass replay from
PortalsDrawnCount=0 has no mechanism to derive that leftover value.
Printed divergence: expected [(mode=1,counterBefore=2) x2], actual
[(mode=1,counterBefore=0) x2]; the PC sequence (ov=0 both times)
matches exactly. An initial-condition gap in the fixture, not a
guard/count defect — the identical harness reproduces holtburg-
doorway-still's and foundry-deep's own steady states exactly from
cold starts.
MUTATION (verified): swap AlphaDepthLeaf's recorded seal mode from 0
to 1 -> AlphaDepthTranscript_HoltburgDoorwayStill_MatchesRetailFrame2
fails (Assert.Equal Collections differ at pos 5: expected
(0,0),(0,1)... actual (1,0),(1,1)...).
DispatcherFixture and DrawScope (WalkFrameDriverTests.cs) are widened
from private to internal so the new gate can drive a real
WbDrawDispatcher without duplicating that fixture.
F4 — manifest comment truth. VulkanShaderManifestTests.cs's
portal_depth.vert.spv re-pin comment had the ULP direction backward
(claimed the OLD 0x3F7FFFFE literal was NEARER the camera than
retail's real 0x3F7FFFEF constant; portal_depth.vert's own shader-
source comment already had this correct — FARTHER, larger z/w) and
cited T1 as living in WalkVisibilityMathTests when
PortalDepthVert_FarPunchConstant_MatchesRetailExactBits is in this
same file. Both corrected.
Gates: dotnet build tests/AcDream.App.Tests -c Release -> 0 warnings/0
errors. Hermetic lane (Lane!=InstalledDat&...&Status!=KnownFailure):
6829/6829 passed. InstalledDat lane (real client_cell_1.dat): 253
total, 247 passed, 5 failed — exactly the two #383 layout tests
(LayoutImporterMediaBearingChildSweepTests/LayoutImporterInvisibleSweepTests),
TowerAscentReplayTests' KnownFailure, Oh_doorway_still_first_frame_diff
(#458), and this round's own AlphaDepthTranscript_TerraceEdge
KnownFailure — no other divergence. Shader tests (VulkanShaderDescriptorContractTests
+ VulkanShaderManifestTests + RenderPackSpirvValidatorTests +
SkyVertexLayoutTests): 35/35. Register/divergence tests: 52/52.
dotnet build src/AcDream.App -c Release: 0 warnings/0 errors.
Register: no new AD row. F1/F2 are straight bug fixes toward retail
fidelity (not deviations); AD-119 (the C2 blend-vs-write-mask row)
does not describe the count order, so its neighbour needed no
correction per F2's own instruction.
Deviation from the fix-round spec's literal text: §7 F1 said "swap the
any-vertex hits instead of the four per-plane accumulators" as the
split-plane case's mutation — the code actually ported uses per-vertex
`if (x != 12f) flag = false;` clears, not a per-vertex OR; the mutation
verified above (restore the any-vertex FORM entirely) exercises the
same defect class and is the mutation actually run. §7 also names
"four" alphadepth.log fixtures where five exist on disk
(cathedral-arrival, cathedral-leak, foundry-deep,
holtburg-doorway-still, terrace-edge); all five are gated rather than
four, with cathedral-leak an unremarked addition alongside the four
the spec names.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
S4-c1 per docs/research/2026-09-01-overhaul/s4-depth-alpha-packet.md §6.
S3 chunk 2 already landed the persistent portalsDrawnCount latch, the
gated clear, the exit-seal counting, and the look-in isolation — this
chunk covers only what §1/§2 of the packet name as still owed: C0-C3.
C0 — far-punch depth constant (R1: DrawPortalPolyInternal @0x0059bc90's
tail). portal_depth.vert's punch branch carried the decimal 0.99999988,
which reinterprets as bits 0x3F7FFFFE — fifteen ULPs FARTHER from the
camera than retail's real constant, bits 0x3F7FFFEF. Now writes
`uintBitsToFloat(0x3F7FFFEFu)` so the exact bits survive the GLSL/SPIR-V
compiler instead of trusting a decimal literal to round-trip unchanged.
Recompiled via tools/compile-shaders.ps1 (glslc 1.4.350.0 backend
recorded, managed shaderc path used); portal_depth.vert.spv's SHA-256
re-pinned in VulkanShaderManifestTests
(51c60d0924d62c61548efcf5f9e7672a121b1b68ca0a06755e32f1a4d73a8acf,
was 4ac1c452e7ac0d08a32f67fb03f21229af2d1605baa81f407240a3626251dfd7).
T1 (new Fact PortalDepthVert_FarPunchConstant_MatchesRetailExactBits in
VulkanShaderManifestTests.cs): a SOURCE pin — reads portal_depth.vert's
punch line and reinterprets whatever literal it carries (uintBitsToFloat
hex or a plain decimal) as raw bits, asserts == 0x3F7FFFEF. Verified
against the PRE-CHANGE source by hand-reverting the line to
`clipPos.z = clipPos.w * 0.99999988;` and re-running just this test:
Assert.Equal() Failure: Values differ
Expected: 1065353199
Actual: 1065353214
(1065353199 = 0x3F7FFFEF, 1065353214 = 0x3F7FFFFE). Line restored and
the test re-confirmed green afterward. MUTATION: any other literal fails
the same way.
C1 — the ±12 local-input reject (R2: 0x59BCD6-0x59BD28 then
0x59BD40-0x59BD66). The Ghidra arbitration table in
oh1-depth-lifecycle.md governs over the pseudo-C's own nested-if reading
of the four x87 FCOM results (BinaryNinja's `test ah, 0x44` condition
synthesis is FPU-flag-ambiguous and reads backward at face value — see
feedback_bn_decomp_field_names.md on decompiler flag mush as an artifact
class, not semantics): the table's row says "whole poly on any
local-input x/y == +/-12 boundary is rejected before count/clip" — taken
as written, not re-derived from the pseudo-C's literal branch nesting.
Ported as one shared predicate,
WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard(ReadOnlySpan
<Vector3>): true iff any vertex's X or Y is exactly +12f/-12f (retail
tests LOCAL x/y before xformStart, the world transform). Wired at BOTH
producers that own the LOCAL polygon before it leaves cell/building
space:
- WalkFrameDriver.OnPunchGeometry (the walk's punch-event producer,
IWalkEventSink.OnPunchGeometry) — checked on the building-local
WalkPolygon.Vertices before TransformToWorld; a hit returns before
MarkIfGrown/any event append (retail's reject -> transform -> clip
-> count order).
- RetailPViewPassExecutor.DrawPortalDepthWrite (the exit-seal
enumeration behind DrawExitPortalMask, the sole caller) — checked on
cell.PortalPolygons[index]'s local vertices before the
Vector3.Transform loop; a hit `continue`s with no `submitted++`.
T2 (three layers):
1. WalkVisibilityMathTests.cs — direct unit tests of the predicate:
Boundary_guard_rejects_a_polygon_with_one_vertex_exactly_on_plus_minus_12
(Theory, x/y == +-12 each), Boundary_guard_admits_a_polygon_whose_
nearest_vertex_is_just_inside_12 (Theory, x/y == +-11.999),
Boundary_guard_rejects_the_whole_polygon_even_when_only_one_of_
several_vertices_hits_it, Boundary_guard_ignores_the_vertical_z_
component, Boundary_guard_admits_the_empty_polygon.
2. WalkFrameDriverTests.OnPunchGeometry_RejectsWholePolygonOnExact
PlusMinus12LocalVertex_ButPunchesJustInside — functional: feeds
OnPunchGeometry a polygon with a vertex at x=12 (no PunchFan/no
"PUNCH:" log line) then one at x=11.999 (punches normally,
leaf.Punches has exactly one entry, log has exactly one "PUNCH:3@v0").
3. RetailPViewPassExecutorTests.DrawPortalDepthWrite_RejectsDegenerate
LocalPolygons_BeforeTransformOrSubmission — a real functional test of
DrawPortalDepthWrite needs a live PortalDepthMaskRenderer the suite
has no fake for, so this is a compiled-call-graph pin (this file's
established pattern for exactly this situation): the guard call
precedes both the Vector3.Transform loop and
PortalDepthMaskRenderer.DrawDepthFan by IL offset, gated by a
conditional branch immediately after it.
MUTATION texts, all verified live during this session then reverted:
- OnPunchGeometry_RejectsWholePolygon... with the C1 guard deleted from
OnPunchGeometry:
Assert.Single() Failure: The collection contained 2 items
Collection: [WalkPolygon { Plane = WalkPlane { Normal = <0, 0, 1>, D = -3 }, Vertices = [<0, 0, 3>, <12, 0, 3>, <5, 5, 3>] }, WalkPolygon { Plane = WalkPlane { Normal = <0, 0, 1>, D = -3 }, Vertices = [<0, 0, 3>, <11.999, 0, 3>, <5, 5, 3>] }]
- DrawPortalDepthWrite_RejectsDegenerateLocalPolygons... with the C1
guard deleted from DrawPortalDepthWrite:
Expected call to WalkVisibilityMath.IsRejectedByPortalPolygonBoundaryGuard.
- Boundary_guard_admits_a_polygon_whose_nearest_vertex_is_just_inside_12
with the predicate widened to `MathF.Abs(x) >= 11.99f ||
MathF.Abs(y) >= 11.99f` (all four rows):
Assert.False() Failure
Expected: False
Actual: True
- Boundary_guard_rejects_a_polygon_with_one_vertex_exactly_on_plus_
minus_12 with the predicate narrowed to strict `x > 12f || x < -12f
|| y > 12f || y < -12f` (all four rows):
Assert.True() Failure
Expected: True
Actual: False
- Boundary_guard_rejects_the_whole_polygon_even_when_only_one_of_
several_vertices_hits_it with the guard checking only
localVertices[0] instead of looping every vertex:
Assert.True() Failure
Expected: True
Actual: False
C2 — no pipeline change for R3 (depth ALWAYS/write/no-cull, color writes
ENABLED with a zero-alpha SRCALPHA/INVSRCALPHA blend). acdream's
PortalDepthMaskRenderer.Rhi.cs:92,100 sets ColorWrite=false alongside
Blend=None; portal_depth.frag writes no color output at all. Provably
pixel-identical (retail's blend collapses to dst'=dst when srcAlpha is
fixed at 0, for any RGB) and the write mask is the SAFER mechanism going
forward (structurally blocks any future accidental color write,
independent of an authored zero-alpha invariant). Added register row
AD-119 to docs/architecture/retail-divergence-register.md (the next free
id after AD-118), citing DrawPortalPolyInternal @0x0059bc90 and
PortalDepthMaskRenderer.Rhi.cs; section 2's active-row count and running
header note updated (90 -> 91).
C3 — the truth table + cross-frame latch tests. The (root kind,
draw_landscape, outside-view count, previous count) table's cells are
mostly already covered by S3 chunk 2's own tests — this chunk adds only
the genuinely missing rows/cases, and leaves every existing test
untouched:
Pre-existing coverage (named, not reproduced):
- interior, ov==0, prior==0 ->
RunFrame_InteriorFloodWithNoExitView_SkipsLandscapeAndNeverFlushesClearsOrSeals
- interior, ov>0, prior==0 ->
RunFrame_InteriorFloodWithExitView_FreshDriverSkipsTheGatedClearThenDrawsSealsAndFloodCells
and OnInteriorFloodDrawTurn_FirstOvFrameSkipsClear_SecondFrameArmedByFirstsSealsClears
(its own frame 1)
- interior, ov>0, prior>0 (T4's "frame 1 seals N>0 -> frame 2
clears" half) ->
OnInteriorFloodDrawTurn_FirstOvFrameSkipsClear_SecondFrameArmedByFirstsSealsClears
(its own frame 2)
- T4's "frame 1 seals 0 -> frame 2 does not clear" half (repeated
across three consecutive ov>0 frames, subsuming the two-frame
case) -> OnInteriorFloodDrawTurn_FloodWithNoExitPortal_NeverClearsAcrossFrames
- one look-in isolated from the root latch ->
LookInDrawCells_NeitherArmsNorConsumesThePortalsDrawnCounter
No further T4 test was added — the two existing facts above already
prove both halves of the two-consecutive-frames latch case exactly.
New rows added this chunk:
- WalkFrame_OutdoorRoot_NeverFiresTheInteriorClearSealMachinery: root
kind == OUTDOOR. RetailFrameWalk.WalkFrame's outdoor branch
((cameraCellId & 0xFFFF) < 0x100) calls DrawLandscape directly and
never calls DrawInside/OnInteriorFloodDrawTurn at all, so the whole
LFLUSH/stamp/CLEAR/SEALS mechanism structurally cannot fire —
driven end-to-end through RunFrame with an outdoor cameraCellId,
asserting SKY present, LFLUSH/CLEAR/SEALS absent, counter stays 0.
MUTATION (verified, then reverted): added a stray
`sink.OnInteriorFloodDrawTurn([], 1);` call to WalkFrame's outdoor
branch:
Assert.DoesNotContain() Failure: Item found in collection
↓ (pos 1)
Collection: ["SKY", "LFLUSH", "SEALS"]
Found: "LFLUSH"
- OnInteriorFloodDrawTurn_OvZeroAfterAPriorArmedCounter_LeavesTheLatch
CompletelyUntouched: interior, ov==0 immediately after an EARLIER
ov>0 frame armed the counter — proves the counter is left EXACTLY
as an earlier frame left it (not merely "not cleared this frame"),
since S3 §8.1 R3 gates the ENTIRE outside_view.view_count>0 block,
including the read-then-zero decision itself, on ov>0.
MUTATION (verified, then reverted): moved
`int armed = PortalsDrawnCount; PortalsDrawnCount = 0;` out of the
`if (outsideViewCount > 0)` gate in
WalkFrameDriver.OnInteriorFloodDrawTurn (unconditional
read-then-zero every call):
Assert.Equal() Failure: Values differ
Expected: 1
Actual: 0
(every OTHER WalkFrameDriverTests fact stayed green under this same
mutation — this new test is the only one that catches it).
- MultipleLookIns_WithinOneFrameAndAcrossFrames_NeverTouchTheRootLatch
(T5): extends the single-look-in fact to TWO look-ins in one frame
then a THIRD in a later frame. MUTATION (verified, then reverted):
a `_mutationLookInCalls` counter in HandleDrawCellsTurn's
LookInStatic branch that resets PortalsDrawnCount on the SECOND
look-in call:
Assert.Equal() Failure: Values differ
Expected: 1
Actual: 0
— while LookInDrawCells_NeitherArmsNorConsumesThePortalsDrawnCounter
(one look-in only) stayed green under the identical mutation,
confirming this test's incremental value over the existing single-
look-in fact.
Gates: dotnet build (App.Tests and App) 0 warnings/0 errors; hermetic
lane 6832/6832 passed; InstalledDat lane against
C:/Users/erikn/Documents/Asheron's Call — exactly the four known
failures (TowerAscentReplayTests.TowerAscent_StaircaseStaysConeVisible_
EveryStep, LayoutImporterMediaBearingChildSweepTests.
MainGameUiAndChatInput_MediaBearingChildrenNowBuildAsRealWidgets and
LayoutImporterInvisibleSweepTests.EveryAuthoredInvisibleWidget_
StartsHiddenAcrossAllLayouts — both #383 — and
WalkTraceConformanceTests.Oh_doorway_still_first_frame_diff #458),
243 passed / 1 skipped / 4 failed / 248 total, no new failures; shader
tests (VulkanShaderDescriptorContractTests/VulkanShaderManifestTests/
RenderPackSpirvValidatorTests/SkyVertexLayoutTests) 35/35; register
tests (Divergence|Register filter) 52/52.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Issue #464 needs a repeatable tilted self-gate frame at the cathedral
stair-arch pose, but the automation's `drag at`/`mousemove` verbs only
move the retained-UI cursor — they never reach mouse-look, so every
scripted route replays the DEFAULT camera and can't reproduce the
owner's upward-tilted frame (docs/ISSUES.md #464, the 2026-09-03
22:07 transcript note: "the automation's `drag at` verb does not
drive mouse-look").
Add `mouselook <dx> <dy>`: it calls the SAME
GameplayInputFrameController.QueueRawMouseDelta the real mouse's Silk
MouseMove callback drives (CameraPointerInputController.
ProcessMouseMove), through an injected delegate threaded
RetailUiAutomationScriptRunner <- RetailUiProbeBindings <-
InteractionRetainedUiDependencies.GameplayInputFrame. That last seam
is resolved fresh on every call rather than captured once at mount,
since GameplayInputFrameController is created per live session
(SessionPlayerComposition), strictly after the retained UI composes
and across reconnects — the same never-capture-a-deferred-Func
discipline the secure-trade command-bus regression taught
(claude-memory/feedback_resolve_deferred_funcs_per_call.md).
The delta only takes effect while mouse-look is active (bracket with
`input down`/`up CameraInstantMouseLook`) and one call consumes
exactly one raw sample on the controller's next tick, so a route must
`sleep` between calls — documented on DoMouseLook and the class doc
comment.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>