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

@@ -6,6 +6,7 @@
#include <random>
#include <set>
#include "FactoryQueries.h"
#include "PortGeometry.h"
#include "StateChecksum.h"
#include "SurfaceMask.h"
@@ -1264,87 +1265,37 @@ void BuildingSystem::forEachIncomingItem(
const Building* BuildingSystem::findBuilding(BuildingId id) const
{
for (const Building& building : m_state.buildings)
{
if (building.id == id)
{
return &building;
}
}
return nullptr;
return ::findBuilding(m_state, id);
}
Building* BuildingSystem::findBuildingMutable(BuildingId id)
{
for (Building& building : m_state.buildings)
{
if (building.id == id)
{
return &building;
}
}
return nullptr;
return ::findBuilding(m_state, id);
}
const ConstructionSite* BuildingSystem::findSite(BuildingId id) const
{
for (const ConstructionSite& site : m_state.constructionQueue)
{
if (site.id == id)
{
return &site;
}
}
return nullptr;
return ::findSite(m_state, id);
}
std::vector<Building> BuildingSystem::getAllBuildings() const
{
return m_state.buildings;
return ::getAllBuildings(m_state);
}
std::vector<ConstructionSite> BuildingSystem::getAllSites() const
{
return std::vector<ConstructionSite>(m_state.constructionQueue.begin(),
m_state.constructionQueue.end());
return ::getAllSites(m_state);
}
namespace
{
bool isProductionBuildingType(BuildingType type)
{
switch (type)
{
case BuildingType::Miner:
case BuildingType::Smelter:
case BuildingType::Assembler:
case BuildingType::ReprocessingPlant:
case BuildingType::Shipyard:
return true;
default:
return false;
}
}
} // namespace
int BuildingSystem::getProductionBuildingCount() const
{
int count = 0;
for (const Building& b : m_state.buildings)
{
if (isProductionBuildingType(b.type)) { ++count; }
}
return count;
return ::getProductionBuildingCount(m_state);
}
int BuildingSystem::getActiveProductionBuildingCount() const
{
int count = 0;
for (const Building& b : m_state.buildings)
{
if (isProductionBuildingType(b.type) && b.production.has_value()) { ++count; }
}
return count;
return ::getActiveProductionBuildingCount(m_state);
}
std::vector<const RecipeDef*>
@@ -1522,7 +1473,7 @@ std::vector<BuildingSystem::BeltTileInfo> BuildingSystem::getAllBeltTiles() cons
bool BuildingSystem::isTileOccupied(QPoint tile) const
{
return m_state.grid.isOccupied(tile);
return ::isTileOccupied(m_state, tile);
}
std::optional<BuildingId> BuildingSystem::findRotateInPlaceTarget(
@@ -1643,53 +1594,12 @@ void BuildingSystem::rotateInPlace(BuildingId id, Rotation newRotation)
const Building* BuildingSystem::findNearestBuilding(QVector2D worldPos,
BuildingType type) const
{
const Building* best = nullptr;
float bestDist = std::numeric_limits<float>::max();
for (const Building& b : m_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;
return ::findNearestBuilding(m_state, worldPos, type);
}
bool BuildingSystem::deliverScrapToSalvageBay(BuildingId bayId)
{
Building* bay = nullptr;
for (Building& b : m_state.buildings)
{
if (b.id == bayId)
{
bay = &b;
break;
}
}
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;
return ::deliverScrapToSalvageBay(m_state, bayId);
}
BuildingId BuildingSystem::placeImmediate(BuildingType type,