show green while a building is only between cycles
tickProduction never starts a cycle in the tick one completed, so a building running back to back is idle for exactly one tick per cycle. The status light classified that tick as "output full" -- hasInputsToStart looks at the input buffers alone -- so the pill blinked green/yellow once per cycle, roughly once a second on a one-second recipe. REQ-UI-STATUS-LIGHT already says a building that is idle yet blocked by neither condition shows green. Add canStartCycle(), which requires a candidate recipe's inputs *and* room for its output, and classify on it; yellow now needs an output buffer that genuinely cannot take the next cycle. The Reprocessing Plant rolls its output in the simulation, so the classifier judges it by the smallest amount any roll could yield. tickProduction's space check goes through the same outputBufferHasRoom(), so the simulation and the light cannot drift apart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user