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:
@@ -1,14 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert named AGF files (in extracted/DATA2 or DATA5) to BMP via AGF2BMP2AGF.exe,
|
||||
into build/textures/. Run: py -3.11 -X utf8 tools/convert_agf.py EV001AA.AGF ..."""
|
||||
import os, sys, shutil, subprocess
|
||||
"""Convert AGF images to BMP via AGF2BMP2AGF.exe, into build/textures/.
|
||||
|
||||
py -3.11 -X utf8 tools/convert_agf.py EV052CA.AGF BG030A.AGF ... # named assets
|
||||
py -3.11 -X utf8 tools/convert_agf.py --scene SC0000 # all .AGF in the scene's
|
||||
# SYS4INI section manifest
|
||||
|
||||
Searches every extracted/DATA* dir (DATA1 holds BG/CS/CB/CA/AE/EM sprites, DATA2 the EV CGs).
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import paths
|
||||
|
||||
EXE = paths.EXTRACTED / "DATA1" / "AGF2BMP2AGF.exe" # tool lives in extracted/DATA1
|
||||
SRC_DIRS = [paths.EXTRACTED / "DATA2", paths.EXTRACTED / "DATA5"]
|
||||
EXE = paths.EXTRACTED / "DATA1" / "AGF2BMP2AGF.exe" # tool lives in extracted/DATA1
|
||||
SRC_DIRS = [paths.EXTRACTED / d for d in ("DATA1", "DATA2", "DATA3", "DATA4", "DATA5")]
|
||||
OUT = paths.BUILD / "textures"
|
||||
|
||||
|
||||
def find(name):
|
||||
for d in SRC_DIRS:
|
||||
p = d / name
|
||||
@@ -16,18 +28,44 @@ def find(name):
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def scene_agfs(scene):
|
||||
"""The .AGF asset names in a scene's SYS4INI section manifest."""
|
||||
secs = json.loads((paths.BUILD / "asset-sections.json").read_text(encoding="utf-8"))
|
||||
files = json.loads((paths.BUILD / "asset-index.json").read_text(encoding="utf-8"))["files"]
|
||||
base = secs["scene_base"][scene.upper().removesuffix(".BIN")]
|
||||
end = next(s["end"] for s in secs["sections"] if s["start"] == base)
|
||||
return [files[p]["name"] for p in range(base, end + 1)
|
||||
if files[p]["name"].upper().endswith(".AGF")]
|
||||
|
||||
|
||||
def convert(name):
|
||||
src = find(name)
|
||||
if not src:
|
||||
print(f"NOT FOUND: {name}"); return False
|
||||
out_bmp = OUT / (os.path.splitext(name)[0] + ".BMP")
|
||||
if out_bmp.exists():
|
||||
return True
|
||||
tmp = OUT / name
|
||||
shutil.copy(src, tmp)
|
||||
r = subprocess.run([str(EXE), name], cwd=str(OUT), capture_output=True, text=True)
|
||||
tmp.unlink(missing_ok=True)
|
||||
if not out_bmp.exists():
|
||||
print(f"FAILED: {name} ({r.stdout.strip()[:60]})"); return False
|
||||
return True
|
||||
|
||||
|
||||
def main(argv):
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
for name in argv:
|
||||
src = find(name)
|
||||
if not src:
|
||||
print(f"NOT FOUND: {name}"); continue
|
||||
tmp = OUT / name
|
||||
shutil.copy(src, tmp)
|
||||
subprocess.run([str(EXE), name], cwd=str(OUT), check=True)
|
||||
tmp.unlink(missing_ok=True) # drop the copied .AGF, keep the .BMP
|
||||
print(f"converted {name} -> {OUT / (os.path.splitext(name)[0] + '.BMP')}")
|
||||
if argv and argv[0] == "--scene":
|
||||
names = scene_agfs(argv[1])
|
||||
print(f"{argv[1]}: {len(names)} .AGF assets in manifest")
|
||||
else:
|
||||
names = argv
|
||||
ok = sum(convert(n) for n in names)
|
||||
print(f"converted {ok}/{len(names)} -> {OUT.relative_to(paths.REPO)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
|
||||
Reference in New Issue
Block a user