// Real-data facts: loads $SOTS_DATA_DIR and checks counts, spot values and // the cross-check report against what the RE notes document. SKIPs cleanly // when the variable is unset. Nothing from the data is embedded here beyond // the handful of published facts being asserted. #include #include #include #include #include "game/data/catalog.h" using namespace game::data; namespace { int failures = 0; #define EXPECT(cond) \ do { \ if (!(cond)) { \ std::printf(" FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++failures; \ } \ } while (0) } // namespace int main() { const char* dir = std::getenv("SOTS_DATA_DIR"); if (!dir || !*dir) { std::printf("real-data: SKIP (SOTS_DATA_DIR not set)\n"); return 0; } Catalog cat = load_catalog(dir); // ---- counts --------------------------------------------------------- std::size_t player = 0, npc = 0; for (const WeaponDef& w : cat.weapons) (w.scope == WeaponScope::Player ? player : npc)++; std::printf("weapons %zu (player %zu, NPC %zu) sections %zu techs %zu edges %zu turrets %zu races %zu strings %zu\n", cat.weapons.size(), player, npc, cat.sections.size(), cat.tech.nodes.size(), cat.tech.edges.size(), cat.turrets.size(), cat.races.size(), cat.strings.size()); EXPECT(cat.weapons.size() == 207); EXPECT(player == 123 && npc == 84); EXPECT(cat.sections.size() == 875); EXPECT(cat.tech.nodes.size() == 293); EXPECT(cat.tech.edges.size() == 354); EXPECT(cat.turrets.size() == 42); EXPECT(cat.races.size() == 7); EXPECT(cat.weapon_ids.size() == 123); EXPECT(cat.weapon_ids.deleted().size() == 3 && cat.weapon_ids.is_deleted(36) && cat.weapon_ids.is_deleted(58) && cat.weapon_ids.is_deleted(59)); EXPECT(cat.strings_loaded && cat.strings.size() == 5196); EXPECT(cat.tech.groups.size() == 9); std::map per_race; for (const ShipSectionDef& s : cat.sections) per_race[s.race]++; EXPECT(per_race["Human"] == 144 && per_race["Hiver"] == 137 && per_race["Morrigi"] == 141 && per_race["Liir"] == 135 && per_race["Tarkas"] == 132 && per_race["Zuul"] == 122 && per_race["_NPC"] == 64); // ---- spot facts ----------------------------------------------------- const WeaponDef* g = cat.weapon("bal_gauss"); EXPECT(g && g->id && *g->id == 8); EXPECT(g && g->cost && *g->cost == 50 && g->turret_size == "small" && g->turret_class == "standard"); EXPECT(g && g->requires.size() == 1 && g->requires[0] == "WEP_GsDrvr"); EXPECT(g && g->bolt && g->bolt->rangetable.maximum.range && *g->bolt->rangetable.maximum.range == 455); EXPECT(g && g->bolt && g->bolt->planet.pop && *g->bolt->planet.pop == 3500); EXPECT(g && g->fc.requires_los && *g->fc.requires_los && g->fc.manual_target && !*g->fc.manual_target); EXPECT(g && g->display_name && *g->display_name == "Gauss Cannon"); EXPECT(cat.weapon_by_id(8) == g); EXPECT(cat.weapon("mis") && cat.weapon("mis")->requires.empty()); const ShipSectionDef* fis = cat.section("Human", "DEFission"); EXPECT(fis && fis->id && *fis->id == 47); EXPECT(fis && fis->requires.size() == 2 && fis->requires[1] == "DRV_Node"); EXPECT(fis && fis->section_type == SectionType::Engine && fis->section_class == SectionClass::Destroyer); EXPECT(fis && fis->options.size() == 3 && fis->options[0].members.size() == 5 && fis->options[2].scalar && fis->options[2].members[0] == "DRV_RecFiss"); EXPECT(fis && fis->banks.size() == 1 && fis->banks[0].mounts.size() == 1 && fis->banks[0].turret_size == "small"); EXPECT(fis && fis->netforcelimits && fis->netforcelimits->speed && *fis->netforcelimits->speed == 40); EXPECT(fis && fis->ftlspeed && *fis->ftlspeed == 0.2 && fis->nodespeed && *fis->nodespeed == 4); EXPECT(fis && fis->engine_techera == "fission" && fis->thrusters.size() == 4); EXPECT(fis && fis->display_name && *fis->display_name == "Fission" && fis->unlocked_by.empty()); EXPECT(cat.section_by_id("Human", 47) == fis); EXPECT(cat.section("human", "DEWAR") == nullptr); EXPECT(cat.sections_named("DECommand").size() == 6); // every player race const TechNode* fissn = cat.tech_node("DRV_Fissn"); EXPECT(fissn != nullptr); if (fissn) { bool fusn = false, recfiss = false; for (std::size_t ei : fissn->allows) { const AllowsEdge& e = cat.tech.edges[ei]; if (e.to == "DRV_Fusn") { fusn = true; EXPECT(e.rp && *e.rp == 85000); for (int i = 0; i < kSpeciesCount; ++i) EXPECT(e.pct[static_cast(i)] == 100 && !e.pct_written[static_cast(i)]); } if (e.to == "DRV_RecFiss") { recfiss = true; EXPECT(e.percent(Species::Human) == 50 && e.percent(Species::Liir) == 95 && e.percent(Species::NPC) == 100); } } EXPECT(fusn && recfiss); } const TechNode* root = cat.tech_node("DRV_ROOT"); EXPECT(root && root->is_root()); if (root) for (std::size_t ei : root->allows) { const AllowsEdge& e = cat.tech.edges[ei]; if (e.to == "DRV_Node") EXPECT(e.percent(Species::Human) == 100 && e.percent(Species::Tarkas) == 0); if (e.to == "DRV_Hyper") EXPECT(e.percent(Species::Tarkas) == 100 && e.percent(Species::Human) == 0); } const TechNode* ply = cat.tech_node("IND_PlyAlloy"); EXPECT(ply && ply->option_cost && *ply->option_cost == 1.2 && ply->type == "P" && ply->benefits_inc.size() == 1 && ply->benefits_inc[0] == "TECHBEN_HULLSTR"); const auto* torps = cat.tech.group_members("GRP_Torps"); EXPECT(torps && torps->size() == 12); EXPECT(cat.tech.requirement_exists("GRP_Shields") && cat.tech.roots().size() == 12); EXPECT(cat.tech_node("WEP_HCLAS") && cat.tech_node("WEP_HCLAS")->name == "WEP_HCLas"); // case-insensitive lookup // ---- load-time problems ------------------------------------------- int bad = 0, syntax = 0, other = 0; for (const Problem& p : cat.problems) { if (p.kind == Problem::Kind::BadValue) ++bad; else if (p.kind == Problem::Kind::Syntax) ++syntax; else ++other; } std::printf("problems: %d bad values, %d syntax recoveries, %d other\n", bad, syntax, other); // force_right `o` x22, crew `false` x9 (NPC hulls), trackspeed_mod `1.0f`, // min_inclination `0-5`, max_inclination `90\` -- each keeps its text in raw. EXPECT(bad == 34); EXPECT(syntax == 12); // the 12 lenient recoveries the parser documents EXPECT(other == 0); // ---- cross-check -------------------------------------------------- CrossCheck x = cat.cross_check(); std::printf("cross-check: dangling %zu (manifest gaps %zu, unresolved names %zu); case mismatches %zu/%zu; " "no-requires %zu; missing sectiondesc %zu; roots %zu\n", x.dangling_count(), x.manifest_ids_without_file.size(), x.unresolved_weapon_names.size(), x.weapon_requires_case_mismatch.size(), x.section_requires_case_mismatch.size(), x.weapons_without_requires.size(), x.missing_sectiondesc.size(), x.tech_roots.size()); EXPECT(x.strings_available); EXPECT(x.weapon_requires_dangling.empty()); EXPECT(x.section_requires_dangling.empty()); EXPECT(x.section_option_dangling.empty()); EXPECT(x.tech_ship_section_dangling.empty()); EXPECT(x.tech_weapon_file_dangling.empty()); EXPECT(x.tech_requires_dangling.empty()); EXPECT(x.tech_allows_dangling.empty()); EXPECT(x.tech_allows_unparsed.empty()); EXPECT(x.bank_weapon_dangling.empty()); EXPECT(x.files_without_manifest_id.empty()); EXPECT(x.weapon_turret_pairs_without_row.empty()); EXPECT(x.bank_turret_pairs_without_row.empty()); EXPECT(x.manifest_ids_without_file.size() == 10); int tarkas = 0, dewar = 0; for (const auto& m : x.manifest_ids_without_file) { if (m.scope == "Tarkas") ++tarkas; if (iequals(m.name, "DEWar.shipsection")) ++dewar; } EXPECT(tarkas == 7 && dewar == 3); EXPECT(x.unresolved_weapon_names.size() == 3); for (const auto& r : x.unresolved_weapon_names) EXPECT(r.from.rfind("Species/_NPC/", 0) == 0); EXPECT(x.dangling_count() == 13); EXPECT(x.weapon_requires_case_mismatch.size() == 18); EXPECT(x.section_requires_case_mismatch.size() == 7); EXPECT(x.weapons_without_requires.size() == 30); EXPECT(x.missing_techname.empty() && x.missing_techdesc.empty() && x.missing_sectionname.empty()); EXPECT(x.missing_sectiondesc.size() == 67); EXPECT(x.tech_roots.size() == 12); std::size_t scalar_options = 0, repeated_banks = 0, banks_no_size = 0, banks = 0, mounts = 0; for (const ShipSectionDef& s : cat.sections) { for (const OptionGroup& g : s.options) scalar_options += g.scalar; for (const BankDef& b : s.banks) { ++banks; mounts += b.mounts.size(); repeated_banks += b.repeated_turret_spec; banks_no_size += b.turret_size.empty(); } } std::printf("banks %zu mounts %zu; scalar options %zu; banks with repeated size/class %zu; without size %zu\n", banks, mounts, scalar_options, repeated_banks, banks_no_size); EXPECT(banks == 3721 && mounts == 7375); EXPECT(scalar_options == 229); EXPECT(repeated_banks == 19); EXPECT(banks_no_size == 2); std::printf(failures ? "real-data: %d FAILED\n" : "real-data: OK\n", failures); return failures ? 1 : 0; }