fix issue where status light was flickering to yellow for one tick between cycles
This commit is contained in:
@@ -1515,36 +1515,167 @@ TEST_CASE("BuildingSystem: getProductionStatus classifies production state", "[b
|
||||
miner.production = Production{};
|
||||
REQUIRE(statusOf(miner) == ProductionStatus::Producing); // active cycle -> green
|
||||
|
||||
// A miner has no inputs, so its only idle reason is a full output buffer.
|
||||
// A miner has no inputs, so its only idle reason is an output buffer with no
|
||||
// room for the next cycle's output.
|
||||
miner.production = std::nullopt;
|
||||
miner.outputBuffer.capacity = 2;
|
||||
miner.outputBuffer.items = { makeItem("iron_ore"), makeItem("iron_ore") };
|
||||
REQUIRE(statusOf(miner) == ProductionStatus::Blocked); // -> yellow
|
||||
|
||||
// One item handed off: the next cycle fits again, so the idle tick between two
|
||||
// cycles reads as producing rather than blinking yellow (REQ-UI-STATUS-LIGHT).
|
||||
miner.outputBuffer.items.pop_back();
|
||||
REQUIRE(statusOf(miner) == ProductionStatus::Producing); // -> green
|
||||
|
||||
// An emerging item has not left the building, so it fills the freed slot and
|
||||
// blocks the cycle again (REQ-MAT-OUTPUT-EMERGE).
|
||||
miner.emergingItems.push_back({ BeltItemSlot{ makeItem("iron_ore"), 0.5 } });
|
||||
REQUIRE(miner.getOutputItemCount() == 2);
|
||||
REQUIRE(statusOf(miner) == ProductionStatus::Blocked); // -> yellow
|
||||
}
|
||||
|
||||
SECTION("Assembler: starved vs blocked, input-missing takes precedence")
|
||||
SECTION("Assembler: starved, the transient between cycles, then blocked")
|
||||
{
|
||||
Building assembler; assembler.type = BuildingType::Assembler;
|
||||
assembler.recipeId = assemblerRecipe->id;
|
||||
|
||||
int cycleOutput = 0;
|
||||
for (const RecipeOutput& out : assemblerRecipe->outputs)
|
||||
{
|
||||
cycleOutput += out.amount;
|
||||
}
|
||||
REQUIRE(cycleOutput > 0);
|
||||
// The buffer the simulation would give it (REQ-MAT-OUTPUT-BUFFER).
|
||||
assembler.outputBuffer.capacity = 2 * cycleOutput;
|
||||
|
||||
// Idle with inputs missing -> red.
|
||||
REQUIRE(statusOf(assembler) == ProductionStatus::Starved);
|
||||
|
||||
// Inputs present but idle -> the only remaining reason is a full output
|
||||
// buffer -> yellow.
|
||||
// Inputs present and the output fits: nothing blocks a cycle, so the building
|
||||
// is merely between cycles -> green, not yellow (REQ-UI-STATUS-LIGHT).
|
||||
for (const RecipeIngredient& ing : assemblerRecipe->inputs)
|
||||
{
|
||||
assembler.inputBuffer.counts[ItemType{ing.item}] = ing.amount;
|
||||
}
|
||||
REQUIRE(statusOf(assembler) == ProductionStatus::Producing);
|
||||
|
||||
// Filled to within less than one cycle's output of capacity: no cycle can start
|
||||
// -> yellow.
|
||||
for (int i = 0; i < cycleOutput + 1; ++i)
|
||||
{
|
||||
assembler.outputBuffer.items.push_back(makeItem("x"));
|
||||
}
|
||||
REQUIRE(statusOf(assembler) == ProductionStatus::Blocked);
|
||||
|
||||
// Inputs missing AND output full -> red wins over yellow.
|
||||
// Inputs missing AND output blocked -> red wins over yellow.
|
||||
assembler.inputBuffer.counts.clear();
|
||||
assembler.outputBuffer.capacity = 2;
|
||||
assembler.outputBuffer.items = { makeItem("x"), makeItem("x") };
|
||||
REQUIRE(statusOf(assembler) == ProductionStatus::Starved);
|
||||
}
|
||||
|
||||
SECTION("A multi-item cycle blocks before the output buffer is full")
|
||||
{
|
||||
// Free space smaller than one cycle's output stops the cycle even though the
|
||||
// buffer still has room, so yellow is not the same as "full" (REQ-MAT-CYCLE).
|
||||
const RecipeDef* multiOutputRecipe = nullptr;
|
||||
for (const RecipeDef& r : f.cfg.recipes.recipes)
|
||||
{
|
||||
if (r.building != BuildingType::Assembler || r.inputs.empty()) { continue; }
|
||||
int total = 0;
|
||||
for (const RecipeOutput& out : r.outputs) { total += out.amount; }
|
||||
if (total >= 2) { multiOutputRecipe = &r; break; }
|
||||
}
|
||||
REQUIRE(multiOutputRecipe != nullptr);
|
||||
|
||||
int cycleOutput = 0;
|
||||
for (const RecipeOutput& out : multiOutputRecipe->outputs)
|
||||
{
|
||||
cycleOutput += out.amount;
|
||||
}
|
||||
|
||||
Building assembler; assembler.type = BuildingType::Assembler;
|
||||
assembler.recipeId = multiOutputRecipe->id;
|
||||
assembler.outputBuffer.capacity = 2 * cycleOutput;
|
||||
for (const RecipeIngredient& ing : multiOutputRecipe->inputs)
|
||||
{
|
||||
assembler.inputBuffer.counts[ItemType{ing.item}] = ing.amount;
|
||||
}
|
||||
|
||||
// One item short of a full cycle's worth of free space.
|
||||
for (int i = 0; i < cycleOutput + 1; ++i)
|
||||
{
|
||||
assembler.outputBuffer.items.push_back(makeItem("x"));
|
||||
}
|
||||
REQUIRE(assembler.getOutputItemCount() < assembler.outputBuffer.capacity);
|
||||
REQUIRE(statusOf(assembler) == ProductionStatus::Blocked);
|
||||
|
||||
// Exactly one cycle's worth of free space: the cycle fits again.
|
||||
assembler.outputBuffer.items.pop_back();
|
||||
REQUIRE(statusOf(assembler) == ProductionStatus::Producing);
|
||||
}
|
||||
|
||||
SECTION("Reprocessing Plant: judged by the smallest output a roll could yield")
|
||||
{
|
||||
// The plant rolls one of its outputs per cycle (REQ-BLD-REPROCESSING) and the
|
||||
// roll belongs to the simulation, so the status can only say whether *some*
|
||||
// roll could start: it is blocked once not even the smallest output fits.
|
||||
const RecipeDef* reprocessingRecipe = nullptr;
|
||||
for (const RecipeDef& r : f.cfg.recipes.recipes)
|
||||
{
|
||||
if (r.building == BuildingType::ReprocessingPlant && !r.inputs.empty())
|
||||
{
|
||||
reprocessingRecipe = &r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
REQUIRE(reprocessingRecipe != nullptr);
|
||||
|
||||
int smallestOutput = 0;
|
||||
int largestOutput = 0;
|
||||
for (const RecipeOutput& out : reprocessingRecipe->outputs)
|
||||
{
|
||||
if (smallestOutput == 0 || out.amount < smallestOutput)
|
||||
{
|
||||
smallestOutput = out.amount;
|
||||
}
|
||||
if (out.amount > largestOutput) { largestOutput = out.amount; }
|
||||
}
|
||||
REQUIRE(smallestOutput > 0);
|
||||
|
||||
Building plant; plant.type = BuildingType::ReprocessingPlant;
|
||||
// One cycle's largest output, as initAutoBuffers sizes it
|
||||
// (REQ-MAT-OUTPUT-BUFFER-REPROCESSING).
|
||||
plant.outputBuffer.capacity = largestOutput;
|
||||
for (const RecipeIngredient& ing : reprocessingRecipe->inputs)
|
||||
{
|
||||
plant.inputBuffer.counts[ItemType{ing.item}] = ing.amount;
|
||||
}
|
||||
|
||||
// Empty buffer: a roll fits -> green.
|
||||
REQUIRE(statusOf(plant) == ProductionStatus::Producing);
|
||||
|
||||
// Room for the smallest output but not for the largest: some roll can still
|
||||
// start, so the plant is waiting on the roll rather than blocked.
|
||||
if (smallestOutput < largestOutput)
|
||||
{
|
||||
plant.outputBuffer.items.push_back(makeItem("iron_ingot"));
|
||||
REQUIRE(plant.getOutputItemCount() + largestOutput
|
||||
> plant.outputBuffer.capacity);
|
||||
REQUIRE(statusOf(plant) == ProductionStatus::Producing);
|
||||
plant.outputBuffer.items.pop_back();
|
||||
}
|
||||
|
||||
// Filled so that not even the smallest output fits -> yellow.
|
||||
for (int i = 0; i < largestOutput - smallestOutput + 1; ++i)
|
||||
{
|
||||
plant.outputBuffer.items.push_back(makeItem("iron_ingot"));
|
||||
}
|
||||
REQUIRE(statusOf(plant) == ProductionStatus::Blocked);
|
||||
|
||||
// Without the scrap it is starved regardless of the buffer.
|
||||
plant.inputBuffer.counts.clear();
|
||||
REQUIRE(statusOf(plant) == ProductionStatus::Starved);
|
||||
}
|
||||
|
||||
SECTION("Smelter (auto-recipe) is never grey")
|
||||
{
|
||||
Building smelter; smelter.type = BuildingType::Smelter;
|
||||
|
||||
Reference in New Issue
Block a user