put the block stock where the blocks are
The global building block stock lived on Simulation while being factory data through and through: placement spends it, deconstruction refunds it, and blocks delivered to the HQ by belt add to it. Every system that credited it therefore held a std::function back into Simulation to do so -- BuildingSystem and DeconstructionSystem each carried one, and the arena and three test fixtures had to pass a stub. It moves into FactoryState, seeded by makeFactoryState from world.starting_building_blocks, and both callbacks are gone: the HQ's belt intake and the deconstruction refund now credit the state they are already holding. BuildingSystem::deconstruct stops returning a refund for its caller to remember to credit. It had grown asymmetric -- the queued path credits itself through DeconstructionSystem while the instant path handed a number back -- so it now credits the site's full cost directly and returns void. Checksum order is untouched: Simulation still folds the stock at exactly the point it always did, reading it from the state. The arena no longer discards refunds into a no-op sink; nothing there reads the stock either way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
This commit is contained in:
@@ -28,7 +28,6 @@ bool inputLaneEntryFree(const std::vector<BeltItemSlot>& lane)
|
||||
BuildingSystem::BuildingSystem(const GameConfig& config,
|
||||
BeltSystem& belts,
|
||||
std::function<BuildingId()> allocateBuildingId,
|
||||
std::function<void(int)> addBuildingBlocks,
|
||||
std::function<void(const std::string&, QVector2D,
|
||||
const std::optional<ShipLayoutConfig>&)> spawnShip,
|
||||
std::function<bool(const std::string&)> isItemUnlocked,
|
||||
@@ -36,7 +35,6 @@ BuildingSystem::BuildingSystem(const GameConfig& config,
|
||||
: m_config(config)
|
||||
, m_belts(belts)
|
||||
, m_allocateBuildingId(std::move(allocateBuildingId))
|
||||
, m_addBuildingBlocks(std::move(addBuildingBlocks))
|
||||
, m_spawnShip(std::move(spawnShip))
|
||||
, m_isItemUnlocked(std::move(isItemUnlocked))
|
||||
, m_rng(rng)
|
||||
@@ -158,7 +156,7 @@ std::optional<BuildingId> BuildingSystem::place(FactoryState& state, BuildingTyp
|
||||
// Deconstruct
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int BuildingSystem::deconstruct(FactoryState& state, BuildingId id, Tick currentTick)
|
||||
void BuildingSystem::deconstruct(FactoryState& state, BuildingId id, Tick currentTick)
|
||||
{
|
||||
// Construction site? Removed instantly with the full refund; never queued
|
||||
// for deconstruction (REQ-BLD-DECONSTRUCT).
|
||||
@@ -173,9 +171,9 @@ int BuildingSystem::deconstruct(FactoryState& state, BuildingId id, Tick current
|
||||
state.constructionQueue.erase(it);
|
||||
if (def)
|
||||
{
|
||||
return def->cost;
|
||||
state.buildingBlocksStock += def->cost;
|
||||
}
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +183,7 @@ int BuildingSystem::deconstruct(FactoryState& state, BuildingId id, Tick current
|
||||
for (Building& building : state.buildings)
|
||||
{
|
||||
if (building.id != id) { continue; }
|
||||
if (building.queuedForDeconstruction) { return 0; } // already queued
|
||||
if (building.queuedForDeconstruction) { return; } // already queued
|
||||
|
||||
building.queuedForDeconstruction = true;
|
||||
|
||||
@@ -216,10 +214,8 @@ int BuildingSystem::deconstruct(FactoryState& state, BuildingId id, Tick current
|
||||
{
|
||||
startFrontDeconstruction(state, m_config, currentTick);
|
||||
}
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -411,7 +407,7 @@ void BuildingSystem::tickBeltPull(FactoryState& state)
|
||||
lane.erase(lane.begin());
|
||||
if (isHq)
|
||||
{
|
||||
m_addBuildingBlocks(1);
|
||||
state.buildingBlocksStock += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -41,7 +41,6 @@ public:
|
||||
BuildingSystem(const GameConfig& config,
|
||||
BeltSystem& belts,
|
||||
std::function<BuildingId()> allocateBuildingId,
|
||||
std::function<void(int)> addBuildingBlocks,
|
||||
std::function<void(const std::string&, QVector2D,
|
||||
const std::optional<ShipLayoutConfig>&)> spawnShip,
|
||||
std::function<bool(const std::string&)> isItemUnlocked,
|
||||
@@ -64,12 +63,12 @@ public:
|
||||
{ state.asteroidWidth_tiles = widthTiles; }
|
||||
|
||||
// Mark a building or construction site for demolition (REQ-BLD-DECONSTRUCT).
|
||||
// A construction site is removed instantly and the full cost is returned.
|
||||
// A fully-built building is instead appended to the deconstruction queue
|
||||
// (REQ-BLD-DECON-QUEUE) and stops operating at once; its (partial) refund is
|
||||
// credited later, on completion in tickDeconstruction, so this returns 0 for
|
||||
// it. Returns 0 for unknown ids and for a building already queued.
|
||||
int deconstruct(FactoryState& state, BuildingId id, Tick currentTick);
|
||||
// A construction site is removed instantly and its full cost credited back to the
|
||||
// block stock. A fully-built building is instead appended to the deconstruction
|
||||
// queue (REQ-BLD-DECON-QUEUE) and stops operating at once; its (partial) refund is
|
||||
// credited later, on completion, by DeconstructionSystem. No-op for unknown ids and
|
||||
// for a building already queued.
|
||||
void deconstruct(FactoryState& state, BuildingId id, Tick currentTick);
|
||||
|
||||
// Take a building back out of the deconstruction queue before it is removed
|
||||
// (REQ-BLD-DECON-QUEUE). Clears its queued flag and resumes operation
|
||||
@@ -180,7 +179,6 @@ private:
|
||||
const GameConfig& m_config;
|
||||
BeltSystem& m_belts;
|
||||
std::function<BuildingId()> m_allocateBuildingId;
|
||||
std::function<void(int)> m_addBuildingBlocks;
|
||||
std::function<void(const std::string&, QVector2D,
|
||||
const std::optional<ShipLayoutConfig>&)> m_spawnShip;
|
||||
std::function<bool(const std::string&)> m_isItemUnlocked;
|
||||
|
||||
@@ -54,7 +54,8 @@ void DeconstructionSystem::tick(FactoryState& state, Tick currentTick)
|
||||
state.buildings.erase(it);
|
||||
if (def)
|
||||
{
|
||||
m_addBuildingBlocks(def->cost * m_config.world.refundPercentage / 100);
|
||||
state.buildingBlocksStock +=
|
||||
def->cost * m_config.world.refundPercentage / 100;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "FactoryState.h"
|
||||
#include "GameConfig.h"
|
||||
#include "Tick.h"
|
||||
@@ -14,19 +12,17 @@
|
||||
// It needs no BeltSystem: a belt, splitter or tunnel end is unregistered the moment
|
||||
// it is queued (see BuildingSystem::deconstruct), not when the timer completes.
|
||||
//
|
||||
// Holds the config and the refund sink; the world arrives per tick.
|
||||
// Holds the config alone; the world arrives per tick, the refund going straight into
|
||||
// the block stock it carries (FactoryState.h).
|
||||
class DeconstructionSystem
|
||||
{
|
||||
public:
|
||||
DeconstructionSystem(const GameConfig& config,
|
||||
std::function<void(int)> addBuildingBlocks)
|
||||
: m_config(config), m_addBuildingBlocks(std::move(addBuildingBlocks)) {}
|
||||
explicit DeconstructionSystem(const GameConfig& config) : m_config(config) {}
|
||||
|
||||
void tick(FactoryState& state, Tick currentTick);
|
||||
|
||||
private:
|
||||
const GameConfig& m_config;
|
||||
std::function<void(int)> m_addBuildingBlocks;
|
||||
const GameConfig& m_config;
|
||||
};
|
||||
|
||||
// Starts the timer on the front entry of the deconstruction queue, if it has one and
|
||||
|
||||
@@ -57,15 +57,26 @@ struct FactoryState
|
||||
// derived from config and Simulation's expansion count, which is folded already.
|
||||
// Seeded from config by BuildingSystem's constructor.
|
||||
int asteroidWidth_tiles = 0;
|
||||
|
||||
// The global building block stock (REQ-HQ-STARTING-BLOCKS, REQ-HQ-BELT-INPUT):
|
||||
// what placement spends, what deconstruction refunds, and what blocks delivered to
|
||||
// the HQ add to. Factory data, so it lives with the factory rather than being
|
||||
// reached through a callback by every system that credits it.
|
||||
//
|
||||
// Folded into the checksum by Simulation, in the place it has always occupied
|
||||
// (docs/replay_design.md).
|
||||
int buildingBlocksStock = 0;
|
||||
};
|
||||
|
||||
// A fresh factory for a new run: nothing built, and the asteroid bound seeded from
|
||||
// config. Every owner of a FactoryState creates it this way — the bound has no
|
||||
// sensible default without the config, so a default-constructed state would refuse
|
||||
// every placement on the asteroid.
|
||||
// A fresh factory for a new run: nothing built, the asteroid bound and the starting
|
||||
// block stock seeded from config. Every owner of a FactoryState creates it this way —
|
||||
// the bound has no sensible default without the config, so a default-constructed state
|
||||
// would refuse every placement on the asteroid, and the player would start with nothing
|
||||
// to build from.
|
||||
inline FactoryState makeFactoryState(const GameConfig& config)
|
||||
{
|
||||
FactoryState state;
|
||||
state.asteroidWidth_tiles = config.world.regions.asteroidWidth_tiles;
|
||||
state.asteroidWidth_tiles = config.world.regions.asteroidWidth_tiles;
|
||||
state.buildingBlocksStock = config.world.startingBuildingBlocks;
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
|
||||
, m_currentTick(0)
|
||||
, m_nextDepartureTick(secondsToTicks(m_config.world.departureIntervalSeconds))
|
||||
, m_nextBuildingId(1)
|
||||
, m_buildingBlocksStock(m_config.world.startingBuildingBlocks)
|
||||
, m_gameOver(false)
|
||||
, m_hqProxyEntity(entt::null)
|
||||
, m_playerStation1Entity(entt::null)
|
||||
@@ -85,7 +84,6 @@ void Simulation::reset(unsigned int seed)
|
||||
m_currentTick = 0;
|
||||
m_nextDepartureTick = secondsToTicks(m_config.world.departureIntervalSeconds);
|
||||
m_nextBuildingId = 1;
|
||||
m_buildingBlocksStock = m_config.world.startingBuildingBlocks;
|
||||
m_expansionsPurchased = 0;
|
||||
m_gameOver = false;
|
||||
m_isWon = false;
|
||||
@@ -114,7 +112,6 @@ void Simulation::initializeSubsystems()
|
||||
m_config,
|
||||
m_beltSystem,
|
||||
[this]() { return allocateBuildingId(); },
|
||||
[this](int amount) { m_buildingBlocksStock += amount; },
|
||||
[this](const std::string& id, QVector2D pos,
|
||||
const std::optional<ShipLayoutConfig>& layout) {
|
||||
if (!isSchematicUnlocked(id))
|
||||
@@ -126,8 +123,7 @@ void Simulation::initializeSubsystems()
|
||||
[this](const std::string& itemId) -> bool { return isItemUnlocked(itemId); },
|
||||
m_rng);
|
||||
m_constructionSystem = std::make_unique<ConstructionSystem>(m_config);
|
||||
m_deconstructionSystem = std::make_unique<DeconstructionSystem>(
|
||||
m_config, [this](int amount) { m_buildingBlocksStock += amount; });
|
||||
m_deconstructionSystem = std::make_unique<DeconstructionSystem>(m_config);
|
||||
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
||||
m_aiSystem = std::make_unique<AiSystem>(m_config);
|
||||
m_movementIntentSystem = std::make_unique<MovementIntentSystem>();
|
||||
@@ -658,7 +654,7 @@ unsigned long long Simulation::computeStateChecksum() const
|
||||
hasher.append(m_currentTick);
|
||||
hasher.append(m_nextDepartureTick);
|
||||
hasher.append(m_nextBuildingId);
|
||||
hasher.append(m_buildingBlocksStock);
|
||||
hasher.append(m_factoryState.buildingBlocksStock);
|
||||
hasher.append(m_gameOver);
|
||||
hasher.append(m_isWon);
|
||||
hasher.append(m_artifactCount);
|
||||
@@ -763,7 +759,7 @@ unsigned int Simulation::getSeed() const
|
||||
|
||||
int Simulation::getBuildingBlocksStock() const
|
||||
{
|
||||
return m_buildingBlocksStock;
|
||||
return m_factoryState.buildingBlocksStock;
|
||||
}
|
||||
|
||||
int Simulation::getCurrentAsteroidWidth_tiles() const
|
||||
@@ -782,11 +778,11 @@ int Simulation::getCurrentExpansionCost() const
|
||||
void Simulation::tryExpandAsteroid()
|
||||
{
|
||||
const int cost = getCurrentExpansionCost();
|
||||
if (m_buildingBlocksStock < cost)
|
||||
if (m_factoryState.buildingBlocksStock < cost)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_buildingBlocksStock -= cost;
|
||||
m_factoryState.buildingBlocksStock -= cost;
|
||||
++m_expansionsPurchased;
|
||||
m_buildingSystem->setAsteroidWidth_tiles(m_factoryState, getCurrentAsteroidWidth_tiles());
|
||||
}
|
||||
@@ -879,17 +875,17 @@ std::optional<BuildingId> Simulation::tryPlaceBuilding(BuildingType type, QPoint
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m_buildingBlocksStock < cost)
|
||||
if (m_factoryState.buildingBlocksStock < cost)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
m_buildingBlocksStock -= cost;
|
||||
m_factoryState.buildingBlocksStock -= cost;
|
||||
return m_buildingSystem->place(m_factoryState, type, anchor, rotation, m_currentTick);
|
||||
}
|
||||
|
||||
void Simulation::deconstruct(BuildingId id)
|
||||
{
|
||||
m_buildingBlocksStock += m_buildingSystem->deconstruct(m_factoryState, id, m_currentTick);
|
||||
m_buildingSystem->deconstruct(m_factoryState, id, m_currentTick);
|
||||
}
|
||||
|
||||
void Simulation::cancelDeconstruction(BuildingId id)
|
||||
|
||||
@@ -196,7 +196,6 @@ private:
|
||||
Tick m_currentTick;
|
||||
Tick m_nextDepartureTick;
|
||||
BuildingId m_nextBuildingId;
|
||||
int m_buildingBlocksStock;
|
||||
int m_expansionsPurchased = 0; // REQ-EXP-COST formula variable x
|
||||
bool m_gameOver = false;
|
||||
bool m_isWon = false;
|
||||
|
||||
Reference in New Issue
Block a user