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
This commit is contained in:
2026-07-03 17:05:19 +02:00
parent b9e70ba83a
commit d889b79658
8 changed files with 487 additions and 400 deletions

View File

@@ -86,23 +86,30 @@ def resolve_items(recipes, scrap_threat):
item_threat = {"scrap": scrap_threat}
def resolve_pass():
# All eligible (recipe, output) pairs per item: the max rule requires
# committing an item only once EVERY eligible recipe for it is
# computable — otherwise a shallow shortcut recipe that resolves one
# pass earlier than the base path would win and underprice the item.
recipes_per_item = {}
for recipe in non_repro:
for output in recipe.get("outputs", []):
if eligible(recipe, output):
recipes_per_item.setdefault(output["item"], []).append(
(recipe, output))
def resolve_pass(require_all_recipes=True):
progress = False
best = {}
for recipe in non_repro:
for output in recipe.get("outputs", []):
if output["item"] in item_threat:
continue
if not eligible(recipe, output):
continue
threat = recipe_threat_per_unit(recipe, output, item_threat)
if threat is None:
continue
key = output["item"]
if key not in best or threat > best[key]:
best[key] = threat
# A full sweep before committing keeps the max rule correct when
# several recipes for the same item resolve in the same pass.
for item, pairs in recipes_per_item.items():
if item in item_threat:
continue
threats = [recipe_threat_per_unit(r, o, item_threat)
for r, o in pairs]
if require_all_recipes and any(t is None for t in threats):
continue
threats = [t for t in threats if t is not None]
if threats:
best[item] = max(threats)
for item, threat in best.items():
item_threat[item] = threat
progress = True
@@ -139,6 +146,13 @@ def resolve_items(recipes, scrap_threat):
progress = resolve_pass()
progress = repro_pass() or progress
# Deadlock guard: if recipe cycles keep items waiting on each other,
# fall back to committing with the computable subset of recipes.
progress = True
while progress:
progress = resolve_pass(require_all_recipes=False)
progress = repro_pass() or progress
return item_threat