The scattered per-call-site prefix rewrites were a parallel track that any
future newImage("assets/generated/...") would silently bypass. Replace
them with NxAssetOverlay: installed once from love.load on NX only, it
wraps newImage / newImageData / newSource / filesystem.read / getInfo so a
missing assets/generated path falls back to the active version's
blue|yellow copy. Call sites return to plain love loader calls, and
Assets.resolve goes back to being the platform-free mod-override point.
Two deliberate exceptions remain: the chip-audio worker (separate Lua
state) keeps receiving the prefix explicitly via audio.programPrefix, and
data/generated module loads keep using CacheFs.readActive.
A new guard test (tests/engine/nx_generated_guard_test.lua) fails CI on
any direct love loader call with a literal assets/generated path, so the
class of bug cannot regress by accident. scripts/test.sh --quick is
green across all tiers.
Co-authored-by: Cursor <cursoragent@cursor.com>
Five more places called love.graphics.newImage directly on
assets/generated paths, bypassing the NX prefix rewrite:
- TradeAnim: cable/ball/bubble art
- TownMap: Kanto background, cursor, nest icon
- SurfingMinigame: surf bg/ob sheets
- BattleState: party ball row, substitute doll
All now resolve through Assets.resolve, which maps to the versioned
blue/ or yellow/ save-dir prefix on NX only. Desktop and Android keep
the mountVersion overlay behavior unchanged.
Co-authored-by: Cursor <cursoragent@cursor.com>
Yellow music was still silent because the background worker thread loads
ChipSynth.lua in a fresh Lua state with no GameVersion/Platform context.
The main thread's prefix never reached it.
ChipAudio.slimAudio now resolves the versioned cache prefix on the main
thread and includes it in the audio payload as `programPrefix`.
ChipSynth.loadBanks prefers `audio.programPrefix` when present, falling
back to its own NX detection for the sync path. Blue and Yellow are
handled the same way.
Tests cover the worker prefix hand-off and Blue's programs.bin path.
Co-authored-by: Cursor <cursoragent@cursor.com>
The previous NX gate only rewrote image paths that go through Assets.resolve.
Pokemon Yellow still had no sound and a blank title screen because:
- ChipSynth reads programs.bin directly via love.filesystem.read, bypassing
Assets. On NX the unprefixed path is missing when the mount overlay fails,
so the engine never built and every song/SFX was silent.
- Sound.playPikaCry loads pika_cries WAVs with love.audio.newSource, also
bypassing Assets.resolve.
- TitleState, YellowIntro, and IntroMovie call love.graphics.newImage
directly on unprefixed assets/generated paths, so the Pikachu title and
intro atlases failed to load.
Fix: apply the same NX-only prefix rewrite in those four places.
Desktop/Android keep the existing mountVersion overlay behavior.
Also add ChipSynth._loadBanksForTest and tests covering the new paths.
Co-authored-by: Cursor <cursoragent@cursor.com>
Capture resolve paths and newImage open results for Yellow/Blue art
triage without enabling switch-debug.txt.
Co-authored-by: Cursor <cursoragent@cursor.com>
Desktop and Android keep mountVersion as the overlay; only love-nx
resolves assets/generated to yellow|blue/ save-dir paths.
Co-authored-by: Cursor <cursoragent@cursor.com>
NX fused mount often cannot expose assets/generated; open the real
yellow|blue/assets/generated file with newImage instead of FileData.
Co-authored-by: Cursor <cursoragent@cursor.com>
Probe generated canaries after mountVersion and always readActive for
prefixed caches so sprites are not blanked by empty PhysFS stubs.
Co-authored-by: Cursor <cursoragent@cursor.com>
Disable mouse-yield on NX where stick/touch moves the system pointer between sparse axis events, clamp pad dt, pixel-snap the overlay, and soften FlexLove GC so the launcher cursor stays steady.
Co-authored-by: Cursor <cursoragent@cursor.com>
Only resize when width/height change; love-nx flag mismatches were
recreating the EGL surface every frame.
Co-authored-by: Cursor <cursoragent@cursor.com>
Mirror Data:load's versioned CacheFs read in Assets so Yellow-only NX
Play survives intro without needing a Red root cache mask.
Co-authored-by: Cursor <cursoragent@cursor.com>
Unlock love-nx SDL dock/undock resizing and sync via NxDisplay so
booting docked is not stuck on the conf 720p hint.
Co-authored-by: Cursor <cursoragent@cursor.com>
Skip per-frame mouse warps and FlexLove perf sampling on Switch, feed pad coords through a getPosition bridge, and park that shim before the save editor so desktop paths stay unchanged.
Co-authored-by: Cursor <cursoragent@cursor.com>
A shared imports/ inbox with Red+Yellow was starting Red from the Yellow tab; match by GameVersion.forSha1 for the selected game and document the tab-scoped rescan.
Co-authored-by: Cursor <cursoragent@cursor.com>
Bring FlexLove launcher UI ("big ui moment"), iOS picker dismiss, and Metal updates into the Switch NX branch. Keep pack_love (with libs/), NX inbox/rescan paths, SwitchDiagnostics, and Save Editor PadInput; port Scan again labels into LauncherView.
Co-authored-by: Cursor <cursoragent@cursor.com>
scanInbox never consulted ready, and mobileFileBridge already mirrors
android on Android/iOS, so the dual guard was a no-op.
Co-authored-by: Cursor <cursoragent@cursor.com>
Wire tests/switch_transfer_docs_test.lua into switch-changes detection,
switch-selftest, and the ROM-free T0 lane so docs drift fails CI.
Co-authored-by: Cursor <cursoragent@cursor.com>
Ship stock engine chords only; community mods own their rebinds, and keys
2/3/5 are claimed by the engine before pipeline hotkeys run.
Co-authored-by: Cursor <cursoragent@cursor.com>
platform_nx_* and rom_importer_nx_* run with the love stub and must execute
in CI's ROM-free lane via tests/run_engine.lua, not only T3.
Co-authored-by: Cursor <cursoragent@cursor.com>
Local planning artifacts belong under gitignore (.*) and must not ship in
the PR; useful Switch decisions already live in docs/switch-*.md.
Co-authored-by: Cursor <cursoragent@cursor.com>
UIDocumentPickerViewController reports an interactive dismissal only through
UIAdaptivePresentationControllerDelegate. PickerDelegate implemented
didPickDocumentsAt and documentPickerWasCancelled, so a sheet swiped away
reached neither, onFinish never ran, and the delegate stayed in
liveDelegates -- after which the re-present guard refused every later picker
while still answering true.
Implements the dismissal callback, and self-heals when UIKit is presenting
nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Players extract one zip at the microSD root for install and update; saves under pokemon-love2d/ survive merge. Drop the bare .nro from GitHub Release assets.
Co-authored-by: Cursor <cursoragent@cursor.com>
Write the imported-sha1 newline with string.char(10) so gate_strings_coverage
does not treat the filesystem ledger separator as player-visible text.
Co-authored-by: Cursor <cursoragent@cursor.com>
Split Import/Export paths into imports/saves/{red,blue,yellow}/ and
exports/{red,blue,yellow}/ so MTP destinations match each launcher tab.
Co-authored-by: Cursor <cursoragent@cursor.com>
Retire successful imports to *.sav.imported and record content hashes so
re-pressing Import save (or the same bytes under a new name) cannot clone
slots. Surface multi-import counts and the active game tab in the notice.
Co-authored-by: Cursor <cursoragent@cursor.com>