read_the_room / bundle.py
Ilia-Iliev's picture
Read the Room: full game (Gemma 4 31B via Modal, env-driven endpoint)
c64cea0 verified
Raw
History Blame Contribute Delete
1.97 kB
"""A scenario bundle: one shareable `.zip` carrying the spec plus its art.
An authored scenario has no asset folder on disk (its id is transient), so its art rides
along with it instead. A bundle is a zip of:
scenario.json <- the editable spec (exactly what's in the editor)
scene.<ext> <- the scene banner, if one was added
<character>.<ext> <- one avatar per character, named by the same slug as on disk
That mirrors `scenarios/assets/<id>/` one-for-one, so a downloaded bundle's art could even
be dropped straight into a permanent asset folder. `art` here is the in-session mapping the
creator UI holds: 'scene' and each character slug -> an image filepath (the upload).
"""
import tempfile
import zipfile
from pathlib import Path
from art import IMAGE_EXTS
SPEC_NAME = "scenario.json"
def pack(spec_text, art):
"""Zip the spec text plus any uploaded art into a downloadable bundle; return its path."""
out = Path(tempfile.mkdtemp()) / "scenario-bundle.zip"
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z:
z.writestr(SPEC_NAME, spec_text or "")
for key, path in (art or {}).items():
p = Path(path) if path else None
if p and p.is_file():
z.write(p, f"{key}{p.suffix.lower() or '.png'}")
return str(out)
def unpack(zip_path):
"""Read a bundle back into `(spec_text, art)`. Images are extracted to a temp dir and the
art mapping holds their filepaths, keyed by stem ('scene' or a character slug)."""
out = Path(tempfile.mkdtemp())
spec_text, art = "", {}
with zipfile.ZipFile(zip_path) as z:
for name in z.namelist():
if name == SPEC_NAME:
spec_text = z.read(name).decode("utf-8")
elif name.lower().endswith(IMAGE_EXTS):
dest = out / Path(name).name
dest.write_bytes(z.read(name))
art[dest.stem] = str(dest)
return spec_text, art