fix missed code paths for artificially reduced splitter throughput
This commit is contained in:
@@ -720,11 +720,18 @@ void BeltSystem::routeSplitterItems()
|
|||||||
|
|
||||||
bool routed = false;
|
bool routed = false;
|
||||||
|
|
||||||
|
// A front slot holds only one item, so an item entering at progress 0.0
|
||||||
|
// would have to traverse the whole tile before the next could enter,
|
||||||
|
// throttling that output below belt speed and leaving large gaps. Entering
|
||||||
|
// near the output edge lets the slot clear roughly every quarter tile, so
|
||||||
|
// the output stays packed (fixes the half-blocked / single-output gap bug).
|
||||||
|
constexpr double frontEntryProgress = 0.75;
|
||||||
|
|
||||||
if (matchesA && !matchesB)
|
if (matchesA && !matchesB)
|
||||||
{
|
{
|
||||||
if (!st.frontA)
|
if (!st.frontA)
|
||||||
{
|
{
|
||||||
st.frontA = BeltItemSlot{item, 0.0};
|
st.frontA = BeltItemSlot{item, frontEntryProgress};
|
||||||
routed = true;
|
routed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -732,7 +739,7 @@ void BeltSystem::routeSplitterItems()
|
|||||||
{
|
{
|
||||||
if (!st.frontB)
|
if (!st.frontB)
|
||||||
{
|
{
|
||||||
st.frontB = BeltItemSlot{item, 0.0};
|
st.frontB = BeltItemSlot{item, frontEntryProgress};
|
||||||
routed = true;
|
routed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -743,26 +750,26 @@ void BeltSystem::routeSplitterItems()
|
|||||||
|
|
||||||
if (preferA && !st.frontA)
|
if (preferA && !st.frontA)
|
||||||
{
|
{
|
||||||
st.frontA = BeltItemSlot{item, 0.0};
|
st.frontA = BeltItemSlot{item, frontEntryProgress};
|
||||||
st.nextOutputIsA = false;
|
st.nextOutputIsA = false;
|
||||||
routed = true;
|
routed = true;
|
||||||
}
|
}
|
||||||
else if (!preferA && !st.frontB)
|
else if (!preferA && !st.frontB)
|
||||||
{
|
{
|
||||||
st.frontB = BeltItemSlot{item, 0.0};
|
st.frontB = BeltItemSlot{item, frontEntryProgress};
|
||||||
st.nextOutputIsA = true;
|
st.nextOutputIsA = true;
|
||||||
routed = true;
|
routed = true;
|
||||||
}
|
}
|
||||||
else if (preferA && !st.frontB)
|
else if (preferA && !st.frontB)
|
||||||
{
|
{
|
||||||
// Preferred (A) is full — fall back to B; nextOutputIsA stays.
|
// Preferred (A) is full — fall back to B; nextOutputIsA stays.
|
||||||
st.frontB = BeltItemSlot{item, 0.75};
|
st.frontB = BeltItemSlot{item, frontEntryProgress};
|
||||||
routed = true;
|
routed = true;
|
||||||
}
|
}
|
||||||
else if (!preferA && !st.frontA)
|
else if (!preferA && !st.frontA)
|
||||||
{
|
{
|
||||||
// Preferred (B) is full — fall back to A; nextOutputIsA stays.
|
// Preferred (B) is full — fall back to A; nextOutputIsA stays.
|
||||||
st.frontA = BeltItemSlot{item, 0.75};
|
st.frontA = BeltItemSlot{item, frontEntryProgress};
|
||||||
routed = true;
|
routed = true;
|
||||||
}
|
}
|
||||||
// else both fronts occupied — back stays.
|
// else both fronts occupied — back stays.
|
||||||
|
|||||||
@@ -593,17 +593,18 @@ TEST_CASE("BeltSystem: splitter fallback enters the open output at progress 0.75
|
|||||||
// (North has no downstream tile, so it can never move out).
|
// (North has no downstream tile, so it can never move out).
|
||||||
bs.tryPutItem(tileSpl, makeItem("blockA"));
|
bs.tryPutItem(tileSpl, makeItem("blockA"));
|
||||||
bs.tick(); // back: 0.25
|
bs.tick(); // back: 0.25
|
||||||
bs.tick(); // back: 0.5 -> frontA at 0.0 (preferred A), nextOutputIsA = false
|
bs.tick(); // back: 0.5 -> frontA at 0.75 (preferred A), nextOutputIsA = false
|
||||||
bs.tick(); bs.tick(); bs.tick(); bs.tick(); // frontA: 0.25 -> 0.5 -> 0.75 -> 1.0 (stuck)
|
bs.tick(); bs.tick(); // frontA: 0.75 -> 1.0 (stuck, no North downstream)
|
||||||
|
|
||||||
// Item routed to B as the *preferred* output enters at progress 0.0.
|
// Cycle one item through B as the *preferred* output (also enters at 0.75) to
|
||||||
|
// flip nextOutputIsA back to true and free frontB for the fallback case below.
|
||||||
bs.tryPutItem(tileSpl, makeItem("toB_pref"));
|
bs.tryPutItem(tileSpl, makeItem("toB_pref"));
|
||||||
bs.tick(); // back: 0.25
|
bs.tick(); // back: 0.25
|
||||||
bs.tick(); // back: 0.5 -> frontB at 0.0 (preferred B), nextOutputIsA = true
|
bs.tick(); // back: 0.5 -> frontB at 0.75 (preferred B), nextOutputIsA = true
|
||||||
REQUIRE(southProgressOf("toB_pref") == Approx(0.0));
|
REQUIRE(southProgressOf("toB_pref") == Approx(0.75));
|
||||||
|
|
||||||
// Let it traverse and hand off to the downstream belt, freeing frontB.
|
// One tick reaches the edge and hands off to tileB; the rest just clear frontB.
|
||||||
bs.tick(); bs.tick(); bs.tick(); bs.tick(); // frontB: 0.25 -> 0.5 -> 0.75 -> 1.0 -> tileB
|
bs.tick(); bs.tick(); // frontB: 0.75 -> 1.0 -> tileB, then empty
|
||||||
|
|
||||||
// Next item prefers A again (nextOutputIsA == true), but A is still blocked,
|
// 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.
|
// so it falls back to B — and must enter near the edge at progress 0.75.
|
||||||
@@ -613,6 +614,96 @@ TEST_CASE("BeltSystem: splitter fallback enters the open output at progress 0.75
|
|||||||
REQUIRE(southProgressOf("toB_fallback") == Approx(0.75));
|
REQUIRE(southProgressOf("toB_fallback") == Approx(0.75));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BeltSystem: splitter with an exclusive filter enters its only output at progress 0.75", "[belt]")
|
||||||
|
{
|
||||||
|
// An item that matches only one filter has a single eligible output. Like the
|
||||||
|
// blocked-fallback case, it must enter near the edge (progress 0.75) so the
|
||||||
|
// one-item-wide front does not throttle that output and open large gaps.
|
||||||
|
const double quarterSpeed = 0.25 * static_cast<double>(kTickRateHz);
|
||||||
|
BeltSystem bs(quarterSpeed);
|
||||||
|
|
||||||
|
const QPoint tileSpl(1, 0);
|
||||||
|
bs.placeSplitter(tileSpl, Rotation::North, Rotation::South);
|
||||||
|
bs.setSplitterFilters(tileSpl, {ItemType{"iron_ore"}}, {ItemType{"copper_ore"}});
|
||||||
|
|
||||||
|
// Inverts slotWorldPos to recover a named item's progress along the given output.
|
||||||
|
auto progressOf = [&bs, tileSpl](const std::string& id, Rotation dir) -> std::optional<double>
|
||||||
|
{
|
||||||
|
std::optional<double> progress;
|
||||||
|
bs.forEachVisualItem(QRect(-5, -5, 20, 20), [&](VisualItem vi)
|
||||||
|
{
|
||||||
|
if (vi.type.id != id)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (dir)
|
||||||
|
{
|
||||||
|
case Rotation::North: progress = (tileSpl.y() + 1.0) - vi.worldPos.y(); break;
|
||||||
|
case Rotation::South: progress = vi.worldPos.y() - tileSpl.y(); break;
|
||||||
|
case Rotation::East: progress = vi.worldPos.x() - tileSpl.x(); break;
|
||||||
|
case Rotation::West: progress = (tileSpl.x() + 1.0) - vi.worldPos.x(); break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return progress;
|
||||||
|
};
|
||||||
|
|
||||||
|
// iron_ore matches filterA only -> sole eligible output A.
|
||||||
|
bs.tryPutItem(tileSpl, makeItem("iron_ore"));
|
||||||
|
bs.tick(); // back: 0.25
|
||||||
|
bs.tick(); // back: 0.5 -> routes to frontA at 0.75
|
||||||
|
REQUIRE(progressOf("iron_ore", Rotation::North) == Approx(0.75));
|
||||||
|
|
||||||
|
// copper_ore matches filterB only -> sole eligible output B.
|
||||||
|
bs.tryPutItem(tileSpl, makeItem("copper_ore"));
|
||||||
|
bs.tick(); // back: 0.25
|
||||||
|
bs.tick(); // back: 0.5 -> routes to frontB at 0.75
|
||||||
|
REQUIRE(progressOf("copper_ore", Rotation::South) == Approx(0.75));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BeltSystem: splitter alternation enters the preferred output at progress 0.75", "[belt]")
|
||||||
|
{
|
||||||
|
// With both outputs eligible and free, the preferred output uses the same
|
||||||
|
// near-edge entry as the diverted paths, so an evenly-split splitter keeps
|
||||||
|
// each side packed instead of throttling it to one in-flight item per tile.
|
||||||
|
const double quarterSpeed = 0.25 * static_cast<double>(kTickRateHz);
|
||||||
|
BeltSystem bs(quarterSpeed);
|
||||||
|
|
||||||
|
const QPoint tileSpl(1, 0);
|
||||||
|
bs.placeSplitter(tileSpl, Rotation::North, Rotation::South); // no filters: both match
|
||||||
|
|
||||||
|
auto progressOf = [&bs, tileSpl](const std::string& id, Rotation dir) -> std::optional<double>
|
||||||
|
{
|
||||||
|
std::optional<double> progress;
|
||||||
|
bs.forEachVisualItem(QRect(-5, -5, 20, 20), [&](VisualItem vi)
|
||||||
|
{
|
||||||
|
if (vi.type.id != id)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (dir)
|
||||||
|
{
|
||||||
|
case Rotation::North: progress = (tileSpl.y() + 1.0) - vi.worldPos.y(); break;
|
||||||
|
case Rotation::South: progress = vi.worldPos.y() - tileSpl.y(); break;
|
||||||
|
case Rotation::East: progress = vi.worldPos.x() - tileSpl.x(); break;
|
||||||
|
case Rotation::West: progress = (tileSpl.x() + 1.0) - vi.worldPos.x(); break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return progress;
|
||||||
|
};
|
||||||
|
|
||||||
|
// First item: preferred A (nextOutputIsA starts true) -> frontA at 0.75.
|
||||||
|
bs.tryPutItem(tileSpl, makeItem("first"));
|
||||||
|
bs.tick(); // back: 0.25
|
||||||
|
bs.tick(); // back: 0.5 -> routes to preferred frontA at 0.75, nextOutputIsA = false
|
||||||
|
REQUIRE(progressOf("first", Rotation::North) == Approx(0.75));
|
||||||
|
|
||||||
|
// Second item: preference flipped, B is free -> frontB at 0.75.
|
||||||
|
bs.tryPutItem(tileSpl, makeItem("second"));
|
||||||
|
bs.tick(); // back: 0.25 (first sticks at North 1.0, no downstream)
|
||||||
|
bs.tick(); // back: 0.5 -> routes to preferred frontB at 0.75
|
||||||
|
REQUIRE(progressOf("second", Rotation::South) == Approx(0.75));
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Splitter — direct building input (no output belts)
|
// Splitter — direct building input (no output belts)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user