Fix FIELD minimap camera pan

This commit is contained in:
gamer147
2026-08-16 10:31:01 -04:00
parent 3289948ce7
commit d4d9def8f4
9 changed files with 87 additions and 24 deletions

View File

@@ -20,8 +20,7 @@ public class AnimChannelTests
{
(0x55, new[]{G(1), I(0xcf3a)}), (0x55, new[]{G(2), I(0)}),
(0x55, new[]{G(3), I(360)}), (0x55, new[]{G(4), I(20)}), (0x55, new[]{G(5), I(0)}),
(0x1fb, new[]{G(1), I(0), I(0), I(0), I(1), I(1), I(0), I(0)}),
(0x22f, new[]{G(1), G(2), G(3), G(4), G(5)}),
(0x1fb, new[]{G(1), I(0), I(0), I(0), I(1), I(1), G(3), G(4)}),
(0x55, new[]{G(6), I(300)}), (0x55, new[]{G(7), I(40)}),
(0x55, new[]{G(8), I(20)}), (0x51, new[]{G(8), I(0), G(8)}),
(0x220, new[]{G(1), G(2), G(6), G(7), G(8), G(5)}),
@@ -63,8 +62,7 @@ public class AnimChannelTests
(0x55, new[]{G(4), I(360)}), (0x55, new[]{G(5), I(20)}), (0x55, new[]{G(6), I(0)}),
(0x55, new[]{G(40), I(20)}), (0x51, new[]{G(40), I(0), G(40)}),
(0x55, new[]{G(41), I(60)}), (0x51, new[]{G(41), I(0), G(41)}),
(0x1fb, new[]{G(1), I(0), I(0), I(0), I(1), I(1), I(0), I(0)}),
(0x22f, new[]{G(1), G(2), G(4), G(5), G(6)}),
(0x1fb, new[]{G(1), I(0), I(0), I(0), I(1), I(1), G(4), G(5)}),
(0x228, new[]{G(10), G(1), G(11), G(12), G(13)}),
(0x50, new[]{G(11), G(11), I(40)}), (0x50, new[]{G(12), G(12), G(40)}),

View File

@@ -51,6 +51,41 @@ public class GfxRangeTransformTests
Assert.False(gfx.HasActiveTimedPresentation(1300));
}
[Fact]
public void Opcode22fPansTheSelectedRangeInSyncWithAnOrdinaryObjectTween()
{
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
var script = ScriptAssembler.Assemble(table, "RANGE_PAN", new List<(int, Operand[])>
{
(0x229, new[] { I(1), I(1), I(500), I(350), I(0) }),
(0x22c, new[] { I(100), I(50), I(0) }),
(0x22f, new[] { I(0), I(300), I(300), I(150), I(0) }),
(0x220, new[] { I(50001), I(0), I(300), I(100), I(50), I(0) }),
(0x2, System.Array.Empty<Operand>()),
}, System.Array.Empty<string>());
var vm = new VirtualMachine(script, table, new RecordingHost());
vm.Gfx.SetSurface(1, 1, -1);
vm.Gfx.BindDraw(1, 1, 0, 0, 1, 1, 500, 350);
vm.Gfx.BindDraw(50001, 1, 0, 0, 1, 1, 0, 0);
vm.Run();
vm.Gfx.SnapshotVisibleObjects(1000); // seed the shared one-shot start
var halfway = vm.Gfx.SnapshotVisibleObjects(1150);
var map = halfway.Single(x => x.Handle == 1);
var marker = halfway.Single(x => x.Handle == 50001);
var mapPoint = Transform2DMath.Build(map.Transform).FromLocalOrigin(map.DstX, map.DstY)
.Then(map.RangeTransform!.Value).Apply(0, 0);
var markerPoint = Transform2DMath.Build(marker.Transform)
.FromLocalOrigin(marker.DstX, marker.DstY).Apply(0, 0);
Assert.Equal((700.0, 450.0), mapPoint);
Assert.Equal((50.0, 25.0), markerPoint);
Assert.Null(vm.Gfx.TryGet(0));
Assert.True(vm.Gfx.HasActiveTimedPresentation(1150));
vm.Gfx.SnapshotVisibleObjects(1300);
Assert.False(vm.Gfx.HasActiveTimedPresentation(1300));
}
[Fact]
public void Opcode229SelectsRangeWithoutCreatingAnOrdinaryObject()
{

View File

@@ -182,6 +182,23 @@ public sealed partial class GfxState
}
}
/// <summary>Op 0x22f: arm the selected retained-gfx range transform's delayed one-shot
/// translation target. FIELD uses this channel to pan the world while its ordinary minimap object
/// runs a matching op-0x220 translation.</summary>
public void SetRangeTranslationChannel(long delayMs, long durationMs,
(long X, long Y, long Z) target)
{
lock (_lock)
{
_rangeTransform.TranslationDelayMs = delayMs;
_rangeTransform.TranslationDurationMs = durationMs;
_rangeTransform.TranslationTarget = target;
_rangeTransform.TranslationEnabled = durationMs > 0;
_rangeTransform.OneShotStartMs = -1;
MarkRetainedMutation();
}
}
/// <summary>Op 0x21d: clone the native 0x2d4-byte retained-object record from source to destination.</summary>
public bool CloneObject(long sourceHandle, long destinationHandle)
{

View File

@@ -39,8 +39,10 @@ public sealed partial class VirtualMachine
Gfx.SetDefaultObjectSlot((int)Read(a[0])); return pc + 1;
// ---- SC0000 anim/transform/spritesheet cluster (docs/engine-re.md §"SC0000 anim ... cluster") ----
case "u00421DD0": // 0x22f set-position: (handle)(op2)(x)(y)(z) -> base position (direct set)
Gfx.SetObjectPosition(Read(a[0]), (Read(a[2]), Read(a[3]), Read(a[4]))); return pc + 1;
case "u00421DD0": // pre-reference compatibility
case "set-gfx-range-translation-target": // 0x22f (delay)(duration)(x)(y)(z)
Gfx.SetRangeTranslationChannel(Read(a[0]), Read(a[1]),
(Read(a[2]), Read(a[3]), Read(a[4]))); return pc + 1;
case "u004219E0": // pre-reference compatibility
case "set-gfx-range-transform": // 0x229 (first)(count)(anchor x/y/z)
Gfx.SetRangeTransform(Read(a[0]), Read(a[1]), (Read(a[2]), Read(a[3]), Read(a[4])));

View File

@@ -1528,7 +1528,8 @@ public sealed partial class VirtualMachine
return StepRetainedObject(label, a, pc);
// ---- SC0000 anim/transform/spritesheet cluster (docs/engine-re.md §"SC0000 anim ... cluster") ----
case "u00421DD0": // 0x22f set-position: (handle)(op2)(x)(y)(z) -> base position (direct set)
case "u00421DD0": // pre-reference compatibility
case "set-gfx-range-translation-target": // 0x22f (delay)(duration)(x)(y)(z)
case "u004219E0": // pre-reference compatibility
case "set-gfx-range-transform": // 0x229 (first)(count)(anchor x/y/z)
case "u00421A90": // pre-reference compatibility