subprocess.run(..., capture_output=True, text=True) with no explicit
encoding falls back to locale.getpreferredencoding(False) -- the OS
default codepage. On Windows that's a legacy single-byte codepage
(e.g. cp1252), never UTF-8. When the LuaJIT dump contains a byte with
no mapping in that codepage, subprocess's internal _readerthread
crashes with an uncaught UnicodeDecodeError in a background thread; the
thread dies silently and the caller gets back stdout=None instead of a
string, crashing one line later with
AttributeError: 'NoneType' object has no attribute 'splitlines'.
Concretely, the Yellow-side imported dataset contains:
"_ColosseumHeightText" -> "...6’8” tall!"
The right double quotation mark (U+201D) encodes in UTF-8 as E2 80 9D;
0x9D has no defined character in cp1252, so decoding as cp1252 fails
outright. Verified against the real imported dataset: the Red/Blue-only
dump has zero bytes outside cp1252's defined range; the Yellow dump has
exactly one, at this row.
UTF-8 is the actual encoding these dumps are produced in -- the driver
Lua sources are read/written as UTF-8 throughout this file, and LuaJIT
writes those source strings' bytes back out verbatim -- so passing
encoding="utf-8" explicitly at the three affected call sites
(run_loader, check_data_dump, dump_dataset) is a no-op on platforms
whose default codepage is already UTF-8 (Linux/macOS) and a correctness
fix on Windows.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mod_world_tests is already the mod.world suite and already the T3 tier,
with the same off-the-world refusal pattern the standalone file was
duplicating. Reuses its liveWorld fixture instead of standing up a
second one.
overworld() resolves the world from under the stack, so a call from a
battle hook stacked a second battle over the live one -- on a loss its
afterBattle blacked out and warped with the outer battle still up.
newWild marks the species SEEN before it reports an empty party, so a
refused call still wrote the Pokedex; test the party before building it.
tonumber accepts 5.5, which Pokemon.new writes straight into the stat
calc and the exp curve.
Starting a wild encounter had no supported entry point, so mods built a
BattleState and pushed it themselves -- silently losing onFinish (and
with it evolutions and blackout-on-loss) and pushBattle (entry wipe,
battle theme). Neither failure raises.
Also covers awardExp -> leveledUp -> afterBattle -> checkParty, which
parity_trainer_evolution_order stubs BattleState out of.
A LANGUAGE-profile mod filling the `strings` registry -- the seam #791 opened
up, used for the thing it was opened for. 566 keys: the launcher, OPTIONS,
the save-slot and profile screens, the mod manager, the naming screen, and
the battle menu.
WHAT IT DOES NOT TOUCH is the cartridge. Dialogue, species, items, moves and
places all come out of the player's ROM and stay exactly as they are, so an
English cartridge is still an English adventure with Spanish menus around
it. The lang/ tables for those ship empty on purpose rather than absent:
they are where a full translation would go, and an empty value falls through
to English, so anyone continuing this can fill one row at a time and the
game stays playable throughout.
THE FONT IS THE CONSTRAINT, and it decided the wording. The 8x8 charmap has
no N-tilde, no accented vowels and no inverted punctuation -- the sole
exception in the whole atlas is the small e-acute of POKeMON. So every value
on an 8x8 surface is plain A-Z: DISENO COMBATE, MESETA ANIL, SEGURO? OTRA
VEZ. Not a spelling preference; a glyph that is missing renders as a hole,
which is how the first cut of this shipped "ESPA OL" to a phone. The
launcher draws with a real font and keeps proper Spanish, inverted marks and
all -- the split is by surface, not by taste.
Adding the glyphs to the atlas would let the 8x8 side read properly too, and
lang/font.lua and lang/charmap.lua are already the place that would hook
into. I have not done it here: it is a separate change with its own taste
questions, and it should not ride in on a catalog.
Tested end to end on iOS and macOS.
CANCEL is appended after the `ui.options.rows` hook, deliberately -- that is
what stops a mod from orphaning the exit. But it also means no translation
mod can ever see it: there is no row for one to rewrite, and the hook has
already run by the time it is added.
The result is that a fully translated OPTIONS menu has exactly one English
word left on it, and it is the way out. I hit this with a Spanish catalog
where every row translated and the exit did not.
One call, matching how every other label on this screen is already built.
Nothing changes without a catalog loaded: Strings is an identity function
until a mod supplies one.
Follows the same reasoning as #791.
Two things, the second of which is the reason the first is safe.
A NEW KIND. pickFile("stadium") opens the document picker for a Nintendo 64
cartridge and lands it as picked_stadium.z64. The caller I wrote it for is
the Dramatic Shape voxel mod, which builds Pokemon Stadium battle models out
of the player's own cartridge -- on desktop it opens a dialog for that, and
on iOS it could only print a sandbox path to a screen and ask the player to
put a file somewhere they cannot reach from a phone.
Its own filename, not picked_rom.gb, because that is the name the Game Boy
importer watches: a 32 MB N64 ROM landing there is deleted and then reported
to the player as a broken cartridge.
WHICH IS ALSO WHAT AN UNKNOWN KIND USED TO DO. The switch's default case
treated anything it did not recognise as a Game Boy ROM, so a caller asking
for a kind the build predates lost the player's file -- the worst available
answer to "I have not heard of that one". Unknown kinds are refused now.
That refusal is invisible on its own: pickFile returns false, which is also
what "the picker would not open" returns, and a mod cannot tell them apart.
So the host says what it knows -- love.system.pickFileKinds() returns
"rom,mod,sav,stadium", or nil where there is no bridge. A caller asks first
and keeps whatever fallback it had; the voxel mod shows its folder note
again, which is what it did before any picker existed.
Tested on an iPhone 17 Pro: a Stadium cartridge imports from the picker and
the models build.