The launcher spent ~9ms per frame building and drawing, and the Find Mods
tab could hang the window for minutes. Both had the same root cause: a
retained UI tree rebuilt every frame, and blocking curl calls made from the
draw path.
Replace the vendored FlexLove engine (28.5k lines) with src/ui/kit/ (Kit,
Theme, Layout, Loader). The kit caches Text objects and all measurement,
allocates nothing in the steady state, and draws flat. Build+draw is now
under 1ms at every window size and on every tab (POKEPORT_LAUNCHER_PROF).
Move every network call off the render thread onto a love.thread pool
(src/net/Fetch.lua): mod index fetches, per-mod release checks, find-tab
stats, thumbnails and mod installs. Mod indexes prewarm at boot so the
Find Mods tab is populated before it is opened.
Paginate every list -- mods, find, save slots, settings, release notes,
versions -- with the page size derived from the real viewport height, so a
500-mod index costs what a 10-mod one does. Scrolling is gone.
Anything that waits now raises a non-dismissable loader; per-row background
work shows an inline spinner instead. The in-app updater moves to the top
right beside the settings gear and pulses when an update is waiting.
Theme is black with white outlines, no gradients or glows, and solid
colour-coded embossed buttons with bold labels. The game tabs keep their
cartridge colours. Everything is 1.3x larger. The save editor shares the
theme, and adding an item there is now a searchable pop-up like adding a
Pokemon.
Also:
- Reset rebinds, in Settings and under Touch Controls. Rebinds are additive
(Input:applyBindings layers them over the defaults), so there was no
in-game way to undo one.
- Launch options: --game red [--slot N] / POKEPORT_GAME boots straight into
a game for shortcuts and frontends, falling back to that game's tab when
its ROM is not imported.
Fixes found while porting:
- Ellipsis and letterspacing truncated bytes, not codepoints, so a
multi-byte mod name crashed the first frame on a Japanese index.
Measurement no longer throws on malformed input either.
- The new font set missed UiFont's kana fallback, rendering translated
builds as tofu.
- Fetch workers idle in Channel:demand() and LOVE waits for live threads at
exit, so the process outlived the window; quitting mid-download also
waited on curl's 300s ceiling. Shut the pool down in love.quit and bound
its transfer timeouts.
- In one column the save-slot card drew below the fold, over the footer,
with no scrollbar left to reach it.
The two FlexLove engine tests guarded a scroll manager and an auto-height
propagation bug that no longer exist; replace them with a kit suite covering
page bounds, viewport sizing and UTF-8 truncation, and retarget the NX test
to assert the dependency is gone rather than that its perf guards are set.
the launcher runs before Game:load, so #767's Strings hooks had nothing
filling the catalog and a restart could not help - the ordering is the same
every launch. reads lang/strings.lua from enabled mods only, data not the
entry chunk, sandboxed.
plus a plain pixel fallback on the ui faces, or the kana draw as tofu.
Opening Touch Controls dropped the launcher virtual cursor and swallowed
gamepad input while touch still worked. Share PadCursor with the save
editor, forward pad events, and centralize overlay handoff/resume so both
hosts park and re-arm the pointer cleanly.
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>
Capture resolve paths and newImage open results for Yellow/Blue art
triage without enabling switch-debug.txt.
Co-authored-by: Cursor <cursoragent@cursor.com>
Unlock love-nx SDL dock/undock resizing and sync via NxDisplay so
booting docked is not stuck on the conf 720p hint.
Co-authored-by: Cursor <cursoragent@cursor.com>
Skip per-frame mouse warps and FlexLove perf sampling on Switch, feed pad coords through a getPosition bridge, and park that shim before the save editor so desktop paths stay unchanged.
Co-authored-by: Cursor <cursoragent@cursor.com>
Bring FlexLove launcher UI ("big ui moment"), iOS picker dismiss, and Metal updates into the Switch NX branch. Keep pack_love (with libs/), NX inbox/rescan paths, SwitchDiagnostics, and Save Editor PadInput; port Scan again labels into LauncherView.
Co-authored-by: Cursor <cursoragent@cursor.com>
Route gamepad/touch into the editor instead of dropping them in editorMode,
and hit-test clicks at event coords so a finger tap is not lost under the
Joy-Con virtual cursor.
Co-authored-by: Cursor <cursoragent@cursor.com>
Bring feat/switch-nx up to date with origin/dev (72 commits). Resolve
Input/RomImporter conflicts by keeping GamepadMap (NX face remap + dual-path
gate) while adopting upstream joyBindings rebinds (#632) and Enable-all mods
(#647). Gate shoulder GAME SPEED hotkeys when Select is held so Select+L
display chords still work.
Co-authored-by: Cursor <cursoragent@cursor.com>
NX Play for Blue failed because mountVersion relied on absolute
PHYSFS_mount first. Prefer love.filesystem.mount of blue|yellow, overlay
generated trees by version prefix, and align CacheFs.prefix in bootGame.
Co-authored-by: Cursor <cursoragent@cursor.com>
Makes the iOS build a first-class citizen: ROM/mod/save import through
the system document picker (the README's missing "UIDocumentPicker
handoff"), Files-app drop-in support, and an opt-in Apple Health
step-sync seam consumed by a new gallery mod (Pokewalker).
Native layer (mobile/ios/native/, wired by mobile/ios/patch_love_src.py
on every build, so the fetched love-src tree stays pristine + re-patchable):
- GRPickerBridge.swift: love.system.pickFile("rom"|"mod"|"sav") and
love.system.createFile on iOS with the same contract as love-android's
SAF picker (picked_rom.gb / picked_mod.zip / picked_save.sav /
export_done.flag in the save dir). Reached from wrap_System.cpp via the
ObjC runtime, so liblove needs no Swift interop.
- GRBootstrap.m: sweeps .gb/.gbc/.zip/.sav dropped in Documents (Files
app / Finder sharing) into the save dir on every activation;
UIFileSharingEnabled + LSSupportsOpeningDocumentsInPlace in the plist
overlay. Drop a ROM, open the app, it imports with zero taps.
- GRHealthBridge.swift: love.system.syncHealthSteps() -> read-only
HealthKit step query anchored to the last sync, delivered as
steps_pending.json (merge-not-overwrite). HealthKit entitlement +
usage description included.
Lua:
- RomImporter: iOS rides the Android mobile flows; a 0.5s poll consumes
picker deliveries (iOS pickers are in-process modals, so the Android
refocus rescan never fires); failed pick copies surface as an
on-screen notice via pick_error.txt.
- main.lua: on iOS, stop forwarding touchpressed to the Importer - LOVE
already synthesizes a mousepressed for the primary touch, and the
resulting same-frame double-present made the document picker
auto-dismiss with zero documents (silent import failure).
- mods/pokewalker: opt-in Pokewalker mod (manifest v2, MECHANIC,
permissions declared, mod.card, CHANGELOG, headless test suite 9/9,
modkit validate --base imported + lint clean). Fused into iOS
game.love only; loads dormant anywhere without the bridge.
Build (scripts/build_ios.sh):
- Fix Xcode 26: the global PRODUCT_NAME override also renamed liblove.a
and broke the app link; the app bundle is renamed after the build
instead.
- Fix nondeterministic pack failures: grep -q + pipefail races SIGPIPE
on the game.love content checks.
- Simulator builds sign ad-hoc so entitlements embed (HealthKit works in
the simulator).
- Device builds: signing team auto-detected from the keychain,
CODE_SIGN_STYLE=Automatic + -allowProvisioningUpdates for CLI-only
provisioning, per-team derived bundle ID (explicit App IDs are
globally unique, so third parties can't sign the project default),
gitignored mobile/ios/bundle_id.local pin, and --install to push to a
connected iPhone.
- docs/ios-install.md: a zero-knowledge walkthrough from bare Mac to
playing on an iPhone.
Backward compatibility: no behavior change on desktop or Android. The
new love.system functions exist only under LOVE_IOS; RomImporter's
mobile flag simply includes iOS alongside Android; the main.lua change
is iOS-gated; the Pokewalker mod is packed only by the iOS build script
and its option defaults off.
Verified on an iPhone 17 Pro simulator and an iPhone 16 Pro device:
scripted ROM import to title screen, Files-drop zero-tap import,
picker-driven mod install and save import/export, HealthKit permission
sheet + step credit (4000 steps -> +200 EXP at the default rate through
the engine growth curve).
The reporter labels runs by abusing the in-game player name; give slots
a real label instead. SaveData.renameSlot persists a trimmed label in
the options registry (options.saveSlots[version].names) -- never in the
save file, so renaming needs no save rewrite and an empty slot can be
labeled too -- listSlots rows carry it as `label`, and deleteSlot
drops it with the slot.
In the launcher, right-clicking a slot row opens an inline rename modal
(Enter commits, Esc cancels, empty clears; 24 whole-codepoint cap via
local UTF-8 helpers, since plain luajit has no utf8 library). The row
title shows the label over the player name; badges/time/caught stay on
the meta line. Desktop-only: touch has no secondary button. main.lua
now forwards love.textinput to the importer while it is up.
Backend covered by a new renameSlot block in tests/engine/save_slots.lua
(78/78); docs/launcher.md's registry section documents the label.
# Closed issues
CLOSES#17: Incorrect Character Visuals (Only with GBC filter)
CLOSES#23: Could you allow the player to change the order of the moves
CLOSES#24: Evolution music not playing during evolution
CLOSES#26: Standing on door glitch
CLOSES#27: Battle Intro text automatically continues
CLOSES#29: Visual bug when zoomed out
CLOSES#32: Team Rocket recruiter doesn't battle with you unless you speak with him first
CLOSES#33: Developer/Debug Console
CLOSES#35: Professor Oak's introduction Inaccuracies
CLOSES#36: Missing Pokemon Dex entries when picking starter + Rival Pathing issues
CLOSES#39: Guy who stops player from skipping brock doesn't bring you to brock's gym + doesn't leave once you've beaten brock
CLOSES#40: Bill cutscene is broken
CLOSES#41: Ticket guy failing to be a Ticket guy
CLOSES#42: S.S. anne odd behavior + Missing sailing away animation
CLOSES#43: Dig Attack animation appears to be glitched
CLOSES#44: Pokeball flashing doesn't appear to be accurate
CLOSES#45: Inaccurate Cut Animation
CLOSES#46: Dugtrio i caught in diglett cave has two of the same move
CLOSES#47: Rival ignores player in Lavender Tower
CLOSES#48: Healing pad in lavender tower does not function
CLOSES#49: Incorrect dialogue with parched security guard
CLOSES#50: (Game Breaking!) Rocket grunt guarding poster refuses to move
CLOSES#51: Badges showing up as items I can deposit in PC
CLOSES#52: Visual bug on Celadon Department Store roof (Red Filter)
CLOSES#54: Visual issue on route 15 + Fuchsia City
CLOSES#56: Running animation missing
CLOSES#57: Safari Zone does not display steps while you are inside of it
CLOSES#58: (Game Breaking!) Softlock at cycling road gate
CLOSES#59: Bike visual issues
CLOSES#60: Cycling road not forcing you to get on your bike
CLOSES#61: No keycard doors in Silph Co.
CLOSES#63: Missing teleporter animation
CLOSES#64: Inaccurate spinning
CLOSES#65: Reimplement unused Silph Co. Chief and Professor Oak trainer battles