Turn the rough draft into structured rules for the production tree, progression pacing, and balancing: resource phases, ratio curve, shortcut recipes, refactorability, cost archetypes, threat model, drop pacing, scrap/block economies, and numeric guardrails. Includes agreed decisions: ship scrap derived from threat via a scrap_per_threat key, no duplicate schematic drops (Mk2 upgrade recipes deferred to future work), removal of ship levels, and an action-item list for the follow-up requirements/code changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DyCu8vwChKMbLJQ3xosYEN
275 lines
15 KiB
Markdown
275 lines
15 KiB
Markdown
# 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 ores available from the start.
|
||
2. A third mined ore unlocked mid-game (slower to mine than the starting
|
||
ores).
|
||
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.
|
||
- There is no direct "resource unlock" mechanism. Miner recipes unlock
|
||
**implicitly** (REQ-LOCK-IMPLICIT) when some unlocked schematic's material
|
||
chain reaches that ore. Resource pacing is therefore controlled entirely
|
||
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 third ore, or the ore never unlocks).
|
||
|
||
## Production tree rules
|
||
|
||
### Structure
|
||
|
||
- **One new base input chain per phase.** Factory complexity ramps
|
||
alongside ship size; no phase adds more than one new bottom-level chain.
|
||
- **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
|
||
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 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 an *extension* of the existing production line — adding assemblers and
|
||
belts to what is already built — never a teardown and rebuild.
|
||
- **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 (`ship_level_formula`) does not exist (see Action items). 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.
|
||
- **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 a level-up or
|
||
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_formula`s, because pushing
|
||
rewards are tuned independently of ship production costs.
|
||
- Consequence: the threat value of scrap is the constant
|
||
`1 / scrap_per_threat`. The min-`scrap_drop` schematic derivation
|
||
(REQ-THREAT-SCRAP) and its potential circularity disappear.
|
||
- **Rule:** the late-game input's income rate meaningfully gates capital
|
||
production. The tuning target is expressed as "capital ships per boss
|
||
cycle for a player who fights at parity", not as an absolute rate.
|
||
|
||
## 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:** asteroid expansions compete with buildings for the same
|
||
currency. Expansion pricing makes "more space" a real alternative to
|
||
"more stuff", not an afterthought.
|
||
|
||
## 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.
|
||
- **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:
|
||
|
||
1. **Derive ship scrap from threat.** Add `scrap_per_threat` to
|
||
`world.toml`; remove `[ship.loot].scrap_drop` from `ships.toml`; update
|
||
REQ-RES-SCRAP-DROP; replace REQ-THREAT-SCRAP with the constant
|
||
`1 / scrap_per_threat`. Station `scrap_drop_formula`s stay.
|
||
2. **Remove schematic upgrades.** Owned ship/module schematics leave the
|
||
drop pool (REQ-DEF-SCHEMATIC-DROP); drop the new-unlock/level-up
|
||
annotation from the choice dialog; remove `player_production_level`
|
||
from `ships.toml`, `modules.toml`, and the code paths that increment
|
||
and evaluate it (REQ-BLD-SHIPYARD, REQ-MOD-CONFIG).
|
||
3. **Remove ship levels entirely.** Delete `ship_level_formula`
|
||
(REQ-WAV-SHIP-LEVEL); replace all stat formulas in `ships.toml` and
|
||
`modules.toml` with plain values (REQ-SHP-STATS, REQ-MOD-CONFIG,
|
||
REQ-MOD-STAT-CALC evaluation rules). Enemy defence stations keep their
|
||
station-level formulas — push scaling is unaffected.
|
||
4. **Balancing tool follow-up.** Remove the per-ship-entry `level` from
|
||
`balancing.toml` and the `L<level>` display format (REQ-BAL-TEAM,
|
||
REQ-BAL-CONFIG-GAME, REQ-BAL-UI-WIDGET, REQ-BAL-UI-INSPECT-WINDOW);
|
||
station entries keep their level. Ship stats panels no longer evaluate
|
||
at a level (REQ-MOD-UI-STATS-PANEL, REQ-UI-SHIP-STATS-PANEL).
|
||
5. **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.
|
||
6. **Confirm wave scaling carries alone.** With per-ship level scaling
|
||
gone, `threat_rate_formula` is the only time-scaling axis; verify the
|
||
current `world.toml` values still produce the intended difficulty
|
||
curve (the config reportedly already runs with a flat ship level, so
|
||
this is likely a review, not a retune).
|