Port System Design¶
Document Type: Design Document Version: 0.1 (Draft) Status: In Progress Last Updated: 2026-08-25 Vault destination:
01_Design DocumentsReplaces:IoConnectorandPipeConnectoras separate placed entities Related:[04_Factory & Resource System Design](<./04_Factory & Resource System Design.md>)·[07_Pipe & Fluid System Design](<./07_Pipe & Fluid System Design.md>)·[01_Technical Architecture Document](<../02_Technical Documents/01_Technical Architecture Document.md>)·[09_UI-UX Design Document](<./09_UI-UX Design Document.md>)
1. Summary¶
Every building sits on a universal base: a square ring ("donut") matching the building's grid footprint. The ring carries ports. Ports are the sole interface between a building and the logistics network.
The machine body sits on top of the base. It transfers items and fluids down into the base through a central aperture. The base then routes them to whichever port is configured for output. Inputs travel the same path in reverse.
IoConnector and PipeConnector are removed. Their logic moves onto ports.
2. Why This Change¶
| Gain | Detail |
|---|---|
| One connection rule | Every building connects the same way. Single concept to teach in the tutorial. |
| No building rotation | Any port can serve any role, so a machine is never facing "the wrong way". Removes the most common placement frustration in the genre. |
| Shared base model | The base ring is the shared kit piece from 07_Art Direction Style Bible section 3. One model reused across 40+ buildings, each with a unique top. Largest available modelling time saving. |
| Free throughput lever | Port count scales with footprint, so larger buildings physically sustain more belts. Balance lever with no extra system. |
| Removes a middleman | Items currently pass building → connector → belt. Now building → port → belt. |
3. Inherited Constraints (MUST SATISFY)¶
IoConnector was built to solve specific problems. The port system must not reintroduce them. This section is the acceptance criteria for the refactor.
| # | Problem it solved | Does the port model handle it? | Action required |
|---|---|---|---|
| C1 | Connector placeable before the adjacent building existed; dynamic discovery each tick, no attached_building reference |
Free. Ports are created with their building. No ordering problem exists. | None |
| C2 | Connector survived demolition of an adjacent building (M2.2.5 independence fix) | Free. Ports are owned by the building and die with it. No dangling references. | Ensure neighbouring buildings/belts re-resolve on demolition |
| C3 | Single-item held_item buffer decoupled producer tick from belt tick, preventing a full belt stalling a producer |
NOT free. Must be deliberately carried across. | Each port gets its own buffer. See §5.3 |
| C4 | Belts deliver to a connector via try_accept_item() rather than pushing directly into buildings; reverse-flow guard depends on this |
NOT free. Must be deliberately carried across. | Belts deliver to ports via try_accept_item(). Reverse-flow guard unchanged. See §6 |
| C5 | Connector→connector chaining bridged gaps between distant buildings | LOST. Ports only exist where buildings exist. | ⚠️ Re-examine before accepting. Chaining was not free: it was added deliberately in two later commits, each after fluid-flow debugging. Something built on purpose to fix a specific problem deserves more than "accepted" before it is dropped. |
| C6 | ~~Unknown problems~~ | CLOSED 2026-08-27. Not applicable. The problems connectors solved are C1–C5 above, all introduced or exposed by the single commit that added them and visible in its diff. There is no forgotten sixth class of problem. | — |
Resolved 2026-08-27: there was no pre-Feb-2026 port system. The repository's first commit is 2026-02-23 and
IoConnectorarrives the next day; no.gdfile has ever been deleted, and no commit on any ref touchesconnector_slotor similar. What was actually removed wasBuildingInstance.tick_output()— five days old at the time — which scanned the four edge-adjacent cells and pushed an item onto the first free belt, in fixed N/S/W/E order. No slots, no configuration, no per-face state.The "prior to Feb 2026" claim traced to one line in a dev update dated 2026-02-24 (
docs/00_Ideas/02_Main.md), sitting between two bullets that both describetick_output. It is that update's loose name for the edge scan. The dev log is the commit, one day after the repo was created.Nothing was abandoned for a reason that could still apply. The port model is strictly more capable than what it replaces; the edge scan was replaced because it gave the player no control, not because it went wrong.
4. Base Geometry¶
4.1 Footprint¶
The base always matches the building footprint exactly. A 2×2 building has a 2×2 base. A 3×3 building has a 3×3 base.
4.2 Port count¶
Ports on a given face = the length of that face. One port per grid cell along each edge.
Footprints are not always square. Gas Pump is 2×3, Refinery 3×4, Advanced Press 4×5. On non-square buildings the base is a rectangular ring, not a square one, and opposing face pairs have different port counts.
| Footprint | N/S ports (each) | E/W ports (each) | Total |
|---|---|---|---|
| 1×1 (Silo) | 1 | 1 | 4 |
| 2×2 | 2 | 2 | 8 |
| 2×3 | 2 | 3 | 10 |
| 3×3 | 3 | 3 | 12 |
| 3×4 | 3 | 4 | 14 |
| 4×4 | 4 | 4 | 16 |
| 4×5 | 4 | 5 | 18 |
Note: the Silo is 1×1, giving exactly one port per face. Verify this is sufficient throughput before implementation, since storage is the most heavily connected building in most layouts.
Naming: storage buildings are Silo (solids) and Tank (fluids and gases). The
ChestModeenum is renamedStorageRolewith valuesUNASSIGNED/DEPOSIT/CONSTRUCTIONas part of this refactor.
4.3 Central aperture¶
The centre of the base is open. This is the transfer point between the machine body above and the base layer below. It is internal to the building, not a world-level shaft.
Dimensions pending camera lock. See 07_Art Direction Style Bible section 2.
5. Port Data Model¶
5.1 Port configuration¶
Each port holds:
| Field | Type | Notes |
|---|---|---|
face |
int (0–3) |
N/E/S/W, reuse BeltSegment.Direction |
index |
int |
Position along the face, 0-based |
mode |
enum |
DISABLED / INPUT / OUTPUT |
filter |
String |
Resource or fluid id. Empty = accept any valid |
medium |
enum |
ITEM / FLUID |
Gas: No third medium required. Gases (Chemical Gas, and later Hydrogen, Helium-3, Methane, Ammonia from Gas and Ice Giants) travel as
FLUIDon the pipe network. Ports need two media only. (Earlier text citedResourceTier.FLUID = 4here; that enum is being removed. A resource carriessubstanceandphase, and gases arephase = GAS— see Canonical Design Facts §12a. The port model is unaffected: the phase distinction lives at the Router, not the port.)⚠️ Inconsistency to resolve:
04_Factory & Resource System Designlists Gas Pump output as "Chemical Gas (fluid)", but Chemical Gas does not appear in the fluid table, and M2.1 shipped exactly 4 fluid.tresfiles (coolant, fuel, liquid metal, chemical solvent). Chemical Gas is therefore likely a raw-tier resource in code. This determines whether it can travel on belts and be carried by Genesis. CheckResourceDefinitionand correct either the doc or the data. Affects the Fuel Cell and Polymer Sheet recipes. |buffer|String/float| Held item id, or fluid amount | |player_set|bool| False until the player configures this port. Distinguishes never touched from deliberatelyDISABLED, whichmodealone cannot. Gates inbound auto-enable (§5.1c) |
Default on placement: all ports DISABLED. Player configures via the Building Detail Panel.
5.1a Visual Indication (Port Geometry)¶
Port mode is shown by built geometry, not a drawn symbol. Each port occupies a cell in a 0.25 m band on the top face of the base ring.
| Port mode | Geometry |
|---|---|
INPUT |
Recessed intake slot, a dark opening cut into the base |
OUTPUT |
Protruding nozzle breaking past the footprint edge, with a dark mouth |
DISABLED |
Flush cover plate, nothing open |
Geometry rather than a symbol because a flat mark dissolves at gameplay distance while a change in silhouette and shadow does not. Colour is unavailable: orange is the accent and Mk tabs, cyan means alive, amber and red mean fault.
Implementation: three mesh variants of one kit piece, swapped per port at runtime. The building mesh is untouched.
Port configuration is readable from the gameplay camera without opening any panel. Full spec in 07_Art Direction Style Bible §1.7c.
5.1b Configuration Order, and Auto-Enable (RESOLVED)¶
Ports default to DISABLED, and a building moves nothing until it has a recipe. Placing a building is not a statement about how it connects.
The sequence:
| Step | State |
|---|---|
| 1. Building placed | All ports DISABLED. Nothing enters, nothing leaves. Flush cover plates all round (§5.1a) |
| 2. Recipe set | Automatic where a building has one; chosen where it has several (OI-320). Extraction buildings have none and use their extraction fields instead |
| 3. Ports active | Either configured explicitly, or auto-enabled inbound by the rule below |
5.1c Inbound Auto-Enable¶
A port the player has never touched, on a building with a recipe, with something connected inbound, becomes
INPUTwith an empty filter.
Outputs are never automatic. Inputs are, once there is a recipe to define them.
Why the recipe gate is the whole trick. A permissive default before a recipe is incoherent: accept anything the recipe wants is an empty set when no recipe exists, and accepted_recipe_ids is genuinely multi-valued in shipping data - Assembly Machine and Advanced Assembly carry three recipes each, Press Machine and Advanced Press two - so the valid inputs are unknowable at placement. Gating on step 2 removes that entirely: when the rule can fire, the accepted set is always defined.
The alternative is worse than the extra rule. Pointing a belt at a Smelter and having nothing happen, with no error and no clue, is the single most likely way a new player concludes the game is broken.
Three guards make it safe:
| Guard | Rule |
|---|---|
| Recipe required | The building must have a recipe. No recipe, no auto-enable, no ambiguity about what is accepted |
| Inbound only | Applies to INPUT alone. Which face a building feeds stays a deliberate layout decision, and guessing it routes product into the wrong machine |
| The player always wins | An explicitly configured port is never overridden, including one the player deliberately set to DISABLED. Sealing a face has to stick, or the player cannot say no |
That last guard needs one bit of state:
player_set. It distinguishes never touched from deliberately closed, which the three-valuemodeenum cannot express on its own - both read asDISABLED. It is a boolean on the port, not a fourth enum value, and it is the only addition the whole rule requires.
Auto-enabled ports are real ports. They render as INPUT geometry under §5.1a, appear in the Building Detail Panel exactly like configured ones, and the player can filter or disable them afterwards. There is no hidden second class of port, and flush cover plate still honestly means nothing gets in here.
Auto-enable also applies to a touching neighbour's explicit OUTPUT port (§7), by the same reasoning: something is connected, inbound, and the recipe defines what is acceptable.
What happens on a recipe change: auto-enabled ports follow the new recipe, since their filter is empty and they only ever accepted what the recipe wants. Explicitly filtered ports do not, and a filter that the new recipe cannot use should be surfaced rather than silently cleared - the player set it deliberately. Tracked with the rest of the recipe-change behaviour in OI-320.
Ports are passive; a port never pulls. An OUTPUT port pushes what its building produced. An INPUT port receives what is pushed into it, by a belt connected to it (§6.3) or by a touching OUTPUT port (§7). A DISABLED port does neither. Nothing in the system reaches out and takes.
The building still starts sealed. A newly placed building is flush plate on every face and stays that way until it has a recipe, so an unconfigured building remains obvious rather than silently inert. Auto-enable does not begin before step 2.
Throughput gets no visual of its own. Mk level is already carried by the accent tab count (
07_Art Direction Style Bible§1.7) and port mode by the base geometry (§5.1a). Port throughput follows Base Mk (§5.4), so the tabs already show it. Do not add a third channel for port width - it would be a second encoding of a number the outer tab row already gives.
⚠️ Recipe selection itself is not designed. Nothing in the vault says how a player picks one of an Assembly Machine's three recipes, and step 2 above depends on it. Tracked as OI-320.
Open items for this document are tracked in docs/open-items.md.
5.3a Port Config Survives Upgrades¶
Port configuration must be preserved across a Base Mk upgrade. Mode, filter and buffer on every port carry over untouched.
A player may have configured eight or more ports individually. An upgrade implemented as tear-down-and-recreate would silently reset them all to DISABLED. See 24_Module System Design §5a.3, which states the general rule: an upgrade is a property change, never a replacement.
5.4 Base Mk — Port Upgrades¶
Ports are part of the base, and the base has its own upgrade track, independent of the machine.
| Base Mk | Item throughput | Fluid throughput | Filtering |
|---|---|---|---|
| Mk1 | 1 item/sec | 10 units/sec | No |
| Mk2 | 2 items/sec | 20 units/sec | No |
| Mk3 | 4 items/sec | 40 units/sec | Yes |
The item figures are the belt tiers exactly — Basic 1, Fast 2, Express 4 — and that is the point rather than a coincidence. A port and the belt feeding it are matched at every level, so a belt upgrade does nothing for a single machine until Base Mk rises to meet it. An Express Belt into a Mk1 port delivers 1 item/sec and carries the other three past.
This is the correct model and it is also a good lesson: "why did my Express Belt not speed anything up" has a real answer the player can find and fix. Belt tiers pay for themselves across many buildings on one line, not by making any single machine faster.
Fluid throughput is a cap applied on top of pair equalisation (07_Pipe & Fluid System Design), not a replacement for it. The 10-unit port buffer (§5.3) is unchanged, and the ratio to the item track reflects that one fluid unit is a much smaller thing than one item.
Why separate: the base is a universal kit piece, so Base Mk is one upgrade path shared by every building in the game. One recipe, one research line, applied to any building. Machine Mk stays per-building. Separating the tracks therefore costs far less than doubling.
It also creates a genuine optimisation choice. "The machine is fast enough but the belts back up" and "throughput is fine but processing is slow" become different problems with different answers. A single combined Mk collapses both into one button.
It also fixes the Silo. A 1×1 Silo has one port per face. Without a separate base track, a Mk3 Silo would hold far more but fill and drain at exactly the same rate: a deep buffer with a narrow neck. With Base Mk, throughput becomes something the player can choose to pay for.
Visual indication: two rows of accent tabs, outer for Base Mk and inner for Machine Mk. See 07_Art Direction Style Bible §1.7.
5.2 Configuration UI¶
Port configuration lives in the Building Detail Panel, which is on the M2.7 list and not yet built. This refactor should be sequenced so the panel is built once, with port config included, rather than built and then retrofitted.
5.3 Buffering (satisfies C3)¶
Each port carries its own buffer, mirroring the old held_item:
- Item ports: single item buffer
- Fluid ports: 10 fluid units, matching the old
PipeConnectorbuffer
A full downstream belt fills the port buffer and stops there. The producer is unaffected until the buffer is full.
6. Tick Model¶
6.1 Old phase order (5 phases)¶
P1 buildings produce
P2 connector.tick_pull()
P3 connector.tick_push()
P4 belts advance
P5 Genesis tasks
6.2 New phase order (4 phases)¶
P1 buildings produce
P2 ports transfer (pull from machine into output port buffers,
push from input port buffers into machine,
push output buffers to adjacent belt/pipe/port)
P3 belts and pipes advance
P4 Genesis tasks
⚠️ P2 is rate-limited, and this is where the 20× bug lives. A port holds one item and the loop runs 20 times a second, so a port that transfers whenever its buffer is non-empty moves 20 items/sec — five times an Express Belt, and it looks correct while doing it. Every P2 transfer must be gated by the port's throughput budget from §5.4, including port-to-port. See §7.2.
The buffer is not the rate. The one-item buffer exists to decouple producer from consumer (constraint C3); the throughput value decides how often it may empty. Conflating the two is the specific mistake to avoid.
6.3 Belt interaction (satisfies C4)¶
- A belt connected to a port calls
port.try_accept_item(). Belts never push directly into a building. - The existing reverse-flow guard (
opposite_dir()check) is unchanged and still applies. - A port pushing to a belt performs the same reverse-flow check before inserting.
Connected, not adjacent — and connection works both ways. A belt and a port are connected when the belt's direction of travel aligns with the port. That covers delivery and collection, so it is not an input-only rule:
Relation Belt direction Result Inbound Points at the port A belt delivers into an INPUTportOutbound Leads away from the port An OUTPUTport pushes onto the beltPassing Runs alongside the face Nothing. Adjacent, but not connected Adjacency is proximity; connection is proximity plus agreeing direction. The original wording here - "a belt reaching a cell adjacent to a port" - was loose enough to be implemented as a pure adjacency test, and belts routed past buildings are everywhere in real layouts, so that reading would have had every passing belt interacting with whatever it ran beside.
A belt pointing at an
OUTPUTport, or leading away from anINPUTport, is a direction conflict rather than a connection. The existing reverse-flow guard already rejects it and needs no addition.
7. Direct Building-to-Building Transfer (RESOLVED)¶
With ports on every base, two adjacent buildings have touching ports. Items can flow between them with no belt at all.
This is a consequence of the design, not an added feature. It is also a balance change: most factory games force the player through logistics.
Decision: allowed, at the port's own throughput. Direct transfer is not a special case with a rate of its own. It is a port doing its ordinary job with another port as the counterparty, so it moves at exactly the rate a belt into that same port would.
7.1 The port is the rate limiter, always¶
One rule covers every path into or out of a building:
A port moves at its throughput, and it does not matter what is on the other side of it. Belt, pipe, or another port.
That is the whole decision. There is no second number to tune, no adjacency modifier, and no case where the same port moves at two different speeds depending on what it is talking to.
This makes the port Auto Warz's inserter. The belt is a delivery mechanism, not a throughput mechanism, and a single machine can only ingest as fast as its port allows. An Express Belt running into a Base Mk1 port delivers 1 item/sec, not 4 — the other three are carried past to whatever is further down the line.
That is not a penalty, it is the correct model, and it gives Base Mk the job §5.4 already wanted it to have: "the machine is fast enough but the belts back up" is now a real, diagnosable problem with a specific fix.
7.2 What the obvious implementation would have done¶
Worth recording, because the obvious implementation is badly wrong and nothing in the tick model flags it.
§6.2 moves a port buffer per tick. An item port buffer is one item (§5.3). The simulation runs at 20 TPS. So a direct port-to-port transfer left to the natural phase order moves:
| Rate | |
|---|---|
| Naive port-to-port, one pair | 20 items/sec |
| Two touching 3-wide faces | 60 items/sec |
| Basic Belt | 1 item/sec |
| Fast Belt | 2 items/sec |
| Express Belt | 4 items/sec |
Five to sixty times the best belt in the game, available for free wherever two buildings touch. Belts would not be a weaker choice, they would be pointless. The option table this section used to carry described that outcome as "risk: players build solid blocks of touching machines and skip belts entirely", which reads as a balance worry about player behaviour. It was not a risk and it was not about behaviour — it was arithmetic, and the players would have been right.
7.3 Why it is not throttled below a belt¶
The rejected alternative was direct transfer at a fraction of belt throughput, to keep belts strictly the efficient path.
It makes a zero-length belt better than direct contact. Two machines bolted against each other would move less than the same two machines separated by one tile of Basic Belt, so the optimal play becomes prising them apart and threading a belt into the gap. That is mechanically absurd, it looks absurd, and it punishes exactly the compact building the base-and-port geometry is designed to reward.
A rule that makes the player build something silly to be correct is the wrong rule, regardless of what it protects.
7.4 What actually balances it: ports are finite and spent¶
Direct transfer needs no rate penalty because it already carries a real cost, and the cost is spatial rather than numeric.
- A port does one thing. It holds one item. An OUTPUT port pushing into a neighbouring INPUT port is not also feeding a belt that tick. Adjacency consumes the port.
- Ports are finite. Port count is face length (§4.2). A 1×1 Silo has four. Abut it on all four sides and it has no belt access at all — it can only be reached through the buildings touching it.
- Adjacency does not compose. It cannot split, merge, filter mid-route, buffer beyond one item, or cross any distance. The moment a recipe needs three smelters feeding one assembler, geometry runs out and belts return.
- Nothing is accidental. Ports default to
DISABLED(§5.1b), so a direct link exists only where the player has configured anOUTPUTfacing anINPUT. Adjacency never connects two buildings on its own.
A block of fully abutted machines is a legitimate, compact, efficient build that cannot be fed or drained except around its perimeter. That is a genuine engineering trade, and it is a better balance mechanism than a throughput penalty because the player can see it in the layout rather than reading it in a tooltip.
7.5 Fluids¶
The same rule, and it lands even more naturally.
Fluids do not queue, they equalise: 07_Pipe & Fluid System Design moves a fraction of the pressure difference per tick between connected pairs. A pipe between two buildings is a pair in that network, and two touching fluid ports are the same pair with the pipe removed.
So direct fluid transfer is pair equalisation, subject to the port's fluid throughput cap (§5.4). No new mechanism, and no separate case in the solver.
The fluid port buffer stays 10 units (§5.3), and the dead-band and transfer-rate knobs in that document are unchanged. A direct fluid link is simply a very short network.
7.6 Consequences elsewhere¶
| Area | Consequence |
|---|---|
| Base Mk | Now genuinely load-bearing. It is the only way to raise a building's intake, whether fed by belt or by adjacency. Multipliers set in §5.4 (OI-060) |
| Belt tiers | A faster belt does nothing for one machine until its Base Mk rises to match. Belt upgrades pay off across many buildings on one line, which is the honest reason to build them |
| The Router | Untouched. Splitting, merging, filtering and ratios are still the Router's job, and adjacency cannot do any of them ([20_Belt Logistics - The Router](<./20_Belt Logistics - The Router.md>)) |
| Router to Router | Allowed, by the general rule, and needs no special case (OI-065). A Router is 1×1 with four ports, so a direct Router chain is a 1-wide route moving at port rate — a strictly worse belt, costing a Router per tile instead of a belt per tile. It is self-balancing and not worth forbidding |
| Tutorial | [10_Tutorial & Onboarding Design](<./10_Tutorial & Onboarding Design.md>) step 8 teaches the drill-belt-smelter chain. Still correct: a drill sits on an ore patch and rarely abuts the smelter that wants its output |
| Backpressure | Propagates normally. A full downstream port buffer stalls the producer exactly as a full belt does, so "nothing is ever silently voided" (Canonical §7a) holds unchanged |
| Base ring status | No new state. Output-full and input-starved already distinguish the two failures and neither cares what the port is talking to |
8. Input Keys¶
R currently sets a connector's input_face at placement. E is a stub reserved for a research unlock allowing independent output-face rotation.
With connectors removed and buildings non-rotating, both keys lose their current purpose.
Open items for this document are tracked in docs/open-items.md.
9. Save/Load Impact¶
Removed from save schema:
- connectors: Array [{ x, y, in_face, out_face, held }]
- pipe_connectors: Array [{ x, y, in_face, out_face, held_fluid, held_amount }]
Added:
- Per-building ports: Array of port configs (§5.1)
Removed Genesis task types:
- PLACE_CONNECTOR
- PLACE_PIPE_CONNECTOR
Added Genesis task types: none. GenesisTask.Type shrinks by two and grows by none. See §9a.
Decision: Existing saves break. No migration path will be written. Project is pre-Early Access, so migration code would be written and discarded.
9a. Reconfiguring a Port (RESOLVED)¶
Port configuration is an instant UI action in the Building Detail Panel. It is not a Genesis task, and no CONFIGURE_PORT type is added.
This looked like it collided with a project rule, and does not. AGENTS.md states "All placement is queued as a Genesis task. No instant placement, ever." That governs placement - putting a thing into the world that was not there. Configuring a port places nothing.
Every port already physically exists. Port count is face length (§4.2) and the base ships with all of them, on every building, from the moment it is placed. DISABLED is not an absent port, it is a closed one - a flush cover plate over hardware that is already fitted. Configuration opens and closes what is there. It is a valve, not a pipe.
The decisive argument is internal consistency. §5.1c already changes a port's mode with no Genesis involvement at all: connect a belt inbound to a building with a recipe, and a never-touched port becomes INPUT on its own. If manual configuration required a bot to travel there, the identical state change would sometimes need a task and sometimes not, decided by whether the player used the panel or a belt. That is not a rule anyone could hold in their head.
And the cost of the alternative is not small. A Refinery is 3×4, so fourteen ports. A CONFIGURE_PORT task per port means fourteen round trips before the building does anything, with the player's factory stalled behind Genesis's queue and travel time. Placement earns its queue because a building is a material commitment; a port mode is a setting.
| Placement | Port configuration | |
|---|---|---|
| Creates something | Yes | No - the port is already there |
| Costs materials | Yes | No |
| Genesis task | Yes, always | No |
| Reversible for free | No | Yes |
The mesh swap is not construction. §5.1a specifies three mesh variants of one kit piece, swapped per port at runtime. Swapping which variant is shown is a visual state change, not a build step, and it is the same swap auto-enable already performs.
Consequence for the refactor: GenesisTask.Type loses PLACE_CONNECTOR and PLACE_PIPE_CONNECTOR and gains nothing, so the enum ends two values smaller. The Building Detail Panel absorbs the whole of port configuration (§5.2), which is one more reason it is on the critical path for OI-004.
10. Code Impact Summary¶
Files affected:
| File | Change |
|---|---|
BuildingInstance |
Gains ports array; pop_output_item() and receive_items() take a face/port argument |
SimulationManager |
Phase order 5 → 4; connector registry and lookup removed |
BeltSegment |
Delivers to ports rather than connectors; reverse-flow guard unchanged |
PipeSystem |
_rebuild_networks() BFS bridge logic via PipeConnector removed, replaced with port bridging |
IoConnector.gd |
Deleted |
05_Asset List & Delivery Specification.md |
io_connector and pipe_connector assets removed from the list. New building_base kit asset added. |
PipeConnector |
Deleted |
GenesisBot |
PLACE_CONNECTOR and PLACE_PIPE_CONNECTOR task types removed |
| Save/load | Schema change per §9 |
| Building Detail Panel | Built new, with port config included |
This is a coordinated multi-file change. Recommend Claude Code as a single planned refactor, not incremental Copilot edits.
Open items for this document are tracked in docs/open-items.md, area ports.
12. Revision History¶
| Version | Date | Changes |
|---|---|---|
| 0.2 | 2026-08-28 | Removed the reference to ResourceTier.FLUID = 4 when explaining gases. The enum is being replaced by substance + phase; the port model itself is unchanged, since the phase distinction lives at the Router rather than the port. |
| 0.1 | 2026-08-25 | Initial draft. Donut base concept, port model, connector removal, inherited constraints. |
| 0.2 | 2026-08-30 | Direct building-to-building transfer decided (OI-003): allowed, at the port's own throughput, with no adjacency modifier. §7 rewritten from an option table into a specification, recording that the natural implementation would have moved 20 items/sec against an Express Belt's 4, and why throttling below a belt is worse than allowing it. §5.4 gains the Base Mk throughput multipliers (OI-060): 1 / 2 / 4 items per second, matching the belt tiers exactly, so a belt upgrade does nothing for one machine until Base Mk matches. §6.2 gains a warning that P2 must be rate-limited, since the buffer is not the rate. Router-to-Router answered in §7.6 (OI-065). Unblocks OI-004. |
| 0.3 | 2026-08-30 | Port defaults decided (OI-058): asymmetric. New §5.1b - OUTPUT always explicit, ports default to INPUT with an empty filter accepting anything the recipe wants. No fourth state needed, because explicit output plus directed-into delivery leaves no accidental transfer to guard against. §6.3 tightened from adjacent to directed into, so a belt routed past a building delivers nothing to it. Recorded that port throughput gets no visual of its own, since the accent tabs already carry Mk and the base geometry already carries mode. |
| 0.4 | 2026-08-30 | §5.1b replaced, correcting v0.3 the same day. Defaults are not asymmetric: all ports default to DISABLED, because what a building accepts does not exist until a recipe is set, and accepted_recipe_ids is multi-valued in shipping data. §5.1b is now Configuration Order - Recipe, Then Ports, giving the place/recipe/configure sequence and stating that ports are passive: OUTPUT pushes, INPUT receives, nothing pulls. The v0.3 changes to §6.3 (directed-into) and the no-extra-visual rule are unaffected and stand. Raised OI-320 for recipe selection, which is undesigned and which the sequence depends on. |
| 0.5 | 2026-08-30 | §6.3 corrected: the test is connected, not directed into, which was input-only. A belt is connected when its travel direction aligns with the port, whether pointing at it to deliver or leading away from it to collect; alongside is adjacent but not connected. Added the three-way table and the note that a direction conflict is already handled by the existing reverse-flow guard. Also removed a line left over from v0.3 claiming ports are open to input by default, which v0.4 reversed. |
| 0.6 | 2026-08-30 | Inbound auto-enable added as §5.1c. Ports still default DISABLED, but a port the player has never touched, on a building with a recipe, with something connected inbound, becomes INPUT with an empty filter. The recipe gate is what makes this coherent where v0.3's permissive default was not: when the rule can fire, the accepted set is always defined. Outputs stay explicit. Adds one field, player_set, so a deliberately DISABLED port is never re-opened - mode alone cannot tell never touched from closed on purpose. Recipe-change behaviour for filtered ports folded into OI-320. |
| 0.7 | 2026-08-30 | Port reconfiguration decided (OI-259): new §9a. It is an instant UI action in the Building Detail Panel, not a Genesis task, and no CONFIGURE_PORT type is added - GenesisTask.Type shrinks by two and grows by none. The apparent collision with AGENTS.md's no instant placement rule is not real: that rule governs placement, and every port already exists from the moment the building does, with DISABLED meaning closed rather than absent. The decisive argument is internal consistency with §5.1c, which already changes port mode with no Genesis involvement. |