game/sim/construction.{h,cpp}: Game::ShipRecords (four per-hull-class arrays plus the
per-design vector, sized by ENUMERATION against the wire, not by what the code touches)
and the completion bookkeeping BuildQueue::ProcessTurn performs -- the per-class built
counter, whose indexed increment has EXACTLY ONE writer in the whole image, and the
find-or-append per-design record keyed by the design's object id. RunSystemConstruction
wraps the point pass and keeps each completion's design id, which the point pass alone
does not report.
game/sim/colony: corrected from the instruction stream -- with points <= 0 the entry test
branches to the epilogue, so the REMOVAL SWEEP IS SKIPPED TOO. Carried as a labelled
hypothesis: no corpus save can reach the state that shows it.
app/construction_phase.{h,cpp}: S11's build-queue sub-pass, reported on its own line
because what blocks it is not what blocks the rest of the colony turn. It is blocked on
the per-system output term for points; it is NOT what the archived ship census waits on.
tests/game_sim/test_construction.cpp: 65 checks, including a corpus oracle the campaign
already owned and had not noticed -- zuul-turn16-noderoute -> zuul-turn17-rollpending is a
real consecutive-turn pair in which six orders complete and one is partially advanced.
The test SOLVES for the point total rather than assuming it, so a non-FIFO order, a
per-order budget or skip-instead-of-stop each falsify it.
96 lines
3.8 KiB
C++
96 lines
3.8 KiB
C++
#include "app/construction_phase.h"
|
|
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <map>
|
|
|
|
#include "game/sim/construction.h"
|
|
|
|
namespace sots::app {
|
|
|
|
namespace {
|
|
|
|
std::string fmt(const char* f, ...) {
|
|
char buf[512];
|
|
va_list ap;
|
|
va_start(ap, f);
|
|
std::vsnprintf(buf, sizeof buf, f, ap);
|
|
va_end(ap);
|
|
return std::string(buf);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
ConstructionPhaseResult RunBuildQueues(mars::stream::shapes::SaveGame& game) {
|
|
ConstructionPhaseResult r;
|
|
|
|
// The owner of a queue is the system's `PID`, which is the player's OBJECT id, not its
|
|
// position in the player vector. The two are different numbers and confusing them is a
|
|
// trap this codebase has already paid for once.
|
|
std::map<std::int32_t, std::size_t> playerIndex;
|
|
for (std::size_t i = 0; i < game.sim.players.size(); ++i)
|
|
playerIndex[game.sim.players[i].playerID] = i;
|
|
|
|
int shipBorne = 0;
|
|
int ordersByOwner = 0;
|
|
int unownedQueues = 0;
|
|
|
|
for (auto& e : game.sim.systems) {
|
|
if (!e.sys.bq.has_value()) continue;
|
|
++r.queuesVisited;
|
|
auto& q = *e.sys.bq;
|
|
r.ordersPending += static_cast<int>(q.orders.size());
|
|
for (const auto& o : q.orders) r.pointsDemanded += o.conleft;
|
|
if (!q.orders.empty()) {
|
|
if (playerIndex.count(e.sys.pid))
|
|
++ordersByOwner;
|
|
else
|
|
++unownedQueues;
|
|
}
|
|
}
|
|
|
|
// The ship-borne queues. Every one of them is gated behind the ship's `hbq` flag, and
|
|
// the count is reported because an order hiding in one would falsify the "no orders on
|
|
// the reference pair" reading, which is the load-bearing claim of this phase.
|
|
for (const auto& fe : game.sim.fleets)
|
|
for (const auto& se : fe.flt.ships)
|
|
if (se.ship.hbq) shipBorne += static_cast<int>(se.ship.bq2.orders.size());
|
|
|
|
r.blockedOnPoints = r.ordersPending > 0;
|
|
|
|
if (r.queuesVisited == 0) {
|
|
r.notes.push_back("no system carries a build queue: a queue is written only for a "
|
|
"system with an owner");
|
|
return r;
|
|
}
|
|
|
|
r.notes.push_back(fmt("%d system build queue(s), %d pending order(s) demanding %d "
|
|
"construction point(s); %d order(s) in ship-borne queues",
|
|
r.queuesVisited, r.ordersPending, r.pointsDemanded, shipBorne));
|
|
|
|
if (r.ordersPending == 0 && shipBorne == 0) {
|
|
r.notes.push_back("NOTHING TO BUILD. The pass is faithful and idle: with no order "
|
|
"anywhere in the game it cannot create a ship, so it closes no "
|
|
"leaf here. On the reference pairs the archived ship census is "
|
|
"still one destroyer higher than ours, and the order behind that "
|
|
"destroyer is created inside the turn by the AI -- that leaf is "
|
|
"blocked on AI order generation, not on this phase");
|
|
return r;
|
|
}
|
|
|
|
// Points would come from the system's output vector; that term is the roadmap's item 1
|
|
// and is not this lane's. The pass is therefore driven with nothing and reports the
|
|
// demand, so the shape of the gap is visible in the run log.
|
|
r.notes.push_back(fmt("BLOCKED: construction points come from the per-system output "
|
|
"term (out[7] scaled by the shipyard bonus, then out[8] = min(out"
|
|
"[7], demand)), which is unmodelled. %d order(s) would be offered "
|
|
"points this turn", r.ordersPending));
|
|
if (unownedQueues)
|
|
r.notes.push_back(fmt("%d queue(s) hold orders but their system's owner id resolves "
|
|
"to no player in the save -- reported, not skipped silently",
|
|
unownedQueues));
|
|
(void)ordersByOwner;
|
|
return r;
|
|
}
|
|
|
|
} // namespace sots::app
|