depend on factory data instead of BuildingSystem in the AI path

Ten queries that read nothing but FactoryState become free functions in
FactoryQueries.h; the BuildingSystem methods stay as one-line forwards, so no
existing caller moves yet.

That lets the AI path drop its dependency on the system entirely. AiSystem,
SalvagerSystem, DeliverScrapEvaluator and DeliverScrapExecutor took a
BuildingSystem& purely to call findBuilding, findNearestBuilding and
deliverScrapToSalvageBay — all three are state-pure — so they now take
FactoryState& and say what they actually read. Four forward declarations of
BuildingSystem go with them.

No facade: the queries are plain free functions over the data. A facade was
considered to spare the ~180 UI call sites, but the AI needed only the data and
would have been given GameConfig it has no use for.

isProductionBuildingType moves to BuildingType.h beside isAutoRecipeBuildingType
and isBeltSubsystemType rather than being copied into the new file.

Verified with a golden-checksum capture before and after — all four sample ticks
identical.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
This commit is contained in:
2026-08-04 21:26:49 +02:00
parent 2522a8c974
commit 7540c21d5c
17 changed files with 238 additions and 126 deletions

View File

@@ -0,0 +1,122 @@
#include "FactoryQueries.h"
#include <limits>
#include "Item.h"
#include "ItemType.h"
const Building* findBuilding(const FactoryState& state, BuildingId id)
{
for (const Building& building : state.buildings)
{
if (building.id == id)
{
return &building;
}
}
return nullptr;
}
Building* findBuilding(FactoryState& state, BuildingId id)
{
for (Building& building : state.buildings)
{
if (building.id == id)
{
return &building;
}
}
return nullptr;
}
const ConstructionSite* findSite(const FactoryState& state, BuildingId id)
{
for (const ConstructionSite& site : state.constructionQueue)
{
if (site.id == id)
{
return &site;
}
}
return nullptr;
}
std::vector<Building> getAllBuildings(const FactoryState& state)
{
return state.buildings;
}
std::vector<ConstructionSite> getAllSites(const FactoryState& state)
{
return std::vector<ConstructionSite>(state.constructionQueue.begin(),
state.constructionQueue.end());
}
int getProductionBuildingCount(const FactoryState& state)
{
int count = 0;
for (const Building& b : state.buildings)
{
if (isProductionBuildingType(b.type)) { ++count; }
}
return count;
}
int getActiveProductionBuildingCount(const FactoryState& state)
{
int count = 0;
for (const Building& b : state.buildings)
{
if (isProductionBuildingType(b.type) && b.production.has_value()) { ++count; }
}
return count;
}
bool isTileOccupied(const FactoryState& state, QPoint tile)
{
return state.grid.isOccupied(tile);
}
const Building* findNearestBuilding(const FactoryState& state, QVector2D worldPos,
BuildingType type)
{
const Building* best = nullptr;
float bestDist = std::numeric_limits<float>::max();
for (const Building& b : state.buildings)
{
if (b.type != type)
{
continue;
}
QVector2D center(b.anchor.x() + b.footprint.width() / 2.0f,
b.anchor.y() + b.footprint.height() / 2.0f);
float dist = (center - worldPos).length();
if (dist < bestDist)
{
bestDist = dist;
best = &b;
}
}
return best;
}
bool deliverScrapToSalvageBay(FactoryState& state, BuildingId bayId)
{
Building* bay = findBuilding(state, bayId);
if (!bay || bay->type != BuildingType::SalvageBay)
{
return false;
}
if (bay->queuedForDeconstruction)
{
return false; // queued for deconstruction: stopped operating (REQ-BLD-DECON-QUEUE)
}
// Emerging scrap still counts against the bay's holding capacity
// (REQ-MAT-OUTPUT-EMERGE).
if (bay->getOutputItemCount() >= bay->outputBuffer.capacity)
{
return false;
}
bay->outputBuffer.items.push_back(Item{ItemType{"scrap"}});
return true;
}