AI aim and turret aim ride Entity.RideHeight; tests pin the hull GridOffset on a slope and at both aim sites
Combat and TurretController spelled GetHeight + GridOffset inline; they call target.RideHeight now. The drawn-ship test authors a hull with GridOffset 2.5 on the well's slope. Two new tests aim at a target hull raised 40 u. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
fe5d03d04b
commit
4bac2623bf
4 changed files with 44 additions and 11 deletions
|
|
@ -109,9 +109,8 @@ public class CombatState : BaseState
|
|||
// Cut 3 (docs/fire-control-cut.md): the one prediction function, shared with FireControl.Fire's
|
||||
// own snapshot and the roll's deviation measurement -- two separate predictions here and in the
|
||||
// roll would let an AI aim at one point and get judged for missing a different one.
|
||||
var targetHullData = _agent.ItemManager.GetData(target.Hull) as HullData;
|
||||
var predictedPosition = FireControl.PredictedIntercept(testWeapon, _agent.Ship, target);
|
||||
predictedPosition.y = _agent.Ship.Zone.GetHeight(predictedPosition.xz) + targetHullData.GridOffset;
|
||||
predictedPosition.y = target.RideHeight(predictedPosition.xz);
|
||||
toTarget = normalize(predictedPosition - _agent.Ship.Position);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,13 +64,12 @@ public class TurretController : Behavior, IInitializableBehavior
|
|||
var diff = target.Position - Entity.Position;
|
||||
if (_predictShots)
|
||||
{
|
||||
var targetHullData = Entity.ItemManager.GetData(target.Hull) as HullData;
|
||||
var targetVelocity = float3(target.Velocity.x, 0, target.Velocity.y);
|
||||
var predictedPosition = first_order_intercept(
|
||||
Entity.Position, float3.zero, _shotSpeed,
|
||||
target.Position, targetVelocity
|
||||
);
|
||||
predictedPosition.y = Entity.Zone.GetHeight(predictedPosition.xz) + targetHullData.GridOffset;
|
||||
predictedPosition.y = target.RideHeight(predictedPosition.xz);
|
||||
Entity.Aim = normalize(predictedPosition - Entity.Position);
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -84,12 +84,12 @@ public sealed class FireControlPerWeaponTests : IDisposable
|
|||
(ItemRotation Rotation, float Arc)[] mounts = null, bool beam = false, WeaponFuse? fuse = null, float blast = 0f,
|
||||
float range = 100f, float targetRange = 60f, float accuracy = 1f, float minHit = .2f, bool lockWeapon = false,
|
||||
bool turret = false, float energy = 0f, float heat = 0f, float visibility = 0f, int magazine = 0, bool singleAmmoBurst = false, bool decoy = false, bool charged = false, bool auto = false,
|
||||
float effective = float.PositiveInfinity, float damageAtMax = 0f)
|
||||
float effective = float.PositiveInfinity, float damageAtMax = 0f, float velocity = 0f, float targetGridOffset = 0f)
|
||||
{
|
||||
mounts ??= new[] { (ItemRotation.None, 0f) };
|
||||
var hullData = new HullData
|
||||
{
|
||||
Name = "Hull", HullType = HullType.Ship, Shape = SolidShape(5, 4), Durability = 1000000, Mass = 1000, Armor = 0,
|
||||
Name = "Hull", HullType = HullType.Ship, Shape = SolidShape(5, 4), Durability = 1000000, Mass = 1000, Armor = 0, GridOffset = targetGridOffset,
|
||||
Hardpoints = { new HardpointData { Type = HardpointType.Sensors, Position = new int2(0, 0), Shape = new Shape() } }
|
||||
};
|
||||
var shooterHullData = new HullData
|
||||
|
|
@ -135,7 +135,7 @@ public sealed class FireControlPerWeaponTests : IDisposable
|
|||
behavior.MinRange = Constant(0);
|
||||
behavior.EffectiveRange = Constant(effective);
|
||||
behavior.DamageAtMaxRange = damageAtMax;
|
||||
behavior.Velocity = Constant(0);
|
||||
behavior.Velocity = Constant(velocity);
|
||||
behavior.Spread = Constant(0);
|
||||
behavior.DamageSpread = Constant(0);
|
||||
behavior.Penetration = Constant(0);
|
||||
|
|
@ -308,6 +308,39 @@ public sealed class FireControlPerWeaponTests : IDisposable
|
|||
Assert.True(r.Gun.Weapon.Firing, "the turret must fire a fused weapon at a designated target out of arc");
|
||||
}
|
||||
|
||||
// The AI aims at the height a target rides at (Entity.RideHeight: field plus the hull's GridOffset), not at the
|
||||
// plane. A target hull raised 40 u, a shot fast enough to be predicted: the turret's and Combat's aim point at the
|
||||
// target's stepped pose. Kills: either aim site spelling its own offset (dropped, added to, or scaled).
|
||||
[Fact]
|
||||
public void ATurretAimsAtTheHeightATargetRidesAt()
|
||||
{
|
||||
var r = Build(turret: true, velocity: 500f, targetGridOffset: 40f);
|
||||
r.Zone.Update(1f);
|
||||
|
||||
AssertAimsAtTarget(r);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CombatAimsAtTheHeightATargetRidesAt()
|
||||
{
|
||||
var r = Build(velocity: 500f, targetGridOffset: 40f);
|
||||
r.Zone.Agents.Add(new Minion(r.Shooter));
|
||||
r.Zone.Update(1f);
|
||||
r.Zone.Update(1f);
|
||||
|
||||
AssertAimsAtTarget(r);
|
||||
}
|
||||
|
||||
private static void AssertAimsAtTarget(Rig r)
|
||||
{
|
||||
var toTarget = r.Target.Position - r.Shooter.Position;
|
||||
Assert.True(abs(toTarget.y) > 30f, "fixture: the raised hull is well above the shooter");
|
||||
var expected = normalize(toTarget);
|
||||
Assert.Equal(expected.x, r.Shooter.Aim.x, 3);
|
||||
Assert.Equal(expected.y, r.Shooter.Aim.y, 3);
|
||||
Assert.Equal(expected.z, r.Shooter.Aim.z, 3);
|
||||
}
|
||||
|
||||
public enum Withheld { NoTarget, OutOfRange, NotVisible, Unlocked }
|
||||
|
||||
// The AI never fires a fused weapon at nothing: a missing target, one out of range, one not visible and one
|
||||
|
|
|
|||
|
|
@ -45,13 +45,13 @@ public sealed class FireControlTests : IDisposable
|
|||
|
||||
// `well` digs the zone one well at the origin (WellFixture), so heights differ across the zone.
|
||||
private (ItemManager items, Ship ship, EquippedItem[] weapons) BuildShipWithHardpoints(
|
||||
bool well, (ItemRotation rotation, float firingArc)[] hardpoints)
|
||||
bool well, (ItemRotation rotation, float firingArc)[] hardpoints, float gridOffset = 0f)
|
||||
{
|
||||
var hullShape = new Shape(Math.Max(hardpoints.Length, 3), 3);
|
||||
foreach (var cell in hullShape.AllCoordinates) hullShape[cell] = true;
|
||||
// Mass defaults to 0 (ItemData.cs:285), which zeroes ThermalMass (Entity.cs MapEntity) and NaNs
|
||||
// UpdateTemperature -- author a real mass so the entity's own temperature stays a real number.
|
||||
var hullData = new HullData { Name = "TestHull", HullType = HullType.Ship, Shape = hullShape, Durability = 1, Mass = 1000 };
|
||||
var hullData = new HullData { Name = "TestHull", HullType = HullType.Ship, Shape = hullShape, Durability = 1, Mass = 1000, GridOffset = gridOffset };
|
||||
for (var i = 0; i < hardpoints.Length; i++)
|
||||
hullData.Hardpoints.Add(new HardpointData
|
||||
{
|
||||
|
|
@ -386,7 +386,7 @@ public sealed class FireControlTests : IDisposable
|
|||
[Fact]
|
||||
public void ADrawnShipRidesTheFieldAtItsDrawnPosition()
|
||||
{
|
||||
var (items, ship, _) = BuildShipWithHardpoints(true, new[] { (ItemRotation.None, 0f) });
|
||||
var (items, ship, _) = BuildShipWithHardpoints(true, new[] { (ItemRotation.None, 0f) }, gridOffset: 2.5f);
|
||||
var zone = ship.Zone;
|
||||
ship.Position = float3(200, 0, 0);
|
||||
zone.Update(1f);
|
||||
|
|
@ -396,7 +396,9 @@ public sealed class FireControlTests : IDisposable
|
|||
var drawn = DrawAhead.Position(ship, lead);
|
||||
|
||||
Assert.Equal(ship.Position.x + 100 * lead, drawn.x, 4);
|
||||
Assert.Equal(zone.GetHeight(float2(drawn.x, drawn.z)) + ((HullData) ship.ItemManager.GetData(ship.Hull)).GridOffset, drawn.y, 4);
|
||||
// The hull rides 2.5 u above the field (authored above): dropping or doubling the offset moves the ship by 2.5.
|
||||
Assert.Equal(zone.GetHeight(float2(drawn.x, drawn.z)) + 2.5f, drawn.y, 4);
|
||||
Assert.Equal(zone.GetHeight(float2(ship.Position.x, ship.Position.z)) + 2.5f, ship.Position.y, 4);
|
||||
Assert.True(abs(drawn.y - ship.Position.y) > .1f, "the slope is real: the drawn height differs from the last step's");
|
||||
var still = DrawAhead.Position(ship, 0f);
|
||||
Assert.Equal(ship.Position.x, still.x);
|
||||
|
|
|
|||
Loading…
Reference in a new issue