#pragma once #include #include #include #include #include #include #include #include #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& filterA, const std::vector& 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 tile. // Returns false if the tile is not a belt, or tile full. bool tryPutItem(QPoint tile, 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 tryTakeItem(Port port); // peekItem: return the type of the leading item without removing it. // Returns nullopt if tile is not a belt, direction mismatches, or tile empty. std::optional peekItem(Port port) const; // -- Maintenance --------------------------------------------------------- void clearTiles(const std::vector& tiles); // REQ-UI-BELT-CLEAR void tick(); // -- Rendering ----------------------------------------------------------- void forEachVisualItem(QRect viewportTiles, std::function 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 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 front; // higher progress; closer to output std::optional back; // lower progress; closer to input }; struct SplitterTile { Rotation outputA; Rotation outputB; std::vector filterA; // empty = accept all std::vector filterB; bool nextOutputIsA; // alternation state std::optional heldItem; // item buffered waiting to exit }; double m_progressPerTick; // beltSpeedTilesPerSecond / kTickRateHz std::map, BeltTile> m_belts; std::map, SplitterTile> m_splitters; };