- 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>
75 lines
4.1 KiB
Python
75 lines
4.1 KiB
Python
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
|
|
def test_native_translation_and_mod_edits_rebuild_the_same_vm_input(tmp_path, monkeypatch):
|
|
from app import mods
|
|
from app.config import get_settings
|
|
from app.converter.nani_package import Package, text_records
|
|
from app.games.tomie.convert import convert_package
|
|
from app.games.tomie.format import unpack_modules
|
|
from app.games.tomie.profile import PROFILE
|
|
from app.translation import apply_changes, locale_tables, text_index
|
|
monkeypatch.setenv("DATA_DIR", str(tmp_path / "data"))
|
|
get_settings.cache_clear()
|
|
package = Package(profile=PROFILE, defaults={"points": 3}, function_data={"names": ["Tomie"]})
|
|
|
|
def script(name, label, commands):
|
|
return {"name": name, "lines": [{"line": 1, "label": label, "commands": commands}]}
|
|
|
|
package.scripts = {
|
|
"StartGameSetting": script("StartGameSetting", "Start", [
|
|
{"op": "PrintText", "args": {"Text": {"template": "Points: {points}"}}},
|
|
{"op": "ExitToTitle", "args": {}}]),
|
|
"MessagePlay": script("MessagePlay", "Start", [{"op": "Return", "args": {}}]),
|
|
"AppScreen": script("AppScreen", "Start", [{"op": "Stop", "args": {}}]),
|
|
}
|
|
package.texts = text_records("Tips", "tip:Title|Description {points}", "Tips")
|
|
media = {key: {} for key in ("backgrounds", "characters", "textures", "videos", "audio", "fonts")}
|
|
monkeypatch.setattr("app.games.tomie.convert.export_media", lambda *_: ({}, media, {}))
|
|
try:
|
|
result = convert_package(tmp_path, tmp_path / "work", lambda _: None, package)
|
|
index = text_index(result.manifest)
|
|
assert {line.source for line in index.lines} == {"Points: {points}", "Title", "Description {points}"}
|
|
line = next(line for line in index.lines if line.source.startswith("Points:"))
|
|
translated = apply_changes(result.manifest, "ru", lines={line.tid: {"t": "Баллы: {points}"}})
|
|
assert locale_tables(translated, "ru")[0][line.tid]["t"] == "Баллы: {points}"
|
|
doc = mods.document(translated)
|
|
mods.validate(doc, translated)
|
|
node = next(node for module in doc["scripts"].values() for label in module["labels"]
|
|
for node in label["body"] if node.get("tid") == line.tid)
|
|
node["what"] = "Score: {points}"
|
|
updated = mods.store(doc, translated)
|
|
runtime = unpack_modules(mods.document(updated)["scripts"])
|
|
assert runtime["StartGameSetting"]["commands"][0]["args"]["Text"] == {"template": "Score: {points}"}
|
|
module = next(module for module in doc["scripts"].values() if module.get("nani", {}).get("name") == "StartGameSetting")
|
|
module["labels"][0]["body"].insert(0, {"id": "inserted", "op": "nani_command", "command": {
|
|
"op": "SetCustomVariable", "args": {"Expression": "points+=1"}}})
|
|
assert unpack_modules(doc["scripts"])["StartGameSetting"]["labels"]["Start"] == 1
|
|
mods.validate(doc, translated)
|
|
node["command"]["op"] = "NativePlugin"
|
|
with pytest.raises(ValueError, match="NativePlugin"):
|
|
mods.validate(doc, translated)
|
|
finally:
|
|
get_settings.cache_clear()
|
|
|
|
|
|
def test_video_recovery_checks_ranges_and_copies_only_declared_bytes(tmp_path):
|
|
from app.games.tomie.assets import recover_object
|
|
data = tmp_path / "data"
|
|
out = tmp_path / "out"
|
|
data.mkdir(); out.mkdir()
|
|
original = b"header-clip-trailer"
|
|
(data / "resources.resource").write_bytes(original)
|
|
tree = {"m_OriginalPath": "Assets/Video/clip.webm", "m_ExternalResources": {
|
|
"m_Source": "resources.resource", "m_Offset": 7, "m_Size": 4}}
|
|
obj = SimpleNamespace(type=SimpleNamespace(name="VideoClip"), read=lambda: SimpleNamespace(m_Name="Clip"),
|
|
read_typetree=lambda: tree)
|
|
recovered = recover_object(obj, data, out, "video")
|
|
assert recovered["path"].read_bytes() == b"clip"
|
|
assert (data / "resources.resource").read_bytes() == original
|
|
tree["m_ExternalResources"]["m_Size"] = len(original)
|
|
with pytest.raises(ValueError, match="диапазон"):
|
|
recover_object(obj, data, out, "invalid")
|