Skip to content
Merged
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
30 changes: 30 additions & 0 deletions Assets/Tests/EditMode/Simulation/ConstructionSystemTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,36 @@ public void ValidatePlacement_InfluenceUsesOwnLivingCompletedFootprints_AtDistan
"an active site supplies spacing, never construction influence");
}

[Test]
public void ValidatePlacement_EveryCompletedBuildingExtendsTheZone_ItsSiteDoesNot()
{
// Corrected D-108: the anchor list is open. A completed Barracks —
// a role the old HQ/Storage/Power list excluded — pushes the zone
// outward by its own radius, so the probe at footprint distance 6
// is placeable even though no HQ/Storage/Power anchor reaches it.
var completed = new Fixture(configure: e => e.TryAddField(1, new GridPos2D(60, 60), 9000));
Assert.That(completed.Construction.PlaceCompletedBuilding(0, 3, 0, 0).IsValid, Is.True,
"far-away HQ prerequisite; its own radius never reaches the probe area");
Assert.That(completed.Construction.PlaceCompletedBuilding(0, 7, 30, 30).IsValid, Is.True,
"completed Barracks outside every old-list anchor radius");
Assert.That(completed.Construction.ValidatePlacement(0, 5, 38, 30), Is.EqualTo(CommandResultCode.Applied),
"footprint distance 6 to the completed Barracks is inside influence (corrected D-108)");

// The SAME building as an active site does not: create the site
// next to a bootstrap anchor, remove the anchor, and the probe at
// the identical distance must fall out of the zone again.
var siteOnly = new Fixture(configure: e => e.TryAddField(1, new GridPos2D(60, 60), 9000));
Assert.That(siteOnly.Construction.PlaceCompletedBuilding(0, 3, 0, 0).IsValid, Is.True, "HQ prerequisite");
Assert.That(siteOnly.Construction.PlaceCompletedBuilding(0, 5, 0, 4).IsValid, Is.True, "Power prerequisite");
EntityId bootstrap = siteOnly.Construction.PlaceCompletedBuilding(0, 3, 24, 30);
Assert.That(bootstrap.IsValid, Is.True, "bootstrap anchor so the Barracks site validates at all");
siteOnly.Step(1); // commit the balance (the Barracks power draw is evaluated)
Assert.That(siteOnly.Construction.TryPlaceBuilding(0, 7, 30, 30), Is.True, "create the active Barracks site");
Assert.That(siteOnly.Entities.DespawnUnit(bootstrap), Is.True, "remove the bootstrap anchor");
Assert.That(siteOnly.Construction.ValidatePlacement(0, 5, 38, 30), Is.EqualTo(CommandResultCode.RejectedInvalidTarget),
"the Barracks SITE supplies spacing, never construction influence");
}

[Test]
public void ValidatePlacement_RequiresOneEmptyRingAroundBuildingsAndSites()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,12 @@ public sealed class ConstructionSystem : IStatefulSimSystem

/// <summary>
/// Maximum footprint-aware Chebyshev distance from an own construction
/// anchor (D-104). Construction anchors are exactly the own, living and
/// COMPLETED HQ, Storage and Power buildings — every other role does NOT
/// extend the build zone (see IsInsideBuildInfluence). The corrected
/// D-108 opens this anchor list to every own completed building; that
/// rule change is a separate PR (RulesHash64 moves) and is NOT yet
/// reflected here.
/// anchor (D-104). Under the corrected D-108 every own, living and
/// COMPLETED building is an anchor and pushes the build zone outward
/// by its own radius — construction sites never do (see
/// IsInsideBuildInfluence). Deliberately accepted (D-108): a chain of
/// cheap buildings can push the zone across the map; expansion is
/// meant to be coupled to the economy.
/// </summary>
public const int BuildInfluenceRadiusCells = 8;

Expand Down Expand Up @@ -1103,16 +1103,27 @@ private bool FootprintIsWalkable(int originX, int originY)
return true;
}

/// <summary>
/// Corrected D-108 anchor rule: EVERY own, living and completed
/// building extends the build zone by its own
/// <see cref="BuildInfluenceRadiusCells"/> — no role list. A
/// construction site is NOT an anchor even though it already carries
/// its definition role (16.3, #44), so the finished-only rule is
/// checked explicitly through <see cref="IsActiveSite"/>, the same
/// register the economy's power and capacity scans use. Deliberately
/// accepted (D-108): a chain of cheap buildings can push the build
/// zone across the map — that coupling of expansion to the economy
/// is the decided behavior, not a defect.
/// </summary>
private bool IsInsideBuildInfluence(byte playerSlot, int originX, int originY)
{
for (int i = 0; i < MaxBuildings; i++)
{
ref readonly PlacementState placement = ref _buildings[i];
if (!placement.IsActive) continue;
if (!SimDefinitions.TryGetBuilding(placement.BuildingDefId, out SimBuildingDefinition def)) continue;
if (def.Role != UnitRole.HQ && def.Role != UnitRole.Storage && def.Role != UnitRole.Power) continue;

EntityId id = UnitCommandStateView.ToEntityId(placement.RawEntityId);
if (IsActiveSite(id)) continue; // finished only: a site never extends the zone
if (!_entityManager.TryGetUnit(id, out UnitState unit) || unit.PlayerId != playerSlot) continue;
if (FootprintDistance(originX, originY, placement.OriginX, placement.OriginY) <= BuildInfluenceRadiusCells)
{
Expand Down
30 changes: 30 additions & 0 deletions tools/Nova.SimRunner.Tests/ConstructionSystemTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,36 @@ public void ValidatePlacement_InfluenceUsesOwnLivingCompletedFootprints_AtDistan
"an active site supplies spacing, never construction influence");
}

[Test]
public void ValidatePlacement_EveryCompletedBuildingExtendsTheZone_ItsSiteDoesNot()
{
// Corrected D-108: the anchor list is open. A completed Barracks —
// a role the old HQ/Storage/Power list excluded — pushes the zone
// outward by its own radius, so the probe at footprint distance 6
// is placeable even though no HQ/Storage/Power anchor reaches it.
var completed = new Fixture(configure: e => e.TryAddField(1, new GridPos2D(60, 60), 9000));
Assert.That(completed.Construction.PlaceCompletedBuilding(0, 3, 0, 0).IsValid, Is.True,
"far-away HQ prerequisite; its own radius never reaches the probe area");
Assert.That(completed.Construction.PlaceCompletedBuilding(0, 7, 30, 30).IsValid, Is.True,
"completed Barracks outside every old-list anchor radius");
Assert.That(completed.Construction.ValidatePlacement(0, 5, 38, 30), Is.EqualTo(CommandResultCode.Applied),
"footprint distance 6 to the completed Barracks is inside influence (corrected D-108)");

// The SAME building as an active site does not: create the site
// next to a bootstrap anchor, remove the anchor, and the probe at
// the identical distance must fall out of the zone again.
var siteOnly = new Fixture(configure: e => e.TryAddField(1, new GridPos2D(60, 60), 9000));
Assert.That(siteOnly.Construction.PlaceCompletedBuilding(0, 3, 0, 0).IsValid, Is.True, "HQ prerequisite");
Assert.That(siteOnly.Construction.PlaceCompletedBuilding(0, 5, 0, 4).IsValid, Is.True, "Power prerequisite");
EntityId bootstrap = siteOnly.Construction.PlaceCompletedBuilding(0, 3, 24, 30);
Assert.That(bootstrap.IsValid, Is.True, "bootstrap anchor so the Barracks site validates at all");
siteOnly.Step(1); // commit the balance (the Barracks power draw is evaluated)
Assert.That(siteOnly.Construction.TryPlaceBuilding(0, 7, 30, 30), Is.True, "create the active Barracks site");
Assert.That(siteOnly.Entities.DespawnUnit(bootstrap), Is.True, "remove the bootstrap anchor");
Assert.That(siteOnly.Construction.ValidatePlacement(0, 5, 38, 30), Is.EqualTo(CommandResultCode.RejectedInvalidTarget),
"the Barracks SITE supplies spacing, never construction influence");
}

[Test]
public void ValidatePlacement_RequiresOneEmptyRingAroundBuildingsAndSites()
{
Expand Down
Loading