// map_probe — SHINSOO-WORLD-RENDERING.md W0 的 load report。 // // map_probe [map_rel] map_rel 默认 OutdoorA1/metin2_map_a1 // // 扫一张地图:setting.txt / TextureSet / 每个区块的 6 类文件 / areadata, // 逐项打印「成功 / 失败 + 原因」,末尾给汇总。不加载 .msenv / Property(W0 剩余项)。 #include "area_data.h" #include "asset_resolver.h" #include "environment.h" #include "m2_coord.h" #include "map_setting.h" #include "property.h" #include "splat.h" #include "terrain_files.h" #include "texture_set.h" #include #include #include #include #include using namespace fmt; static int g_ok = 0, g_bad = 0; static void line(const char* tag, bool ok, const std::string& detail) { std::printf(" [%s] %-28s %s\n", ok ? "OK " : "ERR", tag, detail.c_str()); ok ? ++g_ok : ++g_bad; } int main(int argc, char** argv) { if (argc < 2) { std::fprintf(stderr, "usage: map_probe [map_rel]\n"); return 2; } std::string assets = argv[1]; std::string map_rel = argc > 2 ? argv[2] : "OutdoorA1/metin2_map_a1"; std::string map = assets + "/" + map_rel; std::printf("== map_probe: %s ==\n", map.c_str()); std::string err; MapSetting s; if (!parse_map_setting_file(map + "/setting.txt", s, &err)) { line("setting.txt", false, err); return 1; } line("setting.txt", true, "MapSize " + std::to_string(s.map_size_x) + "x" + std::to_string(s.map_size_y) + " CellScale " + std::to_string(s.cell_scale) + " HeightScale " + std::to_string(s.height_scale) + " env=" + s.environment); // TextureSet:setting 里是 `textureset\metin2_A1.txt`,实盘在 assets/textureset/textureset/ 且小写 { std::string rel = s.texture_set; for (auto& c : rel) if (c == '\\') c = '/'; for (auto& c : rel) c = (char)std::tolower((unsigned char)c); std::string p = assets + "/textureset/" + rel; TextureSet ts; if (parse_texture_set_file(p, ts, &err)) line("TextureSet", true, std::to_string(ts.layers.size()) + " layers (+1 empty = runtime " + std::to_string(ts.runtime_count()) + ")"); else line("TextureSet", false, p + " : " + err); } // 每个区块 int chunks_found = 0, hm_ok = 0, tile_ok = 0, attr_ok = 0, water_ok = 0, area_ok = 0; long total_objs = 0; std::map crc_hist; for (int tx = 0; tx < s.map_size_x; ++tx) { for (int ty = 0; ty < s.map_size_y; ++ty) { std::string dir = map + "/" + m2coord::tile_dir(tx, ty); std::string e; HeightMap hm; TileMap tm; AttrMap am; WaterMap wm; AreaData ad; bool any = false; if (load_height_map(dir + "/height.raw", hm, &e)) { ++hm_ok; any = true; } if (load_tile_map(dir + "/tile.raw", tm, &e)) ++tile_ok; if (load_attr_map(dir + "/attr.atr", am, &e)) ++attr_ok; if (load_water_map(dir + "/water.wtr", wm, &e)) ++water_ok; if (parse_area_data_file(dir + "/areadata.txt", ad, &e)) { ++area_ok; total_objs += (long)ad.objects.size(); for (auto& o : ad.objects) crc_hist[o.crc]++; } if (any) ++chunks_found; } } int n = s.chunk_count(); line("chunk dirs", chunks_found == n, std::to_string(chunks_found) + "/" + std::to_string(n)); line("height.raw", hm_ok == n, std::to_string(hm_ok) + "/" + std::to_string(n) + " as 131x131"); line("tile.raw", tile_ok == n, std::to_string(tile_ok) + "/" + std::to_string(n) + " as 258x258"); line("attr.atr", attr_ok == n, std::to_string(attr_ok) + "/" + std::to_string(n) + " magic 2634"); line("water.wtr", water_ok == n, std::to_string(water_ok) + "/" + std::to_string(n) + " magic 5426"); line("areadata.txt", area_ok == n, std::to_string(area_ok) + "/" + std::to_string(n) + " parsed"); std::printf(" ---- AreaData: %ld objects, %zu distinct Property CRCs ----\n", total_objs, crc_hist.size()); // splat(区块 000000) { TileMap tm; std::string e; if (load_tile_map(map + "/000000/tile.raw", tm, &e)) { SplatSet ss; build_splat(tm, 18, ss); std::string used; for (auto& L : ss.layers) used += std::to_string(L.layer) + "(" + std::to_string(L.coverage) + ") "; line("splat 000000", !ss.layers.empty(), std::to_string(ss.layers.size()) + " used layers: " + used); } } // .msenv(虚拟路径 environment\ -> ETC/ymir work/environment/) Environment env; { std::string p = assets + "/ETC/ymir work/environment/" + s.environment; for (auto& c : p) c = (c == '\\') ? '/' : (char)std::tolower((unsigned char)c); // 只小写文件名部分即可,这里整体小写对 macOS/实盘均成立 std::string p2 = assets + "/ETC/ymir work/environment/" + s.environment; std::string lp = p2; for (auto& c : lp) c = (char)std::tolower((unsigned char)c); if (parse_environment_file(lp, env, &err)) line(".msenv", true, s.environment + " : fog=" + (env.fog.enable ? "on" : "off") + " gradient=" + std::to_string(env.sky.gradient.size()) + " rows, cloudtex=" + env.sky.cloud_texture); else line(".msenv", false, lp + " : " + err); } // Property CRC 注册表 PropertyRegistry reg; if (reg.scan(assets + "/Property", &err)) { std::string bytype; for (auto& [k, v] : reg.type_counts) bytype += k + "=" + std::to_string(v) + " "; line("Property registry", reg.parse_failed == 0, std::to_string(reg.by_crc.size()) + " CRCs (" + std::to_string(reg.files_scanned) + " files, " + std::to_string(reg.parse_failed) + " failed, " + std::to_string(reg.crc_collisions) + " collisions) [" + bytype + "]"); // areadata 的 CRC 覆盖率 long hit = 0, total = 0; std::set missing; for (auto& [crc, cnt] : crc_hist) { total++; if (reg.find(crc)) hit++; else missing.insert(crc); } line("AreaData CRC coverage", missing.empty(), std::to_string(hit) + "/" + std::to_string(total) + " distinct CRCs resolve" + (missing.empty() ? "" : " (missing e.g. " + std::to_string(*missing.begin()) + ")")); } else { line("Property registry", false, err); } // AssetResolver AssetResolver res; if (res.build(assets, AssetResolver::default_priority(), &err)) { line("AssetResolver", true, std::to_string(res.files_indexed) + " files, " + std::to_string(res.by_ymir.size()) + " ymir keys, " + std::to_string(res.ymir_conflicts) + " conflicts"); // TextureSet 图层贴图解析率 TextureSet ts2; std::string rel = s.texture_set, e3; for (auto& c : rel) c = (c == '\\') ? '/' : (char)std::tolower((unsigned char)c); if (parse_texture_set_file(assets + "/textureset/" + rel, ts2, &e3)) { int miss = 0; for (auto& L : ts2.layers) if (res.resolve(L.texture, nullptr).empty()) ++miss; line("TextureSet -> assets", miss == 0, std::to_string(ts2.layers.size() - miss) + "/" + std::to_string(ts2.layers.size()) + " layer textures resolve"); } // Property 引用的模型文件解析率(buildingfile / treefile / effectfile) int pref = 0, pmiss = 0; for (auto& [crc, p] : reg.by_crc) { for (const char* k : {"buildingfile", "treefile", "effectfile", "modelfile"}) { std::string v = p.get(k); if (v.empty()) continue; ++pref; if (res.resolve(v, nullptr).empty()) ++pmiss; } } line("Property models -> assets", true, std::to_string(pref - pmiss) + "/" + std::to_string(pref) + " resolve (misses incl. .spt trees / missing packs)"); } else { line("AssetResolver", false, err); } std::printf("\n== summary: %d ok, %d err ==\n", g_ok, g_bad); return g_bad ? 1 : 0; }