From 99630c999d0f390516233ffe3ef4a22f843ed8c4 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Thu, 12 Feb 2026 21:10:09 -0800 Subject: [PATCH] add optimize-materialized-map --- src/Main.cc | 88 +++++++++++++++++++++++++++++ tests/challenge-mode-random.test.sh | 25 -------- 2 files changed, 88 insertions(+), 25 deletions(-) diff --git a/src/Main.cc b/src/Main.cc index 84f9aaa1..75c22735 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -3161,6 +3161,10 @@ Action a_materialize_map( } auto map_data = make_shared(prs_decompress(read_input_data(args))); auto map_file = make_shared(map_data); + if (!map_file->has_random_sections()) { + throw std::runtime_error("input map file does not have any random sections"); + } + uint32_t seed = args.get("seed", phosg::Arguments::IntFormat::HEX); auto materialized = map_file->materialize_random_sections(seed); if (args.get("disassemble")) { @@ -3172,6 +3176,90 @@ Action a_materialize_map( write_output_data(args, new_data.data(), new_data.size(), "dat"); } }); +Action a_optimize_materialized_map( + "optimize-materialized-map", "\ + optimize-materialized-map [OPTIONS] [INPUT-FILENAME]\n\ + Runs the Challenge Mode random enemy generation algorithm on the input map\n\ + file, looking for the seed that results in the fewest extra events and the\n\ + fewest enemies overall, optionally restricting to specific enemy types. A\n\ + version option is required. The --minimize=TYPE option may be used\n\ + (possibly multiple times) to specify enemies that are undesirable in the\n\ + resulting map, and the optimizer will try to minimize occurrences of those\n\ + enemies. TYPE should be specified as an integer (for example, 0x0040 for\n\ + Hildebears; see Map.cc for a full listing). Event count always takes\n\ + precedence; that is, a map with fewer events is always considered better\n\ + than any map with more events, regardless of the enemy counts. The\n\ + --threads=NUM-THREADS option may be used to limit parallelism; by default,\n\ + uses one thread per CPU core.\n", + +[](phosg::Arguments& args) { + if (args.get("debug")) { + static_game_data_log.min_level = phosg::LogLevel::L_DEBUG; + } + auto map_data = make_shared(prs_decompress(read_input_data(args))); + auto map_file = make_shared(map_data); + if (!map_file->has_random_sections()) { + throw std::runtime_error("input map file does not have any random sections"); + } + + std::unordered_set minimize_types; + for (const auto& arg : args.get_multi("minimize")) { + minimize_types.emplace(std::stoul(arg, nullptr, 16)); + } + + size_t num_threads = args.get("threads", 0); + mutex output_lock; + size_t min_counts = 0xFFFFFFFF; + auto thread_fn = [&](uint64_t seed, size_t) -> bool { + auto materialized = map_file->materialize_random_sections(seed); + + size_t extra_event_count = 0; + size_t total_event_count = 0; + size_t total_enemy_set_count = 0; + size_t minimized_enemy_set_count = 0; + map enemy_set_counts; + for (size_t floor = 0; floor < 0x12; floor++) { + const auto& fs = materialized->floor(floor); + if (!fs.enemy_sets || !fs.events1) { + continue; + } + total_event_count += fs.event_count; + for (size_t z = 0; z < fs.event_count; z++) { + const auto& ev = fs.events1[z]; + if (ev.event_id >= 10000) { + extra_event_count++; + } + } + + total_enemy_set_count += fs.enemy_set_count; + for (size_t z = 0; z < fs.enemy_set_count; z++) { + uint16_t base_type = fs.enemy_sets[z].base_type; + enemy_set_counts.emplace(base_type, 0).first->second++; + if (minimize_types.empty() || minimize_types.count(base_type)) { + minimized_enemy_set_count++; + } + } + } + + size_t this_count = (total_event_count << 16) | minimized_enemy_set_count; + { + lock_guard g(output_lock); + if (this_count < min_counts) { + min_counts = this_count; + string line = std::format("SEED {:08X}: event_count={} (extra={}) enemy_sets=[", + seed, total_event_count, extra_event_count); + for (const auto& it : enemy_set_counts) { + line += std::format("{:04X}={}, ", it.first, it.second); + } + line.resize(line.size() - 2); + line += std::format("] (count={}, minimized={})\n", total_enemy_set_count, minimized_enemy_set_count); + phosg::fwritex(stdout, line); + } + } + + return false; + }; + phosg::parallel_range_blocks(thread_fn, 0, 0x100000000, 0x100, num_threads); + }); Action a_print_free_supermap( "print-free-supermap", "\ diff --git a/tests/challenge-mode-random.test.sh b/tests/challenge-mode-random.test.sh index 378d10d7..8b3125ce 100755 --- a/tests/challenge-mode-random.test.sh +++ b/tests/challenge-mode-random.test.sh @@ -51,28 +51,3 @@ echo "... challenge-ep1/c88109-gc.dat" $EXECUTABLE materialize-map system/quests/challenge-ep1/c88109-gc.dat ./tests/c88109-gc-00000000.dat --seed=00000000 diff tests/challenge-maps-materialized/c88109-gc-00000000.dat ./tests/c88109-gc-00000000.dat rm ./tests/c88109-gc-00000000.dat - -echo "... challenge-ep2/d88201-gc.dat" -$EXECUTABLE materialize-map system/quests/challenge-ep2/d88201-gc.dat ./tests/d88201-gc-00000000.dat --seed=00000000 -diff tests/challenge-maps-materialized/d88201-gc-00000000.dat ./tests/d88201-gc-00000000.dat -rm ./tests/d88201-gc-00000000.dat - -echo "... challenge-ep2/d88202-gc.dat" -$EXECUTABLE materialize-map system/quests/challenge-ep2/d88202-gc.dat ./tests/d88202-gc-00000000.dat --seed=00000000 -diff tests/challenge-maps-materialized/d88202-gc-00000000.dat ./tests/d88202-gc-00000000.dat -rm ./tests/d88202-gc-00000000.dat - -echo "... challenge-ep2/d88203-gc.dat" -$EXECUTABLE materialize-map system/quests/challenge-ep2/d88203-gc.dat ./tests/d88203-gc-00000000.dat --seed=00000000 -diff tests/challenge-maps-materialized/d88203-gc-00000000.dat ./tests/d88203-gc-00000000.dat -rm ./tests/d88203-gc-00000000.dat - -echo "... challenge-ep2/d88204-gc.dat" -$EXECUTABLE materialize-map system/quests/challenge-ep2/d88204-gc.dat ./tests/d88204-gc-00000000.dat --seed=00000000 -diff tests/challenge-maps-materialized/d88204-gc-00000000.dat ./tests/d88204-gc-00000000.dat -rm ./tests/d88204-gc-00000000.dat - -echo "... challenge-ep2/d88205-gc.dat" -$EXECUTABLE materialize-map system/quests/challenge-ep2/d88205-gc.dat ./tests/d88205-gc-00000000.dat --seed=00000000 -diff tests/challenge-maps-materialized/d88205-gc-00000000.dat ./tests/d88205-gc-00000000.dat -rm ./tests/d88205-gc-00000000.dat