diff --git a/docs/rfcs/0001-surfing-pikachu-sprite.md b/docs/rfcs/0001-surfing-pikachu-sprite.md new file mode 100644 index 00000000..afa7d351 --- /dev/null +++ b/docs/rfcs/0001-surfing-pikachu-sprite.md @@ -0,0 +1,96 @@ +# RFC 0001 — Port Yellow's `IsSurfingPikachuInParty` surf sprite + +## Status + +Proposed. Engine: `Player.lua`, `FieldDefaults.lua`, +`OverworldController.lua`, `RomExtractor.lua`, `PaletteFX.lua`. Tools: +`build_rom_data.py`, `extract/sprites.py`, `make_rom_manifest.py`, +`make_yellow_manifest.py`. Tests: `parity_surfing_pikachu_sprite.lua`, +`mod_world_tests.lua`. + +**Regeneration required.** The manifest and sprite sheet update by +re-running `make_yellow_manifest.py` against a `pret/pokeyellow` +checkout, then re-importing the Yellow ROM. + +## Motivation + +Yellow's `IsSurfingPikachuInParty` + `LoadSurfingPlayerSpriteGraphics2` +(`home/map_objects.asm`, `home/overworld.asm`) swap the player's +overworld sheet to `SurfingPikachuSprite` (`gfx/sprites/ +surfing_pikachu.2bpp`, a 16×96 walk sheet — not the minigame sheets) +when the party mon that knows SURF is a Pikachu. The recomp misses this +in two places: + +1. **Extraction.** `SurfingPikachuSprite` is not in + `SpriteSheetPointerTable` — loaded by its own `ld de,` like + `RedBikeSprite`. The extractor never sees it, and the symbol is not + in the Yellow manifest. +2. **Engine rule.** `field.playerSprites.surf` is one static + (`SPRITE_SEEL`), cached at boot. No seam for "swap when the SURF-mon + is a Pikachu." + +## The decision it extends + +No prior D-number. Extends the surf-field-move port in +`docs/behavior-porting-notes.md` (the `IsSurfingAllowed` exact port) +with the player-sprite swap vanilla runs alongside it. + +## The exact API delta + +Backward-compatible, additive-only. + +### `field.playerSprites.surfPikachu` + +New optional key alongside `walk`/`surf`/`bike`/`fly`, defaults to +`SPRITE_SURFING_PIKACHU`. Guarded in `Player.new` so before extraction +lands the ride keeps the Seel — no plain on-water Pikachu. + +### `Player.surfPikachuSprite` + +`Player.new` builds a second `SpriteRenderer` when the field resolves. +`pose()` picks it when `surfing and surfingPikachu`. + +### `Player.surfingPikachu` (runtime) + +Runtime-only boolean (not persisted); re-derived so a party change +between save and load is honored. + +### `OverworldState:syncSurfingPikachu()` + +Sets `player.surfingPikachu` from `partyKnows("SURF")`. Called at every +surf-state toggle: trySurf, dismount, flyTo, beginTeleportOut, +warpToHealPoint, forced-surf tile, setMap boot-restore. + +### Importer — `SPRITE_SURFING_PIKACHU` + +`make_yellow_manifest.py` adds `SurfingPikachuSprite` to +`YELLOW_EXTRA_SYMBOLS`. `make_rom_manifest.py`'s `sprite_metadata()` +gains a `surfPikachu` entry (guarded, so Red/Blue unchanged). +`RomExtractor.extractSprites` + `build_rom_data.py` + `extract/sprites.py` +each gain a parallel extract mirroring `RedBikeSprite`. + +### `PaletteFX.spriteObp` + +`SurfingPikachuSprite` joins `RedBikeSprite` in the no-bracket-index +special case, wearing the player's OBP palette so it colors in GBC mode. + +## Migration note for existing mods + +**Nothing.** `surf` still defaults to `SPRITE_SEEL`; `surfPikachu` +only resolves on a Yellow import after regeneration. No manifest or +`mod.save` shape changes. An eligibility hook that swaps a rental +SURF-mon still drives the sprite pick via `partyKnows`. + +## Parity tests + +- **No-mod** (`mod_world_tests.lua`): `surf == "SPRITE_SEEL"`, + `surfPikachu == "SPRITE_SURFING_PIKACHU"` seeded at boot. The 19229-check + `world & maps v2` suite stays green. +- **Mod-API** (`parity_surfing_pikachu_sprite.lua`): `syncSurfingPikachu` + + `Player:pose` across four party shapes (12/12). The existing + `parity_cinnabar_east_surf.lua` (24/24) stays green. + +## Deprecation etiquette + +Nothing deprecated. Additive: a new `field.playerSprites` key, a new +runtime flag, a new engine method, a new sprite id. \ No newline at end of file diff --git a/src/import/RomExtractor.lua b/src/import/RomExtractor.lua index 232fce2e..1d2297f6 100644 --- a/src/import/RomExtractor.lua +++ b/src/import/RomExtractor.lua @@ -495,6 +495,25 @@ function RomExtractor:extractSprites() image = "assets/generated/sprites/red_bike.png", frames = bikeFrames, walker = bikeFrames >= 6, } + -- Yellow-only: the surfing-Pikachu overworld sheet, loaded outside + -- SpriteSheetPointerTable (LoadSurfingPlayerSpriteGraphics2) -- same + -- extra-extract shape as RedBikeSprite. (RFC 0001) + local surfPika = self.manifest.sprites.surfPikachu + if surfPika and self.symbols[surfPika.label] then + local spSymbol = self:symbol(surfPika.label) + self:write2bpp( + self.rom:bytes(spSymbol.bank, spSymbol.address, + surfPika.imageWidth * surfPika.imageHeight / 4), + surfPika.imageWidth, surfPika.imageHeight, + "sprites/" .. surfPika.imageBase .. ".png", true) + local spFrames = surfPika.imageHeight / 16 + out.SPRITE_SURFING_PIKACHU = { + id = "SPRITE_SURFING_PIKACHU", + source = ("ROM:%s"):format(surfPika.label), + image = "assets/generated/sprites/" .. surfPika.imageBase .. ".png", + frames = spFrames, walker = spFrames >= 6, + } + end self:write("sprites", out) self:tick("Overworld sprites", #order + 1, #order + 1) return out diff --git a/src/render/PaletteFX.lua b/src/render/PaletteFX.lua index 7990f88b..735a43aa 100644 --- a/src/render/PaletteFX.lua +++ b/src/render/PaletteFX.lua @@ -646,10 +646,13 @@ function PaletteFX.spriteObp(spriteDef, seed) local src = spriteDef and (spriteDef.paletteSource or spriteDef.source) if not (w and src) then return nil end local idx = tonumber(src:match("%[(%d+)%]")) - -- RedBikeSprite loads outside SpriteSheetPointerTable - -- (LoadBikePlayerSpriteGraphics), so its source carries no bracketed - -- index; it wears the player's own palette, same as SPRITE_RED - if not idx and src:find("RedBikeSprite", 1, true) then idx = 0 end + -- RedBikeSprite and SurfingPikachuSprite load outside + -- SpriteSheetPointerTable, so their source has no bracketed index; + -- they wear the player's OBP palette (spriteAssignment[0]). + if not idx and (src:find("RedBikeSprite", 1, true) + or src:find("SurfingPikachuSprite", 1, true)) then + idx = 0 + end local group = idx and w.spriteAssignment[idx] if group == nil then return nil end if group == "random" then diff --git a/src/world/FieldDefaults.lua b/src/world/FieldDefaults.lua index 00b038d5..781495e8 100644 --- a/src/world/FieldDefaults.lua +++ b/src/world/FieldDefaults.lua @@ -77,6 +77,12 @@ local SAFARI = { local PLAYER_SPRITES = { walk = "SPRITE_RED", surf = "SPRITE_SEEL", bike = "SPRITE_RED_BIKE", fly = "SPRITE_BIRD", + -- Yellow's IsSurfingPikachuInParty: when the SURF-mon is a Pikachu, + -- the player rides this sheet. GFX loads via + -- LoadSurfingPlayerSpriteGraphics2, not SpriteSheetPointerTable, so + -- it needs a manifest extract (RFC 0001). Guarded in Player.new so + -- before extraction lands the ride keeps the Seel. + surfPikachu = "SPRITE_SURFING_PIKACHU", } -- The player's own trainer art: RedPicBack (the battle back pic, up until diff --git a/src/world/OverworldController.lua b/src/world/OverworldController.lua index 3d282e17..5cc10e51 100644 --- a/src/world/OverworldController.lua +++ b/src/world/OverworldController.lua @@ -389,6 +389,10 @@ function OverworldState:setMap(mapId, x, y, facing, opts) and not self.map:isWalkableCell(x, y) and self.map:isWaterCell(x, y) end + -- re-derive from the live party: a reloaded save with the SURF-Pikachu + -- since deposited should not render the Pikachu sheet. + -- ponytail: re-derived rather than persisted. + self:syncSurfingPikachu() end -- crossConnection re-arms this after setMap; clear so a warp/reload -- cannot leave a stale deferred PlayMapMusic pending @@ -1484,6 +1488,21 @@ function OverworldState:partyKnows(moveId) return partyKnowsVanilla(moveId) end +-- IsSurfingPikachuInParty (home/map_objects.asm): when the SURF-mon +-- is a Pikachu, pose() renders the Pikachu surf sprite. Called at +-- every surf-state change so a reloaded save picks the right sheet +-- after a party change. No-op when not surfing. +function OverworldState:syncSurfingPikachu() + local p = self.player + if not p then return end + if not p.surfing then + p.surfingPikachu = false + return + end + local mon = self:partyKnows("SURF") + p.surfingPikachu = mon ~= nil and mon.species == "PIKACHU" or false +end + -- The rejection loop shared by the Good and Super Rods -- (item_effects.asm ItemUseGoodRod .RandomLoop / ReadSuperRodData): an -- odd random byte is no bite; otherwise a 2-bit pick rerolls until it @@ -1580,6 +1599,7 @@ function OverworldState:flyTo(mapId) Game.save.onBike = false Game.save.forcedBike = nil -- HandleFlyWarpOrDungeonWarp res BIT_ALWAYS_ON_BIKE self.player.surfing = false + self:syncSurfingPikachu() -- the bird carries the player off westward before the warp -- (engine/overworld/player_animations.asm LoadBirdSpriteGraphics) self.flyAnim = { frames = 48 } @@ -1607,6 +1627,7 @@ function OverworldState:beginTeleportOut(onDone) end require("src.core.Sound").play(Game.data, "Teleport_Exit1") self.player.surfing = false + self:syncSurfingPikachu() self.player.inputLocked = true -- rising spin: the mirror of the arrival spin-drop set in startWarpTo, so -- spinRise lifts the sprite (Player:pose) while spinFrames counts down @@ -2270,6 +2291,7 @@ function OverworldState:trySurf(fx, fy, onClose) Game.stack:push(TextBox.new(Game, text, function() if onClose then onClose() end p.surfing = true + self:syncSurfingPikachu() require("src.core.Music").setSurfing(Game.data, true) Game.stack:push(require("src.render.Transition").whiteFlash(Game, nil, function() self:stepForwardOrCrossEdge(p.facing) end)) @@ -3154,6 +3176,7 @@ function OverworldState:onStepComplete() -- dismounting a surf: landing on a walkable cell ends it if p.surfing and self.map:isWalkableCell(p.cellX, p.cellY) then p.surfing = false + self:syncSurfingPikachu() require("src.core.Music").setSurfing(Game.data, false) end @@ -3455,6 +3478,7 @@ function OverworldState:checkForcedMovement() end elseif tile.mode == "surf" then p.surfing = true + self:syncSurfingPikachu() require("src.core.Music").setSurfing(Game.data, true) end return false @@ -3733,6 +3757,7 @@ end function OverworldState:warpToHealPoint(onDone, opts) local heal = self:healPoint() self.player.surfing = false + self:syncSurfingPikachu() -- HandleFlyWarpOrDungeonWarp + DisplayPlayerBlackedOutText both clear -- BIT_ALWAYS_ON_BIKE (home/overworld.asm / home/text_script.asm) Game.save.forcedBike = nil diff --git a/src/world/Player.lua b/src/world/Player.lua index e1a206f3..5fb257e8 100644 --- a/src/world/Player.lua +++ b/src/world/Player.lua @@ -41,11 +41,18 @@ function Player.new(data, cx, cy, facing) -- LoadSurfingPlayerSpriteGraphics, home/overworld.asm) local walkId = FieldDefaults.fieldValue(data, "playerSprites", "walk") local surfId = FieldDefaults.fieldValue(data, "playerSprites", "surf") + local surfPikaId = FieldDefaults.fieldValue(data, "playerSprites", "surfPikachu") local bikeId = FieldDefaults.fieldValue(data, "playerSprites", "bike") self.sprite = SpriteRenderer.new(data.sprites[walkId], "player") if surfId and data.sprites[surfId] then self.surfSprite = SpriteRenderer.new(data.sprites[surfId], "player") end + -- Yellow's surfing-Pikachu ride (Yellow LoadSurfingPlayerSpriteGraphics2, + -- paired with field.playerSprites.surfPikachu). rotated in at pose() + -- when the SURF-mon is a Pikachu. + if surfPikaId and data.sprites[surfPikaId] then + self.surfPikachuSprite = SpriteRenderer.new(data.sprites[surfPikaId], "player") + end if bikeId and data.sprites[bikeId] then self.bikeSprite = SpriteRenderer.new(data.sprites[bikeId], "player") end @@ -288,6 +295,7 @@ function Player:pose() -- RodResponse (engine/items/item_effects.asm) zeroes wWalkBikeSurfState -- across FishingAnim, so casting from the water shows the on-foot sheet local sprite = (self.fishing and self.sprite) + or (self.surfing and self.surfingPikachu and self.surfPikachuSprite) or (self.surfing and self.surfSprite) or (self.onBike and self.bikeSprite) or self.sprite return sprite, self.px, py, facing, phase, flip, hopping diff --git a/tests/mod_world_tests.lua b/tests/mod_world_tests.lua index 6d8063a8..60aefb97 100644 --- a/tests/mod_world_tests.lua +++ b/tests/mod_world_tests.lua @@ -1011,6 +1011,10 @@ do "the boot path seeded field.palettes") check(Data.field.playerSprites.walk == "SPRITE_RED", "the boot path seeded field.playerSprites") + check(Data.field.playerSprites.surf == "SPRITE_SEEL", + "the surf sprite defaults to the Seel") + check(Data.field.playerSprites.surfPikachu == "SPRITE_SURFING_PIKACHU", + "the surfing-Pikachu sprite defaults to SPRITE_SURFING_PIKACHU (RFC 0001; Yellow ride)") check(Data.field.badgeGates.ROUTE_22_GATE.passedFlag == "PASSED_ROUTE22_GATE", "the boot path filled the gaps in a stamped key") check(Data.constants.world.stepFrames == 16, diff --git a/tests/parity_surfing_pikachu_sprite.lua b/tests/parity_surfing_pikachu_sprite.lua new file mode 100644 index 00000000..540f27e9 --- /dev/null +++ b/tests/parity_surfing_pikachu_sprite.lua @@ -0,0 +1,128 @@ +-- Parity port: Yellow's IsSurfingPikachuInParty (home/map_objects.asm). +-- When the party mon that knows SURF is a Pikachu, the player's surf sprite +-- swaps from the default (the Seel) to a Pikachu overworld sheet. This +-- mirrors vanilla Yellow, which repoints wSpritePlayerStatePtr at the +-- surfing-Pikachu sheet during the ride. +-- +-- Covers the engine change that adds field.playerSprites.surfPikachu, +-- Player.surfPikachuSprite, OverworldState:syncSurfingPikachu, and the +-- pose() sprite-pick switch. No sprite bytes are read -- the assertions +-- compare the chosen SpriteRenderer's backing def, so the test is ROM-free +-- against the fixture dataset and runs in CI without an import. +-- +-- Self-contained; run via: +-- luajit tests/parity_surfing_pikachu_sprite.lua +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local Data = require("src.core.Data") +if not (Data.maps and Data.maps.CINNABAR_ISLAND) then Data:load() end + +local Game = require("src.core.Game") +local Input = require("src.core.Input") +local Renderer = require("src.render.Renderer") +local SaveData = require("src.core.SaveData") +local StateStack = require("src.core.StateStack") +local OW = require("src.world.OverworldController") +local S = require("tests.harness").suite("parity surfing-pikachu sprite") +local check, eq = S.check, S.eq + +-- field default: the new key is seeded alongside the existing surf sprite, +-- so a mod-free boot gets the Pikachu overworld sheet by default +check(Data.field.playerSprites.surfPikachu == "SPRITE_SURFING_PIKACHU", + "field.playerSprites.surfPikachu defaults to SPRITE_SURFING_PIKACHU (RFC 0001; Yellow ride)") +check(Data.field.playerSprites.surf == "SPRITE_SEEL", + "the default surf sprite is still the Seel (no behavior change)") + +Game.data = Data +Game.input = Input; Input:init() +Game.renderer = Renderer; Renderer:init() +Game.stack = StateStack +StateStack:init() +Game.overworld = OW + +local function mkMon(species, ...) + local moves = {} + for _, id in ipairs({ ... }) do + table.insert(moves, { id = id, pp = 10, ppUp = 0 }) + end + return { + species = species, level = 30, hp = 50, maxHp = 50, + status = 0, moves = moves, + } +end + +local function freshOw(party) + Game.save = SaveData.newGame() + Game.save.party = party + Game.save.inventory = { SOULBADGE = true } + while Game.stack:top() do Game.stack:pop() end + Game.stack:push(OW, "CINNABAR_ISLAND", 19, 8, "right") + local ow = Game.stack:top() + -- the mount itself goes through trySurf (TextBox + whiteFlash + step), + -- so arm the surf state directly and let syncSurfingPikachu derive + -- the sprite pick -- the unit under test. + ow.player.surfing = true + ow:syncSurfingPikachu() + -- the real SPRITE_SURFING_PIKACHU art only exists after an Yellow + -- import + the RFC 0001 extractor path; inject a sentinel surf sprite + -- so pose()'s selection rule is testable in the fixture dataset. + ow.player.surfPikachuSprite = { def = { id = "SPRITE_TEST_SURF_PIKA" } } + return ow +end + +-- the sprite def backing a Player:surfSprite / surfPikachuSprite, so the +-- assertion can compare identities without drawing +local function surfSpriteId(player) + local sprite, _px, _py = player:pose() + return sprite and sprite.def and sprite.def.id or nil +end + +-- -------------------------------------------------- Pikachu knows SURF +do + local ow = freshOw({ mkMon("PIKACHU", "SURF"), mkMon("SQUIRTLE") }) + check(ow.player.surfing, "player is now surfing") + check(ow.player.surfingPikachu == true, + "surfingPikachu set when a Pikachu knows SURF") + eq(surfSpriteId(ow.player), "SPRITE_TEST_SURF_PIKA", + "pose() draws the surf-pikachu sprite, not the Seel (when set)") + + -- dismount flips it off again: set surfing false and re-sync, the way + -- the dismount paths in OverworldController do + ow.player.surfing = false + ow:syncSurfingPikachu() + check(ow.player.surfingPikachu == false, + "syncSurfingPikachu clears the flag when dismounted") +end + +-- -------------------------------------------------- no Pikachu, no swap +do + local ow = freshOw({ mkMon("SQUIRTLE", "SURF") }) + check(ow.player.surfing, "player is surfing") + check(ow.player.surfingPikachu == false, + "no Pikachu in the SURF-mon: surfingPikachu stays false") + eq(surfSpriteId(ow.player), "SPRITE_SEEL", + "pose() keeps the default Seel surf sprite") +end + +-- -------------------------------------------------- SURF-knower is the +-- Pikachu's species, not a slot position: a Pikachu without SURF behind a +-- Squirtle that has SURF must NOT trigger the swap +do + local ow = freshOw({ mkMon("SQUIRTLE", "SURF"), mkMon("PIKACHU", "THUNDER_SHOCK") }) + check(ow.player.surfingPikachu == false, + "Pikachu present but not the SURF-mon: no swap") + eq(surfSpriteId(ow.player), "SPRITE_SEEL", + "pose() keeps the Seel when the SURF-mon is not a Pikachu") +end + +-- -------------------------------------------------- not surfing: no swap +do + local ow = freshOw({ mkMon("PIKACHU", "SURF") }) + ow.player.surfing = false + ow:syncSurfingPikachu() + check(ow.player.surfingPikachu == false, + "syncSurfingPikachu is a no-op swap when not surfing") +end + +S.finish() \ No newline at end of file diff --git a/tools/build_rom_data.py b/tools/build_rom_data.py index 8428812b..8f0e02a4 100755 --- a/tools/build_rom_data.py +++ b/tools/build_rom_data.py @@ -461,6 +461,27 @@ def extract_sprites(rom, symbols, manifest, out_dir, assets_dir): "walker": bike_frames >= 6, } + # Yellow-only: the surfing-Pikachu overworld ride sheet, loaded + # outside SpriteSheetPointerTable (LoadSurfingPlayerSpriteGraphics2) -- + # same extra extract as RedBikeSprite. (RFC 0001) + surf = manifest["sprites"].get("surfPikachu") + if surf and _has_symbol(symbols, surf["label"]): + surf_symbol = _symbol(symbols, surf["label"]) + surf_length = surf["imageWidth"] * surf["imageHeight"] // 4 + _write_2bpp_png( + rom.bytes(surf_symbol.bank, surf_symbol.address, surf_length), + surf["imageWidth"], surf["imageHeight"], + os.path.join(assets_dir, "sprites", surf["imageBase"] + ".png"), + transparent_color0=True) + surf_frames = surf["imageHeight"] // 16 + out["SPRITE_SURFING_PIKACHU"] = { + "id": "SPRITE_SURFING_PIKACHU", + "source": f"ROM:{surf['label']}", + "image": f"assets/generated/sprites/{surf['imageBase']}.png", + "frames": surf_frames, + "walker": surf_frames >= 6, + } + util.write_lua( os.path.join(out_dir, "sprites.lua"), out, header="Source: canonical Pokemon Red ROM (overworld sprite sheets)") diff --git a/tools/extract/sprites.py b/tools/extract/sprites.py index 8290b07f..e6727e12 100644 --- a/tools/extract/sprites.py +++ b/tools/extract/sprites.py @@ -83,6 +83,23 @@ def extract(pokered, out_dir, assets_dir, sprite_order): "walker": frames >= 6, } + # Yellow-only: the surfing-Pikachu overworld ride sheet, loaded + # outside SpriteSheetPointerTable (LoadSurfingPlayerSpriteGraphics2) -- + # same extra extract as RedBikeSprite. (RFC 0001) + surf_src = files.get("SurfingPikachuSprite") + if surf_src: + png_src = os.path.join(pokered, re.sub(r"\.2bpp$", ".png", surf_src)) + dst = os.path.join(assets_dir, "sprites", "surfing_pikachu.png") + size = gfx.convert_png(png_src, dst, transparent_color0=True) + frames = size[1] // 16 + out["SPRITE_SURFING_PIKACHU"] = { + "id": "SPRITE_SURFING_PIKACHU", + "source": "gfx/sprites.asm SurfingPikachuSprite (LoadSurfingPlayerSpriteGraphics2)", + "image": "assets/generated/sprites/surfing_pikachu.png", + "frames": frames, + "walker": frames >= 6, + } + util.write_lua(os.path.join(out_dir, "sprites.lua"), out, header="Sources: data/sprites/sprites.asm, gfx/sprites/*.png\n" "Frame order (walker): stand D/U/L, walk D/U/L; right = flipped left.") diff --git a/tools/make_rom_manifest.py b/tools/make_rom_manifest.py index 09dd5495..031a849f 100755 --- a/tools/make_rom_manifest.py +++ b/tools/make_rom_manifest.py @@ -280,7 +280,7 @@ def sprite_metadata(pokered, order): bike_png = re.sub(r"\.2bpp$", ".png", bike_source) with Image.open(os.path.join(pokered, bike_png)) as image: bike_width, bike_height = image.size - return { + out_manifest = { "order": out, "bike": { "label": "RedBikeSprite", @@ -289,6 +289,20 @@ def sprite_metadata(pokered, order): "imageHeight": bike_height, }, } + # Yellow-only: the surfing-Pikachu overworld ride sheet, parallel + # to the bike entry; absent in Red/Blue. (RFC 0001) + surf_source = files.get("SurfingPikachuSprite") + if surf_source: + surf_png = re.sub(r"\.2bpp$", ".png", surf_source) + with Image.open(os.path.join(pokered, surf_png)) as image: + surf_width, surf_height = image.size + out_manifest["surfPikachu"] = { + "label": "SurfingPikachuSprite", + "imageBase": "surfing_pikachu", + "imageWidth": surf_width, + "imageHeight": surf_height, + } + return out_manifest def text_metadata(pokered): diff --git a/tools/make_yellow_manifest.py b/tools/make_yellow_manifest.py index f102e459..4e0e3782 100755 --- a/tools/make_yellow_manifest.py +++ b/tools/make_yellow_manifest.py @@ -103,6 +103,10 @@ YELLOW_EXTRA_SYMBOLS = ( "Pic_e5b7d", "Pic_e5ddd", "GFX_e6020", "Pic_e6340", "Pic_e6587", "Pic_e67d6", "GFX_e6e6f", "GFX_e718f", "GFX_e74af", "Pic_e77cf", "Pic_f0abf", "Pic_f0cf4", + # Yellow-only overworld player surf sprite, loaded outside + # SpriteSheetPointerTable (LoadSurfingPlayerSpriteGraphics2) -- + # needs its own extract like RedBikeSprite. (RFC 0001) + "SurfingPikachuSprite", ) # Yellow-only dialogue whose bank labels carry no leading underscore, so diff --git a/tools/rom_manifest_yellow.json b/tools/rom_manifest_yellow.json index 8b94d472..951f17be 100644 --- a/tools/rom_manifest_yellow.json +++ b/tools/rom_manifest_yellow.json @@ -19934,7 +19934,13 @@ "imageWidth": 16, "label": "GamblerAsleepSprite" } - ] + ], + "surfPikachu": { + "imageBase": "surfing_pikachu", + "imageHeight": 96, + "imageWidth": 16, + "label": "SurfingPikachuSprite" + } }, "symbols": { "AbraPicBack": [ @@ -21369,6 +21375,22 @@ 28, 16822 ], + "FanClubChairPrintText1": [ + 43, + 22070 + ], + "FanClubChairPrintText2": [ + 43, + 22210 + ], + "FanClubChairPrintText3": [ + 43, + 22243 + ], + "FanClubChairPrintText4": [ + 43, + 22257 + ], "FarfetchdPicBack": [ 10, 28976 @@ -25293,6 +25315,18 @@ 24, 22540 ], + "SSAnneKitchenCook7EelsAuBarbecueText": [ + 38, + 21363 + ], + "SSAnneKitchenCook7PrimeBeefSteakText": [ + 38, + 21414 + ], + "SSAnneKitchenCook7SalmonDuSaladText": [ + 38, + 21298 + ], "SSAnneKitchenCook7Text": [ 24, 22545 @@ -26069,10 +26103,18 @@ 21, 25898 ], + "SilphCo9FNurseDontGiveUpText": [ + 38, + 30668 + ], "SilphCo9FNurseText": [ 23, 22467 ], + "SilphCo9FNurseThankYouText": [ + 38, + 30683 + ], "SilphCo9FNurseYouLookTiredText": [ 38, 30622 @@ -26237,10 +26279,18 @@ 32, 25380 ], + "SurfingPikachuSprite": [ + 63, + 28143 + ], "SwimmerPic": [ 19, 20787 ], + "TMNotebookText": [ + 39, + 27310 + ], "TamerPic": [ 19, 23374 @@ -26753,6 +26803,10 @@ 6, 21103 ], + "ViridianCityFisherYouCanHaveThisText": [ + 45, + 18396 + ], "ViridianCityGambler1Text": [ 6, 21055 @@ -26801,6 +26855,14 @@ 6, 21043 ], + "ViridianCityYoungster2CaterpieAndWeedleDescriptionText": [ + 45, + 18125 + ], + "ViridianCityYoungster2OkThenText": [ + 45, + 18111 + ], "ViridianCityYoungster2Text": [ 6, 21067 @@ -39174,7 +39236,30 @@ ] }, "labels": [ + "FanClubChairPrintText1", + "FanClubChairPrintText2", + "FanClubChairPrintText3", + "FanClubChairPrintText4", + "MelanieBulbasaurText", + "MelanieOddishText", + "MelanieSandshrewText", + "MelanieText1", + "MelanieText2", + "MelanieText3", + "MelanieText4", + "MelanieText5", + "SSAnneKitchenCook7EelsAuBarbecueText", + "SSAnneKitchenCook7PrimeBeefSteakText", + "SSAnneKitchenCook7SalmonDuSaladText", "SilphCo2FSilphWorkerFPleaseTakeThisText", + "SilphCo9FNurseDontGiveUpText", + "SilphCo9FNurseThankYouText", + "SilphCo9FNurseYouLookTiredText", + "TMNotebookText", + "TeachingHMsText", + "ViridianCityFisherYouCanHaveThisText", + "ViridianCityYoungster2CaterpieAndWeedleDescriptionText", + "ViridianCityYoungster2OkThenText", "_AIBattleUseItemText", "_AIBattleWithdrawText", "_AbandonLearningText", @@ -41860,15 +41945,7 @@ "_YeahText", "_YourNameIsText", "_ZapdosDexEntry", - "_ZubatDexEntry", - "MelanieText1", - "MelanieText2", - "MelanieText3", - "MelanieText4", - "MelanieText5", - "MelanieBulbasaurText", - "MelanieOddishText", - "MelanieSandshrewText" + "_ZubatDexEntry" ], "pointers": { "AgathasRoom": {