Fix AE001H spritesheet animation

This commit is contained in:
gamer147
2026-07-11 20:37:26 -04:00
parent 593a24b510
commit 0bf6b43a32
9 changed files with 93 additions and 51 deletions

View File

@@ -62,7 +62,8 @@ public class AnimChannelTests
vm.Run();
var o = vm.Gfx.TryGet(0x1000)!;
Assert.True(o.SrcAnim);
Assert.Equal(4, o.SrcGridW);
Assert.Equal(4, o.SrcFrameCount);
Assert.Equal(1, o.SrcColumns);
Assert.Equal(2, o.SrcCell);
}
@@ -78,9 +79,9 @@ public class AnimChannelTests
public void SetSrcRect_StoresGridCellPeriod()
{
var g = VisibleObj(0x100);
g.SetSrcRect(0x100, gridW: 4, gridH: 1, cell: 2, period: 800);
g.SetSrcRect(0x100, frameCount: 4, columns: 1, cell: 2, period: 800);
var o = g.TryGet(0x100)!;
Assert.Equal(4, o.SrcGridW);
Assert.Equal(4, o.SrcFrameCount);
Assert.Equal(2, o.SrcCell);
Assert.Equal(800, o.SrcPeriod);
Assert.True(o.SrcAnim);
@@ -91,10 +92,10 @@ public class AnimChannelTests
public void SetSrcRect_ClampsGridToAtLeastOne()
{
var g = VisibleObj(0x100);
g.SetSrcRect(0x100, gridW: 0, gridH: 0, cell: 0, period: 0);
g.SetSrcRect(0x100, frameCount: 0, columns: 0, cell: 0, period: 0);
var o = g.TryGet(0x100)!;
Assert.Equal(1, o.SrcGridW);
Assert.Equal(1, o.SrcGridH);
Assert.Equal(1, o.SrcFrameCount);
Assert.Equal(1, o.SrcColumns);
}
[Fact]

View File

@@ -8,7 +8,7 @@ public class AnimInterpolatorTests
{
var g = new GfxState();
g.SetSurface(1, resId: 5, colorKey: -1);
g.BindDraw(handle, 1, 0, 0, 256, 64, 100, 100); // 256x64 sheet, base pos (100,100)
g.BindDraw(handle, 1, 0, 0, 64, 64, 100, 100); // one 64x64 cell, base pos (100,100)
return g;
}
@@ -24,22 +24,24 @@ public class AnimInterpolatorTests
public void SrcRect_StaticCell_SelectsSubRect()
{
var g = VisibleObj(0x100);
g.SetSrcRect(0x100, gridW: 4, gridH: 1, cell: 2, period: 0); // static cell 2 of 4 across 256px
g.SetSrcRect(0x100, frameCount: 4, columns: 4, cell: 2, period: 0);
var ro = g.SnapshotVisibleObjects(0).Single();
Assert.Equal(128, ro.SrcX); // cell 2 * 64
Assert.Equal(64, ro.W);
}
[Fact]
public void SrcRect_Animated_PingPongsCellAcrossGrid()
public void SrcRect_Animated_AdvancesOneCellPerPeriodAndWraps()
{
var g = VisibleObj(0x100);
g.SetSrcRect(0x100, gridW: 4, gridH: 1, cell: 0, period: 1000);
g.SetSrcRect(0x100, frameCount: 4, columns: 4, cell: 0, period: 100);
g.SnapshotVisibleObjects(0); // seeds start=0
var half = g.SnapshotVisibleObjects(500).Single();
Assert.Equal(192, half.SrcX); // t=1 -> last cell (3) * 64
var back = g.SnapshotVisibleObjects(1000).Single();
Assert.Equal(0, back.SrcX); // ping-ponged back to cell 0
var second = g.SnapshotVisibleObjects(100).Single();
Assert.Equal(64, second.SrcX);
var fourth = g.SnapshotVisibleObjects(300).Single();
Assert.Equal(192, fourth.SrcX);
var wrapped = g.SnapshotVisibleObjects(400).Single();
Assert.Equal(0, wrapped.SrcX);
}
[Fact]

View File

@@ -82,6 +82,27 @@ public class GfxAnimationTests
Assert.True(rotation.HasActiveVisualPresentation(1000));
}
[Fact]
public void LoopingSpritesheet_PreservesCellSize_AdvancesRowMajor_AndWraps()
{
var g = new GfxState();
g.SetSurface(4, 0x37, -1);
g.BindDraw(0xcf3a, 4, 0, 0, 200, 200, 300, 100); // AE001H: 800x400, eight 200x200 cells
g.SetSrcRect(0xcf3a, frameCount: 8, columns: 4, cell: 0, period: 100);
var first = g.SnapshotVisibleObjects(1000).Single();
var second = g.SnapshotVisibleObjects(1100).Single();
var fifth = g.SnapshotVisibleObjects(1400).Single();
var eighth = g.SnapshotVisibleObjects(1700).Single();
var wrapped = g.SnapshotVisibleObjects(1800).Single();
Assert.Equal((0, 0, 200, 200), (first.SrcX, first.SrcY, first.W, first.H));
Assert.Equal((200, 0, 200, 200), (second.SrcX, second.SrcY, second.W, second.H));
Assert.Equal((0, 200, 200, 200), (fifth.SrcX, fifth.SrcY, fifth.W, fifth.H));
Assert.Equal((600, 200, 200, 200), (eighth.SrcX, eighth.SrcY, eighth.W, eighth.H));
Assert.Equal((0, 0, 200, 200), (wrapped.SrcX, wrapped.SrcY, wrapped.W, wrapped.H));
}
[Fact]
public void OneShotRotation_SharesMatrixClockAndMatchesNativeSample()
{

View File

@@ -71,9 +71,10 @@ public sealed class GfxState
// fades from a static mode-0 0x203 such as a CG initialized with 0x00ffffff (opaque identity).
public bool OneShotColorBlend;
// ---- src-rect / spritesheet-cell channel (ops 0x239 static cell, 0x231 animate). Interpolator
// SRC-RECT SCROLL channel: period obj+0x230, start obj+0x21c, grid obj+0x238/0x23c. ----
public long SrcGridW = 1, SrcGridH = 1, SrcCell, SrcPeriod, SrcStart = -1;
// ---- src-rect / spritesheet-cell channel (ops 0x239 one-shot cell, 0x231 looping animation).
// Native obj+0x238 is the TOTAL FRAME COUNT and +0x23c is the COLUMN COUNT. Each frame keeps
// draw-texture's original source-rect size; those operands are not a grid to divide it by. ----
public long SrcFrameCount = 1, SrcColumns = 1, SrcCell, SrcPeriod, SrcStart = -1;
public bool SrcAnim;
// ---- animated color/glow channel (op 0x232). Interpolator COLOR channel: period obj+0x220,
// start obj+0x20c, target obj+0x240 — PING-PONG (distinct from static 0x202/0x203). ----
@@ -158,7 +159,7 @@ public sealed class GfxState
OneShotColorTarget = s.OneShotColorTarget, ColorDelayMs = s.ColorDelayMs,
ColorDurationMs = s.ColorDurationMs, OneShotColorEnabled = s.OneShotColorEnabled,
OneShotColorBlend = s.OneShotColorBlend,
SrcGridW = s.SrcGridW, SrcGridH = s.SrcGridH, SrcCell = s.SrcCell,
SrcFrameCount = s.SrcFrameCount, SrcColumns = s.SrcColumns, SrcCell = s.SrcCell,
SrcPeriod = s.SrcPeriod, SrcStart = s.SrcStart, SrcAnim = s.SrcAnim,
ColorPeriod = s.ColorPeriod, ColorStart = s.ColorStart, ColorTarget = s.ColorTarget,
ColorAnim = s.ColorAnim, SourceSlot = s.SourceSlot, SrcRect = s.SrcRect, Visible = s.Visible,
@@ -282,14 +283,16 @@ public sealed class GfxState
lock (_lock) GetOrCreate(handle).StaticColorMode = mode;
}
/// <summary>Ops 0x239 (static cell, period=0) / 0x231 (animate, period&gt;0): set the spritesheet grid +
/// the visible cell. When animated, the interpolator ping-pongs the cell across the grid over the period.</summary>
public void SetSrcRect(long handle, long gridW, long gridH, long cell, long period)
/// <summary>Ops 0x239 (one-shot cell endpoint, period=0 in this retained model) / 0x231 (looping):
/// set total frame count, column count, and visible cell. Every frame retains draw-texture's source
/// rectangle dimensions. For 0x231, <paramref name="period"/> is milliseconds per frame.</summary>
public void SetSrcRect(long handle, long frameCount, long columns, long cell, long period)
{
lock (_lock)
{
var o = GetOrCreate(handle);
o.SrcGridW = gridW < 1 ? 1 : gridW; o.SrcGridH = gridH < 1 ? 1 : gridH;
o.SrcFrameCount = frameCount < 1 ? 1 : frameCount;
o.SrcColumns = columns < 1 ? 1 : columns;
o.SrcCell = cell; o.SrcPeriod = period; o.SrcStart = -1; o.SrcAnim = true;
}
}
@@ -555,23 +558,18 @@ public sealed class GfxState
blend = BlendKind.Alpha;
}
// ---- src-rect: pick the spritesheet cell (static or ping-ponged across the grid) ----
// ---- src-rect: preserve cell dimensions and offset it row-major through the sheet ----
int srcX = o.SrcRect.X, srcY = o.SrcRect.Y, w = o.SrcRect.W, h = o.SrcRect.H;
if (o.SrcAnim && o.SrcGridW >= 1)
if (o.SrcAnim && o.SrcFrameCount >= 1)
{
int cellW = (int)(o.SrcRect.W / o.SrcGridW);
int cellH = o.SrcGridH >= 1 ? (int)(o.SrcRect.H / o.SrcGridH) : o.SrcRect.H;
long cell = o.SrcCell;
if (o.SrcPeriod > 0)
{
if (o.SrcStart < 0) o.SrcStart = nowMs;
double t = PingPongWeight(nowMs, o.SrcStart, o.SrcPeriod);
cell = (long)System.Math.Round(t * (o.SrcGridW - 1));
cell = ((nowMs - o.SrcStart) / o.SrcPeriod) % o.SrcFrameCount;
}
long cols = o.SrcGridW;
srcX = o.SrcRect.X + (int)(cell % cols) * cellW;
srcY = o.SrcRect.Y + (int)(cell / cols) * cellH;
w = cellW; h = cellH;
srcX = o.SrcRect.X + (int)(cell % o.SrcColumns) * o.SrcRect.W;
srcY = o.SrcRect.Y + (int)(cell / o.SrcColumns) * o.SrcRect.H;
}
// One-shot matrix channels: hold current through delay, then linearly sample current -> target.

View File

@@ -387,9 +387,9 @@ public sealed class VirtualMachine
case "u00421DD0": // 0x22f set-position: (handle)(op2)(x)(y)(z) -> base position (direct set)
case "u004219E0": // 0x229 set-position2: same shape, direct position
Gfx.GetOrCreate(Read(a[0])).V24 = (Read(a[2]), Read(a[3]), Read(a[4])); return pc + 1;
case "u004223C0": // 0x239 spritesheet cell: (handle)(p3)(p4)(gridW)(gridH)(cell) — static cell
case "u004223C0": // 0x239 spritesheet cell: (handle)(delay)(duration)(frame count)(columns)(cell)
Gfx.SetSrcRect(Read(a[0]), Read(a[3]), Read(a[4]), Read(a[5]), 0); return pc + 1;
case "u00421EA0": // 0x231 anim spritesheet: (handle)(period)(gridW)(gridH) — ping-pong the cell
case "u00421EA0": // 0x231 looping spritesheet: (handle)(ms per frame)(frame count)(columns)
Gfx.SetSrcRect(Read(a[0]), Read(a[2]), Read(a[3]), 0, Read(a[1])); return pc + 1;
case "u00421EF0": // 0x232 anim color/glow: (handle)(period)(alpha)(color) — ping-pong the color
Gfx.SetColorAnim(Read(a[0]), Read(a[1]), GfxState.PackColor(Read(a[2]), Read(a[3]))); return pc + 1;