F1 (BLOCKING, punch-fan view alignment). ClipFrameAssembler.
ReassembleOutsideViewFromWalk now keeps exactly ONE outside-view slice per
walk view, index-aligned with RetailFrameWalk.DrawBuilding's own
ActiveViewIndex (retail building_view = portal_view_num @0x0059f3bf): a
collapsed view (ClipPlaneSet.IsNothingVisible — retail polyClipFinish under
a degenerate view leaves <3 vertices, 0x59BDBC-0x59BDBF) now appends its
own ClipViewSlice.NothingVisible=true slot instead of being skipped, which
used to shift every later view's slice down by one. DrawWalkPunchFan draws
NOTHING for a NothingVisible slice and throws ArgumentOutOfRangeException
when activeViewIndex is out of range (fail-loud; never draws unclipped).
New tests: WalkOutsideViewReassemblyTests.
FirstViewCollapses_SecondSurvives_SlicesStayIndexAligned and
PunchLeaf_UsesIndexAlignedSlice_DrawsNothingForCollapsed_ThrowsOutOfRange.
MUTATION M1 (revert the append-nothing-on-skip fix, restoring the old
skip): both new tests fail with
Assert.Equal() Failure: Values differ
Expected: 2
Actual: 1
MUTATION M2 (restore the old ": default" unclipped fallback in
DrawWalkPunchFan): the punch-leaf test fails with
Assert.Equal() Failure: Values differ
Expected: 0
Actual: 1
(both restored; verified clean afterward).
F2 (major, exit-seal pin through the real producer + both overflow cases).
The exit-seal CPU/GPU equivalence pin moved from ClipFrameLayoutTests (which
drove ClipFrame.AppendSlot(ClipPlaneSet) via the zero-caller
ClipFrameAssembler.Assemble) to the new
tests/.../Walk/WalkFrameDriverClipSealTests.cs, which drives
WalkFrameDriver.BeginFrame + the real IWalkEventSink.OnInteriorFloodDrawTurn
hook — the SAME path RetailPViewPassExecutor.DrawExitPortalMask resolves
through via WalkFrameDriver.InteriorFloodViewClipPlanesAt. The new pin
compares WalkFrameDriver's captured planes EXACTLY (not just geometrically)
against ClipPlaneSet.From's independent CCW-normalized-perpendicular
computation, since AppendClipSlot's per-edge formula is bit-identical to
ClipPlaneSet's own. Two overflow cases added: a 9-vertex portal view exit-seals
to exactly the 4 conservative AABB planes containing every source vertex
(over-include, never under-include — AppendClipSlot's fallback for a view too
complex for the 8-plane budget); a 9-vertex OUTSIDE view (the punch-fan
sibling, WalkOutsideViewReassemblyTests.
NineVertexOutsideView_PunchSliceHasZeroPlanes_DrawsUnclipped_NotNothingVisible)
produces a zero-plane, NOT-NothingVisible slice — draws fully unclipped,
distinct from F1's "draw nothing" state.
MUTATION M1 (flip the winding selection, `ccw = area2 < 0f`):
Assert.Equal() Failure: Values are not within 4 decimal places
Expected: 0.9191 (rounded from 0.919144988)
Actual: 0.8944 (rounded from 0.89442724)
MUTATION M2 (drop the normalize from the per-edge plane formula):
Assert.Equal() Failure: Values are not within 4 decimal places
Expected: 0.9191 (rounded from 0.919144988)
Actual: 0.7 (rounded from 0.700000048)
MUTATION M3 (delete the >8-plane AABB overflow branch — the per-edge loop
then indexes a fixed 8-plane stackalloc with a 9-edge polygon):
System.IndexOutOfRangeException : Index was outside the bounds of the array.
at WalkFrameDriver.AppendClipSlot(...) line 1915
at WalkFrameDriver.CaptureViews(...) -> CaptureCellViews -> CaptureCellViewRoute
-> EmitFloodTurns -> IWalkEventSink.OnInteriorFloodDrawTurn
(all three restored; verified clean afterward).
F3 (minor/major). RetailPViewPassExecutor.cs's false bracket comment
(neither KEEP clip — exit seals nor punch fans — ever called Enable/
DisableClipDistances; the six DisableClipDistances() call sites all sat
around the UNCLIPPED particle/weather draws, whose bodies are no-ops on the
only backend) is rewritten to the truth; the six no-op calls and the
private DisableClipDistances() wrapper are deleted.
IWorldPassSurface.EnableClipDistances/DisableClipDistances stay on the
INTERFACE — WorldScenePassExecutor (the separate flat-world path) still
calls them.
F4a (EC transcript exactness). WalkFrameDriver.EmitFloodTurns now prints one
"EC" line per LIVE VIEW of the flood cell (retail's setup_view/DrawEnvCell
loop, PView::DrawCells @0x005a4ab1-0x005a4acc, fires once per live portal_view
slice, not once per cell) via the captured route's own SliceCount — read
directly off _lookInTurns[viewRouteIndex] rather than through
InteriorFloodViewSliceCountAt's flood-index indirection, because this same
loop also serves a building's LOOK-IN flood (WalkDrawStage.LookInStatic),
whose cells never populate InteriorFloodCells (the interior ROOT flood only)
— indexing through that accessor threw ArgumentOutOfRangeException on a real
look-in turn during verification, fixed before landing. The actual CellShell
DRAW event is unchanged (the frame stamp still dedupes it to one submission).
F4b (transcript exactness, trailing weather OC).
WalkTraceConformanceTests.Recorder now implements IWalkEventSink.
OnWeatherTurn (the interface's silent no-op default previously left the
replay side of Signature8 blind to the weather turn's own "OC" line), and
WalkTraceReplayContext.Signature8(WalkOracleFrame) no longer excludes the
trailing per-frame weather ObjectCellTurn — every ObjectCellTurn now reads
literally, on both sides. Implementing OnWeatherTurn alone left
Still_fixture_first_frame_reproduces_exactly(terrace-edge.walk) diverging
ONLY on the newly-un-excluded trailing OC (EXPECTED ...OC:f4180104|
OC:f418000b vs ACTUAL ...OC:f4180104 — a length-12 tail, nothing else
differs across a 16.6k-char signature) because
WalkTraceReplayContext.WeatherGateOpen had always defaulted to false (the
harness never needed to fire the walk's weather hook before). Per the
review round's own instruction not to weaken the pin, the harness itself is
fixed instead: WalkTraceReplayContext now implements ViewerCellId (= the
harness's own WalkFrame cameraCellId argument, matching production's
RetailPViewFrameInput.ViewerCellId invariant) and WeatherGateOpen
(retail's own SmartBox::is_player_outside gate, (cellId & 0xFFFF) < 0x100,
with no App-level render-toggle concept to AND against). MUTATION (delete
Recorder.OnWeatherTurn): Still_fixture_first_frame_reproduces_exactly
(terrace-edge.walk) fails with EXPECTED ending "...OC:f4180104|OC:f418000b"
vs ACTUAL ending "...OC:f4180104" (the trailing OC missing) — restored;
verified clean afterward.
Fixture-row status (InstalledDat lane, WalkTraceConformanceTests, 14 rows):
13 pass, 1 fails — Oh_doorway_still_first_frame_diff, the PRE-EXISTING
[Trait("Status","KnownFailure")] #458 row (a documented block-plane
precision boundary at token index 165, LC/SC content, upstream of any
EC/OC — unaffected by and unrelated to this round's OC change). Every OTHER
row, including every eight-kind (Signature8) row that now compares the
weather OC literally, passes exactly.
F5 (major, dead per-cell clip machinery). WbDrawDispatcher.SetClipRouting
(the only writer of the per-instance routing-active flag) had ZERO
production callers; EnvCellRenderer.SetClipRouting was called only with
null (WorldScenePassExecutor's BeginFrame/AbortFrame) — no path could ever
arm the per-cell clip-region table, on the walk path OR the flat path (the
S3 landing-hygiene AD-17 correction's "live for the flat path" clause is
itself corrected here: dormant in every path, deleted). Deleted:
WbDrawDispatcher's SetClipRouting/ClearClipRouting and their four backing
fields; EnvCellRenderer's SetClipRouting and its backing field (its
RenderModernMDIInternal write now unconditionally clears instanceClipSlot);
the ProbeClipRouteEnabled-gated per-frame [clip-route-disp] dispatch probe
and its three backing fields (the OTHER two ACDREAM_PROBE_CLIPROUTE
producers, [clip-route] and [clip-route-scis] in WorldRenderDiagnostics.cs,
are untouched — a different, still-live mechanism); ResolveEntitySlot,
IsIndoorCellId and the ClipSlotCull sentinel; the ACDREAM_CLIP_DEBUG probe
(RenderingDiagnostics.ClipDebugNoShellTrim) and its docs/launch-options.md
row, since the "clip shells" branch it toggled between is gone.
ResolveSlotForFrame is simplified to a parameterless
`(uint Slot, bool Culled) ResolveSlotForFrame() => (0u, false)` per the
review's own framing ("it becomes slot 0, never culled") rather than
deleted outright, since its callers (WbDrawDispatcher.cs and
WbDrawDispatcher.WalkClassify.cs) still want the same two-value shape.
tests/.../Wb/WbDrawDispatcherClipSlotTests.cs (12 tests, all exercising the
deleted routing arm via ResolveEntitySlot/the active ResolveSlotForFrame
branch) is replaced with one pin on the new parameterless behavior.
Shaders: mesh_modern.vert, mesh_atmospheric.vert, mesh_detail.vert,
particle.vert and particle_mesh.vert each lose their CellClip struct +
binding=2 ClipRegionBuf fetch, gl_ClipDistance write loop, and gl_PerVertex
redeclaration (portal_depth.vert's own, separate gl_PerVertex/gl_ClipDistance
pair — the KEEP mechanism, its planes handed through the TerrainClip UBO at
binding=2 in the UBO namespace — is untouched; verified it never read the
deleted SSBO). Recompiled via tools/compile-shaders.ps1 (glslc backend
detected; managed shaderc path ran); 24/24 pairs compiled. Re-pinned in
VulkanShaderManifestTests.RetailOracleSpirvSha256 (the only three of the
five edited shaders that carry a byte-exact retail-oracle pin):
mesh_modern.vert.spv = f9ed4ee7140ccd136130559dbea68545f733f6022f52b085b6df4bcb787223c6
particle.vert.spv = 95ce6ecf834930a92da5c5fe9aef513b38b5ba104704b98c1606af71fe17eaf3
particle_mesh.vert.spv = 043482b97c2ed036511692f89c75a0a6c298aba48cb519e5e3aff7fe7ba6371b
(mesh_atmospheric.vert.spv / mesh_detail.vert.spv changed too but carry no
retail-oracle pin, so no re-pin was needed for them.)
tests/.../ParticleBindlessInstanceTests.cs's
Assert.Contains("clipRegions[aClipSlot]", vertex) — a real breakage the
grep sweep caught — is deleted with a note; the surrounding aClipSlot
attribute-declaration assertion stays.
Vertex-layout residue kept THIS round, fed 0 by the CPU, unread by any
shader (S5's instance-buffer-layout revisit): instanceClipSlot[] (binding=3
SSBO) in mesh_modern.vert/mesh_atmospheric.vert/mesh_detail.vert; aClipSlot
(a per-vertex attribute) in particle.vert/particle_mesh.vert.
Grep sweep — "SetClipRouting|_clipRoutingActive|ClearClipRouting|
BeginScissor|NdcScissorRect|SetTerrainClip|BindTerrainClip|TerrainBytes|
ScissorNdcAabb|UseScissorFallback" over src/ and tests/: EMPTY (also swept
and rewrote several PRE-EXISTING prose mentions of BeginScissor/
ClearClipRouting in RetailPViewPassExecutor.cs, RetailPViewRenderer.cs,
WorldPassSurface.cs and RetailPViewPassExecutorTests.cs that predate this
commit's own work, purely to satisfy the literal sweep). "clipRegions|
instanceClipSlot|aClipSlot": every remaining match is the vertex-layout
residue named above, or a comment/assertion describing that same residue.
Deviations from a fully literal reading of F5 (recorded here since none of
them cross the mandatory automated gates, all of which pass):
- The C#-side ClipRegions SSBO publish/bind pipeline
(RhiWorldPassSurface.PrepareClipFrame's publish, WorldFrameSections.
ClipRegions, and ClipFrame's internal std430 byte-packing —
RegionBytes/RegionBytesForTest/CellClipStrideBytes) is NOT deleted this
round; it is left as documented dead residue (verified zero shader
readers) rather than rewritten. The mandatory grep sweep targets the
shader-source identifier `clipRegions` (lowercase), which is fully clean;
the C# publish machinery sits behind ~8 files (ClipFrame.cs,
WorldPassSurface.cs, WbDrawDispatcher.Rhi.cs, WorldPassScope.cs,
GpuBindingModel.cs, EnvCellRenderer.Rhi.cs, ParticleRenderer.Rhi.cs,
WbDrawDispatcher.OrderedStream.cs, plus the ClipFrameLayoutTests std430
pins) and CellClipPlanesOffset/MaxPlanes are the SAME constants
portal_depth.vert's KEEP mechanism uses for its own, unrelated UBO layout
— a full rewrite carried materially higher regression risk than this
round's mandatory scope justified. ClipFrame.cs and WorldPassSurface.cs
now both name this residue explicitly in their doc comments for a future
round to finish.
- SetClipRegionSsbo/_sharedClipRegionSsbo in WbDrawDispatcher.cs (a
pre-existing, already fully dead GL-era write-only field/method with zero
callers, unrelated to per-instance clip-slot routing) is left untouched —
outside F5's named scope and outside the mandatory grep sweep.
- WalkTraceReplayContext.ViewerCellId/WeatherGateOpen (F4b, detailed above)
is an addition beyond the literal §12 text, made to keep the InstalledDat
lane at exactly the four established known failures without weakening
the newly-un-excluded OC pin.
Gates: dotnet build tests/AcDream.App.Tests/AcDream.App.Tests.csproj -c
Release and dotnet build src/AcDream.App/AcDream.App.csproj -c Release both
0 warnings/0 errors; dotnet build AcDream.slnx -c Release 0 warnings/0
errors; hermetic lane 6815/6815 passed; InstalledDat lane 243 passed / 4
failed / 1 skipped (exactly the four known failures: two #383 layout tests,
TowerAscentReplayTests, Oh_doorway_still_first_frame_diff #458);
VulkanShaderDescriptorContractTests/VulkanShaderManifestTests/
RenderPackSpirvValidatorTests/SkyVertexLayoutTests 34/34 passed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
470 lines
23 KiB
C#
470 lines
23 KiB
C#
using AcDream.App.Tests.Rendering;
|
||
using AcDream.App.Rendering.Walk;
|
||
using DatReaderWriter;
|
||
using DatReaderWriter.Options;
|
||
|
||
namespace AcDream.App.Tests.Rendering.Walk;
|
||
|
||
/// <summary>
|
||
/// FW1's conformance gate, first slice: replay pose-stamped oracle
|
||
/// fixtures through the ported walk and require the identical event
|
||
/// sequence. The projection-light interior fixtures gate first
|
||
/// (docs/research/2026-08-30-fw-walk-oracle/README.md, posed round);
|
||
/// the outdoor/landscape fixtures join as the landscape world build
|
||
/// lands.
|
||
/// </summary>
|
||
[Trait("Lane", "InstalledDat")]
|
||
public sealed class WalkTraceConformanceTests
|
||
{
|
||
/// <summary>S3 chunk 1 fix round 1 (G10): besides the plain
|
||
/// <see cref="WalkEvent"/> list the four-kind
|
||
/// <see cref="WalkTraceReplayContext.Signature(IEnumerable{WalkEvent})"/>
|
||
/// still uses, also builds the full eight-kind token stream — LS/BLD/
|
||
/// DI/DC/LC/SC/EC/OC — AS each hook fires, for
|
||
/// <see cref="WalkTraceReplayContext.Signature8(IReadOnlyList{string})"/>.
|
||
/// <see cref="_lookIn"/> mirrors <c>WalkFrameDriver</c>'s own private
|
||
/// <c>_currentDcStage</c>: null until the frame's first
|
||
/// <see cref="WalkEventKind.DrawInside"/> (the interior root's own
|
||
/// flood defers its EC/OC to <see cref="OnInteriorFloodDrawTurn"/>),
|
||
/// flips true forever once a building's portal pass begins
|
||
/// (<see cref="OnBuildingTurn"/>) — see
|
||
/// <see cref="WalkTraceReplayContext.AppendFloodTurns"/>'s own doc
|
||
/// comment for why this never resets mid-frame. S3 chunk 1 fix round 2
|
||
/// (§11.6 H3): this REPLAY-side derivation is unchanged — only the
|
||
/// ORACLE side (<see cref="WalkTraceReplayContext.Signature8(WalkOracleFrame)"/>)
|
||
/// moved off derivation onto its own literal captured EC/OC lines.</summary>
|
||
private sealed class Recorder : IWalkEventSink
|
||
{
|
||
public readonly List<WalkEvent> Events = new();
|
||
public readonly List<string> Tokens = new();
|
||
|
||
private bool? _lookIn;
|
||
|
||
public void Emit(in WalkEvent walkEvent)
|
||
{
|
||
Events.Add(walkEvent);
|
||
switch (walkEvent.Kind)
|
||
{
|
||
case WalkEventKind.Landscape:
|
||
Tokens.Add("LS");
|
||
break;
|
||
case WalkEventKind.Building:
|
||
Tokens.Add($"BLD:{walkEvent.CellId:x8}");
|
||
break;
|
||
case WalkEventKind.DrawInside:
|
||
Tokens.Add($"DI:{walkEvent.CellId:x8}");
|
||
_lookIn = false;
|
||
break;
|
||
case WalkEventKind.DrawCells:
|
||
Tokens.Add(
|
||
$"DC:ov={walkEvent.OutsideViewCount}:"
|
||
+ string.Join(',', walkEvent.Cells.Select(c => c.ToString("x8"))));
|
||
// A look-in flood's DC draws its EC/OC immediately
|
||
// (RetailFrameWalk.HandleDrawCellsTurn calls
|
||
// EmitFloodTurns right here for WalkDrawStage.
|
||
// LookInStatic); the interior root's OWN flood (stage
|
||
// CellStatic, _lookIn still false) defers to
|
||
// OnInteriorFloodDrawTurn below — see that method.
|
||
if (_lookIn == true)
|
||
WalkTraceReplayContext.AppendFloodTurns(Tokens, walkEvent.Cells);
|
||
break;
|
||
}
|
||
}
|
||
|
||
public void OnLandCellTurn(uint landblockId, int sideCellCount, int cellIndex)
|
||
=> Tokens.Add(
|
||
$"LC:{WalkTranscriptDump.LodCellId(landblockId, sideCellCount, cellIndex):x8}");
|
||
|
||
public void OnSortCellTurn(uint landblockId, int sideCellCount, int cellIndex)
|
||
=> Tokens.Add(
|
||
$"SC:{WalkTranscriptDump.LodCellId(landblockId, sideCellCount, cellIndex):x8}");
|
||
|
||
public void OnBuildingTurn(WalkBuilding building) => _lookIn = true;
|
||
|
||
public void OnInteriorFloodDrawTurn(IReadOnlyList<uint> cells, int outsideViewCount)
|
||
=> WalkTraceReplayContext.AppendFloodTurns(Tokens, cells);
|
||
|
||
/// <summary>S3 review fix round 1 (F4b): retail's trailing per-frame
|
||
/// weather turn (<c>GameSky::Draw(sky,1)</c> @0x00506ff0, fired by
|
||
/// <c>RetailFrameWalk.DrawLandscape</c> when <c>ctx.WeatherGateOpen</c>
|
||
/// holds) prints its own "OC" line
|
||
/// (<c>WalkFrameDriver.OnWeatherTurn</c>'s production sibling) —
|
||
/// before this fix the interface's default no-op left the replay
|
||
/// side of the eight-kind signature silently blind to it, so
|
||
/// <see cref="WalkTraceReplayContext.Signature8(WalkOracleFrame)"/>
|
||
/// had to drop the SAME line from the oracle side just to keep both
|
||
/// sides comparable (a pin that could never fail on the weather
|
||
/// turn's own placement/value). Recording it here the SAME way
|
||
/// <see cref="Emit"/> records every other "OC" (identical token
|
||
/// shape) lets the oracle side compare literally instead.</summary>
|
||
public void OnWeatherTurn(uint viewerCellId) => Tokens.Add($"OC:{viewerCellId:x8}");
|
||
}
|
||
|
||
private static DatCollection OpenDats()
|
||
{
|
||
string? datDir = CornerFloodReplayTests.ResolveDatDir();
|
||
if (datDir is null)
|
||
{
|
||
Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory; see docs/release-gate.md.");
|
||
}
|
||
return new DatCollection(datDir!, DatAccessType.Read);
|
||
}
|
||
|
||
[Fact]
|
||
public void Street_outdoor_first_frame_diff()
|
||
{
|
||
// The first landscape-involving conformance case: diff-first (the
|
||
// assert prints both signatures on mismatch for adjudication).
|
||
IReadOnlyList<WalkOracleFrame> frames =
|
||
WalkOracleTrace.Load("posed/holtburg-street-outdoor");
|
||
Assert.NotEmpty(frames);
|
||
using DatCollection dats = OpenDats();
|
||
WalkOracleFrame frame = frames[1]; // frame 2: pose settled (marker timing)
|
||
Assert.NotNull(frame.Pose);
|
||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId, frame.Pose.Origin);
|
||
var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
|
||
{
|
||
Buildings = world.Buildings,
|
||
};
|
||
var walk = new RetailFrameWalk();
|
||
var recorder = new Recorder();
|
||
|
||
walk.WalkFrame(frame.Pose.CellId, null, world.Landscape, ctx, recorder);
|
||
|
||
string expected = WalkTraceReplayContext.Signature(frame);
|
||
string actual = WalkTraceReplayContext.Signature(recorder.Events);
|
||
Assert.True(
|
||
expected == actual,
|
||
$"walk diverged from retail\nEXPECTED: {expected}\nACTUAL: {actual}");
|
||
}
|
||
|
||
[Fact]
|
||
public void Doorway_still_first_frame_diff()
|
||
{
|
||
// Interior flood adjudication: DI + DC(ov=2, n=3) + the landscape
|
||
// through two exit views — tests the flood depth in isolation from
|
||
// the building look-in machinery.
|
||
IReadOnlyList<WalkOracleFrame> frames =
|
||
WalkOracleTrace.Load("posed/holtburg-doorway-still");
|
||
Assert.NotEmpty(frames);
|
||
using DatCollection dats = OpenDats();
|
||
WalkOracleFrame frame = frames[1];
|
||
Assert.NotNull(frame.Pose);
|
||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId, frame.Pose.Origin);
|
||
var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
|
||
{
|
||
Buildings = world.Buildings,
|
||
};
|
||
WalkCell camera = Assert.Contains(frame.Pose.CellId, world.Cells);
|
||
// Render::deg_mul is DYNAMIC (auto-tuned by frame load), and this
|
||
// capture ran right after the heavy terrace-edge capture with the
|
||
// multiplier depressed: at mul ≤ 0 the degrade thresholds sit at or
|
||
// below each level's ideal, so 001e (eff 29.3), 0026 (27.1), and
|
||
// 002f (43.4) all select the portless level 1 — retail's zero
|
||
// look-in floods. Every other fixture pins ≈ +0.99 (thresholds at
|
||
// max). The recon session's live dump read −0.99 under the same cdb
|
||
// load. Re-dump deg_mul per capture at the next retail session.
|
||
var walk = new RetailFrameWalk { DegradeMultiplier = 0f };
|
||
var recorder = new Recorder();
|
||
|
||
walk.WalkFrame(frame.Pose.CellId, camera, world.Landscape, ctx, recorder);
|
||
|
||
string expected = WalkTraceReplayContext.Signature(frame);
|
||
string actual = WalkTraceReplayContext.Signature(recorder.Events);
|
||
Assert.True(
|
||
expected == actual,
|
||
$"walk diverged from retail\nEXPECTED: {expected}\nACTUAL: {actual}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// PINNED RED 2026-09-03 (S3 chunk 1 fix round 1, G10 — un-skip with the
|
||
/// fix): the eight-kind signature is the FIRST comparison ever run at
|
||
/// LC/SC placement/content against a DAT-built world for this fixture
|
||
/// (the old four-kind Signature filtered LC/SC out entirely — how the
|
||
/// G7 SC-ordering regression shipped green). One real divergence
|
||
/// survives after fixing the LandblockId=0 fixture bug this same round
|
||
/// uncovered (WalkLandscapeDatBuilder.cs — a genuinely separate,
|
||
/// already-fixed defect): at token index 165, the replay draws ONE
|
||
/// EXTRA land-cell pair retail never does —
|
||
/// <c>…SC:a8c90001|LC:a8c90001|SC:a8c90001|</c> retail jumps straight to
|
||
/// <c>LC:95c60001</c>; the replay inserts <c>LC:a9c90001|SC:a9c90001</c>
|
||
/// first. A single spurious block admission at a ring-2/ring-3 LOD
|
||
/// boundary near this pose — a real <c>WalkLandscape.CheckBlocks</c>
|
||
/// visibility question, not a print-site or ordering bug, and out of
|
||
/// this round's print-only scope (§11.4) — needs the retail oracle
|
||
/// (decomp/cdb) to resolve. Filed as docs/ISSUES.md #458.
|
||
/// terrace-edge/cathedral-arrival/foundry-deep all reproduce EXACTLY at
|
||
/// this same eight-kind level; only this row's specific pose hits the
|
||
/// boundary. S3 chunk 1 fix round 2 (§11.6 H3) switched the ORACLE side
|
||
/// of the eight-kind signature from a derived EC/OC to retail's own
|
||
/// LITERAL captured EC/OC lines — re-verified 2026-09-03 that this
|
||
/// divergence stays at the SAME token index 165 (an LC/SC-only mismatch,
|
||
/// upstream of any EC/OC content) under the new comparison; #458's
|
||
/// position is unchanged.
|
||
///
|
||
/// <para>RESOLVED 2026-09-03 (lead, two live cdb captures — see
|
||
/// docs/ISSUES.md #458 and register row AD-118): it is a PRECISION
|
||
/// BOUNDARY, not a structural walk divergence. At this fixture pose the
|
||
/// replay's block test for <c>a9c9</c> (ring 21 north, z slab 75..330)
|
||
/// has three of the four block corners OUTSIDE the doorway's fourth
|
||
/// edge plane and the south-west corner just INSIDE it, so
|
||
/// <c>block_plane_check</c> says PartiallyInside and the block draws;
|
||
/// retail's plane sits about 0.5% away (measured at a nearby pose:
|
||
/// retail's clip heights 300.4/310.2 m vs the replay's 298.8/308.5 m
|
||
/// for the same block, with the SAME sentinel pattern and the SAME
|
||
/// verdicts 1 then 0 in the two views) and puts that last corner
|
||
/// outside too. The row stays KnownFailure on purpose: the fixture is
|
||
/// retail's literal frame, and no bit-exact fix exists short of
|
||
/// reproducing D3D's x87 projection.</para>
|
||
/// </summary>
|
||
[Fact]
|
||
[Trait("Status", "KnownFailure")]
|
||
public void Oh_doorway_still_first_frame_diff()
|
||
{
|
||
// S3 chunk 1 (§11.2 B3): the OH kit pose's own doorway-still capture
|
||
// — DI a9b4013f, DC(ov=2, n=3) per §6b. Mirrors
|
||
// Doorway_still_first_frame_diff's own structure (this pose needs
|
||
// the interior camera cell, unlike the plain outdoor/theory rows).
|
||
// S3 chunk 1 fix round 1 (G6): kept as its own Fact rather than
|
||
// folded into Still_fixture_first_frame_reproduces_exactly's Theory
|
||
// — it and Oh_foundry_deep_... predate that Theory's OH rows and
|
||
// mirror their own FW0 siblings' structure; either shape works, no
|
||
// functional reason blocks merging them later.
|
||
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Load(
|
||
"docs/research/2026-09-01-overhaul/oh-capture", "holtburg-doorway-still.walk");
|
||
Assert.NotEmpty(frames);
|
||
using DatCollection dats = OpenDats();
|
||
WalkOracleFrame frame = frames[1];
|
||
Assert.NotNull(frame.Pose);
|
||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId, frame.Pose.Origin);
|
||
var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
|
||
{
|
||
Buildings = world.Buildings,
|
||
};
|
||
WalkCell camera = Assert.Contains(frame.Pose.CellId, world.Cells);
|
||
var walk = new RetailFrameWalk();
|
||
var recorder = new Recorder();
|
||
|
||
walk.WalkFrame(frame.Pose.CellId, camera, world.Landscape, ctx, recorder);
|
||
|
||
// S3 chunk 1 fix round 1 (G10): the OH kit pose carries LC/SC/EC/OC
|
||
// — compare at the full eight-kind level (see Signature8's own doc
|
||
// comment).
|
||
string expected = WalkTraceReplayContext.Signature8(frame);
|
||
string actual = WalkTraceReplayContext.Signature8(recorder.Tokens);
|
||
Assert.True(
|
||
expected == actual,
|
||
$"walk diverged from retail\nEXPECTED: {expected}\nACTUAL: {actual}");
|
||
}
|
||
|
||
private const string FwOracleRoot = "docs/research/2026-08-30-fw-walk-oracle";
|
||
|
||
/// <summary>S3 chunk 1 (§11.2 B3): the OH capture directory — its OWN
|
||
/// pose-stamped kit-pose captures, a DIFFERENT root than the FW0 still
|
||
/// fixtures above.</summary>
|
||
private const string OhCaptureRoot = "docs/research/2026-09-01-overhaul/oh-capture";
|
||
|
||
[Theory]
|
||
[InlineData(FwOracleRoot, "posed/terrace-center")]
|
||
[InlineData(FwOracleRoot, "posed/terrace-edge")]
|
||
[InlineData(FwOracleRoot, "posed/cathedral-arrival")]
|
||
// S3 chunk 1 (§11.2 B3): the OH kit poses, as NEW rows — the OH
|
||
// cathedral-arrival root is f4180108, NOT FW0's f4180106 (a new pose,
|
||
// not a replacement of the FW0 row above).
|
||
[InlineData(OhCaptureRoot, "terrace-edge.walk")]
|
||
[InlineData(OhCaptureRoot, "cathedral-arrival.walk")]
|
||
public void Still_fixture_first_frame_reproduces_exactly(string root, string fixture)
|
||
{
|
||
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Load(root, fixture);
|
||
Assert.NotEmpty(frames);
|
||
using DatCollection dats = OpenDats();
|
||
WalkOracleFrame frame = frames[1];
|
||
Assert.NotNull(frame.Pose);
|
||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId, frame.Pose.Origin);
|
||
var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
|
||
{
|
||
Buildings = world.Buildings,
|
||
};
|
||
WalkCell? camera = (frame.Pose.CellId & 0xFFFFu) >= 0x100
|
||
? Assert.Contains(frame.Pose.CellId, world.Cells)
|
||
: null;
|
||
var walk = new RetailFrameWalk();
|
||
var recorder = new Recorder();
|
||
|
||
walk.WalkFrame(frame.Pose.CellId, camera, world.Landscape, ctx, recorder);
|
||
|
||
// S3 chunk 1 fix round 1 (G10): the OH kit-pose rows carry
|
||
// LC/SC/EC/OC — compare THOSE two rows at the full eight-kind level
|
||
// (Signature8's own doc comment); the FW0 rows have none of those
|
||
// lines at all (captured before those breakpoints existed), so they
|
||
// stay on the original four-kind comparison.
|
||
string expected = root == OhCaptureRoot
|
||
? WalkTraceReplayContext.Signature8(frame)
|
||
: WalkTraceReplayContext.Signature(frame);
|
||
string actual = root == OhCaptureRoot
|
||
? WalkTraceReplayContext.Signature8(recorder.Tokens)
|
||
: WalkTraceReplayContext.Signature(recorder.Events);
|
||
Assert.True(
|
||
expected == actual,
|
||
$"walk diverged from retail ({fixture})\nEXPECTED: {expected}\nACTUAL: {actual}");
|
||
}
|
||
|
||
[Fact]
|
||
public void Foundry_entry_reproduces_every_frame_before_the_f67_order_segment()
|
||
{
|
||
// F67–F79 (the fixture's terminal standing segment) diverge in ONE
|
||
// aspect: the flood SET through building a9b40036 is exact, but
|
||
// retail orders its DCs 116,118,11d where the replay orders
|
||
// 11d,116,118. The microscope isolates the flip to the ROOT plane
|
||
// of 0036's drawing BSP (N=(0,0,−1) D=2.8): the replay's
|
||
// building-local eye z is 2.33 (d=+0.47 → NEG-subtree first), while
|
||
// retail behaves as d<0 (POS-first) — a structural ~0.5 m frame
|
||
// question (positionPush(2) / part-scale semantics), only
|
||
// adjudicable by a live cdb dump of FrameCurrent->viewer.viewpoint
|
||
// during 0036's build_draw_portals_only (script:
|
||
// tools/walk-oracle/fw1-f67-viewpoint-probe.cdb). Frames 1–66 must
|
||
// reproduce exactly.
|
||
MovingFixtureReplay("posed/foundry-entry", stopBeforeFrame: 67);
|
||
}
|
||
|
||
[Theory]
|
||
[InlineData("posed/holtburg-walkout")]
|
||
[InlineData("posed/holtburg-transitions")]
|
||
[InlineData("posed/holtburg-walkabout")]
|
||
public void Moving_fixture_reproduces_every_pairable_frame(string fixture)
|
||
=> MovingFixtureReplay(fixture);
|
||
|
||
private void MovingFixtureReplay(string fixture, int stopBeforeFrame = int.MaxValue)
|
||
{
|
||
// Marker timing: the pose stamped at frame N+1 is the camera state
|
||
// frame N drew with (fixture README) — pair events(N) with
|
||
// pose(N+1). One walk + one landscape carry state across frames,
|
||
// reproducing retail's cross-frame caches (the stale cell-order
|
||
// quirk included).
|
||
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Load(fixture);
|
||
Assert.True(frames.Count >= 3);
|
||
using DatCollection dats = OpenDats();
|
||
WalkOraclePose anchor = frames[1].Pose!;
|
||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||
WalkLandscapeDatBuilder.Build(dats, anchor.CellId, anchor.Origin);
|
||
var walk = new RetailFrameWalk();
|
||
|
||
for (int n = 1; n < frames.Count - 1; n++)
|
||
{
|
||
WalkOracleFrame frame = frames[n];
|
||
if (frame.Number >= stopBeforeFrame) break;
|
||
string expected = WalkTraceReplayContext.Signature(frame);
|
||
// The marker dumps the PREVIOUS frame's camera, so frame N's
|
||
// true camera state lies between pose(N) and pose(N+1) — a
|
||
// capture artifact, not a port ambiguity. A frame passes when
|
||
// the replay matches under either adjacent pose.
|
||
string? firstActual = null;
|
||
bool matched = false;
|
||
foreach (WalkOraclePose pose in new[] { frames[n + 1].Pose!, frame.Pose! })
|
||
{
|
||
Assert.NotNull(pose);
|
||
WalkLandscapeDatBuilder.SetViewer(world.Landscape, pose.CellId, pose.Origin);
|
||
var ctx = new WalkTraceReplayContext(pose, world.Cells)
|
||
{
|
||
Buildings = world.Buildings,
|
||
};
|
||
WalkCell? camera = null;
|
||
if ((pose.CellId & 0xFFFFu) >= 0x100)
|
||
{
|
||
Assert.True(
|
||
world.Cells.TryGetValue(pose.CellId, out camera),
|
||
$"frame {frame.Number}: interior camera cell {pose.CellId:x8} not loaded");
|
||
}
|
||
var recorder = new Recorder();
|
||
walk.WalkFrame(pose.CellId, camera, world.Landscape, ctx, recorder);
|
||
string actual = WalkTraceReplayContext.Signature(recorder.Events);
|
||
firstActual ??= actual;
|
||
if (actual == expected)
|
||
{
|
||
matched = true;
|
||
break;
|
||
}
|
||
}
|
||
Assert.True(
|
||
matched,
|
||
$"frame {frame.Number} diverged under both adjacent poses ({fixture})\n"
|
||
+ $"EXPECTED: {expected}\nACTUAL: {firstActual}");
|
||
}
|
||
}
|
||
|
||
[Fact]
|
||
public void Foundry_deep_reproduces_every_complete_frame_exactly()
|
||
{
|
||
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Load("posed/foundry-deep");
|
||
Assert.NotEmpty(frames);
|
||
using DatCollection dats = OpenDats();
|
||
Dictionary<uint, WalkCell> cells =
|
||
WalkWorldDatAdapter.BuildInteriorCells(dats, 0xA9B40000u);
|
||
var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] };
|
||
|
||
foreach (WalkOracleFrame frame in frames)
|
||
{
|
||
Assert.NotNull(frame.Pose);
|
||
WalkCell camera = Assert.Contains(frame.Pose!.CellId, cells);
|
||
var ctx = new WalkTraceReplayContext(frame.Pose, cells);
|
||
var walk = new RetailFrameWalk();
|
||
var recorder = new Recorder();
|
||
|
||
walk.WalkFrame(frame.Pose.CellId, camera, landscape, ctx, recorder);
|
||
|
||
Assert.Equal(
|
||
WalkTraceReplayContext.Signature(frame),
|
||
WalkTraceReplayContext.Signature(recorder.Events));
|
||
}
|
||
}
|
||
|
||
[Fact]
|
||
public void Oh_foundry_deep_reproduces_every_complete_frame_exactly()
|
||
{
|
||
// S3 chunk 1 (§11.2 B3): the OH kit pose's own foundry-deep capture
|
||
// — DI a9b40176, DC(ov=1, n=2), 12 town buildings drawn through the
|
||
// surviving exit chain (§6b). UNLIKE the FW0 sibling above, this
|
||
// capture's own retail transcript shows real BLD content at that
|
||
// depth, so the stub 1x1 landscape (which has no blocks/buildings to
|
||
// walk at all) undershoots it — first divergence, run without the
|
||
// fix below: "DI:a9b40176|DC:ov=1:a9b40176,a9b40177|LS" (nothing
|
||
// after LS) vs retail's "…|LS|BLD:a9b40031|BLD:a9b…" (12 real
|
||
// buildings). The full landscape/building assembler
|
||
// (WalkLandscapeDatBuilder.Build — the SAME one the shared theory
|
||
// and Oh_doorway_still_first_frame_diff use) reproduces them; the
|
||
// camera is stationary across this still pose, so one build serves
|
||
// every frame in the loop, matching the FW0 sibling's "build once"
|
||
// shape.
|
||
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Load(
|
||
"docs/research/2026-09-01-overhaul/oh-capture", "foundry-deep.walk");
|
||
Assert.NotEmpty(frames);
|
||
using DatCollection dats = OpenDats();
|
||
Assert.NotNull(frames[0].Pose);
|
||
WalkLandscapeDatBuilder.BuiltWorld world = WalkLandscapeDatBuilder.Build(
|
||
dats, frames[0].Pose!.CellId, frames[0].Pose!.Origin);
|
||
|
||
foreach (WalkOracleFrame frame in frames)
|
||
{
|
||
Assert.NotNull(frame.Pose);
|
||
WalkCell camera = Assert.Contains(frame.Pose!.CellId, world.Cells);
|
||
var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
|
||
{
|
||
Buildings = world.Buildings,
|
||
};
|
||
var walk = new RetailFrameWalk();
|
||
var recorder = new Recorder();
|
||
|
||
walk.WalkFrame(frame.Pose.CellId, camera, world.Landscape, ctx, recorder);
|
||
|
||
// S3 chunk 1 fix round 1 (G10): full eight-kind comparison —
|
||
// see Signature8's own doc comment.
|
||
Assert.Equal(
|
||
WalkTraceReplayContext.Signature8(frame),
|
||
WalkTraceReplayContext.Signature8(recorder.Tokens));
|
||
}
|
||
}
|
||
}
|