bindGoldData points gen2Palettes, gen2Icons, gen2Pokedex, gen2Landmarks,
gen2Roofs and gen2Sprites at the extractor's own Gold tables through
loadGen, but never gen2Constants -- despite Schemas.GEN2 routing
`constants` to that same namespaced-and-differently-shaped category
palettes and icons are in. A save editor boot left data.gen2Constants
unset, so mod.content.constants:get(...) read an empty table instead
of the cart's ordered name lists, misreading the generation and
rejecting every record a mod shaped off it.
data.gen2Constants now goes through the same loadGen("constants") path
the other five already use, falling back the same way they do when no
ROM cache is active.
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.