- Preserve active track voices in Player.stopAt after fast-forward catchup
- Add start-from-0 fallback in Player.stopAt when at timestamp predates the retained snaps ring
- Mute m4a worker during stopAt pause so it does not synthesize empty buffers while fanfare plays
- Ensure Audio.resumeBgm calls Source:play on bgmSource if stopped
The nine docs/game3 notes that came with the waves - the review triage
ledger, e10 opcode spec, RSE seam design, both sweep ledgers, the pret
citation audit, the artifact-conversion notes, the test baseline and the
merge trial - stay local working documents instead of shipped files. The PR
carries the code, the harness and the tests; the notes carry the team's
bookkeeping.
Removing them from the tip cancels their addition against the base, so the
net docs/game3 diff in this PR is empty while the files themselves stay on
disk (untracked and listed in .git/info/exclude, so nothing re-adds them by
accident).
The wave-3 tint batch as its own commit, so the harness fold before it stays
reviewable on its own.
multichoice.lua: Multichoice.COUNTS is the live option-count source for the
cart - 65 of 65 entries match pret's src/script_menu.c, where
ScriptMenu_MultichoiceGrid reads sMultichoiceLists[id].list and .count
(:915-930). The table is load-bearing in a ROM-less run: with no import
cache present there is nothing else to ask how many options a list has.
adapters.lua: the multichoice operand reader no longer treats row[4] as an
option count. pret asm/macros/event.inc:893-899 lays the command out as
x, y, multichoiceId, ignoreBPress - four bytes and no count field - so the
old read shifted the operand stream and every later decode in the command
went wrong with it.
tests/game3_photo_tint_boundary_test.lua: pins the cache-miss arity fallback
and the yield behaviour it rests on, which is the BUG2 root cause. Run
twice green; the existing tint suite still passes 33/33 alongside it.
Ships the CI fix together with the test-harness work that supports it, so
every tier stays honest on every machine.
scripts/test.sh
- T6 records missing-artifact failures as skips when no imported game3 data
exists at all (empty CI identity, no data/generated): the data-driven
top-level suites assert instead of self-skipping on a fresh runner. The
skipped suite names are printed with that reason; with data present the
same failure gates exactly as before.
- The T3 content verdict now weighs the exit code and the FAIL-line allowlist
together. The exit code used to be discarded, so a mid-run crash
(unresolvable require under POKEPORT_DATA_DIR) printed green checks, zero
FAIL lines, and passed. A non-zero exit with no FAIL line is reported as a
crash with the error line captured - a crash is not a pass.
Test-suite fixes, each run green (several twice)
- tests/run_tests.lua: restore per-suite leaked state and add the Data seam,
so one suite's loaded data cannot leak into the next.
- tests/mod_runtime_tests.lua: save and restore the environment around each
case.
- tests/parity_ss_anne_rooms.lua: _loadModule raw-order fix (30/30).
- tests/parity_ss_anne_departure.lua: the Data seams plus the playMap stub.
- playMap stubs for parity_rival_walkoff, parity_tower_rival,
parity_silph_rival_bug2241 and parity_ss_anne_guard.
- Class-B seams in mod_registry_tests, mod_catalog_tests, mod_ui_tests,
parity_ball_shake_anim, parity_wavy_screen, parity_hidden_coins_bcd_bug1810,
parity_mart_stock and parity_rocket3_sight_bug1814.
Together these close the 57 failing checks that were on the content tier
(allowlist stays at 0), so a content run on a machine with imports ends
ALL TESTS PASSED.
Three follow-ups to the merge snapshot, kept as their own commit so the
review of the wave commit stays intact:
- The PC panel's left data panel broke in the merge: upstream inlines the
egg-aware species read into frontPic while the M8 dedup had removed the
duplicate local, so `sp` was referenced with no binding and the luacheck
gate failed. One shared local sp = Pokemon.speciesOrEgg(hoveredMon) serves
both the TV sprite and the stats card
(pokefirered/src/pokemon_storage_system_data.c:1034, :1057,
MON_DATA_SPECIES_OR_EGG).
- Trainer-card vars fall back to the stringified key: persist_sidecar and
Flags.serialize write tostring(id) keys into session.vars, so a numeric miss
used to read every var as 0.
- tests/game3_trainer_card_photo_tint_test.lua drives the whole Game Corner
photo -> card tint chain (setvar -> UpdateTrainerCardPhotoIcons ->
Flags.serialize/loadInto -> cardData) and pins c.monIconTint/c.monSpecies.
Second batch of the Gen 3 review work: the review lanes closed their queues
and the test gate grew with them.
Review census: 139 routed findings -- 118 fixed, 14 invalid (the report had
Gen 4+ semantics in more than one place), 6 struck as stale after
re-verification, 1 re-routed. Every fix carries its own pret citation in
docs/game3/review-v3-triage.md; the E10 opcode closure is itemised in
docs/game3/e10-opcode-spec.md (18 ops wired, 2 reclassified to the seam class).
Representative ROM-grounded changes:
- The POKéMON start-menu entry is now gated on FLAG_SYS_POKEMON_GET the way
retail does it (pokefirered/src/start_menu.c:217-218, flag 0x828).
- Money ops read their amount raw and gate the change on the disable byte
(pokefirered/src/scrcmd.c:1798-1830, pokefirered/asm/macros/event.inc:1166-1186);
random and the warp family VarGet theirs
(pokefirered/src/scrcmd.c:455-461, :719-731).
- The HM table matches FRLG: there is no Whirlpool HM
(pokefirered/include/constants/items.h:411-418).
- The day-care party-full guard follows src/daycare.c:525, :1081.
- Knock Off keeps its battle-scoped send-out mask
(pokefirered/src/battle_script_commands.c:2730-2752, :4489), carried from
the first batch.
Structural work: the I6/I9/J1 architecture items landed as seams (profile
selector, capability flags, font provider, virtual-object layer), the adopted
footprint register keeps a KEEP verdict, the quantizer target is met, and the
30 drain items plus carves 1-4 are folded in.
Tests: T6 now runs 282 top-level suites (273 + 9 new), the re-sweep ends at
267 pass / 0 fail, the engine tier is 634 suites, modkit 37, and the full
gate ran green twice with T3 active on an imported Red cache. Documented
skips are the lua5.4 oversize-save oracle where lua5.4 is absent and the
config-partials listed in docs/game3/game3-artifact-conversions-v3.md.
Docs shipped: review-v3-triage.md, e10-opcode-spec.md, rse-seams.md,
game3-suite-sweep-v113.md, game3-artifact-conversions-v3.md,
test-baseline-v3.md (plus the first-batch docs already on the branch).
Braille.encode spells a braille message from Latin letters, through
Braille.CODE and the Latin charmap byte of any other character. That
covers the US cart, but the European carts' braille lines use cells no
character reaches: German ä (dots 3-4-5), the full cell, dot 5 alone. A
translation mod therefore cannot hand over the German, Spanish or Italian
braille, and the 39 lines of the Dotted Hole, Ruin Valley and Mt. Ember
stay in English in every language.
pret's braille font holds every dot combination
(pokefirered/include/characters.h:282), so Braille.encode now draws a
Unicode braille character (U+2800-U+283F) as that very cell, mapping
Unicode's dot bits to the cart's cell numbering (dot 1 = 0x01, 4 = 0x02,
2 = 0x04, 5 = 0x08, 3 = 0x10, 6 = 0x20). A cell spells its own number sign,
as the carts' braille does. Latin text is spelled as before.
Second wave of fixes from the Gen 3 codebase review (210 findings), the
regressions the full-suite sweep surfaced, and a T6 tier so the top-level
tests/game3_*.lua suites run inside ./scripts/test.sh instead of outside it.
Battle: Knock Off and Thief/Trick item persistence follow the ROM, not the
review - the party slot is never written through during battle, and the
knocked-off mask suppresses the item on later send-outs
(pokefirered/src/battle_script_commands.c:2731 MOVE_EFFECT_KNOCK_OFF with the
STICKY_HOLD guard at :2732, opponent-steal guard at :2610-2622, mask use at
:4489). Rapid Spin keeps its one-per-use chain order (:8435-8474); Growl
keeps pret's target (src/data/battle_moves.h, include/battle.h:63).
Scripting: operand layouts match pret/asm/macros/event.inc - givemon 15 bytes
(:989-997), comparestat {B,W} (:1573-1576), setptr/loadbytefromptr/setptrbyte
take a word pointer (:118-137). 18 of the 20 E10 ops are wired per
src/scrcmd.c (per-op citations in docs/game3/e10-opcode-spec.md), money and
random and the warp family read their operands as VarGets
(src/scrcmd.c:1798-1830, :455-461, :719-731), and the day-care party-full
guard is in place (src/daycare.c:525,:1081).
Field and UI: fishing counts rounds as the ROM does
(src/field_player_avatar.c:1740-1765), an unresolved map section no longer
reports Pallet Town (src/region_map.c:3782), and a font provider sits behind
a FireRed capability/profile seam.
Save data: profile-driven Options.block, a save version round-trip, and slot
id validation that blocks path traversal (slotDiskPath("firered", "../evil")
returns nil).
Importer: the parallel import path now writes both completion markers,
object kind/clone bytes decode as pret defines them
(include/global.fieldmap.h:110-130), and the HM table drops Whirlpool
(include/constants/items.h:411-418).
Tests: six new scenario suites (battle_ai, capture, event, menu, move,
overworld) and eight engine suites (profile, capabilities, cache paths, font
provider, options block, save version round-trip, version dispatch,
versions_game). T6 runs every top-level game3 suite at GAME3_JOBS default 8
with per-suite logs, failure cause classification and a KNOWN_GAME3_FAILURES
ceiling of 23 as a shrinking guard.
Docs: the working notes under docs/game3 (triage ledger, pret citation audit,
e10 opcode spec, RSE seams, sweep v3/v113, test baseline, artifact
conversions, merge trial).
Full gate at this snapshot: exit 0, all tiers passed - engine 624/624,
T6 279/279 (0 known, 0 fresh failures), gen2 146/146, modkit 37/37, luacheck
clean, privacy gate 3/3 over a 9,711-file publication set.
The party menu drew an egg's slot like any other mon's: level, gender, HP
numbers and HP bar. pret's DisplayPartyPokemonData prints only the nickname
for an egg and blits the slot without its HP frame (sSlotTilemap_MainNoHP /
WideNoHP; pokefirered/src/party_menu.c:781, :2197), and
DisplayPartyPokemonDescriptionData likewise skips the level and gender of an
egg (party_menu.c:804). The slot now does the same, reusing the no-HP frame
the description slots already draw.
Three screens that show a single mon still drew an egg as the species it
will hatch into, and printed its stored nickname untranslated:
- The PC's hovered-mon panel drew the hatch species' front pic, the stored
"EGG" nickname, "/PICHU", gender, level and held item. pret draws the
SPECIES_OR_EGG pic and, for an egg, prints only gText_EggNickname with the
other lines blank (pokemon_storage_system_data.c:1034, :1091).
- The summary's egg page drew the hatch species' front pic, where pret uses
SPECIES_OR_EGG (pokemon_summary_screen.c:4016). It also recognised an egg by
mon.isEgg alone; it now uses Pokemon.isEgg like the rest of the engine.
- The trade scene drew a traded egg as its species and named it by the stored
nickname (trade_scene.c:757, :1239).
The script string buffers (BufferMonNickname and the other nicknameOf
helpers) now also give the language's EGG for an egg, as GetMonData
(MON_DATA_NICKNAME) does (pokemon.c:3020).
The party, the PC boxes and the release animation drew an egg with the icon
of the species it will hatch into, so a Pichu egg showed as a Pichu. pret's
party menu picks the icon from GetMonData(MON_DATA_SPECIES_OR_EGG), which is
SPECIES_EGG for any egg (pokefirered/src/pokemon.c:3245, party_menu.c:2655).
Pokemon.speciesOrEgg does the same, and the icon call sites use it. The
species pass only extracted icons up to NUM_SPECIES - 1, so the egg extractor
now also bakes gMonIconTable[SPECIES_EGG] as pokemon/icons/412.rgba. Cache
version 114 re-imports existing caches so the icon appears.
Party.giveMon stores "EGG" as a new egg's nickname (party.lua for a
gift egg, breeding.lua for the Day-Care's), and the party slots and the
other screens that name a mon through Pokemon.displayName and
displayMonName print that nickname as it is. A translated game therefore
listed its eggs as EGG, while the summary, which prints Strings("EGG")
for an egg, already said OEUF, EI, HUEVO or UOVO.
The cart does not rely on the stored nickname either: its Day-Care
writes the Japanese タマゴ there (daycare.c:1100), and GetMonData
returns gText_EggNickname for any egg's nickname (pokemon.c:3020). Do the
same in both name helpers: an egg reads as Strings("EGG"), whatever its
nickname holds, which also covers the eggs saves already carry. A
hatched egg, whose nickname is cleared, goes back to its species name.
The bufferitemnameplural checks asserted hard-coded item names, but those
come from data/generated/gba/items/pack.lua, which does not exist in a
ROM-free checkout. CI failed 2/70 there ("got BERRIES"), while a machine
with an imported pack passed, so the suite was green locally and red in CI.
Assert the pluralisation rule against each item's own singular name
(ItemsData.displayName) instead: a Poké Ball stack gains "S", a berry stack
has its final letter replaced by "IES", and quantity 1 stays singular.
72/72 with and without the pack; the full engine tier is 616/616 with no
ROM data.
The rest of the Gen 3 review fixes, each with a gated suite in tests/engine/.
ROM semantics were checked against pret/pokefirered.
Scripts
- givemon carried the wrong operand layout, found earlier; four more layout
desyncs came out of pret asm/macros/event.inc: comparestat is {byte,word},
and setptr / loadbytefromptr / setptrbyte each carry a leading byte plus a
word. A wrong size mis-decodes every instruction after the bad one, so
Versions.CACHE_VERSION moves to 113 and existing caches re-import.
- handlers for previously handler-less verbs: comparestat,
bufferitemnameplural, setmonmove, setmonmetlocation, the modern
fateful-encounter pair, the script-locals family (copylocal, setptr,
loadbytefromptr, setptrbyte, copybyte, compare_local_to_* and
compare_ptr_to_*), the RAM-script family (setvaddress, vgoto, vcall,
vgoto_if, vcall_if, vmessage, vbuffermessage, vbufferstring, endram,
returnram) and the same-map forms of the *at verbs.
- setdooropen / setdoorclosed read their coordinates through VarGet.
Battles
- Knock Off and Thief / Trick persist the item change instead of only
touching the in-battle copy.
- knocked-off party slots are tracked in a bitmask, so a slot reused later
does not inherit the flag.
Field and UI
- Player.reset restores facing and clears the surf flags.
- a definition-less Map.load no longer leaves collision unbound.
- an unresolved region-map section no longer reports PALLET TOWN.
- the naming screen splits input from the timer, so update(dt) stops
indexing a number.
- the hall of fame commits through the engine save path and serializes its
fields.
Persistence
- gameStats, the link-battle records and the trainer card are serialized.
- the PC deposit refuses at the 999 cap instead of destroying the overflow.
Robustness
- Data.load runs cached modules sandboxed.
- the file browser quotes shell arguments.
- .meta dimensions are bounds-checked and mids.idx validates its header.
do_save() discarded both pcall results and set _phase = "saved" unconditionally,
so a refused or failed save still showed "<player> saved the game." and closed the
menu. Only a truthy saveGame now reports success; a raised error, an explicit
false, or a missing saveGame enters a new save_failed phase, logs the reason via
Logger, and shows "The game could not be saved."
Test: tests/engine/game3_save_menu_failure_test.lua (8/10 fail on the previous
code, 10/10 pass here).
- Save Editor: Implement full Gen 3 FireRed save editing including PID/personality, IVs/EVs, natures, ability slots, gender, shiny toggles, held items, Pokédex flags, and event/trainer toggles.
- Scripting & Specials: Add Pokédex rating evaluations, Magikarp/Heracross size records, Trainer Fan Club tracking, and FireRed event flag mappings.
- Overworld & Rendering: Interleave metatile and actor draw calls by row bucket with OAM elevation priority sorting for bridges and overhead layers.
- ROM Extractor: Parallelize Gen 3 extraction across 4 concurrent love.thread workers with love.filesystem searchers for cross-platform support (Switch/Xbox/Mobile/Desktop).
- LZ77 Decompressor: Accelerate GBA LZ77 with preallocated FFI buffers and 0-based displacement indexing ensuring 100% byte-for-byte asset parity.
- Testing: Add unit test suites for LZ77, size records, Pokédex rating, Fan Club, elevation priority, Seafoam puzzles, and Gen 3 save editor operations.