From 6fcfeb0fc0b99cf1eadd49b1633dae68f3b53037 Mon Sep 17 00:00:00 2001 From: Dennis Westermann Date: Tue, 18 Aug 2026 14:13:30 +0200 Subject: [PATCH] feat(construction): jedes eigene fertige Gebaeude wird Bauanker (D-108, 21.1b) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Statt der Rollenliste HQ/Lager/Kraftwerk (D-104) ist ab jetzt jedes eigene, lebende, FERTIGGESTELLTE Gebaeude ein Bauanker und schiebt die Bauzone um seinen eigenen Radius weiter. Baustellen niemals — sie tragen seit 16.3 zwar ihre Definitionsrolle, sind aber ueber IsActiveSite ausdruecklich ausgenommen. Bewusst in Kauf genommen (D-108): eine Kette billiger Gebaeude kann die Zone ueber die Karte schieben. Diese Kopplung von Expansion an die Wirtschaft ist das entschiedene Verhalten, kein Fehler. Nachweis 726/726 gruen (Baseline vorher 725/725). Der neue Test wurde gegen die alte Regel gegengeprueft und schlaegt dort fehl (Applied vs. RejectedInvalidTarget), pinnt also die Regelaenderung und nichts Beliebiges. Entgegen der Erwartung im Auftrag bewegt sich weder der gepinnte KI-Ausgang noch RulesHash64 — siehe Bericht; ein Baseline-PR ist nicht noetig. Umsetzung durch den Kimi-K3-Worker. Co-Authored-By: Claude Opus 5 --- .../Simulation/ConstructionSystemTests.cs | 30 +++++++++++++++++++ .../Construction/ConstructionSystem.cs | 27 ++++++++++++----- .../ConstructionSystemTests.cs | 30 +++++++++++++++++++ 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/Assets/Tests/EditMode/Simulation/ConstructionSystemTests.cs b/Assets/Tests/EditMode/Simulation/ConstructionSystemTests.cs index 259fca8..40f38eb 100644 --- a/Assets/Tests/EditMode/Simulation/ConstructionSystemTests.cs +++ b/Assets/Tests/EditMode/Simulation/ConstructionSystemTests.cs @@ -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() { diff --git a/Assets/_Project/Scripts/Simulation/Construction/ConstructionSystem.cs b/Assets/_Project/Scripts/Simulation/Construction/ConstructionSystem.cs index 5bc54dc..044eaf5 100644 --- a/Assets/_Project/Scripts/Simulation/Construction/ConstructionSystem.cs +++ b/Assets/_Project/Scripts/Simulation/Construction/ConstructionSystem.cs @@ -176,12 +176,12 @@ public sealed class ConstructionSystem : IStatefulSimSystem /// /// 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. /// public const int BuildInfluenceRadiusCells = 8; @@ -1103,16 +1103,27 @@ private bool FootprintIsWalkable(int originX, int originY) return true; } + /// + /// Corrected D-108 anchor rule: EVERY own, living and completed + /// building extends the build zone by its own + /// — 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 , 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. + /// 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) { diff --git a/tools/Nova.SimRunner.Tests/ConstructionSystemTests.cs b/tools/Nova.SimRunner.Tests/ConstructionSystemTests.cs index bdba8b6..326f231 100644 --- a/tools/Nova.SimRunner.Tests/ConstructionSystemTests.cs +++ b/tools/Nova.SimRunner.Tests/ConstructionSystemTests.cs @@ -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() {