Gate 2: Initialize owns detach/reattach; delete dead StatOwner (F8)
StatModifier.Initialize recomputed _targets fresh every (re)activation but never reset _applied, so after deactivate -> unequip the target -> equip a replacement -> activate, Update saw `_executed && _applied` (both still true from the stale attachment) and neither branch of its latch fired again -- the modifier silently stopped reaching anything. Differential evidence: the same scenario resolved 6 pre-cut and 1 at Cut 2's HEAD. Chose the ownership fix over the one-line flag reset: Initialize is the entity's own re-activation hook, called exactly once per (re)activation for every StatModifier on it, so it is the one place that already knows a transition is happening. Detaching whatever this modifier was attached to before recomputing _targets removes the split authority between Initialize (which decides targets) and Update (which was silently also deciding attachment state) instead of leaving Update to infer a transition it was never told about. F8: Behaviors.cs's `StatOwner` property had no production caller -- it was added only to make one mutation test's mutant expressible. Deleted; the mutation test now uses `Entity` (a real symbol) as its wrong-owner stand-in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
b0d3354357
commit
5581911fb3
2 changed files with 12 additions and 4 deletions
|
|
@ -21,10 +21,6 @@ public abstract class Behavior
|
|||
protected Entity Entity => Item?.Entity ?? Consumable.Entity;
|
||||
public float Temperature => Item?.Temperature ?? Consumable.Entity.MaxTemp;
|
||||
|
||||
// The StatResolver key for whichever of Item/Consumable this behaviour has -- the two owners a resolved stat
|
||||
// value or an attached modifier can belong to (StatResolver.cs, docs/stats-and-power-cut.md Cut 2).
|
||||
protected object StatOwner => (object) Item ?? Consumable;
|
||||
|
||||
public float3 Direction
|
||||
{
|
||||
get
|
||||
|
|
|
|||
|
|
@ -69,8 +69,20 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa
|
|||
// fresh and silently at every equip, now goes through StatValidation.ResolveStatField -- resolved once,
|
||||
// memoized, and refused loudly (at catalog load, or here if a caller built one without going through the
|
||||
// catalog) instead of leaving `_stats` null for ApplyModifier to throw an opaque NullReferenceException on.
|
||||
//
|
||||
// Gate 2 fix (Soul pass over Cut 2): Initialize is the entity's own re-activation hook, called exactly once
|
||||
// per (re)activation for every StatModifier on it -- it is the one place that already knows "this behaviour's
|
||||
// targets are about to change." Detaching an old attachment, if any, belongs here, not in Update's _executed/
|
||||
// _applied latch: that latch only tracks per-tick execution and was never told a re-Initialize had happened,
|
||||
// so after deactivate -> unequip the target -> equip a replacement -> activate, _applied stayed true from the
|
||||
// stale attachment and Update's `_executed && !_applied` guard never fired again -- the modifier silently
|
||||
// stopped reaching anything. Making Initialize own the detach/reattach transition removes the split
|
||||
// authority instead of just resetting the flag: whatever this modifier was attached to before is explicitly
|
||||
// let go before new targets are computed, so the next Execute+Update reliably reapplies against them.
|
||||
public void Initialize()
|
||||
{
|
||||
if (_applied)
|
||||
RemoveModifier();
|
||||
_targets = TargetsOf(Entity, _data);
|
||||
ValidateNoCycle(Entity, _data, _targets);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue