## Title Packaged `sprimo-tauri` sprite rendering breaks after pack switch; default switch errors and scaling stops applying. ## Severity P1 ## Environment - OS: Windows - App version/build: packaged release (`sprimo-tauri.exe`) - Renderer/backend details: Tauri main overlay + settings pop-out - Evidence screenshots: - `issues/screenshots/issue4.png` - `issues/screenshots/issue4-b.png` - `issues/screenshots/issue4-c.png` - `issues/screenshots/issue4-after-fix2-2026-02-14-145819.png` - `issues/screenshots/issue4-after-fix4-2026-02-14-153233.png` ## Summary In packaged runtime, sprite display is incorrectly split/tiled, switching to `default` can fail, and scaling becomes ineffective after the error. ## Reproduction Steps 1. Run packaged `sprimo-tauri.exe` from ZIP extract. 2. Open settings window. 3. Switch character between `ferris` and `default`. 4. Observe main overlay rendering and debug output. 5. Change scale slider. ## Expected Result - Sprite sheet is split into the correct frame grid regardless of image resolution. - Pack switching works for both `ferris` and `default`. - Scale changes continue to apply after pack changes. ## Actual Result - Main overlay shows incorrectly split/tiled sprite sheet. - Pack switch can produce runtime error and break subsequent behavior. - Scale update stops working reliably after the error. ## Root Cause Analysis 1. Existing splitting logic relied on fixed pixel frame metadata that did not generalize to packaged `sprite.png` dimension variants. 2. Pack metadata inconsistency: - `assets/sprite-packs/ferris/manifest.json` used duplicated `id` (`default`), causing pack identity ambiguity. 3. Settings/runtime flow then entered an unstable state after pack switch failures. 4. Renderer reload lifecycle in tauri UI was unsafe: - `PixiPetRenderer::dispose` performed duplicate teardown (`ticker.destroy` + `app.destroy`), which could trigger runtime `TypeError` during pack reload. - Renderer replacement disposed previous renderer before new renderer creation succeeded, leaving the view in a broken/cropped state on creation failures. 5. Chroma-key conversion tolerance removed most `#FF00FF` background but still left magenta fringe on anti-aliased edges. 6. Scale fit used repeated position deltas and caused directional drift during repeated resizing. 7. API mismatch in tauri window module: - runtime used `getCurrentWindow().currentMonitor()` but this API version exposes monitor lookup as module function (`currentMonitor`), causing `TypeError` and skipping window fit. 8. Scale position math mixed physical window metrics (`outerPosition`/`innerSize`) with logical set operations (`LogicalSize`/`LogicalPosition`), reintroducing cumulative drift in some DPI contexts. 9. Ferris background keying needed adaptive key detection; fixed `#FF00FF` assumptions were still too brittle for packaged atlas variants. 10. Scale-path physical positioning used non-integer coordinates in `setPosition`, triggering runtime arg errors (`expected i32`) and bypassing window fit updates. 11. Monitor-fit cap remained too optimistic for large frame packs, so max scale could still exceed practical visible bounds and appear clipped. 12. Ferris/demogorgon packaged backgrounds are gradient-magenta (not exact `#FF00FF`), requiring a border-connected magenta-family mask instead of exact-key assumptions. ## Fix Plan 1. Introduce generic splitter policy for `sprite.png`: - fixed topology: `8` columns x `7` rows - derive frame size from actual image dimensions - keep chroma-key background handling (`#FF00FF`) in renderer 2. Validate animation frame indices against fixed frame count (`56`) for `sprite.png`. 3. Ensure pack apply path validates atlas geometry before committing `SetSpritePack`. 4. Fix ferris manifest ID uniqueness. ## Implementation Notes Implemented: 1. `crates/sprimo-tauri/src/main.rs` - Added `sprite.png`-specific frame derivation (`8x7`) from PNG dimensions. - Added PNG header dimension decoding utility. - Added animation frame index validation against fixed `56` frames for `sprite.png`. - Applied validation in both `load_active_sprite_pack` and `set_sprite_pack`. 2. `assets/sprite-packs/ferris/manifest.json` - Changed manifest `id` from `default` to `ferris`. 3. `docs/SPRITE_PACK_SCHEMA.md` - Documented Tauri `sprite.png` override behavior and 8x7 derived frame policy. 4. `frontend/tauri-ui/src/renderer/pixi_pet.ts` - Made renderer disposal idempotent and removed duplicate ticker destruction. - Delayed DOM canvas replacement until atlas load succeeds. - Improved chroma-key edge handling with soft alpha + magenta spill suppression. 5. `frontend/tauri-ui/src/main.tsx` - Made pack reload transactional (keep old renderer until new renderer creation succeeds). - Improved fit-window flow so scale apply continues after reload retries. - Added targeted diagnostics for reload failures. 6. `frontend/tauri-ui/src/main.tsx` - Changed scaling anchor to window center and clamped resized window position within current monitor bounds. 7. `frontend/tauri-ui/src/renderer/pixi_pet.ts` - Replaced tolerance-only chroma key with border-connected `#FF00FF` background flood-fill removal and localized edge halo suppression. 8. `crates/sprimo-tauri/capabilities/default.json` - Added `core:window:allow-current-monitor` permission for monitor bounds clamping. 9. `frontend/tauri-ui/src/main.tsx` - switched monitor lookup to module-level `currentMonitor()` with safe fallback so window scaling still applies even if monitor introspection is unavailable. 10. `frontend/tauri-ui/src/renderer/pixi_pet.ts` - added fallback global key cleanup when border-connected background detection is too sparse. 11. `frontend/tauri-ui/src/main.tsx` - moved scale resizing and positioning to physical units (`PhysicalSize`/`PhysicalPosition`) and monitor selection at window-center point (`monitorFromPoint`). 12. `frontend/tauri-ui/src/renderer/pixi_pet.ts` - added adaptive border-derived key color selection with fallback key cleanup pass. 13. `scripts/package_windows.py` - tauri packaging now explicitly rebuilds UI bundle to avoid stale embedded `dist` output. 14. `frontend/tauri-ui/src/main.tsx` - enforced integer physical positioning and monitor work-area size clamping to prevent set-position arg failures and large-scale clipping. 15. `frontend/tauri-ui/src/renderer/pixi_pet.ts` - switched ferris cleanup to hue/saturation/value magenta-band masking with connected background removal and stronger fallback cleanup. 16. `frontend/tauri-ui/src/main.tsx` - added stricter monitor work-area guard (`WINDOW_WORKAREA_MARGIN`) in both scale-cap and resize clamp paths to prevent large-pack clipping at high scales. 17. `frontend/tauri-ui/src/renderer/pixi_pet.ts` - added deterministic border-connected strong-magenta flood-fill cleanup pass so non-`#FF00FF` gradient backgrounds are removed consistently in packaged ferris/demogorgon atlases. ## Verification ### Commands Run - [ ] `just build-release-tauri` - [ ] `just package-win-tauri` - [ ] `just smoke-win-tauri` - [x] `cargo check -p sprimo-tauri` ### Visual Checklist - [x] Before screenshot(s): `issues/screenshots/issue4.png` - [x] Before screenshot(s): `issues/screenshots/issue4-b.png` - [x] Before screenshot(s): `issues/screenshots/issue4-c.png` - [ ] After screenshot(s): `issues/screenshots/issue4-after-YYYYMMDD-HHMMSS.png` ### Result - Status: `Fix Implemented` - Notes: packaged runtime validation and after screenshots for this round are pending. ## Status History - `2026-02-14 00:00` - reporter - `Reported` - packaged runtime failure screenshots attached. - `2026-02-14 00:00` - codex - `Triaged` - localized to sprite splitting/pack identity behavior. - `2026-02-14 00:00` - codex - `Fix Implemented` - applied 8x7 generic splitter policy and pack-ID correction. - `2026-02-14 00:00` - reporter - `In Progress` - reported `issue4-after-fix1` still failing in packaged runtime. - `2026-02-14 00:00` - codex - `Fix Implemented` - hardened renderer reload/dispose and chroma-key edge cleanup. - `2026-02-14 00:00` - reporter - `In Progress` - remaining magenta ferris edge + scale drift reported. - `2026-02-14 00:00` - codex - `Fix Implemented` - switched to border-connected chroma-key removal and center-anchored, monitor-clamped scale fit. - `2026-02-14 00:00` - reporter - `In Progress` - reported `currentMonitor` TypeError and ferris magenta background still visible. - `2026-02-14 00:00` - codex - `Fix Implemented` - corrected monitor API call and added fallback chroma cleanup pass. - `2026-02-14 00:00` - reporter - `In Progress` - reported ferris magenta background still visible and scale drift recurrence. - `2026-02-14 00:00` - codex - `Fix Implemented` - switched to physical-unit resize math, adaptive key detection, and packaging UI refresh enforcement. - `2026-02-14 00:00` - reporter - `In Progress` - reported default clipping, ferris background still present, and set_position float arg error. - `2026-02-14 00:00` - codex - `Fix Implemented` - added integer-safe physical setPosition and HSV magenta cleanup strategy. - `2026-02-14 00:00` - reporter - `In Progress` - reported remaining default clipping and ferris magenta background persistence. - `2026-02-14 00:00` - codex - `Fix Implemented` - tightened work-area scale guard and added border-connected strong-magenta cleanup pass. ## Closure - Current Status: `Fix Implemented` - Close Date: - Owner: - Linked PR/commit: