Use universal packed resource resolution

This commit is contained in:
gamer147
2026-07-21 20:23:32 -04:00
parent 5f816bb438
commit 1a2ea008c6
19 changed files with 161 additions and 154 deletions

View File

@@ -21,11 +21,9 @@ public interface IHost
{
/// <summary>Report a recoverable runtime discrepancy while allowing script execution to continue.</summary>
void ReportWarning(string message) => System.Console.Error.WriteLine(message);
// Script-local resource ids resolve against the currently executing frame's SYS4INI section.
// Interactive hosts track this stack; headless hosts may keep the no-op/default identity behavior.
// Script context is retained for diagnostics/page location; resource operands are universal packed ids.
void EnterScriptContext(string scriptName) { }
void ExitScriptContext() { }
long ResolveTextureResourceId(long resourceId) => resourceId;
void ShowText(int offset, string text);
// Native ADV text subsystem: op 0x7a updates the selected layout's last 20-byte cursor record;
// op 0x204 rasterizes a string into a numbered surface before 0x1fb binds that surface.
@@ -125,8 +123,8 @@ public interface IHost
/// immediately after 0x236 returns.</returns>
long? PlayMovieToSurface(long resourceId, int surfaceSlot, long movieFlags, long syncMask) => null;
bool IsMovieSurfaceActive(int surfaceSlot) => false;
// Native op 0x20f uses a universal raw-catalog id and parks script execution until the movie
// Native op 0x20f uses a universal packed id and parks script execution until the movie
// reaches EOF or the player cancels it. The decoder remains asynchronous; the interactive host
// owns the modal wait so its render loop can continue publishing frames.
void PlayModalMovieToSurface(long rawResourceId, int surfaceSlot, long movieFlags) { }
void PlayModalMovieToSurface(long resourceId, int surfaceSlot, long movieFlags) { }
}

View File

@@ -1,8 +1,8 @@
namespace Age.Engine.Sys4;
/// <summary>
/// Compatibility facade over the runtime SYS4 catalog. Scene-local graphics/voice/movie ids resolve
/// through the executing script's manifest; BGM uses direct names; SFX/cursors use universal packed ids.
/// Typed facade over the runtime SYS4 catalog. Graphics, voice, movie, SFX, and cursor operands use
/// universal packed ids; BGM uses direct names.
/// See docs/asset-resolution-re.md.
/// </summary>
public sealed class ResourceMap
@@ -18,43 +18,26 @@ public sealed class ResourceMap
public static ResourceMap Load() => new(Sys4AssetCatalog.Load(Paths.Sys4Ini));
/// <summary>Resolve a scene-local resId to its asset, or null if out of range / unknown scene.</summary>
public AssetEntry? Resolve(string scene, long resId)
/// <summary>Resolve a universal packed SYS4INI/AAI id to an AGF texture record.</summary>
public AssetEntry? ResolveTexture(long resourceId)
{
return _catalog.ResolveScene(scene, resId);
}
/// <summary>Resolve graphics normally through the scene manifest, with the universal raw-id
/// fallback used by SYSTEM4-owned assets such as SO001.</summary>
public AssetEntry? ResolveTexture(string scene, long resId)
{
var entry = _catalog.ResolveScene(scene, resId) ?? _catalog.ResolveRaw(resId);
var entry = _catalog.ResolvePacked(resourceId);
return entry is { IsPlaceholder: false } &&
entry.Name.EndsWith(".AGF", StringComparison.OrdinalIgnoreCase) ? entry : null;
}
/// <summary>Resolve voice audio through the active SC section when one exists, then through the
/// universal raw catalog used by non-SC frontend scripts such as ROOM.</summary>
public AssetEntry? ResolveVoice(string scene, long resId)
/// <summary>Resolve a universal packed SYS4INI/AAI id to a voice audio record.</summary>
public AssetEntry? ResolveVoice(long resourceId)
{
var entry = _catalog.ResolveScene(scene, resId) ?? _catalog.ResolveRaw(resId);
var entry = _catalog.ResolvePacked(resourceId);
return entry is { IsPlaceholder: false } && IsAudio(entry) ? entry : null;
}
/// <summary>Resolve an already-normalized packed catalog id without applying a scene section base.</summary>
public AssetEntry? ResolveRawTexture(long rawId)
/// <summary>Resolve a universal packed SYS4INI/AAI id to an AGF-named movie record. ReadMovie
/// validates the MPEG signature because still images use the same extension.</summary>
public AssetEntry? ResolveMovie(long resourceId)
{
var entry = _catalog.ResolvePacked(rawId);
return entry is { IsPlaceholder: false } &&
entry.Name.EndsWith(".AGF", StringComparison.OrdinalIgnoreCase) ? entry : null;
}
/// <summary>Resolve op 0x20f's universal raw-catalog movie id without applying the executing
/// script's manifest base. AGE stores these MPEG program streams under .AGF names; ReadMovie
/// validates the payload signature before playback.</summary>
public AssetEntry? ResolveRawMovie(long rawId)
{
var entry = _catalog.ResolveRaw(rawId);
var entry = _catalog.ResolvePacked(resourceId);
return entry is { IsPlaceholder: false } &&
entry.Name.EndsWith(".AGF", StringComparison.OrdinalIgnoreCase) ? entry : null;
}
@@ -81,7 +64,7 @@ public sealed class ResourceMap
/// <summary>
/// Resolve a BGM id to its catalog entry. 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)
/// universal packed resource table used by voices/textures. 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>

View File

@@ -14,7 +14,11 @@ public sealed record AssetEntry(
int ArchiveId = -1,
int FileNumber = -1,
bool IsPlaceholder = false,
int PackId = 0);
int PackId = 0)
{
/// <summary>The exact packed SYS4INI/AAI id AGE uses to address this record.</summary>
public int PackedId => checked((PackId << 24) | RawIndex);
}
/// <summary>A real catalog entry paired with the packed resource id AGE uses at runtime.</summary>
public sealed record PackedAssetEntry(long PackedId, AssetEntry Asset);

View File

@@ -1539,29 +1539,27 @@ public sealed class VirtualMachine
_host.CreateTexture((int)Read(a[0]), (int)Read(a[1]), (int)Read(a[2])); return pc + 1;
case "set-texture": // 0x1f9 (resId)(slot)(colorkey) — load a file into the slot's surface
{
long requestedResourceId = Read(a[0]);
long resolvedResourceId = _host.ResolveTextureResourceId(requestedResourceId);
long resourceId = Read(a[0]);
if (_diagSetTexture) // AGE_DIAG_SETTEX: log the SLOT operand source (literal vs which global) — grey-BG slot dig
System.Console.Error.WriteLine($"[settex] resId=0x{requestedResourceId:x}->0x{resolvedResourceId:x} slot={(int)Read(a[1])} " +
System.Console.Error.WriteLine($"[settex] resId=0x{resourceId:x} slot={(int)Read(a[1])} " +
$"slotOp=(type={a[1].Type} val=0x{a[1].Value:x}){(a[1].Type == 3 ? $" G[0x{a[1].Value:x}]" : "")}");
_host.ReleaseSurface((int)Read(a[1]));
long colorKey = a.Count > 2 ? Read(a[2]) : -1;
Gfx.SetSurface((int)Read(a[1]), resolvedResourceId, colorKey);
_host.SetTexture(resolvedResourceId, (int)Read(a[1]), colorKey);
Gfx.SetSurface((int)Read(a[1]), resourceId, colorKey);
_host.SetTexture(resourceId, (int)Read(a[1]), colorKey);
return pc + 1; // host still tracks dims for get-texture-size
}
case "u00422EB0": // pre-reference compatibility
case "load-raw-texture-surface": // 0x249 (raw catalog id)(slot)(colorkey)
case "load-raw-texture-surface": // 0x249 (packed resource id)(slot)(colorkey)
{
// Native shares 0x1f9's release/load/colorkey path, but constructs its mode-1
// surface subclass and receives an already-global SYS4INI catalog index. The
// CPU compositor does not need the D3D subclass distinction; it does need the
// resource id to bypass the executing script's scene-section normalization.
long rawResourceId = Read(a[0]);
// surface subclass. Both texture opcodes receive the same universal packed id;
// the CPU compositor does not need the D3D subclass distinction.
long resourceId = Read(a[0]);
int surfaceSlot = (int)Read(a[1]);
_host.ReleaseSurface(surfaceSlot);
Gfx.SetSurface(surfaceSlot, rawResourceId, Read(a[2]));
_host.SetTexture(rawResourceId, surfaceSlot, Read(a[2]));
Gfx.SetSurface(surfaceSlot, resourceId, Read(a[2]));
_host.SetTexture(resourceId, surfaceSlot, Read(a[2]));
return pc + 1;
}
case "draw-texture": // 0x1fb (handle)(slot)(srcX)(srcY)(w)(h)(dstX)(dstY) — bind object -> surface + rect + pos
@@ -1652,14 +1650,14 @@ public sealed class VirtualMachine
case "get-initial-root-run": // 0x130 (out)
Write(a[0], _initialRootRun ? 1 : 0);
return pc + 1;
case "play-modal-movie-to-surface": // 0x20f (raw resource)(surface)(movie flags)
case "play-modal-movie-to-surface": // 0x20f (packed resource)(surface)(movie flags)
{
long rawResourceId = Read(a[0]);
long resourceId = Read(a[0]);
int surfaceSlot = (int)Read(a[1]);
// The modal and scene-local paths share retained-surface composition. The host's
// distinct entry point preserves 0x20f's raw-id resolver and blocking lifecycle.
Gfx.SetSurface(surfaceSlot, rawResourceId, 0);
_host.PlayModalMovieToSurface(rawResourceId, surfaceSlot, Read(a[2]));
// Modal and non-modal paths share packed resolution and retained-surface composition.
// The distinct host entry point owns only 0x20f's blocking lifecycle.
Gfx.SetSurface(surfaceSlot, resourceId, 0);
_host.PlayModalMovieToSurface(resourceId, surfaceSlot, Read(a[2]));
return pc + 1;
}
case "u004221A0": // pre-reference compatibility