diff --git a/crates/sylpheed-export/examples/probe_archives.rs b/crates/sylpheed-export/examples/probe_archives.rs new file mode 100644 index 00000000..54c15e21 --- /dev/null +++ b/crates/sylpheed-export/examples/probe_archives.rs @@ -0,0 +1,42 @@ +//! Which disc archives contain UI screen builds? +//! +//! The exporter reads `dat/GP_TITLE.pak` and nothing else, so four of the five +//! main-menu destinations have no screen file to go to: `authored/flow.json` +//! records LOAD GAME as `GP_SAVE_LOAD`, OPTIONS as `GP_OPTIONS`, and NEW GAME's +//! chain as `DLG_SELECT_DIFFICULTY` -> `SELECT DATA`, all measured destinations +//! that this export cannot reach. +//! +//! This asks the cheap question before anyone refactors the exporter: does the +//! EXISTING build detector find anything in those archives? It changes nothing +//! and writes nothing. +//! +//! cargo run --release -p sylpheed-export --example probe_archives +use sylpheed_formats::{pak::PakArchive, ui_layout}; + +fn main() -> anyhow::Result<()> { + let disc = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into()); + let mut names: Vec = std::fs::read_dir(format!("{disc}/dat"))? + .filter_map(|e| e.ok()) + .map(|e| e.file_name().to_string_lossy().into_owned()) + .filter(|n| n.ends_with(".pak")) + .collect(); + names.sort(); + println!("{:<32} {:>7} {:>8}", "archive", "entries", "builds"); + for n in names { + let path = format!("{disc}/dat/{n}"); + let Ok(ar) = PakArchive::open(&path) else { + println!("{n:<32} {:>7} {:>8}", "-", "open failed"); + continue; + }; + let total = ar.entries().len(); + let builds = ar + .entries() + .iter() + .filter(|e| ar.read(e).map(|b| ui_layout::is_build(&b)).unwrap_or(false)) + .count(); + if builds > 0 || n.contains("OPTIONS") || n.contains("SAVE") || n.contains("DIALOG") { + println!("{n:<32} {total:>7} {builds:>8}"); + } + } + Ok(()) +} diff --git a/docs/port/menu-destinations-are-one-line-away.md b/docs/port/menu-destinations-are-one-line-away.md new file mode 100644 index 00000000..ff8868f0 --- /dev/null +++ b/docs/port/menu-destinations-are-one-line-away.md @@ -0,0 +1,70 @@ +# Four of five main-menu destinations are blocked on ONE hardcoded archive + +**Status:** ✅ feasibility established, nothing changed yet. 2026-09-03. + +## The gap, in player terms + +| button | destination | today | +|---|---|---| +| NEW GAME | `DLG_SELECT_DIFFICULTY` → SELECT DATA → video | **jumps straight to the video** | +| LOAD GAME | `GP_SAVE_LOAD` | **dead** | +| TUTORIAL | — | **dead** | +| OPTIONS | `GP_OPTIONS` | **dead** | +| EXTRAS | `extras` | works | + +All four are recorded in `authored/flow.json` as **measured destinations** — +somebody drove the real game to them. They are `blocked` for one structural +reason, stated there: *"not a GP_TITLE build, so there is no screen file to go +to."* + +## The cause is one line + +`crates/sylpheed-export/src/main.rs` hardcodes `let archive = "dat/GP_TITLE.pak"`. + +## And the reader already works on the rest + +`examples/probe_archives.rs` runs the **existing** `ui_layout::is_build` over +every `.pak` on the disc. It decodes nothing new: + +| archive | entries | builds | +|---|---|---| +| `GP_OPTIONS` | 26 | **14** | +| `GP_SAVE_LOAD` | 108 | **18** | +| `GP_DIALOG` | 140 | **105** | +| `GP_TUTORIAL` | 2 | **2** | +| `GP_TITLE` | 16 | 12 | + +**24 archives contain UI screen builds. The exporter reads one.** + +> So this is not blocked on the Decoder and needs no new format work. It is an +> exporter scope limit, and the exporter is the port's. + +## Why this is worth doing before the queued items + +Measured against *"if this is wrong, what does a player experience?"* — the +filter this port adopted after spending two rounds on a plate pulse that turned +out not to be a defect: + +* **four dead menu entries** and a missing difficulty screen: a player hits them + immediately and three of them do nothing at all; +* the audio mix (F2): a player notices, but the menu still works; +* the repeat rate (F1) and the title track (F3): both blocked on measurement. + +## ⚠️ What this does NOT establish + +* **That the screens will render.** `is_build` says the record parses as a build, + not that its sprites resolve, its names are known, or its layout is complete. + `GP_HANGAR_ARSENAL` reports 390 builds and is squarely gameplay, out of scope. +* **Which entry is the difficulty dialog.** `GP_DIALOG` has 105 builds and none + of them is named yet; `DLG_SELECT_DIFFICULTY` is a name from the flow, not an + entry index. +* **That more screens are free.** Every screen the export gains is a screen + `check-all`'s comparisons iterate over, and screen names are authored per + archive+entry — unnamed screens need a naming decision, not just a loop bound. + +## Next unit + +Widen the exporter to **one** further archive — `GP_OPTIONS`, the smallest at 26 +entries — as data rather than a second hardcoded constant, and see what actually +comes out. Not all four at once: 139 new screens arriving together would make any +regression unattributable.