7/10/26
This commit is contained in:
@@ -20,43 +20,60 @@ Static triage of the extracted Raspberry Pi based root filesystem in this worksp
|
||||
- Game version hint: `/games/spk/index/pokemon_pro.sidx -> pokemon_pro-0_83_0.sidx`, and release notes are under `/games/pokemon_pro/data/`.
|
||||
- SPIKE support package version: `/usr/local/spike/VERSION.txt` says `SPIKE3_secure v1.0.6`.
|
||||
|
||||
## Pages
|
||||
## Start Here
|
||||
|
||||
- [HTML Viewer](viewer.html) - self-contained reader with separate research, sound, voice-page, and media searches. Sound results can be previewed and downloaded in place.
|
||||
- [Analysis Evidence Map](analysis-evidence-map.md) - compact map from `analysis/` datasets to findings, confidence, and follow-up work.
|
||||
- [Artifact Index](artifact-index.md) - hashes and generated artifact inventory.
|
||||
- [Next Analysis Targets](next-analysis-targets.md) - prioritized open work.
|
||||
- [Docker/QEMU Emulation](docker-qemu-emulation.md) - what currently boots, what is visible, and why the game is not fully playable.
|
||||
|
||||
## System and Software
|
||||
|
||||
- [HTML Viewer](viewer.html) - self-contained MediaWiki-style viewer generated from these wiki files.
|
||||
- [Boot and Init](boot-and-init.md)
|
||||
- [Filesystem, Users, and Network](filesystem-users-network.md)
|
||||
- [Applications and Services](applications-and-services.md)
|
||||
- [Update and Recovery Flow](update-and-recovery.md)
|
||||
- [Artifact Index](artifact-index.md)
|
||||
- [Next Analysis Targets](next-analysis-targets.md)
|
||||
- [Copy Fail Assessment](copy-fail-assessment.md)
|
||||
- [Package, Image, Netbridge, and Firmware Notes](package-image-firmware.md)
|
||||
|
||||
## Native Components and Emulators
|
||||
|
||||
- [Main Game Behavior Notes](main-game-behavior.md)
|
||||
- [Conagent Reverse Engineering](conagent-re.md)
|
||||
- [Conagent Machine Emulator](conagent-machine-emulator.md)
|
||||
- [Netbridge Reverse Engineering and Emulator](netbridge-re.md)
|
||||
- [Magikarp Topper Serial Key and Emulator](topper-serial-key-emulator.md)
|
||||
- [Docker/QEMU Emulation](docker-qemu-emulation.md)
|
||||
|
||||
## Assets and Radium Format
|
||||
|
||||
- [Godot and Radium Assets](godot-and-radium-assets.md)
|
||||
- [Audio Asset Search](audio-asset-search.md)
|
||||
- [Radium Image Format](radium-image-format.md)
|
||||
- [Full Sound Export](sound-export.md)
|
||||
- [Confirmed Native Sound Names](sound-native-names.md)
|
||||
- [Native Sound Test Labels](sound-native-sound-test.md)
|
||||
- [Draft ASR Sound Labels](sound-asr-draft-labels.md)
|
||||
- [Low-Confidence ASR Review Candidates](sound-asr-review-candidates.md)
|
||||
- [Catalog-Matched ASR Sound Labels](sound-asr-catalog-labels.md)
|
||||
- [Catalog-Offset Candidate Sound Labels](sound-catalog-offset-candidates.md)
|
||||
- [Pokedex Voice Alignment](sound-pokedex-voice.md)
|
||||
- [SPIKE Menu Sound Export](sound-spike-menu.md)
|
||||
- [Best-Named Sound Export](sound-best-named-export.md)
|
||||
- [Unresolved Structural Sound Review](sound-unresolved-review.md)
|
||||
- [Asset Previews](asset-previews.md)
|
||||
- [Radium Section 3 Bitmap Previews](radium-section3-bitmaps.md)
|
||||
- [Conagent Reverse Engineering](conagent-re.md)
|
||||
- [Conagent Machine Emulator](conagent-machine-emulator.md)
|
||||
- [Main Game Behavior Notes](main-game-behavior.md)
|
||||
- [Package, Image, Netbridge, and Firmware Notes](package-image-firmware.md)
|
||||
- [Magikarp Topper Serial Key and Emulator](topper-serial-key-emulator.md)
|
||||
- [Docker/QEMU Emulation Scaffold](docker-qemu-emulation.md)
|
||||
- [Game Asset Spoilers and Possible Unreleased Features](game-asset-spoilers-unreleased.md)
|
||||
|
||||
## Voice-Line Catalogs
|
||||
|
||||
These pages contain generated or inferred labels and are intentionally separated from ordinary research results in the HTML viewer.
|
||||
|
||||
- [Draft ASR Sound Labels](sound-asr-draft-labels.md)
|
||||
- [Low-Confidence ASR Review Candidates](sound-asr-review-candidates.md)
|
||||
- [Catalog-Matched ASR Sound Labels](sound-asr-catalog-labels.md)
|
||||
- [Pokedex Voice Alignment](sound-pokedex-voice.md)
|
||||
|
||||
## Notes and Caveats
|
||||
|
||||
- This is static filesystem triage. I did not execute target AArch64 binaries.
|
||||
- Most findings are static filesystem triage. The QEMU page clearly identifies the separate generated guest copy that was executed during emulator development.
|
||||
- The extracted `/boot` directory is empty here. `/etc/fstab` expects `/dev/mmcblk0p1` to mount at `/boot` as read-only VFAT with `noauto`; the actual boot partition may be a separate extraction artifact.
|
||||
- I did not copy sensitive values into the wiki. In particular, `corefile_passphrase.txt` exists and is referenced by the core export script, but its value is intentionally omitted.
|
||||
- `viewer.html` is generated by `scripts/build_viewer.py`; rebuild it after wiki page edits so Markdown links, sidebar navigation, search, and source views stay in sync.
|
||||
- `viewer.html` and `sound-index.json` are generated by `scripts/build_viewer.py`; rebuild after wiki or best-named sound-gallery edits so navigation, search, previews, and downloads stay in sync.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Analysis Evidence Map
|
||||
|
||||
## Purpose
|
||||
|
||||
This page maps the durable outputs under `analysis/` to the wiki pages that interpret them. It is an inventory of locally generated evidence, not a claim that every recovered label or emulator behavior is target-confirmed.
|
||||
|
||||
Confidence terms used throughout the wiki:
|
||||
|
||||
- **Confirmed static evidence**: directly supported by a target file, hash, string offset, decoded table, or reproducible parser output.
|
||||
- **Validated host artifact**: a host-side parser or emulator passed its local checks, but has not necessarily been accepted by the game or physical hardware.
|
||||
- **Inferred label**: derived from ordering, ASR, content matching, or structural correlation and still needs native/script confirmation.
|
||||
- **Runtime observation**: observed only in the generated QEMU guest copy; it does not modify or authenticate the extracted rootfs evidence.
|
||||
|
||||
## Coverage Summary
|
||||
|
||||
| Analysis area | Durable evidence | Current result | Interpretation |
|
||||
| --- | --- | --- | --- |
|
||||
| Static binary and package triage | `analysis/static-triage/` | Offset strings and ELF metadata for the main game, SPIKE menu, and netbridge; `.sidx` paths; Intel HEX address ranges | Confirmed static evidence; see `main-game-behavior.md` and `package-image-firmware.md`. |
|
||||
| Whole-rootfs audio search | `analysis/audio-search/summary.json` | 7,375 files scanned; 281 files had parseable media streams; none had an audio stream | Supports the conclusion that gameplay audio is stored in Radium `image.bin`, not as ordinary rootfs audio files; see `audio-asset-search.md`. |
|
||||
| Main Radium image | `analysis/radium-image/header.json` | Section 8 CRC matches (`0x3f401e79`); all 2,491 PCM rows are bounded; 12,710 sound references resolve; 12,655 command streams decode with zero malformed streams | Confirmed parser output; see `radium-image-format.md` and `sound-export.md`. |
|
||||
| SPIKE menu Radium image | `analysis/radium-spike-menu-image/header.json` | Section 8 CRC matches (`0xb8fe11f9`); all 54 PCM rows are bounded; 300 sound references resolve; 340 command streams decode with zero malformed streams | Confirmed parser output; see `sound-spike-menu.md`. |
|
||||
| Radium command tables | `analysis/radium-image/command_opcode_rows.tsv`, `analysis/radium-spike-menu-image/command_opcode_rows.tsv` | 108,765 main-game command rows across 13 observed opcodes; 2,065 SPIKE-menu rows across 10 opcodes | The stream structure is decoded, but most non-`0x0b` opcode semantics remain open. |
|
||||
| Radium bitmaps | `analysis/radium-image/section3_bitmap_manifest.tsv` | 1,477 records: 1,449 raw indexed previews and 28 compact payloads still undecoded | Partial static recovery; see `radium-section3-bitmaps.md`. |
|
||||
| Compiled Godot scripts | `analysis/godot-introspection/` | 93 `.gdc` script summaries, 760 sound constants, and 772 validated sound-reference rows | String/catalog recovery is confirmed; general bytecode/control-flow recovery remains incomplete. |
|
||||
| Sound naming | `analysis/all-sound-best-name-manifest.tsv` | One best-available name for 2,545 exports: 2,491 main-game and 54 SPIKE-menu WAVs | Mixed provenance. Native names are stronger than Pokedex alignment, catalog/ASR matches, draft ASR, and structural fallbacks. |
|
||||
| Native sound names | `analysis/radium-image/native_named_sound_manifest.tsv` | 25 registrations recovered from direct native call sites | Confirmed static names. |
|
||||
| Pokedex voice alignment | `analysis/radium-image/pokedex_voice_label_manifest.tsv` | 1,137 aligned voice rows | Strong structural/content alignment, but not a general native object-name map. |
|
||||
| ASR and catalog enrichment | `analysis/radium-image/asr_draft_sound_labels.tsv`, `asr_catalog_sound_label_candidates.tsv`, `asr_review_sound_label_candidates.tsv` | 2,486 ASR rows, 220 catalog matches, and 169 low-confidence review candidates | Inferred labels. The viewer isolates these catalogs from research search by default. |
|
||||
| Conagent backend | `analysis/conagent-machine-emulator/` | Configurable host-side HTTP/time/machine-state emulator | Validated host artifact; protocol coverage is intentionally partial; see `conagent-machine-emulator.md`. |
|
||||
| Netbridge | `analysis/netbridge-emulator/`, `analysis/netbridge-pico-firmware/` | Python UART emulator plus portable/Pico protocol model for recovered framing, version, CRC, and stub node commands | Host tests pass. Full game acceptance and live electrical/timing validation remain open; see `netbridge-re.md`. |
|
||||
| Topper node | `analysis/topper-emulator/` | MicroPython model for the recovered serial-key command path | Static protocol recovery plus host artifact; live nodebus validation remains open. |
|
||||
|
||||
## Important Boundaries
|
||||
|
||||
- `analysis/` is derived material. Original target paths and target hashes remain in `artifact-index.md`.
|
||||
- CRC validation proves that the decoded table matches the extracted image bytes; it does not prove every inferred semantic label.
|
||||
- ASR text is a search aid. It is not a native symbol source and may contain recognition errors.
|
||||
- Emulator unit tests validate the recreated model, not compatibility with every game state or the physical SPIKE bus.
|
||||
- QEMU results come from `emulation/work/rootfs.ext4`, a generated copy with injected helpers and shims. See `docker-qemu-emulation.md` for the runtime boundary and current blockers.
|
||||
|
||||
## Open Enrichment Work
|
||||
|
||||
1. Assign semantics to the remaining Radium opcodes by tracing native consumers.
|
||||
2. Decode the 28 compact section 3 bitmap payloads and verify palette/alpha behavior.
|
||||
3. Recover broader `.gdc` bytecode control flow where strings alone do not establish reachability.
|
||||
4. Promote inferred sound names only when supported by native registration, script linkage, or runtime evidence.
|
||||
5. Validate netbridge and topper models against the game or hardware captures before describing them as target-compatible.
|
||||
+28
-12
@@ -41,7 +41,8 @@ These files are workspace-local generated artifacts, not target-root files.
|
||||
| `a0cf4cad90fcccfd97140599935e56d8e49ef68560aff6e25813e731ac3c20c0` | `rootfs-triage-wiki/scripts/build_native_named_sound_aliases.py` | Builds confirmed-name hardlink aliases for native sound registrations. |
|
||||
| `e9a156d4371e03232c6826bd70e1576e34b6720352beafb06a9f41321c32cf1e` | `rootfs-triage-wiki/scripts/extract_native_sound_test_labels.py` | Extracts native sound-test-adjacent candidate rows and builds audit hardlink aliases for the candidate WAVs. |
|
||||
| `ac0f243ec3fb29e1cd61cdd8b807ac1578bc1202e03538be3314c4380dbea723` | `rootfs-triage-wiki/scripts/extract_godot_gdc_strings.py` | Static decoder for exported Godot `.gdc` files: strips `GDSC` header, decompresses Zstandard payloads, and recovers plain plus XOR-`0xb6` strings. |
|
||||
| `1488286a2a9f59fde8ab7c48f1f992fb5be65db2791d0b5a400a9523cfebc6f6` | `rootfs-triage-wiki/scripts/build_viewer.py` | Static wiki viewer builder with media preview directives for audio, image, video, text, and font assets. |
|
||||
| `94e7de1da3f5183a457c4d26e6e93b8249bac9c4c6fe83f37802fc465b1ddfd2` | `rootfs-triage-wiki/scripts/build_viewer.py` | Static wiki viewer builder with media directives, a deterministic 2,545-sound index, scoped page search, and inline sound preview/download results. |
|
||||
| `4bcc7c331f3d9cb28248ee1479941bbbfc7dc5a2708fd50b137fb036e92301ba` | `rootfs-triage-wiki/sound-index.json` | Generated canonical sound-search index with title, filename, provenance metadata, source gallery, and WAV path for all 2,545 best-named sounds. |
|
||||
| `975df239ca2930e91abf7af5e53cb24a865ce31faf11a2bba9adae6e61995c3a` | `analysis/audio-search/summary.json` | Broad target-root audio scan summary: 7,375 files scanned, 281 stream-bearing files, 0 target-root files with audio streams. |
|
||||
| `9f0132e612f2a6aafb5b00335cbf74b7f5fb57c5b34a4f9bda21e02b6ad0f91b` | `analysis/audio-search/filename-hits.tsv` | Audio-related filename hits; system audio stack files and images, not gameplay sound content. |
|
||||
| `818e61772280cd597c3b101aa90b90764630bd8d7892ea5c20bf5e48f34d408c` | `analysis/audio-search/file-type-hits.tsv` | `file(1)` media/audio-looking hits; target-root video containers plus firmware false positives. |
|
||||
@@ -95,7 +96,7 @@ These files are workspace-local generated artifacts, not target-root files.
|
||||
| `0db013f19c74d3bade949b59ce6dfeda68d35def01c78e1cd374f02b17705d3b` | `analysis/godot-introspection/gdc_sound_refs.tsv` | Validated `SE_*` references from compiled Godot scripts: 760 catalog constants and 12 direct Pokedex scene references. |
|
||||
| `6eafaeeb0bc59a5d05a9a22f60a432a3b0e9fb21b0725609b20e25f6547c41ab` | `analysis/radium-image/wav-preview-manifest.json` | Manifest for 20 capped WAV previews generated from section 8 PCM chunks at 44.1 kHz. |
|
||||
| `b39a9123afb9c65da5babf6b8531a7597f0008c94c3d34e5480647676efaeb26` | `analysis/media-preview-pages.json` | Generated preview-page summary: 2,491 main sounds, 1,137 Pokedex voice labels, 220 catalog-matched ASR labels, 169 low-confidence ASR review candidates, 225 catalog-offset candidate rows, 345 unresolved structural review rows, 54 SPIKE menu sounds, 2,545 complete best-named sound aliases, 25 confirmed native sound aliases, 4 sound-test-adjacent candidate rows, 1,705 draft ASR labels, 1,212 images, 273 videos, 1,170 text files, 5 fonts, and 1,449 Radium section 3 bitmap previews. |
|
||||
| `975eef45348a0a313110617c756978c87cb77eace904bd57acc858dfc6f00b43` | `rootfs-triage-wiki/sound-catalog-offset-candidates.md` | Generated preview page with 225 audit-only catalog-offset candidate audio entries. |
|
||||
| `e7833d43c8cbbe1225fd61f6b2e68d4849931a0f2ae1c7d8def9c92105bb8a1f` | `rootfs-triage-wiki/sound-catalog-offset-candidates.md` | Generated preview page with 225 audit-only catalog-offset candidate audio entries. |
|
||||
| `3f35647f82fd9257351dd687c1614318e4def57efa6b0182f66a1278e3979300` | `rootfs-triage-wiki/sound-asr-review-candidates.md` | Generated preview page with 169 low-confidence ASR review candidate audio entries. |
|
||||
| `c710013ae59cea8451c4211954678b835da98613aa6687d7a88116f5e92eb5d3` | `rootfs-triage-wiki/sound-unresolved-review.md` | Generated preview page with 345 remaining structural object-reference audio entries grouped by duration/channel review buckets. |
|
||||
| `0899658ff2760be786ba4f682c4920a99a55c51350801c602528f47cecf2ffa2` | `rootfs-triage-wiki/radium-section3-bitmaps.md` | Generated preview index for 1,449 decoded Radium section 3 bitmap PNGs and 28 undecoded compact payload rows. |
|
||||
@@ -103,6 +104,16 @@ These files are workspace-local generated artifacts, not target-root files.
|
||||
| `b5ddb933f125cf6b54b81c56f4e3430ded50d843c045a49f0727a1f7ebc5f66f` | `rootfs-triage-wiki/scripts/harvest_loose_openable_assets.py` | Static loose-asset copier for PNG, TTF, MP4, and MOV files. |
|
||||
| `257cb2d9ba653385aba63157a3bc143bb479c5188071ef529996d3f5da258b71` | `analysis/topper-emulator/topper_emulator.py` | MicroPython SPIKE node-board emulator for the Magikarp topper serial-key path; 6,452 bytes. |
|
||||
| `32263b75f49aefba04596c196f85cc4ae4477eb536f767fcdeac7c0d5fc40e9b` | `analysis/topper-emulator/README.md` | Emulator notes and recovered command summary; 5,820 bytes. |
|
||||
| `5b510902f4027e364702a61338ff613bde2b77d8441ba2703b343e642cac0453` | `analysis/netbridge-emulator/netbridge_emu.py` | Python host-side emulator for the RP2040 netbridge UART protocol, bridge commands, CRC response, PTY/socket serving, and a stub nodebus device. |
|
||||
| `542b9835e6b6d8107d68cc6d62bfdfe85995fe61029c36ffcdba3e0295f6d455` | `analysis/netbridge-emulator/test_netbridge_emu.py` | Unit tests for host frame escaping/checksum, version response, firmware CRC response, and node serial-key command. |
|
||||
| `b7a05a1f4d3cffc7d163e26afed6fa5402ee1a94df1687447dc1aefe067e03cd` | `analysis/netbridge-emulator/README.md` | Runbook and porting notes for the host-side netbridge emulator. |
|
||||
| `a27f5e00f09a014391418f6822dc6c5481c224ef0cf42786b8b290ecd508d9c3` | `analysis/netbridge-pico-firmware/CMakeLists.txt` | Pico SDK and portable host-test build file for the RP2040 netbridge firmware recreation. |
|
||||
| `d01867dc66873799b966cf24b7574d9e389277f1556d88b92fd61342ea760222` | `analysis/netbridge-pico-firmware/include/netbridge_protocol.h` | Shared host-frame protocol and model API for the Pico firmware recreation. |
|
||||
| `918b434ad6c8b4a0455b4eea0bd7ed8c341fb4e709bc6012c0aa43b4c074601e` | `analysis/netbridge-pico-firmware/src/netbridge_protocol.c` | Portable STX/ETX/ESC frame encoder/decoder for the Pico firmware recreation. |
|
||||
| `6ea214bb401d325a90721630f156bd917ca83a09a1782d9acb85a24e0ff8016c` | `analysis/netbridge-pico-firmware/src/netbridge_model.c` | Portable bridge command and stub nodebus model for the Pico firmware recreation. |
|
||||
| `708aff6ad0d42b5893c69acf3e7553b710869209fd9404e6c752ccb74cb9b182` | `analysis/netbridge-pico-firmware/src/pico_main.c` | Pico SDK entry point using UART0 on GPIO 16/17 at 2,000,000 baud with GPIO 25 heartbeat. |
|
||||
| `838c17c4d290be3131cda58dbba8b58e7fa7048313462bc86570f71c88a43e71` | `analysis/netbridge-pico-firmware/tests/host_protocol_test.c` | Portable C host test for frame escaping, version, CRC, and node serial-key responses. |
|
||||
| `b55cae30a3b97d2d7f6c412cb2a899cac54674417805791fae101e5547e6ecd9` | `analysis/netbridge-pico-firmware/README.md` | Build, wiring, and scope notes for the Pico firmware recreation. |
|
||||
|
||||
## Docker/QEMU Emulation Scaffold
|
||||
|
||||
@@ -110,21 +121,23 @@ These files are workspace-local generated support artifacts, not target-root evi
|
||||
|
||||
| SHA-256 | Path | Notes |
|
||||
| --- | --- | --- |
|
||||
| `76636f3634164c840c87c506d007d8be3f1590a0eca7318cd8b9734c2dd2dacf` | `emulation/compose.yaml` | Docker Compose entry point for QEMU, noVNC, dashboard, and conagent backend ports. |
|
||||
| `0bd0dbb8078c5a5cc7a8131523cc7475732681a908c1542136f9aa3c73af915f` | `emulation/compose.yaml` | Docker Compose entry point for QEMU, noVNC, dashboard, conagent backend ports, and optional netbridge emulator env. |
|
||||
| `62449c2a30e70eacb59e65edaa581d0e43859ce72aa7d0cebf4ee9bda81d6957` | `emulation/docker/Dockerfile` | Debian-based emulator image with QEMU, noVNC, ffmpeg, qemu-user-static, initramfs tools, AArch64 cross tools, ARM64 Mesa EGL/GLVND runtime packages, and ARM64 Mesa Vulkan/lavapipe drivers. |
|
||||
| `86d366aaadc007f2479b117dd1bca3c2799d90eb9ca0a37f13880cfee1694ffb` | `emulation/docker/fetch-debian-arm64-kernel.sh` | Downloads Debian ARM64 kernel/modules and builds an initramfs with DRM/`virtio_gpu` modules. |
|
||||
| `d3f51f25950db7bcc9c9b9651b830cc3a5703e6914f54ae03da68d4c9ecfc00f` | `emulation/docker/entrypoint.sh` | Container entrypoint for image preparation, noVNC, conagent backend, dashboard, and QEMU startup. |
|
||||
| `7d6d7b90fcbbe703ee2c5ff674c8cf6fc13a37175d0c02ba07e5f1db873ce568` | `emulation/scripts/prepare-rootfs-image.sh` | Builds the generated ext4 guest image from target-root entries and injects guest helpers, Mesa fallback drivers/runtime libraries, Vulkan ICD metadata, GLVND descriptor, and stubs. |
|
||||
| `4d8a48f00db61a658c7a5e03ad245983b19c4f535368e0f7e8ff2dafe52db729` | `emulation/scripts/run-qemu.sh` | Starts QEMU `virt` with `virtio-blk`, multi-output `virtio-gpu`, noVNC-facing VNC, serial logs, networking, and a control socket. |
|
||||
| `dc0dfa3a01323f80d6e902a7df082b1dc1a5c973ef211c8d805889200609c739` | `emulation/docker/entrypoint.sh` | Container entrypoint for image preparation, noVNC, conagent backend, dashboard, optional netbridge emulator, and QEMU startup. |
|
||||
| `6ea31e7664c1179516caa8fb9435667442444b10901d9d564ddc86aa4b429489` | `emulation/scripts/prepare-rootfs-image.sh` | Builds the generated ext4 guest image from target-root entries and injects guest helpers, Mesa fallback drivers/runtime libraries, Vulkan ICD metadata, GLVND descriptor, and stubs. |
|
||||
| `878427ff0b8930097f28ef17cb27d7314e0dba8198f9de091fbef7ca630c1ef9` | `emulation/scripts/run-qemu.sh` | Starts QEMU `virt` with `virtio-blk`, multi-output `virtio-gpu`, noVNC-facing VNC, serial logs, networking, a control socket, and optional netbridge serial. |
|
||||
| `19218459e198a1e21bcbd720b32568c4873d2fb074a3920504b773e63f179c19` | `emulation/guest/emu-init` | Guest init for mounts, diagnostics, `/dev/dri/card1` compatibility symlink, hardware placeholders, control agent startup, and target launch. |
|
||||
| `dcb509523563ad4515d5becf048bda5dcb1e345758a8df0d88ff529761c73c65` | `emulation/guest/setup-fake-devices.sh` | Creates early inert device placeholders and hardware setup log. |
|
||||
| `a02dba6a4793386aea46715c71e0ac617daeebdc089611928051601eab60a59e` | `emulation/guest/setup-fake-devices.sh` | Creates early inert device placeholders, optional `/dev/ttyAMA5` netbridge serial mapping, and hardware setup log. |
|
||||
| `0212843e592f9fb87d2110c4f468df580cc56c1c118d2a4a87dc19e50c754abb` | `emulation/guest/guest-control-agent.sh` | Guest virtio-serial event logger for dashboard switch events. |
|
||||
| `db6adc3604151f4dc8559a00e3e7fe42f63f74c87da54234590df524d61928e5` | `emulation/guest/launch-target.sh` | Launches `boot_display`, SPIKE menu, main game, monitor scripts, or shell with emulator Mesa/EGL environment. |
|
||||
| `b9ea20682dc5f463296f64f535050b7aff0a1d5ebabd2dc33f49a818dbd74590` | `emulation/dashboard/server.py` | Browser dashboard and JSON switch-event API. |
|
||||
| `a8ba5fc6670acd22c70b2941f1c406162a2f68303079ccda0cc49bbdf54f05aa` | `emulation/stubs/spike3emu_stub.c` | AArch64 `LD_PRELOAD` stub for logging/faking selected device opens/ioctls, DRM connector compatibility, GBM call tracing, and EGL call tracing/fallbacks. |
|
||||
| `ceee33ca3ce178067fdc8993f5cc2ef54dcbdc9277fe9b682f3f77505fe690ec` | `emulation/guest/launch-target.sh` | Launches `boot_display`, SPIKE menu, main game, monitor scripts, or shell with emulator Mesa/EGL and optional real-netbridge-serial environment. |
|
||||
| `b8b289eeccb83d3036cbb06ebacedcb2807333ff735097717cc473cac8f21e96` | `emulation/dashboard/server.py` | Browser dashboard and JSON switch-event API. |
|
||||
| `ae65658eb53d8e116a0c1b342140e4267211bd3752a25e1e65b28547cb4df7a5` | `emulation/stubs/spike3emu_stub.c` | AArch64 `LD_PRELOAD` stub for logging/faking selected device opens/ioctls, DRM connector compatibility, GBM/EGL tracing/fallbacks, and optional `/dev/ttyAMA5` passthrough. |
|
||||
| `1062b060d4bf6a4798bcca3ee188d4c53322271cd882f8ef36b6996b716572a0` | `emulation/scripts/capture-vnc.py` | Raw RFB/VNC framebuffer capture helper used to verify browser-visible QEMU output. |
|
||||
| `411cadf6e8e4204c1c2434da69b71f6bde1459611772b68bdcbf7d3838cfc144` | `emulation/README.md` | User-facing emulator runbook and current limitations. |
|
||||
| `69ba774449f0a7ca5b1feba6101e8489528b374c9a66dea988dfde619b637644` | `rootfs-triage-wiki/docker-qemu-emulation.md` | Wiki page documenting implementation, verification status, and current display blocker. |
|
||||
| `61f62ec692e91cddcd1dd81d7edb32aaeb7951f80c82f64b472fa54f9009e217` | `emulation/README.md` | User-facing emulator runbook, optional netbridge mode, and current limitations. |
|
||||
| `239ddcc7b6928582df66273cd557bf939dbc14f792f554c6ee2e6d4844ac00ca` | `rootfs-triage-wiki/docker-qemu-emulation.md` | Wiki page documenting implementation, verified progress, optional netbridge wiring, and the current display and hardware blockers. |
|
||||
| `62eddb1582076ca9e07b3910e71673e7d15e4cd3599d73ce51e3a4148e5b95ad` | `rootfs-triage-wiki/analysis-evidence-map.md` | Cross-reference from durable `analysis/` outputs to wiki findings, confidence boundaries, and open enrichment work. |
|
||||
| `ac568dc027509f7c30475f444490771cdfa37e2510deeb32cda29367ee5ec78a` | `rootfs-triage-wiki/netbridge-re.md` | Wiki page documenting Ghidra-backed netbridge protocol recovery, emulator use, Docker wiring, and Pico firmware recreation. |
|
||||
|
||||
## Generated Static Triage Artifacts
|
||||
|
||||
@@ -150,6 +163,9 @@ These files are workspace-local generated artifacts, not target-root files.
|
||||
| `4b189cabd04c27ccf3583c6d708fe48dde8c17e2b5ad0056610d45a915868a04` | `/games/pokemon_pro/spike3/bin/boot_display` |
|
||||
| `5e12b2e65d077ca0fa0ad18df2fc302d350dfc91b716c41967015db6e964a755` | `/games/pokemon_pro/spike3/bin/bttest` |
|
||||
| `05b7f7c329da79fb24078df69c8401c7a47be5c8a855b73fb937e052bce28d85` | `/games/pokemon_pro/spike3/netbridge/netbridge.elf` |
|
||||
| `624c999309240592979ddeb839258f21cb46633a6b2bbbd43543a55fd97157dc` | `/games/pokemon_pro/spike3/netbridge/rp2040.cfg` |
|
||||
| `5777f6f0db066335e619ecd1ee8b3f1fc594ebff072d52b7ada4f89370eaa96a` | `/games/pokemon_pro/spike3/netbridge/spike3-swd.cfg` |
|
||||
| `b6b795fddf4a593d98595f71bf5638f6737d18b364eae657337147dc075148af` | `/games/pokemon_pro/spike3/netbridge/version.txt` |
|
||||
| `3928411d136f58197dc6fd8dd9a9f2f2f5150c55dd3fa1637ad68a6931eb01f6` | `/games/pokemon_pro/spike3/spike_menu/game` |
|
||||
|
||||
## SPIKE Scripts
|
||||
|
||||
@@ -15,6 +15,28 @@ Purpose:
|
||||
|
||||
Static-analysis boundary: this page documents the emulator scaffold and evidence-based design. Creating and booting `emulation/work/rootfs.ext4` executes a generated guest copy, not the extracted evidence files directly.
|
||||
|
||||
## Current Status
|
||||
|
||||
The emulator is a working analysis scaffold, not yet a playable machine emulator. It boots the generated AArch64 guest, exposes logs and a browser-visible framebuffer, and runs far enough to identify deterministic display and hardware dependencies. It does not yet produce a real game frame or model the complete SPIKE hardware environment.
|
||||
|
||||
| Layer | Status | Evidence / remaining issue |
|
||||
| --- | --- | --- |
|
||||
| Generated guest boot | Working | QEMU reaches `SPIKE3 emulator init complete`; see `emulation/work/logs/qemu-serial.log`. |
|
||||
| Browser access | Working | noVNC and the dashboard respond, and `capture-vnc.py` records nonblack 1360x768 frames. |
|
||||
| DRM device discovery | Working | `virtio_gpu` creates `/dev/dri/card0`, `/dev/dri/renderD128`, and `fb0`; the guest provides the target-compatible `card1` alias. |
|
||||
| Mesa EGL/GLES initialization | Working with compatibility overlay | The native probe reaches EGL and OpenGL ES 3.1 with Mesa softpipe; fbdev mode reaches a real surfaceless display and pbuffer. |
|
||||
| Target display output | Blocked | The game-created EGLImage/FBO remains incomplete. Mesa reports `GL_INVALID_FRAMEBUFFER_OPERATION`, so the captured image is the shim's fallback bands rather than a rendered game frame. |
|
||||
| Audio startup | Partially bypassed | A null ALSA default clears the earlier missing-PCM abort; real amplifier behavior and audio output are not emulated. |
|
||||
| Netbridge/nodebus | Scaffolded, not end-to-end verified | A Python netbridge model and optional `/dev/ttyAMA5` wiring exist, but the full guest boot with that mode has not been validated and OpenOCD programming may block before UART exchange. |
|
||||
| Switch dashboard | Logging only | Events reach the guest log but are not translated into SPIKE nodebus state. |
|
||||
| Video decode | Blocked after display setup | HEVC/V4L2 initialization still expects target hardware capabilities not supplied by the current QEMU machine. |
|
||||
|
||||
### Why It Does Not Fully Work Yet
|
||||
|
||||
The extracted software assumes a Raspberry Pi graphics and I/O stack, while QEMU exposes a generic `virtio-gpu` machine. The compatibility layer can satisfy discovery calls and create a real Mesa context, but it cannot yet reproduce the exact GBM/EGLImage-to-framebuffer path used by the game. That is the immediate reason no genuine game frame reaches noVNC.
|
||||
|
||||
After that display path is fixed, the game still expects several independent machine subsystems: OpenOCD-managed RP2040 netbridge firmware, SPIKE nodebus devices, TAS/MAX amplifier register behavior, physical switches/lamps/coils, and V4L2 HEVC decode. Those are separate emulation targets; success in one does not imply that the others are modeled.
|
||||
|
||||
## Evidence Basis
|
||||
|
||||
The main game and SPIKE menu use a direct DRM/KMS display path rather than a desktop windowing path.
|
||||
@@ -42,6 +64,7 @@ Primary files:
|
||||
| `emulation/guest/emu-init` | Guest init used by QEMU with `init=/usr/local/spike-emu/bin/emu-init`. |
|
||||
| `emulation/dashboard/server.py` | Browser dashboard on port `8090` for links, logs, and virtual switch events. |
|
||||
| `emulation/stubs/spike3emu_stub.c` | Optional AArch64 `LD_PRELOAD` stub for inert fake device opens/ioctls on GPIO, I2C, serial, input, and `/dev/mem` paths. |
|
||||
| `analysis/netbridge-emulator/netbridge_emu.py` | Optional host-side netbridge emulator used when `SPIKE3_NETBRIDGE_EMU=1`. |
|
||||
|
||||
Generated runtime files:
|
||||
|
||||
@@ -50,6 +73,7 @@ Generated runtime files:
|
||||
| `emulation/work/rootfs.ext4` | Generated guest root disk; not checked in as evidence. |
|
||||
| `emulation/work/logs/` | Host/container logs for QEMU, dashboard, noVNC, and conagent backend. |
|
||||
| `emulation/work/control.sock` | QEMU virtio-serial socket used by the dashboard event bridge. |
|
||||
| `emulation/work/netbridge.sock` | Optional QEMU socket backend for the Python netbridge emulator. |
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -73,6 +97,14 @@ SPIKE3_AUTOSTART=game docker compose -f emulation/compose.yaml up
|
||||
SPIKE3_AUTOSTART=monitors docker compose -f emulation/compose.yaml up
|
||||
```
|
||||
|
||||
Run the main game with the Python netbridge emulator attached:
|
||||
|
||||
```sh
|
||||
SPIKE3_NETBRIDGE_EMU=1 SPIKE3_AUTOSTART=game docker compose -f emulation/compose.yaml up --build
|
||||
```
|
||||
|
||||
This starts `analysis/netbridge-emulator/netbridge_emu.py` on `emulation/work/netbridge.sock`, attaches it as a second QEMU PL011 serial port, maps guest `/dev/ttyAMA5` to that serial path, and tells the preload shim not to fake `/dev/ttyAMA5`.
|
||||
|
||||
The guest init accepts these autostart targets:
|
||||
|
||||
- `boot-display`
|
||||
@@ -91,14 +123,23 @@ The generated guest image:
|
||||
- Injects `/usr/local/spike-emu/bin/launch-target.sh` to start `boot_display`, SPIKE menu, game, or monitors.
|
||||
- Injects an optional `LD_PRELOAD` hardware stub at `/usr/local/spike-emu/lib/libspike3emu_stub.so` when the Docker image has `aarch64-linux-gnu-gcc`.
|
||||
- Injects overlay Mesa DRI fallback drivers under `/usr/local/spike-emu/mesa-dri` when the Docker image has ARM64 `virtio_gpu`, `kms_swrast`, `swrast`, and `zink` drivers.
|
||||
- Injects matching ARM64 Debian Mesa/GLVND EGL libraries under `/usr/local/spike-emu/mesa-lib` plus `/usr/share/glvnd/egl_vendor.d/50_mesa.json`, so the fallback DRI drivers are loaded by the same Mesa userspace stack instead of the target rootfs `libEGL`.
|
||||
- Injects matching ARM64 Debian Mesa/GLVND EGL libraries under `/usr/local/spike-emu/mesa-lib` plus `/usr/share/glvnd/egl_vendor.d/50_mesa.json`, so the fallback DRI drivers are loaded by the same Mesa userspace stack instead of the target rootfs `libEGL`. The overlay intentionally does not shadow the target rootfs `libstdc++.so.6`; Debian bookworm's copy lacks `GLIBCXX_3.4.31`/`GLIBCXX_3.4.32`, which `/games/game` requires.
|
||||
- Injects ARM64 Mesa Vulkan/lavapipe support when present: `mesa-vulkan-drivers:arm64`, `libvulkan.so*`, `libvulkan_lvp.so`, and `/usr/share/vulkan/icd.d/lvp_icd.aarch64.json`.
|
||||
- Injects `/usr/local/spike-emu/bin/display-probe`, an AArch64 helper compiled from `emulation/guest-tools/display-probe.c`, to test the guest GBM/EGL path before target launch.
|
||||
- Supports `SPIKE3_DISPLAY_BACKEND=auto|native|fbdev`. `auto` records the probe result under `/run/spike-emu/display-backend.env` and currently selects `fbdev` presentation by default; `native` is still available for forced comparison.
|
||||
- Supports optional `SPIKE3_NETBRIDGE_EMU=1`. In that mode the container starts the Python netbridge emulator, QEMU adds a second PL011 serial backed by `emulation/work/netbridge.sock`, guest setup symlinks `/dev/ttyAMA5` to `/dev/ttyAMA1`, and the preload shim passes `/dev/ttyAMA5` through instead of opening `/dev/null`.
|
||||
- In `fbdev` fallback mode, `emulation/stubs/spike3emu_stub.c` uses real Mesa surfaceless/pbuffer rendering when possible, supplies fake-but-consistent EGL/GBM/EGLImage objects where needed, treats final DRM framebuffer/page-flip calls as successful, and writes visible frames to `/dev/fb0`.
|
||||
- In fallback mode, the shim now also interposes enough GLES object setup for boot-display and SPIKE menu to get past their framebuffer-completeness path: synthetic GLES identity/limits, shader/program calls, framebuffer/renderbuffer/texture calls, and `/sys/class/gpio/*` fake opens/writes.
|
||||
- Injects a generated `/etc/asound.conf` that maps ALSA `default` to the null PCM, clearing the prior missing-default-PCM abort inside the emulator copy.
|
||||
- Injects `/usr/local/spike-emu/bin/fb-message`, compiled from `emulation/guest-tools/fb-message.c`, so target exits can paint a useful diagnostic screen to `/dev/fb0`.
|
||||
- Starts a guest control agent that logs virtio-serial dashboard events to `/run/spike-emu/events.log` and `/connectivity/dump/log/spike-emu/control-agent.log`.
|
||||
- Creates a cache manifest at `emulation/work/rootfs.manifest` and cache status at `emulation/work/rootfs-cache.status`. `SPIKE3_PREPARE_MODE=auto` reuses `emulation/work/rootfs.ext4` when inputs match.
|
||||
|
||||
The dashboard:
|
||||
|
||||
- Links to noVNC and the existing conagent backend emulator.
|
||||
- Reads host logs from `emulation/work/logs/`.
|
||||
- Reports rootfs cache status and requested display backend from `/api/status`.
|
||||
- Sends virtual switch/button events as JSON to `emulation/work/control.sock` when QEMU is running. Verified APIs are `POST /api/event` and `POST /api/switch/<name>`.
|
||||
|
||||
## Verification Status
|
||||
@@ -108,7 +149,7 @@ Commands run from the workspace root:
|
||||
```sh
|
||||
docker compose -f emulation/compose.yaml build spike3-emu
|
||||
docker compose -f emulation/compose.yaml run --rm --entrypoint /workspace/emulation/scripts/prepare-rootfs-image.sh spike3-emu
|
||||
SPIKE3_SKIP_PREPARE=1 SPIKE3_AUTOSTART=boot-display docker compose -f emulation/compose.yaml up --force-recreate
|
||||
SPIKE3_PREPARE_MODE=never SPIKE3_AUTOSTART=boot-display docker compose -f emulation/compose.yaml up --force-recreate
|
||||
```
|
||||
|
||||
Confirmed results:
|
||||
@@ -123,16 +164,30 @@ Confirmed results:
|
||||
- QEMU `virtio-gpu-pci,max_outputs=2` exposes two DRM connectors. The second connector is disconnected by QEMU VNC, so the LD_PRELOAD shim now snapshots the first connected connector and presents connector id `39` as connected with mode `1360x768`. This removes SPIKE menu's repeated `failed to get DRM connector for display_number 1` failure.
|
||||
- Adding `libegl1:arm64`, `libegl-mesa0:arm64`, `libgles2:arm64`, and related ARM64 Mesa/GLVND packages to the Docker image removes the prior Mesa loader fatal `did not find extension DRI_Mesa version 1`.
|
||||
- Adding `mesa-vulkan-drivers:arm64` to the Docker image makes the ARM64 lavapipe ICD and `libvulkan_lvp.so` available to the generated rootfs overlay.
|
||||
- `emulation/scripts/capture-vnc.py` captures QEMU VNC frames to PPM; captures from the current `boot-display` runs were still all black (`1360x768`, RGB extrema all zero).
|
||||
- `emulation/scripts/capture-vnc.py` captures QEMU VNC frames to PPM; earlier captures before the `fbdev` fallback were all black (`1360x768`, RGB extrema all zero).
|
||||
- Cache verification on 2026-07-07: after one rebuild, a second `docker compose -f emulation/compose.yaml run --rm --entrypoint /workspace/emulation/scripts/prepare-rootfs-image.sh spike3-emu` logged `rootfs cache hit; reusing /workspace/emulation/work/rootfs.ext4`. Host `stat` showed `emulation/work/rootfs.ext4` mtime unchanged at `1783433532`.
|
||||
- `boot-display` verification on 2026-07-07: `docker compose -f emulation/compose.yaml up --force-recreate` reached `SPIKE3 emulator init complete; autostart=boot-display`; container-side `capture-vnc.py` wrote `emulation/work/boot-display-fbdev.ppm` with RGB extrema `[(0, 255), (0, 255), (0, 227)]`.
|
||||
- `spike-menu` verification on 2026-07-07: `docker compose -f emulation/compose.yaml run --name spike3-menu-test --rm --service-ports -e SPIKE3_AUTOSTART=spike-menu spike3-emu` wrote `emulation/work/spike-menu-fbdev.ppm` with RGB extrema `[(0, 255), (0, 255), (0, 203)]`.
|
||||
- `game` verification on 2026-07-07: `docker compose -f emulation/compose.yaml run --name spike3-game-test --rm --service-ports -e SPIKE3_AUTOSTART=game spike3-emu` wrote `emulation/work/game-fbdev.ppm` with RGB extrema `[(0, 255), (0, 255), (0, 203)]`.
|
||||
- Diagnostic-screen verification on 2026-07-07: after adding `fb-message` and the generated ALSA null default, `docker compose -f emulation/compose.yaml run --name spike3-game-diag --rm --service-ports -e SPIKE3_AUTOSTART=game spike3-emu` moved past the prior `Unknown PCM default` / `snd_pcm_prepare` abort. It then hit shader/video failures and a segfault; `capture-vnc.py` wrote `emulation/work/game-diagnostic.ppm`, converted to `emulation/work/game-diagnostic.png`, showing `SHADER LIBRARY INIT FAILURE` and `LOG=GAME.STDOUT.LOG`.
|
||||
- Fallback GLES/framebuffer verification on 2026-07-07: after adding synthetic GLES framebuffer/renderbuffer/texture wrappers, `boot-display` no longer logs `failed framebuffer check for created target buffer`, `failed to create framebuffer`, or `create_window RETRY`. `capture-vnc.py` wrote `emulation/work/boot-display-fbo-wrap.ppm` with RGB extrema `[(0, 255), (0, 255), (0, 42)]`.
|
||||
- SPIKE menu verification on 2026-07-07: after adding `/sys/class/gpio/*` to the fake path set, the prior repeated `invalid GPIO 25` / `failed to set GPIO 25` spam disappeared. `capture-vnc.py` wrote `emulation/work/spike-menu-gpio-wrap.ppm` with RGB extrema `[(0, 255), (0, 255), (0, 134)]`. The deterministic blockers observed afterward are hardware-facing: `OpenOCD init failed`, `failed to initialize bridge`, TAS/MAX amp register verify failures, and missing real node hardware.
|
||||
- Main game verification on 2026-07-07: `capture-vnc.py` wrote `emulation/work/game-gl-limits.ppm` with RGB extrema `[(0, 255), (0, 255), (0, 255)]`. The old framebuffer retry is not the blocker, but Godot/Radium still reports `Too many varyings used in shader (1 used, maximum supported is 0)`, followed by HEVC/V4L2 setup errors and `Segmentation fault`.
|
||||
- Rootfs cache recheck on 2026-07-07 after the final shim rebuild: a second one-shot prepare logged `rootfs cache hit; reusing /workspace/emulation/work/rootfs.ext4`.
|
||||
- Mesa dependency verification on 2026-07-07: adding missing ARM64 loader dependencies (`libX11-xcb`, `libwayland-client`, `libxcb-dri2`, `libxcb-present`, `libxcb-randr`, `libxcb-sync`, `libxcb-xfixes`, and `libxshmfence`) changed `display-probe` from `EGL_BAD_PARAMETER` to a successful native GBM/EGL initialization. The probe log reports `EGL_MESA_platform_gbm`, `EGL_MESA_platform_surfaceless`, successful `eglInitialize`, and Mesa DRI loading from `/usr/local/spike-emu/mesa-dri/kms_swrast_dri.so`.
|
||||
- Native path verification on 2026-07-07: forcing `SPIKE3_DISPLAY_BACKEND=native` reaches `OpenGL ES 3.1 Mesa 22.3.6` / `renderer: softpipe`, but `/games/game` repeatedly logs `failed to make image from buffer object` and `failed to create framebuffer`; native is therefore not the default auto presentation path.
|
||||
- fbdev/surfaceless verification on 2026-07-07: forcing `SPIKE3_DISPLAY_BACKEND=fbdev` uses the real Mesa surfaceless display and an implicit pbuffer (`eglMakeCurrent ... draw=<pbuffer> read=<pbuffer>`). This clears the `eglChooseConfig num=0` retry path, but the target-created framebuffer remains incomplete. The serial log shows `failed framebuffer check for created target buffer`, `failed to create framebuffer`, and repeated `GL_INVALID_FRAMEBUFFER_OPERATION` from `glClear`, `glDrawArrays`, and `glDrawElements`. The capture `emulation/work/game-fbdev-implicit-pbuffer.ppm` is nonblank but still only shows the fallback color bands because no valid target swap/copy occurs.
|
||||
- Netbridge wiring verification on 2026-07-07: `python3 -m unittest discover -s analysis/netbridge-emulator`, Python bytecode compilation, and `bash -n` over the changed Docker/QEMU shell scripts all pass. The full `SPIKE3_NETBRIDGE_EMU=1` Docker boot has not yet been run, so guest acceptance of the emulated `/dev/ttyAMA5` path remains an open validation item.
|
||||
|
||||
Current blocker:
|
||||
Historical native-display blocker (superseded by the later Mesa dependency and fbdev work above):
|
||||
|
||||
- `/games/spike3/bin/boot_display` is reached and the old connector/EGL-loader fatals are cleared, but its EGL display creation still fails before it can allocate framebuffer BOs.
|
||||
- The current shim trace confirms `LD_PRELOAD` is loaded and `gbm_create_device(fd=3)` succeeds on the `kms_swrast`/`softpipe` default path, returning a non-null GBM device pointer. The subsequent `eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gbm_device, NULL)` returns `NULL` with `eglGetError() == 0x300c` (`EGL_BAD_PARAMETER`).
|
||||
- Shim fallbacks also return `NULL` with `EGL_BAD_PARAMETER` for `EGL_PLATFORM_SURFACELESS_MESA`, core `eglGetPlatformDisplay`, and `eglGetDisplay(NULL)` even with `EGL_PLATFORM=surfaceless`.
|
||||
- Forcing `MESA_LOADER_DRIVER_OVERRIDE=zink` and `GALLIUM_DRIVER=zink` with `VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.aarch64.json` makes `gbm_create_device` return `NULL`, so that is not the default path to leave enabled.
|
||||
- After EGL display creation fails, `boot_display` later reports shader compile errors and then loops on `failed to lock front buffer: gbm_buffer[0] is null (frame=0)`. This appears to be an uninitialized app framebuffer array after the failed window/context setup, not a direct `gbm_surface_lock_front_buffer` failure.
|
||||
- A nonblank SPIKE frame is not yet confirmed. The next display pass should either make the GBM EGL platform work with QEMU's non-virgl `virtio_gpu`, or bypass the app's EGL/GBM path with a targeted display interposer that supplies a fake EGL context plus framebuffer BOs or streams rendered/uploaded image data directly to the browser.
|
||||
- The implemented `fbdev` fallback bypasses this native EGL/GBM failure enough to produce nonblank QEMU VNC frames and let analysis proceed to the next deterministic target failure.
|
||||
- With fallback enabled, the next deterministic front-end blocker is no longer black VNC, missing ALSA default PCM, or zero GL varying limits. `spike-menu` and `game` proceed through real Mesa initialization and hardware-facing bridge/audio failures. The current main-game display blocker is the EGLImage/FBO attachment path: the target-created framebuffer is incomplete, producing `GL_INVALID_FRAMEBUFFER_OPERATION` and preventing a real game frame from being copied to `/dev/fb0`.
|
||||
|
||||
## Limitations and Next Steps
|
||||
|
||||
@@ -145,8 +200,7 @@ Confirmed current limitations:
|
||||
|
||||
Next implementation targets:
|
||||
|
||||
1. Fix the remaining `boot_display` EGL display creation failure (`EGL_BAD_PARAMETER` from `eglGetPlatformDisplayEXT` on the GBM device) so the app can allocate framebuffer BOs and produce a nonblank noVNC frame.
|
||||
2. Return to `spike-menu`; its connector blocker is cleared, but it still depends on the same GBM/EGL buffer path and also hits expected netbridge/OpenOCD hardware failures.
|
||||
3. Run `game` and capture the first deterministic hardware/display failure from serial logs and `/connectivity/dump/log/spike-emu/`.
|
||||
1. Fix or fake the EGLImage/FBO attachment path used for the target-created framebuffer under fbdev/surfaceless Mesa. The current evidence is `failed framebuffer check for created target buffer`, `failed to create framebuffer`, and Mesa `GL_INVALID_FRAMEBUFFER_OPERATION` errors after `eglMakeCurrent` succeeds with a real pbuffer.
|
||||
2. Run `SPIKE3_NETBRIDGE_EMU=1 SPIKE3_AUTOSTART=game docker compose -f emulation/compose.yaml up --build` and verify whether the bridge thread accepts the emulated `/dev/ttyAMA5` path. If OpenOCD still blocks startup before UART use, stub or bypass the OpenOCD programming step while preserving the UART protocol model.
|
||||
3. Investigate HEVC/V4L2 decode initialization errors after display setup is stable.
|
||||
4. Map dashboard switch events into a first SPIKE nodebus emulator, reusing recovered node command evidence from `main-game-behavior.md` and `topper-serial-key-emulator.md`.
|
||||
5. Add targeted stubs only for confirmed failing APIs or device paths.
|
||||
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
# Netbridge Reverse Engineering and Emulator
|
||||
|
||||
## Scope
|
||||
|
||||
Target artifacts:
|
||||
|
||||
- `/games/pokemon_pro/spike3/netbridge/netbridge.elf`
|
||||
- `/games/pokemon_pro/spike3/netbridge/spike3-swd.cfg`
|
||||
- `/games/pokemon_pro/spike3/netbridge/rp2040.cfg`
|
||||
- `/games/pokemon_pro/spike3/netbridge/version.txt`
|
||||
|
||||
Generated artifacts:
|
||||
|
||||
- `analysis/netbridge-emulator/netbridge_emu.py`
|
||||
- `analysis/netbridge-emulator/test_netbridge_emu.py`
|
||||
- `analysis/netbridge-emulator/README.md`
|
||||
|
||||
Static caveat: this page is based on static rootfs evidence and Ghidra decompilation of the RP2040 firmware. It has not been validated against live cabinet hardware or a logic-analyzer capture.
|
||||
|
||||
## Firmware Identity
|
||||
|
||||
Confirmed facts:
|
||||
|
||||
- `file games/pokemon_pro/spike3/netbridge/netbridge.elf` reports a 32-bit little-endian ARM EABI5 executable, statically linked, with debug info, not stripped.
|
||||
- SHA-256 of `/games/pokemon_pro/spike3/netbridge/netbridge.elf`: `05b7f7c329da79fb24078df69c8401c7a47be5c8a855b73fb937e052bce28d85`.
|
||||
- `/games/pokemon_pro/spike3/netbridge/version.txt` contains `0.98.4`.
|
||||
- Ghidra import path used for this pass: `/pokemon_spike3/netbridge.elf`.
|
||||
- Ghidra auto-detected language `ARM:LE:32:v8`.
|
||||
- `arm-none-eabi-readelf -l` maps executable flash at virtual address `0x10000000`, file offset `0x1000`, size `0x12b40`.
|
||||
- `arm-none-eabi-nm -n` shows `__flash_binary_start` at `0x10000000`, `__logical_binary_start` at `0x10000100`, and `__flash_binary_end` at `0x10013270`.
|
||||
|
||||
Interpretation: the image is RP2040/Pico-SDK style firmware loaded at XIP flash base `0x10000000`. The host-side game programs or verifies it through OpenOCD before talking to it over UART.
|
||||
|
||||
## Host Programming Path
|
||||
|
||||
The game and SPIKE menu binaries both contain these strings:
|
||||
|
||||
```text
|
||||
/dev/ttyAMA2
|
||||
/dev/ttyAMA5
|
||||
netbridge image okay, CRC32=0x%08x
|
||||
netbridge image CRC mismatch, write new image
|
||||
openocd -d0 -f %s -f %s -c "program %s preverify verify reset exit"
|
||||
failed to init bridge thread
|
||||
/games/spike3/netbridge/spike3-swd.cfg
|
||||
/games/spike3/netbridge/rp2040.cfg
|
||||
/games/spike3/netbridge/netbridge.elf
|
||||
```
|
||||
|
||||
String offsets:
|
||||
|
||||
- `/games/pokemon_pro/game`: `/dev/ttyAMA2` at `0x25e9468`, `/dev/ttyAMA5` at `0x25e9478`, OpenOCD command format at `0x25e9998`.
|
||||
- `/games/pokemon_pro/spike3/spike_menu/game`: `/dev/ttyAMA2` at `0x3845a8`, `/dev/ttyAMA5` at `0x3845b8`, OpenOCD command format at `0x384ae8`.
|
||||
|
||||
`spike3-swd.cfg` configures OpenOCD `bcm2835gpio` SWD:
|
||||
|
||||
- SWCLK: Raspberry Pi GPIO 11.
|
||||
- SWDIO: Raspberry Pi GPIO 10.
|
||||
- Adapter speed: 1000 kHz.
|
||||
|
||||
`rp2040.cfg` is the normal RP2040 dual-core OpenOCD target config with flash bank at `0x10000000`.
|
||||
|
||||
Inference: startup tries to verify the RP2040 flash image, rewrites it through Pi GPIO bit-banged SWD if the CRC does not match, resets the RP2040, then opens a Pi UART for bridge messages.
|
||||
|
||||
## Main Firmware Loop
|
||||
|
||||
Ghidra decompilation of `main` shows this initialization order:
|
||||
|
||||
1. Enable watchdog with 1000 ms timeout.
|
||||
2. Initialize ADC/version sensing.
|
||||
3. Initialize nodebus.
|
||||
4. Initialize host UART.
|
||||
5. Initialize status LED, backbox light, power-fail handling, LCD, LCD backlight, I2S, power distribution, WS2812, and DMD.
|
||||
6. Send reset flags to host with command `0x22`.
|
||||
7. Send version to host with command `0x20`.
|
||||
8. Loop forever updating watchdog, status, backbox light, WS2812, LCD, power distribution, ADC, host command handling, and LCD again.
|
||||
|
||||
The decompiled `HOST_SendVersion` sends four bytes:
|
||||
|
||||
```text
|
||||
00 62 04 <adc_version>
|
||||
```
|
||||
|
||||
This matches version `0.98.4`; the fourth byte is the ADC-board/version-ladder result or `0xff` if unavailable.
|
||||
|
||||
## Pi-Facing Host UART Protocol
|
||||
|
||||
`HOSTUART_Init` configures:
|
||||
|
||||
- RP2040 GPIO 16 and GPIO 17 to function 2.
|
||||
- UART base `0x40034000`, consistent with RP2040 `uart0`.
|
||||
- Baud: `2000000`.
|
||||
- Format: 8 data bits, 1 stop bit, no parity per decompiled arguments.
|
||||
- RX/TX FIFOs: host RX `0x1000` bytes, host TX `0x400` bytes.
|
||||
|
||||
`HOSTUART_SendMessage` and `HOSTUART_GetMessage` implement byte-stuffed frames:
|
||||
|
||||
```text
|
||||
0x02 start
|
||||
command escaped if 0x02, 0x03, or 0x1b
|
||||
sequence escaped if needed
|
||||
payload bytes escaped if needed
|
||||
checksum escaped if needed
|
||||
0x03 end
|
||||
```
|
||||
|
||||
Escaping:
|
||||
|
||||
- Literal `0x02`, `0x03`, or `0x1b` is sent as `0x1b` followed by `value | 0x80`.
|
||||
- RX reverses escaping by masking the byte after `0x1b` with `0x7f`.
|
||||
|
||||
Checksum:
|
||||
|
||||
- The checksum byte makes `sum(command, sequence, payload..., checksum) & 0xff == 0`.
|
||||
- Equivalently, `checksum = -sum(command, sequence, payload) & 0xff`.
|
||||
|
||||
## Host Command Map
|
||||
|
||||
Ghidra decompilation of `HOST_Update` gives this first-pass command map:
|
||||
|
||||
| Command | Meaning | Response |
|
||||
| --- | --- | --- |
|
||||
| `0x10..0x1f` | Power-distribution passthrough on SPIKE 3; legacy voltage/frequency/power commands on non-SPIKE 3 path | Usually forwarded or returns one-byte/few-byte synthetic hardware state |
|
||||
| `0x20` | Netbridge version | `00 62 04 <adc_version>` |
|
||||
| `0x21` | Backbox light set/get | Optional one-byte brightness response |
|
||||
| `0x22` | Reset flags | One byte |
|
||||
| `0x23` | Bridge status, then clear | 4-byte little-endian status |
|
||||
| `0x24` | VBB millivolts | 4-byte little-endian value |
|
||||
| `0x25` | Nodebus poll/discovery | One byte node address, or `0` |
|
||||
| `0x27` | LCD reset/status/write/backlight subcommands | Status response for query paths |
|
||||
| `0x28` | DMD status/test/write | Status response for query path |
|
||||
| `0x29` | WS2812 count/debug setup | No direct response in the decoded path |
|
||||
| `0x2a` | WS2812 direct data path | No direct response in the decoded path |
|
||||
| `0x2b` | WS2812 pattern path | No direct response in the decoded path |
|
||||
| `0x2c` | I2S enable | No direct response |
|
||||
| `0x2f` | WS2812 node LED path | No direct response in the decoded path |
|
||||
| `0x30` | Netbridge firmware CRC | 4-byte CRC32 |
|
||||
| `0x40` | Extended bridge command namespace | Subcommand-dependent response |
|
||||
| `0x41` | Direct nodebus request/response path | Node response bytes plus bridge status |
|
||||
|
||||
Extended command `0x40` subcommands currently identified:
|
||||
|
||||
| Subcommand | Meaning |
|
||||
| --- | --- |
|
||||
| `0x00` | Nodebus poll/discovery |
|
||||
| `0x03` | Version bytes `00 62 04` |
|
||||
| `0x04` | Enter/exit node break mode |
|
||||
| `0x05` | Bridge status, then clear |
|
||||
| `0x06` | Set node response-time timeout in milliseconds |
|
||||
| `0x09` | Backbox light helper |
|
||||
| `0x0a` | Backbox light get helper |
|
||||
| `0x0c` | Deliberate infinite loop / hang path |
|
||||
| `0x0d` | Four-slot seeded hash/mixing helper |
|
||||
|
||||
The `0x30` CRC is computed in firmware as:
|
||||
|
||||
```c
|
||||
CRC32(0, &__logical_binary_start, 0x12a40)
|
||||
```
|
||||
|
||||
For this ELF, `__logical_binary_start` maps to file offset `0x1100`. A Python `binascii.crc32` over `games/pokemon_pro/spike3/netbridge/netbridge.elf[0x1100:0x1100+0x12a40]` gives `0x357f5327`.
|
||||
|
||||
## Nodebus Layer
|
||||
|
||||
`NODE_Init` configures a mixed PIO/hardware-UART nodebus:
|
||||
|
||||
- PIO block base `0x50300000`, consistent with RP2040 PIO1.
|
||||
- PIO state machine 0 uses `uart_tx_txen_program`.
|
||||
- GPIO 20 and GPIO 19 are assigned PIO function 7 around the transmit/TX-enable path.
|
||||
- GPIO 21 is assigned UART function 2 around the receive path.
|
||||
- Hardware UART base `0x40038000`, consistent with RP2040 `uart1`.
|
||||
- Decompiled baud literal is `0x70800`, decimal `460800`.
|
||||
|
||||
`NODEUART_WriteAddress(address)` resets node RX state and sends `address | 0x180` into the PIO TX FIFO. `NODEUART_Write(byte)` subtracts the byte from `nodeTxCs` and queues it. `NODEUART_SendEnd(count)` sets the expected receive count and response timing window.
|
||||
|
||||
`NODEBUS_Poll` performs a binary-search-style poll over node addresses up to `0x3f`, using `NODEUART_WritePoll(address)`. It returns the discovered address or `0`.
|
||||
|
||||
Direct host command `0x41` has special handling in `HOSTUART_GetMessage`: as the host frame is parsed, the payload is echoed to the physical nodebus. The payload shape inferred from the decompile and the earlier game-side node wrapper is:
|
||||
|
||||
```text
|
||||
byte 0: node address, high bit accepted by higher layers
|
||||
byte 1: node argument count
|
||||
byte 2..N: node command and arguments
|
||||
last: expected response length
|
||||
```
|
||||
|
||||
The bridge then reads node response bytes and sends host command `0x41` back with the node response followed by bridge status. This is the lower layer used by the previously documented Magikarp/topper commands such as `0xfc`.
|
||||
|
||||
## Other Hardware Functions
|
||||
|
||||
Confirmed from Ghidra decompilation:
|
||||
|
||||
- ADC/version sensing uses GPIO 26 and GPIO 29 as ADC inputs.
|
||||
- LCD helper initializes GPIO 3, 6, and 7.
|
||||
- LCD backlight uses GPIO 14 PWM plus GPIO 15 enable/control.
|
||||
- Backbox light uses GPIO 23 PWM.
|
||||
- Status output uses GPIO 25.
|
||||
- Power-fail output/input uses GPIO 24.
|
||||
- Power-distribution UART uses PIO1 state machines 2 and 3, with GPIO 27 TX and GPIO 28 RX.
|
||||
- WS2812 output uses PIO0 and three configured output pin slots from the firmware's `pins` table.
|
||||
- DMD work is launched on RP2040 core 1.
|
||||
|
||||
Open validation:
|
||||
|
||||
- Exact board connector routing for each RP2040 GPIO.
|
||||
- Nodebus electrical level, half-duplex direction details, and break timing on real hardware.
|
||||
- Exact meaning of every `bridgeStatus` bit.
|
||||
- Which of `/dev/ttyAMA2` and `/dev/ttyAMA5` is the Pi-facing host UART on the real image boot path.
|
||||
|
||||
## Emulator
|
||||
|
||||
`analysis/netbridge-emulator/netbridge_emu.py` implements a first host-side recreation in Python.
|
||||
|
||||
It currently provides:
|
||||
|
||||
- STX/ETX/ESC host frame codec.
|
||||
- Host checksum encode/decode.
|
||||
- Commands `0x20`, `0x22`, `0x23`, `0x24`, `0x25`, `0x27`, `0x28`, `0x30`, `0x40`, and `0x41`.
|
||||
- Inert ACK/no-response handling for early LCD/DMD/WS2812/I2S paths.
|
||||
- Firmware CRC response `0x357f5327`.
|
||||
- PTY, serial-device, and Unix-socket serving modes for QEMU or host-side serial plumbing.
|
||||
- Existing-node stub at address `0x0a` with Magikarp/topper-oriented commands `0xf2`, `0xfc`, `0xfd`, `0xfe`, and `0xff`.
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
python3 analysis/netbridge-emulator/netbridge_emu.py --pty
|
||||
```
|
||||
|
||||
Run as a QEMU socket backend:
|
||||
|
||||
```sh
|
||||
python3 analysis/netbridge-emulator/netbridge_emu.py --unix-socket emulation/work/netbridge.sock
|
||||
```
|
||||
|
||||
Test:
|
||||
|
||||
```sh
|
||||
python3 -m unittest discover -s analysis/netbridge-emulator
|
||||
```
|
||||
|
||||
Verification performed:
|
||||
|
||||
```text
|
||||
python3 -m unittest discover -s analysis/netbridge-emulator
|
||||
....
|
||||
Ran 4 tests in 0.000s
|
||||
OK
|
||||
|
||||
python3 -m py_compile analysis/netbridge-emulator/netbridge_emu.py analysis/netbridge-emulator/test_netbridge_emu.py
|
||||
```
|
||||
|
||||
## Emulation Strategy
|
||||
|
||||
For QEMU:
|
||||
|
||||
1. Set `SPIKE3_NETBRIDGE_EMU=1` for the Docker/QEMU scaffold.
|
||||
2. The container starts `netbridge_emu.py --unix-socket /workspace/emulation/work/netbridge.sock`.
|
||||
3. QEMU attaches that socket as a second PL011 serial port, which appears as guest `/dev/ttyAMA1`.
|
||||
4. Guest setup symlinks `/dev/ttyAMA5` to `/dev/ttyAMA1`, because current preload logs show the target opening `/dev/ttyAMA5`.
|
||||
5. `launch-target.sh` exports `SPIKE3_REAL_NETBRIDGE_SERIAL=1`, causing the preload shim to stop faking `/dev/ttyAMA5`.
|
||||
6. If the game still tries to run OpenOCD before the UART bridge thread is useful, either allow the CRC/status path to bypass programming or stub OpenOCD success until the UART bridge thread opens.
|
||||
7. Route dashboard switch events into the emulator's node model, not directly into the game.
|
||||
|
||||
Docker command:
|
||||
|
||||
```sh
|
||||
SPIKE3_NETBRIDGE_EMU=1 SPIKE3_AUTOSTART=game docker compose -f emulation/compose.yaml up --build
|
||||
```
|
||||
|
||||
Current validation boundary: the Python emulator tests, shell syntax checks, and QEMU wiring edits were verified locally, but the full Docker boot path with `SPIKE3_NETBRIDGE_EMU=1` has not yet been run to confirm the game's bridge thread accepts the emulated serial device.
|
||||
|
||||
For a microcontroller recreation:
|
||||
|
||||
- RP2040 is the best exact physical target because the original uses RP2040 PIO for node TX/TX-enable, WS2812, and power-distribution UART.
|
||||
- ESP32 can still emulate the Pi-facing protocol if it only replaces the bridge at the host UART layer, or if its UART/RMT/peripheral timing can satisfy the nodebus physical layer.
|
||||
- The minimum viable firmware only needs host UART framing, command responses, and a nodebus model that returns `payload || checksum || status` for command `0x41`.
|
||||
|
||||
## Pico Firmware Recreation
|
||||
|
||||
`analysis/netbridge-pico-firmware/` contains a Pico SDK recreation of the same Pi-facing protocol layer.
|
||||
|
||||
Files:
|
||||
|
||||
- `CMakeLists.txt`
|
||||
- `include/netbridge_protocol.h`
|
||||
- `src/netbridge_protocol.c`
|
||||
- `src/netbridge_model.c`
|
||||
- `src/pico_main.c`
|
||||
- `tests/host_protocol_test.c`
|
||||
- `README.md`
|
||||
|
||||
Implemented behavior:
|
||||
|
||||
- UART0 on RP2040 GPIO 16/17 at 2,000,000 baud.
|
||||
- Same STX/ETX/ESC host framing and checksum as the original netbridge.
|
||||
- Same core command stubs as the Python emulator.
|
||||
- Firmware CRC response `0x357f5327`.
|
||||
- Stub nodebus device at address `0x0a`.
|
||||
- Pico GPIO 25 heartbeat LED.
|
||||
|
||||
Portable host-core test:
|
||||
|
||||
```sh
|
||||
cmake -S analysis/netbridge-pico-firmware -B /tmp/spike3-netbridge-host
|
||||
cmake --build /tmp/spike3-netbridge-host --target netbridge_host_test
|
||||
/tmp/spike3-netbridge-host/netbridge_host_test
|
||||
```
|
||||
|
||||
Pico UF2 build, once the Pico SDK is available:
|
||||
|
||||
```sh
|
||||
export PICO_SDK_PATH=/path/to/pico-sdk
|
||||
cmake -S analysis/netbridge-pico-firmware -B /tmp/spike3-netbridge-pico
|
||||
cmake --build /tmp/spike3-netbridge-pico --target spike3_netbridge_pico
|
||||
```
|
||||
|
||||
Verification performed for this pass:
|
||||
|
||||
```text
|
||||
cmake -S analysis/netbridge-pico-firmware -B /tmp/spike3-netbridge-host
|
||||
cmake --build /tmp/spike3-netbridge-host --target netbridge_host_test
|
||||
/tmp/spike3-netbridge-host/netbridge_host_test
|
||||
netbridge host protocol tests passed
|
||||
```
|
||||
|
||||
The UF2 was not built in this pass because `PICO_SDK_PATH` is not configured in the workspace environment.
|
||||
|
||||
## Confidence
|
||||
|
||||
High confidence:
|
||||
|
||||
- RP2040 firmware identity, version, OpenOCD programming path, and SWD GPIO mapping.
|
||||
- Pi-facing UART frame format and checksum.
|
||||
- Host command meanings for version, status, CRC, node poll, and direct node request.
|
||||
- Host UART baud and RP2040 GPIO 16/17 assignment.
|
||||
- Nodebus use of PIO plus UART1 and command `0x41` as the node request path.
|
||||
|
||||
Moderate confidence:
|
||||
|
||||
- The Python emulator or Pico firmware recreation is sufficient to clear early bridge-thread and netbridge CRC paths once connected to the correct guest UART.
|
||||
- Default zero/success responses for LCD/DMD/WS2812/I2S are enough for early game boot.
|
||||
|
||||
Low confidence until hardware-tested:
|
||||
|
||||
- Physical nodebus timings, connector pins, and voltage levels.
|
||||
- Full power-distribution behavior.
|
||||
- Complete `bridgeStatus` bit semantics.
|
||||
@@ -1,5 +1,7 @@
|
||||
# Next Analysis Targets
|
||||
|
||||
The current derived-data inventory and confidence boundaries are summarized in `analysis-evidence-map.md`. This page tracks only the work that remains or still needs stronger validation.
|
||||
|
||||
## Priority 1: Main Game Binary
|
||||
|
||||
Status: first-pass static strings, dependency review, targeted Ghidra decompilation, topper serial-key recovery, a MicroPython emulator artifact, and a Docker/QEMU emulation scaffold are complete; see `main-game-behavior.md`, `topper-serial-key-emulator.md`, and `docker-qemu-emulation.md`.
|
||||
@@ -24,7 +26,7 @@ Suggested first passes:
|
||||
|
||||
Follow-up passes:
|
||||
|
||||
- Continue from `emulation/`: QEMU now boots the generated rootfs, exposes noVNC/dashboard, loads `virtio_gpu`, creates `/dev/dri/card0` plus `/dev/dri/renderD128`, and reaches `/games/spike3/bin/boot_display`. `/dev/dri/card1` is shimmed to card0, SPIKE menu's disconnected second-connector failure is patched in the LD_PRELOAD shim, the Mesa/GLVND overlay clears the prior `DRI_Mesa` loader fatal, and the Docker image now includes ARM64 Mesa Vulkan/lavapipe support. The current blocker is earlier than the repeated `gbm_buffer[0] is null` symptom: `eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gbm_device, NULL)` returns `NULL` with `EGL_BAD_PARAMETER` after `gbm_create_device(fd=3)` succeeds. The next pass should focus on making the QEMU non-virgl `virtio_gpu` work with the app's GBM EGL path or implementing a targeted EGL/GBM display interposer before moving deeper into `spike-menu` and `/games/game`.
|
||||
- Continue from `emulation/`: QEMU now boots the generated rootfs, exposes noVNC/dashboard, uses cached rootfs preparation, loads `virtio_gpu`, creates `/dev/dri/card0` plus `/dev/dri/renderD128`, and reaches `boot-display`, `spike-menu`, and `/games/game`. The generated rootfs includes a display probe, `fbdev` fallback interposer, Mesa surfaceless/pbuffer support, synthetic GLES/GBM/EGLImage fallbacks, GPIO sysfs stubs, framebuffer diagnostic renderer, and null ALSA default. Verified captures include nonblank `emulation/work/boot-display-fbo-wrap.ppm`, `emulation/work/spike-menu-gpio-wrap.ppm`, `emulation/work/game-gl-limits.ppm`, `emulation/work/game-fbdev-real-egl.png`, and the earlier diagnostic screen in `emulation/work/game-diagnostic.png`. The prior black-screen, missing-ALSA-default, boot-display framebuffer retry, SPIKE-menu GPIO 25 spam, Mesa loader dependency, and zero-varying shader blockers are cleared. The current display blocker is the main game's target-created EGLImage/FBO path: `eglMakeCurrent` succeeds with a real pbuffer, but the framebuffer remains incomplete and Mesa reports `GL_INVALID_FRAMEBUFFER_OPERATION`, so VNC still shows fallback color bands instead of a game frame. Other current blockers are TAS/MAX amp register verify reads and bridge/netbridge startup failures (`OpenOCD init failed`, `netbridge image CRC mismatch`, `failed to init bridge thread`). The next pass should fix or fake the FBO attachment path, then decide which bridge/netbridge and audio paths need modeling versus inert stubs.
|
||||
- Validate `analysis/topper-emulator/topper_emulator.py` on live hardware or a nodebus capture.
|
||||
- Recover the exact Magikarp topper `IDENTITY_BOARD_PART_ID` expected by command `0xfe`.
|
||||
- Confirm physical UART pins, baud, voltage level, and any netbridge timing/break behavior required for a MicroPython node.
|
||||
@@ -87,7 +89,7 @@ Follow-up passes:
|
||||
|
||||
## Priority 4: Netbridge Debug Path
|
||||
|
||||
Status: first-pass strings and ELF program-header review complete; see `package-image-firmware.md`.
|
||||
Status: first-pass strings, ELF program-header review, targeted Ghidra decompilation, host protocol recovery, a Python host-side emulator scaffold, Docker/QEMU optional serial wiring, and a Pico SDK firmware recreation are complete; see `package-image-firmware.md`, `netbridge-re.md`, and `docker-qemu-emulation.md`.
|
||||
|
||||
Targets:
|
||||
|
||||
@@ -105,6 +107,18 @@ Findings:
|
||||
|
||||
- `netbridge.elf` is a 32-bit ARM static executable with code mapped at `0x10000000` and RAM at `0x20000000`/`0x20040000`.
|
||||
- Strings include `uart_node.c`, `NODEBUS_Poll`, `NODEUART_Write`, `UART_NodeRxIrq`, and `WS2812_NodeSetLED`.
|
||||
- Ghidra decompilation identifies the Pi-facing host UART as 2,000,000 baud STX/ETX/ESC framed messages on RP2040 GPIO 16/17, with `0x30` returning firmware CRC `0x357f5327` and `0x41` carrying direct nodebus requests.
|
||||
- `analysis/netbridge-emulator/netbridge_emu.py` implements the recovered host frame codec, core bridge commands, CRC response, PTY serving mode, and a stub node at address `0x0a`.
|
||||
- `analysis/netbridge-pico-firmware/` implements the same recovered protocol as a Pico SDK firmware recreation with a portable host-core test.
|
||||
- Docker/QEMU now has an optional `SPIKE3_NETBRIDGE_EMU=1` path that starts the Python emulator on `emulation/work/netbridge.sock`, attaches it as a second PL011 serial, maps guest `/dev/ttyAMA5` to the real serial, and lets the preload shim pass that path through.
|
||||
|
||||
Follow-up passes:
|
||||
|
||||
- Run the full Docker path with `SPIKE3_NETBRIDGE_EMU=1` and verify whether the game bridge thread accepts the emulated `/dev/ttyAMA5` device.
|
||||
- Decide whether the OpenOCD programming path should be allowed, replaced with a success stub, or bypassed once CRC command `0x30` is reachable.
|
||||
- Build a Pico UF2 after configuring `PICO_SDK_PATH`, then bench-test it against a host serial harness before connecting it to target hardware.
|
||||
- Validate nodebus timing, break behavior, voltage level, and connector routing on live hardware or a logic-analyzer capture.
|
||||
- Expand the node model beyond the Magikarp/topper-oriented commands once the game requests additional playfield nodes.
|
||||
|
||||
## Priority 5: Node Firmware
|
||||
|
||||
|
||||
@@ -103,6 +103,8 @@ OpenOCD configs already documented in `applications-and-services.md`:
|
||||
- `spike3-swd.cfg` maps SWCLK to Raspberry Pi GPIO 11 and SWDIO to GPIO 10.
|
||||
- `rp2040.cfg` is present for RP2040 target handling.
|
||||
|
||||
Deeper Ghidra-backed netbridge protocol notes and the host-side emulator scaffold are now documented in `netbridge-re.md`.
|
||||
|
||||
## Node Firmware
|
||||
|
||||
The 17 top-level node firmware HEX files under `/games/pokemon_pro` are Intel HEX style data payloads. All parsed data ranges begin at `0x1000`, consistent with Cortex-M images that leave a bootloader or vector-reserved area below that address.
|
||||
|
||||
+235
-14
@@ -10,6 +10,7 @@ from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OUT = ROOT / "viewer.html"
|
||||
SOUND_INDEX_OUT = ROOT / "sound-index.json"
|
||||
|
||||
|
||||
def read_text(path: Path) -> str:
|
||||
@@ -23,6 +24,23 @@ def title_for(path: Path, text: str) -> str:
|
||||
return path.stem.replace("-", " ").replace("_", " ").title()
|
||||
|
||||
|
||||
def content_group_for(path: Path) -> str:
|
||||
"""Separate narrative research from generated catalogs and galleries."""
|
||||
name = path.name
|
||||
if path.suffix != ".md":
|
||||
return "source"
|
||||
if name.startswith("sound-best-named-gallery-") or name in {
|
||||
"sound-asr-catalog-labels.md",
|
||||
"sound-asr-draft-labels.md",
|
||||
"sound-asr-review-candidates.md",
|
||||
"sound-pokedex-voice.md",
|
||||
}:
|
||||
return "voice"
|
||||
if re.match(r"^(asset-.+-gallery|radium-section3-bitmap-gallery|sound-gallery)-", name):
|
||||
return "media"
|
||||
return "wiki"
|
||||
|
||||
|
||||
def page_order() -> list[str]:
|
||||
readme = read_text(ROOT / "README.md")
|
||||
ordered = ["README.md"]
|
||||
@@ -53,12 +71,48 @@ def build_pages() -> list[dict[str, str]]:
|
||||
"path": name,
|
||||
"title": title_for(path, text),
|
||||
"kind": kind,
|
||||
"group": content_group_for(path),
|
||||
"text": text,
|
||||
}
|
||||
)
|
||||
return pages
|
||||
|
||||
|
||||
def parse_directive_attrs(raw: str) -> dict[str, str]:
|
||||
return dict(re.findall(r'([A-Za-z0-9_-]+)="([^"]*)"', raw))
|
||||
|
||||
|
||||
def build_sound_index() -> dict[str, object]:
|
||||
"""Build one canonical entry for every best-named main/menu sound."""
|
||||
sounds: list[dict[str, str]] = []
|
||||
source_pages: list[str] = []
|
||||
seen_sources: set[str] = set()
|
||||
for path in sorted(ROOT.glob("sound-best-named-gallery-*.md")):
|
||||
source_pages.append(path.name)
|
||||
for match in re.finditer(r"^::audio\{(.*)\}\s*$", read_text(path), re.MULTILINE):
|
||||
attrs = parse_directive_attrs(match.group(1))
|
||||
src = attrs.get("src", "")
|
||||
if not src or src in seen_sources:
|
||||
continue
|
||||
seen_sources.add(src)
|
||||
sounds.append(
|
||||
{
|
||||
"id": f"sound-{len(sounds) + 1:04d}",
|
||||
"src": src,
|
||||
"filename": Path(src).name,
|
||||
"title": attrs.get("title", Path(src).name),
|
||||
"meta": attrs.get("meta", ""),
|
||||
"source_page": path.name,
|
||||
}
|
||||
)
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"generated_from": source_pages,
|
||||
"count": len(sounds),
|
||||
"sounds": sounds,
|
||||
}
|
||||
|
||||
|
||||
HTML_TEMPLATE = r"""<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -161,7 +215,7 @@ HTML_TEMPLATE = r"""<!doctype html>
|
||||
|
||||
.sidebar-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
margin: 14px 0 16px;
|
||||
}
|
||||
@@ -367,6 +421,22 @@ HTML_TEMPLATE = r"""<!doctype html>
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.search-controls {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 116px;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.search-controls select {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: var(--panel);
|
||||
color: var(--ink);
|
||||
padding: 0 8px;
|
||||
min-height: 38px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-results {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
@@ -384,17 +454,79 @@ HTML_TEMPLATE = r"""<!doctype html>
|
||||
|
||||
.search-results.open { display: block; }
|
||||
|
||||
.search-results a {
|
||||
.search-results > a {
|
||||
display: block;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.search-results a:last-child { border-bottom: 0; }
|
||||
.search-results a:hover { background: var(--panel-soft); text-decoration: none; }
|
||||
.search-results > a:last-child { border-bottom: 0; }
|
||||
.search-results > a:hover { background: var(--panel-soft); text-decoration: none; }
|
||||
.search-results small { display: block; color: var(--muted); margin-top: 2px; }
|
||||
|
||||
.sound-search-summary {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
border-bottom: 1px solid var(--line);
|
||||
background: #fff;
|
||||
color: var(--muted);
|
||||
padding: 9px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.sound-search-summary strong { color: var(--ink); }
|
||||
|
||||
.sound-result {
|
||||
border-bottom: 1px solid var(--line);
|
||||
padding: 11px 12px 12px;
|
||||
}
|
||||
|
||||
.sound-result:last-child { border-bottom: 0; }
|
||||
|
||||
.sound-result > strong {
|
||||
display: block;
|
||||
line-height: 1.35;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.sound-result audio {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
margin: 9px 0 7px;
|
||||
}
|
||||
|
||||
.sound-result-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sound-result-actions code {
|
||||
color: var(--quiet);
|
||||
font-size: 10px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sound-download {
|
||||
flex: 0 0 auto;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 7px;
|
||||
background: var(--panel-soft);
|
||||
color: var(--accent);
|
||||
padding: 5px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sound-download:hover { background: var(--accent-soft); text-decoration: none; }
|
||||
|
||||
.article-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 960px) 280px;
|
||||
@@ -776,6 +908,7 @@ HTML_TEMPLATE = r"""<!doctype html>
|
||||
.tabs { grid-column: 1 / -1; width: 100%; justify-content: center; }
|
||||
.tab { flex: 1; }
|
||||
.search-wrap { grid-column: 1 / -1; }
|
||||
.search-controls { grid-template-columns: minmax(0, 1fr) 108px; }
|
||||
.article-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -804,14 +937,23 @@ HTML_TEMPLATE = r"""<!doctype html>
|
||||
</span>
|
||||
</a>
|
||||
<div class="sidebar-stats" aria-label="Wiki counts">
|
||||
<div class="stat"><strong id="page-count">0</strong><span>pages</span></div>
|
||||
<div class="stat"><strong id="artifact-count">0</strong><span>artifacts</span></div>
|
||||
<div class="stat"><strong id="page-count">0</strong><span>research</span></div>
|
||||
<div class="stat"><strong id="voice-count">0</strong><span>voice</span></div>
|
||||
<div class="stat"><strong id="media-count">0</strong><span>media</span></div>
|
||||
</div>
|
||||
<input class="nav-filter" id="nav-filter" type="search" autocomplete="off" placeholder="Filter pages">
|
||||
<nav class="side-section" aria-label="Wiki pages">
|
||||
<p class="side-heading">Pages <span id="visible-page-count"></span></p>
|
||||
<nav class="side-section" aria-label="Research pages">
|
||||
<p class="side-heading">Research <span id="visible-page-count"></span></p>
|
||||
<ul class="page-list" id="page-list"></ul>
|
||||
</nav>
|
||||
<nav class="side-section" aria-label="Voice line catalogs">
|
||||
<p class="side-heading">Voice lines <span id="visible-voice-count"></span></p>
|
||||
<ul class="page-list" id="voice-list"></ul>
|
||||
</nav>
|
||||
<nav class="side-section" aria-label="Media galleries">
|
||||
<p class="side-heading">Media galleries <span id="visible-media-count"></span></p>
|
||||
<ul class="page-list" id="media-list"></ul>
|
||||
</nav>
|
||||
<nav class="side-section" aria-label="Generated artifacts">
|
||||
<p class="side-heading">Generated files <span id="visible-artifact-count"></span></p>
|
||||
<ul class="page-list" id="artifact-list"></ul>
|
||||
@@ -830,7 +972,16 @@ HTML_TEMPLATE = r"""<!doctype html>
|
||||
<button class="tab" id="source-tab" type="button">Source</button>
|
||||
</div>
|
||||
<div class="search-wrap">
|
||||
<input id="search" type="search" autocomplete="off" placeholder="Search the wiki">
|
||||
<div class="search-controls">
|
||||
<input id="search" type="search" autocomplete="off" placeholder="Search research pages">
|
||||
<select id="search-scope" aria-label="Search scope">
|
||||
<option value="wiki">Research</option>
|
||||
<option value="sounds">Sounds</option>
|
||||
<option value="voice">Voice pages</option>
|
||||
<option value="media">Media</option>
|
||||
<option value="all">Everything</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="search-results" id="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -860,6 +1011,8 @@ HTML_TEMPLATE = r"""<!doctype html>
|
||||
let headingIndex = [];
|
||||
|
||||
const pageList = document.getElementById("page-list");
|
||||
const voiceList = document.getElementById("voice-list");
|
||||
const mediaList = document.getElementById("media-list");
|
||||
const artifactList = document.getElementById("artifact-list");
|
||||
const articleView = document.getElementById("article-view");
|
||||
const tocView = document.getElementById("toc");
|
||||
@@ -867,13 +1020,17 @@ HTML_TEMPLATE = r"""<!doctype html>
|
||||
const currentTitle = document.getElementById("current-title");
|
||||
const currentPathEl = document.getElementById("current-path");
|
||||
const pageCount = document.getElementById("page-count");
|
||||
const artifactCount = document.getElementById("artifact-count");
|
||||
const voiceCount = document.getElementById("voice-count");
|
||||
const mediaCount = document.getElementById("media-count");
|
||||
const visiblePageCount = document.getElementById("visible-page-count");
|
||||
const visibleVoiceCount = document.getElementById("visible-voice-count");
|
||||
const visibleMediaCount = document.getElementById("visible-media-count");
|
||||
const visibleArtifactCount = document.getElementById("visible-artifact-count");
|
||||
const navFilter = document.getElementById("nav-filter");
|
||||
const mobileNav = document.getElementById("mobile-nav");
|
||||
const scrim = document.getElementById("scrim");
|
||||
const searchInput = document.getElementById("search");
|
||||
const searchScope = document.getElementById("search-scope");
|
||||
const searchResults = document.getElementById("search-results");
|
||||
const readTab = document.getElementById("read-tab");
|
||||
const sourceTab = document.getElementById("source-tab");
|
||||
@@ -898,6 +1055,8 @@ HTML_TEMPLATE = r"""<!doctype html>
|
||||
|
||||
function kindLabel(page) {
|
||||
if (page.kind !== "markdown") return page.path.split(".").pop() || "file";
|
||||
if (page.group === "voice") return "voice";
|
||||
if (page.group === "media") return "gallery";
|
||||
if (/gallery|asset|sound|radium/i.test(page.path)) return "asset";
|
||||
if (/conagent|emulator|behavior|package|firmware/i.test(page.path)) return "re";
|
||||
return "note";
|
||||
@@ -1234,11 +1393,16 @@ HTML_TEMPLATE = r"""<!doctype html>
|
||||
}
|
||||
|
||||
function renderNavigation() {
|
||||
const pages = wiki.pages.filter((page) => page.kind === "markdown");
|
||||
const artifacts = wiki.pages.filter((page) => page.kind !== "markdown");
|
||||
const pages = wiki.pages.filter((page) => page.group === "wiki");
|
||||
const voices = wiki.pages.filter((page) => page.group === "voice");
|
||||
const media = wiki.pages.filter((page) => page.group === "media");
|
||||
const artifacts = wiki.pages.filter((page) => page.group === "source");
|
||||
pageCount.textContent = pages.length.toLocaleString();
|
||||
artifactCount.textContent = artifacts.length.toLocaleString();
|
||||
voiceCount.textContent = voices.length.toLocaleString();
|
||||
mediaCount.textContent = media.length.toLocaleString();
|
||||
pageList.innerHTML = pages.map(renderNavItem).join("");
|
||||
voiceList.innerHTML = voices.map(renderNavItem).join("");
|
||||
mediaList.innerHTML = media.map(renderNavItem).join("");
|
||||
artifactList.innerHTML = artifacts.map(renderNavItem).join("");
|
||||
filterNavigation(navFilter.value);
|
||||
}
|
||||
@@ -1246,30 +1410,77 @@ HTML_TEMPLATE = r"""<!doctype html>
|
||||
function filterNavigation(query) {
|
||||
const q = query.trim().toLowerCase();
|
||||
let pagesVisible = 0;
|
||||
let voicesVisible = 0;
|
||||
let mediaVisible = 0;
|
||||
let artifactsVisible = 0;
|
||||
pageList.querySelectorAll("li").forEach((item) => {
|
||||
const show = !q || item.dataset.title.includes(q);
|
||||
item.classList.toggle("hidden", !show);
|
||||
if (show) pagesVisible++;
|
||||
});
|
||||
voiceList.querySelectorAll("li").forEach((item) => {
|
||||
const show = !q || item.dataset.title.includes(q);
|
||||
item.classList.toggle("hidden", !show);
|
||||
if (show) voicesVisible++;
|
||||
});
|
||||
mediaList.querySelectorAll("li").forEach((item) => {
|
||||
const show = !q || item.dataset.title.includes(q);
|
||||
item.classList.toggle("hidden", !show);
|
||||
if (show) mediaVisible++;
|
||||
});
|
||||
artifactList.querySelectorAll("li").forEach((item) => {
|
||||
const show = !q || item.dataset.title.includes(q);
|
||||
item.classList.toggle("hidden", !show);
|
||||
if (show) artifactsVisible++;
|
||||
});
|
||||
visiblePageCount.textContent = pagesVisible.toLocaleString();
|
||||
visibleVoiceCount.textContent = voicesVisible.toLocaleString();
|
||||
visibleMediaCount.textContent = mediaVisible.toLocaleString();
|
||||
visibleArtifactCount.textContent = artifactsVisible.toLocaleString();
|
||||
}
|
||||
|
||||
function search(query) {
|
||||
const q = query.trim().toLowerCase();
|
||||
const scope = searchScope.value;
|
||||
if (!q) {
|
||||
searchResults.classList.remove("open");
|
||||
searchResults.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
if (scope === "sounds") {
|
||||
const terms = q.split(/\s+/).filter(Boolean);
|
||||
const matches = wiki.sounds.map((sound) => {
|
||||
const title = sound.title.toLowerCase();
|
||||
const filename = sound.filename.toLowerCase();
|
||||
const haystack = `${title} ${filename} ${sound.meta.toLowerCase()}`;
|
||||
if (!terms.every((term) => haystack.includes(term))) return null;
|
||||
let score = 0;
|
||||
if (title === q) score += 100;
|
||||
if (title.startsWith(q)) score += 40;
|
||||
if (title.includes(q)) score += 20;
|
||||
if (filename.includes(q)) score += 10;
|
||||
return { sound, score };
|
||||
}).filter(Boolean);
|
||||
matches.sort((a, b) => b.score - a.score || a.sound.title.localeCompare(b.sound.title));
|
||||
const cards = matches.slice(0, 24).map(({ sound }) => (
|
||||
`<div class="sound-result" data-sound-id="${escapeAttr(sound.id)}">
|
||||
<strong>${escapeHtml(sound.title)}</strong>
|
||||
<small>${escapeHtml(sound.meta)}</small>
|
||||
<audio controls preload="none" src="${escapeAttr(sound.src)}"></audio>
|
||||
<div class="sound-result-actions">
|
||||
<a class="sound-download" href="${escapeAttr(sound.src)}" download="${escapeAttr(sound.filename)}">Download WAV</a>
|
||||
<code title="${escapeAttr(sound.filename)}">${escapeHtml(sound.filename)}</code>
|
||||
</div>
|
||||
</div>`
|
||||
)).join("");
|
||||
searchResults.innerHTML = `<div class="sound-search-summary"><strong>${matches.length.toLocaleString()}</strong> matches in ${wiki.sounds.length.toLocaleString()} indexed sounds${matches.length > 24 ? "; showing the first 24" : ""}.</div>` +
|
||||
(cards || '<div class="sound-result"><strong>No matching sounds</strong><small>Try a character, callout phrase, object number, source, or filename.</small></div>');
|
||||
searchResults.classList.add("open");
|
||||
return;
|
||||
}
|
||||
const hits = [];
|
||||
for (const page of wiki.pages) {
|
||||
if (scope !== "all" && page.group !== scope) continue;
|
||||
const titleHit = page.title.toLowerCase().includes(q);
|
||||
const idx = page.text.toLowerCase().indexOf(q);
|
||||
if (!titleHit && idx === -1) continue;
|
||||
@@ -1282,6 +1493,7 @@ HTML_TEMPLATE = r"""<!doctype html>
|
||||
}
|
||||
hits.push({ page, snippet, titleHit });
|
||||
}
|
||||
hits.sort((a, b) => Number(b.titleHit) - Number(a.titleHit) || a.page.title.localeCompare(b.page.title));
|
||||
searchResults.innerHTML = hits.slice(0, 12).map(({ page, snippet }) => (
|
||||
`<a href="#/${escapeAttr(page.path)}"><strong>${escapeHtml(page.title)}</strong><small>${snippet}</small></a>`
|
||||
)).join("") || '<a href="#/README.md"><strong>No matches</strong><small>Try a path, hash, service name, or binary name.</small></a>';
|
||||
@@ -1295,8 +1507,14 @@ HTML_TEMPLATE = r"""<!doctype html>
|
||||
mobileNav.addEventListener("click", () => setNavOpen(!document.body.classList.contains("nav-open")));
|
||||
scrim.addEventListener("click", () => setNavOpen(false));
|
||||
searchInput.addEventListener("input", (event) => search(event.target.value));
|
||||
searchScope.addEventListener("change", () => {
|
||||
const labels = { wiki: "research pages", sounds: "all sounds", voice: "voice pages", media: "media galleries", all: "everything" };
|
||||
searchInput.placeholder = `Search ${labels[searchScope.value]}`;
|
||||
search(searchInput.value);
|
||||
});
|
||||
searchInput.addEventListener("keydown", (event) => {
|
||||
if (event.key !== "Enter") return;
|
||||
if (searchScope.value === "sounds") return;
|
||||
const first = searchResults.querySelector("a");
|
||||
if (first) first.click();
|
||||
});
|
||||
@@ -1315,9 +1533,12 @@ HTML_TEMPLATE = r"""<!doctype html>
|
||||
|
||||
|
||||
def main() -> None:
|
||||
data = {"pages": build_pages()}
|
||||
sound_index = build_sound_index()
|
||||
SOUND_INDEX_OUT.write_text(json.dumps(sound_index, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
data = {"pages": build_pages(), "sounds": sound_index["sounds"]}
|
||||
encoded = json.dumps(data, ensure_ascii=False, separators=(",", ":")).replace("</", "<\\/")
|
||||
OUT.write_text(HTML_TEMPLATE.replace("__WIKI_DATA__", encoded), encoding="utf-8")
|
||||
print(f"wrote {SOUND_INDEX_OUT.relative_to(ROOT.parent)} ({sound_index['count']} sounds)")
|
||||
print(f"wrote {OUT.relative_to(ROOT.parent)}")
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ Generated best-available named alias previews for `2545` total sounds across the
|
||||
|
||||
The canonical WAV exports remain unchanged. These aliases are generated under `analysis/radium-image/sounds-best-named/` and `analysis/radium-spike-menu-image/sounds-best-named/` so every sound has one stable, traceable, previewable filename using the best current evidence.
|
||||
|
||||
The HTML viewer's **Sounds** search uses these aliases as one canonical 2,545-entry index. Each result includes an inline audio player and a direct WAV download link, so finding a sound no longer requires opening one of the split gallery pages.
|
||||
|
||||
## Name Sources
|
||||
|
||||
- `asr_draft`: `583`
|
||||
@@ -38,3 +40,4 @@ Confirmed native names are preferred first. Pokedex voice alignment, catalog-mat
|
||||
## Manifest
|
||||
|
||||
- `analysis/all-sound-best-name-manifest.tsv`
|
||||
- [Generated JSON sound index](sound-index.json) - deterministic viewer index generated from the 13 best-named gallery pages by `scripts/build_viewer.py`.
|
||||
|
||||
+20381
File diff suppressed because it is too large
Load Diff
+178
-14
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user