187 lines
9.8 KiB
C++
187 lines
9.8 KiB
C++
// 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 <cstdio>
|
|
#include <cstdlib>
|
|
#include <map>
|
|
#include <string>
|
|
|
|
#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<std::string, std::size_t> 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<std::size_t>(i)] == 100 && !e.pct_written[static_cast<std::size_t>(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;
|
|
}
|