Commit graph

7 commits

Author SHA1 Message Date
Erik
c673f767e9 feat(rendering): port retail building degrade walk
Add exact retail building degrade selection, shared FPS/degrade ownership, complete-body gating, selected-shell submission, ordinary ladder mesh residency, frame-scoped retry rearm, Config controls, installed-DAT census, and lifecycle/allocation proofs.

Reviews: OpenAI retail pass 3 PASS; OpenAI production pass 5 PASS. Gates: Release 0W/0E; focused 285/285; hermetic 16811/16811; InstalledDat 469 pass, 10 documented fail, 1 documented skip; both manifests 30/30.
2026-09-04 22:29:18 +02:00
Erik
a86ec73ece fix(render): restore retail per-cell alpha order
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>
2026-09-04 14:36:48 +02:00
Erik
9ccb61a8ec fix(render): S4 chunk 2 round 1 - particle throw, detail-off immediate draws, EnvCell FIFO, gate honesty
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>
2026-09-04 11:53:03 +02:00
Erik
89f1e2676f feat(render): S4 chunk 2 — retail's two-list alpha FIFO cutover
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>
2026-09-04 11:52:40 +02:00
Erik
766f9e749d fix(render): S4 chunk 1 landing — the four comment sentences the round-2 gate-honesty lens flagged (lead correction; code unchanged)
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>
2026-09-03 23:40:00 +02:00
Erik
323f5a334d fix(render): S4 chunk 1 round 2 (LAST) - honest PM/PC gate, seeded portalsDrawnCount, the SOME-vs-SAME-plane pin, sixth pose, comment truth
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>
2026-09-03 23:38:52 +02:00
Erik
5de42a12f3 fix(render): S4 chunk 1 round 1 — the ±12 guard with retail's quantifier, seal count order, the PM/PC depth-event transcript gate
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>
2026-09-03 23:38:52 +02:00