#include "app/turn_record.h" #include namespace sots::app { namespace { constexpr const char* kUnmodelled[] = { "alliance/vision mask -- rebuilt by the alliance-mask phase of the spine, which is a stub", "trade income -- a budget-derived field, blocked behind the per-system money output", "battles fought -- written by the tail's battle-tally phase, which is a stub", "systems acquired / lost this turn -- the two counted lists are never non-empty in the " "corpus, so their element meaning is unobserved", "the per-hull-class ship census -- needs each design's hull size and its class flag, " "neither of which is on the wire; both come from the game data", }; } // namespace const char* const* TurnRecord::Unmodelled(std::size_t& count) { count = sizeof(kUnmodelled) / sizeof(kUnmodelled[0]); return kUnmodelled; } TurnRecord BuildTurnRecord(const mars::stream::shapes::Player& p, const std::vector& systems, std::int32_t frame, int* danglingOwnedSystems) { TurnRecord r; r.turn = frame; r.savings = p.sav; // The turn's income is the change in savings across the turn, not a budget line: the // previous-turn savings word is on the wire and is stamped before this turn's savings are // written, so the subtraction is exact. r.income = p.sav - p.pvSav; r.colonies = static_cast(p.owners.size()); for (const auto& st : p.techTree.state) if (st.st == kTechStateCompleted) ++r.completedTech; // The population term is a sum of TWO per-system words: the live population and the // pending population bonus that has not been folded into it yet. Both are signed 32-bit // and the accumulator is 64-bit, so the sum is taken in 64-bit and never wraps. int dangling = 0; for (std::int32_t id : p.owners) { const mars::stream::shapes::Sys* sys = nullptr; for (const auto& e : systems) if (e.sysID == id) { sys = &e.sys; break; } if (!sys) { ++dangling; continue; } r.population += static_cast(sys->pop) + static_cast(sys->pbon); } if (danglingOwnedSystems) *danglingOwnedSystems = dangling; return r; } const mars::stream::shapes::PlayerTurnStats* FindArchivedRecord( const mars::stream::shapes::PlayerTurnHistory& hist, std::int32_t turn) { for (const auto& s : hist.stats) if (s.trn == turn) return &s; return nullptr; } TurnRecordDiff CompareTurnRecord(const TurnRecord& built, const mars::stream::shapes::PlayerTurnStats& stored) { TurnRecordDiff d; auto cmp = [&](const char* field, long long a, long long b) { ++d.compared; if (a != b) { char buf[160]; std::snprintf(buf, sizeof buf, "%s: built %lld != stored %lld", field, a, b); d.mismatches.push_back(buf); } }; cmp("trn", built.turn, stored.trn); cmp("pop", built.population, stored.pop); cmp("col", built.colonies, stored.col); cmp("sav", built.savings, stored.sav); cmp("inc", built.income, stored.inc); cmp("tch", built.completedTech, stored.tch); return d; } } // namespace sots::app