feat(a2b): audio — wire play-bgm/play-voice, fix BGM addressing
Wire audio end-to-end (OGG plays natively in Godot; no Frida, no decode):
- IHost.PlayBgm/PlayVoice + VM dispatch for play-bgm(0xbf)/play-voice(0xc4).
Non-Godot hosts no-op them so --selftest + engine 8/8 stay byte-identical.
- GodotAdvHost resolves id->OGG; Main plays via two AudioStreamPlayer nodes
(BGM looping; voice interrupt-on-new).
Key finding (by-ear, systematic-debugging): the two audio ops use DIFFERENT
addressing — the earlier "unified manifest" assumption was wrong for BGM.
- play-voice -> per-scene manifest files[base+id] (offset 0, same as textures).
Confirmed by ear; upgraded med->HIGH.
- play-bgm -> DIRECT LITERAL NAME BGM{id:03d}.OGG, NOT the manifest.
Real game: play-bgm 5->BGM005, 8->BGM008 (manifest gave +1). Proven by
play-bgm 0x23->BGM035 (real standalone track; BGM set skips 030-034) that the
manifest mis-resolved to a graphics entry. Fix is BGM-only:
ResourceMap.BgmPathById; voices/textures unchanged.
Also: Lily's silence root-caused as correct form-gating (G[0xa57/0xa58/0xa59]),
left unseeded by choice (no dummy state). Added diagnostic
`Age.Cli audio <SCENE.BIN> [0xADDR=VAL ...]`. Corrected opcodes.toml (play-bgm
direct-name, play-voice HIGH) + docs/memory (dropped the bogus unified-manifest
/ Frida-BGM006 claims).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,4 +11,6 @@ public sealed class CaptureHost : IHost
|
||||
public void CreateTexture(int slot, int width, int height) { }
|
||||
public void SetTexture(long resourceId, int slot) { }
|
||||
public void DrawTexture(int slot, int srcX, int srcY, int width, int height, int dstX, int dstY) { }
|
||||
public void PlayBgm(long id) { }
|
||||
public void PlayVoice(long id) { }
|
||||
}
|
||||
|
||||
@@ -8,4 +8,6 @@ public interface IHost
|
||||
void CreateTexture(int slot, int width, int height);
|
||||
void SetTexture(long resourceId, int slot);
|
||||
void DrawTexture(int slot, int srcX, int srcY, int width, int height, int dstX, int dstY);
|
||||
void PlayBgm(long id);
|
||||
void PlayVoice(long id);
|
||||
}
|
||||
|
||||
@@ -60,4 +60,30 @@ public sealed class ResourceMap
|
||||
var bmp = Path.Combine(Paths.Textures, Path.GetFileNameWithoutExtension(a.Name) + ".BMP");
|
||||
return File.Exists(bmp) ? bmp : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve a BGM id to its OGG path. BGM is addressed by DIRECT LITERAL NAME (BGM{id:D3}.OGG), NOT the
|
||||
/// per-scene section manifest that voices/textures use. Confirmed by ear (play-bgm 5->BGM005, 8->BGM008)
|
||||
/// and by the play-bgm 0x23->BGM035 case: BGM035 is a real standalone track (the BGM set skips 030-034),
|
||||
/// which the manifest mis-resolved to a graphics entry. See docs/asset-resolution-re.md.
|
||||
/// </summary>
|
||||
public string? BgmPathById(long id)
|
||||
{
|
||||
var name = $"BGM{id:D3}.OGG";
|
||||
foreach (var f in _files)
|
||||
if (f.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
|
||||
return AudioPath(f);
|
||||
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>
|
||||
public static string? AudioPath(AssetEntry a)
|
||||
{
|
||||
if (!a.Name.EndsWith(".OGG", 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +175,8 @@ public sealed class VirtualMachine
|
||||
case "draw-texture": // (handle, slot, srcX, srcY, w, h, dstX, dstY)
|
||||
_host.DrawTexture((int)Read(a[1]), (int)Read(a[2]), (int)Read(a[3]), (int)Read(a[4]),
|
||||
(int)Read(a[5]), (int)Read(a[6]), (int)Read(a[7])); return pc + 1;
|
||||
case "play-bgm": _host.PlayBgm(Read(a[0])); return pc + 1;
|
||||
case "play-voice": _host.PlayVoice(Read(a[0])); return pc + 1;
|
||||
default:
|
||||
_host.OnStub(op); return pc + 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user