diff --git a/bin/app/data/config/modules.toml b/bin/app/data/config/modules.toml index cbcb62a..557926b 100644 --- a/bin/app/data/config/modules.toml +++ b/bin/app/data/config/modules.toml @@ -47,7 +47,7 @@ attack_rate_hz = 2.0 [[module]] id = "railgun_m" -unlock_at_station_level = 1 +unlock_at_station_level = 2 surface_mask = [ "OO", "OO"] @@ -64,7 +64,7 @@ attack_rate_hz = 1.5 [[module]] id = "railgun_l" -unlock_at_station_level = 1 +unlock_at_station_level = 6 unlock_requires = ["railgun_m"] surface_mask = [ "OOO", @@ -101,7 +101,7 @@ collection_rate_hz = 0.5 [[module]] id = "repair_tool" -unlock_at_station_level = -1 +unlock_at_station_level = 0 surface_mask = ["O"] materials = [{item = "repair_tool_module", amount = 1}] production_time_seconds = 1 @@ -110,7 +110,7 @@ glyph = "Rp" [module.repair] repair_rate_hz = 1 -repair_amount_hp = 12 +repair_amount_hp = 9 repair_range_m = 80 # ----------------------------------------------------------------------------- @@ -119,7 +119,7 @@ repair_range_m = 80 [[module]] id = "afterburner" -unlock_at_station_level = -1 +unlock_at_station_level = 2 surface_mask = ["OOO"] materials = [{item = "afterburner_module", amount = 1}] production_time_seconds = 1 @@ -133,7 +133,7 @@ added_main_acceleration_mpss = 60 [[module]] id = "maneuvering_thrusters" -unlock_at_station_level = -1 +unlock_at_station_level = 1 surface_mask = ["OO"] materials = [{item = "maneuvering_thrusters_module", amount = 1}] production_time_seconds = 1 @@ -150,7 +150,7 @@ added_maneuvering_acceleration_mpss = 10 [[module]] id = "armor_plates" -unlock_at_station_level = -1 +unlock_at_station_level = 0 surface_mask = ["OO"] materials = [{item = "armor_plates_module", amount = 1}] production_time_seconds = 1 @@ -163,7 +163,7 @@ added_hp = 1200 [[module]] id = "sensor_booster" -unlock_at_station_level = -1 +unlock_at_station_level = 1 surface_mask = ["OO"] materials = [{item = "sensor_booster_module", amount = 1}] production_time_seconds = 1 @@ -179,7 +179,7 @@ added_sensor_range_m = 50 [[module]] id = "weapon_upgrade" -unlock_at_station_level = -1 +unlock_at_station_level = 4 surface_mask = [ "OO", "OX", @@ -195,7 +195,7 @@ multiplied_damage = 1.2 [[module]] id = "weapon_primer" -unlock_at_station_level = -1 +unlock_at_station_level = 4 surface_mask = [ "OO", "OX", @@ -211,7 +211,7 @@ multiplied_attack_rate_hz = 1.2 [[module]] id = "weapon_stabilizer" -unlock_at_station_level = -1 +unlock_at_station_level = 3 surface_mask = [ "OO", "OX", @@ -234,7 +234,7 @@ multiplied_attack_rate_hz = 0.8 [[module]] id = "drone_bay" -unlock_at_station_level = -1 +unlock_at_station_level = 5 surface_mask = [ "OO", "OO"] @@ -246,7 +246,7 @@ glyph = "Db" [[module]] id = "drone_hangar" -unlock_at_station_level = -1 +unlock_at_station_level = 9 surface_mask = [ "OOOOOO", "OOOOOO"] diff --git a/bin/app/data/config/recipes.toml b/bin/app/data/config/recipes.toml index 256bb1c..cea8dcb 100644 --- a/bin/app/data/config/recipes.toml +++ b/bin/app/data/config/recipes.toml @@ -142,8 +142,11 @@ duration_seconds = 1.5 # Depth-3 chain (ore -> ingot -> plate -> block) is the factory's # 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]] id = "building_block" +unlock_at_station_level = -1 building = "assembler" inputs = [{item = "steel_plate", amount = 2}] outputs = [{item = "building_block", amount = 4}] diff --git a/bin/app/data/config/ships.toml b/bin/app/data/config/ships.toml index 8349a45..4fc7883 100644 --- a/bin/app/data/config/ships.toml +++ b/bin/app/data/config/ships.toml @@ -84,7 +84,7 @@ sensor_range_m = 200 # mount medium hardware. [[ship]] id = "destroyer" -unlock_at_station_level = -1 +unlock_at_station_level = 0 layout = [ "OXOXO", "OOOOO", @@ -120,7 +120,7 @@ sensor_range_m = 220 # supports; no 3x3 area exists for an l gun. [[ship]] id = "cruiser" -unlock_at_station_level = -1 +unlock_at_station_level = 2 layout = [ "XOOX", "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. [[ship]] id = "battlecruiser" -unlock_at_station_level = -1 +unlock_at_station_level = 4 +unlock_requires = ["cruiser"] layout = [ "OOXXOO", "OOOOOO", @@ -179,7 +180,7 @@ materials = [{item = "battlecruiser_hull", amount = 1}] production_time_seconds = 5 [ship.health] -hp = 2200 +hp = 2400 [ship.movement] speed_mps = 20 @@ -200,7 +201,8 @@ sensor_range_m = 260 # so no 2x6 drone hangar fits. [[ship]] id = "battleship" -unlock_at_station_level = 1 +unlock_at_station_level = 6 +unlock_requires = ["battlecruiser"] layout = [ "XOOOOX", "OOOOOO", @@ -242,7 +244,7 @@ sensor_range_m = 280 # stay the only hangar hull. Bow and stern strips hold supports. [[ship]] id = "dreadnought" -unlock_at_station_level = 2 +unlock_at_station_level = 8 unlock_requires = ["battleship"] layout = [ "XXXOOOOOXXX", @@ -286,7 +288,8 @@ sensor_range_m = 300 # the lower decks hold supports and 2x2 point-defense m guns. [[ship]] id = "carrier" -unlock_at_station_level = -1 +unlock_at_station_level = 9 +unlock_requires = ["battleship"] layout = [ "XOOOOOOOOX", "OOOOOOOOOO", diff --git a/bin/app/data/config/world.toml b/bin/app/data/config/world.toml index cdac851..9424e09 100644 --- a/bin/app/data/config/world.toml +++ b/bin/app/data/config/world.toml @@ -1,7 +1,7 @@ [world] height_tiles = 40 refund_percentage = 100 -starting_building_blocks = 1000 +starting_building_blocks = 200 scrap_despawn_seconds = 120 scrap_per_threat = 0.25 tile_size_m = 10 @@ -19,7 +19,9 @@ enemy_buffer_width_tiles = 20 [expansion] 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_expand_columns_tiles = 10 @@ -32,10 +34,13 @@ target_hysteresis = 0.40 # keep current target unless [artifacts] artifact_chance_formula = "0.05 * x" # 5% chance per station level -artifact_win_count = 3 +artifact_win_count = 5 [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_max_seconds = 45 spawn_duration_seconds = 10 diff --git a/docs/content_design.md b/docs/content_design.md index f72b8d7..d0f4609 100644 --- a/docs/content_design.md +++ b/docs/content_design.md @@ -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 arena results (guns are shared across many hulls, so gun changes move many matchups at once — hull HP moves exactly one). Current values - after round-4 trims: drone 60, frigate 300, destroyer 550, - cruiser 1500, battlecruiser 2200, battleship 6300, + after round-5 trims: drone 60, frigate 300, destroyer 550, + cruiser 1500, battlecruiser 2400, battleship 6300, dreadnought/carrier 24000. (The capital hulls sit well above the 15/threat prior while their guns sit below the DPS prior — the arena 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 — inside the 30–60 s parity-fight band implied by the 25-ship fleet target. Capital mirrors run longer (~90 s), deliberately. -- **Repair ≈ 1 HP/s per threat:** repair_tool heals 12 HP × 1 Hz at 13 - threat (arena round 1: at 2 HP/s-per-threat the escorted team won - flawlessly — in-combat sustain effectively removes enemy DPS and must - be priced like DPS, not like HP). Salvager: collection range 60 (was +- **Repair ≈ 0.7 HP/s per threat:** repair_tool heals 9 HP × 1 Hz at 13 + threat (nerfed 25 → 12 → 9: in-combat sustain effectively removes + enemy DPS and must be priced like DPS, not like HP — the escorted + team held a persistent +24% at 12). Salvager: collection range 60 (was a nonsensical 500, beyond any sensor), cargo 20, 0.5 collections/s. `scrap_despawn_seconds` raised 30 → 120: a capital kill drops 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 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 22–72 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 5–10%, 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 at 0–3%; swarm vs cruisers +7%; glass vs armored resolved at +3% for 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, 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 2–3, 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 8–9 ≈ 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`: ~15–18 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 20–24 target window. + ## Balancing targets (first pass, July 2026) The six root numbers for the balancing pass. Every derived value (threat diff --git a/docs/progression_design.md b/docs/progression_design.md index e6f755d..6e9fb17 100644 --- a/docs/progression_design.md +++ b/docs/progression_design.md @@ -373,9 +373,3 @@ in `requirements.md` and the git history). Still open: 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. **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.