Seed Mt Moon Super Nerd dialogue (#1743), play item-use heal jingles
(#1635), use white fades for fly/teleport warps (#1644), add a
Yellow-only Advanced palette overlay (#1639), parse Yellow Super Rod
data including Safari Dragonair (#1074), and apply title rOBP0 when
baking Pikachu eye OAM so pupils stay black with white glints.
Strings(stat:upper()) is a dynamic argument -- tools/modkit.py's
STRINGS_CALL harvester only matches a literal string right after
Strings(/Strings.source(, so it can't discover "ATTACK"/"DEFENSE"/etc.
from these call sites. Translation coverage happened to still work
only because the same literals are independently harvested from
unrelated call sites (MoveEffects.lua's STAT_LABEL, BattleState.lua's
literal Strings("ATTACK") calls) -- real but fragile, found in review.
Reuse the codebase's existing pattern for exactly this situation
(MoveEffects.lua's STAT_LABEL): a local table built at require time
with Strings.source(...), which the harvester can see, resolved to a
translated label at use time with Strings(TABLE[key]). Adds one such
table to ItemEffects.lua (covering its X-item and vitamin call sites,
including "hp") and one to TrainerAI.lua.
Both the player-side and AI-trainer stat-rise messages (X ATTACK/
DEFENSE/etc. and the vitamins) passed the raised stat's name as a raw
uppercase Lua string (stat:upper()), bypassing Strings() entirely, so
it always rendered in English regardless of the active language even
though the surrounding sentence template was already translated.
Wrap the substituted stat name in Strings() at every call site
(src/inventory/ItemEffects.lua's two player-side messages and
src/battle/TrainerAI.lua's AI-trainer X-item message, found in
review), reusing the same "ATTACK"/"DEFENSE"/"SPEED"/"SPECIAL"/"HP"
keys SummaryMenu.lua's stat labels already look up the same way.
ItemEffects.use walked speciesDef.tmhm with a bare ipairs() to check
whether the species could learn the machine's move. A record with no
tmhm field at all -- a mod species that never set one, or any record
missing it for whatever reason -- hit ipairs(nil) and took the whole
game down on the first TM/HM use, rather than reaching the ordinary
"can't learn that move" refusal a species whose list simply omits the
move already gets.
An absent list now reads the same as an empty one: nothing to learn,
same refusal, same sound, same text.
When a Pokemon faints in battle, onFaint() clears it from the battle
participant set. The revive item restored HP but never re-added the
mon to self.participants, so at awardExp() time the revived mon passed
the HP check but failed the participant gate -- getting no exp.
Fix: re-add the revived mon to battle.participants in the revive item
effect, so it's counted as a participant and receives its share of
experience at battle end.
Fixes#648
Introduce a tuned .luacheckrc and scripts/lint.sh so the engine has a
standing static-analysis baseline -- the tool that would have caught both
bugs in the previous commit before they shipped.
The config is high-signal by design: it keeps the categories that catch
real defects (undefined globals/locals, unused values, unreachable code)
and mutes the cosmetic ones the codebase deliberately lives with (a self/dt
an interface requires but a method ignores, documented empty fall-through
branches, long lines). It marks `love` mutable (games assign callbacks onto
it) and teaches it LuaJIT's table.unpack.
.luacheckrc is tracked via a .gitignore exception, matching how .github and
.gitignore opt out of the blanket dotfile ignore.
`luacheck src` now reports 7 benign warnings and 0 errors, down from 185.
Also drop one dead `require` (ItemEffects loaded src.pokemon.Pokemon and
never used it).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q6bFAiQyZ5jDmewsbB4LG9