feat(a2b): first-pass texture render — full-screen event-CG layer
Wire asset resolution into a live Godot render. VM executes set-texture -> ResourceMap resolves (scene,resId) -> files[section_base+resId] across all archives -> pre-converted BMP -> composite behind the dialogue. The full-screen event-CG layer (EV052*) renders end-to-end from the executed bytecode. - Age.Engine/Sys4/ResourceMap.cs: Resolve + BMP TexturePath; Paths: asset JSONs - GodotAdvHost: create/set/draw-texture -> TextureRect in a _stage layer - IHost.DrawTexture + VM dispatch extended with dst x/y (draw-texture args 7/8) - project.godot 800x600; convert_agf.py all-archive + --scene batch - engine 8/8, C# --selftest still byte-matches vm0 trace (VM behaviour unchanged) Known limitations (next chunk = graphics geometry/blend subsystem): - sprites + BG* via the CG-load subroutine get garbage dst/size — native ops stubbed (0x208 get-texture-size + sprite position/anim chain) - AE* fades draw opaque/instant (no alpha); no chromakey - slot model approximates the game's immediate-mode blit-onto-slot-0 canvas - AGF pre-converted to BMP offline (runtime decoder deferred) See docs/phase-a-slice-plan.md (A2b section). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,9 @@ public static class Paths
|
||||
public static string GameDir => Path.Combine(Workspace, "姫狩りダンジョンマイスター");
|
||||
public static string Build => Path.Combine(Repo, "build");
|
||||
public static string OpcodesJson => Path.Combine(Build, "opcodes.json");
|
||||
public static string AssetSectionsJson => Path.Combine(Build, "asset-sections.json");
|
||||
public static string AssetIndexJson => Path.Combine(Build, "asset-index.json");
|
||||
public static string Textures => Path.Combine(Build, "textures");
|
||||
|
||||
private static string FindRepo()
|
||||
{
|
||||
|
||||
63
engine/Age.Engine/Sys4/ResourceMap.cs
Normal file
63
engine/Age.Engine/Sys4/ResourceMap.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Age.Engine.Sys4;
|
||||
|
||||
/// <summary>One SYS4INI asset entry.</summary>
|
||||
public sealed record AssetEntry(string Name, string Archive, long Offset, long Size);
|
||||
|
||||
/// <summary>
|
||||
/// Static asset resolver. SYS4INI's file list is sectioned (one per scene: SCxxxx.BIN + its
|
||||
/// cross-archive asset manifest); file_number is the index within a section. So a bytecode
|
||||
/// resId resolves as files[section_base(scene) + resId] -- unified for graphics and audio.
|
||||
/// See docs/asset-resolution-re.md. Built from build/asset-index.json + build/asset-sections.json.
|
||||
/// </summary>
|
||||
public sealed class ResourceMap
|
||||
{
|
||||
private readonly IReadOnlyList<AssetEntry> _files;
|
||||
private readonly IReadOnlyDictionary<string, int> _sceneBase; // "SC0000" -> section base index
|
||||
|
||||
public ResourceMap(IReadOnlyList<AssetEntry> files, IReadOnlyDictionary<string, int> sceneBase)
|
||||
{
|
||||
_files = files;
|
||||
_sceneBase = sceneBase;
|
||||
}
|
||||
|
||||
public static ResourceMap Load(string indexPath, string sectionsPath)
|
||||
{
|
||||
var files = new List<AssetEntry>();
|
||||
using (var idx = JsonDocument.Parse(File.ReadAllText(indexPath)))
|
||||
foreach (var f in idx.RootElement.GetProperty("files").EnumerateArray())
|
||||
files.Add(new AssetEntry(
|
||||
f.GetProperty("name").GetString()!,
|
||||
f.GetProperty("archive").GetString()!,
|
||||
f.GetProperty("offset").GetInt64(),
|
||||
f.GetProperty("size").GetInt64()));
|
||||
|
||||
var sceneBase = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
using (var sec = JsonDocument.Parse(File.ReadAllText(sectionsPath)))
|
||||
foreach (var p in sec.RootElement.GetProperty("scene_base").EnumerateObject())
|
||||
sceneBase[p.Name] = p.Value.GetInt32();
|
||||
|
||||
return new ResourceMap(files, sceneBase);
|
||||
}
|
||||
|
||||
public static ResourceMap Load() => Load(Paths.AssetIndexJson, Paths.AssetSectionsJson);
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var key = scene.EndsWith(".BIN", StringComparison.OrdinalIgnoreCase)
|
||||
? scene[..^4] : scene;
|
||||
if (!_sceneBase.TryGetValue(key, out var b)) return null;
|
||||
long p = b + resId;
|
||||
return p >= 0 && p < _files.Count ? _files[(int)p] : null;
|
||||
}
|
||||
|
||||
/// <summary>Pre-converted BMP path for an AGF asset (see tools/convert_agf.py).</summary>
|
||||
public static string? TexturePath(AssetEntry a)
|
||||
{
|
||||
if (!a.Name.EndsWith(".AGF", StringComparison.OrdinalIgnoreCase)) return null;
|
||||
var bmp = Path.Combine(Paths.Textures, Path.GetFileNameWithoutExtension(a.Name) + ".BMP");
|
||||
return File.Exists(bmp) ? bmp : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user