implement belt system

This commit is contained in:
2026-04-19 16:18:39 +02:00
parent ffe69f08b5
commit f2d912b4eb
10 changed files with 864 additions and 8 deletions

117
src/lib/sim/BeltSystem.h Normal file
View File

@@ -0,0 +1,117 @@
#pragma once
#include <functional>
#include <map>
#include <optional>
#include <utility>
#include <vector>
#include <QPoint>
#include <QPointF>
#include <QRect>
#include "Item.h"
#include "ItemType.h"
#include "Port.h"
#include "Rotation.h"
// Carries item type and fractional world position for the renderer.
// worldPos is in tile units (1 tile = 1.0 unit); origin matches tile coords.
struct VisualItem
{
ItemType type;
QPointF worldPos;
};
// Isolated belt-and-splitter transport layer. See architecture.md §Belt Subsystem.
//
// Buildings interact only through tryPutItem / tryTakeItem.
// Rendering reads only through forEachVisualItem.
// No other system inspects tile contents.
class BeltSystem
{
public:
explicit BeltSystem(double beltSpeedTilesPerSecond);
// -- Placement -----------------------------------------------------------
// Register a new belt tile. Any items already on this tile are cleared.
void placeBelt(QPoint tile, Rotation direction);
// Register a new splitter tile. outputA and outputB are the two exit
// directions (e.g. West and East for a default-rotation splitter).
// Items entering from any adjacent belt whose direction points into this
// tile are held and routed to one of the two outputs.
void placeSplitter(QPoint tile, Rotation outputA, Rotation outputB);
// Remove a belt or splitter tile (on demolish). Items are discarded.
void removeTile(QPoint tile);
// -- Splitter filter configuration (REQ-BLD-SPLITTER) -------------------
// filterA / filterB: empty means "accept all".
void setSplitterFilters(QPoint tile,
const std::vector<ItemType>& filterA,
const std::vector<ItemType>& filterB);
// -- Port interface (buildings <-> belts) --------------------------------
// port.tile = the belt tile adjacent to the building
// port.direction = direction items flow on that tile
//
// tryPutItem: place item onto port.tile entering from the opposite side.
// Returns false if the tile is not a belt, direction mismatches, or tile full.
bool tryPutItem(Port port, Item item);
// tryTakeItem: remove and return the leading item from port.tile.
// Returns nullopt if tile is not a belt, direction mismatches, or tile empty.
std::optional<Item> tryTakeItem(Port port);
// -- Maintenance ---------------------------------------------------------
void clearTiles(const std::vector<QPoint>& tiles); // REQ-UI-BELT-CLEAR
void tick();
// -- Rendering -----------------------------------------------------------
void forEachVisualItem(QRect viewportTiles,
std::function<void(VisualItem)> visit) const;
private:
void advanceProgress();
void moveItemsToNextTile();
void routeSplitterItems();
// Place item into back slot of an existing belt tile at progress 0.
// Returns false if tile is not a belt or is full.
bool tryPlaceOnBelt(QPoint tile, Item item);
static std::pair<int, int> key(QPoint tile);
static QPoint adjacentTile(QPoint tile, Rotation dir);
// Returns the world-space centre of a slot given tile origin and progress.
static QPointF slotWorldPos(QPoint tile, Rotation dir, double progress);
struct BeltItemSlot
{
Item item;
double progress; // [0.0, 1.0]: 0 = just entered, 1 = at output edge
};
struct BeltTile
{
Rotation direction;
std::optional<BeltItemSlot> front; // higher progress; closer to output
std::optional<BeltItemSlot> back; // lower progress; closer to input
};
struct SplitterTile
{
Rotation outputA;
Rotation outputB;
std::vector<ItemType> filterA; // empty = accept all
std::vector<ItemType> filterB;
bool nextOutputIsA; // alternation state
std::optional<Item> heldItem; // item buffered waiting to exit
};
double m_progressPerTick; // beltSpeedTilesPerSecond / kTickRateHz
std::map<std::pair<int, int>, BeltTile> m_belts;
std::map<std::pair<int, int>, SplitterTile> m_splitters;
};