From f930d22063c49e74ffd698e60295749bb5ce3623 Mon Sep 17 00:00:00 2001 From: dayfine Date: Tue, 3 Nov 2026 19:20:49 -0800 Subject: [PATCH 01/13] FRLG 00.26: the Bike Voucher from the Fan Club, back to Cerulean, the Bicycle New row 00.26 in PokemonFRLG_AutoStoryTableCeladon.cpp: from Vermilion's Center to the Fan Club's chairman (YES, the Bike Voucher), north round Route 6's trainers, through the Underground Path and Route 5, the tree into Cerulean, the Bike Shop's clerk (the Bicycle), 5 Repels and 2 Escape Ropes at the Mart, and the nurse. Two waypoints keep the planned walks off Oak's aide on Vermilion's (25, 7). The Fan Club and Bike Shop maps are generated from pokefirered. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../Overworld/Maps/CeruleanCity.json | 9 +- .../Overworld/Maps/CeruleanCity_BikeShop.json | 75 ++++++ .../Overworld/Maps/VermilionCity.json | 5 +- .../Maps/VermilionCity_PokemonFanClub.json | 103 ++++++++ .../Overworld/PokemonFRLG_OverworldMaps.cpp | 3 + .../PokemonFRLG/PokemonFRLG_Tests.cpp | 1 + .../PokemonFRLG_AutoStoryCeladon_Tests.cpp | 227 ++++++++++++++++++ .../PokemonFRLG_AutoStoryRunPlan_Tests.cpp | 8 +- .../AutoStory/PokemonFRLG_AutoStoryTable.cpp | 2 +- .../AutoStory/PokemonFRLG_AutoStoryTable.h | 2 + .../PokemonFRLG_AutoStoryTableCeladon.cpp | 84 +++++++ .../PokemonFRLG_AutoStoryTableSteps.h | 6 +- .../AutoStory/PokemonFRLG_AutoStory_Tests.cpp | 4 +- SerialPrograms/cmake/SourceFiles.cmake | 2 + 14 files changed, 520 insertions(+), 11 deletions(-) create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_BikeShop.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_PokemonFanClub.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity.json index 9334a3aa83..1265934c96 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity.json +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity.json @@ -93,6 +93,7 @@ "warp_1": [30, 11], "warp_3": [22, 19], "warp_4": [31, 21], + "warp_5": [13, 28], "warp_6": [29, 28], "warp_7": [1, 12], "warp_9": [31, 8] @@ -132,7 +133,9 @@ { "x": 13, "y": 28, - "enter": "cave_entrance" + "enter": "cave_entrance", + "to_map": "CeruleanCity_BikeShop", + "to_point": "warp_1" }, { "x": 29, @@ -170,7 +173,9 @@ { "x": 14, "y": 28, - "enter": "cave_entrance" + "enter": "cave_entrance", + "to_map": "CeruleanCity_BikeShop", + "to_point": "warp_1" }, { "x": 23, diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_BikeShop.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_BikeShop.json new file mode 100644 index 0000000000..ad5464b469 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_BikeShop.json @@ -0,0 +1,75 @@ +{ + "name": "CeruleanCity_BikeShop", + "source": { + "decomp_file": "data/maps/CeruleanCity_BikeShop/map.json", + "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", + "date": "2026-10-31" + }, + "allow_running": false, + "tiles": [ + "###########", + "###########", + ".####...#..", + "..#.##..#..", + ".#..#...###", + "..#.#.....#", + "........#..", + "..#........", + "..#......#.", + "###########" + ], + "points": { + "warp_1": [5, 8] + }, + "warps": [ + { + "x": 4, + "y": 9, + "enter": "walk_into", + "to_map": "CeruleanCity", + "to_point": "warp_5" + }, + { + "x": 5, + "y": 8, + "enter": "walk_south", + "to_map": "CeruleanCity", + "to_point": "warp_5" + }, + { + "x": 6, + "y": 9, + "enter": "walk_into", + "to_map": "CeruleanCity", + "to_point": "warp_5" + } + ], + "objects": [ + { + "name": "man", + "kind": "npc", + "x": 9, + "y": 3 + }, + { + "name": "youngster", + "kind": "npc", + "x": 9, + "y": 7, + "wander": { + "x0": 8, + "y0": 6, + "x1": 10, + "y1": 8 + } + }, + { + "name": "woman_2", + "kind": "npc", + "x": 5, + "y": 5 + } + ], + "flag_sets": {}, + "connections": [] +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity.json index 3e6026ea80..8d7cd08e6e 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity.json +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity.json @@ -94,6 +94,7 @@ "warp_1": [23, 34], "warp_2": [24, 34], "warp_4": [15, 6], + "warp_5": [12, 17], "warp_9": [14, 25] }, "warps": [ @@ -133,7 +134,9 @@ { "x": 12, "y": 17, - "enter": "walk_into" + "enter": "walk_into", + "to_map": "VermilionCity_PokemonFanClub", + "to_point": "warp_1" }, { "x": 19, diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_PokemonFanClub.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_PokemonFanClub.json new file mode 100644 index 0000000000..e087d94793 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_PokemonFanClub.json @@ -0,0 +1,103 @@ +{ + "name": "VermilionCity_PokemonFanClub", + "source": { + "decomp_file": "data/maps/VermilionCity_PokemonFanClub/map.json", + "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", + "date": "2026-10-31" + }, + "allow_running": false, + "tiles": [ + "############", + "############", + "............", + ".....22.....", + "#....88....#", + "#..64##64..#", + "#..64##64..#", + "#..........#", + "#..........#", + "............", + "#..........#", + "############" + ], + "elevation": [ + "000000000000", + "000000000000", + "333333333333", + "333333333333", + "033304403330", + "033330033330", + "033330033330", + "033333333330", + "033333333330", + "333333333333", + "033333333330", + "000000000000" + ], + "points": { + "warp_1": [5, 10] + }, + "warps": [ + { + "x": 4, + "y": 10, + "enter": "walk_into", + "to_map": "VermilionCity", + "to_point": "warp_5" + }, + { + "x": 5, + "y": 10, + "enter": "walk_south", + "to_map": "VermilionCity", + "to_point": "warp_5" + }, + { + "x": 6, + "y": 10, + "enter": "walk_into", + "to_map": "VermilionCity", + "to_point": "warp_5" + } + ], + "objects": [ + { + "name": "gentleman", + "kind": "npc", + "x": 5, + "y": 4 + }, + { + "name": "worker_f", + "kind": "npc", + "x": 6, + "y": 4 + }, + { + "name": "pikachu", + "kind": "npc", + "x": 7, + "y": 6 + }, + { + "name": "seel", + "kind": "npc", + "x": 4, + "y": 6 + }, + { + "name": "woman_1", + "kind": "npc", + "x": 4, + "y": 5 + }, + { + "name": "fat_man", + "kind": "npc", + "x": 7, + "y": 5 + } + ], + "flag_sets": {}, + "connections": [] +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.cpp index c5d00a8bbc..ac63176941 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.cpp @@ -71,6 +71,9 @@ const char* const MAP_NAMES[] = { "SSAnne_CaptainsOffice", // Vermilion Gym, past the tree Cut fells. "VermilionCity_Gym", + // The BIKE VOUCHER and the BICYCLE. + "VermilionCity_PokemonFanClub", + "CeruleanCity_BikeShop", "CeruleanCave_1F", "CeruleanCave_2F", "CeruleanCave_B1F", diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp index 6e350b7d43..532ceb31c3 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp @@ -753,6 +753,7 @@ void add_tests(UnitTestDatabase& database){ add_tests_AutoStorySimulation(database); add_tests_AutoStoryVermilion(database); add_tests_AutoStoryVermilionGym(database); + add_tests_AutoStoryCeladon(database); add_tests_AutoStoryTrashCans(database); add_tests_AutoStoryPaceSim(database); add_tests_AutoStoryItems(database); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp new file mode 100644 index 0000000000..73f3f34a0f --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp @@ -0,0 +1,227 @@ +/* AutoStory Celadon Tests + * + * From: https://github.com/PokemonAutomation/ + * + * Rows from 00.26 on (PokemonFRLG_AutoStoryTableCeladon.cpp) on the + * overworld sim: 00.26 from where 00.25 ends, to the Fan Club's chairman, + * north past Route 6's trainers, through the tree into Cerulean, to the + * Bike Shop's clerk, the Mart and the nurse, with Oak's aide on + * Vermilion's (25, 7); and what its Mart step buys. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/TestRunners/UnitTest.h" +#include "VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMap.h" +#include "VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.h" +#include "VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.h" +#include "PokemonFRLG_AutoStoryEvents.h" +#include "PokemonFRLG_AutoStoryMart.h" +#include "PokemonFRLG_AutoStoryRowSim.h" +#include "PokemonFRLG_AutoStoryTable.h" +#include "PokemonFRLG_AutoStoryTableSteps.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +namespace{ + +class Test_AutoStoryCeladon : public UnitTest{ +public: + Test_AutoStoryCeladon(std::string name, std::function body) + : UnitTest("PokemonFRLG::AutoStory - Simulation: " + std::move(name)) + , m_body(std::move(body)) + {} + virtual UnitTestResult run(Logger&, CancellableScope&) const override{ + try{ + return m_body(); + }catch (Exception& e){ + return std::string(e.name()) + ": " + e.message(); + } + } + +private: + std::function m_body; +}; + +size_t row_index(const std::string& id){ + const std::vector& rows = story_checkpoints(); + const auto row = std::find_if(rows.begin(), rows.end(), [&](const StoryCheckpoint& data){ return data.id == id; }); + if (row == rows.end()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "No checkpoint " + id + "."); + } + return row - rows.begin(); +} + +const StoryCheckpoint& checkpoint(const std::string& id){ + return story_checkpoints()[row_index(id)]; +} + +MapData& map_named(std::vector& maps, const std::string& name){ + for (MapData& map : maps){ + if (map.name == name){ + return map; + } + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "No map " + name + "."); +} + +// The maps as 00.25 leaves them: story_maps() with Oak's aide on +// Vermilion's (25, 7), shown by Lt. Surge's win, and Route 6's Jeff +// beaten (00.21). Isabelle and Elijah are not. +std::vector maps_after_surge(){ + const std::vector all = overworld_maps(); + std::set flags = map_flag_set(all, "after_hall_of_fame"); + flags.merge(map_flag_set(all, "new_game")); + flags.erase("FLAG_HIDE_VERMILION_CITY_OAKS_AIDE"); + std::vector maps = overworld_maps(flags); + for (MapObject& object : map_named(maps, StoryTable::ROUTE6).objects){ + if (object.name == "camper" && object.trainer){ + object.trainer.reset(); + } + } + return maps; +} + +// Whoever stands `tiles` ahead of `pose`, or nothing. +const MapObject* object_ahead(const std::vector& maps, const StoryPose& pose, int tiles){ + TilePos tile = pose.tile; + for (int c = 0; c < tiles; c++){ + tile = tile.next(*pose.facing); + } + for (const MapData& map : maps){ + if (map.name == pose.map){ + return object_at(map, tile); + } + } + return nullptr; +} + +// The row's last route's target. +std::optional last_target(const StoryCheckpoint& row){ + for (auto step = row.steps.rbegin(); step != row.steps.rend(); ++step){ + if (step->kind == StoryStepKind::ROUTE && step->target){ + return story_pose(*step->target); + } + } + return std::nullopt; +} + + +// 00.26 comes right after 00.25 and starts where it ends, at Vermilion's +// nurse. +UnitTestResult test_starts_where_lt_surge_ends(){ + if (row_index("00.26") != row_index("00.25") + 1){ + return "Expected 00.26 right after 00.25."; + } + const std::optional end = last_target(checkpoint("00.25")); + const StoryCheckpoint& row = checkpoint("00.26"); + const std::optional start = row.start ? story_pose(*row.start) : std::nullopt; + if (!end || !start || start->map != end->map || !(start->tile == end->tile) || start->facing != end->facing){ + return "Expected 00.26 to start where 00.25's walk to the nurse ends."; + } + return true; +} + +// 00.26 on the sim from Vermilion's nurse to Cerulean's, the maps as +// 00.25 leaves them: no trainer meets the player (Isabelle and Elijah +// stay unfought), no violation, each talk faces the one it's for (the +// chairman beside the player, the Bike Shop's clerk across the counter), +// and the walk ends at Cerulean's nurse. +UnitTestResult test_bike_voucher_row(){ + const StoryCheckpoint& row = checkpoint("00.26"); + const std::vector maps = maps_after_surge(); + const MapData& city = *std::find_if(maps.begin(), maps.end(), [](const MapData& map){ return map.name == StoryTable::VERMILION; }); + if (object_at(city, {25, 7}) == nullptr){ + return "Expected Oak's aide on (25, 7) after Lt. Surge."; + } + StoryPose at = *story_pose(*row.start); + OverworldSim sim(maps, MapPosition{at.map, at.tile}, *at.facing); + std::vector talked_to; + for (const StoryStep& step : row.steps){ + if (step.kind == StoryStepKind::TALK){ + const StoryPose here{sim.state().position.map, sim.state().position.point, sim.state().facing}; + const bool counter = here.map == StoryTable::BIKE_SHOP; + const MapObject* person = object_ahead(maps, here, counter ? 2 : 1); + if (person == nullptr || (counter && object_ahead(maps, here, 1) != nullptr)){ + return std::format("00.26/{}: no one to talk to from {}.", step.name, to_string(sim.state().position)); + } + talked_to.push_back(person->name); + } + if (const std::string failure = row_step_on_sim(sim, at, step, row.id); !failure.empty()){ + return failure; + } + // A walk the sim's player didn't finish, stopped by someone the + // maps the program plans on don't have. + if (!(sim.state().position == MapPosition{at.map, at.tile})){ + return std::format("00.26/{}: expected {}, on {}.", step.name, to_string(MapPosition{at.map, at.tile}), to_string(sim.state().position)); + } + } + if (talked_to != std::vector{"gentleman", "man"}){ + return "00.26: expected to talk to the chairman, then the Bike Shop's clerk."; + } + if (!sim.state().trainers_beaten.empty() || !sim.state().violations.empty()){ + return "00.26: a battle or a violation on the way" + (sim.state().violations.empty() ? std::string(".") : ": " + sim.state().violations.back()); + } + const MapPosition nurse{StoryTable::CERULEAN_CENTER, {7, 4}}; + if (!(sim.state().position == nurse)){ + return "00.26: ended on " + to_string(sim.state().position) + ", not Cerulean's nurse."; + } + return true; +} + +// 00.26's Mart step, at its money floor: 5 Repels and 2 Escape Ropes, +// and 3000 or more left. +UnitTestResult test_repels_and_escape_ropes(){ + const StoryCheckpoint& row = checkpoint("00.26"); + const auto step = std::find_if(row.steps.begin(), row.steps.end(), [](const StoryStep& data){ + return parse_mart_variant(data.variant).has_value(); + }); + if (step == row.steps.end()){ + return "00.26: expected a Mart step."; + } + const MartPlan plan = *parse_mart_variant(step->variant); + if (plan.map != StoryTable::CERULEAN_MART || !plan.money_floor){ + return "00.26: expected the Mart step at Cerulean's Mart, with a money floor."; + } + const std::optional> buys = plan_mart_buys(mart_stock(plan.map), plan, *plan.money_floor); + if (!buys){ + return "00.26: the Mart step names an item Cerulean's Mart doesn't sell."; + } + size_t repels = 0; + size_t escape_ropes = 0; + size_t spent = 0; + for (const MartBuy& buy : *buys){ + repels += buy.item == "repel" ? buy.quantity : 0; + escape_ropes += buy.item == "escape-rope" ? buy.quantity : 0; + spent += buy.cost; + } + if (repels < 5 || escape_ropes != 2 || *plan.money_floor < spent + 3000){ + return std::format("00.26: bought {} Repels and {} Escape Ropes for {} from {}.", repels, escape_ropes, spent, *plan.money_floor); + } + return true; +} + +} + + +void add_tests_AutoStoryCeladon(UnitTestDatabase& database){ + database.add("00.26 starts where 00.25 ends", test_starts_where_lt_surge_ends); + database.add("00.26 the Fan Club, Route 6 and the Underground Path north, the Bike Shop, past Oak's aide", test_bike_voucher_row); + database.add("00.26 Repels and Escape Ropes, 3000 left", test_repels_and_escape_ropes); +} + + + +} +} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp index 3c0fa43f5a..2ca095e21e 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp @@ -118,7 +118,7 @@ class Test_RunPlan : public UnitTest{ }; void add_tests_RunPlan(UnitTestDatabase& database){ - const std::string TODAY = "00.01 00.02 00.03 00.04 00.05 00.06 00.07 00.08 00.09 00.10 00.11 00.12 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25"; + const std::string TODAY = "00.01 00.02 00.03 00.04 00.05 00.06 00.07 00.08 00.09 00.10 00.11 00.12 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26"; const RunPlanScenario scenarios[] = { {.name = "handoffs off: the story as before", .first = "00.01", .expected_ids = TODAY}, {.name = "handoffs off: a range", .first = "00.02", .last = "00.04", .expected_ids = "00.02 00.03 00.04"}, @@ -171,8 +171,8 @@ void add_tests_GiftRunPlan(UnitTestDatabase& database){ .expected_ids = "00.11 00.12", .expected_stop = StoryHandoff::GIFT_RNG, }, - {.name = "Gift RNG off: 00.12b is passed over", .first = "00.11", .expected_ids = "00.11 00.12 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25"}, - {.name = "Gift RNG off: 00.12b runs when started at", .first = "00.12b", .expected_ids = "00.12b 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25"}, + {.name = "Gift RNG off: 00.12b is passed over", .first = "00.11", .expected_ids = "00.11 00.12 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26"}, + {.name = "Gift RNG off: 00.12b runs when started at", .first = "00.12b", .expected_ids = "00.12b 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26"}, { .name = "Gift RNG: started at 00.12b, from the save beside the salesman", .first = "00.12b", @@ -187,7 +187,7 @@ void add_tests_GiftRunPlan(UnitTestDatabase& database){ .expected_ids = "", .expected_stop = StoryHandoff::GIFT_RNG, }, - {.name = "Starter RNG alone: 00.12b runs when started at", .first = "00.12b", .handoffs = {.starter_rng = true}, .expected_ids = "00.12b 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25"}, + {.name = "Starter RNG alone: 00.12b runs when started at", .first = "00.12b", .handoffs = {.starter_rng = true}, .expected_ids = "00.12b 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26"}, { .name = "Starter RNG first, with both on", .first = "00.03", diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp index 273924e2d8..1da9219118 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp @@ -792,7 +792,7 @@ const std::vector& story_checkpoints(){ running_shoes(), route3(), magikarp_salesman(), after_gift_rng(), magikarp_to_box(), mt_moon(), route4_to_cerulean(), cerulean_gym(), misty(), cerulean_grind(), cerulean_rival(), nugget_bridge(), route25(), bill(), back_to_cerulean(), oddish(), to_vermilion(), ss_anne(), - vermilion_gym(), to_lt_surge(), lt_surge(), + vermilion_gym(), to_lt_surge(), lt_surge(), bike_voucher(), }; return checkpoints; } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h index 9299ace65d..b0850ac674 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h @@ -43,6 +43,8 @@ void add_tests_AutoStorySimulation(UnitTestDatabase& database); void add_tests_AutoStoryVermilion(UnitTestDatabase& database); // Rows 00.23 to 00.25 on the overworld sim (PokemonFRLG_AutoStoryVermilionGym_Tests.cpp). void add_tests_AutoStoryVermilionGym(UnitTestDatabase& database); +// Rows from 00.26 on on the overworld sim (PokemonFRLG_AutoStoryCeladon_Tests.cpp). +void add_tests_AutoStoryCeladon(UnitTestDatabase& database); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp new file mode 100644 index 0000000000..3b11f0c819 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp @@ -0,0 +1,84 @@ +/* AutoStory Table: to Celadon + * + * From: https://github.com/PokemonAutomation/ + * + * Rows from 00.26 on, after the THUNDERBADGE: the BICYCLE, and the way + * east and south to Celadon City. + * + */ + +#include "PokemonFRLG_AutoStoryMart.h" +#include "PokemonFRLG_AutoStoryTableSteps.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ +namespace StoryTable{ + + +// The BIKE VOUCHER and the BICYCLE, from Vermilion's Center to +// Cerulean's (pokeguy 43:50 to 45:00). +// - The Pokemon Fan Club (VermilionCity_PokemonFanClub map.json, +// scripts.inc), in by Vermilion's (12, 17): the chairman (5, 4) sits +// above a table (5..6, 5..6) with the worker (6, 4) east of him, so he +// is talked to from (4, 4), west of him. "Did you come to hear about +// my POKEMON?" YES: his story, then the BIKE VOUCHER +// (FLAG_GOT_BIKE_VOUCHER). Oak's aide stands on Vermilion's (25, 7) +// since Lt. Surge, where the story's maps don't have him: the way +// planned from the Center's door goes east along his row 7, so the +// walk goes down to the water (14, 10) first, as 00.25 came up from +// it. From the Fan Club, up to (22, 10) first, so that the way on to +// Route 6 goes up column 22, west of him, not round by column 27. +// - Route 6 north, the trainers as 00.21 left them: Jeff (13, 33) +// beaten, Isabelle (13, 32) and Elijah (20, 25) not. As in 00.24, onto +// Jeff's row, west to the wall (7, 33) and up to row 31, above +// Isabelle's line; then east along row 31 to the wall (17, 31) and up +// column 16, west of Elijah's line (17..19, 25) and east of Keigo's +// (4..8, 16), to the ledge (16, 11). The first three legs are 00.24's, +// walked on console B; the two after end on walls, which the walker +// holds again after a wild battle in the grass. +// - The Underground Path north, Route 5's sides, which have no trainer, +// and Cerulean's south edge, whose way into the city without the +// burgled house is the tree (26, 32) (pokeguy 44:42). +// - The Bike Shop (CeruleanCity_BikeShop), in by (13, 28): the clerk +// (9, 3) is across the counter (8, 3), talked to from (7, 3). With the +// voucher he gives the BICYCLE (FLAG_GOT_BICYCLE) with no YES/NO; it +// isn't registered or ridden yet. +// - The Mart: 5 Repels (row 9, 350 each) and 2 Escape Ropes (row 8, +// 550 each; pokefirered items.json), 2850 in all. Console B had about +// 9000 after Lt. Surge (2026-11-03); the floor of 6000 leaves 3000 or +// more after them. +// - The nurse, and the save in Cerulean's Center. +StoryCheckpoint bike_voucher(){ + const MartPlan repels{ + .map = CERULEAN_MART, + .lines = {{.item = "repel", .quantity = 5}, {.item = "escape-rope", .quantity = 2}}, + .budget = 2850, + .money_floor = 6000, + }; + return row("00.26", "The BIKE VOUCHER from the Fan Club, back north to Cerulean, the BICYCLE, Repels and Escape Ropes", at(VERMILION_CENTER, 7, 4, NORTH), after_brock(), StartMenu::FULL, { + running(route("below_center", at(VERMILION, 14, 10), "out of the Center, down to the water (14, 10): off Oak's aide's row 7")), + running(route("beside_chairman", at(FAN_CLUB, 4, 4, EAST), "east along row 10, down to the Fan Club's door (12, 17), in, and up its west side beside the chairman (5, 4)")), + talk("chairman", "\"I chair the POKEMON Fan Club!\" ... \"Did you come to hear about my POKEMON?\""), + answer("hear_story", true, "YES: his story"), + scene("bike_voucher", at(FAN_CLUB, 4, 4, EAST), "the story of his Rapidash; the BIKE VOUCHER"), + running(route("west_of_oaks_aide", at(VERMILION, 22, 10), "out of the Fan Club, east along row 18 and up column 22 to (22, 10)")), + running(route("route6_south_path", at(ROUTE6, 11, 34, NORTH), "up column 22 west of Oak's aide (25, 7), out of the city onto Route 6's path, below Jeff's row")), + running(legs("round_isabelle_and_elijah", {{NORTH, 1}, {WEST, 4}, {NORTH, 2}, {EAST, 9}, {NORTH, 19}}, "onto Jeff's row (11, 33), west to the wall (7, 33), up to (7, 31), east along row 31 to the wall (16, 31), up column 16 to the ledge (16, 12): out of Isabelle's and Elijah's lines")), + running(route("below_cerulean_tree", at(CERULEAN, 26, 33, NORTH), "the Underground Path north, up Route 5's side into Cerulean, below the tree (26, 32)")), + cut_tree("cut_cerulean_tree", at(CERULEAN, 26, 32, NORTH), "the tree; a step onto its tile"), + running(route("bike_clerk", at(BIKE_SHOP, 7, 3, EAST), "west into the Bike Shop (13, 28), across the counter from the clerk (9, 3)")), + talk("talk_bike_clerk", "\"Oh, that's... A BIKE VOUCHER!\""), + scene("bicycle", at(BIKE_SHOP, 7, 3, EAST), "the voucher exchanged for a BICYCLE"), + running(route("mart_clerk", at(CERULEAN_MART, 4, 3, WEST), "out of the Bike Shop to the Mart's clerk")), + mart_step("buy_repels", "5 Repels and 2 Escape Ropes, 2850 at most", repels), + running(route("cerulean_nurse", at(CERULEAN_CENTER, 7, 4, NORTH), "out of the Mart to the Center")), + heal("heal_cerulean"), + }); +} + + +} +} +} +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h index 626d2ee879..79ab143447 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h @@ -57,6 +57,8 @@ inline constexpr const char* VERMILION_GYM = "VermilionCity_Gym"; inline constexpr const char* SS_ANNE_1F = "SSAnne_1F_Corridor"; inline constexpr const char* SS_ANNE_2F = "SSAnne_2F_Corridor"; inline constexpr const char* CAPTAINS_OFFICE = "SSAnne_CaptainsOffice"; +inline constexpr const char* FAN_CLUB = "VermilionCity_PokemonFanClub"; +inline constexpr const char* BIKE_SHOP = "CeruleanCity_BikeShop"; Pokemon::Pose at(const char* map, int x, int y, std::optional facing = std::nullopt); @@ -113,10 +115,12 @@ StoryStep options_step(); StoryStep cut_tree(std::string name, Pokemon::Pose tree, std::string why); -// Rows in files of their own (PokemonFRLG_AutoStoryTableVermilionGym.cpp). +// Rows in files of their own (PokemonFRLG_AutoStoryTableVermilionGym.cpp, +// PokemonFRLG_AutoStoryTableCeladon.cpp). StoryCheckpoint vermilion_gym(); StoryCheckpoint to_lt_surge(); StoryCheckpoint lt_surge(); +StoryCheckpoint bike_voucher(); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp index d8239ee597..46731bcf9e 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp @@ -217,8 +217,8 @@ UnitTestResult check_checkpoint(const StoryCheckpoint& checkpoint, StoryPose& po UnitTestResult test_routes_and_chain(){ const std::vector& checkpoints = story_checkpoints(); - if (checkpoints.size() != 32 || checkpoints[0].start){ - return "Expected 32 checkpoints, the first from a new game."; + if (checkpoints.size() != 33 || checkpoints[0].start){ + return "Expected 33 checkpoints, the first from a new game."; } StoryPose pose; for (size_t c = 0; c < checkpoints.size(); c++){ diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 10671ab95a..e6ea492e32 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1864,6 +1864,7 @@ file(GLOB LIBRARY_SOURCES Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture.h Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCapture_Tests.cpp + Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice.h Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryChoice_Tests.cpp @@ -1904,6 +1905,7 @@ file(GLOB LIBRARY_SOURCES Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySummary.h Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h + Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableVermilionGym.cpp From f9d0e01c789558b05efa2d48a53b19e2c3d334de Mon Sep 17 00:00:00 2001 From: dayfine Date: Tue, 3 Nov 2026 19:21:15 -0800 Subject: [PATCH 02/13] FRLG tests: register the story rows' sim tests together Keeps add_tests() under the function length limit with 00.26's tests. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp index 532ceb31c3..3b44aaa041 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp @@ -713,6 +713,13 @@ static void add_tests_Naming(UnitTestDatabase& database){ add_tests_NamingText(database); } +// The story's rows on the overworld sim, a file of tests per stage. +static void add_tests_AutoStoryRows(UnitTestDatabase& database){ + add_tests_AutoStoryVermilion(database); + add_tests_AutoStoryVermilionGym(database); + add_tests_AutoStoryCeladon(database); +} + void add_tests(UnitTestDatabase& database){ add_tests_WhiteDialogDetector(database); add_tests_AdvanceWhiteDialogDetector(database); @@ -751,9 +758,7 @@ void add_tests(UnitTestDatabase& database){ add_tests_PlayerFacingReader(database); add_tests_AutoStory(database); add_tests_AutoStorySimulation(database); - add_tests_AutoStoryVermilion(database); - add_tests_AutoStoryVermilionGym(database); - add_tests_AutoStoryCeladon(database); + add_tests_AutoStoryRows(database); add_tests_AutoStoryTrashCans(database); add_tests_AutoStoryPaceSim(database); add_tests_AutoStoryItems(database); From 51c0ce6128e5981da56b370a770874b96a1e129f Mon Sep 17 00:00:00 2001 From: dayfine Date: Tue, 3 Nov 2026 19:51:15 -0800 Subject: [PATCH 03/13] FRLG 00.27: east over Route 9 to Route 10's Pokemon Center New row 00.27 in PokemonFRLG_AutoStoryTableCeladon.cpp: from Cerulean's Center, the city's south tree again, out east and Route 9's tree (2, 8), Picnicker Alicia, Hiker Jeremy (waited for to look north) and Bug Catcher Conner, the only three of Route 9's nine the way can't walk around, legs round Hiker Brice's row and above Picnicker Caitlin's line, down the two ledges, and Route 10's north part to its Center's nurse. Route 9, Route 10 and Route 10's Center are generated from pokefirered; the save card reads ROUTE 10. The row sim takes hop() and the new step_onto(), a single step onto the tile above a ledge, which a held leg would hop. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../Menus/PokemonFRLG_SaveCardReader.cpp | 1 + .../Menus/PokemonFRLG_SaveCardReader.h | 2 +- .../Overworld/Maps/CeruleanCity.json | 5 + .../PokemonFRLG/Overworld/Maps/Route10.json | 321 ++++++++++++++++++ .../Maps/Route10_PokemonCenter_1F.json | 110 ++++++ .../PokemonFRLG/Overworld/Maps/Route9.json | 175 ++++++++++ .../Overworld/PokemonFRLG_OverworldMaps.cpp | 4 + .../PokemonFRLG_AutoStoryBattleData.cpp | 5 + .../PokemonFRLG_AutoStoryCeladon_Tests.cpp | 74 +++- .../AutoStory/PokemonFRLG_AutoStoryEvents.cpp | 1 + .../AutoStory/PokemonFRLG_AutoStoryRowSim.cpp | 19 +- .../AutoStory/PokemonFRLG_AutoStoryRowSim.h | 4 +- .../PokemonFRLG_AutoStoryRunPlan_Tests.cpp | 8 +- .../AutoStory/PokemonFRLG_AutoStoryTable.cpp | 2 +- .../PokemonFRLG_AutoStoryTableCeladon.cpp | 70 ++++ .../PokemonFRLG_AutoStoryTableSteps.cpp | 4 + .../PokemonFRLG_AutoStoryTableSteps.h | 7 + .../AutoStory/PokemonFRLG_AutoStory_Tests.cpp | 10 +- 18 files changed, 800 insertions(+), 22 deletions(-) create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10_PokemonCenter_1F.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route9.json diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.cpp index f1affe3765..3b445df2bd 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.cpp @@ -73,6 +73,7 @@ class PlaceNameMatcher : public OCR::SmallDictionaryMatcher{ {"cerulean-city", "Cerulean City"}, {"route-24", "Route 24"}, {"route-25", "Route 25"}, + {"route-10", "Route 10"}, {"vermilion-city", "Vermilion City"}, }){ JsonArray names; diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.h index 0b19e50624..7cd1683b16 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.h @@ -33,7 +33,7 @@ ImageFloatBox save_card_title_box(); // The place on the save card: "pallet-town", "viridian-city", // "viridian-forest", "pewter-city", "route-1" to "route-4", "cerulean-city", -// "route-24", "route-25", "vermilion-city"; empty if it can't be read, as +// "route-10", "route-24", "route-25", "vermilion-city"; empty if it can't be read, as // in a language the reader has no names for. std::string read_save_card_location(Logger& logger, Language language, const ImageViewRGB32& screen); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity.json index 1265934c96..003c2d8035 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity.json +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity.json @@ -294,6 +294,11 @@ "side": "west", "to_map": "Route4", "offset": 10 + }, + { + "side": "east", + "to_map": "Route9", + "offset": 10 } ] } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10.json new file mode 100644 index 0000000000..3f3acb6179 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10.json @@ -0,0 +1,321 @@ +{ + "name": "Route10", + "source": { + "decomp_file": "data/maps/Route10/map.json", + "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", + "date": "2026-10-31" + }, + "tiles": [ + "########################", + "##wwwwwwwwwwwwwwwwwwww##", + "##wwwwwwwwwwwwwwwwwwww##", + "##wwwwwwwwwwwwwwwwwwww##", + "##.................www##", + "####..#############www##", + "##....~~~~~~~~~~~.#www##", + "##....~~~~~~~~~~~.#www##", + "......~~~~~~~~~~~.#www##", + "......~~~~~~~~~~~.#www##", + "......~~~~~~~~~~~.#www##", + "##................#www##", + "##vvvvvvvvvvvvv..##www##", + "##................#www##", + "##................#www##", + "###########.......#www##", + "###########.......#www##", + "################..#www##", + "################..#www##", + "########.#######..#www##", + "##.........SSD##..#www##", + "##..###S##........#www##", + "##................#www##", + "##........#.......#www##", + "##................#www##", + "##........#.......#www##", + "##................#www##", + "##........#.......#www##", + "##................#www##", + "##................#www##", + "###################www##", + "#################..www##", + "#################..www##", + "#################..www##", + "#################..www##", + "#################..www##", + "#.###############..www##", + "#..##############..www##", + "#..##############..www##", + "#.###############..www##", + "#vv####.####.####..www##", + "#............###...www##", + "##....S......###...www##", + "##...........###...www##", + "##.................www##", + "##.................www##", + "##...............wwwww##", + "##wwwwwwwwwwwwwwwwwwww##", + "##wwwwwwwwwwwwwwwwwwww##", + "##wwwwwwwwwwwwwwwwwwww##", + "########################", + "########################", + "########################", + "########################", + "########################", + "########################", + "#################.....##", + "########.########.....##", + "###...................##", + "###............S......##", + "###...................##", + "###.........#####.....##", + "##..........#####v.vvv##", + "##...############.....##", + "##...############.....##", + "##vvv############.....##", + "##...############vvvv.##", + "##...############.....##", + "##....................##", + "##....................##", + "##....................##", + "###########...........##", + "###########...........##", + "###########...........##", + "###########vv.##########", + "###########...##########", + "########......##########", + "########......##########", + "########.....###########", + "########.....###########" + ], + "elevation": [ + "000000000000000000000000", + "001111111111111111111100", + "001111111111111111111100", + "001111111111111111111100", + "003333333333333333311100", + "000033000000000000011100", + "003333333333333333011100", + "003333333333333333011100", + "333333333333333333011100", + "333333333333333333011100", + "333333333333333333011100", + "003333333333333333011100", + "000000000000000330011100", + "003333333333333333011100", + "003333333333333333011100", + "000000000003333333011100", + "000000000003333333011100", + "000000000000000033011100", + "000000000000000033011100", + "000000003000000033011100", + "003333333330000033011100", + "003300000033333333011100", + "003333333333333333011100", + "003333333303333333011100", + "003333333333333333011100", + "003333333303333333011100", + "003333333333333333011100", + "003333333303333333011100", + "003333333333333333011100", + "003333333333333333011100", + "000000000000000000011100", + "000000000000000003311100", + "000000000000000003311100", + "000000000000000003311100", + "000000000000000003311100", + "000000000000000003311100", + "030000000000000003311100", + "033000000000000003311100", + "033000000000000003311100", + "030000000000000003311100", + "000000030000300003311100", + "033333333333300033311100", + "003333033333300033311100", + "003333333333300033311100", + "003333333333333333311100", + "003333333333333333311100", + "003333333333333331111100", + "001111111111111111111100", + "001111111111111111111100", + "001111111111111111111100", + "000000000000000000000000", + "000000000000000000000000", + "000000000000000000000000", + "000000000000000000000000", + "000000000000000000000000", + "000000000000000000000000", + "000000000000000003333300", + "000000003000000003333300", + "000333333333333333333300", + "000333333333333033333300", + "000333333333333333333300", + "000333333333000003333300", + "003333333333000000300000", + "003330000000000003333300", + "003330000000000003333300", + "000000000000000003333300", + "003330000000000000000300", + "003330000000000003333300", + "003333333333333333333300", + "003333333333333333333300", + "003333333333333333333300", + "000000000003333333333300", + "000000000003333333333300", + "000000000003333333333300", + "000000000000030000000000", + "000000000003330000000000", + "000000003333330000000000", + "000000003333330000000000", + "000000003333300000000000", + "000000003333300000000000" + ], + "points": { + "warp_3": [13, 20] + }, + "warps": [ + { + "x": 8, + "y": 19, + "enter": "cave_entrance" + }, + { + "x": 8, + "y": 57, + "enter": "cave_entrance" + }, + { + "x": 7, + "y": 40, + "enter": "cave_entrance" + }, + { + "x": 13, + "y": 20, + "enter": "walk_into", + "to_map": "Route10_PokemonCenter_1F", + "to_point": "warp_1" + }, + { + "x": 2, + "y": 37, + "enter": "walk_east" + } + ], + "objects": [ + { + "name": "picnicker", + "kind": "npc", + "x": 7, + "y": 60, + "trainer": { + "sight": 2, + "looks": [ + "north", + "east", + "south", + "west" + ] + } + }, + { + "name": "hiker", + "kind": "npc", + "x": 4, + "y": 62, + "trainer": { + "sight": 1, + "looks": [ + "north", + "east", + "south", + "west" + ] + } + }, + { + "name": "poke_maniac", + "kind": "npc", + "x": 15, + "y": 70, + "wander": { + "x0": 13, + "y0": 70, + "x1": 17, + "y1": 70 + }, + "trainer": { + "sight": 2, + "looks": [ + "west", + "east" + ] + } + }, + { + "name": "hiker", + "kind": "npc", + "x": 4, + "y": 68, + "facing": "west", + "trainer": { + "sight": 2 + } + }, + { + "name": "poke_maniac", + "kind": "npc", + "x": 13, + "y": 44, + "facing": "west", + "trainer": { + "sight": 5 + } + }, + { + "name": "picnicker", + "kind": "npc", + "x": 9, + "y": 27, + "facing": "south", + "trainer": { + "sight": 2 + } + }, + { + "name": "cut_tree", + "kind": "npc", + "x": 10, + "y": 20, + "hide_flag": "FLAG_TEMP_12" + }, + { + "name": "cut_tree", + "kind": "npc", + "x": 10, + "y": 22, + "hide_flag": "FLAG_TEMP_13" + }, + { + "name": "cut_tree", + "kind": "npc", + "x": 10, + "y": 24, + "hide_flag": "FLAG_TEMP_14" + }, + { + "name": "cut_tree", + "kind": "npc", + "x": 10, + "y": 26, + "hide_flag": "FLAG_TEMP_15" + } + ], + "flag_sets": {}, + "connections": [ + { + "side": "west", + "to_map": "Route9", + "offset": 0 + } + ] +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10_PokemonCenter_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10_PokemonCenter_1F.json new file mode 100644 index 0000000000..4f4c5f3a1d --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10_PokemonCenter_1F.json @@ -0,0 +1,110 @@ +{ + "name": "Route10_PokemonCenter_1F", + "source": { + "decomp_file": "data/maps/Route10_PokemonCenter_1F/map.json", + "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", + "date": "2026-10-31" + }, + "allow_running": false, + "tiles": [ + "#.#############", + "###############", + "....###..##....", + "....#######....", + "...............", + "...............", + "#..........##..", + "...........##..", + "...............", + "###############" + ], + "elevation": [ + "030000000000000", + "000000000000000", + "333300033003333", + "333300000003333", + "333333333333333", + "330333333333333", + "044033333330033", + "330333333330033", + "333333333333333", + "000000000000000" + ], + "points": { + "warp_1": [7, 8] + }, + "warps": [ + { + "x": 6, + "y": 8, + "enter": "walk_into", + "to_map": "Route10", + "to_point": "warp_3" + }, + { + "x": 7, + "y": 8, + "enter": "walk_south", + "to_map": "Route10", + "to_point": "warp_3" + }, + { + "x": 8, + "y": 8, + "enter": "walk_into", + "to_map": "Route10", + "to_point": "warp_3" + }, + { + "x": 1, + "y": 6, + "enter": "step_on" + } + ], + "objects": [ + { + "name": "nurse", + "kind": "npc", + "x": 7, + "y": 2 + }, + { + "name": "gentleman", + "kind": "npc", + "x": 9, + "y": 4, + "wander": { + "x0": 8, + "y0": 4, + "x1": 10, + "y1": 4 + } + }, + { + "name": "fat_man", + "kind": "npc", + "x": 2, + "y": 3, + "wander": { + "x0": 1, + "y0": 3, + "x1": 3, + "y1": 3 + } + }, + { + "name": "youngster", + "kind": "npc", + "x": 14, + "y": 6 + }, + { + "name": "scientist", + "kind": "npc", + "x": 12, + "y": 5 + } + ], + "flag_sets": {}, + "connections": [] +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route9.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route9.json new file mode 100644 index 0000000000..dae6c11e89 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route9.json @@ -0,0 +1,175 @@ +{ + "name": "Route9", + "source": { + "decomp_file": "data/maps/Route9/map.json", + "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", + "date": "2026-10-31" + }, + "tiles": [ + "########################################################################", + "......#~~~~~~~~~~~............######~~~~~~######.................#######", + "#######~~~~~~~~~~~............######~~~~~~######.................#######", + "#######~~~~~~~~~~~#####.......######......######..........#####..#######", + "#######~~~~~~~~~~~#####vvvvv..######vv..vv######vv..vvvvvv#####vv#######", + "#######~~~~~~~~~~~#####.......######......######..........#####..#######", + "......#...........#####.......######......................#####..#######", + "#######vvvvvvvv#..#####......S###########.................#####..#######", + "...............########.............#####....#####...##########.........", + "..#............########.............#####....#####...#####..............", + "###########....########vvvvvvvvvvv..#####....#####...#####..............", + "....#######vvvv########.............#####vvvv#####vv.#####...#####...###", + "###########....########.............#####....#####...#####vvv#####vvv###", + "###########..........................................#####...#####...###", + "###########...............######.....................#####...#####...###", + "###########vvvvvvvvv..vvvv######vv..vvvvvvvvvvvvvvvvv#####vvv#####vvv###", + "###########...............######.....................................###", + "###########...............######.....................................###", + "########################################################################", + "########################################################################" + ], + "points": {}, + "warps": [], + "objects": [ + { + "name": "picnicker", + "kind": "npc", + "x": 14, + "y": 10, + "facing": "west", + "trainer": { + "sight": 3 + } + }, + { + "name": "hiker", + "kind": "npc", + "x": 20, + "y": 17, + "trainer": { + "sight": 4, + "looks": [ + "north", + "west" + ] + } + }, + { + "name": "hiker", + "kind": "npc", + "x": 61, + "y": 16, + "facing": "south", + "trainer": { + "sight": 1 + } + }, + { + "name": "camper", + "kind": "npc", + "x": 26, + "y": 7, + "trainer": { + "sight": 3, + "looks": [ + "north", + "east", + "south", + "west" + ] + } + }, + { + "name": "bug_catcher", + "kind": "npc", + "x": 26, + "y": 1, + "facing": "south", + "trainer": { + "sight": 2 + } + }, + { + "name": "bug_catcher", + "kind": "npc", + "x": 49, + "y": 7, + "facing": "east", + "trainer": { + "sight": 3 + } + }, + { + "name": "hiker", + "kind": "npc", + "x": 53, + "y": 2, + "wander": { + "x0": 50, + "y0": 2, + "x1": 56, + "y1": 2 + }, + "trainer": { + "sight": 1, + "looks": [ + "west", + "east" + ] + } + }, + { + "name": "picnicker", + "kind": "npc", + "x": 60, + "y": 9, + "facing": "east", + "trainer": { + "sight": 4 + } + }, + { + "name": "camper", + "kind": "npc", + "x": 36, + "y": 6, + "facing": "east", + "trainer": { + "sight": 5 + } + }, + { + "name": "cut_tree", + "kind": "npc", + "x": 2, + "y": 8, + "hide_flag": "FLAG_TEMP_12" + }, + { + "name": "item_ball", + "kind": "item", + "x": 12, + "y": 17, + "hide_flag": "FLAG_HIDE_ROUTE9_TM40" + }, + { + "name": "item_ball", + "kind": "item", + "x": 65, + "y": 17, + "hide_flag": "FLAG_HIDE_ROUTE9_BURN_HEAL" + } + ], + "flag_sets": {}, + "connections": [ + { + "side": "west", + "to_map": "CeruleanCity", + "offset": -10 + }, + { + "side": "east", + "to_map": "Route10", + "offset": 0 + } + ] +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.cpp index ac63176941..e31e98db16 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.cpp @@ -74,6 +74,10 @@ const char* const MAP_NAMES[] = { // The BIKE VOUCHER and the BICYCLE. "VermilionCity_PokemonFanClub", "CeruleanCity_BikeShop", + // East over Route 9 to Route 10's Pokemon Center. + "Route9", + "Route10", + "Route10_PokemonCenter_1F", "CeruleanCave_1F", "CeruleanCave_2F", "CeruleanCave_B1F", diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryBattleData.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryBattleData.cpp index 6dc31f2431..66dbac9de5 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryBattleData.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryBattleData.cpp @@ -70,6 +70,11 @@ constexpr StoryTrainer STORY_TRAINERS[] = { {.map = "VermilionCity_Gym", .battle = "engineer_baily", .trainer = "TRAINER_ENGINEER_BAILY"}, {.map = "VermilionCity_Gym", .battle = "gentleman_tucker", .trainer = "TRAINER_GENTLEMAN_TUCKER"}, {.map = "VermilionCity_Gym", .battle = "lt_surge", .trainer = "TRAINER_LEADER_LT_SURGE"}, + // Route 9's three the way can't walk around (Route9 scripts.inc): + // Alicia (14, 10), Jeremy (20, 17) and Conner (49, 7). + {.map = "Route9", .battle = "picnicker_alicia", .trainer = "TRAINER_PICNICKER_ALICIA"}, + {.map = "Route9", .battle = "hiker_jeremy", .trainer = "TRAINER_HIKER_JEREMY"}, + {.map = "Route9", .battle = "bug_catcher_conner", .trainer = "TRAINER_BUG_CATCHER_CONNER"}, }; using JsonTable = std::map>; diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp index 73f3f34a0f..e2949a9723 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp @@ -6,7 +6,8 @@ * overworld sim: 00.26 from where 00.25 ends, to the Fan Club's chairman, * north past Route 6's trainers, through the tree into Cerulean, to the * Bike Shop's clerk, the Mart and the nurse, with Oak's aide on - * Vermilion's (25, 7); and what its Mart step buys. + * Vermilion's (25, 7); and what its Mart step buys. 00.27 from where + * 00.26 ends, east over Route 9 to Route 10's Pokemon Center. * */ @@ -117,21 +118,31 @@ std::optional last_target(const StoryCheckpoint& row){ } -// 00.26 comes right after 00.25 and starts where it ends, at Vermilion's -// nurse. -UnitTestResult test_starts_where_lt_surge_ends(){ - if (row_index("00.26") != row_index("00.25") + 1){ - return "Expected 00.26 right after 00.25."; +// Row `id` comes right after row `previous` and starts where its last +// walk ends. +UnitTestResult starts_where_ends(const std::string& previous, const std::string& id){ + if (row_index(id) != row_index(previous) + 1){ + return "Expected " + id + " right after " + previous + "."; } - const std::optional end = last_target(checkpoint("00.25")); - const StoryCheckpoint& row = checkpoint("00.26"); + const std::optional end = last_target(checkpoint(previous)); + const StoryCheckpoint& row = checkpoint(id); const std::optional start = row.start ? story_pose(*row.start) : std::nullopt; if (!end || !start || start->map != end->map || !(start->tile == end->tile) || start->facing != end->facing){ - return "Expected 00.26 to start where 00.25's walk to the nurse ends."; + return "Expected " + id + " to start where " + previous + "'s last walk ends."; } return true; } +// 00.26 starts at Vermilion's nurse, where 00.25 ends. +UnitTestResult test_starts_where_lt_surge_ends(){ + return starts_where_ends("00.25", "00.26"); +} + +// 00.27 starts at Cerulean's nurse, where 00.26 ends. +UnitTestResult test_starts_where_bicycle_ends(){ + return starts_where_ends("00.26", "00.27"); +} + // 00.26 on the sim from Vermilion's nurse to Cerulean's, the maps as // 00.25 leaves them: no trainer meets the player (Isabelle and Elijah // stay unfought), no violation, each talk faces the one it's for (the @@ -211,6 +222,48 @@ UnitTestResult test_repels_and_escape_ropes(){ return true; } +// 00.27 on the sim from Cerulean's nurse to Route 10's, the maps as 00.26 +// leaves them, both trees grown back: the trees cut, Alicia, Jeremy and +// Conner fought where the table steps into their lines and no one else +// met (Brice, Caitlin and the rest unfought), no violation, each step +// ending where the table says, and the walk ending at Route 10's nurse. +UnitTestResult test_route10_row(){ + const StoryCheckpoint& row = checkpoint("00.27"); + const std::vector maps = maps_after_surge(); + StoryPose at = *story_pose(*row.start); + OverworldSim sim(maps, MapPosition{at.map, at.tile}, *at.facing); + for (const StoryStep& step : row.steps){ + if (const std::string failure = row_step_on_sim(sim, at, step, row.id); !failure.empty()){ + return failure; + } + // A step into a trainer's line ends where the battle after it says. + const bool into_line = step.kind == StoryStepKind::STEP && !step.variant.empty(); + if (!into_line && !(sim.state().position == MapPosition{at.map, at.tile})){ + return std::format("00.27/{}: expected {}, on {}.", step.name, to_string(MapPosition{at.map, at.tile}), to_string(sim.state().position)); + } + } + if (sim.state().trainers_beaten != std::vector{"picnicker", "hiker", "bug_catcher"}){ + return "00.27: expected to fight Alicia, Jeremy and Conner, no one else; fought " + std::to_string(sim.state().trainers_beaten.size()) + "."; + } + if (!sim.state().violations.empty()){ + return "00.27: a violation on the way: " + sim.state().violations.back(); + } + const MapPosition nurse{StoryTable::ROUTE10_CENTER, {7, 4}}; + if (!(sim.state().position == nurse)){ + return "00.27: ended on " + to_string(sim.state().position) + ", not Route 10's nurse."; + } + return true; +} + +// The next row starts from 00.27's save in Route 10's Center, whose save +// card says ROUTE 10: a place of its own, not Route 1's. +UnitTestResult test_route10_save_place(){ + if (save_card_place(StoryTable::ROUTE10_CENTER) != "route-10" || save_card_place("Route1") == "route-10"){ + return "Wrong save card place for Route 10's Pokemon Center."; + } + return true; +} + } @@ -218,6 +271,9 @@ void add_tests_AutoStoryCeladon(UnitTestDatabase& database){ database.add("00.26 starts where 00.25 ends", test_starts_where_lt_surge_ends); database.add("00.26 the Fan Club, Route 6 and the Underground Path north, the Bike Shop, past Oak's aide", test_bike_voucher_row); database.add("00.26 Repels and Escape Ropes, 3000 left", test_repels_and_escape_ropes); + database.add("00.27 starts where 00.26 ends", test_starts_where_bicycle_ends); + database.add("00.27 the two trees, Route 9's Alicia, Jeremy and Conner, to Route 10's Pokemon Center", test_route10_row); + database.add("00.27 saves in Route 10's Pokemon Center, ROUTE 10 on the save card", test_route10_save_place); } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp index 374d404311..9f969c6317 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp @@ -380,6 +380,7 @@ std::string save_card_place(const std::string& map){ {"Route24", "route-24"}, {"Route25", "route-25"}, {"VermilionCity", "vermilion-city"}, + {"Route10", "route-10"}, }; for (const auto& [prefix, place] : PLACES){ if (map == prefix || map.rfind(prefix + "_", 0) == 0){ diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.cpp index aafc6eb001..97908546db 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.cpp @@ -46,11 +46,24 @@ std::string cut_on_sim(OverworldSim& sim, StoryPose& at, const StoryStep& step){ return ""; } +// A hop() or step_onto(): one step, ending where the table says. Moves +// `at` there; returns the failure, or empty. +std::string step_onto_on_sim(OverworldSim& sim, StoryPose& at, const StoryStep& step){ + if (sim.state().facing != step.direction){ + sim.tap(step.direction); + } + sim.tap(step.direction); + at = *story_pose(*step.pose_after); + return sim.state().position == MapPosition{at.map, at.tile} ? "" : "expected to end on " + to_string(MapPosition{at.map, at.tile}) + "."; +} + } // Runs `step` of row `id` on `sim` as the program does: routes and legs // by the sim's walker from `at`, where the table says the player stands, -// fighting a trainer who sees the player on the way; a step one tap at a +// fighting a trainer who sees the player on the way; a hop down a ledge +// (hop()) or a step onto the tile before one (step_onto()), to where the +// table says; a step one tap at a // time, or for a step into a trainer's line by step_until_seen(), each // watched for the trainer; a battle from the trainer's message, which // must be up; a cut tree cut and stepped onto. Moves `at` to where the @@ -69,6 +82,10 @@ std::string row_step_on_sim(OverworldSim& sim, StoryPose& at, const StoryStep& s at = route ? StoryPose{route->back().end.map, route->back().end.point, route->back().facing_after} : at; return failure.empty() ? "" : where + failure + on(); } + if (step.kind == StoryStepKind::STEP && step.variant.empty() && step.pose_after){ + const std::string failure = step_onto_on_sim(sim, at, step); + return failure.empty() ? "" : where + failure + on(); + } if (step.kind == StoryStepKind::STEP){ const auto tap = [&sim, &step]{ if (sim.state().facing != step.direction){ diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.h index 3ffb4cbcf7..fa709bdc69 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRowSim.h @@ -29,7 +29,9 @@ std::optional> walked_route(const StoryPose& at, const St // Runs `step` of row `id` on `sim` as the program does: routes and legs // by the sim's walker from `at`, where the table says the player stands, -// fighting a trainer who sees the player on the way; a step one tap at a +// fighting a trainer who sees the player on the way; a hop down a ledge +// (hop()) or a step onto the tile before one (step_onto()), to where the +// table says; a step one tap at a // time, or for a step into a trainer's line by step_until_seen(), each // watched for the trainer; a battle from the trainer's message, which // must be up; a cut tree cut and stepped onto. Moves `at` to where the diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp index 2ca095e21e..2d2300044a 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryRunPlan_Tests.cpp @@ -118,7 +118,7 @@ class Test_RunPlan : public UnitTest{ }; void add_tests_RunPlan(UnitTestDatabase& database){ - const std::string TODAY = "00.01 00.02 00.03 00.04 00.05 00.06 00.07 00.08 00.09 00.10 00.11 00.12 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26"; + const std::string TODAY = "00.01 00.02 00.03 00.04 00.05 00.06 00.07 00.08 00.09 00.10 00.11 00.12 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26 00.27"; const RunPlanScenario scenarios[] = { {.name = "handoffs off: the story as before", .first = "00.01", .expected_ids = TODAY}, {.name = "handoffs off: a range", .first = "00.02", .last = "00.04", .expected_ids = "00.02 00.03 00.04"}, @@ -171,8 +171,8 @@ void add_tests_GiftRunPlan(UnitTestDatabase& database){ .expected_ids = "00.11 00.12", .expected_stop = StoryHandoff::GIFT_RNG, }, - {.name = "Gift RNG off: 00.12b is passed over", .first = "00.11", .expected_ids = "00.11 00.12 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26"}, - {.name = "Gift RNG off: 00.12b runs when started at", .first = "00.12b", .expected_ids = "00.12b 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26"}, + {.name = "Gift RNG off: 00.12b is passed over", .first = "00.11", .expected_ids = "00.11 00.12 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26 00.27"}, + {.name = "Gift RNG off: 00.12b runs when started at", .first = "00.12b", .expected_ids = "00.12b 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26 00.27"}, { .name = "Gift RNG: started at 00.12b, from the save beside the salesman", .first = "00.12b", @@ -187,7 +187,7 @@ void add_tests_GiftRunPlan(UnitTestDatabase& database){ .expected_ids = "", .expected_stop = StoryHandoff::GIFT_RNG, }, - {.name = "Starter RNG alone: 00.12b runs when started at", .first = "00.12b", .handoffs = {.starter_rng = true}, .expected_ids = "00.12b 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26"}, + {.name = "Starter RNG alone: 00.12b runs when started at", .first = "00.12b", .handoffs = {.starter_rng = true}, .expected_ids = "00.12b 00.12c 00.13 00.14 00.14b 00.14c 00.15 00.15b 00.16 00.17 00.18 00.19 00.20 00.21 00.22 00.23 00.24 00.25 00.26 00.27"}, { .name = "Starter RNG first, with both on", .first = "00.03", diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp index 1da9219118..79f27f6e73 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp @@ -792,7 +792,7 @@ const std::vector& story_checkpoints(){ running_shoes(), route3(), magikarp_salesman(), after_gift_rng(), magikarp_to_box(), mt_moon(), route4_to_cerulean(), cerulean_gym(), misty(), cerulean_grind(), cerulean_rival(), nugget_bridge(), route25(), bill(), back_to_cerulean(), oddish(), to_vermilion(), ss_anne(), - vermilion_gym(), to_lt_surge(), lt_surge(), bike_voucher(), + vermilion_gym(), to_lt_surge(), lt_surge(), bike_voucher(), route10_center(), }; return checkpoints; } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp index 3b11f0c819..a1a9cca889 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp @@ -77,6 +77,76 @@ StoryCheckpoint bike_voucher(){ }); } +// East out of Cerulean over Route 9 to Route 10's Pokemon Center +// (pokeguy 45:12 to 47:27), on foot: the Bicycle isn't registered yet. +// - Cerulean (CeruleanCity map.json): the way east, column 39..40 up +// from row 33 to rows 18..19 and out of the city's east edge, is +// reached from the city only through the tree (26, 32), grown back +// since 00.26 (its flag is a FLAG_TEMP), or the burgled house. Cut +// from above. Route 9 joins the city's rows 18..19 by its rows 8..9 +// (offset -10); its tree (2, 8) (FLAG_TEMP_12) closes them, (2, 9) +// being rock: cut from (1, 8), west of it. +// - Route 9 (Route9 map.json, scripts.inc), its ledges all facing south. +// Of its nine trainers, three can't be walked around, as in pokeguy's +// run, where Jeremy happened to look away: +// - Picnicker Alicia (14, 10) faces west, sight 3, over (11..13, 10), +// the only tiles above the ledge row 11 the way west goes down by: +// fought on the step onto (11, 10); she walks up to (12, 10). Then +// down the ledge (11, 11). +// - Hiker Jeremy (20, 17) looks north and west (FACE_UP_AND_LEFT), +// sight 4: north over (20, 13..16), every way east of column 19 +// below the rocks of row 12. Fought on (20, 13), waiting for him to +// look north (TRAINER_WAIT_VARIANT); he walks up to (20, 14). +// - Bug Catcher Conner (49, 7) faces east, sight 3, over (50..52, 7): +// the way north from row 13 is column 52 alone (the ledges of row +// 11 face south), and row 7 is the way onto the ridge. Fought on the +// step onto (52, 7); he walks up to (51, 7). +// - Hiker Brice wanders row 2 (50..56), looking west and east, sight +// 1, over (49..57, 2), and the way up from the ledges of row 4 is +// (50..51, 4) below him. The leg up column 51 ends on row 3 if he +// stands at (51, 2), else it goes on to (51, 1) past him, and he may +// see the player there; either way the legs to walls after it meet +// on (48, 1): west to the rock (48, 3) or (48, 1), up to (48, 1). +// Row 1 is clear of his sight; east along it to the rock (64, 1) +// and down column 64 past the hidden Rare Candy (63, 2), not picked +// up. A held leg down to (64, 3) would hop the ledge (64, 4) and go +// on, so the last tile is one step, then the hop. +// - Picnicker Caitlin (60, 9) faces east over (61..64, 9): the way to +// Route 10 goes along row 8, above her line, by legs to (70, 8), a +// tile short of the edge. Camper Chris (26, 7), +// Bug Catcher Brent (26, 1), Camper Drew (36, 6) and Hiker Alan (61, +// 16) are off the way; TM40 (12, 17) is left. +// - Route 10 (Route10 map.json) joins Route 9's rows 8..10 by its own; +// its north part, down to the Center's door (13, 20), has no trainer +// on the way and its grass is walked round. The trees west of the +// door (10, 20..26) stay. +// - The nurse, and the save in Route 10's Center: the save card names +// ROUTE 10. +StoryCheckpoint route10_center(){ + return row("00.27", "East over Route 9 to Route 10's Pokemon Center: the two trees, Alicia, Jeremy and Conner", at(CERULEAN_CENTER, 7, 4, NORTH), after_brock(), StartMenu::FULL, { + running(route("above_cerulean_tree", at(CERULEAN, 26, 31, SOUTH), "out of the Center, south through the city to above the tree (26, 32), grown back")), + cut_tree("cut_cerulean_tree", at(CERULEAN, 26, 32, SOUTH), "the tree; a step onto its tile"), + running(route("west_of_route9_tree", at(ROUTE9, 1, 8, EAST), "down to row 33, east and up column 39 or 40 to row 18, east out of the city onto Route 9, west of its tree (2, 8)")), + cut_tree("cut_route9_tree", at(ROUTE9, 2, 8, EAST), "Route 9's tree; a step onto its tile"), + running(route("above_alicia_line", at(ROUTE9, 11, 9, SOUTH), "east to (11, 9), above the west end of Alicia's line")), + into_line("alicia_line", SOUTH, "onto (11, 10), in Alicia's line: she walks up to (12, 10)"), + battle("picnicker_alicia", at(ROUTE9, 11, 10, EAST), false, "Oddish, Bellsprout, Oddish and Bellsprout, Lv18"), + hop("alicia_ledge_hop", SOUTH, at(ROUTE9, 11, 12, SOUTH), "down the ledge (11, 11)"), + running(route("west_of_jeremy_sight", at(ROUTE9, 19, 13, EAST), "down to row 13 and east along it to (19, 13), west of Jeremy's north line")), + into_sight("jeremy_sight", EAST, "onto (20, 13), four above Jeremy (20, 17): when he looks north he walks up to (20, 14)"), + battle("hiker_jeremy", at(ROUTE9, 20, 13, SOUTH), false, "Machop and Onix, Lv20"), + running(route("below_conner_line", at(ROUTE9, 52, 8, NORTH), "east along row 13 and up column 52 to (52, 8), below the east end of Conner's line")), + into_line("conner_line", NORTH, "onto (52, 7), the end of Conner's line: he walks up to (51, 7)"), + battle("bug_catcher_conner", at(ROUTE9, 52, 7, WEST), false, "Caterpie, Weedle and Venonat, Lv20"), + running(legs("round_brice", {{NORTH, 2}, {WEST, 1}, {NORTH, 2}, {WEST, 3}, {NORTH, 2}, {EAST, 16}, {SOUTH, 1}}, "up to the ledge (52, 4), up column 51 to Brice's row, west to the rock (48, 3) (or (48, 1)), up to (48, 1), east along row 1 to the rock (64, 1), down to (64, 2)")), + step_onto("above_east_ledge", SOUTH, at(ROUTE9, 64, 3, SOUTH), "onto (64, 3), above the ledge (64, 4)"), + hop("east_ledge_hop", SOUTH, at(ROUTE9, 64, 5, SOUTH), "down the ledge (64, 4)"), + running(legs("above_caitlin_line", {{SOUTH, 3}, {EAST, 6}}, "down column 64 to row 8, east along it to (70, 8): above Caitlin's line (61..64, 9)")), + running(route("route10_nurse", at(ROUTE10_CENTER, 7, 4, NORTH), "east onto Route 10, round its grass and down to the Center's door (13, 20), to the nurse")), + heal("heal_route10"), + }); +} + } } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.cpp index cb9c7bccba..ecd1e48e4e 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.cpp @@ -69,6 +69,10 @@ StoryStep hop(std::string name, Direction direction, Pokemon::Pose landing, std: return step; } +StoryStep step_onto(std::string name, Direction direction, Pokemon::Pose tile, std::string why){ + return hop(std::move(name), direction, std::move(tile), std::move(why)); +} + StoryStep answer(std::string name, bool yes, std::string why){ StoryStep step = make(StoryStepKind::ANSWER, std::move(name), std::move(why)); step.yes = yes; diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h index 79ab143447..9f8b0d4968 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableSteps.h @@ -59,6 +59,9 @@ inline constexpr const char* SS_ANNE_2F = "SSAnne_2F_Corridor"; inline constexpr const char* CAPTAINS_OFFICE = "SSAnne_CaptainsOffice"; inline constexpr const char* FAN_CLUB = "VermilionCity_PokemonFanClub"; inline constexpr const char* BIKE_SHOP = "CeruleanCity_BikeShop"; +inline constexpr const char* ROUTE9 = "Route9"; +inline constexpr const char* ROUTE10 = "Route10"; +inline constexpr const char* ROUTE10_CENTER = "Route10_PokemonCenter_1F"; Pokemon::Pose at(const char* map, int x, int y, std::optional facing = std::nullopt); @@ -96,6 +99,9 @@ StoryStep into_sight(std::string name, Direction direction, std::string why); // ledge (they hold into walls, and a held ledge hops), so where the only // way on is down a ledge the table takes it as this one step. StoryStep hop(std::string name, Direction direction, Pokemon::Pose landing, std::string why); +// One step `direction` onto `tile`, the next one, where a walk's hold +// would go on: onto the tile before a ledge, which a held leg hops. +StoryStep step_onto(std::string name, Direction direction, Pokemon::Pose tile, std::string why); StoryStep answer(std::string name, bool yes, std::string why); StoryStep scene(std::string name, std::optional pose_after, std::string why); StoryStep battle(std::string name, Pokemon::Pose pose_after, bool may_lose, std::string why); @@ -121,6 +127,7 @@ StoryCheckpoint vermilion_gym(); StoryCheckpoint to_lt_surge(); StoryCheckpoint lt_surge(); StoryCheckpoint bike_voucher(); +StoryCheckpoint route10_center(); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp index 46731bcf9e..145111f1a9 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory_Tests.cpp @@ -217,8 +217,8 @@ UnitTestResult check_checkpoint(const StoryCheckpoint& checkpoint, StoryPose& po UnitTestResult test_routes_and_chain(){ const std::vector& checkpoints = story_checkpoints(); - if (checkpoints.size() != 33 || checkpoints[0].start){ - return "Expected 33 checkpoints, the first from a new game."; + if (checkpoints.size() != 34 || checkpoints[0].start){ + return "Expected 34 checkpoints, the first from a new game."; } StoryPose pose; for (size_t c = 0; c < checkpoints.size(); c++){ @@ -587,7 +587,7 @@ UnitTestResult check_lines(const StoryCheckpoint& checkpoint, std::set Date: Thu, 5 Nov 2026 12:26:58 -0800 Subject: [PATCH 04/13] FRLG: use shared wall recovery without changing navigation behavior (#228) * Extract common wall convergence search behind game movement adapters * Use shared wall convergence search for FRLG re-anchoring * Document wall recovery policy and expose span-based API before helpers * Show expected and actual wall holds in table-driven failures * Use static movement policies with explicit map context and document test rooms * Select static recovery policy with explicit FRLG world context --- .../PokemonFRLG_OverworldReanchor.cpp | 77 +++++++------------ 1 file changed, 26 insertions(+), 51 deletions(-) diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldReanchor.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldReanchor.cpp index 670cf0a2e9..d20f2e2844 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldReanchor.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldReanchor.cpp @@ -6,6 +6,7 @@ #include #include +#include "Pokemon/Navigation/Pokemon_WallRecovery.h" #include "PokemonFRLG_OverworldReanchor.h" namespace PokemonAutomation{ @@ -126,15 +127,6 @@ struct Hypothesis{ bool operator==(const Hypothesis& other) const = default; }; -bool converged(const std::vector& positions){ - for (const Hypothesis& position : positions){ - if (!(position.at == positions[0].at)){ - return false; - } - } - return true; -} - // Whether a person stands, or may wander, on a tile `wall` passes after // `from`. The step rules don't have people, but one there stops the player // short of the wall. @@ -183,41 +175,33 @@ std::optional, RouteStep>> hold_all( return std::make_pair(std::move(ends), hold); } -int total_tiles(const std::vector& legs){ - int total = 0; - for (const RouteStep& leg : legs){ - total += leg.tiles; - } - return total; -} +using Found = Pokemon::WallAnchor; -struct Found{ - std::vector holds; - MapPosition at; +// Runtime world alternatives and movement mode, separate from the policy type. +struct ReanchorContext{ + const std::vector>& worlds; + bool surfing = false; }; -// Every way of exactly `depth` holds that brings all of `positions` to one -// tile, in a fixed order. -void search( - const std::vector>& worlds, const std::vector& positions, bool surfing, - size_t depth, std::vector& holds, std::vector& found -){ - for (Direction direction : Pokemon::ALL_DIRECTIONS){ - auto next = hold_all(worlds, positions, surfing, direction); - if (!next || next->first == positions || next->second.tiles == 0){ - continue; - } - holds.push_back(next->second); - if (converged(next->first)){ - if (depth == 1){ - found.push_back(Found{holds, next->first[0].at}); - } - }else if (depth > 1){ - search(worlds, next->first, surfing, depth - 1, holds, found); +class ReanchorPolicy{ +public: + using Hypothesis = PokemonFRLG::Hypothesis; + using Position = MapPosition; + using Hold = RouteStep; + + static Position position(const Hypothesis& hypothesis){ return hypothesis.at; } + static int tiles(const Hold& hold){ return hold.tiles; } + static std::optional> hold( + const ReanchorContext& context, const std::vector& positions, Direction direction + ){ + auto next = hold_all(context.worlds, positions, context.surfing, direction); + if (!next){ + return std::nullopt; } - holds.pop_back(); + return Pokemon::WallRecoveryStep{std::move(next->first), std::move(next->second)}; } -} + +}; // Whether `route`, planned from `at` on other maps, walks the same on // `world`: each leg to a wall stops at its end, each open leg gets there. @@ -257,18 +241,9 @@ bool walks_same_everywhere( std::vector find_player( const std::vector>& worlds, const std::vector& positions, bool surfing ){ - std::vector found; - if (converged(positions)){ - found.push_back(Found{{}, positions[0].at}); - } - for (size_t depth = 1; found.empty() && depth <= MAX_HOLDS; depth++){ - std::vector holds; - search(worlds, positions, surfing, depth, holds, found); - std::stable_sort(found.begin(), found.end(), [](const Found& a, const Found& b){ - return total_tiles(a.holds) < total_tiles(b.holds); - }); - } - return found; + return Pokemon::find_wall_anchors( + positions, ReanchorContext{.worlds = worlds, .surfing = surfing}, {.max_holds = MAX_HOLDS} + ); } } From 8d6bbe7fb2fe26f08d1a521b9060d424a67aaa80 Mon Sep 17 00:00:00 2001 From: dayfine Date: Fri, 6 Nov 2026 20:01:28 -0800 Subject: [PATCH 05/13] Use string views for Celadon test checkpoint lookups --- .../AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp index c044040fdb..a8dda7e923 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "Common/Cpp/Exceptions.h" #include "Common/Cpp/TestRunners/UnitTest.h" @@ -67,16 +68,16 @@ class Test_AutoStoryCeladon : public UnitTest{ AutoStoryCeladonCase m_case; }; -size_t row_index(const std::string& id){ +size_t row_index(std::string_view id){ const std::vector& rows = story_checkpoints(); const auto row = std::find_if(rows.begin(), rows.end(), [&](const StoryCheckpoint& data){ return data.id == id; }); if (row == rows.end()){ - throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "No checkpoint " + id + "."); + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, std::format("No checkpoint {}.", id)); } return row - rows.begin(); } -const StoryCheckpoint& checkpoint(const std::string& id){ +const StoryCheckpoint& checkpoint(std::string_view id){ return story_checkpoints()[row_index(id)]; } From fa131a8b09669913808adf860c2358b7fdaafaf1 Mon Sep 17 00:00:00 2001 From: Di Fan <26423504+dayfine@users.noreply.github.com> Date: Fri, 6 Nov 2026 20:15:32 -0800 Subject: [PATCH 06/13] FRLG: build maps from pinned Packages resources (#238) * Navigation tests: match shared FileIO Windows compiler policy * Build FRLG map data from verified pinned Packages resources * Canonicalize embedded map dependencies to prevent duplicate Ninja outputs * Retire duplicate FRLG map builder through the Simulation battle-data exporter * Adopt published v2 FRLG resource pack and approved Simulation tools * Allow native map parity fixtures from pinned v2 publishers --- .../workflows/cpp-ci-serial-programs-base.yml | 15 + .github/workflows/runtime-map-reader.yml | 5 +- .gitignore | 3 + SerialPrograms/CMakeLists.txt | 4 +- .../Overworld/Maps/CeruleanCave_1F.json | 194 -------- .../Overworld/Maps/CeruleanCave_2F.json | 180 ------- .../Overworld/Maps/CeruleanCave_B1F.json | 159 ------- .../Overworld/Maps/CeruleanCity.json | 304 ------------ .../Overworld/Maps/CeruleanCity_BikeShop.json | 75 --- .../Overworld/Maps/CeruleanCity_Gym.json | 116 ----- .../Overworld/Maps/CeruleanCity_House2.json | 76 --- .../Overworld/Maps/CeruleanCity_Mart.json | 86 ---- .../Maps/CeruleanCity_PokemonCenter_1F.json | 126 ----- .../PokemonFRLG/Overworld/Maps/MtMoon_1F.json | 229 --------- .../Overworld/Maps/MtMoon_B1F.json | 121 ----- .../Overworld/Maps/MtMoon_B2F.json | 218 --------- .../Overworld/Maps/PalletTown.json | 122 ----- .../Maps/PalletTown_PlayersHouse_1F.json | 65 --- .../Maps/PalletTown_PlayersHouse_2F.json | 47 -- .../Maps/PalletTown_ProfessorOaksLab.json | 128 ----- .../Maps/PalletTown_RivalsHouse.json | 70 --- .../Overworld/Maps/PewterCity.json | 172 ------- .../Overworld/Maps/PewterCity_Gym.json | 80 ---- .../Overworld/Maps/PewterCity_Mart.json | 79 ---- .../Overworld/Maps/PewterCity_Museum_1F.json | 109 ----- .../Maps/PewterCity_PokemonCenter_1F.json | 111 ----- .../PokemonFRLG/Overworld/Maps/Route1.json | 91 ---- .../PokemonFRLG/Overworld/Maps/Route10.json | 321 ------------- .../Maps/Route10_PokemonCenter_1F.json | 110 ----- .../PokemonFRLG/Overworld/Maps/Route2.json | 211 --------- .../PokemonFRLG/Overworld/Maps/Route22.json | 90 ---- .../PokemonFRLG/Overworld/Maps/Route24.json | 183 -------- .../PokemonFRLG/Overworld/Maps/Route25.json | 200 -------- .../Overworld/Maps/Route25_SeaCottage.json | 66 --- .../Route2_ViridianForest_NorthEntrance.json | 78 --- .../Route2_ViridianForest_SouthEntrance.json | 78 --- .../PokemonFRLG/Overworld/Maps/Route3.json | 142 ------ .../PokemonFRLG/Overworld/Maps/Route4.json | 148 ------ .../Maps/Route4_PokemonCenter_1F.json | 112 ----- .../PokemonFRLG/Overworld/Maps/Route5.json | 86 ---- .../PokemonFRLG/Overworld/Maps/Route6.json | 176 ------- .../PokemonFRLG/Overworld/Maps/Route9.json | 175 ------- .../Overworld/Maps/SSAnne_1F_Corridor.json | 148 ------ .../Overworld/Maps/SSAnne_2F_Corridor.json | 108 ----- .../Overworld/Maps/SSAnne_CaptainsOffice.json | 42 -- .../Overworld/Maps/SSAnne_Exterior.json | 129 ----- .../Maps/UndergroundPath_NorthEntrance.json | 64 --- .../UndergroundPath_NorthSouthTunnel.json | 96 ---- .../Maps/UndergroundPath_SouthEntrance.json | 64 --- .../Overworld/Maps/VermilionCity.json | 246 ---------- .../Overworld/Maps/VermilionCity_Gym.json | 137 ------ .../Maps/VermilionCity_PokemonCenter_1F.json | 125 ----- .../Maps/VermilionCity_PokemonFanClub.json | 103 ---- .../Overworld/Maps/ViridianCity.json | 216 --------- .../Overworld/Maps/ViridianCity_Mart.json | 92 ---- .../Maps/ViridianCity_PokemonCenter_1F.json | 99 ---- .../Overworld/Maps/ViridianForest.json | 224 --------- .../Overworld/PokemonFRLG_OverworldMaps.h | 4 +- SerialPrograms/cmake/EmbedTextFiles.cmake | 5 +- .../cmake/PokesimMapResources.cmake | 15 + devtools/maps/RESOURCE_MAPS.md | 46 ++ devtools/maps/frlg_battle_data.py | 277 +---------- devtools/maps/frlg_maps.py | 444 ------------------ devtools/maps/map-resources.lock.json | 11 + devtools/maps/tests/test_frlg_maps.py | 90 ---- devtools/runtime-map-tests/CMakeLists.txt | 17 + devtools/runtime-map-tests/prepare_parity.py | 14 +- 67 files changed, 136 insertions(+), 7841 deletions(-) delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_1F.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_2F.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_B1F.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_BikeShop.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_Gym.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_House2.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_Mart.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_PokemonCenter_1F.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_1F.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_B1F.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_B2F.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_PlayersHouse_1F.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_PlayersHouse_2F.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_ProfessorOaksLab.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_RivalsHouse.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Gym.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Mart.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Museum_1F.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_PokemonCenter_1F.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route1.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10_PokemonCenter_1F.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route22.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route24.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route25.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route25_SeaCottage.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2_ViridianForest_NorthEntrance.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2_ViridianForest_SouthEntrance.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route3.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route4.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route4_PokemonCenter_1F.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route5.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route6.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route9.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_1F_Corridor.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_2F_Corridor.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_CaptainsOffice.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_Exterior.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_NorthEntrance.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_NorthSouthTunnel.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_SouthEntrance.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_Gym.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_PokemonCenter_1F.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_PokemonFanClub.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity_Mart.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity_PokemonCenter_1F.json delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianForest.json create mode 100644 SerialPrograms/cmake/PokesimMapResources.cmake create mode 100644 devtools/maps/RESOURCE_MAPS.md delete mode 100644 devtools/maps/frlg_maps.py create mode 100644 devtools/maps/map-resources.lock.json delete mode 100644 devtools/maps/tests/test_frlg_maps.py diff --git a/.github/workflows/cpp-ci-serial-programs-base.yml b/.github/workflows/cpp-ci-serial-programs-base.yml index 58f1a9d890..743c68fdbb 100644 --- a/.github/workflows/cpp-ci-serial-programs-base.yml +++ b/.github/workflows/cpp-ci-serial-programs-base.yml @@ -88,6 +88,19 @@ jobs: ref: 'master' path: 'Arduino-Source/Packages' + - name: Install Python for pinned map preparation + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Prepare approved FRLG map resources + id: map-resources + uses: dayfine/PokemonSimulation/integrations/arduino-source/verify-map-packs@906956e94fc912ae9adb0e44845da780f3bc7e4c + with: + layout: resources + operation: prepare + consumer-directory: Arduino-Source + - name: Install Qt uses: jurplel/install-qt-action@v4 with: @@ -204,6 +217,8 @@ jobs: # every file missed, compiling took 10-40% longer than before on Linux and macOS, and # about twice as long on Windows. CCACHE_DEPEND: 1 + POKESIM_TOOLS_SOURCE: ${{ steps.map-resources.outputs.tools-source }} + POKESIM_SOURCE_CACHE: ${{ steps.map-resources.outputs.source-cache }} run: | cd Arduino-Source/SerialPrograms mkdir bin diff --git a/.github/workflows/runtime-map-reader.yml b/.github/workflows/runtime-map-reader.yml index df64d64bbf..1fd59940a7 100644 --- a/.github/workflows/runtime-map-reader.yml +++ b/.github/workflows/runtime-map-reader.yml @@ -5,6 +5,7 @@ on: jobs: native-schema: strategy: + fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} @@ -13,12 +14,12 @@ jobs: - uses: actions/checkout@v7 with: repository: dayfine/PokemonSimulation - ref: 40717c7c809c415c751fd7a348dfc1895f7ec3d7 + ref: 906956e94fc912ae9adb0e44845da780f3bc7e4c path: publisher - uses: actions/setup-python@v6 with: python-version: '3.12' - - run: python devtools/runtime-map-tests/prepare_parity.py --publisher publisher --out runtime-map-pairs.json + - run: python devtools/runtime-map-tests/prepare_parity.py --publisher publisher --publisher-revision 906956e94fc912ae9adb0e44845da780f3bc7e4c --out runtime-map-pairs.json - run: cmake -S devtools/runtime-map-tests -B build-runtime-maps -DCMAKE_BUILD_TYPE=Debug - run: cmake --build build-runtime-maps --config Debug -j4 - run: ctest --test-dir build-runtime-maps -C Debug --output-on-failure diff --git a/.gitignore b/.gitignore index ef6f409ae9..a6b04ddffd 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,6 @@ Packages/* # Local agent/linter environments. .venv-code-health/ __pycache__/ + +# External PokemonSimulation tools/data cache; prepared maps live in ignored Packages. +.pokesim-map-source/ diff --git a/SerialPrograms/CMakeLists.txt b/SerialPrograms/CMakeLists.txt index 9b3302b834..bdf2e03615 100644 --- a/SerialPrograms/CMakeLists.txt +++ b/SerialPrograms/CMakeLists.txt @@ -197,7 +197,9 @@ 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/VideoGames/PokemonFRLG/Overworld/Maps +include(cmake/PokesimMapResources.cmake) +pa_frlg_map_resources(FRLG_MAP_RESOURCES) +pa_embed_text_files(SerialProgramsLib "${FRLG_MAP_RESOURCES}" VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.h PokemonAutomation::NintendoSwitch::PokemonFRLG overworld_map_json) pa_embed_text_files(SerialProgramsLib Source/VideoGames/PokemonFRLG/Programs/AutoStory/BattleData VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryBattleData.h PokemonAutomation::NintendoSwitch::PokemonFRLG autostory_battle_json) diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_1F.json deleted file mode 100644 index e1539f229f..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_1F.json +++ /dev/null @@ -1,194 +0,0 @@ -{ - "name": "CeruleanCave_1F", - "source": { - "decomp_file": "data/maps/CeruleanCave_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########################################", - "#######~~~##~#########wwwwwwwww#~~##~~##", - "#######~~~.~~#########wwwwwwwww#~~.~~~##", - "#######~~##~~#wwwwwwwwwwwwwwwww#~~~~~~##", - "##.####~~##~~#wwwwwwwwww#~~~#ww##.######", - "##~#~~~~~~~~~#wwwwwwwwww#~~~#wwwwwwwwww#", - "#~~#~~~~##~~~#www#~~~~~~~~~~#wwwwwwwwww#", - "#.~#~~~~##~~~#www#~~~~~~~~~~#wwwwwwwwww#", - "#~~#~~~~##~~~#www#~~~~~~~~~~~~~~~~~#www#", - "####~~~~##.###www#~~~######~~~#~~~~#www#", - "##~#~~~~#www##www#~~~##~####~~.~~~~#www#", - "##~~~~~~#www##www##.###~.~##~~~~~~~#www#", - "##~~~~~~#wwwwwwww#~~~~#~~~##~~~#####www#", - "##~~~~~~#wwwwwwww#~~#~##.###~~~###~~~~~#", - "##~~##~~#wwwwwwww#~~~~~~~~~#~~~#~~~~~~~#", - "##~~#.~~#www#~~~~##~~~~~~###~~~#~~~~~###", - "##~~~~~~#~~~#~~~~####~~~####~~~#~~~#####", - "##~~~~~~#~~~#~~~~~~~~~~~~~~~~~~#~~~#####", - "#####.###~~~##.#~~~~~~~~~~~~~~~#~~~#####", - "#####~~~~~~~~~~#~~~~~~~~~~~~~~~#~~~#####", - "#####~~~~~~~~~~#~~~~~~~~~~~~~~~#~~~#####", - "#####~~~~~~~~~~#################~.~#####", - "########################################" - ], - "elevation": [ - "0000000000000000000000000000000000000000", - "0000000333003000000000111111111033003300", - "0000000333333000000000111111111033333300", - "0000000330033011111111111111111033333300", - "0030000330033011111111110444011003000000", - "0030333333333011111111110444011111111110", - "0330333300333011104444444444011111111110", - "0330333300333011104444444444011111111110", - "0330333300333011104444444444444444401110", - "0000333300300011104440000004440444401110", - "0030333301110011104440030000444444401110", - "0033333301110011100000033300444444401110", - "0033333301111111103333033300444000001110", - "0033333301111111103303003000444000333330", - "0033003301111111103333333330444033333330", - "0033033301110444400333333000444033333000", - "0033333303330444400003330000444033300000", - "0033333303330444444444444444444033300000", - "0000030003330000444444444444444033300000", - "0000033333333330444444444444444033300000", - "0000033333333330444444444444444033300000", - "0000033333333330000000000000000033300000", - "0000000000000000000000000000000000000000" - ], - "points": { - "entrance": [33, 21], - "warp_0": [33, 21], - "warp_1": [34, 2], - "warp_2": [1, 7], - "warp_3": [10, 2], - "warp_4": [5, 15], - "warp_5": [30, 10], - "warp_6": [24, 11], - "warp_7": [2, 4] - }, - "warps": [ - { - "x": 33, - "y": 21, - "enter": "walk_south", - "to_map": "CeruleanCity", - "to_point": "warp_7" - }, - { - "x": 34, - "y": 2, - "enter": "step_on", - "to_map": "CeruleanCave_2F", - "to_point": "warp_0" - }, - { - "x": 1, - "y": 7, - "enter": "step_on", - "to_map": "CeruleanCave_B1F", - "to_point": "warp_0" - }, - { - "x": 10, - "y": 2, - "enter": "step_on", - "to_map": "CeruleanCave_2F", - "to_point": "warp_1" - }, - { - "x": 5, - "y": 15, - "enter": "step_on", - "to_map": "CeruleanCave_2F", - "to_point": "warp_2" - }, - { - "x": 30, - "y": 10, - "enter": "step_on", - "to_map": "CeruleanCave_2F", - "to_point": "warp_3" - }, - { - "x": 24, - "y": 11, - "enter": "step_on", - "to_map": "CeruleanCave_2F", - "to_point": "warp_4" - }, - { - "x": 2, - "y": 4, - "enter": "step_on", - "to_map": "CeruleanCave_2F", - "to_point": "warp_5" - } - ], - "objects": [ - { - "name": "item_ball", - "kind": "item", - "x": 7, - "y": 3, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_1F_NUGGET" - }, - { - "name": "item_ball", - "kind": "item", - "x": 11, - "y": 16, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_1F_FULL_RESTORE" - }, - { - "name": "item_ball", - "kind": "item", - "x": 25, - "y": 5, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_1F_MAX_ELIXIR" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 7, - "y": 21, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 13, - "y": 21, - "hide_flag": "FLAG_TEMP_13" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 5, - "y": 20, - "hide_flag": "FLAG_TEMP_14" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 14, - "y": 20, - "hide_flag": "FLAG_TEMP_15" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 11, - "y": 21, - "hide_flag": "FLAG_TEMP_16" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 9, - "y": 18, - "hide_flag": "FLAG_TEMP_17" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_2F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_2F.json deleted file mode 100644 index fa72ccc628..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_2F.json +++ /dev/null @@ -1,180 +0,0 @@ -{ - "name": "CeruleanCave_2F", - "source": { - "decomp_file": "data/maps/CeruleanCave_2F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########################################", - "########################################", - "########################################", - "####~~~~####~~##~~~~~#~~~~~~~~~~~~######", - "#######~~~~~~.#~~###~#~#########~.######", - "####~~####~####~##~#~#~~~~~~~~~#########", - "####~.~~~#~~~~~~~~~###########~~~~######", - "#######~############~~~~~~~~~####~######", - "####~#~~~~~~~~~~~~~~~#######~~~~#~######", - "####~#################~~#~.###~##~######", - "####~#~~~~#~~~~~~~~~~~~.#~~~#~~~#~######", - "####~#~##~#~#####~#######~#####~#~######", - "####~#~~#~#~~~~~#~~~~~~~#~~~~~#~#~######", - "####~#~~#~#####~#######~#####~#~########", - "####~#~.#~#~~~~~#~~~~~#~~~~~#~#~#~######", - "####~####~#~#####~###~#~#~#~#~#~#~######", - "####~#~~~~#~~~~~~~~~#~#~#~#~#~#~#~######", - "####~#~####~#########~#~#~#~###~#~######", - "####~#~~~~#~~~~~#~~~~~#~#~#~~~~~#~######", - "####~############~#####~#~#######~######", - "####~~~~~~~~~~~~~~#~~~~~#~~~~~~~~~######", - "########################################", - "########################################" - ], - "points": { - "warp_0": [33, 4], - "warp_1": [13, 4], - "warp_2": [7, 14], - "warp_3": [26, 9], - "warp_4": [23, 10], - "warp_5": [5, 6] - }, - "warps": [ - { - "x": 33, - "y": 4, - "enter": "step_on", - "to_map": "CeruleanCave_1F", - "to_point": "warp_1" - }, - { - "x": 13, - "y": 4, - "enter": "step_on", - "to_map": "CeruleanCave_1F", - "to_point": "warp_3" - }, - { - "x": 7, - "y": 14, - "enter": "step_on", - "to_map": "CeruleanCave_1F", - "to_point": "warp_4" - }, - { - "x": 26, - "y": 9, - "enter": "step_on", - "to_map": "CeruleanCave_1F", - "to_point": "warp_5" - }, - { - "x": 23, - "y": 10, - "enter": "step_on", - "to_map": "CeruleanCave_1F", - "to_point": "warp_6" - }, - { - "x": 5, - "y": 6, - "enter": "step_on", - "to_map": "CeruleanCave_1F", - "to_point": "warp_7" - } - ], - "objects": [ - { - "name": "item_ball", - "kind": "item", - "x": 9, - "y": 18, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_2F_PP_UP" - }, - { - "name": "item_ball", - "kind": "item", - "x": 29, - "y": 16, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_2F_ULTRA_BALL" - }, - { - "name": "item_ball", - "kind": "item", - "x": 33, - "y": 12, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_2F_FULL_RESTORE" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 33, - "y": 10, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 33, - "y": 9, - "hide_flag": "FLAG_TEMP_13" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 25, - "y": 11, - "hide_flag": "FLAG_TEMP_14" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 28, - "y": 20, - "hide_flag": "FLAG_TEMP_15" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 30, - "y": 20, - "hide_flag": "FLAG_TEMP_16" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 9, - "y": 13, - "hide_flag": "FLAG_TEMP_17" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 23, - "y": 16, - "hide_flag": "FLAG_TEMP_18" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 13, - "y": 6, - "hide_flag": "FLAG_TEMP_19" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 4, - "y": 12, - "hide_flag": "FLAG_TEMP_1A" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 10, - "y": 20, - "hide_flag": "FLAG_TEMP_1B" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_B1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_B1F.json deleted file mode 100644 index b06ee61a5f..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCave_B1F.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "name": "CeruleanCave_B1F", - "source": { - "decomp_file": "data/maps/CeruleanCave_B1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########################################", - "##~~~~~~~~~~~~~~##~~~~~############~~~~#", - "##~~~~~~~~~~~~~~#~~~~~~~~#######~~~~~~~#", - "##~~~~~~~~~~~~~~#~~####~~~~~#######~~~~#", - "##~~~~~~#####.###~~####~~~~~#######~~~~#", - "##~~~~~~#~~~~~~~~~~########~#######~#~~#", - "##~~##~~##~~~~~~~~#########~~~~~~~~~~~~#", - "##~~#.~~###~~~~~~##########~~~~~~~~~~~~#", - "##~~~~~~#wwwww##~~~~~~~~~~~~~~~~#~~~####", - "#########wwwww##~~~~~~~~~~~~~~~~#~~~####", - "#www############~~~~~~~~~~~~~~~~#~~~####", - "#www##############.########~###~#~~~####", - "#www#~#~#~########~~~~~~~~#~###~#~~~####", - "#www#~~~~~########~~~~~~~~#~~~~~#~~~####", - "#www#~~~~~########~~~~~~~~#~~~~~#~~~####", - "#www###.###wwww#~~~~~~#~~~#~###~#~~~####", - "#wwwwwwwwwwwwww#~~~~~~#~~~#~###~#~~~####", - "#wwwwwwwwwwwwww#~~~~~~#~~~#~~~~~#~~~####", - "#wwwwwwwwwwwwww#~~~~~~#~~~#~~~~~#~~~####", - "######wwwwwwwww##.##.##~~~###.###~~~####", - "######wwwwwwwwwwww#~~~~~~~###~~~~~~~####", - "######wwwwwwwwwwww##~~~~~####~~~~~~~####", - "########################################" - ], - "elevation": [ - "0000000000000000000000000000000000000000", - "0033333333333333003333300000000000033330", - "0033333333333333033333333000000033333330", - "0033333333333333033000033333000000033330", - "0033333300000300033000033333000000033330", - "0033333303333333333000000003000000030330", - "0033003300333333330000000003333333333330", - "0033033300033333300000000003333333333330", - "0033333301111100444444444444444403330000", - "0000000001111100444444444444444403330000", - "0111000000000000444444444444444403330000", - "0111000000000000000000000004000403330000", - "0111030303000000003333333304000403330000", - "0111033333000000003333333304444403330000", - "0111033333000000003333333304444403330000", - "0111000300011110444444033304000403330000", - "0111111111111110444444033304000403330000", - "0111111111111110444444033304444403330000", - "0111111111111110404444033304444403330000", - "0000001111111110030000033300000003330000", - "0000001111111111110333333300033333330000", - "0000001111111111110033333000033333330000", - "0000000000000000000000000000000000000000" - ], - "points": { - "mewtwo": [7, 12], - "warp_0": [5, 7] - }, - "warps": [ - { - "x": 5, - "y": 7, - "enter": "step_on", - "to_map": "CeruleanCave_1F", - "to_point": "warp_2" - } - ], - "objects": [ - { - "name": "item_ball", - "kind": "item", - "x": 31, - "y": 9, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_B1F_ULTRA_BALL" - }, - { - "name": "item_ball", - "kind": "item", - "x": 32, - "y": 2, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_B1F_MAX_REVIVE" - }, - { - "name": "mewtwo", - "kind": "npc", - "x": 7, - "y": 12, - "hide_flag": "FLAG_HIDE_MEWTWO" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 37, - "y": 1, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 38, - "y": 2, - "hide_flag": "FLAG_TEMP_13" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 35, - "y": 1, - "hide_flag": "FLAG_TEMP_14" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 37, - "y": 4, - "hide_flag": "FLAG_TEMP_15" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 35, - "y": 5, - "hide_flag": "FLAG_TEMP_16" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 2, - "y": 2, - "hide_flag": "FLAG_TEMP_17" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 4, - "y": 1, - "hide_flag": "FLAG_TEMP_18" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 6, - "y": 1, - "hide_flag": "FLAG_TEMP_19" - }, - { - "name": "rock_smash_rock", - "kind": "npc", - "x": 3, - "y": 4, - "hide_flag": "FLAG_TEMP_1A" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity.json deleted file mode 100644 index 003c2d8035..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity.json +++ /dev/null @@ -1,304 +0,0 @@ -{ - "name": "CeruleanCity", - "source": { - "decomp_file": "data/maps/CeruleanCity/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "################wwwww#...#...#wwwwwwwwwwwwwwwwww", - "################wwwww#...#vvv#wwwwwwwwwwwwwwwwww", - "####wwwwwwwwwwwwwwwww#...#...#wwwwwwwwwwwwwwwwww", - "####wwwwwwwwwwwwwwwww#...#...#wwwwwwwwwwwwwwwwww", - "####wwwwwwwwwwwwwwwww#...#...###################", - "####www###############...#...#.........#########", - "####www#.............#...#...#.........#########", - "####www#.............#...#...#.........#########", - "####www##...##########.......#...##...##########", - "####www###############......#######...##########", - "####www###############......#######...##########", - "####www###D######D####......##D####......#######", - "#.##www#..........................#......#######", - "....www#..........................#......#######", - "....www#..........................#......#######", - "....www#.....#######..............#......#######", - "wwwwwww#...vv############.........#......#......", - "wwwwwww#.....##D#########.........#......#######", - "wwwwwww#............#####vvv#######.............", - "########............SSD##...#######.............", - "............................#######vvv#..#######", - "...........................S..#D#.....#..#......", - "......................................#..#######", - "#######...............................#..#######", - "#######.....####......................#..#######", - "#######....#####....S.................#..#######", - "#######....#####.....###############..#..#######", - "#######....#####.....###############..#..#######", - "#######....S#..#.....##D###SSD######..#..#######", - "#######...............................#..#######", - "#######...............................#..#######", - "#######...............................#..#######", - "###################S######.############..#######", - "#######..................................#######", - "#######..................................#######", - "###############....#........#....###############", - "###############....#vvvvvvvv#....###############", - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#........#....###############" - ], - "elevation": [ - "000000000000000011111033303330100000000000000000", - "000000000000000011111033300000100000000000000000", - "000011111111111111111033303330100000000000000000", - "000011111111111111111033303330111111111111111111", - "000011111111111111111033303330000000000000000000", - "000011100000000000000033303330333333333000000000", - "000011103333333333333033303330333333333000000000", - "000011103333333333333033303330333333333000000000", - "000011100333000000000033333330333003330000000000", - "000011100000000000000033333300000003330000000000", - "000011100000000000000033333300000003330000000000", - "000011100000000000000033333300000003333330000000", - "030011103333333333333333333333333303333330000000", - "333311103333333333333333333333333303333330000000", - "333311103333333333333333333333333303333330000000", - "333311103333300000003333333333333303333330000000", - "111111103330000000000000033333333303333330333333", - "111111103333300000000000033333333303333330000000", - "111111103333333333330000000000000003333333333333", - "000000003333333333330000033300000003333333333333", - "333333333333333333333333333300000000000330000000", - "333333333333333333333333333033000333330330333333", - "333333333333333333333333333333333333330330000000", - "000000033333333333333333333333333333330330000000", - "000000033333000033333333333333333333330330000000", - "000000033330000033330333333333333333330330000000", - "000000033330000033333000000000000000330330000000", - "000000033330000033333000000000000000330330000000", - "000000033330033033333000000000000000330330000000", - "000000033333333333333333333333333333330330000000", - "000000033333333333333333333333333333330330000000", - "000000033333333333333333333333333333330330000000", - "000000000000000000000000003000000000000330000000", - "000000033333333333333333333333333333333330000000", - "000000033333333333333333333333333333333330000000", - "000000000000000333303333333303333000000000000000", - "000000000000000333300000000003333000000000000000", - "000000000000000333303333333303333000000000000000", - "000000000000000333303333333303333000000000000000", - "000000000000000333303333333303333000000000000000" - ], - "points": { - "warp_1": [30, 11], - "warp_3": [22, 19], - "warp_4": [31, 21], - "warp_5": [13, 28], - "warp_6": [29, 28], - "warp_7": [1, 12], - "warp_9": [31, 8] - }, - "warps": [ - { - "x": 10, - "y": 11, - "enter": "walk_into" - }, - { - "x": 30, - "y": 11, - "enter": "walk_into", - "to_map": "CeruleanCity_House2", - "to_point": "warp_1" - }, - { - "x": 15, - "y": 17, - "enter": "walk_into" - }, - { - "x": 22, - "y": 19, - "enter": "walk_into", - "to_map": "CeruleanCity_PokemonCenter_1F", - "to_point": "warp_1" - }, - { - "x": 31, - "y": 21, - "enter": "walk_into", - "to_map": "CeruleanCity_Gym", - "to_point": "warp_1" - }, - { - "x": 13, - "y": 28, - "enter": "cave_entrance", - "to_map": "CeruleanCity_BikeShop", - "to_point": "warp_1" - }, - { - "x": 29, - "y": 28, - "enter": "walk_into", - "to_map": "CeruleanCity_Mart", - "to_point": "warp_1" - }, - { - "x": 1, - "y": 12, - "enter": "cave_entrance", - "to_map": "CeruleanCave_1F", - "to_point": "warp_0" - }, - { - "x": 10, - "y": 8, - "enter": "walk_south" - }, - { - "x": 31, - "y": 8, - "enter": "walk_south", - "to_map": "CeruleanCity_House2", - "to_point": "warp_3" - }, - { - "x": 31, - "y": 9, - "enter": "walk_into", - "to_map": "CeruleanCity_House2", - "to_point": "warp_3" - }, - { - "x": 14, - "y": 28, - "enter": "cave_entrance", - "to_map": "CeruleanCity_BikeShop", - "to_point": "warp_1" - }, - { - "x": 23, - "y": 28, - "enter": "walk_into" - }, - { - "x": 17, - "y": 11, - "enter": "walk_into" - } - ], - "objects": [ - { - "name": "policeman", - "kind": "npc", - "x": 31, - "y": 12 - }, - { - "name": "rocket_m", - "kind": "npc", - "x": 33, - "y": 6, - "hide_flag": "FLAG_HIDE_CERULEAN_ROCKET" - }, - { - "name": "little_boy", - "kind": "npc", - "x": 16, - "y": 21, - "wander": { - "x0": 16, - "y0": 18, - "x1": 16, - "y1": 24 - } - }, - { - "name": "balding_man", - "kind": "npc", - "x": 9, - "y": 23, - "wander": { - "x0": 8, - "y0": 22, - "x1": 10, - "y1": 24 - } - }, - { - "name": "slowbro", - "kind": "npc", - "x": 32, - "y": 29 - }, - { - "name": "lass", - "kind": "npc", - "x": 33, - "y": 29 - }, - { - "name": "youngster", - "kind": "npc", - "x": 34, - "y": 22 - }, - { - "name": "blue", - "kind": "npc", - "x": 22, - "y": 0, - "hide_flag": "FLAG_HIDE_CERULEAN_RIVAL" - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 26, - "y": 32, - "hide_flag": "FLAG_TEMP_13" - }, - { - "name": "woman_1", - "kind": "npc", - "x": 12, - "y": 30 - }, - { - "name": "cooltrainer_m", - "kind": "npc", - "x": 1, - "y": 13, - "hide_flag": "FLAG_HIDE_CERULEAN_CAVE_GUARD" - } - ], - "flag_sets": { - "after_hall_of_fame": [ - "FLAG_HIDE_CERULEAN_CAVE_GUARD", - "FLAG_HIDE_CERULEAN_RIVAL", - "FLAG_HIDE_CERULEAN_ROCKET" - ] - }, - "connections": [ - { - "side": "north", - "to_map": "Route24", - "offset": 12 - }, - { - "side": "south", - "to_map": "Route5", - "offset": 0 - }, - { - "side": "west", - "to_map": "Route4", - "offset": 10 - }, - { - "side": "east", - "to_map": "Route9", - "offset": 10 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_BikeShop.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_BikeShop.json deleted file mode 100644 index ad5464b469..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_BikeShop.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "name": "CeruleanCity_BikeShop", - "source": { - "decomp_file": "data/maps/CeruleanCity_BikeShop/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "###########", - "###########", - ".####...#..", - "..#.##..#..", - ".#..#...###", - "..#.#.....#", - "........#..", - "..#........", - "..#......#.", - "###########" - ], - "points": { - "warp_1": [5, 8] - }, - "warps": [ - { - "x": 4, - "y": 9, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_5" - }, - { - "x": 5, - "y": 8, - "enter": "walk_south", - "to_map": "CeruleanCity", - "to_point": "warp_5" - }, - { - "x": 6, - "y": 9, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_5" - } - ], - "objects": [ - { - "name": "man", - "kind": "npc", - "x": 9, - "y": 3 - }, - { - "name": "youngster", - "kind": "npc", - "x": 9, - "y": 7, - "wander": { - "x0": 8, - "y0": 6, - "x1": 10, - "y1": 8 - } - }, - { - "name": "woman_2", - "kind": "npc", - "x": 5, - "y": 5 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_Gym.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_Gym.json deleted file mode 100644 index 519944eecb..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_Gym.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "name": "CeruleanCity_Gym", - "source": { - "decomp_file": "data/maps/CeruleanCity_Gym/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#################", - "#####w#w.w#w#####", - "#################", - "#...............#", - "#......www......#", - "#.wwwwwwwwwwwww.#", - "#.wwwww...wwwww.#", - "#.ww.........ww.#", - "#.wwwww...ww.ww.#", - "#.wwwwwwwwww.ww.#", - "#.ww.........ww.#", - "#.ww.wwww.wwwww.#", - "#.ww.wwww.wwwww.#", - "#.ww......wwwww.#", - "#.wwwwwww.wwwww.#", - "#.wwwwwww.wwwww.#", - "#.wwww.....wwww.#", - "#.wwwwS...Swwww.#", - "#...............#", - "#################" - ], - "elevation": [ - "00000000000000000", - "00000000000000000", - "00000000000000000", - "03333333333333330", - "03333331113333330", - "03111111111111130", - "03111113331111130", - "03113333333331130", - "03111113331131130", - "03111111111131130", - "03113333333331130", - "03113111131111130", - "03113111130111130", - "03113333331111130", - "03111111131111130", - "03111111131111130", - "03111133333111130", - "03111103330111130", - "03333333333333330", - "00000000000000000" - ], - "points": { - "misty_front": [8, 7], - "warp_1": [8, 18] - }, - "warps": [ - { - "x": 7, - "y": 18, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_4" - }, - { - "x": 8, - "y": 18, - "enter": "walk_south", - "to_map": "CeruleanCity", - "to_point": "warp_4" - }, - { - "x": 9, - "y": 18, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_4" - } - ], - "objects": [ - { - "name": "swimmer_m_water", - "kind": "npc", - "x": 10, - "y": 12, - "facing": "west", - "trainer": { - "sight": 1 - } - }, - { - "name": "picnicker", - "kind": "npc", - "x": 4, - "y": 7, - "facing": "east", - "trainer": { - "sight": 4 - } - }, - { - "name": "misty", - "kind": "npc", - "x": 8, - "y": 6 - }, - { - "name": "gym_guy", - "kind": "npc", - "x": 7, - "y": 16 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_House2.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_House2.json deleted file mode 100644 index 2abad6df50..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_House2.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "name": "CeruleanCity_House2", - "source": { - "decomp_file": "data/maps/CeruleanCity_House2/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "##########", - "####.#####", - "........##", - ".........#", - "...##.....", - "...##.....", - "..........", - "#.......##", - "##########" - ], - "points": { - "warp_1": [3, 7], - "warp_3": [4, 1] - }, - "warps": [ - { - "x": 2, - "y": 7, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_1" - }, - { - "x": 3, - "y": 7, - "enter": "walk_south", - "to_map": "CeruleanCity", - "to_point": "warp_1" - }, - { - "x": 4, - "y": 7, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_1" - }, - { - "x": 4, - "y": 1, - "enter": "cave_entrance", - "to_map": "CeruleanCity", - "to_point": "warp_9" - } - ], - "objects": [ - { - "name": "hiker", - "kind": "npc", - "x": 1, - "y": 2 - }, - { - "name": "lass", - "kind": "npc", - "x": 7, - "y": 6, - "wander": { - "x0": 6, - "y0": 5, - "x1": 8, - "y1": 7 - } - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_Mart.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_Mart.json deleted file mode 100644 index db4c9defee..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_Mart.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "name": "CeruleanCity_Mart", - "source": { - "decomp_file": "data/maps/CeruleanCity_Mart/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "###########", - "###########", - "##.#......#", - "...#.......", - "####...##.#", - ".......##.#", - ".##....##.#", - "...........", - "###########" - ], - "elevation": [ - "00000000000", - "00000000000", - "00303333330", - "33003333333", - "00003330030", - "33333330030", - "30033330030", - "33333333333", - "00000000000" - ], - "points": { - "clerk_front": [4, 3], - "warp_1": [4, 7] - }, - "warps": [ - { - "x": 3, - "y": 7, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_6" - }, - { - "x": 4, - "y": 7, - "enter": "walk_south", - "to_map": "CeruleanCity", - "to_point": "warp_6" - }, - { - "x": 5, - "y": 7, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_6" - } - ], - "objects": [ - { - "name": "clerk", - "kind": "npc", - "x": 2, - "y": 3 - }, - { - "name": "woman_1", - "kind": "npc", - "x": 9, - "y": 2, - "wander": { - "x0": 5, - "y0": 2, - "x1": 10, - "y1": 2 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 1, - "y": 7 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_PokemonCenter_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_PokemonCenter_1F.json deleted file mode 100644 index 28d628941f..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/CeruleanCity_PokemonCenter_1F.json +++ /dev/null @@ -1,126 +0,0 @@ -{ - "name": "CeruleanCity_PokemonCenter_1F", - "source": { - "decomp_file": "data/maps/CeruleanCity_PokemonCenter_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#.#############", - "###############", - "....###..##....", - "....#######....", - "...............", - "...............", - "#..........##..", - "...........##..", - "...............", - "###############" - ], - "elevation": [ - "030000000000000", - "000000000000000", - "333300033003333", - "333300000003333", - "333333333333333", - "330333333333333", - "044033333330033", - "330333333330033", - "333333333333333", - "000000000000000" - ], - "points": { - "pc": [11, 2], - "nurse_front": [7, 4], - "warp_1": [7, 8] - }, - "warps": [ - { - "x": 6, - "y": 8, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_3" - }, - { - "x": 7, - "y": 8, - "enter": "walk_south", - "to_map": "CeruleanCity", - "to_point": "warp_3" - }, - { - "x": 8, - "y": 8, - "enter": "walk_into", - "to_map": "CeruleanCity", - "to_point": "warp_3" - }, - { - "x": 1, - "y": 6, - "enter": "step_on" - } - ], - "objects": [ - { - "name": "nurse", - "kind": "npc", - "x": 7, - "y": 2 - }, - { - "name": "gentleman", - "kind": "npc", - "x": 12, - "y": 5 - }, - { - "name": "rocker", - "kind": "npc", - "x": 5, - "y": 4, - "wander": { - "x0": 4, - "y0": 4, - "x1": 6, - "y1": 4 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 4, - "y": 8, - "wander": { - "x0": 3, - "y0": 8, - "x1": 5, - "y1": 8 - } - }, - { - "name": "lass", - "kind": "npc", - "x": 3, - "y": 3 - }, - { - "name": "0", - "kind": "npc", - "x": 2, - "y": 1, - "hide_flag": "FLAG_HIDE_POSTGAME_GOSSIPERS" - }, - { - "name": "0", - "kind": "npc", - "x": 3, - "y": 1, - "hide_flag": "FLAG_HIDE_POSTGAME_GOSSIPERS" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_1F.json deleted file mode 100644 index cf3836faa8..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_1F.json +++ /dev/null @@ -1,229 +0,0 @@ -{ - "name": "MtMoon_1F", - "source": { - "decomp_file": "data/maps/MtMoon_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "################################################", - "################################################", - "##~~~~~~~~~#########~~~~~~~~~~~~~~~~~~~~~~~~~~##", - "##~~~~~~~~~~#######~~~~~~~~~~~~~~~~~~~~~~~~~~~##", - "##~~~~~~~~~~~#####~~~~~~~~~~~~~~~~~~~~~~~~~~~~##", - "##~~~~~~~~~~~#####~~~~~~~~~~~~~~~~~~~~~~~~~~~~##", - "##~~~.~~~~~~~####~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##", - "##~~~~~~~~~~~####~~~~~~~~~~~~~~~~~~~~~~~~#~~~~##", - "##~~~~~~~~~~~###~~~~~~~~~~~~~~~~~~~~~~~~####~~##", - "##~~~~~~~~~~~###~~~~~~~~~~~~~~~~~~~~~~~~~##~~~##", - "##~~~~~~~~~~~###~~~~###############~~~~~~~~~~~##", - "##~~~~~~~~~~~###~~~~###############~~~~~~~~~~~##", - "##~~~~~~~~~~~###~~~~###############~~~~~~~~~~~##", - "##~~~~~~~~~~~###~~~~#####~~~~~####~~~~~~~~~~~~##", - "##~~~~~~~~~~~###~~~.####~~~~~~~~~~~~~~~###~~~~##", - "##~~~~~~~~~~~###~~~~####~~~~~~~~~~~~~~~###~~~~##", - "##~~~~~~~~~~~###~~~~###~~~~~~~~.~~~~~~~###~~~~##", - "##~~~~~~~~~~~~~~~~~~###~~~~###~~~~~~~~~###~~~~##", - "##~~~~~~~~~~~~~~~~~~###~~~~###~~~~~~~~~###~~~~##", - "###########~~~~~~~~~###~~~~###~~~~~~~~~###~~~~##", - "###########~~~~~~~~~###~~~~###~~~~~~~~~###~~~~##", - "###########~~~~~~~~~###~~~~###~~~~~~~~~###~~~~##", - "##~~~~#################~~~~###~~~~~~~~~###~~~~##", - "##~~~~~################~~~~###~~~~~~~~~###~~~~##", - "##~~~~~~###############~~~~###~~~~~~~~~###~~~~##", - "##~~~~~~###~~~~~~~~~~~~~~~~###~~~~~~~~~###~~~~##", - "##~~~~~~###~~~~~~~~S~~~~~~~###~~~~~~~~~###~~~~##", - "##~~~~~~###~~~~~~~~~~~~~~~~###~~~~~~~~~###~~~~##", - "##~~~~~~~~~~~~~~~~~~~~~~~~~###~~~~~~~~~###~~~~##", - "##~~~~~~~~~~~~~~~~~~##########~~~###~~~###~~~~##", - "##~~~~~~~~~~~~~~~~~~##########~~~###~~~###~~~~##", - "##~~~~~~~~~~~~##~~~~##########~~~###~~~###~~~~##", - "##~~~~~~~~~~~####~~~#####~~~~~~~~###~~~~~~~~~~##", - "##~~~~~~~~~~~####~~~######~~~~~~~###~~~~~~~~~~##", - "##~~~~~~~~~~~####~~~######~~~~~~~###~~~~~~~~~~##", - "##~~~~~~~~~~~####~~~#####~~~~~~~~~~~~~~~~~~~~~##", - "##~~~~~~~~~~~####~~~####~~~~~~~~~~~~~~~~~~~~~~##", - "##~~~~~~~~~~~####~.~####~~~~~~~~~~~~~~~~~~~~~~##", - "################################################", - "################################################" - ], - "points": { - "warp_0": [5, 6], - "warp_1": [19, 14], - "warp_2": [31, 16], - "warp_3": [18, 37] - }, - "warps": [ - { - "x": 5, - "y": 6, - "enter": "step_on", - "to_map": "MtMoon_B1F", - "to_point": "warp_0" - }, - { - "x": 19, - "y": 14, - "enter": "step_on", - "to_map": "MtMoon_B1F", - "to_point": "warp_1" - }, - { - "x": 31, - "y": 16, - "enter": "step_on", - "to_map": "MtMoon_B1F", - "to_point": "warp_2" - }, - { - "x": 18, - "y": 37, - "enter": "walk_south", - "to_map": "Route4", - "to_point": "warp_0" - } - ], - "objects": [ - { - "name": "lass", - "kind": "npc", - "x": 20, - "y": 26, - "facing": "south", - "trainer": { - "sight": 2 - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 36, - "y": 30, - "facing": "east", - "trainer": { - "sight": 2 - } - }, - { - "name": "scientist", - "kind": "npc", - "x": 30, - "y": 35, - "trainer": { - "sight": 4, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "lass", - "kind": "npc", - "x": 33, - "y": 4, - "trainer": { - "sight": 2, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 7, - "y": 26, - "facing": "south", - "trainer": { - "sight": 4 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 13, - "y": 17, - "facing": "east", - "trainer": { - "sight": 4 - } - }, - { - "name": "hiker", - "kind": "npc", - "x": 7, - "y": 10, - "wander": { - "x0": 5, - "y0": 8, - "x1": 9, - "y1": 12 - }, - "trainer": { - "sight": 1, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "item_ball", - "kind": "item", - "x": 2, - "y": 22, - "hide_flag": "FLAG_HIDE_MT_MOON_1F_PARALYZE_HEAL" - }, - { - "name": "item_ball", - "kind": "item", - "x": 11, - "y": 35, - "hide_flag": "FLAG_HIDE_MT_MOON_1F_TM09" - }, - { - "name": "item_ball", - "kind": "item", - "x": 26, - "y": 32, - "hide_flag": "FLAG_HIDE_MT_MOON_1F_POTION" - }, - { - "name": "item_ball", - "kind": "item", - "x": 42, - "y": 35, - "hide_flag": "FLAG_HIDE_MT_MOON_1F_RARE_CANDY" - }, - { - "name": "item_ball", - "kind": "item", - "x": 44, - "y": 21, - "hide_flag": "FLAG_HIDE_MT_MOON_1F_ESCAPE_ROPE" - }, - { - "name": "item_ball", - "kind": "item", - "x": 3, - "y": 2, - "hide_flag": "FLAG_HIDE_MT_MOON_1F_MOON_STONE" - }, - { - "name": "balding_man", - "kind": "npc", - "x": 42, - "y": 7 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_B1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_B1F.json deleted file mode 100644 index 448aa61f4d..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_B1F.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "name": "MtMoon_B1F", - "source": { - "decomp_file": "data/maps/MtMoon_B1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "#################################################", - "#################################################", - "##################~~~~~~~~###############~~~~~###", - "##~.~~~##########~~~~~~~~#############~~~~~~~####", - "##~~~~~#########~~~~~~~~~.~##########~~.~~~~~.~##", - "##~~~~~#########~.~~~~~~~~~#########~~~~~~~~~~~##", - "##~~~~~##########################################", - "##~~~~~##########################################", - "##~~~~~##########################################", - "##~~~~###########################################", - "##~~~~~##########################################", - "##~~~#~##########################################", - "##~~~~###########################################", - "##~~~~~##########################################", - "##~~~~~##########################################", - "##~~~~~##########################################", - "##~~~~~~~~~~~~~~~~~~~~~~#########################", - "##~~~~~~~~~~~~~~~~~~~~~~#########################", - "##~~~~~~~~~~~~~~~~~~~~.~#########################", - "#################################################", - "#################################################", - "#########################################~~.~####", - "#########################################~~~~####", - "#########################################~~~~####", - "#########################################~~~~####", - "#########################################~~~~####", - "#########################################~~~~####", - "#########################################~~~~####", - "#########################################~~~~####", - "#########################################~~~~####", - "#########################################~~~~####", - "##########################################~~~####", - "##########################################~~~####", - "##########################################~~~####", - "##########################~~~~~~~~~~~####~~~~####", - "#########################~~~~~~~~~~~~~~~~~~~~####", - "########################~~.~~~~~~~~~~~~~~~~~~####", - "#################################################", - "#################################################", - "#################################################" - ], - "points": { - "warp_0": [3, 3], - "warp_1": [25, 4], - "warp_2": [43, 21], - "warp_3": [22, 18], - "warp_4": [17, 5], - "warp_5": [26, 36], - "warp_6": [39, 4], - "warp_7": [45, 4] - }, - "warps": [ - { - "x": 3, - "y": 3, - "enter": "step_on", - "to_map": "MtMoon_1F", - "to_point": "warp_0" - }, - { - "x": 25, - "y": 4, - "enter": "step_on", - "to_map": "MtMoon_1F", - "to_point": "warp_1" - }, - { - "x": 43, - "y": 21, - "enter": "step_on", - "to_map": "MtMoon_1F", - "to_point": "warp_2" - }, - { - "x": 22, - "y": 18, - "enter": "step_on", - "to_map": "MtMoon_B2F", - "to_point": "warp_0" - }, - { - "x": 17, - "y": 5, - "enter": "step_on", - "to_map": "MtMoon_B2F", - "to_point": "warp_1" - }, - { - "x": 26, - "y": 36, - "enter": "step_on", - "to_map": "MtMoon_B2F", - "to_point": "warp_2" - }, - { - "x": 39, - "y": 4, - "enter": "step_on", - "to_map": "MtMoon_B2F", - "to_point": "warp_3" - }, - { - "x": 45, - "y": 4, - "enter": "step_on", - "to_map": "Route4", - "to_point": "warp_1" - } - ], - "objects": [], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_B2F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_B2F.json deleted file mode 100644 index 33c98997eb..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/MtMoon_B2F.json +++ /dev/null @@ -1,218 +0,0 @@ -{ - "name": "MtMoon_B2F", - "source": { - "decomp_file": "data/maps/MtMoon_B2F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "################################################", - "################################################", - "################################################", - "###~~~~~~~~~~~~~~~~~############################", - "###~~~~~~~~~~~~~~~~~############################", - "###~~~~~~~~~~~~~~~~~###~~~~######~~~~###########", - "###~~~~~~~~~~~~~~~~~###~~~~#~~~~#~~~~#~~~~######", - "###~~########~~########~~~~#~~~~##~~##~~~~######", - "##~~~~~~#8888~~88888###~~~~#~~~~~~~~~~~~~~######", - "##~~~#~~#~~~~~~~~~~~###~~~~#~~~~~~~~~~~~~~######", - "##~~~.#~#~~~~~~~~~~~###~~~~#~~##~~~~~~~~~~######", - "##~~~~~~#~~~~~~~~~~~###~~~~#~~#.~~~~~~~#~~######", - "##~~~~~######~~###~~###~~~~#~~~~~~~~~~~~~~######", - "#########~~~~~~~~#~~###~~~~#~~~~~~~~~~~~~~######", - "#vvvvvv#~~~~~~~~~#~~###~~~~88888888##8888888888#", - "########~~~~~~~~~######~~~~~~~~~~~~##~~~~~~~~~~#", - "########~~~~~~~~~~~~###~~~~~~~~~~~~##~~~~~~~~~~#", - "############~~~########~~~~~~~~~~~~##~~~~~~~~~~#", - "#######vvvv#~~~#vvvvvv#~~~~~~##~~#####~~##~~~~~#", - "############~~~########~~~~~~#~~~~~~~~~~~#~~~~~#", - "############~~~########~~#~~~#~~~~~~~~~~~#~~~~~#", - "########~~~~~~~~~~~~~##~~.~#~#~~~~~~~~~~~#~~~~~#", - "########~~~~~~~~~~~~~##~~~#~~#~~~~~~~~~~~#~~~~~#", - "########~~~~~##############################~~~~#", - "########~~~~~#vvvvvvvvvvvvvvvvvvvvvvvvvvvv#~~~~#", - "########~~~~~##############################~~~~#", - "########~~~~~###############~~~~###########~~~~#", - "########~~~~~##~~~~~~~~~~~~#~~~~#~~##~~~~~~~~~~#", - "########~~~~~###~~~~~~~~~~~##~~##~~##~~~~~~~~~~#", - "########~~~~~##~~~~~~~~~~~~~~~~~~~~##~~~~~######", - "########~~~~~##~~#~~~~~~~~~~~~~~~~~##~~~~~######", - "########~~~~~##~#.~~~~~~~~~~~~~~~~~##~~~~~######", - "########~~~~~####~~~~~~~~~~~~~~~~~~##~~~~~######", - "########~~~~~########################~~~~~######", - "########~~~~~#vvvvvvvvvvvvvvvvvvvvvv#~~~~~######", - "########~~~~~########################~~~~~######", - "########~~~~~########################~~~~~######", - "########~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~######", - "########~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~######", - "################################################" - ], - "points": { - "warp_0": [25, 21], - "warp_1": [31, 11], - "warp_2": [17, 31], - "warp_3": [5, 10] - }, - "regions": { - "wild": [ - [9, 8], - [10, 8], - [11, 8], - [12, 8], - [15, 8], - [16, 8], - [17, 8], - [18, 8], - [19, 8], - [27, 14], - [28, 14], - [29, 14], - [30, 14], - [31, 14], - [32, 14], - [33, 14], - [34, 14], - [37, 14], - [38, 14], - [39, 14], - [40, 14], - [41, 14], - [42, 14], - [43, 14], - [44, 14], - [45, 14], - [46, 14] - ] - }, - "warps": [ - { - "x": 25, - "y": 21, - "enter": "step_on", - "to_map": "MtMoon_B1F", - "to_point": "warp_3" - }, - { - "x": 31, - "y": 11, - "enter": "step_on", - "to_map": "MtMoon_B1F", - "to_point": "warp_4" - }, - { - "x": 17, - "y": 31, - "enter": "step_on", - "to_map": "MtMoon_B1F", - "to_point": "warp_5" - }, - { - "x": 5, - "y": 10, - "enter": "step_on", - "to_map": "MtMoon_B1F", - "to_point": "warp_6" - } - ], - "objects": [ - { - "name": "fossil", - "kind": "npc", - "x": 13, - "y": 7, - "hide_flag": "FLAG_HIDE_DOME_FOSSIL" - }, - { - "name": "fossil", - "kind": "npc", - "x": 14, - "y": 7, - "hide_flag": "FLAG_HIDE_HELIX_FOSSIL" - }, - { - "name": "scientist", - "kind": "npc", - "x": 13, - "y": 11 - }, - { - "name": "rocket_m", - "kind": "npc", - "x": 37, - "y": 21, - "trainer": { - "sight": 3, - "looks": [ - "north", - "east", - "south", - "west" - ] - }, - "hide_flag": "FLAG_HIDE_MISC_KANTO_ROCKETS" - }, - { - "name": "rocket_m", - "kind": "npc", - "x": 12, - "y": 20, - "facing": "south", - "trainer": { - "sight": 2 - }, - "hide_flag": "FLAG_HIDE_MISC_KANTO_ROCKETS" - }, - { - "name": "rocket_m", - "kind": "npc", - "x": 35, - "y": 12, - "facing": "north", - "trainer": { - "sight": 4 - }, - "hide_flag": "FLAG_HIDE_MISC_KANTO_ROCKETS" - }, - { - "name": "rocket_m", - "kind": "npc", - "x": 18, - "y": 27, - "facing": "south", - "trainer": { - "sight": 4 - }, - "hide_flag": "FLAG_HIDE_MISC_KANTO_ROCKETS" - }, - { - "name": "item_ball", - "kind": "item", - "x": 30, - "y": 26, - "hide_flag": "FLAG_HIDE_MT_MOON_B2F_STAR_PIECE" - }, - { - "name": "item_ball", - "kind": "item", - "x": 35, - "y": 5, - "hide_flag": "FLAG_HIDE_MT_MOON_B2F_TM46" - }, - { - "name": "item_ball", - "kind": "item", - "x": 24, - "y": 6, - "hide_flag": "FLAG_HIDE_MT_MOON_B2F_REVIVE" - }, - { - "name": "item_ball", - "kind": "item", - "x": 3, - "y": 11, - "hide_flag": "FLAG_HIDE_MT_MOON_B2F_ANTIDOTE" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown.json deleted file mode 100644 index 63540ac440..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "name": "PalletTown", - "source": { - "decomp_file": "data/maps/PalletTown/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "############..##########", - "############..##########", - "##....................##", - "##....................##", - "##...#####....#####...##", - "##...#####....#####...##", - "##...#####....#####...##", - "##..S#D###...S#D###...##", - "##....................##", - "##....................##", - "##...........#######..##", - "##...####S...#######..##", - "##...........#######..##", - "##...........###D###..##", - "##...S................##", - "##....................##", - "##...........###S##...##", - "##.....wwww...........##", - "##.....wwww...........##", - "##.....wwww...........##" - ], - "elevation": [ - "000000000000330000000000", - "000000000000330000000000", - "003333333333333333333300", - "003333333333333333333300", - "003330000033330000033300", - "003330000033330000033300", - "003330000033330000033300", - "003300000033300000033300", - "003333333333333333333300", - "003333333333333333333300", - "003333333333300000003300", - "003330000033300000003300", - "003333333333300000003300", - "003333333333300000003300", - "003330333333333333333300", - "003333333333333333333300", - "003333333333300000033300", - "003333311113333333333300", - "003333311113333333333300", - "003333311113333333333300" - ], - "points": { - "oaks_lab_front": [16, 14], - "warp_0": [6, 7], - "warp_1": [15, 7], - "warp_2": [16, 13] - }, - "warps": [ - { - "x": 6, - "y": 7, - "enter": "walk_into", - "to_map": "PalletTown_PlayersHouse_1F", - "to_point": "warp_1" - }, - { - "x": 15, - "y": 7, - "enter": "walk_into", - "to_map": "PalletTown_RivalsHouse", - "to_point": "warp_0" - }, - { - "x": 16, - "y": 13, - "enter": "walk_into", - "to_map": "PalletTown_ProfessorOaksLab", - "to_point": "warp_0" - } - ], - "objects": [ - { - "name": "woman_1", - "kind": "npc", - "x": 3, - "y": 10, - "wander": { - "x0": 2, - "y0": 6, - "x1": 4, - "y1": 14 - } - }, - { - "name": "fat_man", - "kind": "npc", - "x": 13, - "y": 17, - "wander": { - "x0": 7, - "y0": 15, - "x1": 19, - "y1": 19 - } - }, - { - "name": "prof_oak", - "kind": "npc", - "x": 10, - "y": 8, - "hide_flag": "FLAG_HIDE_OAK_IN_PALLET_TOWN" - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "north", - "to_map": "Route1", - "offset": 0 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_PlayersHouse_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_PlayersHouse_1F.json deleted file mode 100644 index bc171fed17..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_PlayersHouse_1F.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "PalletTown_PlayersHouse_1F", - "source": { - "decomp_file": "data/maps/PalletTown_PlayersHouse_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#############", - "#############", - "#..........##", - "#............", - "#.....##.....", - "#.....##.....", - "#............", - "##..........#", - "#............", - "#############" - ], - "points": { - "warp_1": [4, 8], - "warp_2": [10, 2] - }, - "warps": [ - { - "x": 5, - "y": 8, - "enter": "walk_into", - "to_map": "PalletTown", - "to_point": "warp_0" - }, - { - "x": 4, - "y": 8, - "enter": "walk_south", - "to_map": "PalletTown", - "to_point": "warp_0" - }, - { - "x": 10, - "y": 2, - "enter": "walk_east", - "to_map": "PalletTown_PlayersHouse_2F", - "to_point": "warp_0" - }, - { - "x": 3, - "y": 9, - "enter": "walk_into", - "to_map": "PalletTown", - "to_point": "warp_0" - } - ], - "objects": [ - { - "name": "mom", - "kind": "npc", - "x": 8, - "y": 4 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_PlayersHouse_2F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_PlayersHouse_2F.json deleted file mode 100644 index 7ca500d896..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_PlayersHouse_2F.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "PalletTown_PlayersHouse_2F", - "source": { - "decomp_file": "data/maps/PalletTown_PlayersHouse_2F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "############", - "###########S", - "#.......##..", - "#.......##..", - "#.2...#.....", - "#.8...#.....", - "#.#.........", - "#...........", - "#..........." - ], - "elevation": [ - "000000000000", - "000000000000", - "033333330033", - "033333330033", - "033333033333", - "003033033333", - "040403333333", - "003033333333", - "033333333333" - ], - "points": { - "start": [6, 6], - "warp_0": [10, 2] - }, - "warps": [ - { - "x": 10, - "y": 2, - "enter": "walk_west", - "to_map": "PalletTown_PlayersHouse_1F", - "to_point": "warp_2" - } - ], - "objects": [], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_ProfessorOaksLab.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_ProfessorOaksLab.json deleted file mode 100644 index 724be58581..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_ProfessorOaksLab.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "name": "PalletTown_ProfessorOaksLab", - "source": { - "decomp_file": "data/maps/PalletTown_ProfessorOaksLab/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#############", - "######SS#####", - ".............", - "#............", - "###.....###..", - ".##..........", - ".............", - ".............", - "#####...#####", - ".............", - ".............", - ".............", - "#...........#", - "#############" - ], - "points": { - "warp_0": [6, 12] - }, - "warps": [ - { - "x": 6, - "y": 12, - "enter": "walk_south", - "to_map": "PalletTown", - "to_point": "warp_2" - }, - { - "x": 7, - "y": 12, - "enter": "walk_into", - "to_map": "PalletTown", - "to_point": "warp_2" - }, - { - "x": 5, - "y": 12, - "enter": "walk_into", - "to_map": "PalletTown", - "to_point": "warp_2" - } - ], - "objects": [ - { - "name": "scientist", - "kind": "npc", - "x": 3, - "y": 11 - }, - { - "name": "worker_f", - "kind": "npc", - "x": 2, - "y": 10, - "wander": { - "x0": 2, - "y0": 6, - "x1": 2, - "y1": 13 - } - }, - { - "name": "scientist", - "kind": "npc", - "x": 11, - "y": 10 - }, - { - "name": "prof_oak", - "kind": "npc", - "x": 6, - "y": 3, - "hide_flag": "FLAG_HIDE_OAK_IN_HIS_LAB" - }, - { - "name": "item_ball", - "kind": "item", - "x": 8, - "y": 4, - "hide_flag": "FLAG_HIDE_BULBASAUR_BALL" - }, - { - "name": "item_ball", - "kind": "item", - "x": 9, - "y": 4, - "hide_flag": "FLAG_HIDE_SQUIRTLE_BALL" - }, - { - "name": "item_ball", - "kind": "item", - "x": 10, - "y": 4, - "hide_flag": "FLAG_HIDE_CHARMANDER_BALL" - }, - { - "name": "blue", - "kind": "npc", - "x": 5, - "y": 4, - "hide_flag": "FLAG_HIDE_RIVAL_IN_LAB" - }, - { - "name": "pokedex", - "kind": "npc", - "x": 4, - "y": 1, - "hide_flag": "FLAG_HIDE_POKEDEX" - }, - { - "name": "pokedex", - "kind": "npc", - "x": 5, - "y": 1, - "hide_flag": "FLAG_HIDE_POKEDEX" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_RivalsHouse.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_RivalsHouse.json deleted file mode 100644 index 641924ae26..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PalletTown_RivalsHouse.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "name": "PalletTown_RivalsHouse", - "source": { - "decomp_file": "data/maps/PalletTown_RivalsHouse/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#############", - "#############", - ".............", - ".............", - "......##.....", - "......##.....", - ".............", - ".............", - "#...........#", - "#############" - ], - "points": { - "warp_0": [4, 8] - }, - "warps": [ - { - "x": 4, - "y": 8, - "enter": "walk_south", - "to_map": "PalletTown", - "to_point": "warp_1" - }, - { - "x": 5, - "y": 8, - "enter": "walk_into", - "to_map": "PalletTown", - "to_point": "warp_1" - }, - { - "x": 3, - "y": 8, - "enter": "walk_into", - "to_map": "PalletTown", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "daisy", - "kind": "npc", - "x": 10, - "y": 6, - "wander": { - "x0": 9, - "y0": 3, - "x1": 11, - "y1": 9 - } - }, - { - "name": "town_map", - "kind": "npc", - "x": 6, - "y": 4, - "hide_flag": "FLAG_HIDE_TOWN_MAP" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity.json deleted file mode 100644 index 845d8ae7dc..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity.json +++ /dev/null @@ -1,172 +0,0 @@ -{ - "name": "PewterCity", - "source": { - "decomp_file": "data/maps/PewterCity/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "################################################", - "#######################....#####################", - "####........#################.........##########", - "####........#################.........##########", - "####........#############D######......##########", - "####........###########...............##########", - "#######.....#####.#####..#######........########", - "####vvv..vvvvvvv...Svvvvvvvvvvvvvvvvv...########", - "####....................................########", - "####............................#####...########", - "######..........................#####...########", - "######..........................#D###...########", - "######..................................########", - "######...##.#######.##..................########", - "######...#############..................########", - "######...##.#######.##..................########", - "######...##S..#D#...##....####..........########", - "######...............#....######........########", - "######...............#....SSD###........########", - "######...............#.................S########", - "######################..........................", - "######..........................................", - "######.......#########..........................", - "######.......#########....##########............", - "######.......#########....##########....########", - "######.......##SSD####....#####S#####...########", - "######....................#.........#...########", - "######....................#.........#...########", - "######..#####.............#.........#...########", - "######..#####.............#.........#...########", - "######..#D###.......S.....#vvvv.vvvv#...########", - "######..................................########", - "######..................................########", - "######..................................########", - "######..................................########", - "######..................................########", - "####################....########################", - "####################....########################", - "####################....########################", - "####################....########################" - ], - "points": { - "gym_front": [15, 17], - "warp_0": [17, 6], - "warp_1": [25, 4], - "warp_2": [15, 16], - "warp_3": [28, 18], - "warp_5": [17, 25] - }, - "warps": [ - { - "x": 17, - "y": 6, - "enter": "cave_entrance", - "to_map": "PewterCity_Museum_1F", - "to_point": "warp_1" - }, - { - "x": 25, - "y": 4, - "enter": "walk_into", - "to_map": "PewterCity_Museum_1F", - "to_point": "warp_3" - }, - { - "x": 15, - "y": 16, - "enter": "walk_into", - "to_map": "PewterCity_Gym", - "to_point": "warp_1" - }, - { - "x": 28, - "y": 18, - "enter": "walk_into", - "to_map": "PewterCity_Mart", - "to_point": "warp_1" - }, - { - "x": 33, - "y": 11, - "enter": "walk_into" - }, - { - "x": 17, - "y": 25, - "enter": "walk_into", - "to_map": "PewterCity_PokemonCenter_1F", - "to_point": "warp_1" - }, - { - "x": 9, - "y": 30, - "enter": "walk_into" - } - ], - "objects": [ - { - "name": "lass", - "kind": "npc", - "x": 6, - "y": 15 - }, - { - "name": "man", - "kind": "npc", - "x": 33, - "y": 17, - "hide_flag": "FLAG_HIDE_PEWTER_MUSEUM_GUIDE" - }, - { - "name": "fat_man", - "kind": "npc", - "x": 21, - "y": 28 - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 33, - "y": 27, - "wander": { - "x0": 31, - "y0": 27, - "x1": 35, - "y1": 27 - } - }, - { - "name": "boy", - "kind": "npc", - "x": 42, - "y": 20, - "hide_flag": "FLAG_HIDE_PEWTER_CITY_GYM_GUIDE" - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 30, - "y": 5, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "scientist", - "kind": "npc", - "x": 46, - "y": 20, - "hide_flag": "FLAG_HIDE_PEWTER_CITY_RUNNING_SHOES_GUY" - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "south", - "to_map": "Route2", - "offset": 12 - }, - { - "side": "east", - "to_map": "Route3", - "offset": 10 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Gym.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Gym.json deleted file mode 100644 index 5f5f46c50b..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Gym.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "name": "PewterCity_Gym", - "source": { - "decomp_file": "data/maps/PewterCity_Gym/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#############", - "####.....####", - "#############", - "#....#.#....#", - "#...........#", - "#...#...#...#", - "#...........#", - "#..#.#.#.#..#", - "##.........##", - "#...#...#...#", - "##.........##", - "#...........#", - "#...S...S...#", - "#...........#", - "#...........#", - "#############" - ], - "points": { - "brock_front": [6, 6], - "warp_1": [6, 14] - }, - "warps": [ - { - "x": 5, - "y": 14, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_2" - }, - { - "x": 6, - "y": 14, - "enter": "walk_south", - "to_map": "PewterCity", - "to_point": "warp_2" - }, - { - "x": 7, - "y": 14, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_2" - } - ], - "objects": [ - { - "name": "brock", - "kind": "npc", - "x": 6, - "y": 5 - }, - { - "name": "camper", - "kind": "npc", - "x": 3, - "y": 8, - "facing": "east", - "trainer": { - "sight": 4 - } - }, - { - "name": "gym_guy", - "kind": "npc", - "x": 7, - "y": 12 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Mart.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Mart.json deleted file mode 100644 index 29f7f0d2cd..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Mart.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "name": "PewterCity_Mart", - "source": { - "decomp_file": "data/maps/PewterCity_Mart/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "###########", - "###########", - "##.#......#", - "...#.......", - "####...##.#", - ".......##.#", - ".##....##.#", - "...........", - "###########" - ], - "elevation": [ - "00000000000", - "00000000000", - "00303333330", - "33003333333", - "00003330030", - "33333330030", - "30033330030", - "33333333333", - "00000000000" - ], - "points": { - "warp_1": [4, 7] - }, - "warps": [ - { - "x": 3, - "y": 7, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_3" - }, - { - "x": 4, - "y": 7, - "enter": "walk_south", - "to_map": "PewterCity", - "to_point": "warp_3" - }, - { - "x": 5, - "y": 7, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_3" - } - ], - "objects": [ - { - "name": "youngster", - "kind": "npc", - "x": 8, - "y": 2 - }, - { - "name": "boy", - "kind": "npc", - "x": 6, - "y": 4 - }, - { - "name": "clerk", - "kind": "npc", - "x": 2, - "y": 3 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Museum_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Museum_1F.json deleted file mode 100644 index ee0947b080..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_Museum_1F.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "name": "PewterCity_Museum_1F", - "source": { - "decomp_file": "data/maps/PewterCity_Museum_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "############################", - "############################", - "...............#............", - "...............#......#....#", - "...####........#...........#", - "...............#............", - "...........#...#...........#", - "...####..###...###.........#", - ".........###.....#..........", - "#........###....##......##..", - "############################" - ], - "points": { - "warp_1": [14, 9], - "warp_3": [21, 9] - }, - "warps": [ - { - "x": 13, - "y": 9, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_0" - }, - { - "x": 14, - "y": 9, - "enter": "walk_south", - "to_map": "PewterCity", - "to_point": "warp_0" - }, - { - "x": 15, - "y": 9, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_0" - }, - { - "x": 21, - "y": 9, - "enter": "walk_south", - "to_map": "PewterCity", - "to_point": "warp_1" - }, - { - "x": 22, - "y": 9, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_1" - }, - { - "x": 8, - "y": 8, - "enter": "walk_east" - } - ], - "objects": [ - { - "name": "worker_m", - "kind": "npc", - "x": 16, - "y": 5 - }, - { - "name": "old_man_1", - "kind": "npc", - "x": 2, - "y": 4 - }, - { - "name": "scientist", - "kind": "npc", - "x": 21, - "y": 3 - }, - { - "name": "old_amber", - "kind": "npc", - "x": 22, - "y": 3, - "hide_flag": "FLAG_HIDE_OLD_AMBER" - }, - { - "name": "scientist", - "kind": "npc", - "x": 26, - "y": 4 - }, - { - "name": "scientist", - "kind": "npc", - "x": 26, - "y": 7 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_PokemonCenter_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_PokemonCenter_1F.json deleted file mode 100644 index 87ce82cab4..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/PewterCity_PokemonCenter_1F.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "name": "PewterCity_PokemonCenter_1F", - "source": { - "decomp_file": "data/maps/PewterCity_PokemonCenter_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#.#############", - "###############", - "....###..##....", - "....#######....", - "...............", - "...............", - "#..........##..", - "...........##..", - "...............", - "###############" - ], - "elevation": [ - "030000000000000", - "000000000000000", - "333300033003333", - "333300000003333", - "333333333333333", - "330333333333333", - "044033333330033", - "330333333330033", - "333333333333333", - "000000000000000" - ], - "points": { - "nurse_front": [7, 4], - "warp_1": [7, 8] - }, - "warps": [ - { - "x": 6, - "y": 8, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_5" - }, - { - "x": 7, - "y": 8, - "enter": "walk_south", - "to_map": "PewterCity", - "to_point": "warp_5" - }, - { - "x": 8, - "y": 8, - "enter": "walk_into", - "to_map": "PewterCity", - "to_point": "warp_5" - }, - { - "x": 1, - "y": 6, - "enter": "step_on" - } - ], - "objects": [ - { - "name": "jigglypuff", - "kind": "npc", - "x": 2, - "y": 2 - }, - { - "name": "gentleman", - "kind": "npc", - "x": 4, - "y": 8 - }, - { - "name": "nurse", - "kind": "npc", - "x": 7, - "y": 2 - }, - { - "name": "youngster", - "kind": "npc", - "x": 1, - "y": 2 - }, - { - "name": "woman_1", - "kind": "npc", - "x": 13, - "y": 2 - }, - { - "name": "gba_kid", - "kind": "npc", - "x": 10, - "y": 7 - }, - { - "name": "gba_kid", - "kind": "npc", - "x": 14, - "y": 6 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route1.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route1.json deleted file mode 100644 index c48c746b72..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route1.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "name": "Route1", - "source": { - "decomp_file": "data/maps/Route1/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "##########....##########", - "##########....##########", - "##....................##", - "##....................##", - "##......##............##", - "##vvvvvv##vvvvvv......##", - "##......##~~~~~~~~~~~~##", - "##......##~~~~~~~~~~~~##", - "##......##~~~~~~~~~~~~##", - "##......##~~~~~~~~~~~~##", - "##vvvvvv##~~~~~~~~~~~~##", - "##......##............##", - "##....................##", - "##..............~~~~~~##", - "##..............~~~~~~##", - "####vvvvvv######~~~~~~##", - "####......######~~~~~~##", - "##..............~~~~~~##", - "##....................##", - "##....................##", - "##vv.vvvv..vvvvvvvvvvv##", - "##....................##", - "##....................##", - "##....................##", - "##..........~~~~~~....##", - "############~~~~~~....##", - "############~~~~~~vvvv##", - "##..........~~~~~~....##", - "##..........~~~~~~....##", - "##....................##", - "##....................##", - "##vvvv...Svvvvvvvvvvvv##", - "##..~~~~~~~......~~~~~##", - "##..~~~~~~~......~~~~~##", - "##~~~~~~~......~~~~~..##", - "##~~~~~~~...~~.~~~~~..##", - "############~~##########", - "##.........#~~#.......##", - "############~~##########", - "############~~##########" - ], - "points": {}, - "warps": [], - "objects": [ - { - "name": "clerk", - "kind": "npc", - "x": 6, - "y": 28, - "wander": { - "x0": 6, - "y0": 27, - "x1": 6, - "y1": 29 - } - }, - { - "name": "boy", - "kind": "npc", - "x": 19, - "y": 16, - "wander": { - "x0": 18, - "y0": 16, - "x1": 20, - "y1": 16 - } - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "north", - "to_map": "ViridianCity", - "offset": -12 - }, - { - "side": "south", - "to_map": "PalletTown", - "offset": 0 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10.json deleted file mode 100644 index 3f3acb6179..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10.json +++ /dev/null @@ -1,321 +0,0 @@ -{ - "name": "Route10", - "source": { - "decomp_file": "data/maps/Route10/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########################", - "##wwwwwwwwwwwwwwwwwwww##", - "##wwwwwwwwwwwwwwwwwwww##", - "##wwwwwwwwwwwwwwwwwwww##", - "##.................www##", - "####..#############www##", - "##....~~~~~~~~~~~.#www##", - "##....~~~~~~~~~~~.#www##", - "......~~~~~~~~~~~.#www##", - "......~~~~~~~~~~~.#www##", - "......~~~~~~~~~~~.#www##", - "##................#www##", - "##vvvvvvvvvvvvv..##www##", - "##................#www##", - "##................#www##", - "###########.......#www##", - "###########.......#www##", - "################..#www##", - "################..#www##", - "########.#######..#www##", - "##.........SSD##..#www##", - "##..###S##........#www##", - "##................#www##", - "##........#.......#www##", - "##................#www##", - "##........#.......#www##", - "##................#www##", - "##........#.......#www##", - "##................#www##", - "##................#www##", - "###################www##", - "#################..www##", - "#################..www##", - "#################..www##", - "#################..www##", - "#################..www##", - "#.###############..www##", - "#..##############..www##", - "#..##############..www##", - "#.###############..www##", - "#vv####.####.####..www##", - "#............###...www##", - "##....S......###...www##", - "##...........###...www##", - "##.................www##", - "##.................www##", - "##...............wwwww##", - "##wwwwwwwwwwwwwwwwwwww##", - "##wwwwwwwwwwwwwwwwwwww##", - "##wwwwwwwwwwwwwwwwwwww##", - "########################", - "########################", - "########################", - "########################", - "########################", - "########################", - "#################.....##", - "########.########.....##", - "###...................##", - "###............S......##", - "###...................##", - "###.........#####.....##", - "##..........#####v.vvv##", - "##...############.....##", - "##...############.....##", - "##vvv############.....##", - "##...############vvvv.##", - "##...############.....##", - "##....................##", - "##....................##", - "##....................##", - "###########...........##", - "###########...........##", - "###########...........##", - "###########vv.##########", - "###########...##########", - "########......##########", - "########......##########", - "########.....###########", - "########.....###########" - ], - "elevation": [ - "000000000000000000000000", - "001111111111111111111100", - "001111111111111111111100", - "001111111111111111111100", - "003333333333333333311100", - "000033000000000000011100", - "003333333333333333011100", - "003333333333333333011100", - "333333333333333333011100", - "333333333333333333011100", - "333333333333333333011100", - "003333333333333333011100", - "000000000000000330011100", - "003333333333333333011100", - "003333333333333333011100", - "000000000003333333011100", - "000000000003333333011100", - "000000000000000033011100", - "000000000000000033011100", - "000000003000000033011100", - "003333333330000033011100", - "003300000033333333011100", - "003333333333333333011100", - "003333333303333333011100", - "003333333333333333011100", - "003333333303333333011100", - "003333333333333333011100", - "003333333303333333011100", - "003333333333333333011100", - "003333333333333333011100", - "000000000000000000011100", - "000000000000000003311100", - "000000000000000003311100", - "000000000000000003311100", - "000000000000000003311100", - "000000000000000003311100", - "030000000000000003311100", - "033000000000000003311100", - "033000000000000003311100", - "030000000000000003311100", - "000000030000300003311100", - "033333333333300033311100", - "003333033333300033311100", - "003333333333300033311100", - "003333333333333333311100", - "003333333333333333311100", - "003333333333333331111100", - "001111111111111111111100", - "001111111111111111111100", - "001111111111111111111100", - "000000000000000000000000", - "000000000000000000000000", - "000000000000000000000000", - "000000000000000000000000", - "000000000000000000000000", - "000000000000000000000000", - "000000000000000003333300", - "000000003000000003333300", - "000333333333333333333300", - "000333333333333033333300", - "000333333333333333333300", - "000333333333000003333300", - "003333333333000000300000", - "003330000000000003333300", - "003330000000000003333300", - "000000000000000003333300", - "003330000000000000000300", - "003330000000000003333300", - "003333333333333333333300", - "003333333333333333333300", - "003333333333333333333300", - "000000000003333333333300", - "000000000003333333333300", - "000000000003333333333300", - "000000000000030000000000", - "000000000003330000000000", - "000000003333330000000000", - "000000003333330000000000", - "000000003333300000000000", - "000000003333300000000000" - ], - "points": { - "warp_3": [13, 20] - }, - "warps": [ - { - "x": 8, - "y": 19, - "enter": "cave_entrance" - }, - { - "x": 8, - "y": 57, - "enter": "cave_entrance" - }, - { - "x": 7, - "y": 40, - "enter": "cave_entrance" - }, - { - "x": 13, - "y": 20, - "enter": "walk_into", - "to_map": "Route10_PokemonCenter_1F", - "to_point": "warp_1" - }, - { - "x": 2, - "y": 37, - "enter": "walk_east" - } - ], - "objects": [ - { - "name": "picnicker", - "kind": "npc", - "x": 7, - "y": 60, - "trainer": { - "sight": 2, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "hiker", - "kind": "npc", - "x": 4, - "y": 62, - "trainer": { - "sight": 1, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "poke_maniac", - "kind": "npc", - "x": 15, - "y": 70, - "wander": { - "x0": 13, - "y0": 70, - "x1": 17, - "y1": 70 - }, - "trainer": { - "sight": 2, - "looks": [ - "west", - "east" - ] - } - }, - { - "name": "hiker", - "kind": "npc", - "x": 4, - "y": 68, - "facing": "west", - "trainer": { - "sight": 2 - } - }, - { - "name": "poke_maniac", - "kind": "npc", - "x": 13, - "y": 44, - "facing": "west", - "trainer": { - "sight": 5 - } - }, - { - "name": "picnicker", - "kind": "npc", - "x": 9, - "y": 27, - "facing": "south", - "trainer": { - "sight": 2 - } - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 10, - "y": 20, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 10, - "y": 22, - "hide_flag": "FLAG_TEMP_13" - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 10, - "y": 24, - "hide_flag": "FLAG_TEMP_14" - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 10, - "y": 26, - "hide_flag": "FLAG_TEMP_15" - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "west", - "to_map": "Route9", - "offset": 0 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10_PokemonCenter_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10_PokemonCenter_1F.json deleted file mode 100644 index 4f4c5f3a1d..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route10_PokemonCenter_1F.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "name": "Route10_PokemonCenter_1F", - "source": { - "decomp_file": "data/maps/Route10_PokemonCenter_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#.#############", - "###############", - "....###..##....", - "....#######....", - "...............", - "...............", - "#..........##..", - "...........##..", - "...............", - "###############" - ], - "elevation": [ - "030000000000000", - "000000000000000", - "333300033003333", - "333300000003333", - "333333333333333", - "330333333333333", - "044033333330033", - "330333333330033", - "333333333333333", - "000000000000000" - ], - "points": { - "warp_1": [7, 8] - }, - "warps": [ - { - "x": 6, - "y": 8, - "enter": "walk_into", - "to_map": "Route10", - "to_point": "warp_3" - }, - { - "x": 7, - "y": 8, - "enter": "walk_south", - "to_map": "Route10", - "to_point": "warp_3" - }, - { - "x": 8, - "y": 8, - "enter": "walk_into", - "to_map": "Route10", - "to_point": "warp_3" - }, - { - "x": 1, - "y": 6, - "enter": "step_on" - } - ], - "objects": [ - { - "name": "nurse", - "kind": "npc", - "x": 7, - "y": 2 - }, - { - "name": "gentleman", - "kind": "npc", - "x": 9, - "y": 4, - "wander": { - "x0": 8, - "y0": 4, - "x1": 10, - "y1": 4 - } - }, - { - "name": "fat_man", - "kind": "npc", - "x": 2, - "y": 3, - "wander": { - "x0": 1, - "y0": 3, - "x1": 3, - "y1": 3 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 14, - "y": 6 - }, - { - "name": "scientist", - "kind": "npc", - "x": 12, - "y": 5 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2.json deleted file mode 100644 index 706949faeb..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2.json +++ /dev/null @@ -1,211 +0,0 @@ -{ - "name": "Route2", - "source": { - "decomp_file": "data/maps/Route2/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########....############", - "########....############", - "##~~~~~~~...############", - "##~~~~~~~...#.........##", - "##~~~~~~~...#.........##", - "##~~~~~~~...#.........##", - "##~~~~~~~...#########.##", - "##~~~~~~~...#########.##", - "##~~~~~~~...#########.##", - "##..........#########.##", - "##..........#########.##", - "##..........#####.###.##", - "##..........#.S.......##", - "####....###.#.........##", - "###########...........##", - "##########............##", - "##############........##", - "##############........##", - "##############........##", - "##############........##", - "##############........##", - "###################...##", - "#################D#...##", - "##############........##", - "################......##", - "################......##", - "##################.#####", - "################......##", - "################......##", - "################......##", - "################......##", - "################......##", - "################vvv.vv##", - "################......##", - "################......##", - "################......##", - "################......##", - "################vvv.vv##", - "################......##", - "################......##", - "################......##", - "#################....###", - "########################", - "########################", - "########################", - "##........##############", - "##################..####", - "#################....###", - "################......##", - "################......##", - "###.####.#######......##", - "#####..#########vvv.vv##", - "####....########......##", - "##............##......##", - "##............##......##", - "##vvvvvvvvv...##......##", - "##....~~~~~~~.##......##", - "##....~~~~~~~.##vv.vvv##", - "##....~~~~~~~.##......##", - "##....~~~~~~~.##......##", - "##....~~~~~~~.##......##", - "##............##......##", - "##....................##", - "##....##########......##", - "##....##########......##", - "##....##########......##", - "##....##########......##", - "##............##......##", - "##............##......##", - "##....................##", - "##vvvvvvv..vvv..vv.vvv##", - "##............##......##", - "####..........##......##", - "####...S......##......##", - "####..........##......##", - "####..........##......##", - "####..........##......##", - "####..........##......##", - "#######.......##########", - "#######.....############" - ], - "points": { - "warp_0": [5, 13], - "warp_2": [5, 51] - }, - "warps": [ - { - "x": 5, - "y": 13, - "enter": "walk_south", - "to_map": "Route2_ViridianForest_NorthEntrance", - "to_point": "warp_3" - }, - { - "x": 6, - "y": 13, - "enter": "walk_south", - "to_map": "Route2_ViridianForest_NorthEntrance", - "to_point": "warp_3" - }, - { - "x": 5, - "y": 51, - "enter": "cave_entrance", - "to_map": "Route2_ViridianForest_SouthEntrance", - "to_point": "warp_1" - }, - { - "x": 17, - "y": 11, - "enter": "cave_entrance" - }, - { - "x": 17, - "y": 22, - "enter": "walk_into" - }, - { - "x": 18, - "y": 46, - "enter": "cave_entrance" - }, - { - "x": 18, - "y": 41, - "enter": "walk_south" - }, - { - "x": 19, - "y": 41, - "enter": "walk_south" - }, - { - "x": 19, - "y": 46, - "enter": "cave_entrance" - }, - { - "x": 6, - "y": 51, - "enter": "cave_entrance", - "to_map": "Route2_ViridianForest_SouthEntrance", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "cut_tree", - "kind": "npc", - "x": 16, - "y": 62, - "hide_flag": "FLAG_TEMP_14" - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 15, - "y": 69, - "hide_flag": "FLAG_TEMP_13" - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 11, - "y": 13, - "hide_flag": "FLAG_TEMP_15" - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 18, - "y": 26, - "hide_flag": "FLAG_TEMP_16" - }, - { - "name": "item_ball", - "kind": "item", - "x": 17, - "y": 54, - "hide_flag": "FLAG_HIDE_ROUTE2_ETHER" - }, - { - "name": "item_ball", - "kind": "item", - "x": 17, - "y": 64, - "hide_flag": "FLAG_HIDE_ROUTE2_PARALYZE_HEAL" - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "north", - "to_map": "PewterCity", - "offset": -12 - }, - { - "side": "south", - "to_map": "ViridianCity", - "offset": -12 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route22.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route22.json deleted file mode 100644 index be2d5629b2..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route22.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "name": "Route22", - "source": { - "decomp_file": "data/maps/Route22/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "#############################################...", - "###########################################.#...", - "##################################........#.###.", - "##################################vvvv.vvv#...##", - "######################....................#.....", - "########D######...........................######", - "#####.......###.........................##......", - "###...........#.............######......##......", - "###...........#vvvvvvvwwwwww######vv..vv##......", - "###vvvvvvv..vv#~~~~~~~wwwwww######~~~~~~##......", - "###...........#~~~~~~~wwwwww######~~~~~~##....##", - "###...........#~~~~~~~wwwwww######~~~~~~##....##", - "####...S#######~~~~~~~......######~~~~~~##....##", - "####...........~~~~~~~......######~~~~~~##....##", - "####........................######............##", - "####vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv..vvvvv##", - "####..........................................##", - "####..........................................##", - "####..........................................##", - "################################################", - "################################################", - "################################################", - "################################################", - "################################################" - ], - "elevation": [ - "000000000000000000000000000000000000000000000333", - "000000000000000000000000000000000000000000030333", - "000000000000000000000000000000000033333333030003", - "000000000000000000000000000000000000003000033300", - "000000000000000000000033333333333333333333033333", - "000000000000000333333333333333333333333333000000", - "000003333333000333333333333333333333333300333333", - "000333333333330333333333333300000033333300333333", - "000333333333330000000011111100000000330000333333", - "000000000033000333333311111100000033333300333333", - "000333333333330333333311111100000033333300333300", - "000333333333330333333311111100000033333300333300", - "000033300000000333333333333300000033333300333300", - "000033333333333333333333333300000033333300333300", - "000033333333333333333333333300000033333333333300", - "000000000000000000000000000000000000000330000000", - "000033333333333333333333333333333333333333333300", - "000033333333333333333333333333333333333333333300", - "000033333333333333333333333333333333333333333300", - "000000000000000000000000000000000000000000000000", - "000000000000000000000000000000000000000000000000", - "000000000000000000000000000000000000000000000000", - "000000000000000000000000000000000000000000000000", - "000000000000000000000000000000000000000000000000" - ], - "points": {}, - "warps": [ - { - "x": 8, - "y": 5, - "enter": "walk_into" - }, - { - "x": 9, - "y": 5, - "enter": "walk_into" - } - ], - "objects": [ - { - "name": "blue", - "kind": "npc", - "x": 25, - "y": 4, - "hide_flag": "FLAG_HIDE_ROUTE_22_RIVAL" - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "east", - "to_map": "ViridianCity", - "offset": -10 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route24.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route24.json deleted file mode 100644 index c8402d903e..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route24.json +++ /dev/null @@ -1,183 +0,0 @@ -{ - "name": "Route24", - "source": { - "decomp_file": "data/maps/Route24/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########################", - "########################", - "########################", - "####.........>...<......", - "####.........>...<......", - "####.........>...<......", - "###vvvv..vvvv#...#vvvvvv", - "###.....................", - "###.....................", - "###.....................", - "###..........###########", - "###..........#..........", - "###..........#...#######", - "###..........#...#######", - "###..........#...#######", - "###..........#...#######", - "###...www#...#...#######", - "###...www#...#...#######", - "###...www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#######", - "###~~~www#...#...#wwwwww", - "###~~~www#...#...#wwwwww", - "###~~~www#...#...#wwwwww", - "###~~~www#...#...#wwwwww", - "###~~~www#...#...#wwwwww", - "####wwwww#...#...#wwwwww", - "####wwwww#...#...#wwwwww", - "####wwwww#...#...#wwwwww", - "####wwwww#...#...#wwwwww", - "####wwwww#...#...#wwwwww", - "####wwwww#...#...#wwwwww", - "####wwwww#...#...#wwwwww", - "####wwwww#...#...#wwwwww" - ], - "elevation": [ - "000000000000000000000000", - "000000000000000000000000", - "000000000000000000000000", - "000033333333303330333333", - "000033333333303330333333", - "000033333333303330333333", - "000000033000003330000000", - "000333333333333333333333", - "000333333333333333333333", - "000333333333333333333333", - "000333333333300000000000", - "000333333333303333333333", - "000333333333303330000000", - "000333333333303330000000", - "000333333333303330000000", - "000333333333303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330000000", - "000333111033303330111111", - "000333111033303330100000", - "000333111033303330100000", - "000333111033303330100000", - "000333111033303330100000", - "000011111033303330100000", - "000011111033303330100000", - "000011111033303330100000", - "000011111033303330100000", - "000011111033303330100000", - "000011111033303330100000", - "000011111033303330100000", - "000011111033303330100000" - ], - "points": {}, - "warps": [], - "objects": [ - { - "name": "man", - "kind": "npc", - "x": 12, - "y": 15, - "hide_flag": "FLAG_HIDE_NUGGET_BRIDGE_ROCKET" - }, - { - "name": "camper", - "kind": "npc", - "x": 12, - "y": 19, - "facing": "west", - "trainer": { - "sight": 2 - } - }, - { - "name": "lass", - "kind": "npc", - "x": 10, - "y": 22, - "facing": "east", - "trainer": { - "sight": 2 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 12, - "y": 25, - "facing": "west", - "trainer": { - "sight": 2 - } - }, - { - "name": "lass", - "kind": "npc", - "x": 10, - "y": 28, - "facing": "east", - "trainer": { - "sight": 2 - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 12, - "y": 31, - "facing": "west", - "trainer": { - "sight": 2 - } - }, - { - "name": "camper", - "kind": "npc", - "x": 5, - "y": 21, - "facing": "north", - "trainer": { - "sight": 5 - } - }, - { - "name": "item_ball", - "kind": "item", - "x": 11, - "y": 4, - "hide_flag": "FLAG_HIDE_ROUTE24_TM45" - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "south", - "to_map": "CeruleanCity", - "offset": -12 - }, - { - "side": "east", - "to_map": "Route25", - "offset": 0 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route25.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route25.json deleted file mode 100644 index 29dcb02843..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route25.json +++ /dev/null @@ -1,200 +0,0 @@ -{ - "name": "Route25", - "source": { - "decomp_file": "data/maps/Route25/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########################################################################", - "########################################################################", - "############...#...#.....#.....#..........##############################", - ".~~~~~~~~......#...#.....#.....#..........####################wwww######", - ".~~~~~~~~................###.#............######S##D##########wwww######", - "................#.#.#....#...#.....#.........................#wwww######", - "vvvvvvvvvv##..##....#.##.#.........#.........................#wwwww#####", - "................#...#....#......##...........................#wwwwwww###", - ".............#..##..#...........#............................#wwwwwwwwww", - ".............#......#.#.........#..........wwwwww.....wwwwww.#wwwwwwwwww", - "###########################################wwwwww.....wwwwww.#wwwwwwwwww", - "...........................................wwwwww.....wwwwww.#wwwwwwwwww", - "#########################################....................#wwwwwwwwww", - "##############################################################wwwwwwwwww", - "#########################################wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", - "#########################################wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", - "#########################################wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", - "#########################################wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", - "#########################################wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", - "#########################################wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww" - ], - "elevation": [ - "000000000000000000000000000000000000000000000000000000000000000000000000", - "000000000000000000000000000000000000000000000000000000000000000000000000", - "000000000000333033303333303333303333333333000000000000000000000000000000", - "333333333333333033303333303333303333333333000000000000000000001111000000", - "333333333333333333333333300030333333333333000000000000000000001001000000", - "333333333333333303030333303330333330333333333333333333333333301001000000", - "000000000000330033330300303333333330333333333333333333333333301001100000", - "333333333333333303330333303333330033333333333333333333333333301000111000", - "333333333333303300330333333333330333333333333333333333333333301000001111", - "333333333333303333330303333333330333333333311111133333111111301000000000", - "000000000000000000000000000000000000000000011111133333111111301000000000", - "333333333333333333333333333333333333333333311111133333111111301000000000", - "000000000000000000000000000000000000000003333333333333333333301000000000", - "000000000000000000000000000000000000000000000000000000000000001000000000", - "000000000000000000000000000000000000000001111111111111111111111000000000", - "000000000000000000000000000000000000000001100000000000000000000000000000", - "000000000000000000000000000000000000000001000000000000000000000000000000", - "000000000000000000000000000000000000000001000000000000000000000000000000", - "000000000000000000000000000000000000000001000000000000000000000000000000", - "000000000000000000000000000000000000000001000000000000000000000000000000" - ], - "points": { - "warp_0": [51, 4] - }, - "warps": [ - { - "x": 51, - "y": 4, - "enter": "walk_into", - "to_map": "Route25_SeaCottage", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "hiker", - "kind": "npc", - "x": 11, - "y": 4, - "facing": "east", - "trainer": { - "sight": 4 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 18, - "y": 2, - "facing": "south", - "trainer": { - "sight": 2 - } - }, - { - "name": "hiker", - "kind": "npc", - "x": 17, - "y": 7, - "facing": "east", - "trainer": { - "sight": 2 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 22, - "y": 4, - "trainer": { - "sight": 2, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "picnicker", - "kind": "npc", - "x": 22, - "y": 8, - "facing": "east", - "trainer": { - "sight": 2 - } - }, - { - "name": "hiker", - "kind": "npc", - "x": 27, - "y": 9, - "facing": "north", - "trainer": { - "sight": 3 - } - }, - { - "name": "camper", - "kind": "npc", - "x": 28, - "y": 4, - "facing": "south", - "trainer": { - "sight": 3 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 36, - "y": 4, - "trainer": { - "sight": 2, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "lass", - "kind": "npc", - "x": 42, - "y": 5, - "facing": "south", - "trainer": { - "sight": 3 - } - }, - { - "name": "item_ball", - "kind": "item", - "x": 26, - "y": 2, - "hide_flag": "FLAG_HIDE_ROUTE25_TM43" - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 30, - "y": 3, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "beauty", - "kind": "npc", - "x": 49, - "y": 10 - }, - { - "name": "man", - "kind": "npc", - "x": 49, - "y": 11 - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "west", - "to_map": "Route24", - "offset": 0 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route25_SeaCottage.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route25_SeaCottage.json deleted file mode 100644 index 0ec5259806..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route25_SeaCottage.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "name": "Route25_SeaCottage", - "source": { - "decomp_file": "data/maps/Route25_SeaCottage/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "###############", - "###############", - "#.##########..#", - "###D######D#.##", - "..............#", - "....##........#", - "..............#", - "#.............#", - "#..............", - "###..........##", - "###############" - ], - "points": { - "warp_1": [7, 9] - }, - "warps": [ - { - "x": 6, - "y": 9, - "enter": "walk_into", - "to_map": "Route25", - "to_point": "warp_0" - }, - { - "x": 7, - "y": 9, - "enter": "walk_south", - "to_map": "Route25", - "to_point": "warp_0" - }, - { - "x": 8, - "y": 9, - "enter": "walk_into", - "to_map": "Route25", - "to_point": "warp_0" - } - ], - "objects": [ - { - "name": "bill", - "kind": "npc", - "x": 7, - "y": 5, - "hide_flag": "FLAG_HIDE_BILL_HUMAN_SEA_COTTAGE" - }, - { - "name": "clefairy", - "kind": "npc", - "x": 10, - "y": 6, - "hide_flag": "FLAG_HIDE_BILL_CLEFAIRY" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2_ViridianForest_NorthEntrance.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2_ViridianForest_NorthEntrance.json deleted file mode 100644 index 021edbb11d..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2_ViridianForest_NorthEntrance.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "name": "Route2_ViridianForest_NorthEntrance", - "source": { - "decomp_file": "data/maps/Route2_ViridianForest_NorthEntrance/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "###############", - "#######.#######", - "##...#...#...##", - "#.............#", - "#..........##.#", - "###........##.#", - "#.............#", - "#..........##.#", - "###........##.#", - "#.............#", - "#.............#", - "###############" - ], - "points": { - "warp_1": [7, 10], - "warp_3": [7, 1] - }, - "warps": [ - { - "x": 6, - "y": 10, - "enter": "walk_into", - "to_map": "ViridianForest", - "to_point": "warp_2" - }, - { - "x": 7, - "y": 10, - "enter": "walk_south", - "to_map": "ViridianForest", - "to_point": "warp_2" - }, - { - "x": 8, - "y": 10, - "enter": "walk_into", - "to_map": "ViridianForest", - "to_point": "warp_2" - }, - { - "x": 7, - "y": 1, - "enter": "cave_entrance", - "to_map": "Route2", - "to_point": "warp_0" - } - ], - "objects": [ - { - "name": "youngster", - "kind": "npc", - "x": 5, - "y": 4 - }, - { - "name": "old_man_1", - "kind": "npc", - "x": 4, - "y": 7 - }, - { - "name": "cooltrainer_f", - "kind": "npc", - "x": 10, - "y": 5 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2_ViridianForest_SouthEntrance.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2_ViridianForest_SouthEntrance.json deleted file mode 100644 index 3b00abc4eb..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route2_ViridianForest_SouthEntrance.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "name": "Route2_ViridianForest_SouthEntrance", - "source": { - "decomp_file": "data/maps/Route2_ViridianForest_SouthEntrance/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "###############", - "#######.#######", - "##...#...#...##", - "#.............#", - "#..........##.#", - "###........##.#", - "#.............#", - "#..........##.#", - "###........##.#", - "#.............#", - "#.............#", - "###############" - ], - "points": { - "warp_1": [7, 10], - "warp_3": [7, 1] - }, - "warps": [ - { - "x": 6, - "y": 10, - "enter": "walk_into", - "to_map": "Route2", - "to_point": "warp_2" - }, - { - "x": 7, - "y": 10, - "enter": "walk_south", - "to_map": "Route2", - "to_point": "warp_2" - }, - { - "x": 8, - "y": 10, - "enter": "walk_into", - "to_map": "Route2", - "to_point": "warp_2" - }, - { - "x": 7, - "y": 1, - "enter": "cave_entrance", - "to_map": "ViridianForest", - "to_point": "warp_0" - } - ], - "objects": [ - { - "name": "woman_2", - "kind": "npc", - "x": 10, - "y": 6 - }, - { - "name": "woman_1", - "kind": "npc", - "x": 4, - "y": 7, - "wander": { - "x0": 3, - "y0": 6, - "x1": 5, - "y1": 8 - } - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route3.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route3.json deleted file mode 100644 index 91a4f7fc9c..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route3.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "name": "Route3", - "source": { - "decomp_file": "data/maps/Route3/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "####################################################################........########", - "####################################################################........########", - "####################################################################........########", - "#############......................#################################.......#########", - "###########..........................###############################.....###########", - "###########..........................######................#########.....###########", - "###########.........###.......##.....#####...................#######.....###########", - "###########vvv..vvvv###vvvvvvv##vvv..#####...................#######.....###########", - "#######.............###.......##.....#####vvvv..#######vvvvv.#######vvv..###########", - "....................###.......##................#######......#######.....###########", - "....................###.......###...............#######......#####.......~~~~#######", - ".........###........###.......###~~~~~~~........#######...............##S~~~~~~#####", - "..##########vvvv..vv###vvvvvvv###~~~~~~~~~~~~~~########................~~~~~~~~#####", - "############........###.......###~~~~~~~~~~~~~~########................~~~~~~~~#####", - "############.................######~~~~~~~~~~~~########...............~~~~~~~~~#####", - "##############...............######~~~~~~~~~~~~##########.............~~~~~~~~~#####", - "#######################################~~~~~~~~##########.............~~~~~~########", - "####################################################################################", - "####################################################################################", - "####################################################################################" - ], - "points": {}, - "warps": [], - "objects": [ - { - "name": "youngster", - "kind": "npc", - "x": 70, - "y": 13 - }, - { - "name": "lass", - "kind": "npc", - "x": 40, - "y": 11, - "wander": { - "x0": 40, - "y0": 10, - "x1": 40, - "y1": 12 - }, - "trainer": { - "sight": 3, - "looks": [ - "north", - "south" - ] - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 32, - "y": 6, - "facing": "east", - "trainer": { - "sight": 4 - } - }, - { - "name": "lass", - "kind": "npc", - "x": 30, - "y": 3, - "facing": "west", - "trainer": { - "sight": 5 - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 25, - "y": 4, - "facing": "south", - "trainer": { - "sight": 2 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 29, - "y": 10, - "facing": "west", - "trainer": { - "sight": 5 - } - }, - { - "name": "lass", - "kind": "npc", - "x": 19, - "y": 9, - "facing": "west", - "trainer": { - "sight": 5 - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 12, - "y": 6, - "facing": "east", - "trainer": { - "sight": 3 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 17, - "y": 4, - "facing": "south", - "trainer": { - "sight": 2 - } - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "north", - "to_map": "Route4", - "offset": 60 - }, - { - "side": "west", - "to_map": "PewterCity", - "offset": -10 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route4.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route4.json deleted file mode 100644 index 5c8b219633..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route4.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "name": "Route4", - "source": { - "decomp_file": "data/maps/Route4/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "############################################################################################################", - "##########.....#############################################################################################", - "###########################################..<......>.#########.<.....<###############...........###########", - "###########################################..<......>..>.....<..<.....<.....................................", - "####...#..#################################..<......>..>.....<..<.....<.....................................", - "###..#....SSD##.###.############.########....#vv..vv#..>.....<..<.....#vvvvvvvvvvvvvvvvvvvvvvvvvv###########", - "###..................##########........................>.....<..<................................#wwwwwwwwww", - "###vvvvv..........S..##########...S....................>.....<..#v..vvvvvvvvvvvvvvvvvvvvvvvvvvvvv#wwwwwwwwww", - "###..................#########.........................>.....<...................................#wwwwwwwwww", - "###............vvvvvvv########vvvvvvvvvvv...vvvvvvvvvvv#.....#v#......................####vvvvvvv###########", - "###.#.................########.................................#vvvvv..####vvvvvvvvvvv####..................", - "###vvvvvvvvv........#.########.........................................####~~~~~~~~~~~####..................", - "###............vvvvvvv###################..............................####~~~~~~~~~~~####..................", - "######.#..............###########################......................####~~~~~~~~~~~~~##.......###########", - "########.............############################......................####~~~~~~~~~~~~~##vv...vv###########", - "########vvv.......###############################vv..vvvvvvvvv..vvvvv######~~~~~~~~~~~~~##.......###########", - "########........#################################....................######~~~~~~~~~~~~.##.......###########", - "########........#################################....................######~~~~~~~~~~~...........###########", - "########.....vvv########################################.............#################.........#############", - "########........############################################################################################" - ], - "elevation": [ - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "000000000033333000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "000000000000000000000000000000000000000000033033333303000000000303333300000000000000003333333333300000000000", - "000000000000000000000000000000000000000000033033333303303333303303333303333333333333333333333333333333333333", - "000033303300000000000000000000000000000000033033333303303333303303333303333333333333333333333333333333333333", - "000330333300000300030000000000003000000003333000330003303333303303333300000000000000000000000000000000000000", - "000333333333333333333000000000033333333333333333333333303333303303333333333333333333333333333333301111111111", - "000000003333333333033000000000033303333333333333333333303333303300330000000000000000000000000000001111111111", - "000333333333333333333000000000333333333333333333333333303333303333333333333333333333333333333333301111111111", - "000333333333333000000000000000000000000003330000000000003333300033333333333333333333330000000000000000000000", - "000303333333333333333300000000333333333333333333333333333333333000000330000000000000000000333333333333333333", - "000000000000333333330300000000333333333333333333333333333333333333333330000333333333330000333333333333333333", - "000333333333333000000000000000000000000003333333333333333333333333333330000333333333330000333333333333333333", - "000000303333333333333300000000000000000000000000033333333333333333333330000333333333333300333333300000000000", - "000000003333333333333000000000000000000000000000033333333333333333333330000333333333333300003330000000000000", - "000000000003333333000000000000000000000000000000000330000000003300000000000333333333333300333333300000000000", - "000000003333333300000000000000000000000000000000033333333333333333333000000333333333333300333333300000000000", - "000000003333333300000000000000000000000000000000033333333333333333333000000333333333333333333333300000000000", - "000000003333300000000000000000000000000000000000000000003333333333333000000000000000003333333330000000000000", - "000000003333333300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - ], - "points": { - "warp_0": [19, 5], - "warp_1": [32, 5], - "warp_2": [12, 5] - }, - "warps": [ - { - "x": 19, - "y": 5, - "enter": "cave_entrance", - "to_map": "MtMoon_1F", - "to_point": "warp_3" - }, - { - "x": 32, - "y": 5, - "enter": "cave_entrance", - "to_map": "MtMoon_B1F", - "to_point": "warp_7" - }, - { - "x": 12, - "y": 5, - "enter": "walk_into", - "to_map": "Route4_PokemonCenter_1F", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "woman_1", - "kind": "npc", - "x": 9, - "y": 8, - "wander": { - "x0": 8, - "y0": 7, - "x1": 10, - "y1": 9 - } - }, - { - "name": "lass", - "kind": "npc", - "x": 75, - "y": 3, - "facing": "east", - "trainer": { - "sight": 4 - } - }, - { - "name": "item_ball", - "kind": "item", - "x": 67, - "y": 5, - "hide_flag": "FLAG_HIDE_ROUTE4_TM05" - }, - { - "name": "boy", - "kind": "npc", - "x": 15, - "y": 14, - "wander": { - "x0": 14, - "y0": 14, - "x1": 16, - "y1": 14 - } - }, - { - "name": "black_belt", - "kind": "npc", - "x": 47, - "y": 3 - }, - { - "name": "black_belt", - "kind": "npc", - "x": 50, - "y": 3 - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "south", - "to_map": "Route3", - "offset": -60 - }, - { - "side": "east", - "to_map": "CeruleanCity", - "offset": -10 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route4_PokemonCenter_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route4_PokemonCenter_1F.json deleted file mode 100644 index c3fe2d586c..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route4_PokemonCenter_1F.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "name": "Route4_PokemonCenter_1F", - "source": { - "decomp_file": "data/maps/Route4_PokemonCenter_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#.#############", - "###############", - "....###..##....", - "....#######....", - "...............", - "...............", - "#..........##..", - "...........##..", - "...............", - "###############" - ], - "elevation": [ - "030000000000000", - "000000000000000", - "333300033003333", - "333300000003333", - "333333333333333", - "330333333333333", - "044033333330033", - "330333333330033", - "333333333333333", - "000000000000000" - ], - "points": { - "nurse_front": [7, 4], - "salesman_front": [2, 3], - "warp_1": [7, 8] - }, - "warps": [ - { - "x": 6, - "y": 8, - "enter": "walk_into", - "to_map": "Route4", - "to_point": "warp_2" - }, - { - "x": 7, - "y": 8, - "enter": "walk_south", - "to_map": "Route4", - "to_point": "warp_2" - }, - { - "x": 8, - "y": 8, - "enter": "walk_into", - "to_map": "Route4", - "to_point": "warp_2" - }, - { - "x": 1, - "y": 6, - "enter": "step_on" - } - ], - "objects": [ - { - "name": "nurse", - "kind": "npc", - "x": 7, - "y": 2 - }, - { - "name": "balding_man", - "kind": "npc", - "x": 1, - "y": 3 - }, - { - "name": "gentleman", - "kind": "npc", - "x": 12, - "y": 5 - }, - { - "name": "boy", - "kind": "npc", - "x": 5, - "y": 4, - "wander": { - "x0": 4, - "y0": 3, - "x1": 6, - "y1": 5 - } - }, - { - "name": "youngster", - "kind": "npc", - "x": 14, - "y": 4 - }, - { - "name": "clipboard", - "kind": "npc", - "x": 12, - "y": 6 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route5.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route5.json deleted file mode 100644 index 1b4d929c1d..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route5.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "name": "Route5", - "source": { - "decomp_file": "data/maps/Route5/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#vvvvvvvv#....###############", - "###############....#~~~~~~~~#....###############", - "###############....#~~~~~~~~#....###############", - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#vvvvvvvv#....###############", - "###############....#~~~~~~~~#....###############", - "###############....#~~~~~~~~#....###############", - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#vvvvvvvv#....###############", - "###############....#~~~~~~~~#....###############", - "###############....#~~~~~~~~#....###############", - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#vvvvvvvv#....###############", - "###############....#........#....###############", - "###############....#........#....###############", - "###############....#.#####..#....###############", - "###############....#.#####..#....###############", - "###############....#.#####..#....###############", - "###############....#.##D##..#....###############", - "###############....#........#....###############", - "###############....vvvvvvvvvv....###############", - "###############..................###############", - "###############...............##################", - "###############...............##################", - "###############...............#D################", - "###############.......#....#....S###############", - "###############.......######.....###############", - "################################################", - "##############........######......##############", - "################################################", - "################################################", - "########################..######################", - "#######################....#####################" - ], - "points": { - "warp_0": [31, 31] - }, - "warps": [ - { - "x": 31, - "y": 31, - "enter": "walk_into", - "to_map": "UndergroundPath_NorthEntrance", - "to_point": "warp_1" - }, - { - "x": 23, - "y": 25, - "enter": "walk_into" - }, - { - "x": 24, - "y": 32, - "enter": "walk_south" - }, - { - "x": 25, - "y": 32, - "enter": "walk_south" - } - ], - "objects": [], - "flag_sets": {}, - "connections": [ - { - "side": "north", - "to_map": "CeruleanCity", - "offset": 0 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route6.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route6.json deleted file mode 100644 index 4ce8f0cda2..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route6.json +++ /dev/null @@ -1,176 +0,0 @@ -{ - "name": "Route6", - "source": { - "decomp_file": "data/maps/Route6/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########################", - "########################", - "########################", - "########################", - "########################", - "##.......###..###.....##", - "##.......#......#.....##", - "##vvv.vvvv......vvv.vv##", - "##....................##", - "##....................##", - "##....................##", - "##vvvvvvv.vvvvvvvv######", - "##................######", - "##................#D####", - "##....................##", - "##~~~~...~~~~~~......S##", - "##~~~~...~~~~~~~~~~~~~##", - "##~~~~...~~~~~~~~~~~~~##", - "##~~~~...~~~~~~~~~~~~~##", - "##~~~~..............~~##", - "##~~~~..............~~##", - "##~~~~..............~~##", - "##~~~~~~~~~~~~~~~...~~##", - "##wwwwwwwwwwwww~~...~~##", - "##wwwwwwwwwwwww~~...~~##", - "##wwwwwwwwwwwww~~...~~##", - "##wwwwwwwwwwwww~~...~~##", - "##..................~~##", - "##..................~~##", - "##..................~~##", - "#######......~~~~#######", - "##....#......~~~~#....##", - "#######......~~~~#######", - "#######......~~~~#######", - "##########...###########", - "######...#...#....######", - "########.#...###########", - "########.#...###########", - "wwwwwwwww#...###########", - "wwwwwwwww#...###########" - ], - "elevation": [ - "000000000000000000000000", - "000000000000000000000000", - "000000000000000000000000", - "000000000000000000000000", - "000000000000000000000000", - "003333333000330003333300", - "003333333033333303333300", - "000003000033333300030000", - "003333333333333333333300", - "003333333333333333333300", - "003333333333333333333300", - "000000000300000000000000", - "003333333333333333000000", - "003333333333333333000000", - "003333333333333333333300", - "003333333333333333333000", - "003333333333333333333300", - "003333333333333333333300", - "003333333333333333333300", - "003333333333333333333300", - "003333333333333333333300", - "003333333333333333333300", - "003333333333333333333300", - "001111111111111333333300", - "001111111111111333333300", - "001111111111111333333300", - "001111111111111333333300", - "003333333333333333333300", - "003333333333333333333300", - "003333333333333333333300", - "000000033333333330000000", - "003333033333333330333300", - "000000033333333330000000", - "000000033333333330000000", - "000000000033300000000000", - "000000333033303333000000", - "000000003033300000000000", - "000000003033300000000000", - "111111111033300000000000", - "000000001033300000000000" - ], - "points": { - "warp_0": [19, 13] - }, - "warps": [ - { - "x": 19, - "y": 13, - "enter": "walk_into", - "to_map": "UndergroundPath_SouthEntrance", - "to_point": "warp_1" - }, - { - "x": 12, - "y": 5, - "enter": "cave_entrance" - }, - { - "x": 13, - "y": 5, - "enter": "cave_entrance" - } - ], - "objects": [ - { - "name": "bug_catcher", - "kind": "npc", - "x": 3, - "y": 16, - "facing": "east", - "trainer": { - "sight": 5 - } - }, - { - "name": "camper", - "kind": "npc", - "x": 12, - "y": 21 - }, - { - "name": "picnicker", - "kind": "npc", - "x": 13, - "y": 21 - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 20, - "y": 25, - "facing": "west", - "trainer": { - "sight": 3 - } - }, - { - "name": "picnicker", - "kind": "npc", - "x": 13, - "y": 32, - "facing": "west", - "trainer": { - "sight": 3 - } - }, - { - "name": "camper", - "kind": "npc", - "x": 13, - "y": 33, - "facing": "west", - "trainer": { - "sight": 3 - } - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "south", - "to_map": "VermilionCity", - "offset": -12 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route9.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route9.json deleted file mode 100644 index dae6c11e89..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/Route9.json +++ /dev/null @@ -1,175 +0,0 @@ -{ - "name": "Route9", - "source": { - "decomp_file": "data/maps/Route9/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########################################################################", - "......#~~~~~~~~~~~............######~~~~~~######.................#######", - "#######~~~~~~~~~~~............######~~~~~~######.................#######", - "#######~~~~~~~~~~~#####.......######......######..........#####..#######", - "#######~~~~~~~~~~~#####vvvvv..######vv..vv######vv..vvvvvv#####vv#######", - "#######~~~~~~~~~~~#####.......######......######..........#####..#######", - "......#...........#####.......######......................#####..#######", - "#######vvvvvvvv#..#####......S###########.................#####..#######", - "...............########.............#####....#####...##########.........", - "..#............########.............#####....#####...#####..............", - "###########....########vvvvvvvvvvv..#####....#####...#####..............", - "....#######vvvv########.............#####vvvv#####vv.#####...#####...###", - "###########....########.............#####....#####...#####vvv#####vvv###", - "###########..........................................#####...#####...###", - "###########...............######.....................#####...#####...###", - "###########vvvvvvvvv..vvvv######vv..vvvvvvvvvvvvvvvvv#####vvv#####vvv###", - "###########...............######.....................................###", - "###########...............######.....................................###", - "########################################################################", - "########################################################################" - ], - "points": {}, - "warps": [], - "objects": [ - { - "name": "picnicker", - "kind": "npc", - "x": 14, - "y": 10, - "facing": "west", - "trainer": { - "sight": 3 - } - }, - { - "name": "hiker", - "kind": "npc", - "x": 20, - "y": 17, - "trainer": { - "sight": 4, - "looks": [ - "north", - "west" - ] - } - }, - { - "name": "hiker", - "kind": "npc", - "x": 61, - "y": 16, - "facing": "south", - "trainer": { - "sight": 1 - } - }, - { - "name": "camper", - "kind": "npc", - "x": 26, - "y": 7, - "trainer": { - "sight": 3, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 26, - "y": 1, - "facing": "south", - "trainer": { - "sight": 2 - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 49, - "y": 7, - "facing": "east", - "trainer": { - "sight": 3 - } - }, - { - "name": "hiker", - "kind": "npc", - "x": 53, - "y": 2, - "wander": { - "x0": 50, - "y0": 2, - "x1": 56, - "y1": 2 - }, - "trainer": { - "sight": 1, - "looks": [ - "west", - "east" - ] - } - }, - { - "name": "picnicker", - "kind": "npc", - "x": 60, - "y": 9, - "facing": "east", - "trainer": { - "sight": 4 - } - }, - { - "name": "camper", - "kind": "npc", - "x": 36, - "y": 6, - "facing": "east", - "trainer": { - "sight": 5 - } - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 2, - "y": 8, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "item_ball", - "kind": "item", - "x": 12, - "y": 17, - "hide_flag": "FLAG_HIDE_ROUTE9_TM40" - }, - { - "name": "item_ball", - "kind": "item", - "x": 65, - "y": 17, - "hide_flag": "FLAG_HIDE_ROUTE9_BURN_HEAL" - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "west", - "to_map": "CeruleanCity", - "offset": -10 - }, - { - "side": "east", - "to_map": "Route10", - "offset": 0 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_1F_Corridor.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_1F_Corridor.json deleted file mode 100644 index ef4b147d64..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_1F_Corridor.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "name": "SSAnne_1F_Corridor", - "source": { - "decomp_file": "data/maps/SSAnne_1F_Corridor/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - ".................#####.........", - ".................##D##.........", - ".................#...#.........", - ".................#...#.........", - ".................#...#.........", - ".................#...#.........", - "#.################...##########", - "##################...##########", - "###...........................#", - "###...........................#", - "#.............................#", - "#...######################....#", - "#...######################....#", - "#...######################....#", - "#...######################....#", - "#...######################....#", - "#...########################..#", - "#...########################..#", - "#...########################..#", - ".###.##########################", - "###############################" - ], - "elevation": [ - "0000000000000000000000000000000", - "0000000000000000000000000000000", - "0000000000000000003330000000000", - "0000000000000000003330000000000", - "0000000000000000003330000000000", - "0000000000000000003330000000000", - "0000000000000000003330000000000", - "0000000000000000003330000000000", - "0003333333333333333333333333330", - "0003333333333333333333333333330", - "0333333333333333333333333333330", - "0333000000000000000000000033330", - "0333000000000000000000000033330", - "0333000000000000000000000033330", - "0333000000000000000000000033330", - "0333000000000000000000000033330", - "0333000000000000000000000033330", - "0333000000000000000000000000330", - "0333000000000000000000000000330", - "0000000000000000000000000000000", - "0000000000000000000000000000000" - ], - "points": { - "warp_0": [3, 8], - "warp_2": [19, 1], - "warp_3": [20, 0] - }, - "warps": [ - { - "x": 3, - "y": 8, - "enter": "walk_west", - "to_map": "SSAnne_2F_Corridor", - "to_point": "warp_0" - }, - { - "x": 2, - "y": 18, - "enter": "walk_south" - }, - { - "x": 19, - "y": 1, - "enter": "walk_into", - "to_map": "SSAnne_Exterior", - "to_point": "warp_2" - }, - { - "x": 20, - "y": 0, - "enter": "walk_into", - "to_map": "SSAnne_Exterior", - "to_point": "warp_3" - }, - { - "x": 3, - "y": 20, - "enter": "walk_into" - }, - { - "x": 28, - "y": 17, - "enter": "walk_west" - }, - { - "x": 5, - "y": 10, - "enter": "walk_south" - }, - { - "x": 8, - "y": 10, - "enter": "walk_south" - }, - { - "x": 11, - "y": 10, - "enter": "walk_south" - }, - { - "x": 14, - "y": 10, - "enter": "walk_south" - }, - { - "x": 17, - "y": 10, - "enter": "walk_south" - }, - { - "x": 23, - "y": 10, - "enter": "walk_south" - }, - { - "x": 20, - "y": 10, - "enter": "walk_south" - } - ], - "objects": [ - { - "name": "sailor", - "kind": "npc", - "x": 18, - "y": 8 - }, - { - "name": "worker_m", - "kind": "npc", - "x": 12, - "y": 9 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_2F_Corridor.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_2F_Corridor.json deleted file mode 100644 index 9f74e4ce17..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_2F_Corridor.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "name": "SSAnne_2F_Corridor", - "source": { - "decomp_file": "data/maps/SSAnne_2F_Corridor/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "##################################", - "##################################", - "#..###########################.###", - "#..###########################.###", - "#....#########################...#", - "#....#########################...#", - "#....#########################...#", - "#....#########################...#", - "#....#######################.....#", - "#....#######################.....#", - "#....#D###D###D###D###D###D#.....#", - "###..............................#", - "###..............................#", - "###..............................#", - "#................................#", - "##################################" - ], - "points": { - "warp_0": [2, 2], - "warp_2": [30, 2] - }, - "warps": [ - { - "x": 2, - "y": 2, - "enter": "walk_east", - "to_map": "SSAnne_1F_Corridor", - "to_point": "warp_0" - }, - { - "x": 3, - "y": 12, - "enter": "walk_west" - }, - { - "x": 30, - "y": 2, - "enter": "walk_east", - "to_map": "SSAnne_CaptainsOffice", - "to_point": "warp_0" - }, - { - "x": 6, - "y": 10, - "enter": "walk_into" - }, - { - "x": 10, - "y": 10, - "enter": "walk_into" - }, - { - "x": 14, - "y": 10, - "enter": "walk_into" - }, - { - "x": 18, - "y": 10, - "enter": "walk_into" - }, - { - "x": 22, - "y": 10, - "enter": "walk_into" - }, - { - "x": 26, - "y": 10, - "enter": "walk_into" - } - ], - "objects": [ - { - "name": "blue", - "kind": "npc", - "x": 30, - "y": 2, - "hide_flag": "FLAG_HIDE_SS_ANNE_RIVAL" - }, - { - "name": "sailor", - "kind": "npc", - "x": 2, - "y": 7, - "wander": { - "x0": 2, - "y0": 5, - "x1": 2, - "y1": 9 - } - } - ], - "flag_sets": { - "new_game": [ - "FLAG_HIDE_SS_ANNE_RIVAL" - ] - }, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_CaptainsOffice.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_CaptainsOffice.json deleted file mode 100644 index 03a230a27e..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_CaptainsOffice.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "SSAnne_CaptainsOffice", - "source": { - "decomp_file": "data/maps/SSAnne_CaptainsOffice/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "#########", - "#########", - "#......##", - "####.#..#", - "####....#", - "#.......#", - "###.....#", - "###.....#", - "###.....#", - "#########" - ], - "points": { - "warp_0": [3, 7] - }, - "warps": [ - { - "x": 3, - "y": 7, - "enter": "walk_west", - "to_map": "SSAnne_2F_Corridor", - "to_point": "warp_2" - } - ], - "objects": [ - { - "name": "captain", - "kind": "npc", - "x": 5, - "y": 4 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_Exterior.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_Exterior.json deleted file mode 100644 index 20658451d2..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/SSAnne_Exterior.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "name": "SSAnne_Exterior", - "source": { - "decomp_file": "data/maps/SSAnne_Exterior/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "wwwwwwwwww##wwwwwwwwwwwwwwwwww#####wwwwwwwwww#########################", - "wwwwwwwwww##wwwwwwwwwwwwwwwwww#####wwwwwwwwww#########################", - "wwww##wwwwwwwwwwwwwwwwwwwwwwww#####wwwwwwwwww##........###.###########", - "wwww##wwwwwwwwwwwwww####wwwwww#####wwwwwwwww###........###.###########", - "##wwwwwwww##ww##ww######www##w#####w##wwww#####............###########", - "##wwwww##w##########www#########.#######ww##wwwwwwwwwwwww..###########", - "wwww##w####w##ww##wwwww####ww##...##ww####wwwwwwwwwwwwwww..###########", - "wwww####w##wwwwwwwwwwwwwwwwwwww...wwwwww##wwwwwwwwwwwwwww..###########", - "www##w##wwwwwwwwwwwwwwwwwwwwwww...wwwwwwwwwwwwwwwwwwwwwww..###########", - "w####wwwwwwwwwwwwwwwwwwwwwwwwww...wwwwwwwwwwwwwwwwwwwwwww..###########", - "w##wwwwwwwwwwwwwwwwwwww##wwwwww#.#wwwwww##wwwwwwwwwwwwwww..###########", - "ww##wwwwwwwwwwwwwwwww####ww#####.###ww######wwwwwwwwwwwww..###########", - "ww##wwwwwwwwwwwwwwwww##ww#######.#######ww##wwwwwwwwwwwww..###########", - "##wwwwwwwwwwwwwwwwww##www##www##.##w##wwwwww##wwwwwwwwwww..###########", - "##wwwwwwwwwwwwwwww####wwwwwwww##.##wwwwwwwww##wwwwwwwwwww..###########", - "w##wwwwwwwwwwwwwww##wwwwwwwwww######wwwwwwwww##wwwwwwwwww..###########", - "w##wwwwwwwwwwwwww##wwwwwwwwwwwwwwwwwwwwwwwwww##ww##wwwwww..###########", - "ww##wwwwwwwwww##w##wwwwwwwwwwwwwwwwwwwwwwwwwwww####wwwwww..###########", - "ww##w##wwwww######wwwwwwwwwwwwwwwwwwwwwwwwwwwww##w##wwwww..###########", - "www####w##ww##ww##wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##wwwww..###########", - "www##w######wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##www..###########", - "wwwwww##ww##wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##www..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##wwww..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww####ww..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##ww..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##www..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##www..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww##ww..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww####..###########", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww#################", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww###############", - "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww################" - ], - "elevation": [ - "1111111111001111111111111111110000011111111110000000000000000000000000", - "1111111111001111111111111111110000011111111110000000000000000000000011", - "1111001111111111111111111111110000011111111110033333333000300000000000", - "1111001111111111111100001111110000011111111100033333333000300000000000", - "0011111111001100110000001110010000010011110000033333333333300000000000", - "0011111001000000000011100000000030000000110011111111111113300000000000", - "1111001000010011001111100001100333001100001111111111111113300000000011", - "1111000010011111111111111111111333111111001111111111111113300000000000", - "1110010011111111111111111111111333111111111111111111111113300000000000", - "1000011111111111111111111111111333111111111111111111111113300000000000", - "1001111111111111111111100111111030111111001111111111111113300000000000", - "1100111111111111111110000110000030001100000011111111111113300000000000", - "1100111111111111111110011000000030000000110011111111111113300000000000", - "0011111111111111111100111001111030110011111100111111111113300000000000", - "0011111111111111110000111110110030011011111100111111111113300000000000", - "1001111111111111110011111011110000011110111110011111111113300000000000", - "1001111111111111100111101111111111111111110110011001111113300000000000", - "1100111111111100100111011111111111111111111101100001111113300000000000", - "1100100111110000001110111111111111111111111111100100111113300000000000", - "1110000100110011001111111111111111111111111111011100111113300000000000", - "1110010000001111111011111111111111111111111111101111001113300000000000", - "1111110011001111101111111111111111111111111111110111001113300000000000", - "1111111111111111011111111111111111111111111111111110011113300000000000", - "1111111111111011111111111111111111111111111111111010000113300000000000", - "1111111111111111111111111111111111111111111111111111100113300000000000", - "1111111111111111111111111111111111111111111111111101001113300000000000", - "1111111111111111111111111111111111111111111111111110001113300000000000", - "1111111111111111111111111111111111111111111111111111100113300000000000", - "1111111111111111111111111111111111111111111111111111100003300000000000", - "1111111111111111111111111111111111111111111111111111000000000000000000", - "1111111111111111111111111111111111111111111111111111110000000000000000", - "1111111111111111111111111111111111111111111111111111110000000000000000" - ], - "points": { - "warp_1": [32, 5], - "warp_2": [32, 14], - "warp_3": [33, 15] - }, - "warps": [ - { - "x": 31, - "y": 5, - "enter": "walk_into", - "to_map": "VermilionCity", - "to_point": "warp_0" - }, - { - "x": 32, - "y": 5, - "enter": "walk_north", - "to_map": "VermilionCity", - "to_point": "warp_1" - }, - { - "x": 32, - "y": 14, - "enter": "walk_south", - "to_map": "SSAnne_1F_Corridor", - "to_point": "warp_2" - }, - { - "x": 33, - "y": 15, - "enter": "walk_into", - "to_map": "SSAnne_1F_Corridor", - "to_point": "warp_3" - }, - { - "x": 33, - "y": 5, - "enter": "walk_into", - "to_map": "VermilionCity", - "to_point": "warp_2" - } - ], - "objects": [ - { - "name": "ss_anne", - "kind": "npc", - "x": 30, - "y": 16, - "hide_flag": "FLAG_HIDE_SS_ANNE" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_NorthEntrance.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_NorthEntrance.json deleted file mode 100644 index 7ae2377b26..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_NorthEntrance.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "UndergroundPath_NorthEntrance", - "source": { - "decomp_file": "data/maps/UndergroundPath_NorthEntrance/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "#############", - "#############", - "#...........#", - "..##.##..##..", - "..#..##...#..", - "..#..##...#..", - "..##.....##..", - ".............", - "#...........#", - "#############" - ], - "points": { - "warp_1": [6, 8], - "warp_3": [7, 4] - }, - "warps": [ - { - "x": 5, - "y": 8, - "enter": "walk_into", - "to_map": "Route5", - "to_point": "warp_0" - }, - { - "x": 6, - "y": 8, - "enter": "walk_south", - "to_map": "Route5", - "to_point": "warp_0" - }, - { - "x": 7, - "y": 8, - "enter": "walk_into", - "to_map": "Route5", - "to_point": "warp_0" - }, - { - "x": 7, - "y": 4, - "enter": "walk_west", - "to_map": "UndergroundPath_NorthSouthTunnel", - "to_point": "warp_0" - } - ], - "objects": [ - { - "name": "little_girl", - "kind": "npc", - "x": 5, - "y": 6 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_NorthSouthTunnel.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_NorthSouthTunnel.json deleted file mode 100644 index 72752cc45a..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_NorthSouthTunnel.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "name": "UndergroundPath_NorthSouthTunnel", - "source": { - "decomp_file": "data/maps/UndergroundPath_NorthSouthTunnel/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "########", - "########", - "#....###", - "#....###", - "#....###", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "#......#", - "###....#", - "###....#", - "###....#", - "########" - ], - "points": { - "warp_0": [4, 3], - "warp_1": [3, 60] - }, - "warps": [ - { - "x": 4, - "y": 3, - "enter": "walk_east", - "to_map": "UndergroundPath_NorthEntrance", - "to_point": "warp_3" - }, - { - "x": 3, - "y": 60, - "enter": "walk_west", - "to_map": "UndergroundPath_SouthEntrance", - "to_point": "warp_3" - } - ], - "objects": [], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_SouthEntrance.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_SouthEntrance.json deleted file mode 100644 index 000f7e215e..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/UndergroundPath_SouthEntrance.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "UndergroundPath_SouthEntrance", - "source": { - "decomp_file": "data/maps/UndergroundPath_SouthEntrance/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "#############", - "#############", - "#...........#", - "..##.##..##..", - "..#..##...#..", - "..#..##...#..", - "..##.....##..", - ".............", - "#...........#", - "#############" - ], - "points": { - "warp_1": [6, 8], - "warp_3": [7, 4] - }, - "warps": [ - { - "x": 5, - "y": 8, - "enter": "walk_into", - "to_map": "Route6", - "to_point": "warp_0" - }, - { - "x": 6, - "y": 8, - "enter": "walk_south", - "to_map": "Route6", - "to_point": "warp_0" - }, - { - "x": 7, - "y": 8, - "enter": "walk_into", - "to_map": "Route6", - "to_point": "warp_0" - }, - { - "x": 7, - "y": 4, - "enter": "walk_west", - "to_map": "UndergroundPath_NorthSouthTunnel", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "woman_2", - "kind": "npc", - "x": 5, - "y": 6 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity.json deleted file mode 100644 index 8d7cd08e6e..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity.json +++ /dev/null @@ -1,246 +0,0 @@ -{ - "name": "VermilionCity", - "source": { - "decomp_file": "data/maps/VermilionCity/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "wwwwwwwwwwwwwwwwwwwww#...#######################", - "wwwwwwwwwwwwwwwwwwwww#...#######################", - "wwwwww####wwwwwwwwwww#...#######################", - "wwww######www#####www#...#######################", - "wwww##ww##############...#######################", - "www##www##############...#######################", - "www##www#D###SSD######...########S##############", - "ww##wwww.................................#######", - "ww##wwww.................................#######", - "w##wwwww.................................#######", - "w##wwwwwwwwwww...........................#######", - "w##wwwwwwwwwww...........................#######", - "w##wwwwwwwwwwwwwwwww.....................#######", - "w##wwwwwwwwwwwwwwwww.....................#######", - "w##wwwwwwww.....wwww.....................#######", - "ww##wwww...#####..####.....####..........#######", - "ww##wwww...#####..####.....####..........##.....", - "www##www..S#D###..#D##.....SSD###############S##", - "www##www..........................S.............", - "ww##wwww........................................", - "ww##wwww........................................", - "ww##wwww.......................www.....wwwwwwwww", - "ww##wwww...#######.........####www.....wwwwwwwww", - "www##www...#######..wwww...####www64.64wwwwwwwww", - "www##www##S########.wwww...#D##www.....ww#######", - "www##www.....#D#....wwww.......wwww...www#######", - "www##www............wwwwwwwwwwwwwww...www#######", - "ww##wwww............wwwwwwwwwwwwwww...www#######", - "ww##wwww............ww................www#######", - "ww##wwwwwwwwwwwwwwwwww................www##wwwww", - "ww##wwwwwwwwwwwwwwwwww................www##wwwww", - "www##wwwwwwwwwwwwwwwww...wwwwwwwwwwwwwwww##wwwww", - "www##wwwwwwwwwwwwwwwww...wwwwwwwwwwwwwwww##wwwww", - "wwwww##wwwwwwwwwwwww##...####wwwwwwwwwww##wwwwww", - "wwwww##wwwwwwwww######...########wwww##w##wwwwww", - "wwwwww##wwwwww######ww###wwww####wwww##w##wwwwww", - "wwwwww####wwww##wwwwww###wwwwwwww##wwwww##wwwwww", - "wwwwwwww######wwwwwwww.#.wwwwwwww########wwwwwww", - "wwwwwwwwww####wwwwwwww.#.wwwwwwwwww######wwwwwww", - "wwwwwwwwwwwwwwwwwwwwww.#.wwwwwwwwwwwwwwwwwwwwwww" - ], - "elevation": [ - "000000000000000000001033300000000000000000000000", - "000000000000000000001033300000000000000000000000", - "000001000011111111111033300000000000000000000000", - "000100000011100000111033300000000000000000000000", - "000100110000000000000033300000000000000000000000", - "011001110000000000000033300000000000000000000000", - "011001110000000000000033300000000000000000000000", - "010011113333333333333333333333333333333330000000", - "010011113333333333333333333333333333333330000000", - "000111113333333333333333333333333333333330000000", - "000111111111113333333333333333333333333330000000", - "000111111111113333333333333333333333333330000000", - "000111111111111111113333333333333333333330000000", - "000111111111111111113333333333333333333330000000", - "000111111111111111113333333333333333333330000000", - "010011113330000033000033333000033333333330000000", - "010011113330000033000033333000033333333330033333", - "001001113300000033000033333000000000000000000000", - "001001113333333333333333333333333303333333333333", - "000011113333333333333333333333333333333333333333", - "000011113333333333333333333333333333333333333333", - "000011113333333333333333333333311133333111111111", - "000011113330000000333333333000011133333111111111", - "001001113330000000331111333000011133333111111111", - "001001110000000000031111333000011133333110000000", - "001001113333300033331111333333311113331110000000", - "011001113333333333331111111111111113331110000000", - "010011113333333333331111111111111113331110000000", - "010011113333333333331133333333333333331110000000", - "010011111111111111111133333333333333331110011111", - "010011111111111111111133333333333333331110011000", - "011001111111111111111133311111111111111110010000", - "001001111111111111111133311111111111111110010000", - "001110011111111111110033300001111111111100110000", - "000110011111111100000033300000000111100100110000", - "000011001111110000000000000110000111100100110000", - "000001000011110011111100011111111001111100100000", - "000001110000001110001100011000001000000001100000", - "000000001100001000000100010000000010000001000000", - "000000000000000000000100010000000000000000000000" - ], - "points": { - "warp_0": [22, 34], - "warp_1": [23, 34], - "warp_2": [24, 34], - "warp_4": [15, 6], - "warp_5": [12, 17], - "warp_9": [14, 25] - }, - "warps": [ - { - "x": 22, - "y": 34, - "enter": "walk_south", - "to_map": "SSAnne_Exterior", - "to_point": "warp_1" - }, - { - "x": 23, - "y": 34, - "enter": "walk_south", - "to_map": "SSAnne_Exterior", - "to_point": "warp_1" - }, - { - "x": 24, - "y": 34, - "enter": "walk_south", - "to_map": "SSAnne_Exterior", - "to_point": "warp_1" - }, - { - "x": 9, - "y": 6, - "enter": "walk_into" - }, - { - "x": 15, - "y": 6, - "enter": "walk_into", - "to_map": "VermilionCity_PokemonCenter_1F", - "to_point": "warp_1" - }, - { - "x": 12, - "y": 17, - "enter": "walk_into", - "to_map": "VermilionCity_PokemonFanClub", - "to_point": "warp_1" - }, - { - "x": 19, - "y": 17, - "enter": "walk_into" - }, - { - "x": 29, - "y": 17, - "enter": "walk_into" - }, - { - "x": 28, - "y": 24, - "enter": "walk_into" - }, - { - "x": 14, - "y": 25, - "enter": "walk_into", - "to_map": "VermilionCity_Gym", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "woman_1", - "kind": "npc", - "x": 22, - "y": 11, - "wander": { - "x0": 18, - "y0": 11, - "x1": 26, - "y1": 11 - } - }, - { - "name": "old_man_1", - "kind": "npc", - "x": 17, - "y": 9 - }, - { - "name": "old_man_2", - "kind": "npc", - "x": 36, - "y": 10 - }, - { - "name": "machop", - "kind": "npc", - "x": 35, - "y": 11, - "wander": { - "x0": 33, - "y0": 9, - "x1": 37, - "y1": 13 - } - }, - { - "name": "sailor", - "kind": "npc", - "x": 31, - "y": 30, - "wander": { - "x0": 27, - "y0": 30, - "x1": 35, - "y1": 30 - } - }, - { - "name": "sailor", - "kind": "npc", - "x": 24, - "y": 33 - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 19, - "y": 24, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "scientist", - "kind": "npc", - "x": 25, - "y": 7, - "hide_flag": "FLAG_HIDE_VERMILION_CITY_OAKS_AIDE" - } - ], - "flag_sets": { - "new_game": [ - "FLAG_HIDE_VERMILION_CITY_OAKS_AIDE" - ] - }, - "connections": [ - { - "side": "north", - "to_map": "Route6", - "offset": 12 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_Gym.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_Gym.json deleted file mode 100644 index 2ed0d496e1..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_Gym.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "name": "VermilionCity_Gym", - "source": { - "decomp_file": "data/maps/VermilionCity_Gym/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "###########", - "###########", - "#..#...#..#", - "#.........#", - "#.........#", - "#.........#", - "####...####", - "####...####", - "...........", - "...........", - ".#.#.#.#.#.", - "...........", - ".#.#.#.#.#.", - "...........", - ".#.#.#.#.#.", - "...........", - "...........", - "...S...S...", - "...........", - "...........", - "###########" - ], - "elevation": [ - "00000000000", - "00000000000", - "03303330330", - "03333333330", - "03333333330", - "03333333330", - "00000000000", - "00000000000", - "33333333333", - "33333333333", - "30303030303", - "33333333333", - "30303030303", - "33333333333", - "30303030303", - "33333333333", - "33333333333", - "33303330333", - "33333333333", - "33333333333", - "00000000000" - ], - "points": { - "warp_1": [5, 19] - }, - "warps": [ - { - "x": 4, - "y": 19, - "enter": "walk_into", - "to_map": "VermilionCity", - "to_point": "warp_9" - }, - { - "x": 5, - "y": 19, - "enter": "walk_south", - "to_map": "VermilionCity", - "to_point": "warp_9" - }, - { - "x": 6, - "y": 19, - "enter": "walk_into", - "to_map": "VermilionCity", - "to_point": "warp_9" - } - ], - "objects": [ - { - "name": "lt_surge", - "kind": "npc", - "x": 5, - "y": 2 - }, - { - "name": "balding_man", - "kind": "npc", - "x": 2, - "y": 11, - "trainer": { - "sight": 3, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "sailor", - "kind": "npc", - "x": 8, - "y": 13, - "trainer": { - "sight": 3, - "looks": [ - "north", - "east", - "south", - "west" - ] - } - }, - { - "name": "gym_guy", - "kind": "npc", - "x": 4, - "y": 17 - }, - { - "name": "gentleman", - "kind": "npc", - "x": 7, - "y": 8, - "facing": "west", - "trainer": { - "sight": 3 - } - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_PokemonCenter_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_PokemonCenter_1F.json deleted file mode 100644 index 5e53a3ba51..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_PokemonCenter_1F.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "name": "VermilionCity_PokemonCenter_1F", - "source": { - "decomp_file": "data/maps/VermilionCity_PokemonCenter_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#.#############", - "###############", - "....###..##....", - "....#######....", - "...............", - "...............", - "#..........##..", - "...........##..", - "...............", - "###############" - ], - "elevation": [ - "030000000000000", - "000000000000000", - "333300033003333", - "333300000003333", - "333333333333333", - "330333333333333", - "044033333330033", - "330333333330033", - "333333333333333", - "000000000000000" - ], - "points": { - "nurse_front": [7, 4], - "warp_1": [7, 8] - }, - "warps": [ - { - "x": 6, - "y": 8, - "enter": "walk_into", - "to_map": "VermilionCity", - "to_point": "warp_4" - }, - { - "x": 7, - "y": 8, - "enter": "walk_south", - "to_map": "VermilionCity", - "to_point": "warp_4" - }, - { - "x": 8, - "y": 8, - "enter": "walk_into", - "to_map": "VermilionCity", - "to_point": "warp_4" - }, - { - "x": 1, - "y": 6, - "enter": "step_on" - } - ], - "objects": [ - { - "name": "nurse", - "kind": "npc", - "x": 7, - "y": 2 - }, - { - "name": "man", - "kind": "npc", - "x": 4, - "y": 8, - "wander": { - "x0": 3, - "y0": 8, - "x1": 5, - "y1": 8 - } - }, - { - "name": "hiker", - "kind": "npc", - "x": 12, - "y": 5 - }, - { - "name": "youngster", - "kind": "npc", - "x": 14, - "y": 4, - "wander": { - "x0": 14, - "y0": 3, - "x1": 14, - "y1": 5 - } - }, - { - "name": "cooltrainer_f", - "kind": "npc", - "x": 6, - "y": 4 - }, - { - "name": "0", - "kind": "npc", - "x": 2, - "y": 1, - "hide_flag": "FLAG_HIDE_FAME_CHECKER_LT_SURGE_JOURNAL" - }, - { - "name": "0", - "kind": "npc", - "x": 3, - "y": 1, - "hide_flag": "FLAG_HIDE_FAME_CHECKER_LT_SURGE_JOURNAL" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_PokemonFanClub.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_PokemonFanClub.json deleted file mode 100644 index e087d94793..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/VermilionCity_PokemonFanClub.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "name": "VermilionCity_PokemonFanClub", - "source": { - "decomp_file": "data/maps/VermilionCity_PokemonFanClub/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "############", - "############", - "............", - ".....22.....", - "#....88....#", - "#..64##64..#", - "#..64##64..#", - "#..........#", - "#..........#", - "............", - "#..........#", - "############" - ], - "elevation": [ - "000000000000", - "000000000000", - "333333333333", - "333333333333", - "033304403330", - "033330033330", - "033330033330", - "033333333330", - "033333333330", - "333333333333", - "033333333330", - "000000000000" - ], - "points": { - "warp_1": [5, 10] - }, - "warps": [ - { - "x": 4, - "y": 10, - "enter": "walk_into", - "to_map": "VermilionCity", - "to_point": "warp_5" - }, - { - "x": 5, - "y": 10, - "enter": "walk_south", - "to_map": "VermilionCity", - "to_point": "warp_5" - }, - { - "x": 6, - "y": 10, - "enter": "walk_into", - "to_map": "VermilionCity", - "to_point": "warp_5" - } - ], - "objects": [ - { - "name": "gentleman", - "kind": "npc", - "x": 5, - "y": 4 - }, - { - "name": "worker_f", - "kind": "npc", - "x": 6, - "y": 4 - }, - { - "name": "pikachu", - "kind": "npc", - "x": 7, - "y": 6 - }, - { - "name": "seel", - "kind": "npc", - "x": 4, - "y": 6 - }, - { - "name": "woman_1", - "kind": "npc", - "x": 4, - "y": 5 - }, - { - "name": "fat_man", - "kind": "npc", - "x": 7, - "y": 5 - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity.json deleted file mode 100644 index 5bf976082a..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity.json +++ /dev/null @@ -1,216 +0,0 @@ -{ - "name": "ViridianCity", - "source": { - "decomp_file": "data/maps/ViridianCity/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "###################.....########################", - "###################....S########################", - "###################.....########################", - "###################.....########################", - "###################.....####..............######", - "########..................................######", - "########.##########.......................######", - "########.###########.............######...######", - "########.##########..............######...######", - "########.###########...######....######...######", - "########.##########.....#####...S..#D#....######", - "########.###########...##D###.............######", - "########.##########........##vvvvvvvvvvvvv######", - "########.###########......................######", - "########.##########.......................######", - "######...###########.........###################", - ".........##########.S..######.............######", - "........................#####.....####....######", - ".......................##D###.....####....######", - "..................................SSD#....######", - "#######...................................######", - "#######...................................######", - "#######...................................######", - "###########.............#####.............######", - "###########.##..........#####.............######", - "#######.....##..........#####.............######", - "#######....wwwwww.......SSD##.............######", - "#######....wwwwww.........................######", - "#######....wwwwww.........................######", - "#######....wwwwww.........................######", - "#######vvvvwwwwwwvvv...vvvvvvvvvvvvvvvvvvv######", - "#######.............S.....................######", - "#######...................................######", - "#######...................................######", - "##############............................######", - "##############............................######", - "######################....######################", - "######################....######################", - "######################....######################", - "######################....######################" - ], - "elevation": [ - "000000000000000000033333000000000000000000000000", - "000000000000000000033330000000000000000000000000", - "000000000000000000033333000000000000000000000000", - "000000000000000000033333000000000000000000000000", - "000000000000000000033333000033333333333333000000", - "000000003333333333333333333333333333333333000000", - "000000003000000000033333333333333333333333000000", - "000000003000000000003333333333333000000333000000", - "000000003000000000033333333333333000000333000000", - "000000003000000000003330000003333000000333000000", - "000000003000000000033333000003330330003333000000", - "000000003000000000003330000003333333333333000000", - "000000003000000000033333333000000000000000000000", - "000000003000000000003333333333333333333333000000", - "000000003000000000033333333333333333333333000000", - "000000333000000000003333333330000000000000000000", - "333333333000000000030330000003333333333333000000", - "333333333333333333333333000003333300003333000000", - "333333333333333333333330000003333300003333000000", - "333333333333333333333333333333333300003333000000", - "000000033333333333333333333333333333333333000000", - "000000033333333333333333333333333333333333000000", - "000000033333333333333333333333333333333333000000", - "000000000003333333333333000003333333333333000000", - "000000000003003333333333000003333333333333000000", - "000000033333003333333333000003333333333333000000", - "000000033331111113333333000003333333333333000000", - "000000033331111113333333333333333333333333000000", - "000000033331111113333333333333333333333333000000", - "000000033331111113333333333333333333333333000000", - "000000000001111110003330000000000000000000000000", - "000000033333333333330333333333333333333333000000", - "000000033333333333333333333333333333333333000000", - "000000033333333333333333333333333333333333000000", - "000000000000003333333333333333333333333333000000", - "000000000000003333333333333333333333333333000000", - "000000000000000000000033330000000000000000000000", - "000000000000000000000033330000000000000000000000", - "000000000000000000000033330000000000000000000000", - "000000000000000000000033330000000000000000000000" - ], - "points": { - "warp_0": [26, 26], - "warp_4": [36, 19] - }, - "warps": [ - { - "x": 26, - "y": 26, - "enter": "walk_into", - "to_map": "ViridianCity_PokemonCenter_1F", - "to_point": "warp_1" - }, - { - "x": 25, - "y": 11, - "enter": "walk_into" - }, - { - "x": 36, - "y": 10, - "enter": "walk_into" - }, - { - "x": 25, - "y": 18, - "enter": "walk_into" - }, - { - "x": 36, - "y": 19, - "enter": "walk_into", - "to_map": "ViridianCity_Mart", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "fat_man", - "kind": "npc", - "x": 8, - "y": 26 - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 11, - "y": 24, - "hide_flag": "FLAG_TEMP_11" - }, - { - "name": "old_man_1", - "kind": "npc", - "x": 34, - "y": 11 - }, - { - "name": "var_0", - "kind": "npc", - "x": 21, - "y": 6 - }, - { - "name": "woman_3", - "kind": "npc", - "x": 20, - "y": 12 - }, - { - "name": "youngster", - "kind": "npc", - "x": 33, - "y": 26, - "wander": { - "x0": 33, - "y0": 24, - "x1": 33, - "y1": 28 - } - }, - { - "name": "boy", - "kind": "npc", - "x": 16, - "y": 22, - "wander": { - "x0": 15, - "y0": 20, - "x1": 17, - "y1": 24 - } - }, - { - "name": "cut_tree", - "kind": "npc", - "x": 18, - "y": 5, - "hide_flag": "FLAG_TEMP_12" - }, - { - "name": "item_ball", - "kind": "item", - "x": 17, - "y": 5, - "hide_flag": "FLAG_HIDE_VIRIDIAN_CITY_POTION" - } - ], - "flag_sets": {}, - "connections": [ - { - "side": "north", - "to_map": "Route2", - "offset": 12 - }, - { - "side": "south", - "to_map": "Route1", - "offset": 12 - }, - { - "side": "west", - "to_map": "Route22", - "offset": 10 - } - ] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity_Mart.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity_Mart.json deleted file mode 100644 index 9b3e8509a4..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity_Mart.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "name": "ViridianCity_Mart", - "source": { - "decomp_file": "data/maps/ViridianCity_Mart/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "###########", - "###########", - "##.#......#", - "...#.......", - "####...##.#", - ".......##.#", - ".##....##.#", - "...........", - "###########" - ], - "elevation": [ - "00000000000", - "00000000000", - "00303333330", - "33003333333", - "00003330030", - "33333330030", - "30033330030", - "33333333333", - "00000000000" - ], - "points": { - "clerk_front": [4, 3], - "warp_1": [4, 7] - }, - "warps": [ - { - "x": 3, - "y": 7, - "enter": "walk_into", - "to_map": "ViridianCity", - "to_point": "warp_4" - }, - { - "x": 4, - "y": 7, - "enter": "walk_south", - "to_map": "ViridianCity", - "to_point": "warp_4" - }, - { - "x": 5, - "y": 7, - "enter": "walk_into", - "to_map": "ViridianCity", - "to_point": "warp_4" - } - ], - "objects": [ - { - "name": "clerk", - "kind": "npc", - "x": 2, - "y": 3 - }, - { - "name": "youngster", - "kind": "npc", - "x": 6, - "y": 2, - "wander": { - "x0": 5, - "y0": 1, - "x1": 7, - "y1": 3 - } - }, - { - "name": "woman_1", - "kind": "npc", - "x": 9, - "y": 5, - "wander": { - "x0": 9, - "y0": 4, - "x1": 9, - "y1": 6 - } - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity_PokemonCenter_1F.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity_PokemonCenter_1F.json deleted file mode 100644 index e0341f1440..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianCity_PokemonCenter_1F.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "name": "ViridianCity_PokemonCenter_1F", - "source": { - "decomp_file": "data/maps/ViridianCity_PokemonCenter_1F/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "allow_running": false, - "tiles": [ - "#.#############", - "###############", - "....###..##....", - "....#######....", - "...............", - "...............", - "#..........##..", - "...........##..", - "...............", - "###############" - ], - "elevation": [ - "030000000000000", - "000000000000000", - "333300033003333", - "333300000003333", - "333333333333333", - "330333333333333", - "044033333330033", - "330333333330033", - "333333333333333", - "000000000000000" - ], - "points": { - "nurse_front": [7, 4], - "warp_1": [7, 8] - }, - "warps": [ - { - "x": 6, - "y": 8, - "enter": "walk_into", - "to_map": "ViridianCity", - "to_point": "warp_0" - }, - { - "x": 7, - "y": 8, - "enter": "walk_south", - "to_map": "ViridianCity", - "to_point": "warp_0" - }, - { - "x": 8, - "y": 8, - "enter": "walk_into", - "to_map": "ViridianCity", - "to_point": "warp_0" - }, - { - "x": 1, - "y": 6, - "enter": "step_on" - } - ], - "objects": [ - { - "name": "nurse", - "kind": "npc", - "x": 7, - "y": 2 - }, - { - "name": "gentleman", - "kind": "npc", - "x": 12, - "y": 5 - }, - { - "name": "boy", - "kind": "npc", - "x": 4, - "y": 7 - }, - { - "name": "youngster", - "kind": "npc", - "x": 2, - "y": 3, - "wander": { - "x0": 1, - "y0": 2, - "x1": 3, - "y1": 4 - } - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianForest.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianForest.json deleted file mode 100644 index a6e51104b3..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps/ViridianForest.json +++ /dev/null @@ -1,224 +0,0 @@ -{ - "name": "ViridianForest", - "source": { - "decomp_file": "data/maps/ViridianForest/map.json", - "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", - "date": "2026-10-31" - }, - "tiles": [ - "######################################################", - "######################################################", - "######################################################", - "######################################################", - "######################################################", - "###########.............###........................###", - "###########.............###........................###", - "###########.............###........................###", - "#..#####..#.............###.....###############....###", - "####...####.............###.....###############....###", - "####...####.............###.....###############....###", - "####...####.....###.....###.....###############....###", - "####..S####~~~~~###~~~~~###~~~~~###############....###", - "###~~~~~###~~~~~###~~~~~###~~~~~###############....###", - "###~~~~~###~~~~~###~~~~~###~~~~~###############....###", - "###~~~~~###~~~~~###~~~~~###~~~~~~~~~~~.............###", - "###~~~~~###~~~~~###~~~~~###~~~~~~~~~~~.............###", - "###~~~~~###~~~~~###~~~~~###~~~~~~~~~~~.............###", - "###~~~~~###~~~~~###~~~~~###~~~~~~~~~~~.......#########", - "###~~~~~###~~~~~###~~~~~###~~~~~~~~~~........#########", - "###~~~~~###~~~~~###~~~~~###~~~~~~######......#########", - "###~~~~~###~~~~~###~~~~~###...~~~######......#########", - "###~~~~~###~~~~~###.....###......######......#########", - "###~~~~~###~~~~~###..............######......#########", - "###~~~~~###~~~~~#####............######......#########", - "###~~~~~###~~~~~#####............######............###", - "###~~~~~###~~~~~~~#####################....S.......###", - "###~~~~~...~~~~~~~#####################............###", - "###...............#####################............###", - "###......S........#####################~~~~........###", - "###...............#####################~~~~~###....###", - "###.....................###############~~~~~###....###", - "###.....................###############~~~~~###....###", - "###.....................###############~~~~~###....###", - "##################......###############~~~~~###....###", - "##################......###############~~~~~###....###", - "#######################################~~~~~###....###", - "#######################################~~~~~###....###", - "#######################################~~~~~###....###", - "###~~~~~~~~~~~~~~~#####################~~~~~###....###", - "###~~~~~~~~~~~~~~~#####################~~~~~###....###", - "###~~~~~~~~~~~~~~~#####################~~~~~###....###", - "###~~~~~~~~~~~~~~~#####################~~~~~###....###", - "###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~######~~~~........###", - "############~~~~~~~~~~~~~~~~S..~~######~~~.........###", - "############~~~~~~~~~~~~~~~~...~~######~~~.........###", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "############..~~~#########~~...~~######~~~...#########", - "###~~~~~~~~~..~~~~~~~~~~~~~~###~~~~~~~~~~~...~~~~~~###", - "###~~~~~~~~~..~~~~~~~~~~~~~~###~~~~~~~~~~~...~~~~~~###", - "###~~~~~~~~~...~~~~~~~~~~~~.###.~~~~~~~~~...~~~~~~~###", - "###~~~~~~~~~....~~~~~~~~~~.......~~~~~~~....~~~~~~~###", - "###~~~~~~~~............................S....~~~~~~~###", - "###~~~~~~~~....................S...........~~~~~~~~###", - "###~~~~~~~~................................~~~~~~~~###", - "###########################......#####################", - "######################################################", - "######################################################", - "######################################################", - "######################################################", - "######################################################", - "######################################################" - ], - "points": { - "warp_0": [29, 62], - "warp_2": [5, 9] - }, - "warps": [ - { - "x": 29, - "y": 62, - "enter": "walk_south", - "to_map": "Route2_ViridianForest_SouthEntrance", - "to_point": "warp_3" - }, - { - "x": 28, - "y": 62, - "enter": "walk_south", - "to_map": "Route2_ViridianForest_SouthEntrance", - "to_point": "warp_3" - }, - { - "x": 5, - "y": 9, - "enter": "cave_entrance", - "to_map": "Route2_ViridianForest_NorthEntrance", - "to_point": "warp_1" - }, - { - "x": 6, - "y": 9, - "enter": "cave_entrance", - "to_map": "Route2_ViridianForest_NorthEntrance", - "to_point": "warp_1" - }, - { - "x": 30, - "y": 62, - "enter": "walk_south", - "to_map": "Route2_ViridianForest_SouthEntrance", - "to_point": "warp_3" - }, - { - "x": 4, - "y": 9, - "enter": "cave_entrance", - "to_map": "Route2_ViridianForest_NorthEntrance", - "to_point": "warp_1" - } - ], - "objects": [ - { - "name": "youngster", - "kind": "npc", - "x": 29, - "y": 58 - }, - { - "name": "boy", - "kind": "npc", - "x": 45, - "y": 58 - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 47, - "y": 45, - "facing": "west", - "trainer": { - "sight": 5 - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 47, - "y": 29, - "facing": "west", - "trainer": { - "sight": 4 - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 7, - "y": 22, - "facing": "west", - "trainer": { - "sight": 4 - } - }, - { - "name": "item_ball", - "kind": "item", - "x": 5, - "y": 41, - "hide_flag": "FLAG_HIDE_VIRIDIAN_FOREST_POKE_BALL" - }, - { - "name": "item_ball", - "kind": "item", - "x": 40, - "y": 21, - "hide_flag": "FLAG_HIDE_VIRIDIAN_FOREST_ANTIDOTE" - }, - { - "name": "item_ball", - "kind": "item", - "x": 21, - "y": 34, - "hide_flag": "FLAG_HIDE_VIRIDIAN_FOREST_POTION" - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 43, - "y": 6, - "trainer": { - "sight": 1, - "looks": [ - "south", - "north" - ] - } - }, - { - "name": "bug_catcher", - "kind": "npc", - "x": 16, - "y": 5, - "facing": "north", - "trainer": { - "sight": 1 - } - }, - { - "name": "item_ball", - "kind": "item", - "x": 49, - "y": 60, - "hide_flag": "FLAG_HIDE_VIRIDIAN_FOREST_POTION_2" - } - ], - "flag_sets": {}, - "connections": [] -} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.h index 7fa58f230e..5f0b05d189 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.h @@ -11,8 +11,8 @@ * Center, and the S.S. Anne's corridors to the Captain's office; and the * three floors of Cerulean Cave, where Mewtwo waits on B1F. * - * Each map is a JSON file, Maps/.json next to this file, generated by - * devtools/maps/frlg_maps.py and built into the program by CMake + * Each map is a pinned PokemonSimulation JSON resource, prepared under + * Packages/Resources/Pokemon/Maps/frlg and built into the program by CMake * (pa_embed_text_files() in cmake/EmbedTextFiles.cmake), so the program and * its tests read the same copy. The schema is Pokemon::MapData's * (Pokemon/Navigation/Pokemon_MapData.h). A warp leads to point diff --git a/SerialPrograms/cmake/EmbedTextFiles.cmake b/SerialPrograms/cmake/EmbedTextFiles.cmake index 3bf5f4ae91..943c6f25ab 100644 --- a/SerialPrograms/cmake/EmbedTextFiles.cmake +++ b/SerialPrograms/cmake/EmbedTextFiles.cmake @@ -17,7 +17,10 @@ # at run time, so no literal exceeds MSVC's limit of 16380 bytes. function(pa_embed_text_files TARGET FOLDER HEADER NAMESPACE FUNCTION) - file(GLOB FILES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${FOLDER}/*.json") + # Canonicalize once so resource verification and embedding watch the same + # paths. Mixed absolute/../ aliases can produce duplicate Ninja outputs. + file(REAL_PATH "${FOLDER}" MAP_DIRECTORY BASE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + file(GLOB FILES CONFIGURE_DEPENDS "${MAP_DIRECTORY}/*.json") if(NOT FILES) message(FATAL_ERROR "pa_embed_text_files(): no .json files in ${FOLDER}.") endif() diff --git a/SerialPrograms/cmake/PokesimMapResources.cmake b/SerialPrograms/cmake/PokesimMapResources.cmake new file mode 100644 index 0000000000..67d0124ce1 --- /dev/null +++ b/SerialPrograms/cmake/PokesimMapResources.cmake @@ -0,0 +1,15 @@ +# Resolve FRLG's prepared map input. Configuration never prepares or downloads maps. +# Supply -DPOKESIM_TOOLS_SOURCE=/path/to/tools -DPOKESIM_SOURCE_CACHE=/path/to/cache. +# See devtools/maps/RESOURCE_MAPS.md for the explicit prepare step and pinned CI. +function(pa_frlg_map_resources OUT) + get_filename_component(CONSUMER "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../.." ABSOLUTE) + set(POKESIM_TOOLS_SOURCE "$ENV{POKESIM_TOOLS_SOURCE}" CACHE PATH "Pinned PokemonSimulation tools checkout") + set(POKESIM_SOURCE_CACHE "$ENV{POKESIM_SOURCE_CACHE}" CACHE PATH "Approved publishing Git cache") + if(NOT POKESIM_TOOLS_SOURCE OR NOT POKESIM_SOURCE_CACHE) + message(FATAL_ERROR "Set POKESIM_TOOLS_SOURCE and POKESIM_SOURCE_CACHE after preparing devtools/maps/map-resources.lock.json; see devtools/maps/RESOURCE_MAPS.md.") + endif() + include("${POKESIM_TOOLS_SOURCE}/integrations/arduino-source/resource-maps.cmake") + pa_pokesim_map_directory(MAPS frlg "${CONSUMER}" "${POKESIM_SOURCE_CACHE}") + file(RELATIVE_PATH RELATIVE_MAPS "${CMAKE_CURRENT_SOURCE_DIR}" "${MAPS}") + set(${OUT} "${RELATIVE_MAPS}" PARENT_SCOPE) +endfunction() diff --git a/devtools/maps/RESOURCE_MAPS.md b/devtools/maps/RESOURCE_MAPS.md new file mode 100644 index 0000000000..7d314b00e4 --- /dev/null +++ b/devtools/maps/RESOURCE_MAPS.md @@ -0,0 +1,46 @@ +# FRLG map resources + +FRLG's existing `overworld_map_json()` API now embeds JSON from prepared +`Packages/Resources/Pokemon/Maps/frlg`, rather than an authored source-map copy. +Its native map decoder, route/story/recovery code and controller behavior are +unchanged. Resource preparation happens before configuring, with no implicit +fallback to the deleted source directory and no map download during configure. +This is a change of build input, not runtime filesystem loading. + +The resource lock and CI tools pin published PokemonSimulation main +`906956e94fc912ae9adb0e44845da780f3bc7e4c`. The 53-map resource pack now uses the +compact v2 wire format, read by #114's shared decoder. V2 groups records by kind +and omits empty sections; map geometry, triggers and per-kind record order must +match the previous v1 pack. Native schema, map, route/story and recovery tests +verify this adoption; their current results are recorded in the fork PR. +BDSP's held map differences are not imported or overwritten. + +Clone a full tools/data checkout outside deployed Resources, then explicitly +select the published tool version used by CI: + +```sh +git clone https://github.com/dayfine/PokemonSimulation.git /path/to/pokesim +git -C /path/to/pokesim checkout 906956e94fc912ae9adb0e44845da780f3bc7e4c +PYTHONPATH=/path/to/pokesim/src python3 -m pokesim.maps.consumer_sync prepare \ + --layout resources --source /path/to/pokesim --consumer /path/to/Arduino-Source +cmake -S SerialPrograms -B /path/to/build \ + -DPOKESIM_TOOLS_SOURCE=/path/to/pokesim -DPOKESIM_SOURCE_CACHE=/path/to/pokesim +``` + +CI checks out Packages, prepares this same resource lock through the pinned +integration action, and passes its tools/cache paths to CMake. Missing, partial, +modified, extra or unapproved resource data fails before compilation. The pack +includes pinned license/provenance. Generated resources and external caches are +ignored; the resource lock is checked in. + +The duplicate FRLG builder and Python tests are removed. `frlg_battle_data.py` +is a thin CLI forwarder to `pokesim.games.frlg.battle_data_export`; set +`PYTHONPATH=/src` when running it. All five existing battle-data +outputs reproduced byte-for-byte after the move. Map-builder tests already live +beside their source in PokemonSimulation. Native game tests remain here. +The shared Sinnoh decomp CLI remains a separate consumer migration. + +Native map, story, transition and recovery tests must run from this tree without +the old map directory before adopting this handoff in the owning chain. This +fork setup is not yet an upstream resource submission: handoff must make the +required data available through the agreed Packages repository/process. diff --git a/devtools/maps/frlg_battle_data.py b/devtools/maps/frlg_battle_data.py index d89c74e5ec..b46a637431 100644 --- a/devtools/maps/frlg_battle_data.py +++ b/devtools/maps/frlg_battle_data.py @@ -1,277 +1,14 @@ -"""Writes the FRLG battle data AutoStory chooses its attacks from, as JSON, from the pret/pokefirered decompilation. +"""Forward native battle-data generation to the pinned PokemonSimulation tools. -Usage: - python3 devtools/maps/frlg_battle_data.py --out SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/BattleData - python3 devtools/maps/frlg_battle_data.py --out --check +Example: + PYTHONPATH=/path/to/pokesim/src python3 devtools/maps/frlg_battle_data.py \ + --out SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/BattleData --check -Reads, at frlg_maps.COMMIT (cached in --cache like frlg_maps.py): -- src/battle_main.c gTypeEffectiveness: type_chart.json, each pairing that - isn't 1x, in tenths; -- src/data/pokemon/species_info.h: species.json, every species' two types - and base stats: HP, Attack, Defense, Sp. Atk, Sp. Def, Speed; -- src/data/battle_moves.h: moves.json, every move's power, type, PP, - accuracy, priority, effect (EFFECT_FLINCH_HIT is "flinch-hit") and its - secondary effect's chance; -- src/data/trainers.h and src/data/trainer_parties.h: trainers.json, every - trainer's AI flags (AI_SCRIPT_TRY_TO_FAINT is "try-to-faint"), items and - party (not the Ruby/Sapphire placeholders, DUMMY_TRAINER_MON): each - member's species, level, iv (0 to 255, as pret has it), nature and moves. - A party that gives no moves gets the game's (GiveBoxMonInitialMoveset: - the last four of its level-up moves up to its level, from - src/data/pokemon/level_up_learnsets.h). The nature is the one - CreateNPCTrainerParty's personality gives: a hash of the trainer's and - the species' names (charmap.txt) on top of 0x78 for a female trainer, - 0x80 for a double battle, else 0x88, mod 25; -- data/maps/*/scripts.inc, map.json and data/scripts/trainers.inc: - map_trainers.json, the trainers each map battles (trainerbattle_* - TRAINER_X in its scripts or its objects'). -Names are the program's slugs: SPECIES_MR_MIME is "mr-mime", MOVE_WATER_GUN -"water-gun", TYPE_WATER "water". One entry per line, so a change in pret -shows as a line in the diff. --check writes nothing: it regenerates the files -and fails if any differs. +The implementation and FireRed source pin belong to PokemonSimulation. Use the +same tools checkout documented in RESOURCE_MAPS.md; no map-builder copy is needed. """ -import argparse -import json -import os -import re -import sys -from concurrent.futures import ThreadPoolExecutor -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -import frlg_maps # noqa: E402 - -# Slugs the plain rule (lower case, '_' to '-') gets wrong: the program's -# names, from Resources/Pokemon/Pokedex/Pokedex-National.json and PokeAPI. -SPECIES_SLUGS = {'FARFETCHD': 'farfetch-d'} -MOVE_SLUGS = { - 'FAINT_ATTACK': 'feint-attack', - 'HI_JUMP_KICK': 'high-jump-kick', - 'SMELLING_SALT': 'smelling-salts', - 'VICE_GRIP': 'vise-grip', -} -# Not species a battle can show. -SKIPPED_SPECIES = re.compile(r'NONE$|EGG$|OLD_UNOWN_') -# The AI flags, as include/constants/battle_ai.h names them. -AI_FLAG = re.compile(r'AI_SCRIPT_(\w+)') -# gTypeEffectiveness's multipliers, in tenths. -MULTIPLIERS = {'TYPE_MUL_NO_EFFECT': 0, 'TYPE_MUL_NOT_EFFECTIVE': 5, 'TYPE_MUL_SUPER_EFFECTIVE': 20} - - -def slug(name, fixes=None): - return (fixes or {}).get(name) or name.lower().replace('_', '-') - - -def type_slug(name): - return name[len('TYPE_'):].lower() - - -def entries(text, prefix): - """(NAME, body) for each designated initializer `[PREFIX_NAME] = { body }`.""" - pattern = re.compile(r'\[' + prefix + r'_(\w+)\]\s*=\s*\{((?:(?!\n \[).)*?)\n \},', re.S) - return pattern.findall(text) - - -def field(body, name): - match = re.search(r'\.' + name + r'\s*=\s*([^,\n]+(?:\{[^}]*\})?)', body) - return match.group(1).strip() if match else None - - -def type_chart(text): - table = re.search(r'gTypeEffectiveness\[\d+\]\s*=\s*\{(.*?)\};', text, re.S).group(1) - chart = [] - for attack, defend, mul in re.findall(r'(TYPE_\w+),\s*(TYPE_\w+),\s*(TYPE_MUL_\w+)', table): - if attack in ('TYPE_FORESIGHT', 'TYPE_ENDTABLE'): - continue - chart.append([type_slug(attack), type_slug(defend), MULTIPLIERS[mul]]) - return chart - - -def species(text): - data = {} - for name, body in entries(text, 'SPECIES'): - if SKIPPED_SPECIES.search(name): - continue - types = re.search(r'\.types\s*=\s*\{(TYPE_\w+),\s*(TYPE_\w+)\}', body) - stats = [int(field(body, f)) for f in ('baseHP', 'baseAttack', 'baseDefense', 'baseSpAttack', 'baseSpDefense', 'baseSpeed')] - data[slug(name, SPECIES_SLUGS)] = [type_slug(types.group(1)), type_slug(types.group(2))] + stats - return data - - -def moves(text): - data = {} - for name, body in entries(text, 'MOVE'): - if name == 'NONE': - continue - data[slug(name, MOVE_SLUGS)] = [ - int(field(body, 'power')), type_slug(field(body, 'type')), int(field(body, 'pp')), - int(field(body, 'accuracy')), int(field(body, 'priority')), - slug(field(body, 'effect')[len('EFFECT_'):]), int(field(body, 'secondaryEffectChance')), - ] - return data - - -def learnsets(text, pointers): - """{species slug: [(level, move slug), ...]} from level_up_learnsets.h.""" - by_name = {} - for name, body in re.findall(r'static const u16 (s\w+LevelUpLearnset)\[\]\s*=\s*\{(.*?)\};', text, re.S): - by_name[name] = [(int(lvl), slug(move, MOVE_SLUGS)) for lvl, move in re.findall(r'LEVEL_UP_MOVE\(\s*(\d+),\s*MOVE_(\w+)\)', body)] - return {slug(s, SPECIES_SLUGS): by_name[n] for s, n in re.findall(r'\[SPECIES_(\w+)\]\s*=\s*(s\w+LevelUpLearnset)', pointers)} - - -def initial_moves(learnset, level): - """GiveBoxMonInitialMoveset: each level-up move up to `level` in turn; - one already known is skipped, a fifth pushes out the first.""" - known = [] - for move_level, move in learnset: - if move_level > level: - break - if move in known: - continue - if len(known) == 4: - known.pop(0) - known.append(move) - return known - - -def charmap(text): - """{character: byte} for the single characters charmap.txt maps.""" - chars = {} - for char, code in re.findall(r"^'(\\?.)'\s*=\s*([0-9A-F]{2})\s*$", text, re.M): - chars[char[-1]] = int(code, 16) - return chars - - -def name_sum(name, chars): - return sum(chars[c] for c in name) - - -def species_names(text): - return {slug(name, SPECIES_SLUGS): value for name, value in re.findall(r'\[SPECIES_(\w+)\]\s*=\s*_\("(.*?)"\)', text)} - - -def parties(text, moves_at): - """sParty_X: [(species, level, iv, [moves]), ...]; `moves_at(species, - level)` gives a member's moves where the party doesn't.""" - data = {} - # A placeholder's {DUMMY_TRAINER_MON} on one line doesn't match. - for name, body in re.findall(r'struct TrainerMon\w+ (sParty_\w+)\[\]\s*=\s*\{\n((?:(?!\nstatic ).)*?)\n\};', text, re.S): - party = [] - for mon in re.findall(r'\{(.*?)\n \}', body, re.S): - species, level = slug(field(mon, 'species')[len('SPECIES_'):], SPECIES_SLUGS), int(field(mon, 'lvl')) - given = re.search(r'\.moves\s*=\s*\{(.*?)\}', mon) - if given: - moves = [slug(m.strip()[len('MOVE_'):], MOVE_SLUGS) for m in given.group(1).split(',') if m.strip() != 'MOVE_NONE'] - else: - moves = moves_at(species, level) - party.append((species, level, int(field(mon, 'iv')), moves)) - data[name] = party - return data - - -def personality_base(body): - """CreateNPCTrainerParty's personality before the name hash.""" - if field(body, 'doubleBattle') == 'TRUE': - return 0x80 - if 'F_TRAINER_FEMALE' in field(body, 'encounterMusic_gender'): - return 0x78 - return 0x88 - - -def trainers(text, party_text, learned, names, chars): - by_party = parties(party_text, lambda species, level: initial_moves(learned.get(species, []), level)) - data = {} - for name, body in entries(text, 'TRAINER'): - if name == 'NONE': - continue - party = re.search(r'\.party\s*=\s*\w+\((sParty_\w+)\)', body) - # Left out: the Ruby/Sapphire placeholders (DUMMY_TRAINER_MON). - if not party or party.group(1) not in by_party: - continue - trainer_sum = name_sum(re.search(r'\.trainerName\s*=\s*_\("(.*?)"\)', body).group(1), chars) - name_hash = 0 - members = [] - for species, level, iv, moves in by_party[party.group(1)]: - name_hash += trainer_sum + name_sum(names[species], chars) - nature = ((personality_base(body) + (name_hash << 8)) & 0xFFFFFFFF) % 25 - members.append([species, level, iv, nature, moves]) - items = re.search(r'\.items\s*=\s*\{(.*?)\}', body).group(1) - data['TRAINER_' + name] = { - 'ai': [slug(flag) for flag in AI_FLAG.findall(field(body, 'aiFlags') or '')], - 'items': [slug(item.strip()[len('ITEM_'):]) for item in items.split(',') if item.strip() not in ('', 'ITEM_NONE')], - 'party': members, - } - return data - - -def script_trainers(text): - """{label: TRAINER_X} for each script whose first line battles a trainer.""" - return dict(re.findall(r'^(\w+)::\n\s*trainerbattle\w* (TRAINER_\w+)', text, re.M)) - - -def map_trainers(decomp): - """The trainers each map battles: in its own scripts, and its objects' - scripts in data/scripts/trainers.inc (most trainers on routes).""" - groups = json.loads(decomp.read('data/maps/map_groups.json')) - names = sorted(m for group in groups['group_order'] for m in groups[group]) - shared = script_trainers(decomp.read('data/scripts/trainers.inc').decode()) - read = lambda m: (decomp.read(f'data/maps/{m}/scripts.inc').decode(), json.loads(decomp.read(f'data/maps/{m}/map.json'))) - with ThreadPoolExecutor(16) as pool: - sources = list(pool.map(read, names)) - data = {} - for name, (scripts, source) in zip(names, sources): - battled = set(re.findall(r'trainerbattle\w* (TRAINER_\w+)', scripts)) - battled |= {shared[o['script']] for o in source.get('object_events', []) if o.get('script') in shared} - if battled: - data[name] = sorted(battled) - return data - - -def json_lines(data): - """A JSON object (or a list), one entry per line.""" - if isinstance(data, list): - lines = [json.dumps(item) for item in data] - return '[\n' + ',\n'.join(lines) + '\n]\n' - lines = [f'{json.dumps(key)}: {json.dumps(value)}' for key, value in data.items()] - return '{\n' + ',\n'.join(lines) + '\n}\n' - - -def files(decomp): - read = lambda rel: decomp.read(rel).decode() - return { - 'type_chart': json_lines(type_chart(read('src/battle_main.c'))), - 'species': json_lines(species(read('src/data/pokemon/species_info.h'))), - 'moves': json_lines(moves(read('src/data/battle_moves.h'))), - 'trainers': json_lines(trainers( - read('src/data/trainers.h'), read('src/data/trainer_parties.h'), - learnsets(read('src/data/pokemon/level_up_learnsets.h'), read('src/data/pokemon/level_up_learnset_pointers.h')), - species_names(read('src/data/text/species_names.h')), charmap(read('charmap.txt')), - )), - 'map_trainers': json_lines(map_trainers(decomp)), - } - - -def main(): - parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument('--commit', default=frlg_maps.COMMIT) - parser.add_argument('--cache', default=os.path.expanduser('~/.cache/pokefirered')) - parser.add_argument('--out', required=True, help='folder of the .json files') - parser.add_argument('--check', action='store_true', help='check the files in --out instead of writing them') - args = parser.parse_args() - generated = files(frlg_maps.Decomp(args.commit, args.cache)) - if args.check: - differ = [] - for name, text in generated.items(): - path = os.path.join(args.out, name + '.json') - if not os.path.exists(path) or open(path, newline='').read() != text: - differ.append(name) - if differ: - sys.exit(f'Differ from the generator: {" ".join(differ)}. Regenerate them without --check.') - print(f'{len(generated)} files in {args.out} match the generator.') - return - os.makedirs(args.out, exist_ok=True) - for name, text in generated.items(): - with open(os.path.join(args.out, name + '.json'), 'w', newline='') as out: - out.write(text) +from pokesim.games.frlg.battle_data_export import main if __name__ == '__main__': diff --git a/devtools/maps/frlg_maps.py b/devtools/maps/frlg_maps.py deleted file mode 100644 index 6af5fd6f06..0000000000 --- a/devtools/maps/frlg_maps.py +++ /dev/null @@ -1,444 +0,0 @@ -"""Writes FRLG overworld maps as JSON, from the pret/pokefirered decompilation. - -TODO: move the maps to PokemonAutomation/Packages (Resources/PokemonFRLG/Maps) when ready. - -Usage: - python3 devtools/maps/frlg_maps.py --out SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/Maps \ - Route3 Route4 Route4_PokemonCenter_1F - python3 devtools/maps/frlg_maps.py --out - python3 devtools/maps/frlg_maps.py --out --check - -Downloads each map's data/maps//map.json, its layout's map.bin and its -tilesets' metatile_attributes.bin at --commit into --cache, then writes -/.json in the schema of Pokemon::MapData (Pokemon_MapData.h). -A map whose header doesn't allow running (indoors) says so; a trainer has -their sight and the ways they look. -Warps and connections lead only to maps in the same run: a warp to another map -has no destination, and a connection to one is left out. A warp arrives at -point "warp_" of its map, the tile of that map's warp n. Other named points -and flag sets come from POINTS and FLAG_SETS below. - -Without map names, the maps are the ones already in --out; with them, those -maps are added to the ones in --out, so warps and connections still join -them all. --check writes nothing: it regenerates them and fails if any file -differs. The program -builds in the files of SerialPrograms/Source/VideoGames/PokemonFRLG/ -Overworld/Maps, the folder to write and check. -""" -import argparse -import json -import os -import re -import struct -import subprocess -import sys - -COMMIT = 'ce6406d8c2eaf46d543c8a2606de0c3cb1951982' -# When COMMIT was pinned, for each map's "source". -COMMIT_DATE = '2026-10-31' - -# Behaviors with a glyph of their own (Pokemon_MapData.h has the legend). -TERRAIN = { - 'MB_TALL_GRASS': '~', 'MB_WARP_DOOR': 'D', - 'MB_IMPASSABLE_NORTH': '8', 'MB_IMPASSABLE_SOUTH': '2', - 'MB_IMPASSABLE_EAST': '6', 'MB_IMPASSABLE_WEST': '4', -} -# Open tiles whose warp is taken other than by walking into it: how. -WARP_ENTRIES = { - 'MB_CAVE_DOOR': 'cave_entrance', - 'MB_LADDER': 'step_on', 'MB_UP_ESCALATOR': 'step_on', 'MB_DOWN_ESCALATOR': 'step_on', - 'MB_SOUTH_ARROW_WARP': 'walk_south', 'MB_NORTH_ARROW_WARP': 'walk_north', - 'MB_EAST_ARROW_WARP': 'walk_east', 'MB_WEST_ARROW_WARP': 'walk_west', - # Stairs at the side of a room warp like an exit mat, the way the stairs - # go up or down (IsDirectionalStairWarpMetatileBehavior()). - 'MB_UP_RIGHT_STAIR_WARP': 'walk_east', 'MB_DOWN_RIGHT_STAIR_WARP': 'walk_east', - 'MB_UP_LEFT_STAIR_WARP': 'walk_west', 'MB_DOWN_LEFT_STAIR_WARP': 'walk_west', -} -# Ledges, by the way the player hops over them. -LEDGES = {'MB_JUMP_NORTH': '^', 'MB_JUMP_SOUTH': 'v', 'MB_JUMP_WEST': '<', 'MB_JUMP_EAST': '>'} -FLOOR = ('MB_NORMAL', 'MB_CAVE', 'MB_ROCK_STAIRS', 'MB_TALL_GRASS') -# A metatile's encounter type (METATILE_ATTRIBUTE_ENCOUNTER_TYPE in -# include/fieldmap.h): on a map with land encounters, a step onto a LAND -# tile may start a wild battle, as on every floor tile of a cave. -ENCOUNTER_TYPE_SHIFT, ENCOUNTER_TYPE_MASK = 24, 0x7 -TILE_ENCOUNTER_LAND = 1 -# Open tiles closed on one side, and the region that lists those of them -# with wild encounters, which their glyph can't say. -CLOSED_GLYPHS = '8264' -WILD_REGION = 'wild' -DIRECTIONS = {'up': 'north', 'down': 'south', 'left': 'west', 'right': 'east'} - -# Places and flag sets the decompilation doesn't name, written into the map -# they belong to. -# A `*_front` point is where the player stands to talk to someone across a -# counter, or to walk north through a door. -POINTS = { - # Where a new game starts (WarpToPlayersRoom() in src/new_game.c). - 'PalletTown_PlayersHouse_2F': {'start': (6, 6)}, - 'PalletTown': {'oaks_lab_front': (16, 14)}, - # The clerk is across the counter to the west. - 'ViridianCity_Mart': {'clerk_front': (4, 3)}, - 'ViridianCity_PokemonCenter_1F': {'nurse_front': (7, 4)}, - 'PewterCity': {'gym_front': (15, 17)}, - 'PewterCity_PokemonCenter_1F': {'nurse_front': (7, 4)}, - 'PewterCity_Gym': {'brock_front': (6, 6)}, - # The Magikarp salesman is at (1, 3), west of `salesman_front`. - 'Route4_PokemonCenter_1F': {'nurse_front': (7, 4), 'salesman_front': (2, 3)}, - # The tile in front of the PC. - 'CeruleanCity_PokemonCenter_1F': {'pc': (11, 2), 'nurse_front': (7, 4)}, - # The clerk (2, 3) is across the counter to the west. - 'CeruleanCity_Mart': {'clerk_front': (4, 3)}, - # Below Misty (8, 6). - 'CeruleanCity_Gym': {'misty_front': (8, 7)}, - # Where the player stands after entering from Cerulean City. - 'CeruleanCave_1F': {'entrance': (33, 21)}, - # Mewtwo's tile. Its battle starts by talking to it from the south. - 'CeruleanCave_B1F': {'mewtwo': (7, 12)}, - 'VermilionCity_PokemonCenter_1F': {'nurse_front': (7, 4)}, -} -FLAG_SETS = { - # Set once the player has entered the Hall of Fame. They clear the way - # to Cerulean Cave: its guard, the rival and the Rocket. - 'CeruleanCity': {'after_hall_of_fame': [ - 'FLAG_HIDE_CERULEAN_CAVE_GUARD', - 'FLAG_HIDE_CERULEAN_RIVAL', - 'FLAG_HIDE_CERULEAN_ROCKET', - ]}, - # Set at every new game (EventScript_ResetAllMapFlags in - # data/event_scripts.s): people who are on the map only in a scene. - # The S.S. Anne's rival stands on the Captain's door until his scene - # brings him out. - 'SSAnne_2F_Corridor': {'new_game': ['FLAG_HIDE_SS_ANNE_RIVAL']}, - 'VermilionCity': {'new_game': ['FLAG_HIDE_VERMILION_CITY_OAKS_AIDE']}, -} - -# Tiles a script changes for good once the story is past it, written as -# the story finds them after: Vermilion Gym's beams across rows 6 and 7 -# go off once both switches are found -# (VermilionCity_Gym_EventScript_SetBeamsOff: (4..6, 6..7) floor, -# FLAG_FOUND_BOTH_VERMILION_GYM_SWITCHES). No route of the story's goes -# north of them before that, and one after can't end a leg on them as on -# a wall. -OPENED_TILES = { - 'VermilionCity_Gym': [(x, y) for y in (6, 7) for x in (4, 5, 6)], -} - - -class Decomp: - def __init__(self, commit, cache): - self.raw = 'https://raw.githubusercontent.com/pret/pokefirered/' + commit - self.cache = os.path.join(cache, commit) - - def path(self, rel): - local = os.path.join(self.cache, rel) - if not os.path.exists(local): - os.makedirs(os.path.dirname(local), exist_ok=True) - subprocess.run(['curl', '-sfL', f'{self.raw}/{rel}', '-o', local], check=True) - return local - - def read(self, rel): - with open(self.path(rel), 'rb') as file: - return file.read() - - -def behaviors(decomp): - names = {} - for line in decomp.read('include/constants/metatile_behaviors.h').decode().splitlines(): - match = re.match(r'#define (MB_\w+)\s+(0x[0-9A-Fa-f]+|\d+)', line) - if match: - names[int(match.group(2), 0)] = match.group(1) - return names - - -def tileset_attributes(decomp, symbol): - """The metatile attributes of tileset `symbol` (gTileset_), from the - file src/data/tilesets/metatiles.h names for gMetatileAttributes_: - folder names don't always follow the symbol (gTileset_SSAnne is ss_anne).""" - attributes = symbol.replace('gTileset_', 'gMetatileAttributes_') - match = re.search( - r'\b' + attributes + r'\[\]\s*=\s*INCBIN_U32\("([^"]+)"\)', - decomp.read('src/data/tilesets/metatiles.h').decode(), - ) - if not match: - sys.exit(f'No {attributes} in src/data/tilesets/metatiles.h.') - data = decomp.read(match.group(1)) - return [struct.unpack_from('> ENCOUNTER_TYPE_SHIFT) & ENCOUNTER_TYPE_MASK == TILE_ENCOUNTER_LAND - - -def encounter_glyph(glyph, attributes): - """On a map with land encounters: `glyph` for a tile with these metatile - attributes, open floor where wild Pokemon appear written as tall grass.""" - return '~' if glyph == '.' and land_encounter(attributes) else glyph - - -def wild_edge(glyph, attributes): - """On a map with land encounters: whether a tile closed on one side, with - these metatile attributes, has wild encounters. Its glyph can't say so; - the map lists it in region WILD_REGION.""" - return glyph in CLOSED_GLYPHS and land_encounter(attributes) - - -def grid(decomp, layout, names, unhandled, signs=frozenset(), land_encounters=False): - """The map's tile rows, elevation rows, how the warp on each tile is - entered, by (x, y), where it isn't by walking into it, and the tiles closed - on one side that have wild encounters. With `land_encounters`, open floor - where wild Pokemon appear (a cave's) is written as tall grass, the glyph - for "open, with wild encounters".""" - width, height = layout['width'], layout['height'] - blocks = decomp.read(layout['blockdata_filepath']) - primary = tileset_attributes(decomp, layout['primary_tileset']) - secondary = tileset_attributes(decomp, layout['secondary_tileset']) - tiles, elevation, entries, wild_edges = [], [], {}, [] - for y in range(height): - row, levels = '', '' - for x in range(width): - value = struct.unpack_from('> 10) & 3, value >> 12 - attributes = primary[metatile] if metatile < 640 else secondary[metatile - 640] - behavior = names.get(attributes & 0x1ff, hex(attributes & 0x1ff)) - glyph, entry = tile_glyph(behavior, collision, unhandled, (x, y) in signs) - if land_encounters and entry is None: - glyph = encounter_glyph(glyph, attributes) - if wild_edge(glyph, attributes): - wild_edges.append([x, y]) - row += glyph - if entry: - entries[(x, y)] = entry - levels += '%x' % level - tiles.append(row) - elevation.append(levels) - return tiles, elevation, entries, wild_edges - - -def wander_box(obj, tiles): - """The tiles a wandering person may stand on, kept inside the map, or None - if they stand still.""" - movement = obj.get('movement_type', '') - if 'WANDER' not in movement: - return None - x = 0 if 'UP_AND_DOWN' in movement else int(obj.get('movement_range_x') or 0) - y = 0 if 'LEFT_AND_RIGHT' in movement else int(obj.get('movement_range_y') or 0) - if x == 0 and y == 0: - return None - return { - 'x0': max(obj['x'] - x, 0), 'y0': max(obj['y'] - y, 0), - 'x1': min(obj['x'] + x, len(tiles[0]) - 1), 'y1': min(obj['y'] + y, len(tiles) - 1), - } - - -# Which way a person stands, by how they move. -FACINGS = { - 'MOVEMENT_TYPE_FACE_UP': 'north', 'MOVEMENT_TYPE_FACE_DOWN': 'south', - 'MOVEMENT_TYPE_FACE_LEFT': 'west', 'MOVEMENT_TYPE_FACE_RIGHT': 'east', -} -# The ways a trainer who turns looks. A trainer who moves some other way -# (wanders, looks around) may look any way. -TURNING_LOOKS = { - 'MOVEMENT_TYPE_FACE_DOWN_AND_UP': ['south', 'north'], - 'MOVEMENT_TYPE_FACE_LEFT_AND_RIGHT': ['west', 'east'], - 'MOVEMENT_TYPE_FACE_UP_AND_LEFT': ['north', 'west'], - 'MOVEMENT_TYPE_FACE_UP_AND_RIGHT': ['north', 'east'], - 'MOVEMENT_TYPE_FACE_DOWN_AND_LEFT': ['south', 'west'], - 'MOVEMENT_TYPE_FACE_DOWN_AND_RIGHT': ['south', 'east'], - 'MOVEMENT_TYPE_WANDER_UP_AND_DOWN': ['north', 'south'], - 'MOVEMENT_TYPE_WANDER_LEFT_AND_RIGHT': ['west', 'east'], -} -ALL_LOOKS = ['north', 'east', 'south', 'west'] - - -def trainer(obj): - """A trainer's sight and, unless they only face one way, the ways they - look; None for anyone who doesn't battle on sight.""" - sight = int(obj.get('trainer_sight_or_berry_tree_id') or 0) - if obj.get('trainer_type') != 'TRAINER_TYPE_NORMAL' or sight < 1: - return None - movement = obj.get('movement_type', '') - if movement in FACINGS: - return {'sight': sight} - return {'sight': sight, 'looks': TURNING_LOOKS.get(movement, ALL_LOOKS)} - - -def map_object(obj, tiles): - kind = obj['graphics_id'].replace('OBJ_EVENT_GFX_', '').lower() - data = {'name': kind, 'kind': 'item' if kind == 'item_ball' else 'npc', 'x': obj['x'], 'y': obj['y']} - box = wander_box(obj, tiles) - if box: - data['wander'] = box - sight = trainer(obj) - if sight: - # A trainer who faces one way looks that way. - if 'looks' not in sight: - data['facing'] = FACINGS[obj['movement_type']] - data['trainer'] = sight - flag = obj.get('flag') or '0' - if flag != '0': - data['hide_flag'] = flag - return data - - -# Tiles the player never walks onto: their elevation only matters for doors, -# which the player is put on by a warp and leaves at once. -UNWALKABLE = '#^v<>DS' -GROUND = '3' - - -def ground_only(tiles, elevation): - """Whether every walkable tile is at ground level, so that the map can - leave out its elevation grid (the loader reads ground everywhere).""" - return all(level == GROUND for row, levels in zip(tiles, elevation) - for tile, level in zip(row, levels) if tile not in UNWALKABLE) - - -def warp_point(index): - return f'warp_{index}' - - -def warp(event, entries, loaded, sources): - """A warp event as MapData's warp: how it is entered, and the point it - leads to, if its map is loaded and has that warp.""" - data = {'x': event['x'], 'y': event['y'], 'enter': entries.get((event['x'], event['y']), 'walk_into')} - to_map, index = loaded.get(event['dest_map']), int(event['dest_warp_id']) - if to_map is not None and index < len(sources[to_map].get('warp_events', [])): - data['to_map'] = to_map - data['to_point'] = warp_point(index) - return data - - -def arrivals(sources, loaded): - """For each map, the indices of its warps that a warp leads to.""" - arriving = {name: set() for name in sources} - for source in sources.values(): - for event in source.get('warp_events', []): - to_map = loaded.get(event['dest_map']) - if to_map is not None and int(event['dest_warp_id']) < len(sources[to_map].get('warp_events', [])): - arriving[to_map].add(int(event['dest_warp_id'])) - return arriving - - -def land_encounter_maps(decomp): - """The ids of the maps that have land encounters (src/data/wild_encounters.json).""" - groups = json.loads(decomp.read('src/data/wild_encounters.json'))['wild_encounter_groups'] - return {e['map'] for group in groups for e in group.get('encounters', []) if 'land_mons' in e} - - -def map_json(decomp, name, sources, layouts, names, loaded, unhandled, land_maps=frozenset()): - source = sources[name] - signs = frozenset((e['x'], e['y']) for e in source.get('bg_events', []) if e.get('type') == 'sign') - tiles, elevation, entries, wild_edges = grid( - decomp, layouts[source['layout']], names, unhandled, signs, source['id'] in land_maps - ) - for x, y in OPENED_TILES.get(name, []): - tiles[y] = tiles[y][:x] + '.' + tiles[y][x + 1:] - warp_events = source.get('warp_events', []) - points = {n: [x, y] for n, (x, y) in POINTS.get(name, {}).items()} - for index in sorted(arrivals(sources, loaded)[name]): - points[warp_point(index)] = [warp_events[index]['x'], warp_events[index]['y']] - data = { - 'name': name, - 'source': {'decomp_file': f'data/maps/{name}/map.json', 'revision': COMMIT, 'date': COMMIT_DATE}, - 'allow_running': source.get('allow_running', True), - 'tiles': tiles, 'elevation': elevation, - 'points': points, - 'regions': {WILD_REGION: wild_edges} if wild_edges else {}, - 'warps': [warp(event, entries, loaded, sources) for event in warp_events], - # A few objects stand off the map, where no route meets them. - 'objects': [ - map_object(obj, tiles) for obj in source.get('object_events', []) - if 0 <= obj['x'] < len(tiles[0]) and 0 <= obj['y'] < len(tiles) - ], - 'flag_sets': FLAG_SETS.get(name, {}), - 'connections': [ - {'side': DIRECTIONS[c['direction']], 'to_map': loaded[c['map']], 'offset': c['offset']} - for c in (source.get('connections') or []) if c['map'] in loaded - ], - } - if not data['regions']: - del data['regions'] - if ground_only(tiles, elevation): - del data['elevation'] - if data['allow_running']: - del data['allow_running'] - return data - - -def json_text(data): - text = json.dumps(data, indent=4) - # A point's [x, y] on one line. - return re.sub(r'\[\s*(-?\d+),\s*(-?\d+)\s*\]', r'[\1, \2]', text) + '\n' - - -def main(): - parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument('--commit', default=COMMIT) - parser.add_argument('--cache', default=os.path.expanduser('~/.cache/pokefirered')) - parser.add_argument('--out', required=True, 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: the maps already in --out') - args = parser.parse_args() - if os.path.isdir(args.out): - args.maps = sorted(set(args.maps) | {f[:-5] for f in os.listdir(args.out) if f.endswith('.json')}) - if not args.maps: - parser.error('no maps given, and none in --out') - - decomp = Decomp(args.commit, args.cache) - names = behaviors(decomp) - layouts = {l['id']: l for l in json.loads(decomp.read('data/layouts/layouts.json'))['layouts'] if 'id' in l} - sources = {m: json.loads(decomp.read(f'data/maps/{m}/map.json')) for m in args.maps} - loaded = {source['id']: m for m, source in sources.items()} - unhandled = {} - land_maps = land_encounter_maps(decomp) - maps = [(name, json_text(map_json(decomp, name, sources, layouts, names, loaded, unhandled, land_maps))) for name in args.maps] - if unhandled: - print(f'Non-collision behaviors written as floor: {unhandled}') - if args.check: - differ = [] - for name, text in maps: - path = os.path.join(args.out, name + '.json') - if not os.path.exists(path) or open(path, newline='').read() != text: - differ.append(name) - if differ: - sys.exit(f'Differ from the generator: {" ".join(differ)}. Regenerate them without --check.') - print(f'{len(maps)} maps in {args.out} match the generator.') - return - os.makedirs(args.out, exist_ok=True) - for name, text in maps: - with open(os.path.join(args.out, name + '.json'), 'w', newline='') as out: - out.write(text) - - -if __name__ == '__main__': - main() diff --git a/devtools/maps/map-resources.lock.json b/devtools/maps/map-resources.lock.json new file mode 100644 index 0000000000..3bded16bc7 --- /dev/null +++ b/devtools/maps/map-resources.lock.json @@ -0,0 +1,11 @@ +{ + "format": "pokesim-map-resource-lock/1", + "repository": "dayfine/PokemonSimulation", + "games": { + "frlg": { + "revision": "906956e94fc912ae9adb0e44845da780f3bc7e4c", + "manifest_path": "data/maps/runtime-manifest.json", + "manifest_sha256": "856e07a265da0b273172a7aa97a2aa335c90ea0a39162d50912b55fb60a86bc7" + } + } +} diff --git a/devtools/maps/tests/test_frlg_maps.py b/devtools/maps/tests/test_frlg_maps.py deleted file mode 100644 index a25d3051d1..0000000000 --- a/devtools/maps/tests/test_frlg_maps.py +++ /dev/null @@ -1,90 +0,0 @@ -import unittest - -from devtools.maps import frlg_maps - -TILES = ['.....'] * 5 - - -def event(**fields): - obj = {'graphics_id': 'OBJ_EVENT_GFX_BUG_CATCHER', 'x': 2, 'y': 2, 'movement_type': 'MOVEMENT_TYPE_FACE_RIGHT', - 'movement_range_x': 1, 'movement_range_y': 1, 'trainer_type': 'TRAINER_TYPE_NORMAL', - 'trainer_sight_or_berry_tree_id': '3', 'flag': '0'} - obj.update(fields) - return obj - - -class TrainerTest(unittest.TestCase): - def test_facing_trainer_looks_ahead(self): - data = frlg_maps.map_object(event(), TILES) - self.assertEqual(data['facing'], 'east') - self.assertEqual(data['trainer'], {'sight': 3}) - - def test_wandering_trainer_looks_both_ways(self): - data = frlg_maps.map_object(event(movement_type='MOVEMENT_TYPE_WANDER_UP_AND_DOWN'), TILES) - self.assertNotIn('facing', data) - self.assertEqual(data['trainer'], {'sight': 3, 'looks': ['north', 'south']}) - self.assertEqual(data['wander'], {'x0': 2, 'y0': 1, 'x1': 2, 'y1': 3}) - - def test_trainer_looking_around_looks_every_way(self): - data = frlg_maps.map_object(event(movement_type='MOVEMENT_TYPE_LOOK_AROUND'), TILES) - self.assertEqual(data['trainer']['looks'], frlg_maps.ALL_LOOKS) - - def test_others_are_no_trainers(self): - for fields in ({'trainer_type': 'TRAINER_TYPE_NONE'}, {'trainer_sight_or_berry_tree_id': '0'}): - data = frlg_maps.map_object(event(**fields), TILES) - self.assertNotIn('trainer', data) - self.assertNotIn('facing', data) - - -class EncounterTest(unittest.TestCase): - # MtMoon_1F's cave floor: behavior MB_CAVE (0x08), encounter type LAND. - CAVE_FLOOR = 0x08 | (frlg_maps.TILE_ENCOUNTER_LAND << frlg_maps.ENCOUNTER_TYPE_SHIFT) - - def test_cave_floor_has_wild_encounters(self): - self.assertEqual(frlg_maps.encounter_glyph('.', self.CAVE_FLOOR), '~') - - def test_floor_without_encounters_stays_open(self): - self.assertEqual(frlg_maps.encounter_glyph('.', 0x08), '.') - - def test_walls_stay_walls(self): - self.assertEqual(frlg_maps.encounter_glyph('#', self.CAVE_FLOOR), '#') - - # MtMoon_B2F's floor closed on the north: MB_IMPASSABLE_NORTH (0x32), - # encounter type LAND, as wild as the cave floor around it. - CLOSED_NORTH = 0x32 | (frlg_maps.TILE_ENCOUNTER_LAND << frlg_maps.ENCOUNTER_TYPE_SHIFT) - - def test_closed_side_floor_has_wild_encounters(self): - for glyph in '8264': - self.assertTrue(frlg_maps.wild_edge(glyph, self.CLOSED_NORTH)) - - def test_closed_side_floor_without_encounters_is_not_wild(self): - self.assertFalse(frlg_maps.wild_edge('8', 0x32)) - - def test_other_tiles_are_no_wild_edges(self): - for glyph in '.~#': - self.assertFalse(frlg_maps.wild_edge(glyph, self.CAVE_FLOOR)) - - -class FakeDecomp: - def __init__(self, files): - self.files = files - - def read(self, rel): - return self.files[rel] - - -class TilesetTest(unittest.TestCase): - # gTileset_SSAnne's folder is ss_anne, which the symbol doesn't say. - METATILES = (b'const u32 gMetatileAttributes_SS[] = INCBIN_U32("data/tilesets/secondary/ss/metatile_attributes.bin");\n' - b'const u32 gMetatileAttributes_SSAnne[] = INCBIN_U32("data/tilesets/secondary/ss_anne/metatile_attributes.bin");\n') - - def test_attributes_from_the_file_metatiles_h_names(self): - decomp = FakeDecomp({ - 'src/data/tilesets/metatiles.h': self.METATILES, - 'data/tilesets/secondary/ss_anne/metatile_attributes.bin': bytes([1, 0, 0, 0, 0x22, 0, 0, 1]), - }) - self.assertEqual(frlg_maps.tileset_attributes(decomp, 'gTileset_SSAnne'), [1, 0x01000022]) - - -if __name__ == '__main__': - unittest.main() diff --git a/devtools/runtime-map-tests/CMakeLists.txt b/devtools/runtime-map-tests/CMakeLists.txt index 379c1e1694..71d5d94407 100644 --- a/devtools/runtime-map-tests/CMakeLists.txt +++ b/devtools/runtime-map-tests/CMakeLists.txt @@ -22,6 +22,10 @@ add_library(RuntimeMapDecoder STATIC ) target_include_directories(RuntimeMapDecoder PUBLIC "${ROOT}" "${ROOT}/SerialPrograms/Source") target_compile_features(RuntimeMapDecoder PUBLIC cxx_std_23) +# Match the application's existing policy for this unchanged platform-specific helper. +if(MSVC) + set_property(SOURCE "${ROOT}/Common/Cpp/Filesystem/FileIO.cpp" APPEND PROPERTY COMPILE_OPTIONS /wd4505) +endif() add_executable(RuntimeMapTests "${NAV}/Pokemon_RuntimeMap_Tests.cpp") target_compile_definitions(RuntimeMapTests PRIVATE PA_RUNTIME_MAP_STANDALONE) target_link_libraries(RuntimeMapTests PRIVATE RuntimeMapDecoder) @@ -34,8 +38,21 @@ add_executable(RuntimeMapLegacyTests ) target_link_libraries(RuntimeMapLegacyTests PRIVATE RuntimeMapDecoder) include("${ROOT}/SerialPrograms/cmake/EmbedTextFiles.cmake") +# Resource consumers also register canonical paths; embed dependencies must not +# retain a second ../ alias, which Ninja can treat as duplicate generated outputs. +file(GLOB CANONICAL_MAPS "${NAV}/TestMaps/*.json") +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${CANONICAL_MAPS}) pa_embed_text_files(RuntimeMapLegacyTests ../../SerialPrograms/Source/Pokemon/Navigation/TestMaps Pokemon/Navigation/Pokemon_Navigation_Tests.h PokemonAutomation::Pokemon navigation_test_map_json) +get_property(MAP_DEPENDENCIES DIRECTORY PROPERTY CMAKE_CONFIGURE_DEPENDS) +foreach(INPUT IN LISTS MAP_DEPENDENCIES) + if(INPUT MATCHES "TestMaps/.*\\.json$") + file(REAL_PATH "${INPUT}" CANONICAL_INPUT) + if(NOT INPUT STREQUAL CANONICAL_INPUT) + message(FATAL_ERROR "Map embedding registered a noncanonical dependency: ${INPUT}") + endif() + endif() +endforeach() foreach(target RuntimeMapDecoder RuntimeMapTests RuntimeMapLegacyTests) if(MSVC) target_compile_definitions(${target} PRIVATE _CRT_SECURE_NO_WARNINGS) diff --git a/devtools/runtime-map-tests/prepare_parity.py b/devtools/runtime-map-tests/prepare_parity.py index 5e513c22c7..fb1cdea9ef 100644 --- a/devtools/runtime-map-tests/prepare_parity.py +++ b/devtools/runtime-map-tests/prepare_parity.py @@ -10,6 +10,7 @@ import argparse import json +import re from pathlib import Path import subprocess import sys @@ -22,19 +23,24 @@ def main(): parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('--publisher', type=Path, required=True) parser.add_argument('--out', type=Path, required=True) + parser.add_argument('--publisher-revision', default=PUBLISHER_REVISION, + help='Full immutable publisher SHA; defaults to the original v1 fixture pin') args = parser.parse_args() + revision = args.publisher_revision + if not re.fullmatch(r'[0-9a-f]{40}', revision): + parser.error('Publisher revision must be a full lowercase commit SHA') publisher = args.publisher.resolve() def git(*arguments): return subprocess.check_output(['git', '-C', str(publisher), *arguments], text=True).strip() - if git('rev-parse', 'HEAD') != PUBLISHER_REVISION or git('status', '--porcelain', '--untracked-files=no'): - parser.error('Publisher must be a clean checkout of ' + PUBLISHER_REVISION) + if git('rev-parse', 'HEAD') != revision or git('status', '--porcelain', '--untracked-files=no'): + parser.error('Publisher must be a clean checkout of ' + revision) sys.path.insert(0, str(publisher / 'src')) from pokesim.maps.item_identity import enrich_map from pokesim.maps.runtime_map_coordinates import compact_map, legacy_map pairs = [] for game in ('frlg', 'bdsp'): for path in sorted((publisher / 'data/games' / game / 'maps/runtime').glob('*.json')): - source = json.loads(path.read_text(encoding='utf-8')) + source = legacy_map(json.loads(path.read_text(encoding='utf-8'))) for variant in (source, enrich_map(source, game)): compact = compact_map(variant) pairs.append({'input': compact, 'expectation': legacy_map(compact)}) @@ -42,7 +48,7 @@ def git(*arguments): parser.error('Expected both variants of all 75 pinned maps') with args.out.open('x', encoding='utf-8') as stream: json.dump(pairs, stream) - print('Prepared 150 full map records from ' + PUBLISHER_REVISION) + print('Prepared 150 full map records from ' + revision) if __name__ == '__main__': From e6de8c8d660a5e786836d935e9fc034613d70a81 Mon Sep 17 00:00:00 2001 From: Di Fan <26423504+dayfine@users.noreply.github.com> Date: Sat, 7 Nov 2026 20:26:20 -0800 Subject: [PATCH 07/13] Fix missing native test fixtures and MSVC narrowing warning (#243) * Build FRLG map data from verified pinned Packages resources * Retire duplicate FRLG map builder through the Simulation battle-data exporter * Adopt published v2 FRLG resource pack and approved Simulation tools * Run full native validation against complete pinned image fixtures * Include native image golden sidecars in fixture pin --------- Co-authored-by: Claude Opus 5.5 --- .github/workflows/cpp-ci-serial-programs-base.yml | 4 +++- devtools/maps/RESOURCE_MAPS.md | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cpp-ci-serial-programs-base.yml b/.github/workflows/cpp-ci-serial-programs-base.yml index 743c68fdbb..aed4c045ea 100644 --- a/.github/workflows/cpp-ci-serial-programs-base.yml +++ b/.github/workflows/cpp-ci-serial-programs-base.yml @@ -260,7 +260,9 @@ jobs: uses: actions/checkout@v7 if: inputs.run-tests with: - repository: 'PokemonAutomation/CommandLineTests' + # Complete fork fixtures include pending FRLG image submissions. + repository: 'dayfine/CommandLineTests' + ref: 'd150a6c547434110a920f95ba7857b93d5bb9222' path: 'CommandLineTests' # The unit tests assume the downloadable resources they need are already downloaded diff --git a/devtools/maps/RESOURCE_MAPS.md b/devtools/maps/RESOURCE_MAPS.md index 7d314b00e4..3b02a8d8f9 100644 --- a/devtools/maps/RESOURCE_MAPS.md +++ b/devtools/maps/RESOURCE_MAPS.md @@ -44,3 +44,13 @@ Native map, story, transition and recovery tests must run from this tree without the old map directory before adopting this handoff in the owning chain. This fork setup is not yet an upstream resource submission: handoff must make the required data available through the agreed Packages repository/process. + +## Full native test fixtures + +Fork CI pins `dayfine/CommandLineTests` at +`d150a6c547434110a920f95ba7857b93d5bb9222`. This includes the pending FRLG +fixture submissions needed by the existing native image tests and preserves +all current upstream fixture files. It is a test-data pin, independent of the +map resource lock. The upstream-only fixture checkout lacked 69 required frames; +those failures must not be skipped or treated as passing detector tests. +Upstream handoff must also arrange publication of the required image fixtures. From 20d6eca1d6621b3dc2506b59d9d4ff8f1691cb34 Mon Sep 17 00:00:00 2001 From: Di Fan <26423504+dayfine@users.noreply.github.com> Date: Sun, 8 Nov 2026 16:08:54 -0800 Subject: [PATCH 08/13] FRLG: author Parcel checkpoint in JSON and execute it through the story table (#260) * PokemonFRLG: Load an exported checkpoint record back into a story row read_story_checkpoint_export() is the inverse of story_checkpoint_export(): it reads a pokesim-native-checkpoint-export/1 record (the shared checkpoint through Pokemon::read_story_checkpoint, plus FRLG's menu and supplies) into the same StoryCheckpoint type that story_checkpoints() holds, so build_checkpoint() and make_story_segment() take loaded rows unchanged. The envelope constants are checked, every native field is required and unknown fields fail, so a record is read as written or refused. PokemonSimulation's data/story/native/frlg-parcel-00.05.json (its commit 051785fa251f37b71eb661d4b583435d70f69439, the export tool's output for row 00.05) is built into the program from StoryData/ as autostory_checkpoint_json(), and load_story_checkpoint() reads it. Tests: CheckpointExport reads the capture and checks it exports back to the same file and to the same JSON as table row 00.05, and refuses eight altered copies; the existing export cases also round-trip every native field. AutoStory - Simulation runs the loaded 00.05 and the table's 00.05 through FakeGame and compares describe(), the trace, the log lines and the save pose. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_013VUubhHeYSMGSSDXZkKZss * Document Parcel JSON equivalence checks and migration lifetime * Order Parcel JSON for readers and distinguish snapshot from requirements * Load Parcel story table entry from authored JSON and replay it in both repos * Separate FRLG checkpoint JSON parsing and migration writing * Share authored checkpoint envelopes and embedded resource loading * Use shared authored checkpoint loading for FRLG --------- Co-authored-by: Claude Fable 5.1 --- .../workflows/cpp-ci-serial-programs-base.yml | 30 ++ SerialPrograms/CMakeLists.txt | 2 + .../PokemonFRLG/PokemonFRLG_Tests.cpp | 4 +- .../Programs/AutoStory/Data/JsonParser.cpp | 233 +++++++++++++++ .../Programs/AutoStory/Data/JsonParser.h | 47 +++ .../JsonWriter.cpp} | 40 +-- .../Programs/AutoStory/Data/JsonWriter.h | 26 ++ .../Programs/AutoStory/Data/Json_Tests.cpp | 282 ++++++++++++++++++ .../Json_Tests.h} | 10 +- .../AutoStory/Data/LegacyJsonFormat.h | 53 ++++ .../AutoStory/PokemonFRLG_AutoStoryExport.h | 18 -- .../PokemonFRLG_AutoStoryExport_Tests.cpp | 102 ------- .../PokemonFRLG_AutoStorySimulation_Tests.cpp | 38 ++- .../AutoStory/PokemonFRLG_AutoStoryTable.cpp | 16 +- .../Programs/AutoStory/StoryData/README.md | 27 ++ .../StoryData/frlg-parcel-00.05.json | 71 +++++ .../StoryData/frlg-parcel-00.05.plan.json | 42 +++ SerialPrograms/cmake/SourceFiles.cmake | 11 +- 18 files changed, 896 insertions(+), 156 deletions(-) create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.h rename SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/{PokemonFRLG_AutoStoryExport.cpp => Data/JsonWriter.cpp} (77%) create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonWriter.h create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.cpp rename SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/{PokemonFRLG_AutoStoryExport_Tests.h => Data/Json_Tests.h} (66%) create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/LegacyJsonFormat.h delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport.h delete mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.cpp create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/README.md create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-parcel-00.05.json create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-parcel-00.05.plan.json diff --git a/.github/workflows/cpp-ci-serial-programs-base.yml b/.github/workflows/cpp-ci-serial-programs-base.yml index e2393a205f..c27db9594d 100644 --- a/.github/workflows/cpp-ci-serial-programs-base.yml +++ b/.github/workflows/cpp-ci-serial-programs-base.yml @@ -101,6 +101,36 @@ jobs: operation: prepare consumer-directory: Arduino-Source + - name: Checkout authored checkpoint simulator + if: startsWith(inputs.os, 'ubuntu') && inputs.run-tests + uses: actions/checkout@v7 + with: + repository: dayfine/PokemonSimulation + ref: 8cb831caf8bfe1f06b1aec9a4a7e55e6e09b0604 + path: AuthoredStorySource + + - name: Check identical authored Parcel JSON and replay its scenarios + if: startsWith(inputs.os, 'ubuntu') && inputs.run-tests + env: + PYTHONPATH: AuthoredStorySource/src + run: | + python3 - <<'PYTHON' + from pathlib import Path + from pokesim.story.replay_binding import load_binding + from pokesim.story.replay_cases import read_cases, check_case + from pokesim.story.checkpoint_replay import run + source = Path('AuthoredStorySource') + folder = source / 'data/story/parcel-pilot' + native = Path('Arduino-Source/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData') + assert (native / 'frlg-parcel-00.05.plan.json').read_bytes() == (folder / 'frlg-parcel-00.05.plan.json').read_bytes() + binding = load_binding(folder / 'authored-binding.json', source) + cases = read_cases(folder / 'cases.json') + for case in cases: + check_case(case, run(binding, case['input'])) + print(case['id'] + ': authored JSON replay passed') + assert len(cases) == 7 + PYTHON + - name: Install Qt uses: jurplel/install-qt-action@v4 with: diff --git a/SerialPrograms/CMakeLists.txt b/SerialPrograms/CMakeLists.txt index 753c2a0c68..bd7c93a134 100644 --- a/SerialPrograms/CMakeLists.txt +++ b/SerialPrograms/CMakeLists.txt @@ -233,6 +233,8 @@ pa_embed_text_files(SerialProgramsLib "${FRLG_MAP_RESOURCES}" VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.h PokemonAutomation::NintendoSwitch::PokemonFRLG overworld_map_json) pa_embed_text_files(SerialProgramsLib Source/VideoGames/PokemonFRLG/Programs/AutoStory/BattleData VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryBattleData.h PokemonAutomation::NintendoSwitch::PokemonFRLG autostory_battle_json) +pa_embed_text_files(SerialProgramsLib Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData + VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.h PokemonAutomation::NintendoSwitch::PokemonFRLG autostory_checkpoint_json) # Create binary target diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp index 0b5eec80e5..cd7311f935 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp @@ -52,7 +52,7 @@ #include "Programs/AutoStory/PokemonFRLG_AutoStoryRngLoop.h" #include "Programs/AutoStory/PokemonFRLG_AutoStoryTable.h" #include "Programs/AutoStory/PokemonFRLG_AutoStoryTrashCans.h" -#include "Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.h" +#include "Programs/AutoStory/Data/Json_Tests.h" #include "Programs/RngManipulation/PokemonFRLG_GiftRngAttempts_Tests.h" #include "Programs/RngManipulation/PokemonFRLG_RngPlanInputs_Tests.h" #include "Programs/RngManipulation/PokemonFRLG_RngPlanLog_Tests.h" @@ -932,7 +932,7 @@ void add_tests(UnitTestDatabase& database){ add_tests_OverworldSim(database); add_tests_PlayerFacingReader(database); add_tests_AutoStory(database); - add_tests_AutoStoryExport(database); + add_tests_AutoStoryJson(database); add_tests_AutoStorySimulation(database); add_tests_AutoStoryRows(database); add_tests_AutoStoryTrashCans(database); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp new file mode 100644 index 0000000000..780b3a9a88 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp @@ -0,0 +1,233 @@ +/* FRLG Checkpoint JSON Parser + * From: https://github.com/PokemonAutomation/ + */ + +#include +#include +#include +#include +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Json/JsonArray.h" +#include "Common/Cpp/Json/JsonObject.h" +#include "Pokemon/AutoStory/Pokemon_StoryStepJson.h" +#include "Pokemon/AutoStory/Pokemon_AuthoredCheckpointJson.h" +#include "LegacyJsonFormat.h" +#include "JsonParser.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +namespace{ + +// Reading. Every field is required and no other is allowed, as in the +// shared codec: the record says what the table said, not what it left out. +const JsonObject& fields(const JsonValue& value, std::initializer_list keys, std::string_view source){ + const JsonObject& object = value.to_object_throw(std::string(source)); + for (const auto& [key, unused] : object){ + bool known = false; + for (std::string_view allowed : keys){ + known |= key == allowed; + } + if (!known){ + throw ParseException(std::format("{}: unsupported field {}", source, key)); + } + } + return object; +} + +void expect_string(const JsonObject& root, const std::string& key, std::string_view expected, std::string_view source){ + const std::string& actual = root.get_string_throw(key, std::string(source)); + if (actual != expected){ + throw ParseException(std::format("{}: {} is {}, not {}", source, key, actual, expected)); + } +} + +// The value of `values` that `name` names `label`. +template +Enum read_named(std::initializer_list values, const char* (*name)(Enum), const std::string& label, std::string_view source){ + for (Enum value : values){ + if (label == name(value)){ + return value; + } + } + throw ParseException(std::format("{}: unsupported value {}", source, label)); +} + +// An integer within [`low`, `high`], as the native field's type holds it. +int64_t bounded(const JsonValue& value, int64_t low, int64_t high, std::string_view source){ + const int64_t number = value.to_integer_throw(std::string(source)); + if (number < low || number > high){ + throw ParseException(std::format("{}: {} is out of range", source, number)); + } + return number; +} + +int read_int(const JsonValue& value, std::string_view source){ + return (int)bounded(value, std::numeric_limits::min(), std::numeric_limits::max(), source); +} + +uint8_t read_byte(const JsonValue& value, std::string_view source){ + return (uint8_t)bounded(value, 0, std::numeric_limits::max(), source); +} + +uint16_t read_word(const JsonValue& value, std::string_view source){ + return (uint16_t)bounded(value, 0, std::numeric_limits::max(), source); +} + +size_t read_count(const JsonValue& value, std::string_view source){ + return (size_t)bounded(value, 0, std::numeric_limits::max(), source); +} + +std::vector read_strings(const JsonValue& value, std::string_view source){ + std::vector result; + for (const JsonValue& item : value.to_array_throw(std::string(source))){ + result.push_back(item.to_string_throw(std::string(source))); + } + return result; +} + +std::optional read_stats(const JsonValue& value, std::string_view source){ + if (value.is_null()){ + return std::nullopt; + } + const JsonObject& root = fields(value, {"hp", "attack", "defense", "sp_attack", "sp_defense", "speed"}, source); + return Stats{ + .hp = read_word(root.get_value_throw("hp"), source), + .attack = read_word(root.get_value_throw("attack"), source), + .defense = read_word(root.get_value_throw("defense"), source), + .sp_attack = read_word(root.get_value_throw("sp_attack"), source), + .sp_defense = read_word(root.get_value_throw("sp_defense"), source), + .speed = read_word(root.get_value_throw("speed"), source), + }; +} + +std::vector read_moves(const JsonValue& value, std::string_view source){ + std::vector result; + for (const JsonValue& item : value.to_array_throw(std::string(source))){ + const JsonObject& row = fields(item, {"move", "pp"}, source); + result.push_back({.move = row.get_string_throw("move"), .pp = read_byte(row.get_value_throw("pp"), source)}); + } + return result; +} + +PartyMember read_member(const JsonValue& value, std::string_view source){ + const JsonObject& root = fields(value, {"species", "moves", "moves_through_level", "moves_read", "level", "stats"}, source); + return PartyMember{ + .species = root.get_string_throw("species"), + .moves = read_moves(root.get_value_throw("moves"), source), + .moves_through_level = read_byte(root.get_value_throw("moves_through_level"), source), + .moves_read = root.get_boolean_throw("moves_read"), + .level = read_byte(root.get_value_throw("level"), source), + .stats = read_stats(root.get_value_throw("stats"), source), + }; +} + +std::vector read_slots(const JsonValue& value, std::string_view source){ + std::vector result; + for (const JsonValue& item : value.to_array_throw(std::string(source))){ + result.push_back(read_count(item, source)); + } + return result; +} + +std::optional read_foe_out(const JsonValue& value, std::string_view source){ + if (value.is_null()){ + return std::nullopt; + } + return read_count(value, source); +} + +Supplies read_supplies(const JsonValue& value, std::string_view source){ + const JsonObject& root = fields(value, {"potions", "antidotes", "parlyz_heals", "pocket", "status", "hurt", "hp_red", + "hp_fill", "lead", "protected_slots", "foes", "trainer", "risk", "foe_out", "wild_foes"}, source); + Supplies result; + result.potions = read_int(root.get_value_throw("potions"), source); + result.antidotes = read_int(root.get_value_throw("antidotes"), source); + result.parlyz_heals = read_int(root.get_value_throw("parlyz_heals"), source); + result.pocket = read_strings(root.get_value_throw("pocket"), source); + result.status = read_named({StatusCondition::NONE, StatusCondition::POISON, StatusCondition::PARALYSIS, StatusCondition::OTHER}, + status_name, root.get_string_throw("status"), source); + result.hurt = root.get_boolean_throw("hurt"); + result.hp_red = root.get_boolean_throw("hp_red"); + result.hp_fill = root.get_double_throw("hp_fill"); + result.lead = read_member(root.get_value_throw("lead"), source); + result.protected_slots = read_slots(root.get_value_throw("protected_slots"), source); + result.foes = read_strings(root.get_value_throw("foes"), source); + result.trainer = root.get_string_throw("trainer"); + result.risk = read_named({BattleRisk::LOW, BattleRisk::HIGH}, battle_risk_name, root.get_string_throw("risk"), source); + result.foe_out = read_foe_out(root.get_value_throw("foe_out"), source); + result.wild_foes = read_strings(root.get_value_throw("wild_foes"), source); + return result; +} + +void check_envelope(const JsonObject& root, std::string_view source){ + expect_string(root, "format", StoryJsonInternal::FORMAT, source); + expect_string(root, "game", StoryJsonInternal::GAME, source); + expect_string(root, "status", StoryJsonInternal::STATUS, source); + expect_string(root, "state_basis", StoryJsonInternal::STATE_BASIS, source); + if (read_strings(root.get_value_throw("unbound"), source) != StoryJsonInternal::unbound_fields()){ + throw ParseException(std::format("{}: unbound is not the export's list", source)); + } + if (root.get_string_throw("source_revision").empty()){ + throw ParseException(std::format("{}: source_revision names no revision", source)); + } +} + +StoryCheckpoint apply_execution(Pokemon::AuthoredCheckpoint parsed, std::string_view source){ + const JsonObject& execution = parsed.execution; + Pokemon::require_story_json_fields(execution, {"menu"}, source); + StoryCheckpoint result; + static_cast(result) = std::move(parsed.checkpoint); + const std::string& menu = execution.get_string_throw("menu"); + if (menu == "before_pokemon"){ + result.menu = StartMenu::BEFORE_POKEMON; + }else if (menu == "before_pokedex"){ + result.menu = StartMenu::BEFORE_POKEDEX; + }else if (menu == "full"){ + result.menu = StartMenu::FULL; + }else{ + throw ParseException("Unsupported authored checkpoint menu."); + } + return result; +} + + +} // namespace + +StoryCheckpoint read_authored_checkpoint(const JsonValue& value, std::string_view source){ + return apply_execution(Pokemon::read_authored_checkpoint(value, "frlg", source), source); +} + +StoryCheckpoint load_authored_checkpoint(std::string_view file){ + return apply_execution(Pokemon::load_authored_checkpoint(autostory_checkpoint_json(), file, "frlg"), file); +} + +StoryCheckpoint read_story_checkpoint_export(const JsonValue& value, std::string_view source){ + const JsonObject& root = fields(value, + {"format", "game", "status", "checkpoint", "native_fields", "state_basis", "unbound", "source_revision"}, source); + check_envelope(root, source); + StoryCheckpoint result; + Pokemon::StoryCheckpoint& common = result; + common = Pokemon::read_story_checkpoint(root.get_value_throw("checkpoint"), source); + const JsonObject& native = fields(root.get_value_throw("native_fields"), {"menu", "supplies"}, source); + result.menu = read_named({StartMenu::BEFORE_POKEMON, StartMenu::BEFORE_POKEDEX, StartMenu::FULL}, + StoryJsonInternal::menu_name, native.get_string_throw("menu"), source); + result.supplies = read_supplies(native.get_value_throw("supplies"), source); + return result; +} + +StoryCheckpoint load_story_checkpoint(std::string_view file){ + for (const auto& [name, text] : autostory_checkpoint_json()){ + if (name == file){ + return read_story_checkpoint_export(parse_json(text), std::format("StoryData/{}.json", file)); + } + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, std::format("StoryData/{}.json is not built into the program.", file)); +} + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.h new file mode 100644 index 0000000000..9b20466141 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.h @@ -0,0 +1,47 @@ +/* FRLG Checkpoint JSON Parser + * From: https://github.com/PokemonAutomation/ + */ + +#ifndef PokemonAutomation_PokemonFRLG_CheckpointJsonParser_H +#define PokemonAutomation_PokemonFRLG_CheckpointJsonParser_H + +#include +#include +#include +#include +#include "Common/Cpp/Json/JsonValue.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +// Read a data-authored checkpoint, retaining native bookkeeping defaults. +// Unknown envelope fields/menu values and malformed common actions throw ParseException. +StoryCheckpoint read_authored_checkpoint(const JsonValue& value, std::string_view source); + +// Load an embedded authored plan by filename without .json; no controller IO. +StoryCheckpoint load_authored_checkpoint(std::string_view file); + + +// Embedded StoryData/*.json records: (filename without .json, text), sorted by name. +const std::vector>& autostory_checkpoint_json(); + + +// Migration only: read a captured native-table export, including all bookkeeping. +// Validate the format/game/status/state basis/unbound list and require a nonempty +// source revision. Unknown fields/values and missing native fields throw ParseException. +// The reader cannot verify the source revision against the captured table. +StoryCheckpoint read_story_checkpoint_export(const JsonValue& value, std::string_view source); + +// Migration only: read an embedded export capture by filename without .json. +// Throws InternalProgramError if the file is not built into the program. +StoryCheckpoint load_story_checkpoint(std::string_view file); + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation + +#endif diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonWriter.cpp similarity index 77% rename from SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport.cpp rename to SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonWriter.cpp index a0b85c21ea..069658edee 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonWriter.cpp @@ -1,25 +1,22 @@ -/* FRLG Authored Checkpoint Export +/* Legacy FRLG Checkpoint JSON Writer * From: https://github.com/PokemonAutomation/ */ + #include #include -#include "Common/Cpp/Exceptions.h" #include "Common/Cpp/Json/JsonArray.h" #include "Common/Cpp/Json/JsonObject.h" #include "Pokemon/AutoStory/Pokemon_StoryStepJson.h" -#include "PokemonFRLG_AutoStoryExport.h" +#include "LegacyJsonFormat.h" +#include "JsonWriter.h" + namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ + + namespace{ -const char* menu_name(StartMenu menu){ - switch (menu){ - case StartMenu::BEFORE_POKEMON: return "before_pokemon"; - case StartMenu::BEFORE_POKEDEX: return "before_pokedex"; - case StartMenu::FULL: return "full"; - } - throw ParseException("Unsupported FRLG START menu."); -} + JsonArray strings(const std::vector& values){ JsonArray result; for (const std::string& value : values){ @@ -27,12 +24,14 @@ JsonArray strings(const std::vector& values){ } return result; } + int64_t count(size_t value){ if (value > (size_t)std::numeric_limits::max()){ throw ParseException("FRLG checkpoint count exceeds JSON integer range."); } return (int64_t)value; } + JsonObject stats_json(const Stats& stats){ JsonObject result; result["hp"] = stats.hp; @@ -43,6 +42,7 @@ JsonObject stats_json(const Stats& stats){ result["speed"] = stats.speed; return result; } + JsonObject member_json(const PartyMember& member){ JsonObject result; result["species"] = member.species; @@ -60,6 +60,7 @@ JsonObject member_json(const PartyMember& member){ result["stats"] = member.stats ? JsonValue(stats_json(*member.stats)) : JsonValue(); return result; } + JsonObject supplies_json(const Supplies& supplies){ JsonObject result; result["potions"] = supplies.potions; @@ -83,21 +84,26 @@ JsonObject supplies_json(const Supplies& supplies){ result["wild_foes"] = strings(supplies.wild_foes); return result; } + + } // namespace + JsonValue story_checkpoint_export(const StoryCheckpoint& checkpoint){ JsonObject result; - result["format"] = "pokesim-native-checkpoint-export/1"; - result["game"] = "frlg"; - result["status"] = "native-table-export"; + result["format"] = std::string(StoryJsonInternal::FORMAT); + result["game"] = std::string(StoryJsonInternal::GAME); + result["status"] = std::string(StoryJsonInternal::STATUS); result["checkpoint"] = Pokemon::story_checkpoint_json(checkpoint); JsonObject native; - native["menu"] = menu_name(checkpoint.menu); + native["menu"] = StoryJsonInternal::menu_name(checkpoint.menu); native["supplies"] = supplies_json(checkpoint.supplies); result["native_fields"] = std::move(native); - result["state_basis"] = "native-table-bookkeeping"; - result["unbound"] = strings({"runtime_configuration", "map_pins", "executor_bindings", "entry_evidence", "effective_save_retry_policy"}); + result["state_basis"] = std::string(StoryJsonInternal::STATE_BASIS); + result["unbound"] = strings(StoryJsonInternal::unbound_fields()); return result; } + + } // namespace PokemonFRLG } // namespace NintendoSwitch } // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonWriter.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonWriter.h new file mode 100644 index 0000000000..0fa8d2d229 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonWriter.h @@ -0,0 +1,26 @@ +/* Legacy FRLG Checkpoint JSON Writer + * From: https://github.com/PokemonAutomation/ + */ + +#ifndef PokemonAutomation_PokemonFRLG_CheckpointJsonWriter_H +#define PokemonAutomation_PokemonFRLG_CheckpointJsonWriter_H + +#include "Common/Cpp/Json/JsonValue.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +// Migration diagnostics only: capture shared fields and FRLG bookkeeping. +// This is not the authored production format or observed entry requirements. +// Runtime configuration, map pins, policies and executor bindings remain unbound. +JsonValue story_checkpoint_export(const StoryCheckpoint& checkpoint); + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation + +#endif diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.cpp new file mode 100644 index 0000000000..2b4715df94 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.cpp @@ -0,0 +1,282 @@ +/* FRLG Checkpoint Export Tests + * From: https://github.com/PokemonAutomation/ + */ + +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Json/JsonArray.h" +#include "Common/Cpp/Json/JsonObject.h" +#include "Pokemon/AutoStory/Pokemon_StoryStepJson.h" +#include "JsonParser.h" +#include "JsonWriter.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h" +#include "Json_Tests.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +namespace{ + +struct ExportInput{ + std::string_view id; + bool custom = false; +}; + +struct ExportExpectation{ + std::vector events; + std::string_view menu; + std::string_view native_json = {}; +}; + +struct ExportCase{ + ExportInput input; + ExportExpectation expectation; +}; + +const ExportCase CASES[] = { + {.input = {.id = "00.05"}, .expectation = {.events = {"below_mart_door", "mart_door", "parcel_from_clerk"}, .menu = "before_pokedex"}}, + {.input = {.id = "00.05", .custom = true}, .expectation = { + .events = {"below_mart_door", "mart_door", "parcel_from_clerk"}, .menu = "full", + .native_json = R"({"menu":"full","supplies":{"potions":2,"antidotes":3,"parlyz_heals":4,"pocket":["antidote",""],"status":"PSN","hurt":true,"hp_red":true,"hp_fill":0.25,"lead":{"species":"wartortle","moves":[{"move":"bite","pp":0}],"moves_through_level":18,"moves_read":true,"level":19,"stats":{"hp":50,"attack":20,"defense":30,"sp_attack":40,"sp_defense":35,"speed":25}},"protected_slots":[0,2],"foes":["staryu"],"trainer":"misty","risk":"high","foe_out":0,"wild_foes":["pidgey"]}})" + }}, +}; + +void customize(StoryCheckpoint& row){ + row.menu = StartMenu::FULL; + Supplies& state = row.supplies; + state.potions = 2; + state.antidotes = 3; + state.parlyz_heals = 4; + state.pocket = {"antidote", ""}; + state.status = StatusCondition::POISON; + state.hurt = true; + state.hp_red = true; + state.hp_fill = 0.25; + state.lead = PartyMember{.species = "wartortle", .moves = {{"bite", 0}}, + .moves_through_level = 18, .moves_read = true, .level = 19, + .stats = Stats{.hp = 50, .attack = 20, .defense = 30, .sp_attack = 40, .sp_defense = 35, .speed = 25}}; + state.protected_slots = {0, 2}; + state.foes = {"staryu"}; + state.trainer = "misty"; + state.risk = BattleRisk::HIGH; + state.foe_out = 0; + state.wild_foes = {"pidgey"}; +} + +// The export as the tool writes it to a file: with its source revision. +JsonValue exported_file(const StoryCheckpoint& row, const std::string& revision){ + JsonValue result = story_checkpoint_export(row); + result.to_object_throw()["source_revision"] = revision; + return result; +} + +std::string check_export(StoryCheckpoint row, const ExportCase& test){ + if (test.input.custom){ + customize(row); + } + const JsonValue encoded = story_checkpoint_export(row); + const JsonObject& root = encoded.to_object_throw(); + const JsonObject& native = root.get_object_throw("native_fields"); + if (native.get_string_throw("menu") != test.expectation.menu){ + return "Expected menu " + std::string(test.expectation.menu) + ", read " + native.get_string_throw("menu"); + } + if (!test.expectation.native_json.empty() && !(JsonValue(native.clone()) == parse_json(std::string(test.expectation.native_json)))){ + return "Expected native fields " + std::string(test.expectation.native_json) + ", read " + native.dump(); + } + const auto decoded = Pokemon::read_story_checkpoint(root.get_value_throw("checkpoint"), "export test"); + if (!(Pokemon::story_checkpoint_json(decoded) == Pokemon::story_checkpoint_json(row))){ + return "Expected every authored common field to survive export/import."; + } + // Every native field too: the whole file, read back, exports the same. + const StoryCheckpoint reread = read_story_checkpoint_export(exported_file(row, "test"), "export test"); + if (!(exported_file(reread, "test") == exported_file(row, "test"))){ + return "Expected every native field to survive export/import, read " + story_checkpoint_export(reread).dump(-1); + } + std::vector names; + for (const auto& step : decoded.steps){ + names.push_back(step.name); + } + return names == test.expectation.events ? "" : "Expected Parcel's three authored events in order."; +} + +class ExportTest : public UnitTest{ +public: + explicit ExportTest(ExportCase test) + : UnitTest("PokemonFRLG::CheckpointExport - " + std::string(test.input.id) + (test.input.custom ? " custom" : " authored")), m_test(test){} + virtual UnitTestResult run(Logger&, CancellableScope&) const override{ + for (const auto& row : story_checkpoints()){ + if (row.id == m_test.input.id){ + const std::string error = check_export(row, m_test); + return error.empty() ? UnitTestResult(true) : UnitTestResult(error); + } + } + return UnitTestResult("Expected checkpoint " + std::string(m_test.input.id) + ", read no matching row."); + } +private: + ExportCase m_test; +}; + + +// Reading the Parcel capture built into the program (StoryData/ +// frlg-parcel-00.05.json, PokemonSimulation's data/story/native copy of +// the export tool's output): as written, it is row 00.05 and exports back +// to the same file; changed, it is refused rather than read with a guess. +struct LoadInput{ + std::string_view name; + // Applied to the capture before reading; none reads it as written. + void (*mutate)(JsonObject& root) = nullptr; + bool authored = false; +}; + +struct LoadExpectation{ + // The table row the loaded one must export like; empty when rejected. + std::string_view row = {}; + bool rejected = false; +}; + +struct LoadCase{ + LoadInput input; + LoadExpectation expectation; +}; + +// `object` without `key`: JsonObject has no erase. +JsonObject without(const JsonObject& object, std::string_view key){ + JsonObject result; + for (const auto& [name, value] : object){ + if (name != key){ + result[name] = value.clone(); + } + } + return result; +} + +void add_top_level_field(JsonObject& root){ + root["mystery"] = 1; +} + +void change_format(JsonObject& root){ + root["format"] = "pokesim-story-plan/1"; +} + +void add_native_field(JsonObject& root){ + root.get_object_throw("native_fields")["speed"] = "fast"; +} + +void unknown_menu(JsonObject& root){ + root.get_object_throw("native_fields")["menu"] = "before_bag"; +} + +void drop_potions(JsonObject& root){ + JsonObject& native = root.get_object_throw("native_fields"); + native["supplies"] = without(native.get_object_throw("supplies"), "potions"); +} + +void negative_pp(JsonObject& root){ + JsonObject& supplies = root.get_object_throw("native_fields").get_object_throw("supplies"); + supplies.get_object_throw("lead").get_array_throw("moves")[0].to_object_throw()["pp"] = -1; +} + +void foe_out_as_text(JsonObject& root){ + root.get_object_throw("native_fields").get_object_throw("supplies")["foe_out"] = "0"; +} + +void drop_revision(JsonObject& root){ + root = without(root, "source_revision"); +} + +void unknown_execution_menu(JsonObject& root){ + root.get_object_throw("execution")["menu"] = "guess"; +} + +const LoadCase LOAD_CASES[] = { + {.input = {.name = "authored Parcel plan", .authored = true}, .expectation = {.row = "00.05"}}, + {.input = {.name = "authored plan unknown field", .mutate = add_top_level_field, .authored = true}, .expectation = {.rejected = true}}, + {.input = {.name = "authored plan unknown format", .mutate = change_format, .authored = true}, .expectation = {.rejected = true}}, + {.input = {.name = "authored plan unknown menu", .mutate = unknown_execution_menu, .authored = true}, .expectation = {.rejected = true}}, + {.input = {.name = "the Parcel capture is row 00.05"}, .expectation = {.row = "00.05"}}, + {.input = {.name = "an unknown envelope field", .mutate = add_top_level_field}, .expectation = {.rejected = true}}, + {.input = {.name = "another format", .mutate = change_format}, .expectation = {.rejected = true}}, + {.input = {.name = "an unknown native field", .mutate = add_native_field}, .expectation = {.rejected = true}}, + {.input = {.name = "an unknown START menu", .mutate = unknown_menu}, .expectation = {.rejected = true}}, + {.input = {.name = "a missing supplies field", .mutate = drop_potions}, .expectation = {.rejected = true}}, + {.input = {.name = "PP below zero", .mutate = negative_pp}, .expectation = {.rejected = true}}, + {.input = {.name = "foe_out as text", .mutate = foe_out_as_text}, .expectation = {.rejected = true}}, + {.input = {.name = "no source revision", .mutate = drop_revision}, .expectation = {.rejected = true}}, +}; + +const std::string& parcel_text(const LoadInput& input){ + for (const auto& [name, text] : autostory_checkpoint_json()){ + if (name == (input.authored ? "frlg-parcel-00.05.plan" : "frlg-parcel-00.05")){ + return text; + } + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "StoryData/frlg-parcel-00.05.json is not built into the program."); +} + +const StoryCheckpoint* table_row(std::string_view id){ + for (const StoryCheckpoint& row : story_checkpoints()){ + if (row.id == id){ + return &row; + } + } + return nullptr; +} + +std::string check_loaded(const StoryCheckpoint& loaded, const JsonValue& capture, const LoadCase& test){ + const std::string revision = test.input.authored ? "" : capture.to_object_throw().get_string_throw("source_revision"); + if (!test.input.authored && !(exported_file(loaded, revision) == capture)){ + return "Expected the loaded row to export as the capture, read " + story_checkpoint_export(loaded).dump(-1); + } + const StoryCheckpoint* row = table_row(test.expectation.row); + if (!row){ + return "Expected table row " + std::string(test.expectation.row) + ", read no matching row."; + } + if (!(story_checkpoint_export(loaded) == story_checkpoint_export(*row))){ + return "Expected the loaded row to export as the table's " + row->id + ", read " + story_checkpoint_export(loaded).dump(-1); + } + return ""; +} + +std::string check_load(const LoadCase& test){ + JsonValue capture = parse_json(parcel_text(test.input)); + if (test.input.mutate){ + test.input.mutate(capture.to_object_throw()); + } + try{ + const StoryCheckpoint loaded = test.input.authored ? read_authored_checkpoint(capture, test.input.name) + : read_story_checkpoint_export(capture, test.input.name); + if (test.expectation.rejected){ + return "Expected rejection, read checkpoint " + loaded.id; + } + return check_loaded(loaded, capture, test); + }catch (const ParseException& error){ + return test.expectation.rejected ? "" : "Unexpected parse failure: " + error.message(); + } +} + +class LoadTest : public UnitTest{ +public: + explicit LoadTest(LoadCase test) + : UnitTest("PokemonFRLG::CheckpointExport - read " + std::string(test.input.name)), m_test(test){} + virtual UnitTestResult run(Logger&, CancellableScope&) const override{ + const std::string error = check_load(m_test); + return error.empty() ? UnitTestResult(true) : UnitTestResult(error); + } +private: + LoadCase m_test; +}; + +} // namespace + +void add_tests_AutoStoryJson(UnitTestDatabase& database){ + for (const ExportCase& test : CASES){ + database.add(test); + } + for (const LoadCase& test : LOAD_CASES){ + database.add(test); + } +} + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.h similarity index 66% rename from SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.h rename to SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.h index 90eabc74a9..b43a86f3d1 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.h @@ -1,14 +1,18 @@ /* FRLG Checkpoint Export Tests * From: https://github.com/PokemonAutomation/ */ -#ifndef PokemonAutomation_PokemonFRLG_AutoStoryExport_Tests_H -#define PokemonAutomation_PokemonFRLG_AutoStoryExport_Tests_H + +#ifndef PokemonAutomation_PokemonFRLG_AutoStoryJson_Tests_H +#define PokemonAutomation_PokemonFRLG_AutoStoryJson_Tests_H + #include "Common/Cpp/TestRunners/UnitTestDatabase.h" + namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ + // Register evaluated-table and non-default native-extension preservation tests. -void add_tests_AutoStoryExport(UnitTestDatabase& database); +void add_tests_AutoStoryJson(UnitTestDatabase& database); } // namespace PokemonFRLG } // namespace NintendoSwitch } // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/LegacyJsonFormat.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/LegacyJsonFormat.h new file mode 100644 index 0000000000..527273360c --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/LegacyJsonFormat.h @@ -0,0 +1,53 @@ +/* Legacy FRLG Checkpoint Export Format + * From: https://github.com/PokemonAutomation/ + */ + +#ifndef PokemonAutomation_PokemonFRLG_LegacyJsonFormat_H +#define PokemonAutomation_PokemonFRLG_LegacyJsonFormat_H + +#include +#include +#include +#include "Common/Cpp/Exceptions.h" +#include "VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +namespace StoryJsonInternal{ + +// The envelope constants the export writes and the reader requires. +constexpr std::string_view FORMAT = "pokesim-native-checkpoint-export/1"; +// Game identifier in the legacy envelope. +constexpr std::string_view GAME = "frlg"; +// Marks a captured table row rather than an authored plan. +constexpr std::string_view STATUS = "native-table-export"; +// Describes the native bookkeeping snapshot, not observed game state. +constexpr std::string_view STATE_BASIS = "native-table-bookkeeping"; +// Fields deliberately left outside the captured native table state. +inline const std::vector& unbound_fields(){ + static const std::vector UNBOUND{ + "runtime_configuration", "map_pins", "executor_bindings", "entry_evidence", "effective_save_retry_policy", + }; + return UNBOUND; +} + +// Stable legacy spellings of the native START menu variants. +inline const char* menu_name(StartMenu menu){ + switch (menu){ + case StartMenu::BEFORE_POKEMON: return "before_pokemon"; + case StartMenu::BEFORE_POKEDEX: return "before_pokedex"; + case StartMenu::FULL: return "full"; + } + throw ParseException("Unsupported FRLG START menu."); +} + +} // namespace StoryJsonInternal + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation + +#endif diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport.h deleted file mode 100644 index f2cc73c6b2..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport.h +++ /dev/null @@ -1,18 +0,0 @@ -/* FRLG Authored Checkpoint Export - * From: https://github.com/PokemonAutomation/ - */ -#ifndef PokemonAutomation_PokemonFRLG_AutoStoryExport_H -#define PokemonAutomation_PokemonFRLG_AutoStoryExport_H -#include "Common/Cpp/Json/JsonValue.h" -#include "PokemonFRLG_AutoStoryEvents.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonFRLG{ -// Capture evaluated shared fields and every FRLG checkpoint extension. -// Native supplies are bookkeeping, not observed state or entry requirements. -// Runtime options, map pins, effective policies and execution remain unbound. -JsonValue story_checkpoint_export(const StoryCheckpoint& checkpoint); -} // namespace PokemonFRLG -} // namespace NintendoSwitch -} // namespace PokemonAutomation -#endif diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.cpp deleted file mode 100644 index bc4e0f3f12..0000000000 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* FRLG Checkpoint Export Tests - * From: https://github.com/PokemonAutomation/ - */ -#include "Common/Cpp/Json/JsonArray.h" -#include "Common/Cpp/Json/JsonObject.h" -#include "Pokemon/AutoStory/Pokemon_StoryStepJson.h" -#include "PokemonFRLG_AutoStoryExport.h" -#include "PokemonFRLG_AutoStoryTable.h" -#include "PokemonFRLG_AutoStoryExport_Tests.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonFRLG{ -namespace{ -struct ExportInput{ - std::string_view id; - bool custom = false; -}; -struct ExportExpectation{ - std::vector events; - std::string_view menu; - std::string_view native_json = {}; -}; -struct ExportCase{ - ExportInput input; - ExportExpectation expectation; -}; -const ExportCase CASES[] = { - {.input = {.id = "00.05"}, .expectation = {.events = {"below_mart_door", "mart_door", "parcel_from_clerk"}, .menu = "before_pokedex"}}, - {.input = {.id = "00.05", .custom = true}, .expectation = { - .events = {"below_mart_door", "mart_door", "parcel_from_clerk"}, .menu = "full", - .native_json = R"({"menu":"full","supplies":{"potions":2,"antidotes":3,"parlyz_heals":4,"pocket":["antidote",""],"status":"PSN","hurt":true,"hp_red":true,"hp_fill":0.25,"lead":{"species":"wartortle","moves":[{"move":"bite","pp":0}],"moves_through_level":18,"moves_read":true,"level":19,"stats":{"hp":50,"attack":20,"defense":30,"sp_attack":40,"sp_defense":35,"speed":25}},"protected_slots":[0,2],"foes":["staryu"],"trainer":"misty","risk":"high","foe_out":0,"wild_foes":["pidgey"]}})" - }}, -}; -void customize(StoryCheckpoint& row){ - row.menu = StartMenu::FULL; - Supplies& state = row.supplies; - state.potions = 2; - state.antidotes = 3; - state.parlyz_heals = 4; - state.pocket = {"antidote", ""}; - state.status = StatusCondition::POISON; - state.hurt = true; - state.hp_red = true; - state.hp_fill = 0.25; - state.lead = PartyMember{.species = "wartortle", .moves = {{"bite", 0}}, - .moves_through_level = 18, .moves_read = true, .level = 19, - .stats = Stats{.hp = 50, .attack = 20, .defense = 30, .sp_attack = 40, .sp_defense = 35, .speed = 25}}; - state.protected_slots = {0, 2}; - state.foes = {"staryu"}; - state.trainer = "misty"; - state.risk = BattleRisk::HIGH; - state.foe_out = 0; - state.wild_foes = {"pidgey"}; -} -std::string check_export(StoryCheckpoint row, const ExportCase& test){ - if (test.input.custom){ - customize(row); - } - const JsonValue encoded = story_checkpoint_export(row); - const JsonObject& root = encoded.to_object_throw(); - const JsonObject& native = root.get_object_throw("native_fields"); - if (native.get_string_throw("menu") != test.expectation.menu){ - return "Expected menu " + std::string(test.expectation.menu) + ", read " + native.get_string_throw("menu"); - } - if (!test.expectation.native_json.empty() && !(JsonValue(native.clone()) == parse_json(std::string(test.expectation.native_json)))){ - return "Expected native fields " + std::string(test.expectation.native_json) + ", read " + native.dump(); - } - const auto decoded = Pokemon::read_story_checkpoint(root.get_value_throw("checkpoint"), "export test"); - if (!(Pokemon::story_checkpoint_json(decoded) == Pokemon::story_checkpoint_json(row))){ - return "Expected every authored common field to survive export/import."; - } - std::vector names; - for (const auto& step : decoded.steps){ - names.push_back(step.name); - } - return names == test.expectation.events ? "" : "Expected Parcel's three authored events in order."; -} -class ExportTest : public UnitTest{ -public: - explicit ExportTest(ExportCase test) - : UnitTest("PokemonFRLG::CheckpointExport - " + std::string(test.input.id) + (test.input.custom ? " custom" : " authored")), m_test(test){} - virtual UnitTestResult run(Logger&, CancellableScope&) const override{ - for (const auto& row : story_checkpoints()){ - if (row.id == m_test.input.id){ - const std::string error = check_export(row, m_test); - return error.empty() ? UnitTestResult(true) : UnitTestResult(error); - } - } - return UnitTestResult("Expected checkpoint " + std::string(m_test.input.id) + ", read no matching row."); - } -private: - ExportCase m_test; -}; -} // namespace -void add_tests_AutoStoryExport(UnitTestDatabase& database){ - for (const ExportCase& test : CASES){ - database.add(test); - } -} -} // namespace PokemonFRLG -} // namespace NintendoSwitch -} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySimulation_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySimulation_Tests.cpp index b420fb235c..f435c6c48d 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySimulation_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySimulation_Tests.cpp @@ -33,6 +33,7 @@ #include "PokemonFRLG_AutoStoryEvents.h" #include "PokemonFRLG_AutoStoryPaceSim.h" #include "PokemonFRLG_AutoStoryRowSim.h" +#include "Data/JsonParser.h" #include "PokemonFRLG_AutoStoryTable.h" namespace PokemonAutomation{ @@ -328,7 +329,7 @@ struct AutoStorySimulationInput{ }; struct AutoStorySimulationExpectation{ - const char* description; + std::string description; }; struct AutoStorySimulationTestCase{ @@ -887,7 +888,42 @@ UnitTestResult test_out_of_bill_cottage(){ } +// 00.05 as the table authors it and as the data file built into the +// program (StoryData/frlg-parcel-00.05.json, the export tool's capture of +// that row): the loaded row builds the same events, and run against the +// fake game walks the same route, leaves the same trace and saves in the +// same place. +UnitTestResult test_data_row_parity(){ + const std::vector& table = story_checkpoints(); + const auto authored = std::find_if(table.begin(), table.end(), [](const StoryCheckpoint& row){ return row.id == "00.05"; }); + if (authored == table.end()){ + return "Expected row 00.05 in the table."; + } + const StoryCheckpoint loaded = load_story_checkpoint("frlg-parcel-00.05"); + FakeGame describer; + const std::string authored_events = Pokemon::describe(Pokemon::build_story_checkpoint(*authored, describer)); + const std::string loaded_events = Pokemon::describe(Pokemon::build_story_checkpoint(loaded, describer)); + if (authored_events != loaded_events){ + return "Expected the same events from the table and the data:\n" + authored_events + "\n" + loaded_events; + } + Simulation from_table; + from_table.rows = {*authored}; + Simulation from_data; + from_data.rows = {loaded}; + const bool completed = from_table.run({}).completed && from_data.run({}).completed; + if (!completed || from_table.game.trace != from_data.game.trace || from_table.lines != from_data.lines){ + return "Expected the same run from the table and the data:" + joined(from_table.game.trace) + joined(from_data.game.trace); + } + if (from_table.saves.size() != 1 || from_data.saves.size() != 1 + || Pokemon::to_string(from_table.saves[0].second) != Pokemon::to_string(from_data.saves[0].second) + ){ + return "Expected both runs to save once, in the same place:" + joined(from_table.lines) + joined(from_data.lines); + } + return true; +} + const AutoStorySimulationTestCase AUTO_STORY_SIMULATION_CASES[] = { + {.input = {.name = "00.05 loaded row parity", .body = test_data_row_parity}, .expectation = {.description = "loaded row preserves events, trace and save pose"}}, {.input = {.name = "new game to Brock, in order, each start where the last save stood", .body = test_whole_story}, .expectation = {.description = "runs full story progression from new game through Brock gym"}}, {.input = {.name = "start and end checkpoints", .body = test_start_and_end}, .expectation = {.description = "executes segment bounded by start and end checkpoints"}}, {.input = {.name = "a failed walk reloads its checkpoint's save", .body = test_retry_from_save}, .expectation = {.description = "reloads checkpoint save state on simulated movement failure"}}, diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp index 1f579f2983..3f88fe03e1 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp @@ -5,6 +5,7 @@ */ #include "PokemonFRLG_AutoStoryCapture.h" +#include "Data/JsonParser.h" #include "PokemonFRLG_AutoStoryChoice.h" #include "PokemonFRLG_AutoStoryGrind.h" #include "PokemonFRLG_AutoStoryMart.h" @@ -81,13 +82,10 @@ StoryCheckpoint starter(){ }); } +// TODO: Load an ordered series of authored checkpoints together and construct +// a partial story table, instead of loading one file in each row factory. StoryCheckpoint parcel(){ - return row("00.05", "Route 1 to Viridian Mart: Oak's Parcel", at(LAB, 7, 8, SOUTH), {}, StartMenu::BEFORE_POKEDEX, { - route("below_mart_door", at(VIRIDIAN, 36, 20, NORTH), "Route 1, running from wild battles"), - step_into("mart_door", NORTH, "the clerk's scene starts on entering"), - // ApproachCounter: up 4 from the mat, then faces the clerk. - scene("parcel_from_clerk", at(VIRIDIAN_MART, 4, 3, WEST), "the clerk gives OAK'S PARCEL"), - }); + return load_authored_checkpoint("frlg-parcel-00.05.plan"); } StoryCheckpoint pokedex(){ @@ -800,6 +798,6 @@ const std::vector& story_checkpoints(){ -} // namespace PokemonFRLG -} // namespace NintendoSwitch -} // namespace PokemonAutomation +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/README.md b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/README.md new file mode 100644 index 0000000000..55a5b23722 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/README.md @@ -0,0 +1,27 @@ +# FRLG authored checkpoint pilot + +`frlg-parcel-00.05.plan.json` is the production definition of checkpoint `00.05`. The story table loads it with `load_authored_checkpoint()`: there is no second C++ Parcel event list. Its `pokesim-authored-checkpoint/1` contract contains the common checkpoint plus FRLG executor configuration (`execution.menu`). It has no export revision, bookkeeping snapshot, or claimed observation. + +The same file is checked into PokemonSimulation at `data/story/parcel-pilot/frlg-parcel-00.05.plan.json`; the paired CI check compares its exact bytes and runs the existing seven replay scenarios there. The schema is `schemas/authored-checkpoint.schema.json`, referencing `story-checkpoint.schema.json`. + +## The two gates + +1. Native tests load the actual table entry through JSON, run the existing engine/fake-game simulator, and compare its events/trace/save pose with the captured pre-migration checkpoint. Existing full-story progression tests also use the JSON-backed table entry. Unsupported fields/menu values and malformed common actions are rejected. +2. PokemonSimulation loads the same authored file with explicit map/action bindings and scripted evidence, then asserts the seven scenarios, including the full successful semantic action sequence and interruption/failure cases. It does not import the native supplies snapshot as requirements. + +`frlg-parcel-00.05.json` is the old table-export fixture used for migration regression checks only. Its provenance and snapshot fields are not part of the authored plan. It can be removed once the native expectations are independent of the capture. + +## Semantics + +- Checkpoint fields are ordered `id`, `title`, `start`, `events`; each event has an expanded block beginning with `id`, `kind`. +- `start` is the assumed checkpoint/save pose, not a localization observation. +- `declared_after` preserves the current scene executor’s author-declared arrival fallback. It is not independent evidence of position or Parcel receipt. Real door/scene verification remains a separate executor change. +- `execution.menu` configures the existing FRLG save implementation. Inventory, party and battle bookkeeping keep their existing native initialization; they are neither asserted preconditions nor observations. +- This first contract makes no new inventory/party requirements. Add explicit requirements only when an action depends on them, with supported verification/assumption semantics on both sides. Relevant prior progress is not proof of a complete execution history. +- The simulator’s map pins, scene evidence and stop/no-save policy live in the binding and cases, not in the authored checkpoint. Python replay is a model check; native fake-game execution checks integration, not live timing/detector correctness. + +No console is required for either gate. + +## Native JSON code + +`Pokemon/AutoStory/Pokemon_AuthoredCheckpointJson.*` owns the common authored envelope, checkpoint codec delegation, and embedded-resource lookup. `../Data/JsonParser.*` validates FRLG execution settings and retains the legacy capture reader. `JsonWriter.*` is retained for migration comparisons and export diagnostics; production authored loading does not depend on it. `Json_Tests.*` keeps the codec tests beside those sources. Once migration expectations are independent of the old capture, remove the legacy reader, writer, and `LegacyJsonFormat.h`; retain the authored parser. diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-parcel-00.05.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-parcel-00.05.json new file mode 100644 index 0000000000..2090ba206e --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-parcel-00.05.json @@ -0,0 +1,71 @@ +{ + "format": "pokesim-native-checkpoint-export/1", + "game": "frlg", + "checkpoint": { + "id": "00.05", + "title": "Route 1 to Viridian Mart: Oak's Parcel", + "start": { + "certainty": "tile", + "map": "PalletTown_ProfessorOaksLab", + "where": {"facing": "south", "kind": "tiles", "tiles": [[7, 8]]} + }, + "events": [ + { + "id": "below_mart_door", + "kind": "route", + "target": { + "certainty": "tile", + "map": "ViridianCity", + "where": {"facing": "north", "kind": "tiles", "tiles": [[36, 20]]} + }, + "why": "Route 1, running from wild battles" + }, + { + "id": "mart_door", + "kind": "step", + "why": "the clerk's scene starts on entering" + }, + { + "id": "parcel_from_clerk", + "kind": "scene", + "declared_after": { + "certainty": "tile", + "map": "ViridianCity_Mart", + "where": {"facing": "west", "kind": "tiles", "tiles": [[4, 3]]} + }, + "why": "the clerk gives OAK'S PARCEL" + } + ] + }, + "native_fields": { + "menu": "before_pokedex", + "supplies": { + "antidotes": 0, + "foe_out": null, + "foes": [], + "hp_fill": 1.0, + "hp_red": false, + "hurt": false, + "lead": { + "level": 0, + "moves": [{"move": "tackle", "pp": 35}, {"move": "tail-whip", "pp": 30}], + "moves_read": false, + "moves_through_level": 5, + "species": "squirtle", + "stats": null + }, + "parlyz_heals": 0, + "pocket": ["potion", "antidote", "parlyz-heal"], + "potions": 0, + "protected_slots": [], + "risk": "low", + "status": "no status", + "trainer": "", + "wild_foes": [] + } + }, + "source_revision": "b170b7d2103e887ded825c37fdbdc19fd654fca8", + "status": "native-table-export", + "state_basis": "native-table-bookkeeping", + "unbound": ["runtime_configuration", "map_pins", "executor_bindings", "entry_evidence", "effective_save_retry_policy"] +} diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-parcel-00.05.plan.json b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-parcel-00.05.plan.json new file mode 100644 index 0000000000..6df2d7a0d2 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/StoryData/frlg-parcel-00.05.plan.json @@ -0,0 +1,42 @@ +{ + "format": "pokesim-authored-checkpoint/1", + "game": "frlg", + "checkpoint": { + "id": "00.05", + "title": "Route 1 to Viridian Mart: Oak's Parcel", + "start": { + "certainty": "tile", + "map": "PalletTown_ProfessorOaksLab", + "where": {"facing": "south", "kind": "tiles", "tiles": [[7, 8]]} + }, + "events": [ + { + "id": "below_mart_door", + "kind": "route", + "target": { + "certainty": "tile", + "map": "ViridianCity", + "where": {"facing": "north", "kind": "tiles", "tiles": [[36, 20]]} + }, + "why": "Route 1, running from wild battles" + }, + { + "id": "mart_door", + "kind": "step", + "why": "the clerk's scene starts on entering", + "direction": "north" + }, + { + "id": "parcel_from_clerk", + "kind": "scene", + "declared_after": { + "certainty": "tile", + "map": "ViridianCity_Mart", + "where": {"facing": "west", "kind": "tiles", "tiles": [[4, 3]]} + }, + "why": "the clerk gives OAK'S PARCEL" + } + ] + }, + "execution": {"menu": "before_pokedex"} +} diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 8d0569c5bf..7f4e1de880 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1915,10 +1915,13 @@ file(GLOB LIBRARY_SOURCES Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySimulation_Tests.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySummary.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySummary.h - Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport.cpp - Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport.h - Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.cpp - Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryExport_Tests.h + Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.cpp + Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonParser.h + Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonWriter.cpp + Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/JsonWriter.h + Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/LegacyJsonFormat.h + Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.cpp + Source/VideoGames/PokemonFRLG/Programs/AutoStory/Data/Json_Tests.h Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.cpp Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTable.h Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp From a328c4c377b3609654d989d26d1a5434fd12f0cd Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 9 Nov 2026 02:12:33 -0800 Subject: [PATCH 09/13] FRLG AutoStory: 00.27 itself from a save below Cerulean Center's door The start pose sim test runs the console's case as well as 00.09's. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013VUubhHeYSMGSSDXZkKZss --- .../PokemonFRLG_AutoStorySimulation_Tests.cpp | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySimulation_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySimulation_Tests.cpp index f1191656a5..506b97cd6b 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySimulation_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStorySimulation_Tests.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "Common/Cpp/Exceptions.h" #include "Common/Cpp/TestRunners/UnitTest.h" @@ -430,18 +431,25 @@ UnitTestResult test_retry_from_save(){ } // console B, FRLG 00.27, 2026-11-09: the save stood below Cerulean Center's -// door, and the run walked as if from the counter where 00.27 starts. 00.09 -// from below Pewter Center's door (17, 25) stops before any step or save. +// door (22, 19), and the run walked as if from the counter where 00.27 +// starts. It, and 00.09 from below Pewter Center's door (17, 25), stop +// before any step or save. UnitTestResult test_save_off_start_pose(){ - Simulation sim; - sim.game.save_stands = StoryPose{"PewterCity", TilePos{17, 26}, Direction::SOUTH}; - const Pokemon::SegmentReport report = sim.run({.from = "00.09", .to = "00.09"}); - const auto stepped = [](const std::string& item){ return item.starts_with("00.09/"); }; - const auto told = [](const std::string& line){ return line.starts_with("start pose mismatch: "); }; - if (report.completed || report.failure.failure != "start_failed" || !sim.saves.empty() - || std::ranges::any_of(sim.game.trace, stepped) || std::ranges::none_of(sim.lines, told) - ){ - return "Expected 00.09 stopped at its start check, the mismatch logged:" + joined(sim.game.trace) + joined(sim.lines); + const std::pair CASES[] = { + {"00.09", StoryPose{"PewterCity", TilePos{17, 26}, Direction::SOUTH}}, + {"00.27", StoryPose{"CeruleanCity", TilePos{22, 20}, Direction::SOUTH}}, + }; + for (const auto& [id, save] : CASES){ + Simulation sim; + sim.game.save_stands = save; + const Pokemon::SegmentReport report = sim.run({.from = id, .to = id}); + const auto stepped = [&id](const std::string& item){ return item.starts_with(id + "/"); }; + const auto told = [](const std::string& line){ return line.starts_with("start pose mismatch: "); }; + if (report.completed || report.failure.failure != "start_failed" || !sim.saves.empty() + || std::ranges::any_of(sim.game.trace, stepped) || std::ranges::none_of(sim.lines, told) + ){ + return "Expected " + id + " stopped at its start check, the mismatch logged:" + joined(sim.game.trace) + joined(sim.lines); + } } return true; } From 78c01f96030a810527c599eae203d2d16bcf6de1 Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 9 Nov 2026 02:24:02 -0800 Subject: [PATCH 10/13] FRLG AutoStory: red tests for row 00.27's walk to Route 10's Center Console B, 2026-11-09, row 00.27 step route10_nurse failed twice with "no fade in and out after walking onto the warp to Route10_PokemonCenter_1F". Run 3: the crossing onto Route 10 wasn't held up, the open legs carried the player a tile east, the leg down column 17 met the fence post (17, 12) and the walk ended in the grass on (14, 10). Run 2: the leg down column 16 ended a tile short, west 3 met the Center's east wall (15, 20) and north 1 ended on (16, 19). Copied from frlg/route10-door-red (0b00d5d84). Both fail now (red) on exactly those end tiles; the map matches pret and the screenshots. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013VUubhHeYSMGSSDXZkKZss --- .../PokemonFRLG_AutoStoryCeladon_Tests.cpp | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp index a8dda7e923..4a72b6cd3d 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp @@ -278,6 +278,54 @@ UnitTestResult test_route10_save_place(){ return true; } +// 00.27's step route10_nurse on the sim from Route 9 (70, 8): east onto +// Route 10, round its grass along row 11, down column 16 past the ledge +// (2..14, 12) and the fence post (17, 12), west along row 21 and north +// onto the Center's door (13, 20). Every leg but the door's is open +// (timed), and each ends a tile from something that sends the next leg +// elsewhere. Console B, 2026-11-09, both runs "no fade in and out": +// - run 3: the crossing onto Route 10 wasn't held up, so the player +// stood a tile further east, row 11 ended on (17, 11), the leg down +// column 17 met the post (17, 12), and west 3 and north 1 ended in the +// grass on (14, 10) (the end frame). +// - run 2: the leg down column 16 ended on (16, 20), west 3 met the +// Center's east wall (15, 20) and north 1 ended on (16, 19) beside it. +// Either way the door should be reached. +std::string route10_nurse_on_sim(const SimScript& script){ + const StoryCheckpoint& row = checkpoint("00.27"); + const auto step = std::find_if(row.steps.begin(), row.steps.end(), [](const StoryStep& data){ return data.name == "route10_nurse"; }); + if (step == row.steps.end()){ + return "00.27: no step route10_nurse."; + } + StoryPose at{StoryTable::ROUTE9, {70, 8}, Direction::EAST}; + OverworldSim sim(maps_after_surge(), MapPosition{at.map, at.tile}, Direction::EAST, script); + const std::string failure = row_step_on_sim(sim, at, *step, row.id); + const MapPosition nurse{StoryTable::ROUTE10_CENTER, {7, 4}}; + if (!failure.empty() || !(sim.state().position == nurse) || sim.state().warps != 1){ + return std::format("00.27/route10_nurse: ended on {}, not Route 10's nurse; {}", to_string(sim.state().position), failure); + } + return ""; +} + +UnitTestResult test_route10_crossing_not_held_up(){ + SimScript script; + script.connections_stall = false; + if (const std::string failure = route10_nurse_on_sim(script); !failure.empty()){ + return "The crossing onto Route 10 not held up (run 3): " + failure; + } + return true; +} + +UnitTestResult test_route10_column_16_short(){ + SimScript script; + // Holds: east 7, south 3, east 11, south 10 down column 16. + script.short_holds[4] = 1; + if (const std::string failure = route10_nurse_on_sim(script); !failure.empty()){ + return "The leg down column 16 a tile short (run 2): " + failure; + } + return true; +} + } // namespace @@ -306,6 +354,14 @@ const AutoStoryCeladonCase AUTO_STORY_CELADON_CASES[] = { .input = {"00.27 saves in Route 10's Pokemon Center, ROUTE 10 on the save card", test_route10_save_place}, .expectation = {}, }, + { + .input = {"00.27 route10_nurse: the crossing onto Route 10 not held up", test_route10_crossing_not_held_up}, + .expectation = {}, + }, + { + .input = {"00.27 route10_nurse: the leg down column 16 a tile short", test_route10_column_16_short}, + .expectation = {}, + }, }; void add_tests_AutoStoryCeladon(UnitTestDatabase& database){ From 2e2cebc189aa2fe2ad16bce92bce177413c41541 Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 9 Nov 2026 02:30:34 -0800 Subject: [PATCH 11/13] FRLG: sim test for a wild battle seen after an open leg's hold Console B, 00.27 run 2: on the open leg "east 11" across Route 10 wild battles showed only after the hold, and the walker took the player to be anywhere on the leg ("stopped 0 to 11 tiles in"). The sim test runs a battle on steps 3, 6 and 10 of an open leg through Route 10's grass and asks for the player bounded to 3 tiles around the battle's. Red: the walker logs "stopped 0 to 11 tiles in" for each. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013VUubhHeYSMGSSDXZkKZss --- .../PokemonFRLG_OverworldLateBattle.h | 33 ++++ .../PokemonFRLG_OverworldLateBattle_Tests.cpp | 167 ++++++++++++++++++ .../PokemonFRLG/PokemonFRLG_Tests.cpp | 2 + SerialPrograms/cmake/SourceFiles.cmake | 2 + 4 files changed, 204 insertions(+) create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.h create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle_Tests.cpp diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.h new file mode 100644 index 0000000000..08ef00c481 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.h @@ -0,0 +1,33 @@ +/* Overworld Late Battle + * + * From: https://github.com/PokemonAutomation/ + * + * Where a wild battle seen only after an open leg's hold stopped the + * player. The walker records the leg's screens as timed events + * (Pokemon::ScreenEventLog) through the hold and the watch after it. The + * shared classifier (Pokemon::classify_interrupt()) says what stopped the + * player, and a wild battle is placed by when its first timed event + * showed, not by when the watcher that cleared it fired: that is after the + * hold, and the leg could have stopped anywhere along it. + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_OverworldLateBattle_H +#define PokemonAutomation_PokemonFRLG_OverworldLateBattle_H + +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +// Registers the late battle unit tests, on the sim. +void add_tests_OverworldLateBattle(UnitTestDatabase& database); + + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation +#endif diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle_Tests.cpp new file mode 100644 index 0000000000..768af93a2d --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle_Tests.cpp @@ -0,0 +1,167 @@ +/* Overworld Late Battle Tests + * + * From: https://github.com/PokemonAutomation/ + * + * A wild battle seen only after an open leg's hold, on the sim: the walker + * must place the player within a few tiles of where the battle stopped + * them, find them again and finish the route. + * + */ + +#include +#include +#include +#include +#include +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/TestRunners/UnitTest.h" +#include "PokemonFRLG_OverworldLateBattle.h" +#include "PokemonFRLG_OverworldMaps.h" +#include "PokemonFRLG_OverworldReanchor.h" +#include "PokemonFRLG_OverworldSim.h" +#include "PokemonFRLG_OverworldWalk.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +namespace{ + +using namespace std::chrono_literals; + +struct LateBattleInput{ + std::string name; + // The leg's step (from 1) the battle starts on. + int battle_step = 1; +}; + +struct LateBattleExpectation{ + // The most the walker's bound on the player's tile may span, in tiles. + int max_spread = 0; +}; + +struct LateBattleCase{ + LateBattleInput input; + LateBattleExpectation expectation; +}; + +const std::vector& maps(){ + static const std::vector MAPS = overworld_maps(); + return MAPS; +} + +// The bound run_route() logged for the stop: " stopped to +// tiles in; ...". +std::optional logged_bound(const std::vector& log){ + for (const std::string& line : log){ + const size_t at = line.find(" stopped "); + WalkedTiles bound; + if (at != std::string::npos && std::sscanf(line.c_str() + at, " stopped %d to %d tiles in", &bound.min, &bound.max) == 2){ + return bound; + } + } + return std::nullopt; +} + +std::string describe(const std::string& failure, const SimState& state, const std::vector& log){ + std::string out = std::format("\n ended on {}, failure: \"{}\"", to_string(state.position), failure); + for (const std::string& line : log){ + out += "\n " + line; + } + for (const std::string& line : state.log){ + out += "\n " + line; + } + return out; +} + +// Console B, FRLG 00.27 run 2, 2026-11-09 (~/FRLGLab/captures/ +// 2026-11-09-0027-route10-legs.log): on the open leg "east 11" across +// Route 10, wild battles (Voltorb) showed only after the hold. The walker +// took the player to be anywhere on the leg ("stopped 0 to 11 tiles in") +// and walked "east 12 (to wall)" to find them, into a second battle. Here +// the leg crosses Route 10's grass on row 10, the battle's flashes are +// missed, its text box ("Wild appeared!") shows +// LATE_WILD_BATTLE_DELAY after its step and its HP boxes 800 ms after +// that, as in Mt. Moon (console B, 2026-11-03, row 00.13). After running, +// the walker must bound the player to a few tiles around the battle's. +class Test_OverworldLateBattle : public UnitTest{ +public: + explicit Test_OverworldLateBattle(LateBattleCase test_case) + : UnitTest("PokemonFRLG::OverworldLateBattle - " + test_case.input.name) + , m_case(std::move(test_case)) + {} + virtual UnitTestResult run(Logger&, CancellableScope&) const override{ + try{ + return run_case(); + }catch (Exception& e){ + return std::string(e.name()) + ": " + e.message(); + } + } + +private: + UnitTestResult run_case() const{ + const MapPosition start{"Route10", {5, 10}}; + const MapPosition goal{"Route10", {16, 10}}; + RouteStep east; + east.direction = Direction::EAST; + east.tiles = 11; + east.wild = true; + east.facing_after = Direction::EAST; + east.end = goal; + SimScript script; + script.encounters = {{.grass_step = m_case.input.battle_step, .seen_late = true}}; + script.timing.battle_opening = OverworldWalkSteps::LATE_WILD_BATTLE_DELAY; + script.timing.battle_screen = OverworldWalkSteps::LATE_WILD_BATTLE_DELAY + 800ms; + OverworldSim sim(maps(), start, Direction::EAST, script); + OverworldSimWalker walker(sim); + const RoutePlanning planning{.maps = &maps(), .costs = default_route_costs(), .start = start, .label = "Sim"}; + const std::string failure = walker.run({east}, planning); + const std::string seen = describe(failure, sim.state(), walker.log()); + if (!failure.empty() || !(sim.state().position == goal) || !sim.state().violations.empty()){ + return "Expected the battle run from and " + to_string(goal) + " reached:" + seen; + } + const std::optional bound = logged_bound(walker.log()); + const int battle = m_case.input.battle_step; + if (!bound || bound->min > battle || bound->max < battle || bound->max - bound->min > m_case.expectation.max_spread){ + return std::format( + "Expected the player placed within {} tiles around tile {} of the leg, got {}:{}", + m_case.expectation.max_spread, battle, + bound ? std::format("{} to {}", bound->min, bound->max) : std::string("no bound"), seen + ); + } + return true; + } + + LateBattleCase m_case; +}; + +} // namespace + + +const LateBattleCase LATE_BATTLE_CASES[] = { + { + .input = {.name = "a battle on step 3 of an open leg, seen after the hold", .battle_step = 3}, + .expectation = {.max_spread = 2}, + }, + { + .input = {.name = "a battle on step 6 of an open leg, seen after the hold", .battle_step = 6}, + .expectation = {.max_spread = 2}, + }, + { + .input = {.name = "a battle on step 10, its HP boxes after the leg's watch", .battle_step = 10}, + .expectation = {.max_spread = 2}, + }, +}; + +void add_tests_OverworldLateBattle(UnitTestDatabase& database){ + for (const LateBattleCase& item : LATE_BATTLE_CASES){ + database.add(item); + } +} + + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp index 2bf7183092..26fcbe3e01 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp @@ -40,6 +40,7 @@ #include "Overworld/PokemonFRLG_OverworldReanchor.h" #include "Overworld/PokemonFRLG_OverworldRecovery.h" #include "Overworld/PokemonFRLG_OverworldRoute.h" +#include "Overworld/PokemonFRLG_OverworldLateBattle.h" #include "Overworld/PokemonFRLG_OverworldSim.h" #include "Overworld/PokemonFRLG_OverworldWalk.h" #include "Overworld/PokemonFRLG_PlayerFacingReader.h" @@ -932,6 +933,7 @@ void add_tests(UnitTestDatabase& database){ add_tests_OverworldReanchor(database); add_tests_OverworldRecovery(database); add_tests_OverworldSim(database); + add_tests_OverworldLateBattle(database); add_tests_PlayerFacingReader(database); add_tests_AutoStory(database); add_tests_AutoStoryJson(database); diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 93848f52d7..035e9bef6e 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1841,6 +1841,8 @@ file(GLOB LIBRARY_SOURCES Source/VideoGames/PokemonFRLG/Inference/Sounds/PokemonFRLG_CatchFanfareDetector.h Source/VideoGames/PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.cpp Source/VideoGames/PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.h + Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.h + Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle_Tests.cpp Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMap.cpp Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMap.h Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMaps.cpp From 0e732b41b95ae229b0752ef29aeb05bcae6892a0 Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 9 Nov 2026 02:40:28 -0800 Subject: [PATCH 12/13] FRLG AutoStory: 00.27 finds its place on walls on the way to Route 10's Center route10_nurse planned one walk from Route 9 (70, 8) to the nurse, and its open legs ended a tile from blockers: on console B (2026-11-09) the crossing onto Route 10 wasn't held up and the leg down column 17 met the fence post (17, 12) (run 3), and the leg down column 16 ended a tile short so west 3 met the Center's wall (15, 20) (run 2). Both "no fade" at the door. The map is right. The walk is now three steps: a route to the wall (17, 11) at the end of row 11, which takes up the crossing's tile; legs down column 16 to row 23, up to the Center's wall (15, 21) and west along row 21 to the wall (10, 21); and the route from three east of there onto the door. The red tests pass, and a new sim test starts each leg a tile off after every open leg of two tiles or more. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013VUubhHeYSMGSSDXZkKZss --- .../PokemonFRLG_AutoStoryCeladon_Tests.cpp | 114 ++++++++++++++---- .../PokemonFRLG_AutoStoryTableCeladon.cpp | 15 ++- 2 files changed, 103 insertions(+), 26 deletions(-) diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp index 4a72b6cd3d..853b4e4af7 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryCeladon_Tests.cpp @@ -278,39 +278,54 @@ UnitTestResult test_route10_save_place(){ return true; } -// 00.27's step route10_nurse on the sim from Route 9 (70, 8): east onto -// Route 10, round its grass along row 11, down column 16 past the ledge -// (2..14, 12) and the fence post (17, 12), west along row 21 and north -// onto the Center's door (13, 20). Every leg but the door's is open -// (timed), and each ends a tile from something that sends the next leg -// elsewhere. Console B, 2026-11-09, both runs "no fade in and out": +// 00.27's walk from Route 9 (70, 8) to Route 10's nurse: its steps from +// route10_ledge_end to route10_nurse. Console B, 2026-11-09, the walk +// then planned in one ended its open legs a tile from a blocker, and both +// runs ended "no fade in and out" at the Center's door (13, 20): // - run 3: the crossing onto Route 10 wasn't held up, so the player // stood a tile further east, row 11 ended on (17, 11), the leg down -// column 17 met the post (17, 12), and west 3 and north 1 ended in the -// grass on (14, 10) (the end frame). -// - run 2: the leg down column 16 ended on (16, 20), west 3 met the -// Center's east wall (15, 20) and north 1 ended on (16, 19) beside it. -// Either way the door should be reached. -std::string route10_nurse_on_sim(const SimScript& script){ +// column 17 met the fence post (17, 12), and the walk ended in the +// grass on (14, 10). +// - run 2: the leg down column 16 ended a tile short, on (16, 20), west +// 3 met the Center's east wall (15, 20) and north 1 ended on (16, 19). +std::vector route10_walk(){ const StoryCheckpoint& row = checkpoint("00.27"); - const auto step = std::find_if(row.steps.begin(), row.steps.end(), [](const StoryStep& data){ return data.name == "route10_nurse"; }); - if (step == row.steps.end()){ - return "00.27: no step route10_nurse."; + const auto named = [&row](std::string_view name){ + return std::find_if(row.steps.begin(), row.steps.end(), [&](const StoryStep& data){ return data.name == name; }); + }; + const auto first = named("route10_ledge_end"); + const auto last = named("route10_nurse"); + if (first == row.steps.end() || last == row.steps.end() || last < first){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "00.27: no steps route10_ledge_end to route10_nurse."); + } + return std::vector(first, last + 1); +} + +const StoryPose ROUTE10_WALK_START{StoryTable::ROUTE9, {70, 8}, Direction::EAST}; + +// `steps` from `first` on, on `sim` from `at`, where the table says the +// player stands: the failure, or empty once at Route 10's nurse. +std::string route10_walk_rest(OverworldSim& sim, StoryPose at, const std::vector& steps, size_t first){ + std::string failure; + for (size_t c = first; c < steps.size() && failure.empty(); c++){ + failure = row_step_on_sim(sim, at, steps[c], "00.27"); } - StoryPose at{StoryTable::ROUTE9, {70, 8}, Direction::EAST}; - OverworldSim sim(maps_after_surge(), MapPosition{at.map, at.tile}, Direction::EAST, script); - const std::string failure = row_step_on_sim(sim, at, *step, row.id); const MapPosition nurse{StoryTable::ROUTE10_CENTER, {7, 4}}; - if (!failure.empty() || !(sim.state().position == nurse) || sim.state().warps != 1){ - return std::format("00.27/route10_nurse: ended on {}, not Route 10's nurse; {}", to_string(sim.state().position), failure); + if (!failure.empty() || !(sim.state().position == nurse)){ + return std::format("ended on {}, not Route 10's nurse; {}", to_string(sim.state().position), failure); } return ""; } +std::string route10_walk_on_sim(const SimScript& script){ + OverworldSim sim(maps_after_surge(), MapPosition{ROUTE10_WALK_START.map, ROUTE10_WALK_START.tile}, Direction::EAST, script); + return route10_walk_rest(sim, ROUTE10_WALK_START, route10_walk(), 0); +} + UnitTestResult test_route10_crossing_not_held_up(){ SimScript script; script.connections_stall = false; - if (const std::string failure = route10_nurse_on_sim(script); !failure.empty()){ + if (const std::string failure = route10_walk_on_sim(script); !failure.empty()){ return "The crossing onto Route 10 not held up (run 3): " + failure; } return true; @@ -318,14 +333,61 @@ UnitTestResult test_route10_crossing_not_held_up(){ UnitTestResult test_route10_column_16_short(){ SimScript script; - // Holds: east 7, south 3, east 11, south 10 down column 16. - script.short_holds[4] = 1; - if (const std::string failure = route10_nurse_on_sim(script); !failure.empty()){ + // Holds: east 7, south 3 and east 12 onto row 11; west 1; south 12 + // down column 16. + script.short_holds[5] = 1; + if (const std::string failure = route10_walk_on_sim(script); !failure.empty()){ return "The leg down column 16 a tile short (run 2): " + failure; } return true; } +// Where an open leg of two tiles or more ends a tile short or long, the +// next leg starts a tile off. From each such start (on an open tile), the +// rest of the walk still reaches the nurse. A leg to a wall ends on its +// tile, and a one-tile leg is one step (as grass_held() takes it), so the +// leg after either starts where planned. The one exception is the door's +// column: the three east from the wall (9, 21) set it, and nothing after +// them can. +UnitTestResult test_route10_tile_off_after_open_legs(){ + const std::vector steps = route10_walk(); + const std::vector& maps = story_maps(); + // above_caitlin_line ends east 6 on (70, 8), an open leg. + RouteStep before{.direction = Direction::EAST, .tiles = 6}; + StoryPose at = ROUTE10_WALK_START; + for (size_t s = 0; s < steps.size(); s++){ + const std::vector route = walked_route(at, steps[s]).value_or(std::vector{}); + MapPosition from{at.map, at.tile}; + for (size_t c = 0; c < route.size(); c++){ + const bool door_column = steps[s].name == "route10_nurse" && c == 0; + const bool off = before.action == RouteAction::WALK && !before.to_wall && !before.warps && before.tiles > 1 && !door_column; + const TilePos on = from.point.next(before.direction); + const TilePos short_of{2 * from.point.x - on.x, 2 * from.point.y - on.y}; + const auto map = std::find_if(maps.begin(), maps.end(), [&](const MapData& data){ return data.name == from.map; }); + for (const TilePos& start : off ? std::vector{short_of, on} : std::vector{}){ + const bool open = map != maps.end() && in_bounds(*map, start) && object_at(*map, start) == nullptr + && (tile_at(*map, start) == TileKind::OPEN || tile_at(*map, start) == TileKind::TALL_GRASS); + if (!open){ + continue; + } + OverworldSim sim(maps_after_surge(), MapPosition{from.map, start}, before.direction); + const std::vector rest(route.begin() + c, route.end()); + const RoutePlanning planning{.maps = &maps, .costs = story_route_costs(), .start = from, .label = steps[s].name}; + std::string failure = OverworldSimWalker(sim).run(rest, planning); + const StoryPose next{route.back().end.map, route.back().end.point, route.back().facing_after}; + failure = failure.empty() ? route10_walk_rest(sim, next, steps, s + 1) : failure; + if (!failure.empty()){ + return std::format("{} leg {} ({}) from {}, not {}: {}", steps[s].name, c + 1, to_string(route[c]), to_string(start), to_string(from.point), failure); + } + } + before = route[c]; + from = route[c].end; + } + at = route.empty() ? at : StoryPose{route.back().end.map, route.back().end.point, route.back().facing_after}; + } + return true; +} + } // namespace @@ -362,6 +424,10 @@ const AutoStoryCeladonCase AUTO_STORY_CELADON_CASES[] = { .input = {"00.27 route10_nurse: the leg down column 16 a tile short", test_route10_column_16_short}, .expectation = {}, }, + { + .input = {"00.27 route10_nurse: a tile off after each open leg", test_route10_tile_off_after_open_legs}, + .expectation = {}, + }, }; void add_tests_AutoStoryCeladon(UnitTestDatabase& database){ diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp index 3b9555e38e..6406c1e857 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryTableCeladon.cpp @@ -119,7 +119,16 @@ StoryCheckpoint bike_voucher(){ // - Route 10 (Route10 map.json) joins Route 9's rows 8..10 by its own; // its north part, down to the Center's door (13, 20), has no trainer // on the way and its grass is walked round. The trees west of the -// door (10, 20..26) stay. +// door (10, 20..26) stay. The walk there ends its open legs where a +// tile of error can't reach a blocker, and finds its place again on +// walls (console B, 2026-11-09, two runs "no fade" at the door): the +// step onto Route 10 is not always held up, so the player may stand a +// tile further east, and the leg along row 11 holds to the wall +// (17, 11) instead of ending on (16, 11) by count. The 12 tiles down +// column 16 may end a tile short or long: from rows 22..29 a tile west +// and up to the Center's wall (15, 20) all end on (15, 21). West along +// row 21 to the wall (9, 21) ends on (10, 21), three west of the +// door's column: the only count no wall after it checks. // - The nurse, and the save in Route 10's Center: the save card names // ROUTE 10. StoryCheckpoint route10_center(){ @@ -142,7 +151,9 @@ StoryCheckpoint route10_center(){ step_onto("above_east_ledge", SOUTH, at(ROUTE9, 64, 3, SOUTH), "onto (64, 3), above the ledge (64, 4)"), hop("east_ledge_hop", SOUTH, at(ROUTE9, 64, 5, SOUTH), "down the ledge (64, 4)"), running(legs("above_caitlin_line", {{SOUTH, 3}, {EAST, 6}}, "down column 64 to row 8, east along it to (70, 8): above Caitlin's line (61..64, 9)")), - running(route("route10_nurse", at(ROUTE10_CENTER, 7, 4, NORTH), "east onto Route 10, round its grass and down to the Center's door (13, 20), to the nurse")), + running(route("route10_ledge_end", at(ROUTE10, 17, 11, EAST), "east onto Route 10, down to row 11 west of the grass and east along it to the wall (17, 11), above the fence post (17, 12)")), + running(legs("below_center_door", {{WEST, 1}, {SOUTH, 12}, {WEST, 1}, {NORTH, 2}, {WEST, 5}, {EAST, 3}}, "into the ledge's gap (16, 11), down column 16 to row 23, a tile west, up to the Center's wall (15, 21), west along row 21 to the wall (10, 21), east to (13, 21), below the door")), + running(route("route10_nurse", at(ROUTE10_CENTER, 7, 4, NORTH), "up onto the Center's door (13, 20), to the nurse")), heal("heal_route10"), }); } From 3d629512975054b1a434baa5f43693077416e7be Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 9 Nov 2026 02:41:16 -0800 Subject: [PATCH 13/13] FRLG: place a wild battle seen after an open leg's hold by its screen events An open leg's screens are now recorded as timed events (the shared ScreenEventLog, with FRLG's screen observer) through its hold and the watch after it. When something is seen only after the hold, the shared classifier says what it was; a wild encounter is placed on the tile its step reached, from when its text box rose less LATE_WILD_BATTLE_DELAY, which is what that delay was measured on. The leg then stops 3 tiles wide walking (5 running) instead of anywhere along it, and the player is found again from there. The sim records its screens the same way, behind the rule place_late_battle; story walks give the walker the observer. Sim test: green for battles on steps 3, 6 and 10 of the leg; with the rule off it still logs the console's "stopped 0 to 11 tiles in". Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013VUubhHeYSMGSSDXZkKZss --- .../PokemonFRLG_OverworldLateBattle.cpp | 68 +++++++++++++++ .../PokemonFRLG_OverworldLateBattle.h | 44 +++++++++- .../PokemonFRLG_OverworldLateBattle_Tests.cpp | 81 ++++++++++++------ .../PokemonFRLG_OverworldRecoveryWalk.cpp | 83 +++++++++++++++++-- .../Overworld/PokemonFRLG_OverworldSim.cpp | 79 ++++++++++++++++-- .../Overworld/PokemonFRLG_OverworldSim.h | 31 +++++++ .../PokemonFRLG_OverworldSim_Tests.cpp | 2 + .../Overworld/PokemonFRLG_OverworldWalk.h | 7 ++ .../PokemonFRLG/PokemonFRLG_Tests.cpp | 2 - .../AutoStory/PokemonFRLG_AutoStoryEvents.cpp | 16 +++- SerialPrograms/cmake/SourceFiles.cmake | 1 + 11 files changed, 368 insertions(+), 46 deletions(-) create mode 100644 SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.cpp diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.cpp new file mode 100644 index 0000000000..976d1a0c85 --- /dev/null +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.cpp @@ -0,0 +1,68 @@ +/* Overworld Late Battle + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include "PokemonFRLG_OverworldWalk.h" +#include "PokemonFRLG_OverworldLateBattle.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +const std::vector& wild_battle_lags(){ + static const std::vector LAGS{ + {Pokemon::ScreenEvent::BATTLE_STARTED, OverworldWalkSteps::LATE_WILD_BATTLE_DELAY}, + }; + return LAGS; +} + +int tile_reached(Milliseconds since_hold, Milliseconds tile_time, int tiles){ + if (tile_time.count() <= 0){ + return tiles; + } + if (since_hold.count() < 0){ + return 0; + } + const int begun = (int)((since_hold + tile_time / 2) / tile_time) + 1; + return std::clamp(begun, 0, tiles); +} + +LateStop place_late_stop( + const RouteStep& step, Milliseconds tile_time, + const Pokemon::ScreenEventLog& log, WallClock hold_start, WallClock now +){ + LateStop out; + out.verdict = Pokemon::classify_interrupt(log, {hold_start, now}, {}); + if (out.verdict.kind != Pokemon::InterruptKind::WILD_ENCOUNTER){ + return out; + } + const std::optional pressed = Pokemon::interrupt_step_time(out.verdict, wild_battle_lags()); + if (!pressed){ + return out; + } + const Milliseconds since_hold = std::chrono::duration_cast(*pressed - hold_start); + out.walked = walked_near(step, tile_reached(since_hold, tile_time, step.tiles), tile_time); + return out; +} + +std::string late_stop_line(const RouteStep& step, const LateStop& stop, WallClock hold_start){ + std::string out = std::format( + "Route: seen after the hold of {}: {}", + to_string(step), Pokemon::describe(stop.verdict, hold_start) + ); + if (stop.walked){ + return out + std::format("; {} to {} tiles in.", stop.walked->min, stop.walked->max); + } + return out + "; not placed."; +} + + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.h index 08ef00c481..b2e7634965 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.h @@ -15,14 +15,56 @@ #ifndef PokemonAutomation_PokemonFRLG_OverworldLateBattle_H #define PokemonAutomation_PokemonFRLG_OverworldLateBattle_H +#include +#include +#include +#include "Common/Cpp/Time.h" #include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Pokemon/AutoStory/Pokemon_Interrupts.h" +#include "Pokemon/AutoStory/Pokemon_ScreenEvents.h" +#include "PokemonFRLG_OverworldReanchor.h" +#include "PokemonFRLG_OverworldRoute.h" namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ -// Registers the late battle unit tests, on the sim. +// The events of a wild battle FRLG has timed from its step: its text box +// ("Wild appeared!", BATTLE_STARTED), LATE_WILD_BATTLE_DELAY +// after the step on Route 1 (console B, 2026-11-01, run 7). The flashes +// are what the walker missed, and the wipe isn't timed on the console yet. +const std::vector& wild_battle_lags(); + +// The tile of a leg (from 1) that a step begun `since_hold` into its hold +// reaches: the steps begin a tile apart from the hold's start. At most +// `tiles`. +int tile_reached(Milliseconds since_hold, Milliseconds tile_time, int tiles); + +// What stopped an open leg after its hold, and where. +struct LateStop{ + Pokemon::InterruptVerdict verdict; + // The tiles of the leg the player may be on. Set for a wild battle + // whose step the log times. + std::optional walked; +}; + +// For `step`, an open leg held from `hold_start` at `tile_time` a tile, +// after which something was seen and cleared by `now`: what `log` shows +// stopped the player there, and for a wild battle, the tile its step +// reached (tile_reached()), give or take walked_near()'s slack. +LateStop place_late_stop( + const RouteStep& step, Milliseconds tile_time, + const Pokemon::ScreenEventLog& log, WallClock hold_start, WallClock now +); + +// "Route: seen after the hold of : wild encounter: +5035 ms battle +// started rose; 5 to 7 tiles in.", times from `hold_start`. +std::string late_stop_line(const RouteStep& step, const LateStop& stop, WallClock hold_start); + + +// Registers the late battle unit tests, on the sim. add_tests_OverworldSim() +// calls it. void add_tests_OverworldLateBattle(UnitTestDatabase& database); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle_Tests.cpp index 768af93a2d..d6b6b4e46b 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle_Tests.cpp @@ -64,17 +64,54 @@ std::optional logged_bound(const std::vector& log){ return std::nullopt; } -std::string describe(const std::string& failure, const SimState& state, const std::vector& log){ - std::string out = std::format("\n ended on {}, failure: \"{}\"", to_string(state.position), failure); - for (const std::string& line : log){ +// A walk on the sim: how it ended, and what the walker and the sim logged. +struct LateBattleRun{ + std::string failure; + SimState state; + std::vector log; +}; + +std::string describe(const LateBattleRun& run){ + std::string out = std::format("\n ended on {}, failure: \"{}\"", to_string(run.state.position), run.failure); + for (const std::string& line : run.log){ out += "\n " + line; } - for (const std::string& line : state.log){ + for (const std::string& line : run.state.log){ out += "\n " + line; } return out; } +const MapPosition LEG_START{"Route10", {5, 10}}; +const MapPosition LEG_END{"Route10", {16, 10}}; + +// "east 11" from LEG_START through Route 10's grass, with a battle on +// `battle_step` whose flashes are missed. +LateBattleRun walk_with_battle(int battle_step, SimWalkerRules rules){ + RouteStep east; + east.direction = Direction::EAST; + east.tiles = 11; + east.wild = true; + east.facing_after = Direction::EAST; + east.end = LEG_END; + SimScript script; + script.encounters = {{.grass_step = battle_step, .seen_late = true}}; + script.timing.battle_opening = OverworldWalkSteps::LATE_WILD_BATTLE_DELAY; + script.timing.battle_screen = OverworldWalkSteps::LATE_WILD_BATTLE_DELAY + 800ms; + OverworldSim sim(maps(), LEG_START, Direction::EAST, script); + OverworldSimWalker walker(sim, rules); + const RoutePlanning planning{.maps = &maps(), .costs = default_route_costs(), .start = LEG_START, .label = "Sim"}; + LateBattleRun run; + run.failure = walker.run({east}, planning); + run.state = sim.state(); + run.log = walker.log(); + return run; +} + +std::string bound_text(const std::optional& bound){ + return bound ? std::format("{} to {}", bound->min, bound->max) : std::string("no bound"); +} + // Console B, FRLG 00.27 run 2, 2026-11-09 (~/FRLGLab/captures/ // 2026-11-09-0027-route10-legs.log): on the open leg "east 11" across // Route 10, wild battles (Voltorb) showed only after the hold. The walker @@ -101,35 +138,25 @@ class Test_OverworldLateBattle : public UnitTest{ private: UnitTestResult run_case() const{ - const MapPosition start{"Route10", {5, 10}}; - const MapPosition goal{"Route10", {16, 10}}; - RouteStep east; - east.direction = Direction::EAST; - east.tiles = 11; - east.wild = true; - east.facing_after = Direction::EAST; - east.end = goal; - SimScript script; - script.encounters = {{.grass_step = m_case.input.battle_step, .seen_late = true}}; - script.timing.battle_opening = OverworldWalkSteps::LATE_WILD_BATTLE_DELAY; - script.timing.battle_screen = OverworldWalkSteps::LATE_WILD_BATTLE_DELAY + 800ms; - OverworldSim sim(maps(), start, Direction::EAST, script); - OverworldSimWalker walker(sim); - const RoutePlanning planning{.maps = &maps(), .costs = default_route_costs(), .start = start, .label = "Sim"}; - const std::string failure = walker.run({east}, planning); - const std::string seen = describe(failure, sim.state(), walker.log()); - if (!failure.empty() || !(sim.state().position == goal) || !sim.state().violations.empty()){ - return "Expected the battle run from and " + to_string(goal) + " reached:" + seen; - } - const std::optional bound = logged_bound(walker.log()); const int battle = m_case.input.battle_step; + const LateBattleRun now = walk_with_battle(battle, {}); + if (!now.failure.empty() || !(now.state.position == LEG_END) || !now.state.violations.empty()){ + return "Expected the battle run from and " + to_string(LEG_END) + " reached:" + describe(now); + } + const std::optional bound = logged_bound(now.log); if (!bound || bound->min > battle || bound->max < battle || bound->max - bound->min > m_case.expectation.max_spread){ return std::format( "Expected the player placed within {} tiles around tile {} of the leg, got {}:{}", - m_case.expectation.max_spread, battle, - bound ? std::format("{} to {}", bound->min, bound->max) : std::string("no bound"), seen + m_case.expectation.max_spread, battle, bound_text(bound), describe(now) ); } + SimWalkerRules unplaced; + unplaced.place_late_battle = false; + const LateBattleRun old = walk_with_battle(battle, unplaced); + const std::optional whole = logged_bound(old.log); + if (!whole || whole->min != 0 || whole->max != 11){ + return "Expected the whole leg, 0 to 11 tiles, without the rule, as on the console, got " + bound_text(whole) + ":" + describe(old); + } return true; } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldRecoveryWalk.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldRecoveryWalk.cpp index b6953fedbe..8aac65097e 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldRecoveryWalk.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldRecoveryWalk.cpp @@ -6,14 +6,18 @@ #include #include +#include +#include #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/ImageTools/ImageDiff.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/Async/InferenceSession.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "VideoGames/PokemonFRLG/PokemonFRLG_Settings.h" +#include "PokemonFRLG_OverworldLateBattle.h" #include "PokemonFRLG_OverworldRecoveryWalk.h" namespace PokemonAutomation{ @@ -41,13 +45,20 @@ Pokemon::LegSpec leg_spec(const RouteStep& step, MoveSpeed speed){ return leg; } -// One step of a route, for a player facing `facing`. If an open leg -// stopped part-way, returns how far the timing puts the player along it. -std::optional walk_step( +// How one step's walk went: if an open leg stopped part-way, how far the +// timing puts the player along it; and when its hold started. +struct StepWalk{ + std::optional stopped; + WallClock hold_start; +}; + +// One step of a route, for a player facing `facing`. +StepWalk walk_step( ConsoleHandle& console, ProControllerContext& context, const RouteStep& step, std::optional facing, const OverworldInterruptSet& interrupts, MoveSpeed speed ){ + StepWalk out{std::nullopt, current_time()}; switch (step.action){ case RouteAction::WALK:{ // Turning first, with a tap, makes the hold start from a standstill @@ -59,19 +70,23 @@ std::optional walk_step( if (const std::string note = speed_note(step, speed); !note.empty()){ console.log(note); } + context.wait_for_all_requests(); + out.hold_start = current_time(); const Pokemon::LegResult result = Pokemon::walk_leg( console, context, leg_mover(step, speed), step.wild ? interrupts.wild : interrupts.other, leg_spec(step, speed), MAX_INTERRUPTIONS_PER_LEG ); if (result.stopped){ - return walked_near(step, result.tiles, tile_time(step, speed)); + out.stopped = walked_near(step, result.tiles, tile_time(step, speed)); + return out; } if (step.warps){ wait_out_warp(console, context, step); static_assert(AFTER_WARP_BATTLE_WATCH >= AFTER_WARP); if (run_from_battle_after_warp(console, context)){ console.log("Route: a wild battle, not the warp to " + step.end.map + "; the player is on " + to_string(step) + " before the warp.", COLOR_ORANGE); - return walked_before_warp(step); + out.stopped = walked_before_warp(step); + return out; } } break; @@ -83,9 +98,54 @@ std::optional walk_step( pbf_press_dpad(context, dpad(step.direction), FACE_HOLD, STEP_RELEASE); break; } - return std::nullopt; + return out; } +// An open leg's screens, recorded with the interrupts' observer from +// before its hold until the watch after it ends, alongside the walker's +// own watchers. Records nothing for other steps, or with no observer. +class LegRecorder{ +public: + LegRecorder( + ConsoleHandle& console, ProControllerContext& context, + const OverworldInterruptSet& interrupts, const RouteStep& step + ) + : m_observer(records(interrupts, step) ? interrupts.make_observer() : nullptr) + , m_watcher(m_observer ? std::make_unique(*m_observer) : nullptr) + , m_session(context, console, callbacks()) + {} + + // Where what was seen after `step`'s hold, from `hold_start`, stopped + // the player (place_late_stop()); nothing if not recorded or not placed. + std::optional place( + ConsoleHandle& console, const RouteStep& step, Milliseconds tile_time, WallClock hold_start + ) const{ + if (!m_watcher){ + return std::nullopt; + } + const LateStop stop = place_late_stop(step, tile_time, m_watcher->log(), hold_start, current_time()); + console.log(late_stop_line(step, stop, hold_start), COLOR_ORANGE); + return stop.walked; + } + +private: + static bool records(const OverworldInterruptSet& interrupts, const RouteStep& step){ + const bool open_leg = step.action == RouteAction::WALK && !step.to_wall && !step.warps; + return open_leg && interrupts.make_observer; + } + std::vector callbacks() const{ + if (!m_watcher){ + return {}; + } + return {*m_watcher}; + } + + std::unique_ptr m_observer; + std::unique_ptr m_watcher; + // With no watcher it runs nothing. + InferenceSession m_session; +}; + // walk_step(), then a watch for what its last step set off. Not DONE if // the player may have stopped short of the step's end: an open leg // stopped, or something seen after the hold, which may have stopped it @@ -104,8 +164,10 @@ StepOutcome walk_and_watch( } clear_open_message(console, context, step.wild ? interrupts.wild : interrupts.other, step); check_overworld(console, context, step); - if (const std::optional walked = walk_step(console, context, step, facing, interrupts, speed)){ - return StepOutcome{.end = StepEnd::STOPPED, .walked = walked}; + const LegRecorder recorder(console, context, interrupts, step); + const StepWalk walked = walk_step(console, context, step, facing, interrupts, speed); + if (walked.stopped){ + return StepOutcome{.end = StepEnd::STOPPED, .walked = walked.stopped}; } if (step.action == RouteAction::FACE){ return StepOutcome{}; @@ -118,7 +180,10 @@ StepOutcome walk_and_watch( } // After a warp it was checked for already. const bool done = late == 0 || step.warps || step.action != RouteAction::WALK; - return StepOutcome{.end = done ? StepEnd::DONE : StepEnd::LATE, .walked = std::nullopt}; + if (done){ + return StepOutcome{}; + } + return StepOutcome{.end = StepEnd::LATE, .walked = recorder.place(console, step, tile_time(step, speed), walked.hold_start)}; } // Holds on along `step`, stopped part-way off the grass, until its warp's diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.cpp index 93fdda5aaf..3395eda537 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.cpp @@ -10,6 +10,7 @@ #include "Common/Cpp/Exceptions.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "Pokemon/Navigation/Pokemon_LegWalker.h" +#include "PokemonFRLG_OverworldLateBattle.h" #include "PokemonFRLG_OverworldReanchor.h" #include "PokemonFRLG_OverworldWalk.h" #include "PokemonFRLG_OverworldSim.h" @@ -44,6 +45,50 @@ const char* sim_screen_name(SimScreen screen){ } +WallClock sim_wall_clock(Milliseconds time){ + return WallClock() + time; +} + +namespace{ + +Pokemon::ScreenEvents observed_events(SimScreen screen){ + switch (screen){ + case SimScreen::MESSAGE: + return {Pokemon::ScreenEvent::DIALOG_BOX}; + case SimScreen::BATTLE_WIPE: + case SimScreen::FADE: + return {Pokemon::ScreenEvent::FADE_BLACK}; + case SimScreen::BATTLE_OPENING: + case SimScreen::BATTLE: + return {Pokemon::ScreenEvent::BATTLE_STARTED}; + case SimScreen::OVERWORLD: + case SimScreen::START_MENU: + case SimScreen::BATTLE_FLASH: + break; + } + return {}; +} + +} // namespace + +Pokemon::ScreenEventLog screen_event_log(const SimState& state, Milliseconds from){ + Pokemon::ScreenEventLog log; + SimScreen screen = state.shown.empty() ? state.screen : state.shown.front().screen; + size_t next = 0; + for (; next < state.shown.size() && state.shown[next].at <= from; next++){ + screen = state.shown[next].screen; + } + Milliseconds at = from; + log.record(observed_events(screen), sim_wall_clock(at)); + for (; next < state.shown.size(); next++){ + at = std::max(at, state.shown[next].at); + log.record(observed_events(state.shown[next].screen), sim_wall_clock(at)); + } + log.record(observed_events(state.screen), sim_wall_clock(std::max(at, state.time))); + return log; +} + + namespace{ // A short press: TURN_TAP and TURN_SETTLE. @@ -105,7 +150,7 @@ OverworldSim::OverworldSim(std::vector maps, MapPosition start, Directi } m_player = PlayerState{start.point, elevation_at(map(), start.point), false}; m_state.facing = facing; - m_state.screen = m_script.start_menu_open ? SimScreen::START_MENU : SimScreen::OVERWORLD; + show(m_script.start_menu_open ? SimScreen::START_MENU : SimScreen::OVERWORLD, m_now); update_view(); } @@ -135,11 +180,17 @@ void OverworldSim::schedule(Milliseconds at, SimScreen screen, bool unfreeze){ void OverworldSim::advance_to(Milliseconds time){ while (!m_pending.empty() && m_pending.front().at <= time){ - m_state.screen = m_pending.front().screen; + show(m_pending.front().screen, m_pending.front().at); m_frozen = m_frozen && !m_pending.front().unfreeze; m_pending.erase(m_pending.begin()); } m_now = std::max(m_now, time); + m_state.time = m_now; +} + +void OverworldSim::show(SimScreen screen, Milliseconds at){ + m_state.screen = screen; + m_state.shown.push_back(SimShown{at, screen}); } @@ -453,7 +504,7 @@ SimBattleEnd OverworldSim::handle_battle(SimBattleChoice choice){ return SimBattleEnd::CANT_RUN; } advance_to(m_now + BATTLE_TIME); - m_state.screen = SimScreen::OVERWORLD; + show(SimScreen::OVERWORLD, m_now); m_frozen = false; m_battle_pending = false; if (m_trainer){ @@ -471,7 +522,7 @@ bool OverworldSim::close_message(){ return false; } advance_to(m_now + MESSAGE_CLOSE); - m_state.screen = SimScreen::OVERWORLD; + show(SimScreen::OVERWORLD, m_now); if (!m_trainer_speaking){ m_frozen = false; return false; @@ -656,6 +707,7 @@ std::optional OverworldSimWalker::walk_step(const RouteStep& step, calls.name = [&](size_t index){ return ints[index].name; }; calls.log = [&](const std::string& message){ m_log.push_back("Leg: " + message); }; calls.fail = [&](const std::string& message){ fail(message); }; + m_leg_start = m_sim.state().time; const Pokemon::LegResult result = Pokemon::walk_leg_with(leg, calls, MAX_INTERRUPTIONS_PER_LEG); if (result.stopped){ return walked_near(step, result.tiles, TILE_TIME); @@ -694,7 +746,24 @@ StepOutcome OverworldSimWalker::walk(const RouteStep& step, std::optional OverworldSimWalker::late_stop(const RouteStep& step){ + if (step.to_wall || !m_rules.place_late_battle){ + return std::nullopt; + } + const WallClock start = sim_wall_clock(m_leg_start); + const LateStop stop = place_late_stop( + step, TILE_TIME, screen_event_log(m_sim.state(), m_leg_start), start, sim_wall_clock(m_sim.state().time) + ); + m_log.push_back(late_stop_line(step, stop, start)); + return stop.walked; } // Whether the screen shows a battle on its way: its wipe's black, or its diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.h index b27b6f1057..9483c2e289 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim.h @@ -36,6 +36,7 @@ #include #include "Common/Cpp/Time.h" #include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Pokemon/AutoStory/Pokemon_ScreenEvents.h" #include "PokemonFRLG_OverworldRecovery.h" #include "PokemonFRLG_OverworldRoute.h" #include "PokemonFRLG_OverworldWalk.h" @@ -117,11 +118,21 @@ struct SimScript{ SimTiming timing; }; +// A screen the sim showed, and when, from its start. +struct SimShown{ + Milliseconds at{0}; + SimScreen screen = SimScreen::OVERWORLD; +}; + // The player and the screen, and what went wrong that the game would show. struct SimState{ MapPosition position; Direction facing = Direction::SOUTH; SimScreen screen = SimScreen::OVERWORLD; + // The sim's clock, from its start. + Milliseconds time{0}; + // Each screen as it showed, oldest first, from `screen` at the start. + std::vector shown; // Whether black shows below the map: the player stands near the bottom // of a map with nothing joined to it (a small room or a gate). bool black_below = false; @@ -136,6 +147,17 @@ struct SimState{ std::vector log; }; +// A time on the sim's clock as a WallClock, for the shared judges. +WallClock sim_wall_clock(Milliseconds time); + +// The screens `state` showed from `from` until its time, as the walker's +// screen observer (FrlgScreenObserver) reports them: a message's box, a +// battle's text box with or without its HP boxes, and the black of a +// battle's wipe or a warp's fade. A battle's flashes are no uniform +// screen, and black below a small room, which the observer's battle text +// box detector may take for a box, isn't modelled. +Pokemon::ScreenEventLog screen_event_log(const SimState& state, Milliseconds from); + // A watcher that fired: which (an index into the watchers asked for), and // how long after the hold or watch started. struct SimSeen{ @@ -208,6 +230,7 @@ class OverworldSim{ }; void advance_to(Milliseconds time); + void show(SimScreen screen, Milliseconds at); void schedule(Milliseconds at, SimScreen screen, bool unfreeze); bool sees(SimWatcher watcher, SimScreen screen) const; std::optional first_seen(SimWatcher watcher, Milliseconds from, Milliseconds to) const; @@ -300,6 +323,11 @@ struct SimWalkerRules{ // Cerulean City onto Nugget Bridge ended a tile short (console B, // 2026-11-03, rows 00.16 and 00.20). bool hold_over_connections = true; + // An open leg after whose hold a wild battle was seen is placed by the + // battle's first timed screen event (place_late_stop()). Without it the + // player may be anywhere on the leg: on Route 10, "east 11" was found + // again from all 12 of its tiles (console B, 2026-11-09, 00.27 run 2). + bool place_late_battle = true; }; // The console side of run_route() on an OverworldSim, as @@ -337,6 +365,7 @@ class OverworldSimWalker{ bool battle; }; StepOutcome walk(const RouteStep& step, std::optional facing); + std::optional late_stop(const RouteStep& step); std::optional walk_step(const RouteStep& step, std::optional facing); bool after_warp(const RouteStep& step, int warps_before); [[noreturn]] void fail_off_route(const RouteStep& step); @@ -358,6 +387,8 @@ class OverworldSimWalker{ std::vector m_wild; std::vector m_other; std::vector m_log; + // When the last WALK leg's first hold started, on the sim's clock. + Milliseconds m_leg_start{0}; }; diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim_Tests.cpp index 2f3b3db492..f8a2c4ca80 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldSim_Tests.cpp @@ -21,6 +21,7 @@ #include #include "Common/Cpp/Exceptions.h" #include "Common/Cpp/TestRunners/UnitTest.h" +#include "PokemonFRLG_OverworldLateBattle.h" #include "PokemonFRLG_OverworldMaps.h" #include "PokemonFRLG_OverworldSim.h" @@ -981,6 +982,7 @@ void add_tests_OverworldSim(UnitTestDatabase& database){ for (const auto& item : OVERWORLD_SIM_CASES){ database.add(item); } + add_tests_OverworldLateBattle(database); } diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldWalk.h b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldWalk.h index d399c6240d..e166617c2a 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldWalk.h +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldWalk.h @@ -17,11 +17,13 @@ #ifndef PokemonAutomation_PokemonFRLG_OverworldWalk_H #define PokemonAutomation_PokemonFRLG_OverworldWalk_H +#include #include #include #include #include #include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Pokemon/AutoStory/Pokemon_ScreenEvents.h" #include "Pokemon/Navigation/Pokemon_LegWalker.h" #include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" #include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h" @@ -46,6 +48,11 @@ using OverworldInterrupts = Pokemon::LegInterrupts()> make_observer = {}; }; // A wild battle, run from, and a message (e.g. "REPEL's effect wore off..."), diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp index 26fcbe3e01..2bf7183092 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp @@ -40,7 +40,6 @@ #include "Overworld/PokemonFRLG_OverworldReanchor.h" #include "Overworld/PokemonFRLG_OverworldRecovery.h" #include "Overworld/PokemonFRLG_OverworldRoute.h" -#include "Overworld/PokemonFRLG_OverworldLateBattle.h" #include "Overworld/PokemonFRLG_OverworldSim.h" #include "Overworld/PokemonFRLG_OverworldWalk.h" #include "Overworld/PokemonFRLG_PlayerFacingReader.h" @@ -933,7 +932,6 @@ void add_tests(UnitTestDatabase& database){ add_tests_OverworldReanchor(database); add_tests_OverworldRecovery(database); add_tests_OverworldSim(database); - add_tests_OverworldLateBattle(database); add_tests_PlayerFacingReader(database); add_tests_AutoStory(database); add_tests_AutoStoryJson(database); diff --git a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp index 83e6ac98f0..9e5ba1799b 100644 --- a/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp @@ -22,6 +22,7 @@ #include "PokemonFRLG_AutoStoryCapture.h" #include "PokemonFRLG_AutoStoryEvents.h" #include "PokemonFRLG_AutoStoryGrind.h" +#include "PokemonFRLG_AutoStoryScreenEvents.h" #include "PokemonFRLG_AutoStorySummary.h" #include "PokemonFRLG_AutoStoryTeachScreens.h" #include "PokemonFRLG_AutoStoryTrashCans.h" @@ -102,6 +103,17 @@ std::optional route_start(const Pokemon::Pose& pose){ return MapPosition{pose.map, tiles[0]}; } +// The story's interrupts for `step`'s walk, with FRLG's screen observer, +// which places a wild battle seen only after an open leg's hold +// (OverworldInterruptSet::make_observer). +OverworldInterruptSet walk_interrupts(StoryEnv& env, const StoryStep& step){ + OverworldInterruptSet out = story_interrupts(env.supplies, step.fight_wild, env.checks, env.language); + out.make_observer = []() -> std::unique_ptr{ + return std::make_unique(); + }; + return out; +} + // `maps`: where the route is planned; the story's but where a step knows // better (the trash cans' walks, past trainers who have walked up). Outcome walk_route(StoryEnv& env, RunState& state, const StoryStep& step, const std::vector& maps = story_maps()){ @@ -131,7 +143,7 @@ Outcome walk_route(StoryEnv& env, RunState& state, const StoryStep& step, const if (!start){ env.console.log("Story: " + step.name + ": on one of " + Pokemon::to_string(state.pose) + "; holds to walls first, to one tile."); } - const OverworldInterruptSet interrupts = story_interrupts(env.supplies, step.fight_wild, env.checks, env.language); + const OverworldInterruptSet interrupts = walk_interrupts(env, step); // Planning lets the walker recover a stopped open leg: it plans back to // the leg's end, then walks the rest as given. From a set of tiles // the start isn't known: the walker reads the facing. @@ -156,7 +168,7 @@ Outcome walk_legs(StoryEnv& env, RunState& state, const StoryStep& step){ if (!facing || !route || route->empty()){ return Pokemon::outcome_failed("bad_legs", to_string(start)); } - const OverworldInterruptSet interrupts = story_interrupts(env.supplies, step.fight_wild, env.checks, env.language); + const OverworldInterruptSet interrupts = walk_interrupts(env, step); // Planning lets the walker recover a stopped open leg: it plans back to // the leg's end, then walks the rest as given. const RoutePlanning planning{.maps = &story_maps(), .costs = story_route_costs(), .start = start, .label = step.name, .wall_check = wall_check(env)}; diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 035e9bef6e..469f62d1b1 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1841,6 +1841,7 @@ file(GLOB LIBRARY_SOURCES Source/VideoGames/PokemonFRLG/Inference/Sounds/PokemonFRLG_CatchFanfareDetector.h Source/VideoGames/PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.cpp Source/VideoGames/PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.h + Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.cpp Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle.h Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldLateBattle_Tests.cpp Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_OverworldMap.cpp