mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
67e6b1fb04
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>
144 lines
5.1 KiB
Lua
144 lines
5.1 KiB
Lua
-- NxAssetOverlay: fused love-nx often cannot mount blue|yellow onto
|
|
-- assets/generated, so on NX the love loaders are wrapped once at boot and
|
|
-- fall back to the versioned save-dir path. Desktop/Android never install
|
|
-- the overlay; the chip worker gets the prefix explicitly via the audio
|
|
-- payload. Self-contained: luajit tests/engine/assets_version_fallback_test.lua
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
if not _G.love then _G.love = require("tests.love_stub") end
|
|
|
|
local T = require("tests.harness")
|
|
local check = T.check
|
|
local eq = T.eq
|
|
|
|
local GameVersion = require("src.core.GameVersion")
|
|
local Platform = require("src.core.Platform")
|
|
local Assets = require("src.render.Assets")
|
|
local Overlay = require("src.core.NxAssetOverlay")
|
|
|
|
local PNG = "assets/generated/tilesets/reds_house.png"
|
|
local savedVersion = GameVersion.get()
|
|
local savedSystem = love.system
|
|
|
|
local function clearPath(path)
|
|
love.filesystem.remove(path)
|
|
end
|
|
|
|
local function setOS(osName)
|
|
love.system = {
|
|
getOS = function() return osName end,
|
|
}
|
|
Platform._resetForTests()
|
|
end
|
|
|
|
-- --- Assets.resolve stays platform-free: no rewrite even for NX Yellow
|
|
setOS("NX")
|
|
GameVersion.set("yellow")
|
|
love.filesystem.write("yellow/" .. PNG, "yellow-png-bytes")
|
|
clearPath(PNG)
|
|
eq(Assets.resolve(PNG), PNG,
|
|
"resolve is the identity without a mod loader (overlay owns NX fallback)")
|
|
|
|
-- --- Overlay installed: every loader falls back to the versioned path
|
|
Overlay.install()
|
|
check(Overlay.isInstalled(), "overlay installs")
|
|
|
|
local img = love.graphics.newImage(PNG)
|
|
eq(img.path, "yellow/" .. PNG, "wrapped newImage receives the yellow/ path")
|
|
|
|
local id = love.image.newImageData(PNG)
|
|
eq(id.path, "yellow/" .. PNG, "wrapped newImageData receives the yellow/ path")
|
|
|
|
eq(love.filesystem.read(PNG), "yellow-png-bytes",
|
|
"wrapped filesystem.read returns the versioned bytes")
|
|
|
|
check(love.filesystem.getInfo(PNG) ~= nil,
|
|
"wrapped getInfo sees the versioned file at the un-prefixed path")
|
|
|
|
-- Assets.image/imageData benefit transparently (no call-site changes)
|
|
Assets.flush()
|
|
local aimg = Assets.image(PNG)
|
|
eq(aimg.path, "yellow/" .. PNG, "Assets.image loads via the overlay")
|
|
|
|
-- Non-string arguments pass through untouched
|
|
local fromData = love.graphics.newImage(id)
|
|
check(fromData ~= nil, "newImage(ImageData) is not rewritten")
|
|
|
|
-- Non-generated paths pass through untouched
|
|
local launcher = love.graphics.newImage("assets/launcher/gear.png")
|
|
eq(launcher.path, "assets/launcher/gear.png",
|
|
"overlay leaves non-generated paths alone")
|
|
|
|
-- The real un-prefixed file wins when it exists
|
|
love.filesystem.write(PNG, "root-png-bytes")
|
|
eq(love.filesystem.read(PNG), "root-png-bytes",
|
|
"overlay prefers the real un-prefixed file over the versioned copy")
|
|
clearPath(PNG)
|
|
|
|
-- Blue gets the same treatment
|
|
GameVersion.set("blue")
|
|
love.filesystem.write("blue/" .. PNG, "blue-png-bytes")
|
|
clearPath("yellow/" .. PNG)
|
|
eq(love.filesystem.read(PNG), "blue-png-bytes",
|
|
"overlay maps generated reads to blue/ for Blue")
|
|
|
|
-- Red has no prefix: nothing is rewritten
|
|
GameVersion.set("red")
|
|
clearPath("blue/" .. PNG)
|
|
eq(love.filesystem.read(PNG), nil, "Red keeps the stock miss behavior")
|
|
|
|
-- Uninstall restores the stock loaders byte for byte
|
|
GameVersion.set("yellow")
|
|
love.filesystem.write("yellow/" .. PNG, "yellow-png-bytes")
|
|
Overlay.uninstall()
|
|
check(not Overlay.isInstalled(), "overlay uninstalls")
|
|
eq(love.filesystem.read(PNG), nil,
|
|
"after uninstall the stock loader no longer sees the versioned path")
|
|
|
|
-- --- ChipSynth honors audio.programPrefix (the worker exception)
|
|
local ChipSynth = require("src.core.ChipSynth")
|
|
ChipSynth.invalidateBanks()
|
|
local PROG = "assets/generated/audio/programs.bin"
|
|
local PROG_BYTES = string.rep("\0", 0x4000 * 2)
|
|
clearPath(PROG)
|
|
love.filesystem.write("yellow/" .. PROG, PROG_BYTES)
|
|
local workerData = { audio = {
|
|
programFile = PROG,
|
|
programPrefix = "yellow/",
|
|
bankOrder = { 1, 2 },
|
|
} }
|
|
local okW, wbanks = pcall(ChipSynth._loadBanksForTest, workerData)
|
|
check(okW and wbanks ~= nil, "loadBanks uses audio.programPrefix when set")
|
|
if okW and wbanks then
|
|
eq(wbanks[1], PROG_BYTES:sub(1, 0x4000),
|
|
"programPrefix loads the bank 1 bytes from the versioned file")
|
|
end
|
|
|
|
-- Without programPrefix the sync path relies on the overlay/mount: with the
|
|
-- overlay uninstalled (this test process), the plain read misses.
|
|
ChipSynth.invalidateBanks()
|
|
local plainData = { audio = { programFile = PROG, bankOrder = { 1, 2 } } }
|
|
local okP = pcall(ChipSynth._loadBanksForTest, plainData)
|
|
check(not okP, "without programPrefix or overlay, programs.bin is a clean miss")
|
|
|
|
-- --- ChipAudio.slimAudio hands the NX prefix to the worker payload
|
|
setOS("NX")
|
|
GameVersion.set("yellow")
|
|
local ChipAudio = require("src.core.ChipAudio")
|
|
local slim = ChipAudio._slimAudioForTest(plainData)
|
|
eq(slim.programPrefix, "yellow/",
|
|
"slimAudio passes the NX cache prefix to the worker")
|
|
setOS("OS X")
|
|
local slimDesktop = ChipAudio._slimAudioForTest(plainData)
|
|
eq(slimDesktop.programPrefix, nil,
|
|
"desktop worker payloads carry no prefix (mount owns the overlay)")
|
|
|
|
clearPath("yellow/" .. PNG)
|
|
clearPath("yellow/" .. PROG)
|
|
|
|
love.system = savedSystem
|
|
Platform._resetForTests()
|
|
GameVersion.set(savedVersion)
|
|
Assets.flush()
|
|
|
|
T.finish()
|