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.
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.
Drives a real turn per case and compares the emitted line to the label in
data/text/battle.asm. Six of the eleven checks fail against the text as it
stood.
The tail case wraps each changed line through Chrome.wrap at the box's own
width and holds it to the two rows printMessage draws. That is the check
that caught SpikesText needing a third row, which is why Spikes is not in
this change.
Five messages in the Gen 2 battle code are written by hand rather than taken
from data/text/battle.asm, and each has drifted from what the game prints:
SuperEffectiveText lost its hyphen and its line break
NotVeryEffectiveText ended on three periods, not the
ellipsis glyph the charmap carries
BattleText_TheresNoPPLeftForThisMove dropped "There's"
PlayerHitTimesText/EnemyHitTimesText printed "Hit 3 time(s)!", showing
the parenthetical on screen; Gen 1
already prints "Hit 3 times!" via
_HitXTimesText
StartPerishText printed a sentence no cart prints
The break is \n, which is what RomExtractorGen2 decodes the cart's own $4e
into, so these read as an extracted line would. Each goes through Strings
now, which is what the rest of this file already does with its messages.
SpikesText is left alone and the reason is written down beside it: its third
row is a `cont`, and engine messages set self.message directly rather than
going through showPages, so printMessage would cut the row carrying <TARGET>.
battle.exp_award hands a mod ctx.applyShare(mon, split, announce) on both
generations, and on Gen 1 the third argument decides whether the mon's
GainedText box is printed -- which is how a mod paying the whole party
prints ONE summary line instead of a box per recipient. Gold accepted
the argument and ignored it, so the same mod source printed one line on
Red and one per party member on Gold.
The Exp Share mod is the live case: it declares games gen1+gen2 and its
description promises "a single shared-exp line instead of one message per
Pokemon", passing true for the fighters and nil for the bench exactly as
the Gen 1 seam asks. On Gold every nil call announced anyway, so a
five-mon party turned every KO into six boxes. There was no mod-side
fix: the emit sits behind no hook, and the argument meaning "quietly" was
discarded.
Gold now reads it, and ONLY when it is actually passed -- by argument
count, not by value. select("#", ...) counts an explicit nil, so
applyShare(mon, split) is distinguishable from applyShare(mon, split,
nil); the first is a Gen 2-era call written against a seam that always
announced and keeps announcing, the second is a deliberate "pay this one
quietly" and is now silent on both games. No mod that exists today
changes behaviour, and a mod that passes the argument gets parity.
Only the { kind = "experience" } event is affected. A silent award is
still a whole award: exp, stat exp, battle.exp_gained, "grew to level",
learned moves and the forget prompt are untouched, in the same order.
giveExperiencePass takes a sixth `silent` parameter that defaults to
announcing, so both of the cart's own passes are unchanged.
RFC: docs/rfcs/0012-gen2-exp-award-announce.md
Docs: docs/mod-api-gen2-compat.md gains the reading and the residual
omitted-argument difference beside the existing payload note.
Tests: tests/gen2_exp_share_test.lua grows the two Route B tests -- the
no-mod parity case and the seam driven through hooks:wrap -- and
its 23 existing checks are unchanged.
Implemented a new deinterleave function in ImageWriter to restore row-major order for PNGs. Updated RomExtractorGen2 to utilize this function for extracting Slot Machine graphics, and added new functions for padding and writing graphics sheets. Updated the ROM manifests to include necessary symbols for Slots and Card Flip assets.
Added functions to write raw graphics assets for the Slot Machine and Card Flip games, ensuring that the necessary files are generated when the corresponding symbols are present in the ROM. Updated the manifest to include required symbols for these assets, preventing fallback to labelled cells when graphics are missing.