refactor(world): extract live projection mechanics

Move appearance rebinding, collision construction, default-pose resolution, local shadow ownership, and exact leave-world presentation into focused owners. Preserve retail parent ordering with staged validation, committed recovery, recursive attached-subtree withdrawal, and retryable exact teardown across parent, pickup, position, unwield, and pose-loss edges.

Co-Authored-By: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-21 16:17:03 +02:00
parent c87bac31a0
commit 69a2ca0c6d
26 changed files with 4172 additions and 430 deletions

View file

@ -0,0 +1,59 @@
using AcDream.Core.Physics;
using AcDream.Core.Physics.Motion;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Types;
namespace AcDream.App.Physics;
/// <summary>
/// Resolves the motion table's authored default-state part pose used by a
/// live object's collision PartArray. Retail keeps the collision parts in the
/// same current pose as the visual PartArray; an unresolved table falls back
/// to Setup placement frames in <see cref="ShadowShapeBuilder"/>.
/// The retained default-pose snapshot approximation is tracked as AP-84 in
/// the retail divergence register until live per-frame collision poses land.
/// </summary>
internal sealed class LiveEntityDefaultPoseResolver
{
private readonly Func<uint, MotionTable?> _loadMotionTable;
private readonly IAnimationLoader _animationLoader;
private readonly bool _dumpMotion;
public LiveEntityDefaultPoseResolver(
Func<uint, MotionTable?> loadMotionTable,
IAnimationLoader animationLoader,
bool dumpMotion)
{
_loadMotionTable = loadMotionTable
?? throw new ArgumentNullException(nameof(loadMotionTable));
_animationLoader = animationLoader
?? throw new ArgumentNullException(nameof(animationLoader));
_dumpMotion = dumpMotion;
}
public IReadOnlyList<Frame>? Resolve(uint motionTableId, int partCount)
{
if (motionTableId == 0u || partCount == 0)
return null;
MotionTable? motionTable = _loadMotionTable(motionTableId);
if (motionTable is null)
return null;
IReadOnlyList<Frame>? pose = MotionTablePose.DefaultStatePartFrames(
motionTable,
_animationLoader.LoadAnimation);
if (_dumpMotion)
{
string description = pose is null
? "null->placement-fallback"
: FormattableString.Invariant(
$"part0=({pose[0].Origin.X:F2},{pose[0].Origin.Y:F2},{pose[0].Origin.Z:F2})");
Console.WriteLine(FormattableString.Invariant(
$"[shape-pose] mt=0x{motionTableId:X8} parts={partCount} {description}"));
}
return pose;
}
}