Implement looping SFX and immediate rotation opcodes

This commit is contained in:
gamer147
2026-07-28 23:45:48 -04:00
parent 82ac160271
commit 3f4124bbb8
13 changed files with 112 additions and 28 deletions

View File

@@ -384,6 +384,25 @@ public class GfxAnimationTests
Assert.Equal((12L, 34L, 5L), o.V16c);
}
[Fact]
public void CurrentRotationSetter_ReplacesTheLiveAxisAngleImmediately()
{
var t = T();
var scene = ScriptAssembler.Assemble(t, "CURRENTROTATION", new List<(int, Operand[])>
{
MovGI(1, 0xcb20), MovGI(2, 0), MovGI(3, 1), MovGI(4, 0), MovGI(5, 180),
(0x1fe, new[] { G(1), G(2), G(3), G(4), G(5) }),
Exit(),
}, System.Array.Empty<string>());
var vm = new VirtualMachine(scene, t, new RecordingHost());
vm.Run();
var o = vm.Gfx.TryGet(0xcb20)!;
Assert.Equal((0.0, 1.0, 0.0, 180.0), o.RotationCurrent);
Assert.False(o.RotationChannelEnabled);
}
[Fact]
public void ResetAnimClock_DispatchClearsGlobalServiceClock()
{

View File

@@ -41,6 +41,8 @@ public class SfxOpsTests
{
(0xb4, new[] { new Operand(0, 0x28), new Operand(0, 0) }),
(0xb5, new[] { new Operand(0, 0) }),
(0xb4, new[] { new Operand(0, 0x2aea), new Operand(0, 9) }),
(0xba, new[] { new Operand(0, 9) }),
(0x1cf, new[] { new Operand(0, 1) }),
(0x2bf, new[] { new Operand(0, 4), new Operand(0, 0), new Operand(0, 100) }),
(0xb6, new[] { new Operand(0, 0) }),
@@ -54,8 +56,9 @@ public class SfxOpsTests
vm.Run();
Assert.Equal("exit", vm.HaltReason);
Assert.Equal((0x28L, 0), Assert.Single(host.SfxLoads));
Assert.Equal(0, Assert.Single(host.SfxStarts));
Assert.Equal([(0x28L, 0), (0x2aeaL, 9)], host.SfxLoads);
Assert.Equal([0, 9], host.SfxStarts);
Assert.Equal([(0, 0), (9, 1)], host.SfxStartRequests);
Assert.Equal(1, Assert.Single(host.VoiceBgmDuckControls));
Assert.Equal((4, 0, 100L), Assert.Single(host.ScheduledSfxStarts));
Assert.Equal(0, Assert.Single(host.SfxReleases));

View File

@@ -39,6 +39,7 @@ internal class RecordingHost : IHost
public readonly List<(long Id, int PlaybackVariant, long DelayMs)> ScheduledVoiceRequests = new();
public readonly List<long> VoiceBgmDuckControls = new();
public readonly List<int> SfxStarts = new();
public readonly List<(int Channel, int StartMode)> SfxStartRequests = new();
public readonly List<(int Channel, int StartMode, long DelayMs)> ScheduledSfxStarts = new();
public readonly List<int> SfxReleases = new();
public readonly List<long> BgmTracks = new();
@@ -171,7 +172,12 @@ internal class RecordingHost : IHost
public void ScheduleVoicePlayback(long id, int playbackVariant, long delayMs)
=> ScheduledVoiceRequests.Add((id, playbackVariant, delayMs));
public void LoadSoundEffect(long resourceId, int channel) => SfxLoads.Add((resourceId, channel));
public void StartSoundEffect(int channel) => SfxStarts.Add(channel);
public void StartSoundEffect(int channel) => StartSoundEffect(channel, 0);
public void StartSoundEffect(int channel, int startMode)
{
SfxStarts.Add(channel);
SfxStartRequests.Add((channel, startMode));
}
public void ScheduleSoundEffectStart(int channel, int startMode, long delayMs)
=> ScheduledSfxStarts.Add((channel, startMode, delayMs));
public void ReleaseSoundEffect(int channel) => SfxReleases.Add(channel);

View File

@@ -133,6 +133,9 @@ public interface IHost
void SetVoiceBgmDuckControl(long flags) { }
void LoadSoundEffect(long resourceId, int channel) { }
void StartSoundEffect(int channel) { }
// Ops 0xb5/0xba share the native channel-start worker. Mode 0 plays once; mode 1
// rewinds the decoder at EOF. The one-argument seam remains for simple hosts.
void StartSoundEffect(int channel, int startMode) => StartSoundEffect(channel);
// Native SetDelay (op 0x2bf) starts an already-loaded channel after delayMs.
// startMode is forwarded to the same worker used by immediate SFX starts.
void ScheduleSoundEffectStart(int channel, int startMode, long delayMs) { }

View File

@@ -979,6 +979,17 @@ public sealed class GfxState
}
}
/// <summary>Op 0x1fe: immediately replace the object's current axis-angle rotation matrix at
/// obj+0xec. Axis components and angle are retained as native floating-point values.</summary>
public void SetCurrentRotation(long handle, (long X, long Y, long Z) axis, long angleDegrees)
{
lock (_lock)
{
var o = GetOrCreate(handle);
o.RotationCurrent = (axis.X, axis.Y, axis.Z, angleDegrees);
}
}
/// <summary>Op 0x1ff: immediately replace the object's current translation matrix at obj+0x16c.</summary>
public void SetCurrentTranslation(long handle, (long X, long Y, long Z) translation)
{

View File

@@ -2398,8 +2398,10 @@ public sealed class VirtualMachine
_host.LoadSoundEffect(resourceId, channel);
return pc + 1;
}
case "u0041D050": // 0xb5 / semantics: sfx-start
_host.StartSoundEffect((int)Read(a[0])); return pc + 1;
case "u0041D050": // 0xb5 / semantics: sfx-start-once
_host.StartSoundEffect((int)Read(a[0]), 0); return pc + 1;
case "sfx-start-loop": // 0xba / same worker, logical decoder rewind at EOF
_host.StartSoundEffect((int)Read(a[0]), 1); return pc + 1;
case "u0041D080": // 0xb6 / semantics: sfx-release
{
int channel = (int)Read(a[0]);
@@ -2554,6 +2556,9 @@ public sealed class VirtualMachine
case "u00420620": // upstream ABI label
case "gfx-set-scale-current": // 0x1fd (handle)(sx%)(sy%)(sz%) -> current scale matrix
Gfx.SetCurrentScale(Read(a[0]), (Read(a[1]), Read(a[2]), Read(a[3]))); return pc + 1;
case "set-current-rotation-axis-angle": // 0x1fe (handle)(axis x/y/z)(angle degrees)
Gfx.SetCurrentRotation(Read(a[0]), (Read(a[1]), Read(a[2]), Read(a[3])), Read(a[4]));
return pc + 1;
case "set-adv-wait-indicator-handle": // 0x212 (layout)(retained handle)
{
TextHistory.SetWaitIndicatorObjectHandle((int)Read(a[0]), Read(a[1]));