refactor: move game-specific import logic into profiles; unblock Tomie import

- Add app/games: GameProfile base, registry, ReaderHooks and renpy/naninovel/
  tomie profiles. pipeline, preflight, detect and mods dispatch via the registry
- Move tomie_* converter modules to app/games/tomie and split out reader hooks,
  import, mod validation and known defects
- Tolerate the 31 authoring defects of the pinned Tomie build as report
  warnings (exact list, per-script counts); any new defect still blocks import
- Frontend: runtime registry (runtimes.ts) with lazy Tomie launcher
- Add profile/registry tests and docs/GAME_PROFILES.md; update IMPORT.md

Full media export of the original Tomie build is not yet verified.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-25 11:35:23 +03:00
1 parent bbbcc6a5d8
commit 12ec82c7e6
30 files changed
+700 -196

No files matched your search

+80
View File
@@ -0,0 +1,80 @@
"""Tomie import: validated native command lists in editable PGVN modules."""
import json
import tempfile
from pathlib import Path
from app.blobs import put_bytes
from app.converter import locales
from app.converter.nani_package import read_package
from app.games.tomie.assets import export_media
from app.games.tomie.format import RUNTIME, modules, prepare
from app.games.tomie.reader import TomieHooks
def inspect_tomie(root: Path, work: Path | None = None) -> dict:
if work is None:
with tempfile.TemporaryDirectory(prefix="pgames-tomie-") as directory:
return inspect_tomie(root, Path(directory))
package = read_package(root, work, media=True, hooks=TomieHooks())
try:
native = prepare(package)
return {"engine": "naninovel", "profile": package.profile, "compatible": True,
"scripts": len(native["scripts"]), "textResources": len(package.texts),
"commands": sum(len(script["commands"]) for script in native["scripts"].values()),
"warnings": _warnings(native)}
finally:
package.close()
def _warnings(native: dict) -> dict:
"""Known authoring defects of the original build, grouped by message."""
result: dict[str, int] = {}
for defect in native.get("known_defects", []):
text = f"{defect['message']} ({defect['location']})"
result[text] = result.get(text, 0) + 1
return result
def convert_tomie(root: Path, work: Path, progress):
progress("Чтение сценариев Tomie и проверка совместимости")
package = read_package(root, work / "unity", media=True, hooks=TomieHooks())
try:
return convert_package(root, work, progress, package)
finally:
package.close()
def convert_package(root: Path, work: Path, progress, package):
from app.converter.pipeline import Result, anchors
native = prepare(package) # all script checks precede media decoding/blob writes
known = _warnings(native)
native.pop("known_defects", None)
scripts = modules(native, package.texts)
files, media, counts = export_media(package, root, work, progress)
def store(path, value):
raw = json.dumps(value, ensure_ascii=False, separators=(",", ":")).encode()
digest, size = put_bytes(raw, ".json")
files[path] = {"hash": digest, "size": size}
resources = {}
for tid, record in package.texts.items():
resources.setdefault(record["collection"], {}).setdefault(record["key"], []).append({"tid": tid, **record})
for path, module in scripts.items():
module["anchors"] = anchors(module["labels"])
store(path, module)
game = {"format": "pgvn/1", "engine": "naninovel", "runtime": RUNTIME, "title": root.name,
"resolution": [1920, 1080], "entry": "nani:" + native["start"]["script"], "scripts": list(scripts),
"characters": {}, "images": {}, "transforms": {}, "transitions": {}, "audio": {}, "files": {},
"defaults": [], "init": [], "ui": {}, "sourceLang": locales.detect_language(
{path: module["labels"] for path, module in scripts.items()}) or "und", "locales": {},
"naniResources": resources,
"tomie": {**{key: value for key, value in native.items() if key != "scripts"}, "media": media}}
store("game.json", game)
report = {"engine": "naninovel", "profile": package.profile, "compatible": True, "format": "pgvn/1",
"runtime": RUNTIME, "scripts": len(package.scripts), "textResources": len(package.texts),
"media": counts, "warnings": [{"message": text, "count": count} for text, count in known.items()],
"knownDefects": sum(known.values())}
return Result({"format": "pgvn/1", "files": files}, report, root.name, None,
sum(entry["size"] for entry in files.values()))