Compare commits
2 Commits
1e7f602865
...
b30addab3d
| Author | SHA1 | Date | |
|---|---|---|---|
| b30addab3d | |||
| 0ce7cd7ae8 |
@@ -30,7 +30,8 @@ Output port indicators are not building tiles themselves. A building may have mo
|
|||||||
## Game World
|
## Game World
|
||||||
|
|
||||||
- REQ-GW-COORDS: Tile coordinates are integer `(x, y)`. The origin `(0, 0)` is the first column of space — the tile immediately to the right of the asteroid's right edge at game start, at the top of the world. X grows right; Y grows down. All asteroid tiles have `x < 0`; asteroid left-expansions add tiles at increasingly negative X. The origin never shifts.
|
- REQ-GW-COORDS: Tile coordinates are integer `(x, y)`. The origin `(0, 0)` is the first column of space — the tile immediately to the right of the asteroid's right edge at game start, at the top of the world. X grows right; Y grows down. All asteroid tiles have `x < 0`; asteroid left-expansions add tiles at increasingly negative X. The origin never shifts.
|
||||||
- REQ-GW-TILE-SIZE: Tiles are square. The tile size in pixels is derived automatically so that the world height (in tiles) exactly fills the game world view's height in pixels. Items on belts are rendered at half-tile size, so each belt tile holds at most 2 items.
|
- REQ-GW-TILE-SIZE: Tiles are square. The tile size in pixels is derived automatically so that the world height (in tiles) exactly fills the game world view's height in pixels. Items on belts are rendered at half-tile size; when multiple items occupy the same tile they are spaced quarter-tile apart along the direction of travel and overlap, rendered in ascending order of progress — the least-progressed item is drawn first (bottom) and the furthest-progressed item is drawn last (on top).
|
||||||
|
- REQ-GW-BELT-CAPACITY: Belt tiles and tunnel entry/exit tiles each hold up to four items simultaneously, queued one behind the other in the direction of travel. Splitter tiles hold up to four items: two unassigned items (progress < 0.5, not yet routed to an output) and one item per output slot (progress ≥ 0.5, committed to a specific output direction). Output-slot items are rendered on top of unassigned items; when both output slots are occupied, their rendering order follows the clockwise port order starting from East.
|
||||||
- REQ-GW-BELT-SPEED: Items on belts move at `world.toml [world].belt_speed_tiles_per_second` tiles per second (default 2).
|
- REQ-GW-BELT-SPEED: Items on belts move at `world.toml [world].belt_speed_tiles_per_second` tiles per second (default 2).
|
||||||
- REQ-GW-HEIGHT: The world height (in tiles) is read from `world.toml [world].height_tiles`.
|
- REQ-GW-HEIGHT: The world height (in tiles) is read from `world.toml [world].height_tiles`.
|
||||||
- REQ-GW-REGIONS: The world is divided into horizontal regions whose widths (in tiles) are read from `world.toml [regions]`:
|
- REQ-GW-REGIONS: The world is divided into horizontal regions whose widths (in tiles) are read from `world.toml [regions]`:
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ void BeltSystem::placeSplitter(QPoint tile, Rotation outputA, Rotation outputB)
|
|||||||
st.outputA = outputA;
|
st.outputA = outputA;
|
||||||
st.outputB = outputB;
|
st.outputB = outputB;
|
||||||
st.nextOutputIsA = true;
|
st.nextOutputIsA = true;
|
||||||
st.backDir = Rotation::North; // irrelevant until back is set
|
|
||||||
m_splitters[key(tile)] = st;
|
m_splitters[key(tile)] = st;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,10 +210,10 @@ bool BeltSystem::tryPutItem(QPoint tile, Item item, Rotation fromDir)
|
|||||||
m_splitters.find(key(tile));
|
m_splitters.find(key(tile));
|
||||||
if (splIt != m_splitters.end())
|
if (splIt != m_splitters.end())
|
||||||
{
|
{
|
||||||
if (!splIt->second.back)
|
if (splIt->second.back.size() < 2)
|
||||||
{
|
{
|
||||||
splIt->second.back = BeltItemSlot{item, 0.0};
|
splIt->second.back.push_back(BeltItemSlot{item, 0.0});
|
||||||
splIt->second.backDir = fromDir;
|
splIt->second.backDir.push_back(fromDir);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -224,15 +223,9 @@ bool BeltSystem::tryPutItem(QPoint tile, Item item, Rotation fromDir)
|
|||||||
m_tunnelEntries.find(key(tile));
|
m_tunnelEntries.find(key(tile));
|
||||||
if (teIt != m_tunnelEntries.end())
|
if (teIt != m_tunnelEntries.end())
|
||||||
{
|
{
|
||||||
TunnelEntryTile& te = teIt->second;
|
if (teIt->second.itemSlots.size() < 4)
|
||||||
if (!te.front)
|
|
||||||
{
|
{
|
||||||
te.front = BeltItemSlot{item, 0.0};
|
teIt->second.itemSlots.push_back(BeltItemSlot{item, 0.0});
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!te.back)
|
|
||||||
{
|
|
||||||
te.back = BeltItemSlot{item, 0.0};
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -251,11 +244,10 @@ std::optional<Item> BeltSystem::tryTakeItem(Port port)
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
BeltTile& bt = beltIt->second;
|
BeltTile& bt = beltIt->second;
|
||||||
if (bt.front && bt.front->progress >= 1.0)
|
if (!bt.itemSlots.empty() && bt.itemSlots.front().progress >= 1.0)
|
||||||
{
|
{
|
||||||
const Item taken = bt.front->item;
|
const Item taken = bt.itemSlots.front().item;
|
||||||
bt.front = bt.back;
|
bt.itemSlots.erase(bt.itemSlots.begin());
|
||||||
bt.back = std::nullopt;
|
|
||||||
return taken;
|
return taken;
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@@ -285,11 +277,11 @@ std::optional<Item> BeltSystem::tryTakeItem(Port port)
|
|||||||
if (txIt != m_tunnelExits.end())
|
if (txIt != m_tunnelExits.end())
|
||||||
{
|
{
|
||||||
TunnelExitTile& tx = txIt->second;
|
TunnelExitTile& tx = txIt->second;
|
||||||
if (tx.direction == port.direction && tx.front && tx.front->progress >= 1.0)
|
if (tx.direction == port.direction && !tx.itemSlots.empty()
|
||||||
|
&& tx.itemSlots.front().progress >= 1.0)
|
||||||
{
|
{
|
||||||
const Item taken = tx.front->item;
|
const Item taken = tx.itemSlots.front().item;
|
||||||
tx.front = tx.back;
|
tx.itemSlots.erase(tx.itemSlots.begin());
|
||||||
tx.back = std::nullopt;
|
|
||||||
return taken;
|
return taken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,9 +300,9 @@ std::optional<ItemType> BeltSystem::peekItem(Port port) const
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
const BeltTile& bt = beltIt->second;
|
const BeltTile& bt = beltIt->second;
|
||||||
if (bt.front && bt.front->progress >= 1.0)
|
if (!bt.itemSlots.empty() && bt.itemSlots.front().progress >= 1.0)
|
||||||
{
|
{
|
||||||
return bt.front->item.type;
|
return bt.itemSlots.front().item.type;
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@@ -335,9 +327,10 @@ std::optional<ItemType> BeltSystem::peekItem(Port port) const
|
|||||||
if (txIt != m_tunnelExits.end())
|
if (txIt != m_tunnelExits.end())
|
||||||
{
|
{
|
||||||
const TunnelExitTile& tx = txIt->second;
|
const TunnelExitTile& tx = txIt->second;
|
||||||
if (tx.direction == port.direction && tx.front && tx.front->progress >= 1.0)
|
if (tx.direction == port.direction && !tx.itemSlots.empty()
|
||||||
|
&& tx.itemSlots.front().progress >= 1.0)
|
||||||
{
|
{
|
||||||
return tx.front->item.type;
|
return tx.itemSlots.front().item.type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,14 +348,14 @@ void BeltSystem::clearTiles(const std::vector<QPoint>& tiles)
|
|||||||
const std::map<std::pair<int, int>, BeltTile>::iterator bIt = m_belts.find(key(tile));
|
const std::map<std::pair<int, int>, BeltTile>::iterator bIt = m_belts.find(key(tile));
|
||||||
if (bIt != m_belts.end())
|
if (bIt != m_belts.end())
|
||||||
{
|
{
|
||||||
bIt->second.front = std::nullopt;
|
bIt->second.itemSlots.clear();
|
||||||
bIt->second.back = std::nullopt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::map<std::pair<int, int>, SplitterTile>::iterator sIt = m_splitters.find(key(tile));
|
const std::map<std::pair<int, int>, SplitterTile>::iterator sIt = m_splitters.find(key(tile));
|
||||||
if (sIt != m_splitters.end())
|
if (sIt != m_splitters.end())
|
||||||
{
|
{
|
||||||
sIt->second.back = std::nullopt;
|
sIt->second.back.clear();
|
||||||
|
sIt->second.backDir.clear();
|
||||||
sIt->second.frontA = std::nullopt;
|
sIt->second.frontA = std::nullopt;
|
||||||
sIt->second.frontB = std::nullopt;
|
sIt->second.frontB = std::nullopt;
|
||||||
}
|
}
|
||||||
@@ -371,8 +364,7 @@ void BeltSystem::clearTiles(const std::vector<QPoint>& tiles)
|
|||||||
m_tunnelEntries.find(key(tile));
|
m_tunnelEntries.find(key(tile));
|
||||||
if (teIt != m_tunnelEntries.end())
|
if (teIt != m_tunnelEntries.end())
|
||||||
{
|
{
|
||||||
teIt->second.front = std::nullopt;
|
teIt->second.itemSlots.clear();
|
||||||
teIt->second.back = std::nullopt;
|
|
||||||
for (TunnelLink& link : m_tunnelLinks)
|
for (TunnelLink& link : m_tunnelLinks)
|
||||||
{
|
{
|
||||||
if (link.entryTile == tile)
|
if (link.entryTile == tile)
|
||||||
@@ -386,8 +378,7 @@ void BeltSystem::clearTiles(const std::vector<QPoint>& tiles)
|
|||||||
m_tunnelExits.find(key(tile));
|
m_tunnelExits.find(key(tile));
|
||||||
if (txIt != m_tunnelExits.end())
|
if (txIt != m_tunnelExits.end())
|
||||||
{
|
{
|
||||||
txIt->second.front = std::nullopt;
|
txIt->second.itemSlots.clear();
|
||||||
txIt->second.back = std::nullopt;
|
|
||||||
for (TunnelLink& link : m_tunnelLinks)
|
for (TunnelLink& link : m_tunnelLinks)
|
||||||
{
|
{
|
||||||
if (link.exitTile == tile)
|
if (link.exitTile == tile)
|
||||||
@@ -419,33 +410,26 @@ void BeltSystem::advanceProgress()
|
|||||||
{
|
{
|
||||||
BeltTile& bt = it->second;
|
BeltTile& bt = it->second;
|
||||||
|
|
||||||
if (bt.front)
|
for (std::size_t i = 0; i < bt.itemSlots.size(); ++i)
|
||||||
{
|
{
|
||||||
bt.front->progress += m_progressPerTick;
|
bt.itemSlots[i].progress += m_progressPerTick;
|
||||||
if (bt.front->progress > 1.0)
|
|
||||||
|
// Absolute cap: slot i cannot exceed 1.0 - i * 0.25.
|
||||||
|
const double absoluteCap = 1.0 - i * 0.25;
|
||||||
|
if (bt.itemSlots[i].progress > absoluteCap)
|
||||||
{
|
{
|
||||||
bt.front->progress = 1.0;
|
bt.itemSlots[i].progress = absoluteCap;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (bt.back)
|
// Gap constraint: must stay 0.25 behind the slot ahead.
|
||||||
{
|
if (i > 0)
|
||||||
bt.back->progress += m_progressPerTick;
|
|
||||||
|
|
||||||
// Back must not overtake front.
|
|
||||||
if (bt.front && bt.back->progress >= bt.front->progress)
|
|
||||||
{
|
{
|
||||||
bt.back->progress = bt.front->progress - m_progressPerTick;
|
const double gapCap = bt.itemSlots[i - 1].progress - 0.25;
|
||||||
if (bt.back->progress < 0.0)
|
if (bt.itemSlots[i].progress > gapCap)
|
||||||
{
|
{
|
||||||
bt.back->progress = 0.0;
|
bt.itemSlots[i].progress = (gapCap < 0.0 ? 0.0 : gapCap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bt.back->progress > 0.5)
|
|
||||||
{
|
|
||||||
bt.back->progress = 0.5;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,12 +438,26 @@ void BeltSystem::advanceProgress()
|
|||||||
{
|
{
|
||||||
SplitterTile& st = it->second;
|
SplitterTile& st = it->second;
|
||||||
|
|
||||||
if (st.back)
|
for (std::size_t i = 0; i < st.back.size(); ++i)
|
||||||
{
|
{
|
||||||
st.back->progress += m_progressPerTick;
|
st.back[i].progress += m_progressPerTick;
|
||||||
if (st.back->progress > 0.5)
|
const double absoluteCap = 0.5 - i * 0.25;
|
||||||
|
if (st.back[i].progress > absoluteCap)
|
||||||
{
|
{
|
||||||
st.back->progress = 0.5;
|
st.back[i].progress = absoluteCap;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
const double gapCap = st.back[i - 1].progress - 0.25;
|
||||||
|
if (gapCap < 0.0)
|
||||||
|
{
|
||||||
|
st.back[i].progress = 0.0;
|
||||||
|
}
|
||||||
|
else if (st.back[i].progress > gapCap)
|
||||||
|
{
|
||||||
|
st.back[i].progress = gapCap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,29 +488,23 @@ void BeltSystem::advanceTunnelProgress()
|
|||||||
{
|
{
|
||||||
TunnelEntryTile& te = it->second;
|
TunnelEntryTile& te = it->second;
|
||||||
|
|
||||||
if (te.front)
|
for (std::size_t i = 0; i < te.itemSlots.size(); ++i)
|
||||||
{
|
{
|
||||||
te.front->progress += m_progressPerTick;
|
te.itemSlots[i].progress += m_progressPerTick;
|
||||||
if (te.front->progress > 1.0)
|
|
||||||
{
|
|
||||||
te.front->progress = 1.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (te.back)
|
const double absoluteCap = 1.0 - i * 0.25;
|
||||||
{
|
if (te.itemSlots[i].progress > absoluteCap)
|
||||||
te.back->progress += m_progressPerTick;
|
|
||||||
if (te.front && te.back->progress >= te.front->progress)
|
|
||||||
{
|
{
|
||||||
te.back->progress = te.front->progress - m_progressPerTick;
|
te.itemSlots[i].progress = absoluteCap;
|
||||||
if (te.back->progress < 0.0)
|
|
||||||
{
|
|
||||||
te.back->progress = 0.0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (te.back->progress > 0.5)
|
|
||||||
|
if (i > 0)
|
||||||
{
|
{
|
||||||
te.back->progress = 0.5;
|
const double gapCap = te.itemSlots[i - 1].progress - 0.25;
|
||||||
|
if (te.itemSlots[i].progress > gapCap)
|
||||||
|
{
|
||||||
|
te.itemSlots[i].progress = (gapCap < 0.0 ? 0.0 : gapCap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -522,29 +514,23 @@ void BeltSystem::advanceTunnelProgress()
|
|||||||
{
|
{
|
||||||
TunnelExitTile& tx = it->second;
|
TunnelExitTile& tx = it->second;
|
||||||
|
|
||||||
if (tx.front)
|
for (std::size_t i = 0; i < tx.itemSlots.size(); ++i)
|
||||||
{
|
{
|
||||||
tx.front->progress += m_progressPerTick;
|
tx.itemSlots[i].progress += m_progressPerTick;
|
||||||
if (tx.front->progress > 1.0)
|
|
||||||
{
|
|
||||||
tx.front->progress = 1.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tx.back)
|
const double absoluteCap = 1.0 - i * 0.25;
|
||||||
{
|
if (tx.itemSlots[i].progress > absoluteCap)
|
||||||
tx.back->progress += m_progressPerTick;
|
|
||||||
if (tx.front && tx.back->progress >= tx.front->progress)
|
|
||||||
{
|
{
|
||||||
tx.back->progress = tx.front->progress - m_progressPerTick;
|
tx.itemSlots[i].progress = absoluteCap;
|
||||||
if (tx.back->progress < 0.0)
|
|
||||||
{
|
|
||||||
tx.back->progress = 0.0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (tx.back->progress > 0.5)
|
|
||||||
|
if (i > 0)
|
||||||
{
|
{
|
||||||
tx.back->progress = 0.5;
|
const double gapCap = tx.itemSlots[i - 1].progress - 0.25;
|
||||||
|
if (tx.itemSlots[i].progress > gapCap)
|
||||||
|
{
|
||||||
|
tx.itemSlots[i].progress = (gapCap < 0.0 ? 0.0 : gapCap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -561,7 +547,7 @@ void BeltSystem::advanceTunnelProgress()
|
|||||||
}
|
}
|
||||||
if (i > 0)
|
if (i > 0)
|
||||||
{
|
{
|
||||||
const double maxProgress = link.items[i - 1].progress - 0.5;
|
const double maxProgress = link.items[i - 1].progress - 0.25;
|
||||||
if (ti.progress > maxProgress)
|
if (ti.progress > maxProgress)
|
||||||
{
|
{
|
||||||
ti.progress = maxProgress;
|
ti.progress = maxProgress;
|
||||||
@@ -582,7 +568,7 @@ void BeltSystem::moveItemsToNextTile()
|
|||||||
it != m_belts.end(); ++it)
|
it != m_belts.end(); ++it)
|
||||||
{
|
{
|
||||||
BeltTile& bt = it->second;
|
BeltTile& bt = it->second;
|
||||||
if (!bt.front || bt.front->progress < 1.0)
|
if (bt.itemSlots.empty() || bt.itemSlots.front().progress < 1.0)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -595,39 +581,31 @@ void BeltSystem::moveItemsToNextTile()
|
|||||||
|
|
||||||
if (nextBelt != m_belts.end())
|
if (nextBelt != m_belts.end())
|
||||||
{
|
{
|
||||||
if (tryPlaceOnBelt(next, bt.front->item))
|
if (tryPlaceOnBelt(next, bt.itemSlots.front().item))
|
||||||
{
|
{
|
||||||
bt.front = bt.back;
|
bt.itemSlots.erase(bt.itemSlots.begin());
|
||||||
bt.back = std::nullopt;
|
|
||||||
}
|
}
|
||||||
// else: next belt is full — item stays blocked at progress 1.0.
|
// else: next belt is full — item stays blocked at progress 1.0.
|
||||||
}
|
}
|
||||||
else if (nextSplitter != m_splitters.end())
|
else if (nextSplitter != m_splitters.end())
|
||||||
{
|
{
|
||||||
if (!nextSplitter->second.back)
|
if (nextSplitter->second.back.size() < 2)
|
||||||
{
|
{
|
||||||
nextSplitter->second.back = BeltItemSlot{bt.front->item, 0.0};
|
nextSplitter->second.back.push_back(BeltItemSlot{bt.itemSlots.front().item, 0.0});
|
||||||
nextSplitter->second.backDir = bt.direction;
|
nextSplitter->second.backDir.push_back(bt.direction);
|
||||||
bt.front = bt.back;
|
bt.itemSlots.erase(bt.itemSlots.begin());
|
||||||
bt.back = std::nullopt;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const std::map<std::pair<int, int>, TunnelEntryTile>::iterator nextEntry =
|
const std::map<std::pair<int, int>, TunnelEntryTile>::iterator nextEntry =
|
||||||
m_tunnelEntries.find(key(next));
|
m_tunnelEntries.find(key(next));
|
||||||
if (nextEntry != m_tunnelEntries.end() && !nextEntry->second.back)
|
if (nextEntry != m_tunnelEntries.end()
|
||||||
|
&& nextEntry->second.itemSlots.size() < 4)
|
||||||
{
|
{
|
||||||
if (!nextEntry->second.front)
|
nextEntry->second.itemSlots.push_back(
|
||||||
{
|
BeltItemSlot{bt.itemSlots.front().item, 0.0});
|
||||||
nextEntry->second.front = BeltItemSlot{bt.front->item, 0.0};
|
bt.itemSlots.erase(bt.itemSlots.begin());
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
nextEntry->second.back = BeltItemSlot{bt.front->item, 0.0};
|
|
||||||
}
|
|
||||||
bt.front = bt.back;
|
|
||||||
bt.back = std::nullopt;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -663,17 +641,16 @@ void BeltSystem::moveItemsToNextTile()
|
|||||||
it != m_tunnelExits.end(); ++it)
|
it != m_tunnelExits.end(); ++it)
|
||||||
{
|
{
|
||||||
TunnelExitTile& tx = it->second;
|
TunnelExitTile& tx = it->second;
|
||||||
if (!tx.front || tx.front->progress < 1.0)
|
if (tx.itemSlots.empty() || tx.itemSlots.front().progress < 1.0)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QPoint here = QPoint(it->first.first, it->first.second);
|
const QPoint here = QPoint(it->first.first, it->first.second);
|
||||||
const QPoint next = adjacentTile(here, tx.direction);
|
const QPoint next = adjacentTile(here, tx.direction);
|
||||||
if (tryPushToTile(next, tx.front->item, tx.direction))
|
if (tryPushToTile(next, tx.itemSlots.front().item, tx.direction))
|
||||||
{
|
{
|
||||||
tx.front = tx.back;
|
tx.itemSlots.erase(tx.itemSlots.begin());
|
||||||
tx.back = std::nullopt;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -688,23 +665,22 @@ void BeltSystem::moveTunnelItems()
|
|||||||
if (teIt != m_tunnelEntries.end())
|
if (teIt != m_tunnelEntries.end())
|
||||||
{
|
{
|
||||||
TunnelEntryTile& te = teIt->second;
|
TunnelEntryTile& te = teIt->second;
|
||||||
if (te.front && te.front->progress >= 1.0)
|
if (!te.itemSlots.empty() && te.itemSlots.front().progress >= 1.0)
|
||||||
{
|
{
|
||||||
const bool canEnter = link.items.empty()
|
const bool canEnter = link.items.empty()
|
||||||
|| link.items.back().progress >= 0.5;
|
|| link.items.back().progress >= 0.25;
|
||||||
if (canEnter)
|
if (canEnter)
|
||||||
{
|
{
|
||||||
TunnelTransitItem ti;
|
TunnelTransitItem ti;
|
||||||
ti.item = te.front->item;
|
ti.item = te.itemSlots.front().item;
|
||||||
ti.progress = 0.0;
|
ti.progress = 0.0;
|
||||||
link.items.push_back(ti);
|
link.items.push_back(ti);
|
||||||
te.front = te.back;
|
te.itemSlots.erase(te.itemSlots.begin());
|
||||||
te.back = std::nullopt;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transit front → exit back
|
// Transit front → exit
|
||||||
if (!link.items.empty() && link.items.front().progress >= link.length)
|
if (!link.items.empty() && link.items.front().progress >= link.length)
|
||||||
{
|
{
|
||||||
const std::map<std::pair<int, int>, TunnelExitTile>::iterator txIt =
|
const std::map<std::pair<int, int>, TunnelExitTile>::iterator txIt =
|
||||||
@@ -712,14 +688,9 @@ void BeltSystem::moveTunnelItems()
|
|||||||
if (txIt != m_tunnelExits.end())
|
if (txIt != m_tunnelExits.end())
|
||||||
{
|
{
|
||||||
TunnelExitTile& tx = txIt->second;
|
TunnelExitTile& tx = txIt->second;
|
||||||
if (!tx.back && !tx.front)
|
if (tx.itemSlots.size() < 4)
|
||||||
{
|
{
|
||||||
tx.front = BeltItemSlot{link.items.front().item, 0.0};
|
tx.itemSlots.push_back(BeltItemSlot{link.items.front().item, 0.0});
|
||||||
link.items.erase(link.items.begin());
|
|
||||||
}
|
|
||||||
else if (!tx.back && tx.front)
|
|
||||||
{
|
|
||||||
tx.back = BeltItemSlot{link.items.front().item, 0.0};
|
|
||||||
link.items.erase(link.items.begin());
|
link.items.erase(link.items.begin());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -733,24 +704,26 @@ void BeltSystem::routeSplitterItems()
|
|||||||
it != m_splitters.end(); ++it)
|
it != m_splitters.end(); ++it)
|
||||||
{
|
{
|
||||||
SplitterTile& st = it->second;
|
SplitterTile& st = it->second;
|
||||||
if (!st.back || st.back->progress < 0.5)
|
if (st.back.empty() || st.back.front().progress < 0.5)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Item& item = st.back->item;
|
const Item& item = st.back.front().item;
|
||||||
|
|
||||||
const bool matchesA = st.filterA.empty() ||
|
const bool matchesA = st.filterA.empty() ||
|
||||||
std::find(st.filterA.begin(), st.filterA.end(), item.type) != st.filterA.end();
|
std::find(st.filterA.begin(), st.filterA.end(), item.type) != st.filterA.end();
|
||||||
const bool matchesB = st.filterB.empty() ||
|
const bool matchesB = st.filterB.empty() ||
|
||||||
std::find(st.filterB.begin(), st.filterB.end(), item.type) != st.filterB.end();
|
std::find(st.filterB.begin(), st.filterB.end(), item.type) != st.filterB.end();
|
||||||
|
|
||||||
|
bool routed = false;
|
||||||
|
|
||||||
if (matchesA && !matchesB)
|
if (matchesA && !matchesB)
|
||||||
{
|
{
|
||||||
if (!st.frontA)
|
if (!st.frontA)
|
||||||
{
|
{
|
||||||
st.frontA = BeltItemSlot{item, 0.0};
|
st.frontA = BeltItemSlot{item, 0.0};
|
||||||
st.back = std::nullopt;
|
routed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (matchesB && !matchesA)
|
else if (matchesB && !matchesA)
|
||||||
@@ -758,7 +731,7 @@ void BeltSystem::routeSplitterItems()
|
|||||||
if (!st.frontB)
|
if (!st.frontB)
|
||||||
{
|
{
|
||||||
st.frontB = BeltItemSlot{item, 0.0};
|
st.frontB = BeltItemSlot{item, 0.0};
|
||||||
st.back = std::nullopt;
|
routed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (matchesA && matchesB)
|
else if (matchesA && matchesB)
|
||||||
@@ -769,30 +742,36 @@ void BeltSystem::routeSplitterItems()
|
|||||||
if (preferA && !st.frontA)
|
if (preferA && !st.frontA)
|
||||||
{
|
{
|
||||||
st.frontA = BeltItemSlot{item, 0.0};
|
st.frontA = BeltItemSlot{item, 0.0};
|
||||||
st.back = std::nullopt;
|
|
||||||
st.nextOutputIsA = false;
|
st.nextOutputIsA = false;
|
||||||
|
routed = true;
|
||||||
}
|
}
|
||||||
else if (!preferA && !st.frontB)
|
else if (!preferA && !st.frontB)
|
||||||
{
|
{
|
||||||
st.frontB = BeltItemSlot{item, 0.0};
|
st.frontB = BeltItemSlot{item, 0.0};
|
||||||
st.back = std::nullopt;
|
|
||||||
st.nextOutputIsA = true;
|
st.nextOutputIsA = 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.0};
|
st.frontB = BeltItemSlot{item, 0.0};
|
||||||
st.back = std::nullopt;
|
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.0};
|
st.frontA = BeltItemSlot{item, 0.0};
|
||||||
st.back = std::nullopt;
|
routed = true;
|
||||||
}
|
}
|
||||||
// else both fronts occupied — back stays.
|
// else both fronts occupied — back stays.
|
||||||
}
|
}
|
||||||
// else (!matchesA && !matchesB): stall — back stays.
|
// else (!matchesA && !matchesB): stall — back stays.
|
||||||
|
|
||||||
|
if (routed)
|
||||||
|
{
|
||||||
|
st.back.erase(st.back.begin());
|
||||||
|
st.backDir.erase(st.backDir.begin());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -805,24 +784,12 @@ bool BeltSystem::tryPlaceOnBelt(QPoint tile, Item item)
|
|||||||
}
|
}
|
||||||
|
|
||||||
BeltTile& bt = it->second;
|
BeltTile& bt = it->second;
|
||||||
|
if (bt.itemSlots.size() < 4)
|
||||||
if (!bt.front)
|
|
||||||
{
|
{
|
||||||
bt.front = BeltItemSlot{item, 0.0};
|
bt.itemSlots.push_back(BeltItemSlot{item, 0.0});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!bt.back)
|
return false; // all slots occupied
|
||||||
{
|
|
||||||
bt.back = BeltItemSlot{item, 0.0};
|
|
||||||
|
|
||||||
// Ensure ordering invariant: front has higher progress.
|
|
||||||
if (bt.back->progress > bt.front->progress)
|
|
||||||
{
|
|
||||||
std::swap(bt.front, bt.back);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false; // both slots occupied
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BeltSystem::tryPushToTile(QPoint dest, Item item, Rotation fromDir)
|
bool BeltSystem::tryPushToTile(QPoint dest, Item item, Rotation fromDir)
|
||||||
@@ -836,10 +803,10 @@ bool BeltSystem::tryPushToTile(QPoint dest, Item item, Rotation fromDir)
|
|||||||
m_splitters.find(key(dest));
|
m_splitters.find(key(dest));
|
||||||
if (splIt != m_splitters.end())
|
if (splIt != m_splitters.end())
|
||||||
{
|
{
|
||||||
if (!splIt->second.back)
|
if (splIt->second.back.size() < 2)
|
||||||
{
|
{
|
||||||
splIt->second.back = BeltItemSlot{item, 0.0};
|
splIt->second.back.push_back(BeltItemSlot{item, 0.0});
|
||||||
splIt->second.backDir = fromDir;
|
splIt->second.backDir.push_back(fromDir);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -849,15 +816,9 @@ bool BeltSystem::tryPushToTile(QPoint dest, Item item, Rotation fromDir)
|
|||||||
m_tunnelEntries.find(key(dest));
|
m_tunnelEntries.find(key(dest));
|
||||||
if (teIt != m_tunnelEntries.end())
|
if (teIt != m_tunnelEntries.end())
|
||||||
{
|
{
|
||||||
TunnelEntryTile& te = teIt->second;
|
if (teIt->second.itemSlots.size() < 4)
|
||||||
if (!te.front)
|
|
||||||
{
|
{
|
||||||
te.front = BeltItemSlot{item, 0.0};
|
teIt->second.itemSlots.push_back(BeltItemSlot{item, 0.0});
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!te.back)
|
|
||||||
{
|
|
||||||
te.back = BeltItemSlot{item, 0.0};
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -867,15 +828,9 @@ bool BeltSystem::tryPushToTile(QPoint dest, Item item, Rotation fromDir)
|
|||||||
m_tunnelExits.find(key(dest));
|
m_tunnelExits.find(key(dest));
|
||||||
if (txIt != m_tunnelExits.end())
|
if (txIt != m_tunnelExits.end())
|
||||||
{
|
{
|
||||||
TunnelExitTile& tx = txIt->second;
|
if (txIt->second.itemSlots.size() < 4)
|
||||||
if (!tx.front)
|
|
||||||
{
|
{
|
||||||
tx.front = BeltItemSlot{item, 0.0};
|
txIt->second.itemSlots.push_back(BeltItemSlot{item, 0.0});
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!tx.back)
|
|
||||||
{
|
|
||||||
tx.back = BeltItemSlot{item, 0.0};
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -901,19 +856,12 @@ void BeltSystem::forEachVisualItem(QRect viewportTiles,
|
|||||||
|
|
||||||
const BeltTile& bt = entry.second;
|
const BeltTile& bt = entry.second;
|
||||||
|
|
||||||
if (bt.front)
|
// Render least-progressed first (bottom) → most-progressed last (top).
|
||||||
|
for (int i = static_cast<int>(bt.itemSlots.size()) - 1; i >= 0; --i)
|
||||||
{
|
{
|
||||||
VisualItem vi;
|
VisualItem vi;
|
||||||
vi.type = bt.front->item.type;
|
vi.type = bt.itemSlots[i].item.type;
|
||||||
vi.worldPos = slotWorldPos(tile, bt.direction, bt.front->progress);
|
vi.worldPos = slotWorldPos(tile, bt.direction, bt.itemSlots[i].progress);
|
||||||
visit(vi);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bt.back)
|
|
||||||
{
|
|
||||||
VisualItem vi;
|
|
||||||
vi.type = bt.back->item.type;
|
|
||||||
vi.worldPos = slotWorldPos(tile, bt.direction, bt.back->progress);
|
|
||||||
visit(vi);
|
visit(vi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -928,28 +876,51 @@ void BeltSystem::forEachVisualItem(QRect viewportTiles,
|
|||||||
|
|
||||||
const SplitterTile& st = entry.second;
|
const SplitterTile& st = entry.second;
|
||||||
|
|
||||||
if (st.back)
|
// Unassigned items: least-progressed first (bottom), then higher-progressed (top).
|
||||||
|
for (int i = static_cast<int>(st.back.size()) - 1; i >= 0; --i)
|
||||||
{
|
{
|
||||||
VisualItem vi;
|
VisualItem vi;
|
||||||
vi.type = st.back->item.type;
|
vi.type = st.back[i].item.type;
|
||||||
vi.worldPos = slotWorldPos(tile, st.backDir, st.back->progress);
|
vi.worldPos = slotWorldPos(tile, st.backDir[i], st.back[i].progress);
|
||||||
visit(vi);
|
visit(vi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st.frontA)
|
// Output-slot items rendered on top of unassigned, in clockwise order from East.
|
||||||
|
// East=0, South=1, West=2, North=3 — lower rank rendered first (bottom).
|
||||||
|
auto clockwiseRank = [](Rotation r) -> int
|
||||||
{
|
{
|
||||||
VisualItem vi;
|
switch (r)
|
||||||
vi.type = st.frontA->item.type;
|
{
|
||||||
vi.worldPos = slotWorldPos(tile, st.outputA, st.frontA->progress);
|
case Rotation::East: return 0;
|
||||||
visit(vi);
|
case Rotation::South: return 1;
|
||||||
}
|
case Rotation::West: return 2;
|
||||||
|
case Rotation::North: return 3;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
if (st.frontB)
|
const bool aBeforeB = clockwiseRank(st.outputA) <= clockwiseRank(st.outputB);
|
||||||
|
|
||||||
|
auto renderFront = [&](const std::optional<BeltItemSlot>& slot, Rotation dir)
|
||||||
{
|
{
|
||||||
VisualItem vi;
|
if (slot)
|
||||||
vi.type = st.frontB->item.type;
|
{
|
||||||
vi.worldPos = slotWorldPos(tile, st.outputB, st.frontB->progress);
|
VisualItem vi;
|
||||||
visit(vi);
|
vi.type = slot->item.type;
|
||||||
|
vi.worldPos = slotWorldPos(tile, dir, slot->progress);
|
||||||
|
visit(vi);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (aBeforeB)
|
||||||
|
{
|
||||||
|
renderFront(st.frontA, st.outputA);
|
||||||
|
renderFront(st.frontB, st.outputB);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
renderFront(st.frontB, st.outputB);
|
||||||
|
renderFront(st.frontA, st.outputA);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -962,18 +933,11 @@ void BeltSystem::forEachVisualItem(QRect viewportTiles,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TunnelEntryTile& te = entry.second;
|
const TunnelEntryTile& te = entry.second;
|
||||||
if (te.front)
|
for (int i = static_cast<int>(te.itemSlots.size()) - 1; i >= 0; --i)
|
||||||
{
|
{
|
||||||
VisualItem vi;
|
VisualItem vi;
|
||||||
vi.type = te.front->item.type;
|
vi.type = te.itemSlots[i].item.type;
|
||||||
vi.worldPos = slotWorldPos(tile, te.direction, te.front->progress);
|
vi.worldPos = slotWorldPos(tile, te.direction, te.itemSlots[i].progress);
|
||||||
visit(vi);
|
|
||||||
}
|
|
||||||
if (te.back)
|
|
||||||
{
|
|
||||||
VisualItem vi;
|
|
||||||
vi.type = te.back->item.type;
|
|
||||||
vi.worldPos = slotWorldPos(tile, te.direction, te.back->progress);
|
|
||||||
visit(vi);
|
visit(vi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -987,18 +951,11 @@ void BeltSystem::forEachVisualItem(QRect viewportTiles,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TunnelExitTile& tx = entry.second;
|
const TunnelExitTile& tx = entry.second;
|
||||||
if (tx.front)
|
for (int i = static_cast<int>(tx.itemSlots.size()) - 1; i >= 0; --i)
|
||||||
{
|
{
|
||||||
VisualItem vi;
|
VisualItem vi;
|
||||||
vi.type = tx.front->item.type;
|
vi.type = tx.itemSlots[i].item.type;
|
||||||
vi.worldPos = slotWorldPos(tile, tx.direction, tx.front->progress);
|
vi.worldPos = slotWorldPos(tile, tx.direction, tx.itemSlots[i].progress);
|
||||||
visit(vi);
|
|
||||||
}
|
|
||||||
if (tx.back)
|
|
||||||
{
|
|
||||||
VisualItem vi;
|
|
||||||
vi.type = tx.back->item.type;
|
|
||||||
vi.worldPos = slotWorldPos(tile, tx.direction, tx.back->progress);
|
|
||||||
visit(vi);
|
visit(vi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,8 +123,8 @@ private:
|
|||||||
struct BeltTile
|
struct BeltTile
|
||||||
{
|
{
|
||||||
Rotation direction;
|
Rotation direction;
|
||||||
std::optional<BeltItemSlot> front; // higher progress; closer to output
|
// front (highest progress) at index 0; back (just entered) at end. Max 4.
|
||||||
std::optional<BeltItemSlot> back; // lower progress; closer to input
|
std::vector<BeltItemSlot> itemSlots;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SplitterTile
|
struct SplitterTile
|
||||||
@@ -134,8 +134,9 @@ private:
|
|||||||
std::vector<ItemType> filterA; // empty = accept all
|
std::vector<ItemType> filterA; // empty = accept all
|
||||||
std::vector<ItemType> filterB;
|
std::vector<ItemType> filterB;
|
||||||
bool nextOutputIsA; // alternation state
|
bool nextOutputIsA; // alternation state
|
||||||
std::optional<BeltItemSlot> back; // progress [0, 0.5]; entering from input belt
|
// Unassigned items: [0] = routing candidate (higher progress, caps at 0.5). Max 2.
|
||||||
Rotation backDir; // direction of the feeding belt (for animation)
|
std::vector<BeltItemSlot> back;
|
||||||
|
std::vector<Rotation> backDir; // feeding belt direction, parallel to back
|
||||||
std::optional<BeltItemSlot> frontA; // progress [0, 1]; routed to outputA
|
std::optional<BeltItemSlot> frontA; // progress [0, 1]; routed to outputA
|
||||||
std::optional<BeltItemSlot> frontB; // progress [0, 1]; routed to outputB
|
std::optional<BeltItemSlot> frontB; // progress [0, 1]; routed to outputB
|
||||||
};
|
};
|
||||||
@@ -144,15 +145,15 @@ private:
|
|||||||
{
|
{
|
||||||
Rotation direction;
|
Rotation direction;
|
||||||
int maxDistance;
|
int maxDistance;
|
||||||
std::optional<BeltItemSlot> front;
|
// front (highest progress) at index 0; back at end. Max 4.
|
||||||
std::optional<BeltItemSlot> back;
|
std::vector<BeltItemSlot> itemSlots;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TunnelExitTile
|
struct TunnelExitTile
|
||||||
{
|
{
|
||||||
Rotation direction;
|
Rotation direction;
|
||||||
std::optional<BeltItemSlot> front;
|
// front (highest progress) at index 0; back at end. Max 4.
|
||||||
std::optional<BeltItemSlot> back;
|
std::vector<BeltItemSlot> itemSlots;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TunnelTransitItem
|
struct TunnelTransitItem
|
||||||
|
|||||||
@@ -66,17 +66,19 @@ TEST_CASE("BeltSystem: tryPutItem fails after removeTile", "[belt]")
|
|||||||
// Capacity
|
// Capacity
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
TEST_CASE("BeltSystem: two items fit in one tile", "[belt]")
|
TEST_CASE("BeltSystem: four items fit in one tile", "[belt]")
|
||||||
{
|
{
|
||||||
BeltSystem bs(kFastBeltSpeed);
|
BeltSystem bs(kFastBeltSpeed);
|
||||||
const QPoint tile(0, 0);
|
const QPoint tile(0, 0);
|
||||||
bs.placeBelt(tile, Rotation::East);
|
bs.placeBelt(tile, Rotation::East);
|
||||||
|
|
||||||
REQUIRE(bs.tryPutItem(tile, makeItem("iron_ore")));
|
REQUIRE(bs.tryPutItem(tile, makeItem("a")));
|
||||||
REQUIRE(bs.tryPutItem(tile, makeItem("copper_ore")));
|
REQUIRE(bs.tryPutItem(tile, makeItem("b")));
|
||||||
|
REQUIRE(bs.tryPutItem(tile, makeItem("c")));
|
||||||
|
REQUIRE(bs.tryPutItem(tile, makeItem("d")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BeltSystem: third tryPutItem on full tile returns false", "[belt]")
|
TEST_CASE("BeltSystem: fifth tryPutItem on full tile returns false", "[belt]")
|
||||||
{
|
{
|
||||||
BeltSystem bs(kFastBeltSpeed);
|
BeltSystem bs(kFastBeltSpeed);
|
||||||
const QPoint tile(0, 0);
|
const QPoint tile(0, 0);
|
||||||
@@ -84,8 +86,10 @@ TEST_CASE("BeltSystem: third tryPutItem on full tile returns false", "[belt]")
|
|||||||
|
|
||||||
bs.tryPutItem(tile, makeItem("a"));
|
bs.tryPutItem(tile, makeItem("a"));
|
||||||
bs.tryPutItem(tile, makeItem("b"));
|
bs.tryPutItem(tile, makeItem("b"));
|
||||||
|
bs.tryPutItem(tile, makeItem("c"));
|
||||||
|
bs.tryPutItem(tile, makeItem("d"));
|
||||||
|
|
||||||
REQUIRE_FALSE(bs.tryPutItem(tile, makeItem("c")));
|
REQUIRE_FALSE(bs.tryPutItem(tile, makeItem("e")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -217,6 +221,8 @@ TEST_CASE("BeltSystem: item stays blocked when next tile is full", "[belt]")
|
|||||||
// Fill tileB to capacity.
|
// Fill tileB to capacity.
|
||||||
bs.tryPutItem(tileB, makeItem("b1"));
|
bs.tryPutItem(tileB, makeItem("b1"));
|
||||||
bs.tryPutItem(tileB, makeItem("b2"));
|
bs.tryPutItem(tileB, makeItem("b2"));
|
||||||
|
bs.tryPutItem(tileB, makeItem("b3"));
|
||||||
|
bs.tryPutItem(tileB, makeItem("b4"));
|
||||||
|
|
||||||
// Place item in tileA — should be blocked.
|
// Place item in tileA — should be blocked.
|
||||||
bs.tryPutItem(tileA, makeItem("a1"));
|
bs.tryPutItem(tileA, makeItem("a1"));
|
||||||
@@ -226,11 +232,11 @@ TEST_CASE("BeltSystem: item stays blocked when next tile is full", "[belt]")
|
|||||||
REQUIRE(bs.tryTakeItem(eastPort(tileA)).has_value());
|
REQUIRE(bs.tryTakeItem(eastPort(tileA)).has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BeltSystem: belt back slot is capped at progress 0.5", "[belt]")
|
TEST_CASE("BeltSystem: belt second slot is capped at progress 0.75", "[belt]")
|
||||||
{
|
{
|
||||||
// Use progress/tick = 0.4 so the cap is observable: without it, back would
|
// Use progress/tick = 0.4 so the cap is observable: without it, slot[1] would
|
||||||
// advance to 0.8 while front is stuck at 1.0, and then need only 1 more tick
|
// advance to 0.8 while slot[0] is stuck at 1.0. With the 0.75 cap it stays
|
||||||
// after being promoted. With the cap it stays at 0.5 and needs 2 more ticks.
|
// at 0.75 and needs exactly 1 more tick after promotion.
|
||||||
const double medBeltSpeed = 0.4 * static_cast<double>(kTickRateHz);
|
const double medBeltSpeed = 0.4 * static_cast<double>(kTickRateHz);
|
||||||
BeltSystem bs(medBeltSpeed);
|
BeltSystem bs(medBeltSpeed);
|
||||||
|
|
||||||
@@ -239,21 +245,18 @@ TEST_CASE("BeltSystem: belt back slot is capped at progress 0.5", "[belt]")
|
|||||||
|
|
||||||
// Advance front item to the output edge; it stays there (no next tile).
|
// Advance front item to the output edge; it stays there (no next tile).
|
||||||
bs.tryPutItem(tile, makeItem("front_item"));
|
bs.tryPutItem(tile, makeItem("front_item"));
|
||||||
bs.tick(); // front: 0.4
|
bs.tick(); // slot[0]: 0.4
|
||||||
bs.tick(); // front: 0.8
|
bs.tick(); // slot[0]: 0.8
|
||||||
bs.tick(); // front: 1.0 (capped, stuck)
|
bs.tick(); // slot[0]: 1.0 (capped, stuck)
|
||||||
|
|
||||||
// Place back item; front is at 1.0 and not blocking (back < 1.0).
|
// Place second item; slot[0] is at 1.0.
|
||||||
bs.tryPutItem(tile, makeItem("back_item"));
|
bs.tryPutItem(tile, makeItem("back_item"));
|
||||||
bs.tick(); // back: 0.4
|
bs.tick(); // slot[1]: 0.4
|
||||||
bs.tick(); // back would reach 0.8 — must be capped at 0.5
|
bs.tick(); // slot[1] would reach 0.8 — capped at 0.75
|
||||||
|
|
||||||
// Remove front; back (now promoted to front) must be at 0.5, not 0.8.
|
// Remove front; slot[1] (now promoted to slot[0]) must be at 0.75.
|
||||||
REQUIRE(bs.tryTakeItem(eastPort(tile)).has_value());
|
REQUIRE(bs.tryTakeItem(eastPort(tile)).has_value());
|
||||||
// At 0.4/tick, 0.5 → 0.9 after one tick — not at 1.0 yet.
|
// At 0.4/tick, 0.75 → 1.0 (capped) after one tick — available.
|
||||||
bs.tick();
|
|
||||||
REQUIRE_FALSE(bs.tryTakeItem(eastPort(tile)).has_value());
|
|
||||||
// 0.9 → 1.0 after a second tick — now available.
|
|
||||||
bs.tick();
|
bs.tick();
|
||||||
REQUIRE(bs.tryTakeItem(eastPort(tile)).has_value());
|
REQUIRE(bs.tryTakeItem(eastPort(tile)).has_value());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user