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
|
|
@ -4163,9 +4163,22 @@ public sealed class GameWindow : IDisposable
|
|||
// span the doorway gap, so the player could walk through. With
|
||||
// this change the door also registers the part-0 BSP slab
|
||||
// (1.9 × 0.26 × 2.5 m) that retail uses for the real block.
|
||||
// #175 (2026-07-05): BSP part shapes must pose at the motion table's
|
||||
// DEFAULT-STATE frame (the closed pose — what the sequencer renders
|
||||
// for an idle entity and what retail's live CPhysicsPart pose is),
|
||||
// not the Setup's placement frame. The Facility Hub double door
|
||||
// (Setup 0x02000C9D) places its panels AJAR in the placement frame
|
||||
// (yaw −150°/−30°, −0.44 m behind the doorway) while rendering
|
||||
// closed — the user embedded into the visual door on one side and
|
||||
// hit a phantom slab on the other. Null (no motion table / no
|
||||
// cycle / part-count mismatch) falls back to placement frames.
|
||||
var closedPose = MotionTableDefaultPose(
|
||||
spawn.MotionTableId ?? 0u, setup.Parts.Count);
|
||||
|
||||
var raw = AcDream.Core.Physics.ShadowShapeBuilder.FromSetup(
|
||||
setup, entScale,
|
||||
id => _physicsDataCache.GetGfxObj(id)?.BSP?.Root is not null);
|
||||
id => _physicsDataCache.GetGfxObj(id)?.BSP?.Root is not null,
|
||||
partPoseOverride: closedPose);
|
||||
|
||||
// Substitute the real bounding-sphere radius for BSP shapes —
|
||||
// the pure builder's 2.0 placeholder works for typical doors
|
||||
|
|
@ -4254,6 +4267,37 @@ public sealed class GameWindow : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #175: the motion table's default-state pose — frame LowFrame of the
|
||||
/// default style's first cycle animation — the pose an idle entity's
|
||||
/// parts hold (retail: CPartArray init runs the motion table's default
|
||||
/// state; collision tests the resulting live CPhysicsPart poses). Used
|
||||
/// as the BSP shadow-shape part-pose override at registration. Returns
|
||||
/// null (→ placement-frame fallback) when the entity has no motion
|
||||
/// table, the table has no default cycle, or the frame doesn't cover
|
||||
/// <paramref name="partCount"/> parts.
|
||||
/// </summary>
|
||||
private IReadOnlyList<DatReaderWriter.Types.Frame>? MotionTableDefaultPose(
|
||||
uint motionTableId, int partCount)
|
||||
{
|
||||
if (motionTableId == 0u || partCount == 0 || _dats is null) return null;
|
||||
|
||||
var mt = _dats.Get<DatReaderWriter.DBObjs.MotionTable>(motionTableId);
|
||||
if (mt is null) return null;
|
||||
|
||||
if (!mt.Cycles.TryGetValue((int)mt.DefaultStyle, out var cycle)
|
||||
|| cycle.Anims.Count == 0)
|
||||
return null;
|
||||
|
||||
var animRef = cycle.Anims[0];
|
||||
var anim = _dats.Get<DatReaderWriter.DBObjs.Animation>(animRef.AnimId);
|
||||
if (anim is null || anim.PartFrames.Count == 0) return null;
|
||||
|
||||
int idx = System.Math.Clamp((int)animRef.LowFrame, 0, anim.PartFrames.Count - 1);
|
||||
var frames = anim.PartFrames[idx].Frames;
|
||||
return frames.Count >= partCount ? frames : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// R3-W4: one-time per-remote wiring of the animation-dispatch stack —
|
||||
/// the persistent <see cref="RemoteMotion.Sink"/> (ObservedOmega turn
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue