Implement native-backed SC0000 SFX lifecycle

This commit is contained in:
gamer147
2026-07-11 02:15:17 -04:00
parent 377987b5e0
commit 99ce351041
16 changed files with 413 additions and 64 deletions

View File

@@ -25,4 +25,8 @@ public interface IHost
(int Width, int Height) GetTextureSize(int slot);
void PlayBgm(long id);
void PlayVoice(long id);
void LoadSoundEffect(long resourceId, int channel) { }
void StartSoundEffect(int channel) { }
void ReleaseSoundEffect(int channel) { }
void FadeBgm(int targetPercent, long durationMs) { }
}

View File

@@ -76,14 +76,15 @@ public sealed class ResourceMap
return null;
}
/// <summary>Loose extracted OGG path for an audio asset (extracted/DATA{n}/{name}), or null.
/// OGG plays natively in Godot. Used for voices (which DO use the per-scene manifest via Resolve).</summary>
/// <summary>Loose extracted OGG/WAV path for an audio asset (extracted/DATA{n}/{name}), or null.
/// Used by the current bootstrap audio backend; voices and SFX use the scene manifest via Resolve.</summary>
public static string? AudioPath(AssetEntry a)
{
if (!a.Name.EndsWith(".OGG", StringComparison.OrdinalIgnoreCase)) return null;
if (!a.Name.EndsWith(".OGG", StringComparison.OrdinalIgnoreCase) &&
!a.Name.EndsWith(".WAV", StringComparison.OrdinalIgnoreCase)) return null;
var dir = a.Archive.EndsWith(".ALF", StringComparison.OrdinalIgnoreCase)
? a.Archive[..^4] : a.Archive; // "DATA3.ALF" -> "DATA3"
var ogg = Path.Combine(Paths.Extracted, dir, a.Name);
return File.Exists(ogg) ? ogg : null;
var audio = Path.Combine(Paths.Extracted, dir, a.Name);
return File.Exists(audio) ? audio : null;
}
}

View File

@@ -25,6 +25,8 @@ public sealed class VirtualMachine
public long CallScriptDispatches { get; private set; }
public Dictionary<int, long> Globals { get; } = new();
/// <summary>Native/profile-owned values read by scripts but maintained outside script-visible writes.</summary>
public Dictionary<int, long> ExternalGlobals { get; } = new();
public Dictionary<int, string> GlobalStrings { get; } = new();
public GfxState Gfx { get; } = new();
public List<(int Offset, string Text, string Script)> Emitted { get; } = new();
@@ -37,6 +39,7 @@ public sealed class VirtualMachine
_sink = sink ?? NullTraceSink.Instance; }
private static long Gi(Dictionary<int, long> d, int k) => d.TryGetValue(k, out var v) ? v : 0;
private long ReadGlobal(int k) => ExternalGlobals.TryGetValue(k, out var v) ? v : Gi(Globals, k);
private static string Gs(Dictionary<int, string> d, int k) => d.TryGetValue(k, out var v) ? v : "";
private static long PyDiv(long a, long b) { if (b == 0) return 0; long q = a / b, r = a % b; if (r != 0 && (r < 0) != (b < 0)) q--; return q; }
private static long PyMod(long a, long b) { if (b == 0) return 0; long r = a % b; if (r != 0 && (r < 0) != (b < 0)) r += b; return r; }
@@ -73,7 +76,7 @@ public sealed class VirtualMachine
private long Read(Operand op) => op.Type switch
{
T_IMM => op.Value,
T_GINT or T_GFLOAT => Gi(Globals, (int)op.Value),
T_GINT or T_GFLOAT => ReadGlobal((int)op.Value),
T_GPTR => Gi(Globals, (int)Gi(Globals, (int)op.Value)),
T_LINT => Gi(_cur.Locals.I, (int)op.Value),
T_LFLOAT => Gi(_cur.Locals.F, (int)op.Value),
@@ -319,6 +322,16 @@ public sealed class VirtualMachine
}
case "play-bgm": _host.PlayBgm(Read(a[0])); return pc + 1;
case "play-voice": _host.PlayVoice(Read(a[0])); return pc + 1;
case "play-sound-effect": // 0xb4 / semantics: sfx-load
_host.LoadSoundEffect(Read(a[0]), (int)Read(a[1])); return pc + 1;
case "u0041D050": // 0xb5 / semantics: sfx-start
_host.StartSoundEffect((int)Read(a[0])); return pc + 1;
case "u0041D080": // 0xb6 / semantics: sfx-release
_host.ReleaseSoundEffect((int)Read(a[0])); return pc + 1;
case "u0041D2B0": // 0xc2 / semantics: fade-bgm
_host.FadeBgm((int)Read(a[0]), Read(a[1])); return pc + 1;
case "u00415880": // 0xd9 / semantics: clear-run-state-0x1000
return pc + 1;
// ---- gfx command-buffer ops (VM-internal GfxState; docs/engine-re.md op-contract table) ----
case "query-gfx-object?": // 0x215 (out)(handle) -> slot | -1
if (_diagSetTexture) // reuse the flag: show what the slot query returns (grey-BG slot dig)