// Real-data facts test: reads the owner's extracted game text tree from // $SOTS_DATA_DIR and checks a handful of known facts (counts and values // established by the RE repo's verification). Skips cleanly when unset. #include #include #include #include #include #include #include "mars/text/csv.h" #include "mars/text/flat_kv.h" #include "mars/text/manifest.h" using namespace mars::text; static int g_failures = 0; static int g_checks = 0; #define CHECK(cond) \ do { \ ++g_checks; \ if (!(cond)) { \ ++g_failures; \ std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ } \ } while (0) static std::string read_file(const std::string& path) { std::ifstream in(path, std::ios::binary); if (!in) { std::fprintf(stderr, "FAIL cannot read %s\n", path.c_str()); ++g_failures; return {}; } return std::string((std::istreambuf_iterator(in)), std::istreambuf_iterator()); } int main() { const char* root = std::getenv("SOTS_DATA_DIR"); if (!root || !*root) { std::printf("realdata_test: SKIP (SOTS_DATA_DIR not set)\n"); return 0; } const std::string base = std::string(root) + "/"; // globals.txt: a colour and the comment-in-quotes quirk line { auto r = parse_flat_kv(read_file(base + "Data/globals.txt")); CHECK(r.ok()); CHECK(r.value.size() == 364); CHECK(r.value.duplicates().empty()); auto c = r.value.get_color("mars_default_color"); // case-insensitive CHECK(c && c->r == 255 && c->g == 177 && c->b == 39); CHECK(r.value.find("MARS_DEFAULT_COLOR")->key == "MARS_DEFAULT_COLOR"); auto endgame = r.value.get_color("ENDGAME_FILL_COLOR"); CHECK(endgame && endgame->components == 3); } // StrategyVars.txt: typed access { auto r = parse_flat_kv(read_file(base + "Data/Strategy/StrategyVars.txt")); CHECK(r.ok()); CHECK(r.value.size() == 97); CHECK(r.value.get_float("SLAVES_DEATH_RATE").has_value()); } // _turrets.txt: 42 rows, 8 columns, quoted model in the last column { auto r = parse_rows(read_file(base + "Weapons/_turrets.txt")); CHECK(r.ok()); CHECK(r.value.size() == 42); for (const Row& row : r.value) { CHECK(row.tokens.size() == 8); CHECK(row.tokens.back().quoted); CHECK(row.tokens[3].as_int().has_value()); CHECK(row.tokens[5].as_float().has_value()); } } // _weapons.txt: 123 ids, tombstones 36/58/59, no problems { auto r = parse_manifest(read_file(base + "Weapons/_weapons.txt")); CHECK(r.ok()); CHECK(r.value.entries().size() == 123); CHECK((r.value.deleted() == std::vector{36, 58, 59})); CHECK(r.value.id_of("CAN_AM.WEAPON").has_value()); CHECK(r.value.name_of(1).has_value()); } // Human _shipsections.txt: 145 ids; the misspelt-case DEWar entry resolves both ways { auto r = parse_manifest(read_file(base + "Species/Human/sections/_shipsections.txt")); CHECK(r.ok()); CHECK(r.value.entries().size() == 145); CHECK(r.value.id_of("dewar.shipsection") == 98); } // Strings.csv: 5,722 raw records / 5,200 data rows, 4-column schema row, // exactly one multi-line cell (its line break sits just before the closing // quote, so cell stripping removes it), cp1252 bytes untouched { std::string text = read_file(base + "Locale/EN/Strings.csv"); auto raw = split_csv_records(text); CHECK(raw.ok()); CHECK(raw.value.size() == 5722); int multiline = 0; for (const CsvRow& row : raw.value) for (const std::string& c : row.cells) if (c.find("\r\n") != std::string::npos) ++multiline; CHECK(multiline == 1); auto r = parse_csv(text); CHECK(r.ok()); CHECK(r.value.size() == 5200); CHECK(r.value.header && r.value.header->size() == 4 && (*r.value.header)[1] == "String"); int highbytes = 0, embedded_newline = 0; for (const CsvRow& row : r.value.rows) for (const std::string& c : row.cells) { if (c.find('\n') != std::string::npos) ++embedded_newline; for (unsigned char ch : c) if (ch >= 0x80) { ++highbytes; break; } } CHECK(embedded_newline == 0); CHECK(highbytes > 0); // the 4 keys duplicated via a trailing space collapse after stripping std::size_t distinct = 0; { std::vector keys; for (const CsvRow& row : r.value.rows) keys.push_back(row.cells.at(0)); std::sort(keys.begin(), keys.end()); distinct = static_cast(std::unique(keys.begin(), keys.end()) - keys.begin()); } CHECK(distinct == 5196); } // AI template CSV: comment-only, but the schema row is recovered { auto r = parse_csv(read_file(base + "Data/Strategy/AI/aitechpri.csv")); CHECK(r.ok()); CHECK(r.value.size() == 0); CHECK(r.value.header && r.value.header->size() == 7 && (*r.value.header)[0] == "tech"); } // stock_diplomacy_messages.csv: every cell quoted, "# species" header, blank ",," rows dropped { auto r = parse_csv(read_file(base + "Data/Strategy/AI/stock_diplomacy_messages.csv")); CHECK(r.value.size() == 755); CHECK(r.value.header && (*r.value.header)[0] == "species"); } std::printf("realdata_test: %d checks, %d failures\n", g_checks, g_failures); return g_failures == 0 ? 0 : 1; }