re: DIFFICULTY is a dialog -- DLG_SELECT_DIFFICULTY, GP_DIALOG entries 2/3
Closes a negative of mine whose failed assumption I had named: I searched for an 8-record btn-named build in an archive of its own, assuming DIFFICULTY's four items pair with f variants the way GP_TITLE's screens do. It has its own prefix and is not a GamePart screen at all. Three independent routes agree. The image lists DLG_SELECT_DIFFICULTY among the DLG_* dialog names at 0x820A41BB, and GP_DIFFICULTY appears zero times. GP_DIALOG entries 2/3 are the only builds there with pcbtn00..03 -- four buttons at design rows 259/329/399/469, spacing 70, an EN/JP pair. And my capture of the running screen puts its four rows within 4 px of those, with spacing 70.5/69.5/70.0 against the disc's 70/70/70. Reach stated: the entries are identified by button count and geometry, not by a binding from the DLG_ name to a pak entry. No such binding was found. Also notes the consequence for Q6's count-match: NEW GAME opens a DIALOG from an external archive, which still matches the count but is not the same category as OPTIONS or TUTORIAL opening a GamePart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
This commit is contained in:
46
crates/sylpheed-formats/examples/dialog_button_rows.rs
Normal file
46
crates/sylpheed-formats/examples/dialog_button_rows.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
//! Is `GP_DIALOG` entry 2 the `DLG_SELECT_DIFFICULTY` screen?
|
||||
//!
|
||||
//! The image lists `DLG_SELECT_DIFFICULTY` among the `DLG_*` names at
|
||||
//! `0x820A41BB`, so DIFFICULTY is a DIALOG, not a GamePart screen with its own
|
||||
//! pak — which is why a search for an 8-record `btn` build in a difficulty-named
|
||||
//! archive found nothing. `GP_DIALOG` entry 2 carries `pcbtn00`..`03`: four
|
||||
//! buttons, matching EASY / NORMAL / HARD / BACK.
|
||||
//!
|
||||
//! CONTROL: `GP_TITLE` entry 5's five buttons must come back at the rows the disc
|
||||
//! is independently known to place them (162/242/322/401/482, spacing 80).
|
||||
//!
|
||||
//! cargo run -p sylpheed-formats --example dialog_button_rows
|
||||
use sylpheed_formats::{pak::PakArchive, ui_layout};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn rows(ar: &PakArchive, entry: usize, what: &str) {
|
||||
let Ok(by) = ar.read(&ar.entries()[entry]) else { return };
|
||||
let Some(b) = ui_layout::parse_build(&by) else { return };
|
||||
let mut v: Vec<(i32, String)> = b
|
||||
.elements
|
||||
.iter()
|
||||
.filter(|e| {
|
||||
let n = &e.name;
|
||||
(n.starts_with("pcbtn") || n.starts_with("ptbtn")) && !n.contains('f')
|
||||
})
|
||||
.map(|e| (e.rest().map(|k| k.y).unwrap_or(e.pivot_y as i32), e.name.clone()))
|
||||
.collect();
|
||||
v.sort_by_key(|r| r.0);
|
||||
println!("\n{what} (entry {entry}):");
|
||||
for (y, n) in &v {
|
||||
println!(" y {y:5} {n}");
|
||||
}
|
||||
if v.len() > 1 {
|
||||
let sp: Vec<i32> = v.windows(2).map(|w| w[1].0 - w[0].0).collect();
|
||||
println!(" spacing {sp:?}");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
|
||||
let t = PakArchive::open(root.join("dat/GP_TITLE.pak")).expect("GP_TITLE");
|
||||
rows(&t, 5, "CONTROL: GP_TITLE main menu (must be 162/242/322/401/482)");
|
||||
let d = PakArchive::open(root.join("dat/GP_DIALOG.pak")).expect("GP_DIALOG");
|
||||
rows(&d, 2, "GP_DIALOG candidate for DLG_SELECT_DIFFICULTY");
|
||||
rows(&d, 3, "GP_DIALOG entry 3 (the pair)");
|
||||
}
|
||||
66
crates/sylpheed-formats/examples/find_difficulty_names.rs
Normal file
66
crates/sylpheed-formats/examples/find_difficulty_names.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
//! Where is the `DIFFICULTY` screen? Search by NAME, not by structure.
|
||||
//!
|
||||
//! A previous pass searched every pak for a build with exactly 8 `btn`-named
|
||||
//! records, on the assumption that DIFFICULTY's four items (EASY / NORMAL / HARD
|
||||
//! / BACK) pair with `f` focus variants the way GP_TITLE's screens do. Nothing
|
||||
//! plausible turned up, and the assumption was mine — recorded as a negative
|
||||
//! narrower than "not found" (data/gp-title-holds-three-button-screens.txt).
|
||||
//!
|
||||
//! This drops the structural assumption and looks for the words instead, across
|
||||
//! every sprite AND record name in every build on the disc.
|
||||
//!
|
||||
//! CONTROL: the same scan must find `ptbtn11` in GP_TITLE — a name whose home is
|
||||
//! independently known — when asked for it. A name scan that finds nothing
|
||||
//! proves nothing unless it can find something.
|
||||
//!
|
||||
//! cargo run -p sylpheed-formats --example find_difficulty_names
|
||||
use sylpheed_formats::{pak::PakArchive, ui_layout};
|
||||
use std::path::PathBuf;
|
||||
|
||||
const WANTED: &[&str] = &["easy", "normal", "hard", "diff", "level", "rank"];
|
||||
|
||||
fn main() {
|
||||
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
|
||||
let mut paks: Vec<_> = std::fs::read_dir(root.join("dat"))
|
||||
.expect("dat")
|
||||
.filter_map(|e| e.ok().map(|e| e.path()))
|
||||
.filter(|p| p.extension().map(|x| x == "pak").unwrap_or(false))
|
||||
.collect();
|
||||
paks.sort();
|
||||
|
||||
let mut control = false;
|
||||
let mut hits = 0usize;
|
||||
for p in &paks {
|
||||
let Ok(ar) = PakArchive::open(p) else { continue };
|
||||
let pname = p.file_name().unwrap().to_string_lossy().to_string();
|
||||
for (i, e) in ar.entries().iter().enumerate() {
|
||||
let Ok(by) = ar.read(e) else { continue };
|
||||
let Some(b) = ui_layout::parse_build(&by) else { continue };
|
||||
let all: Vec<String> = b
|
||||
.sprites
|
||||
.keys()
|
||||
.chain(b.records.keys())
|
||||
.cloned()
|
||||
.collect();
|
||||
if pname == "GP_TITLE.pak" && all.iter().any(|n| n.contains("ptbtn11")) {
|
||||
control = true;
|
||||
}
|
||||
let m: Vec<&String> = all
|
||||
.iter()
|
||||
.filter(|n| {
|
||||
let l = n.to_lowercase();
|
||||
WANTED.iter().any(|w| l.contains(w))
|
||||
})
|
||||
.collect();
|
||||
if !m.is_empty() {
|
||||
hits += 1;
|
||||
let mut s: Vec<String> = m.iter().map(|x| (*x).clone()).collect();
|
||||
s.sort();
|
||||
s.dedup();
|
||||
println!(" {pname:28} entry {i:3} {:?}", &s[..s.len().min(6)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("\ncontrol (found ptbtn11 in GP_TITLE): {}", if control { "PASSED" } else { "FAILED" });
|
||||
println!("{hits} build(s) carried a difficulty-ish name");
|
||||
}
|
||||
66
docs/re/data/difficulty-is-a-dialog.txt
Normal file
66
docs/re/data/difficulty-is-a-dialog.txt
Normal file
@@ -0,0 +1,66 @@
|
||||
# Where does the DIFFICULTY screen live? ✅ DECODED 2026-08-31.
|
||||
# It is a DIALOG -- `DLG_SELECT_DIFFICULTY`, GP_DIALOG.pak entries 2/3.
|
||||
#
|
||||
# CLOSES A NEGATIVE OF MINE. gp-title-holds-three-button-screens.txt recorded
|
||||
# "not an 8-record btn-named build anywhere on the disc", with the failed
|
||||
# assumption named as mine: I assumed DIFFICULTY's four items pair with `f` focus
|
||||
# variants in an archive of its own, the way GP_TITLE's screens do. Both halves
|
||||
# of that were wrong -- it has its own button prefix and it is not a GamePart
|
||||
# screen at all.
|
||||
#
|
||||
################################################################################
|
||||
# ROUTE 1 -- THE IMAGE. /image/sylpheed.pe, 3 occurrences of "DIFFICULTY":
|
||||
# 0x820A2548 STAGE | DIFFICULTY | TITLE | FADE | BASE_EXTRA ...
|
||||
# (a GamePartTask::RegisterToFactory name list)
|
||||
# 0x820A3377 RECORD_DIFFICULTY (a results/record field)
|
||||
# 0x820A41BB DLG_LEADERBOARD_MENU_NEXT | DLG_SYSTEM_PAUSE |
|
||||
# **DLG_SELECT_DIFFICULTY** | DLG_MISSION_OBJECTIVE |
|
||||
# DLG_MESSAGE_BOX | DLG_MESSAGE_BOX_YES_NO | ...
|
||||
#
|
||||
# ✅ The third is the answer: DIFFICULTY is one of the game's DLG_* dialogs.
|
||||
# ⚠️ "GP_DIFFICULTY" appears 0 times in the image, which is consistent.
|
||||
#
|
||||
################################################################################
|
||||
# ROUTE 2 -- THE DISC. GP_DIALOG.pak entries 2 and 3 are the only builds in that
|
||||
# archive carrying `pcbtn00`..`pcbtn03` -- FOUR buttons, matching
|
||||
# EASY / NORMAL / HARD / BACK. Design rows and spacing:
|
||||
#
|
||||
# entry 2 y 259 / 329 / 399 / 469 spacing 70, 70, 70
|
||||
# entry 3 identical (the EN/JP pair, as everywhere else)
|
||||
#
|
||||
# ✅ CONTROL: the same reader on GP_TITLE entry 5 returns 162/242/322/401/482,
|
||||
# spacing 80 -- the rows that archive is independently known to place.
|
||||
#
|
||||
################################################################################
|
||||
# ROUTE 3 -- THE ORACLE. My own capture of the running DIFFICULTY screen
|
||||
# (captures/menu-nav/live-difficulty-opens-normal.png), with the disc-grounded
|
||||
# calibration capture_y = 64.82 + 0.9919 * design_y:
|
||||
#
|
||||
# design row predicted measured in the capture residual
|
||||
# 259 321.7 323.5 +1.8
|
||||
# 329 391.2 394.0 +2.8
|
||||
# 399 460.6 463.5 +2.9
|
||||
# 469 530.0 533.5 +3.5
|
||||
# measured spacing 70.5 / 69.5 / 70.0 against the disc's 70 / 70 / 70
|
||||
#
|
||||
# Four rows, all under 4 px, with the spacing agreeing exactly. The residual is a
|
||||
# uniform ~+2.8 px offset, which is the calibration's own systematic and not a
|
||||
# mismatch.
|
||||
#
|
||||
# => THREE INDEPENDENT ROUTES: the executable names it a dialog, the disc has a
|
||||
# four-button dialog build, and the running game draws its rows where that
|
||||
# build says they are.
|
||||
#
|
||||
# 📌 WHY IT MATTERS BEYOND THE LOCATION: DIFFICULTY being a DIALOG explains why
|
||||
# NEW GAME's destination is not a screen in GP_TITLE, and it means the "four menu
|
||||
# items load an external archive" reading of the event numbers
|
||||
# (boot-config-and-gamepart-registry.md's count-match) is looser than it looked --
|
||||
# NEW GAME opens a dialog from GP_DIALOG.pak, which is an external archive, but a
|
||||
# dialog is not the same kind of thing as OPTIONS or TUTORIAL opening a GamePart.
|
||||
# The count still matches; the categories are not uniform.
|
||||
#
|
||||
# ⚠️ REACH: entries 2/3 are identified by button count and geometry, not by a
|
||||
# name binding DLG_SELECT_DIFFICULTY to a pak entry. No such binding was found --
|
||||
# the DLG_* names live in a string list, and what maps a name to an archive entry
|
||||
# is not decoded. Another 4-button dialog with the same rows would be
|
||||
# indistinguishable by this evidence.
|
||||
Reference in New Issue
Block a user