# The open export format — v1 The format the disc is converted *into*, and the one the Godot project and any modding tool read. **This is a starting point, and it is yours to revise** — but it is versioned, so a change is a deliberate act with a version bump, not a silent edit. Design rules, in priority order: 1. **A human can read and edit it.** Modding is a goal of this port, which makes the layout part of the product rather than a temp directory. 2. **Names, never hashes.** Where the disc's own name was never recovered — the six `*2D` archives and `GP_READY_ROOM` — emit a stable synthetic id **and say in the file that the real name is unknown**. A modder must be able to tell a recovered name from an invented one. 3. **Provenance travels with the data.** Source archive, entry index, exporter version. This is what keeps the export auditable against the disc instead of drifting into an unverifiable fork. 4. **Say what is unknown.** A field we could not decode is absent and listed in `unresolved` — never guessed, never silently defaulted. **JSON, not XML.** Godot parses JSON natively with `JSON.parse_string`; its `XMLParser` is a SAX-style API that would need a hand-written binding per schema. ## Layout ``` export/ # DERIVED. Regenerable. Gitignored. Never hand-edited. manifest.json screens/title/*.json sprites/*.png audio/music/*.ogg audio/sfx/*.ogg audio/cues.json video/*.ogv authored/ # AUTHORED. Hand-written. Committed. Survives re-export. flow.json # boot sequence + what each button does paint_order.json # per-screen z-order cue_bindings.json # which cue fires on move / confirm / back ``` Godot loads `export/` first, then applies `authored/` over it. ## Common header ```json { "format": "sylpheed.screen/1", "exporter": "sylpheed-export 0.1.0", "source": { "archive": "dat/GP_TITLE.pak", "entry": 5 } } ``` `source.entry` is the pak **entry index** — the stable locator. Not the display ordinal, which renumbers whenever the enumeration rule changes. ## `screens/*.json` ```json { "format": "sylpheed.screen/1", "exporter": "sylpheed-export 0.1.0", "source": { "archive": "dat/GP_TITLE.pak", "entry": 5 }, "name": "main_menu", "name_source": "authored", "design": [1280, 720], "elements": [ { "id": "ptbtn01", "sprite": "sprites/ptbtn01.png", "focus_sprite": "sprites/ptbtn01f.png", "role": "button", "pivot": [42, 22], "rest": { "pos": [542, 162], "scale": [1.0, 1.0], "tint": "#ffffffff" }, "keyframes": [ { "t": 28, "pos": [542, 142] }, { "t": 34, "pos": [542, 157] }, { "t": 64, "pos": [542, 162] } ] } ], "buttons": ["ptbtn01", "ptbtn02", "ptbtn03", "ptbtn04", "ptbtn05"], "unresolved": ["paint_order", "keyframe_time_unit"] } ``` **`role`** comes from the decoded element kind: `0x3002` → `button`, `0x10` → `primitive`, `0x0` → `decoration`. Anything else exports as `"unknown"` with the raw value in `kind_raw`. Do not invent a name for a kind nobody has decoded. **`buttons`** is navigation order: `button`-role elements sorted by resting Y. This is **geometric, not a decoded neighbour graph** — the disc's real navigation structure is unknown and `opt ` is *not* a focus link (measured and refuted). It is right for a vertical menu and should not be trusted for anything else. **`keyframes`** carry the on-disc time verbatim in `t`. A keyframe is the **start of a ramp toward the next**, not a pose that is held. The unit of `t` is HANDOFF Q1 and is unanswered — keep `t` raw so the conversion lives in exactly one place. **`rest`** is the resting pose: the longest run of consecutive keyframes with an unchanged value, falling back to longest-dwell. Neither the first nor the last. **`unresolved`** lists what this file does not answer; a consumer needing one of those must get it from `authored/`. ## `authored/flow.json` ```json { "format": "sylpheed.flow/1", "boot": ["splash_developer", "intro_video", "title", "main_menu"], "screens": { "main_menu": { "actions": { "ptbtn01": { "label": "NEW GAME", "goto": "new_game_intro", "why": "label read off the sprite; target is a placeholder for HANDOFF Q4" } } } } } ``` `goto` may name an exported screen or a **GamePart id** from the executable's own table (29 entries at `.rdata 0x820A1630` — that table is a disc fact; which button reaches which entry is Q4 and is not). ## `export/manifest.json` ```json { "format": "sylpheed.manifest/1", "exporter": "sylpheed-export 0.1.0", "formats_rev": "8b6dbcf", "video_transcode": "ffmpeg -i ADV.wmv -c:v libtheora -q:v 8 -c:a libvorbis -q:a 5 ADV.ogv", "warnings": ["GP_READY_ROOM not exported -- out of scope"] } ``` `formats_rev` pins which decoders produced this export, and `video_transcode` records the exact command so a modder can re-run it rather than reverse-engineer what was done.