From f099593136daa6c487c772252c2fd4f4ea4ab255 Mon Sep 17 00:00:00 2001 From: Andrew Quenehen Date: Mon, 3 Aug 2026 15:01:58 -0300 Subject: [PATCH] fix(switch): write nx-asset-probe.log on every NX Play Capture resolve paths and newImage open results for Yellow/Blue art triage without enabling switch-debug.txt. Co-authored-by: Cursor --- docs/switch-development.md | 2 + main.lua | 5 ++ src/debug/SwitchDiagnostics.lua | 87 ++++++++++++++++++++++++ tests/engine/switch_diagnostics_test.lua | 35 ++++++++++ 4 files changed, 129 insertions(+) diff --git a/docs/switch-development.md b/docs/switch-development.md index 87272d6a..b288d43b 100644 --- a/docs/switch-development.md +++ b/docs/switch-development.md @@ -432,6 +432,8 @@ Community mod zip install smoke (MODS inbox + Play): NXMOD-12 in [switch-hardwar **Opt-in diagnostics:** create an empty `switch-debug.txt` in the save directory; events flush to `switch.log` at ≤1 Hz with build identity (no ROM/save bytes). +**NX asset probe (always on Play):** every Switch Play writes `nx-asset-probe.log` in the save directory (`pokemon-love2d/`). It lists whether `assets/generated/…` vs `yellow|blue/assets/generated/…` exist, what `Assets.resolve` returns, and whether `newImage` / `newImageData` open — for Yellow/Blue blank-sprite triage. No ROM bytes. + **Hardware re-test:** T16 **pass** @ `2699c9a` (naming A=confirm / B=cancel). T19 **pass** (quit/reopen, suspend×10, reboot) — operator 2026-08-01. **Suspend/resume audio:** after resume, chip music is stopped to avoid duplicate streams; confirm on hardware during P0-09/10 (T19). diff --git a/main.lua b/main.lua index ad7d7183..77290f26 100644 --- a/main.lua +++ b/main.lua @@ -188,6 +188,11 @@ local function bootGame(version) -- (Blue/Yellow caches live under blue/ / yellow/). CacheFs.prefix = GameVersion.cachePrefix() CacheFs.mountVersion(GameVersion.get()) + -- NX: always write nx-asset-probe.log so Yellow/Blue art failures are + -- diagnosable from the SD without enabling switch-debug.txt. + pcall(function() + require("src.debug.SwitchDiagnostics").probeAssets(GameVersion.get()) + end) if love.window and love.window.setTitle then local Version = require("src.core.Version") love.window.setTitle(Version.title( diff --git a/src/debug/SwitchDiagnostics.lua b/src/debug/SwitchDiagnostics.lua index b5b34794..e40cb6fc 100644 --- a/src/debug/SwitchDiagnostics.lua +++ b/src/debug/SwitchDiagnostics.lua @@ -171,4 +171,91 @@ function SwitchDiagnostics.maybeFlush(force, now) filesystem.write(LOG_FILE, table.concat(lines, "\n") .. "\n") end +-- One-shot NX asset probe written on every Play. No ROM/save bytes — only +-- paths, sizes, resolve results, and whether newImage/newImageData open. +-- Pull sdmc:.../pokemon-love2d/nx-asset-probe.log after a Yellow boot. +local PROBE_LOG = "nx-asset-probe.log" + +local function probeInfo(filesystem, path) + local info = filesystem.getInfo(path) + if not info then return "missing" end + local size = info.size + if size == nil then + local bytes = filesystem.read(path) + size = type(bytes) == "string" and #bytes or -1 + end + return ("type=%s size=%s"):format(tostring(info.type), tostring(size)) +end + +local function probeOpen(kind, path) + if kind == "image" then + local ok, err = pcall(love.graphics.newImage, path) + return ok and "ok" or ("FAIL " .. tostring(err):gsub("%s+", " "):sub(1, 160)) + end + if not (love.image and love.image.newImageData) then return "skip-no-imageData" end + local ok, err = pcall(love.image.newImageData, path) + return ok and "ok" or ("FAIL " .. tostring(err):gsub("%s+", " "):sub(1, 160)) +end + +function SwitchDiagnostics.probeAssets(version) + local Platform = require("src.core.Platform") + if not Platform.isNX() then return end + local filesystem = fs() + if not filesystem then return end + + local GameVersion = require("src.core.GameVersion") + local Assets = require("src.render.Assets") + local prefix = GameVersion.cachePrefix(version or GameVersion.get()) + local lines = { + SwitchDiagnostics.identityOverlay(), + "probe=nx-asset", + "version=" .. tostring(version or GameVersion.get()), + "cachePrefix=" .. tostring(prefix), + "isNX=" .. tostring(Platform.isNX()), + "saveDir=" .. tostring(filesystem.getSaveDirectory and filesystem.getSaveDirectory() or "?"), + } + + local samples = { + "assets/generated/fonts/font.png", + "assets/generated/tilesets/reds_house.png", + "assets/generated/sprites/red.png", + "assets/generated/sprites/monster.png", + } + for _, path in ipairs(samples) do + local versioned = prefix ~= "" and (prefix .. path) or path + local resolved = Assets.resolve(path) + lines[#lines + 1] = ("--- %s"):format(path) + lines[#lines + 1] = "unprefixed=" .. probeInfo(filesystem, path) + if prefix ~= "" then + lines[#lines + 1] = "versioned=" .. probeInfo(filesystem, versioned) + end + lines[#lines + 1] = "resolve=" .. tostring(resolved) + lines[#lines + 1] = "newImage=" .. probeOpen("image", resolved) + lines[#lines + 1] = "newImageData=" .. probeOpen("imageData", resolved) + if prefix ~= "" and resolved ~= versioned then + lines[#lines + 1] = "newImage_versioned=" .. probeOpen("image", versioned) + lines[#lines + 1] = "newImageData_versioned=" .. probeOpen("imageData", versioned) + end + end + + -- Shallow listing so we can see if the extract tree exists at all. + local roots = { "yellow", "blue", "assets", "yellow/assets/generated", + "yellow/assets/generated/sprites", "blue/assets/generated/sprites" } + for _, dir in ipairs(roots) do + local info = filesystem.getInfo(dir) + if info and info.type == "directory" and filesystem.getDirectoryItems then + local items = filesystem.getDirectoryItems(dir) or {} + local n = math.min(8, #items) + local head = {} + for i = 1, n do head[i] = items[i] end + lines[#lines + 1] = ("list %s count=%d head=%s"):format( + dir, #items, table.concat(head, ",")) + else + lines[#lines + 1] = ("list %s %s"):format(dir, info and info.type or "missing") + end + end + + filesystem.write(PROBE_LOG, table.concat(lines, "\n") .. "\n") +end + return SwitchDiagnostics diff --git a/tests/engine/switch_diagnostics_test.lua b/tests/engine/switch_diagnostics_test.lua index 58b3c173..8c781f6e 100644 --- a/tests/engine/switch_diagnostics_test.lua +++ b/tests/engine/switch_diagnostics_test.lua @@ -70,4 +70,39 @@ check(errLog:find("missing module", 1, true) ~= nil, check(errLog:find("Import again", 1, true) ~= nil, "lua-error.log preserves lines after newline") +-- NX asset probe: always writes nx-asset-probe.log on Play (Switch only). +local Platform = require("src.core.Platform") +local GameVersion = require("src.core.GameVersion") +local savedSystem = love.system +love.system = { getOS = function() return "NX" end } +Platform._resetForTests() +GameVersion.set("yellow") +love.filesystem.write("yellow/assets/generated/fonts/font.png", "font-bytes") +love.filesystem.write("yellow/assets/generated/tilesets/reds_house.png", "house-bytes") +love.filesystem.write("yellow/assets/generated/sprites/red.png", "red-bytes") +SwitchDiagnostics.probeAssets("yellow") +local probe = love.filesystem.read("nx-asset-probe.log") or "" +check(probe:find("probe=nx-asset", 1, true) ~= nil, "probe log writes header") +check(probe:find("cachePrefix=yellow/", 1, true) ~= nil, "probe records yellow prefix") +check(probe:find("resolve=yellow/assets/generated/fonts/font.png", 1, true) ~= nil + or probe:find("versioned=type=file", 1, true) ~= nil, + "probe records versioned font path visibility") +check(not probe:find(string.char(0xEA, 0x9B), 1, true), + "probe log contains no ROM-like binary") + +love.system = { getOS = function() return "OS X" end } +Platform._resetForTests() +love.filesystem.remove("nx-asset-probe.log") +SwitchDiagnostics.probeAssets("yellow") +check(love.filesystem.read("nx-asset-probe.log") == nil, + "probe is a no-op off NX") + +love.system = savedSystem +Platform._resetForTests() +GameVersion.set("red") +love.filesystem.remove("nx-asset-probe.log") +love.filesystem.remove("yellow/assets/generated/fonts/font.png") +love.filesystem.remove("yellow/assets/generated/tilesets/reds_house.png") +love.filesystem.remove("yellow/assets/generated/sprites/red.png") + T.finish()