fix #175 (take 2): the cycle lookup used the bare style key — silent no-op
The shipped derivation looked up mt.Cycles[DefaultStyle]; the dat Cycles dictionary is keyed by the COMBINED (style << 16) | substate word (CMotionTable.cs:683), so the lookup always missed and the pose override silently fell back to placement frames — user re-test: "175 is not fixed". The pins covered the override plumbing but not the derivation, the one part with no offline fixture. Extract the derivation to Core as MotionTablePose.DefaultStatePartFrames using the retail SetDefaultState chain (StyleDefaults[DefaultStyle] -> combined-key LookupCycle, same wrap arithmetic as CMotionTable.cs:683) and pin it against the REAL dat (human MT 0x09000001 resolves a 34-part pose — this test fails on the old key math). Short poses now apply PER PART (ShadowShapeBuilder already falls back per index) so a door anim posing only the panels still overrides them while the BSP-less header keeps its placement frame. [shape-pose] diagnostic (ACDREAM_DUMP_MOTION) prints mt id + resolved part0 pose per BSP registration so live launches show the actual outcome. Suites: Core 2540 / App 713 / UI 425 / Net 385 green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
355e389d4c
commit
bb18614a89
3 changed files with 117 additions and 17 deletions
|
|
@ -4268,14 +4268,12 @@ 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.
|
||||
/// #175: the motion table's default-state pose (the closed pose for
|
||||
/// doors) — the derivation lives in
|
||||
/// <see cref="AcDream.Core.Physics.Motion.MotionTablePose"/> (Core,
|
||||
/// dat-conformance-tested; the first cut here used a bare-style cycle
|
||||
/// key, always missed, and silently no-oped — the "175 is not fixed"
|
||||
/// report). Returns null → placement-frame fallback.
|
||||
/// </summary>
|
||||
private IReadOnlyList<DatReaderWriter.Types.Frame>? MotionTableDefaultPose(
|
||||
uint motionTableId, int partCount)
|
||||
|
|
@ -4285,17 +4283,19 @@ public sealed class GameWindow : IDisposable
|
|||
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 pose = AcDream.Core.Physics.Motion.MotionTablePose.DefaultStatePartFrames(
|
||||
mt, id => _dats.Get<DatReaderWriter.DBObjs.Animation>(id));
|
||||
|
||||
var animRef = cycle.Anims[0];
|
||||
var anim = _dats.Get<DatReaderWriter.DBObjs.Animation>(animRef.AnimId);
|
||||
if (anim is null || anim.PartFrames.Count == 0) return null;
|
||||
if (Environment.GetEnvironmentVariable("ACDREAM_DUMP_MOTION") == "1")
|
||||
{
|
||||
string desc = pose is null ? "null->placement-fallback"
|
||||
: System.FormattableString.Invariant(
|
||||
$"part0=({pose[0].Origin.X:F2},{pose[0].Origin.Y:F2},{pose[0].Origin.Z:F2})");
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[shape-pose] mt=0x{motionTableId:X8} parts={partCount} {desc}"));
|
||||
}
|
||||
|
||||
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;
|
||||
return pose;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
65
src/AcDream.Core/Physics/Motion/MotionTablePose.cs
Normal file
65
src/AcDream.Core/Physics/Motion/MotionTablePose.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Types;
|
||||
|
||||
namespace AcDream.Core.Physics.Motion;
|
||||
|
||||
/// <summary>
|
||||
/// #175: the motion table's DEFAULT-STATE part pose — the pose an idle
|
||||
/// entity's parts hold (retail: <c>CMotionTable::SetDefaultState</c>
|
||||
/// 0x005230a0 installs <c>StyleDefaults[DefaultStyle]</c>'s cycle; the parts
|
||||
/// then sit at that animation's frames — the live <c>CPhysicsPart</c> pose
|
||||
/// collision tests against). Used as the BSP shadow-shape part-pose override
|
||||
/// at server-entity registration (doors: the CLOSED pose).
|
||||
///
|
||||
/// <para>
|
||||
/// Cycle key arithmetic mirrors <see cref="CMotionTable"/>'s
|
||||
/// <c>LookupCycle</c> (CMotionTable.cs:683): <c>(style << 16) |
|
||||
/// (substate & 0xFFFFFF)</c> — the raw dat <c>Cycles</c> dictionary is
|
||||
/// keyed by the COMBINED word, not the bare style (the first cut of this
|
||||
/// helper looked up the bare style, always missed, and silently fell back
|
||||
/// to placement frames — the #175 "not fixed" report).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class MotionTablePose
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolve the default-state pose frames. Returns null (callers fall
|
||||
/// back to placement frames) when the table has no default-style
|
||||
/// substate, no matching cycle, or no animation. A pose covering FEWER
|
||||
/// parts than the Setup is returned as-is —
|
||||
/// <see cref="ShadowShapeBuilder"/> falls back to the placement frame
|
||||
/// PER PART beyond the override's length (e.g. a door anim that poses
|
||||
/// only the panel parts, not the BSP-less frame header).
|
||||
/// </summary>
|
||||
/// <param name="mt">The raw dat motion table (wire MotionTableId).</param>
|
||||
/// <param name="loadAnimation">Animation loader (production:
|
||||
/// <c>id => dats.Get<Animation>(id)</c>).</param>
|
||||
public static IReadOnlyList<Frame>? DefaultStatePartFrames(
|
||||
MotionTable mt,
|
||||
Func<uint, Animation?> loadAnimation)
|
||||
{
|
||||
if (mt is null) return null;
|
||||
|
||||
// SetDefaultState: StyleDefaults[DefaultStyle] → the default substate.
|
||||
if (!mt.StyleDefaults.TryGetValue(mt.DefaultStyle, out var defaultSubstateCmd))
|
||||
return null;
|
||||
|
||||
// LookupCycle key (CMotionTable.cs:683 — same wrap semantics).
|
||||
uint style = (uint)mt.DefaultStyle;
|
||||
uint substate = (uint)defaultSubstateCmd;
|
||||
int key = (int)((style << 16) | (substate & 0xFFFFFFu));
|
||||
|
||||
if (!mt.Cycles.TryGetValue(key, out var cycle) || cycle.Anims.Count == 0)
|
||||
return null;
|
||||
|
||||
var animRef = cycle.Anims[0];
|
||||
var anim = loadAnimation(animRef.AnimId);
|
||||
if (anim is null || anim.PartFrames.Count == 0) return null;
|
||||
|
||||
int idx = Math.Clamp((int)animRef.LowFrame, 0, anim.PartFrames.Count - 1);
|
||||
var frames = anim.PartFrames[idx].Frames;
|
||||
return frames.Count > 0 ? frames : null;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue