From de27314980c4552817e46473d449bd527f46a39b Mon Sep 17 00:00:00 2001 From: dayfine Date: Sun, 1 Nov 2026 13:20:18 -0800 Subject: [PATCH 01/13] BDSP maps: the story's maps as JSON, generated from the Diamond decomp devtools/maps/bdsp_maps.py writes each map from its world rectangle of the pret/pokediamond decompilation (Platinum's for the Ravaged Path), with the named points and regions the story walks to, the objects BDSP shows, and the tiles the console measured as patches. --check regenerates them and fails on any difference. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../Programs/AutoStory/Maps/FloaromaTown.json | 56 ++ .../Programs/AutoStory/Maps/JubilifeCity.json | 139 +++++ .../Programs/AutoStory/Maps/LakeVerity.json | 43 ++ .../Programs/AutoStory/Maps/OreburghCity.json | 91 +++ .../Programs/AutoStory/Maps/OreburghGate.json | 43 ++ .../Programs/AutoStory/Maps/OreburghGym.json | 50 ++ .../Programs/AutoStory/Maps/OreburghMine.json | 56 ++ .../Programs/AutoStory/Maps/RavagedPath.json | 87 +++ .../Programs/AutoStory/Maps/Route201.json | 46 ++ .../Programs/AutoStory/Maps/Route202.json | 68 +++ .../Programs/AutoStory/Maps/Route203.json | 64 ++ .../AutoStory/Maps/Route204North.json | 83 +++ .../AutoStory/Maps/Route204South.json | 56 ++ .../Programs/AutoStory/Maps/SandgemTown.json | 55 ++ .../Programs/AutoStory/Maps/TwinleafTown.json | 48 ++ .../AutoStory/Maps/VerityLakefront.json | 43 ++ devtools/maps/bdsp_maps.py | 555 ++++++++++++++++++ 17 files changed, 1583 insertions(+) create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/FloaromaTown.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/JubilifeCity.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/LakeVerity.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghCity.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGate.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGym.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghMine.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RavagedPath.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204North.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204South.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/VerityLakefront.json create mode 100644 devtools/maps/bdsp_maps.py diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/FloaromaTown.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/FloaromaTown.json new file mode 100644 index 0000000000..db9cf8a5df --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/FloaromaTown.json @@ -0,0 +1,56 @@ +{ + "name": "FloaromaTown", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0032.bin; zone_event narc_0404", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "######........................##", + "##..##........................##", + "..............................##", + "..............................##", + "..............................##", + "..............................##", + "..............................##", + "..................#####.......##", + "..................#####.......##", + "..................#####.......##", + "..................#####.......##", + "..........####...S##.##.......##", + "..........####................##", + "..........####................##", + "..........#D##................##", + ".......................####..S##", + ".......................####...##", + ".......................#D##...##", + "..............................##", + ".............S..................", + "................................", + "................................", + "................................", + "..............#####....####...##", + "..............#####....####...##", + "..............#####....####...##", + "..............##D##....#D##...##", + "..............................##", + "..............................##", + "..............................##", + "########.#...###################", + "########.#...###################" + ], + "points": { + "route_204_arrival": [11, 29], + "old_woman": [3, 25], + "old_man": [2, 25], + "pokemon_center_door": [16, 26] + }, + "objects": [ + {"name": "pokemon_breeder_f_0", "kind": "npc", "x": 19, "y": 21}, + {"name": "lass_1", "kind": "npc", "x": 10, "y": 5, "wander": {"x0": 9, "y0": 5, "x1": 11, "y1": 5}}, + {"name": "school_kid_m_2", "kind": "npc", "x": 16, "y": 16, "wander": {"x0": 15, "y0": 15, "x1": 17, "y1": 17}}, + {"name": "cyrus_3", "kind": "npc", "x": 11, "y": 26}, + {"name": "lass_4", "kind": "npc", "x": 23, "y": 11, "facing": "west"}, + {"name": "grunt_m_7", "kind": "npc", "x": 2, "y": 1, "facing": "east"}, + {"name": "grunt_m_8", "kind": "npc", "x": 3, "y": 1, "facing": "west"}, + {"name": "camper_12", "kind": "npc", "x": 6, "y": 10}, + {"name": "old_woman", "kind": "npc", "x": 3, "y": 25}, + {"name": "old_man", "kind": "npc", "x": 2, "y": 25} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/JubilifeCity.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/JubilifeCity.json new file mode 100644 index 0000000000..71aed1d875 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/JubilifeCity.json @@ -0,0 +1,139 @@ +{ + "name": "JubilifeCity", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0015.bin, files/fielddata/land_data/land_data_release/narc_0016.bin, files/fielddata/land_data/land_data_release/narc_0017.bin, files/fielddata/land_data/land_data_release/narc_0018.bin; zone_event narc_0002", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "##########################################..............########", + "##########################################..............########", + "############################################.....###############", + "############################################......##############", + "############################################......##############", + "############################################......##############", + "############################################......##############", + "#############################################....###############", + "############################....#########.......S.##############", + "############################....#########.........##############", + "#########..#########..######....#########.........##############", + "#########..#########..######....#########.........##############", + "#########..#########..######....#########.........##############", + "#########..#########..######....#########.........##############", + "#########..#########..######....#########.........##############", + "#########..##D###D##..######....####D####.........##############", + "#########.............######...............#.....#######....####", + "#########.............######......................######....####", + "..#######.............######......................######....####", + "..###########S......########...........S.........S##D###....####", + "..#.S.#.......................#..........................#.S####", + "................................................................", + "D...............................................................", + "D...............................................................", + "................................................................", + "..#...#.......................#..........................#..####", + "..############.............####......####...................####", + "..############.............####......####................#######", + "##############.################...##.####..#.....######..#######", + "##############.################...##.####.........#####..#######", + "##############.################...................#D############", + "################################.........................#######", + "################################.........................##D####", + "################################.........................#######", + "################################.........................#######", + "###############............#####.........................#######", + "###############............#####......#####..............#######", + "###############..#########.#####.##########.......######.#######", + "###############..#########.#####.##########.......######.#######", + "###############..#########.#####.###########.....#######.#######", + "##################################..S.##D##.......##D###########", + "###############..#########.#####.........................#######", + "##############...####D####..###..........................#######", + "##############...........................................#######", + "##############.....S.....................................#######", + "##############...........................................#######", + "##############.............#########.....................#######", + "###############............#########.#####...............#######", + "###############............#########.#####...............#######", + "###############.####..####.###############...............#######", + "###############.####..####.###############...............#######", + "###############.####..####.###############...........###########", + "############D##.####..####.############D##...........###########", + "##########.................#########.......#.....#...########.##", + "##########.................#########.................########.##", + "##########............#..#.#########......S..........########.##", + "##########............#..#.#########.................########.##", + "##########.................#########.................########.##", + "###########################################.......##############", + "###########################################.......##############", + "########################################.##.......##############", + "########################################.####...################", + "########################################..........##############", + "########################################..........##############" + ], + "points": { + "trainers_school_door": [40, 40], + "pokemon_center_door": [52, 40], + "tv_station_door": [36, 15], + "poketch_president": [46, 35], + "clown_mart": [55, 33], + "clown_tv_station": [36, 16], + "clown_poketch_company": [15, 18], + "up_the_steps.1": [46, 58], + "up_the_steps.2": [47, 58], + "up_the_steps.3": [46, 59], + "up_the_steps.4": [47, 59], + "up_the_steps.5": [46, 60], + "up_the_steps.6": [47, 60], + "east_entrance.1": [61, 21], + "east_entrance.2": [62, 21], + "east_entrance.3": [63, 21], + "east_entrance.4": [61, 22], + "east_entrance.5": [62, 22], + "east_entrance.6": [63, 22], + "east_entrance.7": [61, 23], + "east_entrance.8": [62, 23], + "east_entrance.9": [63, 23], + "east_entrance.10": [61, 24], + "east_entrance.11": [62, 24], + "east_entrance.12": [63, 24], + "north_road.1": [44, 16], + "north_road.2": [45, 16], + "north_road.3": [46, 16], + "north_road.4": [47, 16], + "north_road.5": [48, 16], + "north_road.6": [44, 17], + "north_road.7": [45, 17], + "north_road.8": [46, 17], + "north_road.9": [47, 17], + "north_road.10": [48, 17], + "north_road.11": [44, 18], + "north_road.12": [45, 18], + "north_road.13": [46, 18], + "north_road.14": [47, 18], + "north_road.15": [48, 18], + "north_road.16": [44, 19], + "north_road.17": [45, 19], + "north_road.18": [46, 19], + "north_road.19": [47, 19], + "north_road.20": [48, 19], + "north_road.21": [44, 20], + "north_road.22": [45, 20], + "north_road.23": [46, 20], + "north_road.24": [47, 20], + "north_road.25": [48, 20] + }, + "objects": [ + {"name": "ace_trainer_f_0", "kind": "npc", "x": 22, "y": 20, "wander": {"x0": 21, "y0": 20, "x1": 23, "y1": 20}}, + {"name": "ace_trainer_m_1", "kind": "npc", "x": 46, "y": 21, "wander": {"x0": 45, "y0": 21, "x1": 47, "y1": 21}}, + {"name": "school_kid_m_2", "kind": "npc", "x": 17, "y": 55}, + {"name": "twin_3", "kind": "npc", "x": 33, "y": 18, "wander": {"x0": 32, "y0": 17, "x1": 34, "y1": 19}}, + {"name": "expert_m_4", "kind": "npc", "x": 44, "y": 44}, + {"name": "school_kid_m_5", "kind": "npc", "x": 51, "y": 34, "wander": {"x0": 51, "y0": 33, "x1": 51, "y1": 35}}, + {"name": "pokefan_m_6", "kind": "npc", "x": 41, "y": 30, "wander": {"x0": 40, "y0": 29, "x1": 42, "y1": 31}}, + {"name": "ace_trainer_m_22", "kind": "npc", "x": 36, "y": 25}, + {"name": "poketch_president", "kind": "npc", "x": 46, "y": 35}, + {"name": "clown_mart", "kind": "npc", "x": 55, "y": 33}, + {"name": "clown_poketch_company", "kind": "npc", "x": 15, "y": 18}, + {"name": "cashier_m_27", "kind": "npc", "x": 26, "y": 42}, + {"name": "clown_tv_station", "kind": "npc", "x": 36, "y": 16}, + {"name": "kid_with_nds_29", "kind": "npc", "x": 52, "y": 46}, + {"name": "kid_with_nds_30", "kind": "npc", "x": 54, "y": 46} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/LakeVerity.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/LakeVerity.json new file mode 100644 index 0000000000..f7b521b3cb --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/LakeVerity.json @@ -0,0 +1,43 @@ +{ + "name": "LakeVerity", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0512.bin; zone_event narc_0298", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "wwwwwwwwwwwwwwww################", + "wwwwwwwwwwwwwwww################", + "wwwwwwwwwwwwwwww################", + "wwwwwwwwwwwwwwww################", + "......wwwwwwwwww##....##########", + "......wwwwwwwwww##....##########", + ".......wwwwwwwww##......########", + ".......wwwwwwwww##......########", + ".......wwwwwww####~.....########", + ".......wwwwwww####~~....########", + "wwwwwwwwwwww####~~~~....########", + "wwwwwwwwwwww####.~~~....########", + "wwwwwwwwww####.~~~~~..##########", + "wwwwwwwwww####.~~~~...##########", + "wwwwwwww####...~~~..############", + "wwwwwwww####........############", + "##########..........############", + "##########..........############", + "############......##############", + "############......##############", + "############......##############", + "############......##############", + "##############..################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################" + ], + "objects": [ + {"name": "rowan", "kind": "npc", "x": 16, "y": 11, "facing": "north"}, + {"name": "counterpart", "kind": "npc", "x": 17, "y": 11, "facing": "north"}, + {"name": "starly_3", "kind": "npc", "x": 16, "y": 19, "facing": "north"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghCity.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghCity.json new file mode 100644 index 0000000000..4cdef2c0b0 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghCity.json @@ -0,0 +1,91 @@ +{ + "name": "OreburghCity", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0022.bin, files/fielddata/land_data/land_data_release/narc_0023.bin, files/fielddata/land_data/land_data_release/narc_0024.bin, files/fielddata/land_data/land_data_release/narc_0175.bin; zone_event narc_0044", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "#########################################......#################", + "#########################################...S..#################", + "#########################################......#################", + "#########################################......#################", + "#########################################...####################", + "#########################################...####################", + "#########################################....###################", + "############.#####.######.#.############.....###################", + "###########..#####..#####...############.....###################", + "#########....#####..#####...############.....###################", + "##.....########D######D######D##########.....###################", + "##..S..###..........................####........#.#...##########", + "##..................................####.....S........##########", + "#D..................................####..............##########", + "##..................................####..............####.#####", + "##..................................####..............####.#####", + "##.....###.............#######......#D##..............####.#####", + "##.....##.......####...#######...............#####....####.#####", + "#########.......####.#.#######..............######......#.######", + "#########.......####...#######..............######.....#########", + "#########.......#D####S###D###...............##D##.....#########", + "#########........................................#.....#########", + "#########........................................#.....#########", + "#########........................................#....##########", + "#########........................................#....##########", + "#########........................................#....##########", + "##########.#.#..########.........................#....##########", + "########################.######.############.....#....##########", + "########################.######.###........#.....#....##D#######", + "########################.######.###.#..#...#.....#.........#####", + "############################################.....###############", + "############################################.....###############", + "############################################.....###############", + "############################################.....###############", + "############################################.....###############", + "############################################.....###############", + "############################################.....##D############", + "###################################.########.....#...........###", + "###################################.....####.....#...........###", + "###################################.....####.....#...........###", + "###################################....#####.....#...........###", + "#####################################..#.........#...........###", + "#####################################..#..#.#....#....#.#....###", + "#####################################..#..#.#....#....#.#....###", + "###################################....#.....................###", + "###################################....#.....................###", + "###################################....#.....................###", + "###################################....#.....................###", + "###################################....#.....................###", + "###################################..###..#.#................###", + "###################################..###..#.#...#.#########.####", + "###################################..###........#.#########.####", + "###################################....#..........#####.....####", + "###################################...............#####.....####", + "#####################################.............##.........###", + "#####################################..####.#.....###.....######", + "#####################################..####.#S....###.....######", + "###########################################.......####.......###", + "############################################.....#####.......###", + "############################################.....#####.....#####", + "######################################################..#..#####", + "################################################################", + "################################################################", + "################################################################" + ], + "points": { + "pokemon_center_door": [47, 20], + "pokemon_center_doorstep": [46, 21], + "gym_door": [26, 20], + "gym_doorstep": [26, 21], + "gate_mouth": [1, 13] + }, + "objects": [ + {"name": "hiker_0", "kind": "npc", "x": 47, "y": 26, "wander": {"x0": 46, "y0": 25, "x1": 48, "y1": 27}}, + {"name": "school_kid_f_1", "kind": "npc", "x": 32, "y": 18, "wander": {"x0": 32, "y0": 16, "x1": 32, "y1": 20}}, + {"name": "pokefan_f_2", "kind": "npc", "x": 45, "y": 38, "wander": {"x0": 45, "y0": 37, "x1": 45, "y1": 39}}, + {"name": "youngster_4", "kind": "npc", "x": 10, "y": 16, "facing": "north"}, + {"name": "worker_15", "kind": "npc", "x": 11, "y": 25}, + {"name": "worker_16", "kind": "npc", "x": 56, "y": 57, "wander": {"x0": 55, "y0": 54, "x1": 57, "y1": 60}}, + {"name": "camper_17", "kind": "npc", "x": 36, "y": 19, "wander": {"x0": 33, "y0": 19, "x1": 39, "y1": 19}}, + {"name": "worker_18", "kind": "npc", "x": 37, "y": 39, "wander": {"x0": 36, "y0": 38, "x1": 38, "y1": 40}}, + {"name": "worker_19", "kind": "npc", "x": 50, "y": 41, "wander": {"x0": 50, "y0": 38, "x1": 50, "y1": 44}}, + {"name": "guitarist_20", "kind": "npc", "x": 23, "y": 13, "wander": {"x0": 22, "y0": 10, "x1": 24, "y1": 16}}, + {"name": "battle_girl_21", "kind": "npc", "x": 36, "y": 23, "wander": {"x0": 36, "y0": 21, "x1": 36, "y1": 25}}, + {"name": "machop_22", "kind": "npc", "x": 55, "y": 56, "wander": {"x0": 53, "y0": 54, "x1": 57, "y1": 58}} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGate.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGate.json new file mode 100644 index 0000000000..14c30e35f5 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGate.json @@ -0,0 +1,43 @@ +{ + "name": "OreburghGate", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0418.bin; zone_event narc_0250", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "################################", + "################################", + "################################", + "################################", + "############..#......###########", + "###########..........###########", + "###########........#.###########", + "###########..###################", + "###########..###################", + "###########..###################", + "###########.####################", + "############.###################", + "###########.####################", + "###########..###################", + "###########..###################", + "###########..###################", + "###########..###################", + "###########..###################", + "############.###################", + "###########..###################", + "#####...##...##########...#.####", + "####....##...##########.....####", + "###D........................D###", + "####...#....................####", + "###########..............#.#####", + "############.......#############", + "###########..#.#...#############", + "################################", + "################################", + "################################", + "################################", + "################################" + ], + "objects": [ + {"name": "picnicker_0", "kind": "npc", "x": 24, "y": 21, "facing": "south"}, + {"name": "camper_1", "kind": "npc", "x": 17, "y": 25, "facing": "north"}, + {"name": "hiker_10", "kind": "npc", "x": 7, "y": 20, "facing": "south"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGym.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGym.json new file mode 100644 index 0000000000..89341d5b3a --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGym.json @@ -0,0 +1,50 @@ +{ + "name": "OreburghGym", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0228.bin; zone_event narc_0046", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "################################", + "################################", + "################################", + "#..........#####################", + "#..........#####################", + "#..........#####################", + "#..##.##...#####################", + "#..........#####################", + "#..##.##...#####################", + "#..........#####################", + "#..........#####################", + "###.....#.######################", + "#..........#####################", + "#####.##...#####################", + "#####.##...#####################", + "#..........#####################", + "#..........#####################", + "#..........#####################", + "#..........#####################", + "##.#....########################", + "##.#....########################", + "#..........#####################", + "#..#...#...#####################", + "#..S...S...#####################", + "#....D.....#####################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################" + ], + "points": { + "entrance": [5, 23], + "roark": [5, 3], + "trainer_first_tier": [4, 18], + "trainer_second_tier": [7, 11] + }, + "objects": [ + {"name": "roark_0", "kind": "npc", "x": 5, "y": 3, "facing": "south"}, + {"name": "gym_guide_1", "kind": "npc", "x": 6, "y": 23, "facing": "south"}, + {"name": "youngster_2", "kind": "npc", "x": 4, "y": 18, "facing": "east"}, + {"name": "youngster_3", "kind": "npc", "x": 7, "y": 11, "facing": "west"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghMine.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghMine.json new file mode 100644 index 0000000000..d8ea9a4ff6 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghMine.json @@ -0,0 +1,56 @@ +{ + "name": "OreburghMine", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0314.bin; zone_event narc_0197", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "################################", + "###############D################", + "#############.....##############", + "#############.....##############", + "#############.....##############", + "#############.....##############", + "#############.....##############", + "#############...################", + "#############...################", + "#############.....##############", + "#############..#..##############", + "###############...##############", + "#############...################", + "#############.....##############", + "###############...##############", + "########...####..............###", + "######.....##.................##", + "##.#..........................##", + "##....####################....##", + "##vvv##..................#....##", + "##....#..................#....##", + "##.##.#..................#....##", + "##..###..................###..##", + "##..###..................###..##", + "##....#..................#....##", + "##.##.#..................#....##", + "####..####################....##", + "####..####################....##", + "####.............#.#........####", + "######......................####", + "################################", + "################################" + ], + "points": { + "roark": [18, 28], + "corridor_exit_start": [14, 10], + "corridor_top.1": [14, 2], + "corridor_top.2": [15, 2], + "corridor_top.3": [16, 2] + }, + "objects": [ + {"name": "roark", "kind": "npc", "x": 18, "y": 28, "facing": "east"}, + {"name": "item_3", "kind": "item", "x": 4, "y": 28}, + {"name": "item_4", "kind": "item", "x": 26, "y": 24}, + {"name": "worker_5", "kind": "npc", "x": 8, "y": 28, "facing": "north"}, + {"name": "worker_6", "kind": "npc", "x": 26, "y": 20, "facing": "west"}, + {"name": "worker_7", "kind": "npc", "x": 21, "y": 15, "facing": "north"}, + {"name": "machop_8", "kind": "npc", "x": 12, "y": 28, "wander": {"x0": 11, "y0": 28, "x1": 13, "y1": 28}}, + {"name": "machop_9", "kind": "npc", "x": 20, "y": 15}, + {"name": "machop_10", "kind": "npc", "x": 27, "y": 15, "facing": "north"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RavagedPath.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RavagedPath.json new file mode 100644 index 0000000000..7c48de6775 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RavagedPath.json @@ -0,0 +1,87 @@ +{ + "name": "RavagedPath", + "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_435.bin, res/field/maps/data/map_data_436.bin; events_ravaged_path.json", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "................................", + "................................", + "........................########", + "........................########", + "........................##....##", + "############............##....##", + "############............##....##", + "####...#####............##www.##", + "##...#...############...##www###", + "##..####.############...##www###", + "##..####.#######...#######www###", + "##..####......##...#######www###", + "##.#.###########...##www##www###", + "###.##########.....##wwwwwwwww##", + "###..######.#########wwwwwwwww##", + "###..#####..#########wwwwwwwww##", + "####.#####vv#########www########", + "##...#####..#########www########", + "##..######..#########www###.....", + "##..######..#########www###.....", + "###.######..######wwwwwww##.....", + "##..######..######wwwwwww##.....", + "##.#.#####..######wwwwwww##.....", + "####.#####vv######www######.....", + "####.#......######www######.....", + "####........######www##.........", + "####....wwwwwwwwwwwww##.........", + "..##....wwwwwwwwwwwww##.........", + "..##....wwwwwwwwwwwww##.........", + "..#######www###########.........", + "..#######www###########.........", + "....#####www####................", + "....#####www####................", + "....##wwwwww####................", + "....##wwwwww####................", + "....##wwwwww####................", + "....##.wwww#.###................", + "....##.#....#.##................", + "....##..#.#...#########.........", + "....##....#..##########.........", + "....##.#.#.......#...###########", + "....###.....######...###########", + "....##############....#.......##", + "....##############....#.......##", + "..............####.....#......##", + "..............####...#######D###", + "..............####...###########", + "..............####...####.......", + "..............####...####.......", + "..............####...####.......", + "..............####...####.......", + "..............#####D#####.......", + "..............###########.......", + "..............###########......." + ], + "points": { + "entrance": [19, 50], + "east_end": [29, 44], + "above_exit": [28, 44], + "rocks.1": [11, 36], + "rocks.2": [7, 37], + "rocks.3": [12, 37], + "rocks.4": [8, 38], + "rocks.5": [10, 38], + "rocks.6": [10, 39], + "rocks.7": [13, 39], + "rocks.8": [7, 40], + "rocks.9": [9, 40], + "rocks.10": [17, 40], + "rocks.11": [12, 41], + "rocks.12": [22, 42], + "rocks.13": [22, 43], + "rocks.14": [23, 44], + "before_rocks.1": [21, 42], + "before_rocks.2": [21, 43] + }, + "objects": [ + {"name": "item_27", "kind": "item", "x": 7, "y": 38}, + {"name": "item_28", "kind": "item", "x": 17, "y": 11}, + {"name": "item_29", "kind": "item", "x": 28, "y": 5}, + {"name": "item_30", "kind": "item", "x": 22, "y": 44} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json new file mode 100644 index 0000000000..88f0b99f7e --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json @@ -0,0 +1,46 @@ +{ + "name": "Route201", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0005.bin, files/fielddata/land_data/land_data_release/narc_0006.bin; zone_event narc_0327", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "################################################################", + "################################################################", + "################################################################", + "##########################################~~~~~~~~~~~...........", + "##########################################~~~~~~~~~~~..S........", + "##########################################~~~~~~~~~~~...........", + "##########################################~~~~~~~~~~~...........", + "....####..........######..##....##..####..~~~~~~~~~##...........", + "....####..........######..##....##..####..~~~~~~~~~##...........", + "##........####............................~~~~~~~~~##vvvvv######", + "##........####............................~~~~~~~~~##.....######", + "..........................................~~~~~~~~~##.....######", + "vvvv####vvvvvvvvvvvvvvvvvvvvvvvv........S.~~~~~~~~~##.....######", + "....####............~~~~~~~~~~............................######", + "....................~~~~~~~~~~............................######", + "....................~~~~~~~~~~............................######", + "....................~~~~~~~~~~............................######", + "########.....S......##########............................######", + "########............##########............~~~~~~~~~~~~~~~~######", + "##############....############################~~~~~~~~~~~~######", + "##############....############################~~~~~~~~~~~~######", + "##############....##############################################", + "##############....##############################################", + "##############....##############################################", + "##############....##############################################" + ], + "points": { + "border_sign": [55, 4], + "potion_giver": [43, 3], + "potion_giver_talk_from": [44, 3], + "route_201_arrival": [15, 24], + "barry_warning": [15, 18], + "route_201_return": [0, 14], + "rowan_stop": [5, 14], + "route_201_east_end": [63, 5] + }, + "objects": [ + {"name": "pokemon_breeder_m_0", "kind": "npc", "x": 27, "y": 15, "wander": {"x0": 26, "y0": 14, "x1": 28, "y1": 16}}, + {"name": "school_kid_m_9", "kind": "npc", "x": 57, "y": 8, "facing": "west"}, + {"name": "lass_11", "kind": "npc", "x": 46, "y": 16, "wander": {"x0": 44, "y0": 16, "x1": 48, "y1": 16}} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json new file mode 100644 index 0000000000..515e652b45 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json @@ -0,0 +1,68 @@ +{ + "name": "Route202", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0014.bin, files/fielddata/land_data/land_data_release/narc_0173.bin; zone_event narc_0328", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "###########........#################", + "###########........#################", + "###########........#################", + "###########........#################", + "###~~~~~~........S..~~~~~......#####", + "###~~~~~~...........~~~~~......#####", + "###.~~~~~...........~~~~~......#####", + "###~~~~~~...........~~~~~......#####", + "###~~~~~~...........~~~~~......#####", + "###~~~~~~.....########.........#####", + "#########vvvvv########vvvvvv...~~###", + "#########.....########~~~~~~~~~~~###", + "#######.......########~~~~~~~~~~~###", + "#######...........####~~~~~~~~~~~###", + "###...............####~~~~~~~~~~~###", + "###...............####~~~~~~~~~~~###", + "###............S..####~~~~~~~~~~~###", + "###...............####~~~~~~~~~~~###", + "###...............####..~~~~~~~~~###", + "###~~~~##...............~~~~~~~~~###", + "###~~~~##.................~~~~~~~###", + "###~~~~##....................~~~~###", + "###~~~~##......................#####", + "###~~~~##vvvvv########vvvvvvvvv#####", + "###~~~~##.....########.........#####", + "###~~~~~~......~~~~~~...S......#####", + "###~~~~~~......~~~~~~..........#####", + "###~~~~~~......~~~~~~..........#####", + "###~~~~~~......~~~~~~..........#####", + "###~~~~~~......~~~~~~..........#####", + "#####################..........#####", + "#####################..........#####" + ], + "points": { + "catch_spot": [19, 27], + "save_spot": [30, 24], + "trainer_tristan": [7, 13], + "trainer_natalie": [22, 18], + "trainer_logan": [26, 4], + "exit_jubilife": [15, 0] + }, + "objects": [ + {"name": "youngster_0", "kind": "npc", "x": 7, "y": 13, "facing": "south"}, + {"name": "lass_1", "kind": "npc", "x": 22, "y": 18, "facing": "south"}, + {"name": "youngster_2", "kind": "npc", "x": 26, "y": 4, "facing": "south"}, + {"name": "youngster_7", "kind": "npc", "x": 10, "y": 8, "wander": {"x0": 9, "y0": 8, "x1": 11, "y1": 8}}, + {"name": "item_8", "kind": "item", "x": 3, "y": 6} + ], + "patches": [ + {"x": 15, "y": 28, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 16, "y": 28, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 17, "y": 28, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 18, "y": 28, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 19, "y": 28, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 20, "y": 28, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 15, "y": 29, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 16, "y": 29, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 17, "y": 29, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 18, "y": 29, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 19, "y": 29, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 20, "y": 29, "glyph": "#", "measured_by": "r253, 2026-11-01"}, + {"x": 21, "y": 30, "glyph": "#", "measured_by": "r254, 2026-11-01"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json new file mode 100644 index 0000000000..88f443c610 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json @@ -0,0 +1,64 @@ +{ + "name": "Route203", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0016.bin, files/fielddata/land_data/land_data_release/narc_0019.bin; zone_event narc_0329", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "#############################################################", + "#############################################################", + "#############################################################", + "#############################################################", + "#####..............###########..~~~~#########################", + "#####..............###########...~~~..~~~~~~~~~~~~~~#########", + "#####..................#######........~~~~~~~~~~~~~.....#####", + "#####..................#######wwwwww##~~~~~~~~~~~....S..#####", + "#####~~~~##...####.....#......wwwwww##~~~~~~~~~~~.......#####", + "#####~~~~##...####.....#......wwwwww##~~~~~~~~~~~.......D####", + "#####~~~~##...####.....#..S...wwwwww##~~~~~~~~~~~.......#####", + "#####~~~~##...####.....#......wwwwww##~~~~~~~~~~~.......#####", + "#####~~~~##...####.....#....~~~~~~~~##~~~~~~~~~S...~~~..#####", + "#####~~~~##...####vvvvv#...~~...~.~~##~~~~~~~~~....~~~..#####", + "#####~~................#....######~~##.....~~....##~~~..#####", + "#####..................#....######~~##vvvvvvvvvvv##~~~..#####", + "#####....S.............#....~~~..~~~.............~~~~~..#####", + "...........................~~~~...~~.............~~~~~..#####", + "...........................~~.....~~.............~~~~~..#####", + "..........................~~~~.....~##..........~~~~~~..#####", + "..........................~~~~~.~~~~#########################", + "#############################################################", + "#############################################################", + "#############################################################" + ], + "points": { + "gate_exit.1": [55, 9], + "gate_exit.2": [54, 9], + "gate_exit.3": [55, 8], + "gate_exit.4": [55, 10], + "west_line.1": [24, 18], + "west_line.2": [25, 18], + "west_line.3": [26, 18], + "west_line.4": [27, 18], + "west_line.5": [28, 18], + "west_line.6": [29, 18], + "west_line.7": [30, 18], + "west_line.8": [31, 18], + "west_line.9": [32, 18], + "west_line.10": [33, 18], + "west_line.11": [34, 18], + "west_line.12": [35, 18], + "west_line.13": [36, 18], + "west_line.14": [37, 18], + "west_line.15": [38, 18], + "west_line.16": [39, 18], + "west_line.17": [40, 18], + "west_line.18": [41, 18], + "west_line.19": [42, 18] + }, + "objects": [ + {"name": "youngster_1", "kind": "npc", "x": 13, "y": 10, "facing": "west"}, + {"name": "lass_2", "kind": "npc", "x": 43, "y": 17, "facing": "west"}, + {"name": "youngster_3", "kind": "npc", "x": 31, "y": 16, "facing": "south"}, + {"name": "lass_4", "kind": "npc", "x": 49, "y": 8, "facing": "south"}, + {"name": "item_10", "kind": "item", "x": 31, "y": 5}, + {"name": "item_11", "kind": "item", "x": 20, "y": 10}, + {"name": "lass_12", "kind": "npc", "x": 15, "y": 17, "wander": {"x0": 14, "y0": 17, "x1": 16, "y1": 17}} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204North.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204North.json new file mode 100644 index 0000000000..65fe1fb8cf --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204North.json @@ -0,0 +1,83 @@ +{ + "name": "Route204North", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0031.bin; zone_event narc_0331", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "########.#...###################", + "########.#...###################", + "####.....#...########.##########", + "##########...#......#.##########", + "#####....#...#......#...........", + "#####....#~~~#......#...######..", + "#####....#~~~###################", + "##########~~~.........##########", + "##........~~~~~.......##########", + "##..######.~~~~..S....##########", + "##..#....#.~~~........##########", + "##..#....#.~~.........##########", + "##########..............########", + "##########..............########", + "##########...####~~~~~~~########", + "##########vvv####~~~~~~~########", + "##########...######~~~~~########", + "##########...######~~~~~########", + "##########...######~~~~~########", + "##########...######~.~~~########", + "##########............~~########", + "##########............##########", + "##########............##########", + "##########...##.......##########", + "##########...##.......##########", + "##########...###################", + "##########...#######.###########", + "##########............##########", + "##########............##########", + "##########............##########", + "##########............##########", + "##########...###################" + ], + "points": { + "cave_exit": [20, 26], + "floaroma_waypoints.1": [20, 27], + "floaroma_waypoints.2": [11, 27], + "floaroma_waypoints.3": [11, 22], + "floaroma_waypoints.4": [19, 22], + "floaroma_waypoints.5": [19, 12], + "floaroma_waypoints.6": [11, 12], + "floaroma_waypoints.7": [11, 5], + "floaroma_path.1": [10, 0], + "floaroma_path.2": [11, 0], + "floaroma_path.3": [12, 0], + "floaroma_path.4": [10, 1], + "floaroma_path.5": [11, 1], + "floaroma_path.6": [12, 1], + "floaroma_path.7": [10, 2], + "floaroma_path.8": [11, 2], + "floaroma_path.9": [12, 2], + "floaroma_path.10": [10, 3], + "floaroma_path.11": [11, 3], + "floaroma_path.12": [12, 3], + "floaroma_path.13": [10, 4], + "floaroma_path.14": [11, 4], + "floaroma_path.15": [12, 4], + "floaroma_path.16": [10, 5], + "floaroma_path.17": [11, 5], + "floaroma_path.18": [12, 5], + "trainer_twin_left": [15, 7], + "trainer_twin_right": [16, 7], + "trainer_taylor": [17, 20], + "trainer_brandon": [20, 12] + }, + "objects": [ + {"name": "bug_catcher_brandon", "kind": "npc", "x": 20, "y": 12}, + {"name": "pokemon_breeder_f_1", "kind": "npc", "x": 17, "y": 20}, + {"name": "twin_2", "kind": "npc", "x": 15, "y": 7, "facing": "south"}, + {"name": "twin_3", "kind": "npc", "x": 16, "y": 7, "facing": "south"}, + {"name": "youngster_4", "kind": "npc", "x": 15, "y": 28, "wander": {"x0": 14, "y0": 28, "x1": 16, "y1": 28}}, + {"name": "item_9", "kind": "item", "x": 2, "y": 10}, + {"name": "ace_trainer_f_10", "kind": "npc", "x": 31, "y": 4, "facing": "west"}, + {"name": "item_11", "kind": "item", "x": 20, "y": 23} + ], + "patches": [ + {"x": 12, "y": 6, "glyph": "#", "measured_by": "r248a, 2026-10-31"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204South.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204South.json new file mode 100644 index 0000000000..d70775dce3 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204South.json @@ -0,0 +1,56 @@ +{ + "name": "Route204South", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0021.bin; zone_event narc_0330", "revision": "default branch", "date": "2026-11-01"}, + "tiles": [ + "################################", + "###########D###.################", + "########..S.....################", + "########........################", + "########........################", + "##########...~##################", + "##########...~##################", + "##########..~~##################", + "##########..~~###wwwwwwwww######", + "####~~~~~~~~~~###wwwwwwwww######", + "####~~~~~~~~~~###wwwwwwwww######", + "####~~~~~~~~~~###wwwwwwwww######", + "####~~~~~~~~~~###wwwwwwwww....##", + "####~~~~~~~~~~###wwwwwwwww....##", + "####~~~~~~~~~~#..wwwww##########", + "##~~~~~~~~~~~~#..wwwww##########", + "##~~~~~~~~~~~~#..wwwww##########", + "##~~~~~~~~~~~~S.################", + "##~~~~~.........#.##############", + "##~~~~~.........#.##############", + "##~~~~~.........#.##############", + "##.#########....#.##############", + "##wwwwwwwww#....#......~########", + "##wwwwwwwww#....#wwwww~~########", + "##wwwwwwwww#....#wwwww~~########", + "##wwwwwwwww#....#wwwww~~########", + "##wwwwwwwww#....#wwwww~~########", + "##wwwwwwwww#....#wwwww~~########", + "##..######.#....#..~~~~~########", + "##..######.#....#...~~~~########", + "##########.#....#.......########", + "############....######..########" + ], + "points": { + "below_door": [11, 2], + "jubilife_entry.1": [14, 31], + "jubilife_entry.2": [14, 30], + "jubilife_entry.3": [14, 29], + "trainer_samantha": [11, 8], + "trainer_sarah": [12, 21], + "trainer_tyler": [7, 18] + }, + "objects": [ + {"name": "lass_0", "kind": "npc", "x": 12, "y": 21}, + {"name": "lass_1", "kind": "npc", "x": 11, "y": 8, "facing": "south"}, + {"name": "youngster_2", "kind": "npc", "x": 7, "y": 18}, + {"name": "youngster_3", "kind": "npc", "x": 13, "y": 2, "wander": {"x0": 12, "y0": 2, "x1": 14, "y1": 2}}, + {"name": "item_7", "kind": "item", "x": 2, "y": 28}, + {"name": "item_8", "kind": "item", "x": 29, "y": 12}, + {"name": "item_9", "kind": "item", "x": 17, "y": 18} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json new file mode 100644 index 0000000000..4f7a4e61b4 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json @@ -0,0 +1,55 @@ +{ + "name": "SandgemTown", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0007.bin, files/fielddata/land_data/land_data_release/narc_0173.bin; zone_event narc_0396", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "########################......####", + "########################......####", + "####################..........####", + "####################..........####", + "####################..........####", + "####################..........####", + "####################......########", + "####################......########", + "####################......########", + "####################......########", + "########D##....##D##S....S#D######", + ".....S........................####", + "..............................####", + "..............................####", + "..............................####", + "..............................####", + "######........................####", + "######........................####", + "######.........#####..........####", + "##########.....#####..........####", + "##########.....#####..........####", + "##########.....#####..........####", + "#######D##....S#D###..S.......####", + "######........................####", + "######........................####", + "######........................####", + "######....................########", + "####################......########", + "####################......########", + "####################......########", + "####################......########", + "####################......########" + ], + "points": { + "route_202_entry_stop": [29, 5], + "lab_door": [8, 10], + "pokemon_center_door": [17, 10], + "pokemon_center_sign": [20, 10], + "poke_mart_door": [27, 10], + "front_row_east_end": [29, 11], + "below_sign_gap.1": [21, 11], + "below_sign_gap.2": [22, 11], + "below_sign_gap.3": [23, 11], + "below_sign_gap.4": [24, 11], + "sandgem_arrival": [0, 12], + "dawn_stop": [4, 12] + }, + "objects": [ + {"name": "youngster_0", "kind": "npc", "x": 27, "y": 23, "wander": {"x0": 26, "y0": 22, "x1": 28, "y1": 24}} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json new file mode 100644 index 0000000000..3431befeae --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json @@ -0,0 +1,48 @@ +{ + "name": "TwinleafTown", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0000.bin; zone_event narc_0389", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "############........############", + "#######....#........#....#######", + "#######....#........#....#######", + "############........############", + "######....................######", + "######....................######", + "######....................######", + "######..#####.............######", + "######..#####......####...######", + "######..#####......####...######", + "######..######.....####...######", + "######..#D###S.....#D##...######", + "######....................######", + "######....................######", + "######..........S.........######", + "######....................######", + "######....................######", + "######.............#####..######", + "######...####......#####..######", + "######...####......#####..######", + "######...####.....######..######", + "######...#D##.....S#D###..######", + "######....................######", + "######....................######", + "######....................######", + "######....................######", + "######....................######", + "######......wwwwwwww......######", + "############wwwwwwww############", + "############wwwwwwww############", + "############wwwwwwww############", + "############wwwwwwww############" + ], + "points": { + "house_doorstep": [20, 22], + "barry_doorstep": [9, 12], + "twinleaf_north_exit": [15, 0] + }, + "objects": [ + {"name": "pokemon_breeder_f_0", "kind": "npc", "x": 23, "y": 14, "wander": {"x0": 22, "y0": 14, "x1": 24, "y1": 14}}, + {"name": "collector_1", "kind": "npc", "x": 12, "y": 25}, + {"name": "guitarist_3", "kind": "npc", "x": 12, "y": 5} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/VerityLakefront.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/VerityLakefront.json new file mode 100644 index 0000000000..ec50cf46e9 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/VerityLakefront.json @@ -0,0 +1,43 @@ +{ + "name": "VerityLakefront", + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0004.bin; zone_event narc_0319", "revision": "default branch", "date": "2026-10-31"}, + "tiles": [ + "....############################", + "....############################", + "....############################", + "....############################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################..##############", + "################..##############", + "################..##############", + "##############.S....<...........", + "##############......<.....####..", + "##############......<.....####..", + "##############......<...........", + "##############......<...........", + "##############......#vvvvv##vvvv", + "################..........##....", + "################................", + "##################..............", + "##################..............", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################", + "################################" + ], + "points": { + "lakefront_arrival": [31, 23], + "below_lake_entrance": [17, 14], + "lake_entrance": [17, 12] + } +} diff --git a/devtools/maps/bdsp_maps.py b/devtools/maps/bdsp_maps.py new file mode 100644 index 0000000000..0bcf886057 --- /dev/null +++ b/devtools/maps/bdsp_maps.py @@ -0,0 +1,555 @@ +"""Writes BDSP AutoStory's maps as JSON, from the Pokemon Diamond decompilation. + +TODO: move the maps to PokemonAutomation/Packages (Resources/PokemonBDSP/Maps) when ready. + +Usage: + python3 devtools/maps/bdsp_maps.py # rewrite every map in OUT + python3 devtools/maps/bdsp_maps.py Route202 # rewrite one + python3 devtools/maps/bdsp_maps.py --check # regenerate, fail on any difference + +Each map is a rectangle of the decompilation's world grid (MAPS below), +decoded tile for tile by devtools/maps/decomp.py from the files cached in +~/.cache/maps/decomp (`python3 -m devtools.maps decomp` fetches missing ones +with `gh api`; this script never fetches, it names the file it lacks). BDSP +keeps Diamond/Pearl's layout, so Diamond is read unless a map says otherwise. +The tables below add what the decompilation doesn't say or BDSP changed: + +- POINTS: named tiles the story walks from and to. A region (a set of + tiles, in order where the order matters) is written as numbered points, + `.1`, `.2`, ...: the shared schema (Pokemon_MapData.h) + has single-tile points only. +- OBJECTS: which of the events file's objects BDSP shows. People hidden by + a story flag are left out, and people who stand still or wander are kept, + unless an entry here says otherwise (`show`, `hide`, `still`, `at`), or + adds someone the console found that Diamond doesn't have. +- WARP_DOORS: warp tiles written 'D' so that no plan steps onto them; the + other warp tiles are written '.', since the story arrives on them. +- PATCHES: tiles measured on the console that differ from Diamond's, with + the run that measured them. The map's tiles stay the decompilation's; the + loader applies the patches. + +The legend is the schema's: '#' wall, '.' open, '~' tall grass, 'w' water, +'^' 'v' '<' '>' ledges hopped the arrow's way, 'D' a door or cave mouth +(walked into) or a warp tile in WARP_DOORS, 'S' a sign or mailbox (read by +facing it; a sign script on a wall too). Items on the ground are objects of +kind "item"; rocks, cut trees, berry soil and steam vents are '#'. + +Rules carried over from the maps' history: terrain from the decomp, people +and BDSP's edits from the console; a route is coded only over tiles a human +or a probe has walked; a map change re-runs every checkpoint that walks it. +Bulbapedia's DP map images have 12 px rows for 16 px columns (3/4 of a tile +per row) and paint cliffs taller than their tiles, so they are for drawing +routes over (`python3 -m devtools.maps overlay`), not for terrain. Scenes' +trigger rectangles (the events files' coord events) have no place in the +schema; Segment00Lines lists those it needs in C++. +""" +import argparse +import json +import os +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +from devtools.maps import decomp # noqa: E402 + +OUT = 'SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps' +CACHE = Path.home() / '.cache' / 'maps' / 'decomp' +# When the cache was filled from the decompilations' default branches. +READ = '2026-10-31' + +LEDGES = {'north': '^', 'south': 'v', 'west': '<', 'east': '>'} +SIGN_GFX = ('MAILBOX',) +OBSTACLE_GFX = ('CUT_TREE', 'ROCK_SMASH', 'STRENGTH_BOULDER', 'BERRY_SOIL', 'VENT') +FACING = {'LOOK_NORTH': 'north', 'LOOK_SOUTH': 'south', 'LOOK_WEST': 'west', 'LOOK_EAST': 'east'} + + +# Each map: its header constant, the world rectangle (x0, z0, width, +# height), and the decompilation when not Diamond. Local (x, y) = world +# (x - x0, z - z0). A map that joins another on the world grid keeps its +# rectangle's world corner, so story code can place them side by side. +MAPS = { + # Cells (3..4, 26) from z 839: the image-traced grid's columns and the + # game's rows. Console: the border sign read from (54, 4) facing east, + # row 3 open east of the grass, row 5 to Sandgem without a stop, the + # Potion lady on row 3 (2026-10-28 to 30, CFR "walk"). + 'Route201': {'header': 'ROUTE_201', 'rect': (96, 839, 64, 25)}, + # Cell (5, 25) from x 159. Console: under the south ledge at (22, 24), + # the save spot (30, 24) against the east trees, Natalie's challenge at + # (22, 22), column 30 north to row 4 (r75), the catch spot's walls. + 'Route202': {'header': 'ROUTE_202', 'rect': (159, 800, 36, 32)}, + # Cells (5..6, 23) from x 191: column 0 is Jubilife's east edge. + # Console (r227 hand pass, 2026-10-31): the Gate's warp at column 55, + # the ledge on row 15, the girl at (43, 17), row 18 straight west to + # Jubilife (35 taps). Diamond has four sand rows under the ledge where + # the console counted three (the first tap may only have turned). + 'Route203': {'header': 'ROUTE_203', 'rect': (191, 740, 61, 24)}, + # Cell (5, 21). Console: row 27 open under the cliff, the stairs, + # Taylor's sight at column 17 (human walk, 2026-10-31). Suspects: + # column 23 rows 12..19 blocked (r242a); three open rows under the cave, + # not four (r241a, r243a). + 'Route204North': {'header': 'ROUTE_204_NORTH', 'rect': (160, 672, 32, 32)}, + # Cell (5, 22). Console: north up column 14 stops at the signpost + # (14, 17); column 13 is grass to the cliff (13, 2); Sarah walked up and + # fought on row 21 (r233a, r234a). 01.08 passed once (r235a). + 'Route204South': {'header': 'ROUTE_204_SOUTH', 'rect': (160, 704, 32, 32), 'read': '2026-11-01'}, + # Cell (5, 26). Console (2026-10-29/30): all three doors, five signs, + # the doors' front row east to the trees and north from it, the Lab's + # sign read facing west, the walk south from Route 202 stopping above + # the Mart at (29, 5). + 'SandgemTown': {'header': 'SANDGEM', 'rect': (160, 832, 34, 32)}, + # Cells (4..5, 23..24). Console (2026-10-30/31): the lamps (43, 53) and + # (49, 53), north from (52, 53) to the Center's door, the School's door + # from the Center's doorstep, the east street to Route 203 (r230a), the + # north street, and the 00.05 people on their events-file tiles. + 'JubilifeCity': {'header': 'JUBLIFE', 'rect': (128, 736, 64, 64)}, + # Cells (8..9, 23..24); Platinum's map_data_022/023/175/024 are the + # same terrain. Console (r204 to r215): 20 D-pad taps from the + # Center's doorstep (46, 21) to below the Gym's door. + 'OreburghCity': {'header': 'OREBURGH', 'rect': (256, 736, 64, 64)}, + # The only cell of matrix 003; Platinum identical. + 'OreburghGate': {'header': 'OREBURGH_GATE_1F', 'rect': (0, 0, 32, 32)}, + # The only cell of matrix 113. 01.04 walks it as a record: north up + # column 5 until a trainer stops us. + 'OreburghGym': {'header': 'OREBURGH_GYM', 'rect': (0, 0, 32, 32)}, + # B2F, the coal room: matrix 006. Console (2026-10-28, r99): the + # corridor's rocks zigzag a row at a time, the room's second row passes + # under the worker, Roark a row above the bottom between two rocks; the + # walk north into the hall went up column 14 (the warp is column 15: a + # stick hold drifts, or BDSP's stairs are wider). + 'OreburghMine': {'header': 'OREBURGH_MINE_B2F', 'rect': (0, 0, 32, 32)}, + # Platinum's matrix 051 (Diamond's matrix is not in the cache), rows + # 0..53. Console (2026-10-31, r235b and D-pad probes): in from the south + # door onto (19, 50), up the stairs (18..20, 45..47) to the wall at row + # 39, the corridor three wide to row 41 and open east on rows 42..44, + # the rocks (22, 42), (22, 43), (23, 44) and the Potion (22, 44). Rows + # 0..29 were a hand trace from the DP image before; nothing walks there. + 'RavagedPath': {'header': 'RAVAGED_PATH', 'rect': (0, 0, 32, 54), 'repo': decomp.Platinum.REPO}, + # Cell (5, 20); the cell above carries the same header but is cut off + # from the town. Console: the old couple (2026-10-31 walk, r251a). + 'FloaromaTown': {'header': 'FLOAROMA', 'rect': (160, 640, 32, 32)}, + # Cell (3, 27). The player's house is the south-east one, door + # (20, 21); Barry's the north-west one, door (9, 11) (the interiors say + # so; Diamond's map constants name them the other way round). + 'TwinleafTown': {'header': 'TWINLEAF', 'rect': (96, 864, 32, 32)}, + # Cell (2, 26), of the four the header covers (the others are trees + # and water). The ledges on column 20 (rows 14..18) are hopped west. + 'VerityLakefront': {'header': 'VERITY_LAKEFRONT', 'rect': (64, 832, 32, 32)}, + # Cell (1, 1) of the lake's matrix 101: the shore where the starter + # scene plays. Only this cell is reached on foot. + 'LakeVerity': {'header': 'LAKE_VERITY', 'rect': (32, 32, 32, 32)}, +} + + +def box(x0, y0, x1, y1): + """Every tile of a box, both corners inclusive, row by row.""" + return [(x, y) for y in range(y0, y1 + 1) for x in range(x0, x1 + 1)] + + +# Named tiles; a list is a region, written as numbered points. +POINTS = { + 'Route201': { + # The arrow sign to Sandgem. Console (CFR "walk"): east along row 4 + # stops at its west side and A reads it. + 'border_sign': (55, 4), + # The Poke Mart lady (10 Potions): Diamond spawns her at (44, 3), the + # console had her at (43, 3) and walking west along row 3 stopped at + # (44, 3), facing her (99715d5d6, f823453e9; r135a missed her on row 4). + 'potion_giver': (43, 3), + 'potion_giver_talk_from': (44, 3), + # Segment 00's lines (Segment00Lines). + 'route_201_arrival': (15, 24), + 'barry_warning': (15, 18), + 'route_201_return': (0, 14), + 'rowan_stop': (5, 14), + 'route_201_east_end': (63, 5), + }, + 'Route202': { + # 00.03's save after the catch: the catch patch's bottom row. The + # sign (24, 25) is 5 right and 2 up; south are trees (r253). + 'catch_spot': (19, 27), + # 00.04's save before the trainers, under the south ledge against + # the east trees (r113a..r117a). + 'save_spot': (30, 24), + 'trainer_tristan': (7, 13), + 'trainer_natalie': (22, 18), + 'trainer_logan': (26, 4), + # The middle of the gap to Jubilife, below its 3-wide neck. + 'exit_jubilife': (15, 0), + }, + 'Route203': { + # The warp tile the Gate puts us on, and its open neighbours. + 'gate_exit': [(55, 9), (54, 9), (55, 8), (55, 10)], + # Row 18 between the cliffs: it runs straight west into Jubilife. + 'west_line': box(24, 18, 42, 18), + }, + 'Route204North': { + 'cave_exit': (20, 26), + # The line a human walked on 2026-10-31: out of the cave, west under + # the cliff, up the stairs, east (Taylor), north up column 19, west + # below the flower beds, north between them on column 11 ((12, 6) + # is blocked on the console). + 'floaroma_waypoints': [(20, 27), (11, 27), (11, 22), (19, 22), (19, 12), (11, 12), (11, 5)], + # The path north into Floaroma: walking north from here leaves. + 'floaroma_path': box(10, 0, 12, 5), + 'trainer_twin_left': (15, 7), + 'trainer_twin_right': (16, 7), + 'trainer_taylor': (17, 20), + 'trainer_brandon': (20, 12), + }, + 'Route204South': { + 'below_door': (11, 2), + # Where the walk north from Jubilife may stop, up to two tiles past + # the border (400 ms holds between looks). + 'jubilife_entry': [(14, 31), (14, 30), (14, 29)], + 'trainer_samantha': (11, 8), + 'trainer_sarah': (12, 21), + 'trainer_tyler': (7, 18), + }, + 'SandgemTown': { + # The walk south from Route 202 down the road's east side stops here, + # above the Mart where the road narrows (console, 2026-10-29/30). + 'route_202_entry_stop': (29, 5), + 'lab_door': (8, 10), + 'pokemon_center_door': (17, 10), + # Walking north from row 14 in its column stops below it. + 'pokemon_center_sign': (20, 10), + 'poke_mart_door': (27, 10), + # East along the doors' front row stops here, at the trees (CFR + # "walk", 2026-10-30): a known tile to plan north from. + 'front_row_east_end': (29, 11), + # The front row below the gap between the Center's and the Mart's + # signs: the only way north from that row that passes no door. + 'below_sign_gap': box(21, 11, 24, 11), + 'sandgem_arrival': (0, 12), + 'dawn_stop': (4, 12), + }, + 'JubilifeCity': { + # A doorway (warp tile) stepped onto from (40, 41). + 'trainers_school_door': (40, 40), + # Walked into from (52, 41); eleven taps west from its doorstep put + # us under the School's door (2026-10-30). + 'pokemon_center_door': (52, 40), + 'tv_station_door': (36, 15), + # Talked to from the east (also works from the south). + 'poketch_president': (46, 35), + # His spawn tile; Diamond has him wander a tile either side, the + # console had him a tile east of it. + 'clown_mart': (55, 33), + 'clown_tv_station': (36, 16), + # Talked to from the south; west along row 18 stops beside him. + 'clown_poketch_company': (15, 18), + # A tile west of 00.05's save below the steps, then north: on + # column 46 or 47 a few rows up (CFR "walk", 2026-10-30). + 'up_the_steps': box(46, 58, 47, 60), + # Where Route 203's road enters, rows 21..24 at the east edge. + 'east_entrance': box(61, 21, 63, 24), + # The north road where it leaves the plaza: north from here goes + # up the road to Route 204 and Rowan's scene. + 'north_road': box(44, 16, 48, 20), + }, + 'OreburghCity': { + 'pokemon_center_door': (47, 20), + # The taps say x 46, a column west of the door. + 'pokemon_center_doorstep': (46, 21), + 'gym_door': (26, 20), + 'gym_doorstep': (26, 21), + 'gate_mouth': (1, 13), + }, + 'OreburghGym': { + # Coming in puts us here; the door is below. + 'entrance': (5, 23), + 'roark': (5, 3), + 'trainer_first_tier': (4, 18), + 'trainer_second_tier': (7, 11), + }, + 'OreburghMine': { + 'roark': (18, 28), + 'corridor_exit_start': (14, 10), + # The first open row below the warp tile, where the walk in from + # the hall leaves us. + 'corridor_top': box(14, 2, 16, 2), + }, + 'RavagedPath': { + 'entrance': (19, 50), + 'east_end': (29, 44), + 'above_exit': (28, 44), + # Rock Smash rocks, open once smashed. + 'rocks': [(11, 36), (7, 37), (12, 37), (8, 38), (10, 38), (10, 39), (13, 39), + (7, 40), (9, 40), (17, 40), (12, 41), (22, 42), (22, 43), (23, 44)], + # West of the two rocks on rows 42 and 43. + 'before_rocks': [(21, 42), (21, 43)], + }, + 'FloaromaTown': { + 'route_204_arrival': (11, 29), + 'old_woman': (3, 25), + 'old_man': (2, 25), + 'pokemon_center_door': (16, 26), + }, + 'TwinleafTown': { + 'house_doorstep': (20, 22), + # Barry's scene fires here, below his door. + 'barry_doorstep': (9, 12), + 'twinleaf_north_exit': (15, 0), + }, + 'VerityLakefront': { + 'lakefront_arrival': (31, 23), + 'below_lake_entrance': (17, 14), + # The lake's scene fires here; it is assumed to leave us here too. + 'lake_entrance': (17, 12), + }, +} + + +# Overrides by events-file object index. `show`: a person hidden by a +# story flag who is there when the story walks the map. `hide`: someone +# BDSP doesn't have, or the story never meets. `still`: a wanderer who +# stood still on the console. `at`: where the console found them. `add`: +# people Diamond doesn't have, from the console. +OBJECTS = { + 'Route201': { + # The Potion lady stands a tile off her spawn on the console, and + # her talk is walked to, not planned (POINTS). + 1: {'hide': 'her talk is walked to, not planned'}, + }, + 'Route203': { + # Stands still in Diamond, wanders near the Gate in BDSP (console). + 0: {'hide': 'wanders in BDSP'}, + }, + 'Route204North': { + # Bug Catcher Brandon paces in Diamond from (22, 13); he stopped us + # on row 12 (r246a), and the DP map draws him at (20, 12). + 0: {'at': (20, 12), 'still': 'r246a', 'name': 'bug_catcher_brandon'}, + }, + 'SandgemTown': { + # Stand still in Diamond, wander on the console. + 1: {'hide': 'wanders in BDSP'}, + 2: {'hide': 'wanders in BDSP'}, + }, + 'JubilifeCity': { + # 00.05's story people, hidden before and after it. + 24: {'show': '00.05', 'name': 'poketch_president'}, + 28: {'show': '00.05', 'name': 'clown_tv_station'}, + # Wander in Diamond; stood still on the console (00.05, three runs). + 25: {'show': '00.05', 'still': '00.05, three runs', 'name': 'clown_mart'}, + 26: {'show': '00.05', 'still': '00.05, three runs', 'name': 'clown_poketch_company'}, + }, + 'FloaromaTown': { + # Grunts at the Meadow until the Valley Windworks is cleared, and + # the man with Cyrus's sprite (whether he is there is for the console). + 7: {'show': 'until the Valley Windworks'}, + 8: {'show': 'until the Valley Windworks'}, + 3: {'show': 'not yet checked on the console'}, + 'add': [ + # Not in Diamond's data. Walking north from (3, 29) stopped 3 + # tiles up at the old woman (r251a). + {'name': 'old_woman', 'x': 3, 'y': 25, 'measured_by': 'r251a, 2026-10-31'}, + {'name': 'old_man', 'x': 2, 'y': 25, 'measured_by': 'r251a, 2026-10-31'}, + ], + }, + 'OreburghMine': { + # Hidden once his scene here is over; the story walks to him. + 0: {'show': '01.03', 'name': 'roark'}, + }, + 'LakeVerity': { + # The starter scene's actors, where the walk in ends. + 0: {'show': 'the starter scene', 'name': 'rowan'}, + 1: {'show': 'the starter scene', 'name': 'counterpart'}, + 3: {'show': 'the starter scene'}, + }, +} + + +WARP_DOORS = { + # The Ravaged Path's door: its warp tile is the mouth. + 'Route204South': [(11, 1)], + # The Route 218 gate, and the doorways of the School and the Global + # Terminal: entered, never stood on by a route. + 'JubilifeCity': [(0, 22), (0, 23), (40, 40), (21, 42)], + # The way up to the entrance hall: stepping on it leaves the room. + 'OreburghMine': [(15, 1)], +} + + +PATCHES = { + 'Route202': ( + # BDSP has trees where Diamond's catch patch has its two bottom rows: + # west along row 29 stopped at column 21, beside them. + [(x, y, '#', 'r253, 2026-11-01') for y in (28, 29) for x in range(15, 21)] + # South from the pocket (21, 29), where the catch's save loads, + # was blocked at once. + + [(21, 30, '#', 'r254, 2026-11-01')] + ), + 'Route204North': [ + # Grass in the data, blocked on the console twice. + (12, 6, '#', 'r248a, 2026-10-31'), + ], +} + + +def no_fetch(repo, path): + raise SystemExit(f'{repo}/{path} is not in {CACHE}. Fetch it with `python3 -m devtools.maps decomp ...` first.') + + +def glyph(attr): + behaviour = attr & 0xff + if behaviour in decomp.JUMP: + return LEDGES[decomp.JUMP[behaviour]] + if behaviour in decomp.JUMP_TWICE: + return LEDGES[decomp.JUMP_TWICE[behaviour]] + if behaviour == decomp.DOOR or behaviour in decomp.CAVE_MOUTHS: + return 'D' + if behaviour in decomp.WARP_TILES or behaviour in decomp.WARP_STAIRS: + return 'W' + if behaviour in decomp.WATER: + return 'w' + if attr & decomp.BLOCKED: + return '#' + if behaviour in decomp.TALL_GRASS: + return '~' + return '.' + + +def wander_box(obj, x, y, width, height): + return {'x0': max(0, x - obj.range_x), 'y0': max(0, y - obj.range_z), + 'x1': min(width - 1, x + obj.range_x), 'y1': min(height - 1, y + obj.range_z)} + + +def place_objects(name, events, rect, rows): + """Writes signs and obstacles into `rows`; returns the objects.""" + x0, z0, width, height = rect + overrides = OBJECTS.get(name, {}) + objects = [] + for index, obj in enumerate(events.objects): + x, y = obj.x - x0, obj.z - z0 + if not (0 <= x < width and 0 <= y < height): + continue + override = overrides.get(index, {}) + if obj.gfx in OBSTACLE_GFX: + rows[y][x] = '#' + continue + if obj.gfx == decomp.ITEM: + objects.append({'name': f'item_{index}', 'kind': 'item', 'x': x, 'y': y}) + continue + if obj.hidden and 'show' not in override or 'hide' in override: + continue + if 'SIGN' in obj.gfx or obj.gfx in SIGN_GFX: + rows[y][x] = 'S' + continue + x, y = override.get('at', (x, y)) + entry = {'name': override.get('name', f'{obj.gfx.lower()}_{index}'), 'kind': 'npc', 'x': x, 'y': y} + if obj.movement in FACING: + entry['facing'] = FACING[obj.movement] + if obj.movement.startswith(('WANDER', 'WALK')) and 'still' not in override: + entry['wander'] = wander_box(obj, x, y, width, height) + objects.append(entry) + for added in overrides.get('add', []): + objects.append({'name': added['name'], 'kind': 'npc', 'x': added['x'], 'y': added['y']}) + return objects + + +def decode_map(name): + spec = MAPS[name] + repo = spec.get('repo', decomp.Diamond.REPO) + game = decomp.game_for(repo, CACHE, None, no_fetch) + header = game.header(spec['header']) + matrix = game.matrix(header.matrix_id) + world = decomp.World(game, matrix) + x0, z0, width, height = spec['rect'] + rows = [] + for y in range(height): + row = [] + for x in range(width): + attr = world.attr(x0 + x, z0 + y) + if attr is None: + raise SystemExit(f'{name}: ({x}, {y}) is outside matrix {header.matrix_id}.') + row.append(glyph(attr)) + rows.append(row) + events = game.events(header.events_id) + for sign in events.signs: + x, y = sign.x - x0, sign.z - z0 + if 0 <= x < width and 0 <= y < height and rows[y][x] == '#': + rows[y][x] = 'S' + objects = place_objects(name, events, spec['rect'], rows) + doors = set(WARP_DOORS.get(name, [])) + for y, row in enumerate(rows): + for x, tile in enumerate(row): + if tile == 'W': + row[x] = 'D' if (x, y) in doors else '.' + lands = sorted({world.land_id(x, z) for z in range(z0, z0 + height, decomp.CELL) + for x in range(x0, x0 + width, decomp.CELL)} - {None}) + source = { + 'decomp_file': f'{repo}: ' + ', '.join(game.land_path(land) for land in lands) + + f'; {game.events_name(header.events_id)}', + 'revision': 'default branch', + 'date': spec.get('read', READ), + } + return [''.join(row) for row in rows], objects, source + + +def points_of(name): + points = {} + for point, value in POINTS.get(name, {}).items(): + if isinstance(value, list): + for i, pos in enumerate(value, 1): + points[f'{point}.{i}'] = pos + else: + points[point] = value + return points + + +def map_text(name): + """The map's JSON: one line per row, point, object and patch.""" + tiles, objects, source = decode_map(name) + lines = ['{', f' "name": {json.dumps(name)},', f' "source": {json.dumps(source)},', ' "tiles": ['] + lines += [f' {json.dumps(row)},' for row in tiles] + lines[-1] = lines[-1].rstrip(',') + lines.append(' ]') + + def section(key, items): + if not items: + return + lines[-1] += ',' + lines.append(f' "{key}": ' + ('{' if key == 'points' else '[')) + lines.extend(f' {item},' for item in items) + lines[-1] = lines[-1].rstrip(',') + lines.append(' ' + ('}' if key == 'points' else ']')) + + section('points', [f'{json.dumps(p)}: [{x}, {y}]' for p, (x, y) in points_of(name).items()]) + section('objects', [json.dumps(o) for o in objects]) + section('patches', [json.dumps({'x': x, 'y': y, 'glyph': g, 'measured_by': m}) + for x, y, g, m in PATCHES.get(name, [])]) + lines.append('}') + return '\n'.join(lines) + '\n' + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.split('\n')[0]) + parser.add_argument('--out', default=OUT, help='folder of the .json files') + parser.add_argument('--check', action='store_true', help='check the files in --out instead of writing them') + parser.add_argument('maps', nargs='*', help='default: every map in MAPS') + args = parser.parse_args() + names = args.maps or sorted(MAPS) + for name in names: + if name not in MAPS: + parser.error(f'{name}: not in MAPS') + texts = {name: map_text(name) for name in names} + if args.check: + differ = [n for n, text in texts.items() + if not os.path.exists(os.path.join(args.out, n + '.json')) + or open(os.path.join(args.out, n + '.json'), newline='').read() != text] + if not args.maps: + extra = sorted(f[:-5] for f in os.listdir(args.out) if f.endswith('.json') and f[:-5] not in MAPS) + differ += [f'{n} (not in MAPS)' for n in extra] + if differ: + sys.exit(f'Differ from the generator: {", ".join(differ)}. Regenerate them without --check.') + print(f'{len(texts)} maps in {args.out} match the generator.') + return + os.makedirs(args.out, exist_ok=True) + for name, text in texts.items(): + with open(os.path.join(args.out, name + '.json'), 'w', newline='') as file: + file.write(text) + print(f'Wrote {len(texts)} maps to {args.out}.') + + +if __name__ == '__main__': + main() From 3a67d5618e32f69591f23dd4bdb99ae1d51487b0 Mon Sep 17 00:00:00 2001 From: dayfine Date: Sun, 1 Nov 2026 13:20:18 -0800 Subject: [PATCH 02/13] BDSP maps: load the built-in maps, with their points and regions, and test them PokemonBDSP_AutoStory_Maps reads the embedded JSON through the shared MapData loader: story_map(), map_point(), and map_region() for the numbered points a region is written as. The tests load every map, check that points and objects stand where they can, and that the lookups find and refuse what they should. PokemonBDSP::add_tests() is now run by the unit test runner, which never called it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- SerialPrograms/CMakeLists.txt | 2 + .../ComputerPrograms_UnitTestRunner.cpp | 2 + .../Source/PokemonBDSP/PokemonBDSP_Tests.cpp | 2 + .../Data/PokemonBDSP_AutoStory_Maps.cpp | 77 +++++ .../Data/PokemonBDSP_AutoStory_Maps.h | 54 +++ .../PokemonBDSP_AutoStory_Maps_Tests.cpp | 311 ++++++++++++++++++ .../PokemonBDSP_AutoStory_Maps_Tests.h | 23 ++ SerialPrograms/cmake/SourceFiles.cmake | 4 + 8 files changed, 475 insertions(+) create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.h diff --git a/SerialPrograms/CMakeLists.txt b/SerialPrograms/CMakeLists.txt index d0966df32f..ad0ff7ae70 100644 --- a/SerialPrograms/CMakeLists.txt +++ b/SerialPrograms/CMakeLists.txt @@ -197,6 +197,8 @@ add_library(SerialProgramsLib STATIC ${CORE_LIBRARY_SOURCES} ${LIBRARY_SOURCES}) include(cmake/EmbedTextFiles.cmake) pa_embed_text_files(SerialProgramsLib Source/Pokemon/Navigation/TestMaps Pokemon/Navigation/Pokemon_Navigation_Tests.h PokemonAutomation::Pokemon navigation_test_map_json) +pa_embed_text_files(SerialProgramsLib Source/PokemonBDSP/Programs/AutoStory/Maps + PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h PokemonAutomation::NintendoSwitch::PokemonBDSP bdsp_map_json) # Create binary target diff --git a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp index 46d51dd63e..e72f9ca61f 100644 --- a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp +++ b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp @@ -31,6 +31,7 @@ #include "NintendoSwitch/Programs/Typewriter/NintendoSwitch_Typewriter_Tests.h" #include "Pokemon/Navigation/Pokemon_Navigation_Tests.h" +#include "PokemonBDSP/PokemonBDSP_Tests.h" #include "VideoGames/PokemonHome/PokemonHome_Tests.h" #include "VideoGames/PokemonSwSh/PokemonSwSh_Tests.h" #include "VideoGames/PokemonLA/PokemonLA_Tests.h" @@ -76,6 +77,7 @@ UnitTestDatabase make_UNIT_TESTS_ALL(){ NintendoSwitch::add_tests_FailedToConnectDetector(ret); NintendoSwitch::add_tests_UpdatePopupDetector(ret); NintendoSwitch::Typewriter::add_tests_TypewriterTextMapping(ret); + NintendoSwitch::PokemonBDSP::add_tests(ret); NintendoSwitch::PokemonFRLG::add_tests(ret); NintendoSwitch::PokemonRSE::add_tests(ret); NintendoSwitch::PokemonHome::add_tests(ret); diff --git a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp index b8ff5e26a5..36f4de8f6d 100644 --- a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp +++ b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp @@ -5,6 +5,7 @@ */ #include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.h" #include "PokemonBDSP_Tests.h" namespace PokemonAutomation{ @@ -14,6 +15,7 @@ namespace PokemonBDSP{ void add_tests(UnitTestDatabase& database){ + add_tests_AutoStory_Maps(database); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp new file mode 100644 index 0000000000..65a16564cc --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp @@ -0,0 +1,77 @@ +/* BDSP AutoStory - Maps + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Json/JsonValue.h" +#include "PokemonBDSP_AutoStory_Maps.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + +using Pokemon::MapData; +using Pokemon::TilePos; + + +namespace{ + +const std::map>& story_maps_by_name(){ + static const std::map> maps = []{ + std::map> parsed; + for (const auto& [name, json] : bdsp_map_json()){ + MapData map = Pokemon::parse_map(parse_json(json), name + ".json"); + if (map.name != name){ + throw ParseException(std::format("{}.json: name is \"{}\".", name, map.name)); + } + parsed.emplace(name, std::move(map)); + } + return parsed; + }(); + return maps; +} + +} + + +const MapData& story_map(const std::string& name){ + const auto& maps = story_maps_by_name(); + if (auto iter = maps.find(name); iter != maps.end()){ + return iter->second; + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, std::format("No BDSP story map named \"{}\".", name)); +} + +TilePos map_point(const std::string& map, const std::string& point){ + const MapData& data = story_map(map); + if (auto iter = data.points.find(point); iter != data.points.end()){ + return iter->second; + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, std::format("{} has no point \"{}\".", map, point)); +} + +std::vector map_region(const std::string& map, const std::string& region){ + const MapData& data = story_map(map); + std::vector tiles; + while (true){ + const auto iter = data.points.find(std::format("{}.{}", region, tiles.size() + 1)); + if (iter == data.points.end()){ + break; + } + tiles.push_back(iter->second); + } + if (tiles.empty()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, std::format("{} has no region \"{}\".", map, region)); + } + return tiles; +} + + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h new file mode 100644 index 0000000000..a4615a47ee --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h @@ -0,0 +1,54 @@ +/* BDSP AutoStory - Maps + * + * From: https://github.com/PokemonAutomation/ + * + * The maps the story walks, as JSON files in the shared map schema + * (Pokemon_MapData.h): PokemonBDSP/Programs/AutoStory/Maps, written by + * devtools/maps/bdsp_maps.py from the Diamond decompilation and built into + * the program by CMake. Each is named as its file ("Route202"). + * + * A map's named tiles are its points. A region, a list of tiles, is the + * points ".1", ".2", ... in order: the schema has + * single-tile points only. + * + * TODO: move the maps to PokemonAutomation/Packages (Resources/PokemonBDSP/Maps) when ready + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Maps_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Maps_H + +#include +#include +#include +#include "Pokemon/Navigation/Pokemon_Grid.h" +#include "Pokemon/Navigation/Pokemon_MapData.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// (name, JSON text) of each map in PokemonBDSP/Programs/AutoStory/Maps/, +// built into the program by pa_embed_text_files() (cmake/EmbedTextFiles.cmake). +const std::vector>& bdsp_map_json(); + +// The map `name`, read from bdsp_map_json() on first use, with its patches +// applied. Throws InternalProgramError for a name it does not have, and +// ParseException if a map does not parse or is not named as its file. +const Pokemon::MapData& story_map(const std::string& name); + +// The point `point` of the map `map`. Throws InternalProgramError for an +// unknown map or point. +Pokemon::TilePos map_point(const std::string& map, const std::string& point); + +// The tiles of the region `region` of the map `map`, in order. Throws +// InternalProgramError for an unknown map, or a region with no tiles. +std::vector map_region(const std::string& map, const std::string& region); + + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp new file mode 100644 index 0000000000..fa82b08059 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp @@ -0,0 +1,311 @@ +/* BDSP AutoStory Maps Tests + * + * From: https://github.com/PokemonAutomation/ + * + * The built-in story maps (Data/PokemonBDSP_AutoStory_Maps.h): every map + * loads, its points and objects stand where they can, and the lookups + * find what the story code asks for. + * + */ + +#include +#include +#include +#include +#include +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Data/PokemonBDSP_AutoStory_Maps.h" +#include "PokemonBDSP_AutoStory_Maps_Tests.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +namespace{ + +using Pokemon::MapData; +using Pokemon::MapObject; +using Pokemon::TileKind; +using Pokemon::TilePos; + +bool is_ground(TileKind tile){ + return tile == TileKind::OPEN || tile == TileKind::TALL_GRASS; +} + +// The person or item that stands still on `pos`, if any. +const MapObject* standing_object(const MapData& map, const TilePos& pos){ + for (const MapObject& object : map.objects){ + if (!object.wander && object.x == pos.x && object.y == pos.y){ + return &object; + } + } + return nullptr; +} + +// "rocks" for "rocks.3", the point's own name otherwise. +std::string region_of(const std::string& point){ + const size_t dot = point.rfind('.'); + if (dot == std::string::npos || dot + 1 == point.size()){ + return point; + } + const bool numbered = std::all_of(point.begin() + dot + 1, point.end(), [](char c){ return '0' <= c && c <= '9'; }); + return numbered ? point.substr(0, dot) : point; +} + + +// Every built-in map parses through the shared loader, named as its file. +class Test_StoryMapsLoad : public UnitTest{ +public: + Test_StoryMapsLoad() + : UnitTest("PokemonBDSP::AutoStory - Maps: the built-in JSON maps load") + {} + virtual UnitTestResult run(Logger& logger, CancellableScope&) const override{ + if (bdsp_map_json().empty()){ + return std::string("No built-in maps."); + } + for (const auto& [name, json] : bdsp_map_json()){ + try{ + const MapData& map = story_map(name); + if (map.name != name){ + return std::format("{}.json: story_map() returned \"{}\".", name, map.name); + } + logger.log(std::format( + "{}: {} x {}, {} points, {} objects, {} patches", + name, map.tiles.width, map.tiles.height, map.points.size(), map.objects.size(), map.patches.size() + )); + }catch (Exception& e){ + return std::format("{}: {}: {}", name, e.name(), e.message()); + } + } + return true; + } +}; + + +// What a named point must be on. Points not listed are ground the player +// stands on, with nobody standing there. +enum class PointOn{ + DOOR, + SIGN, + PERSON, // someone stands still there + WALL, // a rock until smashed +}; +struct PointCase{ + std::string map; + std::string point; // a point, or a region for all its tiles + PointOn on; +}; + +const std::vector& point_cases(){ + static const std::vector cases{ + {.map = "Route201", .point = "border_sign", .on = PointOn::SIGN}, + {.map = "Route202", .point = "trainer_tristan", .on = PointOn::PERSON}, + {.map = "Route202", .point = "trainer_natalie", .on = PointOn::PERSON}, + {.map = "Route202", .point = "trainer_logan", .on = PointOn::PERSON}, + {.map = "Route204North", .point = "trainer_twin_left", .on = PointOn::PERSON}, + {.map = "Route204North", .point = "trainer_twin_right", .on = PointOn::PERSON}, + {.map = "Route204North", .point = "trainer_taylor", .on = PointOn::PERSON}, + {.map = "Route204North", .point = "trainer_brandon", .on = PointOn::PERSON}, + {.map = "Route204South", .point = "trainer_samantha", .on = PointOn::PERSON}, + {.map = "Route204South", .point = "trainer_sarah", .on = PointOn::PERSON}, + {.map = "Route204South", .point = "trainer_tyler", .on = PointOn::PERSON}, + {.map = "SandgemTown", .point = "lab_door", .on = PointOn::DOOR}, + {.map = "SandgemTown", .point = "pokemon_center_door", .on = PointOn::DOOR}, + {.map = "SandgemTown", .point = "pokemon_center_sign", .on = PointOn::SIGN}, + {.map = "SandgemTown", .point = "poke_mart_door", .on = PointOn::DOOR}, + {.map = "JubilifeCity", .point = "trainers_school_door", .on = PointOn::DOOR}, + {.map = "JubilifeCity", .point = "pokemon_center_door", .on = PointOn::DOOR}, + {.map = "JubilifeCity", .point = "tv_station_door", .on = PointOn::DOOR}, + {.map = "JubilifeCity", .point = "poketch_president", .on = PointOn::PERSON}, + {.map = "JubilifeCity", .point = "clown_mart", .on = PointOn::PERSON}, + {.map = "JubilifeCity", .point = "clown_tv_station", .on = PointOn::PERSON}, + {.map = "JubilifeCity", .point = "clown_poketch_company", .on = PointOn::PERSON}, + {.map = "OreburghCity", .point = "pokemon_center_door", .on = PointOn::DOOR}, + {.map = "OreburghCity", .point = "gym_door", .on = PointOn::DOOR}, + {.map = "OreburghCity", .point = "gate_mouth", .on = PointOn::DOOR}, + {.map = "OreburghGym", .point = "roark", .on = PointOn::PERSON}, + {.map = "OreburghGym", .point = "trainer_first_tier", .on = PointOn::PERSON}, + {.map = "OreburghGym", .point = "trainer_second_tier", .on = PointOn::PERSON}, + {.map = "OreburghMine", .point = "roark", .on = PointOn::PERSON}, + {.map = "RavagedPath", .point = "rocks", .on = PointOn::WALL}, + {.map = "FloaromaTown", .point = "old_woman", .on = PointOn::PERSON}, + {.map = "FloaromaTown", .point = "old_man", .on = PointOn::PERSON}, + {.map = "FloaromaTown", .point = "pokemon_center_door", .on = PointOn::DOOR}, + }; + return cases; +} + +// An error naming what `pos` should be, or empty when it is. +std::string check_point_on(const MapData& map, const TilePos& pos, PointOn on){ + const TileKind tile = map.tiles.at(pos); + const MapObject* object = standing_object(map, pos); + switch (on){ + case PointOn::DOOR: + return tile == TileKind::DOOR ? "" : "not a door"; + case PointOn::SIGN: + return tile == TileKind::SIGN ? "" : "not a sign"; + case PointOn::PERSON: + return is_ground(tile) && object != nullptr && object->kind == Pokemon::ObjectKind::NPC ? "" : "nobody stands still there"; + case PointOn::WALL: + return tile == TileKind::WALL ? "" : "not a wall"; + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Unknown PointOn."); +} + +std::string check_ground(const MapData& map, const TilePos& pos){ + if (!is_ground(map.tiles.at(pos))){ + return std::format("'{}', not ground", Pokemon::glyph_of(map.tiles.at(pos))); + } + return standing_object(map, pos) == nullptr ? "" : "someone stands there"; +} + + +// Every point is ground with nobody on it, unless point_cases() says +// otherwise; every numbered region runs from 1 with no gap; every case +// names a point the map has. +class Test_StoryMapPoints : public UnitTest{ +public: + Test_StoryMapPoints() + : UnitTest("PokemonBDSP::AutoStory - Maps: points stand where they can") + {} + virtual UnitTestResult run(Logger& logger, CancellableScope&) const override{ + for (const PointCase& item : point_cases()){ + const MapData& map = story_map(item.map); + if (!map.points.contains(item.point) && !map.points.contains(item.point + ".1")){ + return std::format("{}: no point or region \"{}\".", item.map, item.point); + } + } + size_t checked = 0; + for (const auto& [name, json] : bdsp_map_json()){ + const MapData& map = story_map(name); + for (const auto& [point, pos] : map.points){ + const std::string region = region_of(point); + // map_region() counts from 1 and stops at a gap. + if (region != point && std::stoul(point.substr(region.size() + 1)) > map_region(name, region).size()){ + return std::format("{}: {} is not reached counting {}.1, {}.2, ...", name, point, region, region); + } + const auto it = std::ranges::find_if(point_cases(), [&](const PointCase& item){ + return item.map == name && item.point == region; + }); + const std::string error = it == point_cases().end() ? check_ground(map, pos) : check_point_on(map, pos, it->on); + if (!error.empty()){ + return std::format("{}: {} {}: {}.", name, point, Pokemon::to_string(pos), error); + } + checked++; + } + } + logger.log(std::format("{} points checked.", checked)); + return true; + } +}; + + +// Everyone stands on ground, and a wanderer's box holds their tile. +class Test_StoryMapObjects : public UnitTest{ +public: + Test_StoryMapObjects() + : UnitTest("PokemonBDSP::AutoStory - Maps: objects stand on open ground") + {} + virtual UnitTestResult run(Logger& logger, CancellableScope&) const override{ + size_t checked = 0; + for (const auto& [name, json] : bdsp_map_json()){ + const MapData& map = story_map(name); + for (const MapObject& object : map.objects){ + const TilePos pos{object.x, object.y}; + if (!is_ground(map.tiles.at(pos))){ + return std::format("{}: {} stands on '{}' at {}.", name, object.name, Pokemon::glyph_of(map.tiles.at(pos)), Pokemon::to_string(pos)); + } + const auto& box = object.wander; + if (box && (pos.x < box->x0 || pos.x > box->x1 || pos.y < box->y0 || pos.y > box->y1)){ + return std::format("{}: {} at {} is outside their wander box.", name, object.name, Pokemon::to_string(pos)); + } + checked++; + } + } + logger.log(std::format("{} objects checked.", checked)); + return true; + } +}; + + +// map_point() and map_region() find what the story asks for, regions in +// order, and throw for what a map does not have. +struct LookupCase{ + std::string name; + std::function()> lookup; + // The tiles, or empty when the lookup must throw InternalProgramError. + std::vector expected; +}; + +class Test_StoryMapLookups : public UnitTest{ +public: + Test_StoryMapLookups() + : UnitTest("PokemonBDSP::AutoStory - Maps: map_point() and map_region()") + {} + virtual UnitTestResult run(Logger&, CancellableScope&) const override{ + const std::vector cases{ + { + .name = "a point", + .lookup = []{ return std::vector{map_point("SandgemTown", "pokemon_center_door")}; }, + .expected = {{17, 10}}, + }, + { + .name = "a region, in order", + .lookup = []{ return map_region("Route204North", "floaroma_waypoints"); }, + .expected = {{20, 27}, {11, 27}, {11, 22}, {19, 22}, {19, 12}, {11, 12}, {11, 5}}, + }, + { + .name = "an unknown map", + .lookup = []{ return std::vector{map_point("Route299", "door")}; }, + .expected = {}, + }, + { + .name = "an unknown point", + .lookup = []{ return std::vector{map_point("SandgemTown", "gym_door")}; }, + .expected = {}, + }, + { + .name = "a point is not a region", + .lookup = []{ return map_region("SandgemTown", "lab_door"); }, + .expected = {}, + }, + }; + for (const LookupCase& item : cases){ + std::vector tiles; + try{ + tiles = item.lookup(); + }catch (InternalProgramError&){ + if (item.expected.empty()){ + continue; + } + return item.name + ": threw InternalProgramError."; + } + if (tiles != item.expected){ + std::string read; + for (const TilePos& pos : tiles){ + read += " " + Pokemon::to_string(pos); + } + return std::format("{}: read{}, expected {} tiles{}.", item.name, read, item.expected.size(), + item.expected.empty() ? " (an InternalProgramError)" : ""); + } + } + return true; + } +}; + +} + + +void add_tests_AutoStory_Maps(UnitTestDatabase& database){ + database.add(); + database.add(); + database.add(); + database.add(); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.h new file mode 100644 index 0000000000..59905aa331 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.h @@ -0,0 +1,23 @@ +/* BDSP AutoStory Maps Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Maps_Tests_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Maps_Tests_H + +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +void add_tests_AutoStory_Maps(UnitTestDatabase& database); + + +} +} +} +#endif diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index ffc3642a0a..47ad878a18 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1620,6 +1620,10 @@ file(GLOB LIBRARY_SOURCES Source/PokemonBDSP/PokemonBDSP_Settings.h Source/PokemonBDSP/PokemonBDSP_Tests.cpp Source/PokemonBDSP/PokemonBDSP_Tests.h + Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp + Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h + Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp + Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.h Source/PokemonBDSP/Programs/Eggs/PokemonBDSP_EggAutonomous.cpp Source/PokemonBDSP/Programs/Eggs/PokemonBDSP_EggAutonomous.h Source/PokemonBDSP/Programs/Eggs/PokemonBDSP_EggAutonomousState.cpp From 796dd5627f0803470dd96d57f2dafd6ad502bd19 Mon Sep 17 00:00:00 2001 From: dayfine Date: Sun, 1 Nov 2026 13:22:46 -0800 Subject: [PATCH 03/13] BDSP maps: the five interiors of 00.01 to 00.03 The player's and Barry's houses (both floors) and Rowan's lab, from pokeplatinum at c6689696d5fb, decomp only and not yet checked on the console: their stairs, doors and the tiles the story talks from, and the doors' warps to Twinleaf's and Sandgem's door points. The map tests check that every warp leads to a point that exists. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../AutoStory/Maps/PlayerHouse1F.json | 29 ++++++++++++ .../AutoStory/Maps/PlayerHouse2F.json | 22 +++++++++ .../Programs/AutoStory/Maps/RivalHouse1F.json | 28 ++++++++++++ .../Programs/AutoStory/Maps/RivalHouse2F.json | 25 +++++++++++ .../Programs/AutoStory/Maps/RowansLab.json | 36 +++++++++++++++ .../Programs/AutoStory/Maps/TwinleafTown.json | 4 +- .../PokemonBDSP_AutoStory_Maps_Tests.cpp | 15 ++++++- devtools/maps/bdsp_maps.py | 45 ++++++++++++++++++- 8 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse2F.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse1F.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse2F.json create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RowansLab.json diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json new file mode 100644 index 0000000000..7929140ede --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json @@ -0,0 +1,29 @@ +{ + "name": "PlayerHouse1F", + "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_186.bin; events_twinleaf_town_player_house_1f.json; decomp only, not checked on the console", "revision": "c6689696d5fb", "date": "2026-11-01"}, + "tiles": [ + "#############", + "#############", + "#############", + "#SS..S####..#", + "#.....####..#", + "#SSS..SS....#", + "#...........#", + "#...........#", + "#..##.......#", + "##.##......##", + "##....D....##", + "#############" + ], + "points": { + "stairs": [10, 3], + "mom": [7, 8], + "door": [6, 10] + }, + "warps": [ + {"x": 6, "y": 10, "to_map": "TwinleafTown", "to_point": "player_house_door"} + ], + "objects": [ + {"name": "mom_0", "kind": "npc", "x": 7, "y": 8, "facing": "north"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse2F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse2F.json new file mode 100644 index 0000000000..a33faa27b9 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse2F.json @@ -0,0 +1,22 @@ +{ + "name": "PlayerHouse2F", + "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_187.bin; events_twinleaf_town_player_house_2f.json; decomp only, not checked on the console", "revision": "c6689696d5fb", "date": "2026-11-01"}, + "tiles": [ + "############", + "############", + "############", + "#######S####", + "#S#SSS...###", + "#........###", + "#..........#", + "#..........#", + "#........#.#", + "##.........#", + "##.......#.#", + "############" + ], + "points": { + "stairs": [8, 4], + "start": [4, 6] + } +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse1F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse1F.json new file mode 100644 index 0000000000..7d1b616c29 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse1F.json @@ -0,0 +1,28 @@ +{ + "name": "RivalHouse1F", + "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_184.bin; events_twinleaf_town_rival_house_1f.json; decomp only, not checked on the console", "revision": "c6689696d5fb", "date": "2026-11-01"}, + "tiles": [ + "#############", + "#############", + "#############", + "#..#####..###", + "#..####.....#", + "#....##..####", + "#...........#", + "#...........#", + "#.......##..#", + "##......##.##", + "##....D....##", + "#############" + ], + "points": { + "door": [6, 10], + "stairs": [2, 3] + }, + "warps": [ + {"x": 6, "y": 10, "to_map": "TwinleafTown", "to_point": "rival_house_door"} + ], + "objects": [ + {"name": "pokefan_f_0", "kind": "npc", "x": 4, "y": 6} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse2F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse2F.json new file mode 100644 index 0000000000..d0b03b36e6 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse2F.json @@ -0,0 +1,25 @@ +{ + "name": "RivalHouse2F", + "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_185.bin; events_twinleaf_town_rival_house_2f.json; decomp only, not checked on the console", "revision": "c6689696d5fb", "date": "2026-11-01"}, + "tiles": [ + "############", + "############", + "############", + "############", + "###...S##S##", + "###........#", + "#..........#", + "#..........#", + "#.#........#", + "#.........##", + "#.#.......##", + "############" + ], + "points": { + "stairs": [3, 4], + "barry_talk": [8, 5] + }, + "objects": [ + {"name": "barry", "kind": "npc", "x": 9, "y": 5, "facing": "north"} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RowansLab.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RowansLab.json new file mode 100644 index 0000000000..8faef4e607 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RowansLab.json @@ -0,0 +1,36 @@ +{ + "name": "RowansLab", + "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_222.bin; events_sandgem_town_pokemon_research_lab.json; decomp only, not checked on the console", "revision": "c6689696d5fb", "date": "2026-11-01"}, + "tiles": [ + "######################", + "######################", + "###SSSS####S####.###S#", + "#........#...##......#", + "#.###................#", + "#...................##", + "#.............#....###", + "###........###########", + "###........###########", + "###........###########", + "#............#########", + "##..........##########", + "##..........##########", + "###........###########", + "###........###########", + "###....D...###########", + "######################", + "######################" + ], + "points": { + "door": [7, 15], + "rowan_talk": [7, 6] + }, + "warps": [ + {"x": 7, "y": 15, "to_map": "SandgemTown", "to_point": "lab_door"} + ], + "objects": [ + {"name": "rowan", "kind": "npc", "x": 7, "y": 5, "facing": "south"}, + {"name": "scientist_m_1", "kind": "npc", "x": 3, "y": 12, "wander": {"x0": 1, "y0": 12, "x1": 5, "y1": 12}}, + {"name": "scientist_f_2", "kind": "npc", "x": 10, "y": 12} + ] +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json index 3431befeae..ff22e6feba 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json @@ -38,7 +38,9 @@ "points": { "house_doorstep": [20, 22], "barry_doorstep": [9, 12], - "twinleaf_north_exit": [15, 0] + "twinleaf_north_exit": [15, 0], + "player_house_door": [20, 21], + "rival_house_door": [9, 11] }, "objects": [ {"name": "pokemon_breeder_f_0", "kind": "npc", "x": 23, "y": 14, "wander": {"x0": 22, "y0": 14, "x1": 24, "y1": 14}}, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp index fa82b08059..0c62adb480 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp @@ -133,6 +133,12 @@ const std::vector& point_cases(){ {.map = "FloaromaTown", .point = "old_woman", .on = PointOn::PERSON}, {.map = "FloaromaTown", .point = "old_man", .on = PointOn::PERSON}, {.map = "FloaromaTown", .point = "pokemon_center_door", .on = PointOn::DOOR}, + {.map = "TwinleafTown", .point = "player_house_door", .on = PointOn::DOOR}, + {.map = "TwinleafTown", .point = "rival_house_door", .on = PointOn::DOOR}, + {.map = "PlayerHouse1F", .point = "mom", .on = PointOn::PERSON}, + {.map = "PlayerHouse1F", .point = "door", .on = PointOn::DOOR}, + {.map = "RivalHouse1F", .point = "door", .on = PointOn::DOOR}, + {.map = "RowansLab", .point = "door", .on = PointOn::DOOR}, }; return cases; } @@ -164,7 +170,7 @@ std::string check_ground(const MapData& map, const TilePos& pos){ // Every point is ground with nobody on it, unless point_cases() says // otherwise; every numbered region runs from 1 with no gap; every case -// names a point the map has. +// names a point the map has; every warp leads to a point that exists. class Test_StoryMapPoints : public UnitTest{ public: Test_StoryMapPoints() @@ -195,6 +201,13 @@ class Test_StoryMapPoints : public UnitTest{ } checked++; } + for (const Pokemon::MapWarp& warp : map.warps){ + try{ + map_point(warp.to_map, warp.to_point); + }catch (InternalProgramError& e){ + return std::format("{}: the warp at {}: {}", name, Pokemon::to_string(TilePos{warp.x, warp.y}), e.message()); + } + } } logger.log(std::format("{} points checked.", checked)); return true; diff --git a/devtools/maps/bdsp_maps.py b/devtools/maps/bdsp_maps.py index 0bcf886057..7c4dbf1e72 100644 --- a/devtools/maps/bdsp_maps.py +++ b/devtools/maps/bdsp_maps.py @@ -24,6 +24,7 @@ adds someone the console found that Diamond doesn't have. - WARP_DOORS: warp tiles written 'D' so that no plan steps onto them; the other warp tiles are written '.', since the story arrives on them. +- WARPS: doors that lead to a point of another map in OUT. - PATCHES: tiles measured on the console that differ from Diamond's, with the run that measured them. The map's tiles stay the decompilation's; the loader applies the patches. @@ -63,6 +64,9 @@ FACING = {'LOOK_NORTH': 'north', 'LOOK_SOUTH': 'south', 'LOOK_WEST': 'west', 'LOOK_EAST': 'east'} +# Decoded from pokeplatinum at this revision, not yet checked on the console. +INTERIOR = {'repo': decomp.Platinum.REPO, 'revision': 'c6689696d5fb', 'read': '2026-11-01', 'unverified': True} + # Each map: its header constant, the world rectangle (x0, z0, width, # height), and the decompilation when not Diamond. Local (x, y) = world # (x - x0, z - z0). A map that joins another on the world grid keeps its @@ -137,6 +141,15 @@ # Cell (1, 1) of the lake's matrix 101: the shore where the starter # scene plays. Only this cell is reached on foot. 'LakeVerity': {'header': 'LAKE_VERITY', 'rect': (32, 32, 32, 32)}, + # 00.01 to 00.03's interiors, from pokeplatinum (c6689696d5fb), each + # the only cell of its own matrix: decomp only, not yet checked on the + # console. Mom's warning on the way out is a coord event on the + # player's door tile (6, 10), which the schema has no place for. + 'PlayerHouse1F': {'header': 'TWINLEAF_TOWN_PLAYER_HOUSE_1F', 'rect': (0, 0, 13, 12), **INTERIOR}, + 'PlayerHouse2F': {'header': 'TWINLEAF_TOWN_PLAYER_HOUSE_2F', 'rect': (0, 0, 12, 12), **INTERIOR}, + 'RivalHouse1F': {'header': 'TWINLEAF_TOWN_RIVAL_HOUSE_1F', 'rect': (0, 0, 13, 12), **INTERIOR}, + 'RivalHouse2F': {'header': 'TWINLEAF_TOWN_RIVAL_HOUSE_2F', 'rect': (0, 0, 12, 12), **INTERIOR}, + 'RowansLab': {'header': 'SANDGEM_TOWN_POKEMON_RESEARCH_LAB', 'rect': (0, 0, 22, 18), **INTERIOR}, } @@ -290,6 +303,8 @@ def box(x0, y0, x1, y1): # Barry's scene fires here, below his door. 'barry_doorstep': (9, 12), 'twinleaf_north_exit': (15, 0), + 'player_house_door': (20, 21), + 'rival_house_door': (9, 11), }, 'VerityLakefront': { 'lakefront_arrival': (31, 23), @@ -297,6 +312,14 @@ def box(x0, y0, x1, y1): # The lake's scene fires here; it is assumed to leave us here too. 'lake_entrance': (17, 12), }, + 'PlayerHouse1F': {'stairs': (10, 3), 'mom': (7, 8), 'door': (6, 10)}, + # A new game starts on (4, 6); the TV is the signs at (3, 4) and (4, 4). + 'PlayerHouse2F': {'stairs': (8, 4), 'start': (4, 6)}, + 'RivalHouse1F': {'door': (6, 10), 'stairs': (2, 3)}, + # Barry stands at (9, 5) facing his PC; he is talked to from the west. + 'RivalHouse2F': {'stairs': (3, 4), 'barry_talk': (8, 5)}, + # Rowan is talked to from below, facing north. + 'RowansLab': {'door': (7, 15), 'rowan_talk': (7, 6)}, } @@ -350,6 +373,14 @@ def box(x0, y0, x1, y1): # Hidden once his scene here is over; the story walks to him. 0: {'show': '01.03', 'name': 'roark'}, }, + 'RivalHouse2F': { + # Barry in his room, until he leaves for the lake. + 0: {'show': '00.01', 'name': 'barry'}, + }, + 'RowansLab': { + # Rowan at his desk when the story walks in. + 0: {'show': '00.03', 'name': 'rowan'}, + }, 'LakeVerity': { # The starter scene's actors, where the walk in ends. 0: {'show': 'the starter scene', 'name': 'rowan'}, @@ -370,6 +401,14 @@ def box(x0, y0, x1, y1): } +# Stepping onto (x, y) puts the player at a point of another map. +WARPS = { + 'PlayerHouse1F': [(6, 10, 'TwinleafTown', 'player_house_door')], + 'RivalHouse1F': [(6, 10, 'TwinleafTown', 'rival_house_door')], + 'RowansLab': [(7, 15, 'SandgemTown', 'lab_door')], +} + + PATCHES = { 'Route202': ( # BDSP has trees where Diamond's catch patch has its two bottom rows: @@ -477,10 +516,11 @@ def decode_map(name): row[x] = 'D' if (x, y) in doors else '.' lands = sorted({world.land_id(x, z) for z in range(z0, z0 + height, decomp.CELL) for x in range(x0, x0 + width, decomp.CELL)} - {None}) + unverified = '; decomp only, not checked on the console' if spec.get('unverified') else '' source = { 'decomp_file': f'{repo}: ' + ', '.join(game.land_path(land) for land in lands) - + f'; {game.events_name(header.events_id)}', - 'revision': 'default branch', + + f'; {game.events_name(header.events_id)}{unverified}', + 'revision': spec.get('revision', 'default branch'), 'date': spec.get('read', READ), } return [''.join(row) for row in rows], objects, source @@ -515,6 +555,7 @@ def section(key, items): lines.append(' ' + ('}' if key == 'points' else ']')) section('points', [f'{json.dumps(p)}: [{x}, {y}]' for p, (x, y) in points_of(name).items()]) + section('warps', [json.dumps({'x': x, 'y': y, 'to_map': m, 'to_point': p}) for x, y, m, p in WARPS.get(name, [])]) section('objects', [json.dumps(o) for o in objects]) section('patches', [json.dumps({'x': x, 'y': y, 'glyph': g, 'measured_by': m}) for x, y, g, m in PATCHES.get(name, [])]) From e7ead80e03ff48fdebbd658d2db087787fa19a19 Mon Sep 17 00:00:00 2001 From: dayfine Date: Sun, 1 Nov 2026 13:49:42 -0800 Subject: [PATCH 04/13] BDSP maps: Route 202's training and exit areas as regions catch_save_area, after_training_area, catch_grass and jubilife_exit were computed by the story code from tile loops; they are now regions of Route202, and a map test checks each against the loop it replaces. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../Programs/AutoStory/Maps/Route202.json | 75 ++++++++++++++++++- .../PokemonBDSP_AutoStory_Maps_Tests.cpp | 69 +++++++++++++++++ devtools/maps/bdsp_maps.py | 31 ++++---- 3 files changed, 160 insertions(+), 15 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json index 515e652b45..e52d694739 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json @@ -41,7 +41,80 @@ "trainer_tristan": [7, 13], "trainer_natalie": [22, 18], "trainer_logan": [26, 4], - "exit_jubilife": [15, 0] + "exit_jubilife": [15, 0], + "jubilife_exit.1": [11, 0], + "jubilife_exit.2": [12, 0], + "jubilife_exit.3": [13, 0], + "jubilife_exit.4": [14, 0], + "jubilife_exit.5": [15, 0], + "jubilife_exit.6": [16, 0], + "jubilife_exit.7": [17, 0], + "jubilife_exit.8": [18, 0], + "jubilife_exit.9": [11, 1], + "jubilife_exit.10": [12, 1], + "jubilife_exit.11": [13, 1], + "jubilife_exit.12": [14, 1], + "jubilife_exit.13": [15, 1], + "jubilife_exit.14": [16, 1], + "jubilife_exit.15": [17, 1], + "jubilife_exit.16": [18, 1], + "jubilife_exit.17": [11, 2], + "jubilife_exit.18": [12, 2], + "jubilife_exit.19": [13, 2], + "jubilife_exit.20": [14, 2], + "jubilife_exit.21": [15, 2], + "jubilife_exit.22": [16, 2], + "jubilife_exit.23": [17, 2], + "jubilife_exit.24": [18, 2], + "jubilife_exit.25": [11, 3], + "jubilife_exit.26": [12, 3], + "jubilife_exit.27": [13, 3], + "jubilife_exit.28": [14, 3], + "jubilife_exit.29": [15, 3], + "jubilife_exit.30": [16, 3], + "jubilife_exit.31": [17, 3], + "jubilife_exit.32": [18, 3], + "catch_save_area.1": [15, 25], + "catch_save_area.2": [16, 25], + "catch_save_area.3": [17, 25], + "catch_save_area.4": [18, 25], + "catch_save_area.5": [19, 25], + "catch_save_area.6": [20, 25], + "catch_save_area.7": [21, 25], + "catch_save_area.8": [15, 26], + "catch_save_area.9": [16, 26], + "catch_save_area.10": [17, 26], + "catch_save_area.11": [18, 26], + "catch_save_area.12": [19, 26], + "catch_save_area.13": [20, 26], + "catch_save_area.14": [21, 26], + "catch_save_area.15": [15, 27], + "catch_save_area.16": [16, 27], + "catch_save_area.17": [17, 27], + "catch_save_area.18": [18, 27], + "catch_save_area.19": [19, 27], + "catch_save_area.20": [20, 27], + "catch_save_area.21": [21, 27], + "catch_save_area.22": [21, 28], + "catch_save_area.23": [21, 29], + "after_training_area.1": [18, 25], + "after_training_area.2": [19, 25], + "after_training_area.3": [20, 25], + "after_training_area.4": [18, 26], + "after_training_area.5": [19, 26], + "after_training_area.6": [20, 26], + "after_training_area.7": [18, 27], + "after_training_area.8": [19, 27], + "after_training_area.9": [20, 27], + "catch_grass.1": [18, 25], + "catch_grass.2": [19, 25], + "catch_grass.3": [20, 25], + "catch_grass.4": [18, 26], + "catch_grass.5": [19, 26], + "catch_grass.6": [20, 26], + "catch_grass.7": [18, 27], + "catch_grass.8": [19, 27], + "catch_grass.9": [20, 27] }, "objects": [ {"name": "youngster_0", "kind": "npc", "x": 7, "y": 13, "facing": "south"}, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp index 0c62adb480..b242068168 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp @@ -243,6 +243,74 @@ class Test_StoryMapObjects : public UnitTest{ }; +// Route 202's regions are the tiles the story code computed before they +// were data: each case is the old helper's loop, over the map's tiles. +struct RegionCase{ + std::string region; + std::function(const MapData& map)> old_helper; +}; + +// The tiles of a box, both corners inclusive, row by row, that someone +// can stand on: ground with nobody standing still there. +std::vector standable_in_box(const MapData& map, const TilePos& low, const TilePos& high){ + std::vector tiles; + for (int y = low.y; y <= high.y; y++){ + for (int x = low.x; x <= high.x; x++){ + if (map.tiles.contains({x, y}) && is_ground(map.tiles.at({x, y})) && standing_object(map, {x, y}) == nullptr){ + tiles.push_back({x, y}); + } + } + } + return tiles; +} + +std::vector after_training_area(const MapData& map){ + const TilePos catch_spot = map_point(map.name, "catch_spot"); + return standable_in_box(map, {catch_spot.x - 1, catch_spot.y - 2}, {catch_spot.x + 1, catch_spot.y + 2}); +} + +class Test_Route202Regions : public UnitTest{ +public: + Test_Route202Regions() + : UnitTest("PokemonBDSP::AutoStory - Maps: Route 202's regions are what the story computed") + {} + virtual UnitTestResult run(Logger& logger, CancellableScope&) const override{ + const std::vector cases{ + { + .region = "catch_save_area", // story_catch_save_area() + .old_helper = [](const MapData& map){ return standable_in_box(map, {15, 25}, {21, 29}); }, + }, + { + .region = "after_training_area", // story_after_training_area() + .old_helper = after_training_area, + }, + { + .region = "catch_grass", // the story table's catch_grass() + .old_helper = [](const MapData& map){ + std::vector grass = after_training_area(map); + std::erase_if(grass, [&](const TilePos& pos){ return map.tiles.at(pos) != TileKind::TALL_GRASS; }); + return grass; + }, + }, + { + .region = "jubilife_exit", // the story table's jubilife_exit() + .old_helper = [](const MapData& map){ return standable_in_box(map, {0, 0}, {map.tiles.width - 1, 3}); }, + }, + }; + const MapData& map = story_map("Route202"); + for (const RegionCase& item : cases){ + const std::vector region = map_region("Route202", item.region); + const std::vector expected = item.old_helper(map); + if (region != expected){ + return std::format("Route202 {}: {} tiles, the old helper computes {}.", item.region, region.size(), expected.size()); + } + logger.log(std::format("Route202 {}: {} tiles.", item.region, region.size())); + } + return true; + } +}; + + // map_point() and map_region() find what the story asks for, regions in // order, and throw for what a map does not have. struct LookupCase{ @@ -316,6 +384,7 @@ void add_tests_AutoStory_Maps(UnitTestDatabase& database){ database.add(); database.add(); database.add(); + database.add(); } diff --git a/devtools/maps/bdsp_maps.py b/devtools/maps/bdsp_maps.py index 7c4dbf1e72..01e01b1ebc 100644 --- a/devtools/maps/bdsp_maps.py +++ b/devtools/maps/bdsp_maps.py @@ -35,14 +35,11 @@ facing it; a sign script on a wall too). Items on the ground are objects of kind "item"; rocks, cut trees, berry soil and steam vents are '#'. -Rules carried over from the maps' history: terrain from the decomp, people -and BDSP's edits from the console; a route is coded only over tiles a human -or a probe has walked; a map change re-runs every checkpoint that walks it. -Bulbapedia's DP map images have 12 px rows for 16 px columns (3/4 of a tile -per row) and paint cliffs taller than their tiles, so they are for drawing -routes over (`python3 -m devtools.maps overlay`), not for terrain. Scenes' -trigger rectangles (the events files' coord events) have no place in the -schema; Segment00Lines lists those it needs in C++. +Rules: terrain from the decomp, people and BDSP's edits from the console; +a route is coded only over tiles a human or a probe has walked; a map change +re-runs every checkpoint that walks it. Bulbapedia's DP images (12 px rows +for 16 px columns, cliffs painted tall) are for drawing routes over, not for +terrain. Scenes' coord events have no place in the schema (Segment00Lines). """ import argparse import json @@ -96,10 +93,8 @@ # (14, 17); column 13 is grass to the cliff (13, 2); Sarah walked up and # fought on row 21 (r233a, r234a). 01.08 passed once (r235a). 'Route204South': {'header': 'ROUTE_204_SOUTH', 'rect': (160, 704, 32, 32), 'read': '2026-11-01'}, - # Cell (5, 26). Console (2026-10-29/30): all three doors, five signs, - # the doors' front row east to the trees and north from it, the Lab's - # sign read facing west, the walk south from Route 202 stopping above - # the Mart at (29, 5). + # Cell (5, 26). Console (2026-10-29/30): the three doors, five signs, + # the doors' front row, the Lab's sign, the stop above the Mart (29, 5). 'SandgemTown': {'header': 'SANDGEM', 'rect': (160, 832, 34, 32)}, # Cells (4..5, 23..24). Console (2026-10-30/31): the lamps (43, 53) and # (49, 53), north from (52, 53) to the Center's door, the School's door @@ -112,8 +107,7 @@ 'OreburghCity': {'header': 'OREBURGH', 'rect': (256, 736, 64, 64)}, # The only cell of matrix 003; Platinum identical. 'OreburghGate': {'header': 'OREBURGH_GATE_1F', 'rect': (0, 0, 32, 32)}, - # The only cell of matrix 113. 01.04 walks it as a record: north up - # column 5 until a trainer stops us. + # The only cell of matrix 113. 01.04 walks north up column 5. 'OreburghGym': {'header': 'OREBURGH_GYM', 'rect': (0, 0, 32, 32)}, # B2F, the coal room: matrix 006. Console (2026-10-28, r99): the # corridor's rocks zigzag a row at a time, the room's second row passes @@ -188,6 +182,15 @@ def box(x0, y0, x1, y1): 'trainer_logan': (26, 4), # The middle of the gap to Jubilife, below its 3-wide neck. 'exit_jubilife': (15, 0), + # The open rows at the top: north from any of them is Jubilife. + 'jubilife_exit': box(11, 0, 18, 3), + # Where 00.04b's save after the catch may stand: the patch and the + # lawn column east of it, down to the pocket where r252 saved. + 'catch_save_area': box(15, 25, 21, 27) + [(21, 28), (21, 29)], + # Where training may leave us (the catch spot's column and one either + # side, between the tree rows), and its grass: all of it. + 'after_training_area': box(18, 25, 20, 27), + 'catch_grass': box(18, 25, 20, 27), }, 'Route203': { # The warp tile the Gate puts us on, and its open neighbours. From 45a2eaf736788f2b4a910cfa53bb736520f6e39c Mon Sep 17 00:00:00 2001 From: dayfine Date: Sun, 1 Nov 2026 14:20:08 -0800 Subject: [PATCH 05/13] BDSP maps: the loader takes names as string_view GCC -Werror=dangling-reference flagged story_map("Route202") bound to a reference: with a const std::string& parameter, the temporary string looks like what the result refers to. string_view arguments are not flagged; callers are unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp | 8 ++++---- .../Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h | 9 ++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp index 65a16564cc..065b53300a 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp @@ -38,7 +38,7 @@ const std::map>& story_maps_by_name(){ } -const MapData& story_map(const std::string& name){ +const MapData& story_map(std::string_view name){ const auto& maps = story_maps_by_name(); if (auto iter = maps.find(name); iter != maps.end()){ return iter->second; @@ -46,15 +46,15 @@ const MapData& story_map(const std::string& name){ throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, std::format("No BDSP story map named \"{}\".", name)); } -TilePos map_point(const std::string& map, const std::string& point){ +TilePos map_point(std::string_view map, std::string_view point){ const MapData& data = story_map(map); - if (auto iter = data.points.find(point); iter != data.points.end()){ + if (auto iter = data.points.find(std::string(point)); iter != data.points.end()){ return iter->second; } throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, std::format("{} has no point \"{}\".", map, point)); } -std::vector map_region(const std::string& map, const std::string& region){ +std::vector map_region(std::string_view map, std::string_view region){ const MapData& data = story_map(map); std::vector tiles; while (true){ diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h index a4615a47ee..e6e3a96bef 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h @@ -19,6 +19,7 @@ #define PokemonAutomation_PokemonBDSP_AutoStory_Maps_H #include +#include #include #include #include "Pokemon/Navigation/Pokemon_Grid.h" @@ -36,15 +37,17 @@ const std::vector>& bdsp_map_json(); // The map `name`, read from bdsp_map_json() on first use, with its patches // applied. Throws InternalProgramError for a name it does not have, and // ParseException if a map does not parse or is not named as its file. -const Pokemon::MapData& story_map(const std::string& name); +// The name is a string_view so that a temporary string argument is not +// mistaken (by GCC's -Wdangling-reference) for what the result refers to. +const Pokemon::MapData& story_map(std::string_view name); // The point `point` of the map `map`. Throws InternalProgramError for an // unknown map or point. -Pokemon::TilePos map_point(const std::string& map, const std::string& point); +Pokemon::TilePos map_point(std::string_view map, std::string_view point); // The tiles of the region `region` of the map `map`, in order. Throws // InternalProgramError for an unknown map, or a region with no tiles. -std::vector map_region(const std::string& map, const std::string& region); +std::vector map_region(std::string_view map, std::string_view region); From 1c0ffea21cb2eaceed65a6f096154ea152cbf6ad Mon Sep 17 00:00:00 2001 From: dayfine Date: Sun, 1 Nov 2026 20:18:29 -0800 Subject: [PATCH 06/13] BDSP maps: the house's 1F row 8 is open at x 4 (console) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json index 7929140ede..2a7a89546d 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json @@ -1,6 +1,6 @@ { "name": "PlayerHouse1F", - "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_186.bin; events_twinleaf_town_player_house_1f.json; decomp only, not checked on the console", "revision": "c6689696d5fb", "date": "2026-11-01"}, + "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_186.bin; events_twinleaf_town_player_house_1f.json; decomp, but (4, 8) is open on the console: west along row 8 stops at x 4 (r32, 2026-11-01)", "revision": "c6689696d5fb", "date": "2026-11-01"}, "tiles": [ "#############", "#############", @@ -10,7 +10,7 @@ "#SSS..SS....#", "#...........#", "#...........#", - "#..##.......#", + "#..#........#", "##.##......##", "##....D....##", "#############" From 2a21276ea4eea857b9b17c3c77bea8016db25a55 Mon Sep 17 00:00:00 2001 From: dayfine Date: Sun, 1 Nov 2026 21:29:34 -0800 Subject: [PATCH 07/13] Revert "BDSP maps: the lab's door mat in Sandgem, a row below its door" v3's Mart lady walk went west along that row past the mat on console; r42's entry is likely a story trigger or the run, not the map. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json index 4f7a4e61b4..61111248ec 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json @@ -1,6 +1,6 @@ { "name": "SandgemTown", - "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0007.bin, files/fielddata/land_data/land_data_release/narc_0173.bin; zone_event narc_0396", "revision": "default branch", "date": "2026-10-31"}, + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0007.bin, files/fielddata/land_data/land_data_release/narc_0173.bin; zone_event narc_0396; console: the lab's door mat (8, 11) takes us in from the side (r41, r42)", "revision": "default branch", "date": "2026-10-31"}, "tiles": [ "########################......####", "########################......####", @@ -13,7 +13,7 @@ "####################......########", "####################......########", "########D##....##D##S....S#D######", - ".....S........................####", + ".....S..D.....................####", "..............................####", "..............................####", "..............................####", From ed3b0b00639593c40571f042253f903aaf8b73f2 Mon Sep 17 00:00:00 2001 From: dayfine Date: Sun, 1 Nov 2026 21:31:38 -0800 Subject: [PATCH 08/13] BDSP maps: Sandgem's row 11 has no door at the lab's mat after all The commit before this one, misnamed "Revert ...", put a door at (8, 11); v3's Mart lady walk went west past that mat on console, so it goes. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json index 61111248ec..4f7a4e61b4 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json @@ -1,6 +1,6 @@ { "name": "SandgemTown", - "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0007.bin, files/fielddata/land_data/land_data_release/narc_0173.bin; zone_event narc_0396; console: the lab's door mat (8, 11) takes us in from the side (r41, r42)", "revision": "default branch", "date": "2026-10-31"}, + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0007.bin, files/fielddata/land_data/land_data_release/narc_0173.bin; zone_event narc_0396", "revision": "default branch", "date": "2026-10-31"}, "tiles": [ "########################......####", "########################......####", @@ -13,7 +13,7 @@ "####################......########", "####################......########", "########D##....##D##S....S#D######", - ".....S..D.....................####", + ".....S........................####", "..............................####", "..............................####", "..............................####", From cc0bd03f76c7f45abd95c496706c65dfa15c917c Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 2 Nov 2026 07:16:21 -0800 Subject: [PATCH 09/13] BDSP maps: read a warp's destination from MapData's optional `to` nav/shared made a warp's destination optional (left out for a map not loaded). Every BDSP warp still has to lead to a point that exists. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp index b242068168..df0a559e8e 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp @@ -202,8 +202,11 @@ class Test_StoryMapPoints : public UnitTest{ checked++; } for (const Pokemon::MapWarp& warp : map.warps){ + if (!warp.to){ + return std::format("{}: the warp at {} leads nowhere.", name, Pokemon::to_string(TilePos{warp.x, warp.y})); + } try{ - map_point(warp.to_map, warp.to_point); + map_point(warp.to->map, warp.to->point); }catch (InternalProgramError& e){ return std::format("{}: the warp at {}: {}", name, Pokemon::to_string(TilePos{warp.x, warp.y}), e.message()); } From cc669ea7077ccfe12df1eae805d54be8d20fc250 Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 2 Nov 2026 06:46:29 -0800 Subject: [PATCH 10/13] BDSP maps: list every cell a map's rectangle touches in its source The cell list stepped 32 tiles from the rectangle's corner, so a rectangle not starting on a cell edge missed its last cell: Route203.json named narc_0016 and narc_0019 but not narc_0020. Its tiles are unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E (cherry picked from commit 6517171a9d87b0d6db1d2436932cc73e364c81b2) --- .../PokemonBDSP/Programs/AutoStory/Maps/Route203.json | 2 +- devtools/maps/bdsp_maps.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json index 88f443c610..2e3c409907 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json @@ -1,6 +1,6 @@ { "name": "Route203", - "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0016.bin, files/fielddata/land_data/land_data_release/narc_0019.bin; zone_event narc_0329", "revision": "default branch", "date": "2026-10-31"}, + "source": {"decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0016.bin, files/fielddata/land_data/land_data_release/narc_0019.bin, files/fielddata/land_data/land_data_release/narc_0020.bin; zone_event narc_0329", "revision": "default branch", "date": "2026-10-31"}, "tiles": [ "#############################################################", "#############################################################", diff --git a/devtools/maps/bdsp_maps.py b/devtools/maps/bdsp_maps.py index 01e01b1ebc..4e027f0a28 100644 --- a/devtools/maps/bdsp_maps.py +++ b/devtools/maps/bdsp_maps.py @@ -517,8 +517,10 @@ def decode_map(name): for x, tile in enumerate(row): if tile == 'W': row[x] = 'D' if (x, y) in doors else '.' - lands = sorted({world.land_id(x, z) for z in range(z0, z0 + height, decomp.CELL) - for x in range(x0, x0 + width, decomp.CELL)} - {None}) + # Every cell the rectangle touches, also when it does not start on a cell's edge. + C = decomp.CELL + lands = sorted({world.land_id(C * col, C * row) for row in range(z0 // C, (z0 + height - 1) // C + 1) + for col in range(x0 // C, (x0 + width - 1) // C + 1)} - {None}) unverified = '; decomp only, not checked on the console' if spec.get('unverified') else '' source = { 'decomp_file': f'{repo}: ' + ', '.join(game.land_path(land) for land in lands) From 5d6015237f53a195311d18f1c8a8f1c536a416d4 Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 2 Nov 2026 07:11:13 -0800 Subject: [PATCH 11/13] BDSP maps: PlayerHouse1F and Route201's console fixes as generator patches Both files had their console measurements (r32's open tile in the house, r16's trees and sign on Route 201) edited into the tiles by hand, so bdsp_maps.py --check failed for them. The generator's PATCHES table now holds them; the tiles go back to the decomp's and the loader applies the patches, so the walked grid is unchanged. --check passes for all 21 maps. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E (cherry picked from commit 1f14bca446c165429b75a809098c0a5fefc81589) --- .../Programs/AutoStory/Maps/PlayerHouse1F.json | 7 +++++-- .../Programs/AutoStory/Maps/Route201.json | 9 +++++++++ devtools/maps/bdsp_maps.py | 12 ++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json index 2a7a89546d..a97ce8bbc3 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json @@ -1,6 +1,6 @@ { "name": "PlayerHouse1F", - "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_186.bin; events_twinleaf_town_player_house_1f.json; decomp, but (4, 8) is open on the console: west along row 8 stops at x 4 (r32, 2026-11-01)", "revision": "c6689696d5fb", "date": "2026-11-01"}, + "source": {"decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_186.bin; events_twinleaf_town_player_house_1f.json; decomp only, not checked on the console", "revision": "c6689696d5fb", "date": "2026-11-01"}, "tiles": [ "#############", "#############", @@ -10,7 +10,7 @@ "#SSS..SS....#", "#...........#", "#...........#", - "#..#........#", + "#..##.......#", "##.##......##", "##....D....##", "#############" @@ -25,5 +25,8 @@ ], "objects": [ {"name": "mom_0", "kind": "npc", "x": 7, "y": 8, "facing": "north"} + ], + "patches": [ + {"x": 4, "y": 8, "glyph": ".", "measured_by": "r32, 2026-11-01"} ] } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json index 88f0b99f7e..91ba7195f7 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json @@ -42,5 +42,14 @@ {"name": "pokemon_breeder_m_0", "kind": "npc", "x": 27, "y": 15, "wander": {"x0": 26, "y0": 14, "x1": 28, "y1": 16}}, {"name": "school_kid_m_9", "kind": "npc", "x": 57, "y": 8, "facing": "west"}, {"name": "lass_11", "kind": "npc", "x": 46, "y": 16, "wander": {"x0": 44, "y0": 16, "x1": 48, "y1": 16}} + ], + "patches": [ + {"x": 8, "y": 18, "glyph": "#", "measured_by": "r16, 2026-10-31"}, + {"x": 9, "y": 18, "glyph": "#", "measured_by": "r16, 2026-10-31"}, + {"x": 10, "y": 18, "glyph": "#", "measured_by": "r16, 2026-10-31"}, + {"x": 11, "y": 18, "glyph": "#", "measured_by": "r16, 2026-10-31"}, + {"x": 12, "y": 18, "glyph": "#", "measured_by": "r16, 2026-10-31"}, + {"x": 13, "y": 18, "glyph": "S", "measured_by": "r16, 2026-10-31"}, + {"x": 13, "y": 17, "glyph": ".", "measured_by": "r16, 2026-10-31"} ] } diff --git a/devtools/maps/bdsp_maps.py b/devtools/maps/bdsp_maps.py index 4e027f0a28..4a12ed05b0 100644 --- a/devtools/maps/bdsp_maps.py +++ b/devtools/maps/bdsp_maps.py @@ -421,6 +421,18 @@ def box(x0, y0, x1, y1): # was blocked at once. + [(21, 30, '#', 'r254, 2026-11-01')] ), + 'PlayerHouse1F': [ + # Open on the console, a wall in the data: west along row 8 stops + # at x 4 (r32). + (4, 8, '.', 'r32, 2026-11-01'), + ], + 'Route201': ( + # Row 18 west of the exit path is trees on the console, open in the + # decomp, and the 201 sign stands at (13, 18), not (13, 17) (r16, + # walked by hand). + [(x, 18, '#', 'r16, 2026-10-31') for x in range(8, 13)] + + [(13, 18, 'S', 'r16, 2026-10-31'), (13, 17, '.', 'r16, 2026-10-31')] + ), 'Route204North': [ # Grass in the data, blocked on the console twice. (12, 6, '#', 'r248a, 2026-10-31'), From 45144bf3521542b6cefef93c14769feee336e7ae Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 2 Nov 2026 10:19:36 -0800 Subject: [PATCH 12/13] BDSP maps: write each map's coord events from the decomp Each map JSON now lists its events file's coord events (the scene triggers) under "coord_events": local box, story variable and value, and a source naming the file, index, world tile and script. Diamond's events files carry them after the warps; their variable numbers are named from pokeplatinum's generated/vars_flags.txt, which agrees with Platinum's own events on every trigger both games have. The five interiors read pokeplatinum's JSON. COORD_EVENTS names the triggers the story uses and adds Mom's call (6, 9) in the player's house, found on the console (r45, r46), so hand-measured triggers survive regeneration. The shared map loader ignores the key. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../Programs/AutoStory/Maps/FloaromaTown.json | 3 + .../Programs/AutoStory/Maps/JubilifeCity.json | 7 ++ .../Programs/AutoStory/Maps/OreburghCity.json | 4 ++ .../Programs/AutoStory/Maps/OreburghGate.json | 3 + .../AutoStory/Maps/PlayerHouse1F.json | 4 ++ .../AutoStory/Maps/PlayerHouse2F.json | 8 ++- .../Programs/AutoStory/Maps/Route201.json | 5 ++ .../Programs/AutoStory/Maps/Route202.json | 3 + .../Programs/AutoStory/Maps/Route203.json | 3 + .../Programs/AutoStory/Maps/SandgemTown.json | 3 + .../Programs/AutoStory/Maps/TwinleafTown.json | 4 ++ .../AutoStory/Maps/VerityLakefront.json | 6 +- devtools/maps/bdsp_maps.py | 66 +++++++++++++++-- devtools/maps/decomp.py | 71 +++++++++++++++++-- devtools/maps/tests/test_decomp.py | 58 ++++++++++++++- 15 files changed, 235 insertions(+), 13 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/FloaromaTown.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/FloaromaTown.json index db9cf8a5df..f6f6bfd349 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/FloaromaTown.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/FloaromaTown.json @@ -52,5 +52,8 @@ {"name": "camper_12", "kind": "npc", "x": 6, "y": 10}, {"name": "old_woman", "kind": "npc", "x": 3, "y": 25}, {"name": "old_man", "kind": "npc", "x": 2, "y": 25} + ], + "coord_events": [ + {"name": "coord_event_0", "x": 10, "y": 29, "width": 3, "length": 1, "var": "VAR_UNK_0x4072", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0404.bin coord_events[0] (170, 669), script 2"} ] } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/JubilifeCity.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/JubilifeCity.json index 71aed1d875..4f28c563a9 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/JubilifeCity.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/JubilifeCity.json @@ -135,5 +135,12 @@ {"name": "clown_tv_station", "kind": "npc", "x": 36, "y": 16}, {"name": "kid_with_nds_29", "kind": "npc", "x": 52, "y": 46}, {"name": "kid_with_nds_30", "kind": "npc", "x": 54, "y": 46} + ], + "coord_events": [ + {"name": "dawn_at_the_steps", "x": 45, "y": 60, "width": 4, "length": 1, "var": "VAR_JUBILIFE_CITY_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0002.bin coord_events[0] (173, 796), script 2"}, + {"name": "coord_event_1", "x": 45, "y": 7, "width": 3, "length": 1, "var": "VAR_JUBILIFE_CITY_STATE", "value": 3, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0002.bin coord_events[1] (173, 743), script 4"}, + {"name": "coord_event_2", "x": 60, "y": 21, "width": 1, "length": 4, "var": "VAR_JUBILIFE_CITY_STATE", "value": 1, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0002.bin coord_events[2] (188, 757), script 3"}, + {"name": "coord_event_3", "x": 27, "y": 43, "width": 1, "length": 3, "var": "VAR_GTS_ACCESS_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0002.bin coord_events[3] (155, 779), script 19"}, + {"name": "poketch_president", "x": 44, "y": 40, "width": 5, "length": 1, "var": "VAR_POKETCH_CAMPAIGN_STATE", "value": 1, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0002.bin coord_events[4] (172, 776), script 17"} ] } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghCity.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghCity.json index 4cdef2c0b0..0043b0bea2 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghCity.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghCity.json @@ -87,5 +87,9 @@ {"name": "guitarist_20", "kind": "npc", "x": 23, "y": 13, "wander": {"x0": 22, "y0": 10, "x1": 24, "y1": 16}}, {"name": "battle_girl_21", "kind": "npc", "x": 36, "y": 23, "wander": {"x0": 36, "y0": 21, "x1": 36, "y1": 25}}, {"name": "machop_22", "kind": "npc", "x": 55, "y": 56, "wander": {"x0": 53, "y0": 54, "x1": 57, "y1": 58}} + ], + "coord_events": [ + {"name": "coord_event_0", "x": 10, "y": 12, "width": 1, "length": 4, "var": "VAR_OREBURGH_CITY_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0044.bin coord_events[0] (266, 748), script 3"}, + {"name": "coord_event_1", "x": 6, "y": 12, "width": 1, "length": 4, "var": "VAR_OREBURGH_CITY_STATE", "value": 2, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0044.bin coord_events[1] (262, 748), script 4"} ] } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGate.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGate.json index 14c30e35f5..f3f6b5a73b 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGate.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGate.json @@ -39,5 +39,8 @@ {"name": "picnicker_0", "kind": "npc", "x": 24, "y": 21, "facing": "south"}, {"name": "camper_1", "kind": "npc", "x": 17, "y": 25, "facing": "north"}, {"name": "hiker_10", "kind": "npc", "x": 7, "y": 20, "facing": "south"} + ], + "coord_events": [ + {"name": "coord_event_0", "x": 7, "y": 22, "width": 1, "length": 1, "var": "VAR_OREBURGH_GATE_1F_HIKER_STATE", "value": 1, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0250.bin coord_events[0] (7, 22), script 3"} ] } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json index a97ce8bbc3..c959a1a426 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json @@ -26,6 +26,10 @@ "objects": [ {"name": "mom_0", "kind": "npc", "x": 7, "y": 8, "facing": "north"} ], + "coord_events": [ + {"name": "moms_warning", "x": 6, "y": 10, "width": 1, "length": 1, "var": "VAR_PLAYER_HOUSE_STATE", "value": 1, "source": "pret/pokeplatinum res/field/events/events_twinleaf_town_player_house_1f.json coord_events[0] (6, 10), script 5"}, + {"name": "moms_call", "x": 6, "y": 9, "width": 1, "length": 1, "var": "CONSOLE_MOMS_CALL", "value": 0, "source": "console r45, r46 (2026-11-01): after her heal Mom calls us back as we step above the door; no coord event in the decomp"} + ], "patches": [ {"x": 4, "y": 8, "glyph": ".", "measured_by": "r32, 2026-11-01"} ] diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse2F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse2F.json index a33faa27b9..c78c817c70 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse2F.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse2F.json @@ -18,5 +18,11 @@ "points": { "stairs": [8, 4], "start": [4, 6] - } + }, + "coord_events": [ + {"name": "coord_event_0", "x": 4, "y": 5, "width": 1, "length": 1, "var": "VAR_PLAYER_HOUSE_RIVAL_STATE", "value": 0, "source": "pret/pokeplatinum res/field/events/events_twinleaf_town_player_house_2f.json coord_events[0] (4, 5), script 7"}, + {"name": "coord_event_1", "x": 3, "y": 6, "width": 1, "length": 1, "var": "VAR_PLAYER_HOUSE_RIVAL_STATE", "value": 0, "source": "pret/pokeplatinum res/field/events/events_twinleaf_town_player_house_2f.json coord_events[1] (3, 6), script 8"}, + {"name": "coord_event_2", "x": 5, "y": 6, "width": 1, "length": 1, "var": "VAR_PLAYER_HOUSE_RIVAL_STATE", "value": 0, "source": "pret/pokeplatinum res/field/events/events_twinleaf_town_player_house_2f.json coord_events[2] (5, 6), script 9"}, + {"name": "coord_event_3", "x": 4, "y": 7, "width": 1, "length": 1, "var": "VAR_PLAYER_HOUSE_RIVAL_STATE", "value": 0, "source": "pret/pokeplatinum res/field/events/events_twinleaf_town_player_house_2f.json coord_events[3] (4, 7), script 10"} + ] } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json index 91ba7195f7..fe88167472 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json @@ -43,6 +43,11 @@ {"name": "school_kid_m_9", "kind": "npc", "x": 57, "y": 8, "facing": "west"}, {"name": "lass_11", "kind": "npc", "x": 46, "y": 16, "wander": {"x0": 44, "y0": 16, "x1": 48, "y1": 16}} ], + "coord_events": [ + {"name": "barry_warning", "x": 14, "y": 18, "width": 6, "length": 1, "var": "VAR_FOLLOWER_RIVAL_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0327.bin coord_events[0] (110, 857), script 2"}, + {"name": "rowan_stop", "x": 5, "y": 14, "width": 1, "length": 3, "var": "VAR_FOLLOWER_RIVAL_STATE", "value": 2, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0327.bin coord_events[1] (101, 853), script 5"}, + {"name": "coord_event_2", "x": 14, "y": 19, "width": 6, "length": 1, "var": "VAR_FOLLOWER_RIVAL_STATE", "value": 1, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0327.bin coord_events[2] (110, 858), script 3"} + ], "patches": [ {"x": 8, "y": 18, "glyph": "#", "measured_by": "r16, 2026-10-31"}, {"x": 9, "y": 18, "glyph": "#", "measured_by": "r16, 2026-10-31"}, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json index e52d694739..50486733f1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json @@ -123,6 +123,9 @@ {"name": "youngster_7", "kind": "npc", "x": 10, "y": 8, "wander": {"x0": 9, "y0": 8, "x1": 11, "y1": 8}}, {"name": "item_8", "kind": "item", "x": 3, "y": 6} ], + "coord_events": [ + {"name": "catch_tutorial", "x": 21, "y": 25, "width": 1, "length": 5, "var": "VAR_ROUTE_202_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0328.bin coord_events[0] (180, 825), script 6"} + ], "patches": [ {"x": 15, "y": 28, "glyph": "#", "measured_by": "r253, 2026-11-01"}, {"x": 16, "y": 28, "glyph": "#", "measured_by": "r253, 2026-11-01"}, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json index 2e3c409907..faccffbe74 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json @@ -60,5 +60,8 @@ {"name": "item_10", "kind": "item", "x": 31, "y": 5}, {"name": "item_11", "kind": "item", "x": 20, "y": 10}, {"name": "lass_12", "kind": "npc", "x": 15, "y": 17, "wander": {"x0": 14, "y0": 17, "x1": 16, "y1": 17}} + ], + "coord_events": [ + {"name": "coord_event_0", "x": 5, "y": 17, "width": 1, "length": 4, "var": "VAR_ROUTE_203_RIVAL_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0329.bin coord_events[0] (196, 757), script 6"} ] } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json index 4f7a4e61b4..3691f9266e 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json @@ -51,5 +51,8 @@ }, "objects": [ {"name": "youngster_0", "kind": "npc", "x": 27, "y": 23, "wander": {"x0": 26, "y0": 22, "x1": 28, "y1": 24}} + ], + "coord_events": [ + {"name": "dawn_walks_us_to_the_lab", "x": 4, "y": 10, "width": 1, "length": 6, "var": "VAR_SANDGEM_TOWN_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0396.bin coord_events[0] (164, 842), script 2"} ] } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json index ff22e6feba..9441a324e5 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json @@ -46,5 +46,9 @@ {"name": "pokemon_breeder_f_0", "kind": "npc", "x": 23, "y": 14, "wander": {"x0": 22, "y0": 14, "x1": 24, "y1": 14}}, {"name": "collector_1", "kind": "npc", "x": 12, "y": 25}, {"name": "guitarist_3", "kind": "npc", "x": 12, "y": 5} + ], + "coord_events": [ + {"name": "coord_event_0", "x": 12, "y": 3, "width": 8, "length": 1, "var": "VAR_TWINLEAF_TOWN_GUITARIST_TRIGGER_STATE", "value": 1, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0389.bin coord_events[0] (108, 867), script 4"}, + {"name": "barry_doorstep", "x": 9, "y": 12, "width": 1, "length": 1, "var": "VAR_TWINLEAF_TOWN_RIVAL_TRIGGER_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0389.bin coord_events[1] (105, 876), script 2"} ] } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/VerityLakefront.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/VerityLakefront.json index ec50cf46e9..f7e12b3b98 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/VerityLakefront.json +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/VerityLakefront.json @@ -39,5 +39,9 @@ "lakefront_arrival": [31, 23], "below_lake_entrance": [17, 14], "lake_entrance": [17, 12] - } + }, + "coord_events": [ + {"name": "lake_entrance", "x": 16, "y": 12, "width": 2, "length": 1, "var": "VAR_VERITY_LAKEFRONT_STATE", "value": 0, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0319.bin coord_events[0] (80, 844), script 3"}, + {"name": "coord_event_1", "x": 16, "y": 12, "width": 2, "length": 1, "var": "VAR_VERITY_LAKEFRONT_STATE", "value": 3, "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0319.bin coord_events[1] (80, 844), script 4"} + ] } diff --git a/devtools/maps/bdsp_maps.py b/devtools/maps/bdsp_maps.py index 4a12ed05b0..6454bb3b47 100644 --- a/devtools/maps/bdsp_maps.py +++ b/devtools/maps/bdsp_maps.py @@ -28,6 +28,9 @@ - PATCHES: tiles measured on the console that differ from Diamond's, with the run that measured them. The map's tiles stay the decompilation's; the loader applies the patches. +- COORD_EVENTS: names for the events file's coord events (the scene + triggers), and triggers found on the console that the decompilation + doesn't have, with the run that found them. The legend is the schema's: '#' wall, '.' open, '~' tall grass, 'w' water, '^' 'v' '<' '>' ledges hopped the arrow's way, 'D' a door or cave mouth @@ -39,7 +42,14 @@ a route is coded only over tiles a human or a probe has walked; a map change re-runs every checkpoint that walks it. Bulbapedia's DP images (12 px rows for 16 px columns, cliffs painted tall) are for drawing routes over, not for -terrain. Scenes' coord events have no place in the schema (Segment00Lines). +terrain. + +Each map lists its events file's coord events under "coord_events" (the +shared schema, Pokemon_MapData.h, leaves the key to the game; BDSP AutoStory's +Data/PokemonBDSP_AutoStory_CoordEvents.h reads it): `width` columns and +`length` rows from local (x, y), armed while the story variable `var` holds +`value`, and a `source` that names the file, the event's index, its world +tile and its script. Boxes are clipped to the map's rectangle. """ import argparse import json @@ -138,7 +148,7 @@ # 00.01 to 00.03's interiors, from pokeplatinum (c6689696d5fb), each # the only cell of its own matrix: decomp only, not yet checked on the # console. Mom's warning on the way out is a coord event on the - # player's door tile (6, 10), which the schema has no place for. + # player's door tile (6, 10). 'PlayerHouse1F': {'header': 'TWINLEAF_TOWN_PLAYER_HOUSE_1F', 'rect': (0, 0, 13, 12), **INTERIOR}, 'PlayerHouse2F': {'header': 'TWINLEAF_TOWN_PLAYER_HOUSE_2F', 'rect': (0, 0, 12, 12), **INTERIOR}, 'RivalHouse1F': {'header': 'TWINLEAF_TOWN_RIVAL_HOUSE_1F', 'rect': (0, 0, 13, 12), **INTERIOR}, @@ -440,6 +450,29 @@ def box(x0, y0, x1, y1): } +# Coord events by events-file index: their names (the default is +# coord_event_), and under 'add' the triggers the console found that +# the decompilation doesn't have. +COORD_EVENTS = { + # Barry stops us on row 18 (POINTS barry_warning); Rowan on column 5 + # (POINTS rowan_stop). + 'Route201': {0: 'barry_warning', 1: 'rowan_stop'}, + 'Route202': {0: 'catch_tutorial'}, + 'SandgemTown': {0: 'dawn_walks_us_to_the_lab'}, + 'JubilifeCity': {0: 'dawn_at_the_steps', 4: 'poketch_president'}, + 'TwinleafTown': {1: 'barry_doorstep'}, + 'VerityLakefront': {0: 'lake_entrance'}, + 'PlayerHouse1F': { + 0: 'moms_warning', + 'add': [ + {'name': 'moms_call', 'x': 6, 'y': 9, 'width': 1, 'length': 1, 'var': 'CONSOLE_MOMS_CALL', 'value': 0, + 'measured_by': 'console r45, r46 (2026-11-01): after her heal Mom calls us back as we step above' + ' the door; no coord event in the decomp'}, + ], + }, +} + + def no_fetch(repo, path): raise SystemExit(f'{repo}/{path} is not in {CACHE}. Fetch it with `python3 -m devtools.maps decomp ...` first.') @@ -501,6 +534,29 @@ def place_objects(name, events, rect, rows): return objects +def coord_events(name, repo, game, events_id, rect): + """The map's coord events: the events file's, clipped to `rect`, then COORD_EVENTS' additions.""" + x0, z0, width, height = rect + names = COORD_EVENTS.get(name, {}) + out = [] + for index, event in enumerate(game.events(events_id).coord_events): + left, top = max(event.x, x0), max(event.z, z0) + right, bottom = min(event.x + event.width, x0 + width), min(event.z + event.length, z0 + height) + if left >= right or top >= bottom: + continue + out.append({ + 'name': names.get(index, f'coord_event_{index}'), + 'x': left - x0, 'y': top - z0, 'width': right - left, 'length': bottom - top, + 'var': event.var, 'value': event.value, + 'source': f'{repo} {game.events_path(events_id)} coord_events[{index}] ({event.x}, {event.z}),' + f' script {event.script}', + }) + for added in names.get('add', []): + fields = {k: v for k, v in added.items() if k != 'measured_by'} + out.append({**fields, 'source': added['measured_by']}) + return out + + def decode_map(name): spec = MAPS[name] repo = spec.get('repo', decomp.Diamond.REPO) @@ -540,7 +596,8 @@ def decode_map(name): 'revision': spec.get('revision', 'default branch'), 'date': spec.get('read', READ), } - return [''.join(row) for row in rows], objects, source + triggers = coord_events(name, repo, game, header.events_id, spec['rect']) + return [''.join(row) for row in rows], objects, source, triggers def points_of(name): @@ -556,7 +613,7 @@ def points_of(name): def map_text(name): """The map's JSON: one line per row, point, object and patch.""" - tiles, objects, source = decode_map(name) + tiles, objects, source, triggers = decode_map(name) lines = ['{', f' "name": {json.dumps(name)},', f' "source": {json.dumps(source)},', ' "tiles": ['] lines += [f' {json.dumps(row)},' for row in tiles] lines[-1] = lines[-1].rstrip(',') @@ -574,6 +631,7 @@ def section(key, items): section('points', [f'{json.dumps(p)}: [{x}, {y}]' for p, (x, y) in points_of(name).items()]) section('warps', [json.dumps({'x': x, 'y': y, 'to_map': m, 'to_point': p}) for x, y, m, p in WARPS.get(name, [])]) section('objects', [json.dumps(o) for o in objects]) + section('coord_events', [json.dumps(e) for e in triggers]) section('patches', [json.dumps({'x': x, 'y': y, 'glyph': g, 'measured_by': m}) for x, y, g, m in PATCHES.get(name, [])]) lines.append('}') diff --git a/devtools/maps/decomp.py b/devtools/maps/decomp.py index f5e85b6c55..881fa19347 100644 --- a/devtools/maps/decomp.py +++ b/devtools/maps/decomp.py @@ -12,8 +12,11 @@ pokeplatinum keeps matrices and events as JSON and its map headers in include/data/map_headers.h. pokediamond keeps them as binary NARC entries (the layout pokeplatinum's tools/jsoncnv packs) with the header table in -arm9/src/map_header.c, and gives sprites, movement types and trainer types -by number; pokeplatinum's generated/*.txt tables name them. BDSP keeps +arm9/src/map_header.c, and gives sprites, movement types, trainer types and +story variables by number; pokeplatinum's generated/*.txt tables name them. +An events file lists bg events, object events, warps and coord events (the +scene triggers: stepping onto one of a box of tiles while a story variable +holds a value starts a script), in that order. BDSP keeps Diamond/Pearl's layout; Platinum's few changes show up on the console. """ import datetime @@ -153,11 +156,24 @@ class Sign: z: int +@dataclass +class CoordEvent: + """A scene trigger: `width` x `length` tiles from (x, z), armed while `var` holds `value`.""" + script: int + x: int + z: int + width: int + length: int + var: str + value: int + + @dataclass class Events: objects: list = field(default_factory=list) warps: list = field(default_factory=list) signs: list = field(default_factory=list) + coord_events: list = field(default_factory=list) def strip_prefix(name, prefix): @@ -203,8 +219,11 @@ def land_data(self, land_id): def events_name(self, events_id): return f'{events_id}.json' + def events_path(self, events_id): + return f'res/field/events/{events_id}.json' + def events(self, events_id): - data = json.loads(self.source.text(f'res/field/events/{events_id}.json')) + data = json.loads(self.source.text(self.events_path(events_id))) events = Events() for o in data.get('object_events', []): trainer = o['trainer_type'] != 'TRAINER_TYPE_NONE' @@ -222,6 +241,9 @@ def events(self, events_id): for b in data.get('bg_events', []): if b['type'] in BG_SIGNS: events.signs.append(Sign(b['x'], b['z'])) + for c in data.get('coord_events', []): + events.coord_events.append(CoordEvent( + c['script'], c['x'], c['z'], c['width'], c['length'], c['var'], c['value'])) return events @@ -231,6 +253,11 @@ class Diamond: Sprites, movement types and trainer types are numbers there; `names` is a Source for pret/pokeplatinum, whose generated/*.txt tables list them in order (checked on Route 204 North: the same twelve objects come out). + Story variables are numbered from 0x4000 in both games; Platinum's + generated/vars_flags.txt (a C enum) names them (checked: the coord events + that pokeplatinum also has on Route 202, Sandgem, Jubilife and the + player's house come out with Platinum's variable names). A number that + table does not name stays a number. """ REPO = 'pret/pokediamond' GAME = 'Pokemon Diamond' @@ -271,6 +298,16 @@ def named(self, table, prefix, value): entries = self.table(table) return strip_prefix(entries[value], prefix) if value < len(entries) else str(value) + def var_name(self, number): + if 'vars' not in self._tables: + values = enum_values(self.names.text('generated/vars_flags.txt').splitlines()) + names = {} + for name, value in values.items(): + if name.startswith('VAR_'): + names.setdefault(value, name) + self._tables['vars'] = names + return self._tables['vars'].get(number, str(number)) + def matrix(self, matrix_id): data = self.source.read(f'files/fielddata/mapmatrix/map_matrix/narc_{matrix_id:04d}.bin') width, height, has_headers, has_altitudes, name_len = struct.unpack_from('<5B', data, 0) @@ -297,8 +334,11 @@ def land_data(self, land_id): def events_name(self, events_id): return f'zone_event narc_{events_id:04d}' + def events_path(self, events_id): + return f'files/fielddata/eventdata/zone_event_release/narc_{events_id:04d}.bin' + def events(self, events_id): - data = self.source.read(f'files/fielddata/eventdata/zone_event_release/narc_{events_id:04d}.bin') + data = self.source.read(self.events_path(events_id)) events = Events() offset = 0 (count,) = struct.unpack_from(' value of a C enum's lines: `NAME` (the previous value + 1) or `NAME = number or NAME`.""" + values = {} + following = 0 + for line in lines: + name, _, value = (part.strip() for part in line.partition('=')) + if not name: + continue + if value: + following = int(value, 0) if value[0].isdigit() else values[value] + values[name] = following + following += 1 + return values + + def title(constant): """AROMA_LADY_TAYLOR -> Aroma Lady Taylor.""" return ' '.join(w.capitalize() for w in constant.split('_')) diff --git a/devtools/maps/tests/test_decomp.py b/devtools/maps/tests/test_decomp.py index daf514c156..57f25908a5 100644 --- a/devtools/maps/tests/test_decomp.py +++ b/devtools/maps/tests/test_decomp.py @@ -4,7 +4,9 @@ import unittest from pathlib import Path -from devtools.maps import decomp +from unittest import mock + +from devtools.maps import bdsp_maps, decomp # One synthetic 32 x 32 cell, open ground but for the first row: # wall, tall grass, ledge south, ledge east, door, warp tile, water, cave wall, cave floor. @@ -67,7 +69,9 @@ def platinum_tree(root): ], 'warp_events': [{'x': 5, 'z': 0, 'dest_header_id': 'MAP_HEADER_OTHER', 'dest_warp_id': 0}, {'x': 4, 'z': 1, 'dest_header_id': 'MAP_HEADER_OTHER', 'dest_warp_id': 1}], - 'coord_events': []})) + 'coord_events': [ + {'script': 6, 'x': 2, 'z': 3, 'y': 0, 'width': 1, 'length': 5, 'var': 'VAR_TEST_STATE', 'value': 0}, + {'script': 7, 'x': 30, 'z': 0, 'y': 0, 'width': 4, 'length': 1, 'var': '16390', 'value': 2}]})) def diamond_tree(root): @@ -88,6 +92,7 @@ def diamond_tree(root): gfx[4], gfx[20], gfx[87], gfx[94] = 'OBJ_EVENT_GFX_YOUNGSTER', 'OBJ_EVENT_GFX_LASS', 'OBJ_EVENT_GFX_POKEBALL', 'OBJ_EVENT_GFX_ARROW_SIGNPOST' write(root, 'generated/movement_types.txt', '\n'.join(movement)) write(root, 'generated/object_events_gfx.txt', '\n'.join(gfx)) + write(root, 'generated/vars_flags.txt', 'FLAG_UNUSED\nVARS_START = 16384\nVAR_UNUSED_0x4000 = VARS_START\nVAR_TEST_STATE\n') def obj(gfx_id, movement_id, trainer, flag, data0, range_x, x, z): return struct.pack('<14HI', 0, gfx_id, movement_id, trainer, flag, 1, 0, data0, 0, 0, range_x, 0, x, z, 0) @@ -97,7 +102,8 @@ def obj(gfx_id, movement_id, trainer, flag, data0, range_x, x, z): events += obj(87, 0, 0, 1000, 0, 0, 9, 0) + obj(94, 0, 0, 0, 13, 0, 10, 0) + obj(20, 15, 1, 0, 4, 0, 11, 0) events += obj(4, 5, 0, 0, 0, 2, 12, 0) + obj(20, 0, 0, 0, 0, 0, 40, 0) events += struct.pack(' Date: Thu, 5 Nov 2026 19:44:41 -0800 Subject: [PATCH 13/13] BDSP AutoStory maps: align with PR #231 lint conventions --- .../Data/PokemonBDSP_AutoStory_Maps.cpp | 8 +- .../Data/PokemonBDSP_AutoStory_Maps.h | 6 +- .../PokemonBDSP_AutoStory_Maps_Tests.cpp | 150 ++++++++++-------- .../PokemonBDSP_AutoStory_Maps_Tests.h | 7 +- 4 files changed, 98 insertions(+), 73 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp index 065b53300a..1992a6cb4d 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.cpp @@ -35,7 +35,7 @@ const std::map>& story_maps_by_name(){ return maps; } -} +} // namespace const MapData& story_map(std::string_view name){ @@ -72,6 +72,6 @@ std::vector map_region(std::string_view map, std::string_view region){ -} -} -} +} // namespace PokemonBDSP +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h index e6e3a96bef..988fb7b52f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h @@ -51,7 +51,7 @@ std::vector map_region(std::string_view map, std::string_view -} -} -} +} // namespace PokemonBDSP +} // namespace NintendoSwitch +} // namespace PokemonAutomation #endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp index df0a559e8e..01174e5b99 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.cpp @@ -56,32 +56,26 @@ std::string region_of(const std::string& point){ // Every built-in map parses through the shared loader, named as its file. -class Test_StoryMapsLoad : public UnitTest{ -public: - Test_StoryMapsLoad() - : UnitTest("PokemonBDSP::AutoStory - Maps: the built-in JSON maps load") - {} - virtual UnitTestResult run(Logger& logger, CancellableScope&) const override{ - if (bdsp_map_json().empty()){ - return std::string("No built-in maps."); - } - for (const auto& [name, json] : bdsp_map_json()){ - try{ - const MapData& map = story_map(name); - if (map.name != name){ - return std::format("{}.json: story_map() returned \"{}\".", name, map.name); - } - logger.log(std::format( - "{}: {} x {}, {} points, {} objects, {} patches", - name, map.tiles.width, map.tiles.height, map.points.size(), map.objects.size(), map.patches.size() - )); - }catch (Exception& e){ - return std::format("{}: {}: {}", name, e.name(), e.message()); +UnitTestResult test_story_maps_load(Logger& logger){ + if (bdsp_map_json().empty()){ + return std::string("No built-in maps."); + } + for (const auto& [name, json] : bdsp_map_json()){ + try{ + const MapData& map = story_map(name); + if (map.name != name){ + return std::format("{}.json: story_map() returned \"{}\".", name, map.name); } + logger.log(std::format( + "{}: {} x {}, {} points, {} objects, {} patches", + name, map.tiles.width, map.tiles.height, map.points.size(), map.objects.size(), map.patches.size() + )); + }catch (Exception& e){ + return std::format("{}: {}: {}", name, e.name(), e.message()); } - return true; } -}; + return true; +} // What a named point must be on. Points not listed are ground the player @@ -171,12 +165,7 @@ std::string check_ground(const MapData& map, const TilePos& pos){ // Every point is ground with nobody on it, unless point_cases() says // otherwise; every numbered region runs from 1 with no gap; every case // names a point the map has; every warp leads to a point that exists. -class Test_StoryMapPoints : public UnitTest{ -public: - Test_StoryMapPoints() - : UnitTest("PokemonBDSP::AutoStory - Maps: points stand where they can") - {} - virtual UnitTestResult run(Logger& logger, CancellableScope&) const override{ +UnitTestResult test_story_map_points(Logger& logger){ for (const PointCase& item : point_cases()){ const MapData& map = story_map(item.map); if (!map.points.contains(item.point) && !map.points.contains(item.point + ".1")){ @@ -214,17 +203,11 @@ class Test_StoryMapPoints : public UnitTest{ } logger.log(std::format("{} points checked.", checked)); return true; - } -}; +} // Everyone stands on ground, and a wanderer's box holds their tile. -class Test_StoryMapObjects : public UnitTest{ -public: - Test_StoryMapObjects() - : UnitTest("PokemonBDSP::AutoStory - Maps: objects stand on open ground") - {} - virtual UnitTestResult run(Logger& logger, CancellableScope&) const override{ +UnitTestResult test_story_map_objects(Logger& logger){ size_t checked = 0; for (const auto& [name, json] : bdsp_map_json()){ const MapData& map = story_map(name); @@ -242,8 +225,7 @@ class Test_StoryMapObjects : public UnitTest{ } logger.log(std::format("{} objects checked.", checked)); return true; - } -}; +} // Route 202's regions are the tiles the story code computed before they @@ -272,12 +254,7 @@ std::vector after_training_area(const MapData& map){ return standable_in_box(map, {catch_spot.x - 1, catch_spot.y - 2}, {catch_spot.x + 1, catch_spot.y + 2}); } -class Test_Route202Regions : public UnitTest{ -public: - Test_Route202Regions() - : UnitTest("PokemonBDSP::AutoStory - Maps: Route 202's regions are what the story computed") - {} - virtual UnitTestResult run(Logger& logger, CancellableScope&) const override{ +UnitTestResult test_route202_regions(Logger& logger){ const std::vector cases{ { .region = "catch_save_area", // story_catch_save_area() @@ -310,8 +287,7 @@ class Test_Route202Regions : public UnitTest{ logger.log(std::format("Route202 {}: {} tiles.", item.region, region.size())); } return true; - } -}; +} // map_point() and map_region() find what the story asks for, regions in @@ -323,12 +299,7 @@ struct LookupCase{ std::vector expected; }; -class Test_StoryMapLookups : public UnitTest{ -public: - Test_StoryMapLookups() - : UnitTest("PokemonBDSP::AutoStory - Maps: map_point() and map_region()") - {} - virtual UnitTestResult run(Logger&, CancellableScope&) const override{ +UnitTestResult test_story_map_lookups(Logger&){ const std::vector cases{ { .name = "a point", @@ -376,21 +347,74 @@ class Test_StoryMapLookups : public UnitTest{ } } return true; - } +} + + +struct StoryMapsInput{ + std::string name; + std::function body; }; -} +struct StoryMapsExpectation{ +}; +struct StoryMapsCase{ + StoryMapsInput input; + StoryMapsExpectation expectation; +}; + +class Test_StoryMaps : public UnitTest{ +public: + explicit Test_StoryMaps(StoryMapsCase test_case) + : UnitTest("PokemonBDSP::AutoStory - Maps: " + test_case.input.name) + , m_case(std::move(test_case)) + {} + virtual UnitTestResult run(Logger& logger, CancellableScope&) const override{ + try{ + return m_case.input.body(logger); + }catch (Exception& e){ + return std::string(e.name()) + ": " + e.message(); + } + } + +private: + StoryMapsCase m_case; +}; + +} // namespace + + +const StoryMapsCase STORY_MAPS_CASES[] = { + { + .input = {"the built-in JSON maps load", test_story_maps_load}, + .expectation = {}, + }, + { + .input = {"points stand where they can", test_story_map_points}, + .expectation = {}, + }, + { + .input = {"objects stand on open ground", test_story_map_objects}, + .expectation = {}, + }, + { + .input = {"map_point() and map_region()", test_story_map_lookups}, + .expectation = {}, + }, + { + .input = {"Route 202's regions are what the story computed", test_route202_regions}, + .expectation = {}, + }, +}; void add_tests_AutoStory_Maps(UnitTestDatabase& database){ - database.add(); - database.add(); - database.add(); - database.add(); - database.add(); + for (const auto& item : STORY_MAPS_CASES){ + database.add(item); + } } -} -} -} + +} // namespace PokemonBDSP +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.h index 59905aa331..dc3d4cc52e 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Maps_Tests.h @@ -14,10 +14,11 @@ namespace NintendoSwitch{ namespace PokemonBDSP{ +// Registers AutoStory map unit tests with the database. void add_tests_AutoStory_Maps(UnitTestDatabase& database); -} -} -} +} // namespace PokemonBDSP +} // namespace NintendoSwitch +} // namespace PokemonAutomation #endif