Pin the mount aim's resolved reads, the record's isolation and its Deactivate clear; delete two dead guards
Forecast and Fire drop a mount aim whose gate closed (destroyed, unidentified); a record for one Bay never hides another's mount; Deactivate clears the record. Deleted: the located-or-booked guard in RecordSeenDestroyed and the Entity == subject check in ResolvedAim (the record is read only for layout mounts; a Target change already clears the slot). Comments name AimedAt. Not built or run yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
43536ccf5f
commit
a54e2f53df
5 changed files with 81 additions and 9 deletions
|
|
@ -459,7 +459,7 @@ public abstract class Entity
|
|||
var subject = Target.Value.Entity;
|
||||
var aim = AimedAt;
|
||||
if (subject == null) return AimTarget.None;
|
||||
if (aim.Item != null) return aim.Item.Entity == subject && FireControl.IsRevealed(this, aim.Item) ? aim : AimTarget.None;
|
||||
if (aim.Item != null) return FireControl.IsRevealed(this, aim.Item) ? aim : AimTarget.None;
|
||||
return MountAimable(subject, aim.Mount) ? aim : AimTarget.None;
|
||||
}
|
||||
}
|
||||
|
|
@ -1055,17 +1055,16 @@ public abstract class Entity
|
|||
if (subject.Parent == null && !Zone.Entities.Contains(subject) && !_reveals.ContainsKey(subject)) _iffOverrides.Remove(subject);
|
||||
}
|
||||
|
||||
// This observer perceives `subject`'s gear (ContactOf's Identified gate): each of its destroyed items on a mount of the
|
||||
// hull this ship believes it to be (LayoutOf, by reference) or in the book leaves its mount in the seen-destroyed
|
||||
// record. The same PerceivesDestroyed Sense ends reveals by, applied to located mounts.
|
||||
// This observer perceives `subject`'s gear (ContactOf's Identified gate): each of its destroyed items leaves its mount
|
||||
// in the seen-destroyed record. The same PerceivesDestroyed Sense ends reveals by, applied to mounts. The record is read
|
||||
// only for mounts of LayoutOf (MountAimable, AimCycle), so a true mount outside the believed layout is never asked for.
|
||||
private void RecordSeenDestroyed(Entity subject)
|
||||
{
|
||||
// Indexed, and the layout read only for an item actually perceived destroyed: Sense runs for every pair of ships every tick.
|
||||
// Indexed: Sense runs for every pair of ships every tick.
|
||||
for (var i = 0; i < subject.Equipment.Count; i++)
|
||||
{
|
||||
var item = subject.Equipment[i];
|
||||
if (item.Hardpoint == null || !PerceivesDestroyed(subject, item)) continue;
|
||||
if (!LayoutOf(subject).Contains(item.Hardpoint) && !(_reveals.TryGetValue(subject, out var book) && book.ContainsKey(item))) continue;
|
||||
if (!_seenDestroyed.TryGetValue(subject, out var seen)) _seenDestroyed[subject] = seen = new HashSet<HardpointData>();
|
||||
seen.Add(item.Hardpoint);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -321,8 +321,8 @@ public static class FireControl
|
|||
// GetBehavior<TargetingSystem> calls (Accuracy, Resolution via PSensor, Precision), at ~40 B each -- each
|
||||
// one walks Entity.Equipment, a ReactiveCollection<T> whose enumerator is heap-allocated per foreach. It is
|
||||
// NOT `Behaviors` (the earlier wording here was wrong): EquippedItem.Behaviors is a plain array, allocates
|
||||
// nothing to enumerate. With an item aimed at, ResolveAimPoint -> CellsOf also allocates (a List, then
|
||||
// ToArray) -- about 944 B per HitProbability, on top of the three GetBehavior calls. None of this is
|
||||
// nothing to enumerate. With an item or a mount aimed at, ResolveAimPoint also allocates: CellsOf (a List, then
|
||||
// ToArray) about 944 B per HitProbability, measured; MountCells (a LINQ Select, then ToArray) not measured. Both are on top of the three GetBehavior calls. None of this is
|
||||
// Silhouette's or this pooling's cost; it predates Cut 12.2 and belongs to Entity's own collection choice
|
||||
// and to CellsOf, out of this cut's scope. Cut 10's own guard is what actually matters
|
||||
// operationally: HitProbability and CommitProbability both bail before Forecast is ever called for the
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ public readonly struct Reveal
|
|||
}
|
||||
}
|
||||
|
||||
// What an observer aims at on its target (Entity.Aim): exactly one of a revealed item or a mount of the hull the observer
|
||||
// What an observer aims at on its target (Entity.AimedAt): exactly one of a revealed item or a mount of the hull the observer
|
||||
// believes the target to be (Entity.LayoutOf), or nothing. A mount is a believed position, never the true item on it.
|
||||
public readonly struct AimTarget
|
||||
{
|
||||
|
|
|
|||
|
|
@ -289,6 +289,40 @@ public sealed partial class ContactRevealTests
|
|||
Assert.Equal(3f, sil.AimPoint.y, 4);
|
||||
}
|
||||
|
||||
// ---- AMountAimThatLostItsGateDropsOutOfForecastAndFire ----
|
||||
|
||||
// The aim slot holds the mount, but what prices and freezes a shot is the aim as it resolves now: a mount the observer has
|
||||
// since seen destroyed, or whose hull it no longer identifies, is no aim. The forecast prices the unaimed hull and the
|
||||
// shot is fired aimed at nothing, though nothing wrote the slot at the moment the gate closed.
|
||||
[Theory]
|
||||
[InlineData("destroyed")]
|
||||
[InlineData("unidentified")]
|
||||
public void AMountAimThatLostItsGateDropsOutOfForecastAndFire(string how)
|
||||
{
|
||||
var (shooter, bay, reactor, weapon, gun) = Gunner();
|
||||
var aimedPrice = FireControl.Inspect(weapon, shooter, bay).POnHull;
|
||||
shooter.TrySelectTargetItem(null);
|
||||
var unaimedPrice = FireControl.Inspect(weapon, shooter, bay).POnHull;
|
||||
Assert.True(Math.Abs(aimedPrice - unaimedPrice) > 1e-3f, "fixture: the mount aim and no aim are priced apart");
|
||||
Assert.True(shooter.TrySelectMount(reactor.Hardpoint), "fixture: the mount is aimed at again");
|
||||
|
||||
if (how == "destroyed")
|
||||
{
|
||||
reactor.EquippableItem.Durability = 0f;
|
||||
SenseOf(shooter)();
|
||||
}
|
||||
else
|
||||
{
|
||||
shooter.EntityInfoGathered[bay] = Identify - .01f;
|
||||
}
|
||||
Assert.True(shooter.ResolvedAim.Mount == null, "fixture: the aim no longer resolves");
|
||||
|
||||
if (how == "destroyed")
|
||||
Assert.Equal(unaimedPrice, FireControl.Inspect(weapon, shooter, bay).POnHull, 4);
|
||||
var shot = FireAt(weapon, gun, shooter);
|
||||
Assert.True(shot.AimedMount == null && shot.Aimed == null, "the shot is aimed at nothing");
|
||||
}
|
||||
|
||||
// ---- TheShotKeepsTheAimItWasFiredWith ----
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -217,4 +217,43 @@ public sealed partial class ContactRevealTests
|
|||
|
||||
Assert.True(SameMounts(honest.HullData.Hardpoints.ToArray(), MountsOf(observer.AimCycle(liar))), "no claimed mount is recorded destroyed");
|
||||
}
|
||||
|
||||
// ---- TheRecordIsPerSubject ----
|
||||
|
||||
// Two ships of one hull share their HardpointData. The reactor seen destroyed on one Bay leaves that Bay's cycle and no
|
||||
// other: the observer's memory is of the ship it saw, not of the mount.
|
||||
[Fact]
|
||||
public void TheRecordIsPerSubject()
|
||||
{
|
||||
var (bayA, observer, reactorA) = Identified();
|
||||
var bayB = AddBay(float3(100, 0, 100));
|
||||
observer.EntityInfoGathered[bayB] = 1f;
|
||||
var reactorB = Fitted(bayB, "BayReactor");
|
||||
Assert.True(ReferenceEquals(reactorA.Hardpoint, reactorB.Hardpoint), "fixture: the two Bays share their mount objects");
|
||||
|
||||
reactorA.EquippableItem.Durability = 0f;
|
||||
SenseOf(observer)();
|
||||
|
||||
Assert.False(MountsOf(observer.AimCycle(bayA)).Any(m => ReferenceEquals(m, reactorA.Hardpoint)), "fixture: seen destroyed on A");
|
||||
Assert.True(MountsOf(observer.AimCycle(bayB)).Any(m => ReferenceEquals(m, reactorB.Hardpoint)), "B's reactor mount stays in B's cycle");
|
||||
Target(observer, bayB);
|
||||
Assert.True(observer.TrySelectMount(reactorB.Hardpoint), "and can be aimed at on B");
|
||||
}
|
||||
|
||||
// ---- DeactivateClearsTheRecord ----
|
||||
|
||||
// A ship that warps out forgets what it saw: its fresh book no longer supports the record, so the mount seen destroyed
|
||||
// is back in the cycle.
|
||||
[Fact]
|
||||
public void DeactivateClearsTheRecord()
|
||||
{
|
||||
var (bay, observer, reactor) = Identified();
|
||||
reactor.EquippableItem.Durability = 0f;
|
||||
SenseOf(observer)();
|
||||
Assert.False(MountsOf(observer.AimCycle(bay)).Any(m => ReferenceEquals(m, reactor.Hardpoint)), "fixture: recorded");
|
||||
|
||||
observer.Deactivate();
|
||||
observer.EntityInfoGathered[bay] = 1f;
|
||||
Assert.True(MountsOf(observer.AimCycle(bay)).Any(m => ReferenceEquals(m, reactor.Hardpoint)), "the record went with the observer's memory");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue