continue first full balancing round

This commit is contained in:
2026-07-08 20:33:11 +02:00
parent e32d384c99
commit fc622670d2
6 changed files with 115 additions and 36 deletions

View File

@@ -47,7 +47,7 @@ attack_rate_hz = 2.0
[[module]] [[module]]
id = "railgun_m" id = "railgun_m"
unlock_at_station_level = 1 unlock_at_station_level = 2
surface_mask = [ surface_mask = [
"OO", "OO",
"OO"] "OO"]
@@ -64,7 +64,7 @@ attack_rate_hz = 1.5
[[module]] [[module]]
id = "railgun_l" id = "railgun_l"
unlock_at_station_level = 1 unlock_at_station_level = 6
unlock_requires = ["railgun_m"] unlock_requires = ["railgun_m"]
surface_mask = [ surface_mask = [
"OOO", "OOO",
@@ -101,7 +101,7 @@ collection_rate_hz = 0.5
[[module]] [[module]]
id = "repair_tool" id = "repair_tool"
unlock_at_station_level = -1 unlock_at_station_level = 0
surface_mask = ["O"] surface_mask = ["O"]
materials = [{item = "repair_tool_module", amount = 1}] materials = [{item = "repair_tool_module", amount = 1}]
production_time_seconds = 1 production_time_seconds = 1
@@ -110,7 +110,7 @@ glyph = "Rp"
[module.repair] [module.repair]
repair_rate_hz = 1 repair_rate_hz = 1
repair_amount_hp = 12 repair_amount_hp = 9
repair_range_m = 80 repair_range_m = 80
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -119,7 +119,7 @@ repair_range_m = 80
[[module]] [[module]]
id = "afterburner" id = "afterburner"
unlock_at_station_level = -1 unlock_at_station_level = 2
surface_mask = ["OOO"] surface_mask = ["OOO"]
materials = [{item = "afterburner_module", amount = 1}] materials = [{item = "afterburner_module", amount = 1}]
production_time_seconds = 1 production_time_seconds = 1
@@ -133,7 +133,7 @@ added_main_acceleration_mpss = 60
[[module]] [[module]]
id = "maneuvering_thrusters" id = "maneuvering_thrusters"
unlock_at_station_level = -1 unlock_at_station_level = 1
surface_mask = ["OO"] surface_mask = ["OO"]
materials = [{item = "maneuvering_thrusters_module", amount = 1}] materials = [{item = "maneuvering_thrusters_module", amount = 1}]
production_time_seconds = 1 production_time_seconds = 1
@@ -150,7 +150,7 @@ added_maneuvering_acceleration_mpss = 10
[[module]] [[module]]
id = "armor_plates" id = "armor_plates"
unlock_at_station_level = -1 unlock_at_station_level = 0
surface_mask = ["OO"] surface_mask = ["OO"]
materials = [{item = "armor_plates_module", amount = 1}] materials = [{item = "armor_plates_module", amount = 1}]
production_time_seconds = 1 production_time_seconds = 1
@@ -163,7 +163,7 @@ added_hp = 1200
[[module]] [[module]]
id = "sensor_booster" id = "sensor_booster"
unlock_at_station_level = -1 unlock_at_station_level = 1
surface_mask = ["OO"] surface_mask = ["OO"]
materials = [{item = "sensor_booster_module", amount = 1}] materials = [{item = "sensor_booster_module", amount = 1}]
production_time_seconds = 1 production_time_seconds = 1
@@ -179,7 +179,7 @@ added_sensor_range_m = 50
[[module]] [[module]]
id = "weapon_upgrade" id = "weapon_upgrade"
unlock_at_station_level = -1 unlock_at_station_level = 4
surface_mask = [ surface_mask = [
"OO", "OO",
"OX", "OX",
@@ -195,7 +195,7 @@ multiplied_damage = 1.2
[[module]] [[module]]
id = "weapon_primer" id = "weapon_primer"
unlock_at_station_level = -1 unlock_at_station_level = 4
surface_mask = [ surface_mask = [
"OO", "OO",
"OX", "OX",
@@ -211,7 +211,7 @@ multiplied_attack_rate_hz = 1.2
[[module]] [[module]]
id = "weapon_stabilizer" id = "weapon_stabilizer"
unlock_at_station_level = -1 unlock_at_station_level = 3
surface_mask = [ surface_mask = [
"OO", "OO",
"OX", "OX",
@@ -234,7 +234,7 @@ multiplied_attack_rate_hz = 0.8
[[module]] [[module]]
id = "drone_bay" id = "drone_bay"
unlock_at_station_level = -1 unlock_at_station_level = 5
surface_mask = [ surface_mask = [
"OO", "OO",
"OO"] "OO"]
@@ -246,7 +246,7 @@ glyph = "Db"
[[module]] [[module]]
id = "drone_hangar" id = "drone_hangar"
unlock_at_station_level = -1 unlock_at_station_level = 9
surface_mask = [ surface_mask = [
"OOOOOO", "OOOOOO",
"OOOOOO"] "OOOOOO"]

View File

@@ -142,8 +142,11 @@ duration_seconds = 1.5
# Depth-3 chain (ore -> ingot -> plate -> block) is the factory's # Depth-3 chain (ore -> ingot -> plate -> block) is the factory's
# doubling-time knob; see the block economy rules in progression_design.md. # doubling-time knob; see the block economy rules in progression_design.md.
# Explicitly unlocked at start (-1): building blocks appear in no
# schematic's materials, so implicit unlocking can never reach this recipe.
[[recipe]] [[recipe]]
id = "building_block" id = "building_block"
unlock_at_station_level = -1
building = "assembler" building = "assembler"
inputs = [{item = "steel_plate", amount = 2}] inputs = [{item = "steel_plate", amount = 2}]
outputs = [{item = "building_block", amount = 4}] outputs = [{item = "building_block", amount = 4}]

View File

@@ -84,7 +84,7 @@ sensor_range_m = 200
# mount medium hardware. # mount medium hardware.
[[ship]] [[ship]]
id = "destroyer" id = "destroyer"
unlock_at_station_level = -1 unlock_at_station_level = 0
layout = [ layout = [
"OXOXO", "OXOXO",
"OOOOO", "OOOOO",
@@ -120,7 +120,7 @@ sensor_range_m = 220
# supports; no 3x3 area exists for an l gun. # supports; no 3x3 area exists for an l gun.
[[ship]] [[ship]]
id = "cruiser" id = "cruiser"
unlock_at_station_level = -1 unlock_at_station_level = 2
layout = [ layout = [
"XOOX", "XOOX",
"OOOO", "OOOO",
@@ -158,7 +158,8 @@ sensor_range_m = 250
# stern leave no 3x3 area for an l gun and no 2x6 area for a drone hangar. # stern leave no 3x3 area for an l gun and no 2x6 area for a drone hangar.
[[ship]] [[ship]]
id = "battlecruiser" id = "battlecruiser"
unlock_at_station_level = -1 unlock_at_station_level = 4
unlock_requires = ["cruiser"]
layout = [ layout = [
"OOXXOO", "OOXXOO",
"OOOOOO", "OOOOOO",
@@ -179,7 +180,7 @@ materials = [{item = "battlecruiser_hull", amount = 1}]
production_time_seconds = 5 production_time_seconds = 5
[ship.health] [ship.health]
hp = 2200 hp = 2400
[ship.movement] [ship.movement]
speed_mps = 20 speed_mps = 20
@@ -200,7 +201,8 @@ sensor_range_m = 260
# so no 2x6 drone hangar fits. # so no 2x6 drone hangar fits.
[[ship]] [[ship]]
id = "battleship" id = "battleship"
unlock_at_station_level = 1 unlock_at_station_level = 6
unlock_requires = ["battlecruiser"]
layout = [ layout = [
"XOOOOX", "XOOOOX",
"OOOOOO", "OOOOOO",
@@ -242,7 +244,7 @@ sensor_range_m = 280
# stay the only hangar hull. Bow and stern strips hold supports. # stay the only hangar hull. Bow and stern strips hold supports.
[[ship]] [[ship]]
id = "dreadnought" id = "dreadnought"
unlock_at_station_level = 2 unlock_at_station_level = 8
unlock_requires = ["battleship"] unlock_requires = ["battleship"]
layout = [ layout = [
"XXXOOOOOXXX", "XXXOOOOOXXX",
@@ -286,7 +288,8 @@ sensor_range_m = 300
# the lower decks hold supports and 2x2 point-defense m guns. # the lower decks hold supports and 2x2 point-defense m guns.
[[ship]] [[ship]]
id = "carrier" id = "carrier"
unlock_at_station_level = -1 unlock_at_station_level = 9
unlock_requires = ["battleship"]
layout = [ layout = [
"XOOOOOOOOX", "XOOOOOOOOX",
"OOOOOOOOOO", "OOOOOOOOOO",

View File

@@ -1,7 +1,7 @@
[world] [world]
height_tiles = 40 height_tiles = 40
refund_percentage = 100 refund_percentage = 100
starting_building_blocks = 1000 starting_building_blocks = 200
scrap_despawn_seconds = 120 scrap_despawn_seconds = 120
scrap_per_threat = 0.25 scrap_per_threat = 0.25
tile_size_m = 10 tile_size_m = 10
@@ -19,7 +19,9 @@ enemy_buffer_width_tiles = 20
[expansion] [expansion]
columns_per_expansion_tiles = 10 columns_per_expansion_tiles = 10
cost_building_blocks = 200 # Flat until the escalating cost formula lands (progression_design.md
# action item 4).
cost_building_blocks = 400
[push] [push]
push_expand_columns_tiles = 10 push_expand_columns_tiles = 10
@@ -32,10 +34,13 @@ target_hysteresis = 0.40 # keep current target unless
[artifacts] [artifacts]
artifact_chance_formula = "0.05 * x" # 5% chance per station level artifact_chance_formula = "0.05 * x" # 5% chance per station level
artifact_win_count = 3 artifact_win_count = 5
[waves] [waves]
threat_rate_formula = "x" # Tuned against the factory-size curve (content_design.md, balancing
# targets): stays below the player's achievable military output early,
# crosses it around the late boundary (~cycle 15), overwhelms by ~24.
threat_rate_formula = "2*x + 0.15*x*x"
gap_min_seconds = 15 gap_min_seconds = 15
gap_max_seconds = 45 gap_max_seconds = 45
spawn_duration_seconds = 10 spawn_duration_seconds = 10

View File

@@ -458,8 +458,8 @@ verifies it empirically. Change the anchors first, re-derive stats second.
time) as the *prior*; per-hull HP is the empirical trim knob for time) as the *prior*; per-hull HP is the empirical trim knob for
arena results (guns are shared across many hulls, so gun changes move arena results (guns are shared across many hulls, so gun changes move
many matchups at once — hull HP moves exactly one). Current values many matchups at once — hull HP moves exactly one). Current values
after round-4 trims: drone 60, frigate 300, destroyer 550, after round-5 trims: drone 60, frigate 300, destroyer 550,
cruiser 1500, battlecruiser 2200, battleship 6300, cruiser 1500, battlecruiser 2400, battleship 6300,
dreadnought/carrier 24000. (The capital hulls sit well above the dreadnought/carrier 24000. (The capital hulls sit well above the
15/threat prior while their guns sit below the DPS prior — the arena 15/threat prior while their guns sit below the DPS prior — the arena
consistently prices big ships as tanks with taxed guns.) consistently prices big ships as tanks with taxed guns.)
@@ -473,10 +473,10 @@ verifies it empirically. Change the anchors first, re-derive stats second.
- **TTK check:** a mirror cruiser duel (51 DPS vs 2140 EHP) lasts ~42 s — - **TTK check:** a mirror cruiser duel (51 DPS vs 2140 EHP) lasts ~42 s —
inside the 3060 s parity-fight band implied by the 25-ship fleet inside the 3060 s parity-fight band implied by the 25-ship fleet
target. Capital mirrors run longer (~90 s), deliberately. target. Capital mirrors run longer (~90 s), deliberately.
- **Repair ≈ 1 HP/s per threat:** repair_tool heals 12 HP × 1 Hz at 13 - **Repair ≈ 0.7 HP/s per threat:** repair_tool heals 9 HP × 1 Hz at 13
threat (arena round 1: at 2 HP/s-per-threat the escorted team won threat (nerfed 25 → 12 → 9: in-combat sustain effectively removes
flawlessly — in-combat sustain effectively removes enemy DPS and must enemy DPS and must be priced like DPS, not like HP — the escorted
be priced like DPS, not like HP). Salvager: collection range 60 (was team held a persistent +24% at 12). Salvager: collection range 60 (was
a nonsensical 500, beyond any sensor), cargo 20, 0.5 collections/s. a nonsensical 500, beyond any sensor), cargo 20, 0.5 collections/s.
`scrap_despawn_seconds` raised 30 → 120: a capital kill drops `scrap_despawn_seconds` raised 30 → 120: a capital kill drops
hundreds of scrap collected one per cycle, so 30 s despawned nearly hundreds of scrap collected one per cycle, so 30 s despawned nearly
@@ -517,6 +517,21 @@ DPS (17→14, 70→52 damage), armor 640→1000, repair 25→12, station range
still win flawlessly in round 2, the range ladder (kiting) is the next still win flawlessly in round 2, the range ladder (kiting) is the next
suspect, ahead of further DPS cuts. suspect, ahead of further DPS cuts.
**Arena round 5 results (2026-07-04, with fight durations) — combat
pass converged:** durations validate the TTK anchor: drone mirror
22.7 s, cruiser mirror 71.2 s, battleship mirror 94.6 s, most fights
in the 2272 s band; the destroyers-vs-dreadnought slugfest is the
outlier at 214 s (extreme tank vs. chip damage — acceptable for the
extreme case). Converged: mirrors 510%, swarm vs cruisers +15%,
frigates vs battleship dropped to +13% on its own (confirming the
noise-floor read — now inside band anyway), dreadnought at +3% knife
edge, glass vs armored +10%, two-to-one 78%, station assault stable at
44%. Two two-round signals actioned in round 6: mixed beat
battlecruisers again (+20%, +18%) → battlecruiser hull 2200→2400;
repair escort held +24% twice with all frigates surviving →
repair_amount 12→9. Everything else is frozen; further refinement
moves to real-game playtests.
**Arena round 4 results (2026-07-04) and convergence policy:** mirrors **Arena round 4 results (2026-07-04) and convergence policy:** mirrors
at 03%; swarm vs cruisers +7%; glass vs armored resolved at +3% for at 03%; swarm vs cruisers +7%; glass vs armored resolved at +3% for
armored (armor 1200 confirmed after four rounds of signal). Noise armored (armor 1200 confirmed after four rounds of signal). Noise
@@ -565,6 +580,65 @@ breakable). Round-3 knob changes: stabilizer range ×1.5→×1.3, and
per-hull HP trims — battlecruiser 2700→2500, battleship 7500→7000, per-hull HP trims — battlecruiser 2700→2500, battleship 7500→7000,
dreadnought/carrier 15500→19000. dreadnought/carrier 15500→19000.
### Pacing pass — first values
The mapping between station levels and boss cycles: at the intended
push cadence (first set around cycle 23, roughly one per cycle from
mid on), the destroyed set's level is reached around cycle +2. So
level 2 ≈ the mid boundary, level 6 ≈ mid-late, levels 89 ≈ capitals
with enough cycles left to ramp voidsteel production before late.
**Starting set** (`unlock_at_station_level = -1`), per the starting-set
rule (exactly enough to reach the first push): drone, frigate,
railgun_s, salvager. Note: the building_block recipe must carry an
explicit `-1` — building blocks appear in no schematic's materials, so
implicit unlocking can never reach it (fixed in this pass; it was
silently locked).
**Unlock ladder** (level → unlocks; ← marks `unlock_requires`):
| level | ships | modules | recipes |
|---|---|---|---|
| 0 | destroyer | repair_tool, armor_plates | |
| 1 | | maneuvering_thrusters, sensor_booster | shortcut_steel_plate |
| 2 | cruiser | railgun_m, afterburner | shortcut_control_chip, shortcut_hardened_steel |
| 3 | | weapon_stabilizer | |
| 4 | battlecruiser ← cruiser | weapon_primer, weapon_upgrade | |
| 5 | | drone_bay | |
| 6 | battleship ← battlecruiser | railgun_l ← railgun_m | |
| 8 | dreadnought ← battleship | | |
| 9 | carrier ← battleship | drone_hangar | |
Level 0's pool has exactly three entries, so the first push offers a
full dialog with no artifacts competing. Level 2 is the quartz gate:
cruiser and railgun_m are the first schematics whose chains reach
quartz (the shortcut recipes' outputs only become implicitly unlocked
alongside them, so they cannot drop early).
**Threat rate** `2*x + 0.15*x*x`, tuned against the factory-size curve
with roughly half the player's output assumed military:
| cycle x | rate (threat/s) | player military (≈ curve/2) |
|---|---|---|
| 2 | 4.6 | ~12 |
| 6 | 17.4 | ~30 |
| 15 | 63.8 | ~60 |
| 20 | 100 | ~75 |
| 24 | 134 | — |
Below the player early, crossing at the late boundary, overwhelming by
~24 — the endless-race shape from the progression rules. Typical wave
at x=6 (30 s gap): ~520 threat ≈ two cruisers plus escorts; boss at
x=15: ~3800 threat ≈ two and a half dreadnoughts.
**Economy**: `starting_building_blocks` 1000 → 200 (minimal block loop
+ first shipyard ≈ 150, plus beginner slack); expansion cost 200 → 400
flat (placeholder until the escalating formula, action item 4);
`artifact_win_count` 3 → 5 with chance `0.05*x`: ~1518 pushes in a
winning run offer ~7 artifact options cumulatively, so winning takes
choosing the artifact over a schematic five times — a real decision
each time, and the win lands in the cycle 2024 target window.
## Balancing targets (first pass, July 2026) ## Balancing targets (first pass, July 2026)
The six root numbers for the balancing pass. Every derived value (threat The six root numbers for the balancing pass. Every derived value (threat

View File

@@ -373,9 +373,3 @@ in `requirements.md` and the git history). Still open:
one matching deposit tile). Touches REQ-BLD-MINER ("every asteroid one matching deposit tile). Touches REQ-BLD-MINER ("every asteroid
tile is equivalent" no longer holds), REQ-GW-ASTEROID-EXPAND / tile is equivalent" no longer holds), REQ-GW-ASTEROID-EXPAND /
REQ-EXP-*, `world.toml`, and `visuals.toml`. REQ-EXP-*, `world.toml`, and `visuals.toml`.
6. **Verify the balancing arena end condition.** Arena round 1's
station-assault log showed the arena "(running)" with every ship on
both sides destroyed — per REQ-BAL-SIM-END the station-less team
should have lost the moment its last ship died. Round 2 ended
correctly, so it may only be a log-snapshot artifact; confirm, and
fix the end condition if it can genuinely idle in that state.