Files
gen1recomp/docs/switch-development.md
T
Andrew Quenehen 8654444d1a docs(switch): document WIP status, OLED-only limits, and manual deploy
Clarify that Mac+OpenMTP is contributor tooling (not the final product
contract), record Dusklight method references, and surface how/what we
tested so reviewers and the community can help finish the Switch port.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 06:07:18 -03:00

24 KiB
Raw Blame History

Nintendo Switch development (love-nx)

Status: work in progress — not a finished Switch release.
Tracks experimental support for issue #531. Expect rough edges, manual steps, and host-specific contributor tooling. Do not treat this as a packaged product yet.

Gen1Recomp on Nintendo Switch runs on a pinned love-nx runtime. This document covers what landed so far, known limitations, how hardware was tested, vendor layout, build/deploy, and the current contributor transfer loop.

Current status (honest)

Area State
Feature completeness In development — playable P0 path on one console; not finished or release-gated
Runtime Pinned love-nx 11.5-nx1
Product artifact goal Single fused gen1recomp.nro (game in romfs); loose nro+game.love for iteration
Hardware validated Nintendo Switch OLED only (title override / full memory). Original Switch, Lite, docked mode, and other hosts are untested
Deploy / install Fully manual today — build on a host, copy artifacts by hand; no CI Switch job, no one-click installer, no nxlink/netloader path
Contributor host used macOS + OpenMTP + DBI MTP (see below — temporary coupling)
Network features on NX Self-update / remote mod download disabled (networkValidated == false)
Community help Welcome — especially from people familiar with HOS / love-nx / Switch homebrew packaging

What this branch already does

  • Detect NX via src/core/Platform.lua without reusing Android flags
  • Writable ROM inbox under getSaveDirectory()/imports/ + “Procurar novamente”
  • Joy-Con / gamepad mapping shared by launcher and gameplay (Nintendo A/B UX on NX)
  • Focus loss / joystick reconnect recovery; opt-in switch-debug.txt diagnostics
  • Loose assemble + fused NRO build scripts (scripts/build_switch.sh, scripts/switch/*)
  • Payload gates so ROM / generated cache / saves never enter game.love
  • Community mod zip inbox at imports/mods/ (rescan installs; FIND MODS stays network-gated)
  • Select+face display chords (COLORS / TILT / pipelines) on Joy-Con
  • Hardware evidence for Phase 0 probe, ROM import, naming A/B, save/suspend, fused NRO — see docs/switch-hardware-evidence.md

What is still unfinished / out of this draft

  • Official release packaging and automated Switch CI
  • Cross-host contributor docs (Linux/Windows MTP clients) and less Mac-centric language in player-facing UX
  • Docked vs handheld soak, long-play soak, non-OLED hardware
  • Pro Controller / third-party pad matrices beyond the OLED Joy-Con path already measured
  • VoxelMod (and other community mods) OLED smoke still pending in the evidence scaffold
  • Applet Mode remains unsupported by design (title override required)

Design references (Dusklight)

This work borrowed method — not the native stack — from the Dusklight Switch port, especially LESSONS_AND_REUSE.md:

Dusklight lesson How Gen1Recomp applied it
Emulators hide Tegra failures Gate milestones on real OLED hardware, not Ryujinx/Yuzu alone
Prove the lower layer first tools/switch-probe before full launcher
Know which binary ran Embedded build-info.json (commit / love-nx tag)
Cap continuous logs Opt-in diagnostics, ≤1 Hz flush; Lua error log rotation
Crash symbolization needs the exact ELF Keep pinned love.elf with the NRO under test
Full memory matters Title override; Applet Mode is not the validation path
Do not treat SD FS like desktop POSIX Lua stays on love.filesystem; inbox + MTP for user files
Isolate platform code Capability module instead of Android flag overload
NVK / WSI / audren stacks Not copied — love-nx already supplies video/audio/input/FS

Goal for a finished release is closer to Dusklights single self-contained .nro, not a permanent Mac-only contributor toolchain.

Known limitations (read before reviewing)

  1. Mac + OpenMTP coupling is a current contributor workflow, not the final product contract. Runtime only needs files under the LÖVE save directory / NRO install folder. Players on other OSes should eventually use any reliable MTP (or future) path that lands files in the same places. Todays runbook documents the operators Mac loop because that is what was actually used and tested — do not freeze “macOS + OpenMTP only” into the shipped UX.
  2. Deploy is manual. There is no automated push to the console. Operators build locally, open DBI MTP, copy with a client, exit MTP, then title-override launch. That is intentional for this draft and should improve before a real Switch release.
  3. OLED-only evidence. All pass rows in the P0/P1 matrix were recorded on one Switch OLED. Treat other hardware as unknown until someone re-runs the checklist.
  4. No ROM/save/mod zip bytes in git. Legal dumps and third-party mods stay on the console (or local untracked folders).
  5. AppleDouble sidecars (._*) from macOS MTP clients can break zip/ROM scans — the launcher skips hidden .* names; still prefer clean copies.

How we tested

Layer What Where
Unit / headless Platform NX flags, RomImporter inbox, dual-path input, mod zip inbox, display chords, payload/self-tests tests/*, scripts/test.sh
Probe on hardware getOS()==NX, 1280×720, save path, Joy-Con events tools/switch-probe → OLED
Integration on hardware MTP inbox ROM import, Play Red/Blue, naming A/B, quit/reopen save, suspend×10, reboot, fused NRO alone + NRO-only update docs/switch-hardware-evidence.md
Not done yet Docked soak, ≥30 min long-play, non-OLED, automated deploy, VoxelMod smoke fill-in Matrix deferred / pending rows

Operator evidence must stay in docs/switch-hardware-evidence.md. Do not invent passes for hardware not run.

love-nx 11.5-nx1 (pinned)

Tag: 11.5-nx1

Local layout (not committed):

.bazinga/love-nx/11.5-nx1/
├── love.nro    # homebrew launcher binary (loose mode: copied to gen1recomp.nro)
└── love.elf    # required for fused NRO builds (devkitPro nacptool/elf2nro)

Manifest: scripts/switch/love-nx-11.5-nx1.sha256 lists expected artifact names and SHA-256 checksums. Checksums are filled when binaries are fetched (TBD_* placeholders until then).

Fetch instructions

  1. Open the 11.5-nx1 release and download love.nro and love.elf.

  2. Create the directory: mkdir -p .bazinga/love-nx/11.5-nx1

  3. Move both files into that directory.

  4. Record checksums and update the manifest:

    shasum -a 256 .bazinga/love-nx/11.5-nx1/love.nro \
      .bazinga/love-nx/11.5-nx1/love.elf
    
  5. Replace the TBD_* lines in scripts/switch/love-nx-11.5-nx1.sha256 with the real hashes.

Never commit love-nx binaries, ROM dumps, or generated cache into git. The repo .gitignore excludes .bazinga/ (vendor cache) and /dist/ (build output).

Loose-mode dist layout

Development builds place gen1recomp.nro and game.love side by side:

dist/switch/loose/
├── gen1recomp.nro
└── game.love

Assemble with:

scripts/build_switch.sh --loose

(See scripts/switch/assemble_loose.sh for the underlying copy + checksum step.)

Transfer & deploy (current contributor loop)

Product intent vs todays tooling

Layer Intent
Runtime / players Put the NRO under sdmc:/switch/gen1recomp/ (or equivalent) and land ROMs/mods under the save-dir inboxes. The game does not hard-depend on OpenMTP or macOS.
This drafts operator loop Manual USB MTP via DBI → Run MTP responder on the Switch and OpenMTP on the Mac used for development. Fully manual — no CI deploy, no scripted push.

Treat the Mac + OpenMTP steps below as documented operator procedure for reproducing OLED evidence, not as a permanent “Switch port requires macOS” product rule. Contributions that add Linux/Windows MTP notes or safer automated deploy (without smuggling ROMs into git) are welcome.

Still avoided in this drafts evidence workflow (keeps SD in-console and avoids false POSIX /Volumes assumptions while iterating):

  • Removing the microSD card to mount it on the host for routine deploys
  • Relying on FTP / Sphaira / ad-hoc network shares as the only verified path for this branchs hashes
  • Treating nxlink / netloader as the release deploy story (not wired here yet)
  • DBI MicroSD install / NAND install / NSP-style virtual folders for the .love/.nro pair

If MTP fails on the Mac loop: check cable, USB port, DBI state, and that only one MTP client holds the device — then retry. Do not silently rewrite evidence using an untested path and claim parity with the recorded SHA-256 round-trips.

Manual deploy checklist (today)

  1. Build on the contributor host (scripts/build_switch.sh --loose or fused).
  2. Close Gen1Recomp on the Switch; open DBI → Run MTP responder.
  3. Copy artifacts with your MTP client into 1: SD Card/switch/gen1recomp/ (and ROMs/mods into the save-dir inboxes when needed).
  4. Wait for the transfer queue; refresh; optionally round-trip SHA-256 on first artifacts of a type.
  5. Exit MTP; launch via title override (hold R on a title → hbmenu, not Applet Mode).

OpenMTP + DBI transfer (loose build, Mac operator)

On the Switch

  1. Close Gen1Recomp if it is running.
  2. Open DBI from hbmenu.
  3. Select Run MTP responder (DBI documents X on the main screen).
  4. Keep DBI on that screen for the entire transfer.
  5. Connect the Switch to the Mac with a USB-C data cable.

On the Mac

  1. Close any other MTP clients.

  2. Open OpenMTP and select the DBI device.

  3. In the remote pane, open 1: SD Card.

  4. Navigate to switch/ and create gen1recomp/ if needed.

  5. Enter 1: SD Card/switch/gen1recomp/.

  6. Drag from the local pane:

    dist/switch/loose/gen1recomp.nro
    dist/switch/loose/game.love
    
  7. Wait for the OpenMTP queue to finish completely.

  8. Refresh the remote listing and confirm file sizes match the local files.

  9. On the Switch, exit MTP responder normally in DBI before launching the app.

Expected layout on SD:

1: SD Card/
└── switch/
    └── gen1recomp/
        ├── gen1recomp.nro
        └── game.love

Round-trip SHA-256 verification

For the first deploy of each artifact type (loose pair, later fused NRO), verify MTP integrity:

  1. Before send — record local hashes:

    shasum -a 256 dist/switch/loose/gen1recomp.nro \
      dist/switch/loose/game.love
    
  2. After send — in OpenMTP, copy the same files from 1: SD Card/switch/gen1recomp/ back to an empty local folder, e.g. dist/switch/mtp-roundtrip/.

  3. Compare round-trip hashes:

    shasum -a 256 dist/switch/mtp-roundtrip/gen1recomp.nro \
      dist/switch/mtp-roundtrip/game.love
    
  4. Local pre-send and round-trip hashes must match. Record results in the test report template below.

Repeat whenever a cable glitch or interrupted transfer is suspected.

Title override launch (full memory)

Applet Mode is not the primary validation path. Use title override so hbmenu runs with full memory:

  1. Confirm the OpenMTP transfer queue finished.
  2. Exit MTP responder in DBI; disconnect USB if desired.
  3. Hold R while launching any legitimately installed title.
  4. Keep holding until hbmenu appears.
  5. Confirm hbmenu does not show Applet Mode.
  6. Launch gen1recomp (or the probe NRO during Phase 0).

Album / applet launches are only useful to document applet-specific limitations; P0/P1 gates use title override.

Phase 0 hardware checklist

Complete in order on OLED hardware. Operator fills evidence fields — leave blank until tested.

Step Action Pass Evidence / notes
P0-0a Fetch love-nx 11.5-nx1; record manifest SHA-256 yes See scripts/switch/love-nx-11.5-nx1.sha256
P0-0b Build switch-probe.love per tools/switch-probe/README.md yes
P0-0c Assemble loose probe (game.love = probe) to dist/switch/loose/ yes
P0-0d MTP deploy to 1: SD Card/switch/gen1recomp/; round-trip SHA-256 yes nro 8290ac15…5918f5; love 9f198637…fa2e34f
P0-0e Title override → probe boots; getOS() shows NX yes getOS()=NX, love._os=NX
P0-0f Probe lists 1280×720 (or documented dims), save path, gamepad/touch log yes save sdmc:/switch/gen1recomp/switch-probe; Joy-Con Y→#3 X→#4
P0-1a Replace game.love with unpatched Gen1Recomp build yes feat/switch-nx inbox build
P0-1b MTP replace game.love only; round-trip SHA-256 yes
P0-1c Title override → launcher reaches import screen yes
P0-1d Joy-Con: can navigate launcher (no touch-only) yes Full report: docs/switch-hardware-evidence.md

Operator: Andrew Date: 2026-08-01 Console: Switch OLED only
Deploy: manual Mac + OpenMTP + DBI MTP (not automated)
love-nx tag: 11.5-nx1 gen1recomp commit: df7cea4

Phase 0 test report template

Copy this block into your hardware notes or PR evidence. Do not commit ROM files or ROM hashes of private dumps.

## Switch Phase 0 — hardware report

- Operator:
- Date:
- Console model:
- Atmosphère / HOS version:
- gen1recomp commit:
- love-nx tag: 11.5-nx1
- love.nro SHA-256 (local):
- game.love SHA-256 (local, pre-send):
- MTP round-trip SHA-256 (gen1recomp.nro):
- MTP round-trip SHA-256 (game.love):
- Title override used: yes / no
- Applet Mode observed: yes / no (should be no for P0)
- Probe getOS():
- Probe dimensions:
- Probe save directory shown:
- Gamepad events logged: yes / no
- Touch events logged: yes / no
- Unpatched launcher boot: pass / fail
- Joy-Con launcher navigation: pass / fail / not tested
- Notes:

Fast dev loop (loose mode)

While iterating on Lua/assets:

  1. Edit on Mac; run scripts/test.sh --quick.
  2. Rebuild .bazinga/work/game.love (scripts/build.sh mac --no-notarize or project pack step).
  3. Close Gen1Recomp on Switch.
  4. DBI → Run MTP responder.
  5. OpenMTP → 1: SD Card/switch/gen1recomp/.
  6. Replace only game.love; wait for queue + refresh listing.
  7. Exit MTP responder; launch via title override.
  8. Keep gen1recomp.nro unchanged until the love-nx pin changes.
scripts/test.sh --quick
scripts/build.sh mac --no-notarize
scripts/build_switch.sh --loose
shasum -a 256 .bazinga/work/game.love

Controller input mapping (NX)

Measured on Switch OLED (feat/switch-nx, love-nx 11.5-nx1, 1280×720). Both joystickpressed and gamepadpressed fire for Joy-Con; prefer the gamepad path when joystick:isGamepad() is true.

Path Control Mapping
gamepadpressed D-pad / left stick move
gamepadpressed SDL a / b on NX swapped via NX_GAMEPAD_BINDINGS: physical A (east) = GB A confirm, physical B (south) = GB B cancel
gamepadpressed SDL a / b on desktop identity (SDL south = GB A)
gamepadpressed start / back Start / Select
joystickpressed (raw) only if not isGamepad() face/menu fallback
joystickpressed (raw) #1 / #2 on NX Nintendo B / A → GB B / A
joystickpressed (raw) #9 / #10 Select / Start ( / +)

Nintendo UX on Switch: physical A confirms, physical B cancels (explicit NX remap of SDL face labels).

Dual-path rule: love-nx emits both gamepadpressed and joystickpressed for Joy-Con. When joystick:isGamepad() is true, Input and RomImporter ignore raw face/menu so NamingScreen does not see A+B in one frame. NamingScreen also prefers A over B if both edges still fire.

Implementation: src/core/GamepadMap.lua (NX_RAW_*, ignoreRawForJoystick, displayChordDigit). Launcher and gameplay share the same converter.

Mod zip inbox (NX)

Community mods install from a separate MTP inbox (not mixed into the ROM imports/ scan):

Item Value
Save-relative path imports/mods/
MTP destination 1: SD Card/<save identity>/imports/mods/ (see launcher notice for the live getSaveDirectory() path)
Candidates *.zip only
Rescan MODS tab → Procurar novamente (installs each zip via LauncherMods.installZip; source zips are retained on success and failure)
FIND MODS Remains network-gated / hidden on NX (networkValidated == false)

Do not commit third-party mod zip bytes into git. Drop the zip over MTP, rescan, enable in MODS, then Play.

MTP tip (esp. macOS clients): OpenMTP/Finder often creates AppleDouble sidecars named ._Something.zip / ._cart.gb. Those are not real archives or ROMs — the launcher ignores hidden .* names under both imports/ and imports/mods/. If install still fails with “could not be opened” / “not a zip file”, delete any ._* under the inbox and confirm the real zip starts with the PK magic (re-copy the release asset if unsure). This is a host-side annoyance of the current manual MTP loop, not something players should need forever.

Example zip source: DramaticShape VoxelMod releases — download a release .zip, copy into imports/mods/, rescan, enable.

Joy-Con display chords (Select + face)

PC digit hotkeys for COLORS / TILT / pipelines have Joy-Con equivalents. Hold Select (back / ) and press a face/shoulder button; the engine runs the same path as Game:keypressed for that digit (including writeOptions / Pipelines parity).

Chord (Nintendo UX) Engine key Typical effect
Select + A 2 COLORS cycle
Select + B 3 TILT / perspective cycle
Select + Y 5 GBC FX / V-GRID (mod pipeline)
Select + X 6 T-SHIFT / mod pipeline
Select + L (left shoulder) 7 V-CURVE / mod pipeline

Without Select held, face buttons keep normal GB A/B gameplay mapping (no accidental color/tilt cycles). The Options menu remains available for the same settings — chords are optional shortcuts, not the only path.

On NX, A/B chords resolve through the Nintendo UX face remap so physical A → key 2 and physical B → key 3 match this table.

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).

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).

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 consoles crash_reports/ folder on SD (reachable via the same manual MTP workflow used for game deploys).

  1. Collect — DBI → Run MTP responder; copy sdmc:/crash_reports/*.bin (or the dated subfolder) to the contributor host. Prefer keeping the microSD in-console for routine pulls during this draft.

  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.

    # 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 operators 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.

P0 / P1 hardware matrix (ADR §9)

Operator evidence lives in docs/switch-hardware-evidence.md. Do not invent passes for rows that require hardware not yet run.

ID Requirement Status Evidence
P0-0af love-nx pin, probe, MTP, title override pass Phase 0 checklist above; T4
P0-1ad Unpatched launcher boot + Joy-Con nav pass T4 / docs/switch-hardware-evidence.md
P0-02 MTP inbox import path shown pass T12
P0-03 Rescan imports ROM pass T12
P0-04 Canonical hash routes version pass T12
P0-05 Source dump retained in inbox pass T12
P0-06 Play reaches game after import pass T12
P0-07 Joy-Con launcher navigation pass T16 @ 2699c9a
P0-08 Joy-Con gameplay (incl. naming A/B) pass T16 @ 2699c9a
P0-09 Save survives quit + reopen pass T19
P0-10 ≥10 suspend cycles, no stuck input/dup audio pass T19 (operator 2026-08-01)
P0-12 Fused NRO boots without adjacent game.love pass T24 — docs/switch-hardware-evidence.md
P0-14 Fused NRO MTP round-trip SHA-256 pass T24 — first artifact b019e2e8… @ 6fb5602 (redeploy after Blue fix)
P0-15 Replace NRO only; saves persist pass T24 — operator NRO-only update keeps saves
P1-01 Docked vs handheld spot-check deferred Not exercised on OLED dock yet
P1-02 Applet Mode documented unsupported pass Title override required; Album path not validated
P1-03 Long-play soak (≥30 min) deferred No soak session recorded
P1-04 Reboot persistence pass T19
P1-05 Audio resume after suspend pass T19 (no dup audio reported)
Non-OLED hardware (original / Lite) untested OLED-only evidence so far
Automated / scripted deploy absent Manual MTP only in this draft
Non-macOS contributor MTP runbooks absent Mac+OpenMTP documented as operator loop only

Upstream contribution outline

This draft PR may still be a single large review; maintainers can split later. Suggested review slices:

Each slice should declare: WIP / not finished, no ROM/save bytes committed, love-nx pin with manifest checksums, hardware-tested rows listed (OLED only so far), Applet Mode unsupported, network/updater disabled on NX, deploy still manual, Mac+OpenMTP is contributor tooling not the final product contract.

PR 1 — Platform + import (platform/import)

  • src/core/Platform.lua, conf.lua NX branch
  • src/import/RomImporter.lua (NX flags, inbox, scan, shell/updater gates)
  • Tests: tests/platform_nx_*, tests/rom_importer_nx_*
  • Docs: inbox/MTP import sections only

PR 2 — Input + lifecycle (input/lifecycle)

  • src/core/GamepadMap.lua, Input.lua, main.lua focus/joystick hooks
  • src/debug/SwitchDiagnostics.lua (opt-in probe + error log)
  • Tests: input/diagnostics suites
  • Docs: controller mapping, suspend/audio notes

PR 3 — Build + docs (build/docs)

  • scripts/pack_love.sh, scripts/build_switch.sh, scripts/switch/*
  • assets/switch/icon.jpg, docs/switch-development.md, hardware evidence templates
  • Gates: pack_love.sh --dry-run, verify_payload.sh --self-test, fused build script (devkitPro host)

Pre-merge checklist (all PRs):

  • Manifest scripts/switch/love-nx-11.5-nx1.sha256 filled; binaries not in git
  • verify_payload.sh rejects generated cache / ROM / .sav / .bak
  • P0 matrix rows marked pass only with linked hardware evidence
  • Fused NRO P0-12/14/15 pass with T24 evidence (docs/switch-hardware-evidence.md)
  • Updater / remote mod download hidden on NX (networkValidated == false)