# 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: ```sh 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: ```sh 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: ```sh 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: ```sh 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: ```sh 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: ```sh 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: ```sh 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: ```text 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: ```sh 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: ```c 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: ```sh /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: ```sh 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.