refactor(physics #145): Slice 1 — rename Frame->CellFrame to avoid DatReaderWriter.Types.Frame collision

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) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-21 10:34:36 +02:00
parent 438bb681a5
commit c980763322
8 changed files with 51 additions and 47 deletions

View file

@ -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;
/// <summary>
/// Retail <c>Frame</c> (acclient.h:30647). <see cref="Origin"/> is LOCAL to the
/// owning cell: outdoor → X/Y ∈ [0,192) within the landblock, Z = height;
/// indoor → the EnvCell-relative placement. <see cref="Orientation"/> replaces
/// retail's <c>m_fl2gv</c> 3×3 local→global matrix (equivalent rotation).
/// Retail <c>Frame</c> (acclient.h:30647) — named <c>CellFrame</c> here to avoid
/// colliding with <c>DatReaderWriter.Types.Frame</c>. <see cref="Origin"/> is LOCAL
/// to the owning cell: outdoor → X/Y ∈ [0,192), Z = height; indoor → EnvCell-relative.
/// <see cref="Orientation"/> replaces retail's <c>m_fl2gv</c> 3×3 matrix.
/// </summary>
public readonly record struct Frame(Vector3 Origin, Quaternion Orientation);
public readonly record struct CellFrame(Vector3 Origin, Quaternion Orientation);
/// <summary>
/// Retail <c>Position</c> (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 <see cref="Frame"/>. Neither
/// half is ever reconstructed from a streaming center — see #145 design spec.
/// Full 32-bit cell id + the cell-local <see cref="CellFrame"/>. Never reconstructed
/// from a streaming center — see #145 design spec. The <c>Frame</c> member keeps
/// retail's name.
/// </summary>
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)) { }
}
```

View file

@ -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

View file

@ -3,21 +3,25 @@ using System.Numerics;
namespace AcDream.Core.Physics;
/// <summary>
/// Retail <c>Frame</c> (acclient.h:30647). <see cref="Origin"/> is LOCAL to the
/// owning cell: outdoor → X/Y ∈ [0,192) within the landblock, Z = height;
/// indoor → the EnvCell-relative placement. <see cref="Orientation"/> replaces
/// retail's <c>m_fl2gv</c> 3×3 local→global matrix (equivalent rotation).
/// Retail <c>Frame</c> (acclient.h:30647) — named <c>CellFrame</c> here to avoid
/// colliding with <c>DatReaderWriter.Types.Frame</c> (the dat placement/animation
/// frame, used in physics-adjacent code such as <c>ShadowShapeBuilder</c>).
/// <see cref="Origin"/> is LOCAL to the owning cell: outdoor → X/Y ∈ [0,192) within
/// the landblock, Z = height; indoor → the EnvCell-relative placement.
/// <see cref="Orientation"/> replaces retail's <c>m_fl2gv</c> 3×3 local→global
/// matrix (equivalent rotation).
/// </summary>
public readonly record struct Frame(Vector3 Origin, Quaternion Orientation);
public readonly record struct CellFrame(Vector3 Origin, Quaternion Orientation);
/// <summary>
/// Retail <c>Position</c> (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 <see cref="Frame"/>. Neither
/// half is ever reconstructed from a streaming center — see #145 design spec.
/// prefix, low 16 = cell index) plus the cell-local <see cref="CellFrame"/>. Neither
/// half is ever reconstructed from a streaming center — see #145 design spec. The
/// <c>Frame</c> member keeps retail's name.
/// </summary>
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)) { }
}

View file

@ -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.

View file

@ -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);
}

View file

@ -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 }
}
}
}

View file

@ -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<uint, DatPhysicsScript> { [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<uint, DatPhysicsScript> { [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<uint, DatPhysicsScript> { [0xAAu] = script };
var runner = new PhysicsScriptRunner(
id => table.TryGetValue(id, out var s) ? s : null,

View file

@ -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<uint, DatPhysicsScript> { [scriptId] = script };