Files
dota_factory/docs/progression_design.md
Malte Langkabel d889b79658 Write the v2 production tree into the config files
recipes.toml: full v2 rewrite - iron/copper/quartz mining, smelting
incl. the value-losing scrap sink, reprocessing (4 scrap / 4 s,
voidsteel at 20% of the full pool), tiered intermediates, hulls,
module prefabs, and the three drop-only shortcut recipes.

modules.toml: lasers renamed to railguns (implementation unchanged),
prefab materials and numbers-pass production times; combat stats stay
placeholders for the arena pass.

ships.toml: hull-item materials, numbers-pass base production times,
and geometry-validated default_modules loadouts for every ship (waves
now spawn armed).

visuals.toml: item entries for the new palette (quartz, silicon,
copper_coil, control_chip, capacitor_bank, hardened_steel,
ceramic_plate, voidsteel, voidsteel_plate, railgun modules); retired
titanium/alloy/laser items removed.

world.toml: scrap_per_threat = 0.25 per the numbers pass.

threat_report.py: commit an item's threat only once every eligible
recipe is computable - the previous first-resolved-wins behavior let
shortcut recipes underprice items (same flaw exists in
ThreatCostCalculator, recorded as action item 9). Verified: all
default_modules placements valid, verify_recipes and verify_layouts
pass, and the report reproduces the numbers-pass tables exactly
(fitted: 10.5/47/99/233.5/354.5/722.5/1491.5/1436.5). Doc tables
updated for the three geometry-corrected loadouts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DyCu8vwChKMbLJQ3xosYEN
2026-07-03 17:05:19 +02:00

23 KiB
Raw Blame History

Progression & Balancing Design

Rules and principles that govern the production tree, progression pacing, and balancing. This document contains rules only — concrete content (item lists, recipes, unlock levels, stat numbers) lives in the config files and content_design.md; those numbers must follow the rules stated here.

Player-experience goals

What each phase of a run should feel like:

  • Early: learning belts and ratios with forgiving chains. The building block economy is the main constraint; the player bootstraps a self-sustaining factory from the starting stock.
  • Mid: deeper chains, the first real ratio puzzles, and the first meaningful drop decisions (which schematic, when to push).
  • Late: combat feeds the factory — capital production requires salvage. Progress means extending and refactoring the existing factory, not rebuilding it. Strange ratios are deliberate optimization puzzles.

Overarching: an experienced player gains efficiency through knowledge — layout foresight, understanding chains, exploiting shortcut recipes — never through hidden mechanics. An inexperienced setup should not cost much more than an experienced one; experience pays off in how easily the factory adapts later (see Refactorability).

Resource phases

  • A run has exactly four base inputs:
    1. Two mined resources available from the start, minable on every asteroid tile.
    2. A third mined resource unlocked mid-game, minable only on deposit patches found in expansion territory (see Resource deposits).
    3. A fourth input unlocked late-game, obtainable only from reprocessing salvaged scrap.
  • The fourth input is the core loop hook: capital ship production requires fighting (salvaging and reprocessing), not just mining.
  • Every gating has a fictional reason (concrete fiction in content_design.md): the asteroid is a metal-rich body, so its bulk rock is minable anywhere; the mid resource sits in rare pockets; the late input is battle-forged — created only in the violence of ship destruction, which is why any wreck (including the player's own) yields it and no foundry can make it.
  • The mid resource is dual-gated: schematics (knowledge, via drops) and territory (deposits, via expansions). Tuning must guarantee the deposit-bearing expansion is comfortably affordable by the time the first mid-tier schematics drop, or those drops are dead picks.
  • There is no direct "resource unlock" mechanism. Miner recipes unlock implicitly (REQ-LOCK-IMPLICIT) when some unlocked schematic's material chain reaches that resource. Resource pacing is therefore controlled through the unlock_at_station_level values of ships, modules, and assembler recipe schematics — and the content must guarantee that the chains actually connect (a mid-game schematic must require an item whose chain reaches the mid resource, or it never unlocks).

Resource deposits

  • Rule: freedom first, geography later. The starting resources are minable everywhere, so the player has full layout freedom while learning. Later mined resources are bound to deposit patches — fixed geography as a layout puzzle, introduced once the player is competent.
  • Rule: deposits exist only in expansion territory. Expansions buy space and access to resource tiers — the second leg of the growth curve (see Building block economy).
  • Rule: patch area is the throughput cap. Deposits never deplete but are finite in area; the number of deposit tiles caps how many miners the chain supports. Buying deeper expansions raises the throughput ceiling of high-tier chains.
  • Rule: no empty expansions. Deposit content per expansion is deterministic and config-defined; only the placement within the new columns is randomized. Buying an expansion never rolls "nothing".
  • Rule: mining is binary. A miner whose footprint overlaps at least one deposit tile of a resource can select that resource's recipe; no partial-coverage rate scaling.
  • Deposits arrive at the periphery (expansions add columns on the left), so each new chain starts in fresh space — supporting the refactorability property — and high-tier chains have the longest belt runs to the shipyards, escalating the logistics puzzle with tier.

Production tree rules

Structure

  • Each phase transition adds exactly one new base input chain. A base input is a bottom-level resource entering the factory from outside — a mined resource or the scrap-only input. The early game starts with two ores as the baseline; the transition to mid adds one (the deposit-bound mid resource), the transition to late adds one (the scrap-only input). No transition ever introduces more than one unfamiliar bottom-level chain, so the factory grows in one direction at a time.
  • Intermediates are generic shared parts. Keep the item count low — modules and hulls of a tier draw from a shared pool of that tier's and lower tiers' intermediates rather than each having bespoke inputs.
  • Thematic naming over thematic items. Inputs should be plausible for what the recipe produces (crystals for lasers, heat sinks for bigger lasers). Achieve this through naming and chain membership, not by adding item types: rename a generic part, don't add a parallel one.

Ratios

  • Ratio "niceness" degrades with tier. The producer:consumer ratios needed for 100% throughput follow a curve:
    • Tier 1 (ore → basic material): trivially nice (e.g. 1:1 or 1:2 miner:smelter).
    • Tier 2: slightly complex but still clean (e.g. 2:3).
    • Higher tiers: increasingly strange ratios, as deliberate optimization puzzles.
  • Exceptions in both directions are allowed when there is a reason — a clean late chain as a breather, an odd early chain as a teaser — but the curve is the default.

Shortcut recipes

  • Some strange chains get a shortcut recipe: an explicitly unlockable assembler recipe schematic (unlock_at_station_level ≥ 0, drop-only per REQ-LOCK-EXPLICIT) that skips a step (e.g. t1 → t3 directly) and yields nice ratios for a chain whose base path is strange.
  • Not every strange chain gets a shortcut. Some strangeness is permanent; the absence of a fix is a valid design choice.
  • Shortcuts drop only for known chains. A shortcut recipe enters the drop pool only when both its input items and its output item are already unlocked (in addition to the station level check). The player is never offered a shortcut for a chain they have not built yet. The output-item half of this check already exists in REQ-DEF-SCHEMATIC-DROP; the input half is new (see Action items).
  • Shortcuts are pure rewards, never balance factors. An item's threat value is the maximum across its producing recipes (REQ-THREAT-ITEM), so unlocking a cheaper recipe does not lower the item's threat accounting — the player gains real factory efficiency without their ships being valued cheaper and without enemy wave budgets shifting. Consequently: balance every chain around its base (expensive) path; the shortcut's savings define the size of the reward.

Refactorability

  • Rule (the property): unlocking the next tier or size of a thing must be a local edit of the existing production line — adding assemblers and belts, or replacing a machine or two in place — never a rebuild of the line.
  • What this buys the player: foresight pays off in space, not blocks. An experienced player leaves a little slack in the middle of a line, knowing the next size or tier upgrade means tearing out one assembler and a few belts there and inserting the new step — plus maybe swapping a recipe or two elsewhere — while the rest of the line keeps running untouched.
  • Default technique: the bigger version introduces one new intermediate that is produced from a subset of the smaller version's inputs (possibly plus one additional low-tier material), and otherwise reuses the smaller version's inputs. Existing lines keep running and feed the new intermediate's assemblers.
  • The property is the rule; the technique is only the default. It may be broken where it fights thematic plausibility, as long as the property still holds.

Cost archetypes

Every item has two cost knobs: material quantity and cycle time. Both feed the threat value identically (threat = recursive production-seconds, REQ-MOD-THREAT), so the split between them does not change what an item is worth — it changes what kind of factory pressure it creates:

  • Material-heavy, fast (e.g. armor plates): simple items; stress belt throughput, splitter logistics, and miner/smelter counts.
  • Time-heavy, lean (e.g. shield modules): technically complex items; few inputs — possibly higher-tier ones — but long cycles; stress assembler counts and parallelization.

Rule: each module family commits to a clear archetype, so factories supporting different fleet doctrines feel structurally different to build.

Threat model (balancing backbone)

  • Threat cost = total recursive production-seconds (REQ-MOD-THREAT). One factory-second equals one threat; player output and enemy wave budgets are denominated in the same currency.
  • Rule: combat power per threat is roughly constant across all ships, modules, and tiers. Higher tiers are better per ship and per module slot, not per invested factory-second — their advantage is concentration (fewer, bigger things; slot geometry per content_design.md) and qualitative capabilities, not a better exchange rate. Deviations from this rule are deliberate and documented.
  • Difficulty race: the enemy threat rate (threat_rate_formula) is tuned against the factory output (threat/s) achievable by a competent player — slightly below it early, crossing above it eventually. The game is endless; enemy scaling must ultimately outpace any factory, and player skill shifts when, not whether.
  • All time scaling lives in the threat rate — waves get bigger, ships of a given schematic never get individually stronger. There is no ship level dimension: stat formulas are plain values, and per-ship level scaling does not exist. Push scaling on enemy defence stations is the separate, player-triggered difficulty axis and keeps its level formulas.

Unlock & drop pacing

  • Starting set rule: the schematics unlocked at game start (unlock_at_station_level = -1) must be exactly enough to reach the first push unaided — a functioning block loop, small hulls, a basic weapon, and the salvage loop. Nothing more.
  • The unlock_at_station_level ladder mirrors the resource phases: mid-tier hulls/modules/recipes at low station levels, capital content at higher levels. A schematic must not become available before the chains its materials need can be unlocked alongside it.
  • Schematics can require other schematics. Beyond the station-level gate, a schematic (ship, module, or assembler recipe) may list prerequisite schematics (unlock_requires, REQ-LOCK-PREREQ) that must already be unlocked before it enters the drop pool — e.g. the medium laser requires the small laser; a future Mk2 requires its base version. Station level gates the earliest when; prerequisites gate the order, keeping drop offers coherent with what the player already owns.
  • No duplicate drops. Ship and module schematics leave the drop pool once owned, exactly as assembler recipe schematics already do. There are no schematic level-ups; player power grows through unlock breadth and factory scale only, which keeps power-per-threat exact on both sides. The pool therefore shrinks over a run and late pushes increasingly offer artifacts — intended: the late game is a race for the win condition. Per-item progression may return later as Mk2 upgrade recipes (see Open tasks), never as free level-ups.
  • Artifacts trade power for progress. Artifact options compete with schematic picks in the same choice dialog; the artifact chance must be tuned so that taking one is a real decision (giving up an unlock), not automatic in either direction.

Scrap & reprocessing economy

  • Scrap is the bridge from combat back into the factory, with two sinks: smelting (same basic materials as ore — the safe, boring option) and reprocessing (probabilistic higher intermediates, including the late-game input — the gamble that eventually becomes mandatory).
  • The reprocessing output pool renormalizes over implicitly unlocked items (REQ-LOCK-REPROCESSING-POOL), so its output quality improves automatically as the run progresses. Rule: weights are authored for the fully unlocked pool state; early-game behavior falls out of renormalization for free and needs no separate staging.
  • Rule: ship scrap drops are derived, never authored. A destroyed ship drops threat cost × scrap_per_threat (a world.toml key), with the threat cost computed from its actual hull plus installed modules (REQ-MOD-THREAT) — a kitted-out ship drops more scrap than a bare hull automatically. ships.toml carries no scrap value. Defence stations are the exception: they keep authored scrap_drop_formulas, because pushing rewards are tuned independently of ship production costs.
  • Consequence: the threat value of scrap is the constant 1 / scrap_per_threat (REQ-THREAT-SCRAP). The former min-scrap_drop schematic derivation and its potential circularity are gone.
  • Rule: the late-game input's income rate meaningfully gates capital production — unlocking a capital hull must not mean spamming it; the input trickles in slowly enough that every capital ship is a noticeable investment. The tuning target is relative, not absolute: assume a reference player who destroys and salvages roughly the threat the game spawns ("fighting at parity"), and tune scrap_per_threat, the reprocessing weights, and capital material costs so that this player affords roughly N capital ships per boss cycle. An absolute income rate would be meaningless (income depends entirely on how much the player fights) and would not self-scale; per boss cycle, the target tracks the threat rate as it steps up.

Building block economy

  • Building blocks are the only global currency and the early game's central constraint. The early game is a bootstrap problem: convert the starting stock into a self-sustaining block loop before the first waves bite.
  • Rule: the starting stock suffices for a minimal block loop plus the first shipyard — with a little slack for beginner mistakes, but not enough to skip the loop entirely.
  • Rule: the growth curve lives here. A saturated building produces exactly 1 threat/s, so the player's output curve is their building-count curve — shaping growth over a run means shaping the block and space economy, there is nowhere else it can live. Intended shape: exponential bootstrap (block-limited) → ramp (expansion-limited) → asymptotic squeeze as expansion costs outrun income, racing the enemy threat rate throughout.
  • Rule: escalating expansion costs. Expansion cost is a formula of the number of expansions already purchased, rising steeply enough that expansions eventually outrun any block income. The starting asteroid is deliberately small — filled within the first boss cycle or two, so the early exponential burst is a satisfying ramp, not a balance hole — and from then on the output curve is the expansion curve. Blocks keep a meaningful sink for the entire run, and "grow vs. army" stays a live decision at every moment.
  • Rule: designed doubling time. Block production is a positive feedback loop (blocks buy assemblers, assemblers make blocks); its time constant is a designed quantity, never an accident of quantity choice. The block chain's depth and the per-building costs are tuned against a stated target of the form: "a factory spending X% of its capacity on blocks doubles in ~T minutes."
  • Rule: growth is limited by economy, never by waiting. Construction times stay short; the serial build queue must not be used as a growth brake. Waiting for placed buildings to become operational — especially at the start of a run — is frustration, not gameplay. All growth limiting comes from block income and expansion pricing.
  • Note: block income has a structural ceiling — blocks enter the stock through the HQ's single belt port, so income is capped at belt throughput regardless of assembler count. Per-building costs should be high enough that this cap can bind late-game (see the condensed-block idea under Open tasks).

Numeric guardrails

Constraints that every recipe must respect, independent of tuning:

  • Belt throughput: belt speed and per-tile capacity cap how fast a single belt can feed an input. A recipe whose per-cycle inputs cannot be sustained by one belt per input at 100% duty cycle is a deliberate design (forcing parallel belts/splitters as part of a high-tier puzzle) — never an accident of quantity choice.
  • Buffer burstiness: input buffers hold 2× the per-cycle amount (REQ-MAT-INPUT-BUFFER), so large per-cycle quantities create bursty belt demand. Low tiers prefer small quantities with short cycles; big-batch recipes are reserved for high tiers where burstiness is part of the puzzle.
  • Cycle times scale with tier monotonically — a higher-tier item never has a shorter total chain time than a lower-tier item of the same role.

Open tasks / future work

  • Rework recipes.toml once the rules in this document are fixed: the current tree feels too close to Factorio; apply the thematic-naming rule and the ratio curve to it (renames and quantity changes, not new items).
  • Balancing pass (see placeholders in content_design.md): set the unlock_at_station_level ladder, real threat costs and default_modules, reprocessing weights, and the threat-rate formulas according to the rules above.
  • Condensed building blocks — a drop-unlockable shortcut-style recipe that packs several blocks' worth of value into one belt item, relieving the HQ intake ceiling (see Building block economy) as a late-game reward. The ceiling is the puzzle, the drop is the fix — same philosophy as shortcut recipes.
  • Mk2 upgrade recipes — the deferred design for per-item progression, to revisit once the config has stabilized. A duplicate-style drop unlocks a distinct *_mk2 item whose recipe consumes the Mk1 item plus higher-tier parts. This preserves power-per-threat (the extra power is paid in real production-seconds, since threat is recursive), satisfies the refactorability rule (the Mk1 line keeps running and feeds one new assembler), and keeps balancing one-dimensional (no level variable anywhere). Enemy-side progression happens via default_modules variants per era instead of a level formula.

Action items — changes beyond this document

Agreed changes that require edits to requirements.md, the code, and the configs. Completed items are removed from this list (their outcomes live in requirements.md and the git history). Still open:

  1. Fill unfillable schematic slots with artifacts. With duplicates removed, the schematic drop pool can run dry — previously unreachable. Decision: every slot in the choice dialog that cannot be filled with a schematic because the eligible pool is exhausted is filled with an artifact option instead (in addition to any artifact option granted by the regular artifact roll). A push therefore always awards a full dialog. Update REQ-DEF-SCHEMATIC-DROP.
  2. Confirm wave scaling carries alone. With per-ship level scaling removed, threat_rate_formula is the only time-scaling axis; verify the current world.toml values still produce the intended difficulty curve (likely a review, not a retune, since the config already ran with a flat ship level before the removal).
  3. Gate shortcut-recipe drops on their inputs. Extend the assembler recipe schematic pool eligibility in REQ-DEF-SCHEMATIC-DROP: in addition to the existing station-level and output-item checks, all of the recipe's input item types must be implicitly unlocked as well.
  4. Expansion cost formula. Replace the flat world.toml [expansion].cost_building_blocks with a formula of the number of expansions already purchased (x = expansions bought so far), so expansion costs can escalate per the block economy rules. Update REQ-EXP-COST and wherever the UI displays the expansion cost.
  5. Resource deposits. Add a terrain deposit layer per the Resource deposits rules: deposit patches generated in expansion columns (deterministic content per expansion, randomized placement within the new columns), deposit rendering, and a miner condition (a resource recipe is selectable only if the miner's footprint overlaps at least one matching deposit tile). Touches REQ-BLD-MINER ("every asteroid tile is equivalent" no longer holds), REQ-GW-ASTEROID-EXPAND / REQ-EXP-*, world.toml, and visuals.toml.
  6. Scrap-consuming recipes as threat fallback only. Amend REQ-THREAT-ITEM: recipes that take scrap as an input participate in an item's threat computation only if no scrap-free recipe (miner, smelter, or assembler) produces that item — mirroring the existing rule for the reprocessing path. Otherwise the scrap→ingot smelter recipe would inflate the basic materials' threat via the max-across-recipes rule, poisoning every downstream value.
  7. Per-unit item threat. Amend REQ-THREAT-ITEM and ThreatCostCalculator: a recipe's threat is divided by its output amount, so item threat is production-seconds per unit. Currently a recipe producing 2 copper_wire per run assigns each wire the full run's threat, double-pricing multi-output items and everything downstream of them.
  8. Fixpoint resolution in ThreatCostCalculator. Items downstream of reprocessing-only items (e.g. capital parts built from the scrap-only input) never resolve, because resolution stops after the reprocessing pass instead of iterating; their consumers silently drop the missing materials, so capital hull threat is currently underestimated (found by tools/threat_report.py, which implements the correct fixpoint).
  9. Max rule across staggered recipes in ThreatCostCalculator. An item is committed at the first iteration where any of its recipes resolves, taking the max only over the recipes resolvable at that point. A shallow shortcut recipe (e.g. steel plate from raw ore) resolves one iteration earlier than the base path and wins, silently underpricing the item and everything downstream — violating the "shortcuts are pure rewards" rule. Fix: commit an item's threat only once every eligible recipe for it is computable (as tools/threat_report.py does), with a fallback for recipe cycles.