diff --git a/crates/sylpheed-formats/src/lsta.rs b/crates/sylpheed-formats/src/lsta.rs index 5166b1a..7930efe 100644 --- a/crates/sylpheed-formats/src/lsta.rs +++ b/crates/sylpheed-formats/src/lsta.rs @@ -1,9 +1,18 @@ -//! `LSTA` — a sprite list: a header followed by N inline [`T8aD`](crate::t8ad) -//! frames concatenated back-to-back. +//! `LSTA` — a display list: a header followed by N inline elements concatenated +//! back-to-back, each either a [`T8aD`](crate::t8ad) sprite or a `PRMD` +//! primitive (a flat coloured quad — the same primitive the UI bundles use to +//! dim a scene). //! -//! A `count` lives at `@0x04`, but a few entries disagree with the actual frame -//! count, so we walk by the `T8aD` magic instead (robust) and decode each frame -//! from its slice up to the next frame (or end). +//! The `count` at `@0x04` is **exact, and counts both kinds**: across all 64 +//! lists on the disc, `count == T8aD frames + PRMD primitives` with no +//! exceptions (measured 2026-08-11). An earlier note here said "a few entries +//! disagree with the actual frame count" — they do not; that comparison was +//! counting sprites against a total that includes primitives. +//! +//! [`parse`] walks by magic and returns the **sprites**, deliberately skipping +//! `PRMD` entries, so its result length is `count` only for lists that hold no +//! primitives. Six lists do (in `GP_DEBRIEFING_PILOTLOG`, `GP_MISSION_SELECT`), +//! each with exactly one. use crate::t8ad::{self, T8adImage, T8AD_MAGIC}; @@ -15,8 +24,9 @@ pub fn is_lsta(bytes: &[u8]) -> bool { bytes.len() >= 4 && bytes[0..4] == LSTA_MAGIC } -/// Decode all inline T8aD frames. Frames that don't decode (unsupported T8aD -/// variant) are skipped. Returns `None` only for non-LSTA input. +/// Decode the inline T8aD sprites, skipping `PRMD` primitives. Returns `None` +/// only for non-LSTA input. Every one of the 1 281 sprite frames on the disc +/// decodes (measured 2026-08-11, after the T8aD rectangle-list fix). pub fn parse(bytes: &[u8]) -> Option> { if !is_lsta(bytes) { return None; diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index e20ad44..59e6fb0 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -16,7 +16,7 @@ Promote to a prose `structures/…md` file when a format needs behavioural notes | XPR2 texture + cubemap | 🟡/✅ | `sylpheed-formats/src/texture.rs` + [colour check](xpr2-colour-check.md) | de-tile + A8R8G8B8 and DXT1. **Channel order ✅ confirmed against the running game**: the Delta Saber's decoded atlas is orange-dominant (median saturated hue 23.3°, *zero* cool pixels) and the game renders the same hull at 9.3° — a red↔blue swap would sit at ≈200°. Exact fidelity (gamma/sRGB curve, premultiplied alpha, per-channel scale) is 🟡 untested, since a hue comparison cannot see it; cubemap face ordering ❔ | | T8aD 2D texture | ✅ | `sylpheed-formats/src/t8ad.rs` | **100 % of the disc decodes** (19 216/19 216, measured). The "~15 % deferred variants" were a wrong model, not a variant: a surface is a list of **arbitrary sub-rectangles**, each with a 16-byte header of `dst X, dst Y, width, height`, not a 256×256 grid — `0x1c` is the **rectangle count**. Uncovered area stays transparent. **Colours ✅ CONFIRMED** ([k8888](structures/texture-color-k8888.md)) | | RATC bundle | ✅ | `sylpheed-formats/src/ratc.rs` | child listing confirmed. **"One level deep" is not a limitation — there is nothing deeper**: 2 859 bundles hold 18 002 children at depth 1 and **0 at depth 2**, with no parse failures. Nested RATC blobs are **leaf records that reference siblings by name** (`opt `, the sprite name): 3 311 leaves, all embedding sibling names, **10 144 of 10 148 references resolve**. The 4 that do not are one dangling asset — `pmbase.rat` → `pmbase.t32` in `GP_STAGE_CLEAR.pak`'s four language builds, and `pmbase.t32` is **on the disc nowhere** | -| LSTA sprite list | 🟡 | `sylpheed-formats/src/lsta.rs` | inline T8aD frames | +| LSTA sprite list | ✅ | `sylpheed-formats/src/lsta.rs` | A display list of inline elements: **T8aD sprites and `PRMD` primitives**. The `count` at `0x04` is **exact and counts both** — `count == T8aD + PRMD` for **64/64** lists on the disc, which retires the old "a few entries disagree" note (it compared sprites against a total including primitives). **All 1 281 sprite frames decode** after the T8aD rectangle-list fix | | IXUD subtitle | 🟡/✅ | `sylpheed-formats/src/ixud.rs` + [movie link](movie-subtitle-link.md) | timed cues. **The movie↔subtitle↔voice link is solved — statically**, from the movie config record in `tables.pak` (schema `0x067025b9`), not from the running game as this row previously assumed: [101 movies mapped](captures/movie-subtitle-voice-map.csv), 94 with subtitles, 83 with voice, 21 with a telop overlay. 93 of 94 subtitle refs resolve in the language paks; **`SUBTITLE_S12B.tbl` is missing from all six languages** — a dangling reference on the disc. Naming is `SUBTITLE_.tbl` / `VOICE_` with six documented exceptions. The record's ~104 **script ids** are ❔ — positional pairing drifts by three because the IDXD pool dedupes repeated values | | Fonts (ttf/otf/ttc) | ✅ | `sylpheed-formats/src/font.rs` | standard OpenType, parsed via ttf-parser | | XBG7 mesh | 🟡/❔ | `sylpheed-formats/src/mesh.rs` + `tests/mesh_disc.rs` ([xbg7](structures/xbg7-mesh.md)) | weapons/props: declaration-driven variable stride (36 models), GPU-confirmed. **Stage containers: 5662 sub-models across 22 stages** via content-anchored grouped pools (`stage_models`). Quantized hero bodies (DeltaSaber `f004`) still declined |