fix issue where status light was flickering to yellow for one tick between cycles

This commit is contained in:
2026-08-12 21:26:09 +02:00
parent b6b6acb99c
commit dd061082fb
4 changed files with 230 additions and 17 deletions

View File

@@ -563,10 +563,10 @@ void BuildingSystem::tickProduction(FactoryState& state, Tick currentTick)
}
// 3. Output buffer has space for chosen outputs? Emerging items still
// count against the buffer (REQ-MAT-OUTPUT-EMERGE).
const int newSize = building.getOutputItemCount()
+ static_cast<int>(chosen.size());
if (newSize > building.outputBuffer.capacity)
// count against the buffer (REQ-MAT-OUTPUT-EMERGE). The status light
// asks the same question to decide yellow (REQ-UI-STATUS-LIGHT), so the
// test lives in one place.
if (!outputBufferHasRoom(building, static_cast<int>(chosen.size())))
{
continue;
}

View File

@@ -1,10 +1,47 @@
#include "ProductionRules.h"
#include <algorithm>
#include <limits>
#include "BuildingType.h"
#include "ItemType.h"
#include "ModulesConfig.h"
#include "ShipsConfig.h"
namespace
{
// Items one cycle of this recipe would deposit into the output buffer. A Reprocessing
// Plant rolls exactly one of its outputs per cycle (REQ-BLD-REPROCESSING) and the roll
// happens in the simulation, so the smallest amount any roll could yield is what decides
// whether a cycle could start at all; a larger roll may still not fit.
int getCycleOutputItemCount(const Building& b, const RecipeDef& recipe)
{
if (recipe.outputs.empty())
{
return 0;
}
if (b.type == BuildingType::ReprocessingPlant)
{
int smallest = std::numeric_limits<int>::max();
for (const RecipeOutput& out : recipe.outputs)
{
smallest = std::min(smallest, out.amount);
}
return smallest;
}
int total = 0;
for (const RecipeOutput& out : recipe.outputs)
{
total += out.amount;
}
return total;
}
} // namespace
std::vector<const RecipeDef*>
gatherCandidateRecipes(const GameConfig& config, const Building& b)
{
@@ -128,8 +165,8 @@ bool hasInputsToStart(const GameConfig& config, const Building& b)
}
// Recipe buildings: startable if any candidate recipe's inputs are satisfied.
// A Miner recipe has no inputs, so an idle Miner is always startable and its
// only idle reason is a full output buffer.
// A Miner recipe has no inputs, so an idle Miner is always startable here and its
// only idle reason is an output buffer without room for the next cycle.
for (const RecipeDef* recipe : gatherCandidateRecipes(config, b))
{
if (recipeInputsAvailable(b, *recipe))
@@ -139,6 +176,32 @@ bool hasInputsToStart(const GameConfig& config, const Building& b)
}
return false;
}
bool outputBufferHasRoom(const Building& b, int outputItemCount)
{
return b.getOutputItemCount() + outputItemCount <= b.outputBuffer.capacity;
}
bool canStartCycle(const GameConfig& config, const Building& b)
{
// A shipyard's completed cycle spawns a ship instead of filling an output buffer
// (REQ-BLD-SHIPYARD), so holding the materials is the whole condition.
if (b.type == BuildingType::Shipyard)
{
return hasInputsToStart(config, b);
}
for (const RecipeDef* recipe : gatherCandidateRecipes(config, b))
{
if (recipeInputsAvailable(b, *recipe)
&& outputBufferHasRoom(b, getCycleOutputItemCount(b, *recipe)))
{
return true;
}
}
return false;
}
std::optional<ProductionStatus>
getProductionStatus(const GameConfig& config, const Building& building)
{
@@ -169,9 +232,18 @@ getProductionStatus(const GameConfig& config, const Building& building)
return ProductionStatus::Producing;
}
// Idle: missing inputs (red) take precedence over a full output buffer
// (yellow). If inputs are present yet the building is idle, the only remaining
// reason it could not start a cycle is a full output buffer (REQ-MAT-CYCLE).
// Idle, but blocked by neither condition: the building is only between cycles and
// the simulation starts the next one on a following tick. A building running back
// to back sits here for exactly one tick per cycle, since tickProduction never
// starts a cycle in the tick one completed, so this must read as producing rather
// than blink (REQ-UI-STATUS-LIGHT, REQ-MAT-CYCLE).
if (canStartCycle(config, building))
{
return ProductionStatus::Producing;
}
// Idle for a reason: a missing input (red) takes precedence over an output buffer
// with no room for the next cycle's output (yellow).
return hasInputsToStart(config, building) ? ProductionStatus::Blocked
: ProductionStatus::Starved;
: ProductionStatus::Starved;
}

View File

@@ -53,6 +53,16 @@ double computeShipyardProductionTimeSeconds(
// True when a production cycle could start right now, ignoring output-buffer space.
bool hasInputsToStart(const GameConfig& config, const Building& b);
// True when the building's output side can take `outputItemCount` more items beside
// what it already holds. An emerging item has not left the building yet and so still
// counts against the capacity (REQ-MAT-OUTPUT-EMERGE, REQ-MAT-OUTPUT-BUFFER).
bool outputBufferHasRoom(const Building& b, int outputItemCount);
// True when a production cycle could actually start right now: some candidate recipe
// has its inputs *and* its output fits (REQ-MAT-CYCLE). Stricter than
// hasInputsToStart, which looks at the input buffers alone.
bool canStartCycle(const GameConfig& config, const Building& b);
// Status light for a building, or nullopt for types that show none — belts,
// splitters, tunnels, HQ and defence stations (REQ-UI-STATUS-LIGHT).
std::optional<ProductionStatus> getProductionStatus(const GameConfig& config,