Danger Ores: shared main-ores factory, standardized numbers (A-F), fluent API (G)#1573
Conversation
Replaces the map-specific tetromino_ores.lua with a shared 4-ore config that derives its three vanilla entries from config/vanilla_ores.lua (one source of truth for the numbers) and appends the stone sector. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drops the duplicate voronoi_ores.lua (identical to tetromino_ores.lua). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The clean-border guarantee is a graph colouring and 4 colours is the mathematical minimum; generate() now asserts it with a clear message. The module accepts any 4+-ore config as input. Test covers rejection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same guarantee and enforcement as the Tetrominoes layout; any 4+-ore config plugs in. Test covers rejection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two files were identical except for the commented-out stone sector; 3way now returns the first three x_cross entries directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Standalone lua tests: structural invariants for every main-ore config (strict shape for ratios-based configs, looser for the gradient/joker variants), identity checks for the derived configs, and golden ratio weights for the shared sources so old maps cannot drift by accident. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Most main-ore configs repeat the same per-ore floor tiles and the same dominant+minor resource mixes, differing only in richness curves, ore order, entry weights or tile skins. The factory holds those canonical numbers once; configs compose their entries from it. All runtime tables are built fresh per call so configs never share mutable state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Byte-identical output (verified with a normalized config dump diff); 76 lines become a 6-line composition. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same config as vanilla_ores apart from a steeper early richness curve. Byte-identical output (verified with a normalized config dump diff). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same config as vanilla_ores apart from landfill tiles and a flatter curve. Byte-identical output (verified with a normalized config dump diff). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same mixes; exponential start and trimmed dirt/grass shades for the snow look. Byte-identical output (verified with a normalized config dump diff). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Canonical mixes except the coal sector, which uses the shared coal-rich mix; 3way_ores keeps deriving from this file unchanged. Byte-identical output (verified with a normalized config dump diff). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Canonical mixes on landfill; the rich offset curves and the 10/8/7/2 entry weights stay as this config's own numbers. Byte-identical output (verified with a normalized config dump diff). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Canonical mixes with the time-budgeted richness curves (coal on the 4h budget, the rest on 24h); the uranium extra entry stays hand-written. Byte-identical output (verified with a normalized config dump diff). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Canonical mixes (coal-rich coal sector) with the map's degrading-resource closures plugged in via make_resource. Verified equivalent both with the normalized dump diff and by executing old vs new resource closures over sample coordinates (identical ore names and amounts). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The three vanilla entries keep coming from vanilla_ores directly (same tables); only the stone sector is now a factory entry. Byte-identical output (verified with a normalized config dump diff). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Canonical mix weights, fresh-tables-per-call, entry shape, and rejection of entries with no richness source. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Thanks! I think there's still room for improvement on maintainability. The main objective is to reduce number of config files for better readability/maintainability. Here are a few areas I spotted we could improve: A. Common presetsFor what I can read, the so called "X-Family" with rich coal is same as vanilla ores (coal has just a +5% boost in its coal-heavy definition?).
B. Mods & custom ore generationModded maps, poor man's field, and few others seem to be the only outliers with some more logic in its creation.
C. Vanilla forksThere are mainly only 2 vanilla ores config:
And between the two, nothing really changes other than 2nd having the addition of the stone-heavy sector (historically, all vanilla ores had stone but since the stone mined out from mixed sectors was already enough for vanilla, we ditched the stone-heavy sector to make maps more pleasant to play and resources more "useful" since there's no stone-sink in vanilla to void all the stone). Also, the stone-to-uranium recipe is an artifact of the
D. Ore densitySmall changes in
E. TilesLike A. & D., tiles seems to be pretty standardized:
F. Miscellaneous map info
G. MainOresFactory module
local MOF = require 'maps/shared/danger_ores/modules/main_ores_factory`
-- for vanilla
local ore_config = MOF.default()
-- for vanilla+stone
local ore_config = MOF.default():add_ore('stone')
-- for landfill
local ore_config_landfill = MOF.default():change_tiles('landfill')
--- for modded
local ore_config_modded = MOF.default():add_ore('zinc', {0, 0.35}):add_ore('aliminium', {0, 0.15})
-- for omni
local ore_config_omni = MOF.blank():add_ore('omnite')
--- for gridlocked
local ore_config_rich = MOF.default():richness({0.003, 2.25}):start_value(50)But this bonus change would be a really big & complex refactor that is only done once a year & would be better fit in a separate PR (just like we did for |
|
Thanks for the thorough breakdown! I'd propose splitting this along the line you already drew for G: keep this PR strictly behavior-free, and do the standardization as a follow-up. This PR intentionally changes zero generation behavior — every derived config was verified byte-identical against the original (normalized config dumps diffed before/after; poor_mans' closures additionally executed old-vs-new over sample coordinates). That property makes it safe to merge as groundwork without re-balancing anything. Your point B items (modded, poor man's) are already kept as-is. Follow-up PR — "standardize the numbers" covering A, C, D, E, F, since those do change generated maps and deserve their own review + playtest cycle with each delta listed explicitly:
Separate PR later — G, the If that split works for you I'll open the standardization PR shortly after this one lands. |
The X-Cross family and Poor Man's Coal Fields used a coal sector of 14/6/10/70 against vanilla's 18/9/8/65 -- a +5% coal boost not worth a second canonical mix. Both now use the vanilla coal mix. Behavior change: X-Cross/3-Way/Square/Poor Man's coal sectors yield slightly less coal and slightly more iron/copper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One Direction (exp 0.15/1.2), Landfill (exp 0.06/1.55) and Xmas/X-Cross (exp-start + exp 0.15/1.3) all move to the vanilla euclidean(0, 0.35) start and exponential(0, 0.07, 1.45) richness. Gridlocked keeps its offset curves (deliberately rich, expansion is limited there), and Poor Man's keeps its own value -- the degrading-ore threshold depends on it. Behavior change: patch richness on One Direction, Landfill (and its Permanence/Lazy One/Spiral/Grid Factory users), Xmas Tree, X-Cross, 3-Way and Square shifts to the vanilla curve; the X-family also regains a guaranteed-rich spawn (euclidean start). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
After the mix and curve standardization, four configs became pure duplicates or near-duplicates of the canon: - 3way_ores and vanilla_ores_one_direction were exactly vanilla_ores; their presets now inherit the configuration.lua default - x_cross_ores was vanilla_ores_stone in a different order, which is irrelevant since main_ores_shuffle_order shuffles it anyway; the preset uses vanilla_ores_stone - one_direction_ores_xmas kept only its snow tile trim; that moves inline into the xmas preset as a factory call Two canonical configs remain (vanilla_ores, vanilla_ores_stone) plus the genuine specials (landfill skin, gridlocked richness, joker, poor_mans, coal, scrap, gradient). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The coal sector sat on a 4h-of-one-drill budget against everyone else's 24h. One budget for all four ores; the uranium extra stays. Behavior change: Joker starter coal patches are ~6x richer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Went with your suggestion more directly after all: points A–F are now applied in this PR as four additional per-concern commits (delta table in the updated description), rather than a separate follow-up — only G stays separate. All seven affected maps (X-Cross, One Direction, 3-Way, Xmas Tree, Landfill, Joker, Poor Man's) re-playtested on the standardized numbers. |
Factory.default(), Factory.blank(), :add_ore(name, opts), :change_tiles
(all or one sector), :richness / :start_value (functions, or shorthand
{base, mult, pow} / {base, mult} curves), :sector_weights. The returned
object IS the main_ores array -- assign it to map_config.main_ores
directly -- with methods on its metatable; every call rebuilds the array
in place from a declarative spec, so method order never matters and the
object is always a complete, valid config. Unknown (modded) ores default
to a pure self-mix and no tiles; canonical ores bring their mix and
tile set. Every fluent config is built from fresh tables.
The low-level entry/main_ores builders stay for the special cases
(poor_mans' resource closures) and now accept explicit mix tables and
tiles = false for tile-less entries.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vanilla_ores = Factory.default()
vanilla_ores_stone = Factory.default():add_ore('stone')
vanilla_ores_landfill = Factory.default():change_tiles('landfill')
vanilla_ores_gridlocked = default + stone + landfill + offset curves
+ sector weights
joker_starter_area = default + stone + uranium extra + 24h budget
xmas preset = default + snow tile trims inline
Verified with normalized config dumps: identical to before except entry
order within vanilla_ores_stone and gridlocked (weights follow their
names), which main_ores_shuffle_order randomizes at map build anyway.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
One more: point G turned out compact enough to include here too (phase 3 in the description) — the fluent API per your sketch, with all standard configs now expressed through it ( |
RedRafe
left a comment
There was a problem hiding this comment.
Thanks for the amazing work, I'm really happy how it turned out. I've just left a couple of very minor adjustments.
You think it would be possible for the Fluent builder to already implement full combination of add/remove/update of ores/tiles/richness/start_value? It may not be used in these cases but I've already spotted an instance of needing to tweak the config and missing the API to do so. Job implemented fully once so we dont need to refactor too soon! :)
Per review: the fluent builder now covers the full add/remove/update combination -- :remove_ore, :update_ore(name, opts), :add_tile and :remove_tile (all sectors or one), and :richness / :start_value accept an optional sector name to override one ore instead of the default. Also replaces the hand-rolled copy_array with utils.table's deep_copy; the standalone config tests stub Factorio's built-in util module that utils.table pulls in. New tests cover every added method. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 4h coal budget is deliberate -- cheap-to-clear coal lets players mine through and expand the starter area. Now expressed as a per-sector override: :richness(low_value, 'coal'). Dump-verified identical to the original pre-refactor config. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per review, the old dirt-4/grass-1 trim makes no visible difference;
the one tile that interrupts the tree aesthetic is grass-4. Now:
Factory.default():remove_tile('grass-4').
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9715636 to
ea42815
Compare
|
All review items addressed (three commits at the tip, one per item, inline replies on each thread):
Every new method has test coverage in |
The mix counterpart of :change_tiles -- rewrite every sector's resource
mix, or one sector's, from a canonical mix name or explicit
{resource, weight} pairs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Standard four sectors, every mix rewritten to pure coal, dirt skin, flat guaranteed spawn. Dump-verified identical apart from sector order, which main_ores_shuffle_order randomizes anyway. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One scrap sector wearing every terrain skin; dump-verified byte-identical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Also converted the last two vanilla-resource configs to the fluent API while at it: |
|
In-game checks on the latest round: Coal Maze and Scrap on the fluent configs, Joker with the restored low coal budget, and Xmas Tree with the grass-4-only tile change — all look right. |
:richness{base = 50, mult = 0.003, pow = 2.25} instead of an opaque
{50, 0.003, 2.25} -- the keys mirror the builders semantics
(base + mult * distance^pow); base defaults to 0 and the positional
form still works. All configs move to the named form.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Small readability improvement to the curve shorthands: named keys — |
Follow-up to the review discussion in #1572 (thanks @RedRafe for the nudge), now also covering the A–F standardization points from the review below. Structured as two phases, one commit per concern throughout:
Phase 1: consolidation (zero behavior change)
Shared main-ores factory — the same per-ore tile sets and dominant+minor ratio mixes (iron 75/13/7/5, copper 15/70/10/5, coal 18/9/8/65, stone 25/10/60/5) were copy-pasted across eight files.
config/main_ores_factory.luanow holds those canonical numbers once; the configs compose their entries from it, keeping only their genuinely own numbers. The factory builds fresh tables per call, so configs never share mutable state.Every phase-1 rewrite is byte-identical to the original, verified by diffing normalized config dumps before/after; the poor_mans closures were additionally executed old-vs-new over sample coordinates.
4-ore minimum enforced at map creation — the Tetrominoes and Voronoi layouts now
assert(num_ores >= 4): their clean-border guarantee is a graph colouring and 4 colours is the mathematical minimum, so a too-small config fails at startup instead of silently producing touching same-ore regions. (tetromino_ores/voronoi_oreswere identical and are replaced by the sharedvanilla_ores_stone.)Phase 2: standardization (review points A–F — intentional behavior changes)
euclidean(0,0.35)start,exp(0,0.07,1.45)value); X-family regains a guaranteed-rich spawnJoker starter coal to the 24h budgetreverted per review — the 4h coal budget is intentional (mine-through expansion) and stays, expressed as:richness(low_value, 'coal')3way_ores,x_cross_ores,vanilla_ores_one_direction,one_direction_ores_xmas. 3-Way/Square/One Direction presets inherit theconfiguration.luadefault; X-Cross usesvanilla_ores_stone(order is moot —main_ores_shuffle_ordershuffles it); the xmas config becomesFactory.default():remove_tile('grass-4')inline (per review: the old trim was invisible; grass-4 was the aesthetic problem)Kept as-is per the review: gridlocked's offset curves and entry weights (F1 — after phase 1 it already reads as "standard mixes + richness override"), Poor Man's own value curve (its degrading-ore threshold depends on it), and all modded configs (B). Point E needs no new code: per-sector/all-sector tile replacement already exists via the factory's
tilesparameter (that's how Landfill works); the fluent-API version belongs to the point-G PR.Result: two canonical vanilla configs (
vanilla_ores, andvanilla_ores_stonederived from it) plus the genuine specials. Config count 19 → 15.Phase 3: the fluent API (point G)
Turned out small enough to include here after all. Following your sketch:
The returned object IS the
main_oresarray — assign it tomap_config.main_oresdirectly, no build step. Methods and the declarative spec live on its metatable; every call rebuilds the array in place, so method order never matters and the object is always a complete valid config. Canonical ores bring their mix and tile set; unknown (modded) ores default to a pure self-mix and no tiles;:richness/:start_valueaccept functions or{base, mult, pow}/{base, mult}curve shorthands. The low-levelentry/main_oresbuilders remain for the special cases (poor_mans' resource closures). All standard configs are now expressed through it (dump-verified identical apart from entry order within a config, whichmain_ores_shuffle_orderrandomizes anyway). Moving the module undermap_gen/shared/for use beyond danger-ores can be a later step once the API has settled.Tests — new
config/ore_configs.test.luavalidates the structure of every main-ore config, pins the canonical mix weights, checks the factory's fresh-tables guarantee, and covers the fluent API (chaining identity, per-sector reskins, curve shorthands, the modded path); both layout suites gain <4-ore rejection tests.Verified: normalized config dumps confirm phase 1 changed nothing, phase 2 changed only the deltas listed above, and phase 3 changed nothing again. Load-tested in-game: classic Danger Ore, 3-Way, X-Cross, Square-family, Tetrominoes, Voronoi, One Direction, Landfill, Gridlocked, Joker, Poor Man's, Xmas Tree.
🤖 Generated with Claude Code