7/10/26
This commit is contained in:
49
README.md
49
README.md
@@ -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.
|
||||
|
||||
47
analysis-evidence-map.md
Normal file
47
analysis-evidence-map.md
Normal file
@@ -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.
|
||||
@@ -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
netbridge-re.md
Normal file
348
netbridge-re.md
Normal file
@@ -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.
|
||||
|
||||
@@ -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
sound-index.json
Normal file
20381
sound-index.json
Normal file
File diff suppressed because it is too large
Load Diff
192
viewer.html
192
viewer.html
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user