lsta: the count was never unreliable -- it counts sprites AND PRMD primitives
The module said "a few entries disagree with the actual frame count, so we walk by the T8aD magic instead". They do not disagree. An LSTA is a display list of inline elements that are either T8aD sprites or PRMD primitives (the flat coloured quad the UI bundles use to dim a scene), and the count at 0x04 counts both: across all 64 lists on the disc, count == T8aD + PRMD, with no exceptions. The six lists that looked wrong (GP_DEBRIEFING_PILOTLOG, GP_MISSION_SELECT, two language builds each) each hold exactly one primitive, which is the whole of the off-by-one. Also measured after the T8aD rectangle-list fix: all 1281 sprite frames decode, 100%. parse() still returns sprites and skips primitives -- that is the useful behaviour -- but the docs now say so instead of blaming the header. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,18 @@
|
|||||||
//! `LSTA` — a sprite list: a header followed by N inline [`T8aD`](crate::t8ad)
|
//! `LSTA` — a display list: a header followed by N inline elements concatenated
|
||||||
//! frames concatenated back-to-back.
|
//! 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
|
//! The `count` at `@0x04` is **exact, and counts both kinds**: across all 64
|
||||||
//! count, so we walk by the `T8aD` magic instead (robust) and decode each frame
|
//! lists on the disc, `count == T8aD frames + PRMD primitives` with no
|
||||||
//! from its slice up to the next frame (or end).
|
//! 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};
|
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
|
bytes.len() >= 4 && bytes[0..4] == LSTA_MAGIC
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decode all inline T8aD frames. Frames that don't decode (unsupported T8aD
|
/// Decode the inline T8aD sprites, skipping `PRMD` primitives. Returns `None`
|
||||||
/// variant) are skipped. Returns `None` only for non-LSTA input.
|
/// 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<Vec<T8adImage>> {
|
pub fn parse(bytes: &[u8]) -> Option<Vec<T8adImage>> {
|
||||||
if !is_lsta(bytes) {
|
if !is_lsta(bytes) {
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
@@ -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 ❔ |
|
| 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)) |
|
| 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** |
|
| 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_<base>.tbl` / `VOICE_<base>` with six documented exceptions. The record's ~104 **script ids** are ❔ — positional pairing drifts by three because the IDXD pool dedupes repeated values |
|
| 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_<base>.tbl` / `VOICE_<base>` 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 |
|
| 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 |
|
| 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 |
|
||||||
|
|||||||
Reference in New Issue
Block a user