The tempting move - 360 objects state their own contents, so use that to name
them - does not work, and it is now measured rather than assumed:
objects carrying an Enumeration record 360
key already named by archive-naming routes 1 + 2 348
still unnamed 12
newly named by name_hash(prefix + own record name + ext) 0
Sixteen prefixes x seven extensions x every record name of every unnamed
object: zero gained. A self-index names an object's RECORDS; a TOC key is
name_hash(path + file name). The two vocabularies never meet.
The residual is exactly two objects x six packs, each byte-identical across the
packs and each an unnamed VARIANT of a named _Test table, not a duplicate:
2390212806 (1493 B) Placement_00..02 + RouteTest_00..01 - a trimmed
Stage\Null_Test.tbl (3121167452, 27545 B, same five
records plus four Frame_Test_Asteroid_*)
2719765792 (14124 B) Formation_100, Formation_Fleet_01, Formation_Fleet_02 -
the same three record names as Formation_Test.tbl
(3463590559, 14025 B) but a different payload
That answers the previous iteration's question about the six-record
Placement/RouteTest sibling: it is Null_Test.tbl minus the asteroid frames.
Independent confirmation for stage-mission-tables: name_hash(prefix +
"Formation_Test.tbl") -> key 3463590559, whose records are exactly those three,
so the two frame-ratio exceptions do live in the file it names.
Dangling link recorded, not fixed: idxd-container.md references
idxd-unnamed-keys.md twice and that file does not exist on this branch - it
belongs to auto/idxd-unnamed-keys.
One control missed unexplained: EnumSquadron_Test.tbl matches no pak key under
any of the sixteen prefixes. n=1, not chased.
All fifteen artefacts byte-identical.
The template object is stage\Null_Test.tbl: its key hashes as
name_hash("stage\" + "Null_Test.tbl"), the file stage-definition-table names as
EnumerateNullFrame. Ten records - 4 Frame_Test_Asteroid_01..04, 3
Placement_00..02, 2 RouteTest_00..01, Enumeration - and the nine carrying a
FrameCount are exactly the "9/9 for Null_Test.tbl" stage-mission-tables already
counted. The corpus knew the file and the count, not the record names. A second
object (the same five plus Enumeration, without the asteroid frames) matched
none of ten candidate names across sixteen prefixes.
Enumeration is a record that indexes its own object. Disc-wide: 7750 IDXD
objects, 360 carry one, and in 360/360 its field names equal exactly that
object's other record names - zero subsets, zero strays. Field/record counts
run from 2 past 25 ((3,3)x36, (9,9)x24, (13,13)x24, (2,2)x24, (25,25)x18,
(16,16)x12), every pair on the diagonal.
Enumeration vs Enumerate is one letter and opposite scope: Enumerate is
archive-naming's route-2 seed, a single-record object whose fields name OTHER
objects; Enumeration names the records of its own object. Written up in
idxd-container, which had left "an object's kind is known only from the caller
that loads it" open.
Sharper version of the previous correction: I credited the stage\ prefix to
archive_naming.py, but stage-mission-tables states it in prose ("The prefix is
stage\ - name_hash is case-insensitive") and its stagetbl.py examples pass
'stage\AIParams_S02.tbl'. Noted in asteroid-fields.md.
All fifteen artefacts byte-identical.
Independently reproduced across all 33 paks: 7750 IDXD objects, 1485577 unnamed
field entries, 7094 distinct never-named keys splitting cleanly into 7052 in an
ordinal band (<=0x2198, 94.6% equal to their own field index) and 42 hash-shaped
(>=0x2677C), with ZERO keys in the gap between. The 42 carry exactly 504
entries -- six language copies of one object times two records.
So the preimage target was 42, not 504, and my earlier wording invited the
misreading. Cross-referenced to the idxd-unnamed-keys write-up, which shows the
42 belong to <lang>\script\ID.tbl and cannot be recovered from a 24-bit hash.
The binary region in front of the string pool was the parser's oldest open
note ("Not yet decoded"). It is a uniform 16-byte record array sorted by
name hash, a field count, a 12-byte field array sorted by key, a pool size,
and the pool. The trailing `pool_size == file_len - pool_base` identity makes
the layout self-checking, which is what caught the first wrong version.
Verified over the WHOLE disc with zero failures: 7750/7750 IDXD objects,
190782/190782 records reproducing their stored tag_hash, 1271462/1271462
named fields reproducing their key. IXUD is the same container with
ixud_hash, UTF-16BE and every offset in chars — 1104/1104 objects,
628165/628165 fields, checked with an independent parser.
Field names are stored on disc, so no preimage search is needed: a field's
middle word points at its own name. Only 504 fields disc-wide are hash-keyed
with no name; the other 1485073 nameless fields are positional, keyed by a
literal integer (line slots, movie ids).
Two long-held beliefs are WITHDRAWN:
* The word at 0x08 is not a schema hash. It is record 0's name_hash — the
format has no type field at all, and an object's kind is known only from
the caller that loads it. It survived as "schema" because tables of one
kind share their lowest-hashed record name. Caught by a test asserting
every movie id names a real record: 1005 -> STAGE10_PHASE01 failed because
tag_hash("STAGE10_PHASE01") IS 0x067025B9, that table's supposed schema id.
* The field's middle word is not an always-0xFFFFFFFF flags word. It is
0xFFFFFFFF for 54% of fields, enough to look constant in a small sample;
the tell was that it is constant per key ACROSS records, which a per-record
flag cannot be but a per-name pointer must.
`schema_hash` keeps its name rather than churn 33 call sites, with corrected
docs. The first sweep globbed dat/** and missed hidden/DefTables.pak (1425
objects); the test now walks the whole disc root.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE