move the placement rules and the config-dependent queries off BuildingSystem
isPlacementValid, findRotateInPlaceTarget and the bodyCellsWithinWorldBounds helper become PlacementRules.h — where a building may go and what already sits on those tiles, answered from the factory state and the config. getInputPorts and getSiteSplitterInfo join FactoryQueries.h, whose header comment now says plainly that the last two also take the config because answering them means reading a building definition. computeInputPorts goes to PortGeometry.h alongside outputBodyTile/inputBodyTile: it needs only Port and QPoint, so it belongs in core rather than in sim. BuildingSystem is left with no query that reads the factory — its remaining const methods are the emerging/incoming item walks, the checksum fold, and the buffer initialisers. It changes the factory now; it no longer describes it. 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:
@@ -7,6 +7,7 @@
|
||||
#include <set>
|
||||
|
||||
#include "FactoryQueries.h"
|
||||
#include "PlacementRules.h"
|
||||
#include "ProductionRules.h"
|
||||
#include "PortGeometry.h"
|
||||
#include "StateChecksum.h"
|
||||
@@ -182,87 +183,6 @@ void BuildingSystem::initSalvageBayBuffer(Building& b) const
|
||||
(def && def->outputBufferCapacity) ? *def->outputBufferCapacity : 0;
|
||||
}
|
||||
|
||||
std::vector<Port> BuildingSystem::computeInputPorts(const Building& b) const
|
||||
{
|
||||
return computeInputPorts(b.bodyCells, b.outputPorts);
|
||||
}
|
||||
|
||||
std::vector<Port> BuildingSystem::computeInputPorts(
|
||||
const std::vector<QPoint>& bodyCells,
|
||||
const std::vector<Port>& outputPorts) const
|
||||
{
|
||||
// Build lookup sets for quick membership checks.
|
||||
std::set<std::pair<int, int>> bodySet;
|
||||
for (const QPoint& cell : bodyCells)
|
||||
{
|
||||
bodySet.insert({cell.x(), cell.y()});
|
||||
}
|
||||
|
||||
std::set<std::pair<int, int>> outputPortTiles;
|
||||
for (const Port& port : outputPorts)
|
||||
{
|
||||
outputPortTiles.insert({port.tile.x(), port.tile.y()});
|
||||
}
|
||||
|
||||
// Neighbour deltas and the corresponding "inward" belt direction.
|
||||
const int dx[4] = {-1, 1, 0, 0};
|
||||
const int dy[4] = { 0, 0, -1, 1};
|
||||
const Rotation inward[4] = {
|
||||
Rotation::East, // neighbour is to the West; belt flows East toward building
|
||||
Rotation::West, // neighbour is to the East; belt flows West toward building
|
||||
Rotation::South, // neighbour is above (row-1); belt flows South toward building
|
||||
Rotation::North // neighbour is below (row+1); belt flows North toward building
|
||||
};
|
||||
|
||||
std::set<std::pair<int, int>> seen;
|
||||
std::vector<Port> inputPorts;
|
||||
|
||||
for (const QPoint& cell : bodyCells)
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
const int nx = cell.x() + dx[i];
|
||||
const int ny = cell.y() + dy[i];
|
||||
const std::pair<int, int> neighbor = {nx, ny};
|
||||
|
||||
if (bodySet.count(neighbor)) { continue; }
|
||||
if (outputPortTiles.count(neighbor)){ continue; }
|
||||
if (seen.count(neighbor)) { continue; }
|
||||
|
||||
seen.insert(neighbor);
|
||||
Port port;
|
||||
port.tile = QPoint(nx, ny);
|
||||
port.direction = inward[i];
|
||||
inputPorts.push_back(port);
|
||||
}
|
||||
}
|
||||
|
||||
return inputPorts;
|
||||
}
|
||||
|
||||
std::vector<Port> BuildingSystem::getInputPorts(BuildingId id) const
|
||||
{
|
||||
if (const Building* building = findBuilding(m_state, id))
|
||||
{
|
||||
return building->inputPorts;
|
||||
}
|
||||
if (const ConstructionSite* site = findSite(m_state, id))
|
||||
{
|
||||
// A site stores no ports; derive its output ports from the mask (absolute)
|
||||
// and run the same input-edge scan (REQ-BLD-BELT-DRAG, REQ-MAT-INPUT-PORTS).
|
||||
const BuildingDef* def = m_config.buildings.findBuildingDef(site->type);
|
||||
if (def == nullptr) { return {}; }
|
||||
const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, site->rotation);
|
||||
std::vector<Port> outputPortsAbsolute;
|
||||
outputPortsAbsolute.reserve(mask.outputPorts.size());
|
||||
for (const Port& port : mask.outputPorts)
|
||||
{
|
||||
outputPortsAbsolute.push_back(Port{ site->anchor + port.tile, port.direction });
|
||||
}
|
||||
return computeInputPorts(site->bodyCells, outputPortsAbsolute);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<Item> BuildingSystem::rollReprocessingOutput(const RecipeDef& recipe)
|
||||
{
|
||||
@@ -301,7 +221,7 @@ std::optional<BuildingId> BuildingSystem::place(BuildingType type, QPoint anchor
|
||||
const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, rotation);
|
||||
|
||||
// Reject placements that fall outside the world (REQ-BLD-PLACE-VALID).
|
||||
if (!bodyCellsWithinWorldBounds(mask.bodyCells, anchor))
|
||||
if (!bodyCellsWithinWorldBounds(m_state, m_config, mask.bodyCells, anchor))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -337,70 +257,6 @@ std::optional<BuildingId> BuildingSystem::place(BuildingType type, QPoint anchor
|
||||
return id;
|
||||
}
|
||||
|
||||
bool BuildingSystem::bodyCellsWithinWorldBounds(const std::vector<QPoint>& bodyCells,
|
||||
QPoint anchor) const
|
||||
{
|
||||
const int heightTiles = m_config.world.heightTiles;
|
||||
const int leftEdgeX = -m_state.asteroidWidth_tiles;
|
||||
for (const QPoint& cell : bodyCells)
|
||||
{
|
||||
const QPoint worldCell = anchor + cell;
|
||||
if (worldCell.y() < 0 || worldCell.y() >= heightTiles)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (worldCell.x() < leftEdgeX)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuildingSystem::isPlacementValid(BuildingType type, QPoint anchor,
|
||||
Rotation rotation) const
|
||||
{
|
||||
const BuildingDef* def = m_config.buildings.findBuildingDef(type);
|
||||
if (def == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, rotation);
|
||||
|
||||
if (!bodyCellsWithinWorldBounds(mask.bodyCells, anchor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Terrain: ship-dock (S) cells must sit in space (x >= 0); all other body
|
||||
// (A) cells must sit on the asteroid (x < 0). (REQ-BLD-PLACE-VALID)
|
||||
for (const QPoint& cell : mask.bodyCells)
|
||||
{
|
||||
const QPoint worldCell = anchor + cell;
|
||||
bool isShipDock = false;
|
||||
for (const QPoint& dock : mask.shipDockCells)
|
||||
{
|
||||
if (dock == cell)
|
||||
{
|
||||
isShipDock = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isShipDock)
|
||||
{
|
||||
if (worldCell.x() < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (worldCell.x() >= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Deconstruct
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -595,29 +451,6 @@ void BuildingSystem::setShipLayout(BuildingId id, const ShipLayoutConfig& layout
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<BeltSystem::SplitterInfo>
|
||||
BuildingSystem::getSiteSplitterInfo(BuildingId id) const
|
||||
{
|
||||
for (const ConstructionSite& site : m_state.constructionQueue)
|
||||
{
|
||||
if (site.id != id) { continue; }
|
||||
if (site.type != BuildingType::Splitter) { return std::nullopt; }
|
||||
|
||||
const BuildingDef* def = m_config.buildings.findBuildingDef(site.type);
|
||||
const ParsedSurfaceMask mask = parseSurfaceMask(
|
||||
def ? def->surfaceMask : std::vector<std::string>{}, site.rotation);
|
||||
if (mask.outputPorts.size() < 2) { return std::nullopt; }
|
||||
|
||||
BeltSystem::SplitterInfo info;
|
||||
info.outputA = mask.outputPorts[0].direction;
|
||||
info.outputB = mask.outputPorts[1].direction;
|
||||
info.filterA = site.splitterFilterA;
|
||||
info.filterB = site.splitterFilterB;
|
||||
return info;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void BuildingSystem::setSiteSplitterFilters(BuildingId id,
|
||||
const std::vector<ItemType>& filterA,
|
||||
const std::vector<ItemType>& filterB)
|
||||
@@ -690,7 +523,7 @@ void BuildingSystem::tickConstruction(Tick currentTick)
|
||||
building.outputPorts.push_back(absPort);
|
||||
}
|
||||
building.emergingItems.resize(building.outputPorts.size());
|
||||
building.inputPorts = computeInputPorts(building);
|
||||
building.inputPorts = computeInputPorts(building.bodyCells, building.outputPorts);
|
||||
building.incomingItems.assign(building.inputPorts.size(), {});
|
||||
|
||||
if (building.type == BuildingType::SalvageBay)
|
||||
@@ -1263,56 +1096,6 @@ void BuildingSystem::forEachIncomingItem(
|
||||
|
||||
|
||||
|
||||
std::optional<BuildingId> BuildingSystem::findRotateInPlaceTarget(
|
||||
BuildingType type, QPoint anchor, Rotation rot) const
|
||||
{
|
||||
// Tunnel Entries and Tunnel Exits cannot be rotated in place; re-orienting a
|
||||
// tunnel requires deconstructing and re-placing it (REQ-BLD-ROTATE-IN-PLACE).
|
||||
if (type == BuildingType::TunnelEntry || type == BuildingType::TunnelExit)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const BuildingDef* def = m_config.buildings.findBuildingDef(type);
|
||||
if (!def) { return std::nullopt; }
|
||||
|
||||
const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, rot);
|
||||
if (mask.bodyCells.empty()) { return std::nullopt; }
|
||||
|
||||
// All body cells must be occupied by the same entity.
|
||||
const QPoint firstAbs = anchor + mask.bodyCells[0];
|
||||
const std::optional<BuildingId> firstOwner = m_state.grid.findOwner(firstAbs);
|
||||
if (!firstOwner.has_value()) { return std::nullopt; }
|
||||
const BuildingId candidateId = *firstOwner;
|
||||
|
||||
for (const QPoint& rel : mask.bodyCells)
|
||||
{
|
||||
const std::optional<BuildingId> owner = m_state.grid.findOwner(anchor + rel);
|
||||
if (!owner.has_value() || *owner != candidateId)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the candidate is the same building type with the same cell count.
|
||||
for (const ConstructionSite& site : m_state.constructionQueue)
|
||||
{
|
||||
if (site.id != candidateId) { continue; }
|
||||
if (site.type != type) { return std::nullopt; }
|
||||
if (site.bodyCells.size() != mask.bodyCells.size()) { return std::nullopt; }
|
||||
return candidateId;
|
||||
}
|
||||
for (const Building& b : m_state.buildings)
|
||||
{
|
||||
if (b.id != candidateId) { continue; }
|
||||
if (b.type != type) { return std::nullopt; }
|
||||
if (b.bodyCells.size() != mask.bodyCells.size()) { return std::nullopt; }
|
||||
return candidateId;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void BuildingSystem::rotateInPlace(BuildingId id, Rotation newRotation)
|
||||
{
|
||||
// Construction site path — just update rotation; no ports to recompute.
|
||||
@@ -1348,7 +1131,7 @@ void BuildingSystem::rotateInPlace(BuildingId id, Rotation newRotation)
|
||||
// the lanes to the new port set (REQ-MAT-OUTPUT-EMERGE).
|
||||
b.emergingItems.clear();
|
||||
b.emergingItems.resize(b.outputPorts.size());
|
||||
b.inputPorts = computeInputPorts(b);
|
||||
b.inputPorts = computeInputPorts(b.bodyCells, b.outputPorts);
|
||||
// Likewise discard in-transit input items and re-size the input belts to
|
||||
// the new port set (REQ-MAT-INPUT-INTAKE).
|
||||
b.incomingItems.assign(b.inputPorts.size(), {});
|
||||
@@ -1406,7 +1189,7 @@ BuildingId BuildingSystem::placeImmediate(BuildingType type,
|
||||
building.outputPorts.push_back(absPort);
|
||||
}
|
||||
building.emergingItems.resize(building.outputPorts.size());
|
||||
building.inputPorts = computeInputPorts(building);
|
||||
building.inputPorts = computeInputPorts(building.bodyCells, building.outputPorts);
|
||||
building.incomingItems.assign(building.inputPorts.size(), {});
|
||||
|
||||
if (type == BuildingType::SalvageBay)
|
||||
|
||||
Reference in New Issue
Block a user