An independent review flagged two gaps:
Pokegear.lua's clock card (drawClock and the card-list clock view) prints
the same AM/PM information MainMenu's clock box does, but was left as bare
literals -- so a mod translating AM/PM would now show it correctly on the
title screen and in English on the Pokegear for the exact same hour. Both
call sites now go through Strings("AM")/Strings("PM"), the same source key
MainMenu.lua already added, so no new corpus entry is needed on the
translation-mods side.
SaveMenu.lua's twoLines() only ever split on the first "\n": drawPanel's
box has room for exactly two Chrome.print calls, so a translation needing
a third line (nothing in the current fr/de/es/it overrides does, but
nothing stopped one from trying) had nowhere on screen to go, and the
extra "\n" landed as a literal newline byte drawn as glyph garbage on the
second line with no indication anything was wrong. It now warns once per
source text instead.
Added a gen2_save_menu_translation_test.lua case driving drawPanel() with a
three-line mod translation: confirms only the first two lines render, the
warning fires exactly once (not once per frame), and drawing does not
crash. Pokegear.lua has no engine-tier test suite to extend for the AM/PM
change; verified by direct inspection and a syntax check instead.
MainMenu.lua's row labels (CONTINUE/NEW GAME/OPTION/EXIT GAME), its clock
box's AM/PM half, and its CONTINUE save-summary panel (PLAYER <name>/
BADGES/POKéDEX/TIME, or NO SAVE FILE) were bare literals, invisible to a
translation mod's strings registry. SaveMenu.lua's confirm/overwrite/
saving/saved prompts, its YES/NO choice, and its own copy of the same
summary panel had the same gap. Both mirror the Gen 1 port's already-
translated equivalents (src/ui/TitleState.lua, src/ui/StartMenu.lua),
which route every one of these rows through Strings().
SaveMenu's two-line prompts (module-level OVERWRITE_PROMPT/SAVING_PROMPT,
plus the dynamically-built "%s saved\nthe game." and the confirm/failed
messages) are now single Strings()-resolved strings with an embedded "\n",
matching the Gen 1 port's own single-call convention for two-line messages,
split into the two-slot table drawPanel's fixed-position Chrome.print calls
expect only at draw time -- so a translation sees one whole sentence to
reorder, not two independently-translated fragments.
Added tests/engine/gen2_main_menu_translation_test.lua and
gen2_save_menu_translation_test.lua: drive both screens' drawPanel()/
drawSavePanel() with a mod-loaded Strings catalog and check the translated
text reaches Font.draw, plus a vanilla no-mod case proving the fallback is
unchanged. Confirmed both catch the regression: reverting either file to
its pre-fix content fails the corresponding suite (7 and 15 checks
respectively).
- Replaced `teardownMountedSession` and `flushEditorPackageLoaded` with a unified `SessionLifecycle` approach for managing session transitions and resource cleanup.
- Implemented `SessionLifecycle.endEditorSession` and `SessionLifecycle.endGameSession` to streamline the teardown process for editor and game sessions.
- Introduced `Assets.releaseSession` to handle GPU resource release at session end, ensuring efficient memory management.
- Updated `Game` and `Game2` reset methods to include world and canvas resource releases.
- Enhanced `MapLoader` with a new `releaseAll` method for eager GPU cache cleanup.
- Added tests to verify the new session lifecycle functionality and resource management.
This commit improves the stability and performance of in-process transitions, particularly during editor and game session changes.
further improves and addresses #1662 specifically around android gc pressure.
Route B in CONTRIBUTING-mods.md asks an event/hook change for five things.
This adds the two that were missing and fixes what the third turned up.
link.battle_ended built its payload unconditionally. Route B is explicit
that a new event must not allocate when nothing wants it, and every other
emit in the engine already guards -- Runtime.wants now gates this one too,
so an unsubscribed build runs the branch exactly as it did.
world.talk was handing Runtime.call a closure built fresh on every A press,
purely to have a fallthrough to pass. It is a file-local now, so an unhooked
press allocates nothing it did not allocate before.
The RFC covers motivation, the API delta with call sites, migration (nothing
changes for existing mods), and verification. The backward-compatibility
statement is in it: every item is a new name or a new optional argument, and
example_mew_starter -- api 1, category = "GAMEPLAY", whole-species copy --
still loads, which run_modkit proves on every run.
No registry or schema field is added, so gen_registry_docs has nothing to
emit for this change. Running it does show pre-existing drift in
docs/modding/reference/registries.md (timeFishGroups, an objects refinement)
from earlier Schemas.lua edits that were never regenerated; that is not this
branch's to carry, so it is left alone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
gate_meta_coverage asks every extension point for a unit test through the
public mod API, a no-mod parity test, and docs. The seams commit shipped the
call sites and owed the rest.
Both cases drive the real engine path, and both assert the unhooked build
first, so they fail if the seam is deleted and if it changes vanilla
behaviour. world_talk stands an object on the faced cell: with no mod the A
press reaches talkTo, with a mod that owns the object it does not, and an
object the mod ignores still falls through. link_battle_ended parks a
session at the end of a battle and checks the event carries the result, the
role, and both party copies.
The parity side needed nothing. gate_hooks and gate_events walk the live
catalog, so both seams were covered structurally as soon as the call sites
existed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five small additions. None of them mention the mod that prompted them.
WorldAPI handles gain stepNow, canStep, placeAt, isMoving and setPassable.
scriptMove queues onto OverworldState.scriptMoves, and a non-empty
scriptMoves is how the overworld knows a cutscene is running, so it gates
handleInput -- an actor animated that way freezes the player's controls for
as long as it walks. Right for Oak marching to his lab, wrong for anything
moving on its own schedule. stepNow sets the same per-tile state without the
queue. It skips the collision check on purpose: the caller is replaying a
move that was decided somewhere else, and re-judging it here would let the
two copies disagree about where the actor is. canStep is there for callers
that do want the map's opinion.
OverworldController raises world.talk around the NPC talk path. An object
from spawnNpc has no TEXT_* id, so the vanilla path has nothing to say for
one; a mod that owns the object wraps this and does not call next.
LinkState.newFromSession adopts a transport that is already paired, so a
mode can tunnel a battle through a connection it already has rather than
opening a second one. The hello and fingerprint exchange still runs. When
the battle ends, link.battle_ended carries the result and both lockstep
party copies -- cable rules leave the real party alone, so that is the only
place the damage exists.
Game:startNewGame(opts) is the title's NEW GAME closure made callable, with
opts.intro=false to land straight in the world. CodeEntry.new takes an
optional {length=, charset=} so the same widget can enter an address or a
room code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Introduced `teardownMountedSession` to handle cleanup of mounted versions, generated data, and mod states during transitions between the editor and launcher.
- Added `flushEditorPackageLoaded` to evict save-editor modules from `package.loaded` dynamically, ensuring a clean state for subsequent sessions.
- Implemented `Game:reset` and `Game2:reset` methods to clear session-specific fields, allowing for a fresh start when returning to the launcher.
- Enhanced `Renderer` and `TileRenderer` to release GPU resources immediately, preventing memory leaks during rapid transitions.
- Updated `MagnetTrainRide` to support OAM priority overlays and manage background rendering with new shader functionality.
This commit improves the stability and performance of the application during in-process transitions, particularly on Android.
fixes Yellow color palette for title screen
fixes#1662#1643#1536 and finishes fixing #1597
Drives NamingScreen:drawPanel() with a mod-loaded Strings catalog and
checks the translated text reaches Font.draw for the prompt, a
keyboard cell, the lower/END bottom-row labels, and both lines of the
nickname header (checking the mod's word order actually took, not just
the mon name splicing in), plus a vanilla no-mod case proving the
fallback is unchanged. Reverting src/ui/gen2/NamingScreen.lua to its
pre-fix content fails 6 of the 9 checks, reproducing the reported gap
exactly.
src/ui/gen2/NamingScreen.lua had zero Strings() calls: every prompt
(YOUR NAME?/RIVAL'S NAME?/MOTHER'S NAME?/BOX NAME?/NICKNAME?), the
on-screen keyboard's own letters, and the lower/UPPER/DEL/END bottom
row were bare literals, invisible to a translation mod's `strings`
registry (reported against a real Gold build, gen1recomp#1642). The
Gen 1 naming screen (src/ui/NamingScreen.lua) already routes its title
and every keyboard cell through Strings(); this mirrors that.
NamingScreen.TYPES and the bottom-row label tables are built once at
require time, before any mod's Strings.load has a catalog to answer
from, so their literals are wrapped in Strings.source -- an identity
marker that keeps the catalog generator's harvest of them -- and
drawPanel resolves them live through plain Strings() calls, the same
split used for Gold's OPTION screen and PR #1450's clock screens.
The nickname header ("<MON>'S" then "NICKNAME?", two lines because
Chrome.printThrough draws one row at a time) folds the mon name into
the first line's own format string, Strings("%s'S", self.monName),
rather than translating just the "'S" suffix: a language whose
possessive is not a bare suffix appended to the name can restructure
that whole line instead of being stuck splicing one on.
No real shader runs headless (love_stub does not stub newShader), so
this cannot check a rendered pixel. Font.encode/drawCode/advanceOf/
width are replaced with fakes that hand printThrough a fixed list of
glyph codes, so what is checked is the two things that decide the
outcome: which glyphs skip the shade-remap shader, and what colour is
active when each one draws -- for an all-tile string, an all-TTF one,
a string that mixes both (proving the switch is per glyph, not per
call), an inverted call, and DMG mode's own resolved palette.
Reverting src/ui/gen2/Chrome.lua to its pre-fix content fails every
TTF-mode check. A per-string version of the fix (Font.ttfActive()
checked once for the whole call instead of per glyph) also fails the
mixed-string case here, since it would flatten the naming screen's
tile-based <PK>/<MN> cells to a plain ink tint whenever a TTF is
loaded at all, losing their shader-remapped colour even though they
never draw from the TTF.
Chrome.printThrough (src/ui/gen2/Chrome.lua) runs a string through the
GbcPalette shade-remap shader whenever a palette is given, so a screen
whose background is not white -- the naming/keyboard screen, Diploma,
Pokegear, Credits -- gets its text colour resolved from the same
palette as everything else on it. The shader recovers a shade by
reading the RED CHANNEL of an already-rasterized 2bpp tile pixel
(src/render/GbcPalette.lua's SHADER_SOURCE), which assumes the texture
underneath is one of the four flat GB shades.
A TTF glyph is not: LÖVE's font rasterizer stores glyph coverage as
alpha over a plain white texture and lets the current tint carry the
ink colour, so that same red-channel read always comes back 1.0 --
shade 0 -- painting every TTF character the exact colour of the paper
rect printThrough had just drawn behind it. Since a mod loads a TTF
through src/core/Strings.lua's font registry precisely so a
translation is not boxed into a handful of tile-page glyphs, every
translation mod that ships one hit this: reported against a real Gold
build where the naming screen's on-screen keyboard, Diploma and the
Pokegear all went blank the moment the mod's TTF took over rendering
(gen1recomp#1642). Gen 1 has no such shader, which is why the same mod
draws fine there.
A TTF glyph carries no discrete shade to recover in the first place,
so skip the shader for one and tint it with the palette's own ink
colour directly -- the same shade-3 entry the shader's `rgb = pal3`
branch would have mapped a black tile pixel to.
The switch is per GLYPH rather than per string: a TTF-mod build still
keeps multi-byte charmap sequences (the naming screen's own <PK>/<MN>
cells, the 'd/'l/'s ligatures) and anything a mod names in ttf.tiles on
their ROM tiles (src/render/Font.lua's Font.split), so one call can mix
both kinds of glyph, and a tile glyph drawn alongside a TTF one still
needs the shader to pick up a mod's COLOR/rBGP settings correctly.
Drives OptionsMenu:drawPanel() with a mod-loaded Strings catalog and
checks the translated text reaches Font.draw for a cart-original row
(label and its display value), a port-added row's label, and CANCEL,
plus a vanilla no-mod case proving the fallback is unchanged. Reverting
src/ui/gen2/OptionsMenu.lua to its pre-fix content fails 4 of the 9
checks, reproducing the reported bug exactly.
src/ui/gen2/OptionsMenu.lua had zero Strings() calls: every row label
and every cart-original display value (FAST/MID/SLOW, ON/OFF,
SHIFT/SET, MONO/STEREO, the PRINT contrast ladder, :TYPE) was a bare
literal baked into the module-level ROWS table, invisible to a
translation mod's `strings` registry (reported against a real Gold
build, gen1recomp#1642). The Gen 1 OPTION screen (src/ui/OptionsMenu.lua)
already routes every one of its equivalent rows through Strings().
ROWS is built once at require time, before any mod's Strings.load has
a catalog to answer from, so wrapping its literals directly in
Strings() would freeze the English (src/core/Strings.lua's own note on
this). Labels and cart-original display strings are wrapped in
Strings.source instead -- an identity marker that keeps the catalog
generator's harvest of them, without changing when the lookup runs --
and drawPanel resolves them live through plain Strings() calls, the
same split PR #1450 used for the clock and day-of-week screens.
Also routed the two port-added rows (SCREEN POS, VIBRATION) and the
touch pad ON/OFF row through Strings() at their value call sites,
matching how the Gen 1 screen already treats the same shared
ScreenPosition.label/TouchControls.hapticLabel helpers -- both return
raw, untranslated text on their own. Left MUSIC VOL/SFX VOL's OFF/level
readout and the MUSIC FILTER ladder (OFF/1X/2X/3X) untranslated, since
that mirrors the Gen 1 screen's own established choice for the same
two helpers.
Gold and Silver had no label-keyed string table at all. The manifests
carried no text section, RomExtractorGen2 had no extractText, and
game.data.text was never assigned, so every call through
src/core/RomText.lua fell back to the literal written beside it. The only
Gen 2 text the cache held was the script text in data/generated/text.lua,
keyed by bank:address for the overworld VM, which nothing can look a battle
line up in.
make_gold_manifest.py now walks data/text/'s five dialogue files for their
labels, the way make_rom_manifest.text_metadata walks pokered's, and embeds
each one's symbol. 889 labels, all of them resolving in both editions.
make_silver_manifest.py inherits the list unchanged and re-resolves the
addresses from pokesilver.sym.
RomExtractorGen2:extractText decodes them into data/generated/rom_text.lua.
The mechanism is the one extractOakSpeech already used for _OakText1-7:
resolve the label, decode from the cart, key by name. What is new is that
the list comes from the manifest rather than being written out in Lua, so
all of data/text/ arrives instead of seven strings.
decodeGen2Text also emits the three runtime name slots it used to drop.
PlaceMoveUsersName, PlaceMoveTargetsName and PlaceEnemysName (home/text.asm)
substitute a battler's name as the line prints, so <USER>, <TARGET> and
<ENEMY> are markers, not glyphs. Skipped as control glyphs, SubTookDamageText
decoded as "The SUBSTITUTE / took damage for" with nothing after it.
Game2:load assigns the table to self.data.text, which is what makes the
existing shared RomText helper work on Gold and Silver at all.
The new cache file is listed in the Gold override rather than bumping
CACHE_FORMAT, so caches built before this stage re-import themselves and Red,
Blue and Yellow are left alone.
findPendingRom answered with the first dump in the save directory whose SHA-1
mapped to any not-yet-ready version. On a device with no file picker that
scan IS the import, so with four dumps in the folder, choosing Red imported
and decoded Blue (#1274).
It now takes an optional version and narrows to it. The two Choose paths
pass self.chooseVersion, since a Choose names the cart it is for. The two
Android USB-drop scans pass nothing and still take the first pending cart of
any version, which is what they are for.
A chosen version with no dump present now imports nothing rather than the
wrong cart, and falls through to the notice that says where to put the file.