Add targeting subsystem, reveal rule and selection slot (fire control Cut 2)

TargetingSystem is required interior Tool gear: Accuracy/Resolution/
Precision/Tracking are PerformanceStats reached through the usual
Evaluate path, so lot quality, wear, heat and PowerSupply all apply
(a starved system rolls worse, never switches off). FireControl.Accuracy/
Resolution/Precision fall back to GameplaySettings.UnaidedAccuracy (new,
authored low per Q4) whenever an entity has no targeting system or its
own is destroyed/disabled.

FireControl.IsRevealed derives R5's reveal rule from EntityInfoGathered:
non-hull equipment ranks hardpoint-mounted first, then by size, and
reveals between TargetArmorInfoThreshold and TargetGearInfoThreshold --
the first code to read either setting. Entity.TargetItem/
TrySelectTargetItem/ResolvedTargetItem give both the player and AI one
shared, revoke-on-decay aim-point slot with a single writer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-19 13:02:05 +02:00
commit c4a57a8df0
6 changed files with 191 additions and 2 deletions

View file

@ -2,13 +2,16 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System.Linq;
using CultMath;
using static CultMath.math;
// Cut 1 (docs/fire-control-cut.md): geometry only. Whether a weapon bears on a point is decided here and
// Cut 1 (docs/fire-control-cut.md): geometry -- whether a weapon bears on a point is decided here and
// nowhere else -- no prefab, transform, ArticulationPoint or renderer may influence it (R6, R7). Mount
// direction and arc are both derived per read from catalog data and the item's authored rotation; neither
// is ever stored on the entity.
// Cut 2 adds the reveal rule (R5) and the resolved targeting-system stats an entity fires with, both
// likewise derived on every read and never cached.
public static class FireControl
{
// The planar unit vector a hardpoint's weapon points along, derived solely from the item's authored
@ -40,4 +43,61 @@ public static class FireControl
var planarTarget = normalize(float3(toTarget.x, 0, toTarget.z));
return dot(MountDirection(weapon), planarTarget) >= cos(radians(arc / 2f));
}
// Cut 2, R5: whether `observer` has resolved `item` (which must belong to some entity in the zone) well
// enough to aim at it. Derived fresh from EntityInfoGathered on every call -- nothing caches a reveal
// result, so decay drops it the moment info falls back below tier, with no clearing loop anywhere.
//
// Ranking: the target's own non-hull equipment, hardpoint-mounted gear first (the big obvious things),
// then by size descending, ties broken by equipment order (OrderByDescending is a stable sort, so this
// needs no explicit tie-break key). Item i of N reveals once info crosses
// lerp(TargetArmorInfoThreshold, TargetGearInfoThreshold, i / max(1, N - 1)) -- the first item to reveal
// does so at the armor tier, the last at the gear tier.
public static bool IsRevealed(Entity observer, EquippedItem item)
{
var target = item.Entity;
var ranked = target.Equipment
.Where(e => e.Data.HardpointType != HardpointType.Hull)
.OrderByDescending(e => e.Data.HardpointType != HardpointType.Tool)
.ThenByDescending(e => e.Data.Shape.Coordinates.Length)
.ToList();
var index = ranked.IndexOf(item);
if (index < 0) return false; // the hull itself, or an item that is no longer on this target
var settings = observer.ItemManager.GameplaySettings;
var info = observer.EntityInfoGathered.TryGetValue(target, out var gathered) ? gathered : 0f;
var tier = lerp(settings.TargetArmorInfoThreshold, settings.TargetGearInfoThreshold,
(float) index / max(1, ranked.Count - 1));
return info >= tier;
}
// 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.
// Cut 3's roll is the only planned reader.
public static float Accuracy(Entity entity)
{
var system = entity.GetBehavior<TargetingSystem>();
return system != null && system.Item.Active.Value
? system.Accuracy
: entity.ItemManager.GameplaySettings.UnaidedAccuracy;
}
public static float Resolution(Entity entity)
{
var system = entity.GetBehavior<TargetingSystem>();
return system != null && system.Item.Active.Value ? system.Resolution : 1f;
}
public static float Precision(Entity entity)
{
var system = entity.GetBehavior<TargetingSystem>();
return system != null && system.Item.Active.Value ? system.Precision : 0f;
}
public static float Tracking(Entity entity)
{
var system = entity.GetBehavior<TargetingSystem>();
return system != null && system.Item.Active.Value ? system.Tracking : 0f;
}
}