Auto Warz — Pipe & Fluid System Design¶
Document Type: Design Document Version: 0.3 Last Updated: 2026-03-13 Vault destination:
01_Design DocumentsPhase: Phase 1 — Launch release Related Documents:[01_Master GDD](<../01_Master GDD.md>)·[04_Factory & Resource System Design](<./04_Factory & Resource System Design.md>)·[06_Vertical Layers & Core Power System](<./06_Vertical Layers & Core Power System.md>)·[01_Technical Architecture Document](<../../02_Technical Documents/01_Technical Architecture Document.md>)Folder:01_Design Documents
Overview¶
Fluids are a distinct logistics tier from belts — they move through pipes, obey pressure and gravity, and have physical properties (viscosity, density, temperature) that affect how they behave. Managing fluid networks is a separate design problem from belt logistics: players must think about pressure sources, heat loss, pipe ratings, and flow rate, not just routing.
Fluids have phase = LIQUID or phase = GAS, and ride pipes rather than belts as a consequence of that phase. This is a rule in code, not a per-resource flag — see Canonical Design Facts §12a. ResourceTier no longer exists. Note that the split is currently structural rather than checked: fluids move through fluid_inventory, items through inventory, and nothing validates a resource against its carrier.
Implementation Status (M2.3 — Complete)¶
The following are implemented and shipped:
PipeSegmentnode — auto-connects to all adjacent pipe faces; arm-based visual (dark hub + coloured arms per connected face);fluid_idandfluid_amounttracked per segmentPipeSystem— scene-level registry and tick coordinator;_rebuild_networks()BFS correctly identifies connected segments and bridges across PipeConnectors by checking bothinput_faceandoutput_facesidesPipeNetwork— pair-equalisation fluid simulation (see #Fluid Simulation — Pair Equalisation)PipeConnector— directional pass-through; blue input face / green output face; 10-unit internal buffer; supports output→input chaining with adjacent connectorsFluidDefinitionresource class and all 4 fluid.tresfiles- Session config
pipe_complexity— Basic / Standard / Full
The following are deferred to M2.3.1 or later: - Full temperature simulation (heat loss, solidification, pipe damage) - Flammability and fire propagation - Gravity modifiers (vertical levels not yet in game) - Fluid network diagnostic overlay (pressure/temperature per segment) - Fluid Pump building - Heat Exchanger, Pipe Heater buildings - Pipe HP and rupture system
Fluid leaving the pipe network is owned by
[41_Fluid Packaging & Carriage Design](<../Logistics/41_Fluid Packaging & Carriage Design.md>). In short: what state a substance is in at ambient decides how it travels — solid rides as an ordinary item, liquid goes in a barrel, gas goes in a cylinder — and rail carries fluid as fluid in a tank wagon.⚠️ Molten metal is not container cargo.
fld_liquid_metalis solid at ambient, so it is cast and shipped as metal rather than packaged. That sharpens this document's own job: Insulated Pipe is the reason smelting and casting have to be neighbours, not merely the pipe for the hot fluid. The one exception is the insulated tank wagon, short haul only ([22_Trains & Rail](<../Logistics/22_Trains & Rail.md>)§4.2a).The
min_tempcolumn below therefore decides whether a fluid survives the trip at all, which is load-bearing for more than pipe blockage.
Fluid Properties¶
Every fluid definition (FluidDefinition.tres) carries:
| Property | Type | Description |
|---|---|---|
id |
String | e.g. fld_liquid_metal |
display_name |
String | e.g. "Liquid Metal" |
viscosity |
float | 0.1 (water-thin) → 1.0 (thick). Higher = slower flow rate |
density |
float | kg/m³ equivalent. Higher = more pump power required |
flammable |
bool | If true, pipe rupture near a heat source causes fire |
min_temp |
float | °C — below this the fluid solidifies and blocks the pipe |
max_temp |
float | °C — above this standard pipes take damage |
base_temp |
float | °C — temperature when first produced by a building |
colour |
Color | Visual tint shown inside pipe segments |
Phase 1 Fluid Definitions¶
| Fluid | Viscosity | Density | Flammable | Min Temp | Max Temp | Base Temp |
|---|---|---|---|---|---|---|
fld_coolant |
0.2 | 1.1 | No | -10°C | 80°C | 20°C |
fld_fuel |
0.3 | 0.8 | Yes | -40°C | 60°C | 20°C |
fld_liquid_metal |
0.9 | 7.8 | No | 450°C | 1200°C | 850°C |
fld_chemical_solvent |
0.4 | 1.3 | Yes | 5°C | 120°C | 20°C |
Fluid Simulation — Pair Equalisation¶
Implemented in M2.3. This replaces the greedy pressure-sort propagation originally specified.
Why Pair Equalisation¶
Greedy sort-by-pressure propagation causes oscillation on looping networks: the highest-pressure segment pushes its fluid aggressively each tick, then becomes low-pressure the next tick, causing back-and-forth flickering that never converges. This is especially visible with 3+ segments in a ring.
Pair equalisation eliminates this by iterating all connected segment pairs exactly once per tick — not propagating greedily from a sorted list.
Algorithm¶
func _equalise_fluid(segments: Array[PipeSegment]) -> void:
var visited_pairs: Dictionary = {}
for seg_a: PipeSegment in segments:
for dir: int in range(4):
var offset: Vector2i = BeltSegment.direction_offset(dir as BeltSegment.Direction)
var neighbour_pos: Vector2i = seg_a.grid_pos + offset
var seg_b: PipeSegment = _pipe_system.get_pipe_at(neighbour_pos)
if seg_b == null or seg_b.fluid_id != seg_a.fluid_id:
continue
# Deduplicate — each pair processed once per tick
var key: String = _pair_key(seg_a.grid_pos, seg_b.grid_pos)
if visited_pairs.has(key):
continue
visited_pairs[key] = true
var diff: float = seg_a.fluid_amount - seg_b.fluid_amount
if absf(diff) < 0.5: # dead-band — prevents micro-oscillation at equilibrium
continue
var transfer: float = diff * 0.4 # rate — 40% of imbalance per tick
transfer = clampf(transfer, -seg_b.fluid_amount, seg_a.fluid_amount)
seg_a.fluid_amount -= transfer
seg_b.fluid_amount += transfer
Tuning Knobs¶
| Parameter | Current Value | Effect of Increasing |
|---|---|---|
| Transfer rate | 0.4 | Faster fill, may overshoot on tiny networks |
| Dead-band | 0.5 units | Raise if micro-flicker returns on a specific layout |
Properties¶
- Stable on loops — ring networks converge to equilibrium within ~10 ticks
- O(edges) per tick — scales with connections, not segment count
- No sort step — eliminates the main oscillation source
Pipe Connector¶
The Pipe Connector is the fluid equivalent of the IoConnector for belts — a directional pass-through placed anywhere on the grid.
| Property | Value |
|---|---|
| Footprint | 1×1 cell |
input_face |
Set by R key at placement (blue face) |
output_face |
Always opposite in Phase 1 (green face) |
| Buffer | 10 fluid units |
| E key | Stub — future research unlock for output face rotation |
| Chaining | Output→input chains supported — connector pushes to adjacent connector's buffer if faces align and buffer is empty |
Connector Chaining Rules¶
A PipeConnector tick_push() checks in order:
1. Adjacent PipeConnector on output face whose input_face == opposite_dir(output_face) → transfer if buffer empty
2. Adjacent pipe segment on output face → transfer if segment has capacity
3. Adjacent building on output face → call receive_fluid() if building accepts
Cases where chaining does NOT transfer (stall harmlessly, no crash): - Output→output (two connectors facing same direction into each other) - Input→input (two connectors facing away from each other)
Pipe Segment Visual¶
PipeSegments use an arm-based visual computed from which faces are connected:
- Base slab:
BoxMesh(0.95, 0.06, 0.95)at y=0.03, colourColor(0.18, 0.18, 0.20) - Centre hub:
BoxMesh(0.22, 0.22, 0.22)at y=0.14, colourColor(0.25, 0.55, 1.0) - Per-face arm (NORTH/SOUTH):
BoxMesh(0.22, 0.22, 0.5)at y=0.14, same blue - Per-face arm (EAST/WEST):
BoxMesh(0.5, 0.22, 0.22)at y=0.14, same blue
_update_visual() is called:
- After _update_connected_faces() — updates both the new segment AND all neighbours reciprocally
- After _remove_face_from_neighbours() — on segment removal, neighbours lose their arm toward the removed cell
- After load — PipeSystem.refresh_all_visuals() rebuilds all visuals from saved connectivity
Pipe Types¶
| Pipe Type | Max Temp | Heat Loss | Notes | Unlock |
|---|---|---|---|---|
| Standard Pipe | 150°C | 3°C/seg | — | Available from start |
| Insulated Pipe | 1300°C | 0.3°C/seg | Required for Liquid Metal | Basic Insulation research |
| Underground Pipe | 150°C | 1.5°C/seg | Passes under buildings/belts | Basic Excavation research |
| Large Pipe | 150°C | 3°C/seg | 2× flow rate | Advanced Piping research |
| Reinforced Pipe | 200°C | 3°C/seg | Higher HP, rupture resistant | Industrial research |
Note: Temperature simulation is deferred to M2.3.1. These values are defined for completeness but not active in the current build.
Pressure Model¶
(Specified; pressure simulation active in Standard and Full modes. Basic mode uses fixed flow rate.)
Flow Rate Formula¶
flow_rate = (source_pressure - destination_pressure)
× (1 / viscosity)
× pipe_diameter_modifier
× gravity_modifier
Pressure Sources¶
| Source | Pressure Output |
|---|---|
| Producer building output | 80 kPa |
| Fluid Pump (standard) | +60 kPa |
| Fluid Pump (industrial, research) | +120 kPa |
| Gravity (descending level) | +30 kPa per level |
| Gravity (ascending level) | -40 kPa per level |
Pressure Loss¶
Temperature System¶
(Specified; temperature simulation deferred to M2.3.1 — not active in current build.)
Heat Loss Per Segment¶
| Pipe Type | Heat Loss |
|---|---|
| Standard Pipe | 3°C per segment |
| Insulated Pipe | 0.3°C per segment |
| Underground Pipe | 1.5°C per segment |
Solidification¶
If fluid temperature drops below min_temp, it solidifies and blocks the pipe at that segment. Clear by: placing a Pipe Heater adjacent, removing and replacing the segment, or raising source temperature.
Pipe Damage¶
If fluid exceeds max_temp in a standard pipe, the pipe takes damage each tick. At 0 HP the pipe ruptures.
Buildings¶
Fluid Pump (deferred to M2.3.1)¶
Boosts pressure at its placement point. Required for long runs and ascending vertical levels.
| Property | Value |
|---|---|
| Footprint | 1×1 |
| Power draw | 15 PU |
| Pressure boost | +60 kPa (standard) / +120 kPa (industrial) |
Storage Tank (implemented as BuildingDefinition .tres — fluid logic M2.3.1)¶
| Property | Value |
|---|---|
| Footprint | 2×2 |
| Capacity | 1000 fluid units |
Heat Exchanger (M2.3.1)¶
Transfers heat between two separate fluid networks.
Pipe Heater (M2.3.1)¶
Heats fluid in adjacent pipe segment. Required for Liquid Metal runs without Insulated Pipe.
Flammability & Pipe Rupture (M2.3.1)¶
When a flammable fluid pipe ruptures: 1. Fluid spills onto surrounding cells (3×3 area) 2. If adjacent heat source present → fire starts 3. Fire spreads each tick 4. Suppressed by Fire Suppressor building or burns out naturally
Session Configuration¶
Pipe complexity is a host-configurable session option set at session creation.
| Mode | Pressure | Temperature | Supply Chain Multiplier |
|---|---|---|---|
| Basic | ❌ Off | ❌ Off | ×1.0 |
| Standard | ✅ On | ❌ Off | ×1.15 |
| Full | ✅ On | ✅ On | ×1.30 |
Default: Standard
# In session_config (GameManager.gd)
"pipe_complexity": "basic" # "basic" | "standard" | "full"
const PIPE_COMPLEXITY_MULTIPLIER = {
"basic": 1.0,
"standard": 1.15,
"full": 1.30
}
Simulation Tick Order¶
P1 — Buildings produce (including fluid outputs)
P2 — IoConnectors tick_pull
P2b — Pipe Connectors tick_pull
P3 — IoConnectors tick_push
P3b — Pipe Connectors tick_push
P3c — Pipe segments propagate pressure (Standard/Full modes only)
P3d — Pipe segments equalise fluid (pair-equalisation, all modes)
P3e — Pipe segments update temperature (Full mode only — M2.3.1)
P4 — Belts advance
P5 — Genesis tasks
GDScript Data Structures¶
# FluidDefinition.gd
class_name FluidDefinition
extends Resource
@export var id: String
@export var display_name: String
@export var viscosity: float # 0.1 – 1.0
@export var density: float
@export var flammable: bool
@export var min_temp: float
@export var max_temp: float
@export var base_temp: float
@export var colour: Color
# PipeSegment.gd (node, not resource)
class_name PipeSegment
extends Node3D
var grid_pos: Vector2i
var fluid_id: String
var fluid_amount: float # current fill (0 – max_capacity)
var connected_faces: Array[bool] # [N, E, S, W] — drives visual
# PipeConnector.gd
class_name PipeConnector
extends Node3D
var grid_pos: Vector2i
var input_face: BeltSegment.Direction
var output_face: BeltSegment.Direction
var held_fluid: String
var held_amount: float
const BUFFER_MAX: float = 10.0
Open items for this document are tracked in docs/open-items.md, area pipes.
Revision History¶
| Version | Date | Changes |
|---|---|---|
| 0.2 | 2026-08-28 | Corrected the claim that ResourceTier.FLUID = 4 prevents fluids riding belts. Nothing enforces it; the separation is structural, and ResourceTier is being removed in favour of substance + phase. |
| 0.1 | 2026-02-27 | Initial document — pressure model, temperature system, fluid properties, pipe types, buildings, flammability, vertical integration, tick order, GDScript structures |
| 0.2 | 2026-02-27 | Session configuration added — Basic / Standard / Full modes |
| 0.3 | 2026-03-13 | M2.3 implementation recorded — pair-equalisation algorithm (replaces greedy propagation), PipeConnector chaining rules, arm-based visual spec, implementation status section (implemented vs deferred), data structures updated to match actual node architecture |