#include "app/treaty.h" namespace sots::app { using mars::stream::shapes::DipStat; using mars::stream::shapes::Player; using mars::stream::shapes::PlayerEntry; namespace { // "This kind of treaty was last in force on turn N", one field per treaty kind. A fresh // entry starts all three at -1, which is what distinguishes "never" from "on turn 0". constexpr std::int32_t kNever = -1; std::int32_t* StampField(DipStat& e, Relation r) { switch (r) { case Relation::Allied: return &e.lastally; case Relation::NonAggression: return &e.lastnap; case Relation::CeaseFire: return &e.lastcf; case Relation::War: return nullptr; } return nullptr; } } // namespace Relation RelationTo(const Player& self, const Player& other) { // Self first, and by the index field -- two players with the same index are the same // player as far as this test is concerned, which is how the original short-circuits. if (self.plyrIdx == other.plyrIdx) return Relation::Allied; // x86 masks a variable shift count to five bits. Reproduced rather than corrected, for // the same reason `alliance.cpp` reproduces it: the point is to be the original. const std::uint32_t bit = 1u << (static_cast(other.plyrIdx) & 31u); // The alliance mask is tested UNCONDITIONALLY here -- there is no alliance-id guard, and // that is the difference from the shared-vision mask in alliance.cpp. if (static_cast(self.alliances.al) & bit) return Relation::Allied; if (static_cast(self.alliances.na) & bit) return Relation::NonAggression; if (static_cast(self.alliances.cf) & bit) return Relation::CeaseFire; return Relation::War; } DipStat NewDipStat(std::int32_t otherPlayerId) { DipStat e; e.other = otherPlayerId; e.lastnap = kNever; e.lastally = kNever; e.lastcf = kNever; return e; } TreatyStampResult StampTreatyTurns(std::vector& players, std::int32_t frame) { TreatyStampResult r; const std::size_t n = players.size(); for (std::size_t i = 0; i < n; ++i) { Player& a = players[i].player; for (std::size_t j = 0; j < n; ++j) { if (i == j) continue; // the original compares the POINTERS, not the indices const Player& b = players[j].player; const Relation rel = RelationTo(a, b); if (rel == Relation::War) continue; ++r.pairsStamped; const std::int32_t otherId = players[j].playerID; DipStat* e = nullptr; for (auto& cand : a.dipstats) // linear, first match, exactly as the original if (cand.other == otherId) { e = &cand; break; } if (!e) { a.dipstats.push_back(NewDipStat(otherId)); // appended at the END e = &a.dipstats.back(); ++r.entriesCreated; } std::int32_t* field = StampField(*e, rel); if (field && *field != frame) ++r.fieldsWritten; if (field) *field = frame; } } // The phase's own accounting: one for each stamp that moved a value, plus one for each // record that did not exist before. It is deliberately NOT an attempt to predict the // divergence report's leaf count, which also charges two container leaves to each player // whose ledger goes from empty to non-empty -- on the reference pair this reports 28 // where the report closes 26. The report is the authority; this is what the phase did. r.leafWrites = r.fieldsWritten + r.entriesCreated; return r; } } // namespace sots::app