Commit Graph

546 Commits

Author SHA1 Message Date
Sylpheed RE agent
214ae8fca2 re: resolved -- the REGN base is chunk+0x10 and my section-0 offsets were 16 bytes early
The other branch supplied concrete offsets: for 3506e972 its face record 0 and
its plane normal both begin at 0x1c700, and my chunk + offset_at_0x78 + 16 gives
0x1c700. Same bytes, different bookkeeping -- so the n.p+d result stands
unchanged and was never in dispute.

The base is chunk+0x10, on evidence with power: the loader does addi r3,r31,16;
at +0x10 the six POF0-relocated slots land exactly on 0x70-0x84, the six section
pointers, whereas at +0 they would relocate the u16 counts and leave two section
pointers unrelocated, which is non-functional; and section-0 record 0 reads as a
bbox corner at +0x10 and garbage at +0.

So my '13467/13467 points inside the bbox' was vacuous. Only 11 of 13467 read as
denormal at the wrong base -- the rest were still plausible coordinates, because
a 16-byte shift inside a packed array of f32 triples yields other floats from
the same array. Recorded the general form: a containment test cannot detect a
shift inside a homogeneous array, because the shifted values come from the same
distribution. For that class of error it is not a weak check, it is no check.

'Section 0 is a point list' happens to be right; the evidence I gave for it was
not evidence.
2026-08-26 08:00:18 +00:00
Sylpheed RE agent
84358e61eb re: record an open conflict with auto/regn-reader over the +0x10 base
That branch decodes REGN as a tetrahedral navmesh with strong checks and claims
the POF0 fixup base is chunk+0x10, so every offset on this page was read 16
bytes early.

I could not reproduce that on the one independently checkable thing here: the
plane list gives 133573/133573 unit normals at the unshifted base and 0/133573
at +16, and n.p+d = 0 holds to float round-off unshifted. A 16-byte shift
destroys it, so the blanket statement does not hold for this record.

Likely reconciliation is bookkeeping: a 48-byte face whose plane fields sit at a
different intra-record offset addresses the same bytes from a different origin.
That is a guess and I am not adopting either wording until checked.

Also confirmed: my own section-0 point test passes at 100% at BOTH bases, so it
never had power to distinguish them and should not be cited as validating the
offsets.
2026-08-26 07:55:12 +00:00
Sylpheed RE agent
67cce0e4c0 re: correct my last two savegame sections -- they analysed the COMPRESSED stream
Both preceding sections split the file at 0x88 and called everything after it
the slot table. Wrong: this page already documents the container as GDHA + a
146-byte header + a zlib stream, and savegame.rs implements it. 0x88 is where
the Z1/zlib payload begins (5a31 = 'Z1', 78da = zlib), so the bytes I treated as
slot fields are deflate output.

Invalidated: the '6 pointer-shaped words in the slot region' are not words and
not pointers, so the false-positive rate I derived from them measured nothing.

Survives with different reasoning: 'the slot region is byte-identical' is true
because the two COMPRESSED streams are identical, which does imply identical
payloads -- and inflating both confirms it, 545 bytes byte-for-byte equal.

Stands and is better founded: all 12 differing words are in the header, which is
the only uncompressed region and therefore the only place a word-wise diff means
anything.

Done correctly, the result is a confirmation rather than a discovery: inflating
all three payloads reproduces the documented clear-ratio field stepping 5 -> 6
on the developed save.

The lesson: I found Z1/78da by inspection and nearly wrote it up as new. It was
already decoded four sections above where I was appending. Reading the whole
page first would have saved two wrong commits, and the wrongness was not
cosmetic -- a pointer census ran over deflate output and produced a
plausible-looking table.
2026-08-26 07:50:29 +00:00
Sylpheed RE agent
91074d1ba7 re: all savegame variation is in the header; slot region identical -- and my heuristic overstates
Splitting the same comparison at the documented slot-table boundary (save+136):
the header holds 34 words and ALL 12 differing words, while the slot table holds
35 words and ZERO differences. The slot region is byte-identical between the two
same-state saves, so the documented slot fields are untouched by the churn and a
slot-region diff is meaningful where a whole-file diff is not.

Also corrects my own framing from the previous commit. The slot region contains
6 words in the pointer value range that do NOT change across runs -- a pointer
captured from a moving heap would not sit still, so those are data whose values
merely land in 0x70-0x8F / 0xB0-0xBF. The value-range test alone does not
identify a pointer; a third of what it flags here is not one. What identifies a
pointer is being pointer-shaped AND varying, ideally with a shared constant
delta. So '26% of the file is heap addresses' overstates it and the defensible
figure is the 8 header words that are both.

Left the earlier section in place with the correction after it: a value-range
classifier on 32-bit words always has a false-positive rate, and quoting its raw
count as a fact about the format is the error.
2026-08-26 07:44:55 +00:00
Sylpheed RE agent
2aa5894212 re: the save embeds live guest pointers, and a three-way diff of the captures is invalid
Comparing the two same-state saves (both 276 bytes), only 12 of 69 u32 words
differ, and nine of those carry guest addresses -- 0xBC/0xBD/0x70 prefixes, the
same regions the runtime work uses. Three differ by exactly 0x101080 and two by
exactly 0x300000: a shared constant offset is what a relocated heap does to a
pointer and not what data does.

The split is sharp: pointer-shaped words are 26% of the file and 44% of them
differ; non-pointer words are 8%. So a quarter of this structure is captured
heap addresses that change run to run regardless of play. That is the concrete
form of the page's existing 'much of the rest is uninitialised memory', now
demonstrated by constant deltas rather than inferred from odd-looking values.

Also records that my first pass was wrong: a byte-wise three-way diff reported
135 of 276 bytes differing, but game03 is 280 bytes with a ZERO-byte common
suffix, so offsets do not correspond past 0x00A. The valid equal-length pair
gives 30 bytes in 12 words. The inflated figure looked plausible -- half a save
changing is what dense state would do -- which is why it is written down.
2026-08-26 07:39:13 +00:00
Sylpheed RE agent
7c1507a466 re: REGN cell payload indexes neither points nor planes -- static search exhausted
The previous test only tried section-1 targets. Closing that gap: the payload's
three index-shaped u32s, followed into the point list and the plane list and
checked for the target lying inside the referencing cell, all sit at the 0.203%
random control.

Two cells read 0.81%, 4x the baseline. I am not treating that as a lead: across
this and the previous iteration roughly twenty such tests have been run, and at
that count a single 4x enrichment on ~8000 trials is what noise looks like.
Calling it a signal would be the multiple-comparisons error a long hypothesis
sweep invites.

So REGN's header, grid, points, planes and cell index are decoded, section 1's
slot regions are censused, and the link between the grid and the geometry is not
reachable by any static test I can construct. The honest next step is the PE
code that reads a REGN object -- the same kind of work that cracked the .slb
packing phase -- rather than a twenty-first correlation.
2026-08-26 07:32:08 +00:00
Sylpheed RE agent
1006523767 re: the REGN cell index does not reference section 1 -- two powered tests
The natural coupling in a file with a uniform grid and a list of small volumes
is that the grid indexes the volumes. Tested by spatial agreement, it does not.

Test 1: every u16 in a cell's 32-byte payload, tried as a section-1 index and
checked for its centre lying inside the referencing cell. Every field sits at
the 0.138% random-control rate.

Test 2: every float triple in the payload, checked for lying inside its own
cell. 0.15-0.81%, also chance.

Recorded a worthless number from the same run rather than dropping it: those
triples lie inside the object's BOUNDING BOX in 100.00% at five different
offsets. The bbox spans the whole 500 km map so any mid-range triple passes, and
overlapping windows at +0 and +4 both scoring 100% is the tell -- a real field
would not survive a four-byte shift. Third time in this investigation that a
containment test against something large has produced a meaningless 100%.

Incidental and real: u32 slots at +0, +8 and +12 are below 0x10000 in 100% of
payload records while +4/+16/+20/+24 are in 11% and +28 never, so the record has
three index-shaped fields and four wide ones.
2026-08-26 07:26:45 +00:00
Sylpheed RE agent
a20c5d56f9 re: refute my own BVH reading of REGN section 1
Last iteration I offered 'position + scalar + integer links is the shape of a
BVH node' as a reading of the shape. Tested properly it fails.

Following every u16 half of every integer slot and checking child-sphere-inside-
parent-sphere gives 0.00% for every candidate -- but the informative number is
the RANDOM control, also 0.00%. No node's sphere contains any other node's
sphere anywhere in the file, so there is no nesting for an index to point at and
the hypothesis dies before the indices matter.

The reason is scale: slot 7 has a median of 3139 against a median inter-node
distance of 45457, 14x smaller, and a random other centre falls within it 0.40%
of the time. It is also smaller than the smallest grid cell on any map.

So slot 7 is a LOCAL scale, not a hierarchy radius. 63410 scattered centres each
with a sub-cell extent is the shape of many small independent volumes, which
would fit per-object collision hulls for asteroids and debris -- a reading, not
a measurement.

What this removes is a wrong frame: the file is not a tree, so tree-shaped tests
will keep returning nothing.
2026-08-26 07:20:39 +00:00
Sylpheed RE agent
488e8599b4 re: REGN section 1 slot census, and an index test with no power
96 bytes is 24 slots. Over all 63410 records: slots 4-6 hold values in the
header bbox range (a position), slot 7 is always positive 519..107600 (a radius
or extent), slots 8-11 are DENORMAL as floats -- 1.4e-45 upward -- so they are
integers a float reader would turn into near-zero garbage, and slots 12-23 are
six pairs with distinct even/odd distributions. Slots 2 and 3 are ~always zero.

Position + positive scalar + integer links is the shape of a BVH node, which
would fit a file carrying a point list and a plane list. That is a reading of
the shape and nothing more.

Recorded a failed test and why it failed: splitting the integer slots into u16
halves and checking them against each section's record count accepts ALL THREE
sections at ~100% for slots 8 and 9. A test that accepts every hypothesis
rejects none -- section 2 has tens of thousands of records, so the check
measures the section's size rather than the field's meaning. Slot 11's halves
are consecutive in 54%, which is suggestive and not a rule.

What would settle it is a test with power: follow a candidate index and check
the target is spatially consistent with the record's own position and radius.
2026-08-26 07:14:39 +00:00
Sylpheed RE agent
eb278c7941 re: REGN sections 0/1/2 have strides, and section 2 is a plane list
The three sections recorded as undecoded are fixed-stride arrays and counts[0..2]
are their record counts: 12, 96 and 48 bytes. Section 1's remainder is exactly 0
in 11/11 objects and section 2's exactly 96 in 11/11, which is what makes these
strides rather than a coincidence of division.

Section 0 is a point list: 13467 of 13467 records lie inside their object's own
header bounding box.

Section 2 is a plane list, 12 f32: four zeros, a unit normal (|n|=1 in
133573/133573), a signed distance, a point inside the bbox (133573/133573), and
a trailing 1.0 (133573/133573). The decisive check is algebraic -- n.p + d must
vanish for a real plane, and over all 133573 records the relative residual has a
median of 2.29e-08 and a maximum of 2.15e-07. That is float round-off, not a fit.

So a REGN object carries a point list and a plane list beside its uniform grid,
which fits collision or region-boundary geometry and sits next to MCOL.

Still open: section 1 (96 B, 60631 records), what queries the planes, the zeros
at [0..3], and the constant 96-byte tail.
2026-08-26 07:07:10 +00:00
Sylpheed RE agent
fd71f37d8f re: RATC +0x10 is a u16 pair too, and no bit is a clean predicate
The u16 at +0x10 is 0 in all 2985 bundles; the content is a 16-bit flag word at
+0x12 with 83 distinct values. Reading it as a u32 inflates the field and hides
that the header is built from u16 pairs -- the same shape +0x0c turned out to
have. All 16 bits are used, from 1.4% to 91.5%.

Cross-tabulated every bit against four properties measurable from the bundle:
multi-element, animated, window-starts-at-zero, 30fps. No bit is close to a
clean predicate. The strongest is bit 10 against window-at-zero, 0.79 vs 0.21 --
a real association but not a rule, and exactly the kind of moderate split that
invites over-reading.

Bit meanings stay open, but four candidate readings are excluded rather than
untried and the field is correctly sized. Every property visible in the file has
now been tried, so assigning meanings likely needs the game observed with
individual bundles loaded.
2026-08-26 06:59:08 +00:00
Sylpheed RE agent
a191569167 re: the RATC +0x0c interval is authored, not a keyframe extent
Tested the alternative I recorded last iteration. Parsing every bundle's
keyframe times (2985/2985 parse), the derived-summary reading -- (high,low) ==
(min,max) keyframe time -- holds in 6 of 2985 (0.2%).

The apparent 34.2% match on 'high == min' is a coincidence of zeros: the minimum
keyframe time is 0 in 96% of bundles and high is 0 in 34.9%, so the 1022
'matches' are exactly the both-zero cases. Worth noting that last iteration I
declined to treat the high==0 share as support -- it turned out to be the
confound rather than the signal.

The interval is also narrow: (low-high)/(max-min) has a median of 0.019, about
2% of the keyframe span. It lies inside the keyframe range in 88.6%, entirely
after in 174 and entirely before in 68.

A short authored window is not the shape of a playback range or a whole-animation
loop region, so those readings weaken too. What it is stays open.
2026-08-26 06:52:44 +00:00
Sylpheed RE agent
1fc68e391d re: RATC +0x0c is two u16s forming an ordered interval, not one number
Read as a u32 it looks meaningless -- 179 distinct values up to 248581842. The
raw values give it away (0x0007000F, 0x000F001A, 0x003C0064): two big-endian
u16s. Over all 2985 bundles, high < low in 2985/2985 with no equal and no
inverted cases, and both are bounded by the animation length at +0x08. Span
runs 1-1200, clustering on 1/10/30/8/20; low equals the animation length in 4%.

A strict ordering holding 2985 times rules out flags or a packed count. Which
interval it is stays yellow -- playback range, loop region and active window all
fit equally.

Recorded the alternative I did NOT test: that (high, low) is simply the min and
max keyframe time, making it a derived summary rather than an authored range.
First step written down. The 34.9% of bundles with high == 0 leans against it
but is not evidence on its own.
2026-08-26 06:46:50 +00:00
Sylpheed RE agent
0800e40c80 re: lead the .slb page with its settled state, not its history
The page is 724 lines of chronological record across 16 sections, several of
them superseded readings kept for their reasoning. A reader arriving fresh had
to read all of it to know what is currently true.

Adds a summary table at the top covering the twelve questions the page answers,
each with its confidence, plus the wave-enumeration recipe and an explicit note
that everything below is history. Also names the four mistakes recorded below so
a reader knows they are deliberate rather than stale.
2026-08-26 06:39:40 +00:00
Sylpheed RE agent
2634632787 re: refute the block-id chain -- 8 of 281 is coincidence
Last iteration I noticed BR09_04's block id names BR10_03, itself the next
exception entry, and flagged it as suggestive but untested. Tested: sorting all
282 by offset, entry[i].field equals entry[i+1]'s own id in 8 of 281 (2.8%),
indistinguishable from chance -- and BR09_04 was one of the eight.

Recorded rather than dropped because it is exactly the kind of pattern that
reads as a discovery when spotted in a sample of one.

The offset from the entry's own id is broadly spread, 7 to 14 and beyond with a
peak at 9-11, so the field is near the entry's id without standing in any fixed
relation to it.
2026-08-26 06:34:59 +00:00
Sylpheed RE agent
f189f5fc1f re: the scattered block bytes are an XACT-shaped cue record naming a real cue
Not noise -- a small structured record in three runs at about +1790, +3840 and
+7940. Across all 282: the leading 16-bit value names a real SOUNDS cue
282/282, the block carries two IEEE 1.0 floats in 281, and an 01/02/02/0x64 tail
in 281. Two unit floats and a 100 are the shape of volume, pitch and priority,
so this reads as an XACT cue record -- marked yellow, since every value is a
default and nothing varies enough to prove it.

The id is NOT this entry's own cue: it is consistently a different, higher one
(8501 -> 8504 = BR02_01; 5027 -> 5036 = VOICE_A_036), with a varying offset so
not a fixed stride. BR09_04's field names BR10_03, which is itself the next such
entry -- suggestive of a chain, but one observation and untested.

The practical point, which is settled: the block is a populated metadata record
naming a real cue, not padding.
2026-08-26 06:29:06 +00:00
Sylpheed RE agent
effda99390 re: w0 and w1 are the last two entries of the leading wave's seek table
Both, 282/282 exact: w0 == table[-2], w1 == table[-1]. The trailer's first two
words are a verbatim copy of the tail of the wave's own seek table.

That closes the question and kills the 'usable length or loop end' reading of
w0 -- it is just the previous seek point. It also explains why w1-w0 is always a
multiple of 512 without needing a separate fact: consecutive seek points advance
by whole 512-sample frames, so adjacent entries always differ by a whole number
of them. What I had recorded as a meaningful constraint was a property of the
table the values were copied from.

Neither word is the wave's true length -- w1 lands within 512 of the
PsuedoBytesPerSec length in only 9 of 282, consistent with the correction above.

Still unidentified: the ~40 scattered non-zero bytes elsewhere in the block.
2026-08-26 06:22:56 +00:00
Sylpheed RE agent
640d860b23 re: the seek table's sample total is not the wave length -- durations corrected
Chasing w0 I decoded leading waves and the output matched neither trailer word.
Following that into RIFF waves, where the extent is certain, the decoded sample
count exceeds the seek table's last cumulative sample by a median 9.7%.

The tiebreak is the bank's own PsuedoBytesPerSec: over 14 banks it agrees with
an actual FFmpeg decode to a mean of 0.007 s and with the seek-table duration
only to 0.287 s. Two independent quantities agree with each other and both
disagree with the seek total, so that last entry is the last SEEK POINT, not the
sample count.

Both artifacts regenerated with data_bytes / PsuedoBytesPerSec. Total audio is
408.3 minutes, not the 390.9 I published -- 4.3% in aggregate. Dialogue timings
all rise, e.g. 'They got Leader!' from 1.78/2.46 s to 2.25/2.93 s.

What made this hard to catch is worth recording: the seek total gave BGM of 2.4
minutes, chatter of 2.8 seconds and cutscenes of 11 minutes, and I cited that as
the chain validating itself. It validated the structure, not the scale -- a
uniform 10% error preserves every ratio I checked.
2026-08-26 06:17:07 +00:00
Sylpheed RE agent
00e9a932ea re: name two fields inside the 12288-byte block's trailer
The trailer opens with two little-endian u32 words. w1 is the leading wave's
total sample count -- 282/282 exact, the same number as the last entry of that
wave's own seek table. And w0 < w1 with w1-w0 always a whole multiple of 512
(282/282), i.e. a whole number of XMA1 frames, 9 to 91 with a median of 14.

What w0 means is left at yellow: trailing the total by a whole number of frames
is the shape of a usable-length or loop-end field, but that is a reading of the
shape rather than a measurement, and nothing here separates the candidates.

Also corrects my own description: I called the region after the seek chunk 'zero
padding'. It is not padding -- about 48 non-zero bytes are scattered thinly
across the 12 KiB, roughly 17 per KiB in blocks 1, 3 and 7. That is a sparse
table, still unidentified, and the distinction matters to anyone skipping it.
2026-08-26 06:09:46 +00:00
Sylpheed RE agent
85548bf661 re: the 12288 region is a padded metadata block, and the assignment rule is 100%
Dumped it. In all 282 exceptions the region from the earlier wave's seek to the
first RIFF is exactly 12288 bytes: the seek chunk (240-260 bytes), then 47-57
non-zero trailer bytes, then zero padding out to the block size.

So an entry may hold a leading wave, a 12288-byte padded block, then its RIFF
wave -- and the assignment rule completes: first-or-second wave at/after the
entry offset is 7620/7620 = 100.00%, with ZERO unexplained.

This overturns my own refutation from one iteration ago. I proposed these were
leading segments, tested it as 'the seek should sit AT the first RIFF', got 0 of
282, and recorded the hypothesis refuted. It was right; my predicate was wrong
by exactly the padded block, a constant I had not yet found. A negative result is
only as good as the predicate it tests.

Still unidentified: what the 47-57 trailer bytes hold.
2026-08-26 06:04:40 +00:00
Sylpheed RE agent
4e180b3e1c re: enumerate all 9661 waves, and measure a bank-to-wave assignment rule
Scanning the flat stream for seek chunks satisfying size == 8+4*count with a
non-negative implied start yields 9661 waves and rejects NOTHING -- no false
positives in a gigabyte of audio. Each wave's extent is
[seek_pos - count*2048, seek_pos).

Assignment rule 'an entry names the first wave starting at or after its offset'
is 96.30% correct (7338/7620) against entries whose answer is independently
known from their RIFF.

The 282 exceptions are one class: the gap between the earlier wave's seek and
the first RIFF is EXACTLY 12288 bytes in all 282. I guessed they were leading
segments, which would put that seek AT the RIFF; refuted, 0 of 282.

Recorded at yellow. Correcting by 12288 would reach 100% on this set but is
curve-fitting until the region is identified, and the rule cannot yet unblock
headerless durations -- there is nothing to check it against there.
2026-08-26 05:58:29 +00:00
Sylpheed RE agent
532e7dac74 re: refuse a seek-based recovery of headerless bank durations
343 English headerless banks carry a valid seek chunk, which would lift dialogue
duration coverage from 62% to ~75%, with a median 3.25 s that looks exactly like
in-mission voice.

It is wrong. A seek sits immediately after its OWN data, so the implied wave
start is seek_pos - packets*2048 -- and for 277 of the 343 that start lies
BEFORE the entry begins, meaning the seek describes the previous bank's wave.
Applying it would attach a neighbour's duration to 81% of the recovered rows.
The 66 that start inside the entry are not proven to be that entry's wave
either; 'starts inside' is necessary, not sufficient.

Recorded as refused rather than applied with a caveat, because a plausible
median is exactly what has misled this work twice today. Recovering these needs
the bank-to-wave assignment settled first.
2026-08-26 05:52:02 +00:00
Sylpheed RE agent
0aed00d4f6 re: stage dialogue with per-line audio durations, in both languages
Joins the ISL script's built-in 64 call sites, the caption table and the sound
bank seek tables. Duration is the last cumulative sample over the sample rate --
read, never decoded. 1663 of 2683 call sites (62.0%) have an English duration,
2198 (81.9%) a Japanese one; the gap is the headerless banks, and which language
lacks one differs per line, so the columns are complementary.

Two columns deliberately: FILES names the jpn banks and the game substitutes the
language directory, so a line's English and Japanese recordings are different
files of different lengths -- 'They got Leader!' is 1.78 s in English and 2.46 s
in Japanese. The captions are English, so eng is the matching column. The first
version of this artifact had one column and would have attached Japanese timings
to English text; that is why it was regenerated.
2026-08-26 05:46:03 +00:00
Sylpheed RE agent
856fd64b3b re: full bank manifest -- 4114 banks, 390.9 minutes, and the durations self-validate
With the wave boundary exact and Channels read rather than assumed, every field
describing a bank can be read: path, cue, sound id, channels, rate, data bytes,
packets, samples, seconds. Duration comes from the last cumulative sample in the
seek table over the sample rate -- arithmetic, no decoding.

Nothing was fitted to an expectation, yet every category lands where its content
says it should: BGM mean 146 s, Movie mean 79 s with an 11-minute maximum,
Briefing 10.8 s, in-mission Voice 2.79 s never exceeding 7.3. If the boundary
rule, the channel field or the seek table were misread, the numbers would not
sort into those five shapes.

Incidental: 4 banks run at 44100 Hz against 48000 everywhere else, and the BGM
tracks are the stereo ones. 1021 of the 5135 FILES paths have no RIFF and are
excluded as headerless.
2026-08-26 05:39:16 +00:00
Sylpheed RE agent
e676210e9b re: close both remaining .slb questions in the backlog -- both were my errors
The declared sizes are honest (seek magic at data_at + declared_size, 7620/7620)
and VOICE_TCAF_608 was stereo decoded as mono, not truncated. The four offsets
are a segment-packing phase, not a per-directory header size. Neither was closed
by finding something new; both were closed by correcting a mistake of mine.
2026-08-26 05:30:57 +00:00
Sylpheed RE agent
c3ff6aa0e8 slb: read Channels instead of assuming mono -- and retract the TCAF_608 conclusion
I read the seek chunk's packet count big-endian; it is little-endian at seek+12,
with size == 8 + 4*count. And a seek sits immediately AFTER its own data, so an
entry's first seek usually belongs to the PREVIOUS bank (implied start -25232
for D_452, -145988 for TCAF_608). I was comparing an entry's first seek against
its first data -- different waves by construction, which is why no reading lined
up.

With that fixed, the declared sizes are honest: every RIFF-bearing entry on the
disc has seek magic at exactly data_at + declared_size with count*2048 ==
declared. 7620/7620, zero failures.

VOICE_TCAF_608 is not truncated. Its Channels is 2 and I decoded it as mono;
read as stereo it gives 6520176 bytes = 33.96 s, agreeing with both length
signals in the bank (33.88 s from cumulative samples, 33.97 s from
PsuedoBytesPerSec). 170 of 8021 banks (2.12%) are stereo -- exactly the rate of
my 1-in-60 outlier.

This is the mono/stereo trap already documented on this very page, met from the
other direction: I had written 'at two channels every bank yields one frame' and
then spent several passes blaming missing data for a one-frame decode.

Code fix: to_xma_riffs built the leading segment with a hard-wired mono fmt. It
now reads Channels from the bank's first RIFF. 7 disc tests pass.
2026-08-26 05:27:42 +00:00
Sylpheed RE agent
b98910cab3 re: fix the slots-vs-lines wording at its actual location
The '3 or 4 wrapped lines' sentence is in isl-message-dialogue-link.md itself,
not in cutscene-message-table.md as my correction claimed. Corrected the
sentence in place -- a page has four line SLOTS, of which an English page fills
1 to 4 -- and fixed the misattribution in the correction note rather than
quietly repointing it.
2026-08-26 05:23:35 +00:00
Sylpheed RE agent
0be28c5fe2 re: sweep all six language packs -- and Japanese is not missing captions
The five European packs have byte-for-byte identical MSG key sets: 18813 keys,
4091 ids, zero differences in any direction. Japanese has 14295 keys, which
looks like 4521 missing captions and is not: its id set matches to within 5, its
page count is the same (4765 vs 4758), and its mean lines per page is 1.52
against English's 1.85 with no 4-line pages at all. It carries the same dialogue
and wraps into fewer lines, which is what a language without inter-word spaces
does in a fixed-width box.

Also corrects my own wording in cutscene-message-table.md, which said a page
'holds 3 or 4 wrapped lines'. That counted key SLOTS, which MSG_DEMO records
allocate four at a time. Counting lines with text, an English page holds 1 to 4
(1574/2439/632/113). And it reconciles the 8800 figure used throughout: that is
distinct English keys with non-empty text, out of 18813 keys total.
2026-08-26 05:23:06 +00:00
Sylpheed RE agent
4b54ac2bf0 re: the seek chunk's layout is readable, its packet count is not
Identified the structure: a little-endian size field after the tag, then
0x01000000, a varying word, a zero, then a strictly ascending table. Sizes 64 /
108 / 348 bytes for three sample banks.

Neither obvious reading gives the declared packet count. Entry count minus the
three header words is exactly right for VOICE_D_452 (13) and wrong for the other
two (24 vs 17, 84 vs 371) -- a one-of-three fit, recorded as FAILED rather than
as a rule with exceptions, because two measurements today already returned
plausible numbers for questions they could not answer.

The ascending values step by 1.5-2.2 million, far too large to be packet
indices, so they are on some other scale. Recorded what the next attempt should
know, including that auto/slb-loader chains seek packet counts successfully by
some field that is not either of the two tried here.
2026-08-26 05:16:12 +00:00
Sylpheed RE agent
931deb42d8 re: withdraw my own generalisation -- decoded length cannot locate a bank boundary
Generalising the TCAF_608 test across the 5296 over-declaring banks first looked
like a clean reversal: 59 of 60 gained audio (median 2.10x) from reading the full
declared size instead of stopping at the TOC window.

Then I tested whether the declared size is an honest boundary by reading TWICE
it. 33 of 40 yield >1.5x more audio, median ratio 1.64. The decoder keeps going
indefinitely, because XMA1 packets are self-contained -- the next bank's packets
decode perfectly well and are simply not this bank's. A byte count cannot tell
them apart.

So the 59/60 is withdrawn as evidence for anything. Same error as the unanchored
'data' search earlier on this page: a measurement returning a plausible number
for a question it cannot answer.

What survives: TCAF_608 is genuinely anomalous (86x more input, zero extra
output, where the typical bank yields without limit); the honest-size question
is unresolved and needs a boundary signal independent of the decoder, such as
the seek-chunk packet chain; and the only measured fact is that the declared
size exceeds the TOC window for 5296 of 7586 banks.
2026-08-26 05:10:58 +00:00
Sylpheed RE agent
e368c7064c re: ran the decode test -- the bank data does not continue past the TOC window
Read VOICE_TCAF_608's full declared 759808 bytes out of the flat stream ignoring
window boundaries. It decodes to the same 896 bytes (0.01 s) as stopping at the
window end -- 86x more input, zero extra samples. Control VOICE_D_452, whose
declared size fits, decodes to 2.18 s normally. No segment join is crossed, so
the packing phase is not a confound.

So 'the bytes are outside the window but still in the stream' does not hold for
the one bank the other branch named as not truncated. I still withdraw my word
'truncated' as an over-claim about the other 5295 banks: I measured that their
declared size exceeds their window, not what lies beyond it, and have now tested
exactly one. Recorded the three claims at their actual strengths.
2026-08-26 05:04:50 +00:00
Sylpheed RE agent
a3c6a6502c re: the four .slb offsets are a segment-packing phase, not a header size
Reproduced independently: X = (cumulative start of the .pNN segment) mod 2048.
The running sums of the five sound.pNN file sizes are 0, 1392, 1468, 1600, 1728
-- exactly the four values measured, plus zero. The XMA grid is 2048-aligned
inside each segment file, the segments are not multiples of 2048 long, so each
join shifts the phase.

Two of my explanations on this page were wrong even though the measurements
stand. 'Varies by language and subdirectory' was correlation, not cause --
directories cluster into segments. And there is no header at all: those bytes
are the PREVIOUS bank's audio, which is why they had no length field and looked
like data.

Also records a disagreement I could not resolve. auto/slb-loader withdraws my
'69.8% over-declare' finding as declared-sizes-are-exact, but VOICE_TCAF_608
declares 759808 bytes of data with 55296 bytes to the next TOC entry. Both
readings agree the declared size exceeds the window; mine said 'truncated',
which I withdraw as an over-claim, but 'exact' requires a wave to span ~14 named
entries. Recorded with the experiment that would decide it.
2026-08-26 05:03:56 +00:00
Sylpheed RE agent
c2e7f8f8e4 re: the flat entity count is explained -- arrivals are not spawns
Built-in 12 activate_unit returns 0 when the live object is NULL: it registers an
object that already exists and cannot create one. So a wave arrival is a craft
reaching a point on its route, not a new record, and a count of entity records
was never going to move.

Stage 02's UnitGroup totals Count = 116 across 111 squadrons, matching the live
probe's flat 116. The natural objection is that 116 might match by accident --
it does not: across all 28 stages the roster totals run 2..116, and 116 is the
MAXIMUM and unique to Stage 02, with nothing else within 5.

Three independent things agree: the handler cannot spawn, the roster totals 116,
and the live count sits at 116 while 22 of 113 routed squadrons have a route
starting after t=0.

Still n=1 for the per-member identity (only a Stage 02 save exists), and this
cannot settle timetable-vs-event -- that needs a position probe, not a count.
2026-08-26 04:59:32 +00:00
Sylpheed RE agent
f32bfb1433 re: withdraw the DisableInterval / delayed-route association -- it is a clustering artefact
The pooled cross-tab gave p ~= 0.02 for 'no Yes-flagged squadron has a delayed
route', recorded at a cautious yellow. Breaking it down by stage removes it:
all 55 Yes routes live in 2 stages, and 45 are in S14, which has NO delayed
route at all -- those 45 cannot be evidence either way. Only S04 contains both
a Yes squadron and a delayed route.

Within S04 the exact test is P = C(47,5)/C(57,5) = 0.37, expected 0.88 observed
0. Unremarkable. The pooled p treated 1023 routes as independent draws when they
are two clusters.

The doc had already flagged this as the kind of near-fit that misled the corpus
before; that caution was right and did not go far enough.
2026-08-26 04:53:13 +00:00
Sylpheed RE agent
ea155ea035 re: the wave timetable's t is SECONDS, measured against the live speed law
Route keyframes are (time, quat x4, pos x3), so each consecutive pair gives an
implied speed. The flight-speed law was measured live against a real-time clock
(cruise ~420, max ~1530 world units/s), which makes it an independent yardstick
in known units.

Across all 28 stage tables, 331 Route_* records and 1104 keyframe pairs: under
SECONDS not one pair exceeds the 1530 ceiling (median 147); under frames at 30
or 60 fps, 89% and 94% of the game's routes would demand impossible speeds.

Two corrections recorded. A first pass silently skipped non-numeric fields while
flattening records, shifting every later value -- it reported a median implied
speed of 65534, suspiciously 2^16, which is what exposed it. And the remaining
outliers were Frame_S<NN>_Asteroid records, not routes; the final measurement
filters by record kind rather than dropping whole stages.
2026-08-26 04:47:10 +00:00
Sylpheed RE agent
71d294ece1 re: narrow what the .slb header is -- four things it is not
Probed the bytes before the data offset at all four values. Not a length field
(no word in the first 64 bytes matches the offset, the RIFF position or the entry
size, either endianness). Not a seek table or any ascending index (~50% of
consecutive BE word pairs are non-decreasing, i.e. random; all words distinct,
none zero). Not zero padding in general (5876 of 7358 have content). Not audio
being discarded (offset 0 wins 6 of 7358).

High-entropy content whose size is constant per language and subdirectory, with
no self-describing length -- which points at a loader that knows the size a
priori. Recorded the next step as static PE work rather than another archive
pass; the byte-level evidence here is about exhausted.
2026-08-26 04:37:59 +00:00
Sylpheed RE agent
220e250453 slb: the seek chunk gives the data offset structurally, and breaks the 28 ties
The ties needed a different signal, not a longer scan. Banks carry one: a seek
chunk sitting on a packet boundary, so seek_pos % 2048 IS the data offset. On
the 6033 labelled banks with a seek before their first RIFF, 6031 agree
(99.97%) -- better than the packet scan and structural rather than statistical,
so scan_data_offset now tries it first.

On the scan's 28 ties it resolves 26 correctly and 0 wrongly (2 have no usable
seek). Combined rule scores 7354/7358 = 99.95%, up from 99.62%. 762 of the 1495
RIFF-less banks carry a seek, so the signal exists where it is needed.

Also ruled out, since a wrong offset was this page's whole subject: the header
is not audio being discarded. Adding 0 to the candidate set, it wins 6 of 7358.

7 disc tests pass.
2026-08-26 04:32:23 +00:00
Sylpheed RE agent
f47d013c94 re: the unvoiced script lines are not the resupply case -- 0 of 14 have a cutscene twin
Having found that the cue-less cutscene pages bind their audio through the movie
path, I tested the same explanation on the twelve script lines with no cue and
no bank. It fails: matching their caption text against every line in the
cutscene message table gives 0 of 14 hits, so nothing carries their audio by
another route.

They are ordinary story dialogue ('Katana, wait for me in hell'), which makes a
deliberate subtitle-only design unlikely -- but that is a judgement, not a
measurement, and why they are unvoiced is still open. Also noted: D_609 and
D_610 are equally unvoiced and simply are not among the twelve, because the
twelve counts script-called names and nothing calls them.
2026-08-26 04:24:37 +00:00
Sylpheed RE agent
ce726eaa9c re: the cue-less cutscene pages ARE the resupply lines, and they cross-check the movie work
16 of the 17 pages with an empty slot 4 belong to MSG_DEMO_600..604, whose
caption text matches, word for word, the five resupply lines documented from the
movie side as VOICE_D_450..454. Slot 4 is empty on purpose: that audio binds
through the movie path, not through a DEMO_nnn cue.

That also disposes of the '5 ids with more than one record' item -- each appears
once per stage that uses it, and the repeat counts equal the movie-slot counts
recorded independently for the matching banks: 3/3/4/4/2 both ways, 5 of 5. The
11 extra records over 138 ids are these repeats.

Worth more than closing two items: the cutscene message table and the movie
subtitle chain were decoded from different containers by different routes and
agree on the same five lines with the same multiplicities.

MSG_DEMO_500 remains: one page, KATANA, 2s, no text and no cue.
2026-08-26 04:23:52 +00:00
Sylpheed RE agent
e3c5eeae58 re: check whether the scan's 99.62% transfers to the banks it is actually used on
The labelled set has a RIFF and the scan is unbounded, so it reads past the RIFF
there -- the headline number could have been borrowing discrimination that a
RIFF-less bank cannot offer. Confining the scan to the leading region gives
69.98%, which looks like exactly that problem.

It is not. Split by how much leading audio there is: on the 989 banks with >=24
packets of it, the scan is 100% correct with ZERO ties, whether or not the RIFF
is in range. The 69.98% is an artifact of short leading regions, where two or
three packets are not enough to separate candidates.

A RIFF-less bank is a whole pak entry, so 24 packets are always available. The
99.62% is conservative for the population the scan serves, not optimistic.
2026-08-26 04:18:53 +00:00
Sylpheed RE agent
0abe908edd re: 69.8% of banks declare more data than they store -- and that explains TCAF_608
Of the 7586 banks with a RIFF and a data chunk after it, 5296 declare a data
size larger than the pak entry holds; 2290 declare less (the ordinary
multi-sub-wave case); NONE declare exactly what they hold. This contradicts the
decoder comment claiming the declared size 'is honest per sub-wave'. The code
clamps, so it is a documentation defect, not a crash.

It also closes the loose end from the offset work: eng\Voice\VOICE_TCAF_608,
the single bank where neither offset decoded, is 99% short -- there is nothing
there to decode.

Method note recorded: my first pass searched for 'data' from offset 0, which can
match by chance inside the leading audio region. Anchoring the search after the
first RIFF moved the count 5038 -> 5296.

Separately, the 55 'early RIFF' English banks are not an anomaly: all 55 sit at
exactly 1392 behind a zero-filled header -- a zero-length leading region, which
both the old and new code already handle correctly.
2026-08-26 04:11:45 +00:00
Sylpheed RE agent
16476e94c3 re: sound.pak is fully accounted for -- the last 2 entries were named in the table
static.slb (8970240 bytes, the shared SE bank) and Pj_Silph.xgs (533 bytes, XACT
global settings) both hash into the TOC. Their names come from the BANK_SE and
SETTINGS records of the very IDXD object this page documents -- I had printed
them at the top of the write-up and then reported the entries as unidentified.

9519 of 9519 now: 5100 jpn + 4382 eng + 35 root + these 2.
2026-08-26 04:09:33 +00:00
Sylpheed RE agent
57a9d98de1 slb: the headerless path was decoding stereo at a fixed offset; both are wrong
1495 banks carry no RIFF and take a separate path that hardcoded both the
offset and stereo. Across a random 48-bank sample there was NOT ONE where the
old stereo-at-1392 pair beat the best mono offset; median gain 184x, individual
banks going from 0-4816 decoded bytes to 180000-380000. Stereo shows the same
stop-after-one-frame signature already recorded for the leading segment.

With no RIFF the offset cannot be derived, so scan_data_offset picks among the
four disc offsets by XMA1 packet-header plausibility. Validated on the LABELLED
set -- all 7358 banks that do have a RIFF, where the answer is forced: 7330
correct (99.62%), and all 28 misses are ties on the top score, never a wrong
unique winner. Ties fall back to 1392.

The winning offsets also reproduce, by directory, the distribution measured
independently from the RIFF-bearing banks. jpn\etc splits 1468/1600, so path
alone is not sufficient -- which is why this is a scan and not a lookup table.

7 disc tests pass (build-reborn test -p sylpheed-formats --test
slb_leading_segment_disc, SYLPHEED_DISC wired up).
2026-08-26 04:03:05 +00:00
Sylpheed RE agent
e4ca3ff36b re: withdraw the 'Japanese banks are a separate format' item, one iteration later 2026-08-26 03:57:08 +00:00
Sylpheed RE agent
fde9473c89 slb: derive the leading-stream data offset instead of assuming 1392
HEADERLESS_DATA_OFFSET is the value the offset takes in <lang>\etc\, not a
property of the format. The leading stream is a whole number of 2048-byte XMA1
packets ending at the first RIFF, so its start is first_riff % XMA1_PACKET.
Disc-wide that takes four values -- 1392, 1468, 1600, 1728 -- varying by
language and subdirectory.

Verified by decoding, not by arithmetic: on a random 140-bank sample with a
non-empty leading region, the derived offset yields more audio in 85, identical
in 54 (the eng\etc controls, where it must and does reproduce the old
behaviour) and less in 1. Median gain among the improved is 70x --
eng\Voice\VOICE_TCAF_592 goes 1506 -> 97152 bytes, jpn 2910 -> 127178.

This withdraws my own claim from earlier today that the Japanese banks were a
different undecoded layout. They are the same format with a different offset;
I had treated a constant derived from one subdirectory as a property of the
format. The same error was hiding the identical defect in 1873 eng\Voice banks.
2026-08-26 03:56:50 +00:00
Sylpheed RE agent
2498922e9a re: the '504 unnamed keys' is 504 entries, not 504 names -- it is 42 keys
Independently reproduced across all 33 paks: 7750 IDXD objects, 1485577 unnamed
field entries, 7094 distinct never-named keys splitting cleanly into 7052 in an
ordinal band (<=0x2198, 94.6% equal to their own field index) and 42 hash-shaped
(>=0x2677C), with ZERO keys in the gap between. The 42 carry exactly 504
entries -- six language copies of one object times two records.

So the preimage target was 42, not 504, and my earlier wording invited the
misreading. Cross-referenced to the idxd-unnamed-keys write-up, which shows the
42 belong to <lang>\script\ID.tbl and cannot be recovered from a 24-bit hash.
2026-08-26 03:40:55 +00:00
Sylpheed RE agent
165dbb4d81 re: index + a new open backlog item for the undecoded Japanese banks 2026-08-26 03:34:43 +00:00
Sylpheed RE agent
a4cf037ac0 re: census of sound.pak, and the leading-region rule is English-only
Every FILES path hashes straight into the sound.pak TOC (5135/5135, path as
stored). The archive is then almost fully accounted for: 5100 jpn + 4382 eng +
35 BGM/JNGL = 9517 of 9519. Only eng and jpn resolve -- deu/fra/ita/esp give
zero, which is the archive-side measurement behind 'two voice sets shipped'.
718 Japanese banks have no English counterpart.

More importantly this bounds an existing result. The leading-region rule from
voice-bank-leading-region.md reproduces its five documented banks EXACTLY under
eng\etc\ (16384/2048/14336/45056/59392), so the pipeline is sound -- but it
holds for only 1571 of 4382 English banks and for ZERO of 5100 Japanese ones.
It is a property of some English banks, not of .slb in general, and the Japanese
layout is undecoded.

I nearly misread this as a broken extractor, because FILES lists jpn paths and
those gave non-multiples of 2048. Reproducing the recorded figures exactly under
eng is what distinguished a wrong assumption from a wrong reader.
2026-08-26 03:34:28 +00:00
Sylpheed RE agent
cb1967795a re: withdraw an overstatement -- the wrong-recording case was already settled
My sound-cue write-up said the table 'does not explain' the VOICE_D_452
rejection and implied it was still open. It is not: voice-bank-leading-region.md
settled it earlier -- the bank holds one generic line reused across four stages,
so the binding is correct and the expectation was wrong. The narrower true
statement, which is what the cue table actually adds, is that the reuse is a
movie-slot -> bank relation and not a cue-level one.
2026-08-26 03:29:41 +00:00
Sylpheed RE agent
953c757560 re: index + backlog rows for the cue table and the cutscene message table 2026-08-26 03:24:30 +00:00