add optimize-materialized-map

This commit is contained in:
Martin Michelsen
2026-02-12 21:10:09 -08:00
parent e8c262223b
commit 99630c999d
2 changed files with 88 additions and 25 deletions
+88
View File
@@ -3161,6 +3161,10 @@ Action a_materialize_map(
}
auto map_data = make_shared<string>(prs_decompress(read_input_data(args)));
auto map_file = make_shared<MapFile>(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<uint32_t>("seed", phosg::Arguments::IntFormat::HEX);
auto materialized = map_file->materialize_random_sections(seed);
if (args.get<bool>("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<bool>("debug")) {
static_game_data_log.min_level = phosg::LogLevel::L_DEBUG;
}
auto map_data = make_shared<string>(prs_decompress(read_input_data(args)));
auto map_file = make_shared<MapFile>(map_data);
if (!map_file->has_random_sections()) {
throw std::runtime_error("input map file does not have any random sections");
}
std::unordered_set<uint16_t> minimize_types;
for (const auto& arg : args.get_multi<std::string>("minimize")) {
minimize_types.emplace(std::stoul(arg, nullptr, 16));
}
size_t num_threads = args.get<size_t>("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<uint16_t, size_t> 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<uint64_t>(thread_fn, 0, 0x100000000, 0x100, num_threads);
});
Action a_print_free_supermap(
"print-free-supermap", "\
-25
View File
@@ -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