Skip to content

Fix stranded-terminal recovery on a half-solved field - #88

Merged
wormeyman merged 1 commit into
mainfrom
fix/stranded-terminal-component
Aug 17, 2026
Merged

Fix stranded-terminal recovery on a half-solved field#88
wormeyman merged 1 commit into
mainfrom
fix/stranded-terminal-component

Conversation

@wormeyman

Copy link
Copy Markdown
Collaborator

Two bugs in the same recovery path, found while investigating why big-list blueprint 227 stops planning under the Factorio 2.1 terminal offsets (#81).

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:

before:  entry terminalLocations=47  ->  components [43, 3, 1], best covers 21/23 pumpjacks -> STRANDED
after:   entry terminalLocations=82  ->  components [76, 3, 3], best covers 23/23 pumpjacks -> ok

Two pumpjacks looked unreachable purely because the failed attempt had already pruned their terminals.

That 82 is not 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 one.

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 a single step.

It now 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

Change Snapshot churn
Component selection None. The branch that got it wrong fires zero times across all 1147 big-list blueprints, and on its own it leaves every snapshot untouched.
Context restore Two snapshots, both in AllowsBlueprintWithNonBlockingIsolatedArea - the test that exercises this path by name.

Both selected plans in those two snapshots get better:

CC-DT     effects 114 -> 116, beacons 62 -> 64, pipes 77 -> 78
CC-FLUTE  effects 116 -> 116, beacons 64 -> 64, pipes 81 -> 77

Selection ranks on effects, then fewest beacons, then fewest pipes. CC-DT wins on the primary criterion; CC-FLUTE ties and drops 4 pipes.

Score.HasExpectedScore is unchanged.

An ablation, so the two are not conflated

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, not because 227 needs it.

Checks

  • dotnet test: 4299 passed, 0 failed.
  • dotnet build /p:UseLuaSettings=true: clean.
  • src/lua regenerated and committed; tools/check-lua.sh passes both the Lua 5.2.4 syntax check and the sample.lua run.

Relationship to the other work

This is the second of two latent planner bugs that the 2.1 terminal offsets expose. The first is the Group.Location centroid collision in #87. Both are prerequisites for landing #81, and both are separated out so a subtle logic fix does not land inside a 2743-file snapshot diff.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P9FADuTnjE7SFEQWnpNhfc

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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P9FADuTnjE7SFEQWnpNhfc
@wormeyman
wormeyman merged commit e241972 into main Aug 17, 2026
5 checks passed
@wormeyman
wormeyman deleted the fix/stranded-terminal-component branch August 17, 2026 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant