diff --git a/.gitignore b/.gitignore index 72134dc9..d1c3ae91 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,14 @@ Thumbs.db # Trunk build output dist/ __pycache__/ + +# ── The port ──────────────────────────────────────────────────────────────── +# Generated from the user's own disc. This repo stays clean-room: code, schemas, +# authored mappings and documentation only -- never game content. +/data/base/ +# Transient inter-agent files. Deliberately outside history: they are working +# artefacts with provenance in their manifest, not results. +/exchange/ +!/exchange/.gitkeep +.godot/ +port/.godot/ diff --git a/Cargo.toml b/Cargo.toml index 87d06cd4..ea7604ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "crates/sylpheed-formats", "crates/sylpheed-viewer", "crates/sylpheed-cli", + "crates/sylpheed-export", ] resolver = "2" @@ -11,7 +12,7 @@ version = "0.1.0" edition = "2021" license = "MIT OR Apache-2.0" authors = ["Project Sylpheed Reborn Contributors"] -repository = "https://github.com/sylpheed-reborn/sylpheed-reborn" +repository = "https://git.mc02.dev/fabi/Sylpheed" [workspace.dependencies] # ── Format / parser crates ────────────────────────────────────────────────── diff --git a/README.md b/README.md index 9adf135a..79014b26 100644 --- a/README.md +++ b/README.md @@ -1,225 +1,66 @@ -# Project Sylpheed: Arc of Deception — Reborn +# Sylpheed -A clean-room, open-source reimplementation of **Project Sylpheed: Arc of Deception** (Xbox 360, 2006). +A clean-room reverse engineering and port project for **Project Sylpheed: Arc of +Deception** (Xbox 360, 2007). -Built with **Rust** and the **Bevy** game engine. Runs natively on Windows, macOS, and Linux, and in the browser via WebAssembly. +Three things live here, in one repository so that a change spanning them lands as +one commit: -> **Legal note:** This project contains no original game code or assets. You must own a legitimate copy of Project Sylpheed to use this engine. Assets remain the intellectual property of SETA Corporation / Square Enix. +| | | +|---|---| +| **The decoders** | `crates/sylpheed-formats` — the disc's formats, read and verified disc-wide | +| **The port** | `port/` — a Godot 4 project, plus `crates/sylpheed-export` which converts a disc into the open asset tree it reads | +| **The corpus** | `docs/re/` — what has been reverse engineered, with its evidence, its retractions and its dead ends | ---- +**You need your own copy of the game.** No game content is in this repository and +none ever will be. The exporter reads the disc you supply. -## Current Status: Milestone 1 — Asset Explorer +## The oracle is the real game -- [x] XISO disc image reading via `xdvdfs` -- [x] Xbox 360 texture de-tiling (Morton / Z-order) -- [x] XPR2 texture container parsing -- [x] Bevy custom `AssetLoader` for `.xpr` textures -- [x] Orbit camera viewer -- [x] CLI tools: extract, list, sniff, texture info, texture export -- [x] GitHub Actions CI (Windows + macOS + Linux + WASM) -- [x] WASM / web build target -- [ ] Mesh format (reverse engineering in progress) -- [ ] Audio format (XMA → PCM pipeline) -- [ ] Mission data format +> `sylpheed-cli` and the Explorer are **tools for verifying our decoding**. They +> are hypotheses under test and they have been wrong. When something must be +> checked against the truth, the truth is **the game running in Xenia Canary**, +> captured — not any renderer of ours. ---- +This is stated first because getting it backwards is the most expensive mistake +this project has made. -## Repository Structure +## Layout ``` -sylpheed-reborn/ -└── sylpheed-viewer/ ← Milestone 1: asset explorer (Rust/Bevy workspace) - ├── Cargo.toml ← Workspace root - ├── crates/ - │ ├── sylpheed-formats/ # Format parsers — no Bevy dependency - │ │ ├── xiso.rs # XISO / XDVDFS disc image reader - │ │ ├── texture.rs # XPR2 container + DXT de-tiling (Morton) - │ │ ├── vfs.rs # Virtual filesystem + magic-byte sniffer - │ │ ├── mesh.rs # Mesh parser (stub — RE in progress) - │ │ └── audio.rs # Audio parser (stub — XMA TODO) - │ │ - │ ├── sylpheed-viewer/ # Bevy application - │ │ ├── lib.rs # App setup + WASM entry point - │ │ ├── main.rs # Native binary entry point - │ │ ├── asset_loader.rs # Custom Bevy AssetLoaders - │ │ ├── camera.rs # Orbit camera (LMB orbit, RMB pan, scroll zoom) - │ │ └── ui.rs # egui file browser + RE notes panel - │ │ - │ └── sylpheed-cli/ # Command-line tools - │ └── main.rs # extract / list / sniff / texture info commands - │ - ├── assets/ ← Extracted game files (gitignored) - ├── .github/workflows/ - │ └── ci.yml # CI: Windows + macOS + Linux + WASM - ├── Trunk.toml # WASM build configuration - └── justfile # All build recipes +crates/ + sylpheed-formats/ the decoders. Disc-wide verified; the corpus is its spec + sylpheed-cli/ headless tools -- render a screen, dump a table, probe audio + sylpheed-viewer/ the Explorer: a human's window onto the disc. STATIC data only + sylpheed-export/ disc -> the open, moddable asset tree +port/ the Godot 4 project. Reads open formats ONLY +authored/ decisions that are NOT on the disc, each with its reason +data/ + base/ generated by the exporter. Gitignored, never hand-edited + mods/ drop-in overrides. Yours +docs/ + re/ the corpus: findings, refutations, method traps + game/ how the game is navigated -- menus, modals, flight + port/ the port's mission, its handoff contract, modding rules + agents/ how the agent team works together +tools/ capture harnesses, probes, the share tool +exchange/ transient inter-agent files. NOT in git +docker/ the agent containers ``` ---- +## Where to start -## Getting Started +* [`docs/re/INDEX.md`](docs/re/INDEX.md) — what is decoded +* [`docs/re/REFUTED.md`](docs/re/REFUTED.md) — what has been tested and died +* [`docs/re/METHOD.md`](docs/re/METHOD.md) — traps this project has already paid for +* [`docs/game/navigation.md`](docs/game/navigation.md) — how the game is navigated +* [`docs/port/MODDING.md`](docs/port/MODDING.md) — why the asset tree looks the way it does -### Prerequisites +Xenia Canary is a **separate** repository: it is a fork tracking upstream, and it +carries our instrumentation. -```bash -# Install Rust -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +## Conventions -# Install just (task runner) -cargo install just - -# For web builds only -cargo install trunk -rustup target add wasm32-unknown-unknown -``` - -#### Linux — Bevy system dependencies - -```bash -sudo apt-get install -y \ - libasound2-dev libudev-dev libwayland-dev \ - libxkbcommon-dev libx11-dev libxi-dev pkg-config -``` - -### Step 1 — Extract your disc - -```bash -cd sylpheed-viewer - -# Extract using the CLI tool -just extract /path/to/project_sylpheed.iso - -# Or manually with xdvdfs -cargo install xdvdfs-cli -xdvdfs unpack project_sylpheed.iso ./assets/ -``` - -### Step 2 — Run the viewer - -```bash -cd sylpheed-viewer - -just run # Native viewer -just web # WASM dev server → http://localhost:8080 -``` - -The asset viewer opens an orbit camera scene. Use the left panel to browse extracted game files. Click a `.xpr` file to preview it as a texture. - ---- - -## Build Commands - -All commands run from the `sylpheed-viewer/` directory. - -```bash -just run # Run the native viewer (debug) -just run-dev # Run with hot-reloading -just build-native # Build native release binary -just web # WASM dev server at localhost:8080 -just build-web # WASM release build → ./dist/ - -just extract game.iso # Extract an ISO to ./assets/ -just sniff # Identify file formats in ./assets/ -just sniff-unknown # Show only unrecognised formats (RE focus) - -just test # Run all tests -just ci # Full CI: fmt + lint + test + WASM check -``` - ---- - -## Technology Stack - -| Layer | Choice | Reason | -|-------|--------|--------| -| Language | **Rust** | Memory safety, performance, cross-platform | -| Game engine | **Bevy 0.15** | ECS-first, WASM-native, data-driven | -| XISO reading | **xdvdfs** | Pure Rust, reads Xbox 360 disc images | -| Binary parsing | **binrw** | Derive-macro based, ideal for RE work | -| Web bundler | **Trunk** | Bevy's standard WASM build tool | -| CLI | **clap** | Asset extraction and inspection tools | -| Debug UI | **bevy_egui** | In-viewer asset browser and RE notes panel | - ---- - -## Key Architectural Decisions - -**`sylpheed-formats` has zero Bevy dependency.** All binary format parsers live here and are testable with plain `cargo test`. Bevy integration is a thin layer on top in `sylpheed-viewer`. - -**WASM is a first-class target.** XISO reading is gated behind `#[cfg(not(target_arch = "wasm32"))]` since the browser can't read local files. On the web, assets must be pre-extracted and served over HTTP. - -**Modding is designed in from the start.** The virtual filesystem (`vfs.rs`) is the single choke point for all asset reads — a mod loader only needs to intercept that one layer to override any file. - ---- - -## Reverse Engineering Notes - -### Known file formats - -| Path pattern | Format | Status | Notes | -|---|---|---|---| -| `*.XPR`, `*.XPR2` | XPR2 texture | ✅ Parsing | DXT1/3/5, DXN, Morton de-tiling done | -| `DEFAULT.XEX` | Xbox EXE 2 | 🔬 Study | Main executable — load in Ghidra (PPC BE) | -| `*.XWB`, `*.XSB` | XACT audio | ⏳ TODO | Wave/sound banks; audio is XMA codec | -| `*.pak`, `*.p00` | SETA archive | ⏳ Unknown | Paired header/data format | -| mesh files | Unknown | ⏳ TODO | Run `just sniff-unknown` to find candidates | - -### RE workflow - -```bash -# 1. Extract and map the disc -just extract game.iso -just sniff-unknown # shows hex magic bytes of unknown files - -# 2. Hex-inspect candidates -# Groups of 12 bytes at offsets: likely f32 XYZ vertices -# Groups of 6 bytes: likely u16 triangle indices - -# 3. Cross-reference in Ghidra -# Load DEFAULT.XEX with PowerPC Big-Endian processor -# Search string refs to file extensions → find load functions - -# 4. Implement parser -# Add binrw #[derive(BinRead)] struct in sylpheed-formats/src/mesh.rs -# cargo test -p sylpheed-formats -``` - -### Next steps (Milestone 2) - -1. **Mesh format** — fingerprint with `sniff-unknown`, implement `mesh.rs` -2. **Audio** — parse XWB headers, batch-convert XMA to WAV via `ffmpeg` -3. **Mission data** — format unknown; starts after mesh/texture loading works -4. **Flight model** — document by playing the original; implement as Bevy system - ---- - -## Constraints - -1. **Never copy decompiled or disassembled game code** — reimplement behavior through observation only. -2. **Assets stay gitignored** — `assets/` and `*.iso` are excluded. Never commit game files. -3. **Keep `sylpheed-formats` Bevy-free** — parsers must be testable without a GPU. -4. **WASM must always compile** — CI enforces `cargo check --target wasm32-unknown-unknown`. -5. **`justfile` is the source of truth** for build commands — add new recipes there. - ---- - -## Milestone Roadmap - -| Milestone | Goal | Status | -|---|---|---| -| 1 | Asset Explorer | 🚧 In Progress | -| 2 | Flying Tech Demo | ⏳ Planned | -| 3 | Combat Prototype | ⏳ Planned | -| 4 | Mission 1 Playable | ⏳ Planned | -| 5 | Full Game (all 16 missions) | ⏳ Planned | -| 6 | Mod SDK | ⏳ Planned | - ---- - -## Useful References - -- [xdvdfs](https://github.com/antangelo/xdvdfs) — XISO disc image reader (used in this project) -- [Xenia emulator](https://github.com/xenia-canary/xenia-canary) — GPU/API reference (cloned at `../../xenia-canary/`) -- [xboxdevwiki](https://xboxdevwiki.net) — Xbox 360 hardware documentation -- [binrw docs](https://binrw.rs) — binary format parsing framework -- [Free60 Project](https://free60project.github.io/wiki/) — open Xbox 360 hardware docs +Confidence is per claim, never per document: ✅ `CONFIRMED` · 🟡 `PROBABLE` · +❔ `HYPOTHESIS` · ❌ `REFUTED`. A withdrawn result is kept with its reasoning +rather than deleted — that is why the numbers here can be trusted. diff --git a/godot-import/authored/README.md b/authored/README.md similarity index 100% rename from godot-import/authored/README.md rename to authored/README.md diff --git a/godot-import/authored/flow.json b/authored/flow.json similarity index 100% rename from godot-import/authored/flow.json rename to authored/flow.json diff --git a/godot-import/authored/screen_names.json b/authored/screen_names.json similarity index 100% rename from godot-import/authored/screen_names.json rename to authored/screen_names.json diff --git a/godot-import/authored/timing.json b/authored/timing.json similarity index 100% rename from godot-import/authored/timing.json rename to authored/timing.json diff --git a/godot-import/crates/sylpheed-export/Cargo.toml b/crates/sylpheed-export/Cargo.toml similarity index 87% rename from godot-import/crates/sylpheed-export/Cargo.toml rename to crates/sylpheed-export/Cargo.toml index 6b08a7a8..a8da7e34 100644 --- a/godot-import/crates/sylpheed-export/Cargo.toml +++ b/crates/sylpheed-export/Cargo.toml @@ -49,7 +49,13 @@ license.workspace = true # which a capture of the running game plainly shows. 5414db3 is the revision at # which that fix carries its disc-wide check (30 of 13 991 elements move, 4 # become visible, 0 become invisible), not merely the one where it was written. -sylpheed-formats = { git = "https://git.mc02.dev/fabi/Syplheed-Reborn.git", tag = "formats-pin-2026-08-29" } +# A PATH dependency now that the decoders and the exporter live in one +# repository. This deletes a whole class of failure that the two-repo split +# created: no pinned revision to go stale, no tag to keep alive, no commit that +# a squash-merge can orphan, and no way for the exporter to be built against a +# decoder it was never tested with. A decoder change and the exporter change it +# requires now land in the same commit or not at all. +sylpheed-formats = { path = "../sylpheed-formats" } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/godot-import/crates/sylpheed-export/src/check.rs b/crates/sylpheed-export/src/check.rs similarity index 100% rename from godot-import/crates/sylpheed-export/src/check.rs rename to crates/sylpheed-export/src/check.rs diff --git a/godot-import/crates/sylpheed-export/src/main.rs b/crates/sylpheed-export/src/main.rs similarity index 100% rename from godot-import/crates/sylpheed-export/src/main.rs rename to crates/sylpheed-export/src/main.rs diff --git a/godot-import/crates/sylpheed-export/src/screen.rs b/crates/sylpheed-export/src/screen.rs similarity index 100% rename from godot-import/crates/sylpheed-export/src/screen.rs rename to crates/sylpheed-export/src/screen.rs diff --git a/godot-import/crates/sylpheed-export/src/video.rs b/crates/sylpheed-export/src/video.rs similarity index 100% rename from godot-import/crates/sylpheed-export/src/video.rs rename to crates/sylpheed-export/src/video.rs diff --git a/data/mods/.gitkeep b/data/mods/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/godot-import/docker/Dockerfile b/docker/port/Dockerfile similarity index 100% rename from godot-import/docker/Dockerfile rename to docker/port/Dockerfile diff --git a/godot-import/docker/bin/build-export b/docker/port/bin/build-export similarity index 100% rename from godot-import/docker/bin/build-export rename to docker/port/bin/build-export diff --git a/godot-import/docker/bin/build-reference-cli b/docker/port/bin/build-reference-cli similarity index 100% rename from godot-import/docker/bin/build-reference-cli rename to docker/port/bin/build-reference-cli diff --git a/godot-import/docker/bin/claude-autonomous b/docker/port/bin/claude-autonomous similarity index 100% rename from godot-import/docker/bin/claude-autonomous rename to docker/port/bin/claude-autonomous diff --git a/godot-import/docker/bin/push-work b/docker/port/bin/push-work similarity index 100% rename from godot-import/docker/bin/push-work rename to docker/port/bin/push-work diff --git a/godot-import/docker/bin/screenshot b/docker/port/bin/screenshot similarity index 100% rename from godot-import/docker/bin/screenshot rename to docker/port/bin/screenshot diff --git a/godot-import/docker/bin/seed-claude-config.py b/docker/port/bin/seed-claude-config.py similarity index 100% rename from godot-import/docker/bin/seed-claude-config.py rename to docker/port/bin/seed-claude-config.py diff --git a/godot-import/docker/entrypoint.sh b/docker/port/entrypoint.sh similarity index 100% rename from godot-import/docker/entrypoint.sh rename to docker/port/entrypoint.sh diff --git a/godot-import/docker/sylph-port b/docker/port/sylph-port similarity index 100% rename from godot-import/docker/sylph-port rename to docker/port/sylph-port diff --git a/docs/game/navigation.md b/docs/game/navigation.md new file mode 100644 index 00000000..88b19206 --- /dev/null +++ b/docs/game/navigation.md @@ -0,0 +1,137 @@ +# How the game is navigated + +**Purpose:** so nobody has to rediscover this. Every screen, what reaches it, +what input it takes, and every modal that can interrupt. + +**Status:** skeleton, filled from what the corpus already knows. Most rows are +❔ and are *meant* to be — this page exists to be completed from the oracle, not +to look finished. + +> ## ⚠️ The oracle is Xenia Canary running the real game +> +> Not `sylpheed-cli`, not the viewer, not any renderer of ours. Reborn is a tool +> for *verifying our decoding*; it is a hypothesis under test and it has been +> wrong. **Every row below is only as good as the capture behind it**, and a row +> with no capture is a guess wearing a table cell. +> +> This is written here because the mistake has already been made once, by the +> author of this file, and it propagated into three documents and both agents +> before a human caught it. + +Confidence: ✅ measured from a capture · 🟡 inferred from disc data · ❔ unknown. + +## 1. The screen vocabulary ✅ + +The executable carries its own list — a `.rdata` pointer array at `0x820A1630`, +29 entries, index = GamePart id, confirmed against the factory-registration +strings in the image: + +``` + 0 GP_TITLE 10 GP_BUNK 20 GP_STAGE_CLEAR + 1 GP_ADVERTISE_DEMO 11 GP_READY_ROOM 21 GP_MISSION_LOG + 2 GP_SELECT_STORAGE 12 GP_HANGAR 22 GP_GAMEOVER + 3 GP_LOAD 13 GP_ARSENAL 23 GP_DEBRIEFING + 4 GP_SAVE 14 GP_PILOT_LOG 24 GP_DIALOG + 5 GP_EXTRAS 15 GP_SYSTEM 25 GP_TUTORIAL + 6 GP_MOVIE_THEATER 16 GP_DEMO *26 GP_CHALLENGE + 7 GP_MISSION_SELECT 17 GP_MAIN_GAME 27 GP_LEADERBOARD + 8 GP_OPTIONS 18 GP_SELECTOR 28 GP_TEST + 9 GP_MOVIE 19 GP_PAUSE_MENU +``` + +This is the *vocabulary*, not the graph. **Which button reaches which id is +not decoded** — that is the open question, and filling in §3 is what closes it. + +`GP_TEST` is named here and is not on the disc. + +## 2. Boot 🟡 + +``` +developer logo splash → intro video → title / PRESS Ⓐ → main menu +``` + +* The splash is a **screen, not a video** ✅ — `logo1`–`logo4` are bound by the + movie manifest and have no `.wmv` on the disc. +* The intro is `dat/movie/ADV.wmv` ✅. +* ❔ What *drives* the order. It is observable; the data or code behind it is not + decoded. +* ⚠️ **Two title states exist and look identical.** The title that ends the boot + accepts a single Ⓐ; the title the attract loop returns to accepts nothing — + Ⓐ, START, B, BACK, X, Y all ignored across dozens of delivered presses. They + draw **13 identical quads**, so they differ only to the guest. Capture the + first title after boot; do not tap during the boot. + +## 3. Screens + +One section per screen. Fill from a capture, and cite it. + +### GP_TITLE — main menu 🟡 + +Five buttons, `ptbtn01`–`ptbtn05`, at x=542, y=162/242/322/401/482. + +| button | label | reaches | confidence | +|---|---|---|---| +| `ptbtn01` | ❔ | ❔ | | +| `ptbtn02` | ❔ | ❔ | | +| `ptbtn03` | ❔ | ❔ | | +| `ptbtn04` | ❔ | ❔ | | +| `ptbtn05` | ❔ | ❔ | | + +Labels are baked into the sprites — a human reads them in a minute, but **no +decoded field says which GamePart a button opens.** If the labels are read by +eye, say so; that is an authored mapping, not a disc fact. + +❔ Initial focus · ❔ wrap-around at the ends · ❔ whether left/right does +anything · ❔ what B does. + +### GP_LOAD ❔ · GP_SAVE ❔ · GP_EXTRAS ❔ · GP_OPTIONS ❔ · GP_MISSION_SELECT ❔ + +❔ Not documented. Known fragment: the path +`title → LOAD GAME → slot 01 → YES → READY ROOM → TAKE OFF` reaches flight, and +**stage select would not move** — 16 d-pad presses never left Stage 01. + +### GP_READY_ROOM ❔ + +The largest UI archive on the disc; only 6 of 1 106 names resolve, and it is +ISL-scripted. A probe found the pak holds briefing/tactical-map content rather +than the Ready Room menu itself. Out of scope for the menu milestone. + +## 4. Modals, prompts and alerts + +The interrupting layer, and the one most likely to break a scripted run. + +| modal | seen at | default focus | notes | +|---|---|---|---| +| `Save game?` | after a mission | **YES** ✅ | cursor starts on YES | +| `Do you want to develop this weapon?` | Arsenal | ❔ | | +| ❔ overwrite / delete confirmations | Save/Load | ❔ | | +| ❔ error and "not enough points" alerts | Arsenal | ❔ | | + +⚠️ **A 60 ms d-pad tap is ignored inside a dialog.** It moves the cursor on a +menu and does nothing in a modal, so a script that works on menus silently picks +the default in every prompt. Confirm which side the cursor is on before Ⓐ. + +## 5. In-game flight ❔ + +Not started. **The in-game tutorials are the resource** — they teach the control +scheme in the game's own words, which is exactly the documentation we want, and +they are `S18`–`S23` on disc. + +To fill in: the control map (both stick modes if there are two), throttle, +target select — Ⓐ pressed **twice**, not once, which a sweep that only ever +tapped once "proved" did not exist — weapon cycling, the pause menu, and the +HUD's readouts. + +## 6. Input traps, all measured ✅ + +Real behaviours that read as bugs: + +* **F10 opens the emulator menu bar**, and any Xenia UI makes + `XamInputGetKeystrokeEx` return SUCCESS with an empty keystroke *before* any + driver is asked. The pad looks dead and is not. +* **A signed-in profile is required** — hence `--create_profile_if_none`. +* **A FIFO trace consumer that exits stalls the emulator**, which also reads as + a dead pad. +* **The title is not input-ready for ~10 s** after it appears. +* **Ⓐ on the first title succeeds about half the time**, and nothing measurable + predicts which. Five explanations have been eliminated. Budget retries. diff --git a/godot-import/docs/AUDIO-VERIFICATION.md b/docs/port/AUDIO-VERIFICATION.md similarity index 100% rename from godot-import/docs/AUDIO-VERIFICATION.md rename to docs/port/AUDIO-VERIFICATION.md diff --git a/godot-import/docs/BLOCKED.md b/docs/port/BLOCKED.md similarity index 100% rename from godot-import/docs/BLOCKED.md rename to docs/port/BLOCKED.md diff --git a/godot-import/docs/DECISIONS.md b/docs/port/DECISIONS.md similarity index 100% rename from godot-import/docs/DECISIONS.md rename to docs/port/DECISIONS.md diff --git a/godot-import/docs/FORMAT.md b/docs/port/FORMAT.md similarity index 100% rename from godot-import/docs/FORMAT.md rename to docs/port/FORMAT.md diff --git a/docs/port/MODDING.md b/docs/port/MODDING.md new file mode 100644 index 00000000..5f438ec3 --- /dev/null +++ b/docs/port/MODDING.md @@ -0,0 +1,86 @@ +# Modding is a requirement, not a later feature + +The port has two goals, and the second one constrains the first: **the exported +asset tree is a product**, not a build artefact. Someone who has never read this +repository should be able to open the tree, understand what they are looking at, +change something, and see it in the game. + +That is a design constraint on the **exporter**, today — not a milestone to add +later. Custom *behaviour* (scripting) is a later milestone, but nothing built now +may make it harder. + +## The five rules + +**1. One logical asset, one file. Never split.** + +A sprite is one PNG. A track is one OGG. A screen is one JSON. This is a real +constraint and not a platitude, because **the disc does the opposite everywhere**: +a `.pak` entry spans segment files, a bank holds several sub-waves, and a +cutscene voice is a byte region of a continuous stream chunked across entries +whose boundaries do not match the cues. All of that is the *exporter's* problem +to resolve. If a modder has to reassemble anything, the export is unfinished. + +**2. Names a person recognises.** + +`screens/title/main_menu.json`, not `0x90822a39.json`. Where the disc's own name +was never recovered — the six `*2D` archives, `GP_READY_ROOM` — emit a stable +synthetic name **and say in the file that the real one is unknown**, so a modder +can tell a recovered name from an invented one. + +**3. Modern, editable formats only.** + +| kind | format | why | +|---|---|---| +| data, layout, config | **JSON** | Godot parses it natively (`JSON.parse_string`); every tool speaks it | +| images | **PNG** | RGBA8, lossless, opens anywhere | +| audio | **Ogg Vorbis** | Godot-native, no licence trap | +| video | **Ogg Theora** | the only format Godot 4 plays natively | +| text | **UTF-8** | never UTF-16BE, whatever the disc did | + +Not XML: Godot's `XMLParser` is a SAX-style API needing a hand-written binding +per schema, where JSON is one call. Not a custom binary container, ever — that +would rebuild the exact wall this port exists to remove. + +**4. Base and overrides, never one merged pile.** + +``` +data/ + base/ generated from your disc by the exporter. Gitignored. Rewritten + wholesale -- never hand-edit it, your changes will vanish. + mods/ drop-in overrides. Yours. The exporter never touches this. +``` + +A mod replaces a file by shadowing its path. A modder edits nothing under +`base/`, so re-exporting is always safe, and "did I break it?" is answered by +disabling a mod rather than by re-extracting the disc. + +**5. Provenance in every generated file.** + +Source archive, entry index, exporter version. It is what lets someone check a +file against the disc instead of trusting it — and what stops the export drifting +into an unverifiable fork of the original. + +## Not blocking scripted mods later + +Behaviour modding is a later milestone. Two decisions now keep the door open: + +* **The loader is data-driven and screen-agnostic.** No screen name is hardcoded + in GDScript. A screen is *whatever the JSON describes*, so a mod that adds a + new screen needs no engine change. +* **No GDExtension, no native code in `port/`.** [Godot Mod Loader][gml] — the + established option, Godot 4.1–4.3, used by Brotato and Dome Keeper — works by + substituting **GDScript** at load time. A port whose logic lives in GDScript + stays moddable by it; one that hides logic in native code does not. + +Adopting a mod loader is a decision for that milestone. Making it *possible* is a +constraint on this one. + +[gml]: https://github.com/GodotModding/godot-mod-loader + +## What this rules out, explicitly + +* Atlases or packed archives of sprites — one file per sprite. +* Any format needing our code to read it. +* Hash-named files. +* Hand-edited files under `data/base/`. +* Splitting one playable thing across files to mirror how the disc stored it. diff --git a/godot-import/docs/MISSION.md b/docs/port/PORT-MISSION.md similarity index 100% rename from godot-import/docs/MISSION.md rename to docs/port/PORT-MISSION.md diff --git a/godot-import/docs/loop-task.md b/docs/port/port-loop-task.md similarity index 100% rename from godot-import/docs/loop-task.md rename to docs/port/port-loop-task.md diff --git a/godot-import/.gitignore b/godot-import/.gitignore deleted file mode 100644 index 11bc8073..00000000 --- a/godot-import/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Generated from the user's own disc. This repo stays clean-room: code, -# schemas, authored mappings and docs only -- never game assets. -/export/ - -# Rust -/target/ -**/*.rs.bk - -# Godot -.godot/ -/port/.godot/ -*.import diff --git a/godot-import/Cargo.lock b/godot-import/Cargo.lock deleted file mode 100644 index 214307f6..00000000 --- a/godot-import/Cargo.lock +++ /dev/null @@ -1,1217 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aho-corasick" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" -dependencies = [ - "memchr", -] - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys", -] - -[[package]] -name = "anyhow" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" - -[[package]] -name = "arbitrary-int" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "825297538d77367557b912770ca3083f778a196054b3ee63b22673c4a3cae0a5" - -[[package]] -name = "array-init" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" - -[[package]] -name = "arrayvec" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" - -[[package]] -name = "async-trait" -version = "0.1.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.4", -] - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "binrw" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4b52fe7fbd9e207b879c52c5af7efd861c2f4e234ab4f744b0bdc04a863623" -dependencies = [ - "array-init", - "binrw_derive", - "bytemuck", -] - -[[package]] -name = "binrw_derive" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7196f6935a944081728167ce6e3a599bd38d6d80d21afe4ca17be2097a6682f" -dependencies = [ - "either", - "owo-colors", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "bitbybit" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec187a89ab07e209270175faf9e07ceb2755d984954e58a2296e325ddece2762" -dependencies = [ - "arbitrary-int", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "bitflags" -version = "2.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bytemuck" -version = "1.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" - -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - -[[package]] -name = "bytes" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "ciso" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42222171e20c6a5e2c83cc5295f4a55c27c9397acff30dbae4f3baeffae47f51" -dependencies = [ - "arbitrary-int", - "async-trait", - "bitbybit", - "lz4_flex", - "maybe-async", -] - -[[package]] -name = "clap" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 3.0.4", -] - -[[package]] -name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "const_format" -version = "0.2.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" -dependencies = [ - "const_format_proc_macros", - "konst", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "either" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "flate2" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" -dependencies = [ - "crc32fast", - "miniz_oxide 0.9.1", - "zlib-rs", -] - -[[package]] -name = "futures" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" - -[[package]] -name = "futures-executor" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" - -[[package]] -name = "futures-macro" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.4", -] - -[[package]] -name = "futures-sink" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" - -[[package]] -name = "futures-task" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" - -[[package]] -name = "futures-util" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "image" -version = "0.25.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" -dependencies = [ - "bytemuck", - "byteorder-lite", - "moxcms", - "num-traits", - "png", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "keccak" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "konst" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" -dependencies = [ - "konst_macro_rules", -] - -[[package]] -name = "konst_macro_rules" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" - -[[package]] -name = "libc" -version = "0.2.189" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" - -[[package]] -name = "lz4_flex" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" -dependencies = [ - "twox-hash", -] - -[[package]] -name = "maybe-async" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "miniz_oxide" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" -dependencies = [ - "libc", - "wasi", - "windows-sys", -] - -[[package]] -name = "moxcms" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" -dependencies = [ - "num-traits", - "pxfm", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "png" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide 0.8.9", -] - -[[package]] -name = "pori" -version = "0.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a63d338dec139f56dacc692ca63ad35a6be6a797442479b55acd611d79e906" -dependencies = [ - "nom", -] - -[[package]] -name = "proc-bitfield" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "095c6eb206c97ddef87ce3d7e3e492b017093d80bce62317afdf0665df514ade" -dependencies = [ - "proc-bitfield-macros", - "static_assertions", -] - -[[package]] -name = "proc-bitfield-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89409e6b315ead7f4c4d9a79e27dc1e11272f930cbb1fb3d31f2fc64671deb77" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "proc-macro2" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "pxfm" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" - -[[package]] -name = "quote" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rayon" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde-big-array" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.4", -] - -[[package]] -name = "serde_json" -version = "1.0.151" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "sha3" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" -dependencies = [ - "digest", - "keccak", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" - -[[package]] -name = "socket2" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "sylpheed-export" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap", - "image", - "serde", - "serde_json", - "sylpheed-formats", -] - -[[package]] -name = "sylpheed-formats" -version = "0.1.0" -source = "git+https://git.mc02.dev/fabi/Syplheed-Reborn.git?tag=formats-pin-2026-08-29#7eeae3006a7bb92ae31ba8d2b402891f3a31f070" -dependencies = [ - "anyhow", - "binrw", - "flate2", - "futures", - "rayon", - "serde", - "serde_json", - "thiserror 2.0.20", - "tokio", - "tracing", - "ttf-parser", - "xdvdfs", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" -dependencies = [ - "thiserror-impl 2.0.20", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.4", -] - -[[package]] -name = "tokio" -version = "1.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys", -] - -[[package]] -name = "tokio-macros" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.4", -] - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "ttf-parser" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" - -[[package]] -name = "twox-hash" -version = "2.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5283634e518fe9e82c7b20520bb4bc209009fd16c82077c802f8111ecbb0117a" - -[[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wax" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d12a78aa0bab22d2f26ed1a96df7ab58e8a93506a3e20adb47c51a93b4e1357" -dependencies = [ - "const_format", - "itertools", - "nom", - "pori", - "regex", - "thiserror 1.0.69", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "xdvdfs" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1777c1ccae32a2185ac98adae015aed3fb06242084b5e7f8b08a811fe0e7b936" -dependencies = [ - "arrayvec", - "async-trait", - "bincode", - "ciso", - "encoding_rs", - "log", - "maybe-async", - "proc-bitfield", - "serde", - "serde-big-array", - "sha3", - "wax", -] - -[[package]] -name = "zlib-rs" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" - -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/godot-import/Cargo.toml b/godot-import/Cargo.toml deleted file mode 100644 index 3d3d1ec1..00000000 --- a/godot-import/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[workspace] -resolver = "2" -members = ["crates/sylpheed-export"] - -[workspace.package] -version = "0.1.0" -edition = "2021" -license = "MIT" diff --git a/godot-import/README.md b/godot-import/README.md deleted file mode 100644 index 9f847044..00000000 --- a/godot-import/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# Sylpheed Godot - -A clean-room Godot 4 port of *Project Sylpheed: Arc of Deception*, starting with -the menu shell: developer splash → intro video → title → main menu → submenus. - -**You need your own copy of the game.** Nothing in this repository is game -content. An offline exporter reads the disc you supply and writes an open, -moddable asset tree; the Godot project reads only that tree and never touches a -disc format. - -``` - your disc ──▶ crates/sylpheed-export ──▶ export/ ──▶ port/ (Godot 4) - (Rust; decoders come from JSON + PNG reads ONLY - sylpheed-formats) + OGG + OGV open formats -``` - -## Why the wall - -Two reasons, and the second is the interesting one: - -1. Godot cannot read IPFB archives, RATC bundles, T8aD textures, XMA banks or - WMV video, and it should not learn to. -2. **Modding is a goal of this port.** If the runtime reads the original formats, - modding means reverse engineering. If it reads JSON and PNG, modding means - opening a file. - -## Where the knowledge comes from - -The decoders live in [`sylpheed-formats`][formats], pinned by revision — a -separate project, where the reverse engineering happens. Its -`docs/port/HANDOFF.md` is the contract: what has been decoded, what was measured -off the running game, and what is known to be undecodable. Read it before -assuming a value is on the disc. - -[formats]: https://git.mc02.dev/fabi/Syplheed-Reborn - -## Layout - -| | | -|---|---| -| `crates/sylpheed-export/` | disc → open formats. Regenerates `export/` wholesale | -| `port/` | the Godot 4 project | -| `authored/` | decisions that are **not** on the disc, each with its reason | -| `export/` | generated, gitignored, never hand-edited | -| `tools/` | verification harnesses that hold the port to the reference renderer | -| `docs/` | the mission, the format spec, the agent's loop prompt | - -## Verifying - -**The oracle is the Xenia Canary capture and the game**, not either renderer. -`sylpheed-cli screen render` is an explorer and extraction CLI for verifying -decodes, and it can be wrong -- three times both it and the port agreed and both -were wrong, each caught only by a capture. - -So `tools/verify-screen` is a **consistency check and a regression detector**, -not a grade. It draws every exported screen both ways -- built from the same -`sylpheed-formats` revision the exporter is pinned to -- and reports the largest -per-channel difference in the frame: - -``` -tools/verify-screen # every screen in the manifest -tools/verify-screen main_menu # one of them -``` - -A difference means the two moved apart; `docs/DECISIONS.md` says which one moved -and why, rather than tuning the port until the number goes down. Correctness is -checked against the captures indexed at `docs/re/captures/ORACLE-CAPTURES.md` -- -mind that they are not gamma-neutral, so RMSE against them has a floor. - -## Status - -**P1.** The exporter writes `GP_TITLE`'s twelve screen builds and their sprites, -and the Godot project draws any of them statically at 1280x720 from that tree -alone. `main_menu` matches the reference renderer to within 3/255 on every -channel of every pixel. Next: P2, keyframe animation. diff --git a/godot-import/port/project.godot b/port/project.godot similarity index 100% rename from godot-import/port/project.godot rename to port/project.godot diff --git a/godot-import/port/scenes/boot.tscn b/port/scenes/boot.tscn similarity index 100% rename from godot-import/port/scenes/boot.tscn rename to port/scenes/boot.tscn diff --git a/godot-import/port/scripts/boot.gd b/port/scripts/boot.gd similarity index 100% rename from godot-import/port/scripts/boot.gd rename to port/scripts/boot.gd diff --git a/godot-import/port/scripts/boot.gd.uid b/port/scripts/boot.gd.uid similarity index 100% rename from godot-import/port/scripts/boot.gd.uid rename to port/scripts/boot.gd.uid diff --git a/godot-import/port/scripts/export_tree.gd b/port/scripts/export_tree.gd similarity index 100% rename from godot-import/port/scripts/export_tree.gd rename to port/scripts/export_tree.gd diff --git a/godot-import/port/scripts/export_tree.gd.uid b/port/scripts/export_tree.gd.uid similarity index 100% rename from godot-import/port/scripts/export_tree.gd.uid rename to port/scripts/export_tree.gd.uid diff --git a/godot-import/port/scripts/screen_view.gd b/port/scripts/screen_view.gd similarity index 100% rename from godot-import/port/scripts/screen_view.gd rename to port/scripts/screen_view.gd diff --git a/godot-import/port/scripts/screen_view.gd.uid b/port/scripts/screen_view.gd.uid similarity index 100% rename from godot-import/port/scripts/screen_view.gd.uid rename to port/scripts/screen_view.gd.uid diff --git a/godot-import/tools/screen-strip b/tools/port/screen-strip similarity index 100% rename from godot-import/tools/screen-strip rename to tools/port/screen-strip diff --git a/godot-import/tools/verify-screen b/tools/port/verify-screen similarity index 100% rename from godot-import/tools/verify-screen rename to tools/port/verify-screen diff --git a/godot-import/tools/verify-video-audio b/tools/port/verify-video-audio similarity index 100% rename from godot-import/tools/verify-video-audio rename to tools/port/verify-video-audio