fix #175: door BSP collision poses at the motion-table closed pose
The Facility Hub double door (Setup 0x02000C9D) embeds the player into the visual panel from one side and blocks with a phantom slab on the other: its Setup PLACEMENT frames pose the two panels AJAR (yaw -150/-30 deg, 0.44 m behind the doorway plane — dat-confirmed by the Issue175 inspection) while the rendered door poses them CLOSED from the wire-supplied motion table via the sequencer. ShadowShapeBuilder read placement frames, so the 1.66x0.29x2.95 m physics slabs registered at the ajar pose. Retail tests each part's LIVE CPhysicsPart pose — for an idle door, the motion table's default (closed) state. Fix: ShadowShapeBuilder.FromSetup gains partPoseOverride (BSP part shapes only); RegisterServerEntityCollision derives it from the spawn MotionTableId via GameWindow.MotionTableDefaultPose (default style -> first cycle -> LowFrame part frames). Null/short poses fall back per-part to placement frames — table-less entities and landblock statics unchanged. One-shot snapshot vs retail's per-frame live pose is register row AP-84 (equivalent for the door lifecycle: closed == default pose; open == ETHEREAL bypasses collision, #150). Pins: FromSetup_PartPoseOverride_ReplacesPlacementFrames / _NoOverride_KeepsPlacementFrames / _ShortOverride_FallsBackPerPart. Suites: Core 2539 / App 713 / UI 425 / Net 385 green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
2312259a93
commit
355e389d4c
5 changed files with 160 additions and 6 deletions
|
|
@ -1,12 +1,16 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Options;
|
||||
using DatReaderWriter.Types;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Env = System.Environment;
|
||||
using Placement = DatReaderWriter.Enums.Placement;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics;
|
||||
|
||||
|
|
@ -145,4 +149,82 @@ public class Issue175HubDoorPoseInspectionTests
|
|||
_out.WriteLine("=== no DefaultMotionTable on the setup ===");
|
||||
}
|
||||
}
|
||||
|
||||
// ── #175 fix pins: ShadowShapeBuilder partPoseOverride ──────────────
|
||||
|
||||
private static Setup MakeTwoPartSetup()
|
||||
{
|
||||
var setup = new Setup();
|
||||
setup.Parts.Add(0x01000001u);
|
||||
setup.Parts.Add(0x01000002u);
|
||||
var placement = new AnimationFrame(2);
|
||||
placement.Frames.Clear();
|
||||
placement.Frames.Add(new Frame { Origin = new Vector3(0.88f, -0.44f, 1.37f),
|
||||
Orientation = new Quaternion(0f, 0f, -0.966f, 0.259f) });
|
||||
placement.Frames.Add(new Frame { Origin = new Vector3(-0.88f, -0.44f, 1.37f),
|
||||
Orientation = new Quaternion(0f, 0f, -0.259f, 0.966f) });
|
||||
setup.PlacementFrames[Placement.Default] = placement;
|
||||
return setup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// With a motion-table pose override, the BSP part shapes must use it —
|
||||
/// the closed pose, not the ajar placement pose (the #175 offset).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FromSetup_PartPoseOverride_ReplacesPlacementFrames()
|
||||
{
|
||||
var setup = MakeTwoPartSetup();
|
||||
var closed = new[]
|
||||
{
|
||||
new Frame { Origin = new Vector3(0.85f, 0f, 1.37f), Orientation = Quaternion.Identity },
|
||||
new Frame { Origin = new Vector3(-0.85f, 0f, 1.37f), Orientation = Quaternion.Identity },
|
||||
};
|
||||
|
||||
var shapes = ShadowShapeBuilder.FromSetup(
|
||||
setup, entScale: 1f, hasPhysicsBsp: _ => true, partPoseOverride: closed);
|
||||
|
||||
Assert.Equal(2, shapes.Count);
|
||||
Assert.Equal(new Vector3(0.85f, 0f, 1.37f), shapes[0].LocalPosition);
|
||||
Assert.Equal(Quaternion.Identity, shapes[0].LocalRotation);
|
||||
Assert.Equal(new Vector3(-0.85f, 0f, 1.37f), shapes[1].LocalPosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null override (no motion table) keeps the pre-#175 placement-frame
|
||||
/// behavior — landblock statics and table-less entities unchanged.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FromSetup_NoOverride_KeepsPlacementFrames()
|
||||
{
|
||||
var setup = MakeTwoPartSetup();
|
||||
|
||||
var shapes = ShadowShapeBuilder.FromSetup(
|
||||
setup, entScale: 1f, hasPhysicsBsp: _ => true);
|
||||
|
||||
Assert.Equal(2, shapes.Count);
|
||||
Assert.Equal(new Vector3(0.88f, -0.44f, 1.37f), shapes[0].LocalPosition);
|
||||
Assert.Equal(new Quaternion(0f, 0f, -0.966f, 0.259f), shapes[0].LocalRotation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A short override (fewer frames than parts) falls back to placement
|
||||
/// frames — a mismatched motion table must not misplace collision.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FromSetup_ShortOverride_FallsBackPerPart()
|
||||
{
|
||||
var setup = MakeTwoPartSetup();
|
||||
var shortOverride = new[]
|
||||
{
|
||||
new Frame { Origin = new Vector3(0.85f, 0f, 1.37f), Orientation = Quaternion.Identity },
|
||||
};
|
||||
|
||||
var shapes = ShadowShapeBuilder.FromSetup(
|
||||
setup, entScale: 1f, hasPhysicsBsp: _ => true, partPoseOverride: shortOverride);
|
||||
|
||||
Assert.Equal(2, shapes.Count);
|
||||
Assert.Equal(new Vector3(0.85f, 0f, 1.37f), shapes[0].LocalPosition); // override
|
||||
Assert.Equal(new Vector3(-0.88f, -0.44f, 1.37f), shapes[1].LocalPosition); // placement fallback
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue