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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
isl-phase-guards produced the per-phase clear conditions, but three of the built-ins
in them were unread, leaving the conditions half-readable. All three are read now
via the vtable at 0x820A84BC (control: 69 unit_state -> slot 184 -> 0x8226ADF0).
NONE is named: the corpus has withdrawn two names taken from usage shape, and what I
read does not support naming these.
builtin104 (slot 288, 0x8226BFE0) is a THREE-INSTRUCTION PURE GETTER:
lwz r11, 10160(r3) ; stw r11, 164(r3) ; blr
So all six tutorial stages S18-S23 end on the value of a single engine-written word,
which is why their exits each have exactly one dominating condition and why
isl-builtins.md only ever saw 104 inside a poll loop. That word has EXACTLY ONE
writer in the image -- sub_821AA1B0 at 0x821AAD9C, gated on a kind field being in
(16, 32] -- and r29 there is the return of a preceding call, so the meaning of the
value and of the gate are both unestablished.
builtin7 (slot 40) indexes [phase+324] by local[4], bails when rec+16 (the documented
unit state) is 1, 3 or 4, and resolves local[12] through [phase+244] -- symbol table 1.
isl.py's SYM1_SLOTS already listed slot 12 for built-in 7, derived from operand ranges
alone; reading the implementation shows the mechanism behind that. Two independent
methods agreeing. What it asks ABOUT the route is still unread.
builtin141 (slot 428) has the same unit-array entry and returns 0 when the unit is
gone; everything past 0x8226CA0C is unread. Stage 16 calls it twice with arguments
differing in one position (0 vs -4000), which looks like a coordinate -- and looking
like one is exactly the evidence this corpus does not accept.
No artefact changes: the listings already printed builtin104/7/141 and still do.
All four Stage 02 artefacts regenerate byte-identical.