Merge pull request #1598 from thibautbus/fix/text-extractor-underscore-requirement

Route a handful of pokered dialogue labels through game.data.text
This commit is contained in:
bryanthaboi
2026-08-20 09:51:38 -04:00
committed by GitHub
12 changed files with 405 additions and 47 deletions
+6 -3
View File
@@ -25,8 +25,10 @@
local Menu = require("src.ui.Menu") local Menu = require("src.ui.Menu")
local TextBox = require("src.render.TextBox") local TextBox = require("src.render.TextBox")
-- TMNotebookText (data/text/text_2.asm) has no leading underscore, so the -- TMNotebookText (data/text/text_2.asm) has no leading underscore, but the
-- extractor never collects it and the pamphlet's text is inlined. -- extractor now collects any top-level label in a dedicated text file
-- regardless (tools/extract/text.py), so this is the real ROM label --
-- the literal below is only the fallback for a catalog without it.
local TM_NOTEBOOK_TEXT = "It's a pamphlet\non TMs.\f...\f" local TM_NOTEBOOK_TEXT = "It's a pamphlet\non TMs.\f...\f"
.. "There are 50 TMs\nin all.\f" .. "There are 50 TMs\nin all.\f"
.. "There are also 5\nHMs that can be\vused repeatedly.\f" .. "There are also 5\nHMs that can be\vused repeatedly.\f"
@@ -70,7 +72,8 @@ return {
return true return true
end end
if fx == 3 and fy == 4 then if fx == 3 and fy == 4 then
game.stack:push(TextBox.new(game, TM_NOTEBOOK_TEXT)) local text = game.data.text or {}
game.stack:push(TextBox.new(game, text.TMNotebookText or TM_NOTEBOOK_TEXT))
return true return true
end end
return false return false
+10 -7
View File
@@ -15,10 +15,10 @@ return {
-- pick the dish: bit 7 set (~50%) -> Salmon du Salad, else bit 4 -- pick the dish: bit 7 set (~50%) -> Salmon du Salad, else bit 4
-- set (~25%) -> Eels au Barbecue, else (~25%) -> Prime Beef Steak. -- set (~25%) -> Eels au Barbecue, else (~25%) -> Prime Beef Steak.
-- The three dish texts (SSAnneKitchenCook7SalmonDuSaladText / -- The three dish texts (SSAnneKitchenCook7SalmonDuSaladText /
-- ...EelsAuBarbecueText / ...PrimeBeefSteakText) aren't extracted -- ...EelsAuBarbecueText / ...PrimeBeefSteakText) have no leading
-- into data/generated/text.lua (no leading underscore in -- underscore in pokered/text/SSAnneKitchen.asm, but the extractor
-- pokered/text/SSAnneKitchen.asm), so their literal strings are -- collects them regardless (tools/extract/text.py); the literals
-- ported here verbatim. -- below are only the fallback for a catalog without them.
TEXT_SSANNEKITCHEN_COOK7 = function(game, ow, npc, done) TEXT_SSANNEKITCHEN_COOK7 = function(game, ow, npc, done)
local t = game.data.text local t = game.data.text
push(game, t._SSAnneKitchenCook7MainCourseIsText push(game, t._SSAnneKitchenCook7MainCourseIsText
@@ -27,13 +27,16 @@ return {
local dish local dish
if roll <= 2 then if roll <= 2 then
-- bit 7 of hRandomAdd set (~50%) -- bit 7 of hRandomAdd set (~50%)
dish = "Salmon du Salad!\fLes guests may\ngripe it's fish\vagain, however!" dish = t.SSAnneKitchenCook7SalmonDuSaladText
or "Salmon du Salad!\fLes guests may\ngripe it's fish\vagain, however!"
elseif roll == 3 then elseif roll == 3 then
-- bit 4 set, bit 7 clear (~25%) -- bit 4 set, bit 7 clear (~25%)
dish = "Eels au Barbecue!\fLes guests will\nmutiny, I fear." dish = t.SSAnneKitchenCook7EelsAuBarbecueText
or "Eels au Barbecue!\fLes guests will\nmutiny, I fear."
else else
-- neither bit set (~25%) -- neither bit set (~25%)
dish = "Prime Beef Steak!\fBut, have I enough\nfillets du beef?" dish = t.SSAnneKitchenCook7PrimeBeefSteakText
or "Prime Beef Steak!\fBut, have I enough\nfillets du beef?"
end end
push(game, dish, done) push(game, dish, done)
end) end)
+11 -10
View File
@@ -60,22 +60,23 @@ M.VIRIDIAN_CITY = {
-- you want to know about the two kinds of caterpillar Pokemon; -- you want to know about the two kinds of caterpillar Pokemon;
-- YES -> CATERPIE/WEEDLE description, NO -> "Oh, OK then!". -- YES -> CATERPIE/WEEDLE description, NO -> "Oh, OK then!".
-- ViridianCityYoungster2OkThenText and -- ViridianCityYoungster2OkThenText and
-- ViridianCityYoungster2CaterpieAndWeedleDescriptionText are -- ViridianCityYoungster2CaterpieAndWeedleDescriptionText are defined
-- defined without a leading underscore in pokered/text/ViridianCity.asm -- without a leading underscore in pokered/text/ViridianCity.asm, but
-- and aren't present in data/generated/text.lua, so we fall back to -- tools/extract/text.py now collects them regardless -- the literal
-- the literal strings from pokered. Those fallbacks have to carry the -- strings below are only the fallback for a catalog without them.
-- extractor's markers, not plain newlines: line -> \n, cont -> \v, -- Those fallbacks have to carry the extractor's markers, not plain
-- para -> \f. Spelling cont/para as \n and \n\n put all six lines on -- newlines: line -> \n, cont -> \v, para -> \f. Spelling cont/para as
-- one page with nothing to wait on, so the whole speech scrolled past -- \n and \n\n put all six lines on one page with nothing to wait on,
-- without a button press (#250). -- so the whole speech scrolled past without a button press (#250).
TEXT_VIRIDIANCITY_YOUNGSTER2 = function(game, ow, npc, done) TEXT_VIRIDIANCITY_YOUNGSTER2 = function(game, ow, npc, done)
local t = text(game) local t = text(game)
ask(game, t._ViridianCityYoungster2YouWantToKnowAboutText ask(game, t._ViridianCityYoungster2YouWantToKnowAboutText
or "You want to know\nabout the 2 kinds\vof caterpillar\vPOKéMON?", function(yes) or "You want to know\nabout the 2 kinds\vof caterpillar\vPOKéMON?", function(yes)
if yes then if yes then
push(game, "CATERPIE has no\npoison, but\vWEEDLE does.\fWatch out for its\nPOISON STING!", done) push(game, t.ViridianCityYoungster2CaterpieAndWeedleDescriptionText
or "CATERPIE has no\npoison, but\vWEEDLE does.\fWatch out for its\nPOISON STING!", done)
else else
push(game, "Oh, OK then!", done) push(game, t.ViridianCityYoungster2OkThenText or "Oh, OK then!", done)
end end
end) end)
end, end,
+6 -5
View File
@@ -122,9 +122,8 @@ M.CINNABAR_LAB_METRONOME_ROOM = {
-- TM42 Dream Eater (scripts/ViridianCity.asm, the fisher). The fisher's -- TM42 Dream Eater (scripts/ViridianCity.asm, the fisher). The fisher's
-- YouCanHaveThisText prints before GiveItem, so this gift needs a pre -- YouCanHaveThisText prints before GiveItem, so this gift needs a pre
-- text (#775). Like the SilphCo2F worker (#393) that label carries no -- text (#775). Like the SilphCo2F worker (#393) that label carries no
-- leading underscore, and on Red it sits outside the extractor's symbol -- leading underscore; tools/extract/text.py now collects it regardless,
-- set, so the literal from text/ViridianCity.asm rides along as the -- so preFallback below is just the safety net for a catalog without it.
-- fallback; Yellow resolves the ROM string instead.
M.VIRIDIAN_CITY = { M.VIRIDIAN_CITY = {
talk = { talk = {
TEXT_VIRIDIANCITY_FISHER = gift({ TEXT_VIRIDIANCITY_FISHER = gift({
@@ -146,9 +145,11 @@ M.SILPH_CO_2F = {
talk = { talk = {
TEXT_SILPHCO2F_SILPH_WORKER_F = gift({ TEXT_SILPHCO2F_SILPH_WORKER_F = gift({
flag = "EVENT_GOT_TM36", item = "TM_SELFDESTRUCT", flag = "EVENT_GOT_TM36", item = "TM_SELFDESTRUCT",
-- the label carries no leading underscore: pokered keeps this one in -- the label carries no leading underscore (#393); collected like any
-- the script bank, not the far-text bank (#393) -- other text/*.asm label now, preFallback is just the safety net
pre = "SilphCo2FSilphWorkerFPleaseTakeThisText", pre = "SilphCo2FSilphWorkerFPleaseTakeThisText",
preFallback = "Eeek!\nNo! Stop! Help!\fOh, you're not\nwith TEAM ROCKET."
.. "\vI thought...\vI'm sorry. Here,\vplease take this!",
received = "_SilphCo2FSilphWorkerFReceivedTM36Text", received = "_SilphCo2FSilphWorkerFReceivedTM36Text",
explain = "_SilphCo2FSilphWorkerFTM36ExplanationText", explain = "_SilphCo2FSilphWorkerFTM36ExplanationText",
noRoom = "_SilphCo2FSilphWorkerFTM36NoRoomText", noRoom = "_SilphCo2FSilphWorkerFTM36NoRoomText",
+1
View File
@@ -67,6 +67,7 @@ run_tier() {
# ------- ROM-free tiers: these are what CI runs # ------- ROM-free tiers: these are what CI runs
run_tier "T0 ROM builder version routing" python3 tests/build_rom_data_cli_test.py run_tier "T0 ROM builder version routing" python3 tests/build_rom_data_cli_test.py
run_tier "T0 ROM manifest generator pin/overrides" python3 tests/rom_manifest_generator_test.py
run_tier "T0 switch CI workflow content gate" "$LUA" tests/switch_ci_workflows_test.lua run_tier "T0 switch CI workflow content gate" "$LUA" tests/switch_ci_workflows_test.lua
run_tier "T0 switch transfer docs gate" "$LUA" tests/switch_transfer_docs_test.lua run_tier "T0 switch transfer docs gate" "$LUA" tests/switch_transfer_docs_test.lua
# NX Blue/Yellow asset overlay: ROM-free, must run on every checkout so a # NX Blue/Yellow asset overlay: ROM-free, must run on every checkout so a
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""ROM-free regression tests for the manifest generator's version pin and
its documented, deliberate overrides. No pokered checkout or ROM needed."""
from pathlib import Path
from unittest import TestCase, main, mock
import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools"))
import make_rom_manifest # noqa: E402
class CheckPokeredRevisionTest(TestCase):
def test_matching_revision_is_silent(self):
with mock.patch.object(
make_rom_manifest, "_checkout_revision", return_value="abc"):
make_rom_manifest.check_pokered_revision("/pokered", "abc")
def test_mismatched_revision_raises(self):
with mock.patch.object(
make_rom_manifest, "_checkout_revision", return_value="abc"):
with self.assertRaises(SystemExit):
make_rom_manifest.check_pokered_revision("/pokered", "def")
def test_allow_mismatch_bypasses_the_raise(self):
with mock.patch.object(
make_rom_manifest, "_checkout_revision", return_value="abc"):
make_rom_manifest.check_pokered_revision(
"/pokered", "def", allow_mismatch=True)
def test_unresolvable_checkout_is_silent(self):
# _checkout_revision returns None for a non-git directory; nothing
# to compare against, so this must not block generation.
with mock.patch.object(
make_rom_manifest, "_checkout_revision", return_value=None):
make_rom_manifest.check_pokered_revision("/pokered", "abc")
class ApplyKnownNonreproducibleOverridesTest(TestCase):
def fixtures(self):
texts = {"trainerHeaders": {}}
field_data = {
"seafoam": {
"SEAFOAM_ISLANDS_B3F": {
"pluggedByHolesOn": {
"holes": [{"showObject": "X"}, {"showObject": "Y"}],
},
},
},
"tradeArt": {"bubble": "assets/generated/trade/bubble.png"},
}
return texts, field_data
def test_mtmoonb2f_super_nerd_slot_is_pinned(self):
texts, field_data = self.fixtures()
make_rom_manifest.apply_known_nonreproducible_overrides(
texts, field_data)
self.assertEqual(texts["trainerHeaders"]["MtMoonB2F"][1], {
"after": "_MtMoonB2FSuperNerdTheresAPokemonLabText",
"battle": "_MtMoonB2FSuperNerdTheyreBothMineText",
"event": "EVENT_BEAT_MT_MOON_3_SUPER_NERD",
"won": "_MtMoonB2FSuperNerdOkIllShareText",
})
def test_mtmoonb2f_survives_a_populated_map_entry(self):
# A fresh extraction already fills in the map's other slots (2-5);
# the override must add slot 1 alongside them, not replace them.
texts, field_data = self.fixtures()
texts["trainerHeaders"]["MtMoonB2F"] = {2: {"event": "EVENT_OTHER"}}
make_rom_manifest.apply_known_nonreproducible_overrides(
texts, field_data)
self.assertIn(1, texts["trainerHeaders"]["MtMoonB2F"])
self.assertEqual(
texts["trainerHeaders"]["MtMoonB2F"][2], {"event": "EVENT_OTHER"})
def test_seafoam_boulder_toggle_ids_are_pinned(self):
texts, field_data = self.fixtures()
make_rom_manifest.apply_known_nonreproducible_overrides(
texts, field_data)
holes = field_data["seafoam"]["SEAFOAM_ISLANDS_B3F"][
"pluggedByHolesOn"]["holes"]
self.assertEqual(holes[0]["showObject"],
"TOGGLE_SEAFOAM_ISLANDS_B3F_BOULDER_5")
self.assertEqual(holes[1]["showObject"],
"TOGGLE_SEAFOAM_ISLANDS_B3F_BOULDER_6")
def test_trade_art_is_dropped(self):
texts, field_data = self.fixtures()
make_rom_manifest.apply_known_nonreproducible_overrides(
texts, field_data)
self.assertNotIn("tradeArt", field_data)
def test_missing_trade_art_does_not_raise(self):
texts, field_data = self.fixtures()
del field_data["tradeArt"]
make_rom_manifest.apply_known_nonreproducible_overrides(
texts, field_data)
if __name__ == "__main__":
main()
+7 -1
View File
@@ -133,7 +133,13 @@ def parse_text_file(path, texts, rel):
continue continue
if s in ("ENDC", "vc_patch_end"): if s in ("ENDC", "vc_patch_end"):
continue continue
m = re.match(r"(_\w+)::?\s*$", s) # Most pokered text labels start with "_" by convention, but a
# handful of real dialogue labels don't (e.g. text/ViridianCity.asm's
# ViridianCityYoungster2OkThenText, ViridianCityFisherYouCanHaveThisText).
# text/*.asm, data/text/text_*.asm and dex_text.asm are dedicated text
# files -- every top-level label in them is dialogue, regardless of
# the "_" convention -- so match on the label alone.
m = re.match(r"(\w+)::?\s*$", s)
if m: if m:
flush() flush()
label = m.group(1) label = m.group(1)
+1 -1
View File
@@ -113,7 +113,7 @@ def main():
blue = derive(red, pokered, os.path.abspath(args.symbols)) blue = derive(red, pokered, os.path.abspath(args.symbols))
with open(args.out, "w", encoding="utf-8", newline="\n") as f: with open(args.out, "w", encoding="utf-8", newline="\n") as f:
json.dump(blue, f, ensure_ascii=False, indent=2, sort_keys=True) json.dump(blue, f, ensure_ascii=True, indent=2, sort_keys=True)
f.write("\n") f.write("\n")
print(f"wrote {args.out}") print(f"wrote {args.out}")
+121 -1
View File
@@ -12,6 +12,7 @@ import argparse
import json import json
import os import os
import re import re
import subprocess
import sys import sys
import tempfile import tempfile
@@ -21,6 +22,43 @@ from extract import (battle_anims, constants, field, font, items, maps, # noqa:
pokemon, sprites, text, tilesets, trainers, util) pokemon, sprites, text, tilesets, trainers, util)
from rom_data import CANONICAL_RED_SHA1, SymbolTable # noqa: E402 from rom_data import CANONICAL_RED_SHA1, SymbolTable # noqa: E402
# The pret/pokered commit this manifest was last verified against. Pinned
# deliberately just *before* commit 079d1cc92fc3b0ec82bc1418c2b4045bfca84620
# (PR #596, 2026-08-06), which renamed
# _SilphCo10FGiovanniILostAgainText/_SilphCo10FPorygonText to _SilphCo11F...
# -- data/scripts/victories.lua:183 still hardcodes the old name, so
# generating against a newer pokered would silently blank Giovanni's
# "I lost again!?" rematch line. Advancing this pin past that commit is a
# real, welcome upgrade (it picks up everything pokered has fixed since) --
# it just needs a fresh run diffed against the committed manifest first, and
# victories.lua's (and anything else's) label references fixed up to match
# pokered's new names in the same change, same as any other dependency
# bump. Without a pin at all, running this generator against whatever a
# contributor's checkout happens to be at would silently absorb unaudited
# upstream renames like this one with nobody noticing.
POKERED_REVISION = "cf621a76d4941c93c078eb38e0880fe8db48ef40"
def _checkout_revision(path):
try:
return subprocess.run(
["git", "rev-parse", "HEAD"], cwd=path, check=True,
capture_output=True, text=True).stdout.strip()
except (OSError, subprocess.CalledProcessError):
return None
def check_pokered_revision(pokered, expected, *, allow_mismatch=False):
actual = _checkout_revision(pokered)
if actual is None or actual == expected or allow_mismatch:
return
raise SystemExit(
f"{pokered} is at {actual}, but this generator is pinned to "
f"{expected}. Diff a fresh run against the committed manifest and "
"fix up anything engine code depends on by exact name before "
"bumping the revision constant, or pass --allow-revision-mismatch "
"to generate anyway.")
def simple_constants(pokered, relpath, stop_at=None): def simple_constants(pokered, relpath, stop_at=None):
return util.parse_const_block( return util.parse_const_block(
@@ -628,6 +666,80 @@ def embedded_symbols(data, symbols):
} }
# Trainer parties this project reimplements that have no data to extract:
# CeladonChiefHouse's CHIEF is a talk-only NPC in the ROM (the Celadon Chief
# battle is unused/cut content -- pokered's data/trainers/parties.asm has
# `ChiefData:` followed by `; none`, zero Pokemon). gen1recomp gives the
# CHIEF a real fight after the Hall of Fame (see
# data/scripts/celadon_chief_house.lua and src/import/RomExtractor.lua's
# `trainerPartyOverrides` handling), so this party is hand-authored rather
# than decoded, and belongs in the generator itself rather than only in the
# committed manifest, so it survives a regeneration instead of needing to be
# manually reapplied every time.
TRAINER_PARTY_OVERRIDES = {
"OPP_CHIEF": [
{"species": "MACHOKE", "level": 41},
{"species": "GOLBAT", "level": 41},
{"species": "MAROWAK", "level": 43},
{"species": "WEEZING", "level": 43},
{"species": "PERSIAN", "level": 46},
],
}
def apply_known_nonreproducible_overrides(texts, field_data):
"""Pin a handful of fields a fresh extraction gets right but that this
manifest hasn't shipped, or gets differently than what's shipped, and
that nobody has verified in-game yet. These are deliberately NOT fixed
here -- only kept stable so this generator's output matches the
committed manifest byte-for-byte. Each one is a real, separate,
diagnosed-but-unresolved discrepancy; drop the matching override once
it's actually investigated and the manifest is updated to match.
"""
# MtMoonB2F's Super Nerd (object index 1) isn't part of pokered's real
# def_trainers/trainer table for this map at all -- he's handled by
# bespoke script logic (MtMoonB2FDefeatedSuperNerdScript and friends),
# so a fresh extraction never produces this slot. This manifest has
# always shipped one anyway, under an event name
# (EVENT_BEAT_MT_MOON_3_SUPER_NERD) that has never existed in pokered
# at any point in its history (the real flag is
# EVENT_BEAT_MT_MOON_EXIT_SUPER_NERD, gen1recomp's own
# src/save_convert/data/event_flags.lua flag 1401) -- fabricated data,
# not pokered drift. OverworldState:trainerDefeated() checks
# Game.save.defeatedTrainers[npc.id] before ever consulting this
# header (the same pattern already used for the Fighting Dojo's Karate
# Master, Data:seedFightingDojoKarateMaster()), so this entry is very
# likely already inert -- but removing it outright is a real behavior
# change nobody has verified in-game, so it's preserved as shipped
# rather than silently dropped. The real fix is a
# Data:seedMtMoonB2FSuperNerd()-style engine seed, not manifest data.
texts["trainerHeaders"].setdefault("MtMoonB2F", {})[1] = {
"after": "_MtMoonB2FSuperNerdTheresAPokemonLabText",
"battle": "_MtMoonB2FSuperNerdTheyreBothMineText",
"event": "EVENT_BEAT_MT_MOON_3_SUPER_NERD",
"won": "_MtMoonB2FSuperNerdOkIllShareText",
}
# Seafoam Islands B3F's cross-floor boulder-toggle IDs (which B3F
# object shows/hides when a boulder falls in from B2F above) come out
# of a fresh extraction as TOGGLE_SEAFOAM_ISLANDS_B3F_BOULDER_3/_4;
# this manifest has always shipped _5/_6 instead. Not yet diagnosed
# which is actually correct against a real Seafoam Islands playthrough
# (a live gameplay puzzle, not something to change on a guess) --
# preserved as shipped pending that investigation.
plugged = (
field_data["seafoam"]["SEAFOAM_ISLANDS_B3F"]["pluggedByHolesOn"])
plugged["holes"][0]["showObject"] = "TOGGLE_SEAFOAM_ISLANDS_B3F_BOULDER_5"
plugged["holes"][1]["showObject"] = "TOGGLE_SEAFOAM_ISLANDS_B3F_BOULDER_6"
# tradeArt is new content the current extractor can already produce
# (trade-animation sprite sheets) but that this manifest has never
# shipped -- no engine feature consumes it yet. Dropped here rather
# than silently included, so this PR's diff stays about text labels;
# drop this line once a real trade feature actually needs the field.
field_data.pop("tradeArt", None)
def generate(pokered, symbols_path): def generate(pokered, symbols_path):
symbols = SymbolTable(symbols_path) symbols = SymbolTable(symbols_path)
map_order, map_dims = constants.extract_map_constants(pokered) map_order, map_dims = constants.extract_map_constants(pokered)
@@ -700,6 +812,7 @@ def generate(pokered, symbols_path):
pokered, constants_data["speciesOrder"]) pokered, constants_data["speciesOrder"])
texts = text_metadata(pokered) texts = text_metadata(pokered)
field_data = field_metadata(pokered) field_data = field_metadata(pokered)
apply_known_nonreproducible_overrides(texts, field_data)
audio_data = audio_metadata(pokered, symbols, map_order) audio_data = audio_metadata(pokered, symbols, map_order)
data = { data = {
@@ -737,6 +850,7 @@ def generate(pokered, symbols_path):
"field": field_data, "field": field_data,
"audio": audio_data, "audio": audio_data,
"battleAnimations": battle_animation_metadata(pokered), "battleAnimations": battle_animation_metadata(pokered),
"trainerPartyOverrides": TRAINER_PARTY_OVERRIDES,
} }
data["symbols"] = embedded_symbols(data, symbols) data["symbols"] = embedded_symbols(data, symbols)
return data return data
@@ -749,14 +863,20 @@ def main():
parser.add_argument( parser.add_argument(
"--out", "--out",
default=os.path.join(os.path.dirname(__file__), "rom_manifest.json")) default=os.path.join(os.path.dirname(__file__), "rom_manifest.json"))
parser.add_argument(
"--allow-revision-mismatch", action="store_true",
help="generate even if --pokered isn't at POKERED_REVISION "
"(intentional pin bumps; audit the diff before committing)")
args = parser.parse_args() args = parser.parse_args()
pokered = os.path.abspath(args.pokered) pokered = os.path.abspath(args.pokered)
if not os.path.isfile(os.path.join(pokered, "main.asm")): if not os.path.isfile(os.path.join(pokered, "main.asm")):
raise SystemExit(f"{pokered} is not a pokered checkout") raise SystemExit(f"{pokered} is not a pokered checkout")
check_pokered_revision(
pokered, POKERED_REVISION, allow_mismatch=args.allow_revision_mismatch)
data = generate(pokered, os.path.abspath(args.symbols)) data = generate(pokered, os.path.abspath(args.symbols))
with open(args.out, "w", encoding="utf-8", newline="\n") as f: with open(args.out, "w", encoding="utf-8", newline="\n") as f:
json.dump(data, f, ensure_ascii=False, indent=2, sort_keys=True) json.dump(data, f, ensure_ascii=True, indent=2, sort_keys=True)
f.write("\n") f.write("\n")
print(f"wrote {args.out}") print(f"wrote {args.out}")
+18 -1
View File
@@ -29,6 +29,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from extract import constants, field, util # noqa: E402 from extract import constants, field, util # noqa: E402
from make_rom_manifest import ( # noqa: E402 from make_rom_manifest import ( # noqa: E402
_music_label, _music_label,
check_pokered_revision,
map_metadata, map_metadata,
simple_constants, simple_constants,
sprite_metadata, sprite_metadata,
@@ -36,6 +37,15 @@ from make_rom_manifest import ( # noqa: E402
tileset_metadata, tileset_metadata,
) )
import re # noqa: E402 import re # noqa: E402
# See make_rom_manifest.py's POKERED_REVISION comment for why this pin
# matters -- pokeyellow can drift the same way pokered did for the Silph Co
# 10F/11F rename. Verified against this commit: symbols/text/trainerHeaders/
# trainerPartyOverrides all come out byte-for-byte identical to the
# committed rom_manifest_yellow.json (field.oldManBattle does not -- a
# separate, pre-existing, undiagnosed discrepancy unrelated to text labels,
# left alone the same way field.seafoam/tradeArt were for Red/Blue).
POKEYELLOW_REVISION = "e6ba56989b0f2694f393e6924820be11dcc1fbb8"
from rom_data import SymbolTable # noqa: E402 from rom_data import SymbolTable # noqa: E402
from yellow_symbol_aliases import ( # noqa: E402 from yellow_symbol_aliases import ( # noqa: E402
FAN_CLUB_ID_RENAMES, FAN_CLUB_ID_RENAMES,
@@ -513,17 +523,24 @@ def main():
parser.add_argument("--symbols", default=DEFAULT_SYMBOLS, parser.add_argument("--symbols", default=DEFAULT_SYMBOLS,
help="pokeyellow.sym symbol file") help="pokeyellow.sym symbol file")
parser.add_argument("--out", default=DEFAULT_OUT) parser.add_argument("--out", default=DEFAULT_OUT)
parser.add_argument(
"--allow-revision-mismatch", action="store_true",
help="generate even if --pokeyellow isn't at POKEYELLOW_REVISION")
args = parser.parse_args() args = parser.parse_args()
pokeyellow = os.path.abspath(args.pokeyellow) pokeyellow = os.path.abspath(args.pokeyellow)
if not os.path.isfile(os.path.join(pokeyellow, "main.asm")): if not os.path.isfile(os.path.join(pokeyellow, "main.asm")):
raise SystemExit(f"{pokeyellow} is not a pokeyellow checkout") raise SystemExit(f"{pokeyellow} is not a pokeyellow checkout")
if POKEYELLOW_REVISION is not None:
check_pokered_revision(
pokeyellow, POKEYELLOW_REVISION,
allow_mismatch=args.allow_revision_mismatch)
with open(args.red, encoding="utf-8") as f: with open(args.red, encoding="utf-8") as f:
red = json.load(f) red = json.load(f)
yellow, meta = derive(red, pokeyellow, os.path.abspath(args.symbols)) yellow, meta = derive(red, pokeyellow, os.path.abspath(args.symbols))
with open(args.out, "w", encoding="utf-8", newline="\n") as f: with open(args.out, "w", encoding="utf-8", newline="\n") as f:
json.dump(yellow, f, ensure_ascii=False, indent=2, sort_keys=True) json.dump(yellow, f, ensure_ascii=True, indent=2, sort_keys=True)
f.write("\n") f.write("\n")
print(f"wrote {args.out}") print(f"wrote {args.out}")
+68 -18
View File
@@ -4275,6 +4275,24 @@
"open": 14 "open": 14
} }
], ],
"SILPH_CO_10F": [
{
"block": 84,
"bx": 5,
"by": 4,
"event": "EVENT_SILPH_CO_10_UNLOCKED_DOOR",
"open": 14
}
],
"SILPH_CO_11F": [
{
"block": 32,
"bx": 3,
"by": 6,
"event": "EVENT_SILPH_CO_11_UNLOCKED_DOOR",
"open": 3
}
],
"SILPH_CO_2F": [ "SILPH_CO_2F": [
{ {
"block": 84, "block": 84,
@@ -4416,24 +4434,6 @@
"event": "EVENT_SILPH_CO_9_UNLOCKED_DOOR4", "event": "EVENT_SILPH_CO_9_UNLOCKED_DOOR4",
"open": 14 "open": 14
} }
],
"SILPH_CO_10F": [
{
"block": 84,
"bx": 5,
"by": 4,
"event": "EVENT_SILPH_CO_10_UNLOCKED_DOOR",
"open": 14
}
],
"SILPH_CO_11F": [
{
"block": 32,
"bx": 3,
"by": 6,
"event": "EVENT_SILPH_CO_11_UNLOCKED_DOOR",
"open": 3
}
] ]
}, },
"doorTiles": [ "doorTiles": [
@@ -21758,6 +21758,18 @@
24, 24,
22665 22665
], ],
"SSAnneKitchenCook7EelsAuBarbecueText": [
32,
21107
],
"SSAnneKitchenCook7PrimeBeefSteakText": [
32,
21158
],
"SSAnneKitchenCook7SalmonDuSaladText": [
32,
21043
],
"SSAnneKitchen_h": [ "SSAnneKitchen_h": [
24, 24,
22439 22439
@@ -21970,6 +21982,18 @@
21, 21,
25848 25848
], ],
"SilphCo9FNurseDontGiveUpText": [
33,
19513
],
"SilphCo9FNurseThankYouText": [
33,
19528
],
"SilphCo9FNurseYouLookTiredText": [
33,
19467
],
"SilphCo9F_h": [ "SilphCo9F_h": [
23, 23,
22447 22447
@@ -22074,6 +22098,10 @@
19, 19,
20787 20787
], ],
"TMNotebookText": [
34,
19453
],
"TamerPic": [ "TamerPic": [
19, 19,
23374 23374
@@ -22358,6 +22386,18 @@
13, 13,
27425 27425
], ],
"ViridianCityFisherYouCanHaveThisText": [
41,
17898
],
"ViridianCityYoungster2CaterpieAndWeedleDescriptionText": [
41,
17627
],
"ViridianCityYoungster2OkThenText": [
41,
17613
],
"ViridianCity_h": [ "ViridianCity_h": [
6, 6,
17239 17239
@@ -34071,7 +34111,17 @@
] ]
}, },
"labels": [ "labels": [
"SSAnneKitchenCook7EelsAuBarbecueText",
"SSAnneKitchenCook7PrimeBeefSteakText",
"SSAnneKitchenCook7SalmonDuSaladText",
"SilphCo2FSilphWorkerFPleaseTakeThisText", "SilphCo2FSilphWorkerFPleaseTakeThisText",
"SilphCo9FNurseDontGiveUpText",
"SilphCo9FNurseThankYouText",
"SilphCo9FNurseYouLookTiredText",
"TMNotebookText",
"ViridianCityFisherYouCanHaveThisText",
"ViridianCityYoungster2CaterpieAndWeedleDescriptionText",
"ViridianCityYoungster2OkThenText",
"_AIBattleUseItemText", "_AIBattleUseItemText",
"_AIBattleWithdrawText", "_AIBattleWithdrawText",
"_AbandonLearningText", "_AbandonLearningText",
+50
View File
@@ -21735,6 +21735,18 @@
24, 24,
22665 22665
], ],
"SSAnneKitchenCook7EelsAuBarbecueText": [
32,
21107
],
"SSAnneKitchenCook7PrimeBeefSteakText": [
32,
21158
],
"SSAnneKitchenCook7SalmonDuSaladText": [
32,
21043
],
"SSAnneKitchen_h": [ "SSAnneKitchen_h": [
24, 24,
22439 22439
@@ -21947,6 +21959,18 @@
21, 21,
25848 25848
], ],
"SilphCo9FNurseDontGiveUpText": [
33,
19513
],
"SilphCo9FNurseThankYouText": [
33,
19528
],
"SilphCo9FNurseYouLookTiredText": [
33,
19467
],
"SilphCo9F_h": [ "SilphCo9F_h": [
23, 23,
22447 22447
@@ -22051,6 +22075,10 @@
19, 19,
20787 20787
], ],
"TMNotebookText": [
34,
19453
],
"TamerPic": [ "TamerPic": [
19, 19,
23374 23374
@@ -22335,6 +22363,18 @@
13, 13,
27425 27425
], ],
"ViridianCityFisherYouCanHaveThisText": [
41,
17898
],
"ViridianCityYoungster2CaterpieAndWeedleDescriptionText": [
41,
17627
],
"ViridianCityYoungster2OkThenText": [
41,
17613
],
"ViridianCity_h": [ "ViridianCity_h": [
6, 6,
17239 17239
@@ -34048,7 +34088,17 @@
] ]
}, },
"labels": [ "labels": [
"SSAnneKitchenCook7EelsAuBarbecueText",
"SSAnneKitchenCook7PrimeBeefSteakText",
"SSAnneKitchenCook7SalmonDuSaladText",
"SilphCo2FSilphWorkerFPleaseTakeThisText", "SilphCo2FSilphWorkerFPleaseTakeThisText",
"SilphCo9FNurseDontGiveUpText",
"SilphCo9FNurseThankYouText",
"SilphCo9FNurseYouLookTiredText",
"TMNotebookText",
"ViridianCityFisherYouCanHaveThisText",
"ViridianCityYoungster2CaterpieAndWeedleDescriptionText",
"ViridianCityYoungster2OkThenText",
"_AIBattleUseItemText", "_AIBattleUseItemText",
"_AIBattleWithdrawText", "_AIBattleWithdrawText",
"_AbandonLearningText", "_AbandonLearningText",