Commit Graph

132 Commits

Author SHA1 Message Date
Sylpheed RE agent
c8e8dc0427 re: what the game actually reads at boot -- config.ini, and which
GameParts exist at all

Q6's second half, advanced but not closed, and the negative is the point.

config.ini is the disc's ONLY config file -- one find over the whole
extract -- and its own Shift-JIS header calls it the "Application /
GamePart initial settings table". Its [SYSTEM] section, which that same
comment says holds what the game and every game part share, is EMPTY. So
the boot order is not in disc-side configuration at all, and that search
space is now closed rather than merely unexplored.

What the file DOES carry is the language: XC_LANGUAGE_* -> eng/jpn/deu/
fra/esp/ita, defaulting to eng. That is the mechanism behind the EN/JP
build pairs in GP_TITLE and the <lang>.pak families -- a question the
corpus had described but never traced to its input.

Then the registry. Pulling every RegisterToFactory diagnostic string
binds 24 of the 29 GamePart ids to a C++ class, and five ids have no
registration site: 1, 2, 16, 18, 28. Id 1 is GP_ADVERTISE_DEMO, which
agrees with what I measured two iterations ago -- the attract loop is the
TITLE replaying ADV.wmv, not a transition into an advertise part. Marked
amber, not green: it is an argument from an error message, not from code.

Two things fall out for Q4: ids 3 and 4 are the same class
(GamePart_SaveLoad, one part with two ids), and the menu buttons' ids now
match the executable's own class names rather than a list of table names.
Still a name match, one level closer to the code.

What is still missing is the transitions themselves, and I say so: the
manifest gives the boot-side assets, config.ini the language, the
registry which parts exist. What decides to advance is in
GamePart_Title's code and that dig has not been started.
2026-08-28 18:49:19 +00:00
Sylpheed RE agent
1f822f8b30 re: the menu's sound events are named on the disc; the binding to them is
not

Q8. The cheapest thing nobody had tried was to look at the SE cue names,
and they are semantic: SE_UI_CURSOR (2), SE_UI_DECIDE (3), SE_UI_CANSEL
(4), SE_UI_IMPOSI (5, the error buzz), SE_UI_SUB_WIN_OPN/CLS, and
SE_UI_SPLASH_IN/OUT. That is exactly the move/confirm/back/error
vocabulary the question asks for, named by the authors after the EVENT
rather than the sound. 322 SE cues committed as reference data.

They all live in one bank: BANK_SE is a single field reading Static.slb,
and the disc-wide check agrees -- 0 of the 322 has an entry in FILES, the
5135-path list that names every voice, briefing and BGM bank.

I am calling the binding a NAME MATCH, not a measurement, and the page
says so in the same breath as it gives the table. It is a stronger name
match than Q4's GamePart ids -- these are the authors' own event names --
but nobody has watched the game emit cue 2 on a d-pad press, and this
container is muted against a dummy device so there is no audio path to
watch either.

One new negative, with its reach. Static.slb's 8 353 472 readable bytes
contain zero RIFF, zero seek and zero WAVE, scanned over the whole
buffer. The boundary marker that works for all 7 620 other banks is
absent, so the cue is named, the bank is named, and the wave inside it is
not locatable. The UI sound effects cannot be exported yet, and that is a
gap in the assets rather than in the naming. The named next step is
Pj_Silph.xgs -- the XACT project SETTINGS points at, which IS in
sound.pak at TOC 9454, though its 533 bytes carry no XGSF magic and its
region's phase says they are probably the previous bank's tail.
2026-08-28 18:33:58 +00:00
Sylpheed RE agent
e81dcad632 re: the boot intro and the attract loop are the same video, and the
new-game intro has a name

Q9, answered twice over -- once from the disc and once from frames I had
already captured and not used.

The movie manifest's first eight slots are the whole boot-side flow, and
the slot key is the role: LOGO1-4 -> logo1-4.wmv (not on the disc, which
is why the splash is a screen), ADVERTISE_MOVIE -> ADV.wmv, STAFF_ROLL ->
the credits reel, MS00A -> S00A.wmv, MS01A -> S01A.wmv. So the new-game
intro is S00A.wmv, decoded -- which is the half I could not test at
runtime, because A on NEW GAME hangs the emulator.

And there is no separate boot-intro slot: ADV.wmv IS the advertise movie,
the boot just plays it first. Confirmed independently by matching 19
attract frames against five candidates -- 15 hit ADV with a playhead that
advances monotonically at the sampling rate and ends at 137 s, its full
length, with the title back on the next sample. So the attract movie
plays to its end; nothing cuts it short.

That corrects me. Two iterations ago I recorded the attract movie as
"~85 s, so probably not ADV.wmv" -- arithmetic on a start I never
observed, since sampling began 39 s in. REFUTED and METHOD both take it,
along with the matcher's real failure mode: a near-black frame has no
signature, and its runner-up is not evidence.

Skippability I did NOT settle, and the corpus contradicts itself: one
page says A skips a movie every time, while the boot harness deliberately
never taps during one because it breaks the title. Named the one-boot
test rather than picking a side.
2026-08-28 18:29:39 +00:00
Sylpheed RE agent
e64965b3f9 re: S1 -- the Ready Room probe is a no-go, and not for the reason the
question expected

One iteration, as the mission gates it, and no Ready Room work started.

The two criteria it named both pass. It is NOT 3D: the corpus already
holds a capture of the running screen and it is a flat six-item menu in
the same visual language as the title. And unrecoverable paths do not
mean unrenderable screens -- content enumeration finds 60 builds in the
1106 entries.

What kills it is something the question did not ask. GP_READY_ROOM.pak
does not appear to hold the Ready Room's own menu at all. Two signals
agree: the four largest builds composite to briefing panels -- a planet
readout, unit markers, direction arrows -- and, much harder, the 902
bundles declare 317 distinct element names of which NONE matches any of
the six labels on the screen (takeoff, brief, hangar, pilot, option,
mission, ready all return nothing). The pak is the briefing /
tactical-map content behind the BRIEFINGS item. If this is ever picked
up, the first job is finding where the menu lives -- GP_BUNK.pak is the
obvious place -- not naming this pak's keys.

The probe also reaches back into something already delivered: kind ==
0x3002 finds ZERO buttons across all 902 bundles here, while GP_TITLE has
16. 0x3002 is one member of a 0x3000 family with sub-bits, and this pak
uses 0x3000/0x3004/0x300c/0x3008. Nothing in the milestone changes --
every screen in scope is GP_TITLE -- but the handoff now says not to ship
that equality as a general button test.
2026-08-28 18:24:48 +00:00
Sylpheed RE agent
b3c8632006 re: a music bank is two stems that play together -- not intro+loop, and
not something to concatenate

Q10, and it starts by withdrawing the question's own premise. BGM_001 is
not three sub-waves of 10 KB / 4.47 MB / 4.67 MB: the 10 KB is the bank
header. A bank is exactly TWO waves, and across all 32 BGM banks on the
disc the two always have the SAME duration -- equal to 0.01 s over
lengths from 37 s to 277 s. That alone kills intro+loop and kills two
halves of one piece, both of which require unequal lengths.

Four banks appear to break the rule and do not: BGM_106-109 are the known
leading-region straddle, and the giveaway is that the entry named
BGM_107.slb contains BANK id=1108. The seek packet counts pin each join
exactly, so they realign to the same two-equal-waves shape rather than
being dropped as noise. That trap goes in METHOD.

Then the roles, by decoding both waves to PCM. They are
sample-synchronous: transient-envelope correlation searched over +/-5 s
peaks at lag +0.00 s, and both waves stop at the same millisecond,
167.663 s. Two stems of one performance, meant to sound at once. Wave 1
is quieter, has almost no bass and is far more L/R-decorrelated, which
reads as a surround-rear pair or a second intensity layer -- I cannot
separate those two from the file, and say so: ChannelMask is 0x0002 on
both, and this game's channel metadata is already documented as
meaningless.

Two things the port needs that are NOT on the disc, both marked as
authored: the track is not a seamless loop (BGM_001 fades out and is
followed by 6.15 s of silence, no loop-point field found), and nothing
names which bank the menu plays -- all 32 BGM cues are numeric.
2026-08-28 18:13:10 +00:00
Sylpheed RE agent
7fef19b3a0 re: the transition between screens is a fade through black, and most of
its timing is on the disc

Q7. Every title-side screen carries a full-screen black .prm quad that
paints last, and its keyframe group IS the transition: black at T0, clear
by T1, clear until T2, then back to black on exit. Read with the corpus's
start-of-a-ramp rule and Q1's time unit that gives 0.87s for EXTRAS,
0.97s for the main menu, 4.08s for the title -- from the file, not from a
stopwatch.

The disc-wide check is per-pak all-or-nothing rather than the 41% the
headline count suggests, and GP_TITLE's 6 of 12 is the useful row: the
six builds carrying a fade quad are exactly the six SCREENS, and the six
without are exactly the six overlays. GP_DIALOG is 0 of 133. That is
independent corroboration of the overlay finding from two iterations ago.

One piece is NOT on the disc and says so: the fade-OUT length. The fourth
keyframe has no time slot, because a group's last block stops four bytes
short. Measured instead, at 30fps, ~0.4s and the same both directions.

And a warning I earned: the luminance rise after a transition is NOT the
quad's ramp. The incoming screen's own elements animate in after the quad
has cleared -- 1.47s observed against a declared 0.97s. Time the fade
from where the frame is pure black.

Rig: screenshot samples at 0.5 Hz and cannot see a 0.4s fade at all,
which is why an earlier burst called this an instant cut. ffmpeg x11grab
at 30fps instead; both go in METHOD.
2026-08-28 18:05:19 +00:00
Sylpheed RE agent
4e745c8177 re: the title menu wraps -- Q5 measured, Q4 driven, and one of my own
method lines withdrawn

Q5, measured off two boots: up/down move one item and WRAP at both ends
on the 5-item main menu and the 3-item EXTRAS alike; left/right do
nothing; B goes up one level and restores focus to the item you came
from (4/4); B on the main menu returns to the title; B on the title does
nothing. The menu opens on TUTORIAL -- the middle item -- 2/2, though a
third recorded run implies NEW GAME, so that one is reproducible rather
than invariant and says so.

Q4 by driving: LOAD GAME opens the save-slot list, TUTORIAL the lesson
list, OPTIONS the settings menu, EXTRAS build 6, MISSION SELECT the stage
list. NEW GAME is not tested -- A on it hangs the emulator and this
iteration needed the session. The GamePart ID behind each is NOT
measured: it is the entry of the decoded id table whose name matches the
screen I saw, and the page says so rather than wearing the badge.

And the withdrawal. Last iteration I wrote that these menus drop d-pad
presses shorter than ~0.3s. They do not. Once wrap is measured, every
press count I had is exactly right -- four presses moved four steps
THROUGH the bottom, which lands one above where a non-wrapping menu
would. I invented hardware flakiness instead of testing the ends of the
list. METHOD keeps the withdrawal rather than deleting the line.

Also: label brightness is not a cursor oracle here -- the background art
outshines the highlight on some rows. menu_focus.py reads the focus ring
instead, 254 vs <82, no tuning.
2026-08-28 17:53:46 +00:00
Sylpheed RE agent
88b3ce9af5 re: which GP_TITLE build is which screen, measured against the game
Q2. The archive is eight screens shipped twice, English and Japanese --
not the "build 4 title, 5 main menu, 6/8/9 submenus" the handoff claimed.
Build 8 is the JAPANESE main menu; 6 and 9 are the EN and JP EXTRAS, and
EXTRAS is the only submenu GP_TITLE holds. The PRESS (A) BUTTON plate is
its own build (2/3), composited over the title art and faded in a beat
later, not a state of build 4.

Confirmed by booting to the main menu and walking it: title, PRESS (A),
main menu and EXTRAS each match their render element for element. Builds
0/1 and 10/11 -- a DELTASABER / SYLPHEED A.I. plate -- were looked for in
the whole boot filmstrip, every title-side screen and the attract loop,
and appear in none of them; the reach of that negative is written down
rather than filled in with a guess.

Two rig traps went into METHOD: the menus drop d-pad presses shorter than
~0.3 s, and a grab 2.5 s after a transition can catch a screen mid-fade
-- which nearly wrote "the returned title has no plate" into the corpus.
2026-08-28 16:55:33 +00:00
Sylpheed RE agent
27e8c51978 re: a keyframe time is worth half a rendered frame, and the ramp is linear
Q1 of the menu port, measured against the running game rather than reasoned
about. The developer-logo splash is the cheap target: it is the first thing the
guest draws and its bundle declares short, unambiguous ramps.

Two results, both frame-exact and both emulator-speed-independent (frame numbers
are VdSwap counts, the guest's own frames):

  * the ramp is LINEAR. A declared 15-unit fade lands on round(255*k/15) for all
    seven of its samples with zero error, k stepping 2,4,6,8,10,12,14. No ease
    can reproduce a constant step of 34 at both ends.
  * the animation clock advances 2.000 time units per submitted frame, over six
    consecutive intervals with no residual, with 1 unit as the quantum
    underneath (one frame in the fade-out advances by 1).

The conversion to seconds is one step further and is flagged as such: 300 frames
took 10.87 s = 27.6 present-frames/second, which reads as a 30 Hz title at 92 %
under the emulator and gives 1 unit = 1/60 s -- the title build 4.2 s, the main
menu build 1.1 s. That reading is not proven, because the rate was measured
while the guest was still streaming from the ISO; the page names the one test
that would settle it and says what changes if it goes the other way.

Committed beside it: the raw draw capture and the per-frame quad CSV, so the
numbers can be re-derived without a disc or an emulator.
2026-08-28 15:56:33 +00:00
Sylpheed RE agent
1054d43676 docs/re: finish the INDEX merge I left unresolved
Commit 03ece95 ("Merge branch 'auto/idxd-unnamed-keys'") shipped INDEX.md with
live conflict markers. My check was worthless in a way worth naming: I ran

  git add docs/re/INDEX.md ; git status --short | grep -E '^(UU|AA)'

in that order. Staging a conflicted file is exactly what marks it resolved, so
the grep found nothing and reported "no unmerged" -- the control passed BECAUSE
of the action it was meant to gate. Both halves of the hunk then went into the
tree verbatim, leaving two competing IDXD rows.

Resolved as a union, which is what it always was:
  * HEAD's "IDXD object/table" row -- the 2026-08-25 record/index-region decode
    -- supersedes the branch's older one-line version of the same row;
  * the branch's "IDXD nameless field keys" row is new and is kept.
2026-08-28 15:55:18 +02:00
Sylpheed RE agent
e1dcc689bc agent: move the loop prompt's memory into the corpus, and let it push
The autonomous agent's loop prompt had grown to ~9 000 words of accumulated
findings, refutations and traps. That is a symptom, not a style: it was doing
the job the corpus should do, in the one place that dies with the container.
Three concrete failures followed from it, and each gets a structural fix rather
than a louder instruction.

1. IT REPEATED WORK IT HAD ALREADY DONE.
   The "do not revive" list and the method traps existed ONLY in the prompt, so
   nothing pointed a fresh iteration at them. Extracted verbatim into two
   tracked files:

     docs/re/REFUTED.md  105 claims tested and dead, grouped by subject so a
                         grep for your noun finds the neighbourhood
     docs/re/METHOD.md   the traps already paid for -- controls, inference,
                         searching, reading data, runtime

   Both are linked from INDEX.md, and the loop prompt now opens by requiring
   them to be read. This is the fix for "re-derived something already known":
   the knowledge is now where the next iteration looks, not in a context window.

2. IT FORGOT TO ARM THE NEXT WAKEUP.
   The prompt tried to solve this by shouting at itself in the first line. The
   real fix is to stop asking: `loose` now defaults to a FIXED interval (45m),
   so the harness owns the cadence and a forgotten ScheduleWakeup cannot end the
   run. SYLPH_LOOP_INTERVAL= (empty) restores self-pacing.

3. IT COULD NOT PUBLISH, SO THE WORK ONLY EXISTED IN THE CONTAINER.
   New `push-work`, plus a read-only credentials mount
   (SYLPH_GIT_CREDENTIALS, default ~/.sylph-git-credentials). It pushes the
   CURRENT branch only, refuses anything that is not auto/*, and never
   force-pushes -- so the consolidated line stays a human's decision and a
   confused iteration cannot rewrite history. The loop prompt now requires a
   push on every iteration that commits, rather than at the end of some longer
   arc, which is exactly when a container dies.

The prompt itself drops from ~9 000 words to 85 lines and, more importantly,
stops accumulating: findings go in the corpus, and the prompt points at it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 15:46:59 +02:00
Sylpheed RE agent
03ece95c06 Merge branch 'auto/idxd-unnamed-keys'
# Conflicts:
#	docs/re/INDEX.md
2026-08-28 15:27:51 +02:00
Sylpheed RE agent
1fce3f9c71 Merge branch 'auto/slb-loader'
# Conflicts:
#	docs/re/INDEX.md
#	docs/re/structures/slb-data-offset.md
2026-08-28 15:27:46 +02:00
Sylpheed RE agent
8124b85fc7 Merge branch 'auto/regn-reader'
# Conflicts:
#	docs/re/BACKLOG.md
#	docs/re/INDEX.md
#	docs/re/structures/regn-map-grid.md
#	tools/re-capture/regn_decode.py
2026-08-28 15:26:35 +02:00
Sylpheed RE agent
245b73243b Merge branch 'auto/isl-builtins-26-28-29'
# Conflicts:
#	docs/re/INDEX.md
#	tools/re-capture/isl.py
2026-08-28 15:24:55 +02:00
Sylpheed RE agent
168bebc700 re: S28 does have an asteroid definition - it borrows Stage 14's
Withdrawing my own repeated claim that S28_p1 has an asteroid volume with no
definition. It came from a set-difference over FILE NAMES, which cannot see
reuse. Joining the fields per PHASE settles it:

  S28.Phase_1   MapMesh = S28_p1_AsteroidVolume_wp.col
                AsteroidDefinition = S14_p2_asteroid.tbl

S28 scatters Stage 14 phase 2's rock list inside its own volume. Nine
definition tables and nine _AsteroidVolume_wp meshes, with one table shared by
two phases: S14_p2_asteroid.tbl -> S14.Phase_2 and S28.Phase_1.

The earlier census's own arithmetic already contained the answer: the four
values counted x12 (2 per language pack) were S01, S04, S05 - two phases of ONE
stage - and S14_p2, which is two phases of TWO different stages. The count was
right; reading x12 as "two phases of the same stage" was the error.

test_s8p1_asteroid.tbl is not test-only either - it is what S02.Phase_1 ships
with. A test_ name here is a leftover, not a dead table.

The true residual is two phases and it is the opposite pairing to what I
recorded: S01.Phase_3 has the volume and no definition (S01 keeps its volume
across all three phases but scatters rocks only in 1-2), and S02.Phase_1 has a
definition and no volume (MapMesh is the plain mapmesh_box_500km).

All fifteen artefacts byte-identical.
2026-08-28 03:44:14 +00:00
Sylpheed RE agent
1f0f90e304 re: the 8 values per asteroid are index, quaternion, position
Measured over every Frame_*Asteroid* record on the disc - 384 records, 37518
items (6253 per language pack x 6; the remaining 380 per pack belong to the
seven record-less Alpha frames, the same 57-vs-64 split as before).

  slot 0    equals the running index 0..N-1   37518 / 37518, max 587, and the
            largest FrameCount is 588, so the last index is N-1
  slots 1-4 a unit quaternion                 37518 / 37518 within 1e-4 of 1.0
            (min 0.999999, max 1.000001)
  slot 4    sign of the real part             min +0.001, never negative -
            canonical w-last, so the order is (x, y, z, w)
  slots 5-7 position in metres                -163858..+79572, -114308..+76422,
            -105224..+110263 - inside the +/-250 km half-extent of
            mapmesh_box_500km

len(anonymous fields) == 8 * FrameCount on 384 of 384, zero mismatches.

The unit-norm test is the control: an arbitrary 4-tuple has no reason to sit on
the unit sphere and every one of the 37518 does. Two further cross-checks fell
out - the maximum index is exactly max(FrameCount) - 1, and every position lies
inside the 500 km box the corpus already knows (world unit = 1 metre).

There is no scale slot: an asteroid's size comes from its group's
AsteroidModelName, which is why a group is one model plus one placement list.

Not settled: whether the quaternion is object->world or world->object and its
handedness - a norm test cannot separate those; and why S28_p1 has an asteroid
volume with no definition.

All fifteen artefacts byte-identical.
2026-08-28 02:03:23 +00:00
Sylpheed RE agent
863a88dbf3 re: EnumAsteroid counts the rocks, and the hash join was mine to fix
EnumAsteroid is the frame record's FrameCount - 57/57, zero mismatches. Each
AsteroidFrameName is itself an IDXD record name; those records carry one named
field, FrameCount, plus a run of anonymous ones, and the anonymous fields
number exactly 8 x FrameCount on all 384 occurrences (ratio histogram
{8.0: 384}). The frame record is the placement table, 8 values per asteroid,
and EnumAsteroid counts the rocks. The frame-name -> EnumAsteroid map is
single-valued for all 64 names.

mission-wave-arrivals already met these records - they were the outliers that
broke its route-speed measurement, filtered out by name. regn-map-grid already
guessed the link and said so honestly ("a reading, not a measurement; nothing
here counts objects"). Neither says the count is exact.

The abandoned table lost its placement data too: exactly 7 of 64 frame names
have no record, and they are exactly the seven groups of the unreferenced
tenth object (Frame_Alpha_S01_Asteroid_{01,02,04}, Frame_Alpha_S01_Haze_
{01,02,03}, Frame_TAlpha_S01_Asteroid_03). 57 + 7 = 64, zero partials.

Withdrawing the previous commit's red flag: the hash join works and my call
was wrong. AsteroidDefinition values resolve to their objects' pak entry keys
as name_hash("stage\" + name), 9 of 9, each landing on the object the
frame-prefix join independently predicted. I had hashed the bare file name;
archive_naming.py hashes prefix + name over a path-prefix list. Two
independent joins, 9/9 agreement, both leaving the same tenth object
unreferenced.

Not settled: what the 8 values per asteroid are; why S28_p1 has a volume and
no definition.

All fifteen artefacts byte-identical.
2026-08-28 01:48:52 +00:00
Sylpheed RE agent
c53da9ea2a re: the asteroid-field tables (AsteroidGroup_00N) decoded
New doc docs/re/structures/asteroid-fields.md, indexed.

Half the chain was already in the corpus: stage-definition-table and
stage-mission-tables record the per-phase AsteroidDefinition naming a .tbl,
and unit-datasheet-static partitions Generic and finds 10 per pack whose only
field is {EnumAsteroidGroup}, "an asteroid-group file". Neither says what is
inside them.

The table: 10 objects per pack x 6 packs = 60, 384 AsteroidGroup_* records,
4 or 7 groups per object. Every record carries exactly three fields, all 384,
no partials - AsteroidModelName (28 distinct; mob_n050..n056 x24 each,
mob_n040..n043 x18), AsteroidFrameName (64 distinct, Frame_S<NN>[_pN]_
Asteroid_NN, the same names regn-map-grid reports the Route tables showing),
and EnumAsteroid (37 distinct, 1..588, 6633 per pack summed).

Enum<Thing> is a COUNT; Enumerate<Thing> names a TABLE. EnumAsteroidGroup
equals its object's group count in 54 of 60 and EnumAsteroid is a plain
integer - the opposite of the Enumerate* join rule. The -erate is the
difference. Count-style fields still are not safe: the 6 failures are ONE
object repeated per language pack, declaring 4 while holding 7.

The tenth object is unreferenced and it is the one that miscounts. Joining by
frame prefix, nine objects match the nine AsteroidDefinition values one to
one; the tenth carries Frame_Alpha_S01_Asteroid, Frame_Alpha_S01_Haze and
Frame_TAlpha_S01_Asteroid and is named by nothing.

S28 has an asteroid VOLUME but no asteroid DEFINITION: mcol-collision lists
nine _AsteroidVolume_wp meshes (…, S28_p1); the nine AsteroidDefinition values
are those eight plus test_s8p1. Eight shared, one different each way.

Not settled: what EnumAsteroid counts of; why S28_p1 has a volume and no
definition. The name->object join did not reproduce by hash under name_hash,
mod 0x00FFF9D7, or a 24-bit mask - the join used is by frame prefix.

All fifteen artefacts byte-identical.
2026-08-28 01:32:52 +00:00
Sylpheed RE agent
bb6fb9024b re: r0 is not a base register - the last shortlisted row withdrawn
sub_825F2CF0 / sub_825F2F88 (r0, 30 names each, 97% disc names) are not
name-block readers at all: they are the compiler's VMX save/restore helper
pair. 145 instructions each, 72 differ, every difference stvx<->lvx over
v14..v127.

Root cause: in `addi rD, rA, N` the rA slot reads as literal zero when it
names r0, so `addi r11, r0, -N` is `li r11, -N` - vector spill offsets, not
displacements. The solver excluded r1 only; the bogus base landed inside the
stage-settings name block, dense enough to "resolve" 30 real field names.

name_block_bases.py now skips r0 as well. Rows 277 -> 261, functions 190 ->
176, non-64K bases 170 -> 154, data-table rows 53 -> 50; 154+107=261 and
277-16=261. Artefact diff -100/+3, every removed row line carries r0, control
sub_82341A20 r30 = 0x82088F94 217/226 untouched.

Nothing lost: all 30 names are a strict subset of sub_8230D1F8 r29 (129
names), the stage-settings loader already in the corpus. String-xref join
agrees - FinalPassBG, FogMinDistance, ScreenColorR, ExposureKey_BeginValue
each exist once with exactly one xref, from sub_8230D1F8.

Fourteen other artefacts byte-identical; name-block-bases.txt byte-identical
on a second run.
2026-08-28 00:21:26 +00:00
Claude (auto)
aeec0dab8f re: the leaderboard screen, and a Detail_* prefix trap
sub_8219E560 r11, censused against tables.pak exactly like the result screens.
444 instructions, one call site (sub_821A2A80), 100 % of its names are tables.pak
names.

The 18 names partition exactly: 13 record names + 5 field-only names.  The 13 are
the 12 Detail_* panel elements plus MISSIONS -- a 0-field record already owned by
isl-condition-builtins.md and challenge-mission-gate.md, so not new.  The five
field-only names are the screen's parameters: DETAIL_TITLE, one field in each of
six mode records (ScoreAttack, TimeAttack, Extra01..Extra04), and
Detail_Board_{Permanent,Monthly,Friend,Self}, four fields of CHIPS -- the board
scopes.

All 12 Detail_* record names are in the code block, zero residual -- the opposite
of the boss motions, where the block was a 25-of-55 subset.  The panel is Rank,
GamerTag, Main_{Time,Points}_{Self,Live}, Warships, Warplanes,
Weapon_{Nose,Main1,Main2,Main3}: your figure against the selected entry's, plus
that pilot's kills and four weapon slots.  _Live, GamerTag and the Friend/Monthly
boards make it the Xbox Live detail view.

The trap worth keeping: tables.pak has 12 Detail* RECORD names and 12 Detail*
FIELD names, and they are DISJOINT (0 overlap).  The fields are a different axis
-- 4 board scopes + 6 modes + Detail_Window_{Known,Unknown}.  A Detail_* name
means nothing until you say whether it is a record or a field.

Not settled: what Detail_Window_Known/_Unknown switch between, and whether
Extra01..04 map onto the four challenge missions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 23:58:13 +00:00
Claude (auto)
e548db693e re: the Stage 16 boss closed end to end; five disc fields the image never names
New doc docs/re/structures/stage16-boss.md joining three iterations' pieces: the
data object (player-tuning-tables), the collision table (collisionset) and now the
loader.

sub_82315AE8 r11 is the boss loader, and it says so itself: 558 instructions, one
call site (sub_82308AB8), and its 21-entry block is 19 field names plus the
Japanese error "the boss's laser shell ID could not be obtained".

Zero partials: every one of the 19 is a Guardian or Core field -- 16 of Guardian's
19, 5 of Core's 10, DamageLevel1/2 shared.  The boss reads as a rotating,
vertically-moving platform with a guard cycle, three shell ids (AAGun, Laser,
HomingLaser -- the Shell_S16Boss_* set) and a Core that periodically drops
lock-on.

The residual is the finding: 8 disc fields the loader never names, and five of
them are not strings in the executable at all -- InitHight (the game's own typo
for Height) and the four DamageLevel{1,2}Bomb{,Rand}Time.  Controls: ChargeTime
(10 chars) and GuardTime (9) are the same shape and both ARE present, so it is not
an extraction artefact; HP (2 chars) is below the extractor's floor (shortest row
is 6); Radius exists but is generic.

So the disc carries fields no code names -- the mirror image of the usual trap, a
loader naming a field the disc never values.  Whether they are read positionally
or are dead data is not settled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 23:32:09 +00:00
Claude (auto)
e7c89fdcd1 re: the debriefing and pilot-record screens; the save-screen block was already owned
Read-before-claiming caught one of the three.  savegame-format.md already
documents the shape AND this exact list -- the LOAD/SAVE screen's config key list
is compiled into the executable as a pointer array of key strings, run starting
0x820A0074.  sub_82286BC8's 18 names are that list (17/18 are a tables.pak field
or record).  Nothing new; residual PLAYER_AMMO_LESS_10, not a tables.pak name.

The other two are new screens of the same shape and they close exactly.  All
names resolve into tables.pak: sub_822814D8 24/24, sub_8227A3A0 21/21.

sub_822814D8 = the debriefing screen.  24 = 2 screen ids (STAGE_RESULT,
EX_STAGE_RESULT) + 21 stage_* fields + one sound cue (SE_BOSS_CORE_CHARGE); the
tables.pak STAGE_RESULT record has exactly 21 fields.

sub_8227A3A0 = the pilot record / career screen.  21 = 5 screen ids (LAST_RESULT,
EX_BASE, EX_MAIN, EX_OVERVIEW, OVERVIEW) + 7 ex_overview_* + 9 overview_*; the
records EX_OVERVIEW and OVERVIEW have exactly 7 and 9 fields.

2+21+1 = 24 and 5+7+9 = 21, with 21/7/9 measured independently off the pak.

The debriefing readout is nine num/points pairs plus three points-only lines:
kills by class, objectives, clear time, shoot-down ratio and both damage
categories carry a raw count and its score contribution, while friendly_fire,
shoot_down_others and weight have no counter column -- the same partition
mission-scoring.md measures on the settings side.  OVERVIEW is the seven
EX_OVERVIEW fields plus overview_rank and overview_medals.

Not settled: what EX_ means (its ids have no record of that name), and
PLAYER_AMMO_LESS_10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 22:38:56 +00:00
Claude (auto)
2456d28f1a re: AIParams disc-wide, and a correction to my own last entry
CORRECTION FIRST.  Last iteration I recorded sub_8233C368 as unblocking a
NEEDS-HUMAN item -- "the corpus carries the AI tail of Maneuver as
NEEDS-HUMAN/runtime; it is statically reachable after all."  That was wrong.
stage-mission-tables.md already documents AIParams_S02.tbl as exact original
values obtained by static RE, directly portable, listing all 20 field names and
both shapes.  I grepped FiringLength and saw the file but did not read the
section.  Finding the owning doc is not reading it.  The only genuinely new part
was the loader's name.

What is new: the census generalises Stage 02 to the disc.  23 AIParams objects,
identical in all six GP_MAIN_GAME_* paks, sharing ONE declared-name set of 34
profiles; 782 profile records = 23 x 34; 0 declared names without a record in
their own object.  So "34 AI profiles" is not a Stage-02 fact -- every stage
carries the same 34 and only the values move.  The roster is declared by an
Enumerate_AIs record whose field names are the profile names, the same
declaration-table mechanism that closed DefTables.

Type predicts the field count with exactly two exceptions: Fleet -> 6 fields is
253/253 zero partials; Squad -> 20 fields is 483/529.  The 46-record residual in
full: AI_Test and AI_CraftSquadron_Test, both Type = Squad with only the six base
fields, in all 23 objects.  No profile's shape varies between objects.

New regenerator aiparams_census.py, 45-line artefact, byte-identical across two
runs; the other fourteen verify unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 21:57:24 +00:00
Claude (auto)
ae37e15d7d re: the base-solver -- 277 name-block loaders indexed, and the analog block is SOLVED
New reusable tool, tools/re-capture/name_block_bases.py -> docs/re/data/name-block-bases.txt
(2880 lines, ~65 s, byte-identical across two runs).

A loader that reads a table by field name keeps one base pointer and emits
"addi rX, rBASE, -N" per name, so no static xref sees the strings.  Solve the base
from the DISPLACEMENT SET alone: every (string address, displacement) pair implies
a candidate base, and the true base collects a vote from every name it explains,
so it wins outright.  My first cut took candidates from ONE displacement and
scored the unit loader at 52/226 against the right answer's 217/226 -- vote over
the whole set, not a probe.

Control passes with no prior knowledge: the tool recovers sub_82341A20 -> r30 =
0x82088F94 at 217/226, and independently recovers sub_8230D1F8 (129/132),
sub_822F9498 (90/91) and sub_822AE628 (81/108).  277 name-block-reading functions
image-wide, with the schema each names.

The analog block is SOLVED: sub_821A6CF0, r29 = 0x820A1630, 22/24.  In code order
it names ControlTweakName, YawMagForNormal, the 12 Tweak fields, the 8
AnalogRevice_* curves and GP_MAIN_GAME -- the whole schema in the object's own
order plus its pak.  r29 is built at 0x821A6D34 as addi r29, r11, 5680 =
0x820A0000 + 5680, matching the solved base exactly.  It is the same function that
reads PlayerParams.

Two of my own verdicts withdrawn: "referenced by nothing" and the softened "not
found by these routes".  The measurements behind them were right; the conclusions
were wrong.  The base was solvable from the data the whole time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 21:22:50 +00:00
Claude (auto)
38e0a63946 re: the unit loader was already in the corpus (sub_82341A20), and 0-xref does not mean no reader
Item "what loads the 114 unit Maneuver records" -- the corpus already answers it,
and I should have found that before framing it as new.  My own error, corrected:
I claimed live-unit-definitions.md did not exist.  It does, at docs/re/, not under
structures/.  Looking in one directory is not looking.

sub_82341A20 is the unit-definition loader, documented in unit-struct-runtime.md
with a checked-in 177-line layout at
crates/sylpheed-formats/data/unit_definition_layout.txt.  Verified independently:
3969 instructions, 227 of its 236 "addi rX, r30, -N" displacements resolve to
strings -- the unit schema in order, Generic, UncertainName, IsDestructible,
NamePlate, Size_X/Y/Z ...  Maneuver sits at exactly 0x82088F94 - 13404.

Record keys are tag_hash, confirmed on the data side: the Maneuver record's key is
0x43FAA517 = tag_hash("Maneuver"), not name_hash (0x63A248D7), x114 records.

That explains the 0-xref phenomenon and softens my own verdict from last
iteration.  Maneuver's string has 0 xrefs AND a known reader: the loader takes its
address as a register-passed base plus a displacement, which no static xref can
see.  So the AnalogRevice/Tweak verdict goes from RED to AMBER -- a
base-plus-displacement sweep over the 2283 functions forming the 0x820A high half
reaches it 0 times, but run against the unit block as a control the same sweep
recovers only 5 references and misses sub_82341A20 itself.  The measurement
stands; "the reader is unreachable" does not.

Also fixed: player-tuning-tables.md credited live-unit-definitions.md with
documenting sub_822F9498.  It documents sub_82341A20, a different function.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 21:02:58 +00:00
Claude (auto)
6334f36dde re: the object is PlayerParams, and sub_822F9498 is ITS loader -- not the unit loader
Resolving every string sub_822F9498 references, in code order, gives 90, and they
are exactly the 890e1be4 object's schema in its record order: Misc (5 effect
names), SpecialAttack (+17), TacticalManeuver (+7), SpecialWeapon (+8), Booster
(+50).  It never names Generic, Maneuver, Explosion, Shield or StructureCount, and
the string Maneuver has 0 xrefs.  One call site, from sub_821A6CF0, which is
itself called once and references the literal PlayerParams.

CORRECTION to unit-datasheet-static.md, which calls sub_822F9498 the
unit-definition loader.  It loads one object: the player parameter table.  Its
AA_/AV_ interleave still stands as a struct layout, but the struct is
PlayerParams's Booster record, not each unit's Maneuver.  What loads the 114 unit
Maneuver records is now an open question -- it uses none of these strings.

Ranking all 114 units by how many of Booster's 50 values they reproduce: the top
five are exactly the five _Player units, all at 39/50; the sixth drops to 13/50;
none matches 50/50.  The five players agree with each other on all 50 -- the three
player ships share one flight model.  Booster stands alone on 10 fields; the
eleventh, AA_Yaw_Max, is 65.0 vs 65, formatting not a value (last iteration's
"11 differ" over-counted).

Still open, and sharper: nothing selects Booster.  PlayerParams is loaded once,
unconditionally, no branch -- yet the measured flight tracks the unit Maneuver
(1.25/1.20/1.28) and misses Booster badly.  Both cannot govern the same craft; the
next test is a runtime watch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 20:48:56 +00:00
Claude (auto)
92d1a39ce2 re: the player-tuning consumer -- found for half, BLOCKED for the other, two of my labels refuted
The consumer is sub_822F9498, the unit-definition loader the corpus already
documents.  The field-name block 0x8209F300-600 (BulletTimeVolumePC, FireBirdPower,
ChargeMaximum, Turn180RequiredTime, ShieldDoubler_Time, ...) is 36/36 referenced,
every reference inside that one function.

REFUTED, my own label: "Booster is a new schema".  Its 50 field names are a strict
subset of the unit Maneuver record -- 50/50 shared, 0 Booster-only.
CruisingVelocity occurs in exactly 115 records disc-wide: 114 Maneuver + this one.
The first check compared against Generic and scored 0/50, which read as brand new:
the wrong record of a multi-record object is not a control.

REFUTED: "Booster is the player craft's flight envelope".  flight-speed-law.md
measured ~125/~420/~1530; unit Maneuver 100/350/1200 gives consistent ratios
1.25/1.20/1.28, Booster 100/612/2100 gives 1.25/0.69/0.73.  Booster is a SECOND,
faster profile: 39 of 50 values identical to the player unit, and of the 11 that
move the three velocities scale by exactly x1.75 and two accelerations by x1.5.

BLOCKED: the AnalogRevice/Tweak reader.  That schema is its own contiguous block at
0x820A119C-0x820A1378 headed by ControlTweakName -- 0 of 28 strings has a code
xref and no instruction anywhere names a 0x820A1xxx operand, against a control of
36/36 on the neighbouring block.  Runtime watch needed.

Caught on the way: mov_stick_play/eye_stick_play exist twice; the referenced copy
belongs to sub_822AE628, which reads GP_HANGAR_ARSENAL's ControlTweak records (6 of
them, and none anywhere else) -- a clean, wrong answer if taken for ours.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 20:33:27 +00:00
Claude (auto)
c171a194d9 re: the GP_MAIN_GAME unnamed block -- 333 of 337 already owned, 4 genuinely new
All six language copies carry the identical 337 unnamed IDXD hashes.  Partitioned
by record-name shape (53 shapes), 333 map onto families the corpus already
documents: 131 weapon datasheets, 114 unit datasheets, 64 unit Faces tables, 10
unit message sets, 8 chatter rule tables, 5 enumerations/formations/placement.

114 is exactly the corpus's unit count (43 Craft + 71 Vessel) -- these are the
tables the corpus has always worked with, reached by SHAPE because they have no
names.  Naming them adds nothing.

The Enumerate object in each GP_MAIN_GAME_* is EMPTY, zero fields, which is why
route 2 named 1283 entries in DefTables and 0 here.

The 4 unclassified objects are new -> docs/re/structures/player-tuning-tables.md:
the analog stick response curves (8 axes, 11 samples + a named Count = 11, tested
8/8; yaw/roll/throttle are the identity ramp, the shaping is all on pitch and the
camera axes; adv_yaw is non-monotone and unexplained), the player craft's flight
envelope (Booster, 50 fields -- the player side of the AA_/AV_ pair documented
for NPCs -- plus TacticalManeuver, SpecialAttack + three gauge bands,
SpecialWeapon, Misc), the Stage 16 boss (identified by Shell_S16Boss_* ids;
Guardian HP 65000, Core 42000), and one unidentified Generic naming eff_n0071.

New regenerator main_game_unnamed.py, 112-line artefact, byte-identical across
two runs; the other twelve artefacts verify unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 20:17:10 +00:00
Claude (auto)
53084a11ca re: the Enumerate declaration tables close DefTables (130 -> 1413 of 1425 IDXD)
The string harvest could never name DefTables because those names are not
spelled out as "something.tbl" anywhere on the disc -- they are declared.

An IDXD object whose single record is named Enumerate is a DECLARATION TABLE:
its field NAMES are the names of other objects, each resolving as
name_hash("<field name>.tbl").  EnumLODSet_test.tbl declares 676,
EnumGameModel_test.tbl 360; the disc holds 144 such objects (138 in DefTables,
one in each GP_MAIN_GAME_*) declaring 1298 distinct names.

Route 2 names +1283 entries route 1 could not.  130 + 1283 + 12 = 1425, no
overlap, 99.2 % coverage.  Zero partials: of 5 suffixes x 6 prefixes, ('', .tbl)
scored 1036/1036 and every other combination scored 0.  Residual in full: 8
declaration tables nothing declares, 2 LOD sets (Model rou_e004 / rou_e013), 2
motion sets; 15 declared names have no pak entry at all.

REFUTED alongside it: the 40 XPR2 manifests are not the naming source -- their
82 Name= values and 82 DataFile/Source paths resolve 0 entries under any of the
5 suffixes.  They share the MODEL namespace only: 40 of the 82 appear as the
Model field value inside the tables.

Artefact +17/-18, every line paired, byte-identical across two runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 19:59:20 +00:00
Claude (auto)
bcc23cba4a re: why each archive is low-coverage -- two different reasons, cleanly separated
Splitting every unnamed entry by magic turns the coverage percentages into an
explanation.

The three low-coverage UI archives have ZERO unnamed IDXD.  GP_HANGAR_ARSENAL
is 180 IDXD, 180 named, 0 unnamed -- its 1191 unnamed entries are 1149 T8aD/RATC
plus 42 LSTA, i.e. sprites.  GP_MISSION_SELECT and GP_DEBRIEFING_PILOTLOG hold
no IDXD objects at all.  So "22.6 % named" is misleading: every data table in
that pak is named, and these three are the same artwork-naming phenomenon as the
blocked 2D and READY_ROOM archives.

DefTables is the only genuine data gap: 1425 IDXD, 130 named, 1295 unnamed, in
17 record-name shapes -- Generic + Level_0..Level_3 (807, LOD sets) and Default +
EnumMotions + Generic + ReferenceFrames +/- Motion_break/dead/down (463, motion
sets).  One LOD and one motion table per model.  Level_0, EnumMotions and
ReferenceFrames appear in no document.

Control: the 100 % archives have no unnamed entry of any kind, and GP_MAIN_GAME_E
is 667/337 IDXD with zero unnamed artwork -- two failure modes, not a gradient.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 19:41:29 +00:00
Claude (auto)
589d09f2b5 re: all 24 settings objects named -- stage\StageParameter_S<NN>.tbl
The queued item (the 29-object resource manifest) turned out to be already
settled by challenge-mission-gate.md and stage-mission-tables.md, so the
iteration moved to the open question those docs make answerable.

Hashing EVERY string that appears in any IDXD object on the disc resolves all
24: StageParameter_S01..S16 (story), S24..S29 (challenge), StageParameter_
Tutorial shared by all six tutorials, and StageParameter_Test.  16+6+1+1 = 24,
which is exactly why the settings family is 24 against 29 stage records.

IsBoss16Enable confirmed independently: that object is StageParameter_S16.
GravityFactor is non-zero in S10 (700), S11 (400), S03 (250), S27 (250); the
three unscored objects are S24, S27, S28; the one without SplinterCell is _Test.

Refuted on the way: TOC order is not stage order (the TOC is hash-sorted and the
Boss16 object is 17th, not 16th), and neither the StageResource template nor a
real 19-field record names the settings table.  The AUTO_SETTINGS filenames are
a different scheme -- none of the 28 hashes to any of these 24.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 19:05:28 +00:00
Claude (auto)
5f764458cd re: the Phase_1/2/3 block of the settings family -- mostly per stage, not per phase
72 records, 94 distinct field names, 31 in every one (post-processing core, five
Fog*, three ScreenColor*, SpaceSize, five Supply*, UnderCommandSquadron, the
first BGOperate slot).  Optional families sit in clean tiers: Nebura_* and
ColorLayer* in 69/72, DOF_*/UnsharpMask_*/ExposureKey_* in 15/72, FinalPass* in
3/72.

REFUTED: BGOperateFrameCount is NOT the number of BGOperateFrameName_i slots --
36 of 72.  What holds is Count <= slots, 72/72: a fixed slot array with a live
count, the same shape as MessageCount under the 32-slot clamp.

68 of the fields common to all three phases NEVER differ in any of the 24
objects.  The phase block is a per-stage environment block copied three times;
what a phase change is actually for is the backdrop animation (BGOperate*) and
the supply/command squadron assignment.

MapPath is not in this family -- its 87 records per pack are 29 x 3, the
resource manifest's Phase_N.

Also fixed a non-determinism the verify loop caught: most_common() over a set
iteration ordered ties differently per run; now sorted by (-count, name).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 18:50:27 +00:00
Claude (auto)
26b7728ceb re: the rest of the stage-settings object -- cameras, player limits, difficulty
53 objects in GP_MAIN_GAME_E carry a Phase_1 record, in TWO families: 29 are
the resource manifest the corpus already owns (Phase_N = 4 fields) and 24 are
the settings table (Phase_N = 31-90 fields).  That answers the 24-vs-28 puzzle
left open by the scoring entry -- the 29 is a different table, not the settings.

Camera: three chase rigs in metres, 13 of 14 fields identical in every stage --
Nose (0, 4.5, 7), Near (0, 10, 40), Far (0, 15, 80), FOV 0.92; only CameraFar
varies, once.  Player: BulletLimit 512 / HomingLimit 256 / LaserLimit 32 and
the three 0.30 axis adjustments are constant, GravityFactor is non-zero in 4 of
24 stages, and IsBoss16Enable appears in exactly ONE object -- the first
per-stage handle for a family whose filenames do not resolve.

Difficulty_Easy/Normal/Hard is a SECOND difficulty record (8 damage and
guidance multipliers), separate from Score_*.

New doc structures/stage-settings-table.md; mission_scoring.py extended.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 18:35:05 +00:00
Claude (auto)
192d3bf67f re: mission scoring and the S/A/B/C/D rank thresholds
sub_8230D1F8 -- the loader that contaminated the AA_/AV_ offset search -- is the
stage-settings loader.  Reading its 122 field NAMES instead of its offsets found
the scoring block, which nothing in docs/re owned.

24 IDXD objects per language pack x 6 = 144, each holding Score_Easy /
Score_Normal / Score_Hard: 72 records per pack on one 22-field schema, no
variants.  Difficulty moves 10 of the 22 fields and never the five RankScore_*
thresholds -- the rank bar is per stage, difficulty scales the earning rate
(x0.5 / x1.0 / x2.0) and the penalties.  23 of 24 objects differ from the
commonest Normal record; 9 of 72 records zero the scoring entirely.

Not settled: which object is which stage.  None of AUTO_SETTINGS's 28 filenames
resolves to any of the 24 under 19 prefixes, and 24 vs 28 is unexplained.

New doc structures/mission-scoring.md, regenerator mission_scoring.py, artefact
data/mission-scoring.txt.  Twelve artefacts now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 18:22:36 +00:00
Claude (auto)
1a1a8d98d9 re: the AA_/AV_ selection is BLOCKED for static RE, with the mechanism
Route 3 (call-graph bound): sub_821A6CF0 and sub_821AB650 each read 196/200/204
off THREE different base registers -- three unrelated objects, not the block.

Route 4 (data-flow bound): the definition object lives in the global at
0x828F358C; 18 functions touch it, 9 also touch block offsets.  The two best
are refuted -- sub_8230D1F8 is the rank/score loader storing
CraftScore_Adjustment, FFPenalty_Zessel_Maximum and RankScore_S/A/B at exactly
256/320/324/328/332, and sub_82398CC0 uses r19 as a float-constant pool.

So the offset region is shared by two unrelated objects and a constant pool:
offset-based discrimination is contaminated by construction, which is why it
has now failed three times.  Catching the selection needs a runtime watch.

Side finding, unowned by the corpus: sub_8230D1F8 is the rank/score loader.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 18:07:42 +00:00
Claude (auto)
4ffbd39a01 re: AA_/AV_ are one interleaved block; the selection is still unfound
sub_822F9498 (the unit-definition loader, called only from sub_821A6CF0) is the
only function referencing the AA_/AV_ name strings.  Mapping each name to the
stfs that follows gives an exact interleave: AV_ at X, AA_ at X+8, for all five
axes -- PitchPlus 196/204, PitchMinus 212/220, Yaw 228/236, Roll 244/252,
AxisMode 320/328.  A selector is an offset of 0 or 8, not two lookups.

The 20 strings exist twice in the image; the first block is referenced by
nothing.  Data control: the ten suffixes match exactly between families.

The selection stays a reading: functions loading two or more of 196/204/320/328
number 39 image-wide, and only two are call-graph-reachable from the loader.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 17:54:53 +00:00
Claude (auto)
36f97c6322 re: the player-facing chatter partition; correcting the wingman claim
The 13 tables that voice every player-facing event are all
PresetMessage_Katana_*, the PLAYER's own sets -- unit-group-table.md already
names the link (DeltaSaber_T_Player carries msg=MessageSet_Katana).  Exact
partition: 13 voice all 14 events, 131 voice none, 0 voice some, and no
non-Katana table voices any of them.  CharacterKATANA is the only speaker
exclusive to the set.

Corrects preset-message-rules.md, which called those the wingman tables.  The
wingman roster is owned by isl-condition-builtins.md (UNITS: Bird1-Sandra ...
Rhino2-Katana, Rhino3-Ellen) and needed no experiment.

Residual: Katana_09_S10-1 and Katana_14_S16-2 voice none of the 14.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 17:42:28 +00:00
Claude (auto)
b710c9b9f0 re: Interval and IntervalFluctuation are the cooldown; the chatter system closes
sub_82210C38 retires a line and is the only chatter function reading the rule's
+20/+24 as words: node[+16] = Fluctuation * rand01 + Interval frames, state
0x20.  Interval is a floor, Fluctuation a uniform additive jitter.

The queue node IS the cooldown timer -- the tick counts +16 down and only then
frees the slot, so the already-queued bail in sub_82210670 and the cooldown are
one mechanism: a speaker cannot repeat an event until its node expires.  A
lingering node still holds one of the 128 pool slots.

Also: +20 on the node is a ducking level, raised while a higher-priority line
plays and released on retirement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 17:28:57 +00:00
Claude (auto)
f78565fe9e re: the bark queue, Priority ordering, and Yes = a one-shot line
sub_822109B0 allocates 68-byte nodes from a pool capped at 128; the Pattern
byte at +23 doubles as the occupancy flag.  Priority orders the pending list
descending; the >=10 front-push arm is dead because the disc's range is 1..9.

The tick sub_8220FC50 does rule[+36] |= (1 << node[+25]) & rule[+32], so a
line enters the used mask only if its Yes bit is set: Yes = one-shot, No =
repeatable.  388 one-shot lines on the disc.  Closes what reads +32.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 17:17:01 +00:00
Claude (auto)
8dc270091b re: the bark firing sites and the Probability roll
36 of the 64 event names are executable strings, so the string-xref join names
the eight sites that raise them; all eight share one entry, sub_8220FA98.
Probability is a per-cent roll -- the scaling constant at 0x820856F8 is exactly
100.0.  sub_82210670 suppresses an already-queued (speaker, rule) pair, treats
a message as eligible only if its bit is clear in the runtime mask at +36, and
picks uniformly, then hands Pattern/Priority/EffectiveTime to sub_822109B0.

Measured negative: the pick path never reads +20, +24 or +32.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 17:04:18 +00:00
Claude (auto)
6ee041c1bf re: the chatter phase merge is not additive; 224 authored variants never play
sub_82215A58 reads CrewCount + PresetMessage_Phase1/2/3 and reaches the loader
from ONE call site, so the three phase tables fold into one map keyed by the
event record name.  Every merge collides; sub_82213840 reconciles on the
message list plus +16/+17/+18/+20/+24/+28 (NOT the +32 Yes mask), and the
incumbent always wins.  Measured: 26432 collisions, 26208 identical, 224
different (189 differ only in the message list), 0 mask-only differences.

Refuted handle: intersecting functions by the object's offsets finds dozens of
unrelated layouts -- offset shape is not an identifier.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 16:50:42 +00:00
Claude (auto)
c4fd90baa9 re: the seven chatter fields, read off sub_82213980
The one function referencing all seven field-name strings is the rule table's
loader.  Interval / IntervalFluctuation / EffectiveTime are SECONDS, emitted
as *60 frame counts; Probability is a percentage and zero skips the record;
Pattern is a 4-arm enum of which only Sound and Window ship; the Yes/No pair
element is a u32 bitmask, which is why MessageCount is clamped to 32 (max on
disc is 26).  40-byte object layout recorded.  13 dead records characterised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 16:35:56 +00:00
Claude (auto)
6a81268cf1 re: all 144 chatter rule tables named; the undeclared 8 are NOT tutorial
Three more naming routes: strip _msg from a message table (137/144, superset
of route 1, zero non-rule hits), predict the name from the Sperkers roster
(6/6, control 0/4), and sweep the naming grammar (1/144).  Union 144/144,
and every rule table has its _msg companion.

Refutes the reading left by 2c2af7a: the undeclared tables are two story-stage
tables and six TCAF fleet/ship tables, no tutorial content at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 16:22:19 +00:00
Claude (auto)
2c2af7ad78 re: the reactive-chatter rule table behind ORDOR_SQUADRON_EXTENDED
864 records = 144 rule tables per language pack x 6.  One schema for all
9216 event records; MessageCount*2 == positional count with zero mismatches.
Named 136/144 by two independent routes that agree as sets.  2388/2405
message ids join the settled sound-cue table.

Corrects squadron-orders.md: the executable misspells all four SQUADRON
entries of the 0x820AEEB0 enum as ORDOR_, and the disc data matches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 16:07:17 +00:00
Sylpheed RE agent
15b97af170 re: which archives the disc can name -- 100% for menu paks, 0.0% for the 2D paks and READY_ROOM
Chasing more prefixes for the 419 HUD config paths would have been the
same mistake twice, so this censuses the whole disc: harvest every
plausible asset-name string from every archive (6027), hash each under
the 16 known path prefixes, and ask per archive what fraction of its TOC
that explains.  idxd-container.md and idxd-tag-hash.md own the hash;
neither says which archives are reachable by it.

The result is bimodal.  GP_TITLE 16/16, GP_PAUSE_MENU 11/11,
GP_STAGE_CLEAR 44/44, GP_CHALLENGE 151/151, GP_MOVIE_THEATER 56/56,
MiscBin 40/40, GP_GAMEOVER, GP_BUNK, GP_SYSTEM, GP_TUTORIAL and fonts
are at 100%; tables.pak 78/79, GP_DIALOG 139/140, the six language paks
115/117; GP_MAIN_GAME_* 751/1119.  Then the cliff: the six
GP_MAIN_GAME_*2D.pak at 0 of 711 each, and GP_READY_ROOM at 6 of 1106 --
the largest UI pak on the disc, not previously noted anywhere.

Eleven paks at 100% in the same run is the control that makes 0.0% a
finding rather than a failed guess.

So the 419 HUD paths are not missing assets: nothing in the 2D paks is
reachable by name from the disc's own strings at all.  Those TOC keys
hash names that are not written anywhere readable.

Also refuted first: the 419 values under 13 name transformations, every
one scoring 0 against the E2D 711 and against all 16630 entries.

Not settled: what those names are.  The lever left is the hash's shape
-- the top byte is the character-sum checksum -- but that needs a name
corpus the disc does not contain.

New structure doc, artefact and regenerator; the other eight regenerate
byte-identical.
2026-08-27 15:08:24 +00:00
Sylpheed RE agent
bb37b16847 re: the in-game HUD config -- 16 records, 419 asset paths, a per-stage ResourceTable
Checked first: no docs/re file mentions ArmsStatus, RangeFinder, Radar,
Sight, Wing, NamePlate or ResourceTable.  Only HudResource had been
opened; the other fifteen records had not.

The six IDXD entries of GP_MAIN_GAME_E2D.pak: two carry the 16-record
HUD config, two the 13-record ObjectiveMarker_*/TutorialMarker_* set,
one Face (52 portrait sprites), one ResourceTable.  Between them they
name 419 distinct asset paths -- the whole flight HUD -- with new
subdirectory prefixes throughout (ArmsSt, ActvArm, RangeF, Marker,
Manuva, Map, Speed, Radar, Sight, Wing, Hitmark, Lockon, Info).

The decisive control uses the config's own exact path strings, so no
guessing is left in the loop: 419 distinct .prt/.t32/.tbl values, ZERO
resolve as a pak entry under 10 prefixes, and the four config filenames
resolve to nothing either.  The .t32 sprites certainly exist -- 574 T8aD
in that pak.  So the 2D pak is not addressed by name_hash of the name
its config uses.

This supersedes the earlier framing: the 28 'dangling' .prt names were
never a missing-asset story; they are 28 of a set where none of the 419
resolves.

ResourceTable is 58 positional fields = 29 pairs, alternating
HudResource.tbl / HudMarkerResource.tbl, identical in all six language
paks, with exactly one override at pair index 25 -- HudResource_S26.tbl.
Reading: indexed by stage number minus 1, so index 25 is S26, the one
stage with its own HUD config.  Arithmetic exact, indexing unproven, not
adopted.

New structure doc, artefact and regenerator; the other seven regenerate
byte-identical.
2026-08-27 14:53:49 +00:00
Sylpheed RE agent
acbcd8d75c re: Stage\script.tbl's 11 non-MISSION fields, and the mission dialogue table
structures/mission-script-ssb.md owns this manifest and names its 40
fields but never read the 11 that are not MISSION<n> = StageNN.ssb.
They are DIALOG_MESSAGE, DIALOG_LOCAL_STRING, FONT (+size), TEXT_POS,
TEXT_LINES and the five pgmsg_*.prt.  Two sibling records were also
unread: GP_TEST (PATH = dat\GP_TEST\, a debug archive not on the disc)
and TEXTS (a second text style).

Probed 7 values x 33 prefixes x 41 archives.  One resolves:
message\MissionDialogMessage.tbl, in all six GP_MAIN_GAME_* paks --
200 records, 25280 bytes, every name S<NN>_P<n>_<KIND> with five kinds
40 each (HINT_PAUSE, HINT, OBJECTIVE, GRAPH, LOSE), fields positional
and tagged 0..3, each value a message key.  An index from (stage,
phase, kind) to the localised strings, on the same S<NN>_P<n> keying
the ISL corpus already uses.

Control: the 40 stage-phases span stages 1-16 and 24-29 -- a subset of
the 28 shipped, and the six with no hints are exactly 18-23, the
tutorials.  A fifth independent route to the story/tutorial split, and
it gives the phase count per stage.

The other six do not resolve, with the control in the same sweep:
MissionDialog_local_string.tbl and all five pgmsg_*.prt are not a pak
entry under any of the 33 prefixes, while
message\MissionDialogMessage.tbl and Stage\script.tbl both resolve in
6 archives.

name_hash is CASE-INSENSITIVE (message\ == Message\); tag_hash is not.

New structure doc, artefact and regenerator; the other five artefacts
regenerate byte-identical.
2026-08-27 13:37:31 +00:00
Sylpheed RE agent
28a59d839f re: the disc's stage numbers, and the player's craft per stage
The corpus knew '28 stages' and 'six tutorials with no AIParams'.  It
did not know the NUMBERS, and they are not 1..28.  Hashing
stage\UnitGroup_S%02d.tbl for N=0..39 against GP_MAIN_GAME_E.pak:
1-16 and 18-29 ship, 17 does not -- 28 files -- and the six with no
AIParams_SNN.tbl are exactly 18-23.  So S01-S16 story, S17 absent,
S18-S23 tutorials, S24-S29 story: 22 + 6.

eng\GP_HANGAR_ARSENAL_3D.tbl's ResourceID record keys a player-craft
mesh by stage: Unit_St1_6 -> rou_f001 (DeltaSaber T), Unit_St7_16 ->
rou_f002 (W), plus six further fields tagged with the raw numbers
24,25,26,27,28,29 -> rou_f002 x4, rou_f004 (DeltaSaber A) at 28,
rou_f002.  The bare tags are the last six story-stage numbers.  Control
in the same record: tag_hash(name) == tag for 11/11 named fields, so
those six genuinely carry no name.

Cross-check from a different file: grouping the Arsenal pak's 168
stage-scoped entries by which _Player craft their loadout mounts gives
f001 = 6, f002 = 15, f004 = 1, tutorials 5+1 = 6, and every number
closes against ResourceID -- 6 = Unit_St1_6; 15 = Unit_St7_16 (10) plus
tags 24,25,26,27,29 (5); 1 = tag 28; 6+15+1+6 = 28.

So the player flies the DeltaSaber A in exactly one mission, S28, and
the DeltaSaber T only for the first six.

Not settled: the Arsenal entry filenames, so which of the 168 is S24 vs
S25 is constrained but not pinned.

New structure doc, artefact and regenerator; the other five artefacts
regenerate byte-identical.
2026-08-27 13:18:42 +00:00
Sylpheed RE agent
2b2d984f0c re: the Hangar loadout system -- loadout, allow-list, arsenal item
15 loadout records, one per flight position x pilot (Bird1-Sandra ..
Rhino4-Yoji), each with Arm1/Arm2/Arm3/Nose + UnitID.

The same trap as PlayerWeapon, one level up: Arm1/Arm2/Arm3/Nose do NOT
name items.  They name a per-slot ALLOW-LIST record -- one of 24 whose
only named field is Type (the slot kind) -- and the candidate items are
that record's positional, unnamed fields, in order.  Four hops:

  Rhino4-Yoji.Arm1 -> STANDARD_ARM1 -> [Falcon_9AM, Condor_105AM, ...]
  -> item.PlayerWeapon = Turret_NNN -> slot.WeaponID -> Weapon.ID

Controls: Arm1/2/3/Nose -> allow-list record 60/60; allow-list
positional entries -> arsenal item 70/88, and every one of the 18
misses is the single sentinel No_Equipment -- one of the four
WEAPONS-roster values with no item record, i.e. the empty-slot marker.

UnitID is two ID spaces at once: 5 rows name a unit Generic.ID (the
three -Katana rows are the player -- a _Player craft plus an extra,
empty PlayerUnit field), 8 name a character, resolving as Character +
the value into the 64-record character table.

Two values resolve to nothing, both single rows against 13 that do:
Rhino2-Ellen.UnitID = UN_f001_TCAF_DeltaSaber_W exists nowhere (checked
as a Generic.ID across every pak and as a record name, 0 hits) while
UN_f002_TCAF_DeltaSaber_W does -- consistent with a shipped typo,
reported not diagnosed -- and Rhino4-Brandon.UnitID = BRANDON has no
CharacterBRANDON among the 64.

New structure doc, artefact and regenerator; the other four artefacts
regenerate byte-identical.
2026-08-27 12:58:53 +00:00