// Real-data smoke test: feed a player's numbers from a save dump through the budget // roll-up and print the result for eyeballing. // // Input: $SOTS_SAVES_JSON = path to the JSON emitted by the RE repo's save reader // (`save_reader.py SAVE --json`). Skips cleanly when unset. No assertions on exact // values yet: the per-system money output is not fully resolved, so the system income // line is a placeholder. Turning this into a compare-mode test (asserting the next // turn's savings from a save pair) is future work. #include #include #include #include #include #include "game/sim/economy.h" #include "game/sim/numeric.h" #include "mini_json.h" using namespace sots::sim; int main() { const char* path = std::getenv("SOTS_SAVES_JSON"); if (!path || !*path) { std::printf("smoke_real_save: SOTS_SAVES_JSON unset, skipped\n"); return 0; } std::ifstream f(path); if (!f) { std::fprintf(stderr, "smoke_real_save: cannot open %s\n", path); return 1; } std::stringstream ss; ss << f.rdbuf(); const std::string text = ss.str(); minijson::Value root; minijson::Parser parser(text); if (!parser.parse(root)) { std::fprintf(stderr, "smoke_real_save: JSON parse failed\n"); return 1; } const minijson::Value* data = root.get("data"); const minijson::Value* sim = data ? data->get("sim") : nullptr; const minijson::Value* players = sim ? sim->get("players") : nullptr; const minijson::Value* systems = sim ? sim->get("systems") : nullptr; if (!players || !players->isArray()) { std::fprintf(stderr, "smoke_real_save: no data.sim.players array\n"); return 1; } // Index systems by id so a player's owned-system list can be resolved. std::map systemById; if (systems && systems->isArray()) { for (const minijson::Value& entry : systems->arr) { const minijson::Value* id = entry.get("SysID"); const minijson::Value* sys = entry.get("Sys"); if (id && sys) systemById[static_cast(id->number())] = sys; } } std::printf("smoke_real_save: %s\n", path); std::printf(" (system income is a placeholder: population / 1e6 per owned system)\n"); for (const minijson::Value& entry : players->arr) { const minijson::Value* p = entry.get("Player"); if (!p) continue; const minijson::Value* name = p->get("PlryName"); const minijson::Value* npc = p->get("NPC"); if (npc && npc->boolean()) continue; BudgetInputs in; in.savings = static_cast(p->get("Sav") ? p->get("Sav")->number() : 0); in.maintenance = static_cast(p->get("Maint") ? p->get("Maint")->number() : 0); in.researchRate = p->get("ResRate") ? p->get("ResRate")->number() : 0; in.resMod = p->get("ResMod") ? p->get("ResMod")->number(1) : 1; in.resScl = p->get("ResScl") ? p->get("ResScl")->number(1) : 1; in.trm = p->get("TRM") ? p->get("TRM")->number() : 0; in.tra = static_cast(p->get("TRA") ? p->get("TRA")->number() : 0); in.trp = static_cast(p->get("TRP") ? p->get("TRP")->number() : 0); in.shrm = p->get("shrm") ? p->get("shrm")->number() : 0; in.hasResearchTarget = p->get("ResTNm") && !p->get("ResTNm")->string().empty(); const minijson::Value* owners = p->get("owners"); int ownedSystems = 0; if (owners && owners->isArray()) { for (const minijson::Value& o : owners->arr) { const int sysId = static_cast(o.number(-1)); auto it = systemById.find(sysId); if (it == systemById.end()) continue; const minijson::Value* abdn = it->second->get("Abdn"); if (abdn && abdn->boolean()) continue; const double pop = it->second->get("Pop") ? it->second->get("Pop")->number() : 0; in.systemIncome.push_back(static_cast(pop / 1e6)); ++ownedSystems; } } in.ownsSystems = ownedSystems > 0; const Budget b = ComputeBudget(in, false); std::printf(" player %-16s species=%d sav=%d maint=%d systems=%d resRate=%.2f\n", name ? name->string().c_str() : "?", static_cast(p->get("Species") ? p->get("Species")->number(-1) : -1), in.savings, in.maintenance, ownedSystems, in.researchRate); std::printf(" interest=%d debt=%d sysIncome=%d/-%d avail=%d construction=%d\n", b.savingsInterest, b.debtInterest, b.systemIncomePositive, b.systemIncomeNegative, b.available, b.construction); std::printf(" researchMoney=%d RP=%d totalRP=%d net=%d -> savings next turn %d\n", b.researchMoney, b.researchPoints, b.totalResearchPoints, b.net, SaturatingAdd(in.savings, b.net)); } std::printf("smoke_real_save: done (no assertions; compare-mode is future work)\n"); return 0; }