Full Code of konne88/functorio for AI

main d4d09d0c7ee9 cached
49 files
363.7 KB
119.6k tokens
2 symbols
1 requests
Download .txt
Showing preview only (386K chars total). Download the full file or copy to clipboard to get everything.
Repository: konne88/functorio
Branch: main
Commit: d4d09d0c7ee9
Files: 49
Total size: 363.7 KB

Directory structure:
gitextract_pckfgd1u/

├── .devcontainer/
│   ├── Dockerfile
│   └── devcontainer.json
├── .gitignore
├── .vscode/
│   └── settings.json
├── Fulgora150.lean
├── Functorio/
│   ├── Abbreviations.lean
│   ├── Adapter.lean
│   ├── AdapterTest.lean
│   ├── Ascii.lean
│   ├── AssemblyLine.lean
│   ├── AssemblyLineTest.lean
│   ├── AssemblyStation.lean
│   ├── AssemblyStationTest.lean
│   ├── Blueprint.lean
│   ├── Bus.lean
│   ├── BusTest.lean
│   ├── Cap.lean
│   ├── Column.lean
│   ├── Config.lean
│   ├── Crop.lean
│   ├── Direction.lean
│   ├── Entity.lean
│   ├── Expand.lean
│   ├── ExpandTest.lean
│   ├── Factory.lean
│   ├── Fraction.lean
│   ├── Readme.lean
│   ├── Recipe.lean
│   ├── Row.lean
│   ├── Test.lean
│   └── Util.lean
├── Functorio.lean
├── Gleba300.lean
├── LICENSE
├── Nauvis150.lean
├── README.md
├── RedScience150.lean
├── Rocket.lean
├── Spaceship.lean
├── fulgora-150.sh
├── generate-recipe.py
├── gleba-300.sh
├── lake-manifest.json
├── lakefile.toml
├── lean-toolchain
├── nauvis-150.sh
├── red-science-150.sh
├── rocket.sh
└── spaceship.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .devcontainer/Dockerfile
================================================
# FROM mcr.microsoft.com/devcontainers/universal:2
FROM mcr.microsoft.com/devcontainers/base:ubuntu

RUN apt-get update && apt-get install -y \
    ripgrep \
    pigz

USER vscode
ENV ELAN_HOME="/home/vscode/.elan"
ENV PATH="${ELAN_HOME}/bin:${PATH}"
RUN sh -c 'curl https://elan.lean-lang.org/elan-init.sh -sSf | sh -s -- -y'
RUN elan self update
RUN elan default leanprover/lean4:stable
RUN lean --version


================================================
FILE: .devcontainer/devcontainer.json
================================================
{
    "build": {
        "dockerfile": "Dockerfile"
    },
    "customizations": {
        "vscode": {
            "extensions": [
                "leanprover.lean4"
            ]
        }
    }
}



================================================
FILE: .gitignore
================================================
.lake

================================================
FILE: .vscode/settings.json
================================================
{
    "cSpell.words": [
        "biochamber",
        "Bioflux",
        "biosulfur",
        "electromagnetics",
        "Factorio",
        "foldl",
        "Functorio",
        "jellynut",
        "nauvis",
        "pentapod",
        "println",
        "repr",
        "roboport",
        "Roboports",
        "Supercapacitor",
        "yumako"
    ],

    "files.exclude": {
        "**/.git": true,
        "**/.DS_Store": true,
        ".lake": true
    },
}

================================================
FILE: Fulgora150.lean
================================================
import Functorio

instance : Config where
  generateBigPoles := true
  generateRoboports := true
  providerChestCapacity := 3
  adapterMinHeight := 3

def makeWater : Ice 120 -> Bus (Water 2400) :=
  busAssemblyLine (recipe .iceMelting) 2

def makeLightOil : Water 900 -> HeavyOil 1200 -> Bus (LightOil 900) :=
  busAssemblyLine (recipe .heavyOilCracking) 1

def makeHolmiumSolution : Water 960 -> HolmiumOre 192 -> Stone 96 -> Bus (HolmiumSolution 9600) :=
  busAssemblyLine (recipe .holmiumSolution) 16

def makeElectrolyte : HeavyOil 2400 -> HolmiumSolution 2400 -> Stone 240 -> Bus (Electrolyte 3600) :=
  busAssemblyLine (recipe .electrolyte) 10

def makeHolmiumPlate : HolmiumSolution 4500 -> Bus (Holmium 225) :=
  busAssemblyLine (recipe .holmiumPlate) 3

def makeSuperconductor : LightOil 240 -> Holmium 48 -> Copper 48 -> Plastic 48 -> Bus (Superconductor 144) :=
  busAssemblyLine (recipe .superconductor) 2

def makeSupercapacitor : Electrolyte 720 -> Holmium 144 -> Superconductor 144 -> GreenCircuit 288 -> Battery 72 -> Bus (Supercapacitor 108) :=
  busAssemblyLine (recipe .supercapacitor) 6

def makeAccumulator : Iron 144 -> Battery 360 -> Bus (Accumulator 108) :=
  busAssemblyLine { recipe := .accumulator, fabricator := .electromagneticPlant } 6

def makeElectromagneticScience : Electrolyte 2700 -> HolmiumSolution 2700 -> Supercapacitor 108 -> Accumulator 108 -> Bus (ElectromagneticScience 162) :=
  busAssemblyLine (recipe .electromagneticSciencePack) 9

def makeRocketFuel : LightOil 200 -> SolidFuel 200 -> Bus (RocketFuel 20) :=
  busAssemblyLine (recipe .rocketFuel) 4

def makeRocket : BlueCircuit 20 -> LowDensityStructure 20 -> RocketFuel 20 -> Bus Unit :=
  busAssemblyLine (recipe .rocketPart) 1

def fulgora150 := bus do
  let ice <- input .ice 120
  let stone <- input .stone 336
  let holmiumOre <- input .holmiumOre 192
  let iron <- input .ironPlate 144
  let copper <- input .copperPlate 48
  let solidFuel <- input .solidFuel 200
  let plastic <- input .plasticBar 48
  let lowDensityStruct <- input .lowDensityStructure 20
  let greenCircuit <- input .electronicCircuit 288
  let blueCircuit <- input .processingUnit 20
  let battery <- input .battery 432
  let heavyOil <- input .heavyOil 3600

  let (stone0, stone1) <- split stone
  let (battery0, battery1) <- split battery
  let (heavyOil0, heavyOil1) <- split heavyOil

  let water <- makeWater ice
  let (water0, water1) <- split water
  let lightOil <- makeLightOil water0 heavyOil0
  let (lightOil0, lightOil1) <- split lightOil
  let rocketFuel <- makeRocketFuel lightOil0 solidFuel
  let holmiumSolution <- makeHolmiumSolution water1.less holmiumOre stone0
  let (holmiumSolution0, holmiumSolution) <- split holmiumSolution
  let (holmiumSolution1, holmiumSolution2) <- split holmiumSolution
  let electrolyte <- makeElectrolyte heavyOil1 holmiumSolution0 stone1
  let (electrolyte0, electrolyte1) <- split electrolyte
  let holmiumPlate <- makeHolmiumPlate holmiumSolution1
  let (holmiumPlate0, holmiumPlate) <- split holmiumPlate
  let (holmiumPlate1, _holmiumExport) <- split holmiumPlate

  let superconductor <- makeSuperconductor lightOil.less holmiumPlate0 copper plastic
  let supercapacitor <- makeSupercapacitor electrolyte0 holmiumPlate1 superconductor.less greenCircuit battery0
  let accumulator <- makeAccumulator iron battery1
  let _ <- makeElectromagneticScience electrolyte1.less holmiumSolution2 supercapacitor accumulator.less

  makeRocket blueCircuit lowDensityStruct rocketFuel

def main : IO Unit :=
  IO.println (fulgora150.toBlueprint) --  (bootstrap := true))


================================================
FILE: Functorio/Abbreviations.lean
================================================
import Functorio.Bus

abbrev Coal := BusLane .coal
abbrev Stone := BusLane .stone
abbrev CopperOre := BusLane .copperOre
abbrev IronOre := BusLane .ironOre

abbrev Copper := BusLane .copperPlate
abbrev Iron := BusLane .ironPlate
abbrev Steel := BusLane .steelPlate
abbrev Brick := BusLane .stoneBrick

abbrev Water := BusLane .water
abbrev CrudeOil := BusLane .crudeOil
abbrev LightOil := BusLane .lightOil
abbrev HeavyOil := BusLane .heavyOil
abbrev Lubricant := BusLane .lubricant
abbrev Acid := BusLane .sulfuricAcid
abbrev Petrolium := BusLane .petroleumGas

abbrev Cable := BusLane .copperCable
abbrev Plastic := BusLane .plasticBar
abbrev GreenCircuit := BusLane .electronicCircuit
abbrev RedCircuit := BusLane .advancedCircuit
abbrev BlueCircuit := BusLane .processingUnit
abbrev Gear := BusLane .ironGearWheel
abbrev RobotFrame := BusLane .flyingRobotFrame
abbrev LowDensityStructure := BusLane .lowDensityStructure
abbrev ElectricEngine := BusLane .electricEngineUnit
abbrev Battery := BusLane .battery
abbrev Engine := BusLane .engineUnit
abbrev IronStick := BusLane .ironStick
abbrev Sulfur := BusLane .sulfur
abbrev RocketFuel := BusLane .rocketFuel

abbrev Pipe := BusLane .pipe
abbrev Inserter := BusLane .inserter
abbrev YellowBelt := BusLane .transportBelt
abbrev Furnace := BusLane .electricFurnace
abbrev Rail := BusLane .rail
abbrev ProdModule := BusLane .productivityModule
abbrev Wall := BusLane .stoneWall

abbrev YellowAmmo := BusLane .firearmMagazine
abbrev RedAmmo := BusLane .piercingRoundsMagazine
abbrev Grenade := BusLane .grenade

abbrev RedScience := BusLane .automationSciencePack
abbrev GreenScience := BusLane .logisticSciencePack
abbrev BlackScience := BusLane .militarySciencePack
abbrev BlueScience := BusLane .chemicalSciencePack
abbrev PurpleScience := BusLane .productionSciencePack
abbrev YellowScience := BusLane .utilitySciencePack

abbrev HotFluoroketone := BusLane .fluoroketoneHot
abbrev ColdFluoroketone := BusLane .fluoroketoneCold
abbrev Ice := BusLane .ice
abbrev LithiumPlate := BusLane .lithiumPlate
abbrev Lithium := BusLane .lithium
abbrev LithiumBrine := BusLane .lithiumBrine
abbrev Fluorine := BusLane .fluorine
abbrev SolidFuel := BusLane .solidFuel
abbrev Ammonia := BusLane .ammonia
abbrev IcePlatform := BusLane .icePlatform
abbrev AmmoniacalSolution := BusLane .ammoniacalSolution
abbrev CryogenicScience := BusLane .cryogenicSciencePack

abbrev Electrolyte := BusLane .electrolyte
abbrev HolmiumOre := BusLane .holmiumOre
abbrev Holmium := BusLane .holmiumPlate
abbrev HolmiumSolution := BusLane .holmiumSolution
abbrev Superconductor := BusLane .superconductor
abbrev Supercapacitor := BusLane .supercapacitor
abbrev Accumulator := BusLane .accumulator
abbrev ElectromagneticScience := BusLane .electromagneticSciencePack

abbrev Carbon := BusLane .carbon
abbrev Calcite := BusLane .calcite
abbrev ThrusterFuel := BusLane .thrusterFuel
abbrev ThrusterOxidizer := BusLane .thrusterOxidizer
abbrev MoltenIron := BusLane .moltenIron
abbrev MoltenCopper:= BusLane .moltenCopper
abbrev Explosives := BusLane .explosives
abbrev Rocket := BusLane .rocket
abbrev RailgunAmmo := BusLane .railgunAmmo

abbrev AgriculturalScience := BusLane .agriculturalSciencePack
abbrev Bioflux := BusLane .bioflux
abbrev PentapodEgg := BusLane .pentapodEgg
abbrev Jelly := BusLane .jelly
abbrev Spoilage := BusLane .spoilage
abbrev YumakoMash := BusLane .yumakoMash
abbrev IronBacteria := BusLane .ironBacteria
abbrev CopperBacteria := BusLane .copperBacteria
abbrev Nutrients := BusLane .nutrients
abbrev Jellynut := BusLane .jellynut
abbrev JellynutSeed := BusLane .jellynutSeed
abbrev Yumako := BusLane .yumako
abbrev YumakoSeed := BusLane .yumakoSeed


================================================
FILE: Functorio/Adapter.lean
================================================
import Functorio.Factory
import Functorio.Util

private inductive Orientation where
| vertical
| horizontalTopLtBot
| horizontalTopGtBot
| turnNE
| turnES
| turnSW
| turnWN

private def connector (x y : Nat) (isLiquid:Bool) (dir:DirectionV) (orient:Orientation) : Entity :=
  if isLiquid then pipe x y
  else belt x y (
    match dir, orient with
    | .N, .vertical => .N
    | .N, .horizontalTopLtBot => .W
    | .N, .horizontalTopGtBot => .E
    | .N, .turnNE => .N
    | .N, .turnES => .E
    | .N, .turnSW => .W
    | .N, .turnWN => .N
    | .S, .vertical => .S
    | .S, .horizontalTopLtBot => .E
    | .S, .horizontalTopGtBot => .W
    | .S, .turnNE => .E
    | .S, .turnES => .S
    | .S, .turnSW => .S
    | .S, .turnWN => .W
  )

private def singleConnection (isLiquid:Bool) (dir:DirectionV) (topOffset botOffset : InterfaceImpl) (height crossingY : Nat) : Array Entity :=
  if height == 0 then #[] else

  let lowX := min topOffset botOffset
  let highX := max topOffset botOffset
  let diff := highX - lowX - 1

  let vertical := (List.range height).flatMap fun y =>
    if (y < crossingY) then [connector topOffset y isLiquid dir .vertical] else
    if (y > crossingY) then [connector botOffset y isLiquid dir .vertical] else
    []

  let horizontal :=
    if topOffset < botOffset then
      (List.range' (topOffset + 1) diff).map fun x => connector x crossingY isLiquid dir .horizontalTopLtBot else
    if topOffset > botOffset then
      (List.range' (botOffset + 1) diff).map fun x => connector x crossingY isLiquid dir .horizontalTopGtBot
    else
      [connector topOffset crossingY isLiquid dir Orientation.vertical]

  let turns :=
    if topOffset < botOffset then
      [
        connector topOffset crossingY isLiquid dir .turnNE,
        connector botOffset crossingY isLiquid dir .turnSW
      ] else
    if topOffset > botOffset then
      [
        connector topOffset crossingY isLiquid dir .turnWN,
        connector botOffset crossingY isLiquid dir .turnES
      ]
    else
      []

  (vertical ++ horizontal ++ turns).toArray

def createAdapter (interfaces:List InterfaceV) (top bot:List InterfaceImpl) (height:Nat) : List Entity × Nat := Id.run do
  let mut topUsed := 0  -- Amount of space already used at the top by the adapter.
  let mut botUsed := 0  -- Amount of space already used at the bottom by the adapter.
  let mut neededHeight := 0
  let mut entities : Array Entity := #[]

  for ((ingredient, direction), i) in interfaces.zipIdx do
    let isLiquid := ingredient.isLiquid
    let topOffset := top[i]!
    let botOffset := bot[i]!

    -- Adjacent pipes need some padding between them
    let paddingNeeded := isLiquid && i+1 < interfaces.length && interfaces[i+1]!.fst.isLiquid

    -- If the interfaces line up, connect them directly
    if (topOffset == botOffset) then
      entities := entities ++ singleConnection isLiquid direction topOffset botOffset height 0
      topUsed := 0
      botUsed := 0
    -- If the top interface is to the left of the bottom interface, build connector along the bottom.
    else if (topOffset < botOffset) then
      entities := entities ++ singleConnection isLiquid direction topOffset botOffset height (height - botUsed - 1)
      topUsed := 0
      botUsed := botUsed + (if paddingNeeded then 2 else 1)
    -- If the bottom interface is to the left of the top interface, build connector along the top.
    else
      entities := entities ++ singleConnection isLiquid direction topOffset botOffset height topUsed
      topUsed := topUsed + (if paddingNeeded then 2 else 1)
      botUsed := 0

    neededHeight := max (max topUsed botUsed) neededHeight

  (entities.toList, neededHeight)

def adapterV {interface}
  (top:Vector InterfaceImpl interface.length)
  (bot:Vector InterfaceImpl interface.length)
  (width := (top ++ bot).toList.max?.getD 0)
  (minHeight := 0)
  : Factory interface [] interface []
  :=
  let initialHeight := (interface.map fun (ingredient,_) => if ingredient.isLiquid then 2 else 1).sum
  let (_,neededHeight) := createAdapter interface top.toList bot.toList initialHeight
  let height := max neededHeight minHeight
  let (entities,_) := createAdapter interface top.toList bot.toList height
  {
    width := width
    height:= height
    entities:= entities
    wires := []
    interface := {
      n := top
      e := Array.toVector #[]
      s := bot
      w := Array.toVector #[]
    }
    name := "adapter"
  }


================================================
FILE: Functorio/AdapterTest.lean
================================================
import Functorio.Adapter
import Functorio.Ascii

#guard (adapterV (interface:=[(.coal,.N), (.coal,.N)]) #v[0,1] #v[0,1]).toAscii == s!"
 ^^
 ^^
"

#guard (adapterV (interface:=[(.coal,.N), (.coal,.N)]) #v[0,1] #v[2,3]).toAscii == s!"
 ^^
 ↑↑←←
 ↑←←↑
   ^^
"

#guard (adapterV (interface:=[(.coal,.N), (.coal,.N)]) #v[2,3] #v[0,1]).toAscii == s!"
   ^^
 →→↑↑
 ↑→→↑
 ^^
"

#guard (adapterV (interface:=[(.coal,.N), (.coal,.N)]) #v[0,1] #v[0,3]).toAscii == s!"
 ^^
 ↑↑←←
 ^  ^
"

#guard (adapterV (interface:=[(.water,.N), (.lubricant,.N), (.crudeOil,.N)]) #v[0,2,4] #v[5,8,10]).toAscii == s!"
 ^ ^ ^
 | | |||||||
 | |       |
 | ||||||| |
 |       | |
 ||||||  | |
      ^  ^ ^
"

#guard (adapterV (interface:=[(.water,.N), (.ironOre,.N), (.crudeOil,.N)]) #v[0,2,4] #v[5,8,10]).toAscii == s!"
 ^ ^ ^
 | ↑ |||||||
 | ↑←←←←←← |
 ||||||  ↑ |
      ^  ^ ^
"


================================================
FILE: Functorio/Ascii.lean
================================================
import Functorio.Entity
import Functorio.Factory

/-
Legend:

↑ belt
⤒ belt going under-ground
↥ belt going above-ground

| pipe (no directionality)
┴ pipe going underground (above-ground on the top, under-ground on the bottom)

⇨ inserter (moving items from left to right)
↠ long inserter

⚡ power pole
↯ big power pole

A assembly machine
C chemical plant
R refinery
F furnace

^ interface direction (pointing north)
* building
! overlapping entities
-/

private def entitySymbol (e:Entity) : Option Char :=
  match e.type with
  | .belt .N _ => '↑'
  | .belt .E _ => '→'
  | .belt .S _ => '↓'
  | .belt .W _ => '←'

  | .beltDown .N => '⤒'
  | .beltDown .E => '⇥'
  | .beltDown .S => '⤓'
  | .beltDown .W => '⇤'

  | .beltUp .N => '↥'
  | .beltUp .E => '↦'
  | .beltUp .S => '↧'
  | .beltUp .W => '↤'

  | .pipe => '|'
  | .pipeToGround .N => '┴'
  | .pipeToGround .E => '├'
  | .pipeToGround .S => '┬'
  | .pipeToGround .W => '┤'

  | .inserter .N _ => '⇩'
  | .inserter .E _ => '⇦'
  | .inserter .S _ => '⇧'
  | .inserter .W _ => '⇨'

  | .longInserter .N _ => '↡'
  | .longInserter .E _ => '↞'
  | .longInserter .S _ => '↟'
  | .longInserter .W _ => '↠'

  | .pole => '⚡'
  | .bigPole => '↯'

  | .splitter _ _ _ => 'S'
  | .fabricator .assemblingMachine3 _ _ _ => 'A'
  | .fabricator .electricFurnace _ _ _ => 'F'
  | .fabricator .stoneFurnace _ _ _ => 'F'
  | .fabricator .steelFurnace _ _ _ => 'F'
  | .fabricator .electromagneticPlant _ _ _ => 'E'
  | .fabricator .biochamber _ _ _ => 'B'
  | .fabricator .chemicalPlant _ _ _ => 'C'
  | .fabricator .oilRefinery _ _ _ => 'O'
  | .fabricator .rocketSilo _ _ _ => 'L'  -- L is for Launch-site
  | .deciderCombinator _ _ _ => '≥'
  | .arithmeticCombinator _ _ => '+'
  | .heatingTower => 'H'

  | .roboport => 'R'
  | .pump _ => 'P'
  | .passiveProviderChest _ => '🄿'
  | .ironChest => '☐'

  | .refinedConcrete => .none

  | _ =>
    dbg_trace s!"Couldn't print {reprStr e}"
    '?'

private def set {w h} (v:Vector (Vector Char w) h) (x y:Nat) (c:Char) : Vector (Vector Char w) h :=
  -- Mark overlapping entities with !
  v.modify y fun inner => inner.modify x fun element => if element == ' ' then c else '!'

private def dirSymbol (dir:Direction) : Char :=
  match dir with
  | .N => '^'
  | .E => '>'
  | .S => 'v'
  | .W => 'w'

namespace Factory

def toAscii {n e s w} (f:Factory n e s w) : String := Id.run do
  let mut data := Vector.replicate (f.height + 2) (Vector.replicate (f.width + 2) ' ')

  -- draw entities
  for entity in f.entities do
    match entitySymbol entity with
    | .none => pure ()
    | .some symbol =>
      -- draw entity symbol and a box if it's a big entity
      for dx in List.range (entity.width) do
        for dy in List.range (entity.height) do
          let widerSymbol :=
            if dx == entity.width/2 && dy == entity.height/2
            then symbol
            else '*'
          -- +1 so we have space for interface indicators
          data := set data (entity.x + dx + 1) (entity.y + dy + 1) widerSymbol

  -- draw interfaces
  for (offset,i) in f.interface.n.zipIdx do
    data := set data (offset + 1) 0 (dirSymbol n[i]!.snd)
  for (offset,i) in f.interface.e.zipIdx do
    data := set data (f.width + 1) (offset + 1) (dirSymbol e[i]!.snd)
  for (offset,i) in f.interface.s.zipIdx do
    data := set data (offset + 1) (f.height+1) (dirSymbol s[i]!.snd)
  for (offset,i) in f.interface.w.zipIdx do
    data := set data 0 (offset + 1) (dirSymbol w[i]!.snd)

  return "\n" ++ String.intercalate "\n" (data.toList.map fun inner => (String.mk inner.toList).trimRight) ++ "\n"

end Factory


================================================
FILE: Functorio/AssemblyLine.lean
================================================
import Functorio.Entity
import Functorio.Factory
import Functorio.Crop
import Functorio.Recipe
import Functorio.Row
import Functorio.Bus
import Functorio.Cap
import Functorio.Fraction
import Functorio.Util
import Functorio.Config
import Functorio.AssemblyStation

-- Item's per minute
@[simp]
def inputThroughput (process:Process) (stations:Fraction) (items:Fraction) : Fraction :=
  stations * items * process.fabricator.speedup * 60 / process.getRecipe.time

@[simp]
def outputThroughput (process:Process) (stations:Fraction) (ingredient:Ingredient) (items:Fraction) : Fraction :=
  let t := stations * items * process.fabricator.speedup * (1 + process.fabricator.productivity) * 60 / process.getRecipe.time
  if ingredient.isLiquid then t else min expressBeltThroughput t

def expressBeltHalfThroughput := expressBeltThroughput / 2

def findGap (offsets : Array Nat) (minGap:Nat) := Id.run do
  let mut leftBound := 0
  for offset in offsets do
    let gap := offset - leftBound
    if gap >= minGap then
      return (leftBound, gap)
    leftBound := offset + 1

  error! s!"Couldn't find a gap of {minGap}"

def bigPoleInsert [config:Config] {interface} (offsets : Vector InterfaceImpl interface.length) : Factory interface [] interface [] :=
  if !config.generateBigPoles then emptyFactoryH offsets else

  let factory := (emptyFactoryH offsets).expand .S 2
  let (gapStart, gapWidth) := (findGap offsets.toArray 2)

  {
    factory with
    entities := factory.entities ++ [bigPole (gapStart + (gapWidth - 2) / 2) 0]
  }

def roboportInsert [config:Config] {interface} (offsets : Vector InterfaceImpl interface.length) : Factory interface [] interface [] :=
  if !config.generateRoboports then emptyFactoryH offsets else

  let factory := (emptyFactoryH offsets).expand .S 4
    let (gapStart, gapWidth) := (findGap offsets.toArray 2)

  {
    factory with
    entities :=
      factory.entities ++
      [roboport (gapStart + (gapWidth - 4) / 2) 0] ++
      if gapWidth > 4 then [pole (gapStart + gapWidth - 1) 3] else []
  }

def providerChestInsert [config:Config] {interface} (process:Process) (offsets : Vector InterfaceImpl interface.length) : Factory interface [] interface [] :=
  let recipe := process.getRecipe
  if config.providerChestCapacity == 0 || recipe.outputs.isEmpty || recipe.outputs[0]!.snd.isLiquid then emptyFactoryH offsets else

  let outputOffset :=
    (offsets.zipIdx.filter fun (_, i) => interface[i]!.snd == .S)[0]!.fst

  let chest := [
    passiveProviderChest (outputOffset - 2) 1 (capacity:=config.providerChestCapacity),
    inserter (outputOffset - 1) 1 .E
  ]

  let factory := (emptyFactoryH offsets).expand .S 3
  {
    factory with
    entities := (eraseRectangle (outputOffset - 1) 0 1 3 factory.entities) ++ chest ++
      if ((recipe.inputs ++ recipe.outputs).filter (fun input => !input.snd.isLiquid)).length <= 2 then [pole (outputOffset - 3) 1]
      else [pole (outputOffset - 4) 1,
            beltUp (outputOffset - 1) 0 .N,
            beltDown (outputOffset - 1) 2 .N]
  }

def outputBalancerInsert {interface} (offsets : Vector InterfaceImpl interface.length) : Factory interface [] interface [] :=
  let factory := (emptyFactoryH offsets).expand .S 4

  let (x,_) := findGap offsets.toArray 5
  let balancer : List Entity := [
    belt (x+2) 0 .S,
    belt (x+3) 0 .W,
    splitter (x+4) 0 .W (outputPriority := "right"),

    belt x 1 .S,
    belt (x+1) 1 .W,
    belt (x+2) 1 .W,
    belt (x+3) 1 .S,

    belt x 2 .S,
    belt (x+2) 2 .N,
    belt (x+3) 2 .W,
    belt (x+4) 2 .E,

    belt x 3 .E,
    beltDown (x+1) 3 .E,
    pole (x+2) 3,
    beltUp (x+3) 3 .E,
    belt (x+4) 3 .N,
  ]

  let outputOffset :=
    (offsets.zipIdx.filter fun (_, i) => interface[i]!.snd == .S)[0]!.fst

  let adapter :=
    let x := outputOffset
    [belt x 0 .S, belt x 1 .W, belt x 2 .S, belt x 3 .S]

  let avoider :=
    let x := outputOffset - 1
    [beltUp x 0 .N, belt x 1 .W, belt x 2 .E, beltDown x 3 .N]

  let connector :=
    match outputOffset - x with
    | 5 => adapter
    | 6 => adapter ++ avoider
    | _ => error! s!"Couldn't generate outputBalancerInsert for {reprStr interface}, x:= {x}, outputOffset := {outputOffset}"

  {
    factory with
    entities := (eraseRectangle (outputOffset - 1) 0 2 4 factory.entities) ++
                balancer ++ connector
  }

def maxRoboportLogisticsDistance := 46

def assemblyLineNoReturnedInputs [Config] (process:Process) (stations:Nat) : Factory (stationInterface process) [] (stationInterface process) [] :=
  Id.run do
    let output := process.getRecipe.outputs[0]!
    let stationOutput := inputThroughput process 1 output.fst
    let station := station process
    let mut factories : Array (Factory (stationInterface process) [] (stationInterface process) []) := #[
      bigPoleInsert station.interface.s,
      providerChestInsert process station.interface.s,
      roboportInsert station.interface.s
    ]
    let mut outputSinceBalance : Fraction := 0
    let mut distanceFromRoboport : Nat := 0

    for _ in List.range stations do
      if !output.snd.isLiquid && outputSinceBalance + stationOutput > expressBeltHalfThroughput && outputSinceBalance != 0 then
        factories := factories.push (outputBalancerInsert station.interface.s)
        outputSinceBalance := 0
        distanceFromRoboport := distanceFromRoboport + 4

      if distanceFromRoboport + station.height > maxRoboportLogisticsDistance then
        factories := factories.push (roboportInsert station.interface.s)
        distanceFromRoboport := 0

      factories := factories.push station
      outputSinceBalance := outputSinceBalance + stationOutput
      distanceFromRoboport := distanceFromRoboport + station.height

    columnList factories.toList.reverse

def assemblyLineInterface (process:Process) : List InterfaceV :=
  process.inputIngredients.map (., .N) ++
  process.outputIngredients.map (., .S) ++
  process.returnedInputs.map fun (_,ingredient) => (ingredient, .S)

namespace List

def lastIdxOf {A} [BEq A] (l:List A) (a:A) : Nat := Id.run do
  let mut index := l.length
  for (a', i) in l.zipIdx do
    if a == a' then
      index := i
  return index

end List

def filterInterfaceN {n e s w} (factory:Factory n e s w) (n':List InterfaceV) : Factory n' e s w :=
  {
    factory with
    interface := {
      n := n'.toVector.map fun interface => factory.interface.n[n.lastIdxOf interface]!
      e := factory.interface.e
      s := factory.interface.s
      w := factory.interface.w
    }
  }

def connectorInterface (process:Process) : List InterfaceV :=
  (process.returnedInputs.reverse.map fun (_,ingredient) => (ingredient, .N)) ++
  (process.returnedInputs.map fun (_,ingredient) => (ingredient, .S))

def returnedInputsConnector (process:Process) : Factory [] [] (connectorInterface process)  [] :=
  let n := process.returnedInputs.length
  let leftEntities : List Entity :=
    (List.range n).flatMap fun x =>
      (List.range n).map fun y =>
        belt x y (if x < y then .N else .E)

  let rightEntities : List Entity  :=
    (List.range n).flatMap fun x =>
      (List.range n).map fun y =>
        belt (x+n) y (if (n-x-1) <= y then .S else .E)

  {
    entities := leftEntities ++ rightEntities
    interface := {
      n := #v[]
      e := #v[]
      s := Vector.range (connectorInterface process).length
      w := #v[]
    }
    width := 2*n
    height := n
    wires := []
    name := s!"returnedInputsConnector {reprStr process.returnedInputs}"
  }

def connectReturnedInputs (process:Process) (factory:Factory (assemblyLineInterface process) [] (assemblyLineInterface process) []) :Factory [] [] (assemblyLineInterface process) [] :=
  if process.returnedInputs.isEmpty then capN factory else
  let filteredFactory := (filterInterfaceN factory (connectorInterface process)).expand .N 1
  column (returnedInputsConnector process) filteredFactory

def assemblyLine [Config] (process:Process) (stations:Nat) : Factory [] [] (assemblyLineInterface process) [] :=
  let line := assemblyLineNoReturnedInputs process stations
  let returns := emptyFactoryH
  let factory := row line returns
  connectReturnedInputs process factory

def tupleType {T} (ts:List T) (type:T->Type) : Type :=
  match ts with
  | [] => Unit
  | [t] => type t
  | t::types => type t × tupleType types type

def tuple {T} {ts:List T} {type:T->Type} (value : (t:T) -> Nat -> type t) (index:=0) : tupleType ts type :=
  match ts with
  | [] => ()
  | [t] => value t index
  | t::_::_ => (value t index, tuple value (index + 1))

@[simp]
def BusAssemblyLineReturn (process:Process) (stations:Fraction) : Type :=
  let outputs := (process.getRecipe.outputs.map fun (items, ingredient) => (outputThroughput process stations ingredient items, ingredient)) ++ process.returnedInputs
  Bus (tupleType outputs fun (throughput, ingredient) => BusLane ingredient throughput)

@[simp]
def BusAssemblyLineType (process:Process) (stations:Fraction) (remainingInputs: List (Fraction × Ingredient) := process.getRecipe.inputs): Type :=
  match remainingInputs with
  | [] => BusAssemblyLineReturn process stations
  | (items,ingredient)::inputs =>
    let throughput :=
      inputThroughput process stations items +
      List.sum (process.returnedInputs.map fun (throughput, ingredient') => if ingredient' == ingredient then throughput else 0)
    BusLane ingredient throughput -> BusAssemblyLineType process stations inputs

def processBusAssemblyLineArguments
  (process:Process)
  (stations:Fraction)
  (processor: List BusLane' -> BusAssemblyLineReturn process stations)
  (remainingInputs: List (Fraction × Ingredient) := process.getRecipe.inputs)
  (args: List BusLane' := [])
: BusAssemblyLineType process stations remainingInputs := by
  revert args
  refine (match remainingInputs with
  | [] => processor
  | _::inputs => fun args arg =>
    processBusAssemblyLineArguments process stations processor inputs (args ++ [arg.toBusLane'])
  )

def busAssemblyLine [config:Config] (process: Process) (stations:Fraction) : BusAssemblyLineType process stations :=
  processBusAssemblyLineArguments process stations fun inputs => do
    let factory := assemblyLine process stations.roundUp
    let namedFactory := factory.setName s!"{stations}x{reprStr process.recipe}"
    let indexes <- busTapGeneric
      inputs
      ((process.getRecipe.outputs ++ process.returnedInputs).map Prod.snd)
      (unsafeFactoryCast namedFactory)
      (adapterMinHeight := config.adapterMinHeight)
    return tuple (fun (_, _) i => {index:=indexes[i]!})


================================================
FILE: Functorio/AssemblyLineTest.lean
================================================
import Functorio.AssemblyLine
import Functorio.Ascii

#guard (assemblyLine (recipe .advancedCircuit) 3).toAscii == s!"

 ↑↑⇨***⇦↑↓
 ↑↑↠*A*↠↑↓
 ↑↑⚡***⚡↑↓
 ↑↑⇨***⇦↑↓
 ↑↑↠*A*↠↑↓
 ↑↑⚡***⚡↑↓
 ↑↑⇨***⇦↑↓
 ↑↑↠*A*↠↑↓
 ↑↑⚡***⚡↑↓
 ^^     ^v
"

#guard (bus do
  let acid <- input .sulfuricAcid (75/2)
  let green <- input .electronicCircuit 150
  let red <- input .advancedCircuit 15
  let _ <- busAssemblyLine (recipe .processingUnit) 1 acid.exact green red
).toAscii == s!"

  | ↑⇨***⇦↑↓
  |┤↑├*A*↠↑↓
  | ↑⚡***⚡↑↓
  |→↑     ↑↓
  |↑→→→→→→↑↓
  |↑↑↓←←←←←←
  |↑↑↓
>||↑↑→→→→→→→>
>→→↑↑
>→→→↑

"

#guard (bus do
  let copper <- input .copperPlate 750
  let _ <- busAssemblyLine (recipe .copperCable) 5 copper
).toAscii == s!"

 ↑⇨***⇨↓
 ↑ *A* ↓
 ↑⚡***⚡↓
 ↑  ↓←*↓
 ↑↓←←↓S←
 ↑↓ ↑←→↓
 ↑→⇥⚡↦↑↓
 ↑⇨***⇨↓
 ↑ *A* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *A* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *A* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *A* ↓
 ↑⚡***⚡↓
 ↑←↓←←←←
  ↑↓
>→↑→→→→→>

"

#guard (bus do
  let _ <- input .copperPlate 4
  let water <- input .water 600
  let crude <- input .crudeOil 1200
  let _ <- busAssemblyLine (recipe .advancedOilProcessing) 1 water crude
).toAscii == s!"

  | | *****|| | |
  |┤|├***** | | |
  | |⚡**O**┤|├| |
  | ||***** | | |
  | | *****┤| |├|
  | | ||||||| | |
  | | |       | |
  | | | ||||||| |
  | | | |       |
  | | | |   |||||
  | | | |   |
>⇥| | | |↦→⇥|↦→→→→>
>|| | | |   ||||||>
    | | |
>|||| |┤|├||||||||>
        |
        ||||||||||>

"

#guard (bus do
  let nutrients <- input .nutrients 15
  let jellynut <- input .jellynut 120
  let _ <- busAssemblyLine (recipe .jellynutProcessing) 1 nutrients jellynut
).toAscii == s!"

 ↑↑⇨***⇨↓↓
 ↑↑↠*B*↠↓↓
 ↑↑⚡***⚡↓↓
 ↑↑←↓←←←←↓
 ↑←↑↓↓←←←←
  ↑↑↓↓
>→↑↑↓→→→→→>
>→→↑→→→→→→>

"

#guard (bus do
  let nutrients <- input .nutrients 115
  let jellynut <- input .jellynut 120
  let _ <- busAssemblyLine (recipe .jellynutProcessing [(100, .nutrients)]) 1 nutrients jellynut
).toAscii == s!"

 →↓
 ↑→→→→→→→→↓
 ↑        ↓
 ↑↑⇨***⇨↓↓↓
 ↑↑↠*B*↠↓↓↓
 ↑↑⚡***⚡↓↓↓
 ↑↑ ↓←←←←↓↓
 ↑↑←↓↓←←←←↓
 ↑←↑↓↓↓←←←←
  ↑↑↓↓↓
>→↑↑↓↓→→→→→>
>→→↑↓→→→→→→>
    →→→→→→→>

"

#guard (bus do
  let nutrients <- input .nutrients 15
  let mash <- input .yumakoMash 300
  let jelly <- input .jelly 240
  let _ <- busAssemblyLine (recipe .bioflux) 1 nutrients mash jelly
).toAscii == s!"

 ↑↑⇨***⇦↑↓
 ↑↑↠*B*↠↑↓
 ↑↑⚡***⚡↑↓
 ↑↑←→→→→↑↓
 ↑←↑↑↓←←←←
  ↑↑↑↓
>→↑↑↑→→→→→>
>→→↑↑
>→→→↑

"

#guard (bus do
  let nutrients <- input .nutrients 100
  let mash <- input .yumakoMash 400
  let jelly <- input .jelly 400
  let _ <- busAssemblyLine (recipe .bioflux [(160, .jelly), (100, .yumakoMash), (85, .nutrients) ]) 1 nutrients mash jelly
).toAscii == s!"

 →→→→→↓
 ↑→→→↓↓
 ↑↑→↓↓↓
 ↑↑↑↓↓→→→→→→↓
 ↑↑↑↓→→→→→→↓↓
 ↑↑↑→→→→→→↓↓↓
 ↑↑↑←←←←← ↓↓↓
 ↑↑     ↑ ↓↓↓
 ↑↑⇨***⇦↑↓↓↓↓
 ↑↑↠*B*↠↑↓↓↓↓
 ↑↑⚡***⚡↑↓↓↓↓
 ↑↑ →→→→↑↓↓↓↓
 ↑↑ ↑↓←←←←↓↓↓
 ↑↑ ↑↓↓←←←←↓↓
 ↑↑←↑↓↓↓←←←←↓
 ↑←↑↑↓↓↓↓←←←←
  ↑↑↑↓↓↓↓
>→↑↑↑↓↓↓→→→→→>
>→→↑↑↓↓→→→→→→>
>→→→↑↓→→→→→→→>
     →→→→→→→→>

"

instance : Config where
  generateBigPoles := true
  generateRoboports := true
  providerChestCapacity := 3
  adapterMinHeight := 3

#guard (bus do
  let ice <- input .ice 60
  let _ <- busAssemblyLine (recipe .iceMelting) 1 ice
).toAscii == s!"

 ***┤↑├|
 *C*⇦↑ |
 ***⚡↑ |
 ****↑ |
 ****↑ |
 **R*↑ |
 ****↑ |
  ** ↑ |
  *↯ ↑ |
     ↑||
     ↑|
     ↑|
     ↑|
>→→→→↑|||>

"

#guard (bus do
  let ironOre <- input .ironOre 600
  let _ <- busAssemblyLine (recipe .ironPlate) 16 ironOre
).toAscii = s!"

 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑**** ↓
 ↑**** ↓
 ↑**R* ↓
 ↑****⚡↓
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ↑**** ↓
 ↑**** ↓
 ↑**R* ↓
 ↑****⚡↓
 ↑     ↓
 ↑  ⚡🄿⇦↓
 ↑     ↓
 ↑ **  ↓
 ↑ *↯  ↓
 ↑ ↓←←←←
 ↑ ↓
 ↑←↓
  ↑↓
>→↑→→→→→>

"

#guard (bus do
  let stone <- input .stone 750
  let steel <- input .steelPlate 750
  let stick <- input .ironStick 750
  let _ <- busAssemblyLine (recipe .rail) 5 stone stick steel
).toAscii == s!"

 ↑↑⇨***⇦↑↓
 ↑↑↠*A*↠↑↓
 ↑↑ ***↠↑↓
 ↑↑  ⚡  ↑↓
 ↑↑  ↓←*↥↓
 ↑↑↓←←↓S←←
 ↑↑↓ ↑←→→↓
 ↑↑→⇥⚡↦↑⤒↓
 ↑↑⇨***⇦↑↓
 ↑↑↠*A*↠↑↓
 ↑↑ ***↠↑↓
 ↑↑  ⚡  ↑↓
 ↑↑⇨***⇦↑↓
 ↑↑↠*A*↠↑↓
 ↑↑ ***↠↑↓
 ↑↑  ⚡  ↑↓
 ↑↑⇨***⇦↑↓
 ↑↑↠*A*↠↑↓
 ↑↑ ***↠↑↓
 ↑↑  ⚡  ↑↓
 ↑↑⇨***⇦↑↓
 ↑↑↠*A*↠↑↓
 ↑↑ ***↠↑↓
 ↑↑  ⚡  ↑↓
 ↑↑**** ↑↓
 ↑↑**** ↑↓
 ↑↑**R* ↑↓
 ↑↑****⚡↑↓
 ↑↑     ↥↓
 ↑↑  ⚡ 🄿⇦↓
 ↑↑     ⤒↓
 ↑↑ **  ↑↓
 ↑↑ *↯  ↑↓
 ↑↑  →→→↑↓
 ↑↑← ↑↓←←←
 ↑←↑ ↑↓
  ↑↑ ↑↓
>→↑↑ ↑→→→→>
>→⇥↑↦↑
>→→↑

"


================================================
FILE: Functorio/AssemblyStation.lean
================================================
import Functorio.Recipe
import Functorio.Factory
import Functorio.Util
import Functorio.Row

structure Process where
  recipe:RecipeName
  fabricator:Fabricator
  returnedInputs : List (Fraction × Ingredient) := []
  -- fabricatorOk : fabricator.handlesCategory recipeName.getRecipe.category := by decide
  deriving Repr, DecidableEq

namespace Process

@[simp]
def getRecipe (process:Process) : Recipe :=
  let original := process.recipe.getRecipe
  if process.fabricator != .biochamber then original else

  let liquidInputs := original.inputs.filter fun input => input.snd.isLiquid
  let solidInputs := original.inputs.filter fun input => !input.snd.isLiquid

  let extraNutrients := original.time / process.fabricator.speedup / 4
  let solidInputs :=
    if solidInputs.any fun (_,ingredient) => ingredient == .nutrients
    then solidInputs.map fun (items, ingredient) => (items + if ingredient == .nutrients then extraNutrients else 0, ingredient)
    else [( extraNutrients , Ingredient.nutrients )] ++ solidInputs

  { original with
    inputs := liquidInputs ++ solidInputs
  }

def liquidInputs (process:Process) : List Ingredient :=
  (process.getRecipe.inputs.map Prod.snd).filter Ingredient.isLiquid

def liquidOutputs (process:Process): List Ingredient :=
  (process.getRecipe.outputs.map Prod.snd).filter Ingredient.isLiquid

def solidInputs (process:Process): List Ingredient :=
  (process.getRecipe.inputs.map Prod.snd).filter (!Ingredient.isLiquid .)

def solidOutputs (process:Process): List Ingredient :=
  (process.getRecipe.outputs.map Prod.snd).filter (!Ingredient.isLiquid .)

@[simp]
def inputIngredients (process:Process) : List Ingredient :=
  process.getRecipe.inputs.map (Prod.snd)

@[simp]
def outputIngredients (process:Process) : List Ingredient :=
  process.getRecipe.outputs.map (Prod.snd)

end Process

@[simp]
def defaultCategoryFabricator : RecipeCategory -> Fabricator
| .chemistry
| .organicOrChemistry
| .chemistryOrCryogenics => .chemicalPlant
| .basicCrafting
| .crafting
| .advancedCrafting
| .craftingWithFluid
| .craftingWithFluidOrMetallurgy
| .electronics
| .electronicsOrAssembling
| .electronicsWithFluid
| .metallurgyOrAssembling
| .organicOrAssembling
| .pressing
| .parameters
| .cryogenicsOrAssembling => .assemblingMachine3
| .oilProcessing => .oilRefinery
| .rocketBuilding => .rocketSilo
| .smelting => .electricFurnace
| .captiveSpawnerProcess => .captiveBiterSpawner
| .centrifuging => .centrifuge
| .crushing => .crusher
| .cryogenics => .cryogenicPlant
| .electromagnetics => .electromagneticPlant
| .metallurgy => .foundry
| .organic
| .organicOrHandCrafting => .biochamber
| .recyclingOrHandCrafting
| .recycling => .recycler

@[simp]
def recipe (recipe:RecipeName) (returnedInputs : List (Fraction × Ingredient) := []) : Process := {
  recipe := recipe,
  fabricator := defaultCategoryFabricator recipe.getRecipe.category,
  returnedInputs := returnedInputs
}

structure FabricatorConfig where
  direction : Direction
  mirror : Bool
  inputOffsets : List Nat
  outputOffsets : List Nat

def fabricatorConfig : Fabricator -> FabricatorConfig
| .assemblingMachine1
| .recycler
| .steelFurnace
| .stoneFurnace
| .electricFurnace
| .rocketSilo
| .centrifuge
| .crusher
| .captiveBiterSpawner => {
  direction := .N
  mirror := false
  inputOffsets := []
  outputOffsets := []
}
| .assemblingMachine2
| .assemblingMachine3 => {
  direction := .W
  mirror := false
  inputOffsets := [1]
  outputOffsets := [1]
}
| .chemicalPlant
| .biochamber => {
  direction := .W
  mirror := true
  inputOffsets := [0,2]
  outputOffsets := [0,2]
}
| .oilRefinery => {
  direction := .E
  mirror := false
  inputOffsets := [1,3]
  outputOffsets := [0,2,4]
}
| .cryogenicPlant => {
  direction := .E
  mirror := false
  inputOffsets := [0,2,4]
  outputOffsets := [0,2,4]
}
| .foundry => {
  direction := .E
  mirror := false
  inputOffsets := [1,3]
  outputOffsets := [1,3]
}
| .electromagneticPlant => {
  direction := .N
  mirror := false
  inputOffsets := [2]
  outputOffsets := [1]
}

def interfaceE (process:Process) : List InterfaceH :=
  process.liquidOutputs.map (.,.E)

def interfaceW (process:Process) : List InterfaceH :=
  process.liquidInputs.map (.,.E)

def plainStation (process:Process) : Factory [] (interfaceE process) [] (interfaceW process) :=
  let details := fabricatorConfig process.fabricator
  {
    entities := [
      fabricator 0 0 process.fabricator process.recipe details.direction details.mirror
    ]
    wires := []
    width := process.fabricator.width
    height := process.fabricator.height
    interface := {
      n := #v[]
      e := (details.outputOffsets.splitAt process.liquidOutputs.length).fst.castToVector!
      s := #v[]
      w := (details.inputOffsets.splitAt process.liquidInputs.length).fst.castToVector!
    }
    name := reprStr process.getRecipe.name
  }

private def beltline (x:Nat) (dir:Direction) (height:Nat) : List Entity :=
  (List.range height).map (fun y => belt x y dir)

private def pipeline (x:Nat) (height:Nat): List Entity :=
  (List.range height).map fun y => pipe x y

structure Access where
  direction: DirectionV
  ingredient: Ingredient

-- factoryDir = direction in which the factory lies
def accessEntities (x:Nat) (height:Nat) (ewOffsets:List InterfaceImpl) (ns : List InterfaceV) (factoryDir: DirectionH) (numSolidOutputs:Nat) : List Entity :=
  Id.run do
    let mut entities : Vector (Option EntityType) height := Vector.replicate height .none

    let inputInserterDir : DirectionH := match factoryDir with | .E => .W | .W => .E
    let outputInserterDir : DirectionH := factoryDir

    for y in ewOffsets do
      entities := entities.set! y (.some (.pipeToGround factoryDir))

    for ((ingredient, dir), i) in ns.zipIdx do
      for y in List.range height do
        if entities[y]! == .none then
          let inserterDir := match dir with | .N => inputInserterDir | .S => outputInserterDir
          let filter := if dir == .S && numSolidOutputs > 1 then [ingredient] ++ ingredient.spoilResult.toList else []
          match i with
          | 0 => entities := entities.set! y (.some (.inserter inserterDir filter))
          | 1 => entities := entities.set! y (.some (.longInserter inserterDir filter))
          | _ => error! s!"More than 2 belt inputs/outputs per side are not supported, belt index = {i}"
          break

    for y in (List.range height).reverse do
      if entities[y]! == .none then
        entities := entities.set! y (.some .pole)
        break

    return entities.toList.zipIdx.flatMap fun (type, y) =>
      match type with | .none => [] | .some type => [{x:=x, y:=y, type:=type}]

def rightAccessor {ew} (ewOffsets: Vector InterfaceImpl ew.length) (height:Nat) (ns : List InterfaceV) (numSolidOutputs:Nat): Factory ns ew ns ew :=
  match ns with
  | [] => emptyFactoryV ewOffsets
  | _ =>
    {
      width := ns.length + 1
      height := height
      wires := []
      interface := {
        n := (ns.mapIdx fun i _ => i + 1).castToVector!
        e := ewOffsets
        s := (ns.mapIdx fun i _ => i + 1).castToVector!
        w := ewOffsets
      }
      name := "rightAccessor"
      entities :=
        accessEntities 0 height ewOffsets.toList ns .W numSolidOutputs ++
        (ns.zipIdx.flatMap fun ((_, dir), i) => beltline (i + 1) dir height)
    }

def leftAccessor {ew} (ewOffsets: Vector InterfaceImpl ew.length) (height:Nat) (ns : List InterfaceV) (numSolidOutputs:Nat): Factory ns ew ns ew :=
  match ns with
  | [] => emptyFactoryV ewOffsets
  | _ =>
    {
      width := ns.length + 1
      height := height
      wires := []
      interface := {
        n := (ns.mapIdx fun i _ => i).castToVector!
        e := ewOffsets
        s := (ns.mapIdx fun i _ => i).castToVector!
        w := ewOffsets
      }
      name := "leftAccessor"
      entities :=
        accessEntities ns.length height ewOffsets.toList ns .E numSolidOutputs ++
        (ns.zipIdx.flatMap fun ((_, dir), i) => beltline i dir height)
    }


def interfaceNS (process:Process) : List InterfaceV :=
  process.solidInputs.map (.,.N) ++ process.solidOutputs.map (.,.S)

def pipesOnSideStation (process:Process) : Factory
  (interfaceNS process)
  (interfaceE process)
  (interfaceNS process)
  (interfaceW process)
:=
  let station := plainStation process
  let ns := interfaceNS process
  let (leftNS, rightNS) := ns.splitAt (ns.length / 2)
  let leftAccess := leftAccessor station.interface.w station.height leftNS process.solidOutputs.length
  let rightAccess := rightAccessor station.interface.e station.height rightNS process.solidOutputs.length
  unsafeFactoryCast (row3 leftAccess station rightAccess)

private def pipesIn (ingredients:List Ingredient) (underground:Bool := false) (powerPole:Bool := false)
: Factory (ingredients.map (.,.N)) (ingredients.map (.,.E)) (ingredients.map (.,.N)) []
:=
  let pipes := ingredients.length
  let width := if pipes == 0 then 0 else pipes * 2 + 1
  let height := if powerPole then max 2 (pipes * 2 - 1) else pipes * 2 - 1

  let pipelines : List Entity :=
    (List.range pipes).flatMap fun i =>
      (List.range height).map fun y =>
        let x := i * 2 + 1
        pipe x y

  let goDown : List Entity :=
    (List.range pipes).map fun i =>
      let x := 2 * i + 2
      let y := 2 * i

      if i == pipes - 1 && !underground
      then pipe x y
      else pipeToGround x y .W

  let comeUp : List Entity :=
    (List.range pipes).flatMap fun i =>
      let x := 2 * pipes
      let y := 2 * i

      if i == pipes - 1 || underground
      then []
      else [pipeToGround x y .E]

  let power : List Entity :=
    if powerPole then [pole (width-1) 1] else []

  let interfaceNS := ingredients.toVector.mapIdx fun i _ => (i * 2 + 1 : InterfaceImpl)
  let interfaceE := ingredients.toVector.mapIdx fun i _ => (i * 2 : InterfaceImpl)

  {
    width := width
    height := height
    wires := []
    entities := pipelines ++ goDown ++ comeUp ++ power
    interface := {
      n := cast (by simp) interfaceNS
      e := cast (by simp) interfaceE
      s := cast (by simp) interfaceNS
      w := #v[]
    }
    name := s!"pipesIn {reprStr ingredients}"
  }

private def pipesOut (ingredients:List Ingredient) (underground:Bool := false)
: Factory (ingredients.map (.,.S)) [] (ingredients.map (.,.S)) (ingredients.map (.,.E))
:=
  let pipes := ingredients.length
  let width := if pipes == 0 then 0 else pipes * 2 + 1
  let height := pipes * 2 - 1

  let pipelines : List Entity :=
    (List.range pipes).flatMap fun i =>
      (List.range height).map fun y =>
        let x := i * 2 + 1
        pipe x y

  let goDown : List Entity :=
    (List.range pipes).map fun i =>
      let x := 2 * i
      let y := 2 * i

      if i == 0 && !underground
      then pipe x y
      else pipeToGround x y .E

  let comeUp : List Entity :=
    (List.range pipes).flatMap fun i =>
      let x := 0
      let y := 2 * i

      if i == 0 || underground
      then []
      else [pipeToGround x y .W]

  let interfaceNS := ingredients.toVector.mapIdx fun i _ => (i * 2 + 1 : InterfaceImpl)
  let interfaceW := ingredients.toVector.mapIdx fun i _ => (i * 2 : InterfaceImpl)

  {
    wires := []
    width := width
    height := height
    entities := pipelines ++ goDown ++ comeUp
    interface := {
      n := cast (by simp) interfaceNS
      e := #v[]
      s := cast (by simp) interfaceNS
      w := cast (by simp) interfaceW
    }
    name := s!"pipesOut {reprStr ingredients}"
  }

def stationInterface (process:Process) : List InterfaceV :=
  process.inputIngredients.map (., .N) ++
  process.outputIngredients.map (., .S)

abbrev Station process := Factory (stationInterface process) [] (stationInterface process) []

def stationWithoutOverride (process:Process) : Station process :=
  let station := pipesOnSideStation process
  let height := process.fabricator.height

  let ns := interfaceNS process
  let (leftNS, rightNS) := ns.splitAt (ns.length / 2)

  unsafeFactoryCast (row3
    (pipesIn process.liquidInputs (underground:=!leftNS.isEmpty) (powerPole:=
      ns.length == 0 ||
      process.liquidInputs.length + leftNS.length >= height))
    station
    (pipesOut process.liquidOutputs (underground:=!rightNS.isEmpty)))

-- Special case, because it takes 4 inputs.
private def flyingRobotFrameStation : Station (recipe .flyingRobotFrame) :=
  let height := 3
  let entities : List Entity :=
    beltline (x:=0) .N height ++
    beltline (x:=1) .N height ++
    [
      beltUp 2 0 .N,
      longInserter 2 1 .W,
      beltDown 2 2 .N,

      longInserter 3 0 .W,
      pole 3 1,
      inserter 3 2 .W,

      assembler RecipeName.flyingRobotFrame 4 0,

      longInserter 7 0 .W,
      pole 7 1,
      inserter 7 2 .E
    ] ++
    beltline (x:=8) .N height ++
    beltline (x:=9) .S height

  {
    wires := []
    width:= 10, height:=height, entities := entities
    interface := {
      n := #v[0,1,2,8,9]
      e := #v[]
      s := #v[0,1,2,8,9]
      w := #v[]
    }
    name := "flyingRobotFrame"
  }

-- Special case, because it has incredible requirements on output speed
private def nutrientsFromBiofluxStation : Station (recipe .nutrientsFromBioflux) :=
  let height := 5
  let entities : List Entity :=
    beltline (x:=0) .N height ++
    beltline (x:=1) .N height ++
    [
      inserter 2 0 .W,
      longInserter 2 1 .W,
      longInserter 2 2 .W,
      pole 2 3,

      fabricator 3 0 .biochamber .nutrientsFromBioflux,

      inserter 6 0 .W,
      inserter 6 1 .W,
      inserter 6 2 .W,

      inserter 3 3 .N,
      inserter 4 3 .N,
      inserter 5 3 .N,
      pole 6 3,

      belt 3 4 .E,
      belt 4 4 .E,
      belt 5 4 .E,
      belt 6 4 .E
    ] ++
    beltline (x:=7) .S height

  {
    wires := []
    width:= 8, height:=height, entities := entities
    interface := {
      n := #v[0,1,7]
      e := #v[]
      s := #v[0,1,7]
      w := #v[]
    }
    name := "nutrientsFromBioflux"
  }

-- Special case, needs two output inserters to keep up with the production rate.
def railStation : Station (recipe .rail) :=
  let factory := (pipesOnSideStation (recipe .rail)).expand .S 1
  let removedLeftPole := eraseRectangle 2 2 1 1 factory.entities
  let removedPoles := eraseRectangle 6 2 1 1 removedLeftPole
  {factory with
    entities := removedPoles.append [
      longInserter 6 2 .W,
      pole 4 3
    ]
  }

-- Special case, needs more powerpoles to covert the huge size of the building
def rocketPart : Station (recipe .rocketPart) :=
  let factory := pipesOnSideStation (recipe .rocketPart)
  {factory with
    entities := factory.entities.append [
      pole 1 3, pole 11 3,
    ]
  }

def acccessPipe (x:Nat) (ingredient:Ingredient): Factory [] [] [] [(ingredient, .E)] := {
  width := 4, height := 1
  wires := []
  entities := [pipe x 0, pipeToGround (x-1) 0 .E]
  name := "accessPipe"
  interface := {n := #v[], e := #v[], s := #v[], w := #v[0]}
}

-- Special case, because the plant's pipes come out in weird spots
def electrolyteStation : Station (recipe .electrolyte) :=
  {
    wires := []
    width := 13, height := 6,
    name := ".electrolyte"
    interface := {n := #v[1,3,5,11], e := #v[], s := #v[1,3,5,11], w := #v[]}
    entities :=
      pipeline 1 6 ++
      pipeline 3 6 ++
      beltline 5 .N 6 ++
      [
        pipeToGround 2 0 .W, pipeToGround 7 0 .E, pipe 8 0,
        pipeToGround 4 5 .W, pipeToGround 8 5 .E, pipe 9 5,
        inserter 6 1 .W, pole 6 4,
        fabricator 7 1 .electromagneticPlant .electrolyte .E,
      ] ++
      pipeline 11 6
  }

-- Special case, because the plant's pipes come out in weird spots
def electromagneticScienceStation : Station (recipe .electromagneticSciencePack) :=
  {
    wires := []
    width := 14, height := 6,
    name := ".electromagneticSciencePack"
    interface := {n := #v[1,3,5,6,13], e := #v[], s := #v[1,3,5,6,13], w := #v[]}
    entities :=
      pipeline 1 6 ++
      pipeline 3 6 ++
      beltline 5 .N 6 ++
      beltline 6 .N 6 ++
      [
        pipeToGround 2 0 .W, pipeToGround 8 0 .E, pipe 9 0,
        pipeToGround 4 5 .W, pipeToGround 9 5 .E, pipe 10 5,
        inserter 7 1 .W, longInserter 7 2 .W, pole 7 4,
        fabricator 8 1 .electromagneticPlant .electromagneticSciencePack .E,
        inserter 12 1 .W, pole 12 4,
      ] ++
      beltline 13 .S 6
  }

-- Special case, because of 4 solid inputs
private def supercapacitorStation : Station (recipe .supercapacitor) :=
  let height := 4
  let entities : List Entity :=
    pipeline (x:=1) height ++
    beltline (x:=3) .N height ++
    beltline (x:=4) .N height ++
    [
      beltUp 5 0 .N,
      longInserter 5 1 .W,
      beltDown 5 2 .N,
      belt 5 3 .N,

      inserter 6 0 .W,
      longInserter 6 1 .W,
      pipeToGround 2 2 .W, pipeToGround 6 2 .E,
      pole 6 3,

      fabricator 7 0 .electromagneticPlant .supercapacitor,

      longInserter 11 0 .W,
      inserter 11 1 .E,
      pole 11 3,
    ] ++
    beltline (x:=12) .N height ++
    beltline (x:=13) .S height

  {
    wires := []
    width:= 14, height:=height, entities := entities
    interface := {
      n := #v[1,3,4,5,12,13]
      e := #v[]
      s := #v[1,3,4,5,12,13]
      w := #v[]
    }
    name := ".supercapacitor"
  }

def station (process:Process) : Station process :=
  match process.recipe with
  | .flyingRobotFrame => unsafeFactoryCast flyingRobotFrameStation
  | .electrolyte => unsafeFactoryCast electrolyteStation
  | .electromagneticSciencePack => unsafeFactoryCast electromagneticScienceStation
  | .supercapacitor => unsafeFactoryCast supercapacitorStation
  | .rail => unsafeFactoryCast railStation
  | .rocketPart => unsafeFactoryCast rocketPart
  | .nutrientsFromBioflux => unsafeFactoryCast nutrientsFromBiofluxStation
  | _ => stationWithoutOverride process


================================================
FILE: Functorio/AssemblyStationTest.lean
================================================
import Functorio.AssemblyStation
import Functorio.Ascii

#guard (station (recipe .ironPlate)).toAscii == s!"
 ^     v
 ↑⇨***⇨↓
 ↑ *F* ↓
 ↑⚡***⚡↓
 ^     v
"

#guard (station (recipe .electronicCircuit)).toAscii == s!"
 ^     ^v
 ↑⇨***⇦↑↓
 ↑ *A*↠↑↓
 ↑⚡***⚡↑↓
 ^     ^v
"

#guard (station (recipe .advancedCircuit)).toAscii == s!"
 ^^     ^v
 ↑↑⇨***⇦↑↓
 ↑↑↠*A*↠↑↓
 ↑↑⚡***⚡↑↓
 ^^     ^v
"

#guard (station (recipe .superconductor)).toAscii == s!"
  ^ ^^      ^v
  | ↑↑⇨****⇦↑↓
  | ↑↑↠****↠↑↓
  |┤↑↑├**E* ↑↓
  | ↑↑⚡****⚡↑↓
  ^ ^^      ^v
"

#guard (station { recipe := .accumulator, fabricator := .electromagneticPlant }).toAscii == s!"
 ^      ^v
 ↑⇨****⇦↑↓
 ↑ ****↠↑↓
 ↑ **E* ↑↓
 ↑⚡****⚡↑↓
 ^      ^v
"

#guard (station (recipe .supercapacitor)).toAscii == s!"
  ^ ^^^      ^v
  | ↑↑↥⇨****↠↑↓
  | ↑↑↠↠****⇦↑↓
  |┤↑↑⤒├**E* ↑↓
  | ↑↑↑⚡****⚡↑↓
  ^ ^^^      ^v
"

#guard (station (recipe .electromagneticSciencePack)).toAscii == s!"
  ^ ^ ^^      v
  |┤| ↑↑ ├|   ↓
  | | ↑↑⇨****⇨↓
  | | ↑↑↠**** ↓
  | | ↑↑ **E* ↓
  | | ↑↑⚡****⚡↓
  | |┤↑↑  ├|  ↓
  ^ ^ ^^      v
"

#guard (station (recipe .electrolyte)).toAscii == s!"
  ^ ^ ^     v
  |┤| ↑ ├|  |
  | | ↑⇨****|
  | | ↑ ****|
  | | ↑ **E*|
  | | ↑⚡****|
  | |┤↑  ├| |
  ^ ^ ^     v
"

#guard (station (recipe .flyingRobotFrame)).toAscii == s!"
 ^^^     ^v
 ↑↑↥↠***↠↑↓
 ↑↑↠⚡*A*⚡↑↓
 ↑↑⤒⇨***⇦↑↓
 ^^^     ^v
"

#guard (station (recipe .rail)).toAscii == s!"
 ^^     ^v
 ↑↑⇨***⇦↑↓
 ↑↑↠*A*↠↑↓
 ↑↑ ***↠↑↓
 ↑↑  ⚡  ↑↓
 ^^     ^v
"

#guard (station (recipe .processingUnit)).toAscii == s!"
  ^ ^     ^v
  | ↑⇨***⇦↑↓
  |┤↑├*A*↠↑↓
  | ↑⚡***⚡↑↓
  ^ ^     ^v
"

#guard (station (recipe .battery)).toAscii == s!"
  ^ ^     ^v
  |┤↑├***⇦↑↓
  | ↑⇨*C*↠↑↓
  | ↑⚡***⚡↑↓
  ^ ^     ^v
"

#guard (station (recipe .sulfuricAcid)).toAscii == s!"
  ^ ^     ^ v
  |┤↑├***┤↑├|
  | ↑⇨*C*⇦↑ |
  | ↑⚡***⚡↑ |
  ^ ^     ^ v
"

#guard (station (recipe .sulfur)).toAscii == s!"
  ^ ^     v
  |┤|├***⇨↓
  | | *C* ↓
  | ||***⚡↓
  ^ ^     v
"

#guard (station (recipe .solidFuelFromLightOil)).toAscii == s!"
  ^     v
  ||***⇨↓
  | *C* ↓
  | ***⚡↓
  ^     v
"

#guard (station (recipe .rocketFuel)).toAscii == s!"
  ^ ^     v
  | ↑⇨***⇨↓
  |┤↑├*A* ↓
  | ↑⚡***⚡↓
  ^ ^     v
"

#guard (station (recipe .heavyOilCracking)).toAscii == s!"
  ^ ^     v
  |┤|├***||
  | |⚡*C* |
  | ||*** |
  ^ ^     v
"

#guard (station (recipe .advancedOilProcessing)).toAscii == s!"
  ^ ^       v v v
  | | *****|| | |
  |┤|├***** | | |
  | |⚡**O**┤|├| |
  | ||***** | | |
  | | *****┤| |├|
  ^ ^       v v v
"

#guard (station (recipe .rocketPart)).toAscii == s!"
 ^           ^^
 ↑⇨*********⇦↑↑
 ↑ *********↞↑↑
 ↑ ********* ↑↑
 ↑⚡*********⚡↑↑
 ↑ ****L**** ↑↑
 ↑ ********* ↑↑
 ↑ ********* ↑↑
 ↑ ********* ↑↑
 ↑⚡*********⚡↑↑
 ^           ^^
"

#guard (station (recipe .pentapodEgg)).toAscii == s!"
  ^ ^     ^v
  |┤↑├***⇦↑↓
  | ↑⇨*B*↠↑↓
  | ↑⚡***⚡↑↓
  ^ ^     ^v
"

#guard (station (recipe .nutrientsFromBioflux)).toAscii == s!"
 ^^     v
 ↑↑⇨***⇨↓
 ↑↑↠*B*⇨↓
 ↑↑↠***⇨↓
 ↑↑⚡⇩⇩⇩⚡↓
 ↑↑ →→→→↓
 ^^     v
"


================================================
FILE: Functorio/Blueprint.lean
================================================
import Lean.Data.Json
import Lean.Data.Json.FromToJson

import Functorio.Entity
import Functorio.Factory

open Lean
open Lean (Json)

def coppperWire := 5

private def wireTypeNumber (type:WireType) : Nat :=
  match type with
  | .redInput => 1
  | .greenInput => 2
  | .redOutput => 3
  | .greenOutput => 4
  | .copper => coppperWire

private structure BlueprintInner where
  entities: List Json
  tiles: List Json
  wires : List (List Nat)
  item: String
  version: Nat
  deriving ToJson

private structure Blueprint where
  blueprint: BlueprintInner
  deriving ToJson

private structure Position where
  x : Float
  y : Float
  deriving ToJson, Repr

private def entityName (e:Entity) : String :=
  match e.type with
  | .belt _ _ => "express-transport-belt"
  | .beltDown _ | .beltUp _ => "express-underground-belt"
  | .splitter _ _ _ => "express-splitter"
  | .pipe => "pipe"
  | .pipeToGround _ => "pipe-to-ground"
  | .pump _ => "pump"
  | .inserter _ _ => "bulk-inserter"
  | .longInserter _ _ => "long-handed-inserter"
  | .pole => "medium-electric-pole"
  | .bigPole => "big-electric-pole"
  | .roboport => "roboport"
  | .ironChest => "iron-chest"
  | .passiveProviderChest _ => "passive-provider-chest"
  | .refinedConcrete => "refined-concrete"
  | .heatingTower => "heating-tower"
  | .deciderCombinator _ _ _ => "decider-combinator"
  | .arithmeticCombinator _ _ => "arithmetic-combinator"
  | .fabricator f _ _ _ => f.name

private def entityDirection (e:Entity) : Option Direction :=
  match e.type with
  | .belt d _ | .beltDown d | .beltUp d | .splitter d _ _
  | .pipeToGround d | .pump d | .inserter d _ | .longInserter d _
  | .fabricator _ _ d _ | .deciderCombinator d _ _ | .arithmeticCombinator d _ => d
  | .pipe | .pole | .bigPole | .roboport | .ironChest | .passiveProviderChest _
  | .heatingTower | .refinedConcrete => .none

private def signalToJson (s:Signal) : Json :=
  Json.mkObj ([
      ("name", Json.str s.name)
    ] ++
    (s.type.map ("type", Json.str .)).toList)

private def conditionToJson (c:Condition) : Json :=
  Json.mkObj ([
      ("first_signal", signalToJson c.firstSignal),
      ("comparator", c.comparator)
    ] ++
    (c.constantValue.map ("constant", Json.num .)).toList ++
    (c.secondSignal.map ("second_signal", signalToJson .)).toList)

private def outputToJson (o:Output) : Json :=
  Json.mkObj [
    ("signal", signalToJson o.signal),
    ("copy_count_from_input", o.copyCountFromInput)
  ]

private def arithmeticConditionToJson (c:ArithmeticCondition) : Json :=
  Json.mkObj [
    ("first_signal", signalToJson c.firstSignal),
    ("second_constant", c.secondConstant),
    ("operation", c.operation),
    ("output_signal", signalToJson c.outputSignal)
  ]

private def entityProps (e:Entity) : List (String × Json) :=
  match e.type with
  | .pipe | .pipeToGround _ | .pump _
  | .pole | .bigPole | .roboport | .ironChest | .heatingTower | .refinedConcrete => []
  | .inserter _ filter | .longInserter _ filter => [
    ("use_filters", !filter.isEmpty),
    ("filters", Json.arr (filter.mapIdx (fun i ingredient =>
      Json.mkObj [
        ("index", s!"{i + 1}"),
        ("name", ingredient.name),
        ("quality", "normal"),
        ("comparator", "="),
      ]
    )).toArray)
  ]
  | .deciderCombinator _ conditions outputs => [
    ("control_behavior", Json.mkObj [
      ("decider_conditions", Json.mkObj [
        ("conditions", Json.arr (conditions.map conditionToJson).toArray),
        ("outputs", Json.arr (outputs.map outputToJson).toArray),
      ])
    ])
  ]
  | .arithmeticCombinator _ condition => [
    ("control_behavior", Json.mkObj [
      ("arithmetic_conditions", arithmeticConditionToJson condition)
    ])
  ]
  | .belt _ behavior => [
    ("control_behavior",
      if behavior.circuitCondition.isNone
      then Json.mkObj [
        ("circuit_enabled", false),
        ("circuit_read_hand_contents", behavior.circuitReadHandContents),
        ("circuit_contents_read_mode", behavior.circuitContentsReadMode)
      ]
      else Json.mkObj [
        ("circuit_enabled", true),
        ("circuit_condition", conditionToJson behavior.circuitCondition.get!),
        ("circuit_read_hand_contents", behavior.circuitReadHandContents),
        ("circuit_contents_read_mode", behavior.circuitContentsReadMode)
      ]
    ),
  ]
  | .beltDown _ => [("type", "input")]
  | .beltUp _ => [("type", "output")]
  | .passiveProviderChest capacity => match capacity with | .none => [] | .some capacity => [("bar", capacity)]
  | .splitter _ priority filter =>
    match priority with | .none => [] | .some p => [("output_priority", Json.str p)] ++
    match filter with | .none => [] | .some f => [
      ("filter", Json.mkObj [
        ("name", f.name),
        ("quality", "normal"),
        ("comparator", "=")
      ])
    ]
  | .fabricator _ r _ m => [("recipe", r.getRecipe.name), ("recipe_quality", "normal"), ("mirror", m)]

private def directionToNat (d:Direction) :=
  match d with
  | .N => 0
  | .E => 4
  | .S => 8
  | .W => 12

private def entityToJson (id:Nat) (e:Entity) : Json :=
  let dir := entityDirection e
  let p : Position := {
    x := Float.ofInt e.x + e.width.toFloat/2,
    y := Float.ofInt e.y + e.height.toFloat/2
  }

  Json.mkObj ([
    ([
      ("name", entityName e),
      ("position", ToJson.toJson p),
      ("entity_number", id),
    ] : List (String × Json)),
    match dir with | .none => [] | .some dir => [ ("direction", directionToNat dir) ],
    entityProps e
  ].flatten)


private def tileToJson (e:Entity) : Json :=
  let p : Position := {
    x := Float.ofInt e.x
    y := Float.ofInt e.y
  }

  Json.mkObj [
    ("name", entityName e),
    ("position", ToJson.toJson p),
  ]

def isTile (e:Entity) : Bool :=
  match e.type with
  | .refinedConcrete => true
  | _ => false

private def distance (a b : Entity) : Nat :=
  ((Int.ofNat a.x) - (Int.ofNat b.x)).natAbs +
  ((Int.ofNat a.y) - (Int.ofNat b.y)).natAbs

namespace Factory

def withinDistance (a b:Entity) :=
  let d := distance a b
  d <= 9 || (a.type == .bigPole && b.type == .bigPole && d <= 30)

def neededForBootStrap (e:Entity) : Bool :=
  match e.type with
  | .pole | .bigPole | .roboport => true
  | _ => false

def wireToJson (wire:Wire) : List Nat :=
  [wire.src, wireTypeNumber wire.srcType, wire.dst, wireTypeNumber wire.dstType]

def toBlueprint {n e s w} (factory:Factory n e s w) (bootstrap := false) : String :=
  let entities := (factory.entities.filter (fun e =>
    !isTile e && (!bootstrap || neededForBootStrap e))).zipIdx
  let tiles := factory.entities.filter isTile
  let poles := entities.filter (fun (e,_) => e.type == .pole || e.type == .bigPole)
  let wires := poles.flatMap (fun (a,aIdx) => poles.flatMap (fun (b,bIdx) =>
    if aIdx < bIdx && withinDistance a b then [[
      aIdx, coppperWire, bIdx, coppperWire
    ]] else []
  ))

  let blueprint : Blueprint := {
    blueprint := {
      entities:=entities.map (fun (e,idx) => entityToJson idx e),
      tiles:=tiles.map tileToJson,
      wires:= wires ++ factory.wires.map wireToJson,
      item:="blueprint",
      version:= 562949957025792
    }
  }
  Lean.Json.pretty (ToJson.toJson blueprint)

end Factory


================================================
FILE: Functorio/Bus.lean
================================================
import Functorio.Entity
import Functorio.Factory
import Functorio.Column
import Functorio.Row
import Functorio.Fraction
import Functorio.Util

structure LaneConfig where
  ingredient: Ingredient
  refCount : Nat
  deriving Inhabited, Repr

def depletedLane : LaneConfig := {
  ingredient := default, refCount := 0
}

namespace LaneConfig

def depleted (l:LaneConfig) : Bool :=
  l.refCount == 0

end LaneConfig

abbrev LaneConfigs := List LaneConfig

namespace LaneConfigs

def height (lanes:LaneConfigs) : Nat :=
  lanes.length

def useLane (index:Nat) (lanes:LaneConfigs) : LaneConfigs :=
  lanes.modify index fun lane =>
    if lane.refCount == 0 then error! s!"Accessing depleted lane {index}" else
    {
      refCount := lane.refCount - 1
      ingredient := if lane.refCount <= 1 then default else lane.ingredient
    }

def allocLane (ingredient:Ingredient) (lanes:LaneConfigs) (skip : Nat:=0): (Nat × LaneConfigs) := Id.run do
  let newLane := {ingredient := ingredient, refCount := 1}
  let mut skip := skip

  for (lane, i) in lanes.zipIdx do
    if i == 0 then continue   -- Don't alloc the 0th lane, we need the space for exits

    if lane.depleted then
      -- We found an empty lane!

      if ingredient.isLiquid then
        -- Don't alloc if the previous or next lane is already a pipe
        if i > 1 && lanes[i - 1]!.ingredient.isLiquid then continue
        if i < lanes.length - 1 && lanes[i + 1]!.ingredient.isLiquid then continue

      if skip > 0 then
        skip := skip - 1
        continue

      return (i, lanes.set i newLane)

  -- Couldn't find a depleted lane, so we need to alloc a new one
  if ingredient.isLiquid && skip == 0 && lanes.length > 0 && lanes[lanes.length - 1]!.ingredient.isLiquid then
    -- Padding between pipes
    (lanes.length + 1, lanes ++ [depletedLane, newLane])
  else
    let newLanes := lanes ++ List.replicate skip depletedLane ++  [newLane]
    (newLanes.length - 1, newLanes)

def available (lanes:LaneConfigs): List (LaneConfig × Nat) :=
  lanes.zipIdx.filter fun (lane, _) => !lane.depleted

end LaneConfigs

abbrev Throughput := Fraction -- items per minute

structure BusLane (_ : Ingredient) (throughput:Throughput) where
  index: Nat
  deriving Inhabited, Repr

structure BusLane' where
  ingredient:Ingredient
  throughput:Throughput
  index: Nat
  deriving Inhabited, Repr

namespace BusLane

def less {i m n} (l:BusLane i m) (_: n≤m := by decide) : BusLane i n :=
  {index := l.index}

-- TODO: why is this needed?
def exact {i n m} (l:BusLane i m) (_: n=m := by decide): BusLane i n :=
  {index := l.index}

def toBusLane' {i n} (l:BusLane i n) : BusLane' :=
  {ingredient := i, throughput := n, index := l.index }

end BusLane

instance {i n} : CoeOut (BusLane i n) BusLane' where
  coe l := l.toBusLane'

@[simp]
def busInterface (lanes:LaneConfigs) : List InterfaceH :=
  lanes.available.map fun (lane, _) => (lane.ingredient, .E)

def busInterfaceImpl (lanes:LaneConfigs) : Vector InterfaceImpl (busInterface lanes).length :=
  cast (by simp) (lanes.available.toVector.map Prod.snd)

structure BusState where
  input: LaneConfigs
  output: LaneConfigs
  factory : Factory [] (busInterface output) [] (busInterface input)
  deriving Inhabited, Repr

private def emptyBusState : BusState := {
  input := [depletedLane]
  output := [depletedLane]
  factory := emptyFactoryV.setName "emptyBus"
}

abbrev Bus T := StateM BusState T

private abbrev BusFactory (bus:Bus Unit) : Type :=
  Factory [] (busInterface (bus.run emptyBusState).snd.output) [] (busInterface (bus emptyBusState).snd.input)

def bus (b:Bus Unit) : BusFactory b :=
  (b emptyBusState).snd.factory

def input (ingredient:Ingredient) (throughput:Fraction) : Bus (BusLane ingredient throughput) :=
  fun state =>
    if (state.factory.width != 0) then error! s!"input must be called at beginning; here bus is already {state.factory.width} tiles wide" else
      let (index, newConfig) := state.output.allocLane ingredient
      ({index:=index}, {
      input := newConfig
      output := newConfig
      factory := (emptyFactoryV (busInterfaceImpl newConfig))
    })

def inputs (n:Nat) (ingredient:Ingredient) (throughput:Fraction) : Bus (Vector (BusLane ingredient throughput) n) := do
  let mut lanes : Array (BusLane ingredient throughput) := #[]
  for _ in List.range n do
    let lane <- input ingredient throughput
    lanes := lanes.push lane
  return lanes.toList.castToVector!

inductive Cell where
| entity (type:EntityType)
| blocked    -- blocked by another entity that's larger than 1 cell
| empty
deriving Repr, DecidableEq, Inhabited

private inductive AccessType
| put
| get
deriving Inhabited, DecidableEq, Repr

abbrev Matrix w h := Vector (Vector Cell h) w

namespace Matrix

def modifyCell {w h} (matrix:Matrix w h) (x y : Nat) (cell:Cell -> Cell) : Matrix w h :=
  matrix.modify x fun column => column.modify y cell

def setCell {w h} (matrix:Matrix w h) (x y : Nat) (cell:Cell) : Matrix w h :=
  modifyCell matrix x y fun _ => cell

def canApplyEntities {w h} (matrix:Matrix w h) (entities:Option (List Entity)) : Bool :=
  match entities with
  | .none => false | .some entities =>
    entities.all fun e =>
      if e.x >= w then error! s!"Bus is wider than the maximum supported {e.x}" else
      if e.y >= h then error! s!"Bus is taller than the maximum supported {e.y}" else

      match matrix[e.x]![e.y]!, e.type with
      -- Belt/pipe down followed by up cancels out to just a straight belt/pipe, so overriding is fine.
      | .entity (.beltDown .E), .beltUp .E => true
      | .entity (.pipeToGround .W), .pipeToGround .E => true
      -- For splitters, the box below also has to be free.
      | .empty, .splitter .E .none .none =>
        matrix[e.x]![e.y + 1]! == .empty
      | .empty, _ => true
      | _,_ => false

def applyEntities {w h} (matrix:Matrix w h) (entities:Option (List Entity)) : Matrix w h := Id.run do
  match entities with
  | .none => error! s!"Trying to apply invalid entities {reprStr entities}"
  | .some entities =>

    let mut matrix := matrix

    for e in entities do
      if e.type ==  .splitter .E .none .none then
        matrix := matrix.setCell e.x e.y (.entity e.type)
        matrix := matrix.setCell e.x (e.y + 1) .blocked
      else
        matrix := matrix.modifyCell e.x e.y fun cell =>
          match cell, e.type with
          -- Belt/pipe down followed by up cancels out to just a straight belt/pipe.
          | .entity (.beltDown .E), .beltUp .E => .entity (.belt .E)
          | .entity (.pipeToGround .W), .pipeToGround .E => .entity .pipe
          | .empty,_ => .entity e.type
          | _,_ => error! s!"Trying to override entity {reprStr cell} with {reprStr e}"

    return matrix

def toEntities {w h} (matrix : Matrix w h) : List Entity := Id.run do
  let mut entities : Array Entity := #[]

  for (column, x) in matrix.zipIdx do
    for (cell, y) in column.zipIdx do
      match cell with
      | .entity type =>
        entities := entities.push {x:=x, y:=y, type:=type}
      | _ =>
        pure ()

  return entities.toList

def reduceUndergroundEntities {w h} (matrix:Matrix w h) : Matrix w h := Id.run do
  let mut matrix := matrix
  let width := w
  let height := h

  -- Go through all the columns
  for x in List.range matrix.size do
    let mut isUndergroundPipe := false
    let mut isUndergroundBeltN := false
    let mut isUndergroundBeltS := false

    for y in List.range height do
      if y + 1 >= height then continue

      let cell := matrix[x]![y]!
      let nextCell := matrix[x]![y+1]!

      -- Simplify bus belt gets
      if cell == .entity (.beltUp .N) && nextCell == .empty then
        matrix := matrix.setCell x y (.entity (.belt .N))
        matrix := matrix.setCell x (y+1) (.entity (.beltUp .N))

      if cell == .entity (.beltUp .N) && nextCell == .entity (.beltDown .N) then
        matrix := matrix.setCell x y (.entity (.belt .N))
        matrix := matrix.setCell x (y+1) (.entity (.belt .N))

      if isUndergroundBeltN && cell == .empty && nextCell == .empty then
        matrix := matrix.setCell x y (.entity (.beltDown .N))
        matrix := matrix.setCell x (y+1) (.entity (.beltUp .N))

      if isUndergroundBeltN && cell == .empty && nextCell == .entity (.beltDown .N) then
        matrix := matrix.setCell x y (.entity (.beltDown .N))
        matrix := matrix.setCell x (y+1) (.entity (.belt .N))

      -- Simplify bus belt puts
      if cell == .entity (.beltDown .S) && nextCell == .empty then
        matrix := matrix.setCell x y (.entity (.belt .S))
        matrix := matrix.setCell x (y+1) (.entity (.beltDown .S))

      if cell == .entity (.beltDown .S) && nextCell == .entity (.beltUp .S) then
        matrix := matrix.setCell x y (.entity (.belt .S))
        matrix := matrix.setCell x (y+1) (.entity (.belt .S))

      if isUndergroundBeltS && cell == .empty && nextCell == .empty then
        matrix := matrix.setCell x y (.entity (.beltUp .S))
        matrix := matrix.setCell x (y+1) (.entity (.beltDown .S))

      if isUndergroundBeltS && cell == .empty && nextCell == .entity (.beltUp .N) then
        matrix := matrix.setCell x y (.entity (.beltUp .S))
        matrix := matrix.setCell x (y+1) (.entity (.belt .S))

      -- Simplify pipe put/get
      if cell == .entity (.pipeToGround .N) && nextCell == .empty then
        matrix := matrix.setCell x y (.entity .pipe)
        matrix := matrix.setCell x (y+1) (.entity (.pipeToGround .N))

      if cell == .entity (.pipeToGround .N) && nextCell == .entity (.pipeToGround .S) then
        matrix := matrix.setCell x y (.entity .pipe)
        matrix := matrix.setCell x (y+1) (.entity .pipe)

      if isUndergroundPipe && cell == .empty && nextCell == .empty then
        matrix := matrix.setCell x y (.entity (.pipeToGround .S))
        matrix := matrix.setCell x (y+1) (.entity (.pipeToGround .N))

      if isUndergroundPipe && cell == .empty && nextCell == .entity (.pipeToGround .S) then
        matrix := matrix.setCell x y (.entity (.pipeToGround .S))
        matrix := matrix.setCell x (y+1) (.entity .pipe)

      -- Determine whether we are underground
      let cell := matrix[x]![y]!
      if cell == .entity (.beltUp .N) then isUndergroundBeltN := true  -- yes, beltUp means y + 1 is underground
      if cell == .entity (.beltDown .N) then isUndergroundBeltN := false
      if cell == .entity (.beltUp .S) then isUndergroundBeltS := false
      if cell == .entity (.beltDown .S) then isUndergroundBeltS := true
      if cell == .entity (.pipeToGround .N) then isUndergroundPipe := true
      if cell == .entity (.pipeToGround .S) then isUndergroundPipe := false

  -- Go through all the rows
  for y in List.range height do
    let mut isUndergroundPipe := false
    let mut isUndergroundBelt := false

    for x in List.range width do
      if x + 1 >= width then continue

      let cell := matrix[x]![y]!
      let nextCell := matrix[x+1]![y]!

      -- Simplify belt lanes
      if cell == .entity (.beltDown .E) && nextCell == .empty then
        matrix := matrix.setCell x y (.entity (.belt .E))
        matrix := matrix.setCell (x+1) y (.entity (.beltDown .E))

      if cell == .entity (.beltDown .E) && nextCell == .entity (.beltUp .E) then
        matrix := matrix.setCell x y (.entity (.belt .E))
        matrix := matrix.setCell (x+1) y (.entity (.belt .E))

      if isUndergroundBelt && cell == .empty && nextCell == .empty then
        matrix := matrix.setCell x y (.entity (.beltUp .E))
        matrix := matrix.setCell (x+1) y (.entity (.beltDown .E))

      if isUndergroundBelt && cell == .empty && nextCell == .entity (.beltUp .E)  then
        matrix := matrix.setCell x y (.entity (.beltUp .E))
        matrix := matrix.setCell (x+1) y (.entity (.belt .E))

      -- Simplify pipe lanes
      if cell == .entity (.pipeToGround .W) && nextCell == .empty then
        matrix := matrix.setCell x y (.entity (.pipe))
        matrix := matrix.setCell (x+1) y (.entity (.pipeToGround .W))

      if cell == .entity (.pipeToGround .W) && nextCell == .entity (.pipeToGround .E) then
        matrix := matrix.setCell x y (.entity .pipe)
        matrix := matrix.setCell (x+1) y (.entity .pipe)

      if isUndergroundPipe && cell == .empty && nextCell == .empty then
        matrix := matrix.setCell x y (.entity (.pipeToGround .E))
        matrix := matrix.setCell (x+1) y (.entity (.pipeToGround .W))

      if isUndergroundPipe && cell == .empty && nextCell == .entity (.pipeToGround .E) then
        matrix := matrix.setCell x y (.entity (.pipeToGround .E))
        matrix := matrix.setCell (x+1) y (.entity .pipe)

      -- Determine whether we are underground
      let cell := matrix[x]![y]!
      if cell == .entity (.beltDown .E) then isUndergroundBelt := true
      if cell == .entity (.beltUp .E) then isUndergroundBelt := false
      if cell == .entity (.pipeToGround .W) then isUndergroundPipe := true
      if cell == .entity (.pipeToGround .E) then isUndergroundPipe := false

  return matrix

end Matrix

def busTapInterface (inputs: List BusLane') (outputIngredients: List Ingredient) : List InterfaceV :=
  (inputs.map fun input => (input.ingredient, .N)) ++
  (outputIngredients.map fun output => (output, .S))

def pipeAccess (type:AccessType) (split:Bool) (x y:Nat) : List Entity :=
  let ramp :=
    match y with
    | 0 => error! s!"0th lane should never be accessed {y}"
    | 1 => [pipe x 0]
    | _ => [pipeToGround x 0 .N, pipeToGround x (y-1) .S]

  let lane :=
    match type with
    | .put => [pipe x y, pipeToGround (x+1) y .W]
    | .get =>
      if split then [pipeToGround (x-1) y .E, pipe x y, pipeToGround (x+1) y .W]
      else [pipeToGround (x-1) y .E, pipe x y]

  ramp ++ lane

def beltAccessGetWithSplit (x y:Nat) : Option (List Entity) :=
  -- We don't have enough room to access a belt with a splitter if x < 2
  if x < 2 then .none else

  let ramp :=
    match y with
    | 0 => error! s!"0th lane should never be accessed {y}"
    | 1 => []
    | 2 => [belt x 0 .N]
    | _ => [beltDown x (y-2) .N, beltUp x 0 .N]

  .some (ramp ++ [
    beltUp (x-2) y .E,
    splitter (x-1) (y-1) .E,
    beltDown x y .E,
    belt x (y-1) .N,
  ])

def beltAccessGetWithoutSplit (x y:Nat) : List Entity :=
  let lane :=
    match x with
    | 0 => []
    | _ => [beltUp (x-1) y .E]

  let ramp :=
    match y with
    | 0 => error! s!"0th lane should never be accessed {y}"
    | 1 => [belt x 0 .N]
    | _ => [beltDown x (y-1) .N, beltUp x 0 .N]

  lane ++ [belt x y .N] ++ ramp

def beltAccessPut (x y:Nat) : List Entity :=
  let ramp :=
    match y with
    | 0 => error! s!"0th lane should never be allocated {y}"
    | 1 => [belt x 0 .S]
    | _ => [beltUp x (y-1) .S, beltDown x 0 .S]

  [belt x y .E, beltDown (x+1) y .E] ++ ramp

def laneAccess (type:AccessType) (config:LaneConfigs) (ingredient:Ingredient) (yIndex:Nat) (x:Nat) : Option (List Entity) :=
  let split := config[yIndex]!.refCount > 0

  if ingredient.isLiquid
  then pipeAccess type split x yIndex
  else
    match type with
    | .get =>
      if split
      then beltAccessGetWithSplit x yIndex
      else beltAccessGetWithoutSplit x yIndex
    | .put =>
      beltAccessPut x yIndex

def bringDownAllLanes {w h} (lanes:LaneConfigs) (x:Nat) (matrix:Matrix w h) : Nat × Matrix w h := Id.run do
  let mut x := x
  let mut matrix := matrix

  let entities x :=
    lanes.available.map fun (lane, y) =>
      if lane.ingredient.isLiquid then pipeToGround x y .W else beltDown x y .E
  matrix := matrix.applyEntities (entities x)

  return (x+1, matrix)

def bringUpAllLanes {w h} (lanes:LaneConfigs) (x:Nat) (matrix:Matrix w h) : Nat × Matrix w h := Id.run do
  let mut x := x
  let mut matrix := matrix

  let entities x :=
    lanes.available.map fun (lane, y) =>
      if lane.ingredient.isLiquid then pipeToGround x y .E else beltUp x y .E
  while !matrix.canApplyEntities (entities x) do x := x + 1
  matrix := matrix.applyEntities (entities x)

  return (x+1, matrix)

def busTapGeneric
  (inputs:List BusLane')
  (outputs:List Ingredient)
  (factory:Factory [] [] (busTapInterface inputs outputs) [])
  (adapterMinHeight:=0)
: Bus (List Nat) := fun state => Id.run do
  -- Worst case, all outputs are pipes so we add 2 that
  let height := state.output.height + outputs.length * 2
  -- Longer than that, and we'll probably have problems
  -- with underground pipes running out of length anyway.
  let width := 15

  let mut matrix : Matrix width height := Vector.replicate width (Vector.replicate height .empty)
  let mut offsets : Array Nat := #[]
  let mut lanes := state.output
  let mut previousWasLiquid := false
  let mut x := 0

  (x, matrix) := bringDownAllLanes lanes x matrix

  -- Handle all inputs
  for input in inputs do
    lanes := lanes.useLane input.index

    if previousWasLiquid && input.ingredient.isLiquid then x := x + 1  -- Gap between pipes so they don't connect.
    let entities x := laneAccess .get lanes input.ingredient input.index x
    while !matrix.canApplyEntities (entities x) do x := x + 1
    matrix := matrix.applyEntities (entities x)

    previousWasLiquid := input.ingredient.isLiquid
    offsets := offsets.push x
    x := x + 1

  -- Handle all outputs
  let mut outputLanes : Array Nat := #[]
  for (ingredient, i) in outputs.zipIdx do
    if previousWasLiquid && ingredient.isLiquid then x := x + 1  -- Gap between pipes so they don't connect.

    if x > 8 then
      (x, matrix) := bringUpAllLanes lanes x matrix
      (x, matrix) := bringDownAllLanes lanes x matrix

    let (index, newLanes) := lanes.allocLane ingredient (skip := outputs.length - 1 - i)
    lanes := newLanes

    let entities x := laneAccess .put lanes ingredient index x
    while !matrix.canApplyEntities (entities x) do x := x + 1
    matrix := matrix.applyEntities (entities x)

    previousWasLiquid := ingredient.isLiquid
    offsets := offsets.push x
    outputLanes := outputLanes.push index
    x := x + 1

  (x, matrix) := bringUpAllLanes lanes x matrix

  let tapFactory : Factory (busTapInterface inputs outputs) (busInterface lanes) [] (busInterface state.output) := {
    width:= x
    height:= max state.output.height lanes.height
    wires := []
    entities := matrix.reduceUndergroundEntities.toEntities
    interface := {
      n := offsets.toList.castToVector!
      e := busInterfaceImpl lanes
      s := #v[]
      w := busInterfaceImpl state.output
    }
    name := "tapBus'"
  }
  -- TODO: make adapterMinHeight part of Config
  let factoryAndTap := column factory tapFactory adapterMinHeight
  let busFactory := row state.factory factoryAndTap

  (outputLanes.toList, {
    input := state.input
    output := lanes
    factory := busFactory
  })

def busTapNoOutput
  (inputs:List BusLane')
  (factory:Factory [] [] (busTapInterface inputs []) [])
  (adapterMinHeight:=0)
: Bus Unit := do
  let _ <- busTapGeneric inputs [] factory adapterMinHeight

def busTap
  {outputIngredient} {outputThroughput} (inputs:List BusLane')
  (factory:Factory [] [] (busTapInterface inputs [outputIngredient]) [])
  (adapterMinHeight:=0)
: Bus (BusLane outputIngredient outputThroughput) := do
  let outputs <- busTapGeneric inputs [outputIngredient] factory adapterMinHeight
  return {index := outputs[0]!}

def busTap2
  {outputIngredient} {outputThroughput} {outputIngredient'} {outputThroughput'} (inputs:List BusLane')
  (factory:Factory [] [] (busTapInterface inputs [outputIngredient, outputIngredient']) [])
  (adapterMinHeight:=0)
: Bus (BusLane outputIngredient outputThroughput × BusLane outputIngredient' outputThroughput') := do
  let outputs <- busTapGeneric inputs [outputIngredient, outputIngredient'] factory adapterMinHeight
  return ({index := outputs[0]!}, {index := outputs[1]!})

def split {i left input} (l:BusLane i input) (right := input - left) (_:left + right = input := by decide) : Bus (BusLane i left × BusLane i right) :=
  fun state =>
    (({index:= l.index}, {index:=l.index}),
        {state with
          output :=
            state.output.modify l.index fun lane => {lane with
              refCount := lane.refCount + 1
            }
          factory := unsafeFactoryCast state.factory
        }
    )

@[simp]
def expressBeltThroughput : Throughput := 45 * 60  -- 2700

def mergeSolid {i a b} (l:BusLane i a) (l':BusLane i b) : Bus (BusLane i (a + b)) :=
  busTap [l.toBusLane',l'.toBusLane'] {
    entities:=[
      pole 0 0,
      belt 1 0 .E,
      belt 2 0 .S,
      belt 1 1 .N,
      beltDown 2 1 .S,
      belt 0 2 .E,
      belt 1 2 .N,
      belt 2 2 .W,
      belt 0 3 .N,
      belt 1 3 .E,
      belt 2 3 .N,
      splitter 0 4 .N (outputPriority:="left"),
      beltUp 2 4 .S,
    ],
    wires := []
    width:=3,
    height:=5,
    interface:={
      n := #v[]
      e := #v[]
      s := #v[0,1,2]
      w := #v[]
    }
    name := s!"merge {reprStr i}"
  }

def mergeLiquid {i a b} (l:BusLane i a) (l':BusLane i b) : Bus (BusLane i (a + b)) :=
  busTap [l.toBusLane',l'.toBusLane'] {
    entities:= (List.range 5).map (pipe . 0)
    width:=5,
    height:=1,
    wires := []
    interface:={
      n := #v[]
      e := #v[]
      s := #v[0,2,4]
      w := #v[]
    }
    name := s!"merge {reprStr i}"
  }

def merge {i a b} (l:BusLane i a) (l':BusLane i b) (_ : i.isLiquid || a+b ≤ expressBeltThroughput := by decide) : Bus (BusLane i (a + b)) :=
  if i.isLiquid then mergeLiquid l l' else mergeSolid l l'

def splitBalanced {i left input} (l:BusLane i input) (right := input - left) (h:left + right = input := by decide) : Bus (BusLane i left × BusLane i right) :=
  let inputSignal : Signal := {name:= i.name, type := .none}
  let leftSignal : Signal := {name:="signal-L", type:="virtual"}
  let rightSignal : Signal := {name:="signal-R", type:="virtual"}
  let enableSignal : Signal := {name:="signal-check", type:="virtual"}

  let counter x y :=
    deciderCombinator x y .N [
        {
          firstSignal := inputSignal
          secondSignal := .none
          constantValue := .some 0
          comparator:= "≥"
        }
      ] [
        {
          signal:= inputSignal
          copyCountFromInput:=true
        }
      ]

  let multiplier x y outputSignal c :=
    arithmeticCombinator x y .N
      {
        firstSignal:=inputSignal
        outputSignal := outputSignal
        secondConstant:=c
        operation:= "*"
      }

  if i.isLiquid then split l right h else

  let fraction := left / right

  busTap2 [l.toBusLane'] {
    width:=4,
    height:=8,
    wires := [
      -- Hookup left
      { src:= 0, dst:= 1, srcType:= .greenInput, dstType:= .greenInput},
      { src:= 1, dst:= 1, srcType:= .redOutput, dstType:= .redInput},
      { src:= 1, dst:= 2, srcType:= .greenOutput, dstType:= .greenInput},
      -- Hookup right
      { src:= 3, dst:= 4, srcType:= .greenInput, dstType:= .greenInput},
      { src:= 4, dst:= 4, srcType:= .redOutput, dstType:= .redInput},
      { src:= 4, dst:= 5, srcType:= .greenOutput, dstType:= .greenInput},
      -- Hookup combiner
      { src:= 2, dst:= 6, srcType:= .greenOutput, dstType:= .greenInput},
      { src:= 5, dst:= 6, srcType:= .greenOutput, dstType:= .greenInput},
      { src:= 6, dst:= 0, srcType:= .redOutput, dstType:= .redInput},
      { src:= 6, dst:= 3, srcType:= .redOutput, dstType:= .redInput},
    ]
    entities:= [
      -- Left logic
      belt 0 6 .N {
        circuitCondition := .some {
          firstSignal:= enableSignal, secondSignal:=.none, constantValue:=.some 1, comparator:="="
        }
        circuitReadHandContents := true
        circuitContentsReadMode := 0
      },
      counter 0 2,
      multiplier 0 0 leftSignal fraction.num,

      -- Right logic
      belt 1 6 .N {
        circuitCondition := .some {
          firstSignal:= enableSignal, secondSignal:=.none, constantValue:=.some 1, comparator:="≠"
        }
        circuitReadHandContents := true
        circuitContentsReadMode := 0
      },
      counter 1 2,
      multiplier 1 0 rightSignal fraction.den,

      -- Combine left and right
      deciderCombinator 2 2 .S [
        {
          firstSignal:= leftSignal
          secondSignal:= rightSignal
          constantValue:=.none
          comparator:= "<"
        }
      ] [
        {
          signal:=enableSignal
          copyCountFromInput:=false
        }
      ],

      pole 2 1,
      splitter 0 7 .N,
      belt 2 7 .S,
      belt 3 7 .S,

      belt 2 6 .S,
      belt 3 6 .S,

      belt 0 5 .N,
      belt 1 5 .E,
      belt 2 5 .S,
      belt 3 5 .S,

      belt 0 4 .E,
      belt 1 4 .E,
      belt 2 4 .E,
      belt 3 4 .S,
    ]
    interface:={
      n := #v[]
      e := #v[]
      s := #v[1,2,3]
      w := #v[]
    }
    name := s!"splitBalanced {reprStr i}"
  }

def bigPoleFactory : Factory [] [] [] [] := {
  entities := [bigPole 0 0]
  width := 2, height := 2
  name := "bigPole"
  wires := []
  interface := { n:= #v[], e:= #v[], s:= #v[], w:= #v[] }
}

def pipePumps : Bus Unit :=
  fun state =>
    let config := state.output

    let entities := config.zipIdx.flatMap fun (lane, y) =>
      let poles := if y % 7 == 0 then [pole 1 y] else []

      let lanes :=
        if lane.depleted then []
        else if lane.ingredient.isLiquid then
          [pump 3 y .E] ++
          if y % 7 == 0
          then [pipeToGround 0 y .E, pipeToGround 2 y .E]
          else [pipe 0 y, pipe 1 y, pipe 2 y]
        else
          [belt 3 y .E, belt 4 y .E] ++
          if y % 7 == 0
          then [ beltDown 0 y .E, beltUp 2 y .E ]
          else [ belt 0 y .E, belt 1 y .E, belt 2 y .E]

      poles ++ lanes

    let factory : Factory [] (busInterface config) [] (busInterface config) := {
      entities := entities
      width := 5
      wires := []
      height := config.height
      name := "pipePumps"
      interface := {
        n:= #v[]
        e:= busInterfaceImpl config
        s:= #v[]
        w:= busInterfaceImpl config
      }
    }

    ((), {
      input := state.input
      output := config
      factory := row state.factory (column bigPoleFactory factory)
    })

def spoilingChamber {n} {input:Ingredient} {output:Ingredient} (bacteria:BusLane input n) : Bus (BusLane output n) :=
  let factory : Factory [] [] [(input, .N), (output, .S)] [] := {
    name := s!"spoilingChamber {reprStr input}",
    width := 4,
    height := 9,
    entities := [
      belt 1 0 .E,
      belt 2 0 .E,
      belt 3 0 .S,

      belt 0 1 .E,
      belt 1 1 .N,
      belt 2 1 .W,
      belt 3 1 .S,

      belt 0 2 .N,
      splitter 1 2 .N,
      belt 3 2 .S,

      belt 0 3 .N,
      splitter 1 3 .E,
      belt 2 3 .N,
      belt 3 3 .S,

      beltUp 0 4 .N,
      beltUp 2 4 .N,
      belt 3 4 .S,

      inserter 0 5 .S [output, .spoilage],
      inserter 1 5 .S [output, .spoilage],
      inserter 2 5 .S [output, .spoilage],
      beltDown 3 5 .S,

      ironChest 0 6,
      ironChest 1 6,
      ironChest 2 6,
      pole 3 6,

      inserter 0 7 .S,
      inserter 1 7 .S,
      inserter 2 7 .S,
      beltUp 3 7 .S,

      belt 0 8 .W,
      belt 1 8 .W,
      belt 2 8 .W,
      belt 3 8 .S,
    ],
    wires := [],
    interface := {
      n := #v[],
      e := #v[],
      s := #v[2, 3],
      w := #v[]
    }
  }
  busTap [bacteria] (unsafeFactoryCast factory)

def removeExcess {i n} (l:BusLane i n) : Bus (BusLane i n × BusLane i 0) :=
  busTap2 [l.toBusLane'] {
    entities:=[
      belt 0 0 .E,
      belt 1 0 .S,

      belt 0 1 .N,
      beltDown 1 1 .S,
      pole 2 1,

      belt 0 2 .N,
      belt 1 2 .E,
      belt 2 2 .S,

      splitter 0 3 .N (outputPriority:="left"),
      belt 2 3 .S,

      belt 0 4 .N,
      belt 1 4 .E,
      belt 2 4 .S,

      splitter 0 5 .N (outputPriority:="right") (filter:=Ingredient.spoilage),
      belt 2 5 .S,

      belt 0 6 .N,
      beltUp 1 6 .S,
      belt 2 6 .S,
    ],
    wires := []
    width:=3,
    height:=7,
    interface:={
      n := #v[]
      e := #v[]
      s := #v[0,1,2]
      w := #v[]
    }
    name := s!"removeExcess {reprStr i}"
  }


================================================
FILE: Functorio/BusTest.lean
================================================
import Functorio.Bus
import Functorio.Cap
import Functorio.Ascii

namespace Test

#guard (bus do
  let iron <- inputs 10 .ironOre 2700
  busTapNoOutput [iron[9], iron[1]] (capN emptyFactoryH)
).toAscii == s!"

  ↑↑
>⇥↥↑↦>
>→→↑
>⇥⤒↦→>
>⇥↑↦→>
>⇥↑↦→>
>⇥↑↦→>
>⇥↑↦→>
>⇥↑↦→>
>⇥↑↦→>
>→↑

"

#guard (bus do
  let iron <- inputs 6 .ironOre 2700
  busTapNoOutput [iron[4], iron[1]] (capN emptyFactoryH)
).toAscii == s!"

  ↑↑
>⇥↥↑↦>
>→→↑
>⇥⤒↦→>
>⇥↑↦→>
>→↑
>→→→→>

"

#guard (bus do
  let iron <- inputs 5 .ironOre 2700
  busTapNoOutput [iron[0], iron[1], iron[2]] (capN emptyFactoryH)
).toAscii == s!"

  ↑↑↑
>→↑↑↑
>→→↑↑
>→→→↑
>→→→→→>
>→→→→→>

"

#guard (bus do
  let iron <- inputs 5 .ironOre 2700
  busTapNoOutput [iron[0], iron[2], iron[4]] (capN emptyFactoryH)
).toAscii == s!"

  ↑↑↑
>→↑↑↑
>→⇥↑↑↦>
>→→↑↑
>→→⇥↑↦>
>→→→↑

"

#guard (bus do
  let iron <- inputs 5 .ironOre 2700
  busTapNoOutput [iron[4], iron[3]] (capN emptyFactoryH)
).toAscii == s!"

  ↑↑←
  ↑ ↑
>⇥↑ ↑↦>
>⇥↑ ↑↦>
>⇥↑ ↑↦>
>⇥↑↦↑
>→↑

"

#guard (bus do
  let iron <- inputs 5 .ironOre 2700
  busTapNoOutput [iron[4], iron[2]] (capN emptyFactoryH)
).toAscii == s!"

  ↑↑
>⇥↑↑↦>
>⇥↥↑↦>
>→→↑
>⇥⤒↦→>
>→↑

"

#guard (bus do
  let iron <- inputs 5 .ironOre 2700
  busTapNoOutput [iron[4], iron[0], iron[1], iron[2]] (capN emptyFactoryH)
).toAscii == s!"

  ↥↑↑↑
>→→↑↑↑
>⇥⤒↦↑↑
>⇥↑↦→↑
>⇥↑↦→→→>
>→↑

"

#guard (bus do
  let petrol <- input .petroleumGas 1
  let water <- input .water 1
  let _ <- inputs 5 .ironOre 2700
  busTapNoOutput [petrol, water] (capN (emptyFactoryH #v[0,2]))
).toAscii == s!"

  | |
>|| |
>→→⇥|↦>
>||||
>→→→→→>
>→→→→→>
>→→→→→>
>→→→→→>

"

#guard (bus do
  let _ <- inputs 5 .ironOre 2700
  let petrol <- input .petroleumGas 1
  let water <- input .water 1
  busTapNoOutput [petrol, water] (capN (emptyFactoryH #v[0,2]))
).toAscii == s!"

  | |
>⇥| |↦>
>⇥| |↦>
>⇥| |↦>
>⇥| |↦>
>⇥| |↦>
>|| |
    |
>||||

"

#guard (bus do
  let iron <- inputs 5 .ironOre 2700
  let petrol <- input .petroleumGas 1
  let water <- input .water 1
  busTapNoOutput [petrol, iron[2], water] (capN emptyFactoryH)
).toAscii == s!"

  |↑|
>⇥|↑|↦>
>⇥┴↑|↦>
>→→↑|
>⇥┬ |↦>
>⇥| |↦>
>|| |
    |
>||||

"

#guard (bus do
  let iron <- inputs 2 .ironOre 2700
  let _coal : BusLane .coal 100 <- busTap [iron[0]] (capN emptyFactoryH)
).toAscii == s!"

  ↑↓
>→↑→→>
>→→→→>

"

#guard (bus do
  let iron <- inputs 5 .ironOre 2700
  let _coal : BusLane .coal 100  <- busTap [iron[2]] (capN emptyFactoryH)
).toAscii == s!"

  ↑↓
>⇥↑↓↦>
>⇥↑↓↦>
>→↑→→>
>→→→→>
>→→→→>

"

#guard (bus do
  let iron <- inputs 5 .ironOre 2700
  let _gear : BusLane .coal 100 <- busTap [iron[1], iron[3]] (capN emptyFactoryH)
).toAscii == s!"

  ↑↑↓
>⇥↑↑↓↦>
>→↑↑→→>
>→⇥↑↦→>
>→→↑
>→→→→→>

"

#guard (bus do
  let iron <- inputs 5 .ironOre 2700
  let _water : BusLane .water 100 <- busTap [iron[2]] (capN emptyFactoryH)
).toAscii == s!"

  ↑|
>⇥↑|↦>
>⇥↑|↦>
>→↑||>
>→→→→>
>→→→→>

"

#guard (bus do
  let iron <- inputs 3 .ironOre 2700
  let (iron0, _) <- split iron[0] (left:=100)
  busTapNoOutput [iron0] (capN emptyFactoryH)
).toAscii == s!"

  *↑
>→S→→>
>→→→→>
>→→→→>

"

#guard (bus do
  let iron <- inputs 4 .ironOre 2700
  let (iron0, _) <- split iron[2] (left:=100)
  busTapNoOutput [iron0] (capN emptyFactoryH)
).toAscii == s!"

   ↑
>→⇥↑↦>
>⇥*↑↦>
>→S→→>
>→→→→>

"

#guard (bus do
  let iron <- inputs 4 .ironOre 2700
  let water <- input .water 1000
  let (iron0, _) <- split iron[2] (left:=100)
  busTapNoOutput [water, iron0] (capN emptyFactoryH)
).toAscii == s!"

  |↑
>⇥┴↑↦>
>⇥*↑↦>
>→S→→>
>⇥┬↦→>
>||

"

#guard (bus do
  let iron <- inputs 5 .ironOre 2700
  let petrol <- input .petroleumGas 1
  let water <- input .water 10
  let (water0, _) <- split (left:=5) water
  busTapNoOutput [petrol, iron[2], water0] (capN emptyFactoryH)
).toAscii == s!"

  |↑|
>⇥|↑|↦>
>⇥┴↑|↦>
>→→↑|
>⇥┬ |↦>
>⇥| |↦>
>|| |
    |
>|||||>

"

#guard (bus do
  let iron <- inputs 10 .ironOre 2700
  let petrol <- input .petroleumGas 1
  let water <- input .water 10
  let (water0, _) <- split (left:=5) water
  busTapNoOutput [petrol, iron[2], water0] (capN emptyFactoryH)
).toAscii == s!"

  |↑|
>⇥|↑|↦>
>⇥┴↑|↦>
>→→↑|
>⇥┬ |↦>
>⇥| |↦>
>⇥| |↦>
>⇥| |↦>
>⇥| |↦>
>⇥| |↦>
>⇥| |↦>
>|| |
    |
>|||||>

"

#guard (bus do
  let iron0 <- inputs 3 .ironOre 2700
  let water <- input .water 1000
  let iron1 <- inputs 3 .ironOre 2700
  let (water0, _) <- split (left:=1) water
  busTapNoOutput [water0, iron1[0], iron1[1], iron0[0], iron0[1], iron0[2]] (capN emptyFactoryH)
).toAscii == s!"

  |↑↑↑↑↑←
  |↑↑↑↑←↑
  |↑↑↑←↑↑
  |↑↑←↑↑↑
  |↑←↑↑↑↑
  | ↑↥↑↑↑
>⇥| ↑↦↑↑↑
>⇥| ↑⤒↦↑↑
>⇥| ↑↑↦→↑
>||┤↑↑├|||>
>→→→↑↑
>→→→→↑
>→→→→→→→→→>

"

#guard (bus do
  let iron0 <- inputs 3 .ironOre 2700
  let water <- input .water 1000
  let iron1 <- inputs 3 .ironOre 2700
  let (water0, _) <- split (left:=1) water
  busTapNoOutput [water0, iron1[0], iron1[1], iron0[0]] (capN emptyFactoryH)
).toAscii == s!"

  |↑↑↑←
  |↑↑←↑
  |↑←↑↑
  | ↑↥↑
>⇥| ↑↦↑
>⇥| ↑⤒↦→>
>⇥| ↑↑↦→>
>||┤↑↑├|>
>→→→↑↑
>→→→→↑
>→→→→→→→>

"

#guard (bus do
  let iron <- inputs 6 .ironOre 2700
  let water <- input .water 1000
  let (iron0, _) <- split iron[2] (left:=100)
  let (iron1, _) <- split iron[4] (left:=100)
  busTapNoOutput [water, iron0, iron1] (capN emptyFactoryH)
).toAscii == s!"

  |↑↑
>⇥┴↑↑↦>
>⇥*↑↑↦>
>→S⇥↑↦>
>→⇥*↑↦>
>→→S→→>
>⇥┬↦→→>
>||

"

#guard (bus do
  let iron <- inputs 10 .ironOre 2700
  let (iron0, _) <- split iron[6] (left:=100)
  let (iron1, _) <- split iron[4] (left:=100)
  let (iron2, _) <- split iron[2] (left:=100)
  let _coal : BusLane .coal 4 <- busTap [iron0, iron1, iron2] (capN emptyFactoryH)
).toAscii == s!"

   ↑↑↑→→→→→→↓
   ↑↑↑←←←←  ↓
   ↑↑←←  ↑  ↓
   ↑  ↑  ↑  ↓
>→⇥↑↦⇥↑↦⇥↑↦⇥↓↦>
>→⇥↑↦⇥↑ *↑↦⇥↓↦>
>→⇥↑↦⇥↑↦S→→⇥↓↦>
>→⇥↑ *↑↦→→→⇥↓↦>
>→⇥↑↦S→→→→→⇥↓↦>
>⇥*↑↦→→→→→→⇥↓↦>
>→S→→→→→→→→⇥↓↦>
>→→→→→→→→→→⇥↓↦>
>→→→→→→→→→→⇥↓↦>
>→→→→→→→→→→⇥↓↦>
            →→>

"

#guard (bus do
  let iron <- inputs 15 .ironOre 2700
  let petrol <- input .petroleumGas 2
  let (petrol0, petrol1) <- split (left:=1) petrol
  let water <- input .water 2
  let (water0, water1) <- split (left:=1) water
  busTapNoOutput [petrol0, iron[0], water0] (capN emptyFactoryH)
  pipePumps
  busTapNoOutput [water1, iron[5], petrol1] (capN emptyFactoryH)
).toAscii == s!"

      **
      *↯
  ┴↑|  ⚡    |↑|
>→→↑|       |↑|
>⇥┬ |↦→→→→→⇥|↑|↦>
>⇥| |↦→→→→→⇥|↑|↦>
>⇥| |↦→→→→→⇥|↑|↦>
>⇥| |↦→→→→→⇥┴↑|↦>
>⇥| |↦→→→→→→→↑|
>⇥| |↦⇥⚡↦→→⇥┬ |↦>
>⇥| |↦→→→→→⇥| |↦>
>⇥| |↦→→→→→⇥| |↦>
>⇥| |↦→→→→→⇥| |↦>
>⇥| |↦→→→→→⇥| |↦>
>⇥| |↦→→→→→⇥| |↦>
>⇥| |↦→→→→→⇥| |↦>
>⇥| |↦⇥⚡↦→→⇥| |↦>
>⇥| |↦→→→→→⇥| |↦>
>||┤|├|||*P┤|├|
    |       |
>||||||||*P||

"

#guard (bus do
  let iron0 <- input .ironPlate 10
  let iron1 <- input .ironPlate 15
  let _ <- merge iron0 iron1
).toAscii == s!"

  ⚡→↓
   ↑⤓
  →↑←
  ↑→↑
  *S↧
  ↑↑↓
>→↑↑→→>
>→→↑

"

#guard (bus do
  let water0 <- input .water 10
  let water1 <- input .water 15
  let _ <- merge water0 water1
).toAscii == s!"

  |||||
  | | |
>|| | ||>
    |
>||||

"

#guard (bus do
  let iron <- input .ironOre 10
  let _ <- splitBalanced iron (left:=3)
).toAscii == s!"

 **
 ++⚡
 ***
 ≥≥≥
 →→→↓
 ↑→↓↓
 ↑↑↓↓
 *S↓↓
  ↑↓↓
>→↑↓→→>
   →→→>

"

#guard (bus do
  let bacteria <- input .ironBacteria 10
  let _iron : BusLane .ironOre 10 <- spoilingChamber bacteria
).toAscii == s!"

  →→↓
 →↑←↓
 ↑*S↓
 ↑*↑↓
 ↥S↥↓
 ⇧⇧⇧⤓
 ☐☐☐⚡
 ⇧⇧⇧↧
 ←←←↓
  →↑↓
  ↑↓←
  ↑↓
>→↑→→>

"


================================================
FILE: Functorio/Cap.lean
================================================
import Functorio.Factory

def capN {n e s w} (f:Factory n e s w) : Factory [] e s w :=
  {
    width:= f.width,
    height:= f.height,
    entities := f.entities,
    wires := f.wires,
    interface := {
      n := #v[]
      e := f.interface.e
      s := f.interface.s
      w := f.interface.w
    }
    name := f.name
  }

def capE {n e s w} (f:Factory n e s w) : Factory n [] s w :=
  {
    width:= f.width,
    height:= f.height,
    entities := f.entities,
    wires := f.wires,
    interface := {
      n := f.interface.n
      e := #v[]
      s := f.interface.s
      w := f.interface.w
    }
    name := f.name
  }

def capS {n e s w} (f:Factory n e s w) : Factory n e [] w :=
  {
    width:= f.width,
    height:= f.height,
    entities := f.entities,
    wires := f.wires,
    interface := {
      n := f.interface.n
      e := f.interface.e
      s := #v[]
      w := f.interface.w
    }
    name := f.name
  }

def capW {n e s w} (f:Factory n e s w) : Factory n e s [] :=
  {
    width:= f.width
    height:= f.height
    entities := f.entities
    wires := f.wires
    interface := {
      n := f.interface.n
      e := f.interface.e
      s := f.interface.s
      w := #v[]
    }
    name := f.name
  }

def capAll {n e s w} (f:Factory n e s w) : Factory [] [] [] [] :=
  capN (capE (capS (capW f)))


================================================
FILE: Functorio/Column.lean
================================================
import Functorio.Factory
import Functorio.Expand
import Functorio.Adapter
import Functorio.Util

private def columnPerfect {n e s w e' s' w'} (top : Factory n e s w) (bot:Factory s e' s' w'): Factory n (e ++ e') s' (w ++ w') :=
  if top.interface.s != bot.interface.n then impossible! "perfect column must have exactly the same interface positions" else
  if top.width != bot.width then impossible! "perfect column must have exactly the same width" else
  let width := top.width
  {
    width := width,
    height := top.height + bot.height,
    entities := top.entities ++ bot.entities.map (Entity.offsetPosition 0 top.height)
    wires := top.wires ++ bot.wires.map fun wire => wire.incrementLabels top.entities.length
    interface :=
    {
      n := top.interface.n
      e := cast (by simp) (top.interface.e ++ increaseOffset top.height (bot.interface.e))
      s := bot.interface.s
      w := cast (by simp) (top.interface.w ++ increaseOffset top.height (bot.interface.w))
    }
    name := s!"col({top.name},{bot.name})"
  }

private def columnSameWidth {n e s w e' s' w'} (top : Factory n e s w) (bot:Factory s e' s' w') (adapterMinHeight:Nat) : Factory n (e ++ e') s' (w ++ w') :=
  if top.width != bot.width then impossible! s!"same width of factories expected {top.width}" else
  let width := top.width
  let adapter := adapterV (top.interface.s) (bot.interface.n) width adapterMinHeight
  columnPerfect top (columnPerfect adapter bot)

def column {n e s w e' s' w'} (top : Factory n e s w) (bot:Factory s e' s' w') (adapterMinHeight := 0) : Factory n (e ++ e') s' (w ++ w') :=
  -- Similar code in rowTwo

  -- Align the two factories on their 0th interface,
  -- then expand the smaller one to match the bigger one's size.
  if (top.width > bot.width) then
    let diff := top.width - bot.width
    let shift := if top.interface.s.isEmpty then 0
                 else min diff ((top.interface.s)[0]! - (bot.interface.n)[0]!)
    columnSameWidth top ((bot.expand .E (diff - shift)).expand .W shift) adapterMinHeight else
  if (top.width < bot.width) then
    let diff := bot.width - top.width
    let shift := if top.interface.s.isEmpty then 0
                 else min diff ((bot.interface.n)[0]! - (top.interface.s)[0]!)
    columnSameWidth ((top.expand .E (diff - shift)).expand .W shift) bot adapterMinHeight else
  columnSameWidth top bot adapterMinHeight

def columnList {i} (fs:List (Factory i [] i [])) : Factory i [] i [] :=
  match fs with
  | f::fs => fs.foldl column f
  | [] => error! "can't make a column with empty list of factories yet"

def column3 {n e s w e' s' w' e'' s'' w''} (top : Factory n e s w) (middle:Factory s e' s' w') (bot:Factory s'  e'' s'' w'') : Factory n (e ++ e' ++ e'') s'' (w ++ w' ++ w'') :=
  column (column top middle) bot


================================================
FILE: Functorio/Config.lean
================================================
class Config where
  generateRoboports : Bool := false
  generateBigPoles : Bool := false
  providerChestCapacity : Nat := 0
  adapterMinHeight : Nat := 0

instance : Config where


================================================
FILE: Functorio/Crop.lean
================================================
import Functorio.Factory

private def cropOption {n e s w} (f:Factory n e s w) : Option (Factory n e s w) :=
  let xs := f.entities.map (fun e => e.x)
  let ys := f.entities.map (fun e => e.y)
  do
    let xMin <- List.min? xs
    let xMax <- List.max? xs
    let yMin <- List.min? ys
    let yMax <- List.max? ys
    some {
      width := xMax - xMin + 1,
      height:= yMax - yMin + 1,
      entities:= f.entities.map (fun e => {
        x := e.x - xMin,
        y := e.y - yMin,
        type := e.type
      }),
      wires := f.wires
      interface := {
        n := decreaseOffset xMin (f.interface.n)
        e := decreaseOffset yMin (f.interface.e)
        s := decreaseOffset xMin (f.interface.s)
        w := decreaseOffset yMin (f.interface.w)
      }
      name := f.name
    }

-- Removes empty space from all sides of the factory.
def crop {n e s w} (f:Factory n e s w) : Factory n e s w :=
  (cropOption f).getD f


================================================
FILE: Functorio/Direction.lean
================================================

inductive Direction where
  | N
  | E
  | S
  | W
  deriving DecidableEq, Repr, Inhabited


================================================
FILE: Functorio/Entity.lean
================================================
import Lean.Data.Json
import Lean.Data.Json.Printer
import Lean.Data.Json.FromToJson

import Functorio.Direction
import Functorio.Recipe

open Lean
open Lean (Json)

structure Signal where
  type: Option String
  name: String
  deriving DecidableEq, Inhabited, Repr

structure Condition where
  firstSignal: Signal
  secondSignal: Option Signal
  constantValue: Option Nat
  comparator: String
  deriving DecidableEq, Inhabited, Repr

structure Output where
  signal: Signal
  copyCountFromInput : Bool
  deriving DecidableEq, Inhabited, Repr

structure BeltControlBehavior where
  circuitCondition: Option Condition
  circuitReadHandContents: Bool
  circuitContentsReadMode: Nat
  deriving DecidableEq, Inhabited, Repr

structure ArithmeticCondition where
  firstSignal: Signal
  secondConstant: Nat
  operation: String
  outputSignal: Signal
  deriving DecidableEq, Inhabited, Repr

inductive EntityType
  | belt (dir:Direction) (behavior:BeltControlBehavior := {
      circuitCondition := .none
      circuitReadHandContents := false
      circuitContentsReadMode := 0
    })
  | beltDown (direction:Direction)
  | beltUp (direction:Direction)
  | splitter (direction:Direction) (outputPriority:Option String) (filter:Option Ingredient)
  | pipe
  | pipeToGround (direction:Direction)
  | pump (direction:Direction)
  | inserter (direction:Direction) (filter:List Ingredient)
  | longInserter (direction:Direction) (filter:List Ingredient)
  | pole
  | bigPole
  | fabricator (fabricator:Fabricator) (recipe:RecipeName) (direction:Direction) (mirror:Bool)
  | heatingTower
  | roboport
  | ironChest
  | passiveProviderChest (capacity:Option Nat)
  | deciderCombinator (direction:Direction) (conditions:List Condition) (outputs:List Output)
  | arithmeticCombinator (direction:Direction) (condition:ArithmeticCondition)
  | refinedConcrete
  deriving DecidableEq, Inhabited, Repr

structure Entity where
  x : Nat
  y : Nat
  type : EntityType
  deriving DecidableEq, Inhabited, Repr

def belt x y d (behavior : BeltControlBehavior := {
    circuitCondition := .none
    circuitReadHandContents := false
    circuitContentsReadMode := 0
  })
:=
  ({x:=x,y:=y,type:=.belt d behavior} : Entity)

def beltDown x y d := ({x:=x,y:=y,type:=.beltDown d} : Entity)

def beltUp x y d := ({x:=x,y:=y,type:=.beltUp d} : Entity)

def splitter x y d (outputPriority : Option String := .none) (filter := Option.none) := ({x:=x,y:=y,type:=.splitter d outputPriority filter} : Entity)

def pipe x y := ({x:=x,y:=y,type:=.pipe} : Entity)

def pipeToGround x y d := ({x:=x,y:=y,type:=.pipeToGround d} : Entity)

def pump x y d := ({x:=x,y:=y,type:=.pump d} : Entity)

def inserter x y d (filter:=[]) := ({x:=x,y:=y,type:=.inserter d filter} : Entity)

def longInserter x y d (filter:=[]) := ({x:=x,y:=y,type:=.longInserter d filter} : Entity)

def pole x y := ({x:=x,y:=y,type:=.pole} : Entity)

def bigPole x y := ({x:=x,y:=y,type:=.bigPole} : Entity)

def fabricator x y f r (d := Direction.N) (mirror:=false) := ({x:=x,y:=y,type:=.fabricator f r d mirror} : Entity)

def heatingTower x y := ({x:=x,y:=y,type:=.heatingTower} : Entity)

def assembler r x y (d := Direction.W) := fabricator x y .assemblingMachine3 r d

def furnace r x y := fabricator x y .electricFurnace r

def chemicalPlant r x y (mirror:=false) (d := Direction.W) := fabricator x y .chemicalPlant r d mirror

def refinery r x y (mirror:=false) (d := Direction.E) := fabricator x y .oilRefinery r d mirror

def rocketSilo x y := fabricator x y .rocketSilo .rocketPart

def roboport x y := ({x:=x,y:=y,type:=.roboport} : Entity)

def ironChest x y := ({x:=x,y:=y,type:=.ironChest} : Entity)

def passiveProviderChest x y (capacity : Option Nat := .none) := ({x:=x,y:=y,type:=.passiveProviderChest capacity} : Entity)

def refinedConcrete x y := ({x:=x,y:=y,type:=.refinedConcrete} : Entity)

def deciderCombinator x y (direction:Direction) (conditions:List Condition) (outputs:List Output) :=
  ({x:=x, y:=y, type:=.deciderCombinator direction conditions outputs} : Entity)

def arithmeticCombinator x y (direction:Direction) (condition:ArithmeticCondition) :=
  ({x:=x, y:=y, type:=.arithmeticCombinator direction condition} : Entity)

def recyler x y d (mirror:=false) := ({x:=x,y:=y,type:=.fabricator .recycler RecipeName.itemUnknownRecycling d mirror} : Entity)

namespace Entity

def width (e:Entity) : Nat :=
  match e.type with
  | .belt _ _ | .beltDown _ | .beltUp _ | .pipe | .pipeToGround _ | .inserter _ _ | .longInserter _ _
  | .pole | .ironChest | .passiveProviderChest _ | .refinedConcrete => 1
  | .bigPole => 2
  | .splitter dir _ _ => if dir == .N || dir == .S then 2 else 1
  | .deciderCombinator dir _ _ | .arithmeticCombinator dir _ | .pump dir => if dir == .N || dir == .S then 1 else 2
  | .heatingTower => 3
  | .roboport => 4
  | .fabricator f _ dir _ => if dir == .N || dir == .S then f.width else f.height

def height (e:Entity) : Nat :=
  match e.type with
  | .belt _ _ | .beltDown _ | .beltUp _ | .pipe | .pipeToGround _ | .inserter _ _
  | .longInserter _ _ | .pole | .ironChest | .passiveProviderChest _ | .refinedConcrete => 1
  | .bigPole => 2
  | .splitter dir _ _ => if dir == .N || dir == .S then 1 else 2
  | .deciderCombinator dir _ _ | .arithmeticCombinator dir _ | .pump dir => if dir == .N || dir == .S then 2 else 1
  | .heatingTower => 3
  | .roboport => 4
  | .fabricator f _ dir _ => if dir == .N || dir == .S then f.height else f.width

def offsetPosition (dx dy:Nat) (entity:Entity) : Entity := {
  x := entity.x + dx
  y := entity.y + dy
  type := entity.type
}

end Entity

def eraseRectangle (x y width height:Nat) (es:List Entity) : List Entity :=
  es.filter fun e => !(
    x <= e.x && e.x < x + width &&
    y <= e.y && e.y < y + height
  )


================================================
FILE: Functorio/Expand.lean
================================================
import Functorio.Factory

private def expansionEntity (ingredient:Ingredient) (direction:Direction) (x y:Nat) : Entity :=
  if ingredient.isLiquid then pipe x y else belt x y direction

private def expandS {n e s w} (distance:Nat) (f:Factory n e s w) : Factory n e s w:=
  let expansion := (List.range' f.height distance).flatMap fun y =>
    s.mapIdx fun i (ingredient, dir) =>
      let x := f.interface.s[i]!
      expansionEntity ingredient dir x y

  {
    width := f.width,
    height:= f.height + distance,
    entities:= f.entities ++ expansion
    wires := f.wires,
    interface := f.interface
    name := f.name
  }

private def expandN {n e s w} (distance:Nat) (f:Factory n e s w) : Factory n e s w:=
  let expansion := (List.range distance).flatMap fun y =>
    n.mapIdx fun i (ingredient, dir) =>
      let x := f.interface.n[i]!
      expansionEntity ingredient dir x y

  {
    width := f.width
    height:= f.height + distance
    entities:= f.entities.map (Entity.offsetPosition 0 distance) ++ expansion
    wires := f.wires
    interface := {
      n := f.interface.n
      e := increaseOffset distance (f.interface.e)
      s := f.interface.s
      w := increaseOffset distance (f.interface.w)
    }
    name := f.name
  }

private def expandE {n e s w} (distance:Nat) (f:Factory n e s w) : Factory n e s w:=
  let expansion := (List.range' f.width distance).flatMap fun x =>
    e.mapIdx fun i (ingredient, dir) =>
      let y := f.interface.e[i]!
      expansionEntity ingredient dir x y

  {
    width := f.width + distance,
    height:= f.height,
    entities:= f.entities ++ expansion,
    wires := f.wires
    interface := f.interface
    name := f.name
  }

private def expandW {n e s w} (distance:Nat) (f:Factory n e s w) : Factory n e s w:=
  let expansion := (List.range distance).flatMap fun x =>
    w.mapIdx fun i (ingredient, dir) =>
      let y := f.interface.w[i]!
      expansionEntity ingredient dir x y

  {
    width := f.width + distance,
    height:= f.height,
    entities:= f.entities.map (Entity.offsetPosition distance 0) ++ expansion
    wires := f.wires
    interface := {
      n := increaseOffset distance f.interface.n
      e := f.interface.e
      s := increaseOffset distance (f.interface.s)
      w := f.interface.w
    }
    name := f.name
  }

namespace Factory

def expand {n e s w} (direction:Direction) (distance:Nat) (f:Factory n e s w) : Factory n e s w :=
  match direction with
  | .N => expandN distance f
  | .S => expandS distance f
  | .E => expandE distance f
  | .W => expandW distance f

end Factory


================================================
FILE: Functorio/ExpandTest.lean
================================================
import Functorio.Factory
import Functorio.Expand
import Functorio.Column
import Functorio.Ascii

#guard (
  (@emptyFactoryH [(.coal,.S), (.coal,.N), (.water,.N)] #v[0,2,4]).expand .S 3
).toAscii == s!"
 v ^ ^
 ↓ ↑ |
 ↓ ↑ |
 ↓ ↑ |
 v ^ ^
"

#guard (column
  ((@emptyFactoryH [(.coal,.S), (.coal,.N), (.water,.N)] #v[0,2,4]).expand .N 2)
  ((@emptyFactoryH [(.coal,.S), (.coal,.N), (.water,.N)] #v[0,2,4]).expand .S 3)
).toAscii == s!"
 v ^ ^
 ↓ ↑ |
 ↓ ↑ |
 ↓ ↑ |
 ↓ ↑ |
 ↓ ↑ |
 v ^ ^
"


================================================
FILE: Functorio/Factory.lean
================================================
import Functorio.Entity
import Functorio.Util
import Functorio.Recipe

-- coordinates increase from N to S, and W to E
--
--   +--->
--   |
--   V

abbrev InterfaceImpl := Nat  -- offset

def increaseOffset {l} (n:Nat) (is:Vector InterfaceImpl l) : Vector InterfaceImpl l :=
  is.map fun offset => offset + n

def decreaseOffset {l} (n:Nat) (is:Vector InterfaceImpl l) : Vector InterfaceImpl l :=
  is.map fun offset => offset - n

-- Horizontal (H) directions
inductive DirectionH where
  | E
  | W
  deriving DecidableEq, Inhabited, Repr

instance : Coe DirectionH Direction where
  coe d := match d with | .E => .E | .W => .W

-- Vertical (V) directions
inductive DirectionV where
  | N
  | S
  deriving DecidableEq, Inhabited, Repr

instance : Coe DirectionV Direction where
  coe d := match d with | .N => .N | .S => .S

abbrev InterfaceH := Ingredient × DirectionH
abbrev InterfaceV := Ingredient × DirectionV

structure InterfaceImpls (n:List InterfaceV) (e:List InterfaceH) (s:List InterfaceV) (w:List InterfaceH) where
  n: Vector InterfaceImpl (n.length)
  e: Vector InterfaceImpl (e.length)
  s: Vector InterfaceImpl (s.length)
  w: Vector InterfaceImpl (w.length)
  deriving Inhabited, Repr

inductive WireType where
  | redInput
  | greenInput
  | redOutput
  | greenOutput
  | copper
  deriving DecidableEq, Inhabited, Repr

structure Wire where
  src: Nat
  srcType: WireType
  dst: Nat
  dstType: WireType
  deriving DecidableEq, Inhabited, Repr

namespace Wire

def incrementLabels (wire:Wire) (n:Nat) : Wire :=
  {src:=wire.src+n, dst:=wire.dst+n, srcType:=wire.srcType, dstType:=wire.dstType}

end Wire

structure Factory (n:List InterfaceV) (e:List InterfaceH) (s:List InterfaceV) (w:List InterfaceH) where
  width: Nat
  height: Nat
  entities: List Entity
  wires: List Wire
  interface: InterfaceImpls n e s w
  name: String
  deriving Repr

def errorFactory {n e s w} : Factory n e s w := {
  entities := []
  wires := []
  height := max n.length s.length
  width := max e.length w.length
  interface := {
    n := Vector.range (n.length)
    e := Vector.range (e.length)
    s := Vector.range (s.length)
    w := Vector.range (w.length)
  }
  name := "error"
}

instance {n e s w} : Inhabited (Factory n e s w) where
  default := errorFactory

instance {n e s w} : ToString (Factory n e s w) where
  toString f := f.name

def unsafeFactoryCast {n e s w n' e' s' w'} (f:Factory n e s w) : Factory n' e' s' w' :=
  match decEq n n', decEq e e', decEq s s', decEq w w' with
  | isTrue _, isTrue _, isTrue _, isTrue _  => cast (by subst_eqs; simp) f
  | _, _, _, _ => error! s!"unsafeFactoryCast failed for {f.name}"

namespace Factory

def setName {n e s w} (name:String) (f:Factory n e s w) : Factory n e s w :=
  {
    width:=f.width
    height:=f.height
    entities:=f.entities
    wires := f.wires
    interface:=f.interface
    name:=name
  }

end Factory


def emptyFactoryV {i} (offsets:Vector InterfaceImpl i.length := Vector.range i.length) : Factory [] i [] i := {
  entities := []
  wires := []
  interface := {
    n := #v[]
    e := offsets
    s := #v[]
    w := offsets
  }
  width := 0
  height := if i.isEmpty then 0 else offsets[i.length-1]! + 1
  name := s!"emptyFactoryV {reprStr i}"
}

def emptyFactoryH {i} (offsets:Vector InterfaceImpl i.length := Vector.range i.length) : Factory i [] i [] := {
  entities := []
  wires := []
  interface := {
    n := offsets
    e := #v[]
    s := offsets
    w := #v[]
  }
  width := if i.isEmpty then 0 else offsets[i.length-1]! + 1
  height := 0
  name := s!"emptyFactoryH {reprStr i}"
}

namespace Factory

def refinedConcreteFloor {n e s w} (f:Factory n e s w) : Factory n e s w := Id.run do
  let mut tiles : Array Entity := #[]

  for x in List.range f.width do
    for y in List.range f.height do
      tiles := tiles.push (refinedConcrete x y)

  { f with entities := f.entities ++ tiles.toList }

end Factory


================================================
FILE: Functorio/Fraction.lean
================================================
-- We define our structure, because the standard library
-- adds a proof to the structure. This means that equal
-- numbers may not be definitionally equal.

abbrev Fraction := Nat × Nat

namespace Fraction

@[simp]
def num (f:Fraction) : Nat := f.fst

@[simp]
def den (f:Fraction) : Nat := f.snd

@[simp]
def mk (n m:Nat) : Fraction := (n,m)

def roundUp (f:Fraction) : Nat :=
  if f.den == 1
  then f.num
  else (f.num / f.den) + 1

end Fraction

instance : ToString Fraction where
  toString r := s!"{r.num}/{r.den}"

@[simp]
instance (n:Nat) : OfNat Fraction n where
  ofNat := Fraction.mk n 1

attribute [simp] OfNat.ofNat

-- attribute [simp] Nat.gcd

@[simp]
def normalize (r:Fraction) : Fraction :=
  let gcd : Nat := Nat.gcd r.num r.den
  Fraction.mk (r.num / gcd) (r.den / gcd)

@[simp]
instance : Zero Fraction where
  zero := 0

@[simp]
instance : HMul Fraction Fraction Fraction where
  hMul a b := normalize (Fraction.mk (a.num * b.num) (a.den * b.den))

@[simp]
instance : HDiv Fraction Fraction Fraction where
  hDiv a b := normalize (Fraction.mk (a.num * b.den) (a.den * b.num))

@[simp]
instance : HAdd Fraction Fraction Fraction where
  hAdd a b := normalize (Fraction.mk (a.num * b.den + b.num * a.den) (a.den * b.den))

@[simp]
instance : Add Fraction where
  add a b := normalize (Fraction.mk (a.num * b.den + b.num * a.den) (a.den * b.den))

@[simp]
instance : HSub Fraction Fraction Fraction where
  hSub a b := normalize (Fraction.mk (a.num * b.den - b.num * a.den) (a.den * b.den))

@[simp]
instance : Coe Nat Fraction where
  coe n := Fraction.mk n 1

@[simp]
instance : LE Fraction where
  le a b := a.num * b.den ≤ b.num * a.den

@[simp]
instance : LT Fraction where
  lt a b := a.num * b.den < b.num * a.den

@[simp]
instance : DecidableLE Fraction :=
  fun a b => Nat.decLe (a.num * b.den) (b.num * a.den)

@[simp]
instance : DecidableLT Fraction :=
  fun a b => Nat.decLt (a.num * b.den) (b.num * a.den)

@[simp]
instance : Min Fraction where
  min a b := if a ≤ b then a else b


================================================
FILE: Functorio/Readme.lean
================================================
import Functorio.Abbreviations
import Functorio.AssemblyLine
import Functorio.Bus

namespace Readme

def makeIron : IronOre 300 -> Bus (Iron 300) :=
  busAssemblyLine (recipe .ironPlate) 8

def makeCopper : CopperOre 150 -> Bus (Copper 150) :=
  busAssemblyLine (recipe .copperPlate) 4

def makeGear : Iron 300 -> Bus (Gear 150) :=
  busAssemblyLine (recipe .ironGearWheel) 1

def makeRedScience : Copper 150 -> Gear 150 -> Bus (RedScience 150) :=
  busAssemblyLine (recipe .automationSciencePack) 10

def makeSteel : Iron 2700 -> Bus (Steel 540) :=
  busAssemblyLine (recipe .steelPlate) 72

def makeBelt : Iron 150 -> Gear 150 -> Bus (YellowBelt 300):=
  busAssemblyLine (recipe .transportBelt) 1

def makeAcid : Water 6000 -> Sulfur 300 -> Iron 60 ->Bus (Acid 3000) :=
  busAssemblyLine (recipe .sulfuricAcid) 1

def redScience := bus do
  let ironOre <- input .ironOre 300
  let copperOre <- input .copperOre 150

  let iron : Iron 300 <- makeIron ironOre
  let copper : Copper 150 <- makeCopper copperOre
  let gear : Gear 150 <- makeGear iron
  let _science : RedScience 150 <- makeRedScience copper gear

def gearFactory := bus do
  let ironOre : IronOre 300 <- input .ironOre 300
  let iron : Iron 300 <- makeIron ironOre
  let _gear <- makeGear iron

def splitIron := bus do
  let iron : Iron 450 <- input .ironPlate 450
  let (iron0, iron1) <- split (left:=300) (right:=150) iron
  let gear <- makeGear iron0
  let belt <- makeBelt iron1 gear

def mergeSteel := bus do
  let iron0 <- input .ironPlate 2700
  let iron1 <- input .ironPlate 2700
  let steel0 <- makeSteel iron0
  let steel1 <- makeSteel iron1
  let steel <- merge steel0 steel1

def acidFactory := bus do
  let iron <- input .ironPlate 60
  let sulfur <- input .sulfur 300
  let water <- input .water 6000
  let _ <- makeAcid water sulfur iron

def gearFactory1 :=
  station (recipe .ironGearWheel)

def gearFactory3 :=
  assemblyLine (recipe .ironGearWheel) 3

def readme :=
  columnList [
    capAll redScience.refinedConcreteFloor,
    capAll gearFactory.refinedConcreteFloor,
    capAll splitIron.refinedConcreteFloor,
    capAll mergeSteel.refinedConcreteFloor,
    capAll acidFactory.refinedConcreteFloor,
    capAll gearFactory1.refinedConcreteFloor,
    capAll gearFactory3.refinedConcreteFloor
  ]

end Readme


================================================
FILE: Functorio/Recipe.lean
================================================
-- Generated by generate-recipe.py. Do not modify.

import Functorio.Fraction

import Functorio.Direction

inductive Ingredient
  | accumulator
  | activeProviderChest
  | advancedCircuit
  | agriculturalSciencePack
  | agriculturalTower
  | ammonia
  | ammoniacalSolution
  | arithmeticCombinator
  | artificialJellynutSoil
  | artificialYumakoSoil
  | artilleryShell
  | artilleryTurret
  | artilleryWagon
  | assemblingMachine1
  | assemblingMachine2
  | assemblingMachine3
  | asteroidCollector
  | atomicBomb
  | automationSciencePack
  | barrel
  | battery
  | batteryEquipment
  | batteryMk2Equipment
  | batteryMk3Equipment
  | beacon
  | beltImmunityEquipment
  | bigElectricPole
  | bigMiningDrill
  | biochamber
  | bioflux
  | biolab
  | biterEgg
  | blueprint
  | blueprintBook
  | boiler
  | bottomlessChest
  | bufferChest
  | bulkInserter
  | burnerGenerator
  | burnerInserter
  | burnerMiningDrill
  | calcite
  | cannonShell
  | captiveBiterSpawner
  | captureRobotRocket
  | car
  | carbon
  | carbonFiber
  | carbonicAsteroidChunk
  | cargoBay
  | cargoLandingPad
  | cargoWagon
  | centrifuge
  | chemicalPlant
  | chemicalSciencePack
  | cliffExplosives
  | clusterGrenade
  | coal
  | coin
  | combatShotgun
  | concrete
  | constantCombinator
  | constructionRobot
  | copperBacteria
  | copperCable
  | copperOre
  | copperPlate
  | crudeOil
  | crudeOilBarrel
  | crusher
  | cryogenicPlant
  | cryogenicSciencePack
  | deciderCombinator
  | deconstructionPlanner
  | defenderCapsule
  | depletedUraniumFuelCell
  | destroyerCapsule
  | dischargeDefenseEquipment
  | displayPanel
  | distractorCapsule
  | efficiencyModule
  | efficiencyModule2
  | efficiencyModule3
  | electricEnergyInterface
  | electricEngineUnit
  | electricFurnace
  | electricMiningDrill
  | electrolyte
  | electromagneticPlant
  | electromagneticSciencePack
  | electronicCircuit
  | emptyModuleSlot
  | energyShieldEquipment
  | energyShieldMk2Equipment
  | engineUnit
  | exoskeletonEquipment
  | explosiveCannonShell
  | explosiveRocket
  | explosiveUraniumCannonShell
  | explosives
  | expressLoader
  | expressSplitter
  | expressTransportBelt
  | expressUndergroundBelt
  | fastInserter
  | fastLoader
  | fastSplitter
  | fastTransportBelt
  | fastUndergroundBelt
  | firearmMagazine
  | fissionReactorEquipment
  | flamethrower
  | flamethrowerAmmo
  | flamethrowerTurret
  | fluidWagon
  | fluorine
  | fluoroketoneCold
  | fluoroketoneColdBarrel
  | fluoroketoneHot
  | fluoroketoneHotBarrel
  | flyingRobotFrame
  | foundation
  | foundry
  | fusionGenerator
  | fusionPowerCell
  | fusionReactor
  | fusionReactorEquipment
  | gate
  | grenade
  | gunTurret
  | hazardConcrete
  | heatExchanger
  | heatInterface
  | heatPipe
  | heatingTower
  | heavyArmor
  | heavyOil
  | heavyOilBarrel
  | holmiumOre
  | holmiumPlate
  | holmiumSolution
  | ice
  | icePlatform
  | infinityCargoWagon
  | infinityChest
  | infinityPipe
  | inserter
  | ironBacteria
  | ironChest
  | ironGearWheel
  | ironOre
  | ironPlate
  | ironStick
  | itemUnknown
  | jelly
  | jellynut
  | jellynutSeed
  | lab
  | landMine
  | landfill
  | laneSplitter
  | laserTurret
  | lava
  | lightArmor
  | lightOil
  | lightOilBarrel
  | lightningCollector
  | lightningRod
  | linkedBelt
  | linkedChest
  | lithium
  | lithiumBrine
  | lithiumPlate
  | loader
  | locomotive
  | logisticRobot
  | logisticSciencePack
  | longHandedInserter
  | lowDensityStructure
  | lubricant
  | lubricantBarrel
  | mechArmor
  | mediumElectricPole
  | metallicAsteroidChunk
  | metallurgicSciencePack
  | militarySciencePack
  | modularArmor
  | moltenCopper
  | moltenIron
  | nightVisionEquipment
  | nuclearFuel
  | nuclearReactor
  | nutrients
  | offshorePump
  | oilRefinery
  | oneWayValve
  | overflowValve
  | overgrowthJellynutSoil
  | overgrowthYumakoSoil
  | oxideAsteroidChunk
  | passiveProviderChest
  | pentapodEgg
  | personalLaserDefenseEquipment
  | personalRoboportEquipment
  | personalRoboportMk2Equipment
  | petroleumGas
  | petroleumGasBarrel
  | piercingRoundsMagazine
  | piercingShotgunShell
  | pipe
  | pipeToGround
  | pistol
  | plasticBar
  | poisonCapsule
  | powerArmor
  | powerArmorMk2
  | powerSwitch
  | processingUnit
  | productionSciencePack
  | productivityModule
  | productivityModule2
  | productivityModule3
  | programmableSpeaker
  | promethiumAsteroidChunk
  | promethiumSciencePack
  | proxyContainer
  | pump
  | pumpjack
  | qualityModule
  | qualityModule2
  | qualityModule3
  | quantumProcessor
  | radar
  | rail
  | railChainSignal
  | railRamp
  | railSignal
  | railSupport
  | railgun
  | railgunAmmo
  | railgunTurret
  | rawFish
  | recycler
  | refinedConcrete
  | refinedHazardConcrete
  | repairPack
  | requesterChest
  | roboport
  | rocket
  | rocketFuel
  | rocketLauncher
  | rocketPart
  | rocketSilo
  | rocketTurret
  | science
  | scrap
  | selectionTool
  | selectorCombinator
  | shotgun
  | shotgunShell
  | simpleEntityWithForce
  | simpleEntityWithOwner
  | slowdownCapsule
  | smallElectricPole
  | smallLamp
  | solarPanel
  | solarPanelEquipment
  | solidFuel
  | spacePlatformFoundation
  | spacePlatformHub
  | spacePlatformStarterPack
  | spaceSciencePack
  | speedModule
  | speedModule2
  | speedModule3
  | spidertron
  | splitter
  | spoilage
  | stackInserter
  | steam
  | steamEngine
  | steamTurbine
  | steelChest
  | steelFurnace
  | steelPlate
  | stone
  | stoneBrick
  | stoneFurnace
  | stoneWall
  | storageChest
  | storageTank
  | submachineGun
  | substation
  | sulfur
  | sulfuricAcid
  | sulfuricAcidBarrel
  | supercapacitor
  | superconductor
  | tank
  | teslaAmmo
  | teslaTurret
  | teslagun
  | thruster
  | thrusterFuel
  | thrusterOxidizer
  | toolbeltEquipment
  | topUpValve
  | trainStop
  | transportBelt
  | treeSeed
  | tungstenCarbide
  | tungstenOre
  | tungstenPlate
  | turboLoader
  | turboSplitter
  | turboTransportBelt
  | turboUndergroundBelt
  | undergroundBelt
  | upgradePlanner
  | uranium235
  | uranium238
  | uraniumCannonShell
  | uraniumFuelCell
  | uraniumOre
  | uraniumRoundsMagazine
  | utilitySciencePack
  | water
  | waterBarrel
  | wood
  | woodenChest
  | yumako
  | yumakoMash
  | yumakoSeed
  deriving DecidableEq, Repr, Inhabited

inductive RecipeCategory
  | advancedCrafting
  | basicCrafting
  | captiveSpawnerProcess
  | centrifuging
  | chemistry
  | chemistryOrCryogenics
  | crafting
  | craftingWithFluid
  | craftingWithFluidOrMetallurgy
  | crushing
  | cryogenics
  | cryogenicsOrAssembling
  | electromagnetics
  | electronics
  | electronicsOrAssembling
  | electronicsWithFluid
  | metallurgy
  | metallurgyOrAssembling
  | oilProcessing
  | organic
  | organicOrAssembling
  | organicOrChemistry
  | organicOrHandCrafting
  | parameters
  | pressing
  | recycling
  | recyclingOrHandCrafting
  | rocketBuilding
  | smelting
  deriving DecidableEq, Repr, Inhabited

namespace Ingredient

def isLiquid : Ingredient -> Bool
| .ammonia => true
| .ammoniacalSolution => true
| .crudeOil => true
| .electrolyte => true
| .fluorine => true
| .fluoroketoneCold => true
| .fluoroketoneHot => true
| .heavyOil => true
| .holmiumSolution => true
| .lava => true
| .lightOil => true
| .lithiumBrine => true
| .lubricant => true
| .moltenCopper => true
| .moltenIron => true
| .petroleumGas => true
| .steam => true
| .sulfuricAcid => true
| .thrusterFuel => true
| .thrusterOxidizer => true
| .water => true
| _ => false

def spoilResult : Ingredient -> Option Ingredient
| .agriculturalSciencePack => .some spoilage
| .bioflux => .some spoilage
| .copperBacteria => .some copperOre
| .ironBacteria => .some ironOre
| .jelly => .some spoilage
| .jellynut => .some spoilage
| .nutrients => .some spoilage
| .rawFish => .some spoilage
| .yumako => .some spoilage
| .yumakoMash => .some spoilage
| _ => .none

def name : Ingredient -> String
| .accumulator => "accumulator"
| .activeProviderChest => "active-provider-chest"
| .advancedCircuit => "advanced-circuit"
| .agriculturalSciencePack => "agricultural-science-pack"
| .agriculturalTower => "agricultural-tower"
| .ammonia => "ammonia"
| .ammoniacalSolution => "ammoniacal-solution"
| .arithmeticCombinator => "arithmetic-combinator"
| .artificialJellynutSoil => "artificial-jellynut-soil"
| .artificialYumakoSoil => "artificial-yumako-soil"
| .artilleryShell => "artillery-shell"
| .artilleryTurret => "artillery-turret"
| .artilleryWagon => "artillery-wagon"
| .assemblingMachine1 => "assembling-machine-1"
| .assemblingMachine2 => "assembling-machine-2"
| .assemblingMachine3 => "assembling-machine-3"
| .asteroidCollector => "asteroid-collector"
| .atomicBomb => "atomic-bomb"
| .automationSciencePack => "automation-science-pack"
| .barrel => "barrel"
| .battery => "battery"
| .batteryEquipment => "battery-equipment"
| .batteryMk2Equipment => "battery-mk2-equipment"
| .batteryMk3Equipment => "battery-mk3-equipment"
| .beacon => "beacon"
| .beltImmunityEquipment => "belt-immunity-equipment"
| .bigElectricPole => "big-electric-pole"
| .bigMiningDrill => "big-mining-drill"
| .biochamber => "biochamber"
| .bioflux => "bioflux"
| .biolab => "biolab"
| .biterEgg => "biter-egg"
| .blueprint => "blueprint"
| .blueprintBook => "blueprint-book"
| .boiler => "boiler"
| .bottomlessChest => "bottomless-chest"
| .bufferChest => "buffer-chest"
| .bulkInserter => "bulk-inserter"
| .burnerGenerator => "burner-generator"
| .burnerInserter => "burner-inserter"
| .burnerMiningDrill => "burner-mining-drill"
| .calcite => "calcite"
| .cannonShell => "cannon-shell"
| .captiveBiterSpawner => "captive-biter-spawner"
| .captureRobotRocket => "capture-robot-rocket"
| .car => "car"
| .carbon => "carbon"
| .carbonFiber => "carbon-fiber"
| .carbonicAsteroidChunk => "carbonic-asteroid-chunk"
| .cargoBay => "cargo-bay"
| .cargoLandingPad => "cargo-landing-pad"
| .cargoWagon => "cargo-wagon"
| .centrifuge => "centrifuge"
| .chemicalPlant => "chemical-plant"
| .chemicalSciencePack => "chemical-science-pack"
| .cliffExplosives => "cliff-explosives"
| .clusterGrenade => "cluster-grenade"
| .coal => "coal"
| .coin => "coin"
| .combatShotgun => "combat-shotgun"
| .concrete => "concrete"
| .constantCombinator => "constant-combinator"
| .constructionRobot => "construction-robot"
| .copperBacteria => "copper-bacteria"
| .copperCable => "copper-cable"
| .copperOre => "copper-ore"
| .copperPlate => "copper-plate"
| .crudeOil => "crude-oil"
| .crudeOilBarrel => "crude-oil-barrel"
| .crusher => "crusher"
| .cryogenicPlant => "cryogenic-plant"
| .cryogenicSciencePack => "cryogenic-science-pack"
| .deciderCombinator => "decider-combinator"
| .deconstructionPlanner => "deconstruction-planner"
| .defenderCapsule => "defender-capsule"
| .depletedUraniumFuelCell => "depleted-uranium-fuel-cell"
| .destroyerCapsule => "destroyer-capsule"
| .dischargeDefenseEquipment => "discharge-defense-equipment"
| .displayPanel => "display-panel"
| .distractorCapsule => "distractor-capsule"
| .efficiencyModule => "efficiency-module"
| .efficiencyModule2 => "efficiency-module-2"
| .efficiencyModule3 => "efficiency-module-3"
| .electricEnergyInterface => "electric-energy-interface"
| .electricEngineUnit => "electric-engine-unit"
| .electricFurnace => "electric-furnace"
| .electricMiningDrill => "electric-mining-drill"
| .electrolyte => "electrolyte"
| .electromagneticPlant => "electromagnetic-plant"
| .electromagneticSciencePack => "electromagnetic-science-pack"
| .electronicCircuit => "electronic-circuit"
| .emptyModuleSlot => "empty-module-slot"
| .energyShieldEquipment => "energy-shield-equipment"
| .energyShieldMk2Equipment => "energy-shield-mk2-equipment"
| .engineUnit => "engine-unit"
| .exoskeletonEquipment => "exoskeleton-equipment"
| .explosiveCannonShell => "explosive-cannon-shell"
| .explosiveRocket => "explosive-rocket"
| .explosiveUraniumCannonShell => "explosive-uranium-cannon-shell"
| .explosives => "explosives"
| .expressLoader => "express-loader"
| .expressSplitter => "express-splitter"
| .expressTransportBelt => "express-transport-belt"
| .expressUndergroundBelt => "express-underground-belt"
| .fastInserter => "fast-inserter"
| .fastLoader => "fast-loader"
| .fastSplitter => "fast-splitter"
| .fastTransportBelt => "fast-transport-belt"
| .fastUndergroundBelt => "fast-underground-belt"
| .firearmMagazine => "firearm-magazine"
| .fissionReactorEquipment => "fission-reactor-equipment"
| .flamethrower => "flamethrower"
| .flamethrowerAmmo => "flamethrower-ammo"
| .flamethrowerTurret => "flamethrower-turret"
| .fluidWagon => "fluid-wagon"
| .fluorine => "fluorine"
| .fluoroketoneCold => "fluoroketone-cold"
| .fluoroketoneColdBarrel => "fluoroketone-cold-barrel"
| .fluoroketoneHot => "fluoroketone-hot"
| .fluoroketoneHotBarrel => "fluoroketone-hot-barrel"
| .flyingRobotFrame => "flying-robot-frame"
| .foundation => "foundation"
| .foundry => "foundry"
| .fusionGenerator => "fusion-generator"
| .fusionPowerCell => "fusion-power-cell"
| .fusionReactor => "fusion-reactor"
| .fusionReactorEquipment => "fusion-reactor-equipment"
| .gate => "gate"
| .grenade => "grenade"
| .gunTurret => "gun-turret"
| .hazardConcrete => "hazard-concrete"
| .heatExchanger => "heat-exchanger"
| .heatInterface => "heat-interface"
| .heatPipe => "heat-pipe"
| .heatingTower => "heating-tower"
| .heavyArmor => "heavy-armor"
| .heavyOil => "heavy-oil"
| .heavyOilBarrel => "heavy-oil-barrel"
| .holmiumOre => "holmium-ore"
| .holmiumPlate => "holmium-plate"
| .holmiumSolution => "holmium-solution"
| .ice => "ice"
| .icePlatform => "ice-platform"
| .infinityCargoWagon => "infinity-cargo-wagon"
| .infinityChest => "infinity-chest"
| .infinityPipe => "infinity-pipe"
| .inserter => "inserter"
| .ironBacteria => "iron-bacteria"
| .ironChest => "iron-chest"
| .ironGearWheel => "iron-gear-wheel"
| .ironOre => "iron-ore"
| .ironPlate => "iron-plate"
| .ironStick => "iron-stick"
| .itemUnknown => "item-unknown"
| .jelly => "jelly"
| .jellynut => "jellynut"
| .jellynutSeed => "jellynut-seed"
| .lab => "lab"
| .landMine => "land-mine"
| .landfill => "landfill"
| .laneSplitter => "lane-splitter"
| .laserTurret => "laser-turret"
| .lava => "lava"
| .lightArmor => "light-armor"
| .lightOil => "light-oil"
| .lightOilBarrel => "light-oil-barrel"
| .lightningCollector => "lightning-collector"
| .lightningRod => "lightning-rod"
| .linkedBelt => "linked-belt"
| .linkedChest => "linked-chest"
| .lithium => "lithium"
| .lithiumBrine => "lithium-brine"
| .lithiumPlate => "lithium-plate"
| .loader => "loader"
| .locomotive => "locomotive"
| .logisticRobot => "logistic-robot"
| .logisticSciencePack => "logistic-science-pack"
| .longHandedInserter => "long-handed-inserter"
| .lowDensityStructure => "low-density-structure"
| .lubricant => "lubricant"
| .lubricantBarrel => "lubricant-barrel"
| .mechArmor => "mech-armor"
| .mediumElectricPole => "medium-electric-pole"
| .metallicAsteroidChunk => "metallic-asteroid-chunk"
| .metallurgicSciencePack => "metallurgic-science-pack"
| .militarySciencePack => "military-science-pack"
| .modularArmor => "modular-armor"
| .moltenCopper => "molten-copper"
| .moltenIron => "molten-iron"
| .nightVisionEquipment => "night-vision-equipment"
| .nuclearFuel => "nuclear-fuel"
| .nuclearReactor => "nuclear-reactor"
| .nutrients => "nutrients"
| .offshorePump => "offshore-pump"
| .oilRefinery => "oil-refinery"
| .oneWayValve => "one-way-valve"
| .overflowValve => "overflow-valve"
| .overgrowthJellynutSoil => "overgrowth-jellynut-soil"
| .overgrowthYumakoSoil => "overgrowth-yumako-soil"
| .oxideAsteroidChunk => "oxide-asteroid-chunk"
| .passiveProviderChest => "passive-provider-chest"
| .pentapodEgg => "pentapod-egg"
| .personalLaserDefenseEquipment => "personal-laser-defense-equipment"
| .personalRoboportEquipment => "personal-roboport-equipment"
| .personalRoboportMk2Equipment => "personal-roboport-mk2-equipment"
| .petroleumGas => "petroleum-gas"
| .petroleumGasBarrel => "petroleum-gas-barrel"
| .piercingRoundsMagazine => "piercing-rounds-magazine"
| .piercingShotgunShell => "piercing-shotgun-shell"
| .pipe => "pipe"
| .pipeToGround => "pipe-to-ground"
| .pistol => "pistol"
| .plasticBar => "plastic-bar"
| .poisonCapsule => "poison-capsule"
| .powerArmor => "power-armor"
| .powerArmorMk2 => "power-armor-mk2"
| .powerSwitch => "power-switch"
| .processingUnit => "processing-unit"
| .productionSciencePack => "production-science-pack"
| .productivityModule => "productivity-module"
| .productivityModule2 => "productivity-module-2"
| .productivityModule3 => "productivity-module-3"
| .programmableSpeaker => "programmable-speaker"
| .promethiumAsteroidChunk => "promethium-asteroid-chunk"
| .promethiumSciencePack => "promethium-science-pack"
| .proxyContainer => "proxy-container"
| .pump => "pump"
| .pumpjack => "pumpjack"
| .qualityModule => "quality-module"
| .qualityModule2 => "quality-module-2"
| .qualityModule3 => "quality-module-3"
| .quantumProcessor => "quantum-processor"
| .radar => "radar"
| .rail => "rail"
| .railChainSignal => "rail-chain-signal"
| .railRamp => "rail-ramp"
| .railSignal => "rail-signal"
| .railSupport => "rail-support"
| .railgun => "railgun"
| .railgunAmmo => "railgun-ammo"
| .railgunTurret => "railgun-turret"
| .rawFish => "raw-fish"
| .recycler => "recycler"
| .refinedConcrete => "refined-concrete"
| .refinedHazardConcrete => "refined-hazard-concrete"
| .repairPack => "repair-pack"
| .requesterChest => "requester-chest"
| .roboport => "roboport"
| .rocket => "rocket"
| .rocketFuel => "rocket-fuel"
| .rocketLauncher => "rocket-launcher"
| .rocketPart => "rocket-part"
| .rocketSilo => "rocket-silo"
| .rocketTurret => "rocket-turret"
| .science => "science"
| .scrap => "scrap"
| .selectionTool => "selection-tool"
| .selectorCombinator => "selector-combinator"
| .shotgun => "shotgun"
| .shotgunShell => "shotgun-shell"
| .simpleEntityWithForce => "simple-entity-with-force"
| .simpleEntityWithOwner => "simple-entity-with-owner"
| .slowdownCapsule => "slowdown-capsule"
| .smallElectricPole => "small-electric-pole"
| .smallLamp => "small-lamp"
| .solarPanel => "solar-panel"
| .solarPanelEquipment => "solar-panel-equipment"
| .solidFuel => "solid-fuel"
| .spacePlatformFoundation => "space-platform-foundation"
| .spacePlatformHub => "space-platform-hub"
| .spacePlatformStarterPack => "space-platform-starter-pack"
| .spaceSciencePack => "space-science-pack"
| .speedModule => "speed-module"
| .speedModule2 => "speed-module-2"
| .speedModule3 => "speed-module-3"
| .spidertron => "spidertron"
| .splitter => "splitter"
| .spoilage => "spoilage"
| .stackInserter => "stack-inserter"
| .steam => "steam"
| .steamEngine => "steam-engine"
| .steamTurbine => "steam-turbine"
| .steelChest => "steel-chest"
| .steelFurnace => "steel-furnace"
| .steelPlate => "steel-plate"
| .stone => "stone"
| .stoneBrick => "stone-brick"
| .stoneFurnace => "stone-furnace"
| .stoneWall => "stone-wall"
| .storageChest => "storage-chest"
| .storageTank => "storage-tank"
| .submachineGun => "submachine-gun"
| .substation => "substation"
| .sulfur => "sulfur"
| .sulfuricAcid => "sulfuric-acid"
| .sulfuricAcidBarrel => "sulfuric-acid-barrel"
| .supercapacitor => "supercapacitor"
| .superconductor => "superconductor"
| .tank => "tank"
| .teslaAmmo => "tesla-ammo"
| .teslaTurret => "tesla-turret"
| .teslagun => "teslagun"
| .thruster => "thruster"
| .thrusterFuel => "thruster-fuel"
| .thrusterOxidizer => "thruster-oxidizer"
| .toolbeltEquipment => "toolbelt-equipment"
| .topUpValve => "top-up-valve"
| .trainStop => "train-stop"
| .transportBelt => "transport-belt"
| .treeSeed => "tree-seed"
| .tungstenCarbide => "tungsten-carbide"
| .tungstenOre => "tungsten-ore"
| .tungstenPlate => "tungsten-plate"
| .turboLoader => "turbo-loader"
| .turboSplitter => "turbo-splitter"
| .turboTransportBelt => "turbo-transport-belt"
| .turboUndergroundBelt => "turbo-underground-belt"
| .undergroundBelt => "underground-belt"
| .upgradePlanner => "upgrade-planner"
| .uranium235 => "uranium-235"
| .uranium238 => "uranium-238"
| .uraniumCannonShell => "uranium-cannon-shell"
| .uraniumFuelCell => "uranium-fuel-cell"
| .uraniumOre => "uranium-ore"
| .uraniumRoundsMagazine => "uranium-rounds-magazine"
| .utilitySciencePack => "utility-science-pack"
| .water => "water"
| .waterBarrel => "water-barrel"
| .wood => "wood"
| .woodenChest => "wooden-chest"
| .yumako => "yumako"
| .yumakoMash => "yumako-mash"
| .yumakoSeed => "yumako-seed"

end Ingredient

structure Recipe where
  name: String
  -- The `Fraction` indicates how many items are needed to execute the recipe.
  inputs : List (Fraction × Ingredient)
  -- The `Fraction` indicates how many output items are generated by executing the recipe.
  outputs : List (Fraction × Ingredient)
  category : RecipeCategory
  -- Number of seconds that it takes the user to execute the recipe.
  time : Fraction
  deriving DecidableEq, Repr, Inhabited

inductive FluidBoxType where
  | input
  | output
  | inputOutput
  deriving DecidableEq, Repr, Inhabited

structure FluidBox where
  side: Direction
  offset: Nat
  type: FluidBoxType
  deriving DecidableEq, Repr, Inhabited

structure FabricatorDetails where
  name : String
  width : Nat
  height : Nat
  speedup : Fraction
  productivity : Fraction
  moduleSlots : Nat
  fluidBoxes : List FluidBox
  categories : List RecipeCategory
  deriving DecidableEq, Repr, Inhabited

inductive RecipeName
  | accumulator
  | accumulatorRecycling
  | acidNeutralisation
  | activeProviderChest
  | activeProviderChestRecycling
  | advancedCarbonicAsteroidCrushing
  | advancedCircuit
  | advancedCircuitRecycling
  | advancedMetallicAsteroidCrushing
  | advancedOilProcessing
  | advancedOxideAsteroidCrushing
  | advancedThrusterFuel
  | advancedThrusterOxidizer
  | agriculturalSciencePack
  | agriculturalSciencePackRecycling
  | agriculturalTower
  | agriculturalTowerRecycling
  | ammoniaRocketFuel
  | ammoniacalSolutionSeparation
  | arithmeticCombinator
  | arithmeticCombinatorRecycling
  | artificialJellynutSoil
  | artificialJellynutSoilRecycling
  | artificialYumakoSoil
  | artificialYumakoSoilRecycling
  | artilleryShell
  | artilleryShellRecycling
  | artilleryTurret
  | artilleryTurretRecycling
  | artilleryWagon
  | artilleryWagonRecycling
  | assemblingMachine1
  | assemblingMachine1Recycling
  | assemblingMachine2
  | assemblingMachine2Recycling
  | assemblingMachine3
  | assemblingMachine3Recycling
  | asteroidCollector
  | asteroidCollectorRecycling
  | atomicBomb
  | atomicBombRecycling
  | automationSciencePack
  | automationSciencePackRecycling
  | barrel
  | barrelRecycling
  | basicOilProcessing
  | battery
  | batteryEquipment
  | batteryEquipmentRecycling
  | batteryMk2Equipment
  | batteryMk2EquipmentRecycling
  | batteryMk3Equipment
  | batteryMk3EquipmentRecycling
  | batteryRecycling
  | beacon
  | beaconRecycling
  | beltImmunityEquipment
  | beltImmunityEquipmentRecycling
  | bigElectricPole
  | bigElectricPoleRecycling
  | bigMiningDrill
  | bigMiningDrillRecycling
  | biochamber
  | biochamberRecycling
  | bioflux
  | biofluxRecycling
  | biolab
  | biolabRecycling
  | biolubricant
  | bioplastic
  | biosulfur
  | biterEgg
  | biterEggRecycling
  | blueprintBookRecycling
  | blueprintRecycling
  | boiler
  | boilerRecycling
  | bottomlessChestRecycling
  | bufferChest
  | bufferChestRecycling
  | bulkInserter
  | bulkInserterRecycling
  | burnerGeneratorRecycling
  | burnerInserter
  | burnerInserterRecycling
  | burnerMiningDrill
  | burnerMiningDrillRecycling
  | burntSpoilage
  | calciteRecycling
  | cannonShell
  | cannonShellRecycling
  | captiveBiterSpawner
  | captiveBiterSpawnerRecycling
  | captureRobotRocket
  | captureRobotRocketRecycling
  | car
  | carRecycling
  | carbon
  | carbonFiber
  | carbonFiberRecycling
  | carbonRecycling
  | carbonicAsteroidChunkRecycling
  | carbonicAsteroidCrushing
  | carbonicAsteroidReprocessing
  | cargoBay
  | cargoBayRecycling
  | cargoLandingPad
  | cargoLandingPadRecycling
  | cargoWagon
  | cargoWagonRecycling
  | castingCopper
  | castingCopperCable
  | castingIron
  | castingIronGearWheel
  | castingIronStick
  | castingLowDensityStructure
  | castingPipe
  | castingPipeToGround
  | castingSteel
  | centrifuge
  | centrifugeRecycling
  | chemicalPlant
  | chemicalPlantRecycling
  | chemicalSciencePack
  | chemicalSciencePackRecycling
  | cliffExplosives
  | cliffExplosivesRecycling
  | clusterGrenade
  | clusterGrenadeRecycling
  | coalLiquefaction
  | coalRecycling
  | coalSynthesis
  | coinRecycling
  | combatShotgun
  | combatShotgunRecycling
  | concrete
  | concreteFromMoltenIron
  | concreteRecycling
  | constantCombinator
  | constantCombinatorRecycling
  | constructionRobot
  | constructionRobotRecycling
  | copperBacteria
  | copperBacteriaCultivation
  | copperBacteriaRecycling
  | copperCable
  | copperCableRecycling
  | copperOreRecycling
  | copperPlate
  | copperPlateRecycling
  | crudeOilBarrel
  | crudeOilBarrelRecycling
  | crusher
  | crusherRecycling
  | cryogenicPlant
  | cryogenicPlantRecycling
  | cryogenicSciencePack
  | cryogenicSciencePackRecycling
  | deciderCombinator
  | deciderCombinatorRecycling
  | deconstructionPlannerRecycling
  | defenderCapsule
  | defenderCapsuleRecycling
  | depletedUraniumFuelCellRecycling
  | destroyerCapsule
  | destroyerCapsuleRecycling
  | dischargeDefenseEquipment
  | dischargeDefenseEquipmentRecycling
  | displayPanel
  | displayPanelRecycling
  | distractorCapsule
  | distractorCapsuleRecycling
  | efficiencyModule
  | efficiencyModule2
  | efficiencyModule2Recycling
  | efficiencyModule3
  | efficiencyModule3Recycling
  | efficiencyModuleRecycling
  | electricEnergyInterfaceRecycling
  | electricEngineUnit
  | electricEngineUnitRecycling
  | electricFurnace
  | electricFurnaceRecycling
  | electricMiningDrill
  | electricMiningDrillRecycling
  | electrolyte
  | electromagneticPlant
  | electromagneticPlantRecycling
  | electromagneticSciencePack
  | electromagneticSciencePackRecycling
  | electronicCircuit
  | electronicCircuitRecycling
  | emptyCrudeOilBarrel
  | emptyFluoroketoneColdBarrel
  | emptyFluoroketoneHotBarrel
  | emptyHeavyOilBarrel
  | emptyLightOilBarrel
  | emptyLubricantBarrel
  | emptyModuleSlotRecycling
  | emptyPetroleumGasBarrel
  | emptySulfuricAcidBarrel
  | emptyWaterBarrel
  | energyShieldEquipment
  | energyShieldEquipmentRecycling
  | energyShieldMk2Equipment
  | energyShieldMk2EquipmentRecycling
  | engineUnit
  | engineUnitRecycling
  | exoskeletonEquipment
  | exoskeletonEquipmentRecycling
  | explosiveCannonShell
  | explosiveCannonShellRecycling
  | explosiveRocket
  | explosiveRocketRecycling
  | explosiveUraniumCannonShell
  | explosiveUraniumCannonShellRecycling
  | explosives
  | explosivesRecycling
  | expressLoader
  | expressLoaderRecycling
  | expressSplitter
  | expressSplitterRecycling
  | expressTransportBelt
  | expressTransportBeltRecycling
  | expressUndergroundBelt
  | expressUndergroundBeltRecycling
  | fastInserter
  | fastInserterRecycling
  | fastLoader
  | fastLoaderRecycling
  | fastSplitter
  | fastSplitterRecycling
  | fastTransportBelt
  | fastTransportBeltRecycling
  | fastUndergroundBelt
  | fastUndergroundBeltRecycling
  | firearmMagazine
  | firearmMagazineRecycling
  | fishBreeding
  | fissionReactorEquipment
  | fissionReactorEquipmentRecycling
  | flamethrower
  | flamethrowerAmmo
  | flamethrowerAmmoRecycling
  | flamethrowerRecycling
  | flamethrowerTurret
  | flamethrowerTurretRecycling
  | fluidWagon
  | fluidWagonRecycling
  | fluoroketone
  | fluoroketoneColdBarrel
  | fluoroketoneColdBarrelRecycling
  | fluoroketoneCooling
  | fluoroketoneHotBarrel
  | fluoroketoneHotBarrelRecycling
  | flyingRobotFrame
  | flyingRobotFrameRecycling
  | foundation
  | foundationRecycling
  | foundry
  | foundryRecycling
  | fusionGenerator
  | fusionGeneratorRecycling
  | fusionPowerCell
  | fusionPowerCellRecycling
  | fusionReactor
  | fusionReactorEquipment
  | fusionReactorEquipmentRecycling
  | fusionReactorRecycling
  | gate
  | gateRecycling
  | grenade
  | grenadeRecycling
  | gunTurret
  | gunTurretRecycling
  | hazardConcrete
  | hazardConcreteRecycling
  | heatExchanger
  | heatExchangerRecycling
  | heatInterface
  | heatInterfaceRecycling
  | heatPipe
  | heatPipeRecycling
  | heatingTower
  | heatingTowerRecycling
  | heavyArmor
  | heavyArmorRecycling
  | heavyOilBarrel
  | heavyOilBarrelRecycling
  | heavyOilCracking
  | holmiumOreRecycling
  | holmiumPlate
  | holmiumPlateRecycling
  | holmiumSolution
  | iceMelting
  | icePlatform
  | icePlatformRecycling
  | iceRecycling
  | infinityCargoWagonRecycling
  | infinityChest
  | infinityChestRecycling
  | infinityPipe
  | infinityPipeRecycling
  | inserter
  | inserterRecycling
  | ironBacteria
  | ironBacteriaCultivation
  | ironBacteriaRecycling
  | ironChest
  | ironChestRecycling
  | ironGearWheel
  | ironGearWheelRecycling
  | ironOreRecycling
  | ironPlate
  | ironPlateRecycling
  | ironStick
  | ironStickRecycling
  | itemUnknownRecycling
  | jellyRecycling
  | jellynutProcessing
  | jellynutRecycling
  | jellynutSeedRecycling
  | kovarexEnrichmentProcess
  | lab
  | labRecycling
  | landMine
  | landMineRecycling
  | landfill
  | landfillRecycling
  | laneSplitterRecycling
  | laserTurret
  | laserTurretRecycling
  | lightArmor
  | lightArmorRecycling
  | lightOilBarrel
  | lightOilBarrelRecycling
  | lightOilCracking
  | lightningCollector
  | lightningCollectorRecycling
  | lightningRod
  | lightningRodRecycling
  | linkedBeltRecycling
  | linkedChestRecycling
  | lithium
  | lithiumPlate
  | lithiumPlateRecycling
  | lithiumRecycling
  | loader
  | loaderRecycling
  | locomotive
  | locomotiveRecycling
  | logisticRobot
  | logisticRobotRecycling
  | logisticSciencePack
  | logisticSciencePackRecycling
  | longHandedInserter
  | longHandedInserterRecycling
  | lowDensityStructure
  | lowDensityStructureRecycling
  | lubricant
  | lubricantBarrel
  | lubricantBarrelRecycling
  | mechArmor
  | mechArmorRecycling
  | mediumElectricPole
  | mediumElectricPoleRecycling
  | metallicAsteroidChunkRecycling
  | metallicAsteroidCrushing
  | metallicAsteroidReprocessing
  | metallurgicSciencePack
  | metallurgicSciencePackRecycling
  | militarySciencePack
  | militarySciencePackRecycling
  | modularArmor
  | modularArmorRecycling
  | moltenCopper
  | moltenCopperFromLava
  | moltenIron
  | moltenIronFromLava
  | nightVisionEquipment
  | nightVisionEquipmentRecycling
  | nuclearFuel
  | nuclearFuelRecycling
  | nuclearFuelReprocessing
  | nuclearReactor
  | nuclearReactorRecycling
  | nutrientsFromBioflux
  | nutrientsFromBiterEgg
  | nutrientsFromFish
  | nutrientsFromSpoilage
  | nutrientsFromYumakoMash
  | nutrientsRecycling
  | offshorePump
  | offshorePumpRecycling
  | oilRefinery
  | oilRefineryRecycling
  | oneWayValveRecycling
  | overflowValveRecycling
  | overgrowthJellynutSoil
  | overgrowthJellynutSoilRecycling
  | overgrowthYumakoSoil
  | overgrowthYumakoSoilRecycling
  | oxideAsteroidChunkRecycling
  | oxideAsteroidCrushing
  | oxideAsteroidReprocessing
  | parameter0
  | parameter1
  | parameter2
  | parameter3
  | parameter4
  | parameter5
  | parameter6
  | parameter7
  | parameter8
  | parameter9
  | passiveProviderChest
  | passiveProviderChestRecycling
  | pentapodEgg
  | pentapodEggRecycling
  | personalLaserDefenseEquipment
  | personalLaserDefenseEquipmentRecycling
  | personalRoboportEquipment
  | personalRoboportEquipmentRecycling
  | personalRoboportMk2Equipment
  | personalRoboportMk2EquipmentRecycling
  | petroleumGasBarrel
  | petroleumGasBarrelRecycling
  | piercingRoundsMagazine
  | piercingRoundsMagazineRecycling
  | piercingShotgunShell
  | piercingShotgunShellRecycling
  | pipe
  | pipeRecycling
  | pipeToGround
  | pipeToGroundRecycling
  | pistol
  | pistolRecycling
  | plasticBar
  | plasticBarRecycling
  | poisonCapsule
  | poisonCapsuleRecycling
  | powerArmor
  | powerArmorMk2
  | powerArmorMk2Recycling
  | powerArmorRecycling
  | powerSwitch
  | powerSwitchRecycling
  | processingUnit
  | processingUnitRecycling
  | productionSciencePack
  | productionSciencePackRecycling
  | productivityModule
  | productivityModule2
  | productivityModule2Recycling
  | productivityModule3
  | productivityModule3Recycling
  | productivityModuleRecycling
  | programmableSpeaker
  | programmableSpeakerRecycling
  | promethiumAsteroidChunkRecycling
  | promethiumSciencePack
  | promethiumSciencePackRecycling
  | proxyContainerRecycling
  | pump
  | pumpRecycling
  | pumpjack
  | pumpjackRecycling
  | qualityModule
  | qualityModule2
  | qualityModule2Recycling
  | qualityModule3
  | qualityModule3Recycling
  | qualityModuleRecycling
  | quantumProcessor
  | quantumProcessorRecycling
  | radar
  | radarRecycling
  | rail
  | railChainSignal
  | railChainSignalRecycling
  | railRamp
  | railRampRecycling
  | railRecycling
  | railSignal
  | railSignalRecycling
  | railSupport
  | railSupportRecycling
  | railgun
  | railgunAmmo
  | railgunAmmoRecycling
  | railgunRecycling
  | railgunTurret
  | railgunTurretRecycling
  | rawFishRecycling
  | recycler
  | recyclerRecycling
  | refinedConcrete
  | refinedConcreteRecycling
  | refinedHazardConcrete
  | refinedHazardConcreteRecycling
  | repairPack
  | repairPackRecycling
  | requesterChest
  | requesterChestRecycling
  | roboport
  | roboportRecycling
  | rocket
  | rocketFuel
  | rocketFuelFromJelly
  | rocketFuelRecycling
  | rocketLauncher
  | rocketLauncherRecycling
  | rocketPart
  | rocketRecycling
  | rocketSilo
  | rocketSiloRecycling
  | rocketTurret
  | rocketTurretRecycling
  | scienceRecycling
  | scrapRecycling
  | selectionToolRecycling
  | selectorCombinator
  | selectorCombinatorRecycling
  | shotgun
  | shotgunRecycling
  | shotgunShell
  | shotgunShellRecycling
  | simpleCoalLiquefaction
  | simpleEntityWithForceRecycling
  | simpleEntityWithOwnerRecycling
  | slowdownCapsule
  | slowdownCapsuleRecycling
  | smallElectricPole
  | smallElectricPoleRecycling
  | smallLamp
  | smallLampRecycling
  | solarPanel
  | solarPanelEquipment
  | solarPanelEquipmentRecycling
  | solarPanelRecycling
  | solidFuelFromAmmonia
  | solidFuelFromHeavyOil
  | solidFuelFromLightOil
  | solidFuelFromPetroleumGas
  | solidFuelRecycling
  | spacePlatformFoundation
  | spacePlatformFoundationRecycling
  | spacePlatformHubRecycling
  | spacePlatformStarterPack
  | spacePlatformStarterPackRecycling
  | spaceSciencePack
  | spaceSciencePackRecycling
  | speedModule
  | speedModule2
  | speedModule2Recycling
  | speedModule3
  | speedModule3Recycling
  | speedModuleRecycling
  | spidertron
  | spidertronRecycling
  | splitter
  | splitterRecycling
  | spoilageRecycling
  | stackInserter
  | stackInserterRecycling
  | steamCondensation
  | steamEngine
  | steamEngineRecycling
  | steamTurbine
  | steamTurbineRecycling
  | steelChest
  | steelChestRecycling
  | steelFurnace
  | steelFurnaceRecycling
  | steelPlate
  | steelPlateRecycling
  | stoneBrick
  | stoneBrickRecycling
  | stoneFurnace
  | stoneFurnaceRecycling
  | stoneRecycling
  | stoneWall
  | stoneWallRecycling
  | storageChest
  | storageChestRecycling
  | storageTank
  | storageTankRecycling
  | submachineGun
  | submachineGunRecycling
  | substation
  | substationRecycling
  | sulfur
  | sulfurRecycling
  | sulfuricAcid
  | sulfuricAcidBarrel
  | sulfuricAcidBarrelRecycling
  | supercapacitor
  | supercapacitorRecycling
  | superconductor
  | superconductorRecycling
  | tank
  | tankRecycling
  | teslaAmmo
  | teslaAmmoRecycling
  | teslaTurret
  | teslaTurretRecycling
  | teslagun
  | teslagunRecycling
  | thruster
  | thrusterFuel
  | thrusterOxidizer
  | thrusterRecycling
  | toolbeltEquipment
  | toolbeltEquipmentRecycling
  | topUpValveRecycling
  | trainStop
  | trainStopRecycling
  | transportBelt
  | transportBeltRecycling
  | treeSeedRecycling
  | tungstenCarbide
  | tungstenCarbideRecycling
  | tungstenOreRecycling
  | tungstenPlate
  | tungstenPlateRecycling
  | turboLoader
  | turboLoaderRecycling
  | turboSplitter
  | turboSplitterRecycling
  | turboTransportBelt
  | turboTransportBeltRecycling
  | turboUndergroundBelt
  | turboUndergroundBeltRecycling
  | undergroundBelt
  | undergroundBeltRecycling
  | upgradePlannerRecycling
  | uranium235Recycling
  | uranium238Recycling
  | uraniumCannonShell
  | uraniumCannonShellRecycling
  | uraniumFuelCell
  | uraniumFuelCellRecycling
  | uraniumOreRecycling
  | uraniumProcessing
  | uraniumRoundsMagazine
  | uraniumRoundsMagazineRecycling
  | utilitySciencePack
  | utilitySciencePackRecycling
  | waterBarrel
  | waterBarrelRecycling
  | woodProcessing
  | woodRecycling
  | woodenChest
  | woodenChestRecycling
  | yumakoMashRecycling
  | yumakoProcessing
  | yumakoRecycling
  | yumakoSeedRecycling
  deriving DecidableEq, Repr, Inhabited

namespace RecipeName

def getRecipe : RecipeName -> Recipe
| .accumulator => {
  name := "accumulator",
  inputs := [(2, .ironPlate), (5, .battery)],
  outputs := [(1, .accumulator)],
  category := .electronics
  time := 10
}
| .accumulatorRecycling => {
  name := "accumulator-recycling",
  inputs := [(1, .accumulator)],
  outputs := [(5/4, .battery), (1/2, .ironPlate)],
  category := .recycling
  time := 5/8
}
| .acidNeutralisation => {
  name := "acid-neutralisation",
  inputs := [(1000, .sulfuricAcid), (1, .calcite)],
  outputs := [(10000, .steam)],
  category := .chemistryOrCryogenics
  time := 5
}
| .activeProviderChest => {
  name := "active-provider-chest",
  inputs := [(1, .steelChest), (3, .electronicCircuit), (1, .advancedCircuit)],
  outputs := [(1, .activeProviderChest)],
  category := .crafting
  time := 1/2
}
| .activeProviderChestRecycling => {
  name := "active-provider-chest-recycling",
  inputs := [(1, .activeProviderChest)],
  outputs := [(3/4, .electronicCircuit), (1/4, .steelChest), (1/4, .advancedCircuit)],
  category := .recycling
  time := 1/32
}
| .advancedCarbonicAsteroidCrushing => {
  name := "advanced-carbonic-asteroid-crushing",
  inputs := [(1, .carbonicAsteroidChunk)],
  outputs := [(5, .carbon), (2, .sulfur), (1/20, .carbonicAsteroidChunk)],
  category := .crushing
  time := 5
}
| .advancedCircuit => {
  name := "advanced-circuit",
  inputs := [(2, .electronicCircuit), (2, .plasticBar), (4, .copperCable)],
  outputs := [(1, .advancedCircuit)],
  category := .electronics
  time := 6
}
| .advancedCircuitRecycling => {
  name := "advanced-circuit-recycling",
  inputs := [(1, .advancedCircuit)],
  outputs := [(1, .copperCable), (1/2, .electronicCircuit), (1/2, .plasticBar)],
  category := .recycling
  time := 3/8
}
| .advancedMetallicAsteroidCrushing => {
  name := "advanced-metallic-asteroid-crushing",
  inputs := [(1, .metallicAsteroidChunk)],
  outputs := [(10, .ironOre), (4, .copperOre), (1/20, .metallicAsteroidChunk)],
  category := .crushing
  time := 5
}
| .advancedOilProcessing => {
  name := "advanced-oil-processing",
  inputs := [(50, .water), (100, .crudeOil)],
  outputs := [(25, .heavyOil), (45, .lightOil), (55, .petroleumGas)],
  category := .oilProcessing
  time := 5
}
| .advancedOxideAsteroidCrushing => {
  name := "advanced-oxide-asteroid-crushing",
  inputs := [(1, .oxideAsteroidChunk)],
  outputs := [(3, .ice), (2, .calcite), (1/20, .oxideAsteroidChunk)],
  category := .crushing
  time := 5
}
| .advancedThrusterFuel => {
  name := "advanced-thruster-fuel",
  inputs := [(100, .water), (2, .carbon), (1, .calcite)],
  outputs := [(1500, .thrusterFuel)],
  category := .chemistry
  time := 10
}
| .advancedThrusterOxidizer => {
  name := "advanced-thruster-oxidizer",
  inputs := [(100, .water), (2, .ironOre), (1, .calcite)],
  outputs := [(1500, .thrusterOxidizer)],
  category := .chemistry
  time := 10
}
| .agriculturalSciencePack => {
  name := "agricultural-science-pack",
  inputs := [(1, .bioflux), (1, .pentapodEgg)],
  outputs := [(1, .agriculturalSciencePack)],
  category := .organic
  time := 4
}
| .agriculturalSciencePackRecycling => {
  name := "agricultural-science-pack-recycling",
  inputs := [(1, .agriculturalSciencePack)],
  outputs := [(1/4, .agriculturalSciencePack)],
  category := .recycling
  time := 1/4
}
| .agriculturalTower => {
  name := "agricultural-tower",
  inputs := [(10, .steelPlate), (3, .electronicCircuit), (20, .spoilage), (1, .landfill)],
  outputs := [(1, .agriculturalTower)],
  category := .crafting
  time := 10
}
| .agriculturalTowerRecycling => {
  name := "agricultural-tower-recycling",
  inputs := [(1, .agriculturalTower)],
  outputs := [(5, .spoilage), (5/2, .steelPlate), (3/4, .electronicCircuit), (1/4, .landfill)],
  category := .recycling
  time := 5/8
}
| .ammoniaRocketFuel => {
  name := "ammonia-rocket-fuel",
  inputs := [(50, .water), (500, .ammonia), (10, .solidFuel)],
  outputs := [(1, .rocketFuel)],
  category := .chemistryOrCryogenics
  time := 10
}
| .ammoniacalSolutionSeparation => {
  name := "ammoniacal-solution-separation",
  inputs := [(50, .ammoniacalSolution)],
  outputs := [(5, .ice), (50, .ammonia)],
  category := .chemistryOrCryogenics
  time := 1
}
| .arithmeticCombinator => {
  name := "arithmetic-combinator",
  inputs := [(5, .copperCable), (5, .electronicCircuit)],
  outputs := [(1, .arithmeticCombinator)],
  category := .crafting
  time := 1/2
}
| .arithmeticCombinatorRecycling => {
  name := "arithmetic-combinator-recycling",
  inputs := [(1, .arithmeticCombinator)],
  outputs := [(5/4, .copperCable), (5/4, .electronicCircuit)],
  category := .recycling
  time := 1/32
}
| .artificialJellynutSoil => {
  name := "artificial-jellynut-soil",
  inputs := [(2, .jellynutSeed), (50, .nutrients), (5, .landfill)],
  outputs := [(10, .artificialJellynutSoil)],
  category := .crafting
  time := 2
}
| .artificialJellynutSoilRecycling => {
  name := "artificial-jellynut-soil-recycling",
  inputs := [(1, .artificialJellynutSoil)],
  outputs := [(5/4, .nutrients), (1/8, .landfill), (1/20, .jellynutSeed)],
  category := .recycling
  time := 1/8
}
| .artificialYumakoSoil => {
  name := "artificial-yumako-soil",
  inputs := [(2, .yumakoSeed), (50, .nutrients), (5, .landfill)],
  outputs := [(10, .artificialYumakoSoil)],
  category := .crafting
  time := 2
}
| .artificialYumakoSoilRecycling => {
  name := "artificial-yumako-soil-recycling",
  inputs := [(1, .artificialYumakoSoil)],
  outputs := [(5/4, .nutrients), (1/8, .landfill), (1/20, .yumakoSeed)],
  category := .recycling
  time := 1/8
}
| .artilleryShell => {
  name := "artillery-shell",
  inputs := [(1, .radar), (1, .calcite), (4, .tungstenPlate), (8, .explosives)],
  outputs := [(1, .artilleryShell)],
  category := .crafting
  time := 15
}
| .artilleryShellRecycling => {
  name := "artillery-shell-recycling",
  inputs := [(1, .artilleryShell)],
  outputs := [(2, .explosives), (1, .tungstenPlate), (1/4, .radar), (1/4, .calcite)],
  category := .recycling
  time := 15/16
}
| .artilleryTurret => {
  name := "artillery-turret",
  inputs := [(60, .tungstenPlate), (60, .refinedConcrete), (40, .ironGearWheel), (10, .processingUnit)],
  outputs := [(1, .artilleryTurret)],
  category := .crafting
  time := 40
}
| .artilleryTurretRecycling => {
  name := "artillery-turret-recycling",
  inputs := [(1, .artilleryTurret)],
  outputs := [(15, .tungstenPlate), (15, .refinedConcrete), (10, .ironGearWheel), (5/2, .processingUnit)],
  category := .recycling
  time := 5/2
}
| .artilleryWagon => {
  name := "artillery-wagon",
  inputs := [(60, .engineUnit), (60, .tungstenPlate), (60, .refinedConcrete), (40, .ironGearWheel), (10, .processingUnit)],
  outputs := [(1, .artilleryWagon)],
  category := .crafting
  time := 4
}
| .artilleryWagonRecycling => {
  name := "artillery-wagon-recycling",
  inputs := [(1, .artilleryWagon)],
  outputs := [(15, .engineUnit), (15, .tungstenPlate), (15, .refinedConcrete), (10, .ironGearWheel), (5/2, .processingUnit)],
  category := .recycling
  time := 1/4
}
| .assemblingMachine1 => {
  name := "assembling-machine-1",
  inputs := [(3, .electronicCircuit), (5, .ironGearWheel), (9, .ironPlate)],
  outputs := [(1, .assemblingMachine1)],
  category := .crafting
  time := 1/2
}
| .assemblingMachine1Recycling => {
  name := "assembling-machine-1-recycling",
  inputs := [(1, .assemblingMachine1)],
  outputs := [(9/4, .ironPlate), (5/4, .ironGearWheel), (3/4, .electronicCircuit)],
  category := .recycling
  time := 1/32
}
| .assemblingMachine2 => {
  name := "assembling-machine-2",
  inputs := [(2, .steelPlate), (3, .electronicCircuit), (5, .ironGearWheel), (1, .assemblingMachine1)],
  outputs := [(1, .assemblingMachine2)],
  category := .crafting
  time := 1/2
}
| .assemblingMachine2Recycling => {
  name := "assembling-machine-2-recycling",
  inputs := [(1, .assemblingMachine2)],
  outputs := [(5/4, .ironGearWheel), (3/4, .electronicCircuit), (1/2, .steelPlate), (1/4, .assemblingMachine1)],
  category := .recycling
  time := 1/32
}
| .assemblingMachine3 => {
  name := "assembling-machine-3",
  inputs := [(2, .assemblingMachine2), (4, .speedModule)],
  outputs := [(1, .assemblingMachine3)],
  category := .crafting
  time := 1/2
}
| .assemblingMachine3Recycling => {
  name := "assembling-machine-3-recycling",
  inputs := [(1, .assemblingMachine3)],
  outputs := [(1, .speedModule), (1/2, .assemblingMachine2)],
  category := .recycling
  time := 1/32
}
| .asteroidCollector => {
  name := "asteroid-collector",
  inputs := [(20, .lowDensityStructure), (8, .electricEngineUnit), (5, .processingUnit)],
  outputs := [(1, .asteroidCollector)],
  category := .crafting
  time := 10
}
| .asteroidCollectorRecycling => {
  name := "asteroid-collector-recycling",
  inputs := [(1, .asteroidCollector)],
  outputs := [(5, .lowDensityStructure), (2, .electricEngineUnit), (5/4, .processingUnit)],
  category := .recycling
  time := 5/8
}
| .atomicBomb => {
  name := "atomic-bomb",
  inputs := [(10, .processingUnit), (10, .explosives), (100, .uranium235)],
  outputs := [(1, .atomicBomb)],
  category := .crafting
  time := 50
}
| .atomicBombRecycling => {
  name := "atomic-bomb-recycling",
  inputs := [(1, .atomicBomb)],
  outputs := [(25, .uranium235), (5/2, .processingUnit), (5/2, .explosives)],
  category := .recycling
  time := 25/8
}
| .automationSciencePack => {
  name := "automation-science-pack",
  inputs := [(1, .copperPlate), (1, .ironGearWheel)],
  outputs := [(1, .automationSciencePack)],
  category := .crafting
  time := 5
}
| .automationSciencePackRecycling => {
  name := "automation-science-pack-recycling",
  inputs := [(1, .automationSciencePack)],
  outputs := [(1/4, .automationSciencePack)],
  category := .recycling
  time := 5/16
}
| .barrel => {
  name := "barrel",
  inputs := [(1, .steelPlate)],
  outputs := [(1, .barrel)],
  category := .crafting
  time := 1
}
| .barrelRecycling => {
  name := "barrel-recycling",
  inputs := [(1, .barrel)],
  outputs := [(1/4, .steelPlate)],
  category := .recycling
  time := 1/16
}
| .basicOilProcessing => {
  name := "basic-oil-processing",
  inputs := [(100, .crudeOil)],
  outputs := [(45, .petroleumGas)],
  category := .oilProcessing
  time := 5
}
| .battery => {
  name := "battery",
  inputs := [(20, .sulfuricAcid), (1, .ironPlate), (1, .copperPlate)],
  outputs := [(1, .battery)],
  category := .chemistryOrCryogenics
  time := 4
}
| .batteryEquipment => {
  name := "battery-equipment",
  inputs := [(5, .battery), (10, .steelPlate)],
  outputs := [(1, .batteryEquipment)],
  category := .crafting
  time := 10
}
| .batteryEquipmentRecycling => {
  name := "battery-equipment-recycling",
  inputs := [(1, .batteryEquipment)],
  outputs := [(5/2, .steelPlate), (5/4, .battery)],
  category := .recycling
  time := 5/8
}
| .batteryMk2Equipment => {
  name := "battery-mk2-equipment",
  inputs := [(10, .batteryEquipment), (15, .processingUnit), (5, .lowDensityStructure)],
  outputs := [(1, .batteryMk2Equipment)],
  category := .crafting
  time := 10
}
| .batteryMk2EquipmentRecycling => {
  name := "battery-mk2-equipment-recycling",
  inputs := [(1, .batteryMk2Equipment)],
  outputs := [(15/4, .processingUnit), (5/2, .batteryEquipment), (5/4, .lowDensityStructure)],
  category := .recycling
  time := 5/8
}
| .batteryMk3Equipment => {
  name := "battery-mk3-equipment",
  inputs := [(5, .batteryMk2Equipment), (10, .supercapacitor)],
  outputs := [(1, .batteryMk3Equipment)],
  category := .crafting
  time := 10
}
| .batteryMk3EquipmentRecycling => {
  name := "battery-mk3-equipment-recycling",
  inputs := [(1, .batteryMk3Equipment)],
  outputs := [(5/2, .supercapacitor), (5/4, .batteryMk2Equipment)],
  category := .recycling
  time := 5/8
}
| .batteryRecycling => {
  name := "battery-recycling",
  inputs := [(1, .battery)],
  outputs := [(1/4, .ironPlate), (1/4, .copperPlate)],
  category := .recycling
  time := 1/4
}
| .beacon => {
  name := "beacon",
  inputs := [(20, .electronicCircuit), (20, .advancedCircuit), (10, .steelPlate), (10, .copperCable)],
  outputs := [(1, .beacon)],
  category := .electronics
  time := 15
}
| .beaconRecycling => {
  name := "beacon-recycling",
  inputs := [(1, .beacon)],
  outputs := [(5, .electronicCircuit), (5, .advancedCircuit), (5/2, .steelPlate), (5/2, .copperCable)],
  category := .recycling
  time := 15/16
}
| .beltImmunityEquipment => {
  name := "belt-immunity-equipment",
  inputs := [(5, .advancedCircuit), (10, .steelPlate)],
  outputs := [(1, .beltImmunityEquipment)],
  category := .crafting
  time := 10
}
| .beltImmunityEquipmentRecycling => {
  name := "belt-immunity-equipment-recycling",
  inputs := [(1, .beltImmunityEquipment)],
  outputs := [(5/2, .steelPlate), (5/4, .advancedCircuit)],
  category := .recycling
  time := 5/8
}
| .bigElectricPole => {
  name := "big-electric-pole",
  inputs := [(8, .ironStick), (5, .steelPlate), (4, .copperCable)],
  outputs := [(1, .bigElectricPole)],
  category := .electronics
  time := 1/2
}
| .bigElectricPoleRecycling => {
  name := "big-electric-pole-recycling",
  inputs := [(1, .bigElectricPole)],
  outputs := [(2, .ironStick), (5/4, .steelPlate), (1, .copperCable)],
  category := .recycling
  time := 1/32
}
| .bigMiningDrill => {
  name := "big-mining-drill",
  inputs := [(200, .moltenIron), (1, .electricMiningDrill), (20, .tungstenCarbide), (10, .electricEngineUnit), (10, .advancedCircuit)],
  outputs := [(1, .bigMiningDrill)],
  category := .metallurgy
  time := 30
}
| .bigMiningDrillRecycling => {
  name := "big-mining-drill-recycling",
  inputs := [(1, .bigMiningDrill)],
  outputs := [(5, .tungstenCarbide), (5/2, .electricEngineUnit), (5/2, .advancedCircuit), (1/4, .electricMiningDrill)],
  category := .recycling
  time := 15/8
}
| .biochamber => {
  name := "biochamber",
  inputs := [(5, .nutrients), (1, .pentapodEgg), (20, .ironPlate), (5, .electronicCircuit), (1, .landfill)],
  outputs := [(1, .biochamber)],
  category := .organicOrAssembling
  time := 20
}
| .biochamberRecycling => {
  name := "biochamber-recycling",
  inputs := [(1, .biochamber)],
  outputs := [(5, .ironPlate), (5/4, .nutrients), (5/4, .electronicCircuit), (1/4, .pentapodEgg), (1/4, .landfill)],
  category := .recycling
  time := 5/4
}
| .bioflux => {
  name := "bioflux",
  inputs := [(15, .yumakoMash), (12, .jelly)],
  outputs := [(4, .bioflux)],
  category := .organic
  time := 6
}
| .biofluxRecycling => {
  name := "bioflux-recycling",
  inputs := [(1, .bioflux)],
  outputs := [(1/4, .bioflux)],
  category := .recycling
  time := 3/8
}
| .biolab => {
  name := "biolab",
  inputs := [(1, .lab), (10, .biterEgg), (25, .refinedConcrete), (2, .captureRobotRocket), (3, .uranium235)],
  outputs := [(1, .biolab)],
  category := .crafting
  time := 10
}
| .biolabRecycling => {
  name := "biolab-recycling",
  inputs := [(1, .biolab)],
  outputs := [(1/4, .biolab)],
  category := .recycling
  time := 5/8
}
| .biolubricant => {
  name := "biolubricant",
  inputs := [(60, .jelly)],
  outputs := [(20, .lubricant)],
  category := .organic
  time := 3
}
| .bioplastic => {
  name := "bioplastic",
  inputs := [(1, .bioflux), (4, .yumakoMash)],
  outputs := [(3, .plasticBar)],
  category := .organic
  time := 2
}
| .biosulfur => {
  name := "biosulfur",
  inputs := [(5, .spoilage), (1, .bioflux)],
  outputs := [(2, .sulfur)],
  category := .organic
  time := 2
}
| .biterEgg => {
  name := "biter-egg",
  inputs := [],
  outputs := [(5, .biterEgg)],
  category := .captiveSpawnerProcess
  time := 10
}
| .biterEggRecycling => {
  name := "biter-egg-recycling",
  inputs := [(1, .biterEgg)],
  outputs := [(1/4, .biterEgg)],
  category := .recycling
  time := 5/8
}
| .blueprintBookRecycling => {
  name := "blueprint-book-recycling",
  inputs := [(1, .blueprintBook)],
  outputs := [(1/4, .blueprintBook)],
  category := .recycling
  time := 1/32
}
| .blueprintRecycling => {
  name := "blueprint-recycling",
  inputs := [(1, .blueprint)],
  outputs := [(1/4, .blueprint)],
  category := .recycling
  time := 1/32
}
| .boiler => {
  name := "boiler",
  inputs := [(1, .stoneFurnace), (4, .pipe)],
  outputs := [(1, .boiler)],
  category := .crafting
  time := 1/2
}
| .boilerRecycling => {
  name := "boiler-recycling",
  inputs := [(1, .boiler)],
  outputs := [(1, .pipe), (1/4, .stoneFurnace)],
  category := .recycling
  time := 1/32
}
| .bottomlessChestRecycling => {
  name := "bottomless-chest-recycling",
  inputs := [(1, .bottomlessChest)],
  outputs := [(1/4, .bottomlessChest)],
  category := .recycling
  time := 1/32
}
| .bufferChest => {
  name := "buffer-chest",
  inputs := [(1, .steelChest), (3, .electronicCircuit), (1, .advancedCircuit)],
  outputs := [(1, .bufferChest)],
  category := .crafting
  time := 1/2
}
| .bufferChestRecycling => {
  name := "buffer-chest-recycling",
  inputs := [(1, .bufferChest)],
  outputs := [(3/4, .electronicCircuit), (1/4, .steelChest), (1/4, .advancedCircuit)],
  category := .recycling
  time := 1/32
}
| .bulkInserter => {
  name := "bulk-inserter",
  inputs := [(15, .ironGearWheel), (15, .electronicCircuit), (1, .advancedCircuit), (1, .fastInserter)],
  outputs := [(1, .bulkInserter)],
  category := .crafting
  time := 1/2
}
| .bulkInserterRecycling => {
  name := "bulk-inserter-recycling",
  inputs := [(1, .bulkInserter)],
  outputs := [(15/4, .ironGearWheel), (15/4, .electronicCircuit), (1/4, .advancedCircuit), (1/4, .fastInserter)],
  category := .recycling
  time := 1/32
}
| .burnerGeneratorRecycling => {
  name := "burner-generator-recycling",
  inputs := [(1, .burnerGenerator)],
  outputs := [(1/4, .burnerGenerator)],
  category := .recycling
  time := 1/32
}
| .burnerInserter => {
  name := "burner-inserter",
  inputs := [(1, .ironPlate), (1, .ironGearWheel)],
  outputs := [(1, .burnerInserter)],
  category := .crafting
  time := 1/2
}
| .burnerInserterRecycling => {
  name := "burner-inserter-recycling",
  inputs := [(1, .burnerInserter)],
  outputs := [(1/4, .ironPlate), (1/4, .ironGearWheel)],
  category := .recycling
  time := 1/32
}
| .burnerMiningDrill => {
  name := "burner-mining-drill",
  inputs := [(3, .ironGearWheel), (1, .stoneFurnace), (3, .ironPlate)],
  outputs := [(1, .burnerMiningDrill)],
  category := .crafting
  time := 2
}
| .burnerMiningDrillRecycling => {
  name := "burner-mining-drill-recycling",
  inputs := [(1, .burnerMiningDrill)],
  outputs := [(3/4, .ironGearWheel), (3/4, .ironPlate), (1/4, .stoneFurnace)],
  category := .recycling
  time := 1/8
}
| .burntSpoilage => {
  name := "burnt-spoilage",
  inputs := [(6, .spoilage)],
  outputs := [(1, .carbon)],
  category := .organic
  time := 12
}
| .calciteRecycling => {
  name := "calcite-recycling",
  inputs := [(1, .calcite)],
  outputs := [(1/4, .calcite)],
  category := .recycling
  time := 1/32
}
| .cannonShell => {
  name := "cannon-shell",
  inputs := [(2, .steelPlate), (2, .plasticBar), (1, .explosives)],
  outputs := [(1, .cannonShell)],
  category := .crafting
  time := 8
}
| .cannonShellRecycling => {
  name := "cannon-shell-recycling",
  inputs := [(1, .cannonShell)],
  outputs := [(1/2, .steelPlate), (1/2, .plasticBar), (1/4, .explosives)],
  category := .recycling
  time := 1/2
}
| .captiveBiterSpawner => {
  name := "captive-biter-spawner",
  inputs := [(100, .fluoroketoneCold), (10, .biterEgg), (1, .captureRobotRocket), (15, .uranium235)],
  outputs := [(1, .captiveBiterSpawner)],
  category := .cryogenics
  time := 10
}
| .captiveBiterSpawnerRecycling => {
  name := "captive-biter-spawner-recycling",
  inputs := [(1, .captiveBiterSpawner)],
  outputs := [(1/4, .captiveBiterSpawner)],
  category := .recycling
  time := 5/8
}
| .captureRobotRocket => {
  name := "capture-robot-rocket",
  inputs := [(1, .flyingRobotFrame), (2, .steelPlate), (20, .bioflux), (2, .processingUnit)],
  outputs := [(1, .captureRobotRocket)],
  category := .crafting
  time := 10
}
| .captureRobotRocketRecycling => {
  name := "capture-robot-rocket-recycling",
  inputs := [(1, .captureRobotRocket)],
  outputs := [(5, .bioflux), (1/2, .steelPlate), (1/2, .processingUnit), (1/4, .flyingRobotFrame)],
  category := .recycling
  time := 5/8
}
| .car => {
  name := "car",
  inputs := [(8, .engineUnit), (20, .ironPlate), (5, .steelPlate)],
  outputs := [(1, .car)],
  category := .crafting
  time := 2
}
| .carRecycling => {
  name := "car-recycling",
  inputs := [(1, .car)],
  outputs := [(5, .ironPlate), (2, .engineUnit), (5/4, .steelPlate)],
  category := .recycling
  time := 1/8
}
| .carbon => {
  name := "carbon",
  inputs := [(20, .sulfuricAcid), (2, .coal)],
  outputs := [(1, .carbon)],
  category := .chemistryOrCryogenics
  time := 1
}
| .carbonFiber => {
  name := "carbon-fiber",
  inputs := [(10, .yumakoMash), (1, .carbon)],
  outputs := [(1, .carbonFiber)],
  category := .organic
  time := 5
}
| .carbonFiberRecycling => {
  name := "carbon-fiber-recycling",
  inputs := [(1, .carbonFiber)],
  outputs := [(1/4, .carbonFiber)],
  category := .recycling
  time := 5/16
}
| .carbonRecycling => {
  name := "carbon-recycling",
  inputs := [(1, .carbon)],
  outputs := [(1/4, .carbon)],
  category := .recycling
  time := 1/16
}
| .carbonicAsteroidChunkRecycling => {
  name := "carbonic-asteroid-chunk-recycling",
  inputs := [(1, .carbonicAsteroidChunk)],
  outputs := [(1/4, .carbonicAsteroidChunk)],
  category := .recycling
  time := 1/32
}
| .carbonicAsteroidCrushing => {
  name := "carbonic-asteroid-crushing",
  inputs := [(1, .carbonicAsteroidChunk)],
  outputs := [(10, .carbon), (1/5, .carbonicAsteroidChunk)],
  category := .crushing
  time := 2
}
| .carbonicAsteroidReprocessing => {
  name := "carbonic-asteroid-reprocessing",
  inputs := [(1, .carbonicAsteroidChunk)],
  outputs := [(2/5, .carbonicAsteroidChunk), (1/5, .metallicAsteroidChunk), (1/5, .oxideAsteroidChunk)],
  category := .crushing
  time := 2
}
| .cargoBay => {
  name := "cargo-bay",
  inputs := [(20, .steelPlate), (20, .lowDensityStructure), (5, .processingUnit)],
  outputs := [(1, .cargoBay)],
  category := .crafting
  time := 10
}
| .cargoBayRecycling => {
  name := "cargo-bay-recycling",
  inputs := [(1, .cargoBay)],
  outputs := [(5, .steelPlate), (5, .lowDensityStructure), (5/4, .processingUnit)],
  category := .recycling
  time := 5/8
}
| .cargoLandingPad => {
  name := "cargo-landing-pad",
  inputs := [(200, .concrete), (25, .steelPlate), (10, .processingUnit)],
  outputs := [(1, .cargoLandingPad)],
  category := .crafting
  time := 30
}
| .cargoLandingPadRecycling => {
  name := "cargo-landing-pad-recycling",
  inputs := [(1, .cargoLandingPad)],
  outputs := [(50, .concrete), (25/4, .steelPlate), (5/2, .processingUnit)],
  category := .recycling
  time := 15/8
}
| .cargoWagon => {
  name := "cargo-wagon",
  inputs := [(10, .ironGearWheel), (20, .ironPlate), (20, .steelPlate)],
  outputs := [(1, .cargoWagon)],
  category := .crafting
  time := 1
}
| .cargoWagonRecycling => {
  name := "cargo-wagon-recycling",
  inputs := [(1, .cargoWagon)],
  outputs := [(5, .ironPlate), (5, .steelPlate), (5/2, .ironGearWheel)],
  category := .recycling
  time := 1/16
}
| .castingCopper => {
  name := "casting-copper",
  inputs := [(20, .moltenCopper)],
  outputs := [(2, .copperPlate)],
  category := .metallurgy
  time := 16/5
}
| .castingCopperCable => {
  name := "casting-copper-cable",
  inputs := [(5, .moltenCopper)],
  outputs := [(2, .copperCable)],
  category := .metallurgy
  time := 1
}
| .castingIron => {
  name := "casting-iron",
  inputs := [(20, .moltenIron)],
  outputs := [(2, .ironPlate)],
  category := .metallurgy
  time := 16/5
}
| .castingIronGearWheel => {
  name := "casting-iron-gear-wheel",
  inputs := [(10, .moltenIron)],
  outputs := [(1, .ironGearWheel)],
  category := .metallurgy
  time := 1
}
| .castingIronStick => {
  name := "casting-iron-stick",
  inputs := [(20, .moltenIron)],
  outputs := [(4, .ironStick)],
  category := .metallurgy
  time := 1
}
| .castingLowDensityStructure => {
  name := "casting-low-density-structure",
  inputs := [(80, .moltenIron), (250, .moltenCopper), (5, .plasticBar)],
  outputs := [(1, .lowDensityStructure)],
  category := .metallurgy
  time := 15
}
| .castingPipe => {
  name := "casting-pipe",
  inputs := [(10, .moltenIron)],
  outputs := [(1, .pipe)],
  category := .metallurgy
  time := 1
}
| .castingPipeToGround => {
  name := "casting-pipe-to-ground",
  inputs := [(50, .moltenIron), (10, .pipe)],
  outputs := [(2, .pipeToGround)],
  category := .metallurgy
  time := 1
}
| .castingSteel => {
  name := "casting-steel",
  inputs := [(30, .moltenIron)],
  outputs := [(1, .steelPlate)],
  category := .metallurgy
  time := 16/5
}
| .centrifuge => {
  name := "centrifuge",
  inputs := [(100, .concrete), (50, .steelPlate), (100, .advancedCircuit), (100, .ironGearWheel)],
  outputs := [(1, .centrifuge)],
  category := .crafting
  time := 4
}
| .centrifugeRecycling => {
  name := "centrifuge-recycling",
  inputs := [(1, .centrifuge)],
  outputs := [(25, .concrete), (25, .advancedCircuit), (25, .ironGearWheel), (25/2, .steelPlate)],
  category := .recycling
  time := 1/4
}
| .chemicalPlant => {
  name := "chemical-plant",
  inputs := [(5, .steelPlate), (5, .ironGearWheel), (5, .electronicCircuit), (5, .pipe)],
  outputs := [(1, .chemicalPlant)],
  category := .crafting
  time := 5
}
| .chemicalPlantRecycling => {
  name := "chemical-plant-recycling",
  inputs := [(1, .chemicalPlant)],
  outputs := [(5/4, .steelPlate), (5/4, .ironGearWheel), (5/4, .electronicCircuit), (5/4, .pipe)],
  category := .recycling
  time := 5/16
}
| .chemicalSciencePack => {
  name := "chemical-science-pack",
  inputs := [(2, .engineUnit), (3, .advancedCircuit), (1, .sulfur)],
  outputs := [(2, .chemicalSciencePack)],
  category := .crafting
  time := 24
}
| .chemicalSciencePackRecycling => {
  name := "chemical-science-pack-recycling",
  inputs := [(1, .chemicalSciencePack)],
  outputs := [(1/4, .chemicalSciencePack)],
  category := .recycling
  time := 3/2
}
| .cliffExplosives => {
  name := "cliff-explosives",
  inputs := [(10, .explosives), (10, .calcite), (1, .grenade), (1, .barrel)],
  outputs := [(1, .cliffExplosives)],
  category := .crafting
  time := 8
}
| .cliffExplosivesRecycling => {
  name := "cliff-explosives-recycling",
  inputs := [(1, .cliffExplosives)],
  outputs := [(5/2, .explosives), (5/2, .calcite), (1/4, .grenade), (1/4, .barrel)],
  category := .recycling
  time := 1/2
}
| .clusterGrenade => {
  name := "cluster-grenade",
  inputs := [(7, .grenade), (5, .explosives), (5, .steelPlate)],
  outputs := [(1, .clusterGrenade)],
  category := .crafting
  time := 8
}
| .clusterGrenadeRecycling => {
  name := "cluster-grenade-recycling",
  inputs := [(1, .clusterGrenade)],
  outputs := [(7/4, .grenade), (5/4, .explosives), (5/4, .steelPlate)],
  category := .recycling
  time := 1/2
}
| .coalLiquefaction => {
  name := "coal-liquefaction",
  inputs := [(25, .heavyOil), (50, .steam), (10, .coal)],
  outputs := [(90, .heavyOil), (20, .lightOil), (10, .petroleumGas)],
  category := .oilProcessing
  time := 5
}
| .coalRecycling => {
  name := "coal-recycling",
  inputs := [(1, .coal)],
  outputs := [(1/4, .coal)],
  category := .recycling
  time := 1/32
}
| .coalSynthesis => {
  name := "coal-synthesis",
  inputs := [(10, .water), (5, .carbon), (1, .sulfur)],
  outputs := [(1, .coal)],
  category := .chemistry
  time := 2
}
| .coinRecycling => {
  name := "coin-recycling",
  inputs := [(1, .coin)],
  outputs := [(1/4, .coin)],
  category := .recycling
  time := 1/32
}
| .combatShotgun => {
  name := "combat-shotgun",
  inputs := [(15, .steelPlate), (5, .ironGearWheel), (10, .copperPlate), (10, .wood)],
  outputs := [(1, .combatShotgun)],
  category := .crafting
  time := 10
}
| .combatShotgunRecycling => {
  name := "combat-shotgun-recycling",
  inputs := [(1, .combatShotgun)],
  outputs := [(15/4, .steelPlate), (5/2, .copperPlate), (5/2, .wood), (5/4, .ironGearWheel)],
  category := .recycling
  time := 5/8
}
| .concrete => {
  name := "concrete",
  inputs := [(100, .water), (5, .stoneBrick), (1, .ironOre)],
  outputs := [(10, .concrete)],
  category := .craftingWithFluid
  time := 10
}
| .concreteFromMoltenIron => {
  name := "concrete-from-molten-iron",
  inputs := [(20, .moltenIron), (100, .water), (5, .stoneBrick)],
  outputs := [(10, .concrete)],
  category := .metallurgy
  time := 10
}
| .concreteRecycling => {
  name := "concrete-recycling",
  inputs := [(1, .concrete)],
  outputs := [(1/8, .stoneBrick), (1/40, .ironOre)],
  category := .recycling
  time := 5/8
}
| .constantCombinator => {
  name := "constant-combinator",
  inputs := [(5, .copperCable), (2, .electronicCircuit)],
  outputs := [(1, .constantCombinator)],
  category := .crafting
  time := 1/2
}
| .constantCombinatorRecycling => {
  name := "constant-combinator-recycling",
  inputs := [(1, .constantCombinator)],
  outputs := [(5/4, .copperCable), (1/2, .electronicCircuit)],
  category := .recycling
  time := 1/32
}
| .constructionRobot => {
  name := "construction-robot",
  inputs := [(1, .flyingRobotFrame), (2, .electronicCircuit)],
  outputs := [(1, .constructionRobot)],
  category := .crafting
  time := 1/2
}
| .constructionRobotRecycling => {
  name := "construction-robot-recycling",
  inputs := [(1, .constructionRobot)],
  outputs := [(1/2, .electronicCircuit), (1/4, .flyingRobotFrame)],
  category := .recycling
  time := 1/32
}
| .copperBacteria => {
  name := "copper-bacteria",
  inputs := [(3, .yumakoMash)],
  outputs := [(1/10, .copperBacteria), (1, .spoilage)],
  category := .organicOrHandCrafting
  time := 1
}
| .copperBacteriaCultivation => {
  name := "copper-bacteria-cultivation",
  inputs := [(1, .copperBacteria), (1, .bioflux)],
  outputs := [(4, .copperBacteria)],
  category := .organic
  time := 4
}
| .copperBacteriaRecycling => {
  name := "copper-bacteria-recycling",
  inputs := [(1, .copperBacteria)],
  outputs := [(1/4, .copperBacteria)],
  category := .recycling
  time := 1/16
}
| .copperCable => {
  name := "copper-cable",
  inputs := [(1, .copperPlate)],
  outputs := [(2, .copperCable)],
  category := .electronics
  time := 1/2
}
| .copperCableRecycling => {
  name := "copper-cable-recycling",
  inputs := [(1, .copperCable)],
  outputs := [(1/8, .copperPlate)],
  category := .recycling
  time := 1/32
}
| .copperOreRecycling => {
  name := "copper-ore-recycling",
  inputs := [(1, .copperOre)],
  outputs := [(1/4, .copperOre)],
  category := .recycling
  time := 1/32
}
| .copperPlate => {
  name := "copper-plate",
  inputs := [(1, .copperOre)],
  outputs := [(1, .copperPlate)],
  category := .smelting
  time := 16/5
}
| .copperPlateRecycling => {
  name := "copper-plate-recycling",
  inputs := [(1, .copperPlate)],
  outputs := [(1/4, .copperPlate)],
  category := .recycling
  time := 1/5
}
| .crudeOilBarrel => {
  name := "crude-oil-barrel",
  inputs := [(50, .crudeOil), (1, .barrel)],
  outputs := [(1, .crudeOilBarrel)],
  category := .craftingWithFluid
  time := 1/5
}
| .crudeOilBarrelRecycling => {
  name := "crude-oil-barrel-recycling",
  inputs := [(1, .crudeOilBarrel)],
  outputs := [(1/4, .barrel)],
  category := .recycling
  time := 1/80
}
| .crusher => {
  name := "crusher",
  inputs := [(20, .lowDensityStructure), (10, .steelPlate), (10, .electricEngineUnit)],
  outputs := [(1, .crusher)],
  category := .crafting
  time := 10
}
| .crusherRecycling => {
  name := "crusher-recycling",
  inputs := [(1, .crusher)],
  outputs := [(5, .lowDensityStructure), (5/2, .steelPlate), (5/2, .electricEngineUnit)],
  category := .recycling
  time := 5/8
}
| .cryogenicPlant => {
  name := "cryogenic-plant",
  inputs := [(40, .refinedConcrete), (20, .superconductor), (20, .processingUnit), (20, .lithiumPlate)],
  outputs := [(1, .cryogenicPlant)],
  category := .cryogenicsOrAssembling
  time := 10
}
| .cryogenicPlantRecycling => {
  name := "cryogenic-plant-recycling",
  inputs := [(1, .cryogenicPlant)],
  outputs := [(10, .refinedConcrete), (5, .superconductor), (5, .processingUnit), (5, .lithiumPlate)],
  category := .recycling
  time := 5/8
}
| .cryogenicSciencePack => {
  name := "cryogenic-science-pack",
  inputs := [(6, .fluoroketoneCold), (3, .ice), (1, .lithiumPlate)],
  outputs := [(1, .cryogenicSciencePack), (3, .fluoroketoneHot)],
  category := .cryogenics
  time := 20
}
| .cryogenicSciencePackRecycling => {
  name := "cryogenic-science-pack-recycling",
  inputs := [(1, .cryogenicSciencePack)],
  outputs := [(1/4, .cryogenicSciencePack)],
  category := .recycling
  time := 5/4
}
| .deciderCombinator => {
  name := "decider-combinator",
  inputs := [(5, .copperCable), (5, .electronicCircuit)],
  outputs := [(1, .deciderCombinator)],
  category := .crafting
  time := 1/2
}
| .deciderCombinatorRecycling => {
  name := "decider-combinator-recycling",
  inputs := [(1, .deciderCombinator)],
  outputs := [(5/4, .copperCable), (5/4, .electronicCircuit)],
  category := .recycling
  time := 1/32
}
| .deconstructionPlannerRecycling => {
  name := "deconstruction-planner-recycling",
  inputs := [(1, .deconstructionPlanner)],
  outputs := [(1/4, .deconstructionPlanner)],
  category := .recycling
  time := 1/32
}
| .defenderCapsule => {
  name := "defender-capsule",
  inputs := [(3, .piercingRoundsMagazine), (3, .electronicCircuit), (3, .ironGearWheel)],
  outputs := [(1, .defenderCapsule)],
  category := .crafting
  time := 8
}
| .defenderCapsuleRecycling => {
  name := "defender-capsule-recycling",
  inputs := [(1, .defenderCapsule)],
  outputs := [(3/4, .piercingRoundsMagazine), (3/4, .electronicCircuit), (3/4, .ironGearWheel)],
  category := .recycling
  time := 1/2
}
| .depletedUraniumFuelCellRecycling => {
  name := "depleted-uranium-fuel-cell-recycling",
  inputs := [(1, .depletedUraniumFuelCell)],
  outputs := [(1/4, .depletedUraniumFuelCell)],
  category := .recycling
  time := 1/32
}
| .destroyerCapsule => {
  name := "destroyer-capsule",
  inputs := [(4, .distractorCapsule), (4, .steelPlate), (1, .processingUnit)],
  outputs := [(1, .destroyerCapsule)],
  category := .crafting
  time := 15
}
| .destroyerCapsuleRecycling => {
  name := "destroyer-capsule-recycling",
  inputs := [(1, .destroyerCapsule)],
  outputs := [(1, .distractorCapsule), (1, .steelPlate), (1/4, .processingUnit)],
  category := .recycling
  time := 15/16
}
| .dischargeDefenseEquipment => {
  name := "discharge-defense-equipment",
  inputs := [(5, .processingUnit), (20, .steelPlate), (10, .laserTurret)],
  outputs := [(1, .dischargeDefenseEquipment)],
  category := .electronics
  time := 10
}
| .dischargeDefenseEquipmentRecycling => {
  name := "discharge-defense-equipment-recycling",
  inputs := [(1, .dischargeDefenseEquipment)],
  outputs := [(5, .steelPlate), (5/2, .laserTurret), (5/4, .processingUnit)],
  category := .recycling
  time := 5/8
}
| .displayPanel => {
  name := "display-panel",
  inputs := [(1, .ironPlate), (1, .electronicCircuit)],
  outputs := [(1, .displayPanel)],
  category := .crafting
  time := 1/2
}
| .displayPanelRecycling => {
  name := "display-panel-recycling",
  inputs := [(1, .displayPanel)],
  outputs := [(1/4, .ironPlate), (1/4, .electronicCircuit)],
  category := .recycling
  time := 1/32
}
| .distractorCapsule => {
  name := "distractor-capsule",
  inputs := [(4, .defenderCapsule), (3, .advancedCircuit)],
  outputs := [(1, .distractorCapsule)],
  category := .crafting
  time := 15
}
| .distractorCapsuleRecycling => {
  name := "distractor-capsule-recycling",
  inputs := [(1, .distractorCapsule)],
  outputs := [(1, .defenderCapsule), (3/4, .advancedCircuit)],
  category := .recycling
  time := 15/16
}
| .efficiencyModule => {
  name := "efficiency-module",
  inputs := [(5, .advancedCircuit), (5, .electronicCircuit)],
  outputs := [(1, .efficiencyModule)],
  category := .electronics
  time := 15
}
| .efficiencyModule2 => {
  name := "efficiency-module-2",
  inputs := [(4, .efficiencyModule), (5, .advancedCircuit), (5, .processingUnit)],
  outputs := [(1, .efficiencyModule2)],
  category := .electronics
  time := 30
}
| .efficiencyModule2Recycling => {
  name := "efficiency-module-2-recycling",
  inputs := [(1, .efficiencyModule2)],
  outputs := [(5/4, .advancedCircuit), (5/4, .processingUnit), (1, .efficiencyModule)],
  category := .recycling
  time := 15/8
}
| .efficiencyModule3 => {
  name := "efficiency-module-3",
  inputs := [(4, .efficiencyModule2), (5, .advancedCircuit), (5, .processingUnit), (5, .spoilage)],
  outputs := [(1, .efficiencyModule3)],
  category := .electronics
  time := 60
}
| .efficiencyModule3Recycling => {
  name := "efficiency-module-3-recycling",
  inputs := [(1, .efficiencyModule3)],
  outputs := [(5/4, .advancedCircuit), (5/4, .processingUnit), (5/4, .spoilage), (1, .efficiencyModule2)],
  category := .recycling
  time := 15/4
}
| .efficiencyModuleRecycling => {
  name := "efficiency-module-recycling",
  inputs := [(1, .efficiencyModule)],
  outputs := [(5/4, .advancedCircuit), (5/4, .electronicCircuit)],
  category := .recycling
  time := 15/16
}
| .electricEnergyInterfaceRecycling => {
  name := "electric-energy-interface-recycling",
  inputs := [(1, .electricEnergyInterface)],
  outputs := [(1/4, .electricEnergyInterface)],
  category := .recycling
  time := 1/32
}
| .electricEngineUnit => {
  name := "electric-engine-unit",
  inputs := [(15, .lubricant), (1, .engineUnit), (2, .electronicCircuit)],
  outputs := [(1, .electricEngineUnit)],
  category := .craftingWithFluid
  time := 10
}
| .electricEngineUnitRecycling => {
  name := "electric-engine-unit-recycling",
  inputs := [(1, .electricEngineUnit)],
  outputs := [(1/2, .electronicCircuit), (1/4, .engineUnit)],
  category := .recycling
  time := 5/8
}
| .electricFurnace => {
  name := "electric-furnace",
  inputs := [(10, .steelPlate), (5, .advancedCircuit), (10, .stoneBrick)],
  outputs := [(1, .electricFurnace)],
  category := .crafting
  time := 5
}
| .electricFurnaceRecycling => {
  name := "electric-furnace-recycling",
  inputs := [(1, .electricFurnace)],
  outputs := [(5/2, .steelPlate), (5/2, .stoneBrick), (5/4, .advancedCircuit)],
  category := .recycling
  time := 5/16
}
| .electricMiningDrill => {
  name := "electric-mining-drill",
  inputs := [(3, .electronicCircuit), (5, .ironGearWheel), (10, .ironPlate)],
  outputs := [(1, .electricMiningDrill)],
  category := .crafting
  time := 2
}
| .electricMiningDrillRecycling => {
  name := "electric-mining-drill-recycling",
  inputs := [(1, .electricMiningDrill)],
  outputs := [(5/2, .ironPlate), (5/4, .ironGearWheel), (3/4, .electronicCircuit)],
  category := .recycling
  time := 1/8
}
| .electrolyte => {
  name := "electrolyte",
  inputs := [(10, .heavyOil), (10, .holmiumSolution), (1, .stone)],
  outputs := [(10, .electrolyte)],
  category := .electromagnetics
  time := 5
}
| .electromagneticPlant => {
  name := "electromagnetic-plant",
  inputs := [(150, .holmiumPlate), (50, .steelPlate), (50, .processingUnit), (50, .refinedConcrete)],
  outputs := [(1, .electromagneticPlant)],
  category := .electronicsOrAssembling
  time := 10
}
| .electromagneticPlantRecycling => {
  name := "electromagnetic-plant-recycling",
  inputs := [(1, .electromagneticPlant)],
  outputs := [(75/2, .holmiumPlate), (25/2, .steelPlate), (25/2, .processingUnit), (25/2, .refinedConcrete)],
  category := .recycling
  time := 5/8
}
| .electromagneticSciencePack => {
  name := "electromagnetic-science-pack",
  inputs := [(25, .electrolyte), (25, .holmiumSolution), (1, .supercapacitor), (1, .accumulator)],
  outputs := [(1, .electromagneticSciencePack)],
  category := .electromagnetics
  time := 10
}
| .electromagneticSciencePackRecycling => {
  name := "electromagnetic-science-pack-recycling",
  inputs := [(1, .electromagneticSciencePack)],
  outputs := [(1/4, .electromagneticSciencePack)],
  category := .recycling
  time := 5/8
}
| .electronicCircuit => {
  name := "electronic-circuit",
  inputs := [(1, .ironPlate), (3, .copperCable)],
  outputs := [(1, .electronicCircuit)],
  category := .electronics
  time := 1/2
}
| .electronicCircuitRecycling => {
  name := "electronic-circuit-recycling",
  inputs := [(1, .electronicCircuit)],
  outputs := [(3/4, .copperCable), (1/4, .ironPlate)],
  category := .recycling
  time := 1/32
}
| .emptyCrudeOilBarrel => {
  name := "empty-crude-oil-barrel",
  inputs := [(1, .crudeOilBarrel)],
  outputs := [(1, .barrel), (50, .crudeOil)],
  category := .craftingWithFluid
  time := 1/5
}
| .emptyFluoroketoneColdBarrel => {
  name := "empty-fluoroketone-cold-barrel",
  inputs := [(1, .fluoroketoneColdBarrel)],
  outputs := [(1, .barrel), (50, .fluoroketoneCold)],
  category := .craftingWithFluid
  time := 1/5
}
| .emptyFluoroketoneHotBarrel => {
  name := "empty-fluoroketone-hot-barrel",
  inputs := [(1, .fluoroketoneHotBarrel)],
  outputs := [(1, .barrel), (50, .fluoroketoneHot)],
  category := .craftingWithFluid
  time := 1/5
}
| .emptyHeavyOilBarrel => {
  name := "empty-heavy-oil-barrel",
  inputs := [(1, .heavyOilBarrel)],
  outputs := [(1, .barrel), (50, .heavyOil)],
  category := .craftingWithFluid
  time := 1/5
}
| .emptyLightOilBarrel => {
  name := "empty-light-oil-barrel",
  inputs := [(1, .lightOilBarrel)],
  outputs := [(1, .barrel), (50, .lightOil)],
  category := .craftingWithFluid
  time := 1/5
}
| .emptyLubricantBarrel => {
  name := "empty-lubricant-barrel",
  inputs := [(1, .lubricantBarrel)],
  outputs := [(1, .barrel), (50, .lubricant)],
  category := .craftingWithFluid
  time := 1/5
}
| .emptyModuleSlotRecycling => {
  name := "empty-module-slot-recycling",
  inputs := [(1, .emptyModuleSlot)],
  outputs := [(1/4, .emptyModuleSlot)],
  category := .recycling
  time := 1/32
}
| .emptyPetroleumGasBarrel => {
  name := "empty-petroleum-
Download .txt
gitextract_pckfgd1u/

├── .devcontainer/
│   ├── Dockerfile
│   └── devcontainer.json
├── .gitignore
├── .vscode/
│   └── settings.json
├── Fulgora150.lean
├── Functorio/
│   ├── Abbreviations.lean
│   ├── Adapter.lean
│   ├── AdapterTest.lean
│   ├── Ascii.lean
│   ├── AssemblyLine.lean
│   ├── AssemblyLineTest.lean
│   ├── AssemblyStation.lean
│   ├── AssemblyStationTest.lean
│   ├── Blueprint.lean
│   ├── Bus.lean
│   ├── BusTest.lean
│   ├── Cap.lean
│   ├── Column.lean
│   ├── Config.lean
│   ├── Crop.lean
│   ├── Direction.lean
│   ├── Entity.lean
│   ├── Expand.lean
│   ├── ExpandTest.lean
│   ├── Factory.lean
│   ├── Fraction.lean
│   ├── Readme.lean
│   ├── Recipe.lean
│   ├── Row.lean
│   ├── Test.lean
│   └── Util.lean
├── Functorio.lean
├── Gleba300.lean
├── LICENSE
├── Nauvis150.lean
├── README.md
├── RedScience150.lean
├── Rocket.lean
├── Spaceship.lean
├── fulgora-150.sh
├── generate-recipe.py
├── gleba-300.sh
├── lake-manifest.json
├── lakefile.toml
├── lean-toolchain
├── nauvis-150.sh
├── red-science-150.sh
├── rocket.sh
└── spaceship.sh
Download .txt
SYMBOL INDEX (2 symbols across 1 files)

FILE: generate-recipe.py
  function to_camel_case (line 8) | def to_camel_case(kebab_case_str: str) -> str:
  function float_to_fraction (line 12) | def float_to_fraction(f: float) -> str:
Condensed preview — 49 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (396K chars).
[
  {
    "path": ".devcontainer/Dockerfile",
    "chars": 408,
    "preview": "# FROM mcr.microsoft.com/devcontainers/universal:2\nFROM mcr.microsoft.com/devcontainers/base:ubuntu\n\nRUN apt-get update "
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "chars": 199,
    "preview": "{\n    \"build\": {\n        \"dockerfile\": \"Dockerfile\"\n    },\n    \"customizations\": {\n        \"vscode\": {\n            \"exte"
  },
  {
    "path": ".gitignore",
    "chars": 5,
    "preview": ".lake"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 465,
    "preview": "{\n    \"cSpell.words\": [\n        \"biochamber\",\n        \"Bioflux\",\n        \"biosulfur\",\n        \"electromagnetics\",\n      "
  },
  {
    "path": "Fulgora150.lean",
    "chars": 3591,
    "preview": "import Functorio\n\ninstance : Config where\n  generateBigPoles := true\n  generateRoboports := true\n  providerChestCapacity"
  },
  {
    "path": "Functorio/Abbreviations.lean",
    "chars": 3695,
    "preview": "import Functorio.Bus\n\nabbrev Coal := BusLane .coal\nabbrev Stone := BusLane .stone\nabbrev CopperOre := BusLane .copperOre"
  },
  {
    "path": "Functorio/Adapter.lean",
    "chars": 4431,
    "preview": "import Functorio.Factory\nimport Functorio.Util\n\nprivate inductive Orientation where\n| vertical\n| horizontalTopLtBot\n| ho"
  },
  {
    "path": "Functorio/AdapterTest.lean",
    "chars": 852,
    "preview": "import Functorio.Adapter\nimport Functorio.Ascii\n\n#guard (adapterV (interface:=[(.coal,.N), (.coal,.N)]) #v[0,1] #v[0,1])"
  },
  {
    "path": "Functorio/Ascii.lean",
    "chars": 3599,
    "preview": "import Functorio.Entity\nimport Functorio.Factory\n\n/-\nLegend:\n\n↑ belt\n⤒ belt going under-ground\n↥ belt going above-ground"
  },
  {
    "path": "Functorio/AssemblyLine.lean",
    "chars": 10529,
    "preview": "import Functorio.Entity\nimport Functorio.Factory\nimport Functorio.Crop\nimport Functorio.Recipe\nimport Functorio.Row\nimpo"
  },
  {
    "path": "Functorio/AssemblyLineTest.lean",
    "chars": 4608,
    "preview": "import Functorio.AssemblyLine\nimport Functorio.Ascii\n\n#guard (assemblyLine (recipe .advancedCircuit) 3).toAscii == s!\"\n\n"
  },
  {
    "path": "Functorio/AssemblyStation.lean",
    "chars": 17731,
    "preview": "import Functorio.Recipe\nimport Functorio.Factory\nimport Functorio.Util\nimport Functorio.Row\n\nstructure Process where\n  r"
  },
  {
    "path": "Functorio/AssemblyStationTest.lean",
    "chars": 2984,
    "preview": "import Functorio.AssemblyStation\nimport Functorio.Ascii\n\n#guard (station (recipe .ironPlate)).toAscii == s!\"\n ^     v\n ↑"
  },
  {
    "path": "Functorio/Blueprint.lean",
    "chars": 7185,
    "preview": "import Lean.Data.Json\nimport Lean.Data.Json.FromToJson\n\nimport Functorio.Entity\nimport Functorio.Factory\n\nopen Lean\nopen"
  },
  {
    "path": "Functorio/Bus.lean",
    "chars": 27963,
    "preview": "import Functorio.Entity\nimport Functorio.Factory\nimport Functorio.Column\nimport Functorio.Row\nimport Functorio.Fraction\n"
  },
  {
    "path": "Functorio/BusTest.lean",
    "chars": 7283,
    "preview": "import Functorio.Bus\nimport Functorio.Cap\nimport Functorio.Ascii\n\nnamespace Test\n\n#guard (bus do\n  let iron <- inputs 10"
  },
  {
    "path": "Functorio/Cap.lean",
    "chars": 1314,
    "preview": "import Functorio.Factory\n\ndef capN {n e s w} (f:Factory n e s w) : Factory [] e s w :=\n  {\n    width:= f.width,\n    heig"
  },
  {
    "path": "Functorio/Column.lean",
    "chars": 2778,
    "preview": "import Functorio.Factory\nimport Functorio.Expand\nimport Functorio.Adapter\nimport Functorio.Util\n\nprivate def columnPerfe"
  },
  {
    "path": "Functorio/Config.lean",
    "chars": 180,
    "preview": "class Config where\n  generateRoboports : Bool := false\n  generateBigPoles : Bool := false\n  providerChestCapacity : Nat "
  },
  {
    "path": "Functorio/Crop.lean",
    "chars": 930,
    "preview": "import Functorio.Factory\n\nprivate def cropOption {n e s w} (f:Factory n e s w) : Option (Factory n e s w) :=\n  let xs :="
  },
  {
    "path": "Functorio/Direction.lean",
    "chars": 91,
    "preview": "\ninductive Direction where\n  | N\n  | E\n  | S\n  | W\n  deriving DecidableEq, Repr, Inhabited\n"
  },
  {
    "path": "Functorio/Entity.lean",
    "chars": 5750,
    "preview": "import Lean.Data.Json\nimport Lean.Data.Json.Printer\nimport Lean.Data.Json.FromToJson\n\nimport Functorio.Direction\nimport "
  },
  {
    "path": "Functorio/Expand.lean",
    "chars": 2571,
    "preview": "import Functorio.Factory\n\nprivate def expansionEntity (ingredient:Ingredient) (direction:Direction) (x y:Nat) : Entity :"
  },
  {
    "path": "Functorio/ExpandTest.lean",
    "chars": 485,
    "preview": "import Functorio.Factory\nimport Functorio.Expand\nimport Functorio.Column\nimport Functorio.Ascii\n\n#guard (\n  (@emptyFacto"
  },
  {
    "path": "Functorio/Factory.lean",
    "chars": 3901,
    "preview": "import Functorio.Entity\nimport Functorio.Util\nimport Functorio.Recipe\n\n-- coordinates increase from N to S, and W to E\n-"
  },
  {
    "path": "Functorio/Fraction.lean",
    "chars": 2011,
    "preview": "-- We define our structure, because the standard library\n-- adds a proof to the structure. This means that equal\n-- numb"
  },
  {
    "path": "Functorio/Readme.lean",
    "chars": 2293,
    "preview": "import Functorio.Abbreviations\nimport Functorio.AssemblyLine\nimport Functorio.Bus\n\nnamespace Readme\n\ndef makeIron : Iron"
  },
  {
    "path": "Functorio/Recipe.lean",
    "chars": 178693,
    "preview": "-- Generated by generate-recipe.py. Do not modify.\n\nimport Functorio.Fraction\n\nimport Functorio.Direction\n\ninductive Ing"
  },
  {
    "path": "Functorio/Row.lean",
    "chars": 3189,
    "preview": "import Functorio.Factory\nimport Functorio.Expand\nimport Functorio.Util\n\nprivate def rowPerfect {n e s w n' e' s'} (left "
  },
  {
    "path": "Functorio/Test.lean",
    "chars": 2755,
    "preview": "import Functorio.AssemblyLine\nimport Functorio.Blueprint\nimport Functorio.Column\nimport Functorio.Bus\nimport Functorio.E"
  },
  {
    "path": "Functorio/Util.lean",
    "chars": 1087,
    "preview": "\ndef error! {T} [Inhabited T] (msg:String) : T :=\n  dbg_trace (\"ERROR: \" ++ msg)\n  default\n\ndef unimplemented! {T} [Inha"
  },
  {
    "path": "Functorio.lean",
    "chars": 646,
    "preview": "import Functorio.Abbreviations\nimport Functorio.Adapter\nimport Functorio.AdapterTest\nimport Functorio.Ascii\nimport Funct"
  },
  {
    "path": "Gleba300.lean",
    "chars": 14858,
    "preview": "import Functorio\nimport Functorio.AssemblyLine\n\ninstance : Config where\n  generateBigPoles := true\n  generateRoboports :"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "Nauvis150.lean",
    "chars": 12970,
    "preview": "import Functorio\n\ninstance : Config where\n  generateBigPoles := true\n  generateRoboports := true\n  providerChestCapacity"
  },
  {
    "path": "README.md",
    "chars": 11958,
    "preview": "# Functorio\n\nFunctorio lets you build your Factorio factories in the Lean programming language; giving you conveniences "
  },
  {
    "path": "RedScience150.lean",
    "chars": 800,
    "preview": "import Functorio\n\ndef makeIron : IronOre 300 -> Bus (Iron 300) :=\n  busAssemblyLine (recipe .ironPlate) 8\n\ndef makeCoppe"
  },
  {
    "path": "Rocket.lean",
    "chars": 3199,
    "preview": "import Functorio\n\ninstance : Config where\n  generateBigPoles := true\n  generateRoboports := true\n  providerChestCapacity"
  },
  {
    "path": "Spaceship.lean",
    "chars": 3052,
    "preview": "import Functorio\n\ninstance : Config where\n  adapterMinHeight := 3\n\ndef makeWater : Ice 240 -> Bus (Water 4800) :=\n  busA"
  },
  {
    "path": "fulgora-150.sh",
    "chars": 117,
    "preview": "#!/bin/bash\n\nlake build fulgora-150 > /dev/null\necho -n 0; .lake/build/bin/fulgora-150 | pigz -zc | base64 -w0; echo\n"
  },
  {
    "path": "generate-recipe.py",
    "chars": 8749,
    "preview": "#!/usr/bin/env python3\n\n# Generates the Recipe.lean file from a factorio data dump, get it by passing --dump-data to fac"
  },
  {
    "path": "gleba-300.sh",
    "chars": 115,
    "preview": "#!/bin/bash\n\nlake build gleba-300 > /dev/null\necho -n 0; .lake/build/bin/gleba-300 | pigz -zc | base64 -w0; echo\n\n\n"
  },
  {
    "path": "lake-manifest.json",
    "chars": 115,
    "preview": "{\"version\": \"1.1.0\",\n \"packagesDir\": \".lake/packages\",\n \"packages\": [],\n \"name\": \"functorio\",\n \"lakeDir\": \".lake\"}\n"
  },
  {
    "path": "lakefile.toml",
    "chars": 456,
    "preview": "name = \"functorio\"\nversion = \"0.1.0\"\ndefaultTargets = [\"nauvis-150\"]\n\n[leanOptions]\nautoImplicit = false\n\n[[lean_lib]]\nn"
  },
  {
    "path": "lean-toolchain",
    "chars": 25,
    "preview": "leanprover/lean4:v4.20.1\n"
  },
  {
    "path": "nauvis-150.sh",
    "chars": 115,
    "preview": "#!/bin/bash\n\nlake build nauvis-150 > /dev/null\necho -n 0; .lake/build/bin/nauvis-150 | pigz -zc | base64 -w0; echo\n"
  },
  {
    "path": "red-science-150.sh",
    "chars": 125,
    "preview": "#!/bin/bash\n\nlake build red-science-150 > /dev/null\necho -n 0; .lake/build/bin/red-science-150 | pigz -zc | base64 -w0; "
  },
  {
    "path": "rocket.sh",
    "chars": 107,
    "preview": "#!/bin/bash\n\nlake build rocket > /dev/null\necho -n 0; .lake/build/bin/rocket | pigz -zc | base64 -w0; echo\n"
  },
  {
    "path": "spaceship.sh",
    "chars": 113,
    "preview": "#!/bin/bash\n\nlake build spaceship > /dev/null\necho -n 0; .lake/build/bin/spaceship | pigz -zc | base64 -w0; echo\n"
  }
]

About this extraction

This page contains the full source code of the konne88/functorio GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 49 files (363.7 KB), approximately 119.6k tokens, and a symbol index with 2 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!