#include "app/phase_catalog.h" namespace sots::app { const char* DriverName(Driver d) { switch (d) { case Driver::Host: return "host turn sequence (outside the two turn drivers)"; case Driver::Strategic: return "StrategyServer::ProcessTurn"; case Driver::Player: return "ServerPlayer::ProcessTurn"; case Driver::Tail: return "StrategyServer::OnAllCombatDone_Tail"; } return "?"; } const char* StatusName(PhaseStatus s) { switch (s) { case PhaseStatus::Verified: return "verified"; case PhaseStatus::Implemented: return "implemented"; case PhaseStatus::Partial: return "partial"; case PhaseStatus::Blocked: return "blocked"; case PhaseStatus::Stub: return "stub"; } return "?"; } char StatusGlyph(PhaseStatus s) { switch (s) { case PhaseStatus::Verified: return 'V'; case PhaseStatus::Implemented: return 'I'; case PhaseStatus::Partial: return 'P'; case PhaseStatus::Blocked: return 'B'; case PhaseStatus::Stub: return '.'; } return '?'; } // --------------------------------------------------------------------------------------- // StrategyServer::ProcessTurn -- 32 phases, 0..31 // --------------------------------------------------------------------------------------- namespace { // The host steps around the drivers. Two turn counters live here, not in either driver. constexpr PhaseDesc kHost[] = { {Driver::Host, 0, "H00", "BeginProcessTurn", PhaseStatus::Implemented, "advances the frame counter, which is the turn number the whole game displays; runs " "before the spine and is where the 'Begin processing turn N' line comes from"}, {Driver::Host, 1, "H01", "SaveWriterInvariants", PhaseStatus::Implemented, "the summary's turn number is the simulation's frame counter -- an identity that holds " "across every save in the corpus and belongs to the writer, not to a turn phase"}, }; constexpr PhaseDesc kStrategic[] = { {Driver::Strategic, 0, "S00", "SnapshotPreviousTurn", PhaseStatus::Partial, "bumps the modification counter; the previous-turn shadow-word snapshot is not modelled " "(the shadow words are not all identified on the wire)"}, {Driver::Strategic, 1, "S01", "SystemPrePassMoraleAndAbandon", PhaseStatus::Stub, "per-system morale event + the abandon/chaos check below the minimum chaos population"}, {Driver::Strategic, 2, "S02", "TradeManagerTurn", PhaseStatus::Stub, "ServerTradeManager::ProcessTurn -- feeds the trade slot of the budget"}, {Driver::Strategic, 3, "S03", "RegisterTradeSystems", PhaseStatus::Stub, ""}, {Driver::Strategic, 4, "S04", "RebuildAllianceMasks", PhaseStatus::Stub, "per-player shared-vision / alliance mask, rebuilt from scratch each turn"}, {Driver::Strategic, 5, "S05", "BuildShipActionTypeSets", PhaseStatus::Stub, "builds the two action-type id sets the dispatcher runs over"}, {Driver::Strategic, 6, "S06", "ShipActionsExceptType2", PhaseStatus::Stub, "the ship-action dispatcher over every action type but type 2 (colonise, build, " "terraform, mine, scrap)"}, {Driver::Strategic, 7, "S07", "NodeSpaceTravel", PhaseStatus::Stub, ""}, {Driver::Strategic, 8, "S08", "FleetMovement", PhaseStatus::Stub, "game::sim movement primitives exist and are mechanism-verified, but the fleet/waypoint " "adapter over the save's flight plans is not written"}, {Driver::Strategic, 9, "S09", "ShipActionsType2", PhaseStatus::Stub, "the action type that needs the fleet to have arrived first"}, {Driver::Strategic, 10, "S10", "ShipUpkeep", PhaseStatus::Stub, "upkeep of population carried aboard colony/slaver hulls in transit"}, {Driver::Strategic, 11, "S11", "SystemTurn", PhaseStatus::Partial, "runs game::sim ProcessColonyTurn per system and commits the parts that need neither the " "tuning table nor a carrying capacity; plague, growth, resources, slaves, rebellion and " "the build queue are the sub-passes the model already declares as its input boundary"}, {Driver::Strategic, 12, "S12", "TradeSliderFinalisation", PhaseStatus::Stub, "re-normalises the per-system output rates"}, {Driver::Strategic, 13, "S13", "PlayerTurn", PhaseStatus::Partial, "runs the 12-phase player driver once per player, in save order"}, {Driver::Strategic, 14, "S14", "ProcessMissions", PhaseStatus::Stub, ""}, {Driver::Strategic, 15, "S15", "ProcessStations", PhaseStatus::Stub, ""}, {Driver::Strategic, 16, "S16", "ProcessDefenceSats", PhaseStatus::Stub, ""}, {Driver::Strategic, 17, "S17", "ShipStatCacheRefresh", PhaseStatus::Stub, "re-syncs cached per-ship stat words from the design record"}, {Driver::Strategic, 18, "S18", "ShipActionsForceValidate", PhaseStatus::Stub, "the dispatcher a third time over all action types with the force flag: validate and " "cancel whatever is left"}, {Driver::Strategic, 19, "S19", "ProcessAid", PhaseStatus::Stub, "writes savings and research points of OTHER players; an input to the budget"}, {Driver::Strategic, 20, "S20", "ProcessSpecialProjectsServer", PhaseStatus::Stub, ""}, {Driver::Strategic, 21, "S21", "ProcessSurrenders", PhaseStatus::Stub, ""}, {Driver::Strategic, 22, "S22", "AdvanceAIRebellion", PhaseStatus::Stub, "steps an in-progress AI rebellion; the rebellion object is opaque on the wire"}, {Driver::Strategic, 23, "S23", "ScriptHookTurnStart", PhaseStatus::Stub, "scripted-scenario callback pair; dead in a normal game but not proven so"}, {Driver::Strategic, 24, "S24", "SensorUpdate", PhaseStatus::Stub, "packs 2-bit per-player visibility into every system and fleet"}, {Driver::Strategic, 25, "S25", "ScriptHookPostSensor", PhaseStatus::Stub, ""}, {Driver::Strategic, 26, "S26", "RefreshPlayerViews", PhaseStatus::Stub, ""}, {Driver::Strategic, 27, "S27", "RecomputePlayerReports", PhaseStatus::Stub, ""}, {Driver::Strategic, 28, "S28", "GrantMetSpeciesTechs", PhaseStatus::Stub, "the 'you have met this race, its racial tech appears in your tree' rule -- small, pure, " "draw-free, and the cheapest unimplemented phase in this table"}, {Driver::Strategic, 29, "S29", "SystemTailFixup", PhaseStatus::Stub, ""}, {Driver::Strategic, 30, "S30", "BuildTeamPartition", PhaseStatus::Stub, ""}, {Driver::Strategic, 31, "S31", "EncounterDetectionAndStatusRestore", PhaseStatus::Blocked, "encounter detection is not modelled. The player-status restore that follows it IS -- it " "writes 1 -- but the value the file carries is 4, so a further writer between this phase " "and the autosave is unaccounted. Committing the 1 turned two agreeing leaves into " "disagreeing ones on the turn2->turn3 pair, so it is evaluated and reported instead"}, }; // --------------------------------------------------------------------------------------- // ServerPlayer::ProcessTurn -- 12 phases, 1..12 // --------------------------------------------------------------------------------------- constexpr PhaseDesc kPlayer[] = { {Driver::Player, 1, "P01", "ComputeBudget", PhaseStatus::Blocked, "the formula is verified (0 divergences over 4,284 live calls) but one input is not " "modelled: the money output of each owned system. That needs the population -> base-output " "term, which the colony model declares unresolved. Evaluated and reported, not committed"}, {Driver::Player, 2, "P02", "ApplyNetToSavings", PhaseStatus::Blocked, "saturating add of the budget net into savings; blocked behind P01's missing input"}, {Driver::Player, 3, "P03", "RecordBudgetDerivedFields", PhaseStatus::Blocked, "trade income, savings-given-away and research-points-given-away land on the turn record " "and on two player words that are not identified on the wire"}, {Driver::Player, 4, "P04", "ProcessSpecialProjectsSpend", PhaseStatus::Stub, "special-project spend; the project bodies are opaque on the wire"}, {Driver::Player, 5, "P05", "ProcessResearch", PhaseStatus::Blocked, "the research slice is verified end to end (35 live calls, 0 divergences) but its " "allocation comes from P01's budget, so it cannot be driven yet"}, {Driver::Player, 6, "P06", "ResearchRefund", PhaseStatus::Blocked, "unspent research points converted back to money at the turn's own rate; needs P01 and P05"}, {Driver::Player, 7, "P07", "ClearTimedResearchAccumulators", PhaseStatus::Partial, "zeroes the three timed-research accumulators that are on the wire; two further words the " "phase also zeroes are not identified"}, {Driver::Player, 8, "P08", "DecayRebellionOutputModifier", PhaseStatus::Implemented, "rebel AI only: the output modifier walks down by 0.04f per turn, clamped to [1, 2]"}, {Driver::Player, 9, "P09", "AccumulateTimedResearchBonuses", PhaseStatus::Implemented, "the timed research-bonus vector, iterated from the LAST element down to index 0 -- the " "descending order is load-bearing because float addition is not associative"}, {Driver::Player, 10, "P10", "ConsumeResearchRollPending", PhaseStatus::Partial, "the flag/threshold test and the flag clear are implemented and committed; the draw it " "fires is counted into the RNG ledger but the generator state is only written back under " "--commit-rng, because the turn's other draws are not yet attributed"}, {Driver::Player, 11, "P11", "PostNoResearchEvent", PhaseStatus::Blocked, "the condition is implemented and reported; posting needs the localised event text table " "and the event-id sequence, neither of which the standalone has"}, {Driver::Player, 12, "P12", "PruneRaidTargets", PhaseStatus::Stub, "20-turn ageing of the raid-target list; the records are opaque on the wire"}, }; // --------------------------------------------------------------------------------------- // StrategyServer::OnAllCombatDone_Tail -- 37 phases, 0..36. // // This is the driver the autosave is written from: everything below runs AFTER combat and // BEFORE the file hits disk. Nothing here is implemented. It is listed in full because a // reimplementation that reproduces the spine exactly and stops will still diverge -- two of // these phases draw from the same generator. // --------------------------------------------------------------------------------------- constexpr PhaseDesc kTail[] = { {Driver::Tail, 0, "T00", "IncrementModCount", PhaseStatus::Implemented, "the same modification counter the spine's phase 0 bumps"}, {Driver::Tail, 1, "T01", "ValidateEncounterResultArity", PhaseStatus::Stub, ""}, {Driver::Tail, 2, "T02", "ResolveFirstContact", PhaseStatus::Stub, ""}, {Driver::Tail, 3, "T03", "AnnounceEncounterSightings", PhaseStatus::Stub, ""}, {Driver::Tail, 4, "T04", "TallyBattlesFought", PhaseStatus::Stub, ""}, {Driver::Tail, 5, "T05", "UpdateDiplomacyStatsFromCombat", PhaseStatus::Stub, ""}, {Driver::Tail, 6, "T06", "ApplyEncounterResults", PhaseStatus::Stub, "DRAWS RNG -- node-cannon and salvage paths; the word count is combat-dependent"}, {Driver::Tail, 7, "T07", "ClearEncounters", PhaseStatus::Stub, ""}, {Driver::Tail, 8, "T08", "ScriptHookCombatDone", PhaseStatus::Stub, ""}, {Driver::Tail, 9, "T09", "AdvanceAIRebellionPostCombat", PhaseStatus::Stub, ""}, {Driver::Tail, 10, "T10", "NodeSpaceTravelSecondPass", PhaseStatus::Stub, "node-space travel runs a SECOND time this turn"}, {Driver::Tail, 11, "T11", "DecayNodeLines", PhaseStatus::Stub, "DRAWS RNG -- exactly one unit draw per expired node line per turn"}, {Driver::Tail, 12, "T12", "DrainColonyLossQueue", PhaseStatus::Stub, ""}, {Driver::Tail, 13, "T13", "UpdateTreasuryMorale", PhaseStatus::Stub, ""}, {Driver::Tail, 14, "T14", "UpdateForeignFleetMorale", PhaseStatus::Stub, ""}, {Driver::Tail, 15, "T15", "ProcessBankruptcy", PhaseStatus::Stub, "the decision function is modelled in game::sim; the per-player state it reads is not " "assembled here"}, {Driver::Tail, 16, "T16", "ResolveArrivedColonizers", PhaseStatus::Stub, ""}, {Driver::Tail, 17, "T17", "RebuildPlayerViewTree", PhaseStatus::Stub, ""}, {Driver::Tail, 18, "T18", "PostFleetWarnings", PhaseStatus::Stub, ""}, {Driver::Tail, 19, "T19", "DrainInfraTerraformQueue", PhaseStatus::Stub, ""}, {Driver::Tail, 20, "T20", "ScriptHooksTurnEnd", PhaseStatus::Stub, ""}, {Driver::Tail, 21, "T21", "UpdateSurveyAndSystemStats", PhaseStatus::Stub, ""}, {Driver::Tail, 22, "T22", "TradeSliderFinalisationSecondPass", PhaseStatus::Stub, ""}, {Driver::Tail, 23, "T23", "TradeManagerEndOfTurnHooks", PhaseStatus::Stub, "eight vtable calls, wholly unidentified"}, {Driver::Tail, 24, "T24", "RecomputeMaintenanceAndResearchBonus", PhaseStatus::Stub, "recomputes per-player ship maintenance and research bonus and rebuilds the ship records"}, {Driver::Tail, 25, "T25", "SensorUpdateSecondPass", PhaseStatus::Stub, ""}, {Driver::Tail, 26, "T26", "ScriptHookPostSensorTail", PhaseStatus::Stub, ""}, {Driver::Tail, 27, "T27", "RefreshPlayerViewsSecondPass", PhaseStatus::Stub, ""}, {Driver::Tail, 28, "T28", "UpdateNodeLineSightingMasks", PhaseStatus::Stub, ""}, {Driver::Tail, 29, "T29", "AbortInvisibleInterceptOrders", PhaseStatus::Stub, ""}, {Driver::Tail, 30, "T30", "RebuildCommunicationMasks", PhaseStatus::Stub, ""}, {Driver::Tail, 31, "T31", "UpdateBankruptcyLimits", PhaseStatus::Blocked, "the limits formula is modelled in game::sim; its input is the sum of every owned " "system's MAXIMUM money output, which needs the same unresolved population->output term " "as P01. Evaluated and reported, not committed"}, {Driver::Tail, 32, "T32", "PostIncomingFleetWarnings", PhaseStatus::Stub, ""}, {Driver::Tail, 33, "T33", "ShipManagerEndOfTurnHooks", PhaseStatus::Stub, ""}, {Driver::Tail, 34, "T34", "RecordObservedDesigns", PhaseStatus::Stub, ""}, {Driver::Tail, 35, "T35", "RebuildPlayerReports", PhaseStatus::Stub, ""}, {Driver::Tail, 36, "T36", "FinalizeTurnRecords", PhaseStatus::Stub, "fills every player's turn record and archives it by turn; must stay last"}, }; PhaseTally Tally(const PhaseDesc* p, std::size_t n) { PhaseTally t; t.total = static_cast(n); for (std::size_t i = 0; i < n; ++i) { switch (p[i].status) { case PhaseStatus::Verified: ++t.verified; break; case PhaseStatus::Implemented: ++t.implemented; break; case PhaseStatus::Partial: ++t.partial; break; case PhaseStatus::Blocked: ++t.blocked; break; case PhaseStatus::Stub: ++t.stub; break; } } return t; } } // namespace const PhaseDesc* HostPhases(std::size_t& count) { count = sizeof(kHost) / sizeof(kHost[0]); return kHost; } const PhaseDesc* StrategicPhases(std::size_t& count) { count = sizeof(kStrategic) / sizeof(kStrategic[0]); return kStrategic; } const PhaseDesc* PlayerPhases(std::size_t& count) { count = sizeof(kPlayer) / sizeof(kPlayer[0]); return kPlayer; } const PhaseDesc* TailPhases(std::size_t& count) { count = sizeof(kTail) / sizeof(kTail[0]); return kTail; } PhaseTally TallySpine() { std::size_t ns = 0, np = 0; const PhaseDesc* s = StrategicPhases(ns); const PhaseDesc* p = PlayerPhases(np); PhaseTally a = Tally(s, ns), b = Tally(p, np); PhaseTally t; t.total = a.total + b.total; t.verified = a.verified + b.verified; t.implemented = a.implemented + b.implemented; t.partial = a.partial + b.partial; t.blocked = a.blocked + b.blocked; t.stub = a.stub + b.stub; return t; } PhaseTally TallyTail() { std::size_t n = 0; const PhaseDesc* p = TailPhases(n); return Tally(p, n); } } // namespace sots::app