452 Commits

Author SHA1 Message Date
bryanthaboi 8ca1e08a86 Merge pull request #2097 from thibautbus/fix/gen2-move-effects-validation 2026-09-02 06:30:08 -04:00
bryanthaboi 50da140c02 Merge pull request #2056 from thibautbus/fix/complete-translation-coverage-v0241 2026-09-02 06:29:57 -04:00
bryanthaboi 51801a27bc CLOSES #2092, CLOSES #2098 2026-09-02 05:58:45 -04:00
thibautbus 56a9a3f0b4 Widen Gen 2 move_effects registration to the movedex's own effect ids
Battle.MOVE_EFFECT_RECORDS only lists effects with a standalone handler --
by design, per its own comment: a move whose effect is just "deal damage"
(EFFECT_NORMAL_HIT and the multi-hit/recoil/drain families) has none and
falls through to the generic damage path. But src/mods/Schemas.lua's
`moves.effect = f.id("move_effects")` cross-check treats move_effects as
the complete id space for the field regardless of generation, so every
"full" effect the real Gold/Silver movedex uses read as a dangling
reference the instant any mod's `moves` patch touched the registry --
caught building the Gold/Silver translation mod, whose move_names patch
(name only, never effect) was enough to trigger the scan on all ~130
moves.

registerMoveEffectsInto now also registers a bare `{kind="primary"}`
marker for every effect id data.moves actually uses that MOVE_EFFECT_RECORDS
doesn't already cover. Both of moveEffectRecordFor's call sites already
treat a handler-less record exactly like a missing one (nil-checking
.run/.status before use), so this is a validation-only change with no
battle behavior difference -- a real typo in a mod's own effect patch is
still caught, since the widened set is seeded from the pre-merge data.moves,
not from whatever a mod patches in afterward.
2026-09-02 11:43:30 +02:00
bryanthaboi 700331cb8f CLOSES #2057, CLOSES #2058, CLOSES #2059, CLOSES #2060, CLOSES #2061, CLOSES #2062, CLOSES #2063, CLOSES #2064, CLOSES #2066, CLOSES #2067, CLOSES #2069, CLOSES #2070, CLOSES #2072 2026-09-01 19:03:13 -04:00
thibautbus f07ebfe423 Route remaining Gen2 UI text through translations
CELADON_TM/MON and GOLDENROD_TM/MON stored each Game Corner row as one
hand-padded "NAME    COST" literal; a translated name of a different length
than the English original shifted or overlapped the price that used to be
right-aligned by the padding alone. Split each row into a translatable name
and the existing numeric cost field, print them as two separate calls (the
cost right-aligned against a fixed priceRight column per counter, the same
column-aware approach MartMenu.printPriceOpaque already uses), and clamp the
name to the tile budget before the price column with Font.split (glyph-aware,
so a <PK><MN> macro or multi-byte UTF-8 character -- including one whose
expansion straddles the clamp boundary -- is never cut mid-sequence).

TEXTS (SlotMachine.lua) kept a hand-typed English line array next to each
entry's Strings.source() template; derive the array from source once at load
instead, and cache localizedLines()'s parsed split per source table so the
bet/result screens do not re-run the same gmatch split every draw() call.

ContestMenu.TEXT.alreadyCaught's line split now uses the same gmatch loop
the rest of this file's line-parsing already uses (an anchored ^(.-)\n(.*)$
match assumed exactly one \n and left a nil hole when a translation merges
the two lines into one clause).

Four of PrizeMenu.TEXTS' Game Corner vendor messages (Celadon's and
Goldenrod's prize-vendor intros, Goldenrod's quit line, and the coin
vendor's no-COIN-CASE refusal) used \f where the real cart text
(poke-corpus GoldSilver, e.g. gs.CeladonGameCornerPrizeRoom.
CeladonPrizeRoom_PrizeVendorIntroText) ends in \v: a plain page clear
instead of a scroll, so the vendor's last line appeared alone with no
lead-in instead of continuing under the previous one. Found and confirmed
against the corpus while auditing this branch for the same class of bug as
CenterPcMenu.lua's \n-vs-\v fix.
2026-09-01 21:52:55 +02:00
thibautbus 50b29ad83e Localize Gen2 PC and storage text
messagePages() only split on \n (line) and \f (paragraph), so a translated
message needing a \v scroll-continue break -- the same marker PackMenu and
PrizeMenu's messages already rely on via CommonText.pages -- rendered wrong
here: no line ever scrolled. Delegate to CommonText.pages, the same shared
page-break implementation PackMenu and PrizeMenu already use for their own
messages, instead of a second, incomplete reimplementation local to this
file.

CommonText.pages() also only treats \n as the box's second row, not a page
break, unlike the old local messagePages(), which grouped every two
\n-separated lines into a page regardless of \f. The one BOX_FAILURE_SOURCES
literal with a third line via a second bare \n ("You'll need a\nPOKéMON to
call\nwith.") needed \f instead, the marker every other multi-page message
in this file already uses for the same transition (see RELEASED just
above); pinned with a test.

CenterPcMenu.lua's own TEXT.noMon had the identical \n-vs-\v bug: the
Pokecenter PC's empty-party refusal (_PokecenterPCCantUseText, "ends in
cont" per the comment already on this line) needs \v to scroll "have a #MON
to" up and land "use this!" under it, not a third bare \n line, which
pagesOf() (this screen's own \f/\v-aware paginator, unaffected by the
messagePages() bug above) renders as a lone one-line page instead -- caught
by gen1recomp/dev's own independent fix to the same line while rebasing this
branch onto dev, and confirmed against tests/gen2_pc_screens_test.lua's
scroll assertions. The French/German/Spanish/Italian/Japanese/Korean
overrides already carry the correct \v in their translated values; only the
lookup key needed the same fix, made in gen1recomp-translation-mods
alongside this commit.
2026-09-01 21:52:55 +02:00
thibautbus 62e13556f5 Localize Gen2 options text 2026-09-01 21:52:55 +02:00
thibautbus c5008afd5d Localize Gen2 battle text 2026-09-01 21:52:50 +02:00
thibautbus 23aeda4871 Expose localized Gen2 content registries
GEN2_STATUS_IDS (psn/brn/frz/par/paralysis/slp -> Gen 2's own registry ids)
was declared verbatim in both PartyMenu.lua and SummaryMenu.lua; move it to
Status.GEN2_ID_ALIASES so a future status alias fix only has one copy to
update.

SummaryMenu.TYPE_NAMES was left behind after this branch switched its two
former internal uses to the shared TypeChart.displayName/DISPLAY_NAMES; it
has no remaining callers anywhere in src/ or tests/.
2026-09-01 21:52:42 +02:00
bryanthaboi f036197fd3 Merge pull request #2073 from 1Jamie/android-xbox-vsync
disables adaptive vsync as an option since few platforms support it and it causes issues on ones that incorrectly detect it as a supported
2026-09-01 15:49:21 -04:00
1jamie 5342fcbede disables adaptive vsync as an option since few platforms support it and
it causes issues on them
2026-09-01 14:40:50 -05:00
thibautbus 46c386b19e Route remaining RBY text through translations 2026-09-01 21:08:31 +02:00
bryanthaboi 2f9a920f4d Merge pull request #1825 from BountyHunterKanden/encounter-table-preview
Add API seam for mods to read/give information about altered encounter tables
2026-09-01 13:50:11 -04:00
bryanthaboi 23f59b7868 Merge pull request #2036 from 1Jamie/android-xbox-vsync
fix(present): android + xbox uwp nests that dont fake being desktop windows
2026-09-01 13:49:17 -04:00
bryanthaboi 45742481bf CLOSES #2025, CLOSES #2029, CLOSES #2030, CLOSES #2031, CLOSES #2033, CLOSES #2034, CLOSES #2035, CLOSES #2038, CLOSES #2040, CLOSES #2041, CLOSES #2042, CLOSES #2043, CLOSES #2044, CLOSES #2045, CLOSES #2046, CLOSES #2048, CLOSES #2049, CLOSES #2050, CLOSES #2051, CLOSES #2052, CLOSES #2053, CLOSES #2054 2026-09-01 13:45:00 -04:00
1jamie 19b2b688a8 fixed present sync so android/uwp dont get treated like desktop windows, android is bufferqueue backpressure and xbox uwp is angle under the console compositor; leaving vsync on while we soft-cap was stopping the swapchain from ever locking clean. now they get their own nests, DISPLAY by default so the probe can actually engage, and fail-closed silences the driver so framecap can pace alone. 2026-08-31 16:41:42 -05:00
mleo2003 25073d9ebe fix(checkpoint): keep poisonSteps across a restore (#1971)
`Checkpoint.restore()` rejected most valid checkpoints:

    Checkpoint restoration failed: restored state differed at $.save.poisonSteps

poisonSteps is a plain step counter -- (poisonSteps + 1) % 4 on EVERY step,
not only while a mon is poisoned (OverworldController:applyFieldPoison) -- so
it is non-zero three steps out of four in ordinary play.

Installing the restored world re-enters the map, and the map-enter path zeroes
the counter, correctly mirroring ClearVariablesOnEnterMap. Checkpoint.restore
then re-captures the applied state and compares it against the checkpoint, so
the field it had just discarded failed the comparison and the whole restore
rolled back.

A restore is not a map entry from the player's point of view: the counter
belongs to the state being restored. Carry it across the push.

Why this stayed hidden: the two autosave triggers a checkpoint consumer
naturally uses, map.entered and player.warped, are emitted from inside the
very paths that zero the counter (OverworldController 377/572 and 4664/4783),
so those captures hold 0 and restore cleanly. Only a capture taken at an
arbitrary step -- a manual quicksave, or one tied to the ordinary SAVE --
carries a non-zero value. The T4 title-checkpoint tier misses it for the same
reason: its save is fresh, so the counter is already 0.

The comment above the push claimed Checkpoint.resume was this method's only
caller. Both callers arrive through Checkpoint.apply, which serves resume from
the title session and restore from a settled runtime; that is exactly why the
map-entry side effects matter here. Corrected.

tests/engine/restore_poison_steps_bug1971.lua covers 1..3 and 0 across a
restore, and pins the two behaviours that must NOT change: a plain map entry
still zeroes the counter, and a seamless connection crossing still carries it.
It fails on main (3/6, "got 0, want 3") and passes with this change.
2026-08-31 09:46:45 -07:00
bryanthaboi 998cb03d4c CLOSES #1947, CLOSES #1987, CLOSES #1989, CLOSES #1994, CLOSES #1996, CLOSES #1998, CLOSES #2010, CLOSES #2011, CLOSES #2012, CLOSES #2013, CLOSES #2014, CLOSES #2015, CLOSES #2017, CLOSES #2018, CLOSES #2019, CLOSES #2020, CLOSES #2021, CLOSES #2022 2026-08-31 12:05:42 -04:00
bryanthaboi b9d14e425c hooks 2026-08-31 08:41:02 -04:00
bryanthaboi f4424ea4a0 rquesated hooks 2026-08-31 07:56:27 -04:00
1jamie 7984fe296c Implement idle render governor and update vsync handling for handheld builds
- Introduced the `POKEPORT_IDLE_AFTER` and `POKEPORT_IDLE_FPS` environment variables to manage presentation rates on static screens, allowing game logic and audio to maintain full speed.
- Updated the vsync handling to keep it enabled across all platforms, including KMSDRM handhelds, to leverage PresentSync for improved cadence and pacing.
- Enhanced FrameCap logic to ensure proper handling of performance caps in handheld environments, preventing unnecessary software pacing when hardware capabilities are sufficient.
- Adjusted documentation to reflect these changes and their impact on power efficiency and performance.
2026-08-30 19:47:23 -05:00
1jamie e97def0c4f revereted attaching game spe3ed to sound.setRate so that audio plays the
same reguardless of speed
2026-08-30 16:51:18 -05:00
bryanthaboi c18fe7db50 Merge pull request #1918 from colsonrice/feat/gen2-export-map-objects 2026-08-30 14:05:46 -04:00
bryanthaboi ccd3abb3b3 Merge pull request #1984 from bryanthaboi/feat/uri-launch-args 2026-08-30 14:04:38 -04:00
1jamie a1adeb4676 Enhance PresentProbe and PresentSync for improved frame pacing and sync detection
- Updated PresentProbe to measure inter-present cadence accurately, ensuring that ambiguous signals default to FrameCap.
- Refined classification logic to prioritize stable cadence over unreliable hardware gating, enhancing the robustness of frame pacing.
- Adjusted PresentSync documentation to clarify probe isolation and its implications for sync confirmation.
- Improved test cases to validate the new cadence-based gating logic across various platforms, ensuring consistent behavior in frame pacing.
2026-08-30 12:11:56 -05:00
Adrian Castro 7795a6fd3e refactor(ios): rename app clips to web clips 2026-08-30 18:45:42 +02:00
Adrian Castro 18cf2257a8 fix(ios): capture launch options before scene creation 2026-08-30 18:45:42 +02:00
Adrian Castro 00a2486555 feat: add URI launch options and unified platform builds 2026-08-30 18:45:42 +02:00
1jamie 07071e334c Refine VSync detection and FixedStep logic for improved frame pacing
- Updated PresentSync to accurately measure present() block time, ensuring that the probe does not misclassify sync status due to FrameCap sleep artifacts.
- Enhanced FixedStep to implement a hard ceiling on catch-up debt, preventing input starvation during high-speed scenarios.
- Adjusted logic in Game and Game2 to utilize the new catchupLimit function for setting maxAccum, ensuring consistent behavior across speed multipliers.
- Improved test coverage for PresentProbe and PresentSync to validate the new logic and edge cases.
2026-08-30 11:29:43 -05:00
1jamie f82f565cae Fix to vsync measuring and detection and fixes fixestep so it snaps wallclock dt before applying speed
the display sync stuff i added was probing whether vsync was actually working, but it was measuring the wrong thing. during the probe we software-cap at 60 for safety, and the probe was looking at the gap between frames... which includes the sleep 😅 .....  so it always looked like sync was fine even when the driver was ignoring it. on something like the ally x on windows thats a real problem. vsync says on, probe says gated, we lift the cap and snap logic to the panel hz, then youre basically uncapped. at 2–4x that turns into hitching, dropped frames, dropped input, that weird half second freeze. speed swapping wasnt desyncing the driver, it was just making the bad path hurt more.
the solution is just we time present() itself now, not the gap after it, this way the warmup sleep cant fake a pass. if sync is unclear or broken we just stay on a capped 60 and dont snap logic. Also fixed fixedstep so it snaps wall clock dt before applying speed, so in general the 2-4x speed swaps dont screw the pacing math anymore
2026-08-30 11:12:19 -05:00
bryanthaboi 308605fd61 CLOSES #1944, CLOSES #1952, CLOSES #1967, CLOSES #1968, CLOSES #1969, CLOSES #1970, CLOSES #1972, CLOSES #1974, CLOSES #1975, CLOSES #1976, CLOSES #1977, CLOSES #1978, CLOSES #1979, CLOSES #1980, CLOSES #1981, CLOSES #1982, CLOSES #1983, CLOSES #1985, CLOSES #1986 2026-08-30 10:09:50 -04:00
bryanthaboi 5c850d670e CLOSES #1938, CLOSES #1940, CLOSES #1941, CLOSES #1943, CLOSES #1945, CLOSES #1946, CLOSES #1948, CLOSES #1949, CLOSES #1950, CLOSES #1953, CLOSES #1955, CLOSES #1957, CLOSES #1960, CLOSES #1961, CLOSES #1962, CLOSES #1963, CLOSES #1964 2026-08-30 08:04:35 -04:00
Colson Rice 532972de59 Carry the map's object state when a Gen 2 export moves maps 2026-08-29 21:27:25 -04:00
1jamie 2f6c5527f6 Add PresentSync integration for improved VSync handling
- Introduced PresentSync module to manage display synchronization.
- Updated love.run() to handle display changes and resizing events with PresentSync.
- Enhanced FrameCap logic to accommodate PresentSync requirements.
- Modified VSync to report effective states and handle driver quirks.
- Updated options menus to reflect PresentSync availability and restrictions.
- Added tests to validate PresentSync functionality and its interaction with VSync settings.

should address issue #1910 and issue #1958
2026-08-29 19:43:25 -05:00
bryanthaboi cba47b04e8 CLOSES #1928, CLOSES #1929, CLOSES #1930, CLOSES #1931, CLOSES #1933, CLOSES #1934, CLOSES #1935, CLOSES #1936 2026-08-29 09:00:17 -04:00
bryanthaboi 36ad59bdbb CLOSES #1590, CLOSES #1592, CLOSES #1625, CLOSES #1657, CLOSES #1698, CLOSES #1699, CLOSES #1700, CLOSES #1843, CLOSES #1875, CLOSES #1898, CLOSES #1902, CLOSES #1905, CLOSES #1906, CLOSES #1907, CLOSES #1908, CLOSES #1909, CLOSES #1912, CLOSES #1915, CLOSES #1917, CLOSES #1919, CLOSES #1920, CLOSES #1921, CLOSES #1922, CLOSES #1923, CLOSES #1924, CLOSES #1925, CLOSES #1926, CLOSES #1927 2026-08-29 06:59:38 -04:00
bryanthaboi a9ce423bdc CLOSES #1275, CLOSES #1295, CLOSES #1384, CLOSES #1628, CLOSES #1677, CLOSES #1682, CLOSES #1691, CLOSES #1813, CLOSES #1822, CLOSES #1861, CLOSES #1867, CLOSES #1868, CLOSES #1869, CLOSES #1870, CLOSES #1871, CLOSES #1872, CLOSES #1874, CLOSES #1876, CLOSES #1878, CLOSES #1880, CLOSES #1881, CLOSES #1882, CLOSES #1884, CLOSES #1885, CLOSES #1886, CLOSES #1887, CLOSES #1888, CLOSES #1889, CLOSES #1890, CLOSES #1891, CLOSES #1892, CLOSES #1893, CLOSES #1894, CLOSES #1895, CLOSES #1896, CLOSES #1899, CLOSES #1900, CLOSES #1901
CLOSES #1275, CLOSES #1295, CLOSES #1384, CLOSES #1628, CLOSES #1677, CLOSES #1682, CLOSES #1691, CLOSES #1813, CLOSES #1822, CLOSES #1861, CLOSES #1867, CLOSES #1868, CLOSES #1869, CLOSES #1870, CLOSES #1871, CLOSES #1872, CLOSES #1874, CLOSES #1876, CLOSES #1878, CLOSES #1880, CLOSES #1881, CLOSES #1882, CLOSES #1884, CLOSES #1885, CLOSES #1886, CLOSES #1887, CLOSES #1888, CLOSES #1889, CLOSES #1890, CLOSES #1891, CLOSES #1892, CLOSES #1893, CLOSES #1894, CLOSES #1895, CLOSES #1896, CLOSES #1899, CLOSES #1900, CLOSES #1901
2026-08-28 12:15:36 -04:00
bryanthaboi 4849a5c9f6 Merge branch 'dev' of https://github.com/bryanthaboi/gen1recomp into dev 2026-08-28 10:14:46 -04:00
bryanthaboi 680db777a3 big online energy 2026-08-28 10:14:44 -04:00
bryanthaboi dc99b1f8da Merge pull request #1852 from colsonrice/feat/gen2-save-export
Export Gen 2 cart saves
2026-08-27 13:37:43 -04:00
bryanthaboi 377f1f8a71 Merge pull request #1877 from 1Jamie/flatpak-lic-fix
update flatpak information that was left as template default
2026-08-27 13:36:27 -04:00
bryanthaboi 2441996d45 maybe better menu situation 2026-08-27 12:44:22 -04:00
1jamie 5e7dc556f5 update flatpak information that was left as template default
flatpak properly now marks as oss and points to site and gh
2026-08-27 10:30:44 -05:00
Colson Rice 71a5602b58 Export Gen 2 cart saves
Closes the round trip. Gold, Silver and Crystal progress writes back to a
cartridge save the real game boots.

Writes into the image the save came from. Gen 2 SRAM holds a great deal
this codec does not model and the real game trusts it on CONTINUE, so a
save with no cartridge behind it is refused rather than built from nothing.
That refusal is the known limitation: a game begun in this project has no
lineage to write back into yet.

The image lives BESIDE the slot as saves/<version>/<id>.cart, not in the
save table. 32 KB of binary in the serialized table is 40 KB of Lua source
reparsed on every save and load, for every imported slot, forever.

Only the primary copy is written. TryLoadSaveFile rewrites the backup from
the primary the moment VerifyChecksum passes, so a stale backup heals
itself on the first load and this does not need a second offset mapping.
Crystal arranges its backup differently from Gold and Silver, so that
matters.

What encode now reaches that it did not:

  * the bag, all four pockets, bucketed by each item's own pocket, plus
    wCurBox and the box names. It used to leave them at whatever the
    template carried, so a potion bought in a session never arrived.
  * the RTC footer. importToSlot truncates to 32768, so the image is kept
    at its full length and the footer is carried through. Dropping it
    resets the clock and costs the player daily events, the bug contest
    and a clock-adjustment penalty.
  * 0x1C-0x1E, pokerus and caught data. Left to the template they survive
    POSITIONALLY, so reordering the party gives slot 1 the previous
    occupant's caught level and location.

A bag that cannot be bucketed is refused rather than written short: without
the item table every item falls into ITEM, which holds 20, and a real bag
is bigger than that. Silently dropping the overflow would be worse than
the bug this fixes.

Two bugs in the text encoder that only real names caught: the cart's table
carries the ligature halves PO and KE, so a name containing "PO" became
0x70 where the cart had a plain P; and #glyph counts BYTES in Lua, so every
multi-byte glyph was dropped and came back as "?", which is NIDORAN and
every name with an accent.

Tests

The round trip CHANGES things first, in each place export has to reach, and
reads them back through a fresh decode. Exporting onto the buffer a save was
decoded from proves nothing, because every region encode does not write
matches by construction.

The fixture-gated audit exports a real cart save too, and pins that the
image keeps its size, RTC and all.

./scripts/test.sh passes end to end, and luacheck is clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 10:41:30 -04:00
Colson Rice ebd315b01e Import Gen 2 cart saves
Gold, Silver and Crystal battery saves import now. Export is still refused.

GenSave.lua is pokered's SRAM window and nothing else, which is why the
guard refusing Gen 2 was right to be there. This adds Gen2Save.lua beside
it, covering pokegold and pokecrystal.

Every offset is generated, not transcribed. tools/gen2_sram_offsets.py
reads pokegold.sym and pokecrystal.sym from a pret build and emits
Gen2Layout.lua, including the text table from constants/charmap.asm and
Crystal's backup-save layout. Gen 2 copies a contiguous WRAM block into
SRAM bank 1, so a field's file offset is sPlayerData + (wField -
wPlayerData); the generator asserts that relation against sPokemonData
rather than assuming it, and range-guards anything outside
sGameData..sGameDataEnd.

Gold and Crystal are separate tables because they disagree about nearly
every field. Reading a Crystal save with Gold's numbers gives a party
count of 133 and 13113 hours played, with a checksum that validates.

The cart stores numbers and the engine is keyed by name, so the codec
crosswalks species, moves and items through the generated tables the same
way GenSave.crosswalks does for Gen 1. Without that, an import looks
perfect and the engine cannot read a byte of it.

Shapes that have to match what the engine reads:
  * events is byte index -> packed byte, which Save.scrubEvents validates
    with tonumber. A set of booleans is silently emptied.
  * the bag is one flat save.inventory keyed by item id, which PackMenu
    buckets by each item's pocket. Nothing reads save.keyItems or
    save.balls, and the TM/HM pocket lands here too.
  * position carries the map id, or Save.summary falls through to
    save.spawn and the player resumes somewhere else at their old
    coordinates.
  * mon.status is an ItemEffects.STATUS_CLASS key with statusTurns beside
    it, nil when healthy. 0 is truthy in Lua.

A save the real cartridge would open is not refused: TryLoadSaveFile falls
back to VerifyBackupChecksum, so this does too. Crystal's backup is
contiguous and laid out like the primary; Gold and Silver split theirs
across three sections and have none to offer.

Three suites that pinned Gen 2 import being refused now pin what refuses
instead. Tests live in tests/engine so the ROM-free tier actually runs
them.

./scripts/test.sh passes end to end, and luacheck is clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 10:32:40 -04:00
bryanthaboi eed246f645 CLOSES #1831, CLOSES #1834, CLOSES #1836, CLOSES #1860 2026-08-27 06:09:50 -04:00
bryanthaboi c9d06779d8 Merge pull request #1844 from colsonrice/fix/gen2-save-refusal-message
Refuse a Gen 2 cart save as a Gen 2 save, not as a broken Gen 1 one
2026-08-27 06:04:27 -04:00
bryanthaboi 48d8a4e922 CLOSES #1562, CLOSES #1804, CLOSES #1805, CLOSES #1810, CLOSES #1811, CLOSES #1814, CLOSES #1817, CLOSES #1818, CLOSES #1819, CLOSES #1821, CLOSES #1826, CLOSES #1827, CLOSES #1829, CLOSES #1833, CLOSES #1840, CLOSES #1842, CLOSES #1845, CLOSES #1846, CLOSES #1847, CLOSES #1848, CLOSES #1849, CLOSES #1853, CLOSES #1858, CLOSES #1862 2026-08-27 05:09:26 -04:00
Colson Rice 3a0a72102f Teach the bug420 SaveConvert double the method importToSlot now calls
CI caught this: tests/engine/save_import_retry_bug420.lua replaces
SaveConvert with a minimal double, and importToSlot now asks it
importSupported before it measures the bytes, so the double answered nil
and the call died.

The double stands in for the real module, so it grows with it. Answering
true keeps that case about the thing it is testing, which is that
importToSlot names the game whose cache to read.

./scripts/test.sh passes end to end locally now, every ROM-free tier.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 15:47:08 -04:00