diff --git a/.github/workflows/bdsp-map-resources.yml b/.github/workflows/bdsp-map-resources.yml new file mode 100644 index 0000000000..644eecd648 --- /dev/null +++ b/.github/workflows/bdsp-map-resources.yml @@ -0,0 +1,14 @@ +name: BDSP map resources +on: + pull_request: + workflow_dispatch: +jobs: + integrity: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Prepare and check the approved BDSP resource pin + uses: dayfine/PokemonSimulation/integrations/arduino-source/verify-map-packs@4de7742a764d27293b4525d6ad2a6fc8a95f3e3a + with: + layout: resources + operation: prepare diff --git a/.github/workflows/cpp-ci-serial-programs-base.yml b/.github/workflows/cpp-ci-serial-programs-base.yml index 58f1a9d890..a87f526da4 100644 --- a/.github/workflows/cpp-ci-serial-programs-base.yml +++ b/.github/workflows/cpp-ci-serial-programs-base.yml @@ -88,6 +88,14 @@ jobs: ref: 'master' path: 'Arduino-Source/Packages' + - name: Prepare pinned BDSP map resources + id: map-resources + uses: dayfine/PokemonSimulation/integrations/arduino-source/verify-map-packs@4de7742a764d27293b4525d6ad2a6fc8a95f3e3a + with: + layout: resources + operation: prepare + consumer-directory: Arduino-Source + - name: Install Qt uses: jurplel/install-qt-action@v4 with: @@ -204,6 +212,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 new file mode 100644 index 0000000000..027383c60b --- /dev/null +++ b/.github/workflows/runtime-map-reader.yml @@ -0,0 +1,29 @@ +name: Runtime map reader +on: + pull_request: + workflow_dispatch: +jobs: + native-schema: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + - uses: actions/checkout@v7 + with: + repository: dayfine/PokemonSimulation + ref: 4de7742a764d27293b4525d6ad2a6fc8a95f3e3a + path: publisher + - uses: actions/setup-python@v6 + with: + python-version: '3.12' + - run: python devtools/runtime-map-tests/prepare_parity.py --publisher publisher --publisher-revision 4de7742a764d27293b4525d6ad2a6fc8a95f3e3a --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 + - if: runner.os != 'Windows' + run: ./build-runtime-maps/RuntimeMapTests runtime-map-pairs.json + - if: runner.os == 'Windows' + run: ./build-runtime-maps/Debug/RuntimeMapTests.exe runtime-map-pairs.json diff --git a/.gitignore b/.gitignore index ef6f409ae9..ab64316491 100644 --- a/.gitignore +++ b/.gitignore @@ -77,7 +77,11 @@ build_*/ .cache/* Packages/* - -# Local agent/linter environments. -.venv-code-health/ -__pycache__/ + +# Local agent/linter environments. +.venv-code-health/ +__pycache__/ + +# Prepared, pinned PokemonSimulation maps are external build inputs. +Packages/Resources/Pokemon/Maps/ +.pokesim-map-source/ diff --git a/SerialPrograms/CMakeLists.txt b/SerialPrograms/CMakeLists.txt index ad0ff7ae70..69bc4aca92 100644 --- a/SerialPrograms/CMakeLists.txt +++ b/SerialPrograms/CMakeLists.txt @@ -197,7 +197,18 @@ add_library(SerialProgramsLib STATIC ${CORE_LIBRARY_SOURCES} ${LIBRARY_SOURCES}) include(cmake/EmbedTextFiles.cmake) pa_embed_text_files(SerialProgramsLib Source/Pokemon/Navigation/TestMaps Pokemon/Navigation/Pokemon_Navigation_Tests.h PokemonAutomation::Pokemon navigation_test_map_json) -pa_embed_text_files(SerialProgramsLib Source/PokemonBDSP/Programs/AutoStory/Maps +# Prepared data is validated by the shared publisher tooling. No source-map +# fallback or network download is performed during configuration. +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 "Prepare devtools/maps/map-resources.lock.json and set POKESIM_TOOLS_SOURCE/POKESIM_SOURCE_CACHE; see devtools/maps/RESOURCE_MAPS.md.") +endif() +include("${POKESIM_TOOLS_SOURCE}/integrations/arduino-source/resource-maps.cmake") +get_filename_component(POKESIM_CONSUMER "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE) +pa_pokesim_map_directory(BDSP_MAP_DIRECTORY bdsp "${POKESIM_CONSUMER}" "${POKESIM_SOURCE_CACHE}") +file(RELATIVE_PATH BDSP_MAP_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}" "${BDSP_MAP_DIRECTORY}") +pa_embed_text_files(SerialProgramsLib "${BDSP_MAP_RESOURCES}" PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h PokemonAutomation::NintendoSwitch::PokemonBDSP bdsp_map_json) diff --git a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp index e262f24f95..624be9b35e 100644 --- a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp +++ b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp @@ -33,6 +33,7 @@ #include "Pokemon/AutoStory/Pokemon_AutoStory_Tests.h" #include "Pokemon/Navigation/Pokemon_Navigation_Tests.h" +#include "Pokemon/Navigation/Pokemon_RuntimeMap_Tests.h" #include "PokemonBDSP/PokemonBDSP_Tests.h" #include "VideoGames/PokemonHome/PokemonHome_Tests.h" #include "VideoGames/PokemonSwSh/PokemonSwSh_Tests.h" @@ -73,6 +74,7 @@ UnitTestDatabase make_UNIT_TESTS_ALL(){ Pokemon::add_tests_LegWalker(ret); Pokemon::add_tests_WallRecovery(ret); Pokemon::add_tests_MapData(ret); + Pokemon::add_tests_RuntimeMap(ret); Pokemon::add_tests_RouteLog(ret); Pokemon::add_tests_StoryEvents(ret); Pokemon::add_tests_StorySegments(ret); diff --git a/SerialPrograms/Source/Pokemon/Navigation/Pokemon_MapData.cpp b/SerialPrograms/Source/Pokemon/Navigation/Pokemon_MapData.cpp index 64a4a2a5a9..651228319e 100644 --- a/SerialPrograms/Source/Pokemon/Navigation/Pokemon_MapData.cpp +++ b/SerialPrograms/Source/Pokemon/Navigation/Pokemon_MapData.cpp @@ -11,6 +11,7 @@ #include "Common/Cpp/Exceptions.h" #include "Common/Cpp/Json/JsonArray.h" #include "Common/Cpp/Json/JsonObject.h" +#include "Pokemon_RuntimeMap.h" #include "Pokemon_MapData.h" namespace PokemonAutomation{ @@ -523,7 +524,8 @@ void check_places(const MapData& map, const std::string& where){ MapData parse_map(const JsonValue& json, const std::string& source_name){ - const JsonObject& root = as_object(json, source_name); + const JsonValue normalized = normalize_runtime_map(json, source_name); + const JsonObject& root = as_object(normalized, source_name); const std::string where = source_name + ": "; MapData map; map.name = read_string(root, "name", where); diff --git a/SerialPrograms/Source/Pokemon/Navigation/Pokemon_MapData.h b/SerialPrograms/Source/Pokemon/Navigation/Pokemon_MapData.h index fc4ecada53..3a68ecdeb2 100644 --- a/SerialPrograms/Source/Pokemon/Navigation/Pokemon_MapData.h +++ b/SerialPrograms/Source/Pokemon/Navigation/Pokemon_MapData.h @@ -207,7 +207,8 @@ struct MapData{ std::vector patches; }; -// Reads a map from its parsed JSON. `source_name` names it in errors. +// Reads legacy/unmarked v1 or compact pokesim-runtime-map/2 JSON. +// Both games use this entry point; `source_name` names it in errors. // Throws ParseException, naming the source and the JSON path (e.g. // "Maps/TestTown.json: warps[2].x is missing."), when a field is missing, // doesn't fit the schema, or places something outside the tiles. diff --git a/SerialPrograms/Source/Pokemon/Navigation/Pokemon_RuntimeMap.cpp b/SerialPrograms/Source/Pokemon/Navigation/Pokemon_RuntimeMap.cpp new file mode 100644 index 0000000000..1258409505 --- /dev/null +++ b/SerialPrograms/Source/Pokemon/Navigation/Pokemon_RuntimeMap.cpp @@ -0,0 +1,229 @@ +/* Versioned Runtime Map Decoder + * + * From: https://github.com/PokemonAutomation/ + */ +#include +#include +#include +#include +#include +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Json/JsonArray.h" +#include "Common/Cpp/Json/JsonObject.h" +#include "Pokemon_Grid.h" +#include "Pokemon_RuntimeMap.h" + +namespace PokemonAutomation{ +namespace Pokemon{ +namespace{ + +enum class WireFormat{ LEGACY, COMPACT }; + +const JsonObject& object_at(const JsonValue& value, const std::string& where){ + const JsonObject* object = value.to_object(); + if (!object){ + throw ParseException(where + " is not a JSON object."); + } + return *object; +} + +const JsonArray& array_at(const JsonValue& value, const std::string& where){ + const JsonArray* array = value.to_array(); + if (!array){ + throw ParseException(where + " must be an array."); + } + return *array; +} + +JsonObject copy_without(const JsonObject& object, const std::set& omit){ + JsonObject result; + for (const auto& [key, value] : object){ + if (!omit.contains(key)){ + result[key] = value.clone(); + } + } + return result; +} + +int coordinate(const JsonValue* value, const std::string& where){ + if (!value){ + throw ParseException(where + " is missing."); + } + if (!value->is_integer()){ + throw ParseException(where + " is not an integer."); + } + const int64_t number = value->to_integer_default(); + if (number < std::numeric_limits::min() || number > std::numeric_limits::max()){ + throw ParseException(where + " does not fit a native coordinate."); + } + return (int)number; +} + +TilePos pair_at(const JsonObject& object, const std::array& keys, WireFormat format, const std::string& where){ + if (format == WireFormat::LEGACY){ + if (object.get_value(keys[2])){ + throw ParseException(where + keys[2] + " is not a v1 coordinate field."); + } + return {coordinate(object.get_value(keys[0]), where + keys[0]), coordinate(object.get_value(keys[1]), where + keys[1])}; + } + const JsonValue* value = object.get_value(keys[2]); + if (object.get_value(keys[0]) || object.get_value(keys[1]) || !value){ + throw ParseException(where + keys[2] + " requires only the v2 coordinate pair."); + } + const std::string pair_path = where + keys[2]; + const JsonArray& pair = array_at(*value, pair_path); + if (pair.size() != 2){ + throw ParseException(where + keys[2] + " requires exactly two integers."); + } + return {coordinate(&pair[0], where + keys[2] + "[0]"), coordinate(&pair[1], where + keys[2] + "[1]")}; +} + +JsonValue normalize_wander(const JsonValue& value, WireFormat format, const std::string& where){ + const JsonObject& source = object_at(value, where); + const TilePos low = pair_at(source, {"x0", "y0", "min"}, format, where); + const TilePos high = pair_at(source, {"x1", "y1", "max"}, format, where); + if (format == WireFormat::COMPACT && (low.x > high.x || low.y > high.y)){ + throw ParseException(where + "must have inclusive min <= max bounds."); + } + JsonObject result = copy_without(source, {"x0", "y0", "x1", "y1", "min", "max"}); + result["x0"] = low.x; + result["y0"] = low.y; + result["x1"] = high.x; + result["y1"] = high.y; + return result; +} + +JsonObject normalize_record(const JsonValue& value, WireFormat format, const std::string& where){ + const JsonObject& source = object_at(value, where); + const TilePos pos = pair_at(source, {"x", "y", "coord"}, format, where); + JsonObject result = copy_without(source, {"x", "y", "coord"}); + result["x"] = pos.x; + result["y"] = pos.y; + return result; +} + +// Expand compact pickup defaults without changing authored names or counts. +JsonObject normalize_pickup(JsonObject record, std::string_view where){ + const std::string* item = record.get_string("item"); + if (!item || item->empty()){ + throw ParseException(std::format("{}item must be a nonempty string.", where)); + } + if (const JsonValue* event = record.get_value("source_event")){ + const int index = coordinate(event, std::format("{}source_event", where)); + if (index < 0){ + throw ParseException(std::format("{}source_event must be nonnegative.", where)); + } + if (!record.get_value("name")){ + record["name"] = std::format("item_{}", index); + } + }else if (!record.get_value("name")){ + throw ParseException(std::format("{}unnamed pickup requires source_event.", where)); + } + if (const JsonValue* quantity = record.get_value("quantity")){ + if (!quantity->is_integer() || quantity->to_integer_default() <= 0){ + throw ParseException(std::format("{}quantity must be a positive integer.", where)); + } + }else{ + record["quantity"] = 1; + } + return record; +} + +void append_objects(JsonArray& output, const JsonArray& input, WireFormat format, const std::string& group, const std::string& where){ + for (size_t i = 0; i < input.size(); i++){ + const std::string path = where + group + "[" + std::to_string(i) + "]."; + JsonObject record = normalize_record(input[i], format, path); + if (format == WireFormat::COMPACT){ + if (record.get_value("kind")){ + throw ParseException(path + "kind is redundant in v2 typed arrays."); + } + record["kind"] = group == "npcs" ? "npc" : group == "signs" ? "sign" : "item"; + } + if (group == "interactables"){ + const JsonObject* interaction = record.get_object("interaction"); + const std::string* action = interaction ? interaction->get_string("kind") : nullptr; + if (!action || action->empty() || record.get_value("item") || record.get_value("quantity")){ + throw ParseException(path + "requires an interaction without an item reward."); + } + }else if (group == "items" && record.get_value("interaction")){ + throw ParseException(path + "interactions belong in interactables."); + } + if (group == "items" && (record.get_value("item") || record.get_value("quantity"))){ + record = normalize_pickup(std::move(record), path); + } + if (const JsonValue* wander = record.get_value("wander")){ + record["wander"] = normalize_wander(*wander, format, path + "wander."); + } + output.push_back(std::move(record)); + } +} + +WireFormat read_format(const JsonObject& root, const std::string& where){ + const JsonValue* field = root.get_value("format"); + if (!field){ + return WireFormat::LEGACY; + } + const std::string* format = field->to_string(); + if (format && *format == "pokesim-runtime-map/1"){ + return WireFormat::LEGACY; + } + if (format && *format == "pokesim-runtime-map/2"){ + return WireFormat::COMPACT; + } + throw ParseException(where + "format is unsupported."); +} + +void normalize_objects(JsonObject& output, const JsonObject& root, WireFormat format, const std::string& where){ + JsonArray objects; + const std::array groups{"npcs", "items", "signs", "interactables"}; + if (format == WireFormat::LEGACY){ + for (const std::string& group : groups){ + if (root.get_value(group)){ + throw ParseException(where + group + " is not a v1 object section."); + } + } + if (const JsonValue* value = root.get_value("objects")){ + append_objects(objects, array_at(*value, where + "objects"), format, "objects", where); + output["objects"] = std::move(objects); + } + return; + } + if (root.get_value("objects")){ + throw ParseException(where + "objects cannot be combined with v2 typed arrays."); + } + bool present = false; + for (const std::string& group : groups){ + if (const JsonValue* value = root.get_value(group)){ + present = true; + append_objects(objects, array_at(*value, where + group), format, group, where); + } + } + if (present){ + output["objects"] = std::move(objects); + } +} + +} // namespace + +JsonValue normalize_runtime_map(const JsonValue& json, std::string_view source_name){ + const std::string where = std::string(source_name) + ": "; + const JsonObject& root = object_at(json, where); + const WireFormat format = read_format(root, where); + JsonObject output = copy_without(root, {"format", "objects", "npcs", "items", "signs", "interactables", "warps", "patches", "coord_events"}); + normalize_objects(output, root, format, where); + for (const std::string section : {"warps", "patches", "coord_events"}){ + if (const JsonValue* field = root.get_value(section)){ + const std::string section_path = where + section; + const JsonArray& input = array_at(*field, section_path); + JsonArray records; + for (size_t i = 0; i < input.size(); i++){ + records.push_back(normalize_record(input[i], format, where + section + "[" + std::to_string(i) + "].")); + } + output[section] = std::move(records); + } + } + return output; +} + +} // namespace Pokemon +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/Pokemon/Navigation/Pokemon_RuntimeMap.h b/SerialPrograms/Source/Pokemon/Navigation/Pokemon_RuntimeMap.h new file mode 100644 index 0000000000..ab1b078f7d --- /dev/null +++ b/SerialPrograms/Source/Pokemon/Navigation/Pokemon_RuntimeMap.h @@ -0,0 +1,22 @@ +/* Versioned Runtime Map Decoder + * + * From: https://github.com/PokemonAutomation/ + */ +#ifndef PokemonAutomation_RuntimeMap_H +#define PokemonAutomation_RuntimeMap_H + +#include +#include "Common/Cpp/Json/JsonValue.h" + +namespace PokemonAutomation{ +namespace Pokemon{ + +// Decode v1/v2 runtime wire data to legacy JSON without modifying the input. +// Preserve extension metadata; reject mixed encodings, malformed pairs and unknown formats. +// Group order is NPC, item, sign, interactable; order within each group is retained. +// Identified v2 pickups default quantity to 1; absent names use item_. +JsonValue normalize_runtime_map(const JsonValue& json, std::string_view source_name); + +} // namespace Pokemon +} // namespace PokemonAutomation +#endif diff --git a/SerialPrograms/Source/Pokemon/Navigation/Pokemon_RuntimeMap_Tests.cpp b/SerialPrograms/Source/Pokemon/Navigation/Pokemon_RuntimeMap_Tests.cpp new file mode 100644 index 0000000000..a018a719cd --- /dev/null +++ b/SerialPrograms/Source/Pokemon/Navigation/Pokemon_RuntimeMap_Tests.cpp @@ -0,0 +1,196 @@ +/* Versioned Runtime Map Tests + * + * 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_MapData.h" +#include "Pokemon_RuntimeMap.h" +#ifndef PA_RUNTIME_MAP_STANDALONE +#include "Pokemon_RuntimeMap_Tests.h" +#endif + +namespace PokemonAutomation{ +namespace Pokemon{ +namespace{ + +struct RuntimeMapInput{ + std::string_view name; + std::string_view fields; +}; + +struct RuntimeMapExpectation{ + std::string_view error = {}; + std::string_view normalized_fields = {}; +}; + +struct RuntimeMapCase{ + RuntimeMapInput input; + RuntimeMapExpectation expectation; +}; + +const char* const GOLDEN_V2 = R"( + "format":"pokesim-runtime-map/2", + "world":{"name":"Sinnoh","x":0,"y":0}, + "extension":{"zero":0,"false":false}, + "npcs":[ + {"name":"npc","coord":[0,0],"facing":"east","trainer":{"sight":1},"wander":{"min":[0,0],"max":[1,1]}}, + {"name":"npc","coord":[1,0],"hide_flag":"hidden"} + ], + "items":[{"name":"pickup","coord":[0,1],"item":"potion","quantity":1}], + "signs":[{"name":"sign","coord":[1,1]}], + "interactables":[{"name":"ball","coord":[1,0],"interaction":{"kind":"starter_choice","species":"squirtle"}}], + "warps":[{"coord":[1,1],"enter":"walk_north","to_map":"House","to_point":"exit"}], + "patches":[{"coord":[0,1],"glyph":"#","measured_by":"synthetic"}], + "coord_events":[{"coord":[0,0],"width":1,"length":2,"var":0}] +)"; + +const char* const GOLDEN_V1 = R"( + "world":{"name":"Sinnoh","x":0,"y":0}, + "extension":{"zero":0,"false":false}, + "objects":[ + {"name":"npc","kind":"npc","x":0,"y":0,"facing":"east","trainer":{"sight":1},"wander":{"x0":0,"y0":0,"x1":1,"y1":1}}, + {"name":"npc","kind":"npc","x":1,"y":0,"hide_flag":"hidden"}, + {"name":"pickup","kind":"item","x":0,"y":1,"item":"potion","quantity":1}, + {"name":"sign","kind":"sign","x":1,"y":1}, + {"name":"ball","kind":"item","x":1,"y":0,"interaction":{"kind":"starter_choice","species":"squirtle"}} + ], + "warps":[{"x":1,"y":1,"enter":"walk_north","to_map":"House","to_point":"exit"}], + "patches":[{"x":0,"y":1,"glyph":"#","measured_by":"synthetic"}], + "coord_events":[{"x":0,"y":0,"width":1,"length":2,"var":0}] +)"; + +const RuntimeMapCase RUNTIME_MAP_CASES[] = { + {.input = {.name = "pickup derived name and default quantity", .fields = R"("format":"pokesim-runtime-map/2","items":[{"coord":[0,1],"source_event":3,"item":"potion"}])"}, .expectation = {.normalized_fields = R"("objects":[{"x":0,"y":1,"kind":"item","name":"item_3","source_event":3,"item":"potion","quantity":1}])"}}, + {.input = {.name = "pickup explicit name and multiple quantity", .fields = R"("format":"pokesim-runtime-map/2","items":[{"name":"gift_cache","coord":[0,1],"source_event":3,"item":"potion","quantity":4}])"}, .expectation = {.normalized_fields = R"("objects":[{"x":0,"y":1,"kind":"item","name":"gift_cache","source_event":3,"item":"potion","quantity":4}])"}}, + {.input = {.name = "pickup named quantity default", .fields = R"("format":"pokesim-runtime-map/2","items":[{"name":"pickup","coord":[0,1],"item":"potion"}])"}, .expectation = {.normalized_fields = R"("objects":[{"x":0,"y":1,"kind":"item","name":"pickup","item":"potion","quantity":1}])"}}, + {.input = {.name = "pickup missing event", .fields = R"("format":"pokesim-runtime-map/2","items":[{"coord":[0,1],"item":"potion"}])"}, .expectation = {.error = "source_event"}}, + {.input = {.name = "pickup negative event", .fields = R"("format":"pokesim-runtime-map/2","items":[{"coord":[0,1],"item":"potion","source_event":-1}])"}, .expectation = {.error = "source_event"}}, + {.input = {.name = "pickup boolean event", .fields = R"("format":"pokesim-runtime-map/2","items":[{"coord":[0,1],"item":"potion","source_event":true}])"}, .expectation = {.error = "source_event"}}, + {.input = {.name = "pickup overflow event", .fields = R"("format":"pokesim-runtime-map/2","items":[{"coord":[0,1],"item":"potion","source_event":2147483648}])"}, .expectation = {.error = "source_event"}}, + {.input = {.name = "pickup zero quantity", .fields = R"("format":"pokesim-runtime-map/2","items":[{"coord":[0,1],"name":"p","item":"potion","quantity":0}])"}, .expectation = {.error = "quantity"}}, + {.input = {.name = "pickup negative quantity", .fields = R"("format":"pokesim-runtime-map/2","items":[{"coord":[0,1],"name":"p","item":"potion","quantity":-1}])"}, .expectation = {.error = "quantity"}}, + {.input = {.name = "pickup boolean quantity", .fields = R"("format":"pokesim-runtime-map/2","items":[{"coord":[0,1],"name":"p","item":"potion","quantity":true}])"}, .expectation = {.error = "quantity"}}, + {.input = {.name = "pickup null quantity", .fields = R"("format":"pokesim-runtime-map/2","items":[{"coord":[0,1],"name":"p","item":"potion","quantity":null}])"}, .expectation = {.error = "quantity"}}, + {.input = {.name = "pickup float quantity", .fields = R"("format":"pokesim-runtime-map/2","items":[{"coord":[0,1],"name":"p","item":"potion","quantity":1.5}])"}, .expectation = {.error = "quantity"}}, + {.input = {.name = "pickup empty item", .fields = R"("format":"pokesim-runtime-map/2","items":[{"coord":[0,1],"name":"p","item":""}])"}, .expectation = {.error = "item"}}, + + {.input = {.name = "complete normalized-value parity", .fields = GOLDEN_V2}, .expectation = {.normalized_fields = GOLDEN_V1}}, + {.input = {.name = "implicit v1", .fields = R"("objects":[{"name":"npc","kind":"npc","x":0,"y":0}])"}, .expectation = {}}, + {.input = {.name = "explicit v1", .fields = R"("format":"pokesim-runtime-map/1")"}, .expectation = {}}, + {.input = {.name = "v2 empty map", .fields = R"("format":"pokesim-runtime-map/2")"}, .expectation = {}}, + {.input = {.name = "v2 coordinates and inclusive wander", .fields = R"("format":"pokesim-runtime-map/2","npcs":[{"name":"npc","coord":[0,0],"wander":{"min":[0,0],"max":[1,1]}}])"}, .expectation = {}}, + {.input = {.name = "unsupported version", .fields = R"("format":"pokesim-runtime-map/99")"}, .expectation = {.error = "format"}}, + {.input = {.name = "nonstring version", .fields = R"("format":2)"}, .expectation = {.error = "format"}}, + {.input = {.name = "v1 typed section", .fields = R"("npcs":[])"}, .expectation = {.error = "npcs"}}, + {.input = {.name = "v2 legacy section", .fields = R"("format":"pokesim-runtime-map/2","objects":[])"}, .expectation = {.error = "objects"}}, + {.input = {.name = "mixed v2 position", .fields = R"("format":"pokesim-runtime-map/2","warps":[{"coord":[0,0],"x":0}])"}, .expectation = {.error = "warps[0].coord"}}, + {.input = {.name = "mixed v1 position", .fields = R"("warps":[{"x":0,"y":0,"coord":[0,0]}])"}, .expectation = {.error = "warps[0].coord"}}, + {.input = {.name = "missing v2 position", .fields = R"("format":"pokesim-runtime-map/2","warps":[{}])"}, .expectation = {.error = "warps[0].coord"}}, + {.input = {.name = "short position", .fields = R"("format":"pokesim-runtime-map/2","warps":[{"coord":[0]}])"}, .expectation = {.error = "warps[0].coord"}}, + {.input = {.name = "long position", .fields = R"("format":"pokesim-runtime-map/2","warps":[{"coord":[0,0,0]}])"}, .expectation = {.error = "warps[0].coord"}}, + {.input = {.name = "floating coordinate", .fields = R"("format":"pokesim-runtime-map/2","warps":[{"coord":[0.5,0]}])"}, .expectation = {.error = "coord[0]"}}, + {.input = {.name = "boolean coordinate", .fields = R"("format":"pokesim-runtime-map/2","warps":[{"coord":[false,0]}])"}, .expectation = {.error = "coord[0]"}}, + {.input = {.name = "overflow coordinate", .fields = R"("format":"pokesim-runtime-map/2","warps":[{"coord":[2147483648,0]}])"}, .expectation = {.error = "coord[0]"}}, + {.input = {.name = "outside map", .fields = R"("format":"pokesim-runtime-map/2","warps":[{"coord":[3,0]}])"}, .expectation = {.error = "outside"}}, + {.input = {.name = "mixed wander", .fields = R"("format":"pokesim-runtime-map/2","npcs":[{"name":"npc","coord":[0,0],"wander":{"min":[0,0],"max":[1,1],"x0":0}}])"}, .expectation = {.error = "wander.min"}}, + {.input = {.name = "reversed wander", .fields = R"("format":"pokesim-runtime-map/2","npcs":[{"name":"npc","coord":[0,0],"wander":{"min":[1,1],"max":[0,0]}}])"}, .expectation = {.error = "min <= max"}}, + {.input = {.name = "redundant kind", .fields = R"("format":"pokesim-runtime-map/2","signs":[{"name":"sign","coord":[0,0],"kind":"sign"}])"}, .expectation = {.error = "kind"}}, + {.input = {.name = "starter interaction", .fields = R"("format":"pokesim-runtime-map/2","interactables":[{"name":"ball","coord":[0,0],"interaction":{"kind":"starter_choice","species":"squirtle"}}])"}, .expectation = {}}, + {.input = {.name = "interaction has item", .fields = R"("format":"pokesim-runtime-map/2","interactables":[{"coord":[0,0],"interaction":{"kind":"starter_choice"},"item":"potion"}])"}, .expectation = {.error = "item reward"}}, + {.input = {.name = "interaction missing kind", .fields = R"("format":"pokesim-runtime-map/2","interactables":[{"coord":[0,0],"interaction":{}}])"}, .expectation = {.error = "interaction"}}, + {.input = {.name = "item has interaction", .fields = R"("format":"pokesim-runtime-map/2","items":[{"coord":[0,0],"interaction":{"kind":"starter_choice"}}])"}, .expectation = {.error = "interactables"}}, + {.input = {.name = "bad coordinate event", .fields = R"("format":"pokesim-runtime-map/2","coord_events":[{"coord":[0,true]}])"}, .expectation = {.error = "coord_events[0].coord[1]"}}, +}; + +std::string map_document(std::string_view fields){ + return R"({"name":"ContractMap","source":{"decomp_file":"synthetic","revision":"fixture","date":"2026-11-06"},"tiles":["..",".."],)" + std::string(fields) + "}"; +} + +// This is also used by the standalone pin-based 75-map parity check. +std::string check_pair(const JsonValue& input, const JsonValue& expected){ + const JsonValue normalized = normalize_runtime_map(input, "Maps/parity.json"); + if (!(normalized == expected)){ + return "Full normalized JSON differs (including extension metadata/order)."; + } + const MapData map = parse_map(input, "Maps/parity.json"); + const MapData legacy = parse_map(expected, "Maps/parity-v1.json"); + if (map.objects.size() != legacy.objects.size() || map.warps.size() != legacy.warps.size()){ + return "Native parsed object/warp counts differ."; + } + return {}; +} + +std::string check_runtime_case(const RuntimeMapCase& test){ + try{ + const JsonValue input = parse_json(map_document(test.input.fields)); + if (!test.expectation.normalized_fields.empty()){ + return check_pair(input, parse_json(map_document(test.expectation.normalized_fields))); + } + parse_map(input, "Maps/contract.json"); + }catch (const ParseException& error){ + const std::string message = error.message(); + if (!test.expectation.error.empty() && message.find(test.expectation.error) != std::string::npos && message.find("Maps/contract.json") != std::string::npos){ + return {}; + } + return "Unexpected parse error: " + message; + } + return test.expectation.error.empty() ? "" : "Expected rejection containing " + std::string(test.expectation.error); +} + +#ifndef PA_RUNTIME_MAP_STANDALONE +class RuntimeMapTest : public UnitTest{ +public: + explicit RuntimeMapTest(const RuntimeMapCase& test) + : UnitTest("Navigation/RuntimeMap/" + std::string(test.input.name)), m_test(test){} + virtual UnitTestResult run(Logger&, CancellableScope&) const override{ + const std::string error = check_runtime_case(m_test); + return error.empty() ? UnitTestResult(true) : UnitTestResult(error); + } +private: + const RuntimeMapCase& m_test; +}; +#endif + +} // namespace + +#ifndef PA_RUNTIME_MAP_STANDALONE +void add_tests_RuntimeMap(UnitTestDatabase& database){ + for (const RuntimeMapCase& test : RUNTIME_MAP_CASES){ + database.add(test); + } +} +#endif + +} // namespace Pokemon +} // namespace PokemonAutomation + +#ifdef PA_RUNTIME_MAP_STANDALONE +// Optional argument: a parity pack of {input, expectation} records from a pinned publisher. +int main(int argc, char* argv[]){ + int failed = 0; + for (const auto& test : PokemonAutomation::Pokemon::RUNTIME_MAP_CASES){ + const std::string error = PokemonAutomation::Pokemon::check_runtime_case(test); + std::cout << test.input.name << ": " << (error.empty() ? "PASS" : error) << std::endl; + failed += !error.empty(); + } + if (argc == 2){ + const auto pack = PokemonAutomation::load_json_file(argv[1]); + for (const auto& record : pack.to_array_throw()){ + const auto& object = record.to_object_throw(); + const std::string error = PokemonAutomation::Pokemon::check_pair(object.get_value_throw("input"), object.get_value_throw("expectation")); + failed += !error.empty(); + if (!error.empty()){ + std::cerr << error << std::endl; + } + } + std::cout << "Checked " << pack.to_array_throw().size() << " complete map records." << std::endl; + }else if (argc != 1){ + return 2; + } + return failed; +} +#endif diff --git a/SerialPrograms/Source/Pokemon/Navigation/Pokemon_RuntimeMap_Tests.h b/SerialPrograms/Source/Pokemon/Navigation/Pokemon_RuntimeMap_Tests.h new file mode 100644 index 0000000000..42f2cda081 --- /dev/null +++ b/SerialPrograms/Source/Pokemon/Navigation/Pokemon_RuntimeMap_Tests.h @@ -0,0 +1,17 @@ +/* Versioned Runtime Map Tests + * + * From: https://github.com/PokemonAutomation/ + */ +#ifndef PokemonAutomation_RuntimeMap_Tests_H +#define PokemonAutomation_RuntimeMap_Tests_H +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" + +namespace PokemonAutomation{ +namespace Pokemon{ + +// Register wire-schema compatibility/rejection and full normalized-value checks. +void add_tests_RuntimeMap(UnitTestDatabase& database); + +} // namespace Pokemon +} // namespace PokemonAutomation +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/FloaromaTown.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/FloaromaTown.json deleted file mode 100644 index 5ec01e577b..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/FloaromaTown.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "FloaromaTown", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0032.bin; zone_event narc_0404", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "######........................##", - "##..##........................##", - "..............................##", - "..............................##", - "..............................##", - "..............................##", - "..............................##", - "..................#####.......##", - "..................#####.......##", - "..................#####.......##", - "..................#####.......##", - "..........####...S##.##.......##", - "..........####................##", - "..........####................##", - "..........#D##................##", - ".......................####..S##", - ".......................####...##", - ".......................#D##...##", - "..............................##", - ".............S..................", - "................................", - "................................", - "................................", - "..............#####....####...##", - "..............#####....####...##", - "..............#####....####...##", - "..............##D##....#D##...##", - "..............................##", - "..............................##", - "..............................##", - "########.#...###################", - "########.#...###################" - ], - "points": { - "route_204_arrival": [11, 29], - "old_woman": [3, 25], - "old_man": [2, 25], - "pokemon_center_door": [16, 26] - }, - "coord_events": [ - { - "name": "coord_event_0", - "coord": [10, 29], - "width": 3, - "length": 1, - "var": "VAR_UNK_0x4072", - "value": 0, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0404.bin coord_events[0] (170, 669), script 2" - } - ], - "npcs": [ - {"name": "pokemon_breeder_f_0", "coord": [19, 21]}, - {"name": "lass_1", "coord": [10, 5], "wander": {"min": [9, 5], "max": [11, 5]}}, - {"name": "school_kid_m_2", "coord": [16, 16], "wander": {"min": [15, 15], "max": [17, 17]}}, - {"name": "cyrus_3", "coord": [11, 26]}, - {"name": "lass_4", "coord": [23, 11], "facing": "west"}, - {"name": "grunt_m_7", "coord": [2, 1], "facing": "east"}, - {"name": "grunt_m_8", "coord": [3, 1], "facing": "west"}, - {"name": "camper_12", "coord": [6, 10]}, - {"name": "old_woman", "coord": [3, 25]}, - {"name": "old_man", "coord": [2, 25]} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/JubilifeCity.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/JubilifeCity.json deleted file mode 100644 index 3ba40ce415..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/JubilifeCity.json +++ /dev/null @@ -1,191 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "JubilifeCity", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0015.bin, files/fielddata/land_data/land_data_release/narc_0016.bin, files/fielddata/land_data/land_data_release/narc_0017.bin, files/fielddata/land_data/land_data_release/narc_0018.bin; zone_event narc_0002", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "##########################################..............########", - "##########################################..............########", - "############################################.....###############", - "############################################......##############", - "############################################......##############", - "############################################......##############", - "############################################......##############", - "#############################################....###############", - "############################....#########.......S.##############", - "############################....#########.........##############", - "#########..#########..######....#########.........##############", - "#########..#########..######....#########.........##############", - "#########..#########..######....#########.........##############", - "#########..#########..######....#########.........##############", - "#########..#########..######....#########.........##############", - "#########..##D###D##..######....####D####.........##############", - "#########.............######...............#.....#######....####", - "#########.............######......................######....####", - "..#######.............######......................######....####", - "..###########S......########...........S.........S##D###....####", - "..#.S.#.......................#..........................#.S####", - "................................................................", - "D...............................................................", - "D...............................................................", - "................................................................", - "..#...#.......................#..........................#..####", - "..############.............####......####...................####", - "..############.............####......####................#######", - "##############.################...##.####..#.....######..#######", - "##############.################...##.####.........#####..#######", - "##############.################...................#D############", - "################################.........................#######", - "################################.........................##D####", - "################################.........................#######", - "################################.........................#######", - "###############............#####.........................#######", - "###############............#####......#####..............#######", - "###############..#########.#####.##########.......######.#######", - "###############..#########.#####.##########.......######.#######", - "###############..#########.#####.###########.....#######.#######", - "##################################..S.##D##.......##D###########", - "###############..#########.#####.........................#######", - "##############...####D####..###..........................#######", - "##############...........................................#######", - "##############.....S.....................................#######", - "##############...........................................#######", - "##############.............#########.....................#######", - "###############............#########.#####...............#######", - "###############............#########.#####...............#######", - "###############.####..####.###############...............#######", - "###############.####..####.###############...............#######", - "###############.####..####.###############...........###########", - "############D##.####..####.############D##...........###########", - "##########.................#########.......#.....#...########.##", - "##########.................#########.................########.##", - "##########............#..#.#########......S..........########.##", - "##########............#..#.#########.................########.##", - "##########.................#########.................########.##", - "###########################################.......##############", - "###########################################.......##############", - "########################################.##.......##############", - "########################################.####...################", - "########################################..........##############", - "########################################..........##############" - ], - "points": { - "trainers_school_door": [40, 40], - "pokemon_center_door": [52, 40], - "tv_station_door": [36, 15], - "poketch_president": [46, 35], - "clown_mart": [55, 33], - "clown_tv_station": [36, 16], - "clown_poketch_company": [15, 18], - "up_the_steps.1": [46, 58], - "up_the_steps.2": [47, 58], - "up_the_steps.3": [46, 59], - "up_the_steps.4": [47, 59], - "up_the_steps.5": [46, 60], - "up_the_steps.6": [47, 60], - "east_entrance.1": [61, 21], - "east_entrance.2": [62, 21], - "east_entrance.3": [63, 21], - "east_entrance.4": [61, 22], - "east_entrance.5": [62, 22], - "east_entrance.6": [63, 22], - "east_entrance.7": [61, 23], - "east_entrance.8": [62, 23], - "east_entrance.9": [63, 23], - "east_entrance.10": [61, 24], - "east_entrance.11": [62, 24], - "east_entrance.12": [63, 24], - "north_road.1": [44, 16], - "north_road.2": [45, 16], - "north_road.3": [46, 16], - "north_road.4": [47, 16], - "north_road.5": [48, 16], - "north_road.6": [44, 17], - "north_road.7": [45, 17], - "north_road.8": [46, 17], - "north_road.9": [47, 17], - "north_road.10": [48, 17], - "north_road.11": [44, 18], - "north_road.12": [45, 18], - "north_road.13": [46, 18], - "north_road.14": [47, 18], - "north_road.15": [48, 18], - "north_road.16": [44, 19], - "north_road.17": [45, 19], - "north_road.18": [46, 19], - "north_road.19": [47, 19], - "north_road.20": [48, 19], - "north_road.21": [44, 20], - "north_road.22": [45, 20], - "north_road.23": [46, 20], - "north_road.24": [47, 20], - "north_road.25": [48, 20] - }, - "coord_events": [ - { - "name": "dawn_at_the_steps", - "coord": [45, 60], - "width": 4, - "length": 1, - "var": "VAR_JUBILIFE_CITY_STATE", - "value": 0, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0002.bin coord_events[0] (173, 796), script 2" - }, - { - "name": "coord_event_1", - "coord": [45, 7], - "width": 3, - "length": 1, - "var": "VAR_JUBILIFE_CITY_STATE", - "value": 3, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0002.bin coord_events[1] (173, 743), script 4" - }, - { - "name": "coord_event_2", - "coord": [60, 21], - "width": 1, - "length": 4, - "var": "VAR_JUBILIFE_CITY_STATE", - "value": 1, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0002.bin coord_events[2] (188, 757), script 3" - }, - { - "name": "coord_event_3", - "coord": [27, 43], - "width": 1, - "length": 3, - "var": "VAR_GTS_ACCESS_STATE", - "value": 0, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0002.bin coord_events[3] (155, 779), script 19" - }, - { - "name": "poketch_president", - "coord": [44, 40], - "width": 5, - "length": 1, - "var": "VAR_POKETCH_CAMPAIGN_STATE", - "value": 1, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0002.bin coord_events[4] (172, 776), script 17" - } - ], - "npcs": [ - {"name": "ace_trainer_f_0", "coord": [22, 20], "wander": {"min": [21, 20], "max": [23, 20]}}, - {"name": "ace_trainer_m_1", "coord": [46, 21], "wander": {"min": [45, 21], "max": [47, 21]}}, - {"name": "school_kid_m_2", "coord": [17, 55]}, - {"name": "twin_3", "coord": [33, 18], "wander": {"min": [32, 17], "max": [34, 19]}}, - {"name": "expert_m_4", "coord": [44, 44]}, - {"name": "school_kid_m_5", "coord": [51, 34], "wander": {"min": [51, 33], "max": [51, 35]}}, - {"name": "pokefan_m_6", "coord": [41, 30], "wander": {"min": [40, 29], "max": [42, 31]}}, - {"name": "ace_trainer_m_22", "coord": [36, 25]}, - {"name": "poketch_president", "coord": [46, 35]}, - {"name": "clown_mart", "coord": [55, 33]}, - {"name": "clown_poketch_company", "coord": [15, 18]}, - {"name": "cashier_m_27", "coord": [26, 42]}, - {"name": "clown_tv_station", "coord": [36, 16]}, - {"name": "kid_with_nds_29", "coord": [52, 46]}, - {"name": "kid_with_nds_30", "coord": [54, 46]} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/LakeVerity.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/LakeVerity.json deleted file mode 100644 index db9fba355e..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/LakeVerity.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "LakeVerity", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0512.bin; zone_event narc_0298", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "wwwwwwwwwwwwwwww################", - "wwwwwwwwwwwwwwww################", - "wwwwwwwwwwwwwwww################", - "wwwwwwwwwwwwwwww################", - "......wwwwwwwwww##....##########", - "......wwwwwwwwww##....##########", - ".......wwwwwwwww##......########", - ".......wwwwwwwww##......########", - ".......wwwwwww####~.....########", - ".......wwwwwww####~~....########", - "wwwwwwwwwwww####~~~~....########", - "wwwwwwwwwwww####.~~~....########", - "wwwwwwwwww####.~~~~~..##########", - "wwwwwwwwww####.~~~~...##########", - "wwwwwwww####...~~~..############", - "wwwwwwww####........############", - "##########..........############", - "##########..........############", - "############......##############", - "############......##############", - "############......##############", - "############......##############", - "##############..################", - "################################", - "################################", - "################################", - "################################", - "################################", - "################################", - "################################", - "################################", - "################################" - ], - "npcs": [ - {"name": "rowan", "coord": [16, 11], "facing": "north"}, - {"name": "counterpart", "coord": [17, 11], "facing": "north"}, - {"name": "starly_3", "coord": [16, 19], "facing": "north"} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghCity.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghCity.json deleted file mode 100644 index 523e5ac370..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghCity.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "OreburghCity", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0022.bin, files/fielddata/land_data/land_data_release/narc_0023.bin, files/fielddata/land_data/land_data_release/narc_0024.bin, files/fielddata/land_data/land_data_release/narc_0175.bin; zone_event narc_0044", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "#########################################......#################", - "#########################################...S..#################", - "#########################################......#################", - "#########################################......#################", - "#########################################...####################", - "#########################################...####################", - "#########################################....###################", - "############.#####.######.#.############.....###################", - "###########..#####..#####...############.....###################", - "#########....#####..#####...############.....###################", - "##.....########D######D######D##########.....###################", - "##..S..###..........................####........#.#...##########", - "##..................................####.....S........##########", - "#D..................................####..............##########", - "##..................................####..............####.#####", - "##..................................####..............####.#####", - "##.....###.............#######......#D##..............####.#####", - "##.....##.......####...#######...............#####....####.#####", - "#########.......####.#.#######..............######......#.######", - "#########.......####...#######..............######.....#########", - "#########.......#D####S###D###...............##D##.....#########", - "#########........................................#.....#########", - "#########........................................#.....#########", - "#########........................................#....##########", - "#########........................................#....##########", - "#########........................................#....##########", - "##########.#.#..########.........................#....##########", - "########################.######.############.....#....##########", - "########################.######.###........#.....#....##D#######", - "########################.######.###.#..#...#.....#.........#####", - "############################################.....###############", - "############################################.....###############", - "############################################.....###############", - "############################################.....###############", - "############################################.....###############", - "############################################.....###############", - "############################################.....##D############", - "###################################.########.....#...........###", - "###################################.....####.....#...........###", - "###################################.....####.....#...........###", - "###################################....#####.....#...........###", - "#####################################..#.........#...........###", - "#####################################..#..#.#....#....#.#....###", - "#####################################..#..#.#....#....#.#....###", - "###################################....#.....................###", - "###################################....#.....................###", - "###################################....#.....................###", - "###################################....#.....................###", - "###################################....#.....................###", - "###################################..###..#.#................###", - "###################################..###..#.#...#.#########.####", - "###################################..###........#.#########.####", - "###################################....#..........#####.....####", - "###################################...............#####.....####", - "#####################################.............##.........###", - "#####################################..####.#.....###.....######", - "#####################################..####.#S....###.....######", - "###########################################.......####.......###", - "############################################.....#####.......###", - "############################################DDDDD#####.....#####", - "######################################################..#..#####", - "################################################################", - "################################################################", - "################################################################" - ], - "points": { - "pokemon_center_door": [47, 20], - "pokemon_center_doorstep": [46, 21], - "gym_door": [26, 20], - "gym_doorstep": [26, 21], - "gate_mouth": [1, 13] - }, - "coord_events": [ - { - "name": "coord_event_0", - "coord": [10, 12], - "width": 1, - "length": 4, - "var": "VAR_OREBURGH_CITY_STATE", - "value": 0, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0044.bin coord_events[0] (266, 748), script 3" - }, - { - "name": "coord_event_1", - "coord": [6, 12], - "width": 1, - "length": 4, - "var": "VAR_OREBURGH_CITY_STATE", - "value": 2, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0044.bin coord_events[1] (262, 748), script 4" - } - ], - "npcs": [ - {"name": "hiker_0", "coord": [47, 26], "wander": {"min": [46, 25], "max": [48, 27]}}, - {"name": "school_kid_f_1", "coord": [32, 18], "wander": {"min": [32, 16], "max": [32, 20]}}, - {"name": "pokefan_f_2", "coord": [45, 38], "wander": {"min": [45, 37], "max": [45, 39]}}, - {"name": "youngster_4", "coord": [10, 16], "facing": "north"}, - {"name": "worker_15", "coord": [11, 25]}, - {"name": "worker_16", "coord": [56, 57], "wander": {"min": [55, 54], "max": [57, 60]}}, - {"name": "camper_17", "coord": [36, 19], "wander": {"min": [33, 19], "max": [39, 19]}}, - {"name": "worker_18", "coord": [37, 39], "wander": {"min": [36, 38], "max": [38, 40]}}, - {"name": "worker_19", "coord": [50, 41], "wander": {"min": [50, 38], "max": [50, 44]}}, - {"name": "guitarist_20", "coord": [23, 13], "wander": {"min": [22, 10], "max": [24, 16]}}, - {"name": "battle_girl_21", "coord": [36, 23], "wander": {"min": [36, 21], "max": [36, 25]}}, - {"name": "machop_22", "coord": [55, 56], "wander": {"min": [53, 54], "max": [57, 58]}} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGate.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGate.json deleted file mode 100644 index d0d6ba3ad5..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGate.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "OreburghGate", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0418.bin; zone_event narc_0250", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "################################", - "################################", - "################################", - "################################", - "############..#......###########", - "###########..........###########", - "###########........#.###########", - "###########..###################", - "###########..###################", - "###########..###################", - "###########.####################", - "############.###################", - "###########.####################", - "###########..###################", - "###########..###################", - "###########..###################", - "###########..###################", - "###########..###################", - "############.###################", - "###########..###################", - "#####...##...##########...#.####", - "####....##...##########.....####", - "###D........................D###", - "####...#....................####", - "###########..............#.#####", - "############.......#############", - "###########..#.#...#############", - "################################", - "################################", - "################################", - "################################", - "################################" - ], - "coord_events": [ - { - "name": "coord_event_0", - "coord": [7, 22], - "width": 1, - "length": 1, - "var": "VAR_OREBURGH_GATE_1F_HIKER_STATE", - "value": 1, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0250.bin coord_events[0] (7, 22), script 3" - } - ], - "npcs": [ - {"name": "picnicker_0", "coord": [24, 21], "facing": "south"}, - {"name": "camper_1", "coord": [17, 25], "facing": "north"}, - {"name": "hiker_10", "coord": [7, 20], "facing": "south"} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGym.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGym.json deleted file mode 100644 index f9298e4aca..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghGym.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "OreburghGym", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0228.bin; zone_event narc_0046", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "################################", - "################################", - "################################", - "#..........#####################", - "#..........#####################", - "#..........#####################", - "#..##.##...#####################", - "#..........#####################", - "#..##.##...#####################", - "#..........#####################", - "#..........#####################", - "###.....#.######################", - "#..........#####################", - "#####.##...#####################", - "#####.##...#####################", - "#..........#####################", - "#..........#####################", - "#..........#####################", - "#..........#####################", - "##.#....########################", - "##.#....########################", - "#..........#####################", - "#..#...#...#####################", - "#..S...S...#####################", - "#....D.....#####################", - "################################", - "################################", - "################################", - "################################", - "################################", - "################################", - "################################" - ], - "points": { - "entrance": [5, 23], - "roark": [5, 3], - "trainer_first_tier": [4, 18], - "trainer_second_tier": [7, 11] - }, - "npcs": [ - {"name": "roark_0", "coord": [5, 3], "facing": "south"}, - {"name": "gym_guide_1", "coord": [6, 23], "facing": "south"}, - {"name": "youngster_2", "coord": [4, 18], "facing": "east"}, - {"name": "youngster_3", "coord": [7, 11], "facing": "west"} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghMine.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghMine.json deleted file mode 100644 index 362506d13c..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghMine.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "OreburghMine", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0314.bin; zone_event narc_0197", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "################################", - "###############D################", - "#############.....##############", - "#############.....##############", - "#############.....##############", - "#############.....##############", - "#############.....##############", - "#############...################", - "#############...################", - "#############.....##############", - "#############..#..##############", - "###############...##############", - "#############...################", - "#############.....##############", - "###############...##############", - "########...####..............###", - "######.....##.................##", - "##.#..........................##", - "##....####################....##", - "##vvv##..................#....##", - "##....#..................#....##", - "##.##.#..................#....##", - "##..###..................###..##", - "##..###..................###..##", - "##....#..................#....##", - "##.##.#..................#....##", - "####..####################....##", - "####..####################....##", - "####.............#.#........####", - "######......................####", - "################################", - "################################" - ], - "points": { - "roark": [18, 28], - "corridor_exit_start": [14, 10], - "corridor_top.1": [14, 2], - "corridor_top.2": [15, 2], - "corridor_top.3": [16, 2] - }, - "npcs": [ - {"name": "roark", "coord": [18, 28], "facing": "east"}, - {"name": "worker_5", "coord": [8, 28], "facing": "north"}, - {"name": "worker_6", "coord": [26, 20], "facing": "west"}, - {"name": "worker_7", "coord": [21, 15], "facing": "north"}, - {"name": "machop_8", "coord": [12, 28], "wander": {"min": [11, 28], "max": [13, 28]}}, - {"name": "machop_9", "coord": [20, 15]}, - {"name": "machop_10", "coord": [27, 15], "facing": "north"} - ], - "items": [ - {"name": "item_3", "coord": [4, 28]}, - {"name": "item_4", "coord": [26, 24]} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghMineB1F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghMineB1F.json deleted file mode 100644 index 8b3ed98419..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/OreburghMineB1F.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "OreburghMineB1F", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0313.bin; zone_event narc_0196", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "################################", - "###########DDD##################", - "######...##...##.###############", - "######...##...##.###############", - "####.................###########", - "####.................###########", - "####.................###########", - "####.................###########", - "####.................###########", - "####.................###########", - "######.............#############", - "######.............#############", - "######.............#############", - "######.............#############", - "########.........###############", - "########.........###############", - "########.........###############", - "########.........###############", - "##########.....#################", - "##########.....#################", - "##########.....#################", - "##########.DDD.#################", - "############D###################", - "################################", - "################################", - "################################", - "################################", - "################################", - "################################", - "################################", - "################################", - "################################" - ], - "npcs": [ - {"name": "worker_0", "coord": [7, 4]}, - {"name": "twin_2", "coord": [6, 3]} - ], - "items": [ - {"name": "item_1", "coord": [19, 4]} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json deleted file mode 100644 index f50a0f7ecb..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse1F.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "PlayerHouse1F", - "source": { - "decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_186.bin; events_twinleaf_town_player_house_1f.json; decomp only, not checked on the console", - "revision": "c6689696d5fb", - "date": "2026-11-01" - }, - "tiles": [ - "#############", - "#############", - "#############", - "#SS..S####..#", - "#.....####..#", - "#SSS..SS....#", - "#...........#", - "#...........#", - "#..##.......#", - "##.##......##", - "##....D....##", - "#############" - ], - "points": { - "stairs": [10, 3], - "mom": [7, 8], - "door": [6, 10] - }, - "warps": [ - {"coord": [6, 10], "to_map": "TwinleafTown", "to_point": "player_house_door"} - ], - "coord_events": [ - { - "name": "moms_warning", - "coord": [6, 10], - "width": 1, - "length": 1, - "var": "VAR_PLAYER_HOUSE_STATE", - "value": 1, - "source": "pret/pokeplatinum res/field/events/events_twinleaf_town_player_house_1f.json coord_events[0] (6, 10), script 5" - }, - { - "name": "moms_call", - "coord": [6, 10], - "width": 1, - "length": 1, - "var": "CONSOLE_MOMS_CALL", - "value": 0, - "source": "console r45, r46 (2026-11-01): after her heal Mom calls us back as we step onto the mat (6, 10); no coord event in the decomp" - } - ], - "patches": [ - {"coord": [4, 8], "glyph": ".", "measured_by": "r32, 2026-11-01"} - ], - "npcs": [ - {"name": "mom_0", "coord": [7, 8], "facing": "north"} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse2F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse2F.json deleted file mode 100644 index 366de55b12..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/PlayerHouse2F.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "PlayerHouse2F", - "source": { - "decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_187.bin; events_twinleaf_town_player_house_2f.json; decomp only, not checked on the console", - "revision": "c6689696d5fb", - "date": "2026-11-01" - }, - "tiles": [ - "############", - "############", - "############", - "#######S####", - "#S#SSS...###", - "#........###", - "#..........#", - "#..........#", - "#........#.#", - "##.........#", - "##.......#.#", - "############" - ], - "points": { - "stairs": [8, 4], - "start": [4, 6] - }, - "coord_events": [ - { - "name": "coord_event_0", - "coord": [4, 5], - "width": 1, - "length": 1, - "var": "VAR_PLAYER_HOUSE_RIVAL_STATE", - "value": 0, - "source": "pret/pokeplatinum res/field/events/events_twinleaf_town_player_house_2f.json coord_events[0] (4, 5), script 7" - }, - { - "name": "coord_event_1", - "coord": [3, 6], - "width": 1, - "length": 1, - "var": "VAR_PLAYER_HOUSE_RIVAL_STATE", - "value": 0, - "source": "pret/pokeplatinum res/field/events/events_twinleaf_town_player_house_2f.json coord_events[1] (3, 6), script 8" - }, - { - "name": "coord_event_2", - "coord": [5, 6], - "width": 1, - "length": 1, - "var": "VAR_PLAYER_HOUSE_RIVAL_STATE", - "value": 0, - "source": "pret/pokeplatinum res/field/events/events_twinleaf_town_player_house_2f.json coord_events[2] (5, 6), script 9" - }, - { - "name": "coord_event_3", - "coord": [4, 7], - "width": 1, - "length": 1, - "var": "VAR_PLAYER_HOUSE_RIVAL_STATE", - "value": 0, - "source": "pret/pokeplatinum res/field/events/events_twinleaf_town_player_house_2f.json coord_events[3] (4, 7), script 10" - } - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RavagedPath.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RavagedPath.json deleted file mode 100644 index 5fbeb4969b..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RavagedPath.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "RavagedPath", - "source": { - "decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_435.bin, res/field/maps/data/map_data_436.bin; events_ravaged_path.json", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "................................", - "................................", - "........................########", - "........................########", - "........................##....##", - "############............##....##", - "############............##....##", - "####...#####............##www.##", - "##...#...############...##www###", - "##..####.############...##www###", - "##..####.#######...#######www###", - "##..####......##...#######www###", - "##.#.###########...##www##www###", - "###.##########.....##wwwwwwwww##", - "###..######.#########wwwwwwwww##", - "###..#####..#########wwwwwwwww##", - "####.#####vv#########www########", - "##...#####..#########www########", - "##..######..#########www###.....", - "##..######..#########www###.....", - "###.######..######wwwwwww##.....", - "##..######..######wwwwwww##.....", - "##.#.#####..######wwwwwww##.....", - "####.#####vv######www######.....", - "####.#......######www######.....", - "####........######www##.........", - "####....wwwwwwwwwwwww##.........", - "..##....wwwwwwwwwwwww##.........", - "..##....wwwwwwwwwwwww##.........", - "..#######www###########.........", - "..#######www###########.........", - "....#####www####................", - "....#####www####................", - "....##wwwwww####................", - "....##wwwwww####................", - "....##wwwwww####................", - "....##.wwww#.###................", - "....##.#....#.##................", - "....##..#.#...#########.........", - "....##....#..##########.........", - "....##.#.#.......#...###########", - "....###.....######...###########", - "....##############....#.......##", - "....##############....#.......##", - "..............####.....#......##", - "..............####...#######D###", - "..............####...###########", - "..............####...####.......", - "..............####...####.......", - "..............####...####.......", - "..............####...####.......", - "..............#####D#####.......", - "..............###########.......", - "..............###########......." - ], - "points": { - "entrance": [19, 50], - "east_end": [29, 44], - "above_exit": [28, 44], - "rocks.1": [11, 36], - "rocks.2": [7, 37], - "rocks.3": [12, 37], - "rocks.4": [8, 38], - "rocks.5": [10, 38], - "rocks.6": [10, 39], - "rocks.7": [13, 39], - "rocks.8": [7, 40], - "rocks.9": [9, 40], - "rocks.10": [17, 40], - "rocks.11": [12, 41], - "rocks.12": [22, 42], - "rocks.13": [22, 43], - "rocks.14": [23, 44], - "before_rocks.1": [21, 42], - "before_rocks.2": [21, 43] - }, - "items": [ - {"name": "item_27", "coord": [7, 38]}, - {"name": "item_28", "coord": [17, 11]}, - {"name": "item_29", "coord": [28, 5]}, - {"name": "item_30", "coord": [22, 44]} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse1F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse1F.json deleted file mode 100644 index 82a2a042e4..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse1F.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "RivalHouse1F", - "source": { - "decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_184.bin; events_twinleaf_town_rival_house_1f.json; decomp only, not checked on the console", - "revision": "c6689696d5fb", - "date": "2026-11-01" - }, - "tiles": [ - "#############", - "#############", - "#############", - "#..#####..###", - "#..####.....#", - "#....##..####", - "#...........#", - "#...........#", - "#.......##..#", - "##......##.##", - "##....D....##", - "#############" - ], - "points": { - "door": [6, 10], - "stairs": [2, 3] - }, - "warps": [ - {"coord": [6, 10], "to_map": "TwinleafTown", "to_point": "rival_house_door"} - ], - "npcs": [ - {"name": "pokefan_f_0", "coord": [4, 6]} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse2F.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse2F.json deleted file mode 100644 index 3da7eb8d6a..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RivalHouse2F.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "RivalHouse2F", - "source": { - "decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_185.bin; events_twinleaf_town_rival_house_2f.json; decomp only, not checked on the console", - "revision": "c6689696d5fb", - "date": "2026-11-01" - }, - "tiles": [ - "############", - "############", - "############", - "############", - "###...S##S##", - "###........#", - "#..........#", - "#..........#", - "#.#........#", - "#.........##", - "#.#.......##", - "############" - ], - "points": { - "stairs": [3, 4], - "barry_talk": [8, 5] - }, - "npcs": [ - {"name": "barry", "coord": [9, 5], "facing": "north"} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json deleted file mode 100644 index 5b190bc277..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route201.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "Route201", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0005.bin, files/fielddata/land_data/land_data_release/narc_0006.bin; zone_event narc_0327", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "################################################################", - "################################################################", - "################################################################", - "##########################################~~~~~~~~~~~...........", - "##########################################~~~~~~~~~~~..S........", - "##########################################~~~~~~~~~~~...........", - "##########################################~~~~~~~~~~~...........", - "....####..........######..##....##..####..~~~~~~~~~##...........", - "....####..........######..##....##..####..~~~~~~~~~##...........", - "##........####............................~~~~~~~~~##vvvvv######", - "##........####............................~~~~~~~~~##.....######", - "..........................................~~~~~~~~~##.....######", - "vvvv####vvvvvvvvvvvvvvvvvvvvvvvv........S.~~~~~~~~~##.....######", - "....####............~~~~~~~~~~............................######", - "....................~~~~~~~~~~............................######", - "....................~~~~~~~~~~............................######", - "....................~~~~~~~~~~............................######", - "########.....S......##########............................######", - "########............##########............~~~~~~~~~~~~~~~~######", - "##############....############################~~~~~~~~~~~~######", - "##############....############################~~~~~~~~~~~~######", - "##############....##############################################", - "##############....##############################################", - "##############....##############################################", - "##############....##############################################" - ], - "points": { - "border_sign": [55, 4], - "potion_giver": [43, 3], - "potion_giver_talk_from": [44, 3], - "route_201_arrival": [15, 24], - "barry_warning": [15, 18], - "route_201_return": [0, 14], - "rowan_stop": [5, 14], - "route_201_east_end": [63, 5] - }, - "coord_events": [ - { - "name": "barry_warning", - "coord": [14, 18], - "width": 6, - "length": 1, - "var": "VAR_FOLLOWER_RIVAL_STATE", - "value": 0, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0327.bin coord_events[0] (110, 857), script 2" - }, - { - "name": "rowan_stop", - "coord": [5, 14], - "width": 1, - "length": 3, - "var": "VAR_FOLLOWER_RIVAL_STATE", - "value": 2, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0327.bin coord_events[1] (101, 853), script 5" - }, - { - "name": "coord_event_2", - "coord": [14, 19], - "width": 6, - "length": 1, - "var": "VAR_FOLLOWER_RIVAL_STATE", - "value": 1, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0327.bin coord_events[2] (110, 858), script 3" - } - ], - "patches": [ - {"coord": [8, 18], "glyph": "#", "measured_by": "r16, 2026-10-31"}, - {"coord": [9, 18], "glyph": "#", "measured_by": "r16, 2026-10-31"}, - {"coord": [10, 18], "glyph": "#", "measured_by": "r16, 2026-10-31"}, - {"coord": [11, 18], "glyph": "#", "measured_by": "r16, 2026-10-31"}, - {"coord": [12, 18], "glyph": "#", "measured_by": "r16, 2026-10-31"}, - {"coord": [13, 18], "glyph": "S", "measured_by": "r16, 2026-10-31"}, - {"coord": [13, 17], "glyph": ".", "measured_by": "r16, 2026-10-31"} - ], - "npcs": [ - {"name": "pokemon_breeder_m_0", "coord": [27, 15], "wander": {"min": [26, 14], "max": [28, 16]}}, - {"name": "school_kid_m_9", "coord": [57, 8], "facing": "west"}, - {"name": "lass_11", "coord": [46, 16], "wander": {"min": [44, 16], "max": [48, 16]}} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json deleted file mode 100644 index 896620ce5c..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route202.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "Route202", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0014.bin, files/fielddata/land_data/land_data_release/narc_0173.bin; zone_event narc_0328", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "###########........#################", - "###########........#################", - "###########........#################", - "###########........#################", - "###~~~~~~........S..~~~~~......#####", - "###~~~~~~...........~~~~~......#####", - "###.~~~~~...........~~~~~......#####", - "###~~~~~~...........~~~~~......#####", - "###~~~~~~...........~~~~~......#####", - "###~~~~~~.....########.........#####", - "#########vvvvv########vvvvvv...~~###", - "#########.....########~~~~~~~~~~~###", - "#######.......########~~~~~~~~~~~###", - "#######...........####~~~~~~~~~~~###", - "###...............####~~~~~~~~~~~###", - "###...............####~~~~~~~~~~~###", - "###............S..####~~~~~~~~~~~###", - "###...............####~~~~~~~~~~~###", - "###...............####..~~~~~~~~~###", - "###~~~~##...............~~~~~~~~~###", - "###~~~~##.................~~~~~~~###", - "###~~~~##....................~~~~###", - "###~~~~##......................#####", - "###~~~~##vvvvv########vvvvvvvvv#####", - "###~~~~##.....########.........#####", - "###~~~~~~......~~~~~~...S......#####", - "###~~~~~~......~~~~~~..........#####", - "###~~~~~~......~~~~~~..........#####", - "###~~~~~~......~~~~~~..........#####", - "###~~~~~~......~~~~~~..........#####", - "#####################..........#####", - "#####################..........#####" - ], - "points": { - "catch_spot": [19, 27], - "save_spot": [30, 24], - "trainer_tristan": [7, 13], - "trainer_natalie": [22, 18], - "trainer_logan": [26, 4], - "exit_jubilife": [15, 0], - "jubilife_exit.1": [11, 0], - "jubilife_exit.2": [12, 0], - "jubilife_exit.3": [13, 0], - "jubilife_exit.4": [14, 0], - "jubilife_exit.5": [15, 0], - "jubilife_exit.6": [16, 0], - "jubilife_exit.7": [17, 0], - "jubilife_exit.8": [18, 0], - "jubilife_exit.9": [11, 1], - "jubilife_exit.10": [12, 1], - "jubilife_exit.11": [13, 1], - "jubilife_exit.12": [14, 1], - "jubilife_exit.13": [15, 1], - "jubilife_exit.14": [16, 1], - "jubilife_exit.15": [17, 1], - "jubilife_exit.16": [18, 1], - "jubilife_exit.17": [11, 2], - "jubilife_exit.18": [12, 2], - "jubilife_exit.19": [13, 2], - "jubilife_exit.20": [14, 2], - "jubilife_exit.21": [15, 2], - "jubilife_exit.22": [16, 2], - "jubilife_exit.23": [17, 2], - "jubilife_exit.24": [18, 2], - "jubilife_exit.25": [11, 3], - "jubilife_exit.26": [12, 3], - "jubilife_exit.27": [13, 3], - "jubilife_exit.28": [14, 3], - "jubilife_exit.29": [15, 3], - "jubilife_exit.30": [16, 3], - "jubilife_exit.31": [17, 3], - "jubilife_exit.32": [18, 3], - "catch_save_area.1": [15, 25], - "catch_save_area.2": [16, 25], - "catch_save_area.3": [17, 25], - "catch_save_area.4": [18, 25], - "catch_save_area.5": [19, 25], - "catch_save_area.6": [20, 25], - "catch_save_area.7": [21, 25], - "catch_save_area.8": [15, 26], - "catch_save_area.9": [16, 26], - "catch_save_area.10": [17, 26], - "catch_save_area.11": [18, 26], - "catch_save_area.12": [19, 26], - "catch_save_area.13": [20, 26], - "catch_save_area.14": [21, 26], - "catch_save_area.15": [15, 27], - "catch_save_area.16": [16, 27], - "catch_save_area.17": [17, 27], - "catch_save_area.18": [18, 27], - "catch_save_area.19": [19, 27], - "catch_save_area.20": [20, 27], - "catch_save_area.21": [21, 27], - "catch_save_area.22": [21, 28], - "catch_save_area.23": [21, 29], - "after_training_area.1": [18, 25], - "after_training_area.2": [19, 25], - "after_training_area.3": [20, 25], - "after_training_area.4": [18, 26], - "after_training_area.5": [19, 26], - "after_training_area.6": [20, 26], - "after_training_area.7": [18, 27], - "after_training_area.8": [19, 27], - "after_training_area.9": [20, 27], - "catch_grass.1": [18, 25], - "catch_grass.2": [19, 25], - "catch_grass.3": [20, 25], - "catch_grass.4": [18, 26], - "catch_grass.5": [19, 26], - "catch_grass.6": [20, 26], - "catch_grass.7": [18, 27], - "catch_grass.8": [19, 27], - "catch_grass.9": [20, 27] - }, - "coord_events": [ - { - "name": "catch_tutorial", - "coord": [21, 25], - "width": 1, - "length": 5, - "var": "VAR_ROUTE_202_STATE", - "value": 0, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0328.bin coord_events[0] (180, 825), script 6" - } - ], - "patches": [ - {"coord": [15, 28], "glyph": "#", "measured_by": "r253, 2026-11-01"}, - {"coord": [16, 28], "glyph": "#", "measured_by": "r253, 2026-11-01"}, - {"coord": [17, 28], "glyph": "#", "measured_by": "r253, 2026-11-01"}, - {"coord": [18, 28], "glyph": "#", "measured_by": "r253, 2026-11-01"}, - {"coord": [19, 28], "glyph": "#", "measured_by": "r253, 2026-11-01"}, - {"coord": [20, 28], "glyph": "#", "measured_by": "r253, 2026-11-01"}, - {"coord": [15, 29], "glyph": "#", "measured_by": "r253, 2026-11-01"}, - {"coord": [16, 29], "glyph": "#", "measured_by": "r253, 2026-11-01"}, - {"coord": [17, 29], "glyph": "#", "measured_by": "r253, 2026-11-01"}, - {"coord": [18, 29], "glyph": "#", "measured_by": "r253, 2026-11-01"}, - {"coord": [19, 29], "glyph": "#", "measured_by": "r253, 2026-11-01"}, - {"coord": [20, 29], "glyph": "#", "measured_by": "r253, 2026-11-01"}, - {"coord": [21, 30], "glyph": "#", "measured_by": "r254, 2026-11-01"} - ], - "npcs": [ - {"name": "youngster_0", "coord": [7, 13], "facing": "south"}, - {"name": "lass_1", "coord": [22, 18], "facing": "south"}, - {"name": "youngster_2", "coord": [26, 4], "facing": "south"}, - {"name": "youngster_7", "coord": [10, 8], "wander": {"min": [9, 8], "max": [11, 8]}} - ], - "items": [ - {"name": "item_8", "coord": [3, 6]} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json deleted file mode 100644 index 8a66bb297f..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route203.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "Route203", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0016.bin, files/fielddata/land_data/land_data_release/narc_0019.bin, files/fielddata/land_data/land_data_release/narc_0020.bin; zone_event narc_0329", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "#############################################################", - "#############################################################", - "#############################################################", - "#############################################################", - "#####..............###########..~~~~#########################", - "#####..............###########...~~~..~~~~~~~~~~~~~~#########", - "#####..................#######........~~~~~~~~~~~~~.....#####", - "#####..................#######wwwwww##~~~~~~~~~~~....S..#####", - "#####~~~~##...####.....#......wwwwww##~~~~~~~~~~~.......#####", - "#####~~~~##...####.....#......wwwwww##~~~~~~~~~~~.......D####", - "#####~~~~##...####.....#..S...wwwwww##~~~~~~~~~~~.......#####", - "#####~~~~##...####.....#......wwwwww##~~~~~~~~~~~.......#####", - "#####~~~~##...####.....#....~~~~~~~~##~~~~~~~~~S...~~~..#####", - "#####~~~~##...####vvvvv#...~~...~.~~##~~~~~~~~~....~~~..#####", - "#####~~................#....######~~##.....~~....##~~~..#####", - "#####..................#....######~~##vvvvvvvvvvv##~~~..#####", - "#####....S.............#....~~~..~~~.............~~~~~..#####", - "...........................~~~~...~~.............~~~~~..#####", - "...........................~~.....~~.............~~~~~..#####", - "..........................~~~~.....~##..........~~~~~~..#####", - "..........................~~~~~.~~~~#########################", - "#############################################################", - "#############################################################", - "#############################################################" - ], - "points": { - "gate_exit.1": [55, 9], - "gate_exit.2": [54, 9], - "gate_exit.3": [55, 8], - "gate_exit.4": [55, 10], - "west_line.1": [24, 18], - "west_line.2": [25, 18], - "west_line.3": [26, 18], - "west_line.4": [27, 18], - "west_line.5": [28, 18], - "west_line.6": [29, 18], - "west_line.7": [30, 18], - "west_line.8": [31, 18], - "west_line.9": [32, 18], - "west_line.10": [33, 18], - "west_line.11": [34, 18], - "west_line.12": [35, 18], - "west_line.13": [36, 18], - "west_line.14": [37, 18], - "west_line.15": [38, 18], - "west_line.16": [39, 18], - "west_line.17": [40, 18], - "west_line.18": [41, 18], - "west_line.19": [42, 18] - }, - "coord_events": [ - { - "name": "coord_event_0", - "coord": [5, 17], - "width": 1, - "length": 4, - "var": "VAR_ROUTE_203_RIVAL_STATE", - "value": 0, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0329.bin coord_events[0] (196, 757), script 6" - } - ], - "npcs": [ - {"name": "youngster_1", "coord": [13, 10], "facing": "west"}, - {"name": "lass_2", "coord": [43, 17], "facing": "west"}, - {"name": "youngster_3", "coord": [31, 16], "facing": "south"}, - {"name": "lass_4", "coord": [49, 8], "facing": "south"}, - {"name": "lass_12", "coord": [15, 17], "wander": {"min": [14, 17], "max": [16, 17]}} - ], - "items": [ - {"name": "item_10", "coord": [31, 5]}, - {"name": "item_11", "coord": [20, 10]} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204North.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204North.json deleted file mode 100644 index e98b687858..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204North.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "Route204North", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0031.bin; zone_event narc_0331", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "########.#...###################", - "########.#...###################", - "####.....#...########.##########", - "##########...#......#.##########", - "#####....#...#......#...........", - "#####....#~~~#......#...######..", - "#####....#~~~###################", - "##########~~~.........##########", - "##........~~~~~.......##########", - "##..######.~~~~..S....##########", - "##..#....#.~~~........##########", - "##..#....#.~~.........##########", - "##########..............########", - "##########..............########", - "##########...####~~~~~~~########", - "##########vvv####~~~~~~~########", - "##########...######~~~~~########", - "##########...######~~~~~########", - "##########...######~~~~~########", - "##########...######~.~~~########", - "##########............~~########", - "##########............##########", - "##########............##########", - "##########...##.......##########", - "##########...##.......##########", - "##########...###################", - "##########...#######.###########", - "##########............##########", - "##########............##########", - "##########............##########", - "##########............##########", - "##########...###################" - ], - "points": { - "cave_exit": [20, 26], - "floaroma_waypoints.1": [20, 27], - "floaroma_waypoints.2": [11, 27], - "floaroma_waypoints.3": [11, 22], - "floaroma_waypoints.4": [19, 22], - "floaroma_waypoints.5": [19, 12], - "floaroma_waypoints.6": [11, 12], - "floaroma_waypoints.7": [11, 5], - "floaroma_path.1": [10, 0], - "floaroma_path.2": [11, 0], - "floaroma_path.3": [12, 0], - "floaroma_path.4": [10, 1], - "floaroma_path.5": [11, 1], - "floaroma_path.6": [12, 1], - "floaroma_path.7": [10, 2], - "floaroma_path.8": [11, 2], - "floaroma_path.9": [12, 2], - "floaroma_path.10": [10, 3], - "floaroma_path.11": [11, 3], - "floaroma_path.12": [12, 3], - "floaroma_path.13": [10, 4], - "floaroma_path.14": [11, 4], - "floaroma_path.15": [12, 4], - "floaroma_path.16": [10, 5], - "floaroma_path.17": [11, 5], - "floaroma_path.18": [12, 5], - "trainer_twin_left": [15, 7], - "trainer_twin_right": [16, 7], - "trainer_taylor": [17, 20], - "trainer_brandon": [20, 12] - }, - "patches": [ - {"coord": [12, 6], "glyph": "#", "measured_by": "r248a, 2026-10-31"} - ], - "npcs": [ - {"name": "bug_catcher_brandon", "coord": [20, 12]}, - {"name": "pokemon_breeder_f_1", "coord": [17, 20]}, - {"name": "twin_2", "coord": [15, 7], "facing": "south"}, - {"name": "twin_3", "coord": [16, 7], "facing": "south"}, - {"name": "youngster_4", "coord": [15, 28], "wander": {"min": [14, 28], "max": [16, 28]}}, - {"name": "ace_trainer_f_10", "coord": [31, 4], "facing": "west"} - ], - "items": [ - {"name": "item_9", "coord": [2, 10]}, - {"name": "item_11", "coord": [20, 23]} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204South.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204South.json deleted file mode 100644 index ed745aa65e..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/Route204South.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "Route204South", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0021.bin; zone_event narc_0330", - "revision": "default branch", - "date": "2026-11-01" - }, - "tiles": [ - "################################", - "###########D###.################", - "########..S.....################", - "########........################", - "########........################", - "##########...~##################", - "##########...~##################", - "##########..~~##################", - "##########..~~###wwwwwwwww######", - "####~~~~~~~~~~###wwwwwwwww######", - "####~~~~~~~~~~###wwwwwwwww######", - "####~~~~~~~~~~###wwwwwwwww######", - "####~~~~~~~~~~###wwwwwwwww....##", - "####~~~~~~~~~~###wwwwwwwww....##", - "####~~~~~~~~~~#..wwwww##########", - "##~~~~~~~~~~~~#..wwwww##########", - "##~~~~~~~~~~~~#..wwwww##########", - "##~~~~~~~~~~~~S.################", - "##~~~~~.........#.##############", - "##~~~~~.........#.##############", - "##~~~~~.........#.##############", - "##.#########....#.##############", - "##wwwwwwwww#....#......~########", - "##wwwwwwwww#....#wwwww~~########", - "##wwwwwwwww#....#wwwww~~########", - "##wwwwwwwww#....#wwwww~~########", - "##wwwwwwwww#....#wwwww~~########", - "##wwwwwwwww#....#wwwww~~########", - "##..######.#....#..~~~~~########", - "##..######.#....#...~~~~########", - "##########.#....#.......########", - "############....######..########" - ], - "points": { - "below_door": [11, 2], - "jubilife_entry.1": [14, 31], - "jubilife_entry.2": [14, 30], - "jubilife_entry.3": [14, 29], - "trainer_samantha": [11, 8], - "trainer_sarah": [12, 21], - "trainer_tyler": [7, 18] - }, - "npcs": [ - {"name": "lass_0", "coord": [12, 21]}, - {"name": "lass_1", "coord": [11, 8], "facing": "south"}, - {"name": "youngster_2", "coord": [7, 18]}, - {"name": "youngster_3", "coord": [13, 2], "wander": {"min": [12, 2], "max": [14, 2]}} - ], - "items": [ - {"name": "item_7", "coord": [2, 28]}, - {"name": "item_8", "coord": [29, 12]}, - {"name": "item_9", "coord": [17, 18]} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RowansLab.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RowansLab.json deleted file mode 100644 index 0ba5cde636..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/RowansLab.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "RowansLab", - "source": { - "decomp_file": "pret/pokeplatinum: res/field/maps/data/map_data_222.bin; events_sandgem_town_pokemon_research_lab.json; decomp only, not checked on the console", - "revision": "c6689696d5fb", - "date": "2026-11-01" - }, - "tiles": [ - "######################", - "######################", - "###SSSS####S####.###S#", - "#........#...##......#", - "#.###................#", - "#...................##", - "#.............#....###", - "###........###########", - "###........###########", - "###........###########", - "#............#########", - "##..........##########", - "##..........##########", - "###........###########", - "###........###########", - "###....D...###########", - "######################", - "######################" - ], - "points": { - "door": [7, 15], - "rowan_talk": [7, 6] - }, - "warps": [ - {"coord": [7, 15], "to_map": "SandgemTown", "to_point": "lab_door"} - ], - "npcs": [ - {"name": "rowan", "coord": [7, 5], "facing": "south"}, - {"name": "scientist_m_1", "coord": [3, 12], "wander": {"min": [1, 12], "max": [5, 12]}}, - {"name": "scientist_f_2", "coord": [10, 12]} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json deleted file mode 100644 index 9c3f88a9d9..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/SandgemTown.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "SandgemTown", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0007.bin, files/fielddata/land_data/land_data_release/narc_0173.bin; zone_event narc_0396", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "########################......####", - "########################......####", - "####################..........####", - "####################..........####", - "####################..........####", - "####################..........####", - "####################......########", - "####################......########", - "####################......########", - "####################......########", - "########D##....##D##S....S#D######", - ".....S........................####", - "..............................####", - "..............................####", - "..............................####", - "..............................####", - "######........................####", - "######........................####", - "######.........#####..........####", - "##########.....#####..........####", - "##########.....#####..........####", - "##########.....#####..........####", - "#######D##....S#D###..S.......####", - "######........................####", - "######........................####", - "######........................####", - "######....................########", - "####################......########", - "####################......########", - "####################......########", - "####################......########", - "####################......########" - ], - "points": { - "route_202_entry_stop": [29, 5], - "lab_door": [8, 10], - "pokemon_center_door": [17, 10], - "pokemon_center_sign": [20, 10], - "poke_mart_door": [27, 10], - "front_row_east_end": [29, 11], - "below_sign_gap.1": [21, 11], - "below_sign_gap.2": [22, 11], - "below_sign_gap.3": [23, 11], - "below_sign_gap.4": [24, 11], - "sandgem_arrival": [0, 12], - "dawn_stop": [4, 12] - }, - "coord_events": [ - { - "name": "dawn_walks_us_to_the_lab", - "coord": [4, 10], - "width": 1, - "length": 6, - "var": "VAR_SANDGEM_TOWN_STATE", - "value": 0, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0396.bin coord_events[0] (164, 842), script 2" - } - ], - "npcs": [ - {"name": "youngster_0", "coord": [27, 23], "wander": {"min": [26, 22], "max": [28, 24]}} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json deleted file mode 100644 index d8fa00a75c..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/TwinleafTown.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "TwinleafTown", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0000.bin; zone_event narc_0389", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "############........############", - "#######....#........#....#######", - "#######....#........#....#######", - "############........############", - "######....................######", - "######....................######", - "######....................######", - "######..#####.............######", - "######..#####......####...######", - "######..#####......####...######", - "######..######.....####...######", - "######..#D###S.....#D##...######", - "######....................######", - "######....................######", - "######..........S.........######", - "######....................######", - "######....................######", - "######.............#####..######", - "######...####......#####..######", - "######...####......#####..######", - "######...####.....######..######", - "######...#D##.....S#D###..######", - "######....................######", - "######....................######", - "######....................######", - "######....................######", - "######....................######", - "######......wwwwwwww......######", - "############wwwwwwww############", - "############wwwwwwww############", - "############wwwwwwww############", - "############wwwwwwww############" - ], - "points": { - "house_doorstep": [20, 22], - "barry_doorstep": [9, 12], - "twinleaf_north_exit": [15, 0], - "player_house_door": [20, 21], - "rival_house_door": [9, 11] - }, - "coord_events": [ - { - "name": "coord_event_0", - "coord": [12, 3], - "width": 8, - "length": 1, - "var": "VAR_TWINLEAF_TOWN_GUITARIST_TRIGGER_STATE", - "value": 1, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0389.bin coord_events[0] (108, 867), script 4" - }, - { - "name": "barry_doorstep", - "coord": [9, 12], - "width": 1, - "length": 1, - "var": "VAR_TWINLEAF_TOWN_RIVAL_TRIGGER_STATE", - "value": 0, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0389.bin coord_events[1] (105, 876), script 2" - } - ], - "npcs": [ - {"name": "pokemon_breeder_f_0", "coord": [23, 14], "wander": {"min": [22, 14], "max": [24, 14]}}, - {"name": "collector_1", "coord": [12, 25]}, - {"name": "guitarist_3", "coord": [12, 5]} - ] -} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/VerityLakefront.json b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/VerityLakefront.json deleted file mode 100644 index de9e05d987..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps/VerityLakefront.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "format": "pokesim-runtime-map/2", - "name": "VerityLakefront", - "source": { - "decomp_file": "pret/pokediamond: files/fielddata/land_data/land_data_release/narc_0004.bin; zone_event narc_0319", - "revision": "default branch", - "date": "2026-10-31" - }, - "tiles": [ - "....############################", - "....############################", - "....############################", - "....############################", - "################################", - "################################", - "################################", - "################################", - "################################", - "################################", - "################################", - "################..##############", - "################..##############", - "################..##############", - "##############.S....<...........", - "##############......<.....####..", - "##############......<.....####..", - "##############......<...........", - "##############......<...........", - "##############......#vvvvv##vvvv", - "################..........##....", - "################................", - "##################..............", - "##################..............", - "################################", - "################################", - "################################", - "################################", - "################################", - "################################", - "################################", - "################################" - ], - "points": { - "lakefront_arrival": [31, 23], - "below_lake_entrance": [17, 14], - "lake_entrance": [17, 12] - }, - "coord_events": [ - { - "name": "lake_entrance", - "coord": [16, 12], - "width": 2, - "length": 1, - "var": "VAR_VERITY_LAKEFRONT_STATE", - "value": 0, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0319.bin coord_events[0] (80, 844), script 3" - }, - { - "name": "coord_event_1", - "coord": [16, 12], - "width": 2, - "length": 1, - "var": "VAR_VERITY_LAKEFRONT_STATE", - "value": 3, - "source": "pret/pokediamond files/fielddata/eventdata/zone_event_release/narc_0319.bin coord_events[1] (80, 844), script 4" - } - ] -} diff --git a/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/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 54765460e5..f44aad54c2 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1470,6 +1470,10 @@ file(GLOB LIBRARY_SOURCES Source/Pokemon/Navigation/Pokemon_MapData.cpp Source/Pokemon/Navigation/Pokemon_MapData.h Source/Pokemon/Navigation/Pokemon_MapData_Tests.cpp + Source/Pokemon/Navigation/Pokemon_RuntimeMap.cpp + Source/Pokemon/Navigation/Pokemon_RuntimeMap.h + Source/Pokemon/Navigation/Pokemon_RuntimeMap_Tests.cpp + Source/Pokemon/Navigation/Pokemon_RuntimeMap_Tests.h Source/Pokemon/Navigation/Pokemon_Navigation_Tests.h Source/Pokemon/Navigation/Pokemon_RouteLog.cpp Source/Pokemon/Navigation/Pokemon_RouteLog.h diff --git a/devtools/maps/README.md b/devtools/maps/README.md index a3c713c0dc..37f94d3416 100644 --- a/devtools/maps/README.md +++ b/devtools/maps/README.md @@ -1,5 +1,11 @@ # Map tools for AutoStory routes +BDSP maps are now pinned external build resources. See +[RESOURCE_MAPS.md](RESOURCE_MAPS.md) for preparation, publishing prerequisites +and native validation. The Sinnoh decomp CLI below forwards to the shared +PokemonSimulation parser; set `PYTHONPATH=/path/to/pokesim/src`. Author map +corrections and run decoder/builder tests there. Image fetch/overlay remain local. + Offline helpers that give a route planner its tile grid (standard library only, Python 3.8+): a decoder for the Pokemon Platinum and Diamond decompilations, an overlay that draws a grid and a run's routes over the map diff --git a/devtools/maps/RESOURCE_MAPS.md b/devtools/maps/RESOURCE_MAPS.md new file mode 100644 index 0000000000..874a94d618 --- /dev/null +++ b/devtools/maps/RESOURCE_MAPS.md @@ -0,0 +1,91 @@ +# BDSP map resources + +BDSP's existing `bdsp_map_json()` API embeds the 22 maps from prepared +`Packages/Resources/Pokemon/Maps/bdsp`. The shared nav-core reader handles their +compact v2 format. Source-map copies and the duplicate BDSP builder/Sinnoh parser +are removed; Python authoring and its tests live in PokemonSimulation. Native +map/route/story tests remain here. No controller behavior changes in this handoff. + +## Publishing prerequisite and pins + +[PokemonSimulation #65](https://github.com/dayfine/PokemonSimulation/pull/65) +merged by squash as `4de7742a764d27293b4525d6ad2a6fc8a95f3e3a`. The checked-in resource lock and +CI tools now use that approved revision. The squash preserves the reviewed +candidate's manifest/license and all 22 map bytes. Normal resource preparation +verifies the published ancestry, manifest hash and exports before configuration. + +Before enrichment, all 22 maps matched #226 at +`fc56a720b9c0b1f4d945dbef382d61b0a6382b97` byte-for-byte. The publishing change +adds resolved Diamond/Platinum-baseline identities, quantities, collection flags +and provenance to 15 pickups, with no geometry, point, NPC, trigger or warp edits. +Pickup names and quantity 1 are omitted on the wire: `source_event: 3` +restores internal label `item_3`; `item: escape_rope` identifies the reward. +Explicit custom names and quantities above one remain supported. +These are explicitly remake baselines, not verified BDSP item data. Reading a +map never grants an item or proves that a controller action collected it. + +The shared v2 normalizer preserves all JSON extensions. The existing native +`MapData` model consumes its known navigation fields (including collection +flags); typed battle/inventory consumers for reward metadata remain future work. + +## Prepare and build + +On a fresh Arduino checkout, clone Packages before preparing the map subdirectory. +Skip this command when the checkout already has its Packages repository: + +```sh +git clone --depth 1 --branch master https://github.com/PokemonAutomation/Packages.git /path/to/Arduino-Source/Packages +``` + +Use the same approved tool revision pinned in CI. Preparation reads the separate +data revision from the resource lock; tools and data pins are independent. + +```sh +git clone https://github.com/dayfine/PokemonSimulation.git /path/to/pokesim +git -C /path/to/pokesim checkout 4de7742a764d27293b4525d6ad2a6fc8a95f3e3a +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 +cmake --build /path/to/build -j4 +``` + +Prepared resources include license/provenance and are ignored by Git. CMake +checks their exact inventory, hashes and publishing ancestry before embedding. +Missing, modified, extra or unapproved files fail; configuration never fetches +maps or falls back to an old authored directory. + +To deliberately advance the approved BDSP data pin after publication: + +```sh +PYTHONPATH=/path/to/pokesim/src python3 -m pokesim.maps.consumer_sync update \ + --layout resources --source /path/to/pokesim --consumer /path/to/Arduino-Source \ + --game bdsp --revision FULL_APPROVED_PUBLISHING_SHA +``` + +Update validates the existing lock and files before changing either; it preserves +independent game pins. Commit the resource lock, not the generated Packages files. +Do not rewrite the resource pin merely to make a locally edited map pass. + +## Offline tools and tests + +The existing decomp CLI forwards to the publisher parser. Fetch and overlay +commands still work without the Python package. For decompilation, supply the +approved tools on PYTHONPATH: + +```sh +PYTHONPATH=/path/to/pokesim/src python3 -m devtools.maps decomp ROUTE_204_NORTH --text +python3 -m unittest devtools.maps.tests.test_overlay +cmake -S devtools/runtime-map-tests -B build/runtime-map-tests +cmake --build build/runtime-map-tests -j4 +ctest --test-dir build/runtime-map-tests --output-on-failure +python3 devtools/runtime-map-tests/prepare_parity.py --publisher /path/to/candidate \ + --publisher-revision 4de7742a764d27293b4525d6ad2a6fc8a95f3e3a --out /tmp/bdsp-map-pairs.json +build/runtime-map-tests/RuntimeMapTests /tmp/bdsp-map-pairs.json +``` + +Parity fixtures require a clean checkout of the exact supplied candidate SHA. +They can test an unmerged candidate; that does not approve it as a resource pin. +The native schema workflow compares 150 complete records on three platforms. +Full application build/story validation is a separate gate after publication. +This fork resource setup is not an upstream Packages submission. diff --git a/devtools/maps/__main__.py b/devtools/maps/__main__.py index 57656e2a62..5a89522c7f 100644 --- a/devtools/maps/__main__.py +++ b/devtools/maps/__main__.py @@ -10,7 +10,7 @@ import sys from pathlib import Path -from . import decomp, fetch, grid_source, route_log, svg +from . import fetch, grid_source, route_log, svg CACHE = Path.home() / '.cache' / 'maps' @@ -26,7 +26,7 @@ def main(argv=None): sub = parser.add_subparsers(dest='command', required=True) dec = sub.add_parser('decomp', help="decode a map's tile grid and objects from the pret decompilation") dec.add_argument('map', help='the map header name, e.g. ROUTE_204_NORTH or RAVAGED_PATH') - dec.add_argument('--repo', default=decomp.Platinum.REPO, choices=[decomp.Platinum.REPO, decomp.Diamond.REPO], + dec.add_argument('--repo', default="pret/pokeplatinum", choices=["pret/pokeplatinum", "pret/pokediamond"], help='which decompilation to read (default: pret/pokeplatinum)') dec.add_argument('--rect', nargs=4, type=int, metavar=('X0', 'Z0', 'W', 'H'), help="world rectangle to decode; default: the map's own cell(s)") @@ -53,6 +53,10 @@ def main(argv=None): args = parser.parse_args(argv) if args.command == 'decomp': + try: + from pokesim.maps import dppt_map_decomp_parser as decomp + except ModuleNotFoundError: + parser.error('Set PYTHONPATH to the pinned PokemonSimulation src directory; see devtools/maps/RESOURCE_MAPS.md.') game = decomp.game_for(args.repo, args.cache / 'decomp', args.local) decoded = decomp.decode(game, args.map, tuple(args.rect) if args.rect else None) print(decomp.render_text(decoded) if args.text else decomp.render_cpp(decoded)) diff --git a/devtools/maps/bdsp_maps.py b/devtools/maps/bdsp_maps.py deleted file mode 100644 index 6454bb3b47..0000000000 --- a/devtools/maps/bdsp_maps.py +++ /dev/null @@ -1,671 +0,0 @@ -"""Writes BDSP AutoStory's maps as JSON, from the Pokemon Diamond decompilation. - -TODO: move the maps to PokemonAutomation/Packages (Resources/PokemonBDSP/Maps) when ready. - -Usage: - python3 devtools/maps/bdsp_maps.py # rewrite every map in OUT - python3 devtools/maps/bdsp_maps.py Route202 # rewrite one - python3 devtools/maps/bdsp_maps.py --check # regenerate, fail on any difference - -Each map is a rectangle of the decompilation's world grid (MAPS below), -decoded tile for tile by devtools/maps/decomp.py from the files cached in -~/.cache/maps/decomp (`python3 -m devtools.maps decomp` fetches missing ones -with `gh api`; this script never fetches, it names the file it lacks). BDSP -keeps Diamond/Pearl's layout, so Diamond is read unless a map says otherwise. -The tables below add what the decompilation doesn't say or BDSP changed: - -- POINTS: named tiles the story walks from and to. A region (a set of - tiles, in order where the order matters) is written as numbered points, - `.1`, `.2`, ...: the shared schema (Pokemon_MapData.h) - has single-tile points only. -- OBJECTS: which of the events file's objects BDSP shows. People hidden by - a story flag are left out, and people who stand still or wander are kept, - unless an entry here says otherwise (`show`, `hide`, `still`, `at`), or - adds someone the console found that Diamond doesn't have. -- WARP_DOORS: warp tiles written 'D' so that no plan steps onto them; the - other warp tiles are written '.', since the story arrives on them. -- WARPS: doors that lead to a point of another map in OUT. -- PATCHES: tiles measured on the console that differ from Diamond's, with - the run that measured them. The map's tiles stay the decompilation's; the - loader applies the patches. -- COORD_EVENTS: names for the events file's coord events (the scene - triggers), and triggers found on the console that the decompilation - doesn't have, with the run that found them. - -The legend is the schema's: '#' wall, '.' open, '~' tall grass, 'w' water, -'^' 'v' '<' '>' ledges hopped the arrow's way, 'D' a door or cave mouth -(walked into) or a warp tile in WARP_DOORS, 'S' a sign or mailbox (read by -facing it; a sign script on a wall too). Items on the ground are objects of -kind "item"; rocks, cut trees, berry soil and steam vents are '#'. - -Rules: terrain from the decomp, people and BDSP's edits from the console; -a route is coded only over tiles a human or a probe has walked; a map change -re-runs every checkpoint that walks it. Bulbapedia's DP images (12 px rows -for 16 px columns, cliffs painted tall) are for drawing routes over, not for -terrain. - -Each map lists its events file's coord events under "coord_events" (the -shared schema, Pokemon_MapData.h, leaves the key to the game; BDSP AutoStory's -Data/PokemonBDSP_AutoStory_CoordEvents.h reads it): `width` columns and -`length` rows from local (x, y), armed while the story variable `var` holds -`value`, and a `source` that names the file, the event's index, its world -tile and its script. Boxes are clipped to the map's rectangle. -""" -import argparse -import json -import os -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).resolve().parents[2])) -from devtools.maps import decomp # noqa: E402 - -OUT = 'SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Maps' -CACHE = Path.home() / '.cache' / 'maps' / 'decomp' -# When the cache was filled from the decompilations' default branches. -READ = '2026-10-31' - -LEDGES = {'north': '^', 'south': 'v', 'west': '<', 'east': '>'} -SIGN_GFX = ('MAILBOX',) -OBSTACLE_GFX = ('CUT_TREE', 'ROCK_SMASH', 'STRENGTH_BOULDER', 'BERRY_SOIL', 'VENT') -FACING = {'LOOK_NORTH': 'north', 'LOOK_SOUTH': 'south', 'LOOK_WEST': 'west', 'LOOK_EAST': 'east'} - - -# Decoded from pokeplatinum at this revision, not yet checked on the console. -INTERIOR = {'repo': decomp.Platinum.REPO, 'revision': 'c6689696d5fb', 'read': '2026-11-01', 'unverified': True} - -# Each map: its header constant, the world rectangle (x0, z0, width, -# height), and the decompilation when not Diamond. Local (x, y) = world -# (x - x0, z - z0). A map that joins another on the world grid keeps its -# rectangle's world corner, so story code can place them side by side. -MAPS = { - # Cells (3..4, 26) from z 839: the image-traced grid's columns and the - # game's rows. Console: the border sign read from (54, 4) facing east, - # row 3 open east of the grass, row 5 to Sandgem without a stop, the - # Potion lady on row 3 (2026-10-28 to 30, CFR "walk"). - 'Route201': {'header': 'ROUTE_201', 'rect': (96, 839, 64, 25)}, - # Cell (5, 25) from x 159. Console: under the south ledge at (22, 24), - # the save spot (30, 24) against the east trees, Natalie's challenge at - # (22, 22), column 30 north to row 4 (r75), the catch spot's walls. - 'Route202': {'header': 'ROUTE_202', 'rect': (159, 800, 36, 32)}, - # Cells (5..6, 23) from x 191: column 0 is Jubilife's east edge. - # Console (r227 hand pass, 2026-10-31): the Gate's warp at column 55, - # the ledge on row 15, the girl at (43, 17), row 18 straight west to - # Jubilife (35 taps). Diamond has four sand rows under the ledge where - # the console counted three (the first tap may only have turned). - 'Route203': {'header': 'ROUTE_203', 'rect': (191, 740, 61, 24)}, - # Cell (5, 21). Console: row 27 open under the cliff, the stairs, - # Taylor's sight at column 17 (human walk, 2026-10-31). Suspects: - # column 23 rows 12..19 blocked (r242a); three open rows under the cave, - # not four (r241a, r243a). - 'Route204North': {'header': 'ROUTE_204_NORTH', 'rect': (160, 672, 32, 32)}, - # Cell (5, 22). Console: north up column 14 stops at the signpost - # (14, 17); column 13 is grass to the cliff (13, 2); Sarah walked up and - # fought on row 21 (r233a, r234a). 01.08 passed once (r235a). - 'Route204South': {'header': 'ROUTE_204_SOUTH', 'rect': (160, 704, 32, 32), 'read': '2026-11-01'}, - # Cell (5, 26). Console (2026-10-29/30): the three doors, five signs, - # the doors' front row, the Lab's sign, the stop above the Mart (29, 5). - 'SandgemTown': {'header': 'SANDGEM', 'rect': (160, 832, 34, 32)}, - # Cells (4..5, 23..24). Console (2026-10-30/31): the lamps (43, 53) and - # (49, 53), north from (52, 53) to the Center's door, the School's door - # from the Center's doorstep, the east street to Route 203 (r230a), the - # north street, and the 00.05 people on their events-file tiles. - 'JubilifeCity': {'header': 'JUBLIFE', 'rect': (128, 736, 64, 64)}, - # Cells (8..9, 23..24); Platinum's map_data_022/023/175/024 are the - # same terrain. Console (r204 to r215): 20 D-pad taps from the - # Center's doorstep (46, 21) to below the Gym's door. - 'OreburghCity': {'header': 'OREBURGH', 'rect': (256, 736, 64, 64)}, - # The only cell of matrix 003; Platinum identical. - 'OreburghGate': {'header': 'OREBURGH_GATE_1F', 'rect': (0, 0, 32, 32)}, - # The only cell of matrix 113. 01.04 walks north up column 5. - 'OreburghGym': {'header': 'OREBURGH_GYM', 'rect': (0, 0, 32, 32)}, - # B2F, the coal room: matrix 006. Console (2026-10-28, r99): the - # corridor's rocks zigzag a row at a time, the room's second row passes - # under the worker, Roark a row above the bottom between two rocks; the - # walk north into the hall went up column 14 (the warp is column 15: a - # stick hold drifts, or BDSP's stairs are wider). - 'OreburghMine': {'header': 'OREBURGH_MINE_B2F', 'rect': (0, 0, 32, 32)}, - # Platinum's matrix 051 (Diamond's matrix is not in the cache), rows - # 0..53. Console (2026-10-31, r235b and D-pad probes): in from the south - # door onto (19, 50), up the stairs (18..20, 45..47) to the wall at row - # 39, the corridor three wide to row 41 and open east on rows 42..44, - # the rocks (22, 42), (22, 43), (23, 44) and the Potion (22, 44). Rows - # 0..29 were a hand trace from the DP image before; nothing walks there. - 'RavagedPath': {'header': 'RAVAGED_PATH', 'rect': (0, 0, 32, 54), 'repo': decomp.Platinum.REPO}, - # Cell (5, 20); the cell above carries the same header but is cut off - # from the town. Console: the old couple (2026-10-31 walk, r251a). - 'FloaromaTown': {'header': 'FLOAROMA', 'rect': (160, 640, 32, 32)}, - # Cell (3, 27). The player's house is the south-east one, door - # (20, 21); Barry's the north-west one, door (9, 11) (the interiors say - # so; Diamond's map constants name them the other way round). - 'TwinleafTown': {'header': 'TWINLEAF', 'rect': (96, 864, 32, 32)}, - # Cell (2, 26), of the four the header covers (the others are trees - # and water). The ledges on column 20 (rows 14..18) are hopped west. - 'VerityLakefront': {'header': 'VERITY_LAKEFRONT', 'rect': (64, 832, 32, 32)}, - # Cell (1, 1) of the lake's matrix 101: the shore where the starter - # scene plays. Only this cell is reached on foot. - 'LakeVerity': {'header': 'LAKE_VERITY', 'rect': (32, 32, 32, 32)}, - # 00.01 to 00.03's interiors, from pokeplatinum (c6689696d5fb), each - # the only cell of its own matrix: decomp only, not yet checked on the - # console. Mom's warning on the way out is a coord event on the - # player's door tile (6, 10). - 'PlayerHouse1F': {'header': 'TWINLEAF_TOWN_PLAYER_HOUSE_1F', 'rect': (0, 0, 13, 12), **INTERIOR}, - 'PlayerHouse2F': {'header': 'TWINLEAF_TOWN_PLAYER_HOUSE_2F', 'rect': (0, 0, 12, 12), **INTERIOR}, - 'RivalHouse1F': {'header': 'TWINLEAF_TOWN_RIVAL_HOUSE_1F', 'rect': (0, 0, 13, 12), **INTERIOR}, - 'RivalHouse2F': {'header': 'TWINLEAF_TOWN_RIVAL_HOUSE_2F', 'rect': (0, 0, 12, 12), **INTERIOR}, - 'RowansLab': {'header': 'SANDGEM_TOWN_POKEMON_RESEARCH_LAB', 'rect': (0, 0, 22, 18), **INTERIOR}, -} - - -def box(x0, y0, x1, y1): - """Every tile of a box, both corners inclusive, row by row.""" - return [(x, y) for y in range(y0, y1 + 1) for x in range(x0, x1 + 1)] - - -# Named tiles; a list is a region, written as numbered points. -POINTS = { - 'Route201': { - # The arrow sign to Sandgem. Console (CFR "walk"): east along row 4 - # stops at its west side and A reads it. - 'border_sign': (55, 4), - # The Poke Mart lady (10 Potions): Diamond spawns her at (44, 3), the - # console had her at (43, 3) and walking west along row 3 stopped at - # (44, 3), facing her (99715d5d6, f823453e9; r135a missed her on row 4). - 'potion_giver': (43, 3), - 'potion_giver_talk_from': (44, 3), - # Segment 00's lines (Segment00Lines). - 'route_201_arrival': (15, 24), - 'barry_warning': (15, 18), - 'route_201_return': (0, 14), - 'rowan_stop': (5, 14), - 'route_201_east_end': (63, 5), - }, - 'Route202': { - # 00.03's save after the catch: the catch patch's bottom row. The - # sign (24, 25) is 5 right and 2 up; south are trees (r253). - 'catch_spot': (19, 27), - # 00.04's save before the trainers, under the south ledge against - # the east trees (r113a..r117a). - 'save_spot': (30, 24), - 'trainer_tristan': (7, 13), - 'trainer_natalie': (22, 18), - 'trainer_logan': (26, 4), - # The middle of the gap to Jubilife, below its 3-wide neck. - 'exit_jubilife': (15, 0), - # The open rows at the top: north from any of them is Jubilife. - 'jubilife_exit': box(11, 0, 18, 3), - # Where 00.04b's save after the catch may stand: the patch and the - # lawn column east of it, down to the pocket where r252 saved. - 'catch_save_area': box(15, 25, 21, 27) + [(21, 28), (21, 29)], - # Where training may leave us (the catch spot's column and one either - # side, between the tree rows), and its grass: all of it. - 'after_training_area': box(18, 25, 20, 27), - 'catch_grass': box(18, 25, 20, 27), - }, - 'Route203': { - # The warp tile the Gate puts us on, and its open neighbours. - 'gate_exit': [(55, 9), (54, 9), (55, 8), (55, 10)], - # Row 18 between the cliffs: it runs straight west into Jubilife. - 'west_line': box(24, 18, 42, 18), - }, - 'Route204North': { - 'cave_exit': (20, 26), - # The line a human walked on 2026-10-31: out of the cave, west under - # the cliff, up the stairs, east (Taylor), north up column 19, west - # below the flower beds, north between them on column 11 ((12, 6) - # is blocked on the console). - 'floaroma_waypoints': [(20, 27), (11, 27), (11, 22), (19, 22), (19, 12), (11, 12), (11, 5)], - # The path north into Floaroma: walking north from here leaves. - 'floaroma_path': box(10, 0, 12, 5), - 'trainer_twin_left': (15, 7), - 'trainer_twin_right': (16, 7), - 'trainer_taylor': (17, 20), - 'trainer_brandon': (20, 12), - }, - 'Route204South': { - 'below_door': (11, 2), - # Where the walk north from Jubilife may stop, up to two tiles past - # the border (400 ms holds between looks). - 'jubilife_entry': [(14, 31), (14, 30), (14, 29)], - 'trainer_samantha': (11, 8), - 'trainer_sarah': (12, 21), - 'trainer_tyler': (7, 18), - }, - 'SandgemTown': { - # The walk south from Route 202 down the road's east side stops here, - # above the Mart where the road narrows (console, 2026-10-29/30). - 'route_202_entry_stop': (29, 5), - 'lab_door': (8, 10), - 'pokemon_center_door': (17, 10), - # Walking north from row 14 in its column stops below it. - 'pokemon_center_sign': (20, 10), - 'poke_mart_door': (27, 10), - # East along the doors' front row stops here, at the trees (CFR - # "walk", 2026-10-30): a known tile to plan north from. - 'front_row_east_end': (29, 11), - # The front row below the gap between the Center's and the Mart's - # signs: the only way north from that row that passes no door. - 'below_sign_gap': box(21, 11, 24, 11), - 'sandgem_arrival': (0, 12), - 'dawn_stop': (4, 12), - }, - 'JubilifeCity': { - # A doorway (warp tile) stepped onto from (40, 41). - 'trainers_school_door': (40, 40), - # Walked into from (52, 41); eleven taps west from its doorstep put - # us under the School's door (2026-10-30). - 'pokemon_center_door': (52, 40), - 'tv_station_door': (36, 15), - # Talked to from the east (also works from the south). - 'poketch_president': (46, 35), - # His spawn tile; Diamond has him wander a tile either side, the - # console had him a tile east of it. - 'clown_mart': (55, 33), - 'clown_tv_station': (36, 16), - # Talked to from the south; west along row 18 stops beside him. - 'clown_poketch_company': (15, 18), - # A tile west of 00.05's save below the steps, then north: on - # column 46 or 47 a few rows up (CFR "walk", 2026-10-30). - 'up_the_steps': box(46, 58, 47, 60), - # Where Route 203's road enters, rows 21..24 at the east edge. - 'east_entrance': box(61, 21, 63, 24), - # The north road where it leaves the plaza: north from here goes - # up the road to Route 204 and Rowan's scene. - 'north_road': box(44, 16, 48, 20), - }, - 'OreburghCity': { - 'pokemon_center_door': (47, 20), - # The taps say x 46, a column west of the door. - 'pokemon_center_doorstep': (46, 21), - 'gym_door': (26, 20), - 'gym_doorstep': (26, 21), - 'gate_mouth': (1, 13), - }, - 'OreburghGym': { - # Coming in puts us here; the door is below. - 'entrance': (5, 23), - 'roark': (5, 3), - 'trainer_first_tier': (4, 18), - 'trainer_second_tier': (7, 11), - }, - 'OreburghMine': { - 'roark': (18, 28), - 'corridor_exit_start': (14, 10), - # The first open row below the warp tile, where the walk in from - # the hall leaves us. - 'corridor_top': box(14, 2, 16, 2), - }, - 'RavagedPath': { - 'entrance': (19, 50), - 'east_end': (29, 44), - 'above_exit': (28, 44), - # Rock Smash rocks, open once smashed. - 'rocks': [(11, 36), (7, 37), (12, 37), (8, 38), (10, 38), (10, 39), (13, 39), - (7, 40), (9, 40), (17, 40), (12, 41), (22, 42), (22, 43), (23, 44)], - # West of the two rocks on rows 42 and 43. - 'before_rocks': [(21, 42), (21, 43)], - }, - 'FloaromaTown': { - 'route_204_arrival': (11, 29), - 'old_woman': (3, 25), - 'old_man': (2, 25), - 'pokemon_center_door': (16, 26), - }, - 'TwinleafTown': { - 'house_doorstep': (20, 22), - # Barry's scene fires here, below his door. - 'barry_doorstep': (9, 12), - 'twinleaf_north_exit': (15, 0), - 'player_house_door': (20, 21), - 'rival_house_door': (9, 11), - }, - 'VerityLakefront': { - 'lakefront_arrival': (31, 23), - 'below_lake_entrance': (17, 14), - # The lake's scene fires here; it is assumed to leave us here too. - 'lake_entrance': (17, 12), - }, - 'PlayerHouse1F': {'stairs': (10, 3), 'mom': (7, 8), 'door': (6, 10)}, - # A new game starts on (4, 6); the TV is the signs at (3, 4) and (4, 4). - 'PlayerHouse2F': {'stairs': (8, 4), 'start': (4, 6)}, - 'RivalHouse1F': {'door': (6, 10), 'stairs': (2, 3)}, - # Barry stands at (9, 5) facing his PC; he is talked to from the west. - 'RivalHouse2F': {'stairs': (3, 4), 'barry_talk': (8, 5)}, - # Rowan is talked to from below, facing north. - 'RowansLab': {'door': (7, 15), 'rowan_talk': (7, 6)}, -} - - -# Overrides by events-file object index. `show`: a person hidden by a -# story flag who is there when the story walks the map. `hide`: someone -# BDSP doesn't have, or the story never meets. `still`: a wanderer who -# stood still on the console. `at`: where the console found them. `add`: -# people Diamond doesn't have, from the console. -OBJECTS = { - 'Route201': { - # The Potion lady stands a tile off her spawn on the console, and - # her talk is walked to, not planned (POINTS). - 1: {'hide': 'her talk is walked to, not planned'}, - }, - 'Route203': { - # Stands still in Diamond, wanders near the Gate in BDSP (console). - 0: {'hide': 'wanders in BDSP'}, - }, - 'Route204North': { - # Bug Catcher Brandon paces in Diamond from (22, 13); he stopped us - # on row 12 (r246a), and the DP map draws him at (20, 12). - 0: {'at': (20, 12), 'still': 'r246a', 'name': 'bug_catcher_brandon'}, - }, - 'SandgemTown': { - # Stand still in Diamond, wander on the console. - 1: {'hide': 'wanders in BDSP'}, - 2: {'hide': 'wanders in BDSP'}, - }, - 'JubilifeCity': { - # 00.05's story people, hidden before and after it. - 24: {'show': '00.05', 'name': 'poketch_president'}, - 28: {'show': '00.05', 'name': 'clown_tv_station'}, - # Wander in Diamond; stood still on the console (00.05, three runs). - 25: {'show': '00.05', 'still': '00.05, three runs', 'name': 'clown_mart'}, - 26: {'show': '00.05', 'still': '00.05, three runs', 'name': 'clown_poketch_company'}, - }, - 'FloaromaTown': { - # Grunts at the Meadow until the Valley Windworks is cleared, and - # the man with Cyrus's sprite (whether he is there is for the console). - 7: {'show': 'until the Valley Windworks'}, - 8: {'show': 'until the Valley Windworks'}, - 3: {'show': 'not yet checked on the console'}, - 'add': [ - # Not in Diamond's data. Walking north from (3, 29) stopped 3 - # tiles up at the old woman (r251a). - {'name': 'old_woman', 'x': 3, 'y': 25, 'measured_by': 'r251a, 2026-10-31'}, - {'name': 'old_man', 'x': 2, 'y': 25, 'measured_by': 'r251a, 2026-10-31'}, - ], - }, - 'OreburghMine': { - # Hidden once his scene here is over; the story walks to him. - 0: {'show': '01.03', 'name': 'roark'}, - }, - 'RivalHouse2F': { - # Barry in his room, until he leaves for the lake. - 0: {'show': '00.01', 'name': 'barry'}, - }, - 'RowansLab': { - # Rowan at his desk when the story walks in. - 0: {'show': '00.03', 'name': 'rowan'}, - }, - 'LakeVerity': { - # The starter scene's actors, where the walk in ends. - 0: {'show': 'the starter scene', 'name': 'rowan'}, - 1: {'show': 'the starter scene', 'name': 'counterpart'}, - 3: {'show': 'the starter scene'}, - }, -} - - -WARP_DOORS = { - # The Ravaged Path's door: its warp tile is the mouth. - 'Route204South': [(11, 1)], - # The Route 218 gate, and the doorways of the School and the Global - # Terminal: entered, never stood on by a route. - 'JubilifeCity': [(0, 22), (0, 23), (40, 40), (21, 42)], - # The way up to the entrance hall: stepping on it leaves the room. - 'OreburghMine': [(15, 1)], -} - - -# Stepping onto (x, y) puts the player at a point of another map. -WARPS = { - 'PlayerHouse1F': [(6, 10, 'TwinleafTown', 'player_house_door')], - 'RivalHouse1F': [(6, 10, 'TwinleafTown', 'rival_house_door')], - 'RowansLab': [(7, 15, 'SandgemTown', 'lab_door')], -} - - -PATCHES = { - 'Route202': ( - # BDSP has trees where Diamond's catch patch has its two bottom rows: - # west along row 29 stopped at column 21, beside them. - [(x, y, '#', 'r253, 2026-11-01') for y in (28, 29) for x in range(15, 21)] - # South from the pocket (21, 29), where the catch's save loads, - # was blocked at once. - + [(21, 30, '#', 'r254, 2026-11-01')] - ), - 'PlayerHouse1F': [ - # Open on the console, a wall in the data: west along row 8 stops - # at x 4 (r32). - (4, 8, '.', 'r32, 2026-11-01'), - ], - 'Route201': ( - # Row 18 west of the exit path is trees on the console, open in the - # decomp, and the 201 sign stands at (13, 18), not (13, 17) (r16, - # walked by hand). - [(x, 18, '#', 'r16, 2026-10-31') for x in range(8, 13)] - + [(13, 18, 'S', 'r16, 2026-10-31'), (13, 17, '.', 'r16, 2026-10-31')] - ), - 'Route204North': [ - # Grass in the data, blocked on the console twice. - (12, 6, '#', 'r248a, 2026-10-31'), - ], -} - - -# Coord events by events-file index: their names (the default is -# coord_event_), and under 'add' the triggers the console found that -# the decompilation doesn't have. -COORD_EVENTS = { - # Barry stops us on row 18 (POINTS barry_warning); Rowan on column 5 - # (POINTS rowan_stop). - 'Route201': {0: 'barry_warning', 1: 'rowan_stop'}, - 'Route202': {0: 'catch_tutorial'}, - 'SandgemTown': {0: 'dawn_walks_us_to_the_lab'}, - 'JubilifeCity': {0: 'dawn_at_the_steps', 4: 'poketch_president'}, - 'TwinleafTown': {1: 'barry_doorstep'}, - 'VerityLakefront': {0: 'lake_entrance'}, - 'PlayerHouse1F': { - 0: 'moms_warning', - 'add': [ - {'name': 'moms_call', 'x': 6, 'y': 9, 'width': 1, 'length': 1, 'var': 'CONSOLE_MOMS_CALL', 'value': 0, - 'measured_by': 'console r45, r46 (2026-11-01): after her heal Mom calls us back as we step above' - ' the door; no coord event in the decomp'}, - ], - }, -} - - -def no_fetch(repo, path): - raise SystemExit(f'{repo}/{path} is not in {CACHE}. Fetch it with `python3 -m devtools.maps decomp ...` first.') - - -def glyph(attr): - behaviour = attr & 0xff - if behaviour in decomp.JUMP: - return LEDGES[decomp.JUMP[behaviour]] - if behaviour in decomp.JUMP_TWICE: - return LEDGES[decomp.JUMP_TWICE[behaviour]] - if behaviour == decomp.DOOR or behaviour in decomp.CAVE_MOUTHS: - return 'D' - if behaviour in decomp.WARP_TILES or behaviour in decomp.WARP_STAIRS: - return 'W' - if behaviour in decomp.WATER: - return 'w' - if attr & decomp.BLOCKED: - return '#' - if behaviour in decomp.TALL_GRASS: - return '~' - return '.' - - -def wander_box(obj, x, y, width, height): - return {'x0': max(0, x - obj.range_x), 'y0': max(0, y - obj.range_z), - 'x1': min(width - 1, x + obj.range_x), 'y1': min(height - 1, y + obj.range_z)} - - -def place_objects(name, events, rect, rows): - """Writes signs and obstacles into `rows`; returns the objects.""" - x0, z0, width, height = rect - overrides = OBJECTS.get(name, {}) - objects = [] - for index, obj in enumerate(events.objects): - x, y = obj.x - x0, obj.z - z0 - if not (0 <= x < width and 0 <= y < height): - continue - override = overrides.get(index, {}) - if obj.gfx in OBSTACLE_GFX: - rows[y][x] = '#' - continue - if obj.gfx == decomp.ITEM: - objects.append({'name': f'item_{index}', 'kind': 'item', 'x': x, 'y': y}) - continue - if obj.hidden and 'show' not in override or 'hide' in override: - continue - if 'SIGN' in obj.gfx or obj.gfx in SIGN_GFX: - rows[y][x] = 'S' - continue - x, y = override.get('at', (x, y)) - entry = {'name': override.get('name', f'{obj.gfx.lower()}_{index}'), 'kind': 'npc', 'x': x, 'y': y} - if obj.movement in FACING: - entry['facing'] = FACING[obj.movement] - if obj.movement.startswith(('WANDER', 'WALK')) and 'still' not in override: - entry['wander'] = wander_box(obj, x, y, width, height) - objects.append(entry) - for added in overrides.get('add', []): - objects.append({'name': added['name'], 'kind': 'npc', 'x': added['x'], 'y': added['y']}) - return objects - - -def coord_events(name, repo, game, events_id, rect): - """The map's coord events: the events file's, clipped to `rect`, then COORD_EVENTS' additions.""" - x0, z0, width, height = rect - names = COORD_EVENTS.get(name, {}) - out = [] - for index, event in enumerate(game.events(events_id).coord_events): - left, top = max(event.x, x0), max(event.z, z0) - right, bottom = min(event.x + event.width, x0 + width), min(event.z + event.length, z0 + height) - if left >= right or top >= bottom: - continue - out.append({ - 'name': names.get(index, f'coord_event_{index}'), - 'x': left - x0, 'y': top - z0, 'width': right - left, 'length': bottom - top, - 'var': event.var, 'value': event.value, - 'source': f'{repo} {game.events_path(events_id)} coord_events[{index}] ({event.x}, {event.z}),' - f' script {event.script}', - }) - for added in names.get('add', []): - fields = {k: v for k, v in added.items() if k != 'measured_by'} - out.append({**fields, 'source': added['measured_by']}) - return out - - -def decode_map(name): - spec = MAPS[name] - repo = spec.get('repo', decomp.Diamond.REPO) - game = decomp.game_for(repo, CACHE, None, no_fetch) - header = game.header(spec['header']) - matrix = game.matrix(header.matrix_id) - world = decomp.World(game, matrix) - x0, z0, width, height = spec['rect'] - rows = [] - for y in range(height): - row = [] - for x in range(width): - attr = world.attr(x0 + x, z0 + y) - if attr is None: - raise SystemExit(f'{name}: ({x}, {y}) is outside matrix {header.matrix_id}.') - row.append(glyph(attr)) - rows.append(row) - events = game.events(header.events_id) - for sign in events.signs: - x, y = sign.x - x0, sign.z - z0 - if 0 <= x < width and 0 <= y < height and rows[y][x] == '#': - rows[y][x] = 'S' - objects = place_objects(name, events, spec['rect'], rows) - doors = set(WARP_DOORS.get(name, [])) - for y, row in enumerate(rows): - for x, tile in enumerate(row): - if tile == 'W': - row[x] = 'D' if (x, y) in doors else '.' - # Every cell the rectangle touches, also when it does not start on a cell's edge. - C = decomp.CELL - lands = sorted({world.land_id(C * col, C * row) for row in range(z0 // C, (z0 + height - 1) // C + 1) - for col in range(x0 // C, (x0 + width - 1) // C + 1)} - {None}) - unverified = '; decomp only, not checked on the console' if spec.get('unverified') else '' - source = { - 'decomp_file': f'{repo}: ' + ', '.join(game.land_path(land) for land in lands) - + f'; {game.events_name(header.events_id)}{unverified}', - 'revision': spec.get('revision', 'default branch'), - 'date': spec.get('read', READ), - } - triggers = coord_events(name, repo, game, header.events_id, spec['rect']) - return [''.join(row) for row in rows], objects, source, triggers - - -def points_of(name): - points = {} - for point, value in POINTS.get(name, {}).items(): - if isinstance(value, list): - for i, pos in enumerate(value, 1): - points[f'{point}.{i}'] = pos - else: - points[point] = value - return points - - -def map_text(name): - """The map's JSON: one line per row, point, object and patch.""" - tiles, objects, source, triggers = decode_map(name) - lines = ['{', f' "name": {json.dumps(name)},', f' "source": {json.dumps(source)},', ' "tiles": ['] - lines += [f' {json.dumps(row)},' for row in tiles] - lines[-1] = lines[-1].rstrip(',') - lines.append(' ]') - - def section(key, items): - if not items: - return - lines[-1] += ',' - lines.append(f' "{key}": ' + ('{' if key == 'points' else '[')) - lines.extend(f' {item},' for item in items) - lines[-1] = lines[-1].rstrip(',') - lines.append(' ' + ('}' if key == 'points' else ']')) - - section('points', [f'{json.dumps(p)}: [{x}, {y}]' for p, (x, y) in points_of(name).items()]) - section('warps', [json.dumps({'x': x, 'y': y, 'to_map': m, 'to_point': p}) for x, y, m, p in WARPS.get(name, [])]) - section('objects', [json.dumps(o) for o in objects]) - section('coord_events', [json.dumps(e) for e in triggers]) - section('patches', [json.dumps({'x': x, 'y': y, 'glyph': g, 'measured_by': m}) - for x, y, g, m in PATCHES.get(name, [])]) - lines.append('}') - return '\n'.join(lines) + '\n' - - -def main(): - parser = argparse.ArgumentParser(description=__doc__.split('\n')[0]) - parser.add_argument('--out', default=OUT, help='folder of the .json files') - parser.add_argument('--check', action='store_true', help='check the files in --out instead of writing them') - parser.add_argument('maps', nargs='*', help='default: every map in MAPS') - args = parser.parse_args() - names = args.maps or sorted(MAPS) - for name in names: - if name not in MAPS: - parser.error(f'{name}: not in MAPS') - texts = {name: map_text(name) for name in names} - if args.check: - differ = [n for n, text in texts.items() - if not os.path.exists(os.path.join(args.out, n + '.json')) - or open(os.path.join(args.out, n + '.json'), newline='').read() != text] - if not args.maps: - extra = sorted(f[:-5] for f in os.listdir(args.out) if f.endswith('.json') and f[:-5] not in MAPS) - differ += [f'{n} (not in MAPS)' for n in extra] - if differ: - sys.exit(f'Differ from the generator: {", ".join(differ)}. Regenerate them without --check.') - print(f'{len(texts)} maps in {args.out} match the generator.') - return - os.makedirs(args.out, exist_ok=True) - for name, text in texts.items(): - with open(os.path.join(args.out, name + '.json'), 'w', newline='') as file: - file.write(text) - print(f'Wrote {len(texts)} maps to {args.out}.') - - -if __name__ == '__main__': - main() diff --git a/devtools/maps/decomp.py b/devtools/maps/decomp.py deleted file mode 100644 index 881fa19347..0000000000 --- a/devtools/maps/decomp.py +++ /dev/null @@ -1,593 +0,0 @@ -"""Decodes a map's tile grid from the Pokemon Platinum or Diamond decompilation. - -The pret decompilations hold each 32 x 32 map cell's collision as a land data -file: a 16-byte header of four u32 sizes (attributes, movement models, map -models, height data), then 1024 little-endian u16 tile attributes, row-major, -index z * 32 + x. Bit 0x8000 blocks the tile; the low byte is its behaviour -(tall grass, water, ledges, doors, warps). Cells sit in a matrix: the -overworld's is 30 x 30 (matrix 000), caves have their own; world -x = 32 * column + lx, z = 32 * row + lz, and the events files place objects -in these world coordinates. - -pokeplatinum keeps matrices and events as JSON and its map headers in -include/data/map_headers.h. pokediamond keeps them as binary NARC entries -(the layout pokeplatinum's tools/jsoncnv packs) with the header table in -arm9/src/map_header.c, and gives sprites, movement types, trainer types and -story variables by number; pokeplatinum's generated/*.txt tables name them. -An events file lists bg events, object events, warps and coord events (the -scene triggers: stepping onto one of a box of tiles while a story variable -holds a value starts a script), in that order. BDSP keeps -Diamond/Pearl's layout; Platinum's few changes show up on the console. -""" -import datetime -import json -import re -import struct -import subprocess -import textwrap -from dataclasses import dataclass, field -from pathlib import Path - -CELL = 32 -# Tile attribute bits and behaviours (include/constants/field/map_tile_behaviors.h). -BLOCKED = 0x8000 -TALL_GRASS = {0x02, 0x03} -WATER = {0x10, 0x13, 0x15} # river, waterfall, sea -JUMP = {0x38: 'east', 0x39: 'west', 0x3a: 'north', 0x3b: 'south'} -JUMP_TWICE = {0x5a: 'north', 0x5b: 'south', 0x5c: 'west', 0x5d: 'east'} -WARP_STAIRS = {0x5e, 0x5f} -CAVE_MOUTHS = {0x62, 0x63, 0x64, 0x65} # blocked, walked into -DOOR = 0x69 -WARP_TILES = {0x6c, 0x6d, 0x6e, 0x6f} # walkable, stood on -# Objects that stand on the ground and block it. -OBSTACLES = {'CUT_TREE', 'ROCK_SMASH', 'STRENGTH_BOULDER'} -ITEM = 'POKEBALL' -# bg events: 0 facing, 1 wall sign, 2 hidden item (generated/bg_event_types.txt). -BG_SIGNS = {0, 1} - - -def tile_char(attr): - """Our legend for one tile attribute: (char, note or None).""" - behaviour = attr & 0xff - if behaviour in JUMP or behaviour in JUMP_TWICE: - twice = behaviour in JUMP_TWICE - side = JUMP_TWICE[behaviour] if twice else JUMP[behaviour] - if side == 'south': - return 'v', 'double ledge' if twice else None - return '#', f'ledge jumped {side}{" twice" if twice else ""}, shown as a wall' - if behaviour == DOOR or behaviour in CAVE_MOUTHS: - return 'D', None - if behaviour in WARP_TILES or behaviour in WARP_STAIRS: - return 'W', None - if behaviour in WATER: - return '#', 'water' - if attr & BLOCKED: - return '#', None - if behaviour in TALL_GRASS: - return '"', None - return '.', None - - -def decode_land_data(data): - """The 1024 tile attributes of a land data file (map_data_NNN.bin / narc_NNNN.bin).""" - sizes = struct.unpack('<4I', data[:16]) - if sizes[0] != 2 * CELL * CELL: - raise ValueError(f'land data: attribute block is {sizes[0]} bytes, expected {2 * CELL * CELL}') - return list(struct.unpack(f'<{CELL * CELL}H', data[16:16 + sizes[0]])) - - -def gh_fetch(repo, path): - """One file of a GitHub repository, raw; binary works.""" - result = subprocess.run( - ['gh', 'api', f'repos/{repo}/contents/{path}', '-H', 'Accept: application/vnd.github.raw'], - check=True, stdout=subprocess.PIPE) - return result.stdout - - -class Source: - """Files of one decompilation: a local checkout, or fetched once into the cache.""" - - def __init__(self, repo, cache, local=None, fetch=gh_fetch): - self.repo = repo - self.cache = Path(cache) - self.local = Path(local) if local else None - self.fetch = fetch - - def read(self, path): - if self.local: - return (self.local / path).read_bytes() - cached = self.cache / self.repo / path - if not cached.exists(): - data = self.fetch(self.repo, path) - cached.parent.mkdir(parents=True, exist_ok=True) - cached.write_bytes(data) - return cached.read_bytes() - - def text(self, path): - return self.read(path).decode('utf-8', errors='replace') - - -@dataclass -class MapHeader: - name: str - matrix_id: int - events_id: object # Platinum: events archive name; Diamond: NARC index - - -@dataclass -class Matrix: - matrix_id: int - width: int - height: int - maps: list # land data id per cell, row-major, None for MAP_NONE - headers: list = None # map header name per cell, or None when the matrix has none - - def cells_of(self, header_name): - """(row, col) of every cell carrying this header; all cells when the matrix has no headers.""" - cells = [(i // self.width, i % self.width) for i in range(self.width * self.height) - if self.maps[i] is not None and (self.headers is None or self.headers[i] == header_name)] - return cells - - -@dataclass -class ObjectEvent: - name: str - gfx: str - movement: str - trainer: bool - sight: int - range_x: int - range_z: int - x: int - z: int - hidden: str = '' - - -@dataclass -class Warp: - x: int - z: int - dest: str - - -@dataclass -class Sign: - x: int - z: int - - -@dataclass -class CoordEvent: - """A scene trigger: `width` x `length` tiles from (x, z), armed while `var` holds `value`.""" - script: int - x: int - z: int - width: int - length: int - var: str - value: int - - -@dataclass -class Events: - objects: list = field(default_factory=list) - warps: list = field(default_factory=list) - signs: list = field(default_factory=list) - coord_events: list = field(default_factory=list) - - -def strip_prefix(name, prefix): - return name[len(prefix):] if name.startswith(prefix) else name - - -class Platinum: - """pret/pokeplatinum: JSON matrices and events, map headers in include/data/map_headers.h.""" - REPO = 'pret/pokeplatinum' - GAME = 'Pokemon Platinum' - HEADER = re.compile(r'\[MAP_HEADER_(\w+)\]\s*=\s*\{(.*?)\}', re.S) - - def __init__(self, source): - self.source = source - self._headers = None - - def header(self, name): - if self._headers is None: - text = self.source.text('include/data/map_headers.h') - self._headers = {m.group(1): m.group(2) for m in self.HEADER.finditer(text)} - body = self._headers.get(name) - if body is None: - raise KeyError(f'{name}: no MAP_HEADER_{name} in include/data/map_headers.h') - matrix = re.search(r'\.mapMatrixID\s*=\s*map_matrix_(\d+)', body) - events = re.search(r'\.eventsArchiveID\s*=\s*(\w+)', body) - return MapHeader(name, int(matrix.group(1)), events.group(1)) - - def matrix(self, matrix_id): - data = json.loads(self.source.text(f'res/field/matrices/map_matrix_{matrix_id:03d}.json')) - maps = [None if m == 'MAP_NONE' else int(m[len('MAP_'):]) for row in data['maps'] for m in row] - headers = [strip_prefix(h, 'MAP_HEADER_') for row in data['headers'] for h in row] or None - return Matrix(matrix_id, len(data['maps'][0]), len(data['maps']), maps, headers) - - def land_name(self, land_id): - return f'map_data_{land_id:03d}' - - def land_path(self, land_id): - return f'res/field/maps/data/{self.land_name(land_id)}.bin' - - def land_data(self, land_id): - return decode_land_data(self.source.read(self.land_path(land_id))) - - def events_name(self, events_id): - return f'{events_id}.json' - - def events_path(self, events_id): - return f'res/field/events/{events_id}.json' - - def events(self, events_id): - data = json.loads(self.source.text(self.events_path(events_id))) - events = Events() - for o in data.get('object_events', []): - trainer = o['trainer_type'] != 'TRAINER_TYPE_NONE' - events.objects.append(ObjectEvent( - name=title(strip_prefix(o['id'], 'LOCALID_')), - gfx=strip_prefix(o['graphics_id'], 'OBJ_EVENT_GFX_'), - movement=strip_prefix(o['movement_type'], 'MOVEMENT_TYPE_'), - trainer=trainer, - sight=(o.get('data') or [0])[0] if trainer else 0, - range_x=o.get('movement_range_x', 0), range_z=o.get('movement_range_z', 0), - x=o['x'], z=o['z'], - hidden='' if str(o.get('hidden_flag', '0')) == '0' else str(o['hidden_flag']))) - for w in data.get('warp_events', []): - events.warps.append(Warp(w['x'], w['z'], strip_prefix(w['dest_header_id'], 'MAP_HEADER_'))) - for b in data.get('bg_events', []): - if b['type'] in BG_SIGNS: - events.signs.append(Sign(b['x'], b['z'])) - for c in data.get('coord_events', []): - events.coord_events.append(CoordEvent( - c['script'], c['x'], c['z'], c['width'], c['length'], c['var'], c['value'])) - return events - - -class Diamond: - """pret/pokediamond: binary matrices and events, the header table in arm9/src/map_header.c. - - Sprites, movement types and trainer types are numbers there; `names` is a - Source for pret/pokeplatinum, whose generated/*.txt tables list them in - order (checked on Route 204 North: the same twelve objects come out). - Story variables are numbered from 0x4000 in both games; Platinum's - generated/vars_flags.txt (a C enum) names them (checked: the coord events - that pokeplatinum also has on Route 202, Sandgem, Jubilife and the - player's house come out with Platinum's variable names). A number that - table does not name stays a number. - """ - REPO = 'pret/pokediamond' - GAME = 'Pokemon Diamond' - HEADER_LINE = re.compile(r'\{([^{}]*)\}\s*,?\s*//\s*(MAP_\w+)') - DEFINE = re.compile(r'#define\s+(MAP_\w+)\s+(\d+)') - - def __init__(self, source, names): - self.source = source - self.names = names - self._headers = None - self._map_ids = None - self._tables = {} - - def header(self, name): - if self._headers is None: - text = self.source.text('arm9/src/map_header.c') - self._headers = {m.group(2): m.group(1) for m in self.HEADER_LINE.finditer(text)} - body = self._headers.get(f'MAP_{name}') - if body is None: - raise KeyError(f'{name}: no MAP_{name} row in arm9/src/map_header.c') - matrix = re.search(r'NARC_map_matrix_narc_(\d+)_bin', body) - events = re.search(r'NARC_zone_event_release_narc_(\d+)_bin', body) - return MapHeader(name, int(matrix.group(1)), int(events.group(1))) - - def map_name(self, map_id): - """MAP_ constant name (without the prefix) for a numeric map header id.""" - if self._map_ids is None: - text = self.source.text('include/constants/maps.h') - self._map_ids = {int(m.group(2)): m.group(1) for m in self.DEFINE.finditer(text)} - return strip_prefix(self._map_ids.get(map_id, f'MAP_{map_id}'), 'MAP_') - - def table(self, name): - if name not in self._tables: - self._tables[name] = self.names.text(f'generated/{name}.txt').split() - return self._tables[name] - - def named(self, table, prefix, value): - entries = self.table(table) - return strip_prefix(entries[value], prefix) if value < len(entries) else str(value) - - def var_name(self, number): - if 'vars' not in self._tables: - values = enum_values(self.names.text('generated/vars_flags.txt').splitlines()) - names = {} - for name, value in values.items(): - if name.startswith('VAR_'): - names.setdefault(value, name) - self._tables['vars'] = names - return self._tables['vars'].get(number, str(number)) - - def matrix(self, matrix_id): - data = self.source.read(f'files/fielddata/mapmatrix/map_matrix/narc_{matrix_id:04d}.bin') - width, height, has_headers, has_altitudes, name_len = struct.unpack_from('<5B', data, 0) - offset = 5 + name_len - count = width * height - headers = None - if has_headers: - headers = [self.map_name(h) for h in struct.unpack_from(f'<{count}H', data, offset)] - offset += 2 * count - if has_altitudes: - offset += count - maps = [None if m == 0xffff else m for m in struct.unpack_from(f'<{count}H', data, offset)] - return Matrix(matrix_id, width, height, maps, headers) - - def land_name(self, land_id): - return f'land_data narc_{land_id:04d}' - - def land_path(self, land_id): - return f'files/fielddata/land_data/land_data_release/narc_{land_id:04d}.bin' - - def land_data(self, land_id): - return decode_land_data(self.source.read(self.land_path(land_id))) - - def events_name(self, events_id): - return f'zone_event narc_{events_id:04d}' - - def events_path(self, events_id): - return f'files/fielddata/eventdata/zone_event_release/narc_{events_id:04d}.bin' - - def events(self, events_id): - data = self.source.read(self.events_path(events_id)) - events = Events() - offset = 0 - (count,) = struct.unpack_from('