Files
pokemon_pinball_wiki/radium-image-format.md
2026-07-07 00:25:38 -05:00

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.bin is mmap-backed by the native game loader, not a standard archive.
  • The first 13 little-endian qwords form the image header copied by FUN_008733e0.
  • Header qwords 0 through 9 are section offsets used by FUN_008736c0.
  • Header qword 12 low32 gives the section 8 row count. For the main game image this is 0x9bb (2491) rows.
  • Section 8 starts at file offset 0x44d5396f; each row is 24 bytes: q0, q1, q2.
  • q0 is the data offset for a headerless PCM chunk.
  • q1 contains the masked lookup key and packed width/channel flags used by FUN_007a46e0.
  • q2 low32 is the sample count used by the loader.
  • Section 3 is a pointer table with 1477 entries. The pointed records begin with uint32 id, uint32 flags, uint16 width, and uint16 height; 1449 records currently render as raw indexed bitmap previews.
  • Section 5 is a dense command-stream table with 2531 local object slots and 5 variants per slot.
  • The loader's byte-length formula for opcode 0x0b references is derived_multiplier * (q2 & 0xffffffff) * 2.
  • Applying that formula to all section 8 rows produces 2491 bounded PCM candidate chunks. For 2490 rows, the computed byte length exactly equals the distance to the next row's q0 offset. The last row has no next-row delta for comparison.
  • The section 8 checksum is CRC32 over the aligned table bytes. The stored value 0x3f401e79 matches 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.json
  • analysis/radium-image/section4_table.tsv
  • analysis/radium-image/section3_bitmap_manifest.tsv
  • analysis/radium-image/section3-bitmaps/*.png
  • analysis/radium-image/section8_entries.tsv
  • analysis/radium-image/pcm_candidates.tsv
  • analysis/radium-image/command_sound_refs.tsv
  • analysis/radium-image/command_stream_summary.tsv
  • analysis/radium-image/command_opcode_rows.tsv
  • analysis/radium-image/command_opcode_histogram.tsv
  • analysis/radium-image/opcode_handler_table.tsv
  • analysis/radium-image/native_sound_id_map.tsv
  • analysis/radium-image/native_named_sound_manifest.tsv
  • analysis/radium-image/native_sound_test_label_manifest.tsv
  • analysis/radium-image/catalog_offset_candidate_labels.tsv
  • analysis/radium-image/asr_draft_sound_labels.tsv
  • analysis/radium-image/asr_review_sound_label_candidates.tsv
  • analysis/radium-image/pokedex_voice_label_manifest.tsv
  • analysis/radium-image/sound_export_manifest.tsv
  • analysis/radium-image/sound_export_manifest.json
  • analysis/radium-image/sounds/*.wav
  • analysis/radium-image/sounds-native-named/*.wav
  • analysis/radium-image/sounds-native-sound-test/*.wav
  • analysis/radium-image/sounds-pokedex-voice/*.wav
  • analysis/radium-image/sounds-best-named/*.wav
  • analysis/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.json
  • analysis/radium-spike-menu-image/section4_table.tsv
  • analysis/radium-spike-menu-image/section8_entries.tsv
  • analysis/radium-spike-menu-image/pcm_candidates.tsv
  • analysis/radium-spike-menu-image/command_sound_refs.tsv
  • analysis/radium-spike-menu-image/command_stream_summary.tsv
  • analysis/radium-spike-menu-image/command_opcode_rows.tsv
  • analysis/radium-spike-menu-image/command_opcode_histogram.tsv
  • analysis/radium-spike-menu-image/opcode_handler_table.tsv
  • analysis/radium-spike-menu-image/sound_export_manifest.tsv
  • analysis/radium-spike-menu-image/sound_export_manifest.json
  • analysis/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.json
  • analysis/radium-image/wav-preview/section8_0000_1ch_44100hz.wav
  • analysis/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.tsv
  • analysis/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.tsv
  • analysis/godot-introspection/gdc_strings.tsv
  • analysis/godot-introspection/gdc_sound_refs.tsv
  • analysis/radium-image/native_sound_id_map.tsv
  • analysis/radium-image/native_named_sound_manifest.tsv
  • analysis/radium-image/native_sound_test_label_manifest.tsv
  • analysis/radium-image/catalog_offset_candidate_labels.tsv
  • analysis/radium-image/sounds-native-named/*.wav
  • analysis/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) opens image.bin or image-sc%02d.bin, mmaps it read-only, and copies the first 13 qwords into runtime image metadata.
  • FUN_008736c0(image_index, section_id) returns mmap_base + header_qword[section_id] for section IDs 0 through 9.
  • FUN_00873290(image_index, count_id) returns count fields from the copied header. Count ID 4 returns 2491 for the main image.

Section 3 Bitmap Records

Ghidra xrefs to FUN_008736c0 identify section 3 as a count-indexed pointer table:

  • FUN_0080aca0(index) initializes DAT_033e2658 = FUN_008736c0(0, 3) and DAT_033e2660 = FUN_00873290(0, 1), then returns section3 + index * 8.
  • FUN_0080d1b0(index) and FUN_0080d250(index) fetch the qword pointer from section 3, pass it through FUN_00873360(0, pointer), and read signed 16-bit fields at pointed-record offsets +8 and +10.
  • Count ID 1 is 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) & ~0xf bytes and validates CRC32 with FUN_00a2e1f0.
  • Iterates row triples:
    • q0 = row[0]
    • q1 = row[1]
    • q2 = row[2]
  • Stores q0, q2 low32, and packed flags in a runtime lookup map keyed by image_id << 0x22 | (q1 & 0xfffc0003ffffffff).
  • Later, opcode 0x0b command 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: 0x3f1 entries.
  • Object table stride: 0x20 bytes.
  • Each entry's first qword points to a zero-terminated uint32_t object-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, with 0x13 entries and a 0x10-byte stride.
  • Each opcode table entry is qword handler_address, then qword payload_length. FUN_007a46e0 indexes the second qword directly through DAT_03179e78 to 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 at 0x410ce8, matching the parser argument used for menu extraction.

Static image tables:

  • Section 2 byte at section2 + 7 is 5, used here as the variant slot count.
  • Section 5 begins at offset 0xf8 and maps (object_local_id * variant_count + variant) to command-stream offsets.
  • Section 5 ends at section 4 offset 0x18c70; (0x18c70 - 0xf8) / (5 * 8) = 2531 local object slots.
  • Opcode 0x0b records are 12 bytes 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 530 opcode 0x0b references and 62 section 8 chunks, including local object 184 (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 1137 exported WAVs.
  • The Pokemon data table has 377 in_game=TRUE non-Blank rows; the normal triplet stream would be 1131 clips.
  • 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 in sound-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/game finds GodotSpikeBridge::register_sound(...) and 25 SE_PK_* cue names.
  • Ghidra string xrefs for SE_PK_FX_ATTACK_TYPE_BUG and SE_PK_FX_EOB_POKEDEX_PAGE_TURN both resolve to FUN_005d6b90.
  • FUN_005d6b90 calls FUN_006ec060(name, native_sound_id, 0xffffffff).
  • A static AArch64 BL scan, adjusted for the Ghidra/file-address delta of 0x00100000, finds exactly 25 calls to FUN_006ec060 from this registration table.
  • rootfs-triage-wiki/scripts/extract_native_sound_registrations.py decodes each call's string pointer and w2 immediate into analysis/radium-image/native_sound_id_map.tsv.
  • All 25 recovered native IDs resolve to existing primary_object_local_id rows in analysis/radium-image/sound_export_manifest.tsv.

The confirmed names currently cover:

  • SE_PK_FX_ATTACK_TYPE_BUG through SE_PK_FX_ATTACK_TYPE_WATER, native IDs 196 through 213.
  • SE_PK_FX_EOB_POKEDEX_PAGE_TURN through SE_PK_FX_EOB_POKEDEX_BACKGROUND, native IDs 109 through 115.

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 offset 0x0c starts a Zstandard payload (28 b5 2f fd).
  • The decompressed payload contains both plain ASCII strings and identifiers XOR-obfuscated with byte 0xb6 in a UTF-32LE-like layout.
  • rootfs-triage-wiki/scripts/extract_godot_gdc_strings.py recovers both forms without executing target AArch64 binaries.
  • The current run processed 93 .gdc scripts and wrote 6,682 recovered token rows plus 772 validated sound-reference rows.
  • spike_game_sound_ids.gdc contributes all 760 known SE_* catalog constants.
  • Only two scene scripts reference concrete SpikeSoundIds constants directly: pokedex_slide_out.gdc and pokedex_slide_out_home.gdc; both reference the same six Pokedex EOB sounds.
  • full_sceeen_fx.gdc contains the dynamic prefix SE_PK_FX_ATTACK_TYPE_, but no complete validated sound constant. This is retained in gdc_strings.tsv and excluded from gdc_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 0x83f638 calls snd_pcm_hw_params_set_channels with 2.
  • Address 0x83f644 loads 0xac44 (44100) before the call at 0x83f64c to snd_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) reports RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit.
  • ffprobe reports pcm_s16le, audio stream, 44100 Hz, 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 1449 records
  • 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 295 structural object fallbacks, 196 catalog-offset candidate aliases, 34 low-confidence ASR review aliases, 583 raw ASR-draft names, and 220 catalog-matched ASR inferred names that still need native/script call-site confirmation.
  • The section 3 compact payload subtype has 28 undecoded records, mostly compact 128 x 32 payloads; 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/game still 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.