From 2b98aaa762d4026c8a870ad577a542cd1396f9af Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 19 Sep 2026 07:37:52 +0200 Subject: [PATCH] Delete HardpointTransforms; mount direction and arc become sim data Entity.HardpointTransforms was written only by Unity (EntityInstance.cs), so the simulation's notion of where a gun points was a readback from a renderer that never runs headless -- Combat.cs threw KeyNotFoundException on the first combat tick outside Unity. Deletes the field, its writer, and the readback branch in Behavior.Direction, leaving the item-rotation path (the same convention reaction thrusters use, R6) as the only source of mount direction. Adds FireControl (ServerShared), a static class owning whether a weapon bears on a point: MountDirection (planar, from ItemRotation), ArcFor (the per-hardpoint HardpointData.FiringArc override, falling back to the new GameplaySettings.FiringArc default of 120), and InArc (the planar dot-cone test, R7 -- target height never enters it, and an arc of 360+ passes unconditionally so a turret hardpoint needs no new type, just a number). Combat.cs and TurretController.cs now gate AI fire through InArc instead of the old hardcoded 0.99 dot-product test against a live Unity transform. Settings.cs drops AgentFiringMinDot, which was declared and read by nothing. docs/fire-control-cut.md, Cut 1. Co-Authored-By: Claude Opus 5 --- Assets/Scripts/Gameplay/EntityInstance.cs | 5 --- .../ServerShared/Agents/States/Combat.cs | 4 +- .../ServerShared/Behaviors/Behaviors.cs | 12 +----- .../Behaviors/TurretController.cs | 4 +- Assets/Scripts/ServerShared/Entity.cs | 6 +-- Assets/Scripts/ServerShared/FireControl.cs | 43 +++++++++++++++++++ .../Scripts/ServerShared/FireControl.cs.meta | 11 +++++ Assets/Scripts/ServerShared/ItemData.cs | 3 ++ Assets/Scripts/ServerShared/Settings.cs | 2 +- 9 files changed, 64 insertions(+), 26 deletions(-) create mode 100644 Assets/Scripts/ServerShared/FireControl.cs create mode 100644 Assets/Scripts/ServerShared/FireControl.cs.meta diff --git a/Assets/Scripts/Gameplay/EntityInstance.cs b/Assets/Scripts/Gameplay/EntityInstance.cs index 296457912..e07ba3135 100644 --- a/Assets/Scripts/Gameplay/EntityInstance.cs +++ b/Assets/Scripts/Gameplay/EntityInstance.cs @@ -511,11 +511,6 @@ public class EntityInstance : MonoBehaviour x.Value.material.SetFloat("_Emission", Entity.ItemManager.GameplaySettings.TemperatureEmissionCurve.Evaluate(x.Key.RadiatorTemperature)); } - foreach (var x in Barrels) - { - Entity.HardpointTransforms[x.Key] = (x.Value[0].position.ToCultMath(), x.Value[0].forward.ToCultMath()); - } - LookAtPoint.position = transform.position + Entity.LookDirection.ToUnity() * (Entity.Target.Value != null ? max(Entity.TargetRange,Entity.ItemManager.GameplaySettings.ConvergenceMinimumDistance) : 10000); LocalSpace.localPosition = transform.position = Entity.Position.ToUnity(); diff --git a/Assets/Scripts/ServerShared/Agents/States/Combat.cs b/Assets/Scripts/ServerShared/Agents/States/Combat.cs index f63d49553..6feeed610 100644 --- a/Assets/Scripts/ServerShared/Agents/States/Combat.cs +++ b/Assets/Scripts/ServerShared/Agents/States/Combat.cs @@ -97,9 +97,7 @@ public class CombatState : BaseState toTarget = normalize(predictedPosition - _agent.Ship.Position); } - var shouldFire = dot( - _agent.Ship.HardpointTransforms[_agent.Ship.Hardpoints[testWeapon.Item.Position.x, testWeapon.Item.Position.y]].direction, - toTarget) > .99f; + var shouldFire = FireControl.InArc(testWeapon.Item, toTarget); foreach (var weapon in _agent.Ship.WeaponGroups[selectedGroup].weapons) { if (shouldFire) diff --git a/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs b/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs index dd72122f4..7e44c96e0 100644 --- a/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs +++ b/Assets/Scripts/ServerShared/Behaviors/Behaviors.cs @@ -27,16 +27,8 @@ public abstract class Behavior { if(Item != null) { - var hardpoint = Entity.Hardpoints[Item.Position.x, Item.Position.y]; - if (hardpoint != null && Entity.HardpointTransforms.ContainsKey(hardpoint)) - { - return normalize(Entity.HardpointTransforms[hardpoint].direction); - } - else - { - var itemDirection = Entity.Direction.Rotate(Item.EquippableItem.Rotation); - return float3(itemDirection.x, 0, itemDirection.y); - } + var itemDirection = Entity.Direction.Rotate(Item.EquippableItem.Rotation); + return float3(itemDirection.x, 0, itemDirection.y); } return float3(Entity.Direction.x, 0, Entity.Direction.y); diff --git a/Assets/Scripts/ServerShared/Behaviors/TurretController.cs b/Assets/Scripts/ServerShared/Behaviors/TurretController.cs index 6e39f94e0..6b67cc26b 100644 --- a/Assets/Scripts/ServerShared/Behaviors/TurretController.cs +++ b/Assets/Scripts/ServerShared/Behaviors/TurretController.cs @@ -78,9 +78,7 @@ public class TurretController : Behavior, IInitializableBehavior foreach (var x in _weapons) { var data = x.Data as WeaponData; - var fire = dot( - x.Direction, - Entity.LookDirection) > .99f; + var fire = FireControl.InArc(x.Item, Entity.LookDirection); if (x.Evaluate(data.Range) > dist && fire) { x.Activate(); diff --git a/Assets/Scripts/ServerShared/Entity.cs b/Assets/Scripts/ServerShared/Entity.cs index 2dcf90601..585c5e534 100644 --- a/Assets/Scripts/ServerShared/Entity.cs +++ b/Assets/Scripts/ServerShared/Entity.cs @@ -54,10 +54,8 @@ public abstract class Entity public readonly Dictionary Messages = new Dictionary(); public readonly Dictionary VisibilitySources = new Dictionary(); - public readonly ReactiveDictionary EntityInfoGathered = new ReactiveDictionary(); - public readonly Dictionary HardpointTransforms = - new Dictionary(); - + public readonly ReactiveDictionary EntityInfoGathered = new ReactiveDictionary(); + public (List weapons, List items)[] WeaponGroups; public List PopulationAssignments = new List(); diff --git a/Assets/Scripts/ServerShared/FireControl.cs b/Assets/Scripts/ServerShared/FireControl.cs new file mode 100644 index 000000000..a19bf5838 --- /dev/null +++ b/Assets/Scripts/ServerShared/FireControl.cs @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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 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 +// 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. +public static class FireControl +{ + // The planar unit vector a hardpoint's weapon points along, derived solely from the item's authored + // rotation (R6, the same convention reaction thrusters already use) -- never from a live Unity + // transform. This is the item-rotation path Behavior.Direction now uses unconditionally, after the + // HardpointTransforms readback branch is deleted. + public static float3 MountDirection(EquippedItem item) + { + var itemDirection = item.Entity.Direction.Rotate(item.EquippableItem.Rotation); + return normalize(float3(itemDirection.x, 0, itemDirection.y)); + } + + // Full arc width in degrees: the per-hardpoint override when authored (nonzero), else + // GameplaySettings.FiringArc. Zero on the hardpoint means "use the default," not "no arc." + public static float ArcFor(EquippedItem item) + { + var hardpoint = item.Entity.Hardpoints[item.Position.x, item.Position.y]; + var arc = hardpoint?.FiringArc ?? 0f; + return arc > 0 ? arc : item.ItemManager.GameplaySettings.FiringArc; + } + + // Planar bearing test (R7: the simulation is 2D, the 3D is set dressing -- target height never enters + // this). An arc of 360 degrees or more passes unconditionally, which is how a turret hardpoint + // (authored FiringArc: 360, Q1) tracks all the way around without a second type. + public static bool InArc(EquippedItem weapon, float3 toTarget) + { + var arc = ArcFor(weapon); + if (arc >= 360f) return true; + var planarTarget = normalize(float3(toTarget.x, 0, toTarget.z)); + return dot(MountDirection(weapon), planarTarget) >= cos(radians(arc / 2f)); + } +} diff --git a/Assets/Scripts/ServerShared/FireControl.cs.meta b/Assets/Scripts/ServerShared/FireControl.cs.meta new file mode 100644 index 000000000..0c35816a2 --- /dev/null +++ b/Assets/Scripts/ServerShared/FireControl.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e84f481f2a2e9fdc572e40e239eacc92 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/ServerShared/ItemData.cs b/Assets/Scripts/ServerShared/ItemData.cs index f40179acc..5e82ea6d7 100644 --- a/Assets/Scripts/ServerShared/ItemData.cs +++ b/Assets/Scripts/ServerShared/ItemData.cs @@ -543,6 +543,9 @@ public class HardpointData [Inspectable, JsonProperty("transform"), Key(3)] public string Transform; [Inspectable, JsonProperty("rotation"), Key(4)] public ItemRotation Rotation; [Inspectable, JsonProperty("armor"), Key(5)] public float Armor; + // Full arc width in degrees a weapon on this hardpoint may bear across; 0 means "use + // GameplaySettings.FiringArc" (docs/fire-control-cut.md R6). Turrets author 360 (Q1). + [Inspectable, JsonProperty("firingArc"), Key(6)] public float FiringArc; public override string ToString() { diff --git a/Assets/Scripts/ServerShared/Settings.cs b/Assets/Scripts/ServerShared/Settings.cs index ff5980109..41126bc79 100644 --- a/Assets/Scripts/ServerShared/Settings.cs +++ b/Assets/Scripts/ServerShared/Settings.cs @@ -212,10 +212,10 @@ public class GameplaySettings public float TargetArmorInfoThreshold; public float TargetGearInfoThreshold; public float ConvergenceMinimumDistance; + public float FiringArc = 120; public float AgentRangeExponent = .25f; public float AgentForwardLerp = .5f; public float AgentMaxForwardDistance = 50; - public float AgentFiringMinDot = .99f; } [Serializable, MessagePackObject(keyAsPropertyName: true), JsonObject]