Leave perceived item destruction to batch B: drop the Identified item pass and its tests from this cut

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-11 22:06:24 +02:00
commit 49ace47f34
2 changed files with 5 additions and 74 deletions

View file

@ -216,7 +216,6 @@ public abstract class Entity
// item). Written only by RecordReveal (an act enters) and EndReveals (an end the observer perceives, or a departed
// subject's entries aged past BearingMemory), cleared by Deactivate (ruling reveal-ends-on-perception).
private readonly Dictionary<Entity, Dictionary<EquippedItem, Reveal>> _reveals = new Dictionary<Entity, Dictionary<EquippedItem, Reveal>>();
private readonly List<EquippedItem> _endedItems = new List<EquippedItem>();
private readonly List<Entity> _agedSubjects = new List<Entity>();
public ReactiveProperty<SecurityLevel> CurrentSecurityLevel = new ReactiveProperty<SecurityLevel>(SecurityLevel.Open);
public ReadOnlyReactiveProperty<bool> PresencePermitted;
@ -915,10 +914,9 @@ public abstract class Entity
// What this observer has seen `subject`'s items do: each item's latest act, and nothing the subject's present
// gear or Durability says (ruling contact-reads-book-aim-reads-live). A reveal ends only on what this observer
// perceives (ruling reveal-ends-on-perception): the destruction of an item of a ship it identifies, the death,
// dock or warp of a subject its instruments could see leave, or, for a subject that has left the zone, the
// freshest entry aging past BearingMemory; Deactivate clears it all. Whether an item can be aimed at now is
// FireControl.IsRevealed's question, not this one.
// perceives (ruling reveal-ends-on-perception): the death, dock or warp of a subject its instruments could see
// leave, or, for a subject that has left the zone, the freshest entry aging past BearingMemory; Deactivate
// clears it all. Whether an item can be aimed at now is FireControl.IsRevealed's question, not this one.
public IEnumerable<Reveal> RevealsOf(Entity subject) =>
_reveals.TryGetValue(subject, out var book) ? book.Values : Enumerable.Empty<Reveal>();
@ -964,16 +962,6 @@ public abstract class Entity
if (perSignature < 0f) perSignature = PassiveInfo(other.Position.xz, 1f);
if (signature.Strength * perSignature > threshold) RecordReveal(other, signature.Item, signature.Channel);
}
// The observer resolves this ship's gear (ContactOf's own Identified gate), so it perceives an item of it
// destroyed. Ending after recording: a destroyed item's lingering flash cannot re-enter the book.
if (EntityInfoGathered.TryGetValue(other, out var info) && info >= ItemManager.GameplaySettings.TargetGearInfoThreshold
&& _reveals.TryGetValue(other, out var book))
{
_endedItems.Clear();
foreach (var item in book.Keys)
if (item.EquippableItem.Durability < .01f) _endedItems.Add(item);
for (var j = 0; j < _endedItems.Count; j++) EndReveals(other, _endedItems[j]);
}
}
// A subject that left the zone (death, dock, warp, any removal) is perceived gone under the light-act gate: as
// bright as its non-Emission signature was at removal, from where it was. A re-admitted subject did not leave.

View file

@ -687,9 +687,7 @@ public sealed class ContactRevealTests : IDisposable
// Ruling contact-reads-book-aim-reads-live: what an observer knows of a ship comes from its own book. A gun
// destroyed by a third party out of its sight changes none of its levels and stays in its reveal list, while
// the aiming read, which asks about the item now, refuses the dead gun and still serves the live one. No Sense
// runs after the destruction here (the aiming read needs info 1, at which a Sense would perceive it); the
// unseen case with a Sense is AnIdentifiedObserverSeesGearDestroyed's below-threshold case.
// the aiming read, which asks about the item now, refuses the dead gun and still serves the live one.
[Fact]
public void GearDestroyedOutOfSightLeavesWhatTheObserverKnowsAlone()
{
@ -721,58 +719,6 @@ public sealed class ContactRevealTests : IDisposable
Assert.True(o.ContactOf(e).Classified);
}
// Ruling reveal-ends-on-perception: an observer that resolves a ship's gear (its track at TargetGearInfoThreshold)
// perceives an item of it destroyed, and the reveal of that item ends in the same Sense pass. One that does not
// resolve it perceives nothing.
[Theory]
[InlineData(.8f, 1, 1, 1)]
[InlineData(.8f, 2, 0, 0)]
[InlineData(.79f, 1, 2, 1)]
[InlineData(.79f, 2, 2, 1)]
public void AnIdentifiedObserverSeesGearDestroyed(float info, int destroyed, int remaining, int classifiedKeys)
{
var e = Add(float3(0, 0, 100), new[] { "Gun", "Gun" });
var o = Add(float3(0, 0, 0), new[] { "Eye" });
var guns = Guns(e);
Fire(e, guns[0]);
Fire(e, guns[1]);
o.VisibleEntities.Add(e); // tracked, so what the track holds is not the question
o.EntityInfoGathered[e] = info;
var sense = SenseOf(o);
for (var i = 0; i < destroyed; i++) guns[i].EquippableItem.Durability = 0f;
sense();
Assert.Equal(remaining, o.RevealsOf(e).Count());
var contact = o.ContactOf(e);
Assert.True(contact.Detected);
Assert.Equal(remaining > 0, contact.Classified);
Assert.Equal(remaining > 0 ? classifiedKeys : 0, RevealBookSubjects(o));
if (remaining == 1 && info >= .8f) Assert.Same(guns[1], o.RevealsOf(e).Single().Item);
}
// The same through a real tick: a gun destroyed while its flash still shows, seen by an observer that identifies
// the ship, is not in the book after the tick.
[Fact]
public void ADestroyedGunStillFlashingIsNotRecordedByAnIdentifyingObserver()
{
var e = Add(float3(0, 0, 60), new[] { "Gun", "Gun" });
var o = Add(float3(0, 0, 0), new[] { "Eye" });
var guns = Guns(e);
Fire(e, guns[0]);
Fire(e, guns[1]);
e.SetSignature(new object(), SignatureChannel.Reflection, 1000f, guns[0]); // the dying gun's glare outlasts the tick
guns[0].EquippableItem.Durability = 0f;
o.EntityInfoGathered[e] = 1f; // o's sensor keeps it at about 1 across the tick (decay .5 x dt)
Tick(.01f);
Assert.True(Track(o, e) >= .8f, "fixture: o still identifies e");
Assert.DoesNotContain(o.RevealsOf(e), r => r.Item == guns[0]);
Assert.Contains(o.RevealsOf(e), r => r.Item == guns[1]);
}
// A paint inboxed in the tick its emitter leaves the zone is published the next tick, and is no longer the act of
// a subject in the zone: it warns for its tick and neither creates nor refreshes a reveal. The reveal the
// locker's earlier paint made stays what it was, as a departure out of sight (below the observer's gate) leaves it.
@ -961,10 +907,7 @@ public sealed class ContactRevealTests : IDisposable
var sense = SenseOf(o);
for (var i = 0; i < 10; i++) sense();
Assert.Equal(19, _zone.Entities.Where(x => x != o).Count(x => o.RevealsOf(x).Any())); // fixture: every ship is revealed
// One of them is identified by o (its destroyed gun is ended in the pass), and one has left, quiet, so its
// entries await the prune and its departure sits in the zone's list.
var identified = gunItems.Select(g => _zone.Entities.Single(x => x.Equipment.Contains(g))).First();
o.EntityInfoGathered[identified] = 1f;
// One of them has left, quiet, so its entries await the prune and its departure sits in the zone's list.
var departed = _zone.Entities.Single(x => x.Equipment.Contains(gunItems[18]));
foreach (var source in departed.VisibilitySources.Keys.ToArray()) departed.SetSignature(source, SignatureChannel.Reflection, 0f, gunItems[18]);
_zone.Entities.Remove(departed);