diff --git a/docs/switch-development.md b/docs/switch-development.md index b92db12a..8e9ce0d1 100644 --- a/docs/switch-development.md +++ b/docs/switch-development.md @@ -237,3 +237,24 @@ Implementation: `src/core/GamepadMap.lua` (`NX_RAW_*`, `ignoreRawForJoystick`). **Suspend/resume audio:** after resume, chip music is stopped to avoid duplicate streams; confirm on hardware during P0-09/10 (T19). +## Lua error log (save directory) + +On any uncaught Lua error, Gen1Recomp appends a redacted trace to `lua-error.log` in the LÖVE save directory (`love.filesystem.getSaveDirectory()`). The on-screen error overlay includes a hint pointing at that file. Logs rotate to `lua-error.log.1` when the active file exceeds 32 KiB. ROM/save bytes and non-printable data are stripped — never commit or share logs that might contain private paths without reviewing them first. + +## Native crash triage (love-nx / Atmosphère) + +love-nx native faults land under the console’s `crash_reports/` folder on SD (reachable via the same MTP workflow as game deploys). + +1. **Collect** — DBI → `Run MTP responder`; copy `sdmc:/crash_reports/*.bin` (or the dated subfolder) to the Mac. Do **not** remove the microSD card. +2. **Redact** — delete any attached screenshots or notes that mention ROM filenames, save paths, or private hashes before sharing logs publicly. +3. **Symbolize** — use the **pinned** `love.elf` from `.bazinga/love-nx/11.5-nx1/` that matches `build-info.json` / `scripts/switch/love-nx-11.5-nx1.sha256`. Never use a “latest” download. + + ```bash + # Example: aarch64-none-elf-addr2line from devkitPro + aarch64-none-elf-addr2line -e .bazinga/love-nx/11.5-nx1/love.elf -f -C 0xADDRESS_FROM_CRASH_REPORT + ``` + +4. **Correlate** — compare `gitCommit` / `loveNxTag` from embedded `build-info.json` with the operator’s hardware notes. + +If `addr2line` cannot resolve an address, archive the crash `.bin` with the exact `love.elf` SHA-256 used for the build — addresses are only meaningful against that ELF. + diff --git a/main.lua b/main.lua index fa4d1087..d1b4651d 100644 --- a/main.lua +++ b/main.lua @@ -12,6 +12,20 @@ local editorMode = os.getenv("POKEPORT_EDITOR") == "1" or POKEPORT_EDITOR_MODE = local SwitchDiagnostics = require("src.debug.SwitchDiagnostics") +-- Lua errors: persist a redacted trace in the save dir and surface a hint. +do + local defaultErrorHandler = love.errorhandler + function love.errorhandler(msg) + local hint = SwitchDiagnostics.logLuaError(msg) + if hint and type(msg) == "string" then + msg = msg .. "\n\n" .. hint + end + if defaultErrorHandler then + return defaultErrorHandler(msg) + end + end +end + local Game, EditorApp, Importer, TouchEditor local autopilot -- optional scripted-input dev tool (tests/autopilot.lua) diff --git a/scripts/switch/verify_payload.sh b/scripts/switch/verify_payload.sh index 74185ff2..11e6e524 100755 --- a/scripts/switch/verify_payload.sh +++ b/scripts/switch/verify_payload.sh @@ -70,7 +70,7 @@ verify_love() { run_self_test() { local work clean bad staging work="$(mktemp -d "${TMPDIR:-/tmp}/verify-payload.XXXXXX")" - trap 'rm -rf "$work"' EXIT + trap "rm -rf '$work'" EXIT clean="$work/clean.love" "$ROOT/scripts/pack_love.sh" --output "$clean" --listing "$work/clean-listing.txt" >/dev/null diff --git a/src/debug/SwitchDiagnostics.lua b/src/debug/SwitchDiagnostics.lua index c256475f..2499bff5 100644 --- a/src/debug/SwitchDiagnostics.lua +++ b/src/debug/SwitchDiagnostics.lua @@ -5,6 +5,9 @@ local SwitchDiagnostics = {} local MARKER = "switch-debug.txt" local LOG_FILE = "switch.log" +local ERROR_LOG = "lua-error.log" +local ERROR_LOG_ROTATED = "lua-error.log.1" +local ERROR_LOG_MAX = 32 * 1024 local FLUSH_INTERVAL = 1.0 local RING_SIZE = 64 @@ -65,6 +68,11 @@ function SwitchDiagnostics._resetForTests() bufCount = 0 lastFlushAt = -math.huge identityLine = nil + local filesystem = fs() + if filesystem then + filesystem.remove(ERROR_LOG) + filesystem.remove(ERROR_LOG_ROTATED) + end end function SwitchDiagnostics.isEnabled() @@ -122,6 +130,24 @@ function SwitchDiagnostics.onJoystickEvent(kind, joystick, button, extra) SwitchDiagnostics.onEvent(kind, payload) end +function SwitchDiagnostics.logLuaError(msg) + local filesystem = fs() + if not filesystem then return nil end + + local text = redactString(tostring(msg or "unknown error")) + local existing = filesystem.read(ERROR_LOG) or "" + if #existing > ERROR_LOG_MAX then + filesystem.write(ERROR_LOG_ROTATED, existing) + existing = "" + end + + local stamp = os.date("!%Y-%m-%dT%H:%M:%SZ") + local line = ("[%s] %s\n"):format(stamp, text) + filesystem.write(ERROR_LOG, existing .. line .. SwitchDiagnostics.identityOverlay() .. "\n") + + return "Details saved to lua-error.log in the save directory." +end + function SwitchDiagnostics.maybeFlush(force, now) if not SwitchDiagnostics.isEnabled() then return end now = now or (love and love.timer and love.timer.getTime() or 0) diff --git a/tests/engine/switch_diagnostics_test.lua b/tests/engine/switch_diagnostics_test.lua index 3e83a2dc..ca49dc59 100644 --- a/tests/engine/switch_diagnostics_test.lua +++ b/tests/engine/switch_diagnostics_test.lua @@ -50,4 +50,15 @@ local logLate = love.filesystem.read("switch.log") or "" check(not logMid:find("n=2", 1, true), "flush waits until 1s elapsed") check(logLate:find("n=2", 1, true) ~= nil, "flush includes events after 1s") +-- Lua error log: redacted, no ROM bytes. +local romErr = string.char(0xEA, 0x9B, 0xCA, 0xE6) +local hint = SwitchDiagnostics.logLuaError("probe failure") +check(type(hint) == "string" and hint:find("lua-error.log", 1, true) ~= nil, + "error handler hint mentions lua-error.log") +SwitchDiagnostics.logLuaError(romErr) +local errLog = love.filesystem.read("lua-error.log") or "" +check(errLog:find("probe failure", 1, true) ~= nil, "lua-error.log records message") +check(errLog:find("", 1, true) ~= nil, "lua-error.log strips ROM bytes") +check(not errLog:find(romErr, 1, true), "lua-error.log omits raw ROM bytes") + T.finish()