From 1fc6fc8091b455edb18f724cd04bdf1901078e49 Mon Sep 17 00:00:00 2001 From: Eric J Date: Mon, 17 Aug 2026 12:27:41 -0700 Subject: [PATCH] Fix stranded-terminal recovery on a half-solved field Two bugs in the same recovery path, found while investigating why big-list blueprint 227 stops planning under Factorio 2.1 terminal offsets. 1. The recovery ran on a mutated clone. GetBestSolution swaps context.CenterToTerminals for a per-strategy clone and mutates it, and does not put the originals back when a strategy fails. SelectBestSolution then called EliminateStrandedTerminals on whatever was left, so the recovery reasoned about a half-solved field rather than the real one. Measured on blueprint 227: the recovery saw 47 terminal locations where the field actually has 82. Two pumpjacks looked unreachable purely because the failed attempt had already pruned their terminals, and eliminating what looked like a pocket stranded them. Restoring the originals first makes the recovery see all 82, split them into components of 76, 3 and 3, and find all 23 pumpjacks in the largest. Blueprint 227 plans again. The 82 is not just an internal number agreeing with itself. An independent flood fill over the raw pumpjack geometry counted 82 free terminal cells, which is what says the restored view is the true one. 2. EliminateStrandedTerminals kept the wrong component. It walked one component and, if that component did not cover every pumpjack, assumed it was a stranded pocket and eliminated it. That only holds when the component it happened to walk first is the minority. On blueprint 227 the first walk reached 43 of 47 terminals covering 21 of 23 pumpjacks, and the old code eliminated all 43 - throwing away the main network to keep a two-pumpjack pocket, stranding 19 pumpjacks in one step. Now it enumerates every component and keeps the one serving the most pumpjacks, which makes the choice independent of where the walk started. What each change costs, measured separately rather than assumed: - The component fix changes nothing. The branch that got it wrong fires zero times across all 1147 big-list blueprints, and on its own it leaves every Verify snapshot untouched. - The restore fix changes two snapshots, both in AllowsBlueprintWithNonBlockingIsolatedArea, which is the test that exercises this path by name. Both selected plans get better: CC-DT goes from 114 to 116 beacon effects, and CC-FLUTE drops 4 pipes at equal effects and beacons. An ablation confirms the split: the restore fix alone makes blueprint 227 pass, so the component fix is defensive rather than load-bearing. It is included because it is wrong on its own terms and free to correct. Full suite passes 4299 of 4299. src/lua regenerated; tools/check-lua.sh passes both the Lua 5.2.4 syntax check and the sample run. Builds clean under /p:UseLuaSettings=true. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01P9FADuTnjE7SFEQWnpNhfc --- .../OilField/Steps/AddPipes.0.cs | 96 +++++++++----- src/lua/FactorioTools/AddPipes_0.lua | 118 ++++++++++++------ ...ry.E_ConnectedCentersDelaunay.verified.txt | 63 +++++----- ...heory.E_ConnectedCentersFlute.verified.txt | 79 ++++++------ 4 files changed, 217 insertions(+), 139 deletions(-) diff --git a/src/FactorioTools/OilField/Steps/AddPipes.0.cs b/src/FactorioTools/OilField/Steps/AddPipes.0.cs index b2b8dc47..a10c8b4e 100644 --- a/src/FactorioTools/OilField/Steps/AddPipes.0.cs +++ b/src/FactorioTools/OilField/Steps/AddPipes.0.cs @@ -107,6 +107,11 @@ private static Result SelectBestSolution(Context context, bool eli var result = GetBestSolution(context); if (result.Exception is NoPathBetweenTerminalsException && !eliminateStrandedTerminals) { + // GetBestSolution swaps in a per-strategy clone and mutates it, and does + // not put the originals back when it fails. Without this the recovery + // reasons about a half-solved field. + context.CenterToTerminals = centerToTerminals; + context.LocationToTerminals = locationToTerminals; EliminateStrandedTerminals(context); result = GetBestSolution(context); } @@ -625,49 +630,82 @@ private static Solution GetSolution( }; } + /// + /// Splits the terminal locations into connected components, keeps the one serving + /// the most pumpjacks, and eliminates every other component. + /// + /// This used to decide one component at a time. It walked a component, and if that + /// component did not cover every pumpjack it assumed the component was a stranded + /// pocket and eliminated it. That assumption only holds when the component it + /// happened to walk first is the minority one. + /// + /// Measured on big-list blueprint 227 under Factorio 2.1 terminal offsets: the + /// first walk reached 43 of 47 terminals, covering 21 of 23 pumpjacks, and the old + /// code eliminated all 43. It threw away the main network to keep a two-pumpjack + /// pocket, stranding 19 pumpjacks in a single step. + /// + /// Keeping the largest component instead makes the choice independent of which + /// terminal the walk started from. The branch that got this wrong never runs on the + /// current blueprint corpus - instrumented across all 1147 big-list blueprints, it + /// fired zero times - so this changes no existing plan. + /// private static void EliminateStrandedTerminals(Context context) { - var locationsToExplore = context.LocationToTerminals.Keys.ToReadOnlySet(context, allowEnumerate: true); + var remaining = context.LocationToTerminals.Keys.ToReadOnlySet(context, allowEnumerate: true); - while (locationsToExplore.Count > 0) + var components = new List(); + while (remaining.Count > 0) { - var goals = context.GetLocationSet(locationsToExplore); + var goals = context.GetLocationSet(remaining); var start = goals.EnumerateItems().First(); goals.Remove(start); var result = Dijkstras.GetShortestPaths(context, context.Grid, start, goals, stopOnFirstGoal: false, allowGoalEnumerate: true); - var reachedTerminals = result.ReachedGoals; - reachedTerminals.Add(start); + var component = context.GetLocationSet(result.ReachedGoals); + component.Add(start); + components.Add(component); - var unreachedTerminals = context.GetLocationSet(goals); - unreachedTerminals.ExceptWith(result.ReachedGoals); + remaining.ExceptWith(component); + } + + // One component means everything can already reach everything else. + if (components.Count < 2) + { + return; + } - var reachedPumpjacks = context.GetLocationSet(); - foreach (var location in result.ReachedGoals.EnumerateItems()) + var keep = 0; + var mostPumpjacks = -1; + for (var i = 0; i < components.Count; i++) + { + var centers = context.GetLocationSet(); + foreach (var location in components[i].EnumerateItems()) { var terminals = context.LocationToTerminals[location]; - for (var i = 0; i < terminals.Count; i++) + for (var j = 0; j < terminals.Count; j++) { - reachedPumpjacks.Add(terminals[i].Center); + centers.Add(terminals[j].Center); } } - ILocationSet terminalsToEliminate; - if (reachedPumpjacks.Count == context.CenterToTerminals.Count) + if (centers.Count > mostPumpjacks) { - terminalsToEliminate = unreachedTerminals; - locationsToExplore.Clear(); + mostPumpjacks = centers.Count; + keep = i; } - else + } + + Location strandedTerminal = Location.Invalid; + bool foundStranded = false; + for (var i = 0; i < components.Count; i++) + { + if (i == keep) { - terminalsToEliminate = reachedTerminals; - locationsToExplore = unreachedTerminals; + continue; } - Location strandedTerminal = Location.Invalid; - bool foundStranded = false; - foreach (var location in terminalsToEliminate.EnumerateItems()) + foreach (var location in components[i].EnumerateItems()) { foreach (var terminal in context.LocationToTerminals[location]) { @@ -683,17 +721,13 @@ private static void EliminateStrandedTerminals(Context context) context.LocationToTerminals.Remove(location); } - - if (foundStranded) - { - /* - var clone = new PipeGrid(context.Grid); - AddPipeEntities.Execute(clone, new(), context.CenterToTerminals, new ILocationSet(), undergroundPipes: null, allowMultipleTerminals: true); - Visualizer.Show(clone, new[] { strandedTerminal.Value, locationsToExplore.First() }.Select(x => (IPoint)new Point(x.X, x.Y)), Array.Empty()); - */ + } - throw new NoPathBetweenTerminalsException(strandedTerminal, locationsToExplore.EnumerateItems().First()); - } + // A pumpjack whose every terminal sat outside the kept component genuinely + // cannot join the network, so this is still the right place to give up. + if (foundStranded) + { + throw new NoPathBetweenTerminalsException(strandedTerminal, components[keep].EnumerateItems().First()); } } diff --git a/src/lua/FactorioTools/AddPipes_0.lua b/src/lua/FactorioTools/AddPipes_0.lua index 418e9f17..c47650af 100644 --- a/src/lua/FactorioTools/AddPipes_0.lua +++ b/src/lua/FactorioTools/AddPipes_0.lua @@ -8,6 +8,7 @@ local ListInt32 local ListPlanInfo local ListSolution local NullableInt32 +local ListILocationSet local ListOilFieldPlan local ListTerminalLocation local Comparer_1NullableInt32 @@ -24,6 +25,7 @@ System.import(function (out) ListPlanInfo = System.List(KnapcodeAddPipes.PlanInfo) ListSolution = System.List(KnapcodeAddPipes.Solution) NullableInt32 = System.Nullable(System.Int32) + ListILocationSet = System.List(KnapcodeOilField.ILocationSet) ListOilFieldPlan = System.List(KnapcodeOilField.OilFieldPlan) ListTerminalLocation = System.List(KnapcodeOilField.TerminalLocation) Comparer_1NullableInt32 = System.Comparer_1(NullableInt32) @@ -364,6 +366,11 @@ System.namespace("Knapcode.FactorioTools.OilField", function (namespace) local result = GetBestSolution(context) if System.is(result.Exception, KnapcodeFactorioTools.NoPathBetweenTerminalsException) and not eliminateStrandedTerminals then + -- GetBestSolution swaps in a per-strategy clone and mutates it, and does + -- not put the originals back when it fails. Without this the recovery + -- reasons about a half-solved field. + context.CenterToTerminals = centerToTerminals + context.LocationToTerminals = locationToTerminals EliminateStrandedTerminals(context) result = GetBestSolution(context) end @@ -827,65 +834,100 @@ System.namespace("Knapcode.FactorioTools.OilField", function (namespace) default.UncoveredCenters = uncoveredCenters return default end + -- + -- Splits the terminal locations into connected components, keeps the one serving + -- the most pumpjacks, and eliminates every other component. + -- + -- This used to decide one component at a time. It walked a component, and if that + -- component did not cover every pumpjack it assumed the component was a stranded + -- pocket and eliminated it. That assumption only holds when the component it + -- happened to walk first is the minority one. + -- + -- Measured on big-list blueprint 227 under Factorio 2.1 terminal offsets: the + -- first walk reached 43 of 47 terminals, covering 21 of 23 pumpjacks, and the old + -- code eliminated all 43. It threw away the main network to keep a two-pumpjack + -- pocket, stranding 19 pumpjacks in a single step. + -- + -- Keeping the largest component instead makes the choice independent of which + -- terminal the walk started from. The branch that got this wrong never runs on the + -- current blueprint corpus - instrumented across all 1147 big-list blueprints, it + -- fired zero times - so this changes no existing plan. + -- EliminateStrandedTerminals = function (context) - local locationsToExplore = KnapcodeFactorioTools.CollectionExtensions.ToReadOnlySet1(context.LocationToTerminals:getKeys(), context, true) + local remaining = KnapcodeFactorioTools.CollectionExtensions.ToReadOnlySet1(context.LocationToTerminals:getKeys(), context, true) - while locationsToExplore:getCount() > 0 do - local goals = context:GetLocationSet(locationsToExplore) + local components = ListILocationSet() + while remaining:getCount() > 0 do + local goals = context:GetLocationSet(remaining) local start = KnapcodeFactorioTools.CollectionExtensions.First(goals:EnumerateItems(), KnapcodeOilField.Location) goals:Remove(start) local result = KnapcodeOilField.Dijkstras.GetShortestPaths(context, context.Grid, start, goals, false, true) - local reachedTerminals = result.ReachedGoals - reachedTerminals:Add(start) + local component = context:GetLocationSet(result.ReachedGoals) + component:Add(start) + components:Add(component) - local unreachedTerminals = context:GetLocationSet(goals) - unreachedTerminals:ExceptWith(result.ReachedGoals) + remaining:ExceptWith(component) + end + + -- One component means everything can already reach everything else. + if #components < 2 then + return + end - local reachedPumpjacks = context:GetLocationSet1() - for _, location in System.each(result.ReachedGoals:EnumerateItems()) do + local keep = 0 + local mostPumpjacks = - 1 + for i = 0, #components - 1 do + local centers = context:GetLocationSet1() + for _, location in System.each(components:get(i):EnumerateItems()) do local terminals = context.LocationToTerminals:get(location) - for i = 0, #terminals - 1 do - reachedPumpjacks:Add(terminals:get(i).Center) + for j = 0, #terminals - 1 do + centers:Add(terminals:get(j).Center) end end - local terminalsToEliminate - if reachedPumpjacks:getCount() == context.CenterToTerminals:getCount() then - terminalsToEliminate = unreachedTerminals - locationsToExplore:Clear() - else - terminalsToEliminate = reachedTerminals - locationsToExplore = unreachedTerminals + if centers:getCount() > mostPumpjacks then + mostPumpjacks = centers:getCount() + keep = i end + end - local strandedTerminal = KnapcodeOilField.Location.getInvalid() - local foundStranded = false - for _, location in System.each(terminalsToEliminate:EnumerateItems()) do - for _, terminal in System.each(context.LocationToTerminals:get(location)) do - local terminals = context.CenterToTerminals:get(terminal.Center) - terminals:Remove(terminal) - - if #terminals == 0 then - strandedTerminal = terminal.Terminal - foundStranded = true - end + local strandedTerminal = KnapcodeOilField.Location.getInvalid() + local foundStranded = false + for i = 0, #components - 1 do + local continue + repeat + if i == keep then + continue = true + break end - context.LocationToTerminals:Remove(location) - end + for _, location in System.each(components:get(i):EnumerateItems()) do + for _, terminal in System.each(context.LocationToTerminals:get(location)) do + local terminals = context.CenterToTerminals:get(terminal.Center) + terminals:Remove(terminal) - if foundStranded then - --[[ - var clone = new PipeGrid(context.Grid); - AddPipeEntities.Execute(clone, new(), context.CenterToTerminals, new ILocationSet(), undergroundPipes: null, allowMultipleTerminals: true); - Visualizer.Show(clone, new[] { strandedTerminal.Value, locationsToExplore.First() }.Select(x => (IPoint)new Point(x.X, x.Y)), Array.Empty()); - ]] + if #terminals == 0 then + strandedTerminal = terminal.Terminal + foundStranded = true + end + end - System.throw(KnapcodeFactorioTools.NoPathBetweenTerminalsException(strandedTerminal, KnapcodeFactorioTools.CollectionExtensions.First(locationsToExplore:EnumerateItems(), KnapcodeOilField.Location))) + context.LocationToTerminals:Remove(location) + end + continue = true + until 1 + if not continue then + break end end + + -- A pumpjack whose every terminal sat outside the kept component genuinely + -- cannot join the network, so this is still the right place to give up. + if foundStranded then + System.throw(KnapcodeFactorioTools.NoPathBetweenTerminalsException(strandedTerminal, KnapcodeFactorioTools.CollectionExtensions.First(components:get(keep):EnumerateItems(), KnapcodeOilField.Location))) + end end class = { Execute = Execute diff --git a/test/FactorioTools.Test/OilField/AllowsBlueprintWithNonBlockingIsolatedArea/Theory.E_ConnectedCentersDelaunay.verified.txt b/test/FactorioTools.Test/OilField/AllowsBlueprintWithNonBlockingIsolatedArea/Theory.E_ConnectedCentersDelaunay.verified.txt index 0e37fbac..9db9af10 100644 --- a/test/FactorioTools.Test/OilField/AllowsBlueprintWithNonBlockingIsolatedArea/Theory.E_ConnectedCentersDelaunay.verified.txt +++ b/test/FactorioTools.Test/OilField/AllowsBlueprintWithNonBlockingIsolatedArea/Theory.E_ConnectedCentersDelaunay.verified.txt @@ -1,40 +1,41 @@ - CC-DT -> FBE* (effects: 112, beacons: 62, pipes: 82) - CC-DT -> snug (effects: 105, beacons: 58, pipes: 82) -S CC-DT -> optimize -> FBE* (effects: 114, beacons: 62, pipes: 77) - CC-DT -> optimize -> snug (effects: 101, beacons: 57, pipes: 77) + CC-DT -> FBE* (effects: 109, beacons: 62, pipes: 84) + CC-DT -> snug (effects: 103, beacons: 58, pipes: 84) +S CC-DT -> optimize -> FBE* (effects: 116, beacons: 64, pipes: 78) + CC-DT -> optimize -> snug (effects: 103, beacons: 58, pipes: 78) -. . . . . . . . . . . b b b b b b . b b b b b b . . b b b . . . . . b b b . . . . . . . . . -. . . . . . . . . . . b B b b B b . b B b b B b . . b B b . . . . . b B b . . . . . . . . . -. . . . . . . . . . . b b b b b b . b b b b b b . . b b b b b b . . b b b . . . . . . . . . -. . . . . . . . . E . . . . . . . . . . . . . . . . . . . b B b . . . . . . . . . . . . . . -. . . . b b b b b b b b b . . . . . E . . o < . . > + o o b b b . . . . . . . . . . . . . . -. . . . b B b b B b b B b b b b j j j + o o . E j j j . o j j j E b b b b b b . b b b b b b -E . . . b b b b b b b b b b B b j J j . . ^ . . j J j . o j J j . b B b b B b . b B b b B b -b b b b b b . . . . . . . b b b j j j . . . . . j j j . + j j j . b b b b b b . b b b b b b -b B b b B b . . . . . . . . . . . . . . . . j j j b b b o j j j . . . . . . . . . . . b b b -b b b b b b b b b j j j . . b b b . . . . v j J j b B b o j J j . . . . . . . . . . . b B b -b b b . . . b B b j J j . E b B b . o o o + j j j b b b + j j j . . b b b . j j j E . b b b -b B b . . . b b b j j j . . b b b . o j j j . . . j j j + . . . . . b B b . j J j . . . . . -b b b . . j j j + + < . . > o < . > + j J j . E . j J j o . . . . . b b b . j j j . . b b b +. . . . . . . . . . . b b b b b b . . b b b b b b . b b b b b b . . b b b . . . . . . . . . +. . . . . . . . . . . b B b b B b . . b B b b B b . b B b b B b . . b B b . . . . . . . . . +. . . . . . . . . . . b b b b b b . . b b b b b b . b b b b b b . . b b b . . . . . . . . . +. . . . . . . . . . . b b b . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +. . . . b b b b b b . b B b . . . . . . . b b b . . . . . . . . . . . . . . . . . . . . . . +. . . . b B b b B b . b b b . E j j j E . b B b j j j + E j j j E b b b b b b . b b b b b b +. E . . b b b b b b b b b b b b j J j . . b b b j J j o . j J j . b B b b B b . b B b b B b +b b b b b b . . E . b B b b B b j j j b b b . . j j j o + j j j . b b b b b b . b b b b b b +b B b b B b . . . . b b b b b b ^ . . b B b j j j b b b o j j j . . . . . . . . . . . b b b +b b b b b b b b b j j j b b b . . . . b b b j J j b B b o j J j . . . . . . . . . . . b B b +b b b . . . b B b j J j b B b . . . o o o + j j j b b b + j j j . . b b b . j j j E . b b b +b B b . . . b b b j j j b b b . v . o j j j . . . j j j + . . . . . b B b . j J j . . . . . +b b b . . j j j + + < . . > o o o o + j J j . . . j J j o . . . . . b b b . j j j . . b b b . . . . . j J j . b b b . . o . j j j j j j j j j j j j o o o + < . > o o o + b b b E b B b -b b b . E j j j . b B b . . + . j J j . . . j J j . . . o j j j . . . o . . E b B b . b b b -b B b . . . . . . b b b j j j . j j j . . . j j j o o o + j J j . E . o . . . b b b . b b b -b b b . . . . E . b b b j J j b b b . j j j + o o o j j j j j j j j j + j j j . . . . b B b -b b b b b b b b b b B b j j j b B b . j J j ^ . E . j J j . . . j J j o j J j . . . . b b b -b B b b B b b B b b b b b b b b b b . j j j b b b . j j j . . . j j j + j j j . . b b b . . -b b b b b b b b b . . . b B b j j j + + . . b B b b b b b b b j j j b b b b b b . b B b . . -. . . . . . . b b b . E b b b j J j . o . . b b b b B b b B b j J j b B b b B b . b b b . . -. . . . . . . b B b b b b . E j j j . o . . v . E b b b b b b j j j b b b b b b . b b b . . +b b b . . j j j . b B b . E + o j J j . . . j J j E . . o j j j . . . o . E . b B b . b b b +b B b E . . . . . b b b j j j o j j j E . . j j j o o o + j J j . E . o . . . b b b . b b b +b b b . . . . . . b b b j J j o o o o j j j + o o o j j j j j j j j j + j j j . . . . b B b +b b b b b b b b b b B b j j j . . . o j J j ^ . . . j J j . . . j J j o j J j . . . . b b b +b B b b B b b B b b b b b b b . . . o j j j b b b . j j j . . . j j j + j j j . . b b b . . +b b b b b b b b b . . E b B b j j j + + . . b B b b b b b b b j j j b b b b b b . b B b . . +. . . . . . . b b b . . b b b j J j . o . . b b b b B b b B b j J j b B b b B b . b b b . . +. . . . . . . b B b b b b . . j j j . o . . v . . b b b b b b j j j b b b b b b . b b b . . . . . . . . . b b b b B b . b b b . . + o o o < . > + < . > o + . . . . . . E . . b B b . . . . . . . . . . . . b b b . b B b j j j b b b . j j j b b b ^ b b b . . . . . . . b b b . . -. . . . . . . . . . b b b . b b b j J j b B b . j J j b B b E b B b . . b b b . . . . . . . +. . . . . . . . . . b b b E b b b j J j b B b . j J j b B b E b B b . . b b b . . . . . . . . . . . . . . . . . b B b b b b . j j j b b b . j j j b b b . b b b . . b B b . . . . . . . -. . . . . . . . . . b b b b B b E . . . . . . . . b b b . . v . . b b b b b b . . . . . . . -. . . . . . . . . . . . . b b b . . . . . . . . . b B b j j j . . b B b . . . . . . . . . . +. . . . . . . . . . b b b b B b . . . . . . E . . b b b . . v . . b b b b b b . . . . . . . +. . . . . . . . . . . . E b b b . . . . . . . . . b B b j j j . . b B b . . . . . . . . . . . . . . . . . . . . . . b b b b b b . b b b b b b b b b j J j . . b b b . . . . . . . . . . -. . . . . . . . . . . . b B b b B b . b B b b B b b b b j j j . . b b b . . . . . . . . . . -. . . . . . . . . . . . b b b b b b . b b b b b b b B b . . E . . b B b . . . . . . . . . . -. . . . . . . . . . . . . . . . . . . . . . . . E b b b . . . . . b b b . . . . . . . . . . +. . . . . . . . . . . . b B b b B b . b B b b B b b b b j j j E . b b b . . . . . . . . . . +. . . . . . . . . . . . b b b b b b . b b b b b b b B b . . . . . b B b . . . . . . . . . . +. . . . . . . . . . . . . . . . . . . . . . . . . b b b . . . . . b b b . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . b b b b b b . b b b b b b . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . b B b b B b . b B b b B b . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . b b b b b b . b b b b b b . . . . . . . . . . +. . . . . . . . . . . . . . . . . . . . . . E . . . . . . . . . . . . . . . . . . . . . . . diff --git a/test/FactorioTools.Test/OilField/AllowsBlueprintWithNonBlockingIsolatedArea/Theory.E_ConnectedCentersFlute.verified.txt b/test/FactorioTools.Test/OilField/AllowsBlueprintWithNonBlockingIsolatedArea/Theory.E_ConnectedCentersFlute.verified.txt index 389a2ac4..9c582917 100644 --- a/test/FactorioTools.Test/OilField/AllowsBlueprintWithNonBlockingIsolatedArea/Theory.E_ConnectedCentersFlute.verified.txt +++ b/test/FactorioTools.Test/OilField/AllowsBlueprintWithNonBlockingIsolatedArea/Theory.E_ConnectedCentersFlute.verified.txt @@ -1,40 +1,41 @@ - CC-FLUTE -> FBE* (effects: 116, beacons: 64, pipes: 93) - CC-FLUTE -> snug (effects: 112, beacons: 61, pipes: 93) -S CC-FLUTE -> optimize -> FBE* (effects: 116, beacons: 64, pipes: 81) - CC-FLUTE -> optimize -> snug (effects: 106, beacons: 58, pipes: 81) + CC-FLUTE -> FBE* (effects: 114, beacons: 63, pipes: 87) + CC-FLUTE -> snug (effects: 102, beacons: 57, pipes: 87) +S CC-FLUTE -> optimize -> FBE* (effects: 116, beacons: 64, pipes: 77) + CC-FLUTE -> optimize -> snug (effects: 106, beacons: 59, pipes: 77) -. . . . . . . . . . . b b b b b b . b b b b b b . b b b . . . b b b b b b . . . . . . . . . . . -. . . . . . . . . . . b B b b B b . b B b b B b E b B b . . . b B b b B b E . . . . . . . . . . -. . . . . . . . . . . b b b b b b . b b b b b b . b b b b b b b b b b b b . . . . . . . . . . . -. . . . . . E . . . E b b b . . . . . . . . . . . . . . b B b . . . . . . . . . . . . . . . . . -. . . . b b b b b b . b B b . E . . . . b b b o < . > o b b b . . . . . . . . . . . . . . . . . -. . . . b B b b B b . b b b . . j j j . b B b o j j j + E j j j . b b b b b b . b b b b b b . . -. . . . b b b b b b b b b b b b j J j . b b b o j J j o . j J j . b B b b B b . b B b b B b . . -b b b b b b . . . . b B b b B b j j j . . o o o j j j o + j j j . b b b b b b . b b b b b b . . -b B b b B b . . . . b b b b b b + . . . . o j j j b b b o j j j . . . . . . . . . . . b b b . . -b b b b b b b b b j j j b b b . o . . E . o j J j b B b o j J j . . . . . E . . . . . b B b . . -b b b . . . b B b j J j b B b . o < . . > + j j j b b b + j j j . . b b b . j j j E . b b b . . -b B b . . E b b b j j j b b b . o . . j j j . . . j j j + E . . . . b B b . j J j . . . . . . . -b b b . . j j j + + < b b b > o o . . j J j . . . j J j o . . . . . b b b . j j j . . b b b . . -. . . . . j J j b b b b B b E o j j j j j j j j j j j j o o o + < . > o o o + b b b . b B b . . -b b b . . j j j b B b b b b . o j J j . . . j J j . . . o j j j . . . o . . . b B b . b b b . . -b B b . . . . . b b b . j j j + j j j . . . j j j . E . + j J j . . . o . . . b b b . b b b . . -b b b . . . . . . b b b j J j o + o o j j j ^ b b b j j j j j j j j j + j j j . . . . b B b . . -b b b b b b b b b b B b j j j . . . o j J j . b B b j J j E . . j J j o j J j . . . . b b b . . -b B b b B b b B b b b b b b b . . . + j j j v b b b j j j . . . j j j + j j j E . b b b . . . E -b b b b b b b b b . . E b B b j j j + o o o o . b b b . b b b j j j + o . b b b . b B b . . . . -. . . . . . . b b b . . b b b j J j E o E b b b b B b . b B b j J j b b b b B b . b b b . . . . -. . . . . E . b B b b b b . . j j j . o . b B b b b b . b b b j j j b B b b b b . b b b . . . . -. . . . . . . b b b b B b . b b b . . + o b b b . . . . . . . b b b b b b . . . . b B b . . . . -. . . . . . . . . . b b b . b B b j j j ^ b b b j j j b b b . b B b . . . . . . . b b b . . . . -. . . . . . . . . . b b b . b b b j J j . b B b j J j b B b E b b b . . b b b . . . . . . . . . -. . . . . . . . . . b B b b b b . j j j v b b b j j j b b b . . . b b b b B b . . . . . . . . . -. . . . . . . . . . b b b b B b . E . . o < . > + < . E . > + . . b B b b b b . . . . . . . . . -. . . . . . . . . . . . . b b b . . . . . . . . . b b b j j j . . b b b . . . . . . . . . . . . -. . . . . . . . E . . . b b b b b b . . b b b . . b B b j J j . . . . . . . . E . . . . . . . . -. . . . . . . . . . . . b B b b B b . . b B b . . b b b j j j E . b b b . . . . . . . . . . . . -. . . . . . . . . . . . b b b b b b . . b b b . . . . . . . . . . b B b . . . . . . . . . . . . -. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . b b b . . . . . . . . . . . . -. . . . . . . . . . . . . . . . . . . . . . . b b b b b b . b b b b b b . . . . . . . . . . . . -. . . . . . . . . . . . . . . . . . . . . . . b B b b B b . b B b b B b . . . . . . . . . . . . -. . . . . . . . . . . . . . . . . . . . . E . b b b b b b . b b b b b b . . . . . . . . . . . . +. . . . . . . . . . . b b b b b b . . b b b b b b . b b b b b b . . b b b . . . . . . . . . +. . . . . . . . . . . b B b b B b . . b B b b B b . b B b b B b . . b B b . . . . . . . . . +. . . . . . . . . . . b b b b b b . . b b b b b b . b b b b b b . . b b b . . . . . . . . . +. . . . . . . . . . . b b b . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +. . . . b b b b b b . b B b . . . . . . . b b b . . . . . . . . . . . . . . . . . . . . . . +. . . . b B b b B b . b b b . E j j j E . b B b j j j + E j j j E b b b b b b . b b b b b b +. E . . b b b b b b b b b b b b j J j . . b b b j J j o . j J j . b B b b B b . b B b b B b +b b b b b b . . E . b B b b B b j j j b b b . . j j j o + j j j . b b b b b b . b b b b b b +b B b b B b . . . . b b b b b b ^ . . b B b j j j b b b o j j j . . . . . . . . . . . b b b +b b b b b b b b b j j j b b b . . . . b b b j J j b B b o j J j . . . . . . . . . . . b B b +b b b . . . b B b j J j b B b . . . o o o + j j j b b b + j j j . b b b . . j j j E . b b b +b B b . . . b b b j j j b b b . v . o j j j . . . j j j + . . . . b B b . . j J j . . . . . +b b b . . j j j + + < . . > o o o o + j J j . . . j J j o . . . . b b b . . j j j . . b b b +. . . . . j J j . b b b . . o . j j j j j j j j j j j j o o o + o o o . . . + b b b E b B b +b b b . . j j j . b B b . E + o j J j . . . j J j E . . o j j j . . o . . E o b B b . b b b +b B b E . . . . . b b b j j j o j j j E . . j j j o o o + j J j . E + < . > + b b b . b b b +b b b . . . . . . b b b j J j o o o o j j j + o o o j j j j j j j j j . j j j . . . . b B b +b b b b b b b b b b B b j j j . . . o j J j ^ . . . j J j . . . j J j . j J j . . . . b b b +b B b b B b b B b b b b b b b . . . o j j j b b b . j j j . . . j j j . j j j . . b b b . . +b b b b b b b b b . . E b B b j j j + + . . b B b b b b b b b j j j b b b b b b . b B b . . +. . . . . . . b b b . . b b b j J j . o . . b b b b B b b B b j J j b B b b B b . b b b . . +. . . . . . . b B b b b b . . j j j . o . . v . . b b b b b b j j j b b b b b b . b b b . . +. . . . . . . b b b b B b . b b b . . + o o o < . > + < . > o + . . . . . . E . . b B b . . +. . . . . . . . . . b b b . b B b j j j b b b . j j j b b b ^ b b b . . . . . . . b b b . . +. . . . . . . . . . b b b E b b b j J j b B b . j J j b B b E b B b . . b b b . . . . . . . +. . . . . . . . . . b B b b b b . j j j b b b . j j j b b b . b b b . . b B b . . . . . . . +. . . . . . . . . . b b b b B b . . . . . . E . . b b b . . v . . b b b b b b . . . . . . . +. . . . . . . . . . . . E b b b . . . . . . . . . b B b j j j . . b B b . . . . . . . . . . +. . . . . . . . . . . . b b b b b b . b b b b b b b b b j J j . . b b b . . . . . . . . . . +. . . . . . . . . . . . b B b b B b . b B b b B b b b b j j j E . b b b . . . . . . . . . . +. . . . . . . . . . . . b b b b b b . b b b b b b b B b . . . . . b B b . . . . . . . . . . +. . . . . . . . . . . . . . . . . . . . . . . . . b b b . . . . . b b b . . . . . . . . . . +. . . . . . . . . . . . . . . . . . . . . . . b b b b b b . b b b b b b . . . . . . . . . . +. . . . . . . . . . . . . . . . . . . . . . . b B b b B b . b B b b B b . . . . . . . . . . +. . . . . . . . . . . . . . . . . . . . . . . b b b b b b . b b b b b b . . . . . . . . . . +. . . . . . . . . . . . . . . . . . . . . . E . . . . . . . . . . . . . . . . . . . . . . .