36 KiB
Radium Image Format
Summary
Static Ghidra and file-structure analysis now identifies the main sound payload area in /games/pokemon_pro/image.bin.
Confirmed findings:
/games/pokemon_pro/image.binis mmap-backed by the native game loader, not a standard archive.- The first
13little-endian qwords form the image header copied byFUN_008733e0. - Header qwords
0through9are section offsets used byFUN_008736c0. - Header qword
12low32 gives the section 8 row count. For the main game image this is0x9bb(2491) rows. - Section 8 starts at file offset
0x44d5396f; each row is24bytes:q0,q1,q2. q0is the data offset for a headerless PCM chunk.q1contains the masked lookup key and packed width/channel flags used byFUN_007a46e0.q2low32 is the sample count used by the loader.- Section 3 is a pointer table with
1477entries. The pointed records begin withuint32 id,uint32 flags,uint16 width, anduint16 height;1449records currently render as raw indexed bitmap previews. - Section 5 is a dense command-stream table with
2531local object slots and5variants per slot. - The loader's byte-length formula for opcode
0x0breferences isderived_multiplier * (q2 & 0xffffffff) * 2. - Applying that formula to all section 8 rows produces
2491bounded PCM candidate chunks. For2490rows, the computed byte length exactly equals the distance to the next row'sq0offset. The last row has no next-row delta for comparison. - The section 8 checksum is CRC32 over the aligned table bytes. The stored value
0x3f401e79matches the calculated value.
Conclusion: the missing sounds are not absent and are not plain Ogg/WAV files. They are stored as headerless 16-bit PCM ranges inside /games/pokemon_pro/image.bin, indexed by the Radium section 8 table. The section 5 command-stream scan now resolves every section 8 PCM chunk to at least one opcode 0x0b reference. A small native GodotSpikeBridge registration table maps 25 object IDs to confirmed SE_* names. A Pokedex voice alignment now maps 1137 Radium objects (409 through 1545) to Pokemon description/name/category clips using assets/godot_raw/data/pokemon_data.txt and ASR evidence. The aligned Pokedex stream is normally description, name, category, with six duplicate description variants: Magmar, Flareon, Snorlax, Numel, Magmortar, and Talonflame. Section 3 also contains small bitmap-like records, and 1449 raw indexed records are now exported as PNG previews. The remaining format work is mapping the other command-stream/object IDs to higher-level script/gameplay names and playback context, plus finishing the compact section 3 subtype and section 4 semantics.
The same section-8 PCM table structure is now also confirmed in /games/pokemon_pro/spike3/spike_menu/image.bin. That smaller control image has 54 section 8 rows, a matching section-8 CRC32, 300 resolved section 5 opcode 0x0b references, and 54 bounded PCM candidates exported as WAVs under analysis/radium-spike-menu-image/sounds/.
Reproducible Parser
Generated parser:
python3 rootfs-triage-wiki/scripts/parse_radium_image.py
Generated outputs:
analysis/radium-image/header.jsonanalysis/radium-image/section4_table.tsvanalysis/radium-image/section3_bitmap_manifest.tsvanalysis/radium-image/section3-bitmaps/*.pnganalysis/radium-image/section8_entries.tsvanalysis/radium-image/pcm_candidates.tsvanalysis/radium-image/command_sound_refs.tsvanalysis/radium-image/command_stream_summary.tsvanalysis/radium-image/command_opcode_rows.tsvanalysis/radium-image/command_opcode_histogram.tsvanalysis/radium-image/opcode_handler_table.tsvanalysis/radium-image/native_sound_id_map.tsvanalysis/radium-image/native_named_sound_manifest.tsvanalysis/radium-image/native_sound_test_label_manifest.tsvanalysis/radium-image/catalog_offset_candidate_labels.tsvanalysis/radium-image/asr_draft_sound_labels.tsvanalysis/radium-image/asr_review_sound_label_candidates.tsvanalysis/radium-image/pokedex_voice_label_manifest.tsvanalysis/radium-image/sound_export_manifest.tsvanalysis/radium-image/sound_export_manifest.jsonanalysis/radium-image/sounds/*.wavanalysis/radium-image/sounds-native-named/*.wavanalysis/radium-image/sounds-native-sound-test/*.wavanalysis/radium-image/sounds-pokedex-voice/*.wavanalysis/radium-image/sounds-best-named/*.wavanalysis/all-sound-best-name-manifest.tsv
The same parser was also run against the SPIKE menu image. Its companion binary uses the same opcode-length sequence at file/Ghidra offset 0x410ce8, so the parser is run with an image-specific opcode table offset:
python3 rootfs-triage-wiki/scripts/parse_radium_image.py --image games/pokemon_pro/spike3/spike_menu/image.bin --game-binary games/pokemon_pro/spike3/spike_menu/game --out-dir analysis/radium-spike-menu-image --sound-dir analysis/radium-spike-menu-image/sounds --opcode-size-table-offset 0x410ce8 --object-table-count 0 --write-all-wavs
Generated SPIKE menu control outputs:
analysis/radium-spike-menu-image/header.jsonanalysis/radium-spike-menu-image/section4_table.tsvanalysis/radium-spike-menu-image/section8_entries.tsvanalysis/radium-spike-menu-image/pcm_candidates.tsvanalysis/radium-spike-menu-image/command_sound_refs.tsvanalysis/radium-spike-menu-image/command_stream_summary.tsvanalysis/radium-spike-menu-image/command_opcode_rows.tsvanalysis/radium-spike-menu-image/command_opcode_histogram.tsvanalysis/radium-spike-menu-image/opcode_handler_table.tsvanalysis/radium-spike-menu-image/sound_export_manifest.tsvanalysis/radium-spike-menu-image/sound_export_manifest.jsonanalysis/radium-spike-menu-image/sounds/*.wav
Preview WAV export, capped to avoid duplicating the full 1.15 GB image:
python3 rootfs-triage-wiki/scripts/parse_radium_image.py --write-wav-previews --max-wavs 20
Preview outputs:
analysis/radium-image/wav-preview-manifest.jsonanalysis/radium-image/wav-preview/section8_0000_1ch_44100hz.wavanalysis/radium-image/wav-preview/section8_0001_1ch_44100hz.wav- Additional preview WAVs through
section8_0019_*.
The parser is static and does not execute target AArch64 binaries.
Opcode handler/length table dump:
python3 rootfs-triage-wiki/scripts/dump_radium_opcode_tables.py
This writes analysis/radium-image/opcode_handler_table.tsv and analysis/radium-spike-menu-image/opcode_handler_table.tsv. Each output row records the 16-byte native table entry for one opcode: stored handler address, payload length, observed command counts, and the most common payloads from the generic stream decode.
Section 3 bitmap previews:
python3 rootfs-triage-wiki/scripts/export_radium_section3_bitmaps.py
This writes:
analysis/radium-image/section3_bitmap_manifest.tsvanalysis/radium-image/section3-bitmaps/*.png- Wiki previews in
radium-section3-bitmaps.md
The section 3 exporter is static and intentionally conservative: it emits PNG previews only when the record payload is large enough for raw width * height indexed pixels, and records compact payloads as undecoded.
Confirmed native-name aliases:
python3 rootfs-triage-wiki/scripts/extract_godot_gdc_strings.py
python3 rootfs-triage-wiki/scripts/extract_native_sound_registrations.py
python3 rootfs-triage-wiki/scripts/build_native_named_sound_aliases.py
python3 rootfs-triage-wiki/scripts/extract_native_sound_test_labels.py
python3 rootfs-triage-wiki/scripts/build_catalog_offset_candidate_labels.py
These write:
analysis/godot-introspection/gdc_script_summary.tsvanalysis/godot-introspection/gdc_strings.tsvanalysis/godot-introspection/gdc_sound_refs.tsvanalysis/radium-image/native_sound_id_map.tsvanalysis/radium-image/native_named_sound_manifest.tsvanalysis/radium-image/native_sound_test_label_manifest.tsvanalysis/radium-image/catalog_offset_candidate_labels.tsvanalysis/radium-image/sounds-native-named/*.wavanalysis/radium-image/sounds-native-sound-test/*.wav
Full WAV export:
python3 rootfs-triage-wiki/scripts/parse_radium_image.py --write-all-wavs
This writes one RIFF/WAV file per section 8 row under analysis/radium-image/sounds/. Current output count is 2491 WAV files. The deterministic filename format is:
objNNNN_vV_cmdC_sSSSS_channels_rate.wav
The objNNNN prefix is the best available static name from the resolved Radium local object ID. sSSSS preserves the section 8 chunk index so every filename stays unique and traceable even if higher-level names are recovered later.
Complete best-name aliases:
python3 rootfs-triage-wiki/scripts/build_pokedex_voice_aliases.py
python3 rootfs-triage-wiki/scripts/build_asr_catalog_sound_labels.py
python3 rootfs-triage-wiki/scripts/build_best_named_sound_aliases.py
This creates one alias row per exported sound across the main game and SPIKE menu images. The source priority is confirmed native name, Pokedex voice alignment, catalog-matched ASR constant, accepted draft ASR transcript, catalog-offset candidate, low-confidence ASR review candidate, exact duplicate hash match against a stronger main-image name, SPIKE-menu hash match against a stronger main-image name, then structural object/chunk reference. Current output is 2545 rows in analysis/all-sound-best-name-manifest.tsv: 2491 main-game sounds and 54 SPIKE menu sounds. Current name-source counts are 1137 Pokedex voice alignments, 220 catalog-matched ASR aliases, 583 raw ASR draft aliases, 196 catalog-offset candidate aliases, 34 low-confidence ASR review candidate aliases, 295 main structural object fallbacks, 25 confirmed native names, 1 main-image duplicate hash match, 4 SPIKE-menu hash matches to stronger main-image names, and 50 SPIKE-menu structural fallbacks. Catalog-matched ASR aliases are inferred only when an accepted transcript exactly matches a known SE_PK_VO_* Godot catalog phrase or a narrow documented ASR error variant; the current overlay covers 90 distinct catalog constants. Catalog-offset aliases are marked candidate_unpromoted, are lower priority than direct content evidence, and do not claim a general Godot catalog-to-Radium object map. Low-confidence ASR review candidates are content-derived only and are used only when no stronger semantic label exists. The main-image duplicate is object 192 / section 8 903, which is SHA-256 identical to confirmed-native object 213 / section 8 818 (SE_PK_FX_ATTACK_TYPE_WATER). The alias WAVs are under analysis/radium-image/sounds-best-named/ and analysis/radium-spike-menu-image/sounds-best-named/.
Header Evidence
Main image path: /games/pokemon_pro/image.bin
File size: 0x44d62307 (1154884359) bytes.
Header qwords from analysis/radium-image/header.json:
| Index | Value | Meaning |
|---|---|---|
0 |
0x00000000000000b0 |
Section 0 offset |
1 |
0x000000000001be18 |
Section 1 offset |
2 |
0x000000000001bdf8 |
Section 2 offset |
3 |
0x0000000000018fd0 |
Section 3 offset |
4 |
0x0000000000018c70 |
Section 4 offset |
5 |
0x00000000000000f8 |
Section 5 offset |
6 |
0x00000000000000f8 |
Section 6 offset |
7 |
0x0000000044d62303 |
Near-EOF marker/section offset |
8 |
0x0000000044d5396f |
Section 8 table offset |
9 |
0x0000000000018c70 |
Section 9 offset |
10 |
0x000005c50000001b |
Count pair: low32 27, high32 1477 |
11 |
0x000009e300000000 |
Count pair: high32 2531 |
12 |
0x00000000000009bb |
Section 8 row count: 2491 |
Ghidra evidence:
FUN_008733e0(uint image_index)opensimage.binorimage-sc%02d.bin, mmaps it read-only, and copies the first13qwords into runtime image metadata.FUN_008736c0(image_index, section_id)returnsmmap_base + header_qword[section_id]for section IDs0through9.FUN_00873290(image_index, count_id)returns count fields from the copied header. Count ID4returns2491for the main image.
Section 3 Bitmap Records
Ghidra xrefs to FUN_008736c0 identify section 3 as a count-indexed pointer table:
FUN_0080aca0(index)initializesDAT_033e2658 = FUN_008736c0(0, 3)andDAT_033e2660 = FUN_00873290(0, 1), then returnssection3 + index * 8.FUN_0080d1b0(index)andFUN_0080d250(index)fetch the qword pointer from section 3, pass it throughFUN_00873360(0, pointer), and read signed 16-bit fields at pointed-record offsets+8and+10.- Count ID
1is header qword 10 high32,1477, matching the number of section 3 qword pointers.
Static record observations from /games/pokemon_pro/image.bin:
| Field | Evidence |
|---|---|
| Pointer table offset | Section 3 at 0x18fd0 |
| Pointer count | 1477 |
| Record header | uint32 record_id, uint32 flags, uint16 width, uint16 height |
| Common first dimensions | 17 x 23, 5 x 7, 7 x 7, 128 x 32 |
| Current decoder output | 1449 raw_indexed_preview PNGs and 28 undecoded_compact_payload rows |
rootfs-triage-wiki/scripts/export_radium_section3_bitmaps.py writes analysis/radium-image/section3_bitmap_manifest.tsv and grayscale/alpha PNG previews under analysis/radium-image/section3-bitmaps/. Pixel values are rendered as grayscale for preview; 0xff is rendered transparent. This is a visual reconstruction aid, not a confirmed final palette. The 28 compact payload rows, mostly 128 x 32 records with payloads smaller than width * height, are deliberately left undecoded until their compression/packing rule is recovered.
The wiki exposes these previews in radium-section3-bitmaps.md.
Section 8 Chunk Table
Ghidra evidence from FUN_007a46e0(ulong image_id):
- Calls
FUN_00873290(image_id, 4)to get section 8 row count. - Calls
FUN_008736c0(image_id, 8)to get the section 8 table pointer. - Copies
(row_count * 0x18 + 0xf) & ~0xfbytes and validates CRC32 withFUN_00a2e1f0. - Iterates row triples:
q0 = row[0]q1 = row[1]q2 = row[2]
- Stores
q0,q2low32, and packed flags in a runtime lookup map keyed byimage_id << 0x22 | (q1 & 0xfffc0003ffffffff). - Later, opcode
0x0bcommand streams look up this map and compute byte length as:
byte_length = derived_multiplier * q2_low32 * 2;
The parser implements the same flag extraction seen in FUN_007a46e0.
Section 8 validation results:
| Metric | Value |
|---|---|
| Section 8 offset | 0x44d5396f |
| Row count | 2491 |
| Row size | 24 bytes |
| Raw table bytes | 59784 |
| Aligned CRC bytes | 59792 |
| Stored CRC32 | 0x3f401e79 |
| Calculated CRC32 | 0x3f401e79 |
| Rows with bounded nonzero PCM length | 2491 |
Rows where computed PCM length equals next q0 delta |
2490 |
SPIKE menu image section 8 validation results:
| Metric | Value |
|---|---|
| Image path | /games/pokemon_pro/spike3/spike_menu/image.bin |
| File size | 0xe443a1 (14959521) bytes |
| Section 8 offset | 0xe43e89 |
| Row count | 54 |
| Row size | 24 bytes |
| Raw/aligned table bytes | 1296 |
| Stored CRC32 | 0xb8fe11f9 |
| Calculated CRC32 | 0xb8fe11f9 |
| Rows with bounded nonzero PCM length | 54 |
Rows where computed PCM length equals next q0 delta |
53 |
Resolved opcode 0x0b references |
300 |
| Unique section 8 chunks referenced | 54 |
| Unique local object IDs with references | 59 |
| Exported WAV count | 54 |
| Channel counts | 16 mono, 38 stereo |
First decoded rows from analysis/radium-image/section8_entries.tsv:
| Index | q0 |
Next delta | Multiplier | Sample count | PCM bytes | First bytes |
|---|---|---|---|---|---|---|
0 |
0x4f71e |
278896 |
1 |
139448 |
278896 |
fbffeffff3ffefff... |
1 |
0x9388e |
91134 |
1 |
45567 |
91134 |
0000000000000000... |
2 |
0xa9c8c |
139316 |
1 |
69658 |
139316 |
ffff0300fdff0300... |
3 |
0xcbcc0 |
493628 |
2 |
123407 |
493628 |
fdff01000300fdff... |
4 |
0x1446fc |
541500 |
2 |
135375 |
541500 |
01000100ffffffff... |
The repeated small signed 16-bit values in the first bytes are consistent with little-endian PCM sample data.
Command Stream References
The parser now scans the full section 5 command-stream table used by FUN_007a46e0.
Static game-binary tables:
- Object table base: Ghidra/raw address
0x032521c0. - Object table count:
0x3f1entries. - Object table stride:
0x20bytes. - Each entry's first qword points to a zero-terminated
uint32_tobject-ID list in/games/pokemon_pro/game. - The flag byte used by the preload path is at entry offset
+0x0d. - Opcode handler/size table base: Ghidra/raw address
0x03079e70, with0x13entries and a0x10-byte stride. - Each opcode table entry is
qword handler_address, thenqword payload_length.FUN_007a46e0indexes the second qword directly throughDAT_03179e78to skip non-sound opcodes in the preload scan; the runtime interpreter handlers live in the first qword. - The SPIKE menu companion binary has the same 16-byte table shape at Ghidra/raw address
0x410ce0; its payload-length qword sequence begins at0x410ce8, matching the parser argument used for menu extraction.
Static image tables:
- Section 2 byte at
section2 + 7is5, used here as the variant slot count. - Section 5 begins at offset
0xf8and maps(object_local_id * variant_count + variant)to command-stream offsets. - Section 5 ends at section 4 offset
0x18c70;(0x18c70 - 0xf8) / (5 * 8) = 2531local object slots. - Opcode
0x0brecords are12bytes and carry a section 8 lookup key at command offset+4. - The object table is still used for cross-checking and metadata, but it is not a complete enumerator for section 5. A previous object-table-only scan missed
530opcode0x0breferences and62section 8 chunks, including local object184(SOUND: SUPER SKILL SHOT).
Command-reference extraction results from analysis/radium-image/header.json:
| Metric | Value |
|---|---|
Resolved opcode 0x0b references |
12710 |
| Unique local object IDs with references | 2521 |
| Unique section 8 chunks referenced | 2491 |
Generic section 5 command-stream decode results:
| Image | Streams | Unique stream offsets | Opcode rows | Unique opcodes | Streams with sound refs | Malformed streams |
|---|---|---|---|---|---|---|
| Main game | 12655 |
2531 |
108765 |
0x01,0x02,0x03,0x04,0x05,0x07,0x09,0x0a,0x0b,0x0e,0x10,0x11,0x12 |
12605 |
0 |
| SPIKE menu | 340 |
68 |
2065 |
0x02,0x04,0x05,0x09,0x0a,0x0b,0x0e,0x10,0x11,0x12 |
295 |
0 |
All decoded streams terminate with opcode 0x00. The largest decoded stream in both images is 108 bytes / 18 opcodes. Detailed rows are in command_stream_summary.tsv, command_opcode_rows.tsv, command_opcode_histogram.tsv, and command_opcode_payload_summary.tsv under each analysis directory.
Payload-summary observations:
| Image | Unique stream patterns | Most common pattern | Count |
|---|---|---|---|
| Main game | 14 |
0x05,0x02,0x09,0x09,0x0b,0x10,0x09,0x09,0x11 |
10345 |
| SPIKE menu | 6 |
0x05,0x02,0x0b,0x11 |
220 |
The SPIKE menu pattern set is a subset of the main image patterns. Main-only control opcodes are 0x01, 0x03, and 0x07; the menu still uses the shared object setup (0x05), parameter selector (0x02), envelope/parameter records (0x09, 0x10, 0x11), sound reference (0x0b), and the less-common 0x04, 0x0a, 0x0e, 0x12 records.
Opcode payload shapes from command_opcode_payload_summary.tsv:
| Opcode | Main count | Menu count | Payload bytes | Static interpretation status |
|---|---|---|---|---|
0x05 |
12655 |
340 |
6 |
Object/clip setup record; first byte clusters by stream family, middle fields vary per object. |
0x02 |
12600 |
290 |
2 |
Selector/state byte pair; often follows 0x05. |
0x09 |
44755 |
150 |
15 |
Parameter/envelope record; only 17 unique main payloads and 7 menu payloads. |
0x0b |
12710 |
300 |
11 |
Confirmed sound-reference command; byte 0 is command channel and bytes 3-10 carry the section 8 lookup key. |
0x10 |
12100 |
145 |
4 |
Little-endian scalar, often duration-like; common value 1000 appears in both images. |
0x11 |
12645 |
230 |
1 |
Constant payload 01, likely end/play trigger paired after sound setup. |
0x04 |
520 |
520 |
2 |
Repeated small selector rows, always second byte 7. |
0x0a |
50 |
50 |
1 |
Small selector, values 1 or 2. |
0x0e |
20 |
20 |
4 |
Scalar values 1 or 2. |
0x12 |
20 |
20 |
8 |
Two payloads: ffffffff401f0000 and feffffff401f0000. |
Opcode table details from opcode_handler_table.tsv:
| Opcode | Main payload length | Main handler | Main count | Menu payload length | Menu handler | Menu count | Note |
|---|---|---|---|---|---|---|---|
0x02 |
2 |
0x69dd10 |
12600 |
2 |
0xec1c0 |
290 |
Common selector/state byte pair. |
0x05 |
6 |
0x69de10 |
12655 |
6 |
0xec2c0 |
340 |
First command in every decoded stream. |
0x09 |
15 |
0x69dea0 |
44755 |
15 |
0xec350 |
150 |
Small fixed payload vocabulary. |
0x0b |
11 |
0x6a3140 |
12710 |
11 |
0xf1670 |
300 |
Confirmed sound-reference payload; total command length is 12 including opcode byte. |
0x10 |
4 |
0x69e0a0 |
12100 |
4 |
0xec550 |
145 |
Little-endian scalar values, often near paired sound records. |
0x11 |
1 |
0x69e100 |
12645 |
1 |
0xec5b0 |
230 |
Constant payload 01. |
0x12 |
8 |
0x69e160 |
20 |
8 |
0xec610 |
20 |
Rare branch/loop-looking payload pair in both images. |
Ghidra evidence from FUN_007a46e0 confirms preload semantics for 0x0b: it reads pbVar15[1] as the command channel, masks *(ulong *)(pbVar15 + 4) with 0xfffc0003ffffffff, looks up the section 8 entry, and advances by 0x0c bytes. For other opcodes in the preload path, the function advances by the payload-length qword at DAT_03179e78 + opcode * 0x10; the handler-address side of that table now gives concrete native addresses for follow-up decompilation, but it does not by itself name the command semantics.
objdump -d --start-address=0x69e1d0 --stop-address=0x69e250 games/pokemon_pro/game cross-checks the table alignment: the runtime handler near 0x69e1e0 loads table base 0x3079e70, then reads the payload length from entry offset +8. This is why parse_radium_image.py keeps its existing 0x3079e78 length-table offset while dump_radium_opcode_tables.py records the full entry base at 0x3079e70.
Caveat: section 5 has five variant slots. Many variants currently point to the same command stream, so command_sound_refs.tsv intentionally preserves the per-variant rows even when the stream offset repeats. 530 references do not have object-table metadata; those rows still have section-5 local object IDs and blank object_table_index / object_table_flag fields.
First resolved rows from analysis/radium-image/command_sound_refs.tsv:
| Object ID | Variant | Stream offset | Command offset | Section 8 index | q0 |
PCM bytes |
|---|---|---|---|---|---|---|
9 |
0 |
0x44d1e0d0 |
0x44d1e0da |
2084 |
0x3a76c5a2 |
36612 |
9 |
1 |
0x44d1e0d0 |
0x44d1e0da |
2084 |
0x3a76c5a2 |
36612 |
10 |
0 |
0x44d1e0e9 |
0x44d1e0f3 |
1679 |
0x2e5d6e12 |
56196 |
11 |
0 |
0x44d1e102 |
0x44d1e10c |
543 |
0x10575c9c |
52740 |
This confirms that the command streams directly reference section 8 PCM chunks by packed key. The current output still uses numeric object IDs; mapping those IDs to script names, call sites, or gameplay events remains open.
Native Sound-Test-Adjacent Labels
The native binary contains a small label table near the sound-test UI data. A previous pass started at raw/Ghidra offset 0x0325d940 and decoded each row as a localized label-list pointer followed by a numeric field. Wider table inspection shows nearby speaker-test rows at 0x0325d900 through 0x0325d930, followed by rows at 0x0325d940 through 0x0325d970 that look like sound-test labels.
The numeric field is not confirmed to be a direct Radium local sound ID. Two independent cross-checks disprove direct use for every row: object 65 is ASR-labeled as "Cabinet speaker" while the candidate table row says MUSIC: MAINPLAY, and object 477 is the Pokedex 0030 Nidorina category clip ("Poison Pin") while the candidate row says SPEECH: 'SQUIRTLE'.
Current candidate rows retained for audit:
| Label | Numeric field | Source note |
|---|---|---|
MUSIC: MAINPLAY |
65 |
Sound-test-adjacent candidate; not promoted to best-name export. |
MUSIC: TEAM ROCKET |
81 |
Sound-test-adjacent candidate; not promoted to best-name export. |
SOUND: SUPER SKILL SHOT |
184 |
Sound-test-adjacent candidate; not promoted to best-name export. |
SPEECH: 'SQUIRTLE' |
477 |
Conflicts with confirmed Pokedex voice alignment. |
rootfs-triage-wiki/scripts/extract_native_sound_test_labels.py preserves these rows and preview aliases under analysis/radium-image/sounds-native-sound-test/ for audit only. build_best_named_sound_aliases.py no longer promotes the candidate labels into the best-name manifest.
Draft ASR Sound Labels
The full WAV export is also suitable for local speech-to-text triage. rootfs-triage-wiki/scripts/transcribe_sound_draft_labels.py runs local Whisper against WAV candidates and writes accepted draft transcripts to analysis/radium-image/asr_draft_sound_labels.tsv.
Current ASR pass:
| Metric | Value |
|---|---|
| Rows transcribed | 2486 |
| Accepted draft labels | 1705 |
| Model | tiny.en |
| Duration coverage | 0.25s and longer |
| Channels | 1 or 2 |
| Decode temperature | 0.0 |
Reproduction commands used for the current cumulative pass:
/opt/homebrew/Cellar/openai-whisper/20250625_4/libexec/bin/python rootfs-triage-wiki/scripts/transcribe_sound_draft_labels.py --model tiny.en --model-dir /Users/jordan/.cache/whisper --min-duration 0.25 --max-duration 15.0 --channels 1,2 --resume --temperature 0.0
/opt/homebrew/Cellar/openai-whisper/20250625_4/libexec/bin/python rootfs-triage-wiki/scripts/transcribe_sound_draft_labels.py --model tiny.en --model-dir /Users/jordan/.cache/whisper --min-duration 15.0 --max-duration 9999 --channels 1,2 --resume --temperature 0.0
The second long-clip pass added 39 rows and 0 accepted labels under the current confidence filters. Five sub-0.25s clips remain outside the ASR pass.
These transcripts are content-derived labels only. They are useful for preview search and for prioritizing voice-like clips, but they are not native symbols and may contain recognition errors. The wiki exposes accepted draft rows in sound-asr-draft-labels.md and adds draft titles to the full sound galleries when no confirmed native label exists.
rootfs-triage-wiki/scripts/build_asr_review_sound_labels.py also applies conservative review filters to rejected ASR rows. It writes analysis/radium-image/asr_review_sound_label_candidates.tsv, currently with 169 low-confidence review candidates. build_best_named_sound_aliases.py uses only 34 of those rows as name_source=asr_review_candidate, because stronger confirmed, aligned, catalog-ASR, accepted-ASR, and catalog-offset labels take priority. These aliases are marked confidence=low_confidence_asr and remain content-derived review aids, not confirmed symbols.
Pokedex Voice Alignment
rootfs-triage-wiki/scripts/build_pokedex_voice_aliases.py aligns Radium local objects 409 through 1545 against assets/godot_raw/data/pokemon_data.txt.
Static evidence:
- The aligned range contains
1137exported WAVs. - The Pokemon data table has
377in_game=TRUEnon-Blankrows; the normal triplet stream would be1131clips. - The six extra clips are duplicate description variants for Magmar (
obj0683), Flareon (obj0708), Snorlax (obj0715), Numel (obj0941), Magmortar (obj1095), and Talonflame (obj1270). - The alignment uses the local ASR transcript as evidence, but the canonical names come from
pokemon_data.txt:Pokedex NNNN Pokemon role. - Output aliases are under
analysis/radium-image/sounds-pokedex-voice/and previewed insound-pokedex-voice.md.
Example confirmed alignment rows:
| Object | Section 8 | Canonical label | Evidence |
|---|---|---|---|
409 |
723 |
Pokedex 0001 Bulbasaur description |
ASR matches the Bulbasaur description from pokemon_data.txt. |
410 |
1553 |
Pokedex 0001 Bulbasaur name |
ASR transcript is Bulbasaur!. |
411 |
598 |
Pokedex 0001 Bulbasaur Seed category |
ASR transcript is The Seed Pokemon. |
683 |
789 |
Pokedex 0126 Magmar description alt 2 |
Duplicate Fahrenheit wording variant aligned after the first Magmar description. |
1545 |
1524 |
Pokedex 1019 Hydrapple Apple Hydra category |
End of the aligned Pokedex stream. |
Confirmed Native Sound Names
Ghidra and static disassembly identify a native Godot bridge registration table in /games/pokemon_pro/game:
strings -a /games/pokemon_pro/gamefindsGodotSpikeBridge::register_sound(...)and 25SE_PK_*cue names.- Ghidra string xrefs for
SE_PK_FX_ATTACK_TYPE_BUGandSE_PK_FX_EOB_POKEDEX_PAGE_TURNboth resolve toFUN_005d6b90. FUN_005d6b90callsFUN_006ec060(name, native_sound_id, 0xffffffff).- A static AArch64
BLscan, adjusted for the Ghidra/file-address delta of0x00100000, finds exactly25calls toFUN_006ec060from this registration table. rootfs-triage-wiki/scripts/extract_native_sound_registrations.pydecodes each call's string pointer andw2immediate intoanalysis/radium-image/native_sound_id_map.tsv.- All
25recovered native IDs resolve to existingprimary_object_local_idrows inanalysis/radium-image/sound_export_manifest.tsv.
The confirmed names currently cover:
SE_PK_FX_ATTACK_TYPE_BUGthroughSE_PK_FX_ATTACK_TYPE_WATER, native IDs196through213.SE_PK_FX_EOB_POKEDEX_PAGE_TURNthroughSE_PK_FX_EOB_POKEDEX_BACKGROUND, native IDs109through115.
analysis/radium-image/sounds-native-named/ contains hardlink aliases such as SE_PK_FX_ATTACK_TYPE_BUG__obj0196_s0628_2ch_44100hz.wav. The full analysis/radium-image/sounds/ export remains complete and retains deterministic object/chunk names for the unmapped sounds.
Catalog-Order Offset Audit
Ghidra xrefs to FUN_006ec060 confirm that the direct native registration path is exhausted for now: get_function_xrefs reports 25 unconditional calls, all from FUN_005d6b90, plus non-call data/indirection references. A Ghidra string-anchor report for SE_PK_FX also finds exactly 25 native strings, all referenced from FUN_005d6b90.
Those 25 names form two consecutive anchor blocks when compared with the 760 recovered Godot catalog constants from spike_game_sound_ids.gdc:
| Anchor block | Catalog indices | Radium objects | Offset |
|---|---|---|---|
| EOB Pokedex FX | 40 through 46 |
109 through 115 |
+69 |
| Attack type FX | 118 through 135 |
196 through 213 |
+78 |
Because the offsets differ, there is no confirmed global Godot-catalog-index to Radium-object-ID mapping. rootfs-triage-wiki/scripts/build_catalog_offset_candidate_labels.py writes analysis/radium-image/catalog_offset_candidate_labels.tsv as a candidate-only audit table. Current output has 225 rows: 25 confirmed anchors, 199 unpromoted neighboring offset candidates, and 1 disagreement with the sound-test-adjacent table. build_best_named_sound_aliases.py currently promotes only the 196 unpromoted candidates whose previous best source was a plain main-image object reference, and marks them with name_source=catalog_offset_candidate plus confidence=candidate_unpromoted.
Important caution: under the EOB block's +69 offset, object 184 would be SE_PK_FX_BATTLE_TOTAL_FANFARE; the sound-test-adjacent candidate table labels numeric field 184 as SOUND: SUPER SKILL SHOT. Since the sound-test table already conflicts with ASR/Pokedex evidence elsewhere, this does not disprove the offset candidate, but it is enough to keep that conflicting row out of analysis/all-sound-best-name-manifest.tsv until another native/script call-site confirms it.
Godot GDC String Decoding
The exported Godot .gdc scripts are not plain text, but the format is now partially decoded enough for string/reference triage:
- Each file begins with
GDSC; byte offset0x0cstarts a Zstandard payload (28 b5 2f fd). - The decompressed payload contains both plain ASCII strings and identifiers XOR-obfuscated with byte
0xb6in a UTF-32LE-like layout. rootfs-triage-wiki/scripts/extract_godot_gdc_strings.pyrecovers both forms without executing target AArch64 binaries.- The current run processed
93.gdcscripts and wrote6,682recovered token rows plus772validated sound-reference rows. spike_game_sound_ids.gdccontributes all760knownSE_*catalog constants.- Only two scene scripts reference concrete
SpikeSoundIdsconstants directly:pokedex_slide_out.gdcandpokedex_slide_out_home.gdc; both reference the same six Pokedex EOB sounds. full_sceeen_fx.gdccontains the dynamic prefixSE_PK_FX_ATTACK_TYPE_, but no complete validated sound constant. This is retained ingdc_strings.tsvand excluded fromgdc_sound_refs.tsv.
The GDC pass confirms that the currently direct Godot scene call sites only cover a subset already mapped by the native bridge table. It does not yet provide a broad name-to-object map for the other catalog entries.
WAV Preview Evidence
The parser can wrap selected section 8 chunks as RIFF/WAV without changing the PCM payload.
Sample rate evidence:
- Static disassembly around raw file/objdump address
0x83f638callssnd_pcm_hw_params_set_channelswith2. - Address
0x83f644loads0xac44(44100) before the call at0x83f64ctosnd_pcm_hw_params_set_rate.
Preview validation:
file analysis/radium-image/wav-preview/*.wav
ffprobe -v error -show_entries stream=codec_name,codec_type,sample_rate,channels,duration -of compact=p=0:nk=1 analysis/radium-image/wav-preview/section8_0000_1ch_44100hz.wav
Observed output shape:
file(1)reportsRIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit.ffprobereportspcm_s16le, audio stream,44100Hz, with mono or stereo channels matching the decoded multiplier.
The 44100 Hz choice is supported by the native ALSA initialization path. The per-chunk table itself does not appear to store a separate sample rate.
Remaining Work
Confirmed format pieces:
- mmap image header offsets
- section 8 table location and row size
- section 8 CRC32 rule
- PCM chunk offset and byte-length formula
- static section 5 command-stream references from local object IDs to every section 8 chunk
- generic section 5 opcode stream boundaries, histograms, and native opcode length/handler table dumps for both images
- section 3 pointer-table count and raw indexed bitmap preview extraction for
1449records - 16-bit PCM WAV wrapping for preview extraction
- full WAV export with deterministic object/chunk names
- Ghidra-confirmed native aliases for 25 Godot bridge sound names
- sound-test-adjacent candidate aliases for 4 raw native label rows, retained for audit only
- local ASR draft labels for 1,705 bounded voice-like exports
- low-confidence ASR review candidate aliases for 34 otherwise structural main-game exports
- aligned Pokedex voice aliases for 1,137 description/name/category clips
- SPIKE menu section 8 parse, 300 menu command references, and 54 exported menu WAVs
Still unresolved:
- The main-game export still has
295structural object fallbacks,196catalog-offset candidate aliases,34low-confidence ASR review aliases,583raw ASR-draft names, and220catalog-matched ASR inferred names that still need native/script call-site confirmation. - The section 3 compact payload subtype has
28undecoded records, mostly compact128 x 32payloads; the final palette/alpha semantics for raw indexed previews also still need runtime confirmation. - Section 4 and non-sound section 5/6 command semantics still need naming and complete structure recovery; the new opcode table dump gives native handler addresses, but most handlers still need separate decompilation/call-site interpretation.
/games/pokemon_pro/spike3/spike_menu/gamestill needs its object metadata table recovered; the menu WAVs are associated with section 5 local object references, and 4 exact payload matches inherit stronger main-image ASR names.