Animate items emerging from building output ports

This commit is contained in:
2026-07-14 20:18:47 +02:00
parent af8a2224c0
commit 6a8c456aa1
13 changed files with 282 additions and 122 deletions

View File

@@ -58,12 +58,28 @@ static void runTicks(BuildingSystem& bs, BeltSystem& belts, int n, Tick& tick)
bs.tickConstruction(tick);
bs.tickBeltPull();
bs.tickProduction(tick);
bs.tickBeltPush();
bs.tickOutputBelts();
belts.tick();
++tick;
}
}
// All items currently on a building's output side: buffered plus still-emerging on
// the virtual output belts (REQ-MAT-OUTPUT-EMERGE). A produced item leaves the
// output buffer the moment it starts emerging, so tests count both.
static std::vector<Item> outputSideItems(const Building& b)
{
std::vector<Item> items = b.outputBuffer.items;
for (const std::vector<BeltItemSlot>& lane : b.emergingItems)
{
for (const BeltItemSlot& slot : lane)
{
items.push_back(slot.item);
}
}
return items;
}
// Owns a BuildingSystem and its dependencies for placement-bounds tests.
struct PlacementFixture
{
@@ -402,8 +418,11 @@ TEST_CASE("BuildingSystem: miner produces iron_ore after recipe duration", "[bui
const Building* b = bs.findBuilding(id);
REQUIRE(b != nullptr);
REQUIRE_FALSE(b->outputBuffer.items.empty());
REQUIRE(b->outputBuffer.items.front().type.id == "iron_ore");
// No belt at the output port, so the produced item emerges and stays on the
// building's virtual output belt (REQ-MAT-OUTPUT-EMERGE).
const std::vector<Item> out = outputSideItems(*b);
REQUIRE(out.size() == 1);
REQUIRE(out.front().type.id == "iron_ore");
}
TEST_CASE("BuildingSystem: miner output buffer stalls when full", "[building]")
@@ -436,7 +455,9 @@ TEST_CASE("BuildingSystem: miner output buffer stalls when full", "[building]")
const Building* b = bs.findBuilding(id);
REQUIRE(b != nullptr);
REQUIRE(static_cast<int>(b->outputBuffer.items.size()) == 2);
// Both produced items are held on the output side (buffer + emerging lane),
// which is what the capacity rule counts (REQ-MAT-OUTPUT-EMERGE).
REQUIRE(b->outputItemCount() == 2);
REQUIRE_FALSE(b->production.has_value());
}
@@ -515,7 +536,7 @@ TEST_CASE("BuildingSystem: activeProductionBuildingCount tracks production cycle
const Building* b = bs.findBuilding(id);
REQUIRE(b != nullptr);
REQUIRE(static_cast<int>(b->outputBuffer.items.size()) == 2);
REQUIRE(b->outputItemCount() == 2);
REQUIRE_FALSE(b->production.has_value());
REQUIRE(bs.activeProductionBuildingCount() == 0);
}
@@ -603,7 +624,7 @@ TEST_CASE("BuildingSystem: smelter auto-smelts ore without a recipe selection",
const Building* b = bs.findBuilding(sid);
REQUIRE(b != nullptr);
bool hasIronIngot = false;
for (const Item& item : b->outputBuffer.items)
for (const Item& item : outputSideItems(*b))
{
if (item.type.id == "iron_ingot") { hasIronIngot = true; }
}
@@ -652,7 +673,7 @@ TEST_CASE("BuildingSystem: smelter runs a satisfiable recipe while an incomplete
// Copper was smelted; the lone iron_ore still waits for a second unit.
bool hasCopperIngot = false;
for (const Item& item : b->outputBuffer.items)
for (const Item& item : outputSideItems(*b))
{
if (item.type.id == "copper_ingot") { hasCopperIngot = true; }
}
@@ -734,13 +755,15 @@ TEST_CASE("BuildingSystem: setRecipe clears output buffer and active production"
{
const Building* b = bs.findBuilding(id);
REQUIRE(b != nullptr);
REQUIRE_FALSE(b->outputBuffer.items.empty());
REQUIRE(b->outputItemCount() > 0);
}
bs.setRecipe(id, "mine_copper_ore");
const Building* b = bs.findBuilding(id);
REQUIRE(b->outputBuffer.items.empty());
// Clearing the output buffer on a recipe change also discards emerging items
// (REQ-MAT-OUTPUT-EMERGE).
REQUIRE(b->outputItemCount() == 0);
REQUIRE_FALSE(b->production.has_value());
}