Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions Assets/Tests/EditMode/Gameplay/ArtViewScaleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using NUnit.Framework;
using UnityEngine;
using Nova.Gameplay.Match;
using Nova.Simulation.Definitions;
using Nova.Simulation.State;

namespace Nova.Gameplay.Tests
{
/// <summary>
/// Map size of art models (EditMode lane, D-113): buildings fill their
/// footprint, units keep their authored proportions under one metric
/// factor, small units get a readable minimum height.
/// </summary>
[TestFixture]
public class ArtViewScaleTests
{
[Test]
public void Building_ShrinksToFootprintWidth()
{
float f = ArtViewScale.Factor(UnitRole.Barracks, new Vector3(9f, 8f, 6f));
Assert.That(9f * f, Is.EqualTo(SimDefinitions.BuildingFootprintCells).Within(1e-4f));
}

[Test]
public void Building_SmallerThanFootprint_KeepsAuthoredSize()
{
Assert.That(ArtViewScale.Factor(UnitRole.Storage, new Vector3(2f, 2f, 2f)), Is.EqualTo(1f));
}

[Test]
public void Vehicles_ShareOneFactor_SoTheirRatiosSurvive()
{
float tank = ArtViewScale.Factor(UnitRole.BattleTank, new Vector3(7.5f, 6.5f, 7.5f));
float scout = ArtViewScale.Factor(UnitRole.ScoutVehicle, new Vector3(2.8f, 4f, 4.5f));
Assert.That(tank, Is.EqualTo(scout));
Assert.That(7.5f * tank, Is.LessThan(SimDefinitions.BuildingFootprintCells * 0.65f),
"a battle tank must read clearly smaller than a building");
}

[Test]
public void Infantry_GetsTheMinimumHeight_IndependentOfWeaponWidth()
{
float rifle = ArtViewScale.Factor(UnitRole.BasicInfantry, new Vector3(0.8f, 1.8f, 0.9f));
float wideLauncher = ArtViewScale.Factor(UnitRole.BasicInfantry, new Vector3(1.5f, 1.8f, 0.9f));
Assert.That(1.8f * rifle, Is.EqualTo(ArtViewScale.MinViewHeight(UnitRole.BasicInfantry)).Within(1e-4f));
Assert.That(wideLauncher, Is.EqualTo(rifle), "width must not shrink a soldier");
}

[Test]
public void Infantry_StaysShorterThanABattleTank()
{
float tankHeight = 6.5f * ArtViewScale.Factor(UnitRole.BattleTank, new Vector3(7.5f, 6.5f, 7.5f));
float soldier = 1.8f * ArtViewScale.Factor(UnitRole.BasicInfantry, new Vector3(0.8f, 1.8f, 0.9f));
Assert.That(soldier, Is.LessThan(tankHeight * 0.7f));
}

[Test]
public void TryFactor_ResolvesRoleNames_AndRejectsUnknownOnes()
{
Assert.That(ArtViewScale.TryFactor("LightTank", Vector3.one, out float f), Is.True);
Assert.That(f, Is.EqualTo(ArtViewScale.UnitCellsPerMetre));
Assert.That(ArtViewScale.TryFactor("Zeppelin", Vector3.one, out _), Is.False);
}
}
}
11 changes: 11 additions & 0 deletions Assets/Tests/EditMode/Gameplay/ArtViewScaleTests.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

139 changes: 139 additions & 0 deletions Assets/Tests/EditMode/Gameplay/CommandCardPresenterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,145 @@ public void TryFindRepairBuilder_NoOwnBuilder_ReturnsFalse()
Assert.IsFalse(CommandCardPresenter.TryFindRepairBuilder(null, playerSlot: 0, out _));
}

// ----------------------------------------------------------------
// Unit command bar: per-unit-type presence and harvester readout (D-117)
// ----------------------------------------------------------------

[Test]
public void GetBarCommands_Harvester_HidesCombatOrdersAndFormations()
{
var presenter = new CommandCardPresenter();

BarCommand bar = presenter.GetBarCommands(FactionId.Alliance, new[] { UnitRole.Harvester, UnitRole.Harvester });

Assert.IsTrue(bar.HasFlag(BarCommand.Move));
Assert.IsTrue(bar.HasFlag(BarCommand.Stop));
Assert.IsTrue(bar.HasFlag(BarCommand.Harvest));
Assert.IsTrue(bar.HasFlag(BarCommand.ReturnCargo));
foreach (BarCommand hidden in new[]
{
BarCommand.AttackMove, BarCommand.Guard, BarCommand.Patrol,
BarCommand.AttackTarget, BarCommand.Scatter, BarCommand.Formations, BarCommand.Repair,
})
{
Assert.IsFalse(bar.HasFlag(hidden), $"{hidden} must be hidden for harvesters");
}
}

[Test]
public void GetBarCommands_Builder_OffersRepairButNoCombatOrders()
{
var presenter = new CommandCardPresenter();

BarCommand bar = presenter.GetBarCommands(FactionId.Legion, new[] { UnitRole.Builder });

Assert.IsTrue(bar.HasFlag(BarCommand.Repair));
Assert.IsFalse(bar.HasFlag(BarCommand.Patrol));
Assert.IsFalse(bar.HasFlag(BarCommand.Formations));
Assert.IsFalse(bar.HasFlag(BarCommand.Harvest));
}

[Test]
public void GetBarCommands_ArmedUnits_OfferCombatOrdersAndFormationsButNoRoleActions()
{
var presenter = new CommandCardPresenter();

BarCommand bar = presenter.GetBarCommands(FactionId.Alliance, new[] { UnitRole.LightTank, UnitRole.BasicInfantry });

foreach (BarCommand shown in new[]
{
BarCommand.Move, BarCommand.Stop, BarCommand.AttackMove, BarCommand.Guard, BarCommand.Patrol,
BarCommand.AttackTarget, BarCommand.Scatter, BarCommand.Formations,
})
{
Assert.IsTrue(bar.HasFlag(shown), $"{shown} must be shown for armed units");
}
Assert.IsFalse(bar.HasFlag(BarCommand.Harvest));
Assert.IsFalse(bar.HasFlag(BarCommand.ReturnCargo));
Assert.IsFalse(bar.HasFlag(BarCommand.Repair));
}

[Test]
public void GetBarCommands_MixedSelection_CombatGroupIsUnionRoleActionsAreIntersection()
{
var presenter = new CommandCardPresenter();

BarCommand bar = presenter.GetBarCommands(FactionId.Alliance, new[] { UnitRole.Harvester, UnitRole.LightTank });

Assert.IsTrue(bar.HasFlag(BarCommand.Guard), "one armed unit is enough for the combat group");
Assert.IsTrue(bar.HasFlag(BarCommand.Formations));
Assert.IsFalse(bar.HasFlag(BarCommand.Harvest), "Harvest would be a lie over the tank");
Assert.IsFalse(bar.HasFlag(BarCommand.ReturnCargo));
}

[Test]
public void GetBarCommands_NoMobileRole_ReturnsNone()
{
var presenter = new CommandCardPresenter();

Assert.AreEqual(BarCommand.None, presenter.GetBarCommands(FactionId.Alliance, new[] { UnitRole.HQ }));
Assert.AreEqual(BarCommand.None, presenter.GetBarCommands(FactionId.Alliance, new UnitRole[0]));
}

[Test]
public void IsArmedRole_FollowsTheDefinitionRow()
{
Assert.IsTrue(CommandCardPresenter.IsArmedRole(FactionId.Alliance, UnitRole.LightTank));
Assert.IsFalse(CommandCardPresenter.IsArmedRole(FactionId.Alliance, UnitRole.Harvester));
Assert.IsFalse(CommandCardPresenter.IsArmedRole(FactionId.Alliance, UnitRole.Builder));
Assert.IsFalse(CommandCardPresenter.IsArmedRole(FactionId.Alliance, UnitRole.HQ));
}

[Test]
public void GetHarvesterStatus_DerivesFromOrdersAndMovement()
{
var u = new UnitState();
Assert.AreEqual(HarvesterStatus.Idle, CommandCardPresenter.GetHarvesterStatus(in u));

u.HarvestFieldId = 3;
u.IsMoving = true;
Assert.AreEqual(HarvesterStatus.DrivingToField, CommandCardPresenter.GetHarvesterStatus(in u));
u.IsMoving = false;
Assert.AreEqual(HarvesterStatus.Harvesting, CommandCardPresenter.GetHarvesterStatus(in u));

u.IsReturningCargo = true;
u.IsMoving = true;
Assert.AreEqual(HarvesterStatus.DrivingToRefinery, CommandCardPresenter.GetHarvesterStatus(in u));
u.IsMoving = false;
Assert.AreEqual(HarvesterStatus.WaitingAtRefinery, CommandCardPresenter.GetHarvesterStatus(in u));

Assert.AreEqual("Fährt zum Feld", CommandCardPresenter.FormatHarvesterStatus(HarvesterStatus.DrivingToField));
Assert.AreEqual("Sammelt", CommandCardPresenter.FormatHarvesterStatus(HarvesterStatus.Harvesting));
Assert.AreEqual("Fährt zur Raffinerie", CommandCardPresenter.FormatHarvesterStatus(HarvesterStatus.DrivingToRefinery));
Assert.AreEqual("Wartet an der Raffinerie", CommandCardPresenter.FormatHarvesterStatus(HarvesterStatus.WaitingAtRefinery));
Assert.AreEqual("Untätig", CommandCardPresenter.FormatHarvesterStatus(HarvesterStatus.Idle));
}

[Test]
public void SummarizeHarvesters_SumsCargoCapacityAndStatus_SkippingOtherRoles()
{
var entities = new EntityManager(8);
EntityId a = entities.SpawnUnit(0, new Transform2D(SimFixed.FromInt(1), SimFixed.FromInt(1)), SimFixed.FromInt(3), role: UnitRole.Harvester);
EntityId b = entities.SpawnUnit(0, new Transform2D(SimFixed.FromInt(2), SimFixed.FromInt(2)), SimFixed.FromInt(3), role: UnitRole.Harvester);
EntityId tank = entities.SpawnUnit(0, new Transform2D(SimFixed.FromInt(3), SimFixed.FromInt(3)), SimFixed.FromInt(3), role: UnitRole.LightTank);
entities.GetUnitRef(a).CargoAE = 210;
entities.GetUnitRef(a).HarvestFieldId = 1;
entities.GetUnitRef(b).CargoAE = 100;
int cap = SimDefinitions.HarvesterCargoCapacityAE(FactionId.Legion);
var presenter = new CommandCardPresenter();

HarvesterSummary sum = presenter.SummarizeHarvesters(FactionId.Legion, new[] { a, b, tank }, entities);

Assert.AreEqual(2, sum.Count);
Assert.AreEqual(310, sum.CargoAE);
Assert.AreEqual(2 * cap, sum.CapacityAE);
Assert.AreEqual(1, sum.Harvesting);
Assert.AreEqual(1, sum.Idle);
Assert.AreEqual(310f / (2 * cap), sum.Fill, 1e-5f);
Assert.AreEqual($"Ladung 310 / {2 * cap}", CommandCardPresenter.FormatCargo(sum.CargoAE, sum.CapacityAE));
Assert.AreEqual("1× Sammelt · 1× Untätig", CommandCardPresenter.FormatHarvesterStatusLine(in sum));
}

// ----------------------------------------------------------------
// Display names (canonical GDD name tables)
// ----------------------------------------------------------------
Expand Down
15 changes: 15 additions & 0 deletions Assets/Tests/EditMode/Gameplay/FactionTintTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,20 @@ public void ApplyToPropertyBlock_SetsBothShaderColourProperties()
Assert.That(block.GetColor("_Color"), Is.EqualTo(tint),
"built-in RP reads _Color");
}

[Test]
public void PrefabBaseColor_PreservesHighAlbedoAndDistinguishesFactions()
{
Color alliance = FactionTint.PrefabBaseColor(FactionId.Alliance);
Color legion = FactionTint.PrefabBaseColor(FactionId.Legion);

Assert.That(alliance, Is.Not.EqualTo(legion));
Assert.That(alliance.r, Is.GreaterThan(0.85f), "3D prefab tint must not crush red channel");
Assert.That(alliance.g, Is.GreaterThan(0.85f), "3D prefab tint must not crush green channel");
Assert.That(alliance.b, Is.GreaterThan(0.85f), "3D prefab tint must not crush blue channel");
Assert.That(legion.r, Is.GreaterThan(0.85f), "3D prefab tint must not crush red channel");
Assert.That(legion.g, Is.GreaterThan(0.85f), "3D prefab tint must not crush green channel");
Assert.That(legion.b, Is.GreaterThan(0.85f), "3D prefab tint must not crush blue channel");
}
}
}
127 changes: 127 additions & 0 deletions Assets/Tests/EditMode/Gameplay/FormationPlannerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using Nova.Gameplay;
using EntityId = Nova.Core.EntityId;

namespace Nova.Gameplay.Tests
{
/// <summary>Client-side formation slots (D-117): shape, facing, spacing and crossing-free assignment.</summary>
[TestFixture]
public class FormationPlannerTests
{
private static readonly Vector2 MapMin = Vector2.zero;
private static readonly Vector2 MapMax = new Vector2(127f, 127f);

private static List<FormationMember> Group(int n, Vector2 at, float radius = 0.3f)
{
var list = new List<FormationMember>();
for (int i = 0; i < n; i++)
{
list.Add(new FormationMember(new EntityId(i, 1), at + new Vector2((i % 4) * 1.2f, (i / 4) * 1.2f), radius));
}
return list;
}

[Test]
public void Loose_AndSingleUnits_LeaveTheSpreadToTheSimulation()
{
var slots = new List<Vector2>();
Assert.That(FormationPlanner.Plan(FormationKind.Loose, Group(6, new Vector2(10, 10)), new Vector2(40, 40), MapMin, MapMax, slots), Is.False);
Assert.That(FormationPlanner.Plan(FormationKind.Line, Group(1, new Vector2(10, 10)), new Vector2(40, 40), MapMin, MapMax, slots), Is.False);
}

[TestCase(FormationKind.Line)]
[TestCase(FormationKind.Column)]
[TestCase(FormationKind.Wedge)]
[TestCase(FormationKind.Block)]
public void EverySlot_KeepsAtLeastTheContactSpacing(FormationKind kind)
{
var members = Group(9, new Vector2(10, 10), 0.6f);
var slots = new List<Vector2>();
Assert.That(FormationPlanner.Plan(kind, members, new Vector2(40, 30), MapMin, MapMax, slots), Is.True);
Assert.That(slots.Count, Is.EqualTo(members.Count));
for (int a = 0; a < slots.Count; a++)
{
for (int b = a + 1; b < slots.Count; b++)
{
Assert.That((slots[a] - slots[b]).magnitude, Is.GreaterThanOrEqualTo(2f * 0.6f * 0.84f),
$"{kind}: slots {a} and {b} overlap");
}
}
}

[Test]
public void Line_StandsAcrossTheDirectionOfTravel()
{
var members = Group(5, new Vector2(10, 10));
var slots = new List<Vector2>();
Vector2 centroid = Vector2.zero;
foreach (FormationMember m in members) centroid += m.Position;
centroid /= members.Count;
Vector2 target = centroid + new Vector2(0f, 30f); // travel due north
FormationPlanner.Plan(FormationKind.Line, members, target, MapMin, MapMax, slots);
foreach (Vector2 s in slots)
{
Assert.That(s.y, Is.EqualTo(target.y).Within(0.01f), "a line facing north lies east-west");
}
}

[Test]
public void Column_RunsAlongTheDirectionOfTravel()
{
var members = Group(6, new Vector2(10, 10));
var slots = new List<Vector2>();
FormationPlanner.Plan(FormationKind.Column, members, new Vector2(40f, 11f), MapMin, MapMax, slots);
float minX = float.MaxValue, maxX = float.MinValue, minY = float.MaxValue, maxY = float.MinValue;
foreach (Vector2 s in slots)
{
minX = Mathf.Min(minX, s.x); maxX = Mathf.Max(maxX, s.x);
minY = Mathf.Min(minY, s.y); maxY = Mathf.Max(maxY, s.y);
}
Assert.That(maxX - minX, Is.GreaterThan(maxY - minY), "a column heading east is longer east-west");
}

[Test]
public void Wedge_PutsOneUnitAtTheTip()
{
var members = Group(5, new Vector2(10, 10));
var slots = new List<Vector2>();
FormationPlanner.Plan(FormationKind.Wedge, members, new Vector2(10.6f, 40f), MapMin, MapMax, slots);
float best = float.MinValue;
int tips = 0;
foreach (Vector2 s in slots) best = Mathf.Max(best, s.y);
foreach (Vector2 s in slots) if (Mathf.Abs(s.y - best) < 0.01f) tips++;
Assert.That(tips, Is.EqualTo(1));
}

[Test]
public void Assignment_DoesNotCrossLeftAndRight()
{
// Three units side by side moving north: the westmost keeps the west slot.
var members = new List<FormationMember>
{
new FormationMember(new EntityId(0, 1), new Vector2(20, 10), 0.3f),
new FormationMember(new EntityId(1, 1), new Vector2(10, 10), 0.3f),
new FormationMember(new EntityId(2, 1), new Vector2(15, 10), 0.3f),
};
var slots = new List<Vector2>();
FormationPlanner.Plan(FormationKind.Line, members, new Vector2(15, 40), MapMin, MapMax, slots);
Assert.That(slots[1].x, Is.LessThan(slots[2].x));
Assert.That(slots[2].x, Is.LessThan(slots[0].x));
}

[Test]
public void Slots_StayInsideTheMap()
{
var members = Group(12, new Vector2(3, 3));
var slots = new List<Vector2>();
FormationPlanner.Plan(FormationKind.Line, members, new Vector2(0.5f, 0.5f), MapMin, MapMax, slots);
foreach (Vector2 s in slots)
{
Assert.That(s.x, Is.InRange(0f, 127f));
Assert.That(s.y, Is.InRange(0f, 127f));
}
}
}
}
11 changes: 11 additions & 0 deletions Assets/Tests/EditMode/Gameplay/FormationPlannerTests.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading