test: comprehensive testing gap analysis (9 phases) - #7
Conversation
Audited all 1,173 existing tests across 96 modules and identified gaps in unit coverage, integration testing, visual regression, fuzz testing, and platform-specific verification. Plan spans 9 phases with ~356 new tests, 60+ screenshot scenarios, and 6 fuzz targets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cover previously untested or undertested modules: - input.rs: 21 tests (InputEvent variants, Button/Trigger enums, serde) - error.rs: 14 tests (Display output, From conversions for all variants) - backend.rs: ~45 tests (Color, TextureId, DrawCommand, RecordingBackend test double verifying all SdiBackend default method decomposition) - terminal/interpreter.rs: 10 tests (edge cases, CommandRegistry, Environment) - skin/loader.rs: 16 tests (malformed TOML, partial configs, skin swap) - oasis-ffi: 21 tests (null pointers, edge cases, all input event types) - UI widgets: ~75 tests across 13 files (button, scroll_view, input_field, progress_bar, toggle, badge, tab_bar, shadow, theme, divider, card, avatar, nine_patch, list_view) All 1,407 workspace tests pass. Zero clippy warnings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…l, WM, and audio Add 19 integration tests exercising multi-module workflows: - Shell sessions (8): mkdir/cd/touch/cat chains, cp/mv/find workflows, cwd tracking, file lifecycle, relative paths, skin commands, error recovery, nested directory creation - Window manager (8): open/close/focus cycling, minimize/restore geometry preservation, maximize/restore, focus cycling, close-all cleanup, minimize-then-focus-other, dialog with app windows, move/resize/maximize/restore chain - Audio manager (3): full playback lifecycle, VFS load-and-play, process_request command sequence Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add `screenshot-tests` binary (3.1-3.6) for local visual regression testing: - Skin matrix: 8 skins x 5 views = 40 screenshot scenarios - Browser rendering: 13 HTML/Gemini test pages with fixture files - Widget gallery: all widget types rendered in a single frame - Window manager: maximized, cascaded, and dialog overlay scenarios - HTML report generation with --report flag - CLI filtering with --scenario and --skin flags Test fixtures: 11 HTML pages + 1 Gemini page in test-fixtures/ Usage: cargo run -p oasis-app --bin screenshot-tests [--scenario X] [--skin X] [--report] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add PSP screenshot capture tooling for manual visual regression testing: - scripts/psp-screenshot.sh: driver script that runs EBOOT.PBP in PPSSPPHeadless via Docker and captures framebuffer screenshots - CLI: --list, --report, --timeout, per-scenario filtering - 5 scenarios: dashboard, terminal, browser, input, audio - HTML report generation for side-by-side review - scripts/psp-scenarios.md: manual review checklist for each scenario documenting what to verify (icons, status bar, VRAM corruption, etc.) - docker-compose.yml: add screenshots/psp volume mount to ppsspp service so captured PNGs are accessible on the host - .gitignore: exclude screenshots/psp/ output directory Note: terminal/browser/input/audio scenarios currently capture boot state only. Full scenario support requires PPSSPP input injection patches in docker/ppsspp-patches/ (documented in psp-scenarios.md). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add fuzz testing infrastructure using cargo-fuzz (libFuzzer): Targets: - html_tokenizer: HTML tokenization (tags, entities, comments) - css_parser: CSS stylesheet + inline style parsing - gemini_parser: Gemini text/gemini document parsing - http_response: HTTP response parsing (status, headers, body) - skin_toml: Skin TOML manifest/layout/features parsing - pbp_parser: PSP PBP container format parsing Each target includes seed corpus files for effective fuzzing. Made HttpResponse and parse_response public to enable HTTP fuzzing. Usage: cd crates/oasis-core && cargo +nightly fuzz run <target> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… modules Add proptest to dev-dependencies and write property-based tests for: - VFS path normalization: idempotency, no double slashes, leading slash, write/read roundtrip - Color arithmetic: RGB/RGBA roundtrip, lerp endpoints/clamping, darken/lighten bounds - Layout box model: dimensional relationships, nesting invariants, union commutativity - Playlist: len/add/remove consistency, repeat mode wrapping, shuffle invariants - Navigation history: back/forward symmetry, stack clearing, bookmark idempotency - SDI z-order: uniqueness, move_to_top/bottom correctness, create/destroy counting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add criterion benchmarks for core subsystems: - html_parsing: tokenizer + tree builder at 10KB/50KB/100KB documents - css_cascade: stylesheet parsing (50/100/500 rules) and style_tree cascade - layout_engine: block layout (100/500/1000 divs) and table layout (10x10/20x20/50x10) - paint: paint pass (50/200/500 elements) and full pipeline (parse+layout+paint) - sdi_registry: create/get/destroy/move_to_top at 100/1K/10K objects - vfs: write/read (100/1000 files), readdir (100/1000 entries), deep mkdir Run with: cargo bench -p oasis-core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dering Add 34 new tests + 6 ignored rendering tests to oasis-backend-sdl: - Input mapping: key_down/key_up for all 12 mapped keys, press/release symmetry, unmapped key returns None - Helper functions: intersect_clip (5 cases), isqrt (3 cases), edge_x (4 cases), lerp_color_sdl (3 cases) - Audio edge cases: volume clamping to 0/max, rapid play/pause cycling, double stop, unload nonexistent, play after unload, duration scaling, reinit after shutdown - Rendering correctness (ignored, require display): clear, fill_rect, texture load+blit, draw_text, measure_text, clip_rect restriction Run rendering tests locally: cargo test -p oasis-backend-sdl -- --ignored Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 113 edge case tests across 7 modules to verify graceful error handling and boundary conditions: - Browser HTML tokenizer (22): unclosed tags, deeply nested elements, very long attributes/names, null bytes, empty tags, angle brackets in text, nested comments, CDATA-like content - Browser tree builder (15): orphan end tags, misnested formatting, duplicate html/body tags, void elements with children, 500 siblings, deeply nested divs, text-only documents, unknown tags - Browser CSS parser (24): unclosed blocks, missing colons/semicolons, nested braces, at-rules, pseudo-classes/elements, very long values, extreme specificity, 500 rules, null bytes, trailing garbage - VFS memory (21): dot/dotdot components, special chars, unicode, empty data, 1MB files, deeply nested dirs, many files per dir, read-dir-as-file, write-to-dir-path, remove-then-readd - Network (14): invalid TOML hosts, missing fields, unicode names, PSK special chars, many entries, listener double-stop, start-stop-start - Skin loader (16): invalid TOML, empty manifests/layouts/features, extra fields, invalid/empty colors, zero dimensions, negative positions, unicode names, very long names, 100 layout objects - Terminal interpreter (12): very long commands/args, null bytes, tab-separated args, newlines, case sensitivity, special chars, unicode args, many command registrations Total workspace: 1,629 tests pass, 7 ignored, clippy clean. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix formatting across files introduced in Phases 1-9 of the testing gap analysis. Ensures cargo fmt --all -- --check passes in CI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds .githooks/pre-commit that: - Runs cargo fmt on staged Rust files and re-stages the result - Fails the commit if cargo clippy finds warnings Install with: git config core.hooksPath .githooks Skip once with: git commit --no-verify Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Gemini AI Code ReviewIssues
Previous Issues(none) Suggestions
Notes
Generated by Gemini AI (gemini-3-flash-preview). Supplementary to human reviews. |
Codex AI Code ReviewIssues (if any)
Previous Issues (for incremental reviews)(none) Suggestions (if any)(none) Notes
Generated by Codex AI (gpt-5.2-codex). Supplementary to human reviews. |
Automated fix by Claude in response to Gemini/Codex review. Iteration: 1/5 Co-Authored-By: AI Review Agent <noreply@anthropic.com>
Review Response Agent (Iteration 1)Status: Changes committed and pushed Commit: Fixed Issues
Ignored Issues
Deferred to Human
Notes
Automated summary of agent fixes. |
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues
Previous Issues
Suggestions
Notes
Generated by Gemini AI (gemini-3-flash-preview). Supplementary to human reviews. |
PSP has no OS-level entropy source that getrandom recognises. Add custom backends for both getrandom 0.2 (register_custom_getrandom! macro) and 0.3 (--cfg getrandom_backend="custom") using PSP's MT19937 PRNG via sceKernelUtilsMt19937. Update tls.rs for embedded-tls 0.18 API: - Implement embedded_io::Read/Write for IoAdapter (replaces std::io) - Add PspRng (rand_core 0.6 RngCore + CryptoRng) for TLS entropy - Use UnsecureProvider instead of bare NoVerify (CryptoProvider trait) - Fix borrow checker errors by saving raw buffer pointers before TlsConnection borrows the slices Address Gemini review feedback: - Fix texture resource leak in screenshot_tests.rs (destroy_texture) - Fix pre-commit hook partial staging (only re-stage fully-staged files) - Fix unknown category counting as completed (bail with error instead) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)(none) Previous Issues
Suggestions
Notes
Generated by Gemini AI (gemini-3-flash-preview). Supplementary to human reviews. |
Codex AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)
Previous Issues (for incremental reviews)
Suggestions (if any)(none) Notes
Generated by Codex AI (gpt-5.2-codex). Supplementary to human reviews. |
Previously, if --scenario or --skin was the last argument with no value, iter.next() returned None and the filter was silently disabled, running all scenarios instead of reporting the missing value. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review Response Agent (Iteration 2)Status: No changes needed Fixed Issues
Ignored Issues
Deferred to Human
Notes
The agent reviewed feedback but determined no code changes were required. |
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)(none) Previous Issues (for incremental reviews)
Suggestions (if any)(none) Notes
Generated by Gemini AI (gemini-3-flash-preview). Supplementary to human reviews. |
Codex AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)(none) Previous Issues (for incremental reviews)
Suggestions (if any)(none) Notes
Generated by Codex AI (gpt-5.2-codex). Supplementary to human reviews. |
Review Response Agent (Iteration 3)Status: No changes needed Fixed Issues
Ignored Issues
Deferred to Human
Notes
The agent reviewed feedback but determined no code changes were required. |
Failure Handler Agent (Iteration 1)Status: No changes needed The agent analyzed the failures but no automated fixes could be applied. Manual intervention may be required. |
The PspRng and getrandom backends use `core::arch::asm!("mfc0 ...")`
to read the CPU cycle counter on MIPS. This requires the
`asm_experimental_arch` feature gate on nightly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)(none) Previous Issues (for incremental reviews)
Suggestions (if any)(none) Notes
Generated by Gemini AI (gemini-3-flash-preview). Supplementary to human reviews. |
Review Response Agent (Iteration 4)Status: No changes needed Fixed Issues
Ignored Issues
Deferred to Human
Notes
The agent reviewed feedback but determined no code changes were required. |
Failure Handler Agent (Iteration 2)Status: No changes needed The agent analyzed the failures but no automated fixes could be applied. Manual intervention may be required. |
Rust 2024 edition requires `#[unsafe(no_mangle)]` instead of bare `#[no_mangle]`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Gemini AI Incremental ReviewThis is an incremental review focusing on changes since the last review. Issues (if any)
Previous Issues (for incremental reviews)
Suggestions (if any)
Notes
Generated by Gemini AI (gemini-3-flash-preview). Supplementary to human reviews. |
Review Response Agent (Iteration 5)Status: No changes needed Fixed Issues
Ignored Issues
Deferred to Human
Notes
The agent reviewed feedback but determined no code changes were required. |
Updates the psp dependency to include the merged rust-psp PR #7 which contributes our reverse-engineered abstractions back to the SDK: SrcChannel, AudiocodecDecoder, find_sync, skip_id3v2, and removes the incorrect kernel feature gate from sys::codec. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: PRX audio playback, CPU clock control, and FTP file server Implement three features that close gaps with IR Shell: - PRX audio: kernel-mode background MP3 playback using runtime NID resolution via find_function() for sceAudio and sceMp3 drivers, with dynamic module loading fallback, playlist scanning, decode thread, and atomic playback controls - CPU clock control: resolve scePower driver NIDs at hook init, cycle through 4 clock presets (333/266/222/133 MHz), display CPU MHz and battery percentage in overlay status line - FTP file server: poll-based TCP server (FtpServer) modeled after RemoteListener, wired into main loop via FtpToggle command output variant, processes LIST/GET/PUT/MKDIR/DELETE/STAT/QUIT against VFS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: recursive MP3 scanning for subdirectories in PRX audio scan_playlist() now recursively descends into subdirectories (up to 4 levels deep) when scanning the music directory. Also increases playlist capacity from 16 to 32 tracks and max path length from 80 to 128 bytes to accommodate deeper folder structures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct sceMp3NotifyAddStreamData NID and module load order - Fix wrong NID for sceMp3NotifyAddStreamData: was 0x0DB149F4 (duplicate of sceMp3ReleaseMp3Handle), now correct 0x29BFF3EC - Fix MP3 module dependency load order: mpegbase -> mpeg -> libmp3 (was loading mpeg before its dependency mpegbase) - Add mpeg_vsh.prx as fallback for some firmware versions - Add sceAudio_Service module name variant for wider firmware compat - Add libmp3 module name variant for sceMp3 resolution - Prefer kernel-mode library names (_driver) first for sceAudio Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use sceAudiocodec instead of sceMp3 for kernel-mode MP3 playback sceMp3 is a user-mode library that sctrlHENFindFunction cannot resolve from kernel context. Replace with sceAudiocodec (type 0x1002) which is a kernel-accessible codec driver. Also always start the audio thread regardless of autoplay config (start paused if autoplay=false). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use sceUtilityLoadModule to properly load MP3 codec modules sctrlHENFindFunction on PRO-C2 can only find kernel driver modules (sceAudio, sceCtrl, scePower). Neither sceMp3 nor sceAudiocodec were discoverable because the AV modules weren't loaded into the system. Now resolves sceUtilityLoadModule via find_function from sceUtility_Driver and uses it to load PSP_MODULE_AV_AVCODEC (0x0300), PSP_MODULE_AV_MPEGBASE (0x0301), and PSP_MODULE_AV_MP3 (0x0302). Also adds NULL module name fallback search (searches all loaded modules), tries sceMp3 first then sceAudiocodec as fallback, and logs return codes from every module load call for diagnostics. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(prx): resolve user-mode MP3/codec NIDs via manual export table walking sctrlHENFindFunction on PRO-C2 6.20 only searches kernel-mode modules, so sceMp3 and sceAudiocodec NIDs were invisible even after successfully loading them via sceUtilityLoadModule. This adds a fallback path that resolves sceKernelFindModuleByName from ModuleMgrForKernel, uses it to locate the loaded user-mode module struct, then walks the SceModule's export table (ent_top/ent_size -> SceLibraryEntryTable entries) to find function pointers by NID. Resolution order: sctrlHENFindFunction (fast, kernel) -> export walk (user). Includes diagnostic logging of found modules and export table addresses. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(prx): validate module pointers + try alternative kernel APIs sceKernelFindModuleByName returned 0x03F0DF67 (unaligned, not in any valid PSP memory range), crashing on dereference. This adds: - Pointer validation: alignment check + memory range check before any dereference (KSEG0/KSEG1/user space ranges) - Alternative API: try sceKernelSearchModuleByName (LoadCoreForKernel, NID 0xF0CAB543) which may behave differently on PRO-C2 - UID fallback: if FindModuleByName returns a non-pointer value, try treating it as a SceUID and converting via FindModuleByUID - Multiple offset probing: try ent_top/ent_size at 0x58/0x5C, 0x40/0x44, and 0x24/0x28 (different firmware SceModule layouts) - Hex dump: dump raw struct bytes at key offsets for remote diagnosis Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(prx): enumerate modules via GetModuleIdList + kernel-load flash0 PRXs sceKernelFindModuleByName returns garbage (0x03F12969, unaligned) on PRO-C2 6.20. Replace with two new strategies: 1. Module enumeration: resolve sceKernelGetModuleIdList (NID 0x644CF325) and sceKernelQueryModuleInfo (NID 0x748CBED9) from ModuleMgrForKernel. Enumerate all loaded modules, log their names + text addresses, and match by name pattern to find MP3/codec modules. Use text_addr to locate the embedded SceModuleInfo header and walk its export table (ent_top at +0x24, ent_end at +0x28). 2. Kernel-load flash0 PRXs: after sceUtilityLoadModule (which loads into user space), also try sceKernelLoadModule for flash0:/kd/*.prx files. When loaded from kernel context, modules may register in the kernel module list where sctrlHENFindFunction can discover them. Removed broken FindModuleByName/SearchModuleByName/FindModuleByUID code. Fixed excessive log spam (was logging every find_module_ptr call). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(prx): add NID memory scanner for diagnostic dump When MP3/codec NIDs can't be resolved via standard kernel APIs, scans user memory (0x08800000-0x0A000000) and kernel memory (0x88000000-0x88800000) for known NID values. Writes results to ms0:/seplugins/oasis_memdump.txt with hex context dumps and module name string search. This gives us hard diagnostic data to understand where sceUtilityLoadModule actually puts the AV modules and their export tables on PRO-C2 6.20. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(prx): extract sceAudiocodec from game's resolved import stubs Based on the NID memory scan results showing sceAudiocodec NIDs in user memory at 0x08B63C44 (the game's import NID table), adds a stub extraction strategy: 1. Scan user memory for a cluster of known sceAudiocodec NIDs 2. Walk backwards/forwards to find the full sorted NID table 3. Scan for a pointer to the NID table (SceLibraryStubTable entry) 4. Read the stub table and decode MIPS J-instructions to extract the real function addresses 5. Populate sceAudiocodec function pointer statics Handles both J-instruction format (user-to-user) and logs syscall stubs (user-to-kernel) for separate handling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(prx): run stub extraction before heavy NID scan The NID memory scan (32MB, checking 13 NIDs per word + string search) takes ~5+ seconds and was causing the PSP to crash before the fast stub extraction code could run. Swap order: run try_codec_stub_extraction() first (~0.1s, two targeted scans) then fall back to the diagnostic NID dump only if extraction fails. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(prx): full binary memory dump for offline analysis Replace the text-based NID scan with raw binary dumps of user memory (24MB, 0x08800000-0x0A000000) and kernel memory (8MB, 0x88000000- 0x88800000) to ms0:/seplugins/. Writes in 64KB chunks with 1MB progress logging. These dumps can be analyzed on PC with Python to find the syscall table and resolve sceAudiocodec function pointers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(prx): resolve sceAudiocodec via syscall table cross-reference Replace the unsuccessful direct stub decoding with a syscall table lookup approach discovered through offline memory dump analysis: 1. Extract sceAudiocodec syscall numbers from game's import stubs (jr $ra + syscall N format, N = insn1 >> 6) 2. Find sceAudio's import stubs nearby in user memory 3. Extract sceAudioChReserve's syscall number as a reference 4. Cross-reference with the already-resolved kernel function pointer (from sctrlHENFindFunction) to compute the syscall table base: table_base = kernel_ptr - syscall_num * 4 5. Look up sceAudiocodec kernel function pointers from the table This is firmware-independent since it computes the table base dynamically rather than hardcoding it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(prx): search all user memory for sceAudio import stubs The sceAudio and sceAudiocodec import tables can be far apart in the game binary (1.75MB observed). Expand the NID search from +/-64KB to the full user memory range (0x08800000-0x0A000000). The stub table pointer search remains localized to +/-16KB from the NID table since it's always in the same ELF section. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(prx): call sceAudiocodec through game's import stubs directly Instead of trying to resolve kernel function pointers via the syscall table (which failed because sctrlHENFindFunction returns export table entries, not syscall table entries), use the game's import stub addresses directly as function pointers. Each stub is `jr $ra; syscall N` -- when called from kernel mode, the syscall instruction traps to the kernel's syscall handler which dispatches to the actual codec function and returns to our caller. This is the same mechanism the game uses to call these functions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use dedicated audio channel and fix codec buffer size Three fixes for PRX audio playback: 1. Reserve channel 7 (fallback 6, then -1) instead of auto-select to avoid stealing game audio channels and causing crashes 2. Increase CODEC_BUF_WORDS from 32 to 65 -- sceAudiocodec needs 65 u32 entries and was overflowing into adjacent memory 3. Add error logging for all codec init and decode calls to diagnose playback issues Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: allocate codec buffers in user memory and add sceAvcodec_wrapper module name Two key fixes: 1. Allocate codec, PCM, and read buffers in user memory partition via sceKernelAllocPartitionMemory. Codec functions called through syscall stubs validate pointers are in user range (0x08800000+), so kernel-space statics (0x88xxxxxx) get rejected with 0x807F00FD. 2. Add "sceAvcodec_wrapper" as first module name to try for sctrlHENFindFunction -- this is the actual kernel driver name used by the working mp3play.prx reference plugin. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: set codec input/output size fields and cap decode error loop sceAudiocodecDecode needs codec[7]=input bytes available and codec[9]=output buffer size before each call. Missing these caused every decode to fail with 0x807F00FD. Also cap consecutive decode failures at 100 to prevent infinite spin and log flooding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: use SRC audio channel to avoid conflicting with game audio PSP has 8 regular PCM channels (0-7) that games claim exclusively. Reserving ANY of them causes games to crash when they init audio. Switch to the SRC (Sample Rate Conversion) channel -- a dedicated output path separate from the 8 regular channels. This is the standard approach for PSP plugins doing background audio. API: sceAudioSRCChReserve/sceAudioSRCOutputBlocking instead of sceAudioChReserve/sceAudioOutputBlocking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: set codec[10] field and add diagnostic dumps for decode debugging From mp3play.prx disassembly, sceAudiocodecDecode requires codec[10] set to the input data size (same as codec[7]). Also dumps codec buffer state after init and first-frame MP3 header bytes for diagnosis. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: replace sceAudiocodecGetEDRAM with user RAM to avoid game crash sceAudiocodecGetEDRAM allocates from the PSP's shared 2MB eDRAM pool (used by both GE and media engine). When games try to use their own audio/video codecs, they find the eDRAM already claimed and crash. Instead, we allocate 16KB of working memory from the user RAM partition and set codec[5] manually, completely avoiding the eDRAM pool. This lets games use their full eDRAM allocation while our MP3 decoder works from regular RAM. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: revert to GetEDRAM and skip module loading to prevent game crash The codec requires actual EDRAM for decode (user RAM fails with 0x807F0001). Reverts to sceAudiocodecGetEDRAM. The likely cause of game crashes is sceUtilityLoadModule loading AVCODEC/MPEGBASE/MP3 modules that conflict with the game's own module loading. Now tries sceAudiocodec first (resolves via sctrlHENFindFunction without loading any modules). Only falls back to LoadModule + sceMp3 as a last resort. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: wait for game to load AVCODEC modules before probing codec sceAudiocodec is only available via sctrlHENFindFunction after the AVCODEC module is loaded. Instead of loading it ourselves (which can conflict with the game's own module loading and cause crashes), wait for the game to load it during its init sequence. Retries sceAudiocodec resolution every 2s for up to 15s. Only falls back to sceUtilityLoadModule if the game never loaded the modules (low conflict risk since the game doesn't use audio codecs). Also increases initial audio thread delay from 1s to 3s. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * perf: reduce audio choppiness with larger buffer and less logging - Increase read buffer from 32KB to 64KB (fewer I/O stalls during decode) - Bump audio thread priority from 0x1E to 0x18 for smoother scheduling - Strip all diagnostic logging from decode loop (was causing file I/O contention with MP3 reads on every track init and first 5 frames) - Only error paths still log Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: increase codec probe retries from 8 to 30 (60s total) Some games take longer to activate their audio subsystem. Retry every 2s for up to 60s before falling back to loading modules ourselves. Also removed the per-retry log line to keep output clean. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * perf: streaming buffer refill to eliminate audio hiccups Instead of waiting until the buffer is nearly empty then doing one large blocking read (~60KB), stream 4KB chunks on every decode cycle. This spreads I/O across many frames so no single read stalls the audio output. Buffer compaction happens when half consumed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: update rust-psp to main with SrcChannel and AudiocodecDecoder Updates the psp dependency to include the merged rust-psp PR #7 which contributes our reverse-engineered abstractions back to the SDK: SrcChannel, AudiocodecDecoder, find_sync, skip_id3v2, and removes the incorrect kernel feature gate from sys::codec. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address AI review feedback (iteration 1) Automated fix by Claude in response to Gemini/Codex review. Iteration: 1/5 Co-Authored-By: AI Review Agent <noreply@anthropic.com> * fix: cap consecutive zero-consumed codec decodes to prevent spin loop On malformed MP3 files, sceAudiocodec can return success (ret >= 0) but consume 0 bytes. Without a limit, the decode loop would advance buf_pos by 1 byte at a time while the refill logic keeps topping up the buffer, effectively scanning the entire file byte-by-byte. Cap at 100 consecutive zero-consumed iterations matching the existing error limit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: AI Agent Bot <ai-agent@localhost> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: AI Review Agent <ai-review-agent@localhost>

Summary
docs/testing-gap-analysis.md) and implements all 9 phases, bringing the workspace from 1,173 to 1,629 tests (+456 new tests, +6 fuzz targets, +6 benchmark suites).githooks/pre-commit) that auto-formats staged Rust files and runs clippy before each commitPhases
aff557c09c0f78a6a732doasis-screenshot-testsbinary, 15 HTML/CSS/Gemini test fixtures, comparison reportcbb6454scripts/psp-screenshot.shfor PPSSPP-based screenshot capture1de2c0acargo-fuzztargets (HTML tokenizer, CSS parser, Gemini parser, HTTP response, PBP parser, skin TOML)24bdaf770fbb0be267400d3fa945bfab88bcargo fmtapplied to all new files7f227f1.githooks/pre-commitfor auto-format + clippyTest plan
cargo fmt --all -- --checkpassescargo clippy --workspace -- -D warningspasses (0 warnings)cargo test --workspacepasses (1,629 tests, 7 ignored)cargo deny checkpassesmain-ci.yml) passes on mergeGenerated with Claude Code