diff --git a/Assets/Scripts/ServerShared/Entity.cs b/Assets/Scripts/ServerShared/Entity.cs index caaccbb55..ad645add4 100644 --- a/Assets/Scripts/ServerShared/Entity.cs +++ b/Assets/Scripts/ServerShared/Entity.cs @@ -1244,6 +1244,9 @@ public abstract class Entity foreach (var reveal in pair.Value.Values) freshest = Math.Max(freshest, reveal.Time); if (Zone.Time - freshest >= memory) _agedSubjects.Add(pair.Key); } + // A record of a subject that left and holds no book (a quiet ship, never seen to act) has no entry to age: it goes now. + foreach (var subject in _seenDestroyed.Keys) + if (!entities.Contains(subject) && !_reveals.ContainsKey(subject)) _agedSubjects.Add(subject); for (var i = 0; i < _agedSubjects.Count; i++) EndReveals(_agedSubjects[i], null); _agedSubjects.Clear(); } diff --git a/Assets/Scripts/ServerShared/FireControl.cs b/Assets/Scripts/ServerShared/FireControl.cs index e2f23dd29..9724f5975 100644 --- a/Assets/Scripts/ServerShared/FireControl.cs +++ b/Assets/Scripts/ServerShared/FireControl.cs @@ -157,7 +157,8 @@ public static class FireControl // book holds a reveal of it (Entity.HasReveal: it saw the item act) and the item is still fitted to its ship and // not destroyed. The book says what was seen; the live item says whether it can still be aimed at. Derived on // every read, never cached; watching a ship reveals no gear. Any aiming choice that reads an item's class or -XX + // stats (Combat's target-item choice, its dps) asks this; it is the item gate of Entity's aim slot (TrySelectTargetItem, + // ResolvedAim). A mount is aimed by TrySelectMount, which reads no item at all. public static bool IsRevealed(Entity observer, EquippedItem item) => !item.EquippableItem.IsDestroyed && item.Entity.Equipment.Contains(item) && observer.HasReveal(item.Entity, item); diff --git a/tests/Aetheria.Shared.Tests/MountCycleTests.cs b/tests/Aetheria.Shared.Tests/MountCycleTests.cs index 662606cc8..8bd45133f 100644 --- a/tests/Aetheria.Shared.Tests/MountCycleTests.cs +++ b/tests/Aetheria.Shared.Tests/MountCycleTests.cs @@ -153,6 +153,28 @@ public sealed partial class ContactRevealTests Assert.False(observer.TrySelectMount(reactor.Hardpoint)); } + // ---- TheSeenDestroyedRecordGoesWithTheSubject ---- + + // The record is the observer's memory of one subject: when that subject has left the zone and the observer holds no book + // of it (a quiet ship, never seen to act), the record is dropped at the observer's next Sense, not held for the sitting. + [Theory] + [InlineData("remove")] + [InlineData("warp")] + public void TheSeenDestroyedRecordGoesWithTheSubject(string how) + { + var (bay, observer, reactor) = Identified(); + var sense = SenseOf(observer); + reactor.EquippableItem.Durability = 0f; + sense(); + Assert.False(MountsOf(observer.AimCycle(bay)).Any(m => ReferenceEquals(m, reactor.Hardpoint)), "fixture: recorded while the ship is here"); + Assert.Empty(observer.RevealsOf(bay)); + + Leave(_zone, bay, how); + sense(); + observer.EntityInfoGathered[bay] = 1f; + Assert.True(MountsOf(observer.AimCycle(bay)).Any(m => ReferenceEquals(m, reactor.Hardpoint)), "the record went with the ship"); + } + // ---- ADestroyedClaimedMountIsNotSkippedUnderALie ---- // The known gap (follow-up perceived-destruction-under-hull-lie): perceiving a destruction reads the true item, and the