Skip to content

feat: measure a plan against what the map actually holds - #602

Draft
Maelstromeous wants to merge 10 commits into
mainfrom
claude/world-resource-limits
Draft

Maelstromeous wants to merge 10 commits into
mainfrom
claude/world-resource-limits

Conversation

@Maelstromeous

Copy link
Copy Markdown
Member

Replaces #576 — same commits, renamed branch (claude/concrete-mf-mine-setup-vrbovc was a leftover name from unrelated work).

The planner has always balanced plans against an infinite world. It asks whether a factory's inputs add up, never whether the ground holds enough to begin with — so a mine set to 9,107 Crude Oil a minute solves happily, against a map that holds 5,040.

This adds the missing question, and in answering it turned up two errors in the map data itself.

Raw Resources says how much of the world a plan uses

Three bars per resource, because a plan can fail in three ways:

[Miner Mk.3] @100%   ██████████████░░░░░░░░░░░░░░░░
                     2.2k / 4.9k/min  (45%)
[Miner Mk.3] @250%   █████░░░░░░░░░░░░░░░░░░░░░░░░░
                     2.2k / 12.3k/min  (18%)
- - - - - - - - - - - - - - - - - - - - - - - - - -
[◆] Nodes            ██████████████████████████████
                     19 / 17  (112%)

Extraction against both ceilings, a bar each: what every node gives with no power shards, and what the same nodes give at the 250% clock cap. Crude Oil at 6.7k against 5k and 6.7k against 12.6k is what "needs power shards" looks like — one bar full and red, the next with room — rather than a sentence about it. Past the second, the resource is not on the map in that quantity, however it is clocked.

The extractor those ceilings assume is named by its own icon: the same nodes worked by a Mk.1 come to a quarter of a Mk.3's, so a bare "12.6k at 250%" says nothing alone.

Nodes is how many extractors the plan places against how many exist to stand on — the check a rate ceiling cannot make. Purity is chosen per building group, so a plan can sit well inside the rate and still put 24 miners on 17 nodes. More extractors than nodes is a blocker; enough nodes but not of the purities named is a note, since a new group starts on a normal node. Nodes and well satellites are counted apart, because a miner cannot stand on a well's micro-node.

Every bar runs the full width of its column with its figures and its share underneath, so no bar is shortened by its numbers and all of them are the same length. Green, amber from 60%, red from 80%, and the percentage takes the bar's colour.

Said where it will be seen

Statistics is collapsed by choice for a returning visitor, so warnings inside a table reach nobody. calculateWorldResourceProblems drives every surface from one roll-up: status chips beside the resource name (Beyond the map, Needs power shards, Not enough nodes, Purity mismatch), counts on both the Raw Resources and Statistics headers, and an alert separating "the world does not hold this much" from "more extractors than nodes", because the fixes differ.

Prose is left for advice only: which Converter recipe makes up a shortfall, or which purities to spread across. A plan short of Uranium is pointed at Uranium Ore (Convert: Bauxite). Crude Oil and SAM have no synthesis route at all, so for those the map's total is final and the warning says so rather than implying a way out.

Two corrections to the map data

Node counts are map facts and cannot be derived from the game data, so they live in web/src/utils/world-resources.ts. Everything else — extractor marks, rates, purity multipliers, satellite rates — is read from the parsed game data, so a game update moves the figures without touching that file.

The spec asserted those counts reproduce the extraction limit the parser reports. That check passed while both sides were wrong:

  • Limestone yields 69,300, not 69,900. The limits in parsing/src/parts.ts are hand-entered, because Docs.json does not state them, and that one was a transposed digit. The node table had then been written backwards from it as 15/51/29 — which reproduces 69,900 exactly and puts a node on the map that is not there. The real split is 15/50/29, agreeing with the published figure, the corrected limit and the object count. Game data is republished for it.
  • Water wells and geysers were missing entirely. The table held 63 of the map's 118 well satellites; Water's 55 were left out as unlimited, which is true but leaves nothing for a save to patch over. Water now carries its wells and keeps unlimited. Geysers are a separate export, since a geyser yields power rather than an item and has no part id to key on.

The rate check alone cannot tell one split from another that sums the same way, which is exactly how a 95-node Limestone survived it. So the spec now also asserts the object counts — 459 resource nodes and 118 well satellites — taken by parsing real 1.2 saves. Those hold whatever the world was generated with, so they are the independent measure the rates could never be.

Verification

  • 2,026 web tests and 80 parser tests passing; pnpm lint and vue-tsc clean.
  • Driven in the browser against Mael's MegaPlan, which turns out to place 57 Oil Extractors on 30 oil nodes, 25 Caterium miners on 17, 36 SAM miners on 19, 19 Bauxite miners on 17 and 7 Uranium miners on 5. Header chips checked with Statistics both expanded and collapsed.

Caught during those runs and fixed: a green chip on a resource whose only problem was node count; a warnings column sized by whatever the factory chips left over; fa-triangle-exclamation, a Font Awesome 6 name absent from the vendored build, drawing as a missing glyph; the extractor icon dimmed by an opacity meant for its label; and bars of differing lengths, which is what moved every figure out from beside its bar and under it.

Not seen on screen: no resource in the MegaPlan lands in the 60–80% amber band, so that colour is unit-tested but unverified visually.

Scope

Figures assume the default vanilla world. A footnote under the table says so permanently rather than hiding in help text, and names what changes them: randomised node layouts, the resource-rich and fossil-fuel-rich presets, and the purity settings.

Reading a world's actual layout is deliberately not attempted here. It is possible — a 1.2 save records the generation settings, the seed, and a resource and purity override for every node — and it is now #584, along with reading tiers, unlocked buildings and alternate recipes from the same file.


Generated by Claude Code

claude and others added 10 commits August 20, 2026 01:30
A plan can balance perfectly and still be unbuildable. The satisfaction
maths only ever asks whether a factory's inputs add up, never whether the
ground holds enough of the resource to begin with, so a mine asked for
9,107 Crude Oil a minute solved happily against a map that holds 5,040.

Raw Resources now carries what the world holds beside what the plan takes.
Two ceilings, because they answer different questions: every node worked by
the best extractor with no power shards, and the same nodes at the 250%
clock cap. Over the first, a plan needs shards and says so; over the second,
the resource is not on the map in that quantity and nothing will make it so.

Node counts are map facts and cannot be derived from the game data, so they
are held in world-resources.ts. Everything else — extractor marks, rates,
purity multipliers, well satellite rates — is read from the parsed game
data, and the spec multiplies the node counts back out against those rates
to assert they reproduce the extraction `limit` the parser reads out of the
game for all thirteen resources. A typo in a node count fails there rather
than quietly passing a plan that cannot be built.

Extractors are counted against nodes as well as rates, which is the check a
rate ceiling cannot make: purity is chosen per building group, so nothing
stopped a plan describing 24 Raw Quartz miners against the map's 17 quartz
nodes while every total still balanced. The hard case (more extractors than
nodes, whatever their purity) is separated from the soft one (enough nodes
exist, just not of that purity — where a group defaults to a normal node,
which most plans never revisit), and nodes are counted apart from well
satellites since a miner cannot stand on a micro-node.

Where a resource is over the wall, the Converter recipes that make it are
named from the game data rather than listed here. Crude Oil and SAM have
none — nothing in the game synthesises either — so those say the map's total
is final instead of implying a way out.

Verified against Mael's MegaPlan in the browser, which turns out to place 57
Oil Extractors on 30 oil nodes, 25 Caterium miners on 17, and 7 Uranium
miners on 5. Two things the first run showed: the utilisation chip was green
on a resource whose only problem was node count, so it now takes the row's
worst finding rather than its rate alone; and the warnings column was sized
by whatever the factory chips left over, wrapping to a word a line.

Figures assume the default vanilla world. 1.2's randomised worlds and the
resource-rich game modes deal different node counts, which the help text and
the module say plainly rather than pretending otherwise.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CejFi895Pae9Pqa9MAfnFk
…the headers

The first cut buried the two things a reader wants in prose. Both are ratios,
so both now read as one:

  Extraction  6,740 / 12,600 /min   53%
              Oil Extractor at 250% · 5,040/min at 100%
  Nodes       57 / 30

Extraction is what the plan takes against the most the world can give.
Nodes is how many extractors it places against how many exist to stand on —
the check a rate ceiling cannot make, since purity is picked per building
group and a plan can sit well inside the rate while putting 24 miners on 17
nodes.

The capacity figure now names the machine it assumes. "12,600/min at 250%"
says nothing on its own: the same nodes worked by a Mk.1 come to a quarter of
it, and there was no way to tell from the table which the number meant.

Warnings alone were not enough to reach anyone. Statistics is collapsed by
choice on a returning visitor, so a plan that cannot be built could sit
behind a Show button indefinitely. The Raw Resources header now counts what
is beyond the map and what needs power shards beside its resource count, and
turns red with the first; Statistics repeats the blocker count on its own
header, which survives collapsing the section. Verified both in the browser,
including after collapsing.

The well row is only rendered where wells are in play, rather than every oil
plan carrying a "0 / 18 well nodes" line saying nothing. The warning triangle
was fa-triangle-exclamation, a FontAwesome 6 name absent from the vendored
build, and drew as a missing glyph; it is fa-exclamation-triangle like the
other 25 in the app.

calculateWorldResourceProblems is the one roll-up behind all of it, so the
table and the two headers cannot disagree about what is wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CejFi895Pae9Pqa9MAfnFk
…the resource

Extraction and nodes are the same question asked twice — how much of what
exists is already spoken for — so they are one column of two bars rather than
two columns of numbers:

  Extraction  ▓▓▓░░░░░░░  6.7k / 12.6k /min
              Oil Extractor at 250% · 5k/min at 100%
  Nodes       ▓▓▓▓▓▓▓▓▓▓  57 / 30

Green while there is room, amber from 60%, red from 80%, pinned at full past
the ceiling. A bar is read at a glance where two more numbers had to be
measured against each other first.

What is wrong with a resource now hangs off the resource, as chips beside its
name — Beyond the map, Needs power shards, Not enough nodes, Purity mismatch —
the way the Factories Summary hangs status off a factory. It is a property of
the resource, not a footnote to whichever bar happens to be over. What is left
in prose is advice rather than status: which Converter recipe makes up a
shortfall, or which purities to spread across. The sentence repeating an
overcommitted node count is gone entirely; the bar beside it already reads
19 / 17 in red.

Figures over a thousand read as 2.6k and 92.1k. formatCompact was the obvious
tool and the wrong one: it spends a four-character budget because it sits
under a 36px icon, so 92,100 came out as "92k" and 12,300 as "12k" — fine at
that size, a real loss in a table where the figure is read against the one
beside it. formatCompactPrecise keeps one decimal at every magnitude, with the
promotion threshold tightened by the same factor so 999,900 reads 999.9k
rather than overflowing to "1000k".

Spacing: the heading sat flush against the alert below it. It now carries the
same gap-and-margin shape the Power section's heading uses, and its chips lost
their hand-rolled left margins to the flex gap.

Verified in the browser against Mael's MegaPlan, header chips checked with
Statistics expanded and collapsed. The extractor caption was wrapping "at
100%" onto its own line, where it read as a third bar's label; it no longer
wraps and the column shrink-wraps around it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CejFi895Pae9Pqa9MAfnFk
The bars were laid out `84px | 1fr | figures`, so a longer figure beside a bar
stole track from it and no two bars in the table were the same length — the
one thing a column of bars has to be. The track is a fixed 190px now, and the
extraction figures moved underneath their own bar, snug to it, where nothing
they read can change it.

That freed the room to say what the caption underneath was saying in prose.
Extraction is measured against both ceilings, a bar each: what every node
gives unclocked, and what the same nodes give at the 250% cap. Crude Oil at
6.7k against 5k and 6.7k against 12.6k is what "needs power shards" looks
like — one bar full and red, the next with room — rather than a sentence
about it. The extractor those ceilings assume is its own icon rather than its
name, which is both shorter and the thing people recognise.

A dashed rule separates the two ceilings, being the same measure twice; a
solid one separates extraction from nodes, being a change of subject. Nodes
keeps its figures beside its bar, since two short numbers cost nothing there
and the bar's width no longer depends on them.

The header chip read "5 beyond the map", which said the map is missing rather
than that the plan outruns it. It reads "5 beyond map allowances".

Verified in the browser against Mael's MegaPlan. The extractor icon came out
dimmed at first: the key's 0.75 opacity applied to the icon as well as its
text, and that icon is the only thing naming the machine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CejFi895Pae9Pqa9MAfnFk
The numbers moved out from beside the node bars and under them, where the
extraction figures already sat, so the bar takes the whole column rather than
whatever the figures leave: every bar in the table is now the same length and
as long as the column allows.

Each figure line ends with that bar's share of its ceiling, in the bar's own
colour — a bar says "nearly full", the percentage says how nearly.

Nodes get a diamond in the key, the shape the game draws them as on the map,
matching the extractor icons keying the two extraction bars.

One rule left in the cell, at the one place the subject changes: dotted,
between the 250% ceiling and the nodes. The two extraction ceilings are the
same measure twice and no longer have a line between them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CejFi895Pae9Pqa9MAfnFk
…Water

Every figure in Raw Resources is wrong for a world generated with anything
but the defaults, and the caveat saying so lived inside the help text, which
is off unless you turn it on. It is now a permanent footnote under the table,
naming what actually changes the counts: randomised node layouts, the
resource-rich and fossil-fuel-rich presets, and the purity settings. A plan
holds nothing that says which of those a save was started with.

Water is not unlimited, it is unbounded by nodes: an extractor still needs a
patch of water to stand on, so a big enough plan does run out of map. There is
no node count to measure that against and no plan comes near it, so the row
now says "effectively unlimited" and names what does bound it, rather than
claiming a limit that does not exist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CejFi895Pae9Pqa9MAfnFk
The extraction limits in the parser are hand-entered, because Docs.json does
not state them, and Limestone's was a transposed digit: 69,900 for a resource
whose 94 nodes come to 69,300 with a Miner Mk.3 at the 250% cap.

The world resource table was then written backwards from the wrong figure —
15/51/29 reproduces 69,900 exactly, and puts a node on the map that is not
there. Counting the actual objects in three 1.2 saves (a vanilla one, a
node-randomised one and a fossil-fuel-rich one) gives 459 resource nodes and
118 well satellites in every case, against the 460 this table described. The
published split of 15/50/29 agrees with that count and with the corrected
limit, so both ends are now consistent.

The spec asserts the totals as well as the rates from here on. The rate check
alone cannot tell one split from another that sums the same way, which is how
a 95-node Limestone passed it in the first place; object counts are the
independent measure, and they do not depend on how the world was generated.

Game data is republished as 1.2-08 for the corrected limit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CejFi895Pae9Pqa9MAfnFk
The table held 63 of the map's 118 well satellites. The missing 55 are Water,
left out because water is unlimited and has no ceiling to track - true, but it
meant the object-count check could only ever verify 63 of them, and it leaves
nothing for a game save to patch over when a resource-rich preset moves Water
to 58 satellites. Water now carries its wells and keeps `unlimited`, which
short-circuits both the utilisation status and the node-usage check before
either reads them, so nothing about the warnings changes.

Geysers were absent entirely. They are added as their own export rather than a
table entry: a geyser yields power, not an item, so it has no part id to key on.

Both figures come from the community map. The totals are the half a save can
referee and they agree with it exactly - 7/12/36 Water + 2/7/36 Nitrogen +
8/6/4 Oil is the 118 satellites every 1.2 save reports, and 9/13/9 is the 31
geyser actors.

Note for anyone tempted to check the well split against a randomised save: the
presets redistribute satellites. NRM_Strict reproduces all eleven solid node
counts exactly and still reads Water 55 / Nitrogen 42 / Oil 21 against the
map's 55/45/18, and the resource-rich presets differ again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ne-setup-vrbovc

# Conflicts:
#	web/public/gameData_v1.2-07.json
#	web/public/gameData_v1.2-08.json
#	web/public/gameData_v1.2-09.json
#	web/src/components/planner/Statistics.vue
#	web/src/components/planner/StatisticsResources.vue
#	web/src/config/config.ts
@vercel

vercel Bot commented Aug 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
satisfactory-factories Ready Ready Preview Aug 25, 2026 10:29am

Request Review

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.

2 participants