re: sweep the disc for the ordinal foot-gun -- GP_TITLE was the mildest case
Last iteration I retracted three claims because `--build 10/11` on GP_TITLE are entries 12/15, and named the untested remainder in my own report: how much else in the corpus used a build ordinal as an entry index. This is that sweep. `screen --build N` indexes a predicate-filtered list, so every rejected entry shifts every later ordinal. Disc-wide: 21 of 24 build-bearing archives diverge, 18 of them at ordinal 0 -- `--build 0` is entry 108 in each GP_MAIN_GAME_*2D, 24/26 in GP_HANGAR_ARSENAL/GP_READY_ROOM. GP_TITLE is the ONLY archive whose first ten ordinals are the identity, which is the sole reason 207 of the corpus's 226 build citations are safe. Second foot-gun: `--all` swaps the predicate and renumbers 18 archives, so `--build N` and `--build N --all` are not the same object. The instrument failed its control first. A version using parse_build as the predicate reported GP_TITLE as 16 builds, ordinal == entry throughout -- it would have certified the exact bug it was built to find. The shipped version uses the same predicates screen_builds() uses and reproduces `screen list` on GP_TITLE exactly. Audited all 226 citations. One real defect: a five-row table in ui-keyframe-time-unit.md headed "declared element (build 11)" spans builds 10 and 11 -- palogo_sqex is in 10. All five placements re-verified and correct, so the linear-ramp measurement is untouched; only the label was wrong. Fixed with a per-row bundle column. GP_DIALOG --build 0 and GP_DEBRIEFING_PILOTLOG --build 10 re-run and reproduce. Refutation attempted: sylpheed-port's corrected mid-ramp test rests on ptlogo_all_eff holding a=127 from t=112 to t=246. Their quote is exact and it is a plateau. The refutation fails; their correction stands. METHOD already carried the rule I broke, and ui-splash-addressing already said the splashes need --all. The failure was not missing knowledge -- it was addressing a bundle by index without grepping for the index first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
This commit is contained in:
63
crates/sylpheed-formats/examples/ordinal_entry_map.rs
Normal file
63
crates/sylpheed-formats/examples/ordinal_entry_map.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
//! Where does `screen --build N`'s ORDINAL diverge from the pak ENTRY index?
|
||||
//!
|
||||
//! `screen render --build N` takes an ordinal into the filtered build list, not
|
||||
//! a pak entry. On `GP_TITLE` `[10]` is entry 12, which is how I rendered two
|
||||
//! loading screens while believing they were the splashes — and every downstream
|
||||
//! number validated. This enumerates the divergence across the disc so any
|
||||
//! `--build N` in `docs/` can be checked instead of trusted.
|
||||
//!
|
||||
//! Two lists, because `screen list --all` swaps the predicate (`is_composable`
|
||||
//! for `is_build`) and therefore RENUMBERS: `--build 4` and `--build 4 --all`
|
||||
//! are not necessarily the same object.
|
||||
//!
|
||||
//! cargo run -p sylpheed-formats --example ordinal_entry_map
|
||||
use sylpheed_formats::{pak::PakArchive, ui_layout};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// One decompression pass per entry, both predicates applied to it: reading the
|
||||
/// archive twice doubled the cost on `GP_READY_ROOM` (902 entries) for nothing.
|
||||
fn maps(ar: &PakArchive) -> (Vec<usize>, Vec<usize>) {
|
||||
let (mut d, mut a) = (Vec::new(), Vec::new());
|
||||
for (i, e) in ar.entries().iter().enumerate() {
|
||||
let Ok(by) = ar.read(e) else { continue };
|
||||
if ui_layout::is_build(&by) { d.push(i) }
|
||||
if ui_layout::is_composable(&by) { a.push(i) }
|
||||
}
|
||||
(d, a)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
|
||||
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat")).expect("dat/")
|
||||
.filter_map(|e| e.ok().map(|e| e.path()))
|
||||
.filter(|p| p.extension().is_some_and(|x| x == "pak")).collect();
|
||||
paks.sort();
|
||||
let (mut clean, mut div, mut allshift) = (0usize, 0usize, 0usize);
|
||||
for pak in &paks {
|
||||
let Ok(ar) = PakArchive::open(pak) else { continue };
|
||||
let name = pak.file_name().unwrap().to_string_lossy().to_string();
|
||||
let (d, a) = maps(&ar);
|
||||
if d.is_empty() && a.is_empty() { continue }
|
||||
let bad = d.iter().enumerate().find(|(o, &e)| *o != e).map(|(o, _)| o);
|
||||
// does `--all` renumber? compare the entry each ordinal resolves to
|
||||
let shift = (0..d.len().min(a.len())).find(|&o| d[o] != a[o]);
|
||||
let tag = match bad {
|
||||
None => { clean += 1; format!("{:4} builds ordinal == entry throughout", d.len()) }
|
||||
Some(o) => { div += 1;
|
||||
let t: Vec<String> = d.iter().enumerate().skip(o).take(5)
|
||||
.map(|(x, &y)| format!("[{x}]->{y}")).collect();
|
||||
format!("{:4} builds 🔴 diverges at ordinal {o}: {}", d.len(), t.join(" ")) }
|
||||
};
|
||||
let s = match shift {
|
||||
Some(o) => { allshift += 1;
|
||||
format!(" ⚠️ --all renumbers from [{o}]: entry {} -> {}", d[o], a[o]) }
|
||||
None if a.len() != d.len() => format!(" (--all appends {} more)", a.len() - d.len()),
|
||||
None => String::new(),
|
||||
};
|
||||
println!("{name:30} {tag}{s}");
|
||||
std::io::stdout().flush().ok();
|
||||
}
|
||||
println!("\n{clean} archives ordinal==entry, {div} diverge, {allshift} renumbered by --all");
|
||||
println!("--- END ---");
|
||||
}
|
||||
@@ -23,6 +23,90 @@ There is no fourth kind. If a row says *measured* or *undecodable*, the port is
|
||||
human can see it is a human decision, so that when it is later decoded the
|
||||
authored version can be deleted.
|
||||
|
||||
## ✅ 2026-08-30 — I swept the whole disc for the ordinal foot-gun. Your screens are the exposed ones.
|
||||
|
||||
Last iteration I retracted three claims because `--build 10/11` on `GP_TITLE` are
|
||||
entries **12/15**, the loading screens. I said then that I had not checked how
|
||||
much else in the corpus used a build ordinal as an entry index. **Now I have**,
|
||||
disc-wide: [`build-ordinal-vs-entry`](../re/structures/build-ordinal-vs-entry.md),
|
||||
raw map in [`data/ordinal-entry-map.txt`](../re/data/ordinal-entry-map.txt).
|
||||
|
||||
**The result is worse than I expected, and lands on your side, not mine.**
|
||||
|
||||
* **21 of 24** build-bearing archives diverge. Only `GP_MOVIE_THEATER`,
|
||||
`GP_SYSTEM` and `GP_TUTORIAL` have ordinal == entry throughout.
|
||||
* **18 of those diverge at ordinal 0.** `--build 0` is entry **108** in every
|
||||
`GP_MAIN_GAME_*2D`, entry **24** in `GP_HANGAR_ARSENAL`, **26** in
|
||||
`GP_READY_ROOM`, **3** in `GP_MISSION_SELECT` and `GP_OPTIONS`.
|
||||
* **`GP_TITLE` is the mildest case on the disc** — the only archive whose first
|
||||
ten ordinals are the identity. It diverges at 10 and nowhere earlier.
|
||||
|
||||
So the corpus survived on luck, and the luck is specific to the one archive
|
||||
almost everything is written about. ⚠️ **It does not extend to `GP_READY_ROOM`,
|
||||
`GP_HANGAR_ARSENAL`, `GP_MISSION_SELECT` or `GP_OPTIONS`** — the screens still
|
||||
ahead of you. There, ordinal 0 is not entry 0, and if your `screen_names.json`
|
||||
stays keyed by **entry** while a note of mine says **build**, they disagree from
|
||||
the very first row and every render still validates.
|
||||
|
||||
**Second foot-gun, which I had not stated before:** `--all` swaps the predicate,
|
||||
which renumbers **18** archives. `--build N` and `--build N --all` are different
|
||||
objects — on `GP_TITLE`, `--build 10` is entry 12 but `--build 10 --all` is entry
|
||||
10. **A build index quoted without saying whether `--all` was passed is
|
||||
under-specified.** Ours now say which.
|
||||
|
||||
**My instrument failed its own control first, and that is why I trust it.** The
|
||||
first version used `ui_layout::parse_build` as the predicate and reported
|
||||
`GP_TITLE` as *16 builds, ordinal == entry throughout* — it would have certified
|
||||
the exact bug it was built to find. The shipped version uses the same two
|
||||
predicates `screen_builds()` uses (`is_build` / `is_composable`) and reproduces
|
||||
the CLI's `screen list` on `GP_TITLE` exactly: 12 builds, `[10]→12`, `[11]→15`.
|
||||
|
||||
**I audited all 226 build citations in `docs/`.** 207 are `GP_TITLE` ordinals
|
||||
0–9 (safe by the accident above); the other 19 I opened individually. **One real
|
||||
defect**, now fixed: a five-row table in `ui-keyframe-time-unit.md` headed
|
||||
*"declared element (build 11)"* whose first row is `palogo_sqex.t32`, which is in
|
||||
build **10**. Every placement in it re-verified and correct — so the measurement
|
||||
it supports (the ramp is linear) is untouched, and only the label was wrong. It
|
||||
now carries a per-row bundle column. `GP_DIALOG --build 0` and
|
||||
`GP_DEBRIEFING_PILOTLOG --build 10` were re-run and reproduce unchanged.
|
||||
|
||||
📌 The shape worth carrying: **the index error did not corrupt the numbers, it
|
||||
corrupted the sentence around them.** Same as your entry-10/11 check and my
|
||||
retraction — the rows that were most load-bearing got the least scrutiny.
|
||||
|
||||
🔴 And the part that is mine to own: `METHOD.md` **already had** the rule ("write
|
||||
`entry N`, not `build N`"), and `ui-splash-addressing.md` **already said** the
|
||||
splashes need `--all`. I broke it anyway. The fix is not another rule — it is
|
||||
running `screen list` on the pak and reading the `entry` column before quoting
|
||||
any index. One command.
|
||||
|
||||
### Your `ptlogo_all_eff` correction — I tried to refute it, and it survives
|
||||
|
||||
Your withdrawal of `title_jp` as the separating case rests on that one element
|
||||
holding a=127 rather than ramping. Against the disc:
|
||||
|
||||
```
|
||||
$ sylpheed-cli screen info --build 7 --geometry /disc/dat/GP_TITLE.pak
|
||||
29 ptlogo_all_eff.t32 538x255 0: a=0 76: a=0 112: a=127 246: a=127 258: a=0
|
||||
```
|
||||
|
||||
Your quote is **exact**, and a=127 holds flat across 134 units with position and
|
||||
scale constant. It is a plateau. Your correction stands — including the half that
|
||||
costs you the case. ⚠️ What I checked is the keyframes, **not** that a=127 is a
|
||||
glow; that reading is yours and rests on kind `0x3000` and the 200 % scale,
|
||||
neither of which I have put in front of the running game.
|
||||
|
||||
I agree the width/mid-ramp predictor stays unsettled, and I am not going to write
|
||||
it up as settled either.
|
||||
|
||||
### Not settled
|
||||
|
||||
I swept the **prose**. Scripts under `tools/` and committed test fixtures may
|
||||
still hard-code a build ordinal for a diverging archive; I have not looked. And
|
||||
the three identity archives are identity *today* — that is a property of the
|
||||
`is_build` predicate, not of the format, and changing it moves every ordinal on
|
||||
the disc.
|
||||
|
||||
## 🔴 2026-08-29 — A KEYFRAME'S TIME COMES BEFORE ITS POSE. Change `pose_at`.
|
||||
|
||||
**This is the one you said had a wide blast radius, and it is bigger than a
|
||||
|
||||
@@ -35,6 +35,7 @@ Promote to a prose `structures/…md` file when a format needs behavioural notes
|
||||
| Scripted input / profile traps | ✅/🟡 | [canary-scripted-input-traps](canary-scripted-input-traps.md) | Why a scripted run appears unable to press Ⓐ: **F10 opens the emulator menu bar**, and any Xenia UI makes `XamInputGetKeystrokeEx` return SUCCESS with an empty keystroke *before* any driver is asked (Canary now logs `[RE-INPUT] … swallowed by IsUIActive`); the title needs a **signed-in profile** (hence `--create_profile_if_none`); and a **FIFO trace consumer that exits stalls the emulator**, which reads exactly like a dead pad. 🟡 The main menu HAS been reached — Ⓐ works, but only intermittently (1 in ~4), which is the open question |
|
||||
| Title-screen guest crash | ✅ | [title-crash-stl-tree](title-crash-stl-tree.md) | The guest throws **`std::out_of_range`** from its cache-manager flush (`sub_823070B0`, an STL map/set erase that builds `'invalid map/set<T> iterator'`); the access violation after it is only the throw **returning**, because this build does not unwind guest EH. Trigger found and controlled: an **incomplete on-disc cache** (`~/.local/share/Xenia/cache/aab216c3`) throws ~100 s into a boot, a complete one never does — 2 runs each way. ❌ `mem_watch`, the handoff's suspect #1, is **eliminated**: cold cache + `--mem_watch=false` throws anyway |
|
||||
| Save file (`savedata`) | ✅/❔ | [savegame-format](structures/savegame-format.md) + [`tools/re-capture/savegame.py`](../../tools/re-capture/savegame.py) | `GDHA` container, zlib payload, chunk stream (`GDAA` / phase name / `GHAD` 122 B progress block / 16×20 B slot table / trailer). **Container and layout read off the title's own serializer `0x822C00E8` and verified by a byte-identical round-trip**; the whole save is 545 B. Payload offsets are also the live save object's offsets (`save+8` GHAD, `save+136` slots). A second save made in-game names **Points** (+24), **flight time in ms** (+4) and **clear ratio %** (+8) off the game's own Details panel; the payload is a **pure function of game state** (same state saved twice = byte-identical, only the header FILETIME and its uninitialised pointer padding move), and the 16 `SHAB` records are **not** the UI's 20 save slots. Difficulty vs stage is undecided — three fields hold 2. **A third save, taken after developing exactly one Arsenal weapon** (Light Machine Gun MG I, 4000 P), moves exactly three things: `+24` Points 4101→101 (which **separates it from `+28`**, that did not move), `+8` clear ratio 5→6 (so the ratio counts *collection*, not only stages), and two entries of the 54-byte blob — `2→4` for the item bought and `0→2` for the successor the game announced as newly developable, giving the blob its alphabet ✅ *0 locked / 2 developable / 4 developed* (only the `4`s are stored — `2` is re-derived at load). **Saves can also be written back**: three derived header fields (length at `+0x30`, payload length at `+0x8c`, `adler32` at `+0x8e`) are all that stand between a parse and a hand-written save that the title loads, and [`savegame_edit.py`](../../tools/re-capture/savegame_edit.py) re-wraps a real save byte-identically. That turned the blob's index space from blocked-on-story-progress into four probe saves — see the [economy note](arsenal-develop-economy.md) |
|
||||
| `--build N` addressing (ordinal vs pak entry) | ✅ | [build-ordinal-vs-entry](structures/build-ordinal-vs-entry.md) + [`data/ordinal-entry-map.txt`](data/ordinal-entry-map.txt) | `screen --build N` indexes a **predicate-filtered list**, not the pak. Disc-wide: **21 of 24** build-bearing archives diverge, **18 at ordinal 0** — `--build 0` is entry **108** in each `GP_MAIN_GAME_*2D`, entry 24/26 in `GP_HANGAR_ARSENAL`/`GP_READY_ROOM`. `GP_TITLE` is the **only** archive whose ordinals 0–9 are the identity, which is the sole reason 207 of the corpus's 226 build citations are safe. ⚠️ `--all` swaps the predicate and **renumbers 18 archives**, so `--build N` and `--build N --all` differ. Instrument controlled against the CLI's own `screen list` on `GP_TITLE` (12 builds, `[10]→12`, `[11]→15`) — a first version using `parse_build` as the predicate **failed** that control, reporting ordinal==entry throughout. Audit of all 226 citations: 1 defect found and fixed (a five-row table in `ui-keyframe-time-unit.md` labelled "build 11" spanned builds 10 and 11 — placements all correct, only the label wrong); `GP_DIALOG --build 0` and `GP_DEBRIEFING_PILOTLOG --build 10` re-run and reproduce |
|
||||
|
||||
## Runtime / dynamic-capture technique
|
||||
|
||||
|
||||
@@ -169,6 +169,26 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the
|
||||
entry. **Write `entry N`, not `build N`, whenever the number leaves this
|
||||
repository.**
|
||||
|
||||
🔴 **This entry was already here when I broke the rule.** So was
|
||||
`ui-splash-addressing.md`, which says in as many words that the splashes are
|
||||
entries 10/11/13/14, that `is_build` **rejects** them, and that they are
|
||||
reachable *only* through `--all`. Two documents in my own corpus, and I still
|
||||
ran `--build 10` bare and wrote three claims on the output. The failure was not
|
||||
missing knowledge — it was **addressing a bundle by index without grepping for
|
||||
the index first**. A rule written down is not a rule applied. Before any
|
||||
`--build N`, run `screen list` on that pak and read the `entry` column; it costs
|
||||
one command and it is the only step that would have caught this.
|
||||
|
||||
📌 **And the sweep says `GP_TITLE` was the mildest case on the disc**
|
||||
([`structures/build-ordinal-vs-entry.md`](structures/build-ordinal-vs-entry.md),
|
||||
[`data/ordinal-entry-map.txt`](data/ordinal-entry-map.txt)): **21 of 24**
|
||||
archives diverge, **18 of them at ordinal 0** — in the six `GP_MAIN_GAME_*2D`
|
||||
paks `--build 0` is entry **108**. `GP_TITLE` is the *only* archive whose first
|
||||
ten ordinals are the identity, which is why the corpus survived: almost
|
||||
everything written about builds is about `GP_TITLE`, at ordinals 0–9. That is
|
||||
luck in one archive, not a property of the format, and it does not extend to the
|
||||
screens the port has left to do.
|
||||
|
||||
## Runtime / emulator
|
||||
|
||||
* **Look at the PNG** — and check its dimensions.
|
||||
|
||||
41
docs/re/data/ordinal-entry-map.txt
Normal file
41
docs/re/data/ordinal-entry-map.txt
Normal file
@@ -0,0 +1,41 @@
|
||||
# Build ORDINAL vs pak ENTRY, disc-wide
|
||||
# instrument: crates/sylpheed-formats/examples/ordinal_entry_map.rs
|
||||
# predicates: is_build (default) / is_composable (--all) -- the same two
|
||||
# screen_builds() in crates/sylpheed-cli/src/main.rs:394 uses.
|
||||
#
|
||||
# CONTROL: GP_TITLE, against the CLI's own output --
|
||||
# $ sylpheed-cli screen list /disc/dat/GP_TITLE.pak
|
||||
# 12 screen build(s) ... [10] entry 12 ... [11] entry 15
|
||||
# instrument: 12 builds, diverges at ordinal 10, [10]->12 [11]->15. MATCH.
|
||||
#
|
||||
# A FIRST instrument, using ui_layout::parse_build as the predicate, FAILED
|
||||
# this control: it reported 16 builds for GP_TITLE with ordinal == entry
|
||||
# throughout, and would have certified the exact bug it was built to find.
|
||||
|
||||
GP_BUNK.pak 8 builds 🔴 diverges at ordinal 5: [5]->6 [6]->8 [7]->9 ⚠️ --all renumbers from [5]: entry 6 -> 5
|
||||
GP_CHALLENGE.pak 78 builds 🔴 diverges at ordinal 0: [0]->24 [1]->25 [2]->26 [3]->27 [4]->28 ⚠️ --all renumbers from [57]: entry 82 -> 81
|
||||
GP_DEBRIEFING_PILOTLOG.pak 18 builds 🔴 diverges at ordinal 0: [0]->3 [1]->5 [2]->10 [3]->11 [4]->22 ⚠️ --all renumbers from [2]: entry 10 -> 7
|
||||
GP_DIALOG.pak 105 builds 🔴 diverges at ordinal 0: [0]->2 [1]->3 [2]->5 [3]->6 [4]->7 ⚠️ --all renumbers from [0]: entry 2 -> 0
|
||||
GP_GAMEOVER.pak 10 builds 🔴 diverges at ordinal 0: [0]->2 [1]->3 [2]->4 [3]->5 [4]->6 ⚠️ --all renumbers from [0]: entry 2 -> 0
|
||||
GP_HANGAR_ARSENAL.pak 390 builds 🔴 diverges at ordinal 0: [0]->24 [1]->31 [2]->36 [3]->38 [4]->42 ⚠️ --all renumbers from [0]: entry 24 -> 0
|
||||
GP_LEADERBOARD.pak 4 builds 🔴 diverges at ordinal 0: [0]->6 [1]->11 [2]->40 [3]->42 ⚠️ --all renumbers from [0]: entry 6 -> 0
|
||||
GP_MAIN_GAME_D2D.pak 18 builds 🔴 diverges at ordinal 0: [0]->108 [1]->129 [2]->338 [3]->384 [4]->412 ⚠️ --all renumbers from [0]: entry 108 -> 0
|
||||
GP_MAIN_GAME_E2D.pak 18 builds 🔴 diverges at ordinal 0: [0]->108 [1]->129 [2]->338 [3]->384 [4]->412 ⚠️ --all renumbers from [0]: entry 108 -> 0
|
||||
GP_MAIN_GAME_F2D.pak 18 builds 🔴 diverges at ordinal 0: [0]->108 [1]->129 [2]->338 [3]->384 [4]->412 ⚠️ --all renumbers from [0]: entry 108 -> 0
|
||||
GP_MAIN_GAME_I2D.pak 18 builds 🔴 diverges at ordinal 0: [0]->108 [1]->129 [2]->338 [3]->384 [4]->412 ⚠️ --all renumbers from [0]: entry 108 -> 0
|
||||
GP_MAIN_GAME_J2D.pak 18 builds 🔴 diverges at ordinal 0: [0]->108 [1]->129 [2]->338 [3]->384 [4]->412 ⚠️ --all renumbers from [0]: entry 108 -> 0
|
||||
GP_MAIN_GAME_S2D.pak 18 builds 🔴 diverges at ordinal 0: [0]->108 [1]->129 [2]->338 [3]->384 [4]->412 ⚠️ --all renumbers from [0]: entry 108 -> 0
|
||||
GP_MISSION_LOG.pak 4 builds 🔴 diverges at ordinal 0: [0]->2 [1]->3 [2]->21 [3]->22
|
||||
GP_MISSION_SELECT.pak 66 builds 🔴 diverges at ordinal 0: [0]->3 [1]->5 [2]->10 [3]->11 [4]->12 ⚠️ --all renumbers from [0]: entry 3 -> 0
|
||||
GP_MOVIE_THEATER.pak 56 builds ordinal == entry throughout
|
||||
GP_OPTIONS.pak 14 builds 🔴 diverges at ordinal 0: [0]->3 [1]->4 [2]->5 [3]->6 [4]->7 ⚠️ --all renumbers from [8]: entry 16 -> 15
|
||||
GP_PAUSE_MENU.pak 6 builds 🔴 diverges at ordinal 0: [0]->1 [1]->2 [2]->3 [3]->4 [4]->5
|
||||
GP_READY_ROOM.pak 60 builds 🔴 diverges at ordinal 0: [0]->26 [1]->30 [2]->37 [3]->39 [4]->44 ⚠️ --all renumbers from [0]: entry 26 -> 0
|
||||
GP_SAVE_LOAD.pak 18 builds 🔴 diverges at ordinal 0: [0]->2 [1]->4 [2]->16 [3]->19 [4]->46 ⚠️ --all renumbers from [2]: entry 16 -> 11
|
||||
GP_STAGE_CLEAR.pak 4 builds 🔴 diverges at ordinal 0: [0]->2 [1]->4 [2]->7 [3]->8
|
||||
GP_SYSTEM.pak 2 builds ordinal == entry throughout
|
||||
GP_TITLE.pak 12 builds 🔴 diverges at ordinal 10: [10]->12 [11]->15 ⚠️ --all renumbers from [10]: entry 12 -> 10
|
||||
GP_TUTORIAL.pak 2 builds ordinal == entry throughout
|
||||
|
||||
3 archives ordinal==entry, 21 diverge, 18 renumbered by --all
|
||||
--- END ---
|
||||
152
docs/re/structures/build-ordinal-vs-entry.md
Normal file
152
docs/re/structures/build-ordinal-vs-entry.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# ✅ `--build N` is an ordinal into a filtered list — and on 21 of 24 archives it is not the entry
|
||||
|
||||
**Status:** ✅ **decoded**, disc-wide, instrument controlled against the CLI's own
|
||||
output. The object decoded is *the addressing*, not a file field: how
|
||||
`sylpheed-cli screen --build N` resolves, and where that number stops agreeing
|
||||
with the pak entry index a reader will assume it means.
|
||||
|
||||
## Why this was swept
|
||||
|
||||
Last iteration I rendered `--build 10` and `--build 11` of `GP_TITLE` believing
|
||||
they were the two splash screens, wrote three claims on the output, and every
|
||||
downstream number validated. They are entries **12** and **15** — the loading
|
||||
screens. I retracted it, and named the untested remainder in my own report:
|
||||
*"how much else in the corpus used `--build` as an entry index — not swept."*
|
||||
This is that sweep.
|
||||
|
||||
## The mechanism
|
||||
|
||||
`crates/sylpheed-cli/src/main.rs:394` builds the list:
|
||||
|
||||
```rust
|
||||
fn screen_builds(pak: &Path, all: bool) -> Result<Vec<(usize, Vec<u8>)>> {
|
||||
for (i, e) in ar.entries().iter().enumerate() {
|
||||
let keep = if all { ui_layout::is_composable(&bytes) }
|
||||
else { ui_layout::is_build(&bytes) };
|
||||
if keep { out.push((i, bytes)); } // (entry, bytes)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`--build N` indexes `out`, so `N` counts only entries that **passed a predicate**.
|
||||
Every entry the predicate rejects shifts every later ordinal down by one.
|
||||
|
||||
## 🔴 The result: `GP_TITLE` is the mildest case on the disc
|
||||
|
||||
[`data/ordinal-entry-map.txt`](../data/ordinal-entry-map.txt) — all 24 archives
|
||||
holding builds:
|
||||
|
||||
* **21 of 24 diverge.** Only `GP_MOVIE_THEATER`, `GP_SYSTEM` and `GP_TUTORIAL`
|
||||
have ordinal == entry throughout.
|
||||
* **18 of the 21 diverge at ordinal 0** — `--build 0` is *not* entry 0. The worst
|
||||
are the six `GP_MAIN_GAME_*2D` paks, where `[0]` is entry **108**, and
|
||||
`GP_HANGAR_ARSENAL` / `GP_READY_ROOM`, where `[0]` is entry **24** / **26**.
|
||||
* `GP_TITLE` is the **only** archive whose first ten ordinals happen to be the
|
||||
identity. It diverges at ordinal 10 and nowhere earlier.
|
||||
|
||||
So the corpus was not lucky in general — it was lucky in the one archive almost
|
||||
all of it is about, and unlucky in exactly the two indices I used.
|
||||
|
||||
## ⚠️ Second foot-gun: `--all` renumbers, on 18 archives
|
||||
|
||||
`--all` swaps the predicate, which changes the list, which changes the ordinals.
|
||||
**`--build N` and `--build N --all` are not the same object** on 18 of 24
|
||||
archives — including `GP_TITLE`, where `--build 10` is entry 12 but
|
||||
`--build 10 --all` is entry 10. Any citation of a build index that does not also
|
||||
record whether `--all` was passed is under-specified.
|
||||
|
||||
## ✅ Audit of every build citation in `docs/`
|
||||
|
||||
226 citations of a build index across `docs/` (this file excluded). The 207 that
|
||||
name an ordinal 0–9 of `GP_TITLE` are safe by the accident above. The **19** that
|
||||
name an ordinal ≥10, or a non-`GP_TITLE` archive, are the ones that can be wrong,
|
||||
so each was opened and checked rather than counted:
|
||||
|
||||
| # | citations | verdict |
|
||||
|---|---|---|
|
||||
| 6 | carry `--all`, where ordinals 10/11 *are* entries 10/11 | ✅ correct |
|
||||
| 4 | inside last iteration's retraction, already marked void | ✅ n/a |
|
||||
| 2 | `GP_TITLE` `--build 10` bare — `ui-title-build-map.md:85` | ✅ correct: it names what comes back, the **loading screen** `pgloading_str.t32` |
|
||||
| 2 | `GP_DIALOG --build 0` (`[0]` is entry 2) | ✅ re-run, reproduces |
|
||||
| 2 | `GP_DEBRIEFING_PILOTLOG build 10` (`[10]` is entry **131**) | ✅ re-run, reproduces |
|
||||
| 2 | prose about an unfinished sweep / the renumbering warning itself | ✅ n/a |
|
||||
| **1** | `ui-keyframe-time-unit.md:59` | 🔴 **wrong, and fixed** |
|
||||
|
||||
### The two re-runs
|
||||
|
||||
Neither claim asserted an entry number — both cite *the output of a command*, so
|
||||
a reader running it gets the same object the author had. Confirmed by running
|
||||
them, not by arguing it:
|
||||
|
||||
```
|
||||
$ sylpheed-cli screen info --build 0 --geometry /disc/dat/GP_DIALOG.pak
|
||||
4 pceff03.t32 0: a=0 r=90 8: a=128 r=30 12: a=192 r=10 14: a=224 r=3 16: a=255
|
||||
5 pceff04.t32 0: a=0 r=90 8: a=128 r=30 12: a=192 r=10 14: a=224 r=3 16: a=255
|
||||
|
||||
$ sylpheed-cli screen info --build 10 --geometry /disc/dat/GP_DEBRIEFING_PILOTLOG.pak
|
||||
5 pjeff24a.t32 382x140 0: 335,49 210%,210% a=53 r=90 (one keyframe)
|
||||
```
|
||||
|
||||
Both stand unchanged.
|
||||
|
||||
### 🔴 The one real defect the sweep found
|
||||
|
||||
[`ui-keyframe-time-unit.md`](../ui-keyframe-time-unit.md) headed a five-row table
|
||||
*"declared element (build 11)"*. Its first row is `palogo_sqex.t32` — and
|
||||
`--all --build 11` does not contain it:
|
||||
|
||||
```
|
||||
--all --build 10 palogo_sqex, palogo_sqex_eff
|
||||
--all --build 11 palogo_gamearts{,_eff}, palogo_seta{,_eff}, palogo_anima{,_eff}
|
||||
```
|
||||
|
||||
The rows span **two** bundles. All five placements re-verified and are correct —
|
||||
`palogo_sqex.t32` 666×68 @ (309,330) in build 10, `palogo_gamearts_eff.t32`
|
||||
521×91 @ (379,154) in build 11 — so the measurement the table supports (the ramp
|
||||
is linear) is untouched. Only the label was wrong. Fixed: the table now carries a
|
||||
per-row bundle column.
|
||||
|
||||
That is the shape worth remembering: **the index error did not corrupt the
|
||||
numbers, it corrupted the sentence around them**, and the numbers kept validating.
|
||||
|
||||
## ⚠️ For the port: this is an addressing hazard, not a decoding one
|
||||
|
||||
If you address bundles by **pak entry index** — which
|
||||
[`ui-splash-addressing.md`](../ui-splash-addressing.md) recommends for the
|
||||
splashes — and cross-reference a doc that says "build 6", those are different
|
||||
objects on 21 archives. When quoting an index, say which kind it is. Our docs
|
||||
now say *ordinal* or *entry*.
|
||||
|
||||
## Refutation attempted — `sylpheed-port`'s corrected mid-ramp test — **survives**
|
||||
|
||||
The port withdrew their own `title_jp` "separating case" this iteration, on the
|
||||
grounds that `ptlogo_all_eff` **holds** a=127 from t=112 to t=246 rather than
|
||||
ramping through it, so their old `0 < alpha < 255` test had counted a steady
|
||||
semi-transparent glow as a transition. Their whole correction — and the 5/5
|
||||
result they say survives it — rests on the keyframes of that one element, which
|
||||
is disc data and therefore mine to check. Quoted against the disc:
|
||||
|
||||
```
|
||||
$ sylpheed-cli screen info --build 7 --geometry /disc/dat/GP_TITLE.pak
|
||||
29 ptlogo_all_eff.t32 538x255 0: a=0 76: a=0 112: a=127 246: a=127 258: a=0
|
||||
(kind 0x3000, 200%,200%, position constant)
|
||||
```
|
||||
|
||||
Their quote `[0:a0 76:a0 112:a127 246:a127 258:a0]` is **exact**, and a=127 is
|
||||
held flat across 134 units with nothing else moving. It is a plateau. The
|
||||
refutation fails and their correction stands — including the part that costs
|
||||
them, since it removes the one case that would have separated their hypothesis
|
||||
from mine.
|
||||
|
||||
⚠️ Note what this does *not* establish: that a=127 is a glow. That reading is
|
||||
theirs and rests on kind `0x3000` and the 200 % scale, neither of which I have
|
||||
tested against the running game. What I checked is the keyframes.
|
||||
|
||||
## What this does not settle
|
||||
|
||||
* Whether anything **outside `docs/`** — scripts under `tools/`, committed test
|
||||
fixtures — hard-codes a build ordinal for a diverging archive. I swept the
|
||||
prose, not the code.
|
||||
* The three identity archives are identity *today*. Nothing enforces it; a change
|
||||
to `is_build` moves every ordinal on the disc. This is a property of a
|
||||
predicate, not of the format.
|
||||
@@ -56,13 +56,20 @@ reason to measure in this unit rather than with a stopwatch.
|
||||
|
||||
Every sprite in the capture lands on its declared placement:
|
||||
|
||||
| capture quad | declared element (build 11) | declared placement |
|
||||
|---|---|---|
|
||||
| `666x65 @ (307,331)` | `palogo_sqex.t32` 666×68 | (309,330) |
|
||||
| `525x90 @ (378,155)` | `palogo_gamearts_eff.t32` 521×91 | (379,154) |
|
||||
| `262x108 @ (512,306)` | `palogo_seta_eff.t32` 261×110 | (511,305) |
|
||||
| `499x72 @ (390,162)` | `palogo_gamearts.t32` 500×71 | (390,164) |
|
||||
| `243x86 @ (518,317)` | `palogo_seta.t32` 240×89 | (521,316) |
|
||||
⚠️ The rows span **two** bundles, not one: `palogo_sqex` is in `--all --build 10`
|
||||
and the `gamearts`/`seta` group is in `--all --build 11`. This table said
|
||||
"build 11" over all five until a corpus-wide index audit
|
||||
([`structures/build-ordinal-vs-entry.md`](structures/build-ordinal-vs-entry.md))
|
||||
checked the header against the bundle. Every placement below re-verified and
|
||||
correct; only the label was wrong.
|
||||
|
||||
| capture quad | declared element | in bundle | declared placement |
|
||||
|---|---|---|---|
|
||||
| `666x65 @ (307,331)` | `palogo_sqex.t32` 666×68 | `--all --build 10` | (309,330) |
|
||||
| `525x90 @ (378,155)` | `palogo_gamearts_eff.t32` 521×91 | `--all --build 11` | (379,154) |
|
||||
| `262x108 @ (512,306)` | `palogo_seta_eff.t32` 261×110 | `--all --build 11` | (511,305) |
|
||||
| `499x72 @ (390,162)` | `palogo_gamearts.t32` 500×71 | `--all --build 11` | (390,164) |
|
||||
| `243x86 @ (518,317)` | `palogo_seta.t32` 240×89 | `--all --build 11` | (521,316) |
|
||||
|
||||
(A quad runs a few pixels under its sprite; that offset is already recorded in
|
||||
`ui-title-paint-order-capture.md` and is not what is being measured here.)
|
||||
|
||||
Reference in New Issue
Block a user