make deconstruction its own system

DeconstructionSystem takes over the demolition queue: it runs the front entry's
timer and, when it elapses, removes the building, releases its tiles and credits
the partial refund. Simulation::tick calls it directly, in the position
tickDeconstruction held.

It needs no BeltSystem, unlike its construction counterpart: a belt, splitter or
tunnel end is unregistered the moment it is queued, not when the timer completes.
It does need the refund sink, so it takes the same addBuildingBlocks callback
BuildingSystem holds.

startFrontDeconstruction becomes a shared free function rather than moving:
BuildingSystem::deconstruct starts the timer when it queues the first entry, and
the system restarts it after each completion.

Stubbing the refund sink out in the test helper made two tests fail on the
refund not arriving — correctly. runTicks now threads the caller's stock through
instead.

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-05 07:01:22 +02:00
parent 56b7248ac7
commit a86ba3428a
8 changed files with 154 additions and 102 deletions

View File

@@ -0,0 +1,67 @@
#include "DeconstructionSystem.h"
#include <vector>
#include "Building.h"
#include "tracing.h"
void startFrontDeconstruction(FactoryState& state, const GameConfig& config,
Tick currentTick)
{
if (state.deconstructionQueue.empty()) { return; }
DeconstructionEntry& front = state.deconstructionQueue.front();
if (front.completesAt == 0)
{
front.completesAt =
currentTick + secondsToTicks(config.world.deconstructionTimeSeconds);
}
}
void DeconstructionSystem::tick(FactoryState& state, Tick currentTick)
{
TRACE();
if (state.deconstructionQueue.empty())
{
return;
}
DeconstructionEntry& front = state.deconstructionQueue.front();
// Guard: if the front entry's timer was never started, start it now.
if (front.completesAt == 0)
{
startFrontDeconstruction(state, m_config, currentTick);
return;
}
if (currentTick < front.completesAt)
{
return;
}
// Remove the building from the world and credit its refund (REQ-BLD-DECONSTRUCT).
// Belt/tunnel/splitter tiles were already unregistered when the building was
// queued (see deconstruct), so only tile occupancy and the record remain.
for (std::vector<Building>::iterator it = state.buildings.begin();
it != state.buildings.end();
++it)
{
if (it->id != front.id) { continue; }
const BuildingDef* def = m_config.buildings.findBuildingDef(it->type);
state.grid.release(it->bodyCells);
state.buildings.erase(it);
if (def)
{
m_addBuildingBlocks(def->cost * m_config.world.refundPercentage / 100);
}
break;
}
state.deconstructionQueue.pop_front();
// Start the next queued deconstruction, if any.
startFrontDeconstruction(state, m_config, currentTick);
}