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-gas-barrel", inputs := [(1, .petroleumGasBarrel)], outputs := [(1, .barrel), (50, .petroleumGas)], category := .craftingWithFluid time := 1/5 } | .emptySulfuricAcidBarrel => { name := "empty-sulfuric-acid-barrel", inputs := [(1, .sulfuricAcidBarrel)], outputs := [(1, .barrel), (50, .sulfuricAcid)], category := .craftingWithFluid time := 1/5 } | .emptyWaterBarrel => { name := "empty-water-barrel", inputs := [(1, .waterBarrel)], outputs := [(1, .barrel), (50, .water)], category := .craftingWithFluid time := 1/5 } | .energyShieldEquipment => { name := "energy-shield-equipment", inputs := [(5, .advancedCircuit), (10, .steelPlate)], outputs := [(1, .energyShieldEquipment)], category := .crafting time := 10 } | .energyShieldEquipmentRecycling => { name := "energy-shield-equipment-recycling", inputs := [(1, .energyShieldEquipment)], outputs := [(5/2, .steelPlate), (5/4, .advancedCircuit)], category := .recycling time := 5/8 } | .energyShieldMk2Equipment => { name := "energy-shield-mk2-equipment", inputs := [(10, .energyShieldEquipment), (5, .processingUnit), (5, .lowDensityStructure)], outputs := [(1, .energyShieldMk2Equipment)], category := .crafting time := 10 } | .energyShieldMk2EquipmentRecycling => { name := "energy-shield-mk2-equipment-recycling", inputs := [(1, .energyShieldMk2Equipment)], outputs := [(5/2, .energyShieldEquipment), (5/4, .processingUnit), (5/4, .lowDensityStructure)], category := .recycling time := 5/8 } | .engineUnit => { name := "engine-unit", inputs := [(1, .steelPlate), (1, .ironGearWheel), (2, .pipe)], outputs := [(1, .engineUnit)], category := .advancedCrafting time := 10 } | .engineUnitRecycling => { name := "engine-unit-recycling", inputs := [(1, .engineUnit)], outputs := [(1/2, .pipe), (1/4, .steelPlate), (1/4, .ironGearWheel)], category := .recycling time := 5/8 } | .exoskeletonEquipment => { name := "exoskeleton-equipment", inputs := [(10, .processingUnit), (30, .electricEngineUnit), (20, .steelPlate)], outputs := [(1, .exoskeletonEquipment)], category := .crafting time := 10 } | .exoskeletonEquipmentRecycling => { name := "exoskeleton-equipment-recycling", inputs := [(1, .exoskeletonEquipment)], outputs := [(15/2, .electricEngineUnit), (5, .steelPlate), (5/2, .processingUnit)], category := .recycling time := 5/8 } | .explosiveCannonShell => { name := "explosive-cannon-shell", inputs := [(2, .steelPlate), (2, .plasticBar), (2, .explosives)], outputs := [(1, .explosiveCannonShell)], category := .crafting time := 8 } | .explosiveCannonShellRecycling => { name := "explosive-cannon-shell-recycling", inputs := [(1, .explosiveCannonShell)], outputs := [(1/2, .steelPlate), (1/2, .plasticBar), (1/2, .explosives)], category := .recycling time := 1/2 } | .explosiveRocket => { name := "explosive-rocket", inputs := [(1, .rocket), (2, .explosives)], outputs := [(1, .explosiveRocket)], category := .crafting time := 8 } | .explosiveRocketRecycling => { name := "explosive-rocket-recycling", inputs := [(1, .explosiveRocket)], outputs := [(1/2, .explosives), (1/4, .rocket)], category := .recycling time := 1/2 } | .explosiveUraniumCannonShell => { name := "explosive-uranium-cannon-shell", inputs := [(1, .explosiveCannonShell), (1, .uranium238)], outputs := [(1, .explosiveUraniumCannonShell)], category := .crafting time := 12 } | .explosiveUraniumCannonShellRecycling => { name := "explosive-uranium-cannon-shell-recycling", inputs := [(1, .explosiveUraniumCannonShell)], outputs := [(1/4, .explosiveCannonShell), (1/4, .uranium238)], category := .recycling time := 3/4 } | .explosives => { name := "explosives", inputs := [(10, .water), (1, .sulfur), (1, .coal)], outputs := [(2, .explosives)], category := .chemistryOrCryogenics time := 4 } | .explosivesRecycling => { name := "explosives-recycling", inputs := [(1, .explosives)], outputs := [(1/4, .explosives)], category := .recycling time := 1/4 } | .expressLoader => { name := "express-loader", inputs := [(5, .expressTransportBelt), (1, .fastLoader)], outputs := [(1, .expressLoader)], category := .crafting time := 10 } | .expressLoaderRecycling => { name := "express-loader-recycling", inputs := [(1, .expressLoader)], outputs := [(5/4, .expressTransportBelt), (1/4, .fastLoader)], category := .recycling time := 5/8 } | .expressSplitter => { name := "express-splitter", inputs := [(80, .lubricant), (1, .fastSplitter), (10, .ironGearWheel), (10, .advancedCircuit)], outputs := [(1, .expressSplitter)], category := .craftingWithFluidOrMetallurgy time := 2 } | .expressSplitterRecycling => { name := "express-splitter-recycling", inputs := [(1, .expressSplitter)], outputs := [(5/2, .ironGearWheel), (5/2, .advancedCircuit), (1/4, .fastSplitter)], category := .recycling time := 1/8 } | .expressTransportBelt => { name := "express-transport-belt", inputs := [(20, .lubricant), (10, .ironGearWheel), (1, .fastTransportBelt)], outputs := [(1, .expressTransportBelt)], category := .craftingWithFluidOrMetallurgy time := 1/2 } | .expressTransportBeltRecycling => { name := "express-transport-belt-recycling", inputs := [(1, .expressTransportBelt)], outputs := [(5/2, .ironGearWheel), (1/4, .fastTransportBelt)], category := .recycling time := 1/32 } | .expressUndergroundBelt => { name := "express-underground-belt", inputs := [(40, .lubricant), (80, .ironGearWheel), (2, .fastUndergroundBelt)], outputs := [(2, .expressUndergroundBelt)], category := .craftingWithFluidOrMetallurgy time := 2 } | .expressUndergroundBeltRecycling => { name := "express-underground-belt-recycling", inputs := [(1, .expressUndergroundBelt)], outputs := [(10, .ironGearWheel), (1/4, .fastUndergroundBelt)], category := .recycling time := 1/8 } | .fastInserter => { name := "fast-inserter", inputs := [(2, .electronicCircuit), (2, .ironPlate), (1, .inserter)], outputs := [(1, .fastInserter)], category := .crafting time := 1/2 } | .fastInserterRecycling => { name := "fast-inserter-recycling", inputs := [(1, .fastInserter)], outputs := [(1/2, .electronicCircuit), (1/2, .ironPlate), (1/4, .inserter)], category := .recycling time := 1/32 } | .fastLoader => { name := "fast-loader", inputs := [(5, .fastTransportBelt), (1, .loader)], outputs := [(1, .fastLoader)], category := .crafting time := 3 } | .fastLoaderRecycling => { name := "fast-loader-recycling", inputs := [(1, .fastLoader)], outputs := [(5/4, .fastTransportBelt), (1/4, .loader)], category := .recycling time := 3/16 } | .fastSplitter => { name := "fast-splitter", inputs := [(1, .splitter), (10, .ironGearWheel), (10, .electronicCircuit)], outputs := [(1, .fastSplitter)], category := .pressing time := 2 } | .fastSplitterRecycling => { name := "fast-splitter-recycling", inputs := [(1, .fastSplitter)], outputs := [(5/2, .ironGearWheel), (5/2, .electronicCircuit), (1/4, .splitter)], category := .recycling time := 1/8 } | .fastTransportBelt => { name := "fast-transport-belt", inputs := [(5, .ironGearWheel), (1, .transportBelt)], outputs := [(1, .fastTransportBelt)], category := .pressing time := 1/2 } | .fastTransportBeltRecycling => { name := "fast-transport-belt-recycling", inputs := [(1, .fastTransportBelt)], outputs := [(5/4, .ironGearWheel), (1/4, .transportBelt)], category := .recycling time := 1/32 } | .fastUndergroundBelt => { name := "fast-underground-belt", inputs := [(40, .ironGearWheel), (2, .undergroundBelt)], outputs := [(2, .fastUndergroundBelt)], category := .pressing time := 2 } | .fastUndergroundBeltRecycling => { name := "fast-underground-belt-recycling", inputs := [(1, .fastUndergroundBelt)], outputs := [(5, .ironGearWheel), (1/4, .undergroundBelt)], category := .recycling time := 1/8 } | .firearmMagazine => { name := "firearm-magazine", inputs := [(4, .ironPlate)], outputs := [(1, .firearmMagazine)], category := .crafting time := 1 } | .firearmMagazineRecycling => { name := "firearm-magazine-recycling", inputs := [(1, .firearmMagazine)], outputs := [(1, .ironPlate)], category := .recycling time := 1/16 } | .fishBreeding => { name := "fish-breeding", inputs := [(100, .water), (2, .rawFish), (100, .nutrients)], outputs := [(3, .rawFish)], category := .organicOrChemistry time := 6 } | .fissionReactorEquipment => { name := "fission-reactor-equipment", inputs := [(200, .processingUnit), (50, .lowDensityStructure), (4, .uraniumFuelCell)], outputs := [(1, .fissionReactorEquipment)], category := .crafting time := 10 } | .fissionReactorEquipmentRecycling => { name := "fission-reactor-equipment-recycling", inputs := [(1, .fissionReactorEquipment)], outputs := [(50, .processingUnit), (25/2, .lowDensityStructure), (1, .uraniumFuelCell)], category := .recycling time := 5/8 } | .flamethrower => { name := "flamethrower", inputs := [(5, .steelPlate), (10, .ironGearWheel)], outputs := [(1, .flamethrower)], category := .crafting time := 10 } | .flamethrowerAmmo => { name := "flamethrower-ammo", inputs := [(100, .crudeOil), (5, .steelPlate)], outputs := [(1, .flamethrowerAmmo)], category := .chemistry time := 6 } | .flamethrowerAmmoRecycling => { name := "flamethrower-ammo-recycling", inputs := [(1, .flamethrowerAmmo)], outputs := [(1/4, .flamethrowerAmmo)], category := .recycling time := 3/8 } | .flamethrowerRecycling => { name := "flamethrower-recycling", inputs := [(1, .flamethrower)], outputs := [(5/2, .ironGearWheel), (5/4, .steelPlate)], category := .recycling time := 5/8 } | .flamethrowerTurret => { name := "flamethrower-turret", inputs := [(30, .steelPlate), (15, .ironGearWheel), (10, .pipe), (5, .engineUnit)], outputs := [(1, .flamethrowerTurret)], category := .crafting time := 20 } | .flamethrowerTurretRecycling => { name := "flamethrower-turret-recycling", inputs := [(1, .flamethrowerTurret)], outputs := [(15/2, .steelPlate), (15/4, .ironGearWheel), (5/2, .pipe), (5/4, .engineUnit)], category := .recycling time := 5/4 } | .fluidWagon => { name := "fluid-wagon", inputs := [(10, .ironGearWheel), (16, .steelPlate), (8, .pipe), (1, .storageTank)], outputs := [(1, .fluidWagon)], category := .crafting time := 3/2 } | .fluidWagonRecycling => { name := "fluid-wagon-recycling", inputs := [(1, .fluidWagon)], outputs := [(4, .steelPlate), (5/2, .ironGearWheel), (2, .pipe), (1/4, .storageTank)], category := .recycling time := 3/32 } | .fluoroketone => { name := "fluoroketone", inputs := [(50, .fluorine), (50, .ammonia), (1, .solidFuel), (1, .lithium)], outputs := [(50, .fluoroketoneHot)], category := .cryogenics time := 10 } | .fluoroketoneColdBarrel => { name := "fluoroketone-cold-barrel", inputs := [(50, .fluoroketoneCold), (1, .barrel)], outputs := [(1, .fluoroketoneColdBarrel)], category := .craftingWithFluid time := 1/5 } | .fluoroketoneColdBarrelRecycling => { name := "fluoroketone-cold-barrel-recycling", inputs := [(1, .fluoroketoneColdBarrel)], outputs := [(1/4, .barrel)], category := .recycling time := 1/80 } | .fluoroketoneCooling => { name := "fluoroketone-cooling", inputs := [(10, .fluoroketoneHot)], outputs := [(10, .fluoroketoneCold)], category := .cryogenics time := 5 } | .fluoroketoneHotBarrel => { name := "fluoroketone-hot-barrel", inputs := [(50, .fluoroketoneHot), (1, .barrel)], outputs := [(1, .fluoroketoneHotBarrel)], category := .craftingWithFluid time := 1/5 } | .fluoroketoneHotBarrelRecycling => { name := "fluoroketone-hot-barrel-recycling", inputs := [(1, .fluoroketoneHotBarrel)], outputs := [(1/4, .barrel)], category := .recycling time := 1/80 } | .flyingRobotFrame => { name := "flying-robot-frame", inputs := [(1, .electricEngineUnit), (2, .battery), (1, .steelPlate), (3, .electronicCircuit)], outputs := [(1, .flyingRobotFrame)], category := .crafting time := 20 } | .flyingRobotFrameRecycling => { name := "flying-robot-frame-recycling", inputs := [(1, .flyingRobotFrame)], outputs := [(3/4, .electronicCircuit), (1/2, .battery), (1/4, .electricEngineUnit), (1/4, .steelPlate)], category := .recycling time := 5/4 } | .foundation => { name := "foundation", inputs := [(20, .fluoroketoneCold), (4, .tungstenPlate), (4, .lithiumPlate), (4, .carbonFiber), (20, .stone)], outputs := [(1, .foundation)], category := .craftingWithFluid time := 30 } | .foundationRecycling => { name := "foundation-recycling", inputs := [(1, .foundation)], outputs := [(5, .stone), (1, .tungstenPlate), (1, .lithiumPlate), (1, .carbonFiber)], category := .recycling time := 15/8 } | .foundry => { name := "foundry", inputs := [(20, .lubricant), (50, .tungstenCarbide), (50, .steelPlate), (30, .electronicCircuit), (20, .refinedConcrete)], outputs := [(1, .foundry)], category := .metallurgyOrAssembling time := 10 } | .foundryRecycling => { name := "foundry-recycling", inputs := [(1, .foundry)], outputs := [(25/2, .tungstenCarbide), (25/2, .steelPlate), (15/2, .electronicCircuit), (5, .refinedConcrete)], category := .recycling time := 5/8 } | .fusionGenerator => { name := "fusion-generator", inputs := [(100, .tungstenPlate), (100, .superconductor), (50, .quantumProcessor)], outputs := [(1, .fusionGenerator)], category := .cryogenics time := 30 } | .fusionGeneratorRecycling => { name := "fusion-generator-recycling", inputs := [(1, .fusionGenerator)], outputs := [(25, .tungstenPlate), (25, .superconductor), (25/2, .quantumProcessor)], category := .recycling time := 15/8 } | .fusionPowerCell => { name := "fusion-power-cell", inputs := [(100, .ammonia), (5, .lithiumPlate), (1, .holmiumPlate)], outputs := [(1, .fusionPowerCell)], category := .cryogenics time := 10 } | .fusionPowerCellRecycling => { name := "fusion-power-cell-recycling", inputs := [(1, .fusionPowerCell)], outputs := [(1/4, .fusionPowerCell)], category := .recycling time := 5/8 } | .fusionReactor => { name := "fusion-reactor", inputs := [(200, .tungstenPlate), (200, .superconductor), (250, .quantumProcessor)], outputs := [(1, .fusionReactor)], category := .cryogenics time := 60 } | .fusionReactorEquipment => { name := "fusion-reactor-equipment", inputs := [(1, .fissionReactorEquipment), (10, .fusionPowerCell), (250, .tungstenPlate), (100, .carbonFiber), (25, .supercapacitor), (250, .quantumProcessor)], outputs := [(1, .fusionReactorEquipment)], category := .crafting time := 30 } | .fusionReactorEquipmentRecycling => { name := "fusion-reactor-equipment-recycling", inputs := [(1, .fusionReactorEquipment)], outputs := [(125/2, .tungstenPlate), (125/2, .quantumProcessor), (25, .carbonFiber), (25/4, .supercapacitor), (5/2, .fusionPowerCell), (1/4, .fissionReactorEquipment)], category := .recycling time := 15/8 } | .fusionReactorRecycling => { name := "fusion-reactor-recycling", inputs := [(1, .fusionReactor)], outputs := [(125/2, .quantumProcessor), (50, .tungstenPlate), (50, .superconductor)], category := .recycling time := 15/4 } | .gate => { name := "gate", inputs := [(1, .stoneWall), (2, .steelPlate), (2, .electronicCircuit)], outputs := [(1, .gate)], category := .crafting time := 1/2 } | .gateRecycling => { name := "gate-recycling", inputs := [(1, .gate)], outputs := [(1/2, .steelPlate), (1/2, .electronicCircuit), (1/4, .stoneWall)], category := .recycling time := 1/32 } | .grenade => { name := "grenade", inputs := [(5, .ironPlate), (10, .coal)], outputs := [(1, .grenade)], category := .crafting time := 8 } | .grenadeRecycling => { name := "grenade-recycling", inputs := [(1, .grenade)], outputs := [(5/2, .coal), (5/4, .ironPlate)], category := .recycling time := 1/2 } | .gunTurret => { name := "gun-turret", inputs := [(10, .ironGearWheel), (10, .copperPlate), (20, .ironPlate)], outputs := [(1, .gunTurret)], category := .crafting time := 8 } | .gunTurretRecycling => { name := "gun-turret-recycling", inputs := [(1, .gunTurret)], outputs := [(5, .ironPlate), (5/2, .ironGearWheel), (5/2, .copperPlate)], category := .recycling time := 1/2 } | .hazardConcrete => { name := "hazard-concrete", inputs := [(10, .concrete)], outputs := [(10, .hazardConcrete)], category := .crafting time := 1/4 } | .hazardConcreteRecycling => { name := "hazard-concrete-recycling", inputs := [(1, .hazardConcrete)], outputs := [(1/4, .concrete)], category := .recycling time := 1/64 } | .heatExchanger => { name := "heat-exchanger", inputs := [(10, .steelPlate), (100, .copperPlate), (10, .pipe)], outputs := [(1, .heatExchanger)], category := .crafting time := 3 } | .heatExchangerRecycling => { name := "heat-exchanger-recycling", inputs := [(1, .heatExchanger)], outputs := [(25, .copperPlate), (5/2, .steelPlate), (5/2, .pipe)], category := .recycling time := 3/16 } | .heatInterface => { name := "heat-interface", inputs := [(1, .heatPipe), (5, .electronicCircuit)], outputs := [(1, .heatInterface)], category := .crafting time := 1/2 } | .heatInterfaceRecycling => { name := "heat-interface-recycling", inputs := [(1, .heatInterface)], outputs := [(5/4, .electronicCircuit), (1/4, .heatPipe)], category := .recycling time := 1/32 } | .heatPipe => { name := "heat-pipe", inputs := [(10, .steelPlate), (20, .copperPlate)], outputs := [(1, .heatPipe)], category := .crafting time := 1 } | .heatPipeRecycling => { name := "heat-pipe-recycling", inputs := [(1, .heatPipe)], outputs := [(5, .copperPlate), (5/2, .steelPlate)], category := .recycling time := 1/16 } | .heatingTower => { name := "heating-tower", inputs := [(2, .boiler), (5, .heatPipe), (20, .concrete)], outputs := [(1, .heatingTower)], category := .crafting time := 10 } | .heatingTowerRecycling => { name := "heating-tower-recycling", inputs := [(1, .heatingTower)], outputs := [(5, .concrete), (5/4, .heatPipe), (1/2, .boiler)], category := .recycling time := 5/8 } | .heavyArmor => { name := "heavy-armor", inputs := [(100, .copperPlate), (50, .steelPlate)], outputs := [(1, .heavyArmor)], category := .crafting time := 8 } | .heavyArmorRecycling => { name := "heavy-armor-recycling", inputs := [(1, .heavyArmor)], outputs := [(25, .copperPlate), (25/2, .steelPlate)], category := .recycling time := 1/2 } | .heavyOilBarrel => { name := "heavy-oil-barrel", inputs := [(50, .heavyOil), (1, .barrel)], outputs := [(1, .heavyOilBarrel)], category := .craftingWithFluid time := 1/5 } | .heavyOilBarrelRecycling => { name := "heavy-oil-barrel-recycling", inputs := [(1, .heavyOilBarrel)], outputs := [(1/4, .barrel)], category := .recycling time := 1/80 } | .heavyOilCracking => { name := "heavy-oil-cracking", inputs := [(30, .water), (40, .heavyOil)], outputs := [(30, .lightOil)], category := .organicOrChemistry time := 2 } | .holmiumOreRecycling => { name := "holmium-ore-recycling", inputs := [(1, .holmiumOre)], outputs := [(1/4, .holmiumOre)], category := .recycling time := 1/32 } | .holmiumPlate => { name := "holmium-plate", inputs := [(20, .holmiumSolution)], outputs := [(1, .holmiumPlate)], category := .craftingWithFluidOrMetallurgy time := 1 } | .holmiumPlateRecycling => { name := "holmium-plate-recycling", inputs := [(1, .holmiumPlate)], outputs := [(1/4, .holmiumPlate)], category := .recycling time := 1/16 } | .holmiumSolution => { name := "holmium-solution", inputs := [(10, .water), (2, .holmiumOre), (1, .stone)], outputs := [(100, .holmiumSolution)], category := .chemistry time := 10 } | .iceMelting => { name := "ice-melting", inputs := [(1, .ice)], outputs := [(20, .water)], category := .chemistry time := 1 } | .icePlatform => { name := "ice-platform", inputs := [(400, .ammonia), (50, .ice)], outputs := [(1, .icePlatform)], category := .craftingWithFluid time := 30 } | .icePlatformRecycling => { name := "ice-platform-recycling", inputs := [(1, .icePlatform)], outputs := [(25/2, .ice)], category := .recycling time := 15/8 } | .iceRecycling => { name := "ice-recycling", inputs := [(1, .ice)], outputs := [(1/4, .ice)], category := .recycling time := 1/32 } | .infinityCargoWagonRecycling => { name := "infinity-cargo-wagon-recycling", inputs := [(1, .infinityCargoWagon)], outputs := [(1/4, .infinityCargoWagon)], category := .recycling time := 1/32 } | .infinityChest => { name := "infinity-chest", inputs := [(1, .steelChest), (5, .electronicCircuit)], outputs := [(1, .infinityChest)], category := .crafting time := 1/2 } | .infinityChestRecycling => { name := "infinity-chest-recycling", inputs := [(1, .infinityChest)], outputs := [(5/4, .electronicCircuit), (1/4, .steelChest)], category := .recycling time := 1/32 } | .infinityPipe => { name := "infinity-pipe", inputs := [(1, .pipe), (5, .electronicCircuit)], outputs := [(1, .infinityPipe)], category := .crafting time := 1/2 } | .infinityPipeRecycling => { name := "infinity-pipe-recycling", inputs := [(1, .infinityPipe)], outputs := [(5/4, .electronicCircuit), (1/4, .pipe)], category := .recycling time := 1/32 } | .inserter => { name := "inserter", inputs := [(1, .electronicCircuit), (1, .ironGearWheel), (1, .ironPlate)], outputs := [(1, .inserter)], category := .crafting time := 1/2 } | .inserterRecycling => { name := "inserter-recycling", inputs := [(1, .inserter)], outputs := [(1/4, .electronicCircuit), (1/4, .ironGearWheel), (1/4, .ironPlate)], category := .recycling time := 1/32 } | .ironBacteria => { name := "iron-bacteria", inputs := [(6, .jelly)], outputs := [(4, .spoilage), (1/10, .ironBacteria)], category := .organicOrHandCrafting time := 1 } | .ironBacteriaCultivation => { name := "iron-bacteria-cultivation", inputs := [(1, .ironBacteria), (1, .bioflux)], outputs := [(4, .ironBacteria)], category := .organic time := 4 } | .ironBacteriaRecycling => { name := "iron-bacteria-recycling", inputs := [(1, .ironBacteria)], outputs := [(1/4, .ironBacteria)], category := .recycling time := 1/16 } | .ironChest => { name := "iron-chest", inputs := [(8, .ironPlate)], outputs := [(1, .ironChest)], category := .crafting time := 1/2 } | .ironChestRecycling => { name := "iron-chest-recycling", inputs := [(1, .ironChest)], outputs := [(2, .ironPlate)], category := .recycling time := 1/32 } | .ironGearWheel => { name := "iron-gear-wheel", inputs := [(2, .ironPlate)], outputs := [(1, .ironGearWheel)], category := .crafting time := 1/2 } | .ironGearWheelRecycling => { name := "iron-gear-wheel-recycling", inputs := [(1, .ironGearWheel)], outputs := [(1/2, .ironPlate)], category := .recycling time := 1/32 } | .ironOreRecycling => { name := "iron-ore-recycling", inputs := [(1, .ironOre)], outputs := [(1/4, .ironOre)], category := .recycling time := 1/32 } | .ironPlate => { name := "iron-plate", inputs := [(1, .ironOre)], outputs := [(1, .ironPlate)], category := .smelting time := 16/5 } | .ironPlateRecycling => { name := "iron-plate-recycling", inputs := [(1, .ironPlate)], outputs := [(1/4, .ironPlate)], category := .recycling time := 1/5 } | .ironStick => { name := "iron-stick", inputs := [(1, .ironPlate)], outputs := [(2, .ironStick)], category := .crafting time := 1/2 } | .ironStickRecycling => { name := "iron-stick-recycling", inputs := [(1, .ironStick)], outputs := [(1/8, .ironPlate)], category := .recycling time := 1/32 } | .itemUnknownRecycling => { name := "item-unknown-recycling", inputs := [(1, .itemUnknown)], outputs := [(1/4, .itemUnknown)], category := .recycling time := 1/32 } | .jellyRecycling => { name := "jelly-recycling", inputs := [(1, .jelly)], outputs := [(1/4, .jelly)], category := .recycling time := 1/32 } | .jellynutProcessing => { name := "jellynut-processing", inputs := [(1, .jellynut)], outputs := [(4, .jelly), (1/50, .jellynutSeed)], category := .organicOrHandCrafting time := 1 } | .jellynutRecycling => { name := "jellynut-recycling", inputs := [(1, .jellynut)], outputs := [(1/4, .jellynut)], category := .recycling time := 1/32 } | .jellynutSeedRecycling => { name := "jellynut-seed-recycling", inputs := [(1, .jellynutSeed)], outputs := [(1/4, .jellynutSeed)], category := .recycling time := 1/32 } | .kovarexEnrichmentProcess => { name := "kovarex-enrichment-process", inputs := [(40, .uranium235), (5, .uranium238)], outputs := [(41, .uranium235), (2, .uranium238)], category := .centrifuging time := 60 } | .lab => { name := "lab", inputs := [(10, .electronicCircuit), (10, .ironGearWheel), (4, .transportBelt)], outputs := [(1, .lab)], category := .crafting time := 2 } | .labRecycling => { name := "lab-recycling", inputs := [(1, .lab)], outputs := [(5/2, .electronicCircuit), (5/2, .ironGearWheel), (1, .transportBelt)], category := .recycling time := 1/8 } | .landMine => { name := "land-mine", inputs := [(1, .steelPlate), (2, .explosives)], outputs := [(4, .landMine)], category := .crafting time := 5 } | .landMineRecycling => { name := "land-mine-recycling", inputs := [(1, .landMine)], outputs := [(1/8, .explosives), (1/16, .steelPlate)], category := .recycling time := 5/16 } | .landfill => { name := "landfill", inputs := [(50, .stone)], outputs := [(1, .landfill)], category := .crafting time := 1/2 } | .landfillRecycling => { name := "landfill-recycling", inputs := [(1, .landfill)], outputs := [(1/4, .landfill)], category := .recycling time := 1/32 } | .laneSplitterRecycling => { name := "lane-splitter-recycling", inputs := [(1, .laneSplitter)], outputs := [(1/4, .laneSplitter)], category := .recycling time := 1/32 } | .laserTurret => { name := "laser-turret", inputs := [(20, .steelPlate), (20, .electronicCircuit), (12, .battery)], outputs := [(1, .laserTurret)], category := .crafting time := 20 } | .laserTurretRecycling => { name := "laser-turret-recycling", inputs := [(1, .laserTurret)], outputs := [(5, .steelPlate), (5, .electronicCircuit), (3, .battery)], category := .recycling time := 5/4 } | .lightArmor => { name := "light-armor", inputs := [(40, .ironPlate)], outputs := [(1, .lightArmor)], category := .crafting time := 3 } | .lightArmorRecycling => { name := "light-armor-recycling", inputs := [(1, .lightArmor)], outputs := [(10, .ironPlate)], category := .recycling time := 3/16 } | .lightOilBarrel => { name := "light-oil-barrel", inputs := [(50, .lightOil), (1, .barrel)], outputs := [(1, .lightOilBarrel)], category := .craftingWithFluid time := 1/5 } | .lightOilBarrelRecycling => { name := "light-oil-barrel-recycling", inputs := [(1, .lightOilBarrel)], outputs := [(1/4, .barrel)], category := .recycling time := 1/80 } | .lightOilCracking => { name := "light-oil-cracking", inputs := [(30, .water), (30, .lightOil)], outputs := [(20, .petroleumGas)], category := .organicOrChemistry time := 2 } | .lightningCollector => { name := "lightning-collector", inputs := [(80, .electrolyte), (1, .lightningRod), (8, .supercapacitor), (1, .accumulator)], outputs := [(1, .lightningCollector)], category := .electromagnetics time := 5 } | .lightningCollectorRecycling => { name := "lightning-collector-recycling", inputs := [(1, .lightningCollector)], outputs := [(2, .supercapacitor), (1/4, .lightningRod), (1/4, .accumulator)], category := .recycling time := 5/16 } | .lightningRod => { name := "lightning-rod", inputs := [(12, .copperCable), (8, .steelPlate), (4, .stoneBrick)], outputs := [(1, .lightningRod)], category := .electronics time := 5 } | .lightningRodRecycling => { name := "lightning-rod-recycling", inputs := [(1, .lightningRod)], outputs := [(3, .copperCable), (2, .steelPlate), (1, .stoneBrick)], category := .recycling time := 5/16 } | .linkedBeltRecycling => { name := "linked-belt-recycling", inputs := [(1, .linkedBelt)], outputs := [(1/4, .linkedBelt)], category := .recycling time := 1/32 } | .linkedChestRecycling => { name := "linked-chest-recycling", inputs := [(1, .linkedChest)], outputs := [(1/4, .linkedChest)], category := .recycling time := 1/32 } | .lithium => { name := "lithium", inputs := [(50, .lithiumBrine), (50, .ammonia), (1, .holmiumPlate)], outputs := [(5, .lithium)], category := .chemistryOrCryogenics time := 20 } | .lithiumPlate => { name := "lithium-plate", inputs := [(1, .lithium)], outputs := [(1, .lithiumPlate)], category := .smelting time := 32/5 } | .lithiumPlateRecycling => { name := "lithium-plate-recycling", inputs := [(1, .lithiumPlate)], outputs := [(1/4, .lithiumPlate)], category := .recycling time := 2/5 } | .lithiumRecycling => { name := "lithium-recycling", inputs := [(1, .lithium)], outputs := [(1/4, .lithium)], category := .recycling time := 5/4 } | .loader => { name := "loader", inputs := [(5, .inserter), (5, .electronicCircuit), (5, .ironGearWheel), (5, .ironPlate), (5, .transportBelt)], outputs := [(1, .loader)], category := .crafting time := 1 } | .loaderRecycling => { name := "loader-recycling", inputs := [(1, .loader)], outputs := [(5/4, .inserter), (5/4, .electronicCircuit), (5/4, .ironGearWheel), (5/4, .ironPlate), (5/4, .transportBelt)], category := .recycling time := 1/16 } | .locomotive => { name := "locomotive", inputs := [(20, .engineUnit), (10, .electronicCircuit), (30, .steelPlate)], outputs := [(1, .locomotive)], category := .crafting time := 4 } | .locomotiveRecycling => { name := "locomotive-recycling", inputs := [(1, .locomotive)], outputs := [(15/2, .steelPlate), (5, .engineUnit), (5/2, .electronicCircuit)], category := .recycling time := 1/4 } | .logisticRobot => { name := "logistic-robot", inputs := [(1, .flyingRobotFrame), (2, .advancedCircuit)], outputs := [(1, .logisticRobot)], category := .crafting time := 1/2 } | .logisticRobotRecycling => { name := "logistic-robot-recycling", inputs := [(1, .logisticRobot)], outputs := [(1/2, .advancedCircuit), (1/4, .flyingRobotFrame)], category := .recycling time := 1/32 } | .logisticSciencePack => { name := "logistic-science-pack", inputs := [(1, .inserter), (1, .transportBelt)], outputs := [(1, .logisticSciencePack)], category := .crafting time := 6 } | .logisticSciencePackRecycling => { name := "logistic-science-pack-recycling", inputs := [(1, .logisticSciencePack)], outputs := [(1/4, .logisticSciencePack)], category := .recycling time := 3/8 } | .longHandedInserter => { name := "long-handed-inserter", inputs := [(1, .ironGearWheel), (1, .ironPlate), (1, .inserter)], outputs := [(1, .longHandedInserter)], category := .crafting time := 1/2 } | .longHandedInserterRecycling => { name := "long-handed-inserter-recycling", inputs := [(1, .longHandedInserter)], outputs := [(1/4, .ironGearWheel), (1/4, .ironPlate), (1/4, .inserter)], category := .recycling time := 1/32 } | .lowDensityStructure => { name := "low-density-structure", inputs := [(2, .steelPlate), (20, .copperPlate), (5, .plasticBar)], outputs := [(1, .lowDensityStructure)], category := .crafting time := 15 } | .lowDensityStructureRecycling => { name := "low-density-structure-recycling", inputs := [(1, .lowDensityStructure)], outputs := [(5, .copperPlate), (5/4, .plasticBar), (1/2, .steelPlate)], category := .recycling time := 15/16 } | .lubricant => { name := "lubricant", inputs := [(10, .heavyOil)], outputs := [(10, .lubricant)], category := .chemistry time := 1 } | .lubricantBarrel => { name := "lubricant-barrel", inputs := [(50, .lubricant), (1, .barrel)], outputs := [(1, .lubricantBarrel)], category := .craftingWithFluid time := 1/5 } | .lubricantBarrelRecycling => { name := "lubricant-barrel-recycling", inputs := [(1, .lubricantBarrel)], outputs := [(1/4, .barrel)], category := .recycling time := 1/80 } | .mechArmor => { name := "mech-armor", inputs := [(1, .powerArmorMk2), (200, .holmiumPlate), (100, .processingUnit), (50, .superconductor), (50, .supercapacitor)], outputs := [(1, .mechArmor)], category := .crafting time := 60 } | .mechArmorRecycling => { name := "mech-armor-recycling", inputs := [(1, .mechArmor)], outputs := [(50, .holmiumPlate), (25, .processingUnit), (25/2, .superconductor), (25/2, .supercapacitor), (1/4, .powerArmorMk2)], category := .recycling time := 15/4 } | .mediumElectricPole => { name := "medium-electric-pole", inputs := [(4, .ironStick), (2, .steelPlate), (2, .copperCable)], outputs := [(1, .mediumElectricPole)], category := .electronics time := 1/2 } | .mediumElectricPoleRecycling => { name := "medium-electric-pole-recycling", inputs := [(1, .mediumElectricPole)], outputs := [(1, .ironStick), (1/2, .steelPlate), (1/2, .copperCable)], category := .recycling time := 1/32 } | .metallicAsteroidChunkRecycling => { name := "metallic-asteroid-chunk-recycling", inputs := [(1, .metallicAsteroidChunk)], outputs := [(1/4, .metallicAsteroidChunk)], category := .recycling time := 1/32 } | .metallicAsteroidCrushing => { name := "metallic-asteroid-crushing", inputs := [(1, .metallicAsteroidChunk)], outputs := [(20, .ironOre), (1/5, .metallicAsteroidChunk)], category := .crushing time := 2 } | .metallicAsteroidReprocessing => { name := "metallic-asteroid-reprocessing", inputs := [(1, .metallicAsteroidChunk)], outputs := [(2/5, .metallicAsteroidChunk), (1/5, .carbonicAsteroidChunk), (1/5, .oxideAsteroidChunk)], category := .crushing time := 2 } | .metallurgicSciencePack => { name := "metallurgic-science-pack", inputs := [(200, .moltenCopper), (3, .tungstenCarbide), (2, .tungstenPlate)], outputs := [(1, .metallurgicSciencePack)], category := .metallurgy time := 10 } | .metallurgicSciencePackRecycling => { name := "metallurgic-science-pack-recycling", inputs := [(1, .metallurgicSciencePack)], outputs := [(1/4, .metallurgicSciencePack)], category := .recycling time := 5/8 } | .militarySciencePack => { name := "military-science-pack", inputs := [(1, .piercingRoundsMagazine), (1, .grenade), (2, .stoneWall)], outputs := [(2, .militarySciencePack)], category := .crafting time := 10 } | .militarySciencePackRecycling => { name := "military-science-pack-recycling", inputs := [(1, .militarySciencePack)], outputs := [(1/4, .militarySciencePack)], category := .recycling time := 5/8 } | .modularArmor => { name := "modular-armor", inputs := [(30, .advancedCircuit), (50, .steelPlate)], outputs := [(1, .modularArmor)], category := .crafting time := 15 } | .modularArmorRecycling => { name := "modular-armor-recycling", inputs := [(1, .modularArmor)], outputs := [(25/2, .steelPlate), (15/2, .advancedCircuit)], category := .recycling time := 15/16 } | .moltenCopper => { name := "molten-copper", inputs := [(50, .copperOre), (1, .calcite)], outputs := [(500, .moltenCopper)], category := .metallurgy time := 32 } | .moltenCopperFromLava => { name := "molten-copper-from-lava", inputs := [(500, .lava), (1, .calcite)], outputs := [(15, .stone), (250, .moltenCopper)], category := .metallurgy time := 16 } | .moltenIron => { name := "molten-iron", inputs := [(50, .ironOre), (1, .calcite)], outputs := [(500, .moltenIron)], category := .metallurgy time := 32 } | .moltenIronFromLava => { name := "molten-iron-from-lava", inputs := [(500, .lava), (1, .calcite)], outputs := [(10, .stone), (250, .moltenIron)], category := .metallurgy time := 16 } | .nightVisionEquipment => { name := "night-vision-equipment", inputs := [(5, .advancedCircuit), (10, .steelPlate)], outputs := [(1, .nightVisionEquipment)], category := .crafting time := 10 } | .nightVisionEquipmentRecycling => { name := "night-vision-equipment-recycling", inputs := [(1, .nightVisionEquipment)], outputs := [(5/2, .steelPlate), (5/4, .advancedCircuit)], category := .recycling time := 5/8 } | .nuclearFuel => { name := "nuclear-fuel", inputs := [(1, .uranium235), (1, .rocketFuel)], outputs := [(1, .nuclearFuel)], category := .centrifuging time := 90 } | .nuclearFuelRecycling => { name := "nuclear-fuel-recycling", inputs := [(1, .nuclearFuel)], outputs := [(1/4, .uranium235), (1/4, .rocketFuel)], category := .recycling time := 45/8 } | .nuclearFuelReprocessing => { name := "nuclear-fuel-reprocessing", inputs := [(5, .depletedUraniumFuelCell)], outputs := [(3, .uranium238)], category := .centrifuging time := 60 } | .nuclearReactor => { name := "nuclear-reactor", inputs := [(500, .concrete), (500, .steelPlate), (500, .advancedCircuit), (500, .copperPlate)], outputs := [(1, .nuclearReactor)], category := .crafting time := 8 } | .nuclearReactorRecycling => { name := "nuclear-reactor-recycling", inputs := [(1, .nuclearReactor)], outputs := [(125, .concrete), (125, .steelPlate), (125, .advancedCircuit), (125, .copperPlate)], category := .recycling time := 1/2 } | .nutrientsFromBioflux => { name := "nutrients-from-bioflux", inputs := [(5, .bioflux)], outputs := [(40, .nutrients)], category := .organic time := 2 } | .nutrientsFromBiterEgg => { name := "nutrients-from-biter-egg", inputs := [(1, .biterEgg)], outputs := [(20, .nutrients)], category := .organicOrAssembling time := 2 } | .nutrientsFromFish => { name := "nutrients-from-fish", inputs := [(1, .rawFish)], outputs := [(20, .nutrients)], category := .organicOrAssembling time := 2 } | .nutrientsFromSpoilage => { name := "nutrients-from-spoilage", inputs := [(10, .spoilage)], outputs := [(1, .nutrients)], category := .organicOrAssembling time := 2 } | .nutrientsFromYumakoMash => { name := "nutrients-from-yumako-mash", inputs := [(4, .yumakoMash)], outputs := [(6, .nutrients)], category := .organic time := 4 } | .nutrientsRecycling => { name := "nutrients-recycling", inputs := [(1, .nutrients)], outputs := [(5/2, .spoilage)], category := .recycling time := 1/8 } | .offshorePump => { name := "offshore-pump", inputs := [(3, .pipe), (2, .ironGearWheel)], outputs := [(1, .offshorePump)], category := .crafting time := 1/2 } | .offshorePumpRecycling => { name := "offshore-pump-recycling", inputs := [(1, .offshorePump)], outputs := [(3/4, .pipe), (1/2, .ironGearWheel)], category := .recycling time := 1/32 } | .oilRefinery => { name := "oil-refinery", inputs := [(15, .steelPlate), (10, .ironGearWheel), (10, .stoneBrick), (10, .electronicCircuit), (10, .pipe)], outputs := [(1, .oilRefinery)], category := .crafting time := 8 } | .oilRefineryRecycling => { name := "oil-refinery-recycling", inputs := [(1, .oilRefinery)], outputs := [(15/4, .steelPlate), (5/2, .ironGearWheel), (5/2, .stoneBrick), (5/2, .electronicCircuit), (5/2, .pipe)], category := .recycling time := 1/2 } | .oneWayValveRecycling => { name := "one-way-valve-recycling", inputs := [(1, .oneWayValve)], outputs := [(1/4, .oneWayValve)], category := .recycling time := 1/32 } | .overflowValveRecycling => { name := "overflow-valve-recycling", inputs := [(1, .overflowValve)], outputs := [(1/4, .overflowValve)], category := .recycling time := 1/32 } | .overgrowthJellynutSoil => { name := "overgrowth-jellynut-soil", inputs := [(100, .water), (2, .artificialJellynutSoil), (5, .jellynutSeed), (10, .biterEgg), (50, .spoilage)], outputs := [(1, .overgrowthJellynutSoil)], category := .craftingWithFluid time := 10 } | .overgrowthJellynutSoilRecycling => { name := "overgrowth-jellynut-soil-recycling", inputs := [(1, .overgrowthJellynutSoil)], outputs := [(25/2, .spoilage), (5/2, .biterEgg), (5/4, .jellynutSeed), (1/2, .artificialJellynutSoil)], category := .recycling time := 5/8 } | .overgrowthYumakoSoil => { name := "overgrowth-yumako-soil", inputs := [(100, .water), (2, .artificialYumakoSoil), (5, .yumakoSeed), (10, .biterEgg), (50, .spoilage)], outputs := [(1, .overgrowthYumakoSoil)], category := .craftingWithFluid time := 10 } | .overgrowthYumakoSoilRecycling => { name := "overgrowth-yumako-soil-recycling", inputs := [(1, .overgrowthYumakoSoil)], outputs := [(25/2, .spoilage), (5/2, .biterEgg), (5/4, .yumakoSeed), (1/2, .artificialYumakoSoil)], category := .recycling time := 5/8 } | .oxideAsteroidChunkRecycling => { name := "oxide-asteroid-chunk-recycling", inputs := [(1, .oxideAsteroidChunk)], outputs := [(1/4, .oxideAsteroidChunk)], category := .recycling time := 1/32 } | .oxideAsteroidCrushing => { name := "oxide-asteroid-crushing", inputs := [(1, .oxideAsteroidChunk)], outputs := [(5, .ice), (1/5, .oxideAsteroidChunk)], category := .crushing time := 2 } | .oxideAsteroidReprocessing => { name := "oxide-asteroid-reprocessing", inputs := [(1, .oxideAsteroidChunk)], outputs := [(2/5, .oxideAsteroidChunk), (1/5, .metallicAsteroidChunk), (1/5, .carbonicAsteroidChunk)], category := .crushing time := 1 } | .parameter0 => { name := "parameter-0", inputs := [], outputs := [], category := .parameters time := 1/2 } | .parameter1 => { name := "parameter-1", inputs := [], outputs := [], category := .parameters time := 1/2 } | .parameter2 => { name := "parameter-2", inputs := [], outputs := [], category := .parameters time := 1/2 } | .parameter3 => { name := "parameter-3", inputs := [], outputs := [], category := .parameters time := 1/2 } | .parameter4 => { name := "parameter-4", inputs := [], outputs := [], category := .parameters time := 1/2 } | .parameter5 => { name := "parameter-5", inputs := [], outputs := [], category := .parameters time := 1/2 } | .parameter6 => { name := "parameter-6", inputs := [], outputs := [], category := .parameters time := 1/2 } | .parameter7 => { name := "parameter-7", inputs := [], outputs := [], category := .parameters time := 1/2 } | .parameter8 => { name := "parameter-8", inputs := [], outputs := [], category := .parameters time := 1/2 } | .parameter9 => { name := "parameter-9", inputs := [], outputs := [], category := .parameters time := 1/2 } | .passiveProviderChest => { name := "passive-provider-chest", inputs := [(1, .steelChest), (3, .electronicCircuit), (1, .advancedCircuit)], outputs := [(1, .passiveProviderChest)], category := .crafting time := 1/2 } | .passiveProviderChestRecycling => { name := "passive-provider-chest-recycling", inputs := [(1, .passiveProviderChest)], outputs := [(3/4, .electronicCircuit), (1/4, .steelChest), (1/4, .advancedCircuit)], category := .recycling time := 1/32 } | .pentapodEgg => { name := "pentapod-egg", inputs := [(60, .water), (1, .pentapodEgg), (30, .nutrients)], outputs := [(2, .pentapodEgg)], category := .organic time := 15 } | .pentapodEggRecycling => { name := "pentapod-egg-recycling", inputs := [(1, .pentapodEgg)], outputs := [(1/4, .pentapodEgg)], category := .recycling time := 15/16 } | .personalLaserDefenseEquipment => { name := "personal-laser-defense-equipment", inputs := [(20, .processingUnit), (5, .lowDensityStructure), (5, .laserTurret)], outputs := [(1, .personalLaserDefenseEquipment)], category := .crafting time := 10 } | .personalLaserDefenseEquipmentRecycling => { name := "personal-laser-defense-equipment-recycling", inputs := [(1, .personalLaserDefenseEquipment)], outputs := [(5, .processingUnit), (5/4, .lowDensityStructure), (5/4, .laserTurret)], category := .recycling time := 5/8 } | .personalRoboportEquipment => { name := "personal-roboport-equipment", inputs := [(10, .advancedCircuit), (40, .ironGearWheel), (20, .steelPlate), (45, .battery)], outputs := [(1, .personalRoboportEquipment)], category := .crafting time := 10 } | .personalRoboportEquipmentRecycling => { name := "personal-roboport-equipment-recycling", inputs := [(1, .personalRoboportEquipment)], outputs := [(45/4, .battery), (10, .ironGearWheel), (5, .steelPlate), (5/2, .advancedCircuit)], category := .recycling time := 5/8 } | .personalRoboportMk2Equipment => { name := "personal-roboport-mk2-equipment", inputs := [(5, .personalRoboportEquipment), (50, .processingUnit), (50, .superconductor)], outputs := [(1, .personalRoboportMk2Equipment)], category := .crafting time := 20 } | .personalRoboportMk2EquipmentRecycling => { name := "personal-roboport-mk2-equipment-recycling", inputs := [(1, .personalRoboportMk2Equipment)], outputs := [(25/2, .processingUnit), (25/2, .superconductor), (5/4, .personalRoboportEquipment)], category := .recycling time := 5/4 } | .petroleumGasBarrel => { name := "petroleum-gas-barrel", inputs := [(50, .petroleumGas), (1, .barrel)], outputs := [(1, .petroleumGasBarrel)], category := .craftingWithFluid time := 1/5 } | .petroleumGasBarrelRecycling => { name := "petroleum-gas-barrel-recycling", inputs := [(1, .petroleumGasBarrel)], outputs := [(1/4, .barrel)], category := .recycling time := 1/80 } | .piercingRoundsMagazine => { name := "piercing-rounds-magazine", inputs := [(2, .firearmMagazine), (1, .steelPlate), (2, .copperPlate)], outputs := [(2, .piercingRoundsMagazine)], category := .crafting time := 6 } | .piercingRoundsMagazineRecycling => { name := "piercing-rounds-magazine-recycling", inputs := [(1, .piercingRoundsMagazine)], outputs := [(1/4, .firearmMagazine), (1/4, .copperPlate), (1/8, .steelPlate)], category := .recycling time := 3/8 } | .piercingShotgunShell => { name := "piercing-shotgun-shell", inputs := [(2, .shotgunShell), (5, .copperPlate), (2, .steelPlate)], outputs := [(1, .piercingShotgunShell)], category := .crafting time := 8 } | .piercingShotgunShellRecycling => { name := "piercing-shotgun-shell-recycling", inputs := [(1, .piercingShotgunShell)], outputs := [(5/4, .copperPlate), (1/2, .shotgunShell), (1/2, .steelPlate)], category := .recycling time := 1/2 } | .pipe => { name := "pipe", inputs := [(1, .ironPlate)], outputs := [(1, .pipe)], category := .crafting time := 1/2 } | .pipeRecycling => { name := "pipe-recycling", inputs := [(1, .pipe)], outputs := [(1/4, .ironPlate)], category := .recycling time := 1/32 } | .pipeToGround => { name := "pipe-to-ground", inputs := [(10, .pipe), (5, .ironPlate)], outputs := [(2, .pipeToGround)], category := .crafting time := 1/2 } | .pipeToGroundRecycling => { name := "pipe-to-ground-recycling", inputs := [(1, .pipeToGround)], outputs := [(5/4, .pipe), (5/8, .ironPlate)], category := .recycling time := 1/32 } | .pistol => { name := "pistol", inputs := [(5, .copperPlate), (5, .ironPlate)], outputs := [(1, .pistol)], category := .crafting time := 5 } | .pistolRecycling => { name := "pistol-recycling", inputs := [(1, .pistol)], outputs := [(5/4, .copperPlate), (5/4, .ironPlate)], category := .recycling time := 5/16 } | .plasticBar => { name := "plastic-bar", inputs := [(20, .petroleumGas), (1, .coal)], outputs := [(2, .plasticBar)], category := .chemistryOrCryogenics time := 1 } | .plasticBarRecycling => { name := "plastic-bar-recycling", inputs := [(1, .plasticBar)], outputs := [(1/4, .plasticBar)], category := .recycling time := 1/16 } | .poisonCapsule => { name := "poison-capsule", inputs := [(3, .steelPlate), (3, .electronicCircuit), (10, .coal)], outputs := [(1, .poisonCapsule)], category := .crafting time := 8 } | .poisonCapsuleRecycling => { name := "poison-capsule-recycling", inputs := [(1, .poisonCapsule)], outputs := [(5/2, .coal), (3/4, .steelPlate), (3/4, .electronicCircuit)], category := .recycling time := 1/2 } | .powerArmor => { name := "power-armor", inputs := [(40, .processingUnit), (20, .electricEngineUnit), (40, .steelPlate)], outputs := [(1, .powerArmor)], category := .crafting time := 20 } | .powerArmorMk2 => { name := "power-armor-mk2", inputs := [(100, .efficiencyModule), (100, .speedModule), (60, .processingUnit), (40, .electricEngineUnit), (30, .lowDensityStructure)], outputs := [(1, .powerArmorMk2)], category := .crafting time := 25 } | .powerArmorMk2Recycling => { name := "power-armor-mk2-recycling", inputs := [(1, .powerArmorMk2)], outputs := [(25, .efficiencyModule), (25, .speedModule), (15, .processingUnit), (10, .electricEngineUnit), (15/2, .lowDensityStructure)], category := .recycling time := 25/16 } | .powerArmorRecycling => { name := "power-armor-recycling", inputs := [(1, .powerArmor)], outputs := [(10, .processingUnit), (10, .steelPlate), (5, .electricEngineUnit)], category := .recycling time := 5/4 } | .powerSwitch => { name := "power-switch", inputs := [(5, .ironPlate), (5, .copperCable), (2, .electronicCircuit)], outputs := [(1, .powerSwitch)], category := .crafting time := 2 } | .powerSwitchRecycling => { name := "power-switch-recycling", inputs := [(1, .powerSwitch)], outputs := [(5/4, .ironPlate), (5/4, .copperCable), (1/2, .electronicCircuit)], category := .recycling time := 1/8 } | .processingUnit => { name := "processing-unit", inputs := [(5, .sulfuricAcid), (20, .electronicCircuit), (2, .advancedCircuit)], outputs := [(1, .processingUnit)], category := .electronicsWithFluid time := 10 } | .processingUnitRecycling => { name := "processing-unit-recycling", inputs := [(1, .processingUnit)], outputs := [(5, .electronicCircuit), (1/2, .advancedCircuit)], category := .recycling time := 5/8 } | .productionSciencePack => { name := "production-science-pack", inputs := [(1, .electricFurnace), (1, .productivityModule), (30, .rail)], outputs := [(3, .productionSciencePack)], category := .crafting time := 21 } | .productionSciencePackRecycling => { name := "production-science-pack-recycling", inputs := [(1, .productionSciencePack)], outputs := [(1/4, .productionSciencePack)], category := .recycling time := 21/16 } | .productivityModule => { name := "productivity-module", inputs := [(5, .advancedCircuit), (5, .electronicCircuit)], outputs := [(1, .productivityModule)], category := .electronics time := 15 } | .productivityModule2 => { name := "productivity-module-2", inputs := [(4, .productivityModule), (5, .advancedCircuit), (5, .processingUnit)], outputs := [(1, .productivityModule2)], category := .electronics time := 30 } | .productivityModule2Recycling => { name := "productivity-module-2-recycling", inputs := [(1, .productivityModule2)], outputs := [(5/4, .advancedCircuit), (5/4, .processingUnit), (1, .productivityModule)], category := .recycling time := 15/8 } | .productivityModule3 => { name := "productivity-module-3", inputs := [(4, .productivityModule2), (5, .advancedCircuit), (5, .processingUnit), (1, .biterEgg)], outputs := [(1, .productivityModule3)], category := .electronics time := 60 } | .productivityModule3Recycling => { name := "productivity-module-3-recycling", inputs := [(1, .productivityModule3)], outputs := [(5/4, .advancedCircuit), (5/4, .processingUnit), (1, .productivityModule2), (1/4, .biterEgg)], category := .recycling time := 15/4 } | .productivityModuleRecycling => { name := "productivity-module-recycling", inputs := [(1, .productivityModule)], outputs := [(5/4, .advancedCircuit), (5/4, .electronicCircuit)], category := .recycling time := 15/16 } | .programmableSpeaker => { name := "programmable-speaker", inputs := [(3, .ironPlate), (4, .ironStick), (5, .copperCable), (4, .electronicCircuit)], outputs := [(1, .programmableSpeaker)], category := .crafting time := 2 } | .programmableSpeakerRecycling => { name := "programmable-speaker-recycling", inputs := [(1, .programmableSpeaker)], outputs := [(5/4, .copperCable), (1, .ironStick), (1, .electronicCircuit), (3/4, .ironPlate)], category := .recycling time := 1/8 } | .promethiumAsteroidChunkRecycling => { name := "promethium-asteroid-chunk-recycling", inputs := [(1, .promethiumAsteroidChunk)], outputs := [(1/4, .promethiumAsteroidChunk)], category := .recycling time := 1/32 } | .promethiumSciencePack => { name := "promethium-science-pack", inputs := [(25, .promethiumAsteroidChunk), (1, .quantumProcessor), (10, .biterEgg)], outputs := [(10, .promethiumSciencePack)], category := .cryogenics time := 5 } | .promethiumSciencePackRecycling => { name := "promethium-science-pack-recycling", inputs := [(1, .promethiumSciencePack)], outputs := [(1/4, .promethiumSciencePack)], category := .recycling time := 5/16 } | .proxyContainerRecycling => { name := "proxy-container-recycling", inputs := [(1, .proxyContainer)], outputs := [(1/4, .proxyContainer)], category := .recycling time := 1/32 } | .pump => { name := "pump", inputs := [(1, .engineUnit), (1, .steelPlate), (1, .pipe)], outputs := [(1, .pump)], category := .crafting time := 2 } | .pumpRecycling => { name := "pump-recycling", inputs := [(1, .pump)], outputs := [(1/4, .engineUnit), (1/4, .steelPlate), (1/4, .pipe)], category := .recycling time := 1/8 } | .pumpjack => { name := "pumpjack", inputs := [(5, .steelPlate), (10, .ironGearWheel), (5, .electronicCircuit), (10, .pipe)], outputs := [(1, .pumpjack)], category := .crafting time := 5 } | .pumpjackRecycling => { name := "pumpjack-recycling", inputs := [(1, .pumpjack)], outputs := [(5/2, .ironGearWheel), (5/2, .pipe), (5/4, .steelPlate), (5/4, .electronicCircuit)], category := .recycling time := 5/16 } | .qualityModule => { name := "quality-module", inputs := [(5, .electronicCircuit), (5, .advancedCircuit)], outputs := [(1, .qualityModule)], category := .electronics time := 15 } | .qualityModule2 => { name := "quality-module-2", inputs := [(4, .qualityModule), (5, .advancedCircuit), (5, .processingUnit)], outputs := [(1, .qualityModule2)], category := .electronics time := 30 } | .qualityModule2Recycling => { name := "quality-module-2-recycling", inputs := [(1, .qualityModule2)], outputs := [(5/4, .advancedCircuit), (5/4, .processingUnit), (1, .qualityModule)], category := .recycling time := 15/8 } | .qualityModule3 => { name := "quality-module-3", inputs := [(4, .qualityModule2), (5, .advancedCircuit), (5, .processingUnit), (1, .superconductor)], outputs := [(1, .qualityModule3)], category := .electronics time := 60 } | .qualityModule3Recycling => { name := "quality-module-3-recycling", inputs := [(1, .qualityModule3)], outputs := [(5/4, .advancedCircuit), (5/4, .processingUnit), (1, .qualityModule2), (1/4, .superconductor)], category := .recycling time := 15/4 } | .qualityModuleRecycling => { name := "quality-module-recycling", inputs := [(1, .qualityModule)], outputs := [(5/4, .electronicCircuit), (5/4, .advancedCircuit)], category := .recycling time := 15/16 } | .quantumProcessor => { name := "quantum-processor", inputs := [(10, .fluoroketoneCold), (1, .tungstenCarbide), (1, .processingUnit), (1, .superconductor), (1, .carbonFiber), (2, .lithiumPlate)], outputs := [(1, .quantumProcessor), (5, .fluoroketoneHot)], category := .electromagnetics time := 30 } | .quantumProcessorRecycling => { name := "quantum-processor-recycling", inputs := [(1, .quantumProcessor)], outputs := [(1/2, .lithiumPlate), (1/4, .tungstenCarbide), (1/4, .processingUnit), (1/4, .superconductor), (1/4, .carbonFiber)], category := .recycling time := 15/8 } | .radar => { name := "radar", inputs := [(5, .electronicCircuit), (5, .ironGearWheel), (10, .ironPlate)], outputs := [(1, .radar)], category := .crafting time := 1/2 } | .radarRecycling => { name := "radar-recycling", inputs := [(1, .radar)], outputs := [(5/2, .ironPlate), (5/4, .electronicCircuit), (5/4, .ironGearWheel)], category := .recycling time := 1/32 } | .rail => { name := "rail", inputs := [(1, .stone), (1, .ironStick), (1, .steelPlate)], outputs := [(2, .rail)], category := .crafting time := 1/2 } | .railChainSignal => { name := "rail-chain-signal", inputs := [(1, .electronicCircuit), (5, .ironPlate)], outputs := [(1, .railChainSignal)], category := .crafting time := 1/2 } | .railChainSignalRecycling => { name := "rail-chain-signal-recycling", inputs := [(1, .railChainSignal)], outputs := [(5/4, .ironPlate), (1/4, .electronicCircuit)], category := .recycling time := 1/32 } | .railRamp => { name := "rail-ramp", inputs := [(100, .refinedConcrete), (8, .rail), (10, .steelPlate)], outputs := [(1, .railRamp)], category := .crafting time := 1/2 } | .railRampRecycling => { name := "rail-ramp-recycling", inputs := [(1, .railRamp)], outputs := [(25, .refinedConcrete), (5/2, .steelPlate), (2, .rail)], category := .recycling time := 1/32 } | .railRecycling => { name := "rail-recycling", inputs := [(1, .rail)], outputs := [(1/8, .stone), (1/8, .ironStick), (1/8, .steelPlate)], category := .recycling time := 1/32 } | .railSignal => { name := "rail-signal", inputs := [(1, .electronicCircuit), (5, .ironPlate)], outputs := [(1, .railSignal)], category := .crafting time := 1/2 } | .railSignalRecycling => { name := "rail-signal-recycling", inputs := [(1, .railSignal)], outputs := [(5/4, .ironPlate), (1/4, .electronicCircuit)], category := .recycling time := 1/32 } | .railSupport => { name := "rail-support", inputs := [(20, .refinedConcrete), (10, .steelPlate)], outputs := [(1, .railSupport)], category := .crafting time := 1/2 } | .railSupportRecycling => { name := "rail-support-recycling", inputs := [(1, .railSupport)], outputs := [(5, .refinedConcrete), (5/2, .steelPlate)], category := .recycling time := 1/32 } | .railgun => { name := "railgun", inputs := [(10, .fluoroketoneCold), (10, .tungstenPlate), (10, .superconductor), (20, .quantumProcessor)], outputs := [(1, .railgun)], category := .cryogenics time := 10 } | .railgunAmmo => { name := "railgun-ammo", inputs := [(5, .steelPlate), (10, .copperCable), (2, .explosives)], outputs := [(1, .railgunAmmo)], category := .crafting time := 25 } | .railgunAmmoRecycling => { name := "railgun-ammo-recycling", inputs := [(1, .railgunAmmo)], outputs := [(5/2, .copperCable), (5/4, .steelPlate), (1/2, .explosives)], category := .recycling time := 25/16 } | .railgunRecycling => { name := "railgun-recycling", inputs := [(1, .railgun)], outputs := [(5, .quantumProcessor), (5/2, .tungstenPlate), (5/2, .superconductor)], category := .recycling time := 5/8 } | .railgunTurret => { name := "railgun-turret", inputs := [(100, .fluoroketoneCold), (100, .quantumProcessor), (30, .tungstenPlate), (50, .superconductor), (20, .carbonFiber)], outputs := [(1, .railgunTurret)], category := .cryogenics time := 10 } | .railgunTurretRecycling => { name := "railgun-turret-recycling", inputs := [(1, .railgunTurret)], outputs := [(25, .quantumProcessor), (25/2, .superconductor), (15/2, .tungstenPlate), (5, .carbonFiber)], category := .recycling time := 5/8 } | .rawFishRecycling => { name := "raw-fish-recycling", inputs := [(1, .rawFish)], outputs := [(1/4, .rawFish)], category := .recycling time := 1/32 } | .recycler => { name := "recycler", inputs := [(6, .processingUnit), (20, .steelPlate), (40, .ironGearWheel), (20, .concrete)], outputs := [(1, .recycler)], category := .crafting time := 3 } | .recyclerRecycling => { name := "recycler-recycling", inputs := [(1, .recycler)], outputs := [(10, .ironGearWheel), (5, .steelPlate), (5, .concrete), (3/2, .processingUnit)], category := .recycling time := 3/16 } | .refinedConcrete => { name := "refined-concrete", inputs := [(100, .water), (20, .concrete), (8, .ironStick), (1, .steelPlate)], outputs := [(10, .refinedConcrete)], category := .craftingWithFluid time := 15 } | .refinedConcreteRecycling => { name := "refined-concrete-recycling", inputs := [(1, .refinedConcrete)], outputs := [(1/2, .concrete), (1/5, .ironStick), (1/40, .steelPlate)], category := .recycling time := 15/16 } | .refinedHazardConcrete => { name := "refined-hazard-concrete", inputs := [(10, .refinedConcrete)], outputs := [(10, .refinedHazardConcrete)], category := .crafting time := 1/4 } | .refinedHazardConcreteRecycling => { name := "refined-hazard-concrete-recycling", inputs := [(1, .refinedHazardConcrete)], outputs := [(1/4, .refinedConcrete)], category := .recycling time := 1/64 } | .repairPack => { name := "repair-pack", inputs := [(2, .electronicCircuit), (2, .ironGearWheel)], outputs := [(1, .repairPack)], category := .crafting time := 1/2 } | .repairPackRecycling => { name := "repair-pack-recycling", inputs := [(1, .repairPack)], outputs := [(1/2, .electronicCircuit), (1/2, .ironGearWheel)], category := .recycling time := 1/32 } | .requesterChest => { name := "requester-chest", inputs := [(1, .steelChest), (3, .electronicCircuit), (1, .advancedCircuit)], outputs := [(1, .requesterChest)], category := .crafting time := 1/2 } | .requesterChestRecycling => { name := "requester-chest-recycling", inputs := [(1, .requesterChest)], outputs := [(3/4, .electronicCircuit), (1/4, .steelChest), (1/4, .advancedCircuit)], category := .recycling time := 1/32 } | .roboport => { name := "roboport", inputs := [(45, .steelPlate), (45, .ironGearWheel), (45, .advancedCircuit)], outputs := [(1, .roboport)], category := .crafting time := 5 } | .roboportRecycling => { name := "roboport-recycling", inputs := [(1, .roboport)], outputs := [(45/4, .steelPlate), (45/4, .ironGearWheel), (45/4, .advancedCircuit)], category := .recycling time := 5/16 } | .rocket => { name := "rocket", inputs := [(1, .explosives), (2, .ironPlate)], outputs := [(1, .rocket)], category := .crafting time := 4 } | .rocketFuel => { name := "rocket-fuel", inputs := [(10, .lightOil), (10, .solidFuel)], outputs := [(1, .rocketFuel)], category := .organicOrAssembling time := 15 } | .rocketFuelFromJelly => { name := "rocket-fuel-from-jelly", inputs := [(30, .water), (30, .jelly), (2, .bioflux)], outputs := [(1, .rocketFuel)], category := .organic time := 10 } | .rocketFuelRecycling => { name := "rocket-fuel-recycling", inputs := [(1, .rocketFuel)], outputs := [(5/2, .solidFuel)], category := .recycling time := 15/16 } | .rocketLauncher => { name := "rocket-launcher", inputs := [(5, .ironPlate), (5, .ironGearWheel), (5, .electronicCircuit)], outputs := [(1, .rocketLauncher)], category := .crafting time := 10 } | .rocketLauncherRecycling => { name := "rocket-launcher-recycling", inputs := [(1, .rocketLauncher)], outputs := [(5/4, .ironPlate), (5/4, .ironGearWheel), (5/4, .electronicCircuit)], category := .recycling time := 5/8 } | .rocketPart => { name := "rocket-part", inputs := [(1, .processingUnit), (1, .lowDensityStructure), (1, .rocketFuel)], outputs := [], category := .rocketBuilding time := 3 } | .rocketRecycling => { name := "rocket-recycling", inputs := [(1, .rocket)], outputs := [(1/2, .ironPlate), (1/4, .explosives)], category := .recycling time := 1/4 } | .rocketSilo => { name := "rocket-silo", inputs := [(1000, .steelPlate), (1000, .concrete), (100, .pipe), (200, .processingUnit), (200, .electricEngineUnit)], outputs := [(1, .rocketSilo)], category := .crafting time := 30 } | .rocketSiloRecycling => { name := "rocket-silo-recycling", inputs := [(1, .rocketSilo)], outputs := [(250, .steelPlate), (250, .concrete), (50, .processingUnit), (50, .electricEngineUnit), (25, .pipe)], category := .recycling time := 15/8 } | .rocketTurret => { name := "rocket-turret", inputs := [(4, .rocketLauncher), (4, .processingUnit), (20, .carbonFiber), (20, .steelPlate), (20, .ironGearWheel)], outputs := [(1, .rocketTurret)], category := .crafting time := 10 } | .rocketTurretRecycling => { name := "rocket-turret-recycling", inputs := [(1, .rocketTurret)], outputs := [(5, .carbonFiber), (5, .steelPlate), (5, .ironGearWheel), (1, .rocketLauncher), (1, .processingUnit)], category := .recycling time := 5/8 } | .scienceRecycling => { name := "science-recycling", inputs := [(1, .science)], outputs := [(1/4, .science)], category := .recycling time := 1/32 } | .scrapRecycling => { name := "scrap-recycling", inputs := [(1, .scrap)], outputs := [(1/5, .ironGearWheel), (7/100, .solidFuel), (3/50, .concrete), (1/20, .ice), (1/25, .steelPlate), (1/25, .battery), (1/25, .stone), (3/100, .advancedCircuit), (3/100, .copperCable), (1/50, .processingUnit), (1/100, .lowDensityStructure), (1/100, .holmiumOre)], category := .recyclingOrHandCrafting time := 1/5 } | .selectionToolRecycling => { name := "selection-tool-recycling", inputs := [(1, .selectionTool)], outputs := [(1/4, .selectionTool)], category := .recycling time := 1/32 } | .selectorCombinator => { name := "selector-combinator", inputs := [(2, .advancedCircuit), (5, .deciderCombinator)], outputs := [(1, .selectorCombinator)], category := .crafting time := 1/2 } | .selectorCombinatorRecycling => { name := "selector-combinator-recycling", inputs := [(1, .selectorCombinator)], outputs := [(5/4, .deciderCombinator), (1/2, .advancedCircuit)], category := .recycling time := 1/32 } | .shotgun => { name := "shotgun", inputs := [(15, .ironPlate), (5, .ironGearWheel), (10, .copperPlate), (5, .wood)], outputs := [(1, .shotgun)], category := .crafting time := 10 } | .shotgunRecycling => { name := "shotgun-recycling", inputs := [(1, .shotgun)], outputs := [(15/4, .ironPlate), (5/2, .copperPlate), (5/4, .ironGearWheel), (5/4, .wood)], category := .recycling time := 5/8 } | .shotgunShell => { name := "shotgun-shell", inputs := [(2, .copperPlate), (2, .ironPlate)], outputs := [(1, .shotgunShell)], category := .crafting time := 3 } | .shotgunShellRecycling => { name := "shotgun-shell-recycling", inputs := [(1, .shotgunShell)], outputs := [(1/2, .copperPlate), (1/2, .ironPlate)], category := .recycling time := 3/16 } | .simpleCoalLiquefaction => { name := "simple-coal-liquefaction", inputs := [(25, .sulfuricAcid), (10, .coal), (2, .calcite)], outputs := [(50, .heavyOil)], category := .oilProcessing time := 5 } | .simpleEntityWithForceRecycling => { name := "simple-entity-with-force-recycling", inputs := [(1, .simpleEntityWithForce)], outputs := [(1/4, .simpleEntityWithForce)], category := .recycling time := 1/32 } | .simpleEntityWithOwnerRecycling => { name := "simple-entity-with-owner-recycling", inputs := [(1, .simpleEntityWithOwner)], outputs := [(1/4, .simpleEntityWithOwner)], category := .recycling time := 1/32 } | .slowdownCapsule => { name := "slowdown-capsule", inputs := [(2, .steelPlate), (2, .electronicCircuit), (5, .coal)], outputs := [(1, .slowdownCapsule)], category := .crafting time := 8 } | .slowdownCapsuleRecycling => { name := "slowdown-capsule-recycling", inputs := [(1, .slowdownCapsule)], outputs := [(5/4, .coal), (1/2, .steelPlate), (1/2, .electronicCircuit)], category := .recycling time := 1/2 } | .smallElectricPole => { name := "small-electric-pole", inputs := [(1, .wood), (2, .copperCable)], outputs := [(2, .smallElectricPole)], category := .electronics time := 1/2 } | .smallElectricPoleRecycling => { name := "small-electric-pole-recycling", inputs := [(1, .smallElectricPole)], outputs := [(1/4, .copperCable), (1/8, .wood)], category := .recycling time := 1/32 } | .smallLamp => { name := "small-lamp", inputs := [(1, .electronicCircuit), (3, .copperCable), (1, .ironPlate)], outputs := [(1, .smallLamp)], category := .crafting time := 1/2 } | .smallLampRecycling => { name := "small-lamp-recycling", inputs := [(1, .smallLamp)], outputs := [(3/4, .copperCable), (1/4, .electronicCircuit), (1/4, .ironPlate)], category := .recycling time := 1/32 } | .solarPanel => { name := "solar-panel", inputs := [(5, .steelPlate), (15, .electronicCircuit), (5, .copperPlate)], outputs := [(1, .solarPanel)], category := .electronics time := 10 } | .solarPanelEquipment => { name := "solar-panel-equipment", inputs := [(1, .solarPanel), (2, .advancedCircuit), (5, .steelPlate)], outputs := [(1, .solarPanelEquipment)], category := .crafting time := 10 } | .solarPanelEquipmentRecycling => { name := "solar-panel-equipment-recycling", inputs := [(1, .solarPanelEquipment)], outputs := [(5/4, .steelPlate), (1/2, .advancedCircuit), (1/4, .solarPanel)], category := .recycling time := 5/8 } | .solarPanelRecycling => { name := "solar-panel-recycling", inputs := [(1, .solarPanel)], outputs := [(15/4, .electronicCircuit), (5/4, .steelPlate), (5/4, .copperPlate)], category := .recycling time := 5/8 } | .solidFuelFromAmmonia => { name := "solid-fuel-from-ammonia", inputs := [(15, .ammonia), (6, .crudeOil)], outputs := [(1, .solidFuel)], category := .chemistryOrCryogenics time := 1/2 } | .solidFuelFromHeavyOil => { name := "solid-fuel-from-heavy-oil", inputs := [(20, .heavyOil)], outputs := [(1, .solidFuel)], category := .chemistry time := 1 } | .solidFuelFromLightOil => { name := "solid-fuel-from-light-oil", inputs := [(10, .lightOil)], outputs := [(1, .solidFuel)], category := .chemistry time := 1 } | .solidFuelFromPetroleumGas => { name := "solid-fuel-from-petroleum-gas", inputs := [(20, .petroleumGas)], outputs := [(1, .solidFuel)], category := .chemistry time := 1 } | .solidFuelRecycling => { name := "solid-fuel-recycling", inputs := [(1, .solidFuel)], outputs := [(1/4, .solidFuel)], category := .recycling time := 1/32 } | .spacePlatformFoundation => { name := "space-platform-foundation", inputs := [(20, .steelPlate), (20, .copperCable)], outputs := [(1, .spacePlatformFoundation)], category := .crafting time := 10 } | .spacePlatformFoundationRecycling => { name := "space-platform-foundation-recycling", inputs := [(1, .spacePlatformFoundation)], outputs := [(5, .steelPlate), (5, .copperCable)], category := .recycling time := 5/8 } | .spacePlatformHubRecycling => { name := "space-platform-hub-recycling", inputs := [(1, .spacePlatformHub)], outputs := [(1/4, .spacePlatformHub)], category := .recycling time := 1/32 } | .spacePlatformStarterPack => { name := "space-platform-starter-pack", inputs := [(60, .spacePlatformFoundation), (20, .steelPlate), (20, .processingUnit)], outputs := [(1, .spacePlatformStarterPack)], category := .crafting time := 60 } | .spacePlatformStarterPackRecycling => { name := "space-platform-starter-pack-recycling", inputs := [(1, .spacePlatformStarterPack)], outputs := [(15, .spacePlatformFoundation), (5, .steelPlate), (5, .processingUnit)], category := .recycling time := 15/4 } | .spaceSciencePack => { name := "space-science-pack", inputs := [(2, .ironPlate), (1, .carbon), (1, .ice)], outputs := [(5, .spaceSciencePack)], category := .crafting time := 15 } | .spaceSciencePackRecycling => { name := "space-science-pack-recycling", inputs := [(1, .spaceSciencePack)], outputs := [(1/4, .spaceSciencePack)], category := .recycling time := 15/16 } | .speedModule => { name := "speed-module", inputs := [(5, .advancedCircuit), (5, .electronicCircuit)], outputs := [(1, .speedModule)], category := .electronics time := 15 } | .speedModule2 => { name := "speed-module-2", inputs := [(4, .speedModule), (5, .advancedCircuit), (5, .processingUnit)], outputs := [(1, .speedModule2)], category := .electronics time := 30 } | .speedModule2Recycling => { name := "speed-module-2-recycling", inputs := [(1, .speedModule2)], outputs := [(5/4, .advancedCircuit), (5/4, .processingUnit), (1, .speedModule)], category := .recycling time := 15/8 } | .speedModule3 => { name := "speed-module-3", inputs := [(4, .speedModule2), (5, .advancedCircuit), (5, .processingUnit), (1, .tungstenCarbide)], outputs := [(1, .speedModule3)], category := .electronics time := 60 } | .speedModule3Recycling => { name := "speed-module-3-recycling", inputs := [(1, .speedModule3)], outputs := [(5/4, .advancedCircuit), (5/4, .processingUnit), (1, .speedModule2), (1/4, .tungstenCarbide)], category := .recycling time := 15/4 } | .speedModuleRecycling => { name := "speed-module-recycling", inputs := [(1, .speedModule)], outputs := [(5/4, .advancedCircuit), (5/4, .electronicCircuit)], category := .recycling time := 15/16 } | .spidertron => { name := "spidertron", inputs := [(4, .exoskeletonEquipment), (2, .fissionReactorEquipment), (1, .rocketTurret), (2, .radar), (1, .rawFish)], outputs := [(1, .spidertron)], category := .crafting time := 10 } | .spidertronRecycling => { name := "spidertron-recycling", inputs := [(1, .spidertron)], outputs := [(1, .exoskeletonEquipment), (1/2, .fissionReactorEquipment), (1/2, .radar), (1/4, .rocketTurret), (1/4, .rawFish)], category := .recycling time := 5/8 } | .splitter => { name := "splitter", inputs := [(5, .electronicCircuit), (5, .ironPlate), (4, .transportBelt)], outputs := [(1, .splitter)], category := .pressing time := 1 } | .splitterRecycling => { name := "splitter-recycling", inputs := [(1, .splitter)], outputs := [(5/4, .electronicCircuit), (5/4, .ironPlate), (1, .transportBelt)], category := .recycling time := 1/16 } | .spoilageRecycling => { name := "spoilage-recycling", inputs := [(1, .spoilage)], outputs := [(1/4, .spoilage)], category := .recycling time := 1/32 } | .stackInserter => { name := "stack-inserter", inputs := [(1, .bulkInserter), (1, .processingUnit), (2, .carbonFiber), (10, .jelly)], outputs := [(1, .stackInserter)], category := .crafting time := 1/2 } | .stackInserterRecycling => { name := "stack-inserter-recycling", inputs := [(1, .stackInserter)], outputs := [(5/2, .jelly), (1/2, .carbonFiber), (1/4, .bulkInserter), (1/4, .processingUnit)], category := .recycling time := 1/32 } | .steamCondensation => { name := "steam-condensation", inputs := [(1000, .steam)], outputs := [(90, .water)], category := .chemistryOrCryogenics time := 1 } | .steamEngine => { name := "steam-engine", inputs := [(8, .ironGearWheel), (5, .pipe), (10, .ironPlate)], outputs := [(1, .steamEngine)], category := .crafting time := 1/2 } | .steamEngineRecycling => { name := "steam-engine-recycling", inputs := [(1, .steamEngine)], outputs := [(5/2, .ironPlate), (2, .ironGearWheel), (5/4, .pipe)], category := .recycling time := 1/32 } | .steamTurbine => { name := "steam-turbine", inputs := [(50, .ironGearWheel), (50, .copperPlate), (20, .pipe)], outputs := [(1, .steamTurbine)], category := .crafting time := 3 } | .steamTurbineRecycling => { name := "steam-turbine-recycling", inputs := [(1, .steamTurbine)], outputs := [(25/2, .ironGearWheel), (25/2, .copperPlate), (5, .pipe)], category := .recycling time := 3/16 } | .steelChest => { name := "steel-chest", inputs := [(8, .steelPlate)], outputs := [(1, .steelChest)], category := .crafting time := 1/2 } | .steelChestRecycling => { name := "steel-chest-recycling", inputs := [(1, .steelChest)], outputs := [(2, .steelPlate)], category := .recycling time := 1/32 } | .steelFurnace => { name := "steel-furnace", inputs := [(6, .steelPlate), (10, .stoneBrick)], outputs := [(1, .steelFurnace)], category := .crafting time := 3 } | .steelFurnaceRecycling => { name := "steel-furnace-recycling", inputs := [(1, .steelFurnace)], outputs := [(5/2, .stoneBrick), (3/2, .steelPlate)], category := .recycling time := 3/16 } | .steelPlate => { name := "steel-plate", inputs := [(5, .ironPlate)], outputs := [(1, .steelPlate)], category := .smelting time := 16 } | .steelPlateRecycling => { name := "steel-plate-recycling", inputs := [(1, .steelPlate)], outputs := [(1/4, .steelPlate)], category := .recycling time := 1 } | .stoneBrick => { name := "stone-brick", inputs := [(2, .stone)], outputs := [(1, .stoneBrick)], category := .smelting time := 16/5 } | .stoneBrickRecycling => { name := "stone-brick-recycling", inputs := [(1, .stoneBrick)], outputs := [(1/4, .stoneBrick)], category := .recycling time := 1/5 } | .stoneFurnace => { name := "stone-furnace", inputs := [(5, .stone)], outputs := [(1, .stoneFurnace)], category := .crafting time := 1/2 } | .stoneFurnaceRecycling => { name := "stone-furnace-recycling", inputs := [(1, .stoneFurnace)], outputs := [(5/4, .stone)], category := .recycling time := 1/32 } | .stoneRecycling => { name := "stone-recycling", inputs := [(1, .stone)], outputs := [(1/4, .stone)], category := .recycling time := 1/32 } | .stoneWall => { name := "stone-wall", inputs := [(5, .stoneBrick)], outputs := [(1, .stoneWall)], category := .crafting time := 1/2 } | .stoneWallRecycling => { name := "stone-wall-recycling", inputs := [(1, .stoneWall)], outputs := [(5/4, .stoneBrick)], category := .recycling time := 1/32 } | .storageChest => { name := "storage-chest", inputs := [(1, .steelChest), (3, .electronicCircuit), (1, .advancedCircuit)], outputs := [(1, .storageChest)], category := .crafting time := 1/2 } | .storageChestRecycling => { name := "storage-chest-recycling", inputs := [(1, .storageChest)], outputs := [(3/4, .electronicCircuit), (1/4, .steelChest), (1/4, .advancedCircuit)], category := .recycling time := 1/32 } | .storageTank => { name := "storage-tank", inputs := [(20, .ironPlate), (5, .steelPlate)], outputs := [(1, .storageTank)], category := .crafting time := 3 } | .storageTankRecycling => { name := "storage-tank-recycling", inputs := [(1, .storageTank)], outputs := [(5, .ironPlate), (5/4, .steelPlate)], category := .recycling time := 3/16 } | .submachineGun => { name := "submachine-gun", inputs := [(10, .ironGearWheel), (5, .copperPlate), (10, .ironPlate)], outputs := [(1, .submachineGun)], category := .crafting time := 10 } | .submachineGunRecycling => { name := "submachine-gun-recycling", inputs := [(1, .submachineGun)], outputs := [(5/2, .ironGearWheel), (5/2, .ironPlate), (5/4, .copperPlate)], category := .recycling time := 5/8 } | .substation => { name := "substation", inputs := [(10, .steelPlate), (5, .advancedCircuit), (6, .copperCable)], outputs := [(1, .substation)], category := .electronics time := 1/2 } | .substationRecycling => { name := "substation-recycling", inputs := [(1, .substation)], outputs := [(5/2, .steelPlate), (3/2, .copperCable), (5/4, .advancedCircuit)], category := .recycling time := 1/32 } | .sulfur => { name := "sulfur", inputs := [(30, .water), (30, .petroleumGas)], outputs := [(2, .sulfur)], category := .chemistryOrCryogenics time := 1 } | .sulfurRecycling => { name := "sulfur-recycling", inputs := [(1, .sulfur)], outputs := [(1/4, .sulfur)], category := .recycling time := 1/16 } | .sulfuricAcid => { name := "sulfuric-acid", inputs := [(100, .water), (5, .sulfur), (1, .ironPlate)], outputs := [(50, .sulfuricAcid)], category := .chemistryOrCryogenics time := 1 } | .sulfuricAcidBarrel => { name := "sulfuric-acid-barrel", inputs := [(50, .sulfuricAcid), (1, .barrel)], outputs := [(1, .sulfuricAcidBarrel)], category := .craftingWithFluid time := 1/5 } | .sulfuricAcidBarrelRecycling => { name := "sulfuric-acid-barrel-recycling", inputs := [(1, .sulfuricAcidBarrel)], outputs := [(1/4, .barrel)], category := .recycling time := 1/80 } | .supercapacitor => { name := "supercapacitor", inputs := [(10, .electrolyte), (2, .holmiumPlate), (2, .superconductor), (4, .electronicCircuit), (1, .battery)], outputs := [(1, .supercapacitor)], category := .electromagnetics time := 10 } | .supercapacitorRecycling => { name := "supercapacitor-recycling", inputs := [(1, .supercapacitor)], outputs := [(1, .electronicCircuit), (1/2, .holmiumPlate), (1/2, .superconductor), (1/4, .battery)], category := .recycling time := 5/8 } | .superconductor => { name := "superconductor", inputs := [(5, .lightOil), (1, .holmiumPlate), (1, .copperPlate), (1, .plasticBar)], outputs := [(2, .superconductor)], category := .electromagnetics time := 5 } | .superconductorRecycling => { name := "superconductor-recycling", inputs := [(1, .superconductor)], outputs := [(1/4, .superconductor)], category := .recycling time := 5/16 } | .tank => { name := "tank", inputs := [(32, .engineUnit), (50, .steelPlate), (15, .ironGearWheel), (10, .advancedCircuit)], outputs := [(1, .tank)], category := .crafting time := 5 } | .tankRecycling => { name := "tank-recycling", inputs := [(1, .tank)], outputs := [(25/2, .steelPlate), (8, .engineUnit), (15/4, .ironGearWheel), (5/2, .advancedCircuit)], category := .recycling time := 5/16 } | .teslaAmmo => { name := "tesla-ammo", inputs := [(10, .electrolyte), (1, .supercapacitor), (1, .plasticBar)], outputs := [(1, .teslaAmmo)], category := .electromagnetics time := 30 } | .teslaAmmoRecycling => { name := "tesla-ammo-recycling", inputs := [(1, .teslaAmmo)], outputs := [(1/4, .supercapacitor), (1/4, .plasticBar)], category := .recycling time := 15/8 } | .teslaTurret => { name := "tesla-turret", inputs := [(500, .electrolyte), (1, .teslagun), (10, .supercapacitor), (10, .processingUnit), (50, .superconductor)], outputs := [(1, .teslaTurret)], category := .electromagnetics time := 30 } | .teslaTurretRecycling => { name := "tesla-turret-recycling", inputs := [(1, .teslaTurret)], outputs := [(25/2, .superconductor), (5/2, .supercapacitor), (5/2, .processingUnit), (1/4, .teslagun)], category := .recycling time := 15/8 } | .teslagun => { name := "teslagun", inputs := [(100, .electrolyte), (10, .holmiumPlate), (10, .superconductor), (30, .plasticBar)], outputs := [(1, .teslagun)], category := .electromagnetics time := 30 } | .teslagunRecycling => { name := "teslagun-recycling", inputs := [(1, .teslagun)], outputs := [(15/2, .plasticBar), (5/2, .holmiumPlate), (5/2, .superconductor)], category := .recycling time := 15/8 } | .thruster => { name := "thruster", inputs := [(10, .steelPlate), (10, .processingUnit), (5, .electricEngineUnit)], outputs := [(1, .thruster)], category := .crafting time := 10 } | .thrusterFuel => { name := "thruster-fuel", inputs := [(10, .water), (2, .carbon)], outputs := [(75, .thrusterFuel)], category := .chemistry time := 2 } | .thrusterOxidizer => { name := "thruster-oxidizer", inputs := [(10, .water), (2, .ironOre)], outputs := [(75, .thrusterOxidizer)], category := .chemistry time := 2 } | .thrusterRecycling => { name := "thruster-recycling", inputs := [(1, .thruster)], outputs := [(5/2, .steelPlate), (5/2, .processingUnit), (5/4, .electricEngineUnit)], category := .recycling time := 5/8 } | .toolbeltEquipment => { name := "toolbelt-equipment", inputs := [(3, .advancedCircuit), (10, .carbonFiber)], outputs := [(1, .toolbeltEquipment)], category := .crafting time := 10 } | .toolbeltEquipmentRecycling => { name := "toolbelt-equipment-recycling", inputs := [(1, .toolbeltEquipment)], outputs := [(5/2, .carbonFiber), (3/4, .advancedCircuit)], category := .recycling time := 5/8 } | .topUpValveRecycling => { name := "top-up-valve-recycling", inputs := [(1, .topUpValve)], outputs := [(1/4, .topUpValve)], category := .recycling time := 1/32 } | .trainStop => { name := "train-stop", inputs := [(5, .electronicCircuit), (6, .ironPlate), (6, .ironStick), (3, .steelPlate)], outputs := [(1, .trainStop)], category := .crafting time := 1/2 } | .trainStopRecycling => { name := "train-stop-recycling", inputs := [(1, .trainStop)], outputs := [(3/2, .ironPlate), (3/2, .ironStick), (5/4, .electronicCircuit), (3/4, .steelPlate)], category := .recycling time := 1/32 } | .transportBelt => { name := "transport-belt", inputs := [(1, .ironPlate), (1, .ironGearWheel)], outputs := [(2, .transportBelt)], category := .pressing time := 1/2 } | .transportBeltRecycling => { name := "transport-belt-recycling", inputs := [(1, .transportBelt)], outputs := [(1/8, .ironPlate), (1/8, .ironGearWheel)], category := .recycling time := 1/32 } | .treeSeedRecycling => { name := "tree-seed-recycling", inputs := [(1, .treeSeed)], outputs := [(1/4, .treeSeed)], category := .recycling time := 1/32 } | .tungstenCarbide => { name := "tungsten-carbide", inputs := [(10, .sulfuricAcid), (2, .tungstenOre), (1, .carbon)], outputs := [(1, .tungstenCarbide)], category := .craftingWithFluid time := 1 } | .tungstenCarbideRecycling => { name := "tungsten-carbide-recycling", inputs := [(1, .tungstenCarbide)], outputs := [(1/4, .tungstenCarbide)], category := .recycling time := 1/16 } | .tungstenOreRecycling => { name := "tungsten-ore-recycling", inputs := [(1, .tungstenOre)], outputs := [(1/4, .tungstenOre)], category := .recycling time := 1/32 } | .tungstenPlate => { name := "tungsten-plate", inputs := [(10, .moltenIron), (4, .tungstenOre)], outputs := [(1, .tungstenPlate)], category := .metallurgy time := 10 } | .tungstenPlateRecycling => { name := "tungsten-plate-recycling", inputs := [(1, .tungstenPlate)], outputs := [(1/4, .tungstenPlate)], category := .recycling time := 5/8 } | .turboLoader => { name := "turbo-loader", inputs := [(5, .turboTransportBelt), (1, .expressLoader)], outputs := [(1, .turboLoader)], category := .crafting time := 20 } | .turboLoaderRecycling => { name := "turbo-loader-recycling", inputs := [(1, .turboLoader)], outputs := [(5/4, .turboTransportBelt), (1/4, .expressLoader)], category := .recycling time := 5/4 } | .turboSplitter => { name := "turbo-splitter", inputs := [(80, .lubricant), (1, .expressSplitter), (15, .tungstenPlate), (2, .processingUnit)], outputs := [(1, .turboSplitter)], category := .metallurgy time := 2 } | .turboSplitterRecycling => { name := "turbo-splitter-recycling", inputs := [(1, .turboSplitter)], outputs := [(15/4, .tungstenPlate), (1/2, .processingUnit), (1/4, .expressSplitter)], category := .recycling time := 1/8 } | .turboTransportBelt => { name := "turbo-transport-belt", inputs := [(20, .lubricant), (5, .tungstenPlate), (1, .expressTransportBelt)], outputs := [(1, .turboTransportBelt)], category := .metallurgy time := 1/2 } | .turboTransportBeltRecycling => { name := "turbo-transport-belt-recycling", inputs := [(1, .turboTransportBelt)], outputs := [(5/4, .tungstenPlate), (1/4, .expressTransportBelt)], category := .recycling time := 1/32 } | .turboUndergroundBelt => { name := "turbo-underground-belt", inputs := [(40, .lubricant), (40, .tungstenPlate), (2, .expressUndergroundBelt)], outputs := [(2, .turboUndergroundBelt)], category := .metallurgy time := 2 } | .turboUndergroundBeltRecycling => { name := "turbo-underground-belt-recycling", inputs := [(1, .turboUndergroundBelt)], outputs := [(5, .tungstenPlate), (1/4, .expressUndergroundBelt)], category := .recycling time := 1/8 } | .undergroundBelt => { name := "underground-belt", inputs := [(10, .ironPlate), (5, .transportBelt)], outputs := [(2, .undergroundBelt)], category := .pressing time := 1 } | .undergroundBeltRecycling => { name := "underground-belt-recycling", inputs := [(1, .undergroundBelt)], outputs := [(5/4, .ironPlate), (5/8, .transportBelt)], category := .recycling time := 1/16 } | .upgradePlannerRecycling => { name := "upgrade-planner-recycling", inputs := [(1, .upgradePlanner)], outputs := [(1/4, .upgradePlanner)], category := .recycling time := 1/32 } | .uranium235Recycling => { name := "uranium-235-recycling", inputs := [(1, .uranium235)], outputs := [(1/4, .uranium235)], category := .recycling time := 1/32 } | .uranium238Recycling => { name := "uranium-238-recycling", inputs := [(1, .uranium238)], outputs := [(1/4, .uranium238)], category := .recycling time := 1/32 } | .uraniumCannonShell => { name := "uranium-cannon-shell", inputs := [(1, .cannonShell), (1, .uranium238)], outputs := [(1, .uraniumCannonShell)], category := .crafting time := 12 } | .uraniumCannonShellRecycling => { name := "uranium-cannon-shell-recycling", inputs := [(1, .uraniumCannonShell)], outputs := [(1/4, .cannonShell), (1/4, .uranium238)], category := .recycling time := 3/4 } | .uraniumFuelCell => { name := "uranium-fuel-cell", inputs := [(10, .ironPlate), (1, .uranium235), (19, .uranium238)], outputs := [(10, .uraniumFuelCell)], category := .crafting time := 10 } | .uraniumFuelCellRecycling => { name := "uranium-fuel-cell-recycling", inputs := [(1, .uraniumFuelCell)], outputs := [(1/4, .uraniumFuelCell)], category := .recycling time := 5/8 } | .uraniumOreRecycling => { name := "uranium-ore-recycling", inputs := [(1, .uraniumOre)], outputs := [(1/4, .uraniumOre)], category := .recycling time := 1/32 } | .uraniumProcessing => { name := "uranium-processing", inputs := [(10, .uraniumOre)], outputs := [(7/1000, .uranium235), (993/1000, .uranium238)], category := .centrifuging time := 12 } | .uraniumRoundsMagazine => { name := "uranium-rounds-magazine", inputs := [(1, .piercingRoundsMagazine), (1, .uranium238)], outputs := [(1, .uraniumRoundsMagazine)], category := .crafting time := 10 } | .uraniumRoundsMagazineRecycling => { name := "uranium-rounds-magazine-recycling", inputs := [(1, .uraniumRoundsMagazine)], outputs := [(1/4, .piercingRoundsMagazine), (1/4, .uranium238)], category := .recycling time := 5/8 } | .utilitySciencePack => { name := "utility-science-pack", inputs := [(3, .lowDensityStructure), (2, .processingUnit), (1, .flyingRobotFrame)], outputs := [(3, .utilitySciencePack)], category := .crafting time := 21 } | .utilitySciencePackRecycling => { name := "utility-science-pack-recycling", inputs := [(1, .utilitySciencePack)], outputs := [(1/4, .utilitySciencePack)], category := .recycling time := 21/16 } | .waterBarrel => { name := "water-barrel", inputs := [(50, .water), (1, .barrel)], outputs := [(1, .waterBarrel)], category := .craftingWithFluid time := 1/5 } | .waterBarrelRecycling => { name := "water-barrel-recycling", inputs := [(1, .waterBarrel)], outputs := [(1/4, .barrel)], category := .recycling time := 1/80 } | .woodProcessing => { name := "wood-processing", inputs := [(2, .wood)], outputs := [(1, .treeSeed)], category := .organicOrAssembling time := 2 } | .woodRecycling => { name := "wood-recycling", inputs := [(1, .wood)], outputs := [(1/4, .wood)], category := .recycling time := 1/32 } | .woodenChest => { name := "wooden-chest", inputs := [(2, .wood)], outputs := [(1, .woodenChest)], category := .crafting time := 1/2 } | .woodenChestRecycling => { name := "wooden-chest-recycling", inputs := [(1, .woodenChest)], outputs := [(1/2, .wood)], category := .recycling time := 1/32 } | .yumakoMashRecycling => { name := "yumako-mash-recycling", inputs := [(1, .yumakoMash)], outputs := [(1/4, .yumakoMash)], category := .recycling time := 1/32 } | .yumakoProcessing => { name := "yumako-processing", inputs := [(1, .yumako)], outputs := [(2, .yumakoMash), (1/50, .yumakoSeed)], category := .organicOrHandCrafting time := 1 } | .yumakoRecycling => { name := "yumako-recycling", inputs := [(1, .yumako)], outputs := [(1/4, .yumako)], category := .recycling time := 1/32 } | .yumakoSeedRecycling => { name := "yumako-seed-recycling", inputs := [(1, .yumakoSeed)], outputs := [(1/4, .yumakoSeed)], category := .recycling time := 1/32 } end RecipeName inductive Fabricator | assemblingMachine1 | assemblingMachine2 | assemblingMachine3 | biochamber | captiveBiterSpawner | centrifuge | chemicalPlant | crusher | cryogenicPlant | electricFurnace | electromagneticPlant | foundry | oilRefinery | recycler | rocketSilo | steelFurnace | stoneFurnace deriving DecidableEq, Repr, Inhabited namespace Fabricator @[simp] def details : Fabricator -> FabricatorDetails | .assemblingMachine1 => { name := "assembling-machine-1" speedup := 1/2 productivity := 0 moduleSlots := 0 width := 3 height := 3 fluidBoxes := [ ] categories := [ .advancedCrafting, .basicCrafting, .crafting, .electronics, .pressing, ] } | .assemblingMachine2 => { name := "assembling-machine-2" speedup := 3/4 productivity := 0 moduleSlots := 2 width := 3 height := 3 fluidBoxes := [ { offset := 1 side := .N type := .input }, { offset := 1 side := .S type := .output }, ] categories := [ .advancedCrafting, .basicCrafting, .crafting, .craftingWithFluid, .craftingWithFluidOrMetallurgy, .cryogenicsOrAssembling, .electronics, .electronicsOrAssembling, .electronicsWithFluid, .metallurgyOrAssembling, .organicOrAssembling, .organicOrHandCrafting, .pressing, ] } | .assemblingMachine3 => { name := "assembling-machine-3" speedup := 5/4 productivity := 0 moduleSlots := 4 width := 3 height := 3 fluidBoxes := [ { offset := 1 side := .N type := .input }, { offset := 1 side := .S type := .output }, ] categories := [ .advancedCrafting, .basicCrafting, .crafting, .craftingWithFluid, .craftingWithFluidOrMetallurgy, .cryogenicsOrAssembling, .electronics, .electronicsOrAssembling, .electronicsWithFluid, .metallurgyOrAssembling, .organicOrAssembling, .organicOrHandCrafting, .pressing, ] } | .biochamber => { name := "biochamber" speedup := 2 productivity := 1/2 moduleSlots := 4 width := 3 height := 3 fluidBoxes := [ { offset := 0 side := .N type := .input }, { offset := 2 side := .N type := .input }, { offset := 2 side := .S type := .output }, { offset := 0 side := .S type := .output }, ] categories := [ .organic, .organicOrAssembling, .organicOrChemistry, .organicOrHandCrafting, ] } | .captiveBiterSpawner => { name := "captive-biter-spawner" speedup := 1 productivity := 0 moduleSlots := 0 width := 5 height := 5 fluidBoxes := [ ] categories := [ .captiveSpawnerProcess, ] } | .centrifuge => { name := "centrifuge" speedup := 1 productivity := 0 moduleSlots := 2 width := 3 height := 3 fluidBoxes := [ ] categories := [ .centrifuging, ] } | .chemicalPlant => { name := "chemical-plant" speedup := 1 productivity := 0 moduleSlots := 3 width := 3 height := 3 fluidBoxes := [ { offset := 0 side := .N type := .input }, { offset := 2 side := .N type := .input }, { offset := 0 side := .S type := .output }, { offset := 2 side := .S type := .output }, ] categories := [ .chemistry, .chemistryOrCryogenics, .organicOrChemistry, ] } | .crusher => { name := "crusher" speedup := 1 productivity := 0 moduleSlots := 2 width := 2 height := 3 fluidBoxes := [ ] categories := [ .crushing, ] } | .cryogenicPlant => { name := "cryogenic-plant" speedup := 2 productivity := 0 moduleSlots := 8 width := 5 height := 5 fluidBoxes := [ { offset := 0 side := .S type := .input }, { offset := 2 side := .S type := .input }, { offset := 4 side := .S type := .input }, { offset := 0 side := .N type := .output }, { offset := 2 side := .N type := .output }, { offset := 4 side := .N type := .output }, ] categories := [ .chemistryOrCryogenics, .cryogenics, .cryogenicsOrAssembling, ] } | .electricFurnace => { name := "electric-furnace" speedup := 2 productivity := 0 moduleSlots := 2 width := 3 height := 3 fluidBoxes := [ ] categories := [ .smelting, ] } | .electromagneticPlant => { name := "electromagnetic-plant" speedup := 2 productivity := 1/2 moduleSlots := 5 width := 4 height := 4 fluidBoxes := [ { offset := 2 side := .W type := .inputOutput }, { offset := 1 side := .E type := .inputOutput }, { offset := 2 side := .S type := .inputOutput }, { offset := 1 side := .N type := .inputOutput }, ] categories := [ .electromagnetics, .electronics, .electronicsOrAssembling, .electronicsWithFluid, ] } | .foundry => { name := "foundry" speedup := 4 productivity := 1/2 moduleSlots := 4 width := 5 height := 5 fluidBoxes := [ { offset := 1 side := .S type := .input }, { offset := 3 side := .S type := .input }, { offset := 1 side := .N type := .output }, { offset := 3 side := .N type := .output }, ] categories := [ .craftingWithFluidOrMetallurgy, .metallurgy, .metallurgyOrAssembling, .pressing, ] } | .oilRefinery => { name := "oil-refinery" speedup := 1 productivity := 0 moduleSlots := 3 width := 5 height := 5 fluidBoxes := [ { offset := 1 side := .S type := .input }, { offset := 3 side := .S type := .input }, { offset := 0 side := .N type := .output }, { offset := 2 side := .N type := .output }, { offset := 4 side := .N type := .output }, ] categories := [ .oilProcessing, ] } | .recycler => { name := "recycler" speedup := 1/2 productivity := 0 moduleSlots := 4 width := 1 height := 3 fluidBoxes := [ ] categories := [ .recycling, .recyclingOrHandCrafting, ] } | .rocketSilo => { name := "rocket-silo" speedup := 1 productivity := 0 moduleSlots := 4 width := 9 height := 9 fluidBoxes := [ ] categories := [ .rocketBuilding, ] } | .steelFurnace => { name := "steel-furnace" speedup := 2 productivity := 0 moduleSlots := 0 width := 1 height := 2 fluidBoxes := [ ] categories := [ .smelting, ] } | .stoneFurnace => { name := "stone-furnace" speedup := 1 productivity := 0 moduleSlots := 0 width := 1 height := 2 fluidBoxes := [ ] categories := [ .smelting, ] } def name (f:Fabricator) := f.details.name def width (f:Fabricator) := f.details.width def height (f:Fabricator) := f.details.height @[simp] def speedup (f:Fabricator) := f.details.speedup @[simp] def productivity (f:Fabricator) := f.details.productivity @[simp] def moduleSlots (f:Fabricator) := f.details.moduleSlots def fluidBoxes (f:Fabricator) := f.details.fluidBoxes def handlesCategory (f:Fabricator) (c:RecipeCategory) := f.details.categories.contains c end Fabricator ================================================ FILE: Functorio/Row.lean ================================================ import Functorio.Factory import Functorio.Expand import Functorio.Util private def rowPerfect {n e s w n' e' s'} (left : Factory n e s w) (right:Factory n' e' s' e): Factory (n ++ n') e' (s ++ s') w := if left.interface.e != right.interface.w then unimplemented! s!" Rows currently require that factory interfaces line up perfectly (no adapter is being generated yet). Left offsets: {reprStr left.interface.e} Right offsets: {reprStr right.interface.w} Offending factories row(left,right); left := {left.name} right := {right.name}" else if left.height != right.height then impossible! "Row factories don't have the same height after expanding their dimensions." else let height := left.height { width := left.width + right.width, height := height, entities := left.entities ++ right.entities.map (Entity.offsetPosition left.width 0) wires := left.wires ++ right.wires.map fun wire => wire.incrementLabels left.entities.length interface := { n := cast (by simp) (left.interface.n ++ increaseOffset left.width (right.interface.n)) e := right.interface.e s := cast (by simp) (left.interface.s ++ increaseOffset left.width (right.interface.s)) w := left.interface.w } name := s!"row({left.name},{right.name})" } def row' {n e s w n' e' s'} (left : Factory n e s w) (right:Factory n' e' s' e): Factory (n ++ n') e' (s ++ s') w := -- Similar code in columnTwo -- Align the two factories on their 0th interface (if possible), -- then expand the smaller one to match the bigger one's size. if (left.height > right.height) then let diff := left.height - right.height let shift := if left.interface.e.isEmpty then 0 else min diff (left.interface.e[0]! - right.interface.w[0]!) rowPerfect left ((right.expand .S (diff - shift)).expand .N shift) else if (left.height < right.height) then let diff := right.height - left.height let shift := if left.interface.e.isEmpty then 0 else min diff ((right.interface.w)[0]! - (left.interface.e)[0]!) rowPerfect ((left.expand .S (diff - shift)).expand .N shift) right else rowPerfect left right def row {n e s w n' e' s'} (left : Factory n e s w) (right:Factory n' e' s' e): Factory (n ++ n') e' (s ++ s') w := -- Align the two factories on their 0th interface, -- then expand both interfaces to match their sizes. let diff := Int.ofNat left.height - Int.ofNat right.height let shift := if left.interface.e.isEmpty then Int.ofNat 0 else Int.ofNat left.interface.e[0]! - Int.ofNat right.interface.w[0]! rowPerfect ((left.expand .S (shift - diff).toNat).expand .N (-shift).toNat) ((right.expand .S (diff - shift).toNat).expand .N shift.toNat) def row3 {n e s w n' e' s' n'' e'' s''} (left : Factory n e s w) (middle:Factory n' e' s' e) (right:Factory n'' e'' s'' e') : Factory (n ++ n' ++ n'') e'' (s ++ s' ++ s'') w := (row (row left middle) right) def rowList {i} (fs:List (Factory [] i [] i)) : Factory [] i [] i:= match fs with | f::fs => fs.foldl row f | [] => unimplemented! "Cannot make a row from empty list of factories yet" ================================================ FILE: Functorio/Test.lean ================================================ import Functorio.AssemblyLine import Functorio.Blueprint import Functorio.Column import Functorio.Bus import Functorio.Expand import Functorio.Ascii namespace Test instance : Config where generateBigPoles := false generateRoboports := false providerChestCapacity := 0 adapterMinHeight := 3 #guard (bus do let iron <- input .ironPlate 300 let copper <- input .copperPlate 15 let gear <- busAssemblyLine (recipe .ironGearWheel) 1 iron let _ <- busAssemblyLine (recipe .automationSciencePack) 1 copper gear.less ).toAscii == s!" ↑⇨***⇨↓↑⇨***⇦↑↓ ↑ *A* ↓↑ *A*↠↑↓ ↑⚡***⚡↓↑⚡***⚡↑↓ ↑ ↓←←←←↑ →→→↑↓ ↑ ↓ ↑ ↑↓←←← ↑←↓ ↑← ↑↓ ↑↓ ↑ ↑↓ >→↑→→→→→⇥↑↦↑→→→→> >→→→→→→→→↑ " #guard (bus do let water <- input .water 11400 let petrol <- input .petroleumGas 5400 let iron <- input .ironPlate 60 let (water0, water1) <- split (left:=5400) water let sulfur <- busAssemblyLine (recipe .sulfur) 3 water0 petrol let _ <- busAssemblyLine (recipe .sulfuricAcid) 1 water1.exact sulfur.less iron ).toAscii == s!" |┤|├***⇨↓ | | *C* ↓ | ||***⚡↓ |┤|├***⇨↓ | | *C* ↓ | ||***⚡↓ |┤|├***⇨↓ |┤↑├***┤↑├| | | *C* ↓ | ↑⇨*C*⇦↑ | | ||***⚡↓ | ↑⚡***⚡↑ | | |↓←←←←← |→↑ ↑ | | |↓ |↑ →→→→→↑ | | |↓ |↑ ↑||||||| | |↓ |↑ ↑| >||┤|↓├||||||↑ ↑||||||||> >→→⇥|↓↦→→→→→⇥↑↦↑ >||||→→→→→→→→↑ " #guard (bus do let copper <- input .copperPlate 300 let iron <- input .ironPlate 1050 let (iron0, rest) <- split (left:=600) iron let (iron1, rest) <- split (left:=150) rest let (iron2, rest) <- split (left:=150) rest let iron3 : BusLane .ironPlate 150 := rest let gear <- busAssemblyLine (recipe .ironGearWheel) 2 iron0 let (gear0, rest) <- split (left:=150) gear let gear1 : BusLane .ironGearWheel 150 := rest.less let belt <- busAssemblyLine (recipe .transportBelt) 1 iron1 gear0 let cable <- busAssemblyLine (recipe .copperCable) 2 copper let circuit <- busAssemblyLine (recipe .electronicCircuit) 1 iron2 cable.less let inserter <- busAssemblyLine (recipe .inserter) 1 circuit gear1 iron3 let _ <- busAssemblyLine (recipe .logisticSciencePack) 1 inserter.less belt.less ).toAscii == s!" ↑⇨***⇨↓ ↑⇨***⇨↓ ↑ *A* ↓ ↑ *A* ↓ ↑⚡***⚡↓ ↑⚡***⚡↓ ↑⇨***⇨↓↑⇨***⇦↑↓↑⇨***⇨↓↑⇨***⇦↑↓↑↑⇨***⇦↑↓↑⇨***⇦↑↓ ↑ *A* ↓↑ *A*↠↑↓↑ *A* ↓↑ *A*↠↑↓↑↑↠*A*↠↑↓↑ *A*↠↑↓ ↑⚡***⚡↓↑⚡***⚡↑↓↑⚡***⚡↓↑⚡***⚡↑↓↑↑⚡***⚡↑↓↑⚡***⚡↑↓ ↑ ↓←←←↑ →→↑↓↑ ↓←←←←↑ →→↑↓↑↑ →→→↑↓↑ →→→→↑↓ ↑ ↓ ↑ ↑↓←←↑ ↓ ↑ ↑↓←←↑↑← ↑↓←←←↑ ↑↓←←←← ↑←←↓ ↑←← ↑↓ ↑←↓ ↑←← ↑↓ ↑←↑ ↑↓ ↑←↑↓ ↑↓ ↑ ↑↓ ↑↓ ↑ ↑↓ ↑↑ ↑↓ ↑↑↓ >⇥*↑↓↦→→⇥*↑ ↑↓↦→→↑→→→→→⇥*↑↦↑→→→→↑↑ ↑→→→→→↑↑→→→→→> >→S⇥↓↦→→→S⇥*↑↓↦→→→→→→→→→S→→→→→→→⇥↑↦↑ ↑ →→→→→→→S⇥↓↦→→→→→→→→→→→→→→→→→→↑ ↑ →→→→→→→→→→→→→→→→→→→→→→→→→→→→→↑ " end Test ================================================ FILE: Functorio/Util.lean ================================================ def error! {T} [Inhabited T] (msg:String) : T := dbg_trace ("ERROR: " ++ msg) default def unimplemented! {T} [Inhabited T] (msg:String) : T := dbg_trace ("UNIMPLEMENTED: " ++ msg ++ " (please file a feature request)") default def impossible! {T} [Inhabited T] (msg:String) : T := dbg_trace ("UNREACHABLE: " ++ msg ++ " (please report a bug)") default namespace List def toVector {T} (l:List T) : Vector T (l.length) := l.toArray.toVector def castToVector! {T} [Inhabited T] [Repr T] {n} (l:List T) : Vector T n := match decEq l.length n with | isTrue _ => cast (by subst_eqs; simp) l.toVector | isFalse _ => error! s!"castToVector! failed for {reprStr l}" end List namespace Array def toVector! {T} [Inhabited T] [Repr T] {n} (l:Array T) : Vector T n := match decEq l.size n with | isTrue _ => cast (by subst_eqs; simp) l.toVector | isFalse _ => error! s!"toVector! failed for {reprStr l}" end Array namespace Vector @[inline] def modify {α n} (xs : Vector α n) (i : Nat) (f : α -> α) : Vector α n := ⟨xs.toArray.modify i f, by simp⟩ end Vector ================================================ FILE: Functorio.lean ================================================ import Functorio.Abbreviations import Functorio.Adapter import Functorio.AdapterTest import Functorio.Ascii import Functorio.AssemblyStation import Functorio.AssemblyStationTest import Functorio.AssemblyLine import Functorio.AssemblyLineTest import Functorio.Blueprint import Functorio.Bus import Functorio.BusTest import Functorio.Cap import Functorio.Column import Functorio.Config import Functorio.Crop import Functorio.Entity import Functorio.Expand import Functorio.ExpandTest import Functorio.Factory import Functorio.Fraction import Functorio.Readme import Functorio.Recipe import Functorio.Row import Functorio.Test import Functorio.Util ================================================ FILE: Gleba300.lean ================================================ import Functorio import Functorio.AssemblyLine instance : Config where generateBigPoles := true generateRoboports := true adapterMinHeight := 3 def makeBootstrapMash : Yumako 405 -> Bus (YumakoMash 810 × YumakoSeed (81/10)) := busAssemblyLine { recipe := .yumakoProcessing, fabricator := .assemblingMachine2} 9 def makeNutrientsFromSpoilage: Spoilage 225 → Bus (Nutrients (45/2)) := by exact (busAssemblyLine { recipe := .nutrientsFromSpoilage, fabricator := .assemblingMachine2} 1) def makeNutrientsFromYumakoMash0 : Nutrients (45/2) → YumakoMash 585 → Bus (Nutrients 270 × YumakoMash 465 × Nutrients (15/2)) := by exact (busAssemblyLine (recipe .nutrientsFromYumakoMash [(465, .yumakoMash), (15/2, .nutrients)]) 1) def makeNutrientsFromYumakoMash1 : Nutrients (555/2) → YumakoMash 465 → Bus (Nutrients 810 × YumakoMash 105 × Nutrients (465/2)) := by exact (busAssemblyLine (recipe .nutrientsFromYumakoMash [(105, .yumakoMash), (465/2, .nutrients)]) 3) def bootstrapNutrients (yumako:Yumako 405) : Bus (Nutrients 810 × YumakoSeed (81/10) × YumakoMash 105) := do let (mash, seeds) <- makeBootstrapMash yumako let (mashToSpoil, mash) <- splitBalanced (left:=225) mash let spoilage <- spoilingChamber mashToSpoil let nutrients <- makeNutrientsFromSpoilage spoilage let (nutrients0, mash, nutrients1) <- makeNutrientsFromYumakoMash0 nutrients mash let nutrients <- merge nutrients0 nutrients1 let (nutrients0, mash, nutrients1) <- makeNutrientsFromYumakoMash1 nutrients mash let nutrients <- merge nutrients0 nutrients1 return (nutrients.less, seeds, mash) def makeBioflux0 : Nutrients 345 -> YumakoMash 2700 -> Jelly 2700 -> Bus (Bioflux 1080 × Jelly 540 × YumakoMash 0 × Nutrients 210) := busAssemblyLine (recipe .bioflux [(540, .jelly), (0, .yumakoMash), (210, .nutrients) ]) 9 def makeBioflux1 : Nutrients 210 -> YumakoMash 2700 -> Jelly 2700 -> Bus (Bioflux 1080 × Jelly 540 × YumakoMash 0 × Nutrients 75) := busAssemblyLine (recipe .bioflux [(540, .jelly), (0, .yumakoMash), (75, .nutrients) ]) 9 def makeNutrients0 : Nutrients 75 -> Bioflux 2160 -> Bus (Nutrients 2700 × Bioflux 1860 × Nutrients 60) := busAssemblyLine (recipe .nutrientsFromBioflux [(1860, .bioflux), (60, .nutrients)]) 1 def makeNutrients1 : Nutrients 60 -> Bioflux 1860 -> Bus (Nutrients 2700 × Bioflux 1560 × Nutrients 45) := busAssemblyLine (recipe .nutrientsFromBioflux [(1560, .bioflux), (45, .nutrients)]) 1 def loopbackPentapodEggs (water:Water 3840) (nutrients:Nutrients 2700) : Bus (Nutrients 660 × PentapodEgg 128) := do let process := recipe .pentapodEgg [(660, .nutrients), (128, .pentapodEgg)] let factoryNoLoopback := assemblyLine process 8 let loopback : Factory (assemblyLineInterface process) [] [(.water, .N), (.nutrients, .N), (.nutrients, .S), (.pentapodEgg, .S)] [] := { entities := [ pipe 1 0, pipe 1 1, pipe 1 2, belt 3 0 .N, belt 3 1 .N, belt 4 1 .W, belt 5 1 .W, belt 6 1 .W, belt 7 1 .W, belt 8 1 .W, belt 9 1 .W, belt 10 1 .W, beltUp 9 0 .N, beltDown 9 2 .N, belt 10 0 .S, belt 11 0 .S, belt 11 1 .S, belt 11 2 .S, belt 12 0 .S, belt 12 1 .S, belt 12 2 .S, ] interface := { n := #v[1, 3, 9, 10, 11, 12], e := #v[], s := #v[1, 9, 11, 12], w := #v[], } wires := [] width := 13 height := 3 name := "makePentapodEggsLoopbackInner" } let factory := column factoryNoLoopback loopback let namedFactory := factory.setName "makePentapodEggsLoopback" let indexes <- busTapGeneric [water.toBusLane', nutrients.toBusLane'] [.nutrients, .pentapodEgg] namedFactory return ({index:=indexes[0]!}, {index:=indexes[1]!}) def amplifyPentapodEggs : Water 4800 -> PentapodEgg 128 -> Nutrients 2700 -> Bus (PentapodEgg 240 × Nutrients 150 × PentapodEgg 48) := busAssemblyLine (recipe .pentapodEgg [(150, .nutrients), (48, .pentapodEgg)]) 10 def makePentapodEggs (water:Water 8640) (nutrients0 : Nutrients 2700) (nutrients1 : Nutrients 2700) : Bus (PentapodEgg 288 × Nutrients 810) := do let (water0, water1) <- split water let (nutrients0, eggs) <- loopbackPentapodEggs water0 nutrients0 let (eggs0, nutrients1, eggs1) <- amplifyPentapodEggs water1 eggs nutrients1 let eggs <- merge eggs0 eggs1 let nutrients <- merge nutrients0 nutrients1 return (eggs, nutrients) def makeAgriculturalScience : Nutrients 810 -> Bioflux 1560 -> PentapodEgg 288 -> Bus (AgriculturalScience 315 × PentapodEgg 78 × Bioflux 1350 × Nutrients 705) := busAssemblyLine (recipe .agriculturalSciencePack [(78, .pentapodEgg), (1350, .bioflux), (705, .nutrients)]) 7 def cultivateCopperBacteria0 : Nutrients 705 -> YumakoMash 1080 -> Bus (CopperBacteria 36 × Spoilage 360 × YumakoMash 360 × Nutrients 675):= busAssemblyLine (recipe .copperBacteria [(360, .yumakoMash), (675, .nutrients)]) 2 def cultivateCopperBacteria1 : Nutrients 675 -> CopperBacteria 36 -> Bioflux 1350 -> Bus (CopperBacteria 180 × Bioflux 1320 × CopperBacteria 6 × Nutrients 660):= busAssemblyLine (recipe .copperBacteriaCultivation [ (1320, .bioflux), (6, .copperBacteria), (660, .nutrients)]) 1 def cultivateCopperBacteria2 : Nutrients 660 -> CopperBacteria 186 -> Bioflux 1320 -> Bus (CopperBacteria 360 × Bioflux 1260 × CopperBacteria 126 × Nutrients 630):= busAssemblyLine (recipe .copperBacteriaCultivation [ (1260, .bioflux), (126, .copperBacteria), (630, .nutrients)]) 2 def cultivateCopperBacteria3 : Nutrients 630 -> CopperBacteria 486 -> Bioflux 1260 -> Bus (CopperBacteria 1620 × Bioflux 990 × CopperBacteria 216 × Nutrients 495):= busAssemblyLine (recipe .copperBacteriaCultivation [(990, .bioflux), (216, .copperBacteria), (495, .nutrients)]) 9 def makeBacteriaCopper (nutrients:Nutrients 705) (mash:YumakoMash 1080) (bioflux:Bioflux 1350) : Bus (CopperOre 1836 × YumakoMash 360 × Bioflux 990 × Spoilage 360 × Nutrients 495) := do let (bacteria, spoilage, mash, nutrients) <- cultivateCopperBacteria0 nutrients mash let (bacteria0, bioflux, bacteria1, nutrients) <- cultivateCopperBacteria1 nutrients bacteria bioflux let bacteria <- merge bacteria0 bacteria1 let (bacteria0, bioflux, bacteria1, nutrients) <- cultivateCopperBacteria2 nutrients bacteria bioflux let bacteria <- merge bacteria0 bacteria1 let (bacteria0, bioflux, bacteria1, nutrients) <- cultivateCopperBacteria3 nutrients bacteria bioflux let bacteria <- merge bacteria0 bacteria1 let ore <- spoilingChamber bacteria return (ore, mash, bioflux, spoilage, nutrients) def cultivateIronBacteria0 : Nutrients 495 -> Jelly 2700 -> Bus (Spoilage 1440 × IronBacteria 36 × Jelly 1260 × Nutrients 465):= busAssemblyLine (recipe .ironBacteria [(1260, .jelly), (465, .nutrients)]) 2 def cultivateIronBacteria1 : Nutrients 465 -> IronBacteria 36 -> Bioflux 990 -> Bus (IronBacteria 180 × Bioflux 960 × IronBacteria 6 × Nutrients 450) := busAssemblyLine (recipe .ironBacteriaCultivation [(960, .bioflux), (6, .ironBacteria), (450, .nutrients)]) 1 def cultivateIronBacteria2 : Nutrients 450 -> IronBacteria 186 -> Bioflux 960 -> Bus (IronBacteria 900 × Bioflux 810 × IronBacteria 36 × Nutrients 375):= busAssemblyLine (recipe .ironBacteriaCultivation [(810, .bioflux), (36, .ironBacteria), (375, .nutrients)]) 5 def makeBacteriaIron (nutrients:Nutrients 495) (jelly:Jelly 2700) (bioflux:Bioflux 990) : Bus (IronOre 936 × Jelly 1260 × Bioflux 810 × Spoilage 1440 × Nutrients 375) := do let (spoilage, bacteria, jelly, nutrients) <- cultivateIronBacteria0 nutrients jelly let (bacteria0, bioflux, bacteria1, nutrients) <- cultivateIronBacteria1 nutrients bacteria bioflux let bacteria <- merge bacteria0 bacteria1 let (bacteria0, bioflux, bacteria1, nutrients) <- cultivateIronBacteria2 nutrients bacteria bioflux let bacteria <- merge bacteria0 bacteria1 let ore <- spoilingChamber bacteria return (ore, jelly, bioflux, spoilage, nutrients) def makeBioSulfur : Nutrients 375 -> Spoilage 1800 -> Bioflux 810 -> Bus (Sulfur 540 × Bioflux 630 × Spoilage 900 × Nutrients 330) := busAssemblyLine (recipe .biosulfur [(630, .bioflux), (900, .spoilage), (330, .nutrients)]) 3 def makeBioPlastic : Nutrients 330 -> Bioflux 630 -> YumakoMash 360 -> Bus (Plastic 270 × YumakoMash 120 × Bioflux 570 × Nutrients 315) := busAssemblyLine (recipe .bioplastic [(120, .yumakoMash), (570, .bioflux), (315, .nutrients)]) 1 def makeLowDensityStructure : Steel 40 -> Copper 400 -> Plastic 100 -> Bus (LowDensityStructure 20) := busAssemblyLine (recipe .lowDensityStructure) 4 def makeAcid : Water 6000 -> Sulfur 300 -> Iron 60 -> Bus (Acid 3000) := busAssemblyLine (recipe .sulfuricAcid) 1 def makeCopper : CopperOre 1500 -> Bus (Copper 1500) := busAssemblyLine (recipe .copperPlate) 40 def makeIron : IronOre 900 -> Bus (Iron 900) := busAssemblyLine (recipe .ironPlate) 24 def makeSteel : Iron 225 -> Bus (Steel 45) := busAssemblyLine (recipe .steelPlate) 6 def makeBioRocketFuel : Water 720 -> Nutrients 315 -> Jelly 1260 -> Bioflux 570 -> Bus (RocketFuel 36 × Bioflux 522 × Jelly 540 × Nutrients 285) := busAssemblyLine (recipe .rocketFuelFromJelly [(522, .bioflux), (540, .jelly), (285, .nutrients)]) 2 def makeCable : Copper 1050 -> Bus (Cable 2100) := busAssemblyLine (recipe .copperCable) 7 def makeGreenCircuit : Iron 600 -> Cable 1800 -> Bus (GreenCircuit 600) := busAssemblyLine (recipe .electronicCircuit) 4 def makeRedCircuit : GreenCircuit 100 -> Plastic 100 -> Cable 200 -> Bus (RedCircuit 50) := busAssemblyLine (recipe .advancedCircuit) 4 def makeBlueCircuit : Acid (225/2) -> GreenCircuit 450 -> RedCircuit 45 -> Bus (BlueCircuit (45/2)) := busAssemblyLine (recipe .processingUnit) 3 def makeRocket : BlueCircuit 20 -> LowDensityStructure 20 -> RocketFuel 20 -> Bus Unit := busAssemblyLine (recipe .rocketPart) 1 def makeMashFullBelt : Nutrients 120 -> Yumako 960 -> Bus (YumakoMash 2700 × YumakoSeed (144/5)) := busAssemblyLine (recipe .yumakoProcessing) 8 def makeMashPartialBelt : Nutrients 45 -> Yumako 360 -> Bus (YumakoMash 1080 × YumakoSeed (54/5)) := busAssemblyLine (recipe .yumakoProcessing) 3 def makeNonBiologicalComponents (copperOre : CopperOre 1500) (ironOre: IronOre 900) (water:Water 6000) (sulfur : Sulfur 540) (plastic : Plastic 270) (rocketFuel : RocketFuel 36) : Bus Unit := do let (plastic0, plastic1) <- split plastic let copper <- makeCopper copperOre let iron <- makeIron ironOre let (iron0, iron) <- split iron let (iron1, iron2) <- split iron let (copper0, copper1) <- split copper let steel <- makeSteel iron0 let acid <- makeAcid water sulfur.less iron1 let lowDensityStruct <- makeLowDensityStructure steel.less copper0 plastic0 let cable <- makeCable copper1.less let (cable0, cable1) <- split cable let greenCircuit <- makeGreenCircuit iron2.less cable0 let (greenCircuit0, greenCircuit1) <- split greenCircuit let redCircuit <- makeRedCircuit greenCircuit0 plastic1.less cable1.less let blueCircuit <- makeBlueCircuit acid.less greenCircuit1.less redCircuit.less let _ <- makeRocket blueCircuit.less lowDensityStruct rocketFuel.less def makeJelly (nutrients:Nutrients 810) (jellynut:Jellynut 1440) : Bus (Jelly 2700 × Jelly 2700 × Jelly 2700 × JellynutSeed (216 / 5) × Nutrients 630):= do let (jellynut0, jellynut) <- split jellynut let (jellynut1, jellynut2) <- split jellynut let (jelly0, jellySeed0, nutrients) <- busAssemblyLine (recipe .jellynutProcessing [(750, .nutrients)]) 4 nutrients jellynut0 let (jelly1, jellySeed1, nutrients) <- busAssemblyLine (recipe .jellynutProcessing [(690, .nutrients)]) 4 nutrients jellynut1 let (jelly2, jellySeed2, nutrients) <- busAssemblyLine (recipe .jellynutProcessing [(630, .nutrients)]) 4 nutrients jellynut2 let jellySeed <- merge jellySeed0 jellySeed1 let jellySeed <- merge jellySeed jellySeed2 return (jelly0, jelly1, jelly2, jellySeed, nutrients) def makeMash (nutrients:Nutrients 630) (yumako:Yumako 2280) : Bus (YumakoMash 2700 × YumakoMash 2700 × YumakoMash 1080 × YumakoSeed (342 / 5) × Nutrients 345):= do let (yumako0, yumako) <- split yumako let (yumako1, yumako2) <- split yumako let (mash0, yumakoSeed0, nutrients) <- busAssemblyLine (recipe .yumakoProcessing [(510, .nutrients)]) 8 nutrients yumako0 let (mash1, yumakoSeed1, nutrients) <- busAssemblyLine (recipe .yumakoProcessing [(390, .nutrients)]) 8 nutrients yumako1 let (mash2, yumakoSeed2, nutrients) <- busAssemblyLine (recipe .yumakoProcessing [(345, .nutrients)]) 3 nutrients yumako2 let yumakoSeed <- merge yumakoSeed0 yumakoSeed1 let yumakoSeed <- merge yumakoSeed yumakoSeed2 return (mash0, mash1, mash2, yumakoSeed, nutrients) def makeCarbonFiber := busAssemblyLine (recipe .carbonFiber) def glebaFactory := bus do let water <- input .water 15360 let yumako <- input .yumako 2685 let jellynut <- input .jellynut 1440 let (water0, water) <- split (left:=8640) water let (water1, water2) <- split (right:=6000) water let (yumako0, yumako1) <- split (left:=405) (right:=2280) yumako let (nutrients, yumakoSeed0, _) <- bootstrapNutrients yumako0 let (jelly0, jelly1, jelly2, jellySeed, nutrients) <- makeJelly nutrients jellynut let (mash0, mash1, mash2, yumakoSeed1, nutrients) <- makeMash nutrients yumako1 let yumakoSeed <- merge yumakoSeed0 yumakoSeed1 let (bioflux0, _, _, nutrients) <- makeBioflux0 nutrients mash0 jelly0 let (bioflux1, _, _, nutrients) <- makeBioflux1 nutrients mash1 jelly1 let bioflux <- merge bioflux0 bioflux1 let (eggNutrients0, bioflux, nutrients) <- makeNutrients0 nutrients bioflux let (eggNutrients1, bioflux, nutrients) <- makeNutrients1 nutrients bioflux let (eggs, moreNutrients) <- makePentapodEggs water0 eggNutrients0 eggNutrients1 let nutrients <- merge nutrients moreNutrients let (_, _, bioflux, nutrients) <- makeAgriculturalScience nutrients.less bioflux eggs pipePumps -- Right around here, the pipes on the bus are so long that they need pumps. let (copperOre, mash, bioflux, spoilage0, nutrients) <- makeBacteriaCopper nutrients mash2 bioflux let (copperOre, _) <- removeExcess copperOre let (ironOre, jelly, bioflux, spoilage1, nutrients) <- makeBacteriaIron nutrients jelly2 bioflux let (ironOre, _) <- removeExcess ironOre let spoilage <- merge spoilage0 spoilage1 let (sulfur, bioflux, _, nutrients) <- makeBioSulfur nutrients spoilage bioflux let (sulfur, _) <- removeExcess sulfur let (plastic, _, bioflux, nutrients) <- makeBioPlastic nutrients bioflux mash let (plastic, _) <- removeExcess plastic let (rocketFuel, _, _, _) <- makeBioRocketFuel water1 nutrients jelly bioflux let (rocketFuel, _) <- removeExcess rocketFuel makeNonBiologicalComponents copperOre.less ironOre.less water2 sulfur plastic rocketFuel def main : IO Unit := IO.println (glebaFactory.toBlueprint) -- (bootstrap := true)) ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: Nauvis150.lean ================================================ import Functorio instance : Config where generateBigPoles := true generateRoboports := true providerChestCapacity := 3 adapterMinHeight := 3 def ironFullBeltFactory : IronOre 2700 -> Bus (Iron 2700) := busAssemblyLine (recipe .ironPlate) 72 def ironFactory (ore:Vector (IronOre 2700 ) 8) : Bus (Vector (Iron 2700) 8) := do let iron0 <- ironFullBeltFactory ore[0] let iron1 <- ironFullBeltFactory ore[1] let iron2 <- ironFullBeltFactory ore[2] let iron3 <- ironFullBeltFactory ore[3] let iron4 <- ironFullBeltFactory ore[4] let iron5 <- ironFullBeltFactory ore[5] let iron6 <- ironFullBeltFactory ore[6] let iron7 <- ironFullBeltFactory ore[7] return Array.toVector #[iron0, iron1, iron2, iron3, iron4, iron5, iron6, iron7] def copperFullBeltFactory : CopperOre 2700 -> Bus (Copper 2700) := busAssemblyLine (recipe .copperPlate) 72 def copperFactory (ore:Vector (CopperOre 2700 ) 6) : Bus (Vector (Copper 2700) 6) := do let copper0 <- copperFullBeltFactory ore[0] let copper1 <- copperFullBeltFactory ore[1] let copper2 <- copperFullBeltFactory ore[2] let copper3 <- copperFullBeltFactory ore[3] let copper4 <- copperFullBeltFactory ore[4] let copper5 <- copperFullBeltFactory ore[5] return Array.toVector #[copper0, copper1, copper2, copper3, copper4, copper5] def steelPartialBeltFactory : Iron 2700 -> Bus (Steel 540) := busAssemblyLine (recipe .steelPlate) 72 def steelFactory (iron:Vector (Iron 2700) 4) : Bus (Steel 2160) := do let steel0 <- steelPartialBeltFactory iron[0] let steel1 <- steelPartialBeltFactory iron[1] let steel2 <- steelPartialBeltFactory iron[2] let steel3 <- steelPartialBeltFactory iron[3] let steel01 <- merge steel0 steel1 let steel23 <- merge steel2 steel3 merge steel01 steel23 def brickFactory : Stone 2700 -> Bus (Brick 1350) := busAssemblyLine (recipe .stoneBrick) 36 def gearFactory : Iron 1500 -> Bus (Gear 750) := busAssemblyLine (recipe .ironGearWheel) 5 def redScienceFactory : Copper 150 -> Gear 150 -> Bus (RedScience 150) := busAssemblyLine (recipe .automationSciencePack) 10 def inserterFactory : GreenCircuit 150 -> Gear 150 -> Iron 150 -> Bus (Inserter 150) := busAssemblyLine (recipe .inserter) 1 def yellowBeltFactory : Iron 150 -> Gear 150 -> Bus (YellowBelt 300) := busAssemblyLine (recipe .transportBelt) 1 def greenScienceFactory : Inserter 150 -> YellowBelt 150 -> Bus (GreenScience 150) := busAssemblyLine (recipe .logisticSciencePack) 12 def yellowAmmoFactory : Iron 300 -> Bus (YellowAmmo 75) := busAssemblyLine (recipe .firearmMagazine) 1 def redAmmoFactory : YellowAmmo 75 -> Steel (75/2) -> Copper 75 -> Bus (RedAmmo 75) := busAssemblyLine (recipe .piercingRoundsMagazine) 3 def wallFactory : Brick 750 -> Bus (Wall 150) := busAssemblyLine (recipe .stoneWall) 1 def grenadeFactory : Iron 375 -> Coal 750 -> Bus (Grenade 75) := busAssemblyLine (recipe .grenade) 8 def blackScienceFactory : RedAmmo 75 -> Grenade 75 -> Wall 150 -> Bus (BlackScience 150) := busAssemblyLine (recipe .militarySciencePack) 10 def plasticFactory (petrol:Petrolium 28800) (coal:Coal 1440) : Bus (Plastic 2000 × Plastic 750) := do let (petrol0, petrol1) <- split (left:=20400) petrol let (coal0, coal1) <- split (left:=1020) coal let plastic0 <- busAssemblyLine (recipe .plasticBar) 17 petrol0 coal0 let plastic1 <- busAssemblyLine (recipe .plasticBar) 7 petrol1 coal1 return (plastic0.less, plastic1.less) def acidFactory : Water 6000 -> Sulfur 300 -> Iron 60 -> Bus (Acid 3000) := busAssemblyLine (recipe .sulfuricAcid) 1 def pipeFactory : Iron 450 -> Bus (Pipe 450) := busAssemblyLine (recipe .pipe) 3 def engineFactory : Steel 210 -> Gear 210 -> Pipe 420 -> Bus (Engine 210) := busAssemblyLine (recipe .engineUnit) 28 def sulfurFactory : Water 7200 -> Petrolium 7200 -> Bus (Sulfur 480) := busAssemblyLine (recipe .sulfur) 4 def blueScienceAssemblyLine : Engine 150 -> RedCircuit 225 -> Sulfur 75 -> Bus (BlueScience 150) := busAssemblyLine (recipe .chemicalSciencePack) 24 def furnaceFactory : Steel 600 -> RedCircuit 300 -> Brick 600 -> Bus (Furnace 60) := busAssemblyLine (recipe .electricFurnace) 4 def prodModuleFactory : RedCircuit 250 -> GreenCircuit 250 -> Bus (ProdModule 50) := busAssemblyLine (recipe .productivityModule) 10 def ironStickFactory : Iron 450 -> Bus (IronStick 900) := busAssemblyLine (recipe .ironStick) 3 def railFactory : Stone 750 -> IronStick 750-> Steel 750 -> Bus (Rail 1500) := busAssemblyLine (recipe .rail) 5 def purpleScienceFactory : Furnace 50 -> ProdModule 50 -> Rail 1500 -> Bus (PurpleScience 150) := busAssemblyLine (recipe .productionSciencePack) 14 def batteryFactory : Acid 2400 -> Iron 120 -> Copper 120 -> Bus (Battery 120) := busAssemblyLine (recipe .battery) 8 def electricEngineFactory : Lubricant 900 -> Engine 60 -> GreenCircuit 120 -> Bus (ElectricEngine 60) := busAssemblyLine (recipe .electricEngineUnit) 8 def robotFrameFactory : ElectricEngine 60 -> Battery 120 -> Steel 60 ->GreenCircuit 180 -> Bus (RobotFrame 60) := busAssemblyLine (recipe .flyingRobotFrame) 16 def lowDensityStructureFactory (copperA:Copper 300) (copperB:Copper 2700) (steel:Steel 300) (plastic:Plastic 750) : Bus (LowDensityStructure 150) := do let (steelA, steelB) <- split steel let (plasticA, plasticB) <- split plastic let structA <- busAssemblyLine (recipe .lowDensityStructure) 3 steelA copperA plasticA let structB <- busAssemblyLine (recipe .lowDensityStructure) 27 steelB copperB plasticB merge structA structB def yellowScienceFactory : LowDensityStructure 150 -> BlueCircuit 100 -> RobotFrame 50 -> Bus (YellowScience 150) := busAssemblyLine (recipe .utilitySciencePack) 14 def greenCircuitFactory (copper:Copper 1350) (iron:Iron 900) : Bus (GreenCircuit 900) := do let cable : Cable 2700 <- busAssemblyLine (recipe .copperCable) 9 copper busAssemblyLine (recipe .electronicCircuit) 6 iron cable def greenCircuitFullBeltFactory (copper0:Copper 1350) (copper1:Copper 2700) (iron:Iron 2700) : Bus (GreenCircuit 2700) := do let (copper1, copper2) <- split copper1 let (iron0, iron) <- split iron let (iron1, iron2) <- split iron let green0 <- greenCircuitFactory copper0 iron0 let green1 <- greenCircuitFactory copper1 iron1 let green2 <- greenCircuitFactory copper2 iron2 let green <- merge green0 green1 let green <- merge green green2 return green def redCircuitFactory (copper:Copper 1050) (greenCircuit:GreenCircuit 1000) (plastic:Plastic 1000) : Bus (RedCircuit 500) := do let cable : Cable 2100 <- busAssemblyLine (recipe .copperCable) 7 copper busAssemblyLine (recipe .advancedCircuit) 40 greenCircuit plastic cable.less def blueCircuitFactory : Acid 525 -> GreenCircuit 2100 -> RedCircuit 210 -> Bus (BlueCircuit 105) := busAssemblyLine (recipe .processingUnit) 14 def circuitFactory (copper: Vector (Copper 2700) 4) (iron: Vector (Iron 2700) 2) (plastic:Plastic 2000) (acid:Acid 525) : Bus (GreenCircuit 700 × RedCircuit 775 × BlueCircuit 100) := do -- green let (copper0, copper1) <- split copper[0] let greenForRed <- greenCircuitFullBeltFactory copper0 copper[1] iron[0] let greenForBlue <- greenCircuitFullBeltFactory copper1 copper[2] iron[1] pipePumps -- Right around here, the pipes on the bus are so long that they need pumps. -- red let (greenForRed0, greenForRed) <- split greenForRed let (greenForRed1, greenOut) <- split greenForRed let (copper0, copper1) <- split copper[3] let (plastic0, plastic1) <- split plastic let red0 <- redCircuitFactory copper0 greenForRed0 plastic0 let red1 <- redCircuitFactory copper1.less greenForRed1 plastic1 let red <- merge red0 red1 let (redForBlue, redOut) <- split red -- blue let blueOut <- blueCircuitFactory acid greenForBlue.less redForBlue return (greenOut, redOut.less, blueOut.less) def advancedOilProcessingFactory : Water 19200 -> CrudeOil 38400 -> Bus (HeavyOil 9600 × LightOil 17280 × Petrolium 21120) := busAssemblyLine (recipe .advancedOilProcessing) 32 def heavyOilCrackingFactory : Water 6300 -> HeavyOil 8400 ->Bus (LightOil 6300) := busAssemblyLine (recipe .heavyOilCracking) 7 def lightOilCrackingFactory : Water 23400 -> LightOil 23400 ->Bus (Petrolium 15600) := busAssemblyLine (recipe .lightOilCracking) 26 def lubricantFactory : HeavyOil 1200 -> Bus (Lubricant 1200) := busAssemblyLine (recipe .lubricant) 2 def oilFactory (water:Water 48900) (crude:CrudeOil 38400) : Bus (Petrolium 36000 × Lubricant 900) := do let (water0, rest) <- split (left:=19200) water let (water1, water2) <- split (left:=6300) rest let (heavy, light0, petrol0) <- advancedOilProcessingFactory water0 crude let (heavy0, heavy1) <- split (left:=1200) heavy let light1 <- heavyOilCrackingFactory water1 heavy1 let light <- merge light0 light1 let petrol1 <- lightOilCrackingFactory water2 light.less let petrol <- merge petrol0 petrol1 let lube <- lubricantFactory heavy0 return (petrol.less, lube.less) def nauvisFactory := bus do let copperOre <- inputs 6 .copperOre 2700 let ironOre <- inputs 8 .ironOre 2700 let stone0 <- input .stone 2700 let stone1 <- input .stone 750 let coal <- input .coal 2190 let water <- input .water 62100 let crudeOil <- input .crudeOil 38400 let (water0, water) <- split (left:=48900) water let (water1, water2) <- split (left:=7200) water let (petrol, lubricant) <- oilFactory water0 crudeOil let (petrol0, petrol1) <- split (left:=7200) petrol let copper <- copperFactory copperOre let (copper0, rest) <- split (left:=150) copper[0] let (copper1, rest) <- split (left:=75) rest let (copper2, rest) <- split (left:=120) rest let copper3 : Copper 300 := rest.less let iron <- ironFactory ironOre let (iron0, rest) <- split (left:=60) iron[0] let (iron1, rest) <- split (left:=1500) rest let (iron2, rest) <- split (left:=150) rest let (iron3, rest) <- split (left:=150) rest let (iron4, rest) <- split (left:=300) rest let iron5 : Iron 375 := rest.less let (iron6, rest) <- split (left:=450) iron[1] let (iron7, repr) <- split (left:=450) rest let iron8 : Iron 120 := rest.less let brick <- brickFactory stone0 let (brick0, brick1) <- split (left:=750) brick let steel <- steelFactory (iron.extract 2 6) let (steel0, rest) <- split (left:=75/2) steel let (steel1, rest) <- split (left:=210) rest let (steel2, rest) <- split (left:=600) rest let (steel3, rest) <- split (left:=750) rest let (steel4, rest) <- split (left:=60) rest let steel5 : Steel 300 := rest.less let (coal0, coal1) <- split (left:=1440) coal let sulfur <- sulfurFactory water1 petrol0 let (sulfur0, sulfur1) <- split sulfur let acid <- acidFactory water2 sulfur0 iron0 let (acid0, acid1) <- split (left:=525) acid let (plastic0, plastic1) <- plasticFactory petrol1 coal0 -- pipePumps -- Right around here, the pipes on the bus are so long that they need pumps. let (greenCircuit, redCircuit, blueCircuit) <- circuitFactory (copper.extract 1 5) (iron.extract 6 8) plastic0 acid0 let (greenCircuit0, rest) <- split (left:=150) greenCircuit let (greenCircuit1, rest) <- split (left:=250) rest let (greenCircuit2, greenCircuit3) <- split (left:=120) rest let (redCircuit0, rest) <- split (left:=225) redCircuit let (redCircuit1, redCircuit2) <- split (left:=300) rest let gear <- gearFactory iron1 let (gear0, rest) <- split (left:=150) gear let (gear1, rest) <- split (left:=150) rest let (gear2, rest) <- split (left:=150) rest let gear3 : Gear 210 := rest.less let _ <- redScienceFactory copper0 gear0 let inserter <- inserterFactory greenCircuit0 gear1 iron2 let belt <- yellowBeltFactory iron3 gear2 let _ <- greenScienceFactory inserter belt.less let yellowAmmo <- yellowAmmoFactory iron4 let redAmmo <- redAmmoFactory yellowAmmo steel0 copper1 let wall <- wallFactory brick0 let grenade <- grenadeFactory iron5 coal1 let _ <- blackScienceFactory redAmmo grenade wall let pipe <- pipeFactory iron6 let engine <- engineFactory steel1 gear3 pipe.less let (engine0, engine1) <- split engine let _ <- blueScienceAssemblyLine engine0 redCircuit0 sulfur1.less let furnace <- furnaceFactory steel2 redCircuit1 brick1 let prodModule <- prodModuleFactory redCircuit2 greenCircuit1 let stick <- ironStickFactory iron7 let rail <- railFactory stone1 stick.less steel3 let _ <- purpleScienceFactory furnace.less prodModule rail let battery <- batteryFactory acid1.less iron8 copper2 let electricEngine <- electricEngineFactory lubricant engine1 greenCircuit2 let robotFrame <- robotFrameFactory electricEngine battery steel4 greenCircuit3 let lowDensityStruct <- lowDensityStructureFactory copper3 copper[5] steel5 plastic1 let _ <- yellowScienceFactory lowDensityStruct blueCircuit.less robotFrame.less def main : IO Unit := IO.println (nauvisFactory.toBlueprint) -- (bootstrap := true)) ================================================ FILE: README.md ================================================ # Functorio Functorio lets you build your Factorio factories in the Lean programming language; giving you conveniences like type safety, functions, recursion, version history, libraries, etc. For example, here's a simple factory that generates 150 red science per minute. ```lean 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 ``` Functorio let's you export your factories as blueprints, ready to be imported into Factorio: ![](figures/red-science.png) In addition to making it easy to write factories, Functiorio also makes it safe! Every resource that is passed around (like `iron`, `gear`, `science`, ...) has a type indicating the kind of item and the number of items provided per minute. In addition, every factory (like `makeIron`, `makeGear`, `makeRedScience`, ...) indicates how many resources it takes and returns. Thus, when your factory type checks, it often just works. And here are the stats to prove it: ![](figures/red-science-stats.png) To get started download the [github repo](https://github.com/konne88/functorio) and open it in VSCode. The github repo contains a devcontainer so VSCode will automatically install all necessary dependencies and you can just run to get the blueprint: ```bash ./red-science-150.sh ``` Here's a video to help you get started: [![](figures/quickstart-play.png)](https://youtu.be/NCp2bw8X4V8) Once you're comfortable playing with Functorio, [here](Nauvis150.lean)'s a factory that is a bit more complicated, producing 150 science per minute for all the sciences on Nauvis, and here's a video walkthrough of the factory: [![](figures/nauvis150-play.png)](https://youtu.be/HK3KwjN-Dtc) This will print a blueprint that you can import directly into Factorio. This library is still in early alpha. Let's make it better together! Join the [discord](https://discord.gg/UGEcqxpSMn), send me pull requests, file github issues, email me, etc. Anything you think is interesting is probably also interesting to me. I'm not the first person to have looked into the connection between Factorio and programming languages. Bartosz Milewski gave a [talk](https://www.youtube.com/watch?v=A46KQtriYuM) and wrote a great [blog post](https://bartoszmilewski.com/2021/02/16/functorio/) about the connection; and also came up with the name Functorio in his post. The following sections describe Functorio's features in more detail. At the end of this post you'll see how to build a factory that generates 150 of all the Nauvis sciences (red, green, blue, yellow, purple, black) per minute. # Buses A `bus` lets you connect multiple factories in a conventient a safe way. Here is an example bus that makes gears from iron ore. You just specify how the resources should flow, and the `bus` function creates the required bus entities (belts, pipes, etc) to realize that intention. The way factories get connected resembles the [main bus](https://wiki.factorio.com/tutorial:main_bus) pattern, hence the name. Here's a simple gear bus example: ```lean def gearFactory := bus do let ironOre : IronOre 300 <- input .ironOre 300 let iron : Iron 300 <- makeIron ironOre let gear <- makeGear iron ``` ![](figures/gear-bus.png) The variables on a bus (e.g. `ironOre`, `iron`, `gear`) represent the lanes of the bus, and have the type `BusLane ingredient throughput`. For example `ironOre` has the type `BusLane .ironOre 100` which means this lane carries 100 iron ore per minute. The library also provides shorter name for the types, e.g. `IronOre 100` is a synonym for `BusLane .ironOre 100`. A bus lane can be consumed by passing it into a factory, e.g. `makeIron ironOre`. New lanes can be returned by factories, for example the `makeGear` factory creates the new `gear` lane. The `input` command creates a new bus lane that is expected to be filled outside the factory. This is useful to provide the raw ingredients for your factory. All inputs must be declared at the beginning of the bus, before any factories are used. Bus lanes must only ever be consumed once. It is invalid to pass the same lane into two factories. If this is desired, the lane must first be split into two lanes, using the `split (left:=x) (right:=y)` function. Here is an example: ```lean 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 ``` ![](figures/split.png) Similarly, lanes can be merged with the `merge` function. Here is an example: ```lean 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 ``` ![](figures/merge.png) The bus also supports liquids. Those are used just like solid ingredients, but the bus will generate pipes instead of belts to connect them. e.g. ```lean def acidFactory := bus do let iron <- input .ironPlate 60 let sulfur <- input .sulfur 300 let water <- input .water 6000 let acid <- makeAcid water iron sulfur ``` ![](figures/liquid-bus.png) # Bus Taps We've talked about passing resources around the bus, but we still haven't talked about how `makeBelt` etc is actually implemented. To connect a factory to the bus, you need to create a `busTap`, which takes a list of input belt lanes from the bus, connects to the given `factory` and returns the output of the factory. For example, `makeBelt` can be implemented as follows ```lean def makeBelt iron gear := busTap [iron, gear] factory ``` # Factories A `Factory` is a collection of entities (assemblers, belts, inserters, etc) placed within a rectangle, and has interfaces on all sides of that rectangle (north, east, south and west). For the below example, the north and south interface are the same: two belts, one iron belt facing north and one gear belt facing south. The east and west interfaces are empty. ![](figures/factory-type.png) The type of a factory is `Factory n e s w` where the letters stand for the north, east, south, and west interfaces. Each interface specifies the resource type and travel direction of that resource. Thus the above factory's type is: ```lean gearFactory : Factory [(.iron, .N), (.ironGearWheel, .S)] -- north interfaces [] -- east interfaces [(.iron, .N), (.ironGearWheel, .S)] -- south interfaces [] -- west interfaces ``` The `Factory` type is designed so that you should only have to worry about a factory's interfaces, but not its size, entities, or the position of the interfaces. The interfaces are reflected in the `Factory`'s type, so when your factory type checks, it should ideally just work. # Columns / Rows Two factories with matching north/south interfaces can be combined with the `column` command. Here's an example: ```lean column gearFactory gearFactory ``` ![](figures/two-column.png) There is also a `columnList` command, which connects all the factories from the given list. To create a column of 10 gear factories, you would write: ```lean columnList (List.replicate 10 gearFactory) ``` Similarly, there is also a `row`/`rowList` command, which combines factories with matching east/west interfaces. # Expanding Sometimes the factories that you want to combine don't have the same dimensions. Expanding a factory increases the factory's width/height without changing its interfaces. For example, here we're expanding a factory's south by 5 tiles: ```lean expand .S 5 inserterFactory ``` Expansions are automatically generated when you combine factories with `column`/`row`, so you generally don't have to worry about the dimensions of factories. ![](figures/expand.png) # Adapters Sometimes the factories that you want to combine have the sames interfaces, but the belts/pipes are in different positions. That's where adapters come in. For example, here we're trying to connect some inserter assemblers to a bus. ![](figures/adapter.png) Adapters are automatically generated when you combine factories with `column`/`row`, so you generally don't have to worry about the position of interfaces (but the order of interfaces matters). # Caps Sometimes you want to remove an interface from a factory. The `capN`, `capE`, `capS`, `capW` commands do just that. ```lean capS inserterFactory ``` ![](figures/cap.png) # Assembly Lines Functorio comes with a builtin set of factories to assemble all the recipes required for red, green, blue, purple, yellow, and black science (contributions for more recipes are very welcome!). These factories are designed so that they can be replicated many times into an assembly line. To get just a single assembler for a certain recipe, you can use the `station` function (because it's one station of a larger assembly line): ```lean station .ironGearWheel ``` ![](figures/gear-station.png) If you want to have multiple stations connected, you can call: ```lean assemblyLine .ironGearWheel 3 ``` ![](figures/gear-assembly-line.png) Calling `assemblyLine` is similar to just replicating the station multiple times, but the `assemblyLine` command will also automatically insert output load balancers, roboports, passive provider chests, and big electric poles if desired. To get a factory that can be attached to a bus, call: ```lean busAssemblyLine .ironGearWheel 3 ``` Checkout the file `NauvisScience150.lean` for a full factory that uses the assembly line library for all its needs. You can generate a blueprint as follows: ```bash ./nauvis-150.sh > blueprint.txt ``` Which looks like this: ![](figures/nauvis150.png) And as promised, produces 150 of all the Nauvis sciences per minutes. ![](figures/nauvis150-stats.png) # Configuration You can configure how Functorio generates certain aspects of your factory, by providing a `Config` instance. ```lean instance : Config where generateRoboports := true -- assembly lines add roboports generateBigPoles := true -- assembly lines add big poles providerChestCapacity := 5 -- assembly lines add provider chests for outputs adapterMinHeight := 3 -- minimum height of adapters ``` # Debugging Sometimes you can't convince Lean that two factories have the same interface, even though you know that they do. You can use the `factory.unsafeCastFactory` to change the interface of your factory to whatever you want. Lean isn't all that great a printing types. For example, Lean will print the type of `busAssemblyLine .processingUnit 14` as `BusAssemblyLineType (getRecipe .processingUnit) 14`. This is really quite useless. If you want to know what that type expands to, you can run the following code, and see the simplified type in Lean's InfoView. ```lean def x : BusAssemblyLineType RecipeName.processingUnit 14 := by simp! ``` Which is: ```lean BusLane Ingredient.sulfuricAcid (525, 1) → BusLane Ingredient.electronicCircuit (2100, 1) → BusLane Ingredient.advancedCircuit (210, 1) → Bus (BusLane Ingredient.processingUnit (105, 1)) ``` If a test `#guard TEST.toAscii = OUTPUT` fails you can run this command to see by the new output: ``` #eval IO.print (TEST).toAscii ``` # Future Work This library is still in early alpha. Let's make it better together! Expect bugs! For any problems, please reach out on [discord](https://discord.gg/UGEcqxpSMn), file a ticket on github or contact me via email. If you have a Lean problem, there are many really helpful people in this [chat](https://leanprover.zulipchat.com/). There are still a lot of things that need modeling: e.g. other kinds of belts and inserters, modules, beaconized factories, quality, rails, etc. I'm happy to take contributions! ================================================ FILE: RedScience150.lean ================================================ import Functorio 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 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 main : IO Unit := IO.println (redScience.toBlueprint) -- (bootstrap := true)) ================================================ FILE: Rocket.lean ================================================ import Functorio instance : Config where generateBigPoles := true generateRoboports := true providerChestCapacity := 0 adapterMinHeight := 3 def makeLowDensityStructure : Steel 40 -> Copper 400 -> Plastic 100 -> Bus (LowDensityStructure 20) := busAssemblyLine (recipe .lowDensityStructure) 4 def makeAcid : Water 6000 -> Sulfur 300 -> Iron 60 -> Bus (Acid 3000) := busAssemblyLine (recipe RecipeName.sulfuricAcid) 1 def makeCopper : CopperOre 1500 -> Bus (Copper 1500) := busAssemblyLine (recipe .copperPlate) 40 def makeIron : IronOre 900 -> Bus (Iron 900) := busAssemblyLine (recipe .ironPlate) 24 def makeSteel : Iron 225 -> Bus (Steel 45) := busAssemblyLine (recipe .steelPlate) 6 def makeSulfur : Water 7200 -> Petrolium 7200 -> Bus (Sulfur 480) := busAssemblyLine (recipe .sulfur) 4 def makePlastic : Petrolium 2400 -> Coal 120 -> Bus (Plastic 240) := busAssemblyLine (recipe .plasticBar) 2 def makeCable : Copper 1050 -> Bus (Cable 2100) := busAssemblyLine (recipe .copperCable) 7 def makeGreenCircuit : Iron 600 -> Cable 1800 -> Bus (GreenCircuit 600) := busAssemblyLine (recipe .electronicCircuit) 4 def makeRedCircuit : GreenCircuit 100 -> Plastic 100 -> Cable 200 -> Bus (RedCircuit 50) := busAssemblyLine (recipe .advancedCircuit) 4 def makeBlueCircuit : Acid (225/2) -> GreenCircuit 450 -> RedCircuit 45 -> Bus (BlueCircuit (45/2)) := busAssemblyLine (recipe .processingUnit) 3 def makeSolidFuel : LightOil 2400 -> Bus (SolidFuel 240) := busAssemblyLine (recipe .solidFuelFromLightOil) 4 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 rocketFactory := bus do let copper <- input .copperPlate 1500 let iron <- input .ironPlate 900 let coal <- input .coal 120 let water <- input .water 13200 let petrol <- input .petroleumGas 9600 let lightOil <- input .lightOil 2600 let (water0, water1) <- split (left:=7200) water let (petrol0, petrol1) <- split (left:=7200) petrol let (lightOil0, lightOil1) <- split lightOil let (copper0, copper1) <- split copper let (iron0, iron) <- split iron let (iron1, iron2) <- split iron let steel <- makeSteel iron0 let sulfur <- makeSulfur water0 petrol0 let acid <- makeAcid water1 sulfur.less iron1 let plastic <- makePlastic petrol1 coal let (plastic0, plastic1) <- split plastic let lowDensityStruct <- makeLowDensityStructure steel.less copper0 plastic0 let solidFuel <- makeSolidFuel lightOil0 let rocketFuel <- makeRocketFuel lightOil1 solidFuel.less let cable <- makeCable copper1.less let (cable0, cable1) <- split cable let greenCircuit <- makeGreenCircuit iron2.less cable0 let (greenCircuit0, greenCircuit1) <- split greenCircuit let redCircuit <- makeRedCircuit greenCircuit0 plastic1.less cable1.less let blueCircuit <- makeBlueCircuit acid.less greenCircuit1.less redCircuit.less makeRocket blueCircuit.less lowDensityStruct rocketFuel def main : IO Unit := IO.println (rocketFactory.toBlueprint) -- (bootstrap := true)) ================================================ FILE: Spaceship.lean ================================================ import Functorio instance : Config where adapterMinHeight := 3 def makeWater : Ice 240 -> Bus (Water 4800) := busAssemblyLine (recipe .iceMelting) 4 def makeFuel : Water 1200 -> Carbon 24 -> Calcite 12 -> Bus (ThrusterFuel 18000) := busAssemblyLine (recipe .advancedThrusterFuel) 2 def makeOxidizer : Water 1200 -> IronOre 24 -> Calcite 12 -> Bus (ThrusterOxidizer 18000) := busAssemblyLine (recipe .advancedThrusterOxidizer) 2 def meltIronOre : IronOre 750 -> Calcite 15 -> Bus (MoltenIron 11250) := busAssemblyLine (recipe .moltenIron) 2 def meltCopperOre : CopperOre 750 -> Calcite 15 -> Bus (MoltenCopper 11250) := busAssemblyLine (recipe .moltenCopper) 2 def castIronPlate : MoltenIron 3000 -> Bus (Iron 450) := busAssemblyLine (recipe .castingIron) 2 def castCable : MoltenCopper 2400 -> Bus (Cable 1440) := busAssemblyLine (recipe .castingCopperCable) 2 def castSteel : MoltenIron 4500 -> Bus (Steel 225) := busAssemblyLine (recipe .castingSteel) 2 def makeYellowAmmo : Iron 300 -> Bus (YellowAmmo 75) := busAssemblyLine (recipe .firearmMagazine) 1 def makeCoal : Water 600 -> Carbon 300 -> Sulfur 60 -> Bus (Coal 60) := busAssemblyLine (recipe .coalSynthesis) 2 def makeExplosives : Water 300 -> Sulfur 30 -> Coal 30 -> Bus (Explosives 60) := busAssemblyLine (recipe .explosives) 2 def makeRockets : Explosives (75/2) -> Iron 75 -> Bus (Rocket (75/2)) := busAssemblyLine (recipe .rocket) 2 def makeRailgunAmmo : Steel 30 -> Cable 60 -> Explosives 12 -> Bus (RailgunAmmo 6) := busAssemblyLine (recipe .railgunAmmo) 2 def spaceship := bus do let ice <- input .ice 240 let sulfur <- input .sulfur 90 let calcite <- input .calcite 54 let ironOre <- input .ironOre 774 let copperOre <- input .copperOre 750 let carbon <- input .carbon 324 let (calcite0, calcite) <- split calcite let (calcite1, calcite) <- split calcite let (calcite2, calcite3) <- split calcite let (ironOre0, ironOre1) <- split ironOre let (carbon0, carbon1) <- split carbon let (sulfur0, sulfur1) <- split sulfur let water <- makeWater ice let (water0, water) <- split water let (water1, water) <- split water let (water2, water3) <- split water let coal <- makeCoal water0 carbon0 sulfur0 let explosives <- makeExplosives water1 sulfur1 coal.less let (explosives0, explosives1) <- split explosives let moltenCopper <- meltCopperOre copperOre calcite0 let cable <- castCable moltenCopper.less let moltenIron <- meltIronOre ironOre0 calcite1 let (moltenIron0, moltenIron1) <- split moltenIron let ironPlate <- castIronPlate moltenIron0 let (ironPlate0, ironPlate1) <- split ironPlate let steel <- castSteel moltenIron1.less let railgunAmmo <- makeRailgunAmmo steel.less cable.less explosives0 let rockets <- makeRockets explosives1.less ironPlate0 let bullets <- makeYellowAmmo ironPlate1.less let fuel <- makeFuel water2 carbon1 calcite2 let oxidizer <- makeOxidizer water3.less ironOre1.less calcite3 def main : IO Unit := IO.println (spaceship.toBlueprint) ================================================ FILE: fulgora-150.sh ================================================ #!/bin/bash lake build fulgora-150 > /dev/null echo -n 0; .lake/build/bin/fulgora-150 | pigz -zc | base64 -w0; echo ================================================ FILE: generate-recipe.py ================================================ #!/usr/bin/env python3 # Generates the Recipe.lean file from a factorio data dump, get it by passing --dump-data to factorio from fractions import Fraction import json def to_camel_case(kebab_case_str: str) -> str: parts = kebab_case_str.split('-') return parts[0] + ''.join(x.capitalize() for x in parts[1:]) def float_to_fraction(f: float) -> str: return str(Fraction(f).limit_denominator(1000)) with open('data-raw-dump.json', 'r') as f: data = json.load(f) buildings_data = data['assembling-machine'] | data['furnace'] | data['rocket-silo'] recipes_data = data['recipe'] ingredients_data = data['item'] | data['ammo'] | data['fluid'] | data['item-with-entity-data'] | data['tool'] | data['capsule'] | data['gun'] | data['module'] | data['armor'] recipe_names = [key for key in recipes_data.keys() if key != "recipe-unknown"] building_names = list(buildings_data.keys()) ingredient_names = set() categories = set() for name in recipe_names: recipe = recipes_data[name] categories.add(recipe.get('category', 'crafting')) for ingredient in recipe.get('ingredients', []): ingredient_names.add(ingredient['name']) for ingredient in recipe.get('results', []): ingredient_names.add(ingredient['name']) for building in buildings_data.values(): for category in building["crafting_categories"]: categories.add(category) # Build the Lean file content as a list of strings lean_code = [] # Header lean_code.append("-- Generated by generate-recipe.py. Do not modify.\n") lean_code.append("import Functorio.Fraction\n") lean_code.append("import Functorio.Direction\n") # Ingredient inductive type lean_code.append("inductive Ingredient") for ingredient in sorted(ingredient_names): lean_code.append(f" | {to_camel_case(ingredient)}") lean_code.append(" deriving DecidableEq, Repr, Inhabited\n") # Categories inductive type lean_code.append("inductive RecipeCategory") for category in sorted(list(categories)): lean_code.append(f" | {to_camel_case(category)}") lean_code.append(" deriving DecidableEq, Repr, Inhabited\n") # isLiquid function lean_code.append("namespace Ingredient\n") lean_code.append("def isLiquid : Ingredient -> Bool") for name in sorted(list(ingredient_names)): if ingredients_data.get(name, {}).get('type') == 'fluid': lean_code.append(f"| .{to_camel_case(name)} => true") lean_code.append(f"| _ => false\n") lean_code.append("def spoilResult : Ingredient -> Option Ingredient") for name in sorted(list(ingredient_names)): try: spoil_result = ingredients_data[name]['spoil_result'] lean_code.append(f"| .{to_camel_case(name)} => .some {to_camel_case(spoil_result)}") except Exception: continue lean_code.append(f"| _ => .none\n") lean_code.append("def name : Ingredient -> String") for name in sorted(ingredient_names): lean_code.append(f'| .{to_camel_case(name)} => "{name}"') lean_code.append("\nend Ingredient") # Static Recipe structure lean_code.append(""" 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 """) # RecipeName inductive type lean_code.append("inductive RecipeName") for name in sorted(recipe_names): lean_code.append(f" | {to_camel_case(name)}") lean_code.append(" deriving DecidableEq, Repr, Inhabited\n") # getRecipe function definition lean_code.append("namespace RecipeName\n") lean_code.append("def getRecipe : RecipeName -> Recipe") for name in sorted(recipe_names): recipe = recipes_data[name] camel_case_name = to_camel_case(name) try: inputs = recipe.get('ingredients', []) outputs = recipe.get('results', []) category = recipe.get('category', 'crafting') time = recipe.get('energy_required', 0.5) except Exception as e: print(f"{name} not supported because of {e}") # sort fluids before solids inputs = [i for i in inputs if i['type'] == 'fluid'] + \ [i for i in inputs if i['type'] != 'fluid'] # large solid outputs come first solidOutputs = [i for i in outputs if i['type'] != 'fluid'] solidOutputs.sort(reverse=True, key=lambda a: a['amount']) liquidOutputs = [i for i in outputs if i['type'] == 'fluid'] outputs = solidOutputs + liquidOutputs # Rocket parts cannot be removed from the rocket silo. if name == "rocket-part": outputs = [] lean_code.append(f"| .{camel_case_name} => {{") lean_code.append(f' name := "{name}",') inputs_list = [ f"({float_to_fraction(ingredient['amount'])}, .{to_camel_case(ingredient['name'])})" for ingredient in inputs ] lean_code.append(f" inputs := [{', '.join(inputs_list)}],") outputs_list = [ f"({float_to_fraction(product['amount'] * product.get('probability', 1))}, .{to_camel_case(product['name'])})" for product in outputs ] lean_code.append(f" outputs := [{', '.join(outputs_list)}],") lean_code.append(f" category := .{to_camel_case(category)}") lean_code.append(f" time := {float_to_fraction(time)}") lean_code.append("}") lean_code.append("\nend RecipeName\n") # Fabricators lean_code.append("inductive Fabricator") for building_name in sorted(building_names): building = buildings_data[building_name] lean_code.append(f" | {to_camel_case(building['name'])}") lean_code.append(" deriving DecidableEq, Repr, Inhabited\n") # Fabricator details lean_code.append("namespace Fabricator\n") lean_code.append("@[simp]") lean_code.append("def details : Fabricator -> FabricatorDetails") for building_name in sorted(building_names): building = buildings_data[building_name] ((x0,y0),(x1,y1)) = building["selection_box"] productivity = building.get('effect_receiver', {}).get("base_effect", {}).get("productivity", 0) lean_code.append(f"| .{to_camel_case(building['name'])} => {{") lean_code.append(f' name := "{building["name"]}"') lean_code.append(f" speedup := {float_to_fraction(building['crafting_speed'])}") lean_code.append(f" productivity := {float_to_fraction(productivity)}") lean_code.append(f" moduleSlots := {building.get('module_slots', 0)}") lean_code.append(f" width := {int(x1 - x0)}") lean_code.append(f" height := {int(y1 - y0)}") lean_code.append(f" fluidBoxes := [") for box in building.get("fluid_boxes", []): info = box["pipe_connections"][0] dir = {0: 'N', 4: 'E', 8: 'S', 12: 'W'}[info['direction']] (x,y) = info["position"] offset = int({ 'N': x - x0 - 0.5, 'E': y - y0 - 0.5, 'S': x - x0 - 0.5, 'W': y - y0 - 0.5, }[dir]) lean_code.append(" {") lean_code.append(f" offset := {offset}") lean_code.append(f" side := .{dir}") lean_code.append(f" type := .{to_camel_case(info['flow_direction'])}") lean_code.append(" },") lean_code.append(" ]") lean_code.append(f" categories := [") for category in sorted(building["crafting_categories"]): lean_code.append(f" .{to_camel_case(category)},") lean_code.append(" ]") lean_code.append("}") lean_code.append(""" def name (f:Fabricator) := f.details.name def width (f:Fabricator) := f.details.width def height (f:Fabricator) := f.details.height @[simp] def speedup (f:Fabricator) := f.details.speedup @[simp] def productivity (f:Fabricator) := f.details.productivity @[simp] def moduleSlots (f:Fabricator) := f.details.moduleSlots def fluidBoxes (f:Fabricator) := f.details.fluidBoxes def handlesCategory (f:Fabricator) (c:RecipeCategory) := f.details.categories.contains c end Fabricator """) # Write the content to the output file file = "Functorio/Recipe.lean" with open(file, 'w') as f: f.write('\n'.join(lean_code)) print(f"✅ Successfully generated Lean file at '{file}'") ================================================ FILE: gleba-300.sh ================================================ #!/bin/bash lake build gleba-300 > /dev/null echo -n 0; .lake/build/bin/gleba-300 | pigz -zc | base64 -w0; echo ================================================ FILE: lake-manifest.json ================================================ {"version": "1.1.0", "packagesDir": ".lake/packages", "packages": [], "name": "functorio", "lakeDir": ".lake"} ================================================ FILE: lakefile.toml ================================================ name = "functorio" version = "0.1.0" defaultTargets = ["nauvis-150"] [leanOptions] autoImplicit = false [[lean_lib]] name = "Functorio" [[lean_exe]] name = "nauvis-150" root = "Nauvis150" [[lean_exe]] name = "red-science-150" root = "RedScience150" [[lean_exe]] name = "rocket" root = "Rocket" [[lean_exe]] name = "fulgora-150" root = "Fulgora150" [[lean_exe]] name = "gleba-300" root = "Gleba300" [[lean_exe]] name = "spaceship" root = "Spaceship" ================================================ FILE: lean-toolchain ================================================ leanprover/lean4:v4.20.1 ================================================ FILE: nauvis-150.sh ================================================ #!/bin/bash lake build nauvis-150 > /dev/null echo -n 0; .lake/build/bin/nauvis-150 | pigz -zc | base64 -w0; echo ================================================ FILE: red-science-150.sh ================================================ #!/bin/bash lake build red-science-150 > /dev/null echo -n 0; .lake/build/bin/red-science-150 | pigz -zc | base64 -w0; echo ================================================ FILE: rocket.sh ================================================ #!/bin/bash lake build rocket > /dev/null echo -n 0; .lake/build/bin/rocket | pigz -zc | base64 -w0; echo ================================================ FILE: spaceship.sh ================================================ #!/bin/bash lake build spaceship > /dev/null echo -n 0; .lake/build/bin/spaceship | pigz -zc | base64 -w0; echo