fix: align animation pacing and transforms with native

This commit is contained in:
gamer147
2026-07-10 15:41:17 -04:00
parent 29b2dfca27
commit 014d128ccd
16 changed files with 515 additions and 101 deletions

View File

@@ -20,10 +20,47 @@ public class FrameClockTests
}
[Fact]
public void EffectiveBudget_ScalesBySpeed_AndFloorsAtOne()
public void Advance_RetainsFractionalMillisecondsAtSlowSpeed()
{
Assert.Equal(30, new FrameClock { OpsPerFrame = 30, Speed = 1.0 }.EffectiveBudget);
Assert.Equal(120, new FrameClock { OpsPerFrame = 30, Speed = 4.0 }.EffectiveBudget);
Assert.Equal(1, new FrameClock { OpsPerFrame = 0, Speed = 1.0 }.EffectiveBudget);
var c = new FrameClock { Speed = 0.1 };
for (int i = 0; i < 10; i++) c.Advance(1.0 / 144.0);
Assert.Equal(6, c.NowMs);
}
[Fact]
public void WallClockPacer_RateDoesNotDependOnRenderCallbackCount()
{
static long Simulate(int callbacks)
{
var c = new FrameClock();
var p = new WallClockOpPacer(c);
long ops = 0;
p.OpcodeCompleted(); ops++;
for (int frame = 0; frame < callbacks; frame++)
{
c.Advance(1.0 / callbacks);
while (p.CanRunNext) { p.OpcodeCompleted(); ops++; }
}
return ops;
}
long at60 = Simulate(60), at144 = Simulate(144), at240 = Simulate(240);
Assert.InRange(at60, 198, 202);
Assert.InRange(at144, 198, 202);
Assert.InRange(at240, 198, 202);
Assert.InRange(System.Math.Abs(at60 - at144), 0, 2);
Assert.InRange(System.Math.Abs(at60 - at240), 0, 2);
}
[Fact]
public void WallClockPacer_ResetDropsParkedTimeCredit()
{
var c = new FrameClock();
var p = new WallClockOpPacer(c);
p.OpcodeCompleted();
c.Advance(10);
p.Reset();
p.OpcodeCompleted();
Assert.False(p.CanRunNext);
}
}

View File

@@ -103,6 +103,23 @@ public class GfxAnimationTests
Assert.Equal(1, vm.Gfx.AnimClockGeneration);
}
[Fact]
public void ResetAnimClock_DispatchClearsOnlyGlobalServiceClock()
{
var t = T();
var scene = ScriptAssembler.Assemble(t, "CLOCKRESET", new List<(int, Operand[])>
{
MovGI(1, 400),
(0x238, new[] { G(1) }),
(0x243, System.Array.Empty<Operand>()),
Exit(),
}, System.Array.Empty<string>());
var vm = new VirtualMachine(scene, t, new RecordingHost());
vm.Run();
Assert.Equal(0, vm.Gfx.AnimClockDurationTicks);
Assert.Equal(2, vm.Gfx.AnimClockGeneration);
}
[Fact]
public void SnapshotSamplesDelayedMatrixChannels_WithoutUsingZAsOpacity()
{
@@ -138,4 +155,30 @@ public class GfxAnimationTests
Assert.True(done.Rotation.Enabled);
Assert.Equal(255, done.Alpha); // scale-Z=3, translation-Z=99, rotation-axis-Z=5: still opaque
}
[Fact]
public void Transform2D_UsesNativeAnchoredRowVectorOrder_AndDirectProjection()
{
var t = new TransformState(2, 3, 99, 10, -7, 1234, 100, 50, 888);
var p = Transform2DMath.Apply(120, 60, t);
Assert.Equal((150.0, 73.0), p);
}
[Fact]
public void Transform2D_NegativeScaleMovesFarEdgeAcrossAnchor()
{
var t = new TransformState(-2, 1, 1, 0, 0, 0, 100, 0, 0);
var left = Transform2DMath.Apply(90, 0, t);
var right = Transform2DMath.Apply(110, 0, t);
Assert.Equal((120.0, 0.0), left);
Assert.Equal((80.0, 0.0), right);
}
[Fact]
public void Transform2D_MatchesCapturedNativeSc0000ScaleEndpoint()
{
// Native handle 0xcbc0: base=(0,600), anchor=(400,1000), scale=5.
var t = new TransformState(5, 5, 1, 0, 0, 0, 400, 1000, 0);
Assert.Equal((-1600.0, -1000.0), Transform2DMath.Apply(0, 600, t));
}
}