From c980763322f5039c134aa8180c779665309c892b Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 21 Jun 2026 10:34:36 +0200 Subject: [PATCH] =?UTF-8?q?refactor(physics=20#145):=20Slice=201=20?= =?UTF-8?q?=E2=80=94=20rename=20Frame->CellFrame=20to=20avoid=20DatReaderW?= =?UTF-8?q?riter.Types.Frame=20collision?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new value type collided with DatReaderWriter.Types.Frame (used in physics-adjacent code like ShadowShapeBuilder), which the structural fix (per-file using-aliases across 6 files) would have re-incurred in every later physics slice. Renamed the TYPE to CellFrame; the Position.Frame MEMBER keeps retail's name. Restored the 5 alias-only files to their pre-Slice-1 state; synced spec + plan. Core 1522 passed / 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...6-06-21-145-cell-relative-physics-frame.md | 26 ++++++++++--------- ...-145-cell-relative-physics-frame-design.md | 9 ++++--- src/AcDream.Core/Physics/Position.cs | 22 +++++++++------- .../Physics/ShadowShapeBuilder.cs | 5 ++-- .../Physics/AnimationSequencerTests.cs | 17 ++++++------ .../Physics/ShadowShapeBuilderTests.cs | 7 +++-- .../Vfx/EntityScriptActivatorTests.cs | 9 +++---- .../Streaming/GpuWorldStateActivatorTests.cs | 3 +-- 8 files changed, 51 insertions(+), 47 deletions(-) diff --git a/docs/superpowers/plans/2026-06-21-145-cell-relative-physics-frame.md b/docs/superpowers/plans/2026-06-21-145-cell-relative-physics-frame.md index cdd3cbe1..665bfb0c 100644 --- a/docs/superpowers/plans/2026-06-21-145-cell-relative-physics-frame.md +++ b/docs/superpowers/plans/2026-06-21-145-cell-relative-physics-frame.md @@ -30,7 +30,7 @@ dotnet test tests/AcDream.Core.Net.Tests/AcDream.Core.Net.Tests.csproj | File | Responsibility | Slices | |---|---|---| -| `src/AcDream.Core/Physics/Position.cs` (create) | `Frame` + `Position` value types | 1 | +| `src/AcDream.Core/Physics/Position.cs` (create) | `CellFrame` + `Position` value types (`CellFrame` = retail `Frame`, renamed to dodge `DatReaderWriter.Types.Frame`) | 1 | | `src/AcDream.Core/Physics/LandDefs.cs` (modify) | add `GetBlockOffset` | 1 | | `src/AcDream.Core/Physics/PhysicsBody.cs` (modify) | carry `Position`; computed `WorldPosition`; `AdjustToOutside` on placement; integrate-then-rewrap | 2,4 | | `src/AcDream.Core/Physics/ResolveResult.cs` (modify) | carry the canonicalized `Position` | 2,3 | @@ -57,30 +57,32 @@ dotnet test tests/AcDream.Core.Net.Tests/AcDream.Core.Net.Tests.csproj - [ ] **Step 1: Create the value types** -`src/AcDream.Core/Physics/Position.cs`: +`src/AcDream.Core/Physics/Position.cs` (the value type is named `CellFrame`, NOT `Frame` — `Frame` +collides with `DatReaderWriter.Types.Frame` used in physics-adjacent code; the `Position.Frame` *member* +keeps retail's name): ```csharp using System.Numerics; namespace AcDream.Core.Physics; /// -/// Retail Frame (acclient.h:30647). is LOCAL to the -/// owning cell: outdoor → X/Y ∈ [0,192) within the landblock, Z = height; -/// indoor → the EnvCell-relative placement. replaces -/// retail's m_fl2gv 3×3 local→global matrix (equivalent rotation). +/// Retail Frame (acclient.h:30647) — named CellFrame here to avoid +/// colliding with DatReaderWriter.Types.Frame. is LOCAL +/// to the owning cell: outdoor → X/Y ∈ [0,192), Z = height; indoor → EnvCell-relative. +/// replaces retail's m_fl2gv 3×3 matrix. /// -public readonly record struct Frame(Vector3 Origin, Quaternion Orientation); +public readonly record struct CellFrame(Vector3 Origin, Quaternion Orientation); /// /// Retail Position (acclient.h:30658): the (which-cell, where-inside) pair. -/// One type for indoor and outdoor. The full 32-bit cell id (high 16 = landblock -/// prefix, low 16 = cell index) plus the cell-local . Neither -/// half is ever reconstructed from a streaming center — see #145 design spec. +/// Full 32-bit cell id + the cell-local . Never reconstructed +/// from a streaming center — see #145 design spec. The Frame member keeps +/// retail's name. /// -public readonly record struct Position(uint ObjCellId, Frame Frame) +public readonly record struct Position(uint ObjCellId, CellFrame Frame) { public Position(uint objCellId, Vector3 origin, Quaternion orientation) - : this(objCellId, new Frame(origin, orientation)) { } + : this(objCellId, new CellFrame(origin, orientation)) { } } ``` diff --git a/docs/superpowers/specs/2026-06-21-145-cell-relative-physics-frame-design.md b/docs/superpowers/specs/2026-06-21-145-cell-relative-physics-frame-design.md index 02c7e767..a4c9d44e 100644 --- a/docs/superpowers/specs/2026-06-21-145-cell-relative-physics-frame-design.md +++ b/docs/superpowers/specs/2026-06-21-145-cell-relative-physics-frame-design.md @@ -59,16 +59,19 @@ boundary crossing. ### 3.1 New value types (`AcDream.Core.Physics`) ```csharp -public readonly record struct Frame(Vector3 Origin, Quaternion Orientation); +// Type named CellFrame (retail "Frame") to avoid colliding with +// DatReaderWriter.Types.Frame, which is used in physics-adjacent code. +public readonly record struct CellFrame(Vector3 Origin, Quaternion Orientation); // Origin is LOCAL to ObjCellId's cell: // outdoor → Origin.X/Y ∈ [0,192) within the landblock; Origin.Z = height. // indoor → Origin is the EnvCell-relative placement the BSP path already uses. -public readonly record struct Position(uint ObjCellId, Frame Frame); +public readonly record struct Position(uint ObjCellId, CellFrame Frame); // member keeps retail's name ``` One `Position` type covers indoor and outdoor (retail uniformity). `PhysicsBody.Position` becomes a -`Position` and is **the source of truth.** +`Position` and is **the source of truth.** (The value type is `CellFrame`; the `Position.Frame` member +keeps retail's name. `Frame` collided with `DatReaderWriter.Types.Frame` — renamed during Slice 1.) ### 3.2 The seam: `_liveCenter` is render-only diff --git a/src/AcDream.Core/Physics/Position.cs b/src/AcDream.Core/Physics/Position.cs index 067ffefa..8c9b045a 100644 --- a/src/AcDream.Core/Physics/Position.cs +++ b/src/AcDream.Core/Physics/Position.cs @@ -3,21 +3,25 @@ using System.Numerics; namespace AcDream.Core.Physics; /// -/// Retail Frame (acclient.h:30647). is LOCAL to the -/// owning cell: outdoor → X/Y ∈ [0,192) within the landblock, Z = height; -/// indoor → the EnvCell-relative placement. replaces -/// retail's m_fl2gv 3×3 local→global matrix (equivalent rotation). +/// Retail Frame (acclient.h:30647) — named CellFrame here to avoid +/// colliding with DatReaderWriter.Types.Frame (the dat placement/animation +/// frame, used in physics-adjacent code such as ShadowShapeBuilder). +/// is LOCAL to the owning cell: outdoor → X/Y ∈ [0,192) within +/// the landblock, Z = height; indoor → the EnvCell-relative placement. +/// replaces retail's m_fl2gv 3×3 local→global +/// matrix (equivalent rotation). /// -public readonly record struct Frame(Vector3 Origin, Quaternion Orientation); +public readonly record struct CellFrame(Vector3 Origin, Quaternion Orientation); /// /// Retail Position (acclient.h:30658): the (which-cell, where-inside) pair. /// One type for indoor and outdoor. The full 32-bit cell id (high 16 = landblock -/// prefix, low 16 = cell index) plus the cell-local . Neither -/// half is ever reconstructed from a streaming center — see #145 design spec. +/// prefix, low 16 = cell index) plus the cell-local . Neither +/// half is ever reconstructed from a streaming center — see #145 design spec. The +/// Frame member keeps retail's name. /// -public readonly record struct Position(uint ObjCellId, Frame Frame) +public readonly record struct Position(uint ObjCellId, CellFrame Frame) { public Position(uint objCellId, Vector3 origin, Quaternion orientation) - : this(objCellId, new Frame(origin, orientation)) { } + : this(objCellId, new CellFrame(origin, orientation)) { } } diff --git a/src/AcDream.Core/Physics/ShadowShapeBuilder.cs b/src/AcDream.Core/Physics/ShadowShapeBuilder.cs index 449b302c..4fff4106 100644 --- a/src/AcDream.Core/Physics/ShadowShapeBuilder.cs +++ b/src/AcDream.Core/Physics/ShadowShapeBuilder.cs @@ -4,7 +4,6 @@ using System.Numerics; using DatReaderWriter.DBObjs; using DatReaderWriter.Enums; using DatReaderWriter.Types; -using DatFrame = DatReaderWriter.Types.Frame; namespace AcDream.Core.Physics; @@ -89,9 +88,9 @@ public static class ShadowShapeBuilder uint gfxId = (uint)setup.Parts[i]; if (!hasPhysicsBsp(gfxId)) continue; - DatFrame partFrame = placementFrame is not null && i < placementFrame.Frames.Count + Frame partFrame = placementFrame is not null && i < placementFrame.Frames.Count ? placementFrame.Frames[i] - : new DatFrame { Origin = Vector3.Zero, Orientation = Quaternion.Identity }; + : new Frame { Origin = Vector3.Zero, Orientation = Quaternion.Identity }; // BSP radius default; caller substitutes the real BoundingSphere.Radius // at registration time when available. Loose-but-safe broadphase value. diff --git a/tests/AcDream.Core.Tests/Physics/AnimationSequencerTests.cs b/tests/AcDream.Core.Tests/Physics/AnimationSequencerTests.cs index 3508d6fe..e911d21d 100644 --- a/tests/AcDream.Core.Tests/Physics/AnimationSequencerTests.cs +++ b/tests/AcDream.Core.Tests/Physics/AnimationSequencerTests.cs @@ -10,7 +10,6 @@ using Xunit; // Alias the DatReaderWriter enum so it doesn't clash with // AcDream.Core.Physics.MotionCommand (which is a static class of uint constants). using DRWMotionCommand = DatReaderWriter.Enums.MotionCommand; -using DatFrame = DatReaderWriter.Types.Frame; namespace AcDream.Core.Tests.Physics; @@ -66,7 +65,7 @@ file static class Fixtures { var pf = new AnimationFrame((uint)numParts); for (int p = 0; p < numParts; p++) - pf.Frames.Add(new DatFrame { Origin = origin, Orientation = orientation }); + pf.Frames.Add(new Frame { Origin = origin, Orientation = orientation }); anim.PartFrames.Add(pf); } return anim; @@ -87,8 +86,8 @@ file static class Fixtures var pf1 = new AnimationFrame((uint)numParts); for (int p = 0; p < numParts; p++) { - pf0.Frames.Add(new DatFrame { Origin = fromOrigin, Orientation = fromRot }); - pf1.Frames.Add(new DatFrame { Origin = toOrigin, Orientation = toRot }); + pf0.Frames.Add(new Frame { Origin = fromOrigin, Orientation = fromRot }); + pf1.Frames.Add(new Frame { Origin = toOrigin, Orientation = toRot }); } anim.PartFrames.Add(pf0); anim.PartFrames.Add(pf1); @@ -397,7 +396,7 @@ public sealed class AnimationSequencerTests { var pf = new AnimationFrame(1); float y = 10f - 5f * f; // 10, 5, 0 - pf.Frames.Add(new DatFrame { Origin = new Vector3(0, y, 0), Orientation = Quaternion.Identity }); + pf.Frames.Add(new Frame { Origin = new Vector3(0, y, 0), Orientation = Quaternion.Identity }); linkAnim.PartFrames.Add(pf); } @@ -594,7 +593,7 @@ public sealed class AnimationSequencerTests for (int f = 0; f < 4; f++) { var pf = new AnimationFrame(1); - pf.Frames.Add(new DatFrame { Origin = new Vector3(0, 0, f), Orientation = Quaternion.Identity }); + pf.Frames.Add(new Frame { Origin = new Vector3(0, 0, f), Orientation = Quaternion.Identity }); anim.PartFrames.Add(pf); } @@ -1007,7 +1006,7 @@ public sealed class AnimationSequencerTests anim.Flags = AnimationFlags.PosFrames; for (int f = 0; f < 4; f++) { - anim.PosFrames.Add(new DatFrame + anim.PosFrames.Add(new Frame { Origin = new Vector3(1f, 0f, 0f), Orientation = Quaternion.Identity, @@ -1154,7 +1153,7 @@ public sealed class AnimationSequencerTests for (int f = 0; f < 10; f++) { var pf = new AnimationFrame(1); - pf.Frames.Add(new DatFrame { Origin = new Vector3(0, 0, f), Orientation = Quaternion.Identity }); + pf.Frames.Add(new Frame { Origin = new Vector3(0, 0, f), Orientation = Quaternion.Identity }); anim.PartFrames.Add(pf); } @@ -1205,7 +1204,7 @@ public sealed class AnimationSequencerTests for (int f = 0; f < 10; f++) { var pf = new AnimationFrame(1); - pf.Frames.Add(new DatFrame { Origin = new Vector3(0, 0, f), Orientation = Quaternion.Identity }); + pf.Frames.Add(new Frame { Origin = new Vector3(0, 0, f), Orientation = Quaternion.Identity }); anim.PartFrames.Add(pf); } diff --git a/tests/AcDream.Core.Tests/Physics/ShadowShapeBuilderTests.cs b/tests/AcDream.Core.Tests/Physics/ShadowShapeBuilderTests.cs index 2d94753a..b1679358 100644 --- a/tests/AcDream.Core.Tests/Physics/ShadowShapeBuilderTests.cs +++ b/tests/AcDream.Core.Tests/Physics/ShadowShapeBuilderTests.cs @@ -6,7 +6,6 @@ using DatReaderWriter.DBObjs; using DatReaderWriter.Enums; using DatReaderWriter.Types; using Xunit; -using DatFrame = DatReaderWriter.Types.Frame; namespace AcDream.Core.Tests.Physics; @@ -38,9 +37,9 @@ public class ShadowShapeBuilderTests { Frames = { - new DatFrame { Origin = Vector3.Zero, Orientation = Quaternion.Identity }, - new DatFrame { Origin = Vector3.Zero, Orientation = Quaternion.Identity }, - new DatFrame { Origin = Vector3.Zero, Orientation = Quaternion.Identity } + new Frame { Origin = Vector3.Zero, Orientation = Quaternion.Identity }, + new Frame { Origin = Vector3.Zero, Orientation = Quaternion.Identity }, + new Frame { Origin = Vector3.Zero, Orientation = Quaternion.Identity } } } } diff --git a/tests/AcDream.Core.Tests/Rendering/Vfx/EntityScriptActivatorTests.cs b/tests/AcDream.Core.Tests/Rendering/Vfx/EntityScriptActivatorTests.cs index 4f832dab..4a5bd3fe 100644 --- a/tests/AcDream.Core.Tests/Rendering/Vfx/EntityScriptActivatorTests.cs +++ b/tests/AcDream.Core.Tests/Rendering/Vfx/EntityScriptActivatorTests.cs @@ -7,7 +7,6 @@ using AcDream.Core.World; using DatReaderWriter.Types; using Xunit; using DatPhysicsScript = DatReaderWriter.DBObjs.PhysicsScript; -using DatFrame = DatReaderWriter.Types.Frame; namespace AcDream.Core.Tests.Rendering.Vfx; @@ -141,7 +140,7 @@ public sealed class EntityScriptActivatorTests var hookSink = new ParticleHookSink(system); // Hook offset = (1, 0, 0) in entity-local frame. - var hookOffset = new DatFrame + var hookOffset = new Frame { Origin = new Vector3(1f, 0f, 0f), Orientation = Quaternion.Identity, @@ -195,7 +194,7 @@ public sealed class EntityScriptActivatorTests var system = new ParticleSystem(registry); var hookSink = new ParticleHookSink(system); - var script = BuildScript((0.0, new CreateParticleHook { EmitterInfoId = 100u, Offset = new DatFrame() })); + var script = BuildScript((0.0, new CreateParticleHook { EmitterInfoId = 100u, Offset = new Frame() })); var table = new Dictionary { [0xAAu] = script }; var runner = new PhysicsScriptRunner( id => table.TryGetValue(id, out var s) ? s : null, @@ -258,7 +257,7 @@ public sealed class EntityScriptActivatorTests var system = new ParticleSystem(registry); var hookSink = new ParticleHookSink(system); - var hookOffset = new DatFrame { Origin = new Vector3(1f, 0, 0), Orientation = Quaternion.Identity }; + var hookOffset = new Frame { Origin = new Vector3(1f, 0, 0), Orientation = Quaternion.Identity }; var script = BuildScript( (0.0, new CreateParticleHook { EmitterInfoId = 100u, Offset = hookOffset, PartIndex = 1 })); var table = new Dictionary { [0xAAu] = script }; @@ -298,7 +297,7 @@ public sealed class EntityScriptActivatorTests var system = new ParticleSystem(registry); var hookSink = new ParticleHookSink(system); - var script = BuildScript((0.0, new CreateParticleHook { EmitterInfoId = 100u, Offset = new DatFrame() })); + var script = BuildScript((0.0, new CreateParticleHook { EmitterInfoId = 100u, Offset = new Frame() })); var table = new Dictionary { [0xAAu] = script }; var runner = new PhysicsScriptRunner( id => table.TryGetValue(id, out var s) ? s : null, diff --git a/tests/AcDream.Core.Tests/Streaming/GpuWorldStateActivatorTests.cs b/tests/AcDream.Core.Tests/Streaming/GpuWorldStateActivatorTests.cs index c04a5718..e5a2034e 100644 --- a/tests/AcDream.Core.Tests/Streaming/GpuWorldStateActivatorTests.cs +++ b/tests/AcDream.Core.Tests/Streaming/GpuWorldStateActivatorTests.cs @@ -10,7 +10,6 @@ using DatReaderWriter.DBObjs; using DatReaderWriter.Types; using Xunit; using DatPhysicsScript = DatReaderWriter.DBObjs.PhysicsScript; -using DatFrame = DatReaderWriter.Types.Frame; namespace AcDream.Core.Tests.Streaming; @@ -44,7 +43,7 @@ public sealed class GpuWorldStateActivatorTests script.ScriptData.Add(new PhysicsScriptData { StartTime = 0.0, - Hook = new CreateParticleHook { EmitterInfoId = 100u, Offset = new DatFrame() }, + Hook = new CreateParticleHook { EmitterInfoId = 100u, Offset = new Frame() }, }); var table = new Dictionary { [scriptId] = script };