Aetheria/tests/Aetheria.Shared.Tests/SmokeHullScenarioTests.cs
Meta Vens 2a943370ab Smoke Test Hull fixes: the home rule reads sold hulls only; command and flank geometry pinned; turret off the starboard line
LoadoutGenerator.SoldHulls is the one definition of the hulls HasHome considers (a hull some product sells); AvailableProducts and the
tests read it, so the product-less test hull gives no gear a home. TheSoldHardpointGearWithNoHomeToday describes production again.
SmokeHullCommandTests pins each added hardpoint's firing arc, armour and shape; ArcsFlanks asserts the flank hulls abeam from the
player's facing within 5 degrees; Arcs' Turret moves to (600,300). Not yet built or run at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:35:25 +02:00

87 lines
5 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
// aetheria-release, cut scenarios-smoke: what the Smoke Test Hull scenarios stage, read from the staged run and the
// shipped catalog's hull.
public sealed partial class RunStartTests
{
private static bool Bears(EquippedItem mount, Entity player, Entity target) => FireControl.InArc(mount, target.Position - player.Position);
private static List<EquippedItem> WeaponMounts(Entity ship) =>
ship.Equipment.Where(item => item.Behaviors.OfType<Weapon>().Any()).ToList();
// Weapon Feel: the player flies the Smoke Test Hull with ChargeBlast SG on the 2x2 Energy hardpoint, Autocannon
// on the 2x2 Ballistic hardpoint and a burst weapon; the bare hull ahead lies in every mount's arc.
[Fact]
public void WeaponFeelConditions()
{
var staged = Stage(Arena(false), new WeaponFeel());
var player = staged.Player;
player.GenerateWeaponGroups();
Assert.Equal("Smoke Test Hull", _items.GetData(player.EquippedHull.EquippableItem).Name);
EquippedItem Mount(string design) => Assert.Single(WeaponMounts(player), item => _items.GetData(item.EquippableItem).Name == design);
var charge = Mount("ChargeBlast SG");
var autocannon = Mount("Autocannon");
Assert.Equal((HardpointType.Energy, 2, 2), (charge.Hardpoint.Type, charge.Hardpoint.Shape.Width, charge.Hardpoint.Shape.Height));
Assert.Equal((HardpointType.Ballistic, 2, 2), (autocannon.Hardpoint.Type, autocannon.Hardpoint.Shape.Width, autocannon.Hardpoint.Shape.Height));
var mounts = WeaponMounts(player);
Assert.Contains(mounts, item => ((WeaponItemData)_items.GetData(item.EquippableItem)).WeaponModifiers.HasFlag(WeaponModifiers.RapidFire));
foreach (var mount in mounts)
{
Assert.Contains(player.WeaponGroups, group => group.items.Contains(mount));
Assert.True(Bears(mount, player, Assert.Single(staged.Entities)), $"{_items.GetData(mount.EquippableItem).Name} cannot bear on the hull ahead");
}
}
// Arcs' flanks: each side mount bears on its own flank's hull and on nothing else, the forward mounts bear on the
// bow and on neither flank, and the two side mounts sit in different weapon groups.
[Fact]
public void ArcsFlanks()
{
var staged = Stage(Arena(false), new Arcs());
var player = staged.Player;
player.GenerateWeaponGroups();
var (bow, stern, port, starboard) = (staged.Entities[0], staged.Entities[1], staged.Entities[3], staged.Entities[4]);
// The flank hulls lie abeam from the player's facing, read from the staged positions: within 5 degrees of the
// perpendicular, starboard on the right of the facing and port on the left.
var facing = player.Direction;
var right = CultMath.math.float2(facing.y, -facing.x);
float Abeam(Entity flank, CultMath.float2 side) => CultMath.math.dot(CultMath.math.normalize(flank.Position.xz - player.Position.xz), side);
var abeam = CultMath.math.cos(CultMath.math.radians(5f));
Assert.True(Abeam(starboard, right) >= abeam, "the starboard hull is not abeam to starboard");
Assert.True(Abeam(port, -right) >= abeam, "the port hull is not abeam to port");
var mounts = WeaponMounts(player);
var forward = mounts.Where(item => item.EquippableItem.Rotation == ItemRotation.None).ToList();
var starboardMounts = mounts.Where(item => item.EquippableItem.Rotation == ItemRotation.Clockwise).ToList();
var portMounts = mounts.Where(item => item.EquippableItem.Rotation == ItemRotation.CounterClockwise).ToList();
Assert.NotEmpty(forward);
Assert.NotEmpty(starboardMounts);
Assert.NotEmpty(portMounts);
foreach (var mount in starboardMounts)
{
Assert.True(Bears(mount, player, starboard), "a starboard mount cannot bear on the starboard hull");
Assert.False(Bears(mount, player, port) || Bears(mount, player, bow) || Bears(mount, player, stern), "a starboard mount bears off its flank");
}
foreach (var mount in portMounts)
{
Assert.True(Bears(mount, player, port), "a port mount cannot bear on the port hull");
Assert.False(Bears(mount, player, starboard) || Bears(mount, player, bow) || Bears(mount, player, stern), "a port mount bears off its flank");
}
foreach (var mount in forward)
{
Assert.True(Bears(mount, player, bow), "a forward mount cannot bear on the bow hull");
Assert.False(Bears(mount, player, port) || Bears(mount, player, starboard), "a forward mount bears on a flank");
}
int GroupOf(EquippedItem mount) => Array.FindIndex(player.WeaponGroups, group => group.items.Contains(mount));
var starboardGroups = starboardMounts.Select(GroupOf).ToList();
var portGroups = portMounts.Select(GroupOf).ToList();
Assert.DoesNotContain(-1, starboardGroups.Concat(portGroups));
Assert.Empty(starboardGroups.Intersect(portGroups));
}
}