One aim slot holds an item or a mount; the cycle covers every located mount

Entity.AimedAt (AimTarget: a revealed item or a believed mount) replaces
TargetItem. Two writers, TrySelectTargetItem (IsRevealed) and TrySelectMount
(in LayoutOf, not seen destroyed); ResolvedAim re-validates both on every read.
AimCycle is SchematicRows' enemy rows minus mounts in the observer's
seen-destroyed record, written only in Sense through PerceivesDestroyed.
SchematicRows drops its covering filter. IsLocated and AimsAtMount are deleted;
PendingShot.AimedMount and Silhouette's HardpointData mount replace the bool.
The cycle key calls Entity.CycleAim; the schematic marks read ResolvedAim.
Entity.ToString names an entity (follow-up entity-formats-safely).

The slot is AimedAt, not Aim: Entity.Aim is the pilot's pointing direction.
Not yet built or run; ActionGameManager and SchematicDisplay are uncompiled
without Unity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-15 04:42:38 +02:00
commit f6c0d24dba
13 changed files with 449 additions and 162 deletions

View file

@ -157,24 +157,10 @@ 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
// stats (Combat's target-item choice, its dps) asks this; the selection gate is IsLocated.
XX
public static bool IsRevealed(Entity observer, EquippedItem item) =>
!item.EquippableItem.IsDestroyed && item.Entity.Equipment.Contains(item) && observer.HasReveal(item.Entity, item);
// The selection gate (ruling quiet-gear-identifiable-hull-layout-known): `observer` can aim at `item` when it has seen
// the item act (IsRevealed's question) or the item sits on a mount of the hull the observer believes its ship to be
// (Entity.LayoutOf, by reference: the believed hull's HardpointData are the true item's only while the belief is the
// truth, so under a hull lie no gear is located and the ship stays targetable, ruling ew-aim-under-hull-lie-ruled).
// Locating reads no class or stats. A Tool item sits on no mount, so only an act locates it.
public static bool IsLocated(Entity observer, EquippedItem item) =>
!item.EquippableItem.IsDestroyed && item.Entity.Equipment.Contains(item) &&
(observer.HasReveal(item.Entity, item) || item.Hardpoint != null && observer.LayoutOf(item.Entity).Contains(item.Hardpoint));
// Whether the aim at `aimed` is at its mount (located, not revealed): the aim point is then the mount's cells, not
// the cells of an item the observer has not learned the size of.
public static bool AimsAtMount(Entity observer, EquippedItem aimed) =>
aimed != null && aimed.Hardpoint != null && !IsRevealed(observer, aimed);
// Cut 2, Q4: the stats an entity actually fires with. A working targeting system (equipped, online --
// not destroyed, not shut down) supplies its own resolved stats; anything else falls back to the
// authored unaided figures, which make aiming without one a last resort rather than an alternative.
@ -352,7 +338,8 @@ public static class FireControl
Silhouette sil;
try
{
sil = Silhouette(target, targetHull, source.ResolvedTargetItem, bearing, precision, buffer, AimsAtMount(source, source.ResolvedTargetItem));
var aim = source.ResolvedAim;
sil = Silhouette(target, targetHull, aim.Item, bearing, precision, buffer, aim.Mount);
var settings = source.ItemManager.GameplaySettings;
pSpread = PSpread(weapon.Spread, sil.Span, range, settings.SchematicCellSize);
}
@ -731,14 +718,15 @@ public static class FireControl
var commitHorizon = source.ItemManager.GameplaySettings.CommitHorizon;
var aim = source.ResolvedAim;
var shot = new PendingShot
{
ShotId = zone.NextShotId(),
Source = source,
Target = engaged,
Weapon = item,
Aimed = source.ResolvedTargetItem,
AimAtMount = AimsAtMount(source, source.ResolvedTargetItem),
Aimed = aim.Item,
AimedMount = aim.Mount,
Damage = damageOverride ?? weapon.Damage,
FireMinRange = weapon.MinRange,
FireEffectiveRange = weapon.EffectiveRange,
@ -911,7 +899,7 @@ public static class FireControl
var bearing = Bearing(shot.Target, shot.TravelDirection);
var hullData = shot.Target.ItemManager.GetData(shot.Target.Hull) as HullData;
sil = Silhouette(shot.Target, hullData, shot.Aimed, bearing, shot.Precision, null, shot.AimAtMount);
sil = Silhouette(shot.Target, hullData, shot.Aimed, bearing, shot.Precision, null, shot.AimedMount);
var settings = shot.Target.ItemManager.GameplaySettings;
return p * PSpread(shot.Spread, sil.Span, shot.FireRange, settings.SchematicCellSize) * sil.POnHull;
}
@ -1778,13 +1766,13 @@ public static class FireControl
private const float SigmaFloor = 0.5f;
private static float Sigma(float precision) => max(1f / max(precision, 1e-3f), SigmaFloor);
// The aim point a silhouette is centred on (R10/Q6: the same frozen Aimed both HitProbability and Fire
// read) -- the aimed item's own cell centroid when it currently occupies cells on the target (its mount's cells
// when the aim was frozen at the mount, atMount: the shooter has not learned what is fitted there), else the
// hull's own centre of mass. One path; the uniform-random branch Cut 6d replaced stays deleted.
private static float2 ResolveAimPoint(Entity target, HullData hullData, EquippedItem aimed, bool atMount)
// The aim point a silhouette is centred on (R10/Q6: the same frozen Aimed and AimedMount both HitProbability and Fire
// read) -- the cells of the mount aimed at when the aim was frozen at a mount (the shooter has not learned what is
// fitted there, so never the fitted item's cells), else the aimed item's own cell centroid when it currently occupies
// cells on the target, else the hull's own centre of mass. One path; the uniform-random branch Cut 6d replaced stays deleted.
private static float2 ResolveAimPoint(Entity target, HullData hullData, EquippedItem aimed, HardpointData mount)
{
var aimedCells = aimed == null ? null : atMount && aimed.Hardpoint != null ? MountCells(aimed.Hardpoint) : CellsOf(target, aimed);
var aimedCells = mount != null ? MountCells(mount) : aimed == null ? null : CellsOf(target, aimed);
if (aimedCells != null && aimedCells.Length > 0)
return aimedCells.Aggregate(float2.zero, (total, c) => total + (float2) c) / aimedCells.Length;
return hullData.Shape.CenterOfMass;
@ -1804,7 +1792,7 @@ public static class FireControl
// call that follows, so it still passes null and gets an owned allocation. A caller-supplied buffer may be
// larger than coords.Length (ArrayPool rents "at least"), so every loop below is bounded by coords.Length,
// never buffer.Length.
public static Silhouette Silhouette(Entity target, HullData hull, EquippedItem aimed, float2 bearing, float precision, Interval[] buffer = null, bool atMount = false)
public static Silhouette Silhouette(Entity target, HullData hull, EquippedItem aimed, float2 bearing, float precision, Interval[] buffer = null, HardpointData mount = null)
{
var ell = Lateral(bearing);
var coords = hull.Shape.Coordinates;
@ -1826,7 +1814,7 @@ public static class FireControl
}
var sigma = Sigma(precision);
var aimPoint = ResolveAimPoint(target, hull, aimed, atMount);
var aimPoint = ResolveAimPoint(target, hull, aimed, mount);
var a = dot(aimPoint, ell);
var pOnHull = 0f;
@ -2009,9 +1997,9 @@ public struct PendingShot
public Entity Target;
public EquippedItem Weapon;
public EquippedItem Aimed;
// Whether the aim was at Aimed's mount rather than the item (FireControl.AimsAtMount at Fire), frozen with it: a
// reveal between Fire and Commit does not move where this shot was aimed.
public bool AimAtMount;
// The mount the aim was at, when it was at a mount rather than an item (Entity.ResolvedAim at Fire), frozen with Aimed:
// a reveal between Fire and Commit does not move where this shot was aimed. Null for an item aim or none.
public HardpointData AimedMount;
// The frozen payload (R10, Q6): the gun's own stats at the instant the trigger was pulled. Nothing
// downstream re-evaluates any of these.