re: F2 -- no gain field in tables.pak, with the two unchecked sites named

The port has no gain anywhere and confirm sits 3 dB above the music. Asked
the disc rather than choosing a number.

Five audio-bearing objects in tables.pak, zero tokens matching VOL GAIN
LEVEL DB ATTEN AMP MIX LOUD. Control passes: the same matcher finds 38
SE_UI hits, so "0 hits" is not a broken matcher.

The negative is stronger than a name search usually is, and I expected it
not to be. Dumping the schema shows the token stream is value-then-key
pairs -- "40, LINE_PITCH", "0, Y_OFFSET_ANALOG_STICK" -- so NUMBERS ARE
TOKENS AND THEY CARRY NAMES. A gain in this format would have a name, and
the name search covers exactly the space where it would live. That turns "I
did not find one" into "one is not there in this file".

Reach, and it is why this is undecodable rather than decoded: I did not
check the .slb bank headers, which is the other conventional home for a
per-wave gain beside a wave index -- and is where the play-test's own
framing points. No .slb exists as a loose file on the extracted disc, so it
costs a pak extraction I did not have budget for; it is the first thing the
next attempt should do. Nor did I check the executable, where a mix could
be immediates in the sound-play path around sub_821C5580.

So this is not "the mix is not on the disc". It is "the mix is not in the
table where a cue's fields live".

Pointer, not a finding: object #15 lists po_sound_scr.prt -> SOUND among
GP_OPTIONS' screens, so a user-facing sound options screen exists and at
least one volume is runtime state.

Port keeps authoring nothing yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
This commit is contained in:
sylph-decoder
2026-09-02 17:08:07 +00:00
parent 1a8cb53e8c
commit be09966870
2 changed files with 152 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
//! F2 — is a per-cue or per-bus GAIN on the disc?
//!
//! The port has no gain value anywhere in its export: `confirm` peaks at
//! 0.0 dBFS and sits 3 dB above the music. A cue record commonly carries a
//! volume beside its wave index. This asks the disc directly rather than
//! choosing a number.
//!
//! Method: dump every token of every `tables.pak` object whose tokens mention
//! SOUND/BANK/SE/BGM, so a gain field would appear as a token if one exists.
//! ⚠️ A NEGATIVE here is only as good as its coverage, so this prints the token
//! count per object and does not filter — a field missed by a filter would read
//! exactly like a field that is not there.
//!
//! cargo run --release -p sylpheed-formats --example sound_cue_fields -- $SYLPHEED_DISC
use sylpheed_formats::{idxd::IdxdObject, pak::PakArchive};
fn main() {
let a: Vec<String> = std::env::args().collect();
let root = a.get(1).cloned().unwrap_or_else(|| std::env::var("SYLPHEED_DISC").unwrap());
let arc = PakArchive::open(format!("{root}/dat/tables.pak")).unwrap();
// Anything a gain would plausibly be called, plus the audio nouns.
const GAINY: &[&str] = &["VOL", "GAIN", "LEVEL", "DB", "ATTEN", "AMP", "MIX", "LOUD"];
let mut audio_objs = 0usize;
let mut gain_hits: Vec<(usize, String)> = Vec::new();
for (i, e) in arc.entries().iter().enumerate() {
let Ok(b) = arc.read(e) else { continue };
let Ok(o) = IdxdObject::parse(&b) else { continue };
let t = o.tokens();
let up: Vec<String> = t.iter().map(|s| s.to_uppercase()).collect();
let is_audio = up.iter().any(|s| {
s.contains("SOUND") || s.contains("BANK_") || s.starts_with("SE_") || s.starts_with("BGM_")
});
if !is_audio { continue }
audio_objs += 1;
println!("audio object #{i}: schema {:08x}, {} tokens", o.schema_hash, t.len());
for (j, tok) in up.iter().enumerate() {
if GAINY.iter().any(|g| tok.contains(g)) {
gain_hits.push((i, t[j].clone()));
}
}
}
println!("\naudio-bearing objects examined: {audio_objs}");
println!("tokens matching {GAINY:?}: {}", gain_hits.len());
for (i, tok) in &gain_hits {
println!(" object #{i}: {tok}");
}
if gain_hits.is_empty() {
println!("\n==> NO gain-like token in any audio object of tables.pak.");
}
// CONTROL: the search must be able to FIND a token when one is present.
// Without this, "no hits" is indistinguishable from a broken matcher.
let mut ctrl = 0usize;
for e in arc.entries() {
let Ok(b) = arc.read(e) else { continue };
let Ok(o) = IdxdObject::parse(&b) else { continue };
ctrl += o.tokens().iter().filter(|s| s.to_uppercase().contains("SE_UI")).count();
}
println!("\nCONTROL — the same matcher looking for a token known to exist (\"SE_UI\"): {ctrl} hits");
println!(" {}", if ctrl > 0 { "PASS: the matcher finds tokens that are there" }
else { "FAIL: matcher is broken, the negative above means nothing" });
}