This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/port/FORMAT.md
Sylpheed RE agent b5a193839c port: make the Godot menu shell the agent's primary objective
Sets a new mission: boot the real disc through developer splash -> intro video
-> title -> main menu -> submenus in Godot 4, interactively, with no gameplay,
no 3D and no emulator.

docs/port/MISSION.md defines it -- eight gated milestones, each finished by an
ARTIFACT rather than by compiling, plus the Ready Room as an explicitly gated
stretch goal with a one-iteration probe that decides go/no-go. GP_READY_ROOM is
1106 entries with 6 recoverable names and is ISL-scripted, so it is either a
week or a quarter, and the agent must not start it on its own authority.

Architecture, per the user's decision: the Godot project is INDEPENDENT of the
Rust viewer and never reads a disc format. An offline Rust exporter converts the
disc into open formats; Godot reads only those. No GDExtension, no Rust in the
Godot project, and sylpheed-viewer is off limits -- it stays the human's
verification tool with its static-data rule intact.

docs/port/FORMAT.md specifies the open format, versioned, because modding is the
port's second goal and that makes the layout a deliverable rather than a temp
directory: JSON over XML (Godot parses JSON natively; its XMLParser is SAX),
names never hashes, provenance in every generated file, and unknowns listed
rather than guessed.

The discipline the whole thing rests on is the derived/authored split. `export/`
is regenerated wholesale and never hand-edited; `authored/` is hand-written and
survives a re-export. Three things this milestone needs are NOT on the disc in
any decoded form -- which button does what, paint order, and menu sound cues --
so they live in `authored/` with a stated `why`. Deleting an authored entry
because the exporter can now emit it IS the measure of progress.

`export/` is gitignored: it is generated from the user's own disc and this stays
a clean-room repo.

Container: adds a pinned Godot 4 (windowed under Xvfb for screenshots, plus a
headless wrapper). ffmpeg already carries libtheora, which is the video target --
Godot 4 plays only Ogg Theora natively and the disc's ADV.wmv is WMV3/WMA Pro.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 17:11:35 +02:00

7.1 KiB

The open export format — v1

The format the disc is converted into, and the one the Godot project and any modding tool read. Versioned: every file names its schema, and a breaking change bumps the number rather than editing this page quietly.

Design rules, in priority order:

  1. A human can read and edit it. That is the whole point — modding is the second goal of the port.
  2. Names, never hashes. If we never recovered the disc's name, say so in the file rather than passing off a synthetic id as real.
  3. Provenance travels with the data. Every generated file says where on the disc it came from and which exporter version wrote it, so it stays auditable against the original 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 and never silently defaulted.

Layout

export/                     # DERIVED. Regenerable. Gitignored. Never hand-edited.
  manifest.json             # what this export is: disc id, exporter version, contents
  screens/
    title/
      splash_developer.json
      title.json
      main_menu.json
      submenu_extras.json
    sprites/                # PNG, RGBA8, one per T8aD surface
  audio/
    music/BGM_001.ogg
    sfx/…
    cues.json               # cue name → sound id → bank, straight off the disc table
  video/
    ADV.ogv
  text/en/…

authored/                   # AUTHORED. Hand-written. Committed. Survives a re-export.
  flow.json                 # boot sequence + what each button does
  paint_order.json          # per-screen z-order overrides
  cue_bindings.json         # which cue fires on move / confirm / back

export/ is wiped and rewritten wholesale. authored/ is never touched by the exporter. Godot loads export/ first and then applies authored/ over it.

Common header

Every generated file starts with the same three keys:

{
  "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, which is the stable locator — not the display ordinal, which renumbers whenever the enumeration rule changes.

screens/*.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": "ptbase",
      "sprite": "sprites/ptbase.png",
      "role": "decoration",
      "pivot": [320, 180],
      "rest": { "pos": [320, 180], "scale": [1.0, 1.0], "tint": "#ffffffff" },
      "keyframes": []
    },
    {
      "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], "scale": [1.0, 1.0], "tint": "#ffffffff" },
        { "t": 34, "pos": [542, 157], "scale": [1.0, 1.0], "tint": "#ffffffff" },
        { "t": 64, "pos": [542, 162], "scale": [1.0, 1.0], "tint": "#ffffffff" }
      ]
    }
  ],

  "buttons": ["ptbtn01", "ptbtn02", "ptbtn03", "ptbtn04", "ptbtn05"],
  "unresolved": ["paint_order"]
}

role is derived from the element kind, which is a decoded field: 0x3002button, 0x10primitive, 0x0decoration. 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 the navigation order: the 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 (that was measured and refuted). The ordering is right for a vertical menu and should not be trusted for anything else.

keyframes carry the on-disc time value verbatim in t. A keyframe is the start of a ramp toward the next one, not a pose that is held. The unit of t is not yet confirmed — see MISSION.md §7 — so t stays raw and the consumer applies the conversion in one place.

rest is the resting pose: the longest run of consecutive keyframes with an unchanged value, falling back to longest-dwell for groups that ramp throughout. It is neither the first nor the last keyframe.

unresolved lists what this file does not answer. A consumer that needs one of these must get it from authored/.

authored/flow.json

The part the disc has not told us. Every entry states its evidence.

{
  "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 guess" },
        "ptbtn02": { "label": "LOAD GAME", "goto": "GP_LOAD",
                     "why": "label read off the sprite; GamePart id 3 from the .rdata table" }
      },
      "back": null,
      "why_back": "no B-handler observed; the main menu may be a root with no back"
    }
  }
}

goto may name another 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 not).

authored/paint_order.json

{
  "format": "sylpheed.paint_order/1",
  "screens": {
    "main_menu": {
      "order": ["ptbase", "pteff05", "ptframe1", "ptbtn01", "…"],
      "why": "hand-authored. The declaration table is NOT the paint order -- a
              per-draw capture of the running title paints element 13 first and
              elements 0/1 late. Deriving the order from the bundle is open."
    }
  }
}

When paint order is derived from the file, this file is deleted and screens/* gains an order array. That deletion is the measure of progress — until it happens, every screen here is a hand-tuned approximation wearing the right label.

export/audio/cues.json

Straight off the disc's own cue index; no interpretation.

{
  "format": "sylpheed.cues/1",
  "source": { "archive": "dat/tables.pak", "record": "SOUNDS" },
  "families": { "SE": [1, 901], "BGM": [1001, 1109], "VOICE": [1500, 7331] },
  "cues": { "SE_CURSOR_MOVE": 12 },
  "banks": { "12": "audio/sfx/se_012.ogg" }
}

Which cue a menu event fires is not on the disc as far as we know, so it is in authored/cue_bindings.json, not here.

export/manifest.json

{
  "format": "sylpheed.manifest/1",
  "exporter": "sylpheed-export 0.1.0",
  "disc": { "title": "Project Sylpheed: Arc of Deception", "region": "USA/Europe" },
  "video_transcode": "ffmpeg -i ADV.wmv -c:v libtheora -q:v 8 -c:a libvorbis -q:a 5 ADV.ogv",
  "screens": ["title/splash_developer", "title/title", "title/main_menu"],
  "warnings": ["GP_READY_ROOM not exported -- see MISSION.md §6"]
}

video_transcode records the exact command used, so a modder who dislikes the quality can re-run it rather than reverse-engineer what we did.