Commit Graph

332 Commits

Author SHA1 Message Date
Claude (auto)
19b6eb3ee6 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)
54e36349a4 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)
03dfb02466 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)
1d3db31d3d 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)
5ac7b9527f 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)
fb0b5fbb28 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)
7d34428bec 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)
52c08b7246 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)
5e7a988afc 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)
c679699c1b 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)
1d372d8083 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)
0f7ce6d6cb 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)
ecf54a4512 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 3f20787: 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)
3f20787cdc 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
6ea3d60c6f re: the objective/tutorial marker records; S24-S29 are CHALLENGE, not story
Correction first.  stage-numbering-and-player-craft.md called S24-S29
'story' and reported 22 story stages.  challenge-mission-gate.md OWNS
that split and already had it: S01-S16 story, S18-S23 tutorials,
S24-S29 challenge.  A third confirmation fell out of this iteration:
AUTO_SETTINGS names stage01..16_settings.tbl, tutorial01..06 and
challenge01..06, and its field tags are 1-16, 18-23, 24-29 -- the
shipped stage numbers exactly.  The campaign is 16 story missions, not
22.  AUTO_SETTINGS and IGNORE are likewise already owned by
isl-condition-builtins.md, so nothing there was new either.

What is new: the 11 marker records hud-config.md listed as unread.  12
named fields each, portable HUD data.  Colour is ARGB, three per marker
-- objectives red 0xFFFF0000, guard/waypoint green 0xFF20FF20,
Acropolis/tanker cyan 0xFF00FFFF, sub-objective yellow 0xFFFFFF00, and
every TutorialMarker_* amber 0xFFFFA010.  Each carries a Normal sprite
and an Emphasis part with optional Sub companions; HPGauge 0/1/2;
RadarCursorType Circle/Rectangle/blank; BlinkCycle 0x800 on all eleven.
TutorialTarget is Yes on exactly the five TutorialMarker_* and No on the
six ObjectiveMarker_*.

Parameters binds the roles: TargetMarker -> ObjectiveMarker_Target,
HelpMarker -> ObjectiveMarker_SubObjective, TutoTargetMarker ->
TutorialMarker_Target, plus ReloadDispTime 0.1.
Enumerate_ObjectiveMarkers is the 11-name roster.

Artefact +13 lines / 0 deletions; the other nine regenerate
byte-identical.
2026-08-27 15:46:31 +00:00
Sylpheed RE agent
b9c6253db4 re: CoverArea is a 6-bit MOUNT mask; the three other turret leftovers closed
Six bits, never more.  Over all 835 turrets: 41 distinct values, max
0x3f, bits 6/7 set on none; per-bit 709/117/274/274/260/237.  0x00 (34)
= the empty Weapon_NULL hardpoints with YawLimit 0; 0x01 alone (439) =
craft hardpoints with YawLimit 2.5/1.0/0; 2-6 bits (362) = warship
mounts with YawLimit 45-180.

It tracks the MOUNT, not the weapon.  UN_e107_ADAN_AAFrigate has eight
identical AAFrigate_AAGun turrets: GN_GunXS_01..04 are 0x1d and 05..08
are 0x2d -- same gun, same YawLimit 120, different mask.  The Battleship
spreads five masks over GN_TGunL_01..05 / GN_TGunM_02..03, all firing
the same CAF_Ship_ASGun.

Which sector each bit denotes is NOT determined; six bits and the name
invite +-X/+-Y/+-Z or six hull faces, but nothing static fixes the
convention.  Not adopted.  Refuted on the way: bits 2 and 3 are not a
mutually-exclusive pair -- 188 turrets set both.

The 26 weapons no turret mounts are a coherent set: 11 _P player
variants, the nose/twin mounts, two _Child sub-munitions, the three
S16Boss_*, three Weapon_Test_*, and ADAN_Attacker_S_GunTurret.

Versatile_NNN ships nowhere -- 0 populated records across all 41
archives, only the ??? template row.

The two units one turret short: both extras are missile mounts with no
Frame.  Elan_EX4 = NoseGun + Missile, both mask 0x01, both frameless;
AAFrigate_EX4 = eight framed guns plus one Ship_AAMissile at 0x0c with
no Frame.  n=2, stated as the observed pattern, not a rule.

New artefact and regenerator; the other nine regenerate byte-identical.
2026-08-27 15:35:09 +00:00
Sylpheed RE agent
431461a175 re: BLOCKED -- the 2D / READY_ROOM TOC names, three routes closed with controls
1. A different hash family.  The corpus knows three (idxd-tag-hash.md):
name_hash, tag_hash, ixud_hash.  Scoring all 5977 harvested names x 6
prefixes: GP_TITLE 8/16 and GP_PAUSE_MENU 6/11 under name_hash (the
positive controls), and tag_hash and ixud_hash explain NOTHING anywhere
-- including the paks name_hash does explain.  So they are not the TOC
function, and the unnameable pair is not keyed by a different one.
GP_MAIN_GAME_E2D stays at 0/711 under all three.

2. The executable.  sylpheed.db's strings table holds 7140 rows, of
which exactly two look like asset paths -- Data\gmicon002_2.t32 and
Data\gmicon006_2.t32, in a Data\ directory nothing else on the disc
uses -- and neither resolves in any archive.  The binary is not the name
source; it holds two strays and no table.

3. Name transformations -- 13 of them on the 419 config paths, all 0.

The container runs out here.  Those TOC keys hash names that exist on
neither the disc nor the executable in readable form.  The only lever
left is a dictionary attack using name_hash's shape (top byte = the
character-sum checksum), and that needs a plausible name corpus this
disc does not contain.  Noted as blocked rather than improvised around.

The port does not need these names: sprites and bundles are readable by
content (T8aD, RATC), and the config records already say which asset
each HUD element uses.  Only the archive-key to name mapping is missing.

Artefact +22 lines / 0 deletions; the other eight regenerate
byte-identical.
2026-08-27 15:22:13 +00:00
Sylpheed RE agent
4e9d7deeb8 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
152704afeb 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
8f3973b1c9 re: the variant reading is refuted; HudResource is the in-game screen config
Censused the 32 bundle-less in-game .prt names with the 68 as control:
4 of 32 appear as an element of some 2D bundle, against 58 of 68.
Being an element is normal for a part that exists; these are not
elements either.  Last iteration's 'not adopted' reading is refuted.
28 names are neither a bundle nor an element.

They are not unreferenced: a HudResource record inside the 2D paks' own
IDXD entries names them -- the in-game screen config, parallel to
tables.pak for menus.  GP_MAIN_GAME_E2D.pak has 6 IDXD entries; two
carry HudResource (24 named fields) beside ArmsStatus, Map, ArmsItem,
RangeFinder, Marker, Manuva, Number; two more carry the
ObjectiveMarker_*/TutorialMarker_* set.

Its values carry subdirectory prefixes -- Hitmark\, Lockon\ -- which my
hand-written prefix list never had.  So I rebuilt the negative the right
way round: harvested every IDXD field value containing a backslash, kept
the 60 commonest directories, and re-swept.  241/376 with the hand list;
241 with hand + harvested -- zero new resolutions.  After the language\
miss, this is the closure that counts: the prefix list came from the
disc, not from me.

Still open: whether the 28 are cut features or assembled at runtime from
sprites.  Nothing static separates those two.

Artefact +45 lines / 0 deletions; the other six regenerate
byte-identical.
2026-08-27 14:42:20 +00:00
Sylpheed RE agent
4d9f7ce563 re: an embedded .prt part is a top-level RATC bundle, addressed by its element prefix
Read first: ui-rat-layout.md OWNS the RATC stack and already documents
the 60-byte element declaration table and 'one bundle = one
(context x language) build of that screen'.  What it does not say is how
a part is addressed when it is NOT a pak entry.

The elements of one bundle share a common name prefix, and that prefix
is the part name.  GP_MAIN_GAME_E2D.pak has 130 bundles with 114
distinct element prefixes -- pgmenu_btn00, pghud_wing, pgface,
pghud_range, pgmanuva_eff0 -- exactly the in-game part names.

The 100 in-game-only .prt names match a 2D bundle prefix 68 times; the
control, the 241 shipped parts, matches ZERO.  The two families are
disjoint on the test.  13 shipped parts match a bundle prefix in their
own screen pak, which is the expected shape.

Four of the five mission banners land here: pgmsg_start.prt is E2D
bundle 0x89fac252, a one-element bundle declaring pgmsg_start_sub.rat;
likewise _end_, _failed_, _restart_.  pgmsg_update has no bundle at all,
consistent with having no _sub.rat.

32 in-game names still have no 2D bundle.  They cluster into families
whose base name is a bundle, reading as a variant declared inside a
parent bundle -- a reading, not adopted.

Artefact +38 lines / 0 deletions; the other six regenerate
byte-identical.
2026-08-27 14:27:38 +00:00
Sylpheed RE agent
dc868e14c3 re: who references a .prt decides whether it ships -- menu named, in-game embedded
Censused the whole 135 unresolved set instead of chasing the five.
Cross-tab over all 376 .prt names: referenced by tables.pak AND
resolves 241; neither 100; referenced but absent 35; resolves WITHOUT
being referenced by tables.pak -- ZERO.

So resolving implies a tables.pak reference, 0 counterexamples of 376.
tables.pak holds the menu screen configs and every part it names ships
as <lang3>\<name>.prt.  The 100 names that appear only inside the
GP_MAIN_GAME_*2D.pak bundles never ship as entries -- the in-flight HUD
is authored into the bundles, not loaded by name.  The five pgmsg_*.prt
were never a special case; the split is menu = named + shipped,
in-game = embedded.

It is not a prefix rule: 6 of the 135 pg* names do resolve (pgloading,
pgloading2, pgmsg_scr, pgpause, pgpause_ttrl, pgpbase), because
tables.pak names them.

The 35-name residual is one coherent family -- pgmenu_btn*/item*/pad,
pgfacewin*, pgtextwin, phinfo1-3, 02d, 02d_scr, psview_release: the
in-game pause menu and squadron-order overlay, named by a menu config
but drawn from the in-game bundles.  Consistent with the split, not
separately proved.

Artefact +44 lines / 0 deletions; the other six regenerate
byte-identical.
2026-08-27 14:13:41 +00:00
Sylpheed RE agent
da82f8efba re: where .prt screen parts live; the five mission banners are a real exception
Read first, as the rule now requires: ui-prm-primitives.md,
ui-rat-layout.md, ui-screen-runtime.md -- they own the .prt/RATC/T8aD
stack but describe element names inside bundles, not where parts live.

A .prt IS a pak entry, under a LANGUAGE directory.  Of 376 distinct
.prt names referenced on the disc, 241 resolve as archive entries --
220 under eng\ and jpn\, 38 under each of the other four.  prbase.prt
resolves under all six; palogo1.prt under two.

All five pgmsg_*.prt resolve under nothing, across 15 prefixes that
include every language dir, so this is not the missing-prefix mistake
again.

A lead, refuted by its own control: four of the five have a same-stem
_sub.rat sub-bundle inside every per-language 2D pak, and pgmsg_update
has none -- but only 4 of 376 .prt stems have a _sub.rat, and
prbase.prt, which does ship as an entry, has none.  Not the container
convention, not adopted.

An intermediate pass nearly reported 'the five appear only in the
manifest'; that was wrong because the token census was truncated by a
slice and the RATC hits were pgmsg_start_sub.rat, a longer token.

New artefact and regenerator; the other six regenerate byte-identical.
2026-08-27 14:01:24 +00:00
Sylpheed RE agent
8a3c606706 re: correction -- the dialogue table was already in the corpus, and one negative was false
The previous commit announced message\MissionDialogMessage.tbl as a
find.  structures/ixud-localised-text.md OWNS it and already says
S02_P1_OBJECTIVE and friends are record names in an IDXD map,
message\MissionDialogMessage.tbl, whose positional fields list the
lowercase per-line IXUD names -- and that *_GRAPH is the odd one, a
single named field holding a texture.  I grepped the manifest doc and
not the text doc.  Third overclaim in four days; the rule is now to grep
the doc that owns the DATA, not only the doc that owns the FILE.

Worse, the headline negative was wrong.  MissionDialog_local_string.tbl
DOES resolve -- as language\MissionDialog_local_string.tbl, IXUD, in all
six GP_MAIN_GAME_* paks.  My 33 prefixes omitted language\, which is
precisely the convention ixud-localised-text.md records for language
paks.  A prefix sweep is only as good as its prefix list, and the list
should come from the corpus.

'LOSE has exactly one entry' was wrong too.  Per kind: HINT_PAUSE 4,
HINT 3, LOSE 4, OBJECTIVE 4 positional fields, and GRAPH 1 NAMED field
holding the .t32.  The single-field bucket was GRAPH, not LOSE.

What survives as new: the 11 non-MISSION field values read as a block;
the GP_TEST and TEXTS sibling records; the per-stage phase census; and
the five pgmsg_*.prt resolving nowhere under 34 prefixes x 41 archives,
now with two working controls in the same sweep.

Artefact 8 insertions / 3 deletions, every deleted line replaced by its
corrected form; the other five regenerate byte-identical.
2026-08-27 13:45:35 +00:00
Sylpheed RE agent
e8158f2dfd 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
0ae0d4bca4 re: the stage numbering is consistent everywhere; the manifest still declares the cut S17
Correction first: the previous commit claimed the corpus 'did not know
the stage NUMBERS'.  It did -- structures/mission-script-ssb.md has
recorded S01-S16, S18-S29, S17 absent by a three-way convergence (hash
lookup, the table sweep, the loader's != 16 guard) since before that
pass.  What was actually new there is the player craft per stage and
ResourceID's ordinal tags; the numbering is a fourth independent route,
not a discovery.

The question this item asked: do the ISL StageNN.ssb files and the
UnitGroup_SNN.tbl tables use the same numbering, or is something
silently mis-labelled?  They are identical.  Stage\StageNN.ssb,
stage\UnitGroup_SNN.tbl and stage\Route_SNN.tbl all resolve for the
same 28 N with an EMPTY symmetric difference; AIParams is the same minus
18-23.  Nothing in the corpus is mis-keyed.  Formation_SNN and
Message_SNN resolve 0/40 -- those are field names, not files, and the
four families above are the controls that make that negative real.

New: Stage\script.tbl's SCRIPTS record lists 29 MISSION*.ssb fields --
MISSION1..MISSION29, including MISSION17 = Stage17.ssb -- and only 28 of
29 name a file that ships.  Control: MISSIONn -> Stage<n>.ssb with the
same n, 0/29 mismatched.  So S17 is a cut mission the shipping manifest
still names, which is why the loader carries an explicit != 16 guard
rather than just running 0..27.

Artefact +14 lines / 0 deletions; the other four regenerate
byte-identical.
2026-08-27 13:27:19 +00:00
Sylpheed RE agent
caaa3193f6 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
0ea52c0f48 re: who selects a loadout -- the pak entry does; the Arsenal pak is stage-scoped
Settled by elimination with two controls.  The 15 loadout names appear
as a field VALUE nowhere on the disc -- 0 occurrences across every pak
-- while the control Arbalest_155KG, which is referenced, appears 150
times as a value in the same pak.  They occur only as the field names of
the UNITS record.  They are not in the executable either; control:
WEAPONS (a section key) is, Arbalest_155KG is not.

So nothing references a loadout by name.  The selection is which pak
entry the Hangar loads.  GP_HANGAR_ARSENAL.pak has 180 IDXD entries in
10 shapes; 168 carry a UNITS roster = 28 stages x 6 languages, and the
other 12 = 2 x 6.  Each entry is one stage's whole Hangar config, and
its UNITS roster is the flight for that stage -- 5 distinct rosters over
15 / 5 / 5 / 2 / 1 stages, including one where Rhino2-Katana flies
alone.  Every count is a multiple of 6 and they sum to 28, the disc's
stage count by a fourth independent route.

Corrects the previous commit: it read the pak with setdefault, so
'15 loadouts / 24 allow-lists / STANDARD_ARM1 has 11 entries' is the
union of first-seen records, not one table.  Contents vary per entry --
PlayerSET_ARM1 has 5 distinct contents, STANDARD_ARM1 3, ExSET_NOSE 4.
The chain and both controls (60/60, 70/88) are per-record and
unaffected; only the per-list sizes were over-generalised.

Not settled: the entry filenames.  name_hash probing over 8 templates x
40 indices x 6 languages resolved 0, while the same probe's controls
<lang>\weapon.tbl and <lang>\strings.tbl resolved 12/12.

Artefact +40 lines / 1 changed heading; the other four regenerate
byte-identical.
2026-08-27 13:09:07 +00:00
Sylpheed RE agent
01a5ed0e29 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
Sylpheed RE agent
2519427bab re: the 59-of-131 arsenal question is closed -- an item names a hardpoint
An Arsenal item does not reference a Weapon record.  It references a
Turret_NNN HARDPOINT SLOT on the player craft's own unit table, and the
slot is what carries the WeaponID.  Three hops:

  Arbalest_155KG.PlayerWeapon -> Turret_050  (a slot on
  UN_f001_TCAF_DeltaSaber_T_Player) -> .WeaponID ->
  Weapon_DSaber_P_wep_50_Cannon

Controls, both in the same loop: 0/59 distinct PlayerWeapon values are a
Weapon.ID; 59/59 are a Turret_NNN slot id; the full chain lands on a
Weapon.ID 59/59.  WingmanWeapon resolves identically.  The WEAPONS
roster's 59 = 55 item names + 4 empty-slot sentinels.

Wingmen fly a cheaper gun: following the same 59 slots across craft
variants, the _Player tables give each item its own weapon record (59
distinct) while the AI tables collapse all 59 onto 10 generic classes.
That is most of the 131.

Upgrades yesterday's 'hardpoint catalogue' reading from 21 to adopted,
proved from an independent file, and corrects its '10 distinct WeaponID'
figure -- that was the AI variant, not the player's.

Also adds an __main__ guard to unit_substructures.py so importing
pak_entries from it does not run its report; its artefact is unchanged
and still byte-identical.
2026-08-27 12:49:22 +00:00
Sylpheed RE agent
9281aa7456 re: the destructible-subsystem model -- a unit's sub-records
The corpus has named these since unit-struct-runtime.md but never
opened them.  Per unit table: Turret_NNN 835 records (max 63 on one
unit), ShieldGenerator_NNN 46, Thruster_NNN 38, Hatch_NNN 26,
Bridge_NNN 25, plus one each of Shield/Mass/SE/Explosion/
StructureCount and NS_Body on 68 of 114.

Turret/ShieldGenerator/Thruster/Hatch/Bridge are ONE record shape: a
shared 19-field destructible-part base (ID, Name, ParentStructureID,
Frame = a mesh NODE name, NomalModel, CollisionModel, Radius, HP, the
four Is* flags, SpreadDamage, damaged/destroy motion + time, and the
three Effect_*), with per-kind extras.  Turrets add WeaponID,
AngularVelocity, YawLimit, PitchLimit_Elevation/_Depression, CoverArea,
IsAuto, HasBarrel and up to 80 CannonModel_NNN/CannonFrame_NNN.  Shield
generators, thrusters and bridges add PowerRatio.  Hatches add
SquadronID, LoadedCount, MaxAvailableCount, TakeoffInterval -- a
carrier's launch bay.

Control 1: StructureCount.<Kind>Count == #<Kind>_NNN records, over 684
comparisons -- 612 equal, 55 "0 declared, one blank placeholder"
(55/55 blank in Name AND NomalModel AND Frame), 11 differ, 6 kind
absent.  All 11 exceptions are Turret and all are declared < records.

Control 2: 835/835 Turret_NNN.WeaponID resolve to an ID in the
131-record Weapon datasheet, zero unresolved; 26 weapons are never
mounted on a turret.

Refuted in the same pass: "the DeltaSaber's 59 non-NULL hardpoints are
the 59-name WEAPONS arsenal roster".  The counts match exactly and the
sets overlap in 0 values -- two namespaces, one coincidence.

New structure doc, artefact and regenerator; other artefacts unchanged.
2026-08-27 12:39:50 +00:00
Sylpheed RE agent
9abed411ce re: correction -- Generic is a per-FILE header record, not a table
Yesterday's page said "Generic (394 per pak) is the unit datasheet".
Only 114 of the 394 are.  Every IDXD file carries exactly one Generic
record and its schema is set by what kind of file it is.  Partitioned
by field set, identically in all six GP_MAIN_GAME_*.pak:

  114  has HP                        -- a unit datasheet
  204  {Count} only                  -- a dialogue file
   64  {ID, Name, SideID, Unique}    -- a character (36 TCAF + 28 ADAN)
   10  {EnumAsteroidGroup}           -- an asteroid group
    2  degenerate

204+114+64+10+2 = 394, and 178 distinct Generic.ID = 114 unit + 64
character, the only two shapes carrying an ID.  That settles the
"178 IDs vs 394 records" question the previous entry left open.

Positive control in the same loop: for the 204 dialogue headers Count
equals the number of Message_NNN siblings, 204/204, zero mismatches.

Cross-check from the other side: Maneuver = 114, Effect = 114, the
carrying-entry sets are identical, every unit Generic has a Maneuver
sibling, and Generic.Type splits 43 Craft + 71 Vessel -- the same
43/71/114 unit-struct-runtime.md reached from live guest memory.

New tool generic_partition.py + artefact; ISL artefacts byte-identical.
2026-08-27 12:29:30 +00:00
Sylpheed RE agent
1bcea1fd49 re: the static unit datasheet and the AI flight model
Same technique as the weapon tables -- field names are literal, so a
field-shape search over 190782 records finds the carriers.

Generic (394 per pak, 110 field names, 178 distinct IDs) is the unit
datasheet: HP, ShieldRatio, DefencePoint, AttackCraft/VesselPoint, the
five Resistance* terms, Size_X/Y/Z + radius, RadarRange 10000,
FCSRange 8500, MountedFCS, MountedShieldGenerator, Score/Damage/Mass,
NozzleCount with per-nozzle FX, and Model rou_e006 -- which ties the
sheet to the mesh names the corpus already decodes.

Maneuver (114) is the AI flight model: MaximumVelocity 1200,
CruisingVelocity 700, Acceleration 600, Deceleration 400,
SideThrustAcceleration 1000, Turn_AngularVelocity 180,
MaximumBank_Normal 60, per-axis DragFactor 3.0, afterburner and
reverse-thrust factors, plus nine named manoeuvres each with its own
timing/ratio/length bounds and AA_/AV_ rate pairs.  This is the static
source for what flight-speed-law.md measured at runtime.

Corrects a guess in the same pass: HP_CLASS/HP_ID are not ship stats --
they sit on records named for wingmen and ship classes alongside
HPGauge/RadarCursorType, i.e. a HUD gauge binding table.

New structure doc + artefact; ISL artefacts byte-identical.
2026-08-27 12:20:03 +00:00
Sylpheed RE agent
68676d266b re: the static weapon datasheet -- Weapon, Shell, AssortMissileParam
The corpus's weapon numbers came from a runtime capture; the static
tables are now read with no emulator.  The move: IDXD field names are
literal strings in the pool, so the stat-shaped keys already harvested
from the disassembly (MegaTons, GuidanceType, SpiralType, ...) can be
searched directly.  8648 records carry at least one, clustering on
three record names in every per-language GP_MAIN_GAME_*.pak:

  Weapon              131 records, 21 fields -- the launcher
  Shell               131 records, 37 fields -- the projectile
  AssortMissileParam    9 records, 20 fields -- missile guidance

131, not the 59 of the WEAPONS roster: that roster is the arsenal
menu's list, not the full set.

Controlled negative in the same pass: stageNN_settings.tbl does not
exist.  AUTO_SETTINGS names 28; four path prefixes give 0/116 while
both controls in the same loop are found.

Also decoded: EnumUnit (54 unit ids tying eNNN/fNNN mesh prefixes to
faction/class names), ArmsItemFile (59 weapon -> HUD icon mappings),
SETTINGS (sound config), Parameters (HUD markers).

New structure doc + artefact; ISL artefacts byte-identical.
2026-08-27 12:11:03 +00:00
Sylpheed RE agent
d082ef15ee re: decode the config records -- render/input constants, and a correction
parse() + named() on each named record (data/config-records.txt).

The find, directly portable: Rendering carries the tone-mapping and
bloom numbers (Brightness 1.4, Contrast 1.0, ExposureKeyValue 0.18,
BrightPassThreshold 0.6, LuminanceMin/Max 0.15/3.6, BloomScale 0.5,
AfterimageScale 0.05, StarScale 1.20, GlareType 8), ControlTweak the
input constants (CameraSpeed 0.50, TargetSpeed 0.50, TargetMovingRange
-10..10, mov_trigger_play 20, eye/mov_stick_play 6000), and Camera
NoseCameraFOV 0.92 / ChangeTime 0.20.

Corrects my own last entry: WEAPONS is a 59-entry NAME ROSTER, not a
stat sheet, and UNITS is not units at all -- seven craft-slot/pilot
pairs, the wingman roster.  IGNORE is a 13-name blocklist over the
weapon enumeration.

AUTO_SETTINGS lists exactly 28 stageNN_settings.tbl files -- a third
independent confirmation of the 28-stage count.

The objective-shaped records are UI: SUB_OBJECTIVE is a HUD layout (two
variants ship), SQUADRON_ORDER_OBJECT is 29 .prt sprite names, MISSIONS
holds the challenge categories, STAGES has zero fields.

Docs + one new artefact; ISL artefacts byte-identical.
2026-08-27 12:02:13 +00:00
Sylpheed RE agent
37cb1b49d6 re: the config sections are real disc data -- 64/65, with a clean 0/54 control
Testing every literal key from data/config-keys.txt against the 3496
distinct IDXD record keys with tag_hash:

  section names (arg to sub_82448AA0) -> 64 / 65 present
  field  keys  (arg to sub_824482D0) ->  0 / 54 present

The two classes are each other's control and the dissociation is
total, confirming the model: sub_82448AA0 finds a RECORD keyed by
tag_hash(name); sub_824482D0 reads a FIELD whose name is a literal
string in the pool, so none of them can be a record key.

The single absent section is LANGUAGE -- the one real section in the
disc-root config.ini.  The same API serves both stores.

This deflates my own earlier framing: I had listed "64 sections whose
hardcoded fallbacks are worth pinning".  They are not fallbacks; all
but one are real data.  STAGENN_UNIT_MAX is the genuine exception,
because its name is built at runtime from the stage index.

The artefact now names the pak each record lives in -- notably
GP_HANGAR_ARSENAL.pak holds WEAPONS, UNITS, Camera, ControlTweak,
Rendering, AUTO_SETTINGS and IGNORE.

Docs + regenerated artefact; ISL artefacts byte-identical.
2026-08-27 11:54:36 +00:00
Sylpheed RE agent
491c66e742 re: Stage30-33 do not exist; AIParams is missing for exactly the tutorials
Probing pak entry names with name_hash across all 41 paks:
UnitGroup_SNN.tbl exists for exactly S01-S16 and S18-S29 = 28 stages.
S17 and S30-S33 are absent, so the 33-entry STAGENN_UNIT_MAX table is
headroom over a 28-stage game.  Control is built in: 28 hits from 33
probes shows the loop resolves entry names.

Corroboration that fell out of it: AIParams_SNN.tbl exists for S01-S16
and S24-S29 but is missing for exactly S18-S23 -- the six stages the
corpus identified as tutorials by a completely different route (their
lone guard is request_next() != 1).  Two unrelated methods, same six.

Stage_SNN.tbl probes find 0, which is expected: stagetbl.py documents
that the per-stage record is not name-addressed.

Drops the [phase+356]/[phase+360] consumer question: 243 non-stack lwz
reads from those offsets image-wide, and the only ISL-region hits are
the two writers plus two vtable calls.  Not a cheap static question.

Docs only; artefacts byte-identical.
2026-08-27 11:40:29 +00:00
Sylpheed RE agent
e00ddd96fa re: the section reader is a generic IDXD accessor; UNIT_MAX absence re-tested
Resolving the r4 string at every sub_82448AA0 / sub_824482D0 call site
gives 65 section names and 54 int keys (data/config-keys.txt) -- far
more than the 400-byte config.ini holds -- and BASE_INFO, SYSTEM,
MISSIONS and FONTS are all present as IDXD record keys on the disc.  So
it is a generic named-section accessor over the .tbl containers, not
"the INI reader" as I labelled it yesterday.

My first re-test was invalid: IDXD record keys are tag_hash, not
name_hash.  Scanning with name_hash returned 0 for STAGENN_UNIT_MAX and
0 for every control name too -- the control is what exposed it.

Redone with tag_hash: 7750/7750 entries parsed, 190782 records, 3496
distinct keys, controls BASE_INFO/SYSTEM/MISSIONS/FONTS found, and
STAGE01..33_UNIT_MAX -> 0 records.  With the literal search and
config.ini printed in full, the section exists nowhere on the disc, so
PLANE=200 / VESSEL=20 for every stage now rests on a controlled test.

Docs + one new artefact; ISL artefacts byte-identical.
2026-08-27 11:33:30 +00:00
Sylpheed RE agent
098f9d2ece re: the config is an INI that ships nearly empty -- every stage takes 200/20
Corrects my own label: sub_824480D0/sub_82448AA0/sub_824482D0 is not an
XML reader.  The 40 <?xml pak entries are XPR2 resource manifests
(XBGMesh/Texture declarations).  The real config is a loose config.ini
at the disc root, header comment "application / game-part initial-
settings table", and every key this code touches is an INI section or
key name.

The shipped file is 400 bytes: [SYSTEM] (empty) and [LANGUAGE].  No
[STAGENN_UNIT_MAX] anywhere, and UNIT_MAX/VESSEL appear in 0 pak
entries -- so the lookup always misses and every stage runs on the
defaults PLANE=200, VESSEL=20.

The absence is controlled: the same scan decompressed 26443 entries
across 41 paks and found MSG_DEMO 192x, mapmesh_box_500km 162x and
<?xml exactly 40x, the census number.  Last iteration's null was
worthless -- it used s[8:] instead of s[10:] for the Z1 header and
searched the index rather than the .pNN data.  Using the corpus's own
reader was the whole difference.

Docs only; artefacts byte-identical.
2026-08-27 11:24:01 +00:00
Sylpheed RE agent
4995e1f24a re: STAGENN_UNIT_MAX holds two per-stage caps, PLANE and VESSEL
Reading sub_82261F70 past the table lookup finishes the mechanism the
stage index feeds: the section name is built from the 0-based stage
index, looked up, and two ints land in the phase --

  [phase+356] = PLANE   (default 200)
  [phase+360] = VESSEL  (default 20)

with the defaults taken when a stage has no entry.  Key strings are
0x820A8B14 "PLANE" and 0x820A8B1C "VESSEL"; section and both keys are
the game's own names.  Surrounding reads (GP_SCRIPT, SCRIPTS,
Resource2D, TABLE) plus the pak census's 40 <?xm entries make this an
XML config reader.

Register discipline mattered twice: in sub_82261F70 r21 is the phase
and r31 is the STACK frame, and 0x82273400 lwz r11, 360(r11) in the
built-in switch is a vtable slot 90 call, not a phase read.

Not settled: the values (a crude zlib scan found no UNIT_MAX, but it
did not use the corpus's pak reader, so that is not evidence of
absence), and no consumer of the two fields was identified.

Docs only; artefacts byte-identical.
2026-08-27 11:14:17 +00:00
Sylpheed RE agent
6fff20e54b re: X+12 is a 0-based STAGE INDEX -- proved by the game's own string table
Three iterations circled this.  Following the value rather than the
filename settles it in two hops:

  1. SilphScriptPhase's ctor sub_8225FEF8 never touches r7 (zero
     mentions).  The BASE ctor sub_822700C0 keeps it:
       or  r26, r7, r7   ->   stw r26, 152(r30)
  2. sub_82261F70 indexes a stack table of string pointers with that
     field.  The table is contiguous at 0x820A8880, 20 bytes/entry:
       index 0 = STAGE01_UNIT_MAX ... index 32 = STAGE33_UNIT_MAX,
       then PLANE at 33.

So X+12 = [phase+152] = a 0-based stage index, N -> STAGE(N+1), and the
gate is explained rather than described: <= 32 is the array bound (33
entries) and != 16 is STAGE17, the one stage number with no .ssb.

This retracts my own "not adopted": the stage-index reading was 1-of-1
but coincidence-shaped two iterations ago; 0-based is now PROVED by
index 5 -> STAGE06.

Caught a false positive: 0x82272D88 lwz r11, 152(r11) in the built-in
switch is a virtual call to slot 38 -- r11 is the vptr, not the phase.

Also names a third class: sub_822700C0 stamps 0x820A8E44 =
SilphScriptPhaseBase, so the hierarchy is Base <- ScriptPhase and
Base <- Demo.

Docs only; artefacts byte-identical.
2026-08-27 11:05:50 +00:00
Sylpheed RE agent
685e1d41b4 re: a script load builds TWO phase objects, main at Y+4 and demo at Y+8
sub_8225FEF8's only caller is sub_82260568, which looks like a derived
ctor calling its base -- i.e. every phase is a Demo, which would make
the built-in dispatch inert.  Reading the constructor refutes it: it
allocates 10216 bytes and runs SilphScriptPhase's ctor on object A,
then initialises a second object B and stamps the Demo vptr on it.
They land at Y+4 and Y+8, which is why the load path resolves the
ScriptPhase through Y+4.

The Demo class's 109 overrides all point at three no-op stubs; only
slot 15, slot 111 (Update = sub_82275800), slot 112 and the destructor
survive.  DEMO is the corpus's established cutscene text family, so the
cutscene-runner reading is recorded as a reading, not a result.

Also notes a new handle for the stalled X+12 question: the kind is
passed straight into sub_82260568 as its third argument.

Docs only; artefacts byte-identical.
2026-08-27 10:14:00 +00:00
Sylpheed RE agent
cbf54c9d21 re: the ScriptPhase vtable is 113 slots, and there is a SilphScriptPhaseDemo
RTTI at vtable[-1] names the class .?AVSilphScriptPhase@silph@@ and the
next class's COL at 0x820A8680 terminates the table at 113 entries.
The earlier ">=200 slots" reading used a bad terminator -- scanning for
a non-code word runs straight into the next vtable.  Cross-check: the
147 built-in stubs use 109 distinct slots, min 0 max 110.

The next vtable is .?AVSilphScriptPhaseDemo@silph@@, also 113 slots,
overriding 109 of them with mostly one shared stub -- a cut-down phase.
That explains why sub_822710D0 has two callers: sub_82263408 and
sub_82275800 are the two classes' Update, both slot 111.

Both "unread spawner callers" are placed: sub_82264058 is slot 0 of
SilphScriptPhase and sub_82273910 is slot 0 of the Demo class -- the two
destructors.

Also records a negative: the Stage%02d construction site I proposed last
iteration does not exist (no stage format string among the 43 short %d
strings; no precomputed name hash anywhere), so the stage-index lead
cannot be settled that way.

Docs only; artefacts byte-identical.
2026-08-27 10:05:01 +00:00
Sylpheed RE agent
084ce60bf7 re: the GamePart_MainGame vtable names the code region; a stage-index lead
sub_821A6CF0 is reached only by a tail b from a run of adjustor thunks
whose vtable is at 0x820A319C.  Slot 1 is 'addi r3, r0, 17; blr' -- the
factory id -- so RegisterToFactory<17, class silph::GamePart_MainGame>
names the class, and every thunk's lwz r3, 8(r3) makes slots 4-10
GamePart_MainGame::Impl methods (9 = the per-frame Update
sub_821AA1B0).  Several iterations' worth of inference is now a read.

Lead, not adopted: the disc has 28 scripts numbered Stage01-16 and
Stage18-29 -- exactly one gap, Stage17 -- and the gate excludes exactly
one value, 16, which lines up 0-based.  Control comparison: the refuted
GamePart-id reading had 4 missing values (1-of-4, worthless), this is
1-of-1.  Tighter but still coincidence-shaped.  The Stage%02d
construction site would settle it; not found this pass.

Docs only; artefacts byte-identical.
2026-08-27 09:56:34 +00:00
Sylpheed RE agent
abbd0cfb32 re: the (16,32] gate is != 16 && <= 32, a validity check on a load parameter
Corrects this corpus's own notation: there is no lower bound, values
below 16 pass.

sub_8225EC78(X, kind, ...) -- the function carrying "script load
cancel" -- stores its second argument into X+12 and applies the same
two tests to it immediately, bailing out of the load.  So the gate is
the object's invariant, re-checked at every later touch, not a
selector.  42 sites image-wide have the shape, 34 of them in one code
region plus 3 in sub_8225EC78.

Object chain: X = [GamePart+104] is the current script instance
(installed at 0x821A78EC, previous one torn down via sub_8225EB60);
X+4 -> Y; Y+4 -> the ScriptPhase; Y+72 and [ScriptPhase+10152] get
the answer.

Refutes my own lead in the same pass: "X+12 is a GamePart id and 16 is
the unregistered one" fails its control -- 1, 2 and 18 are also absent
from the RegisterToFactory list, so 16 is one of four gaps.

Docs only; artefacts byte-identical.
2026-08-27 09:48:36 +00:00
Sylpheed RE agent
be0f2b3124 re: [phase+10152]/[phase+10156] are an answer and a request state
Built-ins 102/103/130 are one trio over two phase fields, read off
four consecutive vtable stubs (slots 70/71/72/97).  102's body:

  state==1 -> return 2                     (pending)
  state==2 -> special[0] = (answer==1); state=0; return 0
  else     -> state=1; answer=0; return 2  (issue)

2 and 0 are the dispatcher's thread codes, so 102 blocks the coroutine
until an answer arrives; 103 is the non-blocking form and 130 clears
the answer.  The answer is published by sub_821A9DC8 under the same
(16,32] gate that request_next's writer uses.

Flags an inherited name I could not verify: prompt_yes_no (102) has no
derivation recorded anywhere in the corpus, so 103 and 130 are left
unnamed rather than named off it.

Docs only; artefacts byte-identical.
2026-08-27 09:39:52 +00:00
Sylpheed RE agent
bcff20e4cb isl: built-in 104 is request_next -- named from the game's own log string
Followed the writer, not the reader.  [phase+10160]'s only writer in
the image is one site in the mission frame loop sub_821AA1B0: it does
obj->get() on an object fetched from a registry by id 0x20FFFF02,
stores it to the phase, then clears the object -- read, publish, clear,
every frame.

The id namespace has exactly three members (0x20FFFF00/01/02), each
built at exactly 4 sites, and two of those are in sub_821D5178, which
gets 0x20FFFF01 and 0x20FFFF02 and logs both:

  GamePart_ReadyRoom::Impl::OnCommand - Wait() command is requested.
  Check flow control valiables. WAIT_MODE : %d, REQUEST_NEXT : %d

Argument order gives 0x20FFFF01 = WAIT_MODE, 0x20FFFF02 = REQUEST_NEXT.
PrepareScript corroborates: it sets WAIT_MODE=1, REQUEST_NEXT=0 before
an ISL script runs.  So the six tutorial stages' lone dominating
condition request_next() != 1 is the script waiting on the game part's
proceed flag.

6 artefact lines changed, all 6 pair exactly.
2026-08-27 09:31:01 +00:00
Sylpheed RE agent
3fa898093f isl: rename built-ins 8/9/93 to stopwatch_start/_elapsed/_stop
They are start / read / stop of one of 32 per-phase stopwatches, not
flag operations.  123-127 keep timer_* -- that is the mission timer,
five scalars at [phase+304..320], a different clock.

Artefact check: 84 lines changed across 5 files and all 84 pair
exactly with their old-name partners once column padding is
normalised (0 removed lines without an old name, 0 added lines
without a new one).  data/isl-timers.txt reproduces the same
675/675, 11.2 % control, 82/1 and identical histograms, which is
what shows the rename is cosmetic.

Also withdraws a label from the previous commit: sub_8230C398 is NOT
the message pump.  It runs every frame but drains nothing -- a state
machine on [0x828E1F8C] that only allocates, builds strings, looks up
and PUSHES.  And bus+8216 is weak evidence: sub_82254A08 is a generic
map find with ~120 sites, and the key looked up is a pointer, not a
tag.  The open handle is now the ring buffer at bus+4, not bus+8216.
2026-08-27 09:21:04 +00:00
Sylpheed RE agent
11bcf283cb re: the timer message's consumer is a runtime registry, not a tag compare
The move I flagged last iteration -- "0xAB03E5BA is built once, so
nothing consumes it" -- is refuted by its own control: 25 of the 40
distinct 0xAB03xxxx tags in the image are built exactly once (62.5 %),
and no 4-aligned word anywhere in the image begins 0xAB03, so tags are
never stored statically.

What does stand:
  * sub_82175C20(bus+4, &msg) is a ring-buffer PUSH, not a dispatch.
  * The message class has a one-method vtable {scalar deleting dtor}
    with an MSVC RTTI locator at vtable[-1] -- no handler of its own.
  * The bus at [0x828F35DC] has a map at +8216, looked up via
    sub_82254A08 from sub_823001E8, whose only caller is the per-frame
    pump sub_8230C398.

So the item is NOT settled: the insert path into bus+8216 is unread,
and whether running out of time ends the mission is still unknown.

Docs only; ISL artefacts regenerate byte-identical.
2026-08-27 09:12:36 +00:00
Sylpheed RE agent
a09c9e6517 re: timer_set's second argument is a SECOND countdown, not a warning threshold
mission-phase-timers.md left 180 open as "a limit and a warning
threshold is the obvious reading, but not established".  Reading
sub_822639B8 -- ScriptPhase::Update 0x82263528, same dt as the
stopwatch bank -- settles it the other way:

    if running: [+304] += dt
    if armed:   [+308] -= dt   while [+308] > 0
                else [+312] -= dt, clamped at 0

[+312] is never compared with [+308]; it is decremented, and only in
the A<=0 arm.  Two sequential countdowns.  Disc-wide the second
argument is 180 in all 29 timer_set sites while the first varies
(600 x19, 1200 x8, 900, 1800).

Built-ins 123-127 are vtable slots 90-94 on five scalars at
[phase+304..320].  125 and 126 have ZERO call sites in all 28 scripts:
the script arms, starts and stops this clock but never reads it.

Corrects mission-phase-timers.md, which merged this clock with
stopwatch 0 -- timer_resume starts [+304], set_flag(0) one instruction
later starts the stopwatch the timeline's kind=0 reads.

Docs only; all seven ISL artefacts regenerate byte-identical.
2026-08-27 09:03:18 +00:00