options.lua is a whole-file rewrite, so a caller handing saveOptions a
partial table (only the keys it changed) silently dropped every key it did
not mention: launcher-only keys like lastVersion, and keys the launcher set
(battleBg, tilt) all fell back to defaults.
saveOptions now reads the on-disk file first and folds caller-absent
values underneath before mergeOptions backfills defaults. A table holding
every defaultOptions key is a full snapshot and stays authoritative, so the
fold is inert for all in-repo writers (every one passes loadOptions-ed
tables) and cannot resurrect the bindings/activeProfile deletions the
RESET REBINDS and mod-manager paths make on full tables.
Adds a regression suite (options_partial_write_bug932.lua) pinning the
merge, and updates the #828 suite's partial-write assertion, which now
expects lastVersion to survive a delta write.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Wrap the battle stat box, the PC quantity footer and the dex metric
labels in Strings() (SummaryMenu -- and MoveEffects since #811 --
already do this); give the battle menu a 'battle' lookup context so a
translation can shorten FIGHT/ITEM/RUN independently of the pause menu;
align the metric dex rows with the imperial ones and make the No.
prefix translatable; make the title menu's recolor zone follow Menu's
auto-grown width; honor the declared-but-unread boot.title seam,
drawing an explicit versionRibbon as one centered piece. With an empty
catalog every path is pixel-identical to vanilla.
Yellow's field.oakSpeech manifest carried only shrink frames, so
OakSpeech.lua's `oakGfx.demoSpecies or "NIDORINO"` fallback fired and
the opening speech showed Nidorino's sprite and cry instead of the
player's Pikachu.
- Stamp demoSpecies "PIKACHU" in the Yellow import manifest (source of
truth for fresh ROM imports and developer builds).
- Stamp it in make_yellow_manifest.py so regeneration keeps the value.
- Repair stale Yellow caches in Data:applyVersionedFieldData() with a
fill-if-absent block, matching the #617 oldManBattle RATTATA pattern.
- Add parity test (manifest carries PIKACHU; stale cache filled;
pre-stamped value left alone).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The mod update and Find Mods feeds feed the raw HTTP body straight to
Json.decode. When the endpoint hands back something that is not JSON
(an HTML error page, a proxy/captive prompt, or a plain-text outage
message like "Exceeded secondary rate limit" -- usually still HTTP 200),
the decoder's "unexpected character 'E'" assert escaped through the
pcall and became the error message, blaming the parser instead of the
response.
Add Json.describeUnexpected() as a pre-decode content-type guard: it
returns nil for body shapes the endpoints actually publish (JSON object
or array) and otherwise a short message naming what the server sent
(HTML page / plain text / empty, with a preview). Wire it into
ModUpdate.parseReleases and ModIndex.parse, so both the sync and async
update-check paths surface the real answer instead of the parse error.
HTTP status was already checked upstream by HostShell.httpGet (non-2xx
becomes "HTTP <code> from <url> (...)"); this closes the remaining
"2xx but not JSON" gap everywhere, including bridge platforms that
expose no status or headers.
Add regression tests for plain-text, HTML, and empty bodies; strengthen
the ModIndex HTML soft-fail test.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Importing Red unpacked data/generated, assets/generated and
rom-cache.complete straight into the save-dir root, while Blue and
Yellow land under blue/ and yellow/. Red now uses cachePrefix red/
like the others.
CacheFs.migrateLegacyRedCache moves a pre-existing root cache into
red/ on first boot, from RomImporter.new before the readiness loop
and from mountVersion, so existing installs keep their import instead
of being asked for the ROM again. The move only runs when the root
marker resolves to the save directory, so a dev checkout's source
tree is never touched, and the portable game folder is skipped when
it is the physfs source.
Closes#899
A running launcher cannot replace its own NRO on sdmc/FAT; stage the new
binary and chainload a tiny helper from romfs to swap it in before loading
the game.
The super-effective / not-very-effective hit sounds were only covered by
the manual ears-only driver (tests/drivers/hit_sfx_bug826_test.lua), so
the wFrequencyModifier wiring that fixed#826 (and that #902 reports as
a swap had no CI guard. This suite pins the polynomial-counter bytes
for all three hit sounds against the real audio/sfx programs: bare they
read swapped (super effective ends duller), and pitched they read
correct (super effective 4 shift 1 crack, not very effective
shift 10 thud). A port that drops the modifier fails 11 of 24 checks.
CLOSES#902
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
EOF
)
ota_net_init() now mounts romfs and initializes curl before the GitHub check runs, fixing silent update-check failures after TLS verification was enabled.
Enable HTTPS peer checks via a romfs CA bundle, remove existing NROs before rename on sdmc, and surface launcher replace failures before writing version.txt.