The DEX grid now defaults to Pokedex-number order with an A-Z view, set from two chips in the header. Sorting is view-only: it never dirties the save, resets the scroll, and no-ops on a re-click. Ops.dexList builds the order deterministically and sorts partial mod records last. Tests pin the orderings against the real generated data.
Save Editor
Ships inside every build. Two ways in:
From the launcher. Every SAVE SLOT row that holds a save carries an
Edit label next to Delete. Edit suspends the launcher and opens that
slot's file; Close hands the process back with the slot list re-read.
This is the path most people use, and it is wired in main.lua
(openEditor / closeEditor).
Standalone, where Close quits instead:
# from repo root, game closed
love . --editor
# or
POKEPORT_EDITOR=1 love .
# open a specific save (any path)
love . --editor --save "/path/to/save.lua"
By default it loads the game's active save slot under the LÖVE save directory (same identity as the game, deliberately: the editor edits the game's saves and reads the game's ROM cache):
- macOS:
~/Library/Application Support/LOVE/pokemon-love2d/ - Linux:
~/.local/share/love/pokemon-love2d/ - Windows:
%APPDATA%\love\pokemon-love2d\
If the file isn't there (or you want another copy), use Open..., drop a
save.lua onto the window, or pass --save. Each write makes
save.lua.bak-YYYYMMDD-HHMMSS first.
Layout
| file | role |
|---|---|
Theme.lua |
the launcher's palette + drawing primitives (cards, glow, dashed outlines, letterspaced captions) |
Kit.lua |
immediate-mode widgets built on Theme: buttons, rows, meters, chips, checkboxes, a real text field, pagers |
PadInput.lua |
virtual cursor for Switch / gamepads (stick move, A click, B close, shoulders cycle tabs) |
Ops.lua |
every mutation, behind one funnel that sets dirty + status together |
App.lua |
chrome (version rail, title bar, tab rail, status bar) and the panel router |
panels/ |
one file per tab; pure layout that dispatches into Ops |
panels/SpeciesPicker.lua is the one exception to "one file per tab": it is
the modal species search the inspector opens, drawn by App.draw after the
panel rather than routed through the tab table. Kit has no z-order, so while
it is up Kit.blockClicks shields every widget underneath it.
Switch / gamepad
On Nintendo Switch (and any gamepad without a mouse), the editor uses the same
virtual-cursor idea as the launcher: left stick / D-pad moves a pointer, A
clicks, B closes (with the usual unsaved confirm), L/R cycle tabs, and the
right stick scrolls lists. Touch taps forward as clicks. Without that path the
editor soft-locked until HOME — main.lua used to drop all pad/touch events
while editorMode was set.
The design reference is the SaveEditor.dc.html mockup that this port
transcribes; its measurements are in the same pixel space App.lua draws in.
Two rules the code enforces and the tests assert:
Ops.mark(S, msg)is the only thing that setsS.dirty, and it always writes the status line at the same time. Refusals go throughOps.say, which speaks without dirtying. No branch may silently no-op.- Destructive verbs go through
Ops.arm(S, id, msg): the first call arms and returns false, a second withinOps.ARM_SECONDScommits.Ops.armLabelrelabels the button toConfirm?in between.
Headless tests
Run from repo root (luajit, or the same interpreter as
tests/run_tests.lua). All four run in CI as their own tiers in
scripts/test.sh:
luajit tests/run_save_editor_tests.lua # SaveIO, App load/save/close, party + inspector, all-tab draw smoke
luajit tests/save_editor_task6_tests.lua # Boxes + Items rules
luajit tests/save_editor_task7_tests.lua # Events + Dex rules
luajit tests/save_editor_task8_tests.lua # map browser + spawn points
luajit tests/save_editor_mod_tests.lua # modded species/items stay editable
luajit tests/save_editor_pad_input_test.lua # pad cursor / NX input routing
They drive Ops.lua rather than clicking pixel coordinates. The panels are
layout over Ops, so a redesign moves every coordinate but none of the
rules; asserting against Ops is what keeps the suites meaningful across one.