test(overhaul): land reviewed C1c replay correction and retire AD-118
This commit is contained in:
parent
744b1b0efb
commit
42afec6141
10 changed files with 492 additions and 176 deletions
|
|
@ -218,8 +218,10 @@ remaining closeout gates. No merge main.
|
|||
|
||||
## #472 — Stair-arch walk fixture cannot distinguish retail e229 residency from block rejection
|
||||
|
||||
**Status:** IN-PROGRESS — capture complete; §40's diagnostic resolves the
|
||||
mismatch. Permanent default replay correction and review remain before closure.
|
||||
**Status:** DONE 2026-09-05 — C1c made the captured matrix path, explicit
|
||||
next-marker pose, and live-view EC multiplicity the permanent shared replay
|
||||
defaults. The exact 25-frame Signature8 proof passes; no renderer defect was
|
||||
established. Geometry/membership closeout remains a separate C1a chunk.
|
||||
**Severity:** EVIDENCE BLOCKER (no production renderer defect established)
|
||||
**Component:** InstalledDat walk-conformance harness / retail capture corpus
|
||||
|
||||
|
|
@ -266,8 +268,9 @@ next-marker pose, and per-live-view EC recording reproduce all four stair
|
|||
frames exactly (2158/2158), with e229 Outside and no LC/SC. All six OH walk
|
||||
fixtures plus the new capture are exact (25/25 complete frames). The historical
|
||||
capture/acceptance paragraphs above are superseded; this is a replay-harness
|
||||
defect, not an accepted x87 renderer divergence. Packet §40 contracts the
|
||||
permanent correction, regression tests and reviews. #472 is not closed yet.
|
||||
defect, not an accepted x87 renderer divergence. C1c permanently corrected the
|
||||
shared replay/parser/recorder and added discriminating regression tests; #472
|
||||
is closed. This does not claim universal bit-identical D3D/x87 projection.
|
||||
|
||||
|
||||
## #471 — Production walk-ordered building detail was classified but never rendered
|
||||
|
|
@ -633,23 +636,24 @@ so this class is caught on every gate, not only when the lead remembers the env
|
|||
|
||||
## #458 — Walk admits land block `a9c9` at holtburg-doorway-still that retail does not draw
|
||||
|
||||
**Status:** REOPENED 2026-09-05 — the former x87-only attribution below is
|
||||
disproven by S5 packet §40's diagnostic. Corrected matrix rays/projection and
|
||||
next-marker pose reproduce all four doorway frames exactly (1185/1185).
|
||||
Permanent default replay correction, KnownFailure removal and review remain.
|
||||
The 2026-09-03 investigation/resolution below is retained as superseded history.
|
||||
**Status:** DONE 2026-09-05 — C1c permanently corrected the shared replay to
|
||||
the captured matrix ray/projection path and explicit next-marker pose. All four
|
||||
doorway frames reproduce exactly (1185/1185); the ordinary frame-1 row passes
|
||||
and its KnownFailure trait is removed. The 2026-09-03 investigation/resolution
|
||||
below is retained as superseded history.
|
||||
**Severity:** MEDIUM (retail-order exactness; one far land cell drawn that retail skips — no known visible artifact)
|
||||
**Component:** `WalkLandscape.CheckBlocks` / `LandCellCheck` block-level visibility at a LOD-ring boundary
|
||||
|
||||
**Symptom:** replaying `docs/research/2026-09-01-overhaul/oh-capture/holtburg-doorway-still.walk.log`
|
||||
**Superseded symptom:** replaying `docs/research/2026-09-01-overhaul/oh-capture/holtburg-doorway-still.walk.log`
|
||||
frame 2 (camera cell `a9b4013f`) through `RetailFrameWalk`, the signature diverges at token 165:
|
||||
retail `…LC:a8c90001|SC:a8c90001|LC:95c60001…` vs acdream
|
||||
`…LC:a8c90001|SC:a8c90001|LC:a9c90001|SC:a9c90001|LC:95c60001…` — acdream admits the
|
||||
side-1 (ring ≥ 5) block `a9c9` through the interior root's exit views; retail's
|
||||
`LScape::draw_check_blocks` @0x00505f80 / `landcell_check` @0x005050a0 do not. The other
|
||||
three kit poses (terrace-edge, cathedral-arrival, foundry-deep) reproduce exactly.
|
||||
`WalkTraceConformanceTests.Oh_doorway_still_first_frame_diff` carries the row tagged
|
||||
`Status=KnownFailure` with the position documented.
|
||||
`WalkTraceConformanceTests.Oh_doorway_still_first_frame_diff` formerly carried
|
||||
the row tagged `Status=KnownFailure`; C1c removed the trait after the ordinary
|
||||
shared-path frame-1 equality passed.
|
||||
|
||||
**Lead experiment (2026-09-03):** with `DegradeMultiplier = 0f` (the FW0 sibling's
|
||||
cdb-depressed value) the row diverges at the SAME token 165 — the degrade level is NOT the
|
||||
|
|
@ -695,7 +699,7 @@ pose against the decomp (the per-view union, the block MinZ/MaxZ clip heights, t
|
|||
exit-view edge planes, not the cell test. Resolve in the S3 review round or an S3 chunk 5;
|
||||
remove the KnownFailure trait in the same commit.
|
||||
|
||||
**Resolution (2026-09-03 afternoon, lead + owner, two live cdb captures on the paired 2013
|
||||
**Superseded resolution (2026-09-03 afternoon, lead + owner, two live cdb captures on the paired 2013
|
||||
client at a NEARBY doorway pose — `docs/research/2026-09-01-overhaul/oh-capture/
|
||||
holtburg-doorway-still.blockset.log` and `.blockcheck.log`, templates
|
||||
`tools/walk-oracle/oh/oh-capture-blockset.cdb.template` / `oh-capture-blockcheck.cdb.template`):**
|
||||
|
|
@ -718,8 +722,11 @@ no `LC`. One far LOD block, one corner, one plane, at a boundary. The morning z-
|
|||
structural notes above stand (slab and loop shape match). No bit-exact fix exists short of
|
||||
reproducing D3D's x87 projection and `copy_view`'s float rounding; the conformance row stays
|
||||
`Status=KnownFailure` with its doc comment rewritten to this finding, and the register
|
||||
carries it as AD-118. The two look-in-flood divergences (2)/(3) above are the pre-existing
|
||||
FW1 near-win gate state and remain tracked by `WalkLookInGateSweepTests` (skipped).
|
||||
carries it as AD-118. C1c later disproved that attribution: the capture used the
|
||||
matrix branch and next-marker pose, and the corrected shared replay reproduces
|
||||
the literal transcript. The two historical look-in-flood observations likewise
|
||||
disappear under the corrected association; the text above remains history, not
|
||||
an accepted current failure.
|
||||
|
||||
## #457 — Two App tests pass only in Release (IL-offset pins fail under Debug)
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -743,6 +743,13 @@ Rynthid Recall6150→3032001C corroborates owner generation4; historical route
|
|||
labels are not location evidence (479-rynthid-location-evidence.md). #479
|
||||
transform cause remains unproven. No change to G4 FAIL or the open obligations.
|
||||
|
||||
C1c current (packet§53): reviewed nine-file test/doc return now lands from
|
||||
scratch963728612; all17 source/TRX hashes rechecked, prior lead68/68 and both
|
||||
review lenses closed. Six test blobs unchanged; production/fixtures untouched.
|
||||
#458/#472 closed as harness defects, AD-118 retired, counted104/13/91.
|
||||
Clean post-landing Release/canonical conformance is next, then §45 activation.
|
||||
The later476F1 AD-121 must preserve this retirement and produce92 active rows.
|
||||
|
||||
## 14. Risk register (condensed)
|
||||
|
||||
- **R1 second scene graph.** Graph records own only topology/membership facts;
|
||||
|
|
|
|||
|
|
@ -3763,6 +3763,25 @@ AcDream.App.dll copies remained SHA-256
|
|||
ACE remains UDP 9000/PID 13340. Do not build production or launch another
|
||||
graphical client beside the owner's running client.
|
||||
|
||||
**C1c permanent result (uncommitted implementer return, 2026-09-05):** the
|
||||
ordinary replay now uses the coherent captured `newmethod=1` matrix path; the
|
||||
parser retains literal P(N) while exposing events(N)/P(N+1), including the
|
||||
last raw marker; and the shared Recorder emits EC once per live `TopView`
|
||||
before OC once per cell. The permanent proof consumes all six historical OH
|
||||
walks plus the new capture: 25/25 complete frames match at full ordered
|
||||
Signature8, the doorway frame-1 row passes without KnownFailure, the stair
|
||||
pins four EC turns/two adjacent duplicate pairs and no LC/SC:e2290001, and
|
||||
the focused restored lanes pass 29/29 parser/projection, 27/27 conformance,
|
||||
and 12/12 production-world context invariance. The five required mutations
|
||||
were run separately and restored: legacy rays fail the center round trip at
|
||||
0.7078837 px; one-EC-per-cell fails the stair pin (expected 4, actual 2);
|
||||
same-marker association fails the synthetic final-marker ID and doorway
|
||||
frame 1 at token 165; changing matrix word 0 fails the captured-word binding
|
||||
(1065698052 vs 1065698053); redirecting the sixth fixture fails on the absent
|
||||
canonical `.walk.log`. No fixture or production byte changed, and this result
|
||||
does not claim universal bit-identical D3D/x87 math. Geometry/membership C1a
|
||||
remains separate.
|
||||
|
||||
### 40.2 C1c scope — permanent test-only correction
|
||||
|
||||
One OpenAI implementer uses the existing scratch, fast-forwarding this
|
||||
|
|
@ -4776,3 +4795,22 @@ requirement (AD-121 at landing), actual-command/coherence witnesses and mutation
|
|||
No global allocator expansion; unchanged phase/lifetime review stays closed.
|
||||
Prepared route-476-capture-current-frame.txt preserves first request plus two
|
||||
real resizes, then settled hand-item views; NOT RUN. No new client launched.
|
||||
|
||||
## 53. C1c accepted test-only return landed
|
||||
|
||||
While #476 F1 is implemented, root landed the already-reviewed nine-file C1c
|
||||
return from §43. Scratch96372861249a092998dd0dc6deed6d1d4196eb8a preserves
|
||||
the exact six reviewed test blobs; root rechecked all17 source/TRX hashes in
|
||||
artifacts/s5-c1c-02219318a/README.txt before committing. Both prior reviews and
|
||||
root68/68 proof in§43 remain applicable; no new code finding or review round.
|
||||
The three documentation hunks merge without replacing later campaign evidence.
|
||||
#458/#472 close as replay-harness defects; AD-118 retired in this same commit.
|
||||
Fresh root physical-row recount104 total/13 retired/91 active AD rows.
|
||||
|
||||
No production, fixture, package or graphical change. Exact current production
|
||||
remains #477 + #480; #476 is still unintegrated. Post-landing clean Release and
|
||||
canonical conformance checks are next, classifying every nonpassing identity.
|
||||
Only after those checks may §45's separately bounded geometry/membership witness
|
||||
activate on a new clean base. F1's later AD-121 addition must preserve retired
|
||||
AD-118 and make the combined AD count92, not restore the stale pre-C1c header.
|
||||
No new graphical run; G4 FAIL, remaining owner issues/full lanes/C2 still open.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ namespace AcDream.App.Tests.Rendering.Walk;
|
|||
///
|
||||
/// The final frame of every capture is dropped: the harness detaches at
|
||||
/// the frame MARKER, so the last frame never records its draws and would
|
||||
/// read as a false "outdoor, no landscape" frame.
|
||||
/// read as a false "outdoor, no landscape" frame. Its raw P record is
|
||||
/// nevertheless retained as the preceding complete frame's ReplayPose:
|
||||
/// RenderNormalMode's entry marker is prior-frame state, so events(N) pair
|
||||
/// with P(N+1), not their literal P(N) marker.
|
||||
/// </summary>
|
||||
public static class WalkOracleTrace
|
||||
{
|
||||
|
|
@ -23,6 +26,7 @@ public static class WalkOracleTrace
|
|||
List<WalkOracleEvent>? current = null;
|
||||
int currentNumber = 0;
|
||||
WalkOraclePose? currentPose = null;
|
||||
int frameAwaitingReplayPose = -1;
|
||||
|
||||
foreach (string line in lines)
|
||||
{
|
||||
|
|
@ -30,7 +34,10 @@ public static class WalkOracleTrace
|
|||
if (frameMatch.Success)
|
||||
{
|
||||
if (current is not null)
|
||||
{
|
||||
frames.Add(new WalkOracleFrame(currentNumber, current, currentPose));
|
||||
frameAwaitingReplayPose = frames.Count - 1;
|
||||
}
|
||||
currentNumber = int.Parse(
|
||||
frameMatch.Groups[1].Value, CultureInfo.InvariantCulture);
|
||||
current = new List<WalkOracleEvent>();
|
||||
|
|
@ -60,6 +67,17 @@ public static class WalkOracleTrace
|
|||
BitConverter.Int32BitsToSingle((int)raw[5]),
|
||||
BitConverter.Int32BitsToSingle((int)raw[6]),
|
||||
BitConverter.Int32BitsToSingle((int)raw[7]));
|
||||
// RenderNormalMode's entry marker observes the PREVIOUS
|
||||
// rendered frame: events(N) therefore use marker P(N+1).
|
||||
// Assign it explicitly to the already-complete prior frame.
|
||||
// This also consumes the last raw P marker even though its
|
||||
// own event-incomplete frame remains deliberately dropped.
|
||||
if (frameAwaitingReplayPose >= 0)
|
||||
{
|
||||
frames[frameAwaitingReplayPose] = frames[frameAwaitingReplayPose]
|
||||
with { ReplayPose = currentPose };
|
||||
frameAwaitingReplayPose = -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +198,7 @@ public static class WalkOracleTrace
|
|||
private static readonly Regex ObjectCellTurnPattern = new(@"^OC ([0-9a-f]{8})\s*$", RegexOptions.Compiled);
|
||||
}
|
||||
|
||||
/// <summary>The camera pose dumped at the frame marker (raw dwords from
|
||||
/// <summary>The literal camera pose dumped at the frame marker (raw dwords from
|
||||
/// <c>Render::viewer_pos</c> @0x0081ef00): the camera's cell id, world
|
||||
/// origin, and the Frame quaternion's four raw components (q0..q3 in
|
||||
/// storage order — axis convention resolved by the replay runner).
|
||||
|
|
@ -196,8 +214,14 @@ public sealed record WalkOraclePose(
|
|||
public sealed record WalkOracleFrame(
|
||||
int Number,
|
||||
IReadOnlyList<WalkOracleEvent> Events,
|
||||
WalkOraclePose? Pose = null)
|
||||
WalkOraclePose? Pose = null,
|
||||
WalkOraclePose? ReplayPose = null)
|
||||
{
|
||||
/// <summary><see cref="Pose"/> is the literal P record after this frame's
|
||||
/// marker. <see cref="ReplayPose"/> is the following marker's P record,
|
||||
/// the camera state this frame's events actually rendered with. The
|
||||
/// parser fills it from the raw final marker without creating a fake
|
||||
/// event frame.</summary>
|
||||
/// <summary>The frame's root: the first DrawInside cell, or null when outdoor-rooted.</summary>
|
||||
public uint? InteriorRootCell
|
||||
=> Events.FirstOrDefault(e => e.Kind == WalkOracleEventKind.DrawInside)?.CellId;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public sealed class WalkOracleTraceTests
|
|||
private const string OhRoot = "docs/research/2026-09-01-overhaul/oh-capture";
|
||||
|
||||
/// <summary>(root, fixture name) pairs. Every FW0 still/posed fixture
|
||||
/// keeps its old bare name under <see cref="FwRoot"/>; the five OH
|
||||
/// keeps its old bare name under <see cref="FwRoot"/>; the six OH
|
||||
/// walk captures (§11.2 B3) join under <see cref="OhRoot"/> with the
|
||||
/// <c>.walk</c> filename-half suffix.</summary>
|
||||
public static readonly TheoryData<string, string> AllFixtures = new()
|
||||
|
|
@ -40,6 +40,7 @@ public sealed class WalkOracleTraceTests
|
|||
{ OhRoot, "cathedral-arrival.walk" },
|
||||
{ OhRoot, "foundry-deep.walk" },
|
||||
{ OhRoot, "cathedral-leak.walk" },
|
||||
{ OhRoot, "cathedral-stair-arch.walk" },
|
||||
};
|
||||
|
||||
[Theory]
|
||||
|
|
@ -55,6 +56,33 @@ public sealed class WalkOracleTraceTests
|
|||
Assert.All(frames, f => Assert.NotEmpty(f.Events));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replay_pose_uses_next_marker_including_the_dropped_final_frame()
|
||||
{
|
||||
string[] lines =
|
||||
[
|
||||
"F 1",
|
||||
"P 11110100 3f800000 40000000 40400000 3f800000 00000000 00000000 00000000",
|
||||
"LS",
|
||||
"F 2",
|
||||
"P 22220200 40800000 40a00000 40c00000 00000000 3f800000 00000000 00000000",
|
||||
"LS",
|
||||
"F 3",
|
||||
"P 33330300 40e00000 41000000 41100000 00000000 00000000 3f800000 00000000",
|
||||
];
|
||||
|
||||
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Parse(lines);
|
||||
|
||||
Assert.Equal(2, frames.Count); // F3 remains an incomplete event frame.
|
||||
Assert.Equal(0x11110100u, frames[0].Pose!.CellId);
|
||||
Assert.Equal(0x22220200u, frames[0].ReplayPose!.CellId);
|
||||
Assert.Equal(new System.Numerics.Vector3(4f, 5f, 6f), frames[0].ReplayPose!.Origin);
|
||||
Assert.Equal(0x22220200u, frames[1].Pose!.CellId);
|
||||
Assert.Equal(0x33330300u, frames[1].ReplayPose!.CellId);
|
||||
Assert.Equal(new System.Numerics.Vector3(7f, 8f, 9f), frames[1].ReplayPose!.Origin);
|
||||
Assert.DoesNotContain(frames, frame => frame.Number == 3);
|
||||
}
|
||||
|
||||
/// <summary>S3 chunk 1 fix round 1 (G8/G10): the "assert separately"
|
||||
/// half of <see cref="WalkTraceReplayContext.Signature8(WalkOracleFrame)"/>'s
|
||||
/// own doc comment — that method's derivation never produces the
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Walk;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Walk;
|
||||
|
||||
public sealed class WalkReplayProjectionTests
|
||||
{
|
||||
private const string OhRoot =
|
||||
"docs/research/2026-09-01-overhaul/oh-capture";
|
||||
|
||||
[Fact]
|
||||
public void Captured_matrix_screen_ray_screen_round_trips_without_legacy_half_pixel()
|
||||
{
|
||||
WalkOracleFrame captured = Assert.Single(WalkOracleTrace.Load(
|
||||
OhRoot,
|
||||
"cathedral-stair-arch-e229.combined-r1"));
|
||||
WalkOraclePose pose = Assert.IsType<WalkOraclePose>(captured.ReplayPose);
|
||||
var context = new WalkTraceReplayContext(
|
||||
pose,
|
||||
new Dictionary<uint, WalkCell>());
|
||||
Vector2[] points =
|
||||
[
|
||||
new(512f, 360f),
|
||||
new(0f, 0f),
|
||||
new(1024f, 0f),
|
||||
new(1024f, 720f),
|
||||
new(0f, 720f),
|
||||
new(127.25f, 83.75f),
|
||||
new(817.5f, 611.125f),
|
||||
];
|
||||
|
||||
foreach (Vector2 point in points)
|
||||
{
|
||||
Vector3 ray = context.Rays.RayThrough(point.X, point.Y);
|
||||
Vector2 roundTrip = context.ProjectWorldToScreen(
|
||||
pose.Origin + ray * 100f);
|
||||
float error = Vector2.Distance(point, roundTrip);
|
||||
Assert.True(
|
||||
error <= 0.001f,
|
||||
string.Create(
|
||||
CultureInfo.InvariantCulture,
|
||||
$"matrix round-trip error {error:R} at {point}"));
|
||||
}
|
||||
|
||||
Vector3 legacyCenterRay = LegacyRayThrough(pose, 512f, 360f);
|
||||
Vector2 legacyCenter = context.ProjectWorldToScreen(
|
||||
pose.Origin + legacyCenterRay * 100f);
|
||||
Assert.InRange(MathF.Abs(legacyCenter.X - 512f), 0.45f, 0.55f);
|
||||
Assert.InRange(MathF.Abs(legacyCenter.Y - 360f), 0.45f, 0.55f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Configured_projection_is_bound_to_captured_newmethod_matrix_words()
|
||||
{
|
||||
string[] lines = File.ReadAllLines(FixturePath(
|
||||
"cathedral-stair-arch-e229.projection-mode"));
|
||||
int newMethod = Array.FindIndex(lines, line => line.Trim() == "NEWMETHOD");
|
||||
Assert.True(newMethod >= 0);
|
||||
Assert.Matches(@"\s00000001\s*$", lines[newMethod + 1]);
|
||||
int heading = Array.FindIndex(lines, line => line.Trim() == "VIEW_TO_CLIP");
|
||||
Assert.True(heading >= 0);
|
||||
uint[] captured = lines.Skip(heading + 1)
|
||||
.Take(4)
|
||||
.SelectMany(ParseDumpRow)
|
||||
.ToArray();
|
||||
|
||||
Matrix4x4 configured = WalkTraceReplayContext.CapturedViewToClip();
|
||||
uint[] actual =
|
||||
[
|
||||
Bits(configured.M11), Bits(configured.M12), Bits(configured.M13), Bits(configured.M14),
|
||||
Bits(configured.M21), Bits(configured.M22), Bits(configured.M23), Bits(configured.M24),
|
||||
Bits(configured.M31), Bits(configured.M32), Bits(configured.M33), Bits(configured.M34),
|
||||
Bits(configured.M41), Bits(configured.M42), Bits(configured.M43), Bits(configured.M44),
|
||||
];
|
||||
|
||||
Assert.Equal(captured, actual);
|
||||
}
|
||||
|
||||
private static Vector3 LegacyRayThrough(
|
||||
WalkOraclePose pose,
|
||||
float screenX,
|
||||
float screenY)
|
||||
{
|
||||
var rotation = new Quaternion(pose.Q1, pose.Q2, pose.Q3, pose.Q0);
|
||||
Vector3 right = Vector3.Transform(Vector3.UnitX, rotation);
|
||||
Vector3 forward = Vector3.Transform(Vector3.UnitY, rotation);
|
||||
Vector3 up = Vector3.Transform(Vector3.UnitZ, rotation);
|
||||
float u = screenX * 0.00025f - 0.127875f;
|
||||
float w = screenY * 0.00025f - 0.089875f;
|
||||
return right * u + forward * 0.1330766976f - up * w;
|
||||
}
|
||||
|
||||
private static IEnumerable<uint> ParseDumpRow(string line)
|
||||
{
|
||||
string[] fields = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
Assert.Equal(5, fields.Length);
|
||||
return fields.Skip(1).Select(value => uint.Parse(
|
||||
value,
|
||||
NumberStyles.HexNumber,
|
||||
CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
private static uint Bits(float value) =>
|
||||
BitConverter.SingleToUInt32Bits(value);
|
||||
|
||||
private static string FixturePath(string fixtureName) => Path.Combine(
|
||||
FindRepositoryRoot(),
|
||||
Path.Combine(OhRoot.Split('/')),
|
||||
fixtureName + ".log");
|
||||
|
||||
private static string FindRepositoryRoot()
|
||||
{
|
||||
DirectoryInfo? directory = new(AppContext.BaseDirectory);
|
||||
while (directory is not null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
|
||||
return directory.FullName;
|
||||
directory = directory.Parent;
|
||||
}
|
||||
throw new InvalidOperationException("AcDream.slnx not found.");
|
||||
}
|
||||
}
|
||||
|
|
@ -308,8 +308,7 @@ public sealed partial class WalkTraceConformanceTests
|
|||
Assert.NotEmpty(poseFrames);
|
||||
Assert.True(poseFrames.Count > 1, $"{fixtureName}: need a captured frame 2.");
|
||||
WalkOracleFrame poseFrame = poseFrames[1];
|
||||
Assert.NotNull(poseFrame.Pose);
|
||||
WalkOraclePose pose = poseFrame.Pose!;
|
||||
WalkOraclePose pose = Assert.IsType<WalkOraclePose>(poseFrame.ReplayPose);
|
||||
|
||||
IReadOnlyList<WalkAlphaDepthFrame> depthFrames = WalkAlphaDepthTrace.Load(OhCaptureRoot, fixtureName);
|
||||
Assert.True(depthFrames.Count > 1, $"{fixtureName}: need a captured frame 2's PM/PC content.");
|
||||
|
|
@ -394,8 +393,7 @@ public sealed partial class WalkTraceConformanceTests
|
|||
Assert.NotEmpty(poseFrames);
|
||||
Assert.True(poseFrames.Count > 1, $"{fixtureName}: need a captured frame 2.");
|
||||
WalkOracleFrame poseFrame = poseFrames[1];
|
||||
Assert.NotNull(poseFrame.Pose);
|
||||
WalkOraclePose pose = poseFrame.Pose!;
|
||||
WalkOraclePose pose = Assert.IsType<WalkOraclePose>(poseFrame.ReplayPose);
|
||||
|
||||
IReadOnlyList<WalkAlphaDepthFrame> depthFrames = WalkAlphaDepthTrace.Load(OhCaptureRoot, fixtureName);
|
||||
Assert.True(depthFrames.Count > 1, $"{fixtureName}: need a captured frame 2's AM/FL content.");
|
||||
|
|
|
|||
|
|
@ -34,19 +34,18 @@ public sealed partial class WalkTraceConformanceTests
|
|||
/// ORACLE side (<see cref="WalkTraceReplayContext.Signature8(WalkOracleFrame)"/>)
|
||||
/// moved off derivation onto its own literal captured EC/OC lines.
|
||||
///
|
||||
/// <para>KNOWN LIMIT (S3 review fix round 1, F4a): the production
|
||||
/// transcript prints <c>EC</c> once per LIVE VIEW of a flood cell
|
||||
/// (retail's <c>setup_view</c>/<c>DrawEnvCell</c> loop,
|
||||
/// <c>PView::DrawCells</c> @0x005a4ab1–0x005a4acc), but this replay
|
||||
/// side's <see cref="WalkTraceReplayContext.AppendFloodTurns"/> still
|
||||
/// derives ONE <c>EC</c> per cell — the sink event carries no per-cell
|
||||
/// view count. Every fixture passes only because no captured flood cell
|
||||
/// holds more than one live view at DrawCells time. A fixture with a
|
||||
/// two-view flood cell would fail here on the second <c>EC</c>; S5's
|
||||
/// transcript closeout carries the per-cell view count through the
|
||||
/// sink event and removes this note.</para></summary>
|
||||
/// <para>C1c: the recorder borrows the real cell map and reads each
|
||||
/// cell's live <c>TopView.ViewCount</c> while the flood callback still
|
||||
/// owns it. That reproduces retail's setup_view/DrawEnvCell loop at
|
||||
/// <c>PView::DrawCells</c> @0x005a4ab1–0x005a4acc without changing a
|
||||
/// production event or deriving oracle EC/OC from DC.</para></summary>
|
||||
private sealed class Recorder : IWalkEventSink
|
||||
{
|
||||
private readonly IReadOnlyDictionary<uint, WalkCell> _cells;
|
||||
|
||||
public Recorder(IReadOnlyDictionary<uint, WalkCell> cells) =>
|
||||
_cells = cells;
|
||||
|
||||
public readonly List<WalkEvent> Events = new();
|
||||
public readonly List<string> Tokens = new();
|
||||
|
||||
|
|
@ -78,7 +77,7 @@ public sealed partial class WalkTraceConformanceTests
|
|||
// CellStatic, _lookIn still false) defers to
|
||||
// OnInteriorFloodDrawTurn below — see that method.
|
||||
if (_lookIn == true)
|
||||
WalkTraceReplayContext.AppendFloodTurns(Tokens, walkEvent.Cells);
|
||||
WalkTraceReplayContext.AppendFloodTurns(Tokens, walkEvent.Cells, _cells);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -94,7 +93,7 @@ public sealed partial class WalkTraceConformanceTests
|
|||
public void OnBuildingTurn(WalkBuilding building) => _lookIn = true;
|
||||
|
||||
public void OnInteriorFloodDrawTurn(IReadOnlyList<uint> cells, int outsideViewCount)
|
||||
=> WalkTraceReplayContext.AppendFloodTurns(Tokens, cells);
|
||||
=> WalkTraceReplayContext.AppendFloodTurns(Tokens, cells, _cells);
|
||||
|
||||
/// <summary>S3 review fix round 1 (F4b): retail's trailing per-frame
|
||||
/// weather turn (<c>GameSky::Draw(sky,1)</c> @0x00506ff0, fired by
|
||||
|
|
@ -132,17 +131,17 @@ public sealed partial class WalkTraceConformanceTests
|
|||
Assert.NotEmpty(frames);
|
||||
using DatCollection dats = OpenDats();
|
||||
WalkOracleFrame frame = frames[1]; // frame 2: pose settled (marker timing)
|
||||
Assert.NotNull(frame.Pose);
|
||||
WalkOraclePose pose = Assert.IsType<WalkOraclePose>(frame.ReplayPose);
|
||||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId, frame.Pose.Origin);
|
||||
var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
|
||||
WalkLandscapeDatBuilder.Build(dats, pose.CellId, pose.Origin);
|
||||
var ctx = new WalkTraceReplayContext(pose, world.Cells)
|
||||
{
|
||||
Buildings = world.Buildings,
|
||||
};
|
||||
var walk = new RetailFrameWalk(100f, 0.99f);
|
||||
var recorder = new Recorder();
|
||||
var recorder = new Recorder(world.Cells);
|
||||
|
||||
walk.WalkFrame(frame.Pose.CellId, null, world.Landscape, ctx, recorder);
|
||||
walk.WalkFrame(pose.CellId, null, world.Landscape, ctx, recorder);
|
||||
|
||||
string expected = WalkTraceReplayContext.Signature(frame);
|
||||
string actual = WalkTraceReplayContext.Signature(recorder.Events);
|
||||
|
|
@ -162,14 +161,14 @@ public sealed partial class WalkTraceConformanceTests
|
|||
Assert.NotEmpty(frames);
|
||||
using DatCollection dats = OpenDats();
|
||||
WalkOracleFrame frame = frames[1];
|
||||
Assert.NotNull(frame.Pose);
|
||||
WalkOraclePose pose = Assert.IsType<WalkOraclePose>(frame.ReplayPose);
|
||||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId, frame.Pose.Origin);
|
||||
var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
|
||||
WalkLandscapeDatBuilder.Build(dats, pose.CellId, pose.Origin);
|
||||
var ctx = new WalkTraceReplayContext(pose, world.Cells)
|
||||
{
|
||||
Buildings = world.Buildings,
|
||||
};
|
||||
WalkCell camera = Assert.Contains(frame.Pose.CellId, world.Cells);
|
||||
WalkCell camera = Assert.Contains(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
|
||||
|
|
@ -179,9 +178,9 @@ public sealed partial class WalkTraceConformanceTests
|
|||
// 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(100f, 0f);
|
||||
var recorder = new Recorder();
|
||||
var recorder = new Recorder(world.Cells);
|
||||
|
||||
walk.WalkFrame(frame.Pose.CellId, camera, world.Landscape, ctx, recorder);
|
||||
walk.WalkFrame(pose.CellId, camera, world.Landscape, ctx, recorder);
|
||||
|
||||
string expected = WalkTraceReplayContext.Signature(frame);
|
||||
string actual = WalkTraceReplayContext.Signature(recorder.Events);
|
||||
|
|
@ -191,48 +190,16 @@ public sealed partial class WalkTraceConformanceTests
|
|||
}
|
||||
|
||||
/// <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>
|
||||
/// C1c supersedes the former #458 KnownFailure. The literal eight-kind
|
||||
/// fixture exposed <c>LC/SC:a9c90001</c> only because the replay used
|
||||
/// <c>Render::copy_view</c>'s inactive legacy-ray branch and paired the
|
||||
/// events with the stale same-frame marker. Retail's captured
|
||||
/// <c>newmethod=1</c> matrix path plus the explicit next-marker replay
|
||||
/// pose reproduces the complete frame exactly. The historical numeric
|
||||
/// investigation remains in docs/ISSUES.md; it is not a current
|
||||
/// production divergence or permission to relax this equality.
|
||||
/// </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
|
||||
|
|
@ -248,19 +215,19 @@ public sealed partial class WalkTraceConformanceTests
|
|||
"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);
|
||||
WalkOracleFrame frame = frames[0];
|
||||
WalkOraclePose pose = Assert.IsType<WalkOraclePose>(frame.ReplayPose);
|
||||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId, frame.Pose.Origin);
|
||||
var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
|
||||
WalkLandscapeDatBuilder.Build(dats, pose.CellId, pose.Origin);
|
||||
var ctx = new WalkTraceReplayContext(pose, world.Cells)
|
||||
{
|
||||
Buildings = world.Buildings,
|
||||
};
|
||||
WalkCell camera = Assert.Contains(frame.Pose.CellId, world.Cells);
|
||||
WalkCell camera = Assert.Contains(pose.CellId, world.Cells);
|
||||
var walk = new RetailFrameWalk(100f, 0.99f);
|
||||
var recorder = new Recorder();
|
||||
var recorder = new Recorder(world.Cells);
|
||||
|
||||
walk.WalkFrame(frame.Pose.CellId, camera, world.Landscape, ctx, recorder);
|
||||
walk.WalkFrame(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
|
||||
|
|
@ -272,6 +239,93 @@ public sealed partial class WalkTraceConformanceTests
|
|||
$"walk diverged from retail\nEXPECTED: {expected}\nACTUAL: {actual}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Oh_walk_corpus_reproduces_all_25_complete_frames_at_full_signature8()
|
||||
{
|
||||
string[] fixtures =
|
||||
[
|
||||
"holtburg-doorway-still.walk",
|
||||
"terrace-edge.walk",
|
||||
"cathedral-arrival.walk",
|
||||
"foundry-deep.walk",
|
||||
"cathedral-leak.walk",
|
||||
"cathedral-stair-arch.walk",
|
||||
"cathedral-stair-arch-e229.combined-r1",
|
||||
];
|
||||
|
||||
using DatCollection dats = OpenDats();
|
||||
int comparedFrames = 0;
|
||||
foreach (string fixture in fixtures)
|
||||
{
|
||||
IReadOnlyList<WalkOracleFrame> frames =
|
||||
WalkOracleTrace.Load(OhCaptureRoot, fixture);
|
||||
Assert.Equal(
|
||||
fixture == "cathedral-stair-arch-e229.combined-r1" ? 1 : 4,
|
||||
frames.Count);
|
||||
WalkOraclePose anchor = Assert.IsType<WalkOraclePose>(frames[0].ReplayPose);
|
||||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||||
WalkLandscapeDatBuilder.Build(dats, anchor.CellId, anchor.Origin);
|
||||
var walk = new RetailFrameWalk(100f, 0.99f);
|
||||
|
||||
foreach (WalkOracleFrame frame in frames)
|
||||
{
|
||||
WalkOraclePose pose = Assert.IsType<WalkOraclePose>(frame.ReplayPose);
|
||||
WalkLandscapeDatBuilder.SetViewer(
|
||||
world.Landscape, pose.CellId, pose.Origin);
|
||||
var context = new WalkTraceReplayContext(pose, world.Cells)
|
||||
{
|
||||
Buildings = world.Buildings,
|
||||
};
|
||||
WalkCell? camera = null;
|
||||
if ((pose.CellId & 0xFFFFu) >= 0x100u)
|
||||
{
|
||||
camera = Assert.Contains(pose.CellId, world.Cells);
|
||||
}
|
||||
var recorder = new Recorder(world.Cells);
|
||||
|
||||
walk.WalkFrame(
|
||||
pose.CellId, camera, world.Landscape, context, recorder);
|
||||
|
||||
if (fixture == "cathedral-stair-arch.walk")
|
||||
{
|
||||
Assert.Equal(4, recorder.Tokens.Count(t => t == "EC:f4180106"));
|
||||
Assert.Equal(2, recorder.Tokens.Zip(recorder.Tokens.Skip(1))
|
||||
.Count(pair => pair.First == "EC:f4180106"
|
||||
&& pair.Second == "EC:f4180106"));
|
||||
Assert.DoesNotContain("LC:e2290001", recorder.Tokens);
|
||||
Assert.DoesNotContain("SC:e2290001", recorder.Tokens);
|
||||
}
|
||||
|
||||
string expected = WalkTraceReplayContext.Signature8(frame);
|
||||
string actual = WalkTraceReplayContext.Signature8(recorder.Tokens);
|
||||
Assert.True(
|
||||
expected == actual,
|
||||
$"{fixture} frame {frame.Number} diverged at token "
|
||||
+ $"{FirstMismatch(expected, actual)}\n"
|
||||
+ $"EXPECTED: {expected}\nACTUAL: {actual}");
|
||||
comparedFrames++;
|
||||
}
|
||||
}
|
||||
Assert.Equal(25, comparedFrames);
|
||||
}
|
||||
|
||||
private static string FirstMismatch(string expected, string actual)
|
||||
{
|
||||
string[] expectedTokens = expected.Split('|');
|
||||
string[] actualTokens = actual.Split('|');
|
||||
int common = Math.Min(expectedTokens.Length, actualTokens.Length);
|
||||
int index = 0;
|
||||
while (index < common && expectedTokens[index] == actualTokens[index])
|
||||
index++;
|
||||
string expectedToken = index < expectedTokens.Length
|
||||
? expectedTokens[index]
|
||||
: "<end>";
|
||||
string actualToken = index < actualTokens.Length
|
||||
? actualTokens[index]
|
||||
: "<end>";
|
||||
return $"{index}: expected {expectedToken}, actual {actualToken}";
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -294,20 +348,20 @@ public sealed partial class WalkTraceConformanceTests
|
|||
Assert.NotEmpty(frames);
|
||||
using DatCollection dats = OpenDats();
|
||||
WalkOracleFrame frame = frames[1];
|
||||
Assert.NotNull(frame.Pose);
|
||||
WalkOraclePose pose = Assert.IsType<WalkOraclePose>(frame.ReplayPose);
|
||||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||||
WalkLandscapeDatBuilder.Build(dats, frame.Pose!.CellId, frame.Pose.Origin);
|
||||
var ctx = new WalkTraceReplayContext(frame.Pose, world.Cells)
|
||||
WalkLandscapeDatBuilder.Build(dats, pose.CellId, pose.Origin);
|
||||
var ctx = new WalkTraceReplayContext(pose, world.Cells)
|
||||
{
|
||||
Buildings = world.Buildings,
|
||||
};
|
||||
WalkCell? camera = (frame.Pose.CellId & 0xFFFFu) >= 0x100
|
||||
? Assert.Contains(frame.Pose.CellId, world.Cells)
|
||||
WalkCell? camera = (pose.CellId & 0xFFFFu) >= 0x100
|
||||
? Assert.Contains(pose.CellId, world.Cells)
|
||||
: null;
|
||||
var walk = new RetailFrameWalk(100f, 0.99f);
|
||||
var recorder = new Recorder();
|
||||
var recorder = new Recorder(world.Cells);
|
||||
|
||||
walk.WalkFrame(frame.Pose.CellId, camera, world.Landscape, ctx, recorder);
|
||||
walk.WalkFrame(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
|
||||
|
|
@ -360,51 +414,39 @@ public sealed partial class WalkTraceConformanceTests
|
|||
IReadOnlyList<WalkOracleFrame> frames = WalkOracleTrace.Load(fixture);
|
||||
Assert.True(frames.Count >= 3);
|
||||
using DatCollection dats = OpenDats();
|
||||
WalkOraclePose anchor = frames[1].Pose!;
|
||||
WalkOraclePose anchor = Assert.IsType<WalkOraclePose>(frames[1].ReplayPose);
|
||||
WalkLandscapeDatBuilder.BuiltWorld world =
|
||||
WalkLandscapeDatBuilder.Build(dats, anchor.CellId, anchor.Origin);
|
||||
var walk = new RetailFrameWalk(100f, 0.99f);
|
||||
|
||||
for (int n = 1; n < frames.Count - 1; n++)
|
||||
for (int n = 1; n < frames.Count; 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! })
|
||||
// The parser pairs this event frame explicitly with P(N+1),
|
||||
// including the final raw marker. Never try the stale same-frame
|
||||
// marker as a fallback.
|
||||
WalkOraclePose pose = Assert.IsType<WalkOraclePose>(frame.ReplayPose);
|
||||
WalkLandscapeDatBuilder.SetViewer(world.Landscape, pose.CellId, pose.Origin);
|
||||
var ctx = new WalkTraceReplayContext(pose, world.Cells)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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(world.Cells);
|
||||
walk.WalkFrame(pose.CellId, camera, world.Landscape, ctx, recorder);
|
||||
string actual = WalkTraceReplayContext.Signature(recorder.Events);
|
||||
Assert.True(
|
||||
matched,
|
||||
$"frame {frame.Number} diverged under both adjacent poses ({fixture})\n"
|
||||
+ $"EXPECTED: {expected}\nACTUAL: {firstActual}");
|
||||
actual == expected,
|
||||
$"frame {frame.Number} diverged at its explicit next-marker pose ({fixture})\n"
|
||||
+ $"EXPECTED: {expected}\nACTUAL: {actual}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -420,13 +462,13 @@ public sealed partial class WalkTraceConformanceTests
|
|||
|
||||
foreach (WalkOracleFrame frame in frames)
|
||||
{
|
||||
Assert.NotNull(frame.Pose);
|
||||
WalkCell camera = Assert.Contains(frame.Pose!.CellId, cells);
|
||||
var ctx = new WalkTraceReplayContext(frame.Pose, cells);
|
||||
WalkOraclePose pose = Assert.IsType<WalkOraclePose>(frame.ReplayPose);
|
||||
WalkCell camera = Assert.Contains(pose.CellId, cells);
|
||||
var ctx = new WalkTraceReplayContext(pose, cells);
|
||||
var walk = new RetailFrameWalk(100f, 0.99f);
|
||||
var recorder = new Recorder();
|
||||
var recorder = new Recorder(cells);
|
||||
|
||||
walk.WalkFrame(frame.Pose.CellId, camera, landscape, ctx, recorder);
|
||||
walk.WalkFrame(pose.CellId, camera, landscape, ctx, recorder);
|
||||
|
||||
Assert.Equal(
|
||||
WalkTraceReplayContext.Signature(frame),
|
||||
|
|
@ -455,22 +497,22 @@ public sealed partial class WalkTraceConformanceTests
|
|||
"docs/research/2026-09-01-overhaul/oh-capture", "foundry-deep.walk");
|
||||
Assert.NotEmpty(frames);
|
||||
using DatCollection dats = OpenDats();
|
||||
Assert.NotNull(frames[0].Pose);
|
||||
WalkOraclePose anchor = Assert.IsType<WalkOraclePose>(frames[0].ReplayPose);
|
||||
WalkLandscapeDatBuilder.BuiltWorld world = WalkLandscapeDatBuilder.Build(
|
||||
dats, frames[0].Pose!.CellId, frames[0].Pose!.Origin);
|
||||
dats, anchor.CellId, anchor.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)
|
||||
WalkOraclePose pose = Assert.IsType<WalkOraclePose>(frame.ReplayPose);
|
||||
WalkCell camera = Assert.Contains(pose.CellId, world.Cells);
|
||||
var ctx = new WalkTraceReplayContext(pose, world.Cells)
|
||||
{
|
||||
Buildings = world.Buildings,
|
||||
};
|
||||
var walk = new RetailFrameWalk(100f, 0.99f);
|
||||
var recorder = new Recorder();
|
||||
var recorder = new Recorder(world.Cells);
|
||||
|
||||
walk.WalkFrame(frame.Pose.CellId, camera, world.Landscape, ctx, recorder);
|
||||
walk.WalkFrame(pose.CellId, camera, world.Landscape, ctx, recorder);
|
||||
|
||||
// S3 chunk 1 fix round 1 (G10): full eight-kind comparison —
|
||||
// see Signature8's own doc comment.
|
||||
|
|
|
|||
|
|
@ -19,28 +19,30 @@ namespace AcDream.App.Tests.Rendering.Walk;
|
|||
/// </summary>
|
||||
public sealed class WalkTraceReplayContext : IWalkFrameContext, IRetailFrameWalkContext
|
||||
{
|
||||
// Retail projection globals, dumped live from the capture client
|
||||
// (recon 2026-08-30 evening: Render::bw/bh/xinvscale/yinvscale/tx/ty/vdst).
|
||||
// The declared capture configuration. The active retail path is
|
||||
// Render::copy_view @0054dfc0's newmethod=1 arm, which calls
|
||||
// PrimD3DRender::ScreenToViewTransform @0059aa40 with the actual
|
||||
// viewport and the inverse WorldToView/ViewToClip matrices. The old
|
||||
// scalar xinvscale/tx ray is the other branch and is not equivalent:
|
||||
// it shifts this configuration's center by about half a pixel.
|
||||
public const float RetailViewportWidth = 1024f;
|
||||
public const float RetailViewportHeight = 720f;
|
||||
public const float RetailXInvScale = 0.00025f;
|
||||
public const float RetailYInvScale = 0.00025f;
|
||||
public const float RetailTx = 0.127875f;
|
||||
public const float RetailTy = 0.089875f;
|
||||
public const float RetailVdst = 0.1330766976f;
|
||||
|
||||
private sealed class RetailRayCaster(
|
||||
Vector3 right, Vector3 forward, Vector3 up) : IWalkRayCaster
|
||||
private sealed class CapturedD3dRayCaster(
|
||||
Matrix4x4 inverseWorldToView,
|
||||
Matrix4x4 viewToClip,
|
||||
float viewportWidth,
|
||||
float viewportHeight) : IWalkRayCaster
|
||||
{
|
||||
// Retail's unproject (copy_view's ray path; equal to
|
||||
// ScreenToViewTransform for these globals):
|
||||
// u = sx·xinvscale − tx; w = sy·yinvscale − ty
|
||||
// ray = Xaxis·u + Yaxis·vdst − Zaxis·w
|
||||
public Vector3 RayThrough(float screenX, float screenY)
|
||||
{
|
||||
float u = screenX * RetailXInvScale - RetailTx;
|
||||
float w = screenY * RetailYInvScale - RetailTy;
|
||||
return right * u + forward * RetailVdst - up * w;
|
||||
float ndcX = screenX * 2f / viewportWidth - 1f;
|
||||
float ndcY = 1f - screenY * 2f / viewportHeight;
|
||||
var view = new Vector3(
|
||||
ndcX / viewToClip.M11,
|
||||
ndcY / viewToClip.M22,
|
||||
1f);
|
||||
return Vector3.TransformNormal(view, inverseWorldToView);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,21 +75,57 @@ public sealed class WalkTraceReplayContext : IWalkFrameContext, IRetailFrameWalk
|
|||
Vector3 up = Vector3.Transform(Vector3.UnitZ, rotation);
|
||||
Vector3 right = Vector3.Transform(Vector3.UnitX, rotation);
|
||||
|
||||
// The exact retail frustum: tan(halfFovY) = ty/vdst, aspect = tx/ty.
|
||||
float fovY = 2f * MathF.Atan(RetailTy / RetailVdst);
|
||||
Matrix4x4 view = Matrix4x4.CreateLookAt(pose.Origin, pose.Origin + forward, up);
|
||||
Matrix4x4 projection = Matrix4x4.CreatePerspectiveFieldOfView(
|
||||
fovY, RetailTx / RetailTy, 0.1f, 5000f);
|
||||
Matrix4x4 view = CreateLeftHandedWorldToView(
|
||||
pose.Origin, right, up, forward);
|
||||
Matrix4x4 projection = CapturedViewToClip();
|
||||
_viewProjection = view * projection;
|
||||
|
||||
ViewportWidth = RetailViewportWidth;
|
||||
ViewportHeight = RetailViewportHeight;
|
||||
_rays = new RetailRayCaster(right, forward, up);
|
||||
Assert.True(Matrix4x4.Invert(view, out Matrix4x4 inverseView));
|
||||
_rays = new CapturedD3dRayCaster(
|
||||
inverseView,
|
||||
projection,
|
||||
ViewportWidth,
|
||||
ViewportHeight);
|
||||
|
||||
// The retail CY near plane: N = forward, d = −dot(eye, forward) − znear.
|
||||
CyPlane = new WalkPlane(forward, -Vector3.Dot(pose.Origin, forward) - 0.1f);
|
||||
}
|
||||
|
||||
internal Vector2 ProjectWorldToScreen(Vector3 point)
|
||||
{
|
||||
WalkScreenPoint screen = WalkScreenClip.TransformToScreen(
|
||||
point,
|
||||
_viewProjection,
|
||||
ViewportWidth,
|
||||
ViewportHeight);
|
||||
return new Vector2(screen.X / screen.W, screen.Y / screen.W);
|
||||
}
|
||||
|
||||
private static Matrix4x4 CreateLeftHandedWorldToView(
|
||||
Vector3 eye,
|
||||
Vector3 right,
|
||||
Vector3 up,
|
||||
Vector3 forward) => new(
|
||||
right.X, up.X, forward.X, 0f,
|
||||
right.Y, up.Y, forward.Y, 0f,
|
||||
right.Z, up.Z, forward.Z, 0f,
|
||||
-Vector3.Dot(right, eye),
|
||||
-Vector3.Dot(up, eye),
|
||||
-Vector3.Dot(forward, eye),
|
||||
1f);
|
||||
|
||||
internal static Matrix4x4 CapturedViewToClip()
|
||||
{
|
||||
static float F(uint bits) => BitConverter.UInt32BitsToSingle(bits);
|
||||
return new Matrix4x4(
|
||||
F(0x3F854304), 0f, 0f, 0f,
|
||||
0f, F(0x3FBD8723), 0f, 0f,
|
||||
0f, 0f, F(0x3F8000D2), F(0x3F800000),
|
||||
0f, 0f, F(0xBDCCCE1D), 0f);
|
||||
}
|
||||
|
||||
/// <summary>Building placements (camera-block-local) for the landscape
|
||||
/// fixtures; empty for the interior-only ones.</summary>
|
||||
public Dictionary<WalkBuilding, WalkWorldDatAdapter.BuildingEntry> Buildings { get; set; }
|
||||
|
|
@ -297,9 +335,12 @@ public sealed class WalkTraceReplayContext : IWalkFrameContext, IRetailFrameWalk
|
|||
}
|
||||
|
||||
/// <summary><c>PView::DrawCells</c>'s two complete reverse loops: every
|
||||
/// EnvCell shell far-to-near, THEN every object-list turn far-to-near —
|
||||
/// the SAME reversed cell order twice (not one reversed EC/OC pair per
|
||||
/// cell). S3 chunk 1 fix round 2 (§11.6 H3): this is now ONLY the
|
||||
/// live view's EnvCell entry far-to-near, THEN every cell's object-list
|
||||
/// turn far-to-near — the SAME reversed cell order twice, with EC
|
||||
/// repeated by the cell's live <c>TopView.ViewCount</c> and OC once.
|
||||
/// This reads the borrowed live cells while the flood callback still
|
||||
/// owns their view stacks, matching @005a4ab1–005a4acc and
|
||||
/// @005a4ade–005a4b1a. S3 chunk 1 fix round 2 (§11.6 H3): this is ONLY the
|
||||
/// REPLAY side's derivation — <see cref="WalkTraceConformanceTests"/>'s
|
||||
/// <c>Recorder</c> calls this at the exact hook that fires for each
|
||||
/// flood (a look-in's own <c>DrawCells</c> immediately; the interior
|
||||
|
|
@ -310,10 +351,18 @@ public sealed class WalkTraceReplayContext : IWalkFrameContext, IRetailFrameWalk
|
|||
/// this — the ORACLE side reads its own captured EC/OC lines literally
|
||||
/// instead of re-deriving them (see that method's own doc comment for
|
||||
/// why: a derivation compared to itself proves nothing).</summary>
|
||||
internal static void AppendFloodTurns(List<string> tokens, IReadOnlyList<uint> cells)
|
||||
internal static void AppendFloodTurns(
|
||||
List<string> tokens,
|
||||
IReadOnlyList<uint> cells,
|
||||
IReadOnlyDictionary<uint, WalkCell> liveCells)
|
||||
{
|
||||
for (int i = cells.Count - 1; i >= 0; i--)
|
||||
tokens.Add($"EC:{cells[i]:x8}");
|
||||
{
|
||||
WalkCell cell = liveCells[cells[i]];
|
||||
int liveViewCount = cell.NumView > 0 ? cell.TopView.ViewCount : 0;
|
||||
for (int view = 0; view < liveViewCount; view++)
|
||||
tokens.Add($"EC:{cells[i]:x8}");
|
||||
}
|
||||
for (int i = cells.Count - 1; i >= 0; i--)
|
||||
tokens.Add($"OC:{cells[i]:x8}");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue