Animate items entering building input ports

This commit is contained in:
2026-07-14 20:21:25 +02:00
parent 6a8c456aa1
commit c9f14970a1
7 changed files with 264 additions and 37 deletions

View File

@@ -580,10 +580,93 @@ TEST_CASE("BuildingSystem: smelter input buffer fills from adjacent west-flowing
const Building* b = bs.findBuilding(sid);
REQUIRE(b != nullptr);
const std::map<ItemType, int>::const_iterator it =
// The item was accepted; it may still be travelling inward on the input belt,
// so count buffered + in-transit (REQ-MAT-INPUT-INTAKE).
REQUIRE(b->pendingInputCount(ItemType{"iron_ore"}) >= 1);
}
// An accepted input item travels inward on its input belt before it becomes usable
// stock: it is reserved (counts against the cap) on entry and only enters the
// buffer on reaching the tile centre (REQ-MAT-INPUT-INTAKE).
TEST_CASE("BuildingSystem: accepted input travels inward before entering the buffer",
"[building]")
{
const GameConfig cfg = loadConfig();
BeltSystem belts(static_cast<double>(kTickRateHz)); // fast belt: 1 tile/tick
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
[](const std::string&) -> bool { return true; },
rng);
const BuildingId sid = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0);
Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
belts.placeBelt(QPoint(2, 0), Rotation::West);
belts.tryPutItem(QPoint(2, 0), makeItem("iron_ore"));
belts.tick();
bs.tickBeltPull(); // accepts the item onto the input belt at progress 0.0
const Building* b = bs.findBuilding(sid);
REQUIRE(b != nullptr);
// Reserved but not yet consumable: nothing in the buffer, but it counts against
// the cap via pendingInputCount.
const std::map<ItemType, int>::const_iterator it0 =
b->inputBuffer.counts.find(ItemType{"iron_ore"});
REQUIRE(it != b->inputBuffer.counts.end());
REQUIRE(it->second >= 1);
REQUIRE((it0 == b->inputBuffer.counts.end() || it0->second == 0));
REQUIRE(b->pendingInputCount(ItemType{"iron_ore"}) == 1);
// One more pull tick advances the input belt to the centre; the item arrives.
bs.tickBeltPull();
REQUIRE(b->inputBuffer.counts.at(ItemType{"iron_ore"}) == 1);
REQUIRE(b->pendingInputCount(ItemType{"iron_ore"}) == 1);
}
// The acceptance test counts in-transit items, so buffered + reserved never exceeds
// the per-material cap; excess items stay on the belt (REQ-MAT-INPUT-INTAKE).
TEST_CASE("BuildingSystem: input reservation caps buffered plus in-transit at the cap",
"[building]")
{
const GameConfig cfg = loadConfig();
BeltSystem belts(static_cast<double>(kTickRateHz)); // fast belt
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::ReprocessingPlant,
QPoint(0, 0), Rotation::East, 0);
Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(25.0)) + 1, tick);
// Feed scrap via an input belt without ever running production (only pull), so
// the buffer fills and stays full. Try to over-fill it well past the cap.
belts.placeBelt(QPoint(-1, 0), Rotation::East);
for (int i = 0; i < 20; ++i)
{
belts.tryPutItem(QPoint(-1, 0), makeItem("scrap"), Rotation::East);
belts.tick();
bs.tickBeltPull();
}
const Building* b = bs.findBuilding(id);
REQUIRE(b != nullptr);
const int cap = b->inputBuffer.caps.at(ItemType{"scrap"});
REQUIRE(cap > 0);
// buffered + in-transit is capped; the plant never over-pulls.
REQUIRE(b->pendingInputCount(ItemType{"scrap"}) == cap);
// Excess scrap is left stuck on the feeding belt rather than silently dropped.
REQUIRE(belts.peekItem(eastPort(QPoint(-1, 0))).has_value());
}
// A smelter auto-selects the matching recipe for whatever it is fed, with no
@@ -838,14 +921,12 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta
bs.tickBeltPull();
}
// Verify scrap is in input buffer.
// Verify all five scrap were accepted; some may still be travelling inward on
// the input belt (REQ-MAT-INPUT-INTAKE), so count buffered + in-transit.
{
const Building* b = bs.findBuilding(id);
REQUIRE(b != nullptr);
const std::map<ItemType, int>::const_iterator it =
b->inputBuffer.counts.find(ItemType{"scrap"});
REQUIRE(it != b->inputBuffer.counts.end());
REQUIRE(it->second == 5);
REQUIRE(b->pendingInputCount(ItemType{"scrap"}) == 5);
}
// Run production cycle (3s = 90 ticks + 1 for the completion tick).