fix(core): resolve AnimData HighFrame=-1 sentinel before filtering cycles

Diagnostic spawn dump revealed the player character arrives with
`low=0 high=-1 framerate=30.00 partFrames=33`. The -1 is ACViewer's
"play the whole animation" sentinel (see
references/ACViewer/.../Physics/Animation/AnimSequenceNode.cs:96-113
set_animation_id). My Phase 6.1 code used the raw int values, so
the downstream registration filter evaluated `HighFrame(-1) > LowFrame(0)`
as false and threw away every animated entity whose AnimData used the
sentinel — which, from the live dump, appears to be basically all of
them.

MotionResolver.GetIdleCycle now does the same four-step clamp ACViewer
does: -1 HighFrame → NumFrames-1, clamp LowFrame and HighFrame to
NumFrames-1, and collapse to LowFrame if LowFrame > HighFrame. The
IdleCycle carried up to GameWindow is always in terms of real frame
indices the playback loop can step through. Static poses (framerate==0
or single frame after resolution) still skip registration correctly.

168 tests green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-11 20:25:22 +02:00
parent 5253c7bfe8
commit 49c1a9d29e
2 changed files with 50 additions and 1 deletions

View file

@ -946,6 +946,23 @@ public sealed class GameWindow : IDisposable
else if (idleCycle.Animation.PartFrames.Count <= 1)
_liveAnimRejectPartFrames++;
// Per-entity dump for the first N spawns so we can see actual
// AnimData values for live creatures and compare against retail.
if (_liveSpawnReceived <= 30)
{
if (idleCycle is not null)
Console.WriteLine($"live: SPAWN[{_liveSpawnReceived}] name='{spawn.Name ?? "?"}' " +
$"setup=0x{spawn.SetupTableId:X8} mt=0x{(spawn.MotionTableId ?? 0):X8} " +
$"cycle: anim=0x{(uint)idleCycle.Animation.Id:X8} " +
$"low={idleCycle.LowFrame} high={idleCycle.HighFrame} " +
$"framerate={idleCycle.Framerate:F2} partFrames={idleCycle.Animation.PartFrames.Count} " +
$"numParts={idleCycle.Animation.NumParts}");
else
Console.WriteLine($"live: SPAWN[{_liveSpawnReceived}] name='{spawn.Name ?? "?"}' " +
$"setup=0x{spawn.SetupTableId:X8} mt=0x{(spawn.MotionTableId ?? 0):X8} " +
$"cycle=null (no motion table / resolver failed)");
}
if (idleCycle is not null && idleCycle.Framerate != 0f
&& idleCycle.HighFrame > idleCycle.LowFrame
&& idleCycle.Animation.PartFrames.Count > 1)