Introduce an OPTIONS -> PERFORMANCE setting that scales the port's
optional presentation extras down for weaker hardware, so older/lower-end
devices can run the game smoothly.
The tier governs the three heaviest non-faithful extras -- the 3D TILT,
the GBC FX post-process shader, and survey ZOOM (which renders connected
neighbor maps) -- plus a hard FPS ceiling. It never touches game logic,
which is fixed-step off dt, so every tier plays identically.
- src/core/Performance.lua: tiers (auto/high/balanced/low), a conservative
device auto-detect (ARM handhelds -> low, phones -> balanced, normal
desktops -> high), per-tier caps, and the option-row cycle. Zero
requires, like GameVersion.
- Game:applyOptions clamps the *live* presentation state against the tier
without rewriting stored options, so a lower tier hides the player's
TILT/GBC FX/ZOOM/FPS choices and a higher tier restores them exactly.
- Zoom.offsetRange floors the range at FIT when survey is disallowed, so
the option row, hotkey, and mouse wheel all stop at close-up on LOW.
- New save.options.performance default "auto"; OPTIONS row heads the
display group and re-applies live.
- Tests: tests/engine/performance_tiers.lua (ROM-free); mod_ui_tests row
golden updated for the spliced row.
AUTO resolves to HIGH on a normal desktop and on every options.lua that
predates the option, so the common case is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q6bFAiQyZ5jDmewsbB4LG9
love.filesystem looks for "mods/" in two places: the save directory, and
-- portable installs only -- the game folder, which CacheFs mounts. So a
player who unzips a mod next to the executable of an ordinary install,
which is where very nearly every other game would want it, gets no error
and no mod. The panel just comes up empty, with nothing on screen to
suggest the files are sitting in the wrong folder twenty centimetres away.
That is a hard failure to self-diagnose, and it is worse behind a
launcher: the install lives somewhere the player never opens, so "the
game's mods folder" is a guess to begin with.
The mods panel now looks in those folders before its first listing and
copies what it finds into the tree the game really reads, reporting what
it took in the notice line. It happens on open rather than behind a
button because the failure being fixed is one where nothing suggests
there is anything to press.
Looking is scoped: CacheFs.withMounted puts the folder on the read path
at its own mount point, runs the scan, and takes it straight back off.
Nothing a stray folder contains can shadow a game file or change what the
running game resolves, which is what makes it safe to point at a folder
whose contents nobody has validated. Adoption skips ids the game can
already see, so it is idempotent and never nags twice, and it leaves the
loose folder alone -- deleting files outside the save directory on the
player's behalf is not this code's call to make.
Which strays are worth taking is pure (LauncherMods.pickStrays), matching
how deriveList and locateRoot are already split out, so the engine tier
covers the rules without needing love. SaveData.gameFolders is the old
detectPortable candidate list lifted out unchanged -- portable mode is
just the case where one of those folders holds the marker.
Claude-Session: https://claude.ai/code/session_01JvEthuoNBPfxpvHUD9Pd4N
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.
CLOSES#28: PC in the beginning of the game isn't interactable (The one in your house)
CLOSES#34: Bug when calculating exp after one or many fainted team members.
CLOSES#37: No Grass Cutting
CLOSES#38: Blind TMs
CLOSES#53: Portable Mode
CLOSES#55: changing palletes with hot key
CLOSES#62: Poison status damage issue.