diff --git a/launch-a6-issue98-cottage-gfxobj-dump.ps1 b/launch-a6-issue98-cottage-gfxobj-dump.ps1
new file mode 100644
index 0000000..9917d81
--- /dev/null
+++ b/launch-a6-issue98-cottage-gfxobj-dump.ps1
@@ -0,0 +1,46 @@
+$env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call"
+$env:ACDREAM_LIVE = '1'
+$env:ACDREAM_TEST_HOST = '127.0.0.1'
+$env:ACDREAM_TEST_PORT = '9000'
+$env:ACDREAM_TEST_USER = 'testaccount'
+$env:ACDREAM_TEST_PASS = 'testpassword'
+
+# A6.P3 #98 (2026-05-23 evening v2) — focused capture of the cottage
+# GfxObj 0x01000A2B's full polygon table. ACDREAM_DUMP_GFXOBJS triggers
+# a one-shot JSON dump the first time PhysicsDataCache.CacheGfxObj fires
+# for the listed id. The dump lands in the tests' fixture directory under
+# the worktree, so the harness can load it without copying.
+#
+# Reproduction steps for the user:
+# 1. Run this script (it launches in the foreground; log streams to the
+# file path below).
+# 2. Log into +Acdream. The cottage GfxObj caches when the streaming
+# worker walks the cottage building's mesh — which happens as soon
+# as the cottage landblock enters the streaming N1 (near) tier.
+# Holtburg's cottage (the one with the cellar) is at the spawn area,
+# so just being in-world is enough.
+# 3. Watch the log for "[gfxobj-dump] wrote 0x01000A2B polys=N → ..."
+# then close the client.
+#
+# After the dump file exists at
+# tests/AcDream.Core.Tests/Fixtures/issue98/0x01000A2B.gfxobj.json
+# come back to Claude to continue with the RegisterCottageGfxObj wiring.
+
+$env:ACDREAM_DUMP_GFXOBJS = '0x01000A2B'
+
+# Output dir is the relative fixture path; the dump infrastructure
+# resolves it against the worktree current dir (Set-Location below).
+$env:ACDREAM_DUMP_GFXOBJS_DIR = 'tests/AcDream.Core.Tests/Fixtures/issue98'
+
+# Keep the cell-transit probe on so the launch log shows when the player
+# enters cells — helps correlate the dump event with player position.
+$env:ACDREAM_PROBE_CELL = '1'
+
+$logPath = 'C:\Users\erikn\source\repos\acdream\.claude\worktrees\strange-albattani-3fc83c\a6-issue98-cottage-gfxobj-dump-launch.log'
+Write-Host "Log path: $logPath"
+Write-Host "Dump target: $env:ACDREAM_DUMP_GFXOBJS_DIR\0x01000A2B.gfxobj.json"
+Write-Host ''
+Write-Host 'After login, watch the log for [gfxobj-dump] then close the client.'
+
+Set-Location 'C:\Users\erikn\source\repos\acdream\.claude\worktrees\strange-albattani-3fc83c'
+dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug *> $logPath
diff --git a/tests/AcDream.Core.Tests/Fixtures/issue98/0x01000A2B.gfxobj.json b/tests/AcDream.Core.Tests/Fixtures/issue98/0x01000A2B.gfxobj.json
new file mode 100644
index 0000000..42800dd
--- /dev/null
+++ b/tests/AcDream.Core.Tests/Fixtures/issue98/0x01000A2B.gfxobj.json
@@ -0,0 +1,2346 @@
+{
+ "GfxObjId": 16779819,
+ "BoundingSphereOrigin": {
+ "X": -3.25267,
+ "Y": -1.02984,
+ "Z": 1.11488
+ },
+ "BoundingSphereRadius": 13.9887,
+ "ResolvedPolygons": [
+ {
+ "Id": 0,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 0,
+ "Z": -1
+ },
+ "D": -0
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": -8,
+ "Z": 0
+ },
+ {
+ "X": -12,
+ "Y": -8,
+ "Z": 0
+ },
+ {
+ "X": -12,
+ "Y": 8,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 1,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 0,
+ "Z": -1
+ },
+ "D": -0
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": 5,
+ "Z": 0
+ },
+ {
+ "X": 6,
+ "Y": 5,
+ "Z": 0
+ },
+ {
+ "X": 6,
+ "Y": -5.6,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 2,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 0.9805806,
+ "Z": 0.19611613
+ },
+ "D": -5.4912515
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": 5,
+ "Z": 3
+ },
+ {
+ "X": -8.0145,
+ "Y": 4,
+ "Z": 8
+ },
+ {
+ "X": 6,
+ "Y": 4,
+ "Z": 8
+ }
+ ]
+ },
+ {
+ "Id": 3,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 0,
+ "Z": -1
+ },
+ "D": -0
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": 1.8,
+ "Z": 0
+ },
+ {
+ "X": 8,
+ "Y": 1.8,
+ "Z": 0
+ },
+ {
+ "X": 8,
+ "Y": -2.4,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 4,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -9.4,
+ "Y": 8,
+ "Z": 4
+ },
+ {
+ "X": -8.4,
+ "Y": 8,
+ "Z": 4
+ },
+ {
+ "X": -5.8,
+ "Y": 8,
+ "Z": 3
+ },
+ {
+ "X": -12,
+ "Y": 8,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 5,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 0,
+ "Z": -1
+ },
+ "D": -0
+ },
+ "Vertices": [
+ {
+ "X": 8,
+ "Y": -2.4,
+ "Z": 0
+ },
+ {
+ "X": 6,
+ "Y": -2.4,
+ "Z": 0
+ },
+ {
+ "X": 6,
+ "Y": 1.8,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 6,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 0,
+ "Z": -1
+ },
+ "D": -0
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": -5.6,
+ "Z": 0
+ },
+ {
+ "X": -5.8,
+ "Y": -5.6,
+ "Z": 0
+ },
+ {
+ "X": -5.8,
+ "Y": 5,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 7,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 0,
+ "Z": -1
+ },
+ "D": -0
+ },
+ "Vertices": [
+ {
+ "X": -12,
+ "Y": 8,
+ "Z": 0
+ },
+ {
+ "X": -5.8,
+ "Y": 8,
+ "Z": 0
+ },
+ {
+ "X": -5.8,
+ "Y": -8,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 8,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": 8,
+ "Z": 3
+ },
+ {
+ "X": -5.8,
+ "Y": 8,
+ "Z": 0
+ },
+ {
+ "X": -12,
+ "Y": 8,
+ "Z": 0
+ },
+ {
+ "X": -12,
+ "Y": 8,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 9,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": -8,
+ "Z": 3
+ },
+ {
+ "X": -8.9,
+ "Y": -8,
+ "Z": 10
+ },
+ {
+ "X": -12,
+ "Y": -8,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 10,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 0.9805807,
+ "Z": 0.19611613
+ },
+ "D": -5.4912515
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": 4,
+ "Z": 8
+ },
+ {
+ "X": 6,
+ "Y": 5,
+ "Z": 3
+ },
+ {
+ "X": -5.8,
+ "Y": 5,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 11,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 1,
+ "Y": 0,
+ "Z": 0
+ },
+ "D": 5.8
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": -8,
+ "Z": 0
+ },
+ {
+ "X": -5.8,
+ "Y": -5.6,
+ "Z": 0
+ },
+ {
+ "X": -5.8,
+ "Y": -5.6,
+ "Z": 3
+ },
+ {
+ "X": -5.8,
+ "Y": -8,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 12,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -0.84990263,
+ "Z": 0.52693963
+ },
+ "D": -6.340274
+ },
+ "Vertices": [
+ {
+ "X": -8.0145,
+ "Y": -2.5,
+ "Z": 8
+ },
+ {
+ "X": -5.8,
+ "Y": -5.6,
+ "Z": 3
+ },
+ {
+ "X": 6,
+ "Y": -5.6,
+ "Z": 3
+ },
+ {
+ "X": 6,
+ "Y": -2.5,
+ "Z": 8
+ }
+ ]
+ },
+ {
+ "Id": 13,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 1,
+ "Z": 0
+ },
+ "D": -5
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": 5,
+ "Z": 0
+ },
+ {
+ "X": -5.8,
+ "Y": 5,
+ "Z": 0
+ },
+ {
+ "X": -5.8,
+ "Y": 5,
+ "Z": 3
+ },
+ {
+ "X": 6,
+ "Y": 5,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 14,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0.9143442,
+ "Y": 4.0675455E-05,
+ "Z": 0.40493774
+ },
+ "D": 4.0886106
+ },
+ "Vertices": [
+ {
+ "X": -8.9,
+ "Y": -8,
+ "Z": 10
+ },
+ {
+ "X": -5.8,
+ "Y": -5.6,
+ "Z": 3
+ },
+ {
+ "X": -8.0145,
+ "Y": -2.5,
+ "Z": 8
+ }
+ ]
+ },
+ {
+ "Id": 15,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0.9143856,
+ "Y": 0,
+ "Z": 0.40484422
+ },
+ "D": 4.0895896
+ },
+ "Vertices": [
+ {
+ "X": -8.0145,
+ "Y": -2.5,
+ "Z": 8
+ },
+ {
+ "X": -8.0145,
+ "Y": 4,
+ "Z": 8
+ },
+ {
+ "X": -8.9,
+ "Y": -8,
+ "Z": 10
+ }
+ ]
+ },
+ {
+ "Id": 16,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0.9143857,
+ "Y": 0,
+ "Z": 0.40484422
+ },
+ "D": 4.08959
+ },
+ "Vertices": [
+ {
+ "X": -8.0145,
+ "Y": 4,
+ "Z": 8
+ },
+ {
+ "X": -8.9,
+ "Y": 8,
+ "Z": 10
+ },
+ {
+ "X": -8.9,
+ "Y": -8,
+ "Z": 10
+ }
+ ]
+ },
+ {
+ "Id": 17,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 0,
+ "Z": 1
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": -2.5,
+ "Z": 8
+ },
+ {
+ "X": 6,
+ "Y": -1.432,
+ "Z": 8
+ },
+ {
+ "X": -8.0145,
+ "Y": 4,
+ "Z": 8
+ }
+ ]
+ },
+ {
+ "Id": 18,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 0,
+ "Z": 1
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": -1.432,
+ "Z": 8
+ },
+ {
+ "X": 6,
+ "Y": 0.92,
+ "Z": 8
+ },
+ {
+ "X": -8.0145,
+ "Y": 4,
+ "Z": 8
+ }
+ ]
+ },
+ {
+ "Id": 19,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 0,
+ "Z": 1
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": 0.92,
+ "Z": 8
+ },
+ {
+ "X": 6,
+ "Y": 4,
+ "Z": 8
+ },
+ {
+ "X": -8.0145,
+ "Y": 4,
+ "Z": 8
+ }
+ ]
+ },
+ {
+ "Id": 20,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 0,
+ "Z": 1
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -8.0145,
+ "Y": 4,
+ "Z": 8
+ },
+ {
+ "X": -8.0145,
+ "Y": -2.5,
+ "Z": 8
+ },
+ {
+ "X": 6,
+ "Y": -2.5,
+ "Z": 8
+ }
+ ]
+ },
+ {
+ "Id": 21,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 1,
+ "Y": 0,
+ "Z": 0
+ },
+ "D": -6
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": -2.4,
+ "Z": 3
+ },
+ {
+ "X": 6,
+ "Y": -1.432,
+ "Z": 8
+ },
+ {
+ "X": 6,
+ "Y": -2.5,
+ "Z": 8
+ }
+ ]
+ },
+ {
+ "Id": 22,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -2.4
+ },
+ "Vertices": [
+ {
+ "X": 8,
+ "Y": -2.4,
+ "Z": 0
+ },
+ {
+ "X": 8,
+ "Y": -2.4,
+ "Z": 3
+ },
+ {
+ "X": 6,
+ "Y": -2.4,
+ "Z": 3
+ },
+ {
+ "X": 6,
+ "Y": -2.4,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 23,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 1,
+ "Z": 0
+ },
+ "D": -1.8
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": 1.8,
+ "Z": 0
+ },
+ {
+ "X": 6,
+ "Y": 1.8,
+ "Z": 3
+ },
+ {
+ "X": 8,
+ "Y": 1.8,
+ "Z": 3
+ },
+ {
+ "X": 8,
+ "Y": 1.8,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 24,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 1,
+ "Y": 0,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": 8,
+ "Y": 1.8,
+ "Z": 0
+ },
+ {
+ "X": 8,
+ "Y": 1.8,
+ "Z": 3
+ },
+ {
+ "X": 8,
+ "Y": -2.4,
+ "Z": 3
+ },
+ {
+ "X": 8,
+ "Y": -2.4,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 25,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": -1,
+ "Y": 0,
+ "Z": 0
+ },
+ "D": 6
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": -1.1856,
+ "Z": 10
+ },
+ {
+ "X": 6,
+ "Y": 0.696,
+ "Z": 10
+ },
+ {
+ "X": 6,
+ "Y": 0.92,
+ "Z": 8
+ },
+ {
+ "X": 6,
+ "Y": -1.432,
+ "Z": 8
+ }
+ ]
+ },
+ {
+ "Id": 26,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -0.98177034,
+ "Z": 0.19007075
+ },
+ "D": -2.9264612
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": -1.432,
+ "Z": 8
+ },
+ {
+ "X": 6,
+ "Y": -2.4,
+ "Z": 3
+ },
+ {
+ "X": 8,
+ "Y": -2.4,
+ "Z": 3
+ },
+ {
+ "X": 7.4,
+ "Y": -1.432,
+ "Z": 8
+ }
+ ]
+ },
+ {
+ "Id": 27,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 0.9848628,
+ "Z": 0.17333585
+ },
+ "D": -2.2927606
+ },
+ "Vertices": [
+ {
+ "X": 8,
+ "Y": 1.8,
+ "Z": 3
+ },
+ {
+ "X": 6,
+ "Y": 1.8,
+ "Z": 3
+ },
+ {
+ "X": 6,
+ "Y": 0.92,
+ "Z": 8
+ },
+ {
+ "X": 7.4,
+ "Y": 0.92,
+ "Z": 8
+ }
+ ]
+ },
+ {
+ "Id": 28,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0.99287677,
+ "Y": 0,
+ "Z": 0.119145185
+ },
+ "D": -8.300449
+ },
+ "Vertices": [
+ {
+ "X": 7.4,
+ "Y": -1.432,
+ "Z": 8
+ },
+ {
+ "X": 8,
+ "Y": -2.4,
+ "Z": 3
+ },
+ {
+ "X": 8,
+ "Y": 1.8,
+ "Z": 3
+ },
+ {
+ "X": 7.4,
+ "Y": 0.92,
+ "Z": 8
+ }
+ ]
+ },
+ {
+ "Id": 29,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -0.99249613,
+ "Z": 0.12227553
+ },
+ "D": -2.399459
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": -1.432,
+ "Z": 8
+ },
+ {
+ "X": 7.4,
+ "Y": -1.432,
+ "Z": 8
+ },
+ {
+ "X": 7.4,
+ "Y": -1.1856,
+ "Z": 10
+ },
+ {
+ "X": 6,
+ "Y": -1.1856,
+ "Z": 10
+ }
+ ]
+ },
+ {
+ "Id": 30,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 0.9937864,
+ "Z": 0.11130409
+ },
+ "D": -1.8047162
+ },
+ "Vertices": [
+ {
+ "X": 7.4,
+ "Y": 0.696,
+ "Z": 10
+ },
+ {
+ "X": 7.4,
+ "Y": 0.92,
+ "Z": 8
+ },
+ {
+ "X": 6,
+ "Y": 0.92,
+ "Z": 8
+ },
+ {
+ "X": 6,
+ "Y": 0.696,
+ "Z": 10
+ }
+ ]
+ },
+ {
+ "Id": 31,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 1,
+ "Y": 0,
+ "Z": 0
+ },
+ "D": -7.4
+ },
+ "Vertices": [
+ {
+ "X": 7.4,
+ "Y": -1.1856,
+ "Z": 10
+ },
+ {
+ "X": 7.4,
+ "Y": -1.432,
+ "Z": 8
+ },
+ {
+ "X": 7.4,
+ "Y": 0.92,
+ "Z": 8
+ },
+ {
+ "X": 7.4,
+ "Y": 0.696,
+ "Z": 10
+ }
+ ]
+ },
+ {
+ "Id": 32,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 0,
+ "Z": 1
+ },
+ "D": -10
+ },
+ "Vertices": [
+ {
+ "X": 7.4,
+ "Y": 0.696,
+ "Z": 10
+ },
+ {
+ "X": 6,
+ "Y": 0.696,
+ "Z": 10
+ },
+ {
+ "X": 6,
+ "Y": -1.1856,
+ "Z": 10
+ },
+ {
+ "X": 7.4,
+ "Y": -1.1856,
+ "Z": 10
+ }
+ ]
+ },
+ {
+ "Id": 33,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 1,
+ "Y": 0,
+ "Z": 0
+ },
+ "D": -6
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": -2.5,
+ "Z": 8
+ },
+ {
+ "X": 6,
+ "Y": -5.6,
+ "Z": 3
+ },
+ {
+ "X": 6,
+ "Y": -2.4,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 34,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 1,
+ "Y": 0,
+ "Z": 0
+ },
+ "D": -6
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": -5.6,
+ "Z": 0
+ },
+ {
+ "X": 6,
+ "Y": -2.4,
+ "Z": 0
+ },
+ {
+ "X": 6,
+ "Y": -2.4,
+ "Z": 3
+ },
+ {
+ "X": 6,
+ "Y": -5.6,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 35,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 1,
+ "Y": 0,
+ "Z": 0
+ },
+ "D": -6
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": 4,
+ "Z": 8
+ },
+ {
+ "X": 6,
+ "Y": 0.92,
+ "Z": 8
+ },
+ {
+ "X": 6,
+ "Y": 1.8,
+ "Z": 3
+ },
+ {
+ "X": 6,
+ "Y": 5,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 36,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 1,
+ "Y": 0,
+ "Z": 0
+ },
+ "D": -6
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": 5,
+ "Z": 3
+ },
+ {
+ "X": 6,
+ "Y": 1.8,
+ "Z": 3
+ },
+ {
+ "X": 6,
+ "Y": 1.8,
+ "Z": 0
+ },
+ {
+ "X": 6,
+ "Y": 5,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 37,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -12,
+ "Y": -8,
+ "Z": 3
+ },
+ {
+ "X": -12,
+ "Y": -8,
+ "Z": 0
+ },
+ {
+ "X": -11,
+ "Y": -8,
+ "Z": 0.85
+ }
+ ]
+ },
+ {
+ "Id": 38,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -12,
+ "Y": -8,
+ "Z": 3
+ },
+ {
+ "X": -11,
+ "Y": -8,
+ "Z": 0.85
+ },
+ {
+ "X": -11,
+ "Y": -8,
+ "Z": 1.75
+ }
+ ]
+ },
+ {
+ "Id": 39,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -12,
+ "Y": -8,
+ "Z": 3
+ },
+ {
+ "X": -11,
+ "Y": -8,
+ "Z": 1.75
+ },
+ {
+ "X": -9.95,
+ "Y": -8,
+ "Z": 2.2
+ }
+ ]
+ },
+ {
+ "Id": 40,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": -8,
+ "Z": 3
+ },
+ {
+ "X": -12,
+ "Y": -8,
+ "Z": 3
+ },
+ {
+ "X": -9.95,
+ "Y": -8,
+ "Z": 2.2
+ }
+ ]
+ },
+ {
+ "Id": 41,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -11,
+ "Y": -8,
+ "Z": 0.85
+ },
+ {
+ "X": -12,
+ "Y": -8,
+ "Z": 0
+ },
+ {
+ "X": -5.8,
+ "Y": -8,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 42,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -6.8,
+ "Y": -8,
+ "Z": 0.85
+ },
+ {
+ "X": -11,
+ "Y": -8,
+ "Z": 0.85
+ },
+ {
+ "X": -5.8,
+ "Y": -8,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 43,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -6.8,
+ "Y": -8,
+ "Z": 0.85
+ },
+ {
+ "X": -5.8,
+ "Y": -8,
+ "Z": 0
+ },
+ {
+ "X": -5.8,
+ "Y": -8,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 44,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -6.8,
+ "Y": -8,
+ "Z": 1.75
+ },
+ {
+ "X": -6.8,
+ "Y": -8,
+ "Z": 0.85
+ },
+ {
+ "X": -5.8,
+ "Y": -8,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 45,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -7.85,
+ "Y": -8,
+ "Z": 2.2
+ },
+ {
+ "X": -6.8,
+ "Y": -8,
+ "Z": 1.75
+ },
+ {
+ "X": -5.8,
+ "Y": -8,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 46,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -8.9,
+ "Y": -8,
+ "Z": 2.35
+ },
+ {
+ "X": -7.85,
+ "Y": -8,
+ "Z": 2.2
+ },
+ {
+ "X": -5.8,
+ "Y": -8,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 47,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -9.95,
+ "Y": -8,
+ "Z": 2.2
+ },
+ {
+ "X": -8.9,
+ "Y": -8,
+ "Z": 2.35
+ },
+ {
+ "X": -5.8,
+ "Y": -8,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 48,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -5.6
+ },
+ "Vertices": [
+ {
+ "X": 0.4,
+ "Y": -5.6,
+ "Z": 1
+ },
+ {
+ "X": 0.4,
+ "Y": -5.6,
+ "Z": 2.5
+ },
+ {
+ "X": -1.1,
+ "Y": -5.6,
+ "Z": 2.5
+ }
+ ]
+ },
+ {
+ "Id": 49,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -5.6
+ },
+ "Vertices": [
+ {
+ "X": 0.4,
+ "Y": -5.6,
+ "Z": 1
+ },
+ {
+ "X": -1.1,
+ "Y": -5.6,
+ "Z": 2.5
+ },
+ {
+ "X": -1.1,
+ "Y": -5.6,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 50,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -5.6
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": -5.6,
+ "Z": 3
+ },
+ {
+ "X": -5.8,
+ "Y": -5.6,
+ "Z": 0
+ },
+ {
+ "X": -3,
+ "Y": -5.6,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 51,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -5.6
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": -5.6,
+ "Z": 3
+ },
+ {
+ "X": -3,
+ "Y": -5.6,
+ "Z": 0
+ },
+ {
+ "X": -3,
+ "Y": -5.6,
+ "Z": 2.5
+ }
+ ]
+ },
+ {
+ "Id": 52,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -5.6
+ },
+ "Vertices": [
+ {
+ "X": 1.4,
+ "Y": -5.6,
+ "Z": 1
+ },
+ {
+ "X": 0.4,
+ "Y": -5.6,
+ "Z": 1
+ },
+ {
+ "X": -1.1,
+ "Y": -5.6,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 53,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -5.6
+ },
+ "Vertices": [
+ {
+ "X": 1.4,
+ "Y": -5.6,
+ "Z": 1
+ },
+ {
+ "X": -1.1,
+ "Y": -5.6,
+ "Z": 0
+ },
+ {
+ "X": 6,
+ "Y": -5.6,
+ "Z": 0
+ }
+ ]
+ },
+ {
+ "Id": 54,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -5.6
+ },
+ "Vertices": [
+ {
+ "X": 1.4,
+ "Y": -5.6,
+ "Z": 1
+ },
+ {
+ "X": 6,
+ "Y": -5.6,
+ "Z": 0
+ },
+ {
+ "X": 6,
+ "Y": -5.6,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 55,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -5.6
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": -5.6,
+ "Z": 3
+ },
+ {
+ "X": -3,
+ "Y": -5.6,
+ "Z": 2.5
+ },
+ {
+ "X": -1.1,
+ "Y": -5.6,
+ "Z": 2.5
+ }
+ ]
+ },
+ {
+ "Id": 56,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -5.6
+ },
+ "Vertices": [
+ {
+ "X": 6,
+ "Y": -5.6,
+ "Z": 3
+ },
+ {
+ "X": -5.8,
+ "Y": -5.6,
+ "Z": 3
+ },
+ {
+ "X": -1.1,
+ "Y": -5.6,
+ "Z": 2.5
+ }
+ ]
+ },
+ {
+ "Id": 57,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -5.6
+ },
+ "Vertices": [
+ {
+ "X": 1.4,
+ "Y": -5.6,
+ "Z": 2.5
+ },
+ {
+ "X": 1.4,
+ "Y": -5.6,
+ "Z": 1
+ },
+ {
+ "X": 6,
+ "Y": -5.6,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 58,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -5.6
+ },
+ "Vertices": [
+ {
+ "X": 0.4,
+ "Y": -5.6,
+ "Z": 2.5
+ },
+ {
+ "X": 1.4,
+ "Y": -5.6,
+ "Z": 2.5
+ },
+ {
+ "X": 6,
+ "Y": -5.6,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 59,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": -1,
+ "Z": 0
+ },
+ "D": -5.6
+ },
+ "Vertices": [
+ {
+ "X": -1.1,
+ "Y": -5.6,
+ "Z": 2.5
+ },
+ {
+ "X": 0.4,
+ "Y": -5.6,
+ "Z": 2.5
+ },
+ {
+ "X": 6,
+ "Y": -5.6,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 60,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": -1,
+ "Y": 0,
+ "Z": 0
+ },
+ "D": -12
+ },
+ "Vertices": [
+ {
+ "X": -12,
+ "Y": 8,
+ "Z": 3
+ },
+ {
+ "X": -12,
+ "Y": 8,
+ "Z": 0
+ },
+ {
+ "X": -12,
+ "Y": -8,
+ "Z": 0
+ },
+ {
+ "X": -12,
+ "Y": -8,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 61,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": -0.9143494,
+ "Y": 0,
+ "Z": 0.4049262
+ },
+ "D": -12.186972
+ },
+ "Vertices": [
+ {
+ "X": -12,
+ "Y": -8,
+ "Z": 3
+ },
+ {
+ "X": -8.9,
+ "Y": -8,
+ "Z": 10
+ },
+ {
+ "X": -8.9,
+ "Y": 8,
+ "Z": 10
+ },
+ {
+ "X": -12,
+ "Y": 8,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 62,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0.91433954,
+ "Y": -6.230709E-05,
+ "Z": 0.40494844
+ },
+ "D": 4.088636
+ },
+ "Vertices": [
+ {
+ "X": -8.0145,
+ "Y": 4,
+ "Z": 8
+ },
+ {
+ "X": -5.8,
+ "Y": 5,
+ "Z": 3
+ },
+ {
+ "X": -8.9,
+ "Y": 8,
+ "Z": 10
+ }
+ ]
+ },
+ {
+ "Id": 63,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0.9143494,
+ "Y": 0,
+ "Z": 0.40492606
+ },
+ "D": 4.0884485
+ },
+ "Vertices": [
+ {
+ "X": -8.9,
+ "Y": -8,
+ "Z": 10
+ },
+ {
+ "X": -5.8,
+ "Y": -8,
+ "Z": 3
+ },
+ {
+ "X": -5.8,
+ "Y": -5.6,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 64,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0.91434944,
+ "Y": 0,
+ "Z": 0.4049261
+ },
+ "D": 4.0884485
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": 5,
+ "Z": 3
+ },
+ {
+ "X": -5.8,
+ "Y": 8,
+ "Z": 3
+ },
+ {
+ "X": -8.9,
+ "Y": 8,
+ "Z": 10
+ }
+ ]
+ },
+ {
+ "Id": 65,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 1,
+ "Y": 0,
+ "Z": 0
+ },
+ "D": 5.8
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": 5,
+ "Z": 0
+ },
+ {
+ "X": -5.8,
+ "Y": 5.4,
+ "Z": 0
+ },
+ {
+ "X": -5.8,
+ "Y": 5.4,
+ "Z": 2.5
+ },
+ {
+ "X": -5.8,
+ "Y": 5,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 66,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 1,
+ "Y": 0,
+ "Z": 0
+ },
+ "D": 5.8000007
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": 5.4,
+ "Z": 2.5
+ },
+ {
+ "X": -5.8,
+ "Y": 7.3,
+ "Z": 2.5
+ },
+ {
+ "X": -5.8,
+ "Y": 8,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 67,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 1,
+ "Y": 0,
+ "Z": 0
+ },
+ "D": 5.8000007
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": 5.4,
+ "Z": 2.5
+ },
+ {
+ "X": -5.8,
+ "Y": 8,
+ "Z": 3
+ },
+ {
+ "X": -5.8,
+ "Y": 5,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 68,
+ "NumPoints": 4,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 1,
+ "Y": 0,
+ "Z": 0
+ },
+ "D": 5.8
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": 7.3,
+ "Z": 2.5
+ },
+ {
+ "X": -5.8,
+ "Y": 7.3,
+ "Z": 0
+ },
+ {
+ "X": -5.8,
+ "Y": 8,
+ "Z": 0
+ },
+ {
+ "X": -5.8,
+ "Y": 8,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 69,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": 8,
+ "Z": 3
+ },
+ {
+ "X": -8.4,
+ "Y": 8,
+ "Z": 4
+ },
+ {
+ "X": -8.4,
+ "Y": 8,
+ "Z": 5.5
+ }
+ ]
+ },
+ {
+ "Id": 70,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -5.8,
+ "Y": 8,
+ "Z": 3
+ },
+ {
+ "X": -8.4,
+ "Y": 8,
+ "Z": 5.5
+ },
+ {
+ "X": -8.9,
+ "Y": 8,
+ "Z": 10
+ }
+ ]
+ },
+ {
+ "Id": 71,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -8.4,
+ "Y": 8,
+ "Z": 5.5
+ },
+ {
+ "X": -9.4,
+ "Y": 8,
+ "Z": 5.5
+ },
+ {
+ "X": -8.9,
+ "Y": 8,
+ "Z": 10
+ }
+ ]
+ },
+ {
+ "Id": 72,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -9.4,
+ "Y": 8,
+ "Z": 5.5
+ },
+ {
+ "X": -9.4,
+ "Y": 8,
+ "Z": 4
+ },
+ {
+ "X": -12,
+ "Y": 8,
+ "Z": 3
+ }
+ ]
+ },
+ {
+ "Id": 73,
+ "NumPoints": 3,
+ "SidesType": 0,
+ "Plane": {
+ "Normal": {
+ "X": 0,
+ "Y": 1,
+ "Z": 0
+ },
+ "D": -8
+ },
+ "Vertices": [
+ {
+ "X": -9.4,
+ "Y": 8,
+ "Z": 5.5
+ },
+ {
+ "X": -12,
+ "Y": 8,
+ "Z": 3
+ },
+ {
+ "X": -8.9,
+ "Y": 8,
+ "Z": 10
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs b/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs
index 3f8297b..c8519c4 100644
--- a/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs
+++ b/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs
@@ -484,31 +484,105 @@ public class CellarUpTrajectoryReplayTests
/// First-cap event — the failing tick. Live engine reports cn=(0,0,-1),
/// a downward-facing collision normal, capping the foot sphere at
/// world Z=92.74. Math: head sphere TOP reaches Z=94.0 (the cottage
- /// floor) when foot Z = 94.0 - sphereHeight = 92.80. So the head is
- /// bumping the cottage floor from BELOW.
+ /// floor) when foot Z = 94.0 - sphereHeight = 92.80. The head bumps
+ /// the cottage floor from BELOW — NOT a step-up / AdjustOffset bug.
///
///
- /// This is the actual #98 bug, NOT a step-up / AdjustOffset problem.
- /// Live capture's [resolve] probe pinpoints the blocking
+ /// Live capture's [resolve] probe pinpointed the blocking
/// entity: obj=0xA9B47900 — a landblock-baked static building
- /// (the cottage GfxObj). The cottage's floor polygons live in this
- /// GfxObj, registered as a ShadowEntry, NOT in any of the cottage's
- /// cells. The harness's
- /// loads cell fixtures but does NOT register the cottage GfxObj, so
- /// the harness fails to reproduce the cap — DOCUMENTED here as the
- /// divergence pattern.
+ /// (the cottage GfxObj 0x01000A2B). The cottage's floor polys
+ /// live in this GfxObj as a ShadowEntry, NOT in any cottage cell.
///
///
///
- /// Documents-the-bug pattern: passes WHILE the harness lacks the
- /// cottage GfxObj. When a future session adds the cottage GfxObj
- /// (full polygon list extracted from the live [poly-dump] +
- /// [resolve-bldg] probes), this test will start failing —
- /// the signal to flip it from documenting-the-bug to enforcing-the-fix.
+ /// Apparatus-convergence form (2026-05-23 evening v2): with the
+ /// cottage GfxObj registered via ,
+ /// the harness reproduces the live cn=(0,0,-1) cap event. This test
+ /// enforces THAT specific reproduction. The post-cap position
+ /// processing has a separate residual divergence — documented by
+ /// .
///
///
[Fact]
- public void LiveCompare_FirstCap_HarnessMissesCottageFloorBecauseCottageGfxObjNotRegistered()
+ public void LiveCompare_FirstCap_HarnessReproducesCottageFloorCapNormal()
+ {
+ var (engine, _) = BuildEngineWithCellarFixtures();
+ var captured = LoadCapturedRecord(record =>
+ record.Result.CollisionNormalValid
+ && record.Result.CollisionNormal.Z < -0.99f);
+
+ // Live must have cn=(0,0,-1) at this point — sanity check.
+ Assert.True(captured.Result.CollisionNormalValid,
+ "Captured record must have collisionNormalValid=true.");
+ Assert.True(captured.Result.CollisionNormal.Z < -0.99f,
+ $"Captured record must have downward collision normal; got " +
+ $"{captured.Result.CollisionNormal}.");
+
+ // Replay the call.
+ Assert.NotNull(captured.BodyBefore);
+ var body = SeedBodyFromSnapshot(captured.BodyBefore);
+ var harnessResult = engine.ResolveWithTransition(
+ currentPos: captured.Input.CurrentPos,
+ targetPos: captured.Input.TargetPos,
+ cellId: captured.Input.CellId,
+ sphereRadius: captured.Input.SphereRadius,
+ sphereHeight: captured.Input.SphereHeight,
+ stepUpHeight: captured.Input.StepUpHeight,
+ stepDownHeight: captured.Input.StepDownHeight,
+ isOnGround: captured.Input.IsOnGround,
+ body: body,
+ moverFlags: (ObjectInfoState)captured.Input.MoverFlags,
+ movingEntityId: captured.Input.MovingEntityId);
+
+ // Apparatus convergence: harness reproduces the cap-event collision
+ // normal exactly. If this fails, the cottage GfxObj registration
+ // has regressed (RegisterCottageGfxObj is broken, the dump fixture
+ // is stale, or the harness wiring lost the landblock context).
+ Assert.True(harnessResult.CollisionNormalValid,
+ "Harness must reproduce the live collision-normal-valid signal " +
+ "now that the cottage GfxObj is registered.");
+ Assert.True(harnessResult.CollisionNormal.Z < -0.99f,
+ $"Harness must reproduce the live downward-facing cap normal " +
+ $"(live cn={captured.Result.CollisionNormal}, harness cn={harnessResult.CollisionNormal}).");
+ }
+
+ ///
+ /// A6.P3 issue #98 (2026-05-23 evening v2) — documents-the-bug for the
+ /// residual divergence the apparatus surfaced. With the cottage GfxObj
+ /// registered, the harness reproduces the live cap-event collision
+ /// normal, but the POST-CAP POSITION processing diverges:
+ ///
+ /// - Live: full +X motion preserved (sphere slides +0.0266 m in X
+ /// along the cottage floor before the Y motion is capped).
+ /// - Harness: ZERO X motion (sphere stays at its input X position,
+ /// only the Y component is blocked).
+ ///
+ ///
+ ///
+ /// Both X positions agree on Y=7.2243 and Z=92.7390. Only X differs:
+ /// live X=141.3865 (requested target), harness X=141.3599 (current = no
+ /// move). The requested delta was (+0.0266, -0.4022, 0); live applied
+ /// the +X portion, harness applied nothing.
+ ///
+ ///
+ ///
+ /// Hypothesis (to investigate next session): live's response to a
+ /// cn=(0,0,-1) head-bump treats it as a Z-only constraint and lets the
+ /// XY component of the move complete via edge-slide. Harness's BSP path
+ /// is rejecting the WHOLE move vector when the cottage floor poly
+ /// intersects the head sphere, instead of computing a slid offset.
+ ///
+ ///
+ ///
+ /// Documents-the-bug: PASSES today on the asserted residual magnitude.
+ /// When a future session fixes the post-cap edge-slide, harness X will
+ /// match live X — this test FAILS at that point, signaling that the
+ /// X divergence is closed and the test should be folded back into the
+ /// strict path.
+ ///
+ ///
+ [Fact]
+ public void LiveCompare_FirstCap_ResidualXMotionDivergence_DocumentsNextInvestigation()
{
var (engine, _) = BuildEngineWithCellarFixtures();
var captured = LoadCapturedRecord(record =>
@@ -530,26 +604,20 @@ public class CellarUpTrajectoryReplayTests
moverFlags: (ObjectInfoState)captured.Input.MoverFlags,
movingEntityId: captured.Input.MovingEntityId);
- // Live reported cn=(0,0,-1) blocking the climb at this point.
- Assert.True(captured.Result.CollisionNormalValid,
- "Captured record must have collisionNormalValid=true.");
- Assert.True(captured.Result.CollisionNormal.Z < -0.99f,
- $"Captured record must have downward collision normal; got " +
- $"{captured.Result.CollisionNormal}.");
+ // Live preserved the full +X motion through the cap event; harness
+ // blocked it. Y and Z agree.
+ Assert.Equal(captured.Result.Position.Y, harnessResult.Position.Y, 4);
+ Assert.Equal(captured.Result.Position.Z, harnessResult.Position.Z, 4);
- // Harness does NOT reproduce the live downward push because the
- // cottage GfxObj is not registered — the blocking polygon lives
- // in static obj 0xA9B47900, which BuildEngineWithCellarFixtures
- // intentionally skips today (RegisterStairRampGfxObj is commented
- // out). When the cottage GfxObj's full polygon set is added to
- // the harness, this assertion will start to fail — flip the test
- // to assert the live cn=(0,0,-1) round-trips at that point.
- Assert.False(
- harnessResult.CollisionNormalValid
- && harnessResult.CollisionNormal.Z < -0.99f,
- "Harness should NOT reproduce the cottage-floor cap yet — " +
- "if it does, the cottage GfxObj has been added and this test " +
- "needs to flip to AssertCallMatchesCapture(engine, captured).");
+ float liveDeltaX = captured.Result.Position.X - captured.Input.CurrentPos.X;
+ float harnessDeltaX = harnessResult.Position.X - captured.Input.CurrentPos.X;
+
+ Assert.True(liveDeltaX > 0.02f,
+ $"Live must show +X motion after cap (expected ~+0.0266 m, got {liveDeltaX:F4}).");
+ Assert.True(MathF.Abs(harnessDeltaX) < 0.001f,
+ $"Harness currently zeros X motion through the cap (expected ~0, got {harnessDeltaX:F4}). " +
+ "If this assertion starts failing because harness now preserves +X, the post-cap " +
+ "edge-slide divergence is closed — fold this back into AssertCallMatchesCapture.");
}
///
@@ -868,25 +936,44 @@ public class CellarUpTrajectoryReplayTests
cache.RegisterCellStructForTest(cellId, cellWithBsp);
}
- // ── 2. NO landblock registered ──────────────────────────────
- // Without a landblock, SampleTerrainWalkable returns null and
- // FindEnvCollisions's outdoor-fallback path returns OK without
- // running ValidateWalkable on stub terrain. This is the right
- // shape for indoor-only tests — the cell's BSP would handle
- // collision if hydrated, and falling through to stub terrain
- // produces spurious (0,1,0) wall hits. FindObjCollisions also
- // early-returns without landblock context (line 2153 of
- // TransitionTypes.cs), so the synthetic stair GfxObj is also
- // skipped — fine for the airborne-at-tick-1 isolation.
+ // ── 2. Minimum landblock context for FindObjCollisions ──────
+ // FindObjCollisions (TransitionTypes.cs:2153) early-returns
+ // TransitionState.OK when TryGetLandblockContext fails for the
+ // sphere XY. Without a landblock the harness can't query the
+ // cottage GfxObj's shadow entries — and that's where the
+ // first-cap collision actually lives (live capture confirmed
+ // obj=0xA9B47900 fires the cn=(0,0,-1) push).
+ //
+ // Register an EMPTY-terrain landblock 0xA9B40000 anchored at
+ // world origin (0,0). The landblock test
+ // (worldX >= 0 && worldX < 192) covers every harness sphere
+ // position (X≈141, Y≈7). TerrainSurface gets a flat far-below
+ // surface so SampleTerrainZ returns something the indoor BSP
+ // path never consults (FindEnvCollisions's indoor branch fires
+ // first when the cell has BSP). Outdoor-fallback queries are
+ // harmless because the cell's synthetic BSP returns Collided
+ // before terrain is checked.
+ var heights = new byte[81]; // 9x9 corners
+ var heightTable = new float[256];
+ for (int i = 0; i < 256; i++) heightTable[i] = -1000f; // far below cellar
+ var stubTerrain = new TerrainSurface(heights, heightTable);
+ engine.AddLandblock(
+ landblockId: 0xA9B40000u,
+ terrain: stubTerrain,
+ cells: Array.Empty(),
+ portals: Array.Empty(),
+ worldOffsetX: 0f,
+ worldOffsetY: 0f);
- // ── 3. Synthetic stair-piece GfxObj + ShadowEntry ──────────
- // Temporarily disabled while debugging the airborne-at-tick-1
- // issue. Re-enable once the cell-BSP-is-null + landblock-stub
- // interaction is understood, AND we have a way to register
- // the stair without needing a landblock (e.g., extend
- // FindObjCollisions to query cellScope-only shadows without
- // landblock context).
- // RegisterStairRampGfxObj(engine, cache);
+ // ── 3. Cottage GfxObj 0x01000A2B from dumped fixture ────────
+ // Live capture (2026-05-23 PM v2) attributes the first-cap event
+ // to obj=0xA9B47900 (entity 0x00A9B479 partIdx=0) — a landblock-
+ // baked static building registered as a ShadowEntry. The full
+ // polygon table was extracted via ACDREAM_DUMP_GFXOBJS=0x01000A2B
+ // (issue #98 evening-v2 apparatus); 74 polygons including six
+ // downward-facing cottage-floor triangles at object-local Z=0
+ // that the head sphere bumps from below at world Z=94.
+ RegisterCottageGfxObj(engine, cache);
return (engine, cache);
}
@@ -939,100 +1026,75 @@ public class CellarUpTrajectoryReplayTests
}
///
- /// Constructs a synthetic GfxObj containing the cellar ramp polygon
- /// in WORLD coordinates and registers it as a ShadowEntry scoped to
- /// the cellar cell. The polygon's vertices + normal are reproduced
- /// from the live capture's [poly-dump] data (commit pre-3f56915),
- /// transformed to world frame so the GfxObj can sit at world origin
- /// with identity rotation/scale (simplifies the
- /// FindObjCollisions local-to-world transform).
+ /// A6.P3 issue #98 (2026-05-23 evening v2). Loads the cottage GfxObj
+ /// 0x01000A2B from the JSON fixture
+ /// (tests/AcDream.Core.Tests/Fixtures/issue98/0x01000A2B.gfxobj.json,
+ /// produced via the ACDREAM_DUMP_GFXOBJS capture infrastructure),
+ /// hydrates it as a with a synthetic
+ /// single-leaf BSP, and registers it as a ShadowEntry at the cottage's
+ /// world transform — the same shape production's GameWindow.cs:5893
+ /// registration uses for landblock-baked statics.
///
///
- /// Live capture's local polygon vertices (in building frame):
- /// (0.8,-1.59,-1.5), (0.8,1.31,1.5), (-0.8,1.31,1.5), (-0.8,-1.59,-1.5).
- /// Building's world transform: origin (141.5, 7.155, 92.455), 180° yaw
- /// around Z. After applying yaw + translation, world vertices are:
- /// (140.7, 8.745, 90.955), (140.7, 5.845, 93.955),
- /// (142.3, 5.845, 93.955), (142.3, 8.745, 90.955).
- /// World normal = (0, 0.719, 0.695), world d = -69.5035 — matches
- /// the live cdb capture exactly.
+ /// Transform values come from two evidence sources:
+ ///
+ /// - The cellar cell 0xA9B40147's WorldTransform has translation
+ /// (130.5, 11.5, 94.0) and a 3×3 with M11=M22=-1 / M33=+1
+ /// (a 180° rotation around Z). The cottage GfxObj sits at the
+ /// SAME world transform (its building origin is also at
+ /// (130.5, 11.5, 94.0) per the existing [resolve-bldg] capture
+ /// entOrigin_lb=(130.5,11.5,94.0)).
+ /// - BoundingSphere radius from the dump's
+ /// — 13.989 m.
+ /// Matches the live bspR=13.99 observed in the
+ /// [resolve-bldg] capture; cross-validation that the same
+ /// building is in play.
+ ///
+ ///
+ ///
+ ///
+ /// Entity id 0x00A9B479 mirrors the live capture's
+ /// obj=0xA9B47900 formula (entity.Id × 256 + partIdx=0). Using
+ /// the same id keeps any future probe correlation aligned with live
+ /// log conventions.
///
///
- private static void RegisterStairRampGfxObj(PhysicsEngine engine, PhysicsDataCache cache)
+ private static void RegisterCottageGfxObj(PhysicsEngine engine, PhysicsDataCache cache)
{
- const ushort RampPolyId = 0x0008;
- const uint StairGfxId = 0xDEADBEEFu;
- const uint StairEntityId = 0xC0FFEE00u;
+ const uint CottageGfxId = 0x01000A2Bu;
+ const uint CottageEntityId = 0x00A9B479u;
- // World-frame vertices (winding order preserved from live capture).
- var v0 = new Vector3(140.7f, 8.745f, 90.955f); // ramp foot, X=-side
- var v1 = new Vector3(140.7f, 5.845f, 93.955f); // ramp top, X=-side
- var v2 = new Vector3(142.3f, 5.845f, 93.955f); // ramp top, X=+side
- var v3 = new Vector3(142.3f, 8.745f, 90.955f); // ramp foot, X=+side
- var verts = new[] { v0, v1, v2, v3 };
+ var fixturePath = Path.Combine(FixtureDir, "0x01000A2B.gfxobj.json");
+ Assert.True(File.Exists(fixturePath),
+ $"Cottage GfxObj fixture missing: {fixturePath}. Re-run live " +
+ $"capture with ACDREAM_DUMP_GFXOBJS=0x01000A2B.");
- // Compute normal from cross(v1-v0, v3-v0).
- var edge0 = v1 - v0;
- var edge1 = v3 - v0;
- var normal = Vector3.Normalize(Vector3.Cross(edge0, edge1));
- // Plane equation: N·p + d = 0 → d = -N·v0.
- float d = -Vector3.Dot(normal, v0);
+ var dump = GfxObjDumpSerializer.Read(fixturePath);
+ var physics = GfxObjDumpSerializer.Hydrate(dump);
+ cache.RegisterGfxObjForTest(CottageGfxId, physics);
- var resolved = new Dictionary
- {
- [RampPolyId] = new ResolvedPolygon
- {
- Vertices = verts,
- Plane = new System.Numerics.Plane(normal, d),
- NumPoints = 4,
- SidesType = CullMode.Landblock,
- },
- };
+ // World transform from the cellar cell's WorldTransform: translation
+ // (130.5, 11.5, 94.0) + 180° rotation around Z. The cottage GfxObj
+ // shares this transform (it IS the cellar/cottage geometry).
+ var worldPos = new Vector3(130.5f, 11.5f, 94.0f);
+ var worldRot = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, MathF.PI);
- // Minimal one-leaf BSP containing the ramp poly. Bounding sphere
- // encompasses the polygon (center at poly centroid).
- var leaf = new PhysicsBSPNode
- {
- Type = BSPNodeType.Leaf,
- BoundingSphere = new Sphere
- {
- Origin = new Vector3(141.5f, 7.295f, 92.455f),
- Radius = 3.0f,
- },
- };
- leaf.Polygons.Add(RampPolyId);
-
- var bspTree = new PhysicsBSPTree { Root = leaf };
-
- var gfxPhysics = new GfxObjPhysics
- {
- BSP = bspTree,
- PhysicsPolygons = new Dictionary(),
- Vertices = new VertexArray(),
- Resolved = resolved,
- BoundingSphere = leaf.BoundingSphere,
- };
-
- cache.RegisterGfxObjForTest(StairGfxId, gfxPhysics);
-
- // ShadowEntry: object at world origin (0,0,0), identity rotation,
- // scale 1.0 — keeps the polygon's WORLD-frame vertices intact
- // through the FindObjCollisions local-transform math.
- // cellScope = CellarId so the entry is only queried when the sphere
- // is in cellar cell (matches retail's per-cell shadow scoping for
- // interior statics — Issue #91 family).
engine.ShadowObjects.Register(
- entityId: StairEntityId,
- gfxObjId: StairGfxId,
- worldPos: Vector3.Zero,
- rotation: Quaternion.Identity,
- radius: 5.0f,
+ entityId: CottageEntityId,
+ gfxObjId: CottageGfxId,
+ worldPos: worldPos,
+ rotation: worldRot,
+ radius: physics.BoundingSphere?.Radius ?? 14f,
worldOffsetX: 0f,
worldOffsetY: 0f,
landblockId: 0xA9B40000u,
collisionType: ShadowCollisionType.BSP,
scale: 1.0f,
- cellScope: CellarId);
+ // Landblock-baked statics in production (GameWindow.cs:5899) use
+ // `entity.ParentCellId ?? 0u` — the cottage building has no
+ // ParentCellId (it's a top-level landblock static), so the
+ // scope is landblock-wide (cellScope=0).
+ cellScope: 0u);
}
///