Resolve request/brownout conflict: evaluate power requests nominally
A power request may not depend on power supply (Cut 6/F6), and continuous consumers brown out through a power term on their own performance stats (Cut 7). For Radiator (PumpedHeat) and AetherDrive (Torque) those were the same stat, making six shipped catalog records illegal and leaving six BrownoutTests cases skipped. Resolved per the operator's ruling: a power request is evaluated nominally -- what the item wants at full supply, not what it is currently managing. EquippedItem.EvaluateNominalPower (mirroring ConditionRatio's own NominalContext) pins only PowerSupplyFactor to 1; Heat/Durability stay live, since they are not the term a request would be circular through. Every PowerRequest/RefreshReserve/RefreshInputCapacitor implementation now reads a StatValidation.PowerRequestFields stat through it, so the same stat can carry a brownout curve and be a request field without the request depending on its own answer. StatValidation.ValidateNoPowerSupplyOnRequest (F6's static half of the rule, forbidding the term outright) is deleted -- nominal evaluation removes the circularity at the root instead. Only the dynamic modifier-chain half (StatModifier.ValidateNoPowerSupplyChain) still refuses anything, since EvaluateNominalPower forwards ScaleModifier/ ConstantModifier unchanged (real, not nominal). Un-skips BrownoutTests' six Radiator/AetherDrive cases and adds two new regression tests (a direct EvaluateNominalPower probe, and a two-tick Radiator demand check) backed by a new mutation harness. Updates the four PowerCurveTests/AetheriaStoresTests cases that pinned the now-superseded static refusal to instead assert acceptance. GameData/Aetheria.cc is unchanged and still validates. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
8a00fc9235
commit
8bbf280756
19 changed files with 528 additions and 209 deletions
|
|
@ -163,16 +163,25 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa
|
|||
"modifier chain reaches back to itself -- a stat cannot (even transitively) modify its own magnitude");
|
||||
}
|
||||
|
||||
// Cut 6 (docs/stats-and-power-cut.md): the dynamic half of "a stat that decides a power request may not
|
||||
// depend on power supply, directly or through a modifier chain." StatValidation.ValidateNoPowerSupplyOnRequest
|
||||
// already refuses a request stat's own declared Terms, at catalog load, at Upsert and at equip -- that check
|
||||
// needs no entity. This one does: whether a given modifier's target is "a power request stat" is static (the
|
||||
// (BehaviorData type, field) registry in StatValidation), but which items are actually feeding a modifier
|
||||
// chain into it is a fact about this concrete ship's loadout. Walks the same magnitude->target edges
|
||||
// ValidateNoCycle builds, in the other direction: is this modifier's own magnitude stat "power-tainted" --
|
||||
// does it, or something feeding it through another modifier on this entity, carry a PowerSupply term -- and
|
||||
// does it write onto a known request stat. Refused at equip, naming both the modifying item and the item
|
||||
// whose request it would corrupt.
|
||||
// Cut 6 (docs/stats-and-power-cut.md), narrowed by the nominal-request ruling (operator ruling 2026-09-19,
|
||||
// ItemData.cs's own PowerRequestFields comment): this is now the ONLY surviving half of "a stat that decides
|
||||
// a power request may not depend on power supply, directly or through a modifier chain." The direct half --
|
||||
// a request stat's own declared Terms naming PowerSupply -- used to be refused statically
|
||||
// (StatValidation.ValidateNoPowerSupplyOnRequest, at catalog load, Upsert and equip), but every
|
||||
// PowerRequest/RefreshReserve/RefreshInputCapacitor implementation now reads a PowerRequestFields stat
|
||||
// through EquippedItem.EvaluateNominalPower (Entity.cs), which pins that stat's own PowerSupplyFactor to 1
|
||||
// regardless of its Terms -- so a direct term can no longer make the request depend on its own answer, and
|
||||
// that static check was deleted. What EvaluateNominalPower does NOT pin is ScaleModifier/ConstantModifier --
|
||||
// it forwards those to the item's real, non-nominal resolver entries (same as ConditionRatio's own
|
||||
// NominalContext), so a modifier chain that reaches a power-tainted magnitude stat still corrupts a nominal
|
||||
// read too. That is exactly what this check still catches, and it needs an entity to do it: whether a given
|
||||
// modifier's target is "a power request stat" is static (the (BehaviorData type, field) registry in
|
||||
// StatValidation), but which items are actually feeding a modifier chain into it is a fact about this
|
||||
// concrete ship's loadout. Walks the same magnitude->target edges ValidateNoCycle builds, in the other
|
||||
// direction: is this modifier's own magnitude stat "power-tainted" -- does it, or something feeding it
|
||||
// through another modifier on this entity, carry a PowerSupply term -- and does it write onto a known
|
||||
// request stat. Refused at equip, naming both the modifying item and the item whose request it would
|
||||
// corrupt.
|
||||
private static void ValidateNoPowerSupplyChain(Entity entity, StatModifierData data, Target[] targets, EquippedItem modifyingItem)
|
||||
{
|
||||
if (data.Modifier == null) return;
|
||||
|
|
|
|||
Loading…
Reference in a new issue