The overlay only wrapped the five loaders the boot path needed, leaving a
silent-failure hole: any future state (or current code like Sound.lua's
widenMono, which re-reads the pika-cry WAV via love.sound.newSoundData
with the caller's bare path) could load a generated asset through an
unwrapped API and silently degrade on hardware.
NxAssetOverlay now wraps every read-side love function that accepts a
filesystem path (filesystem.read/load/lines/newFileData/getInfo,
graphics.newImage/newFont, image.newImageData, audio.newSource,
sound.newSoundData, font.newFontData), so new states and mods fall inside
the Blue/Yellow fallback with zero per-call-site work. Write-side
functions stay stock, proven by identity assertions in the fallback
suite. The static guard's forbidden-literal list covers the same APIs.
Co-authored-by: Cursor <cursoragent@cursor.com>
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>
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>
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>
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>