Fix ThreatCostCalculator: per-unit division, scrap fallback, fixpoint, staggered-recipe max
This commit is contained in:
@@ -373,31 +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. **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.
|
||||
|
||||
@@ -224,12 +224,13 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des
|
||||
2. The `production_time_seconds` of every module instance in the configured layout.
|
||||
3. For every material required (the union of the ship's base materials and all module instance materials, with quantities summed per item type): the recursive production time of that material multiplied by the required quantity (see REQ-THREAT-ITEM).
|
||||
|
||||
- REQ-THREAT-ITEM: The threat value of an item type (in seconds) is determined by the recipe that produces it:
|
||||
- **Miner recipe**: the recipe's `duration_seconds`.
|
||||
- **Smelter recipe**: the recipe's `duration_seconds` plus the sum of each input's threat value multiplied by that input's required quantity.
|
||||
- **Assembler recipe**: the recipe's `duration_seconds` plus the sum of each input's threat value multiplied by that input's required quantity.
|
||||
- **Reprocessing-only item** (an item type that has no miner, smelter, or assembler recipe producing it, and is only obtainable via reprocessing): `(scrap_threat × scrap_per_cycle + duration_seconds) / probability`, where `scrap_threat` is the threat value of scrap (see REQ-THREAT-SCRAP), `scrap_per_cycle` is the number of scrap consumed per reprocessing cycle, `duration_seconds` is the reprocessing cycle time, and `probability` is the normalized weight of that item in the reprocessing output pool.
|
||||
- **Multiple recipes**: if an item type can be produced by more than one non-reprocessing recipe (miner, smelter, or assembler), its threat value is the **maximum** across all such recipes. The reprocessing path is only used when no other recipe exists.
|
||||
- REQ-THREAT-ITEM: The threat value of an item type (in production-seconds **per unit**) is determined by the recipe that produces it:
|
||||
- **Miner recipe**: `duration_seconds / output_amount`, where `output_amount` is the number of units produced per cycle.
|
||||
- **Smelter recipe**: `(duration_seconds + Σ (input_threat × input_amount)) / output_amount`, where the sum is over all inputs.
|
||||
- **Assembler recipe**: `(duration_seconds + Σ (input_threat × input_amount)) / output_amount`, where the sum is over all inputs.
|
||||
- **Reprocessing-only item** (an item type that has no miner, smelter, or assembler recipe producing it, and is only obtainable via reprocessing): `(scrap_threat × scrap_per_cycle + duration_seconds) / probability`, where `scrap_threat` is the threat value of scrap (see REQ-THREAT-SCRAP), `scrap_per_cycle` is the number of scrap consumed per reprocessing cycle, `duration_seconds` is the reprocessing cycle time, and `probability` is the normalized weight of that item in the reprocessing output pool. (Reprocessing output amounts are 1 in practice, so per-unit division is already implicit in the formula.)
|
||||
- **Multiple recipes**: if an item type can be produced by more than one non-reprocessing recipe (miner, smelter, or assembler), its threat value is the **maximum** across **all** such eligible recipes, and the threat is committed only once every eligible recipe is computable (so a shallow shortcut recipe that resolves earlier than a deeper base recipe cannot lower the item's threat). The reprocessing path is only used when no other recipe exists. If recipe cycles prevent full resolution, the max over the currently computable subset is used as a fallback.
|
||||
- **Scrap-consuming recipe fallback**: a non-reprocessing recipe that takes `scrap` as an input participates in an item's threat computation only if no scrap-free recipe (miner, smelter, or assembler) produces that item. This mirrors the reprocessing fallback rule and prevents the scrap-to-ingot smelter recipe from inflating basic material threats via the max rule.
|
||||
|
||||
- REQ-THREAT-SCRAP: The threat value of scrap is the constant `1 / world.toml [world].scrap_per_threat`. This is the exact inverse of the scrap-drop conversion in REQ-RES-SCRAP-DROP, so a destroyed ship drops scrap worth precisely its own threat cost. Because scrap threat is now a fixed constant, it no longer depends on any ship's threat cost, removing the potential circularity with REQ-MOD-THREAT for ships built from reprocessing-only materials.
|
||||
- REQ-MOD-STAT-CALC: For each stat (on the ship hull or on a capability module instance), the final value is computed as: `final = base × total_multiplier + total_additive`, where:
|
||||
|
||||
Reference in New Issue
Block a user