After the Fly departure animation finished (bird off-screen) and during
Dig/teleport, the trainer sprite popped back in standing at the old cell
for the whole 32-frame black fade-out before the transition. The
player-hide guard only held while a departure animation was live:
flyAnim went nil the instant path2 completed and the teleportOut
countdown cleared the spin fields at 0, but startWarpTo's Transition
(not isOpaque) keeps the overworld drawing beneath the veil, and the
arrival animation is not armed until setMap's midpoint.
Add a playerHidden flag on OverworldState that bridges the gap:
- set when each departure completes (flyAnim path2 / teleportOut hit 0),
immediately before the warp starts;
- cleared in startWarpTo's Transition enter callback, synchronously
after setMap and before the arrival arms flyArrive / spinDrop, so the
player is never drawable mid-fade and never bare on the landing frame;
- folded into both player-draw guards.
ROM-free regression test (tests/engine/warp_sprite_hidden_bug916.lua)
drives the REAL Transition + setMap headlessly for Dig and Fly and
asserts zero fade frames leave the player drawable bare (would have
observed 31/32 gap frames before the fix). Runs in the CI headless T2
tier.
Dig spin timing/lift and the black fade color are left as-is (fade is
intentional per #607).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
trainers.battleTheme validated and merged onto the trainer record but was
never read: battle music came solely from data.audio.battle[kind] where
kind is computeMusicKind()'s final/gym/trainer/wild. Route both battle-
theme start sites through a single choke point:
- BattleState:playBattleTheme() cues Music.playBattle with the override
(self.trainer.battleTheme via battleTheme()), defaulting to the kind
when unset, so vanilla fights and #782's non-gym Giovanni are unchanged.
- BattleState:enter() and OverworldController:pushBattle() both call it.
- Music.playBattle gains an optional 4th song arg that overrides the kind
default, and real call sites now populate the music.select trainerId.
- Victory jingles stay kind-based: a custom battle theme has no derivable
win-variant.
New ROM-free T2 suite tests/engine/trainer_battle_theme_bug945.lua covers
mod load, override resolution, the choke point, and the nil-override
parity gate.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The originals run GiveItem before printing the received texts, and when
the bag can't hold the TM they print a make-room line instead and leave
EVENT_GOT_TM* unset, so talking to the leader again retries the give.
The victory reward path added the TM straight into the inventory, so a
full bag went to 21/20.
Route the gym TM give through Bag.add, split the TM lines out of the
victory dialogue table into tmPre/tmDialogue/noRoom, and port the
beaten-leader middle branch that re-runs the ReceiveTM script. Saves
that already hold the TM without the flag count as received so they
don't collect a second copy.
Refs #797
checkBoulderPush had an isWarpTileCell escape hatch that let a boulder
be pushed onto any door/warp tile, walkable or not. In pokered,
CheckForCollisionWhenPushingBoulder walks the same wTilesetCollisionPtr
list as player movement (CheckTilePassable) -- there is no hole/warp
exception, so a boulder can never land on a cell the player cannot walk
onto.
The known push targets (CAVERN holes, Victory Road switches) are
walkable tiles in their tileset's coll list already, so removing the
escape hatch only stops pushing boulders into walls.
Fixes#754
Issue #695: Pressing B in PC submenus (BoxMenu, PlayerPC) was exiting
the entire PC session instead of returning to the main PC menu. The
three main-menu items (Bill's PC, player's PC, Prof. Oak's PC) were
missing keepOpen=true, so selecting one popped the main menu off the
stack. Added keepOpen to all three, matching the pattern already used
by BoxMenu and PlayerPC's own rows.
Issue #694: Falling through boulder holes in Seafoam Islands, Victory
Road, and Pokemon Mansion played no sound effect. Added Faint_Fall sfx
before every hole warp -- the scripted onStep holes in seafoam.lua,
story.lua, and story6.lua, plus the warp-tile-based hole detection in
OverworldController takeWarp. Faint_Fall is the companion to Faint_Thud
(already played when boulders fall into holes).
Fixes#695Fixes#694
pushBattle wrapped onFinish to push Transition.battleReturn on any
non-lose result, but that only fires for callers that route through
pushBattle correctly. Moving the same push into BattleState:finish()
instead -- the one choke point every battle (wild, trainer, walk-up,
scripted, link) already passes through on exit -- makes the fade
unconditional rather than dependent on each call site's wiring.
Ports from a downstream fork, hand-surgered hunk-by-hunk to exclude the
fork's randomizer/pokescript work and to skip a FixedStep jitter-tolerance
attempt that never fixed the stutter it targeted.
- src/core/Timing.lua: hardware-accurate frame-delay catalog ported from
pret/pokered, feeding BattleState:waitNext, EffectRegistry's miss/crit
beats, TextBox/ChoiceBox scroll and prompt holds, and the battle
silhouette slide/shake/blink/faint timings.
- Seamless battle transitions: Renderer:drawBattleWipe replaces the old
160x144-only cascade with one wipe drawn over the whole surface at any
zoom or window size; BattleTransition's per-style frame lengths are
corrected against pokered-c's derivation; Transition.battleReturn adds
the post-battle GBFadeInFromWhite the port never had.
- BATTLE SIZE / BATTLE BG options (BattleState:wantsFillScale/bgMode,
Game.fillScaleInStack/worldBgBattleDim): battle surface can fill the
window instead of the fixed integer letterbox, and the area around it
can show white/black/the dimmed overworld instead of only white.
- src/core/FaithfulRes.lua: locks the window to an exact 160x144 multiple.
- Zoom-aware UI anchoring: Renderer:uiScale steps the UI down with survey
zoom (gated to worldActive so the title/intro never shrink);
Renderer:setUIAnchor lets TextBox, ChoiceBox, and an opted-in Menu
(the START menu) pin themselves to a screen edge instead of the
zoomed-out letterbox.
CLOSES#576
The launcher skipped oaks_pc.asm's whole session -- the access text and
the 'Want to get your #DEX rated?' YES/NO -- and played the Pokedex_Rating
jingle the moment the entry was picked, before any text printed. Now the
access text types out, the YES/NO pops, and only once the completion line
and the rating tier have printed does the jingle sound (DisplayDexRating
-> PlayPokedexRatingSfx, auto.wait hands the box to the A/B path), then
the 'Closed link to PROF.OAK's PC.' tail closes the session.
Yellow's IsSurfingPikachuInParty swaps the player's overworld sheet to
a Pikachu-on-a-surfboard when the party mon that knows SURF is a
Pikachu. The recomp was missing both halves of this: the sheet was
never extracted, and the engine had no seam for the swap.
Extraction: SurfingPikachuSprite (gfx/sprites/surfing_pikachu.2bpp)
loads outside SpriteSheetPointerTable via
LoadSurfingPlayerSpriteGraphics2, the same bypass RedBikeSprite uses.
Added the symbol to the Yellow manifest and a parallel extract in
RomExtractor / build_rom_data / extract/sprites, minting
SPRITE_SURFING_PIKACHU. PaletteFX colors it (player OBP palette, same
as the bike).
Engine: new field.playerSprites.surfPikachu (default
SPRITE_SURFING_PIKACHU, guarded so before extraction the ride keeps
the Seel). Player.new caches surfPikachuSprite; pose() picks it when
surfing and the SURF-mon is a Pikachu. New
OverworldState:syncSurfingPikachu derives the flag from partyKnows at
every surf-state toggle (mount, dismount, fly, teleport, blackout,
forced-surf tile, boot-restore). Runtime-only, re-derived at load so a
party change between save and load is honored.
Lane B: RFC at docs/rfcs/0001-surfing-pikachu-sprite.md.
Backward-compatible — existing mods see no change (surf still defaults
to SPRITE_SEEL; surfPikachu only resolves on a Yellow import after
regeneration). Parity tests in tests/parity_surfing_pikachu_sprite.lua
(12/12) and tests/mod_world_tests.lua (19229/19229 with the new
boot-seed checks). tests/parity_cinnabar_east_surf.lua (24/24)
unchanged.
Regeneration required: re-run make_yellow_manifest.py against a
pret/pokeyellow checkout, then re-import the Yellow ROM.
OverworldLoop (home/overworld.asm) jumps straight to .moveAhead while
wWalkCounter is nonzero: JoypadOverworld -- the START check, the A
check, and direction initiation -- only ever runs while the player
stands on a tile, and a button pressed mid-step is simply never seen.
The port ran handleInput() every frame regardless, so a mid-step
A/START pushed its TextBox/StartMenu right there and froze Red between
tiles, mid-animation (the Nurse Joy run-up in the report).
Gate handleInput on player.moving like the original. The port-invented
wall-bonk SFX cooldown is hoisted above the gate so it keeps ticking on
held-direction frames mid-step exactly as before.
Adds tests/parity_midstep_buttons.lua: mid-step and last-frame A/START
presses are swallowed and the step completes, and both buttons work
again once the player stands on the tile.
The vanilla surf/strength flow closed the menu, showed the overworld,
then fired a solid-white blink on the empty map, and set the surfing
sprite while the player was still on land. pokered's
GBPalWhiteOutWithDelay3 runs while the text is still up, so the blink
reads as a text flash.
The blink now sits under the textbox on the stack (only the top state
updates, so it holds its frames until the text closes), and surfing
applies only when the step onto the water happens. Same reorder for
the party-menu STRENGTH texts. The parity surf tests now dismiss the
got-on text before asserting the mount, matching the new order.
Mods that widen field-move rules (use an HM without teaching it, a
rental mon) had to monkey-patch partyKnows, the one function every
field-move path funnels through. Wrap the vanilla check in a hook
instead: next_ is the whole badge-and-knows-move check, so a wrapper
that calls it first keeps vanilla answers winning and only fills the
cases vanilla denies. No hook, no behavior change.
Co-authored-by: johnjohto <johnjohto@users.noreply.github.com>