fix bug where splitters reduce belt throughput, even if one side is blocked

This commit is contained in:
2026-06-14 14:03:50 +02:00
parent 1ea1cc59fb
commit 282ace4c11
2 changed files with 63 additions and 2 deletions

View File

@@ -1,5 +1,7 @@
#include "catch.hpp"
#include <optional>
#include <string>
#include <vector>
#include <QPoint>
@@ -553,6 +555,64 @@ TEST_CASE("BeltSystem: splitter falls back to other output when preferred is blo
REQUIRE_FALSE(bs.peekItem(Port{tileSpl, Rotation::South}).has_value());
}
TEST_CASE("BeltSystem: splitter fallback enters the open output at progress 0.75", "[belt]")
{
// When the preferred output is blocked, the diverted item is dropped onto the
// open output near its edge (progress 0.75) instead of at progress 0.0. This
// closes the large gap that would otherwise appear between items leaving the
// open side of a half-blocked splitter.
//
// Progress/tick = 0.25 so the 0.0-vs-0.75 entry position is observable: a
// normally-routed item starts at 0.0, a fallback item starts at 0.75.
const double quarterSpeed = 0.25 * static_cast<double>(kTickRateHz);
BeltSystem bs(quarterSpeed);
const QPoint tileSpl(1, 0);
const QPoint tileB(1, 1); // South output belt; North output has no belt (blocked).
bs.placeSplitter(tileSpl, Rotation::North, Rotation::South);
bs.placeBelt(tileB, Rotation::South);
// Reads a named item's progress along the South output via the rendering contract.
// slotWorldPos maps a South-bound slot on tileSpl (y = 0) to worldPos.y == progress.
// Matching by id avoids the blocked North item, which also renders at worldPos.y 0.
auto southProgressOf = [&bs](const std::string& id) -> std::optional<double>
{
std::optional<double> progress;
bs.forEachVisualItem(QRect(-5, -5, 20, 20), [&](VisualItem vi)
{
if (vi.type.id == id)
{
progress = vi.worldPos.y();
}
});
return progress;
};
// Permanently block output A: route one item to frontA where it sticks at 1.0
// (North has no downstream tile, so it can never move out).
bs.tryPutItem(tileSpl, makeItem("blockA"));
bs.tick(); // back: 0.25
bs.tick(); // back: 0.5 -> frontA at 0.0 (preferred A), nextOutputIsA = false
bs.tick(); bs.tick(); bs.tick(); bs.tick(); // frontA: 0.25 -> 0.5 -> 0.75 -> 1.0 (stuck)
// Item routed to B as the *preferred* output enters at progress 0.0.
bs.tryPutItem(tileSpl, makeItem("toB_pref"));
bs.tick(); // back: 0.25
bs.tick(); // back: 0.5 -> frontB at 0.0 (preferred B), nextOutputIsA = true
REQUIRE(southProgressOf("toB_pref") == Approx(0.0));
// Let it traverse and hand off to the downstream belt, freeing frontB.
bs.tick(); bs.tick(); bs.tick(); bs.tick(); // frontB: 0.25 -> 0.5 -> 0.75 -> 1.0 -> tileB
// Next item prefers A again (nextOutputIsA == true), but A is still blocked,
// so it falls back to B — and must enter near the edge at progress 0.75.
bs.tryPutItem(tileSpl, makeItem("toB_fallback"));
bs.tick(); // back: 0.25
bs.tick(); // back: 0.5 -> fallback routes to frontB at 0.75
REQUIRE(southProgressOf("toB_fallback") == Approx(0.75));
}
// ---------------------------------------------------------------------------
// Splitter — direct building input (no output belts)
// ---------------------------------------------------------------------------