Skip to content

fix(construction): sites carry the definition role, not Unit (#44, Sprint 16.3) - #71

Merged
cubetribe merged 2 commits into
mainfrom
feat/16-3-site-role
Aug 10, 2026
Merged

fix(construction): sites carry the definition role, not Unit (#44, Sprint 16.3)#71
cubetribe merged 2 commits into
mainfrom
feat/16-3-site-role

Conversation

@cubetribe

@cubetribe cubetribe commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Was

Sprint 16.3 / Issue #44 stellt Baustellen auf ihre Definitionsrolle um. Eine
unfertige Baustelle trägt damit z. B. bereits Refinery, HQ oder
DefensePlatform; fertig ist sie weiterhin erst, wenn das Baustellenregister
keinen aktiven Site-Eintrag mehr enthält.

Die Rollenänderung ist an allen betroffenen Lesern geschlossen:

  • ConstructionSystem bindet den Site-Lookup in die Wirtschaft und normalisiert
    beim Abschluss weiterhin die Rolle (kompatibel zu älteren Mid-Build-Snapshots).
  • EconomySystem zählt Sites weder in der Strombilanz noch als
    Raffinerie-Abladepunkt.
  • CombatSystem erhält ConstructionSystem als Pflichtabhängigkeit und schließt
    aktive Sites als Angreifer und Ziel aus. Auch eine unfertige
    Verteidigungsplattform feuert nicht.
  • SkirmishAiSystem wertet Sites vor Gebäuderollen aus und ignoriert sie bei
    Zielwahl und Bedrohungsanalyse; das kanonische 10.000-Tick-Szenario wählt nur
    fertige Produzenten.
  • Siegprüfung, View-/Prefab-Auflösung, Baustellenmarker, Builder-Warnung,
    Produzentenwahl, Rally- und Reparaturvorprüfung nutzen das Site-/Placement-
    Register statt einer Rollenannahme.

D-105-Integrationshinweis

Die beim Main-Merge sichtbar gewordenen AI-/Combat-/UI-Folgen sind eine
gebundene Integrationsreparatur nach D-105: minimaler Diff, im Sprintvertrag
Version 1.1.0 dokumentiert, unabhängiger Read-only-Review durchgeführt. Die
dauerhafte Stranghoheit ändert sich dadurch nicht.

Nachweis

  • dotnet test tools/Nova.SimRunner.Tests/Nova.SimRunner.Tests.csproj -c Release
    663/663 grün
  • Unity 6000.5.4f1, vollständige EditMode-Suite — 547/547 grün
  • python3 .github/scripts/check_docs.py181 Markdown-Dateien und 5
    Quality-JSONs grün
  • git diff --check — grün
  • keine geschützte Determinismus-Baseline geändert; AiBehaviorId bleibt r6

Manuelle Spielsicht

Nicht gespielt / nach D-105 ausdrücklich zurückgestellt. Grund: Der Kollege
steht für die Runde derzeit nicht zur Verfügung. Ersatznachweis sind die
vollständigen .NET- und Unity-Suiten einschließlich gespiegelter Bau-, Kampf-,
Wirtschafts- und Siegtests. Restrisiko: Baustellenoptik, Marker/Builder-Warnung
und das sichtbare Ausbleiben des Schusses wurden nicht in einer laufenden Runde
beobachtet. Dieser PR ist damit integrierbar, aber nicht spielerisch
abgenommen und kein Meilenstein-Nachweis
.

Changelog

Ein Eintrag unter [Unreleased]: „#44: Baustellen schiessen nicht mehr“.

@cubetribe

Copy link
Copy Markdown
Collaborator Author

Maintainer-Befund zu den vier roten Tests. Es sind zwei verschiedene Ursachen, und keine davon ist eine verschobene Baseline.

Das ist wichtig, weil #70 die Baselines als Erklärung nennt („Golden-Byte-Baselines bewegen sich … die Baseline-Neusetzung kommt als eigener PR"). Würden diese vier Tests über eine Baseline-Neusetzung grün gemacht, friert das eine echte Regression ein — genau das, was die Regel „Verhalten und Baseline nie im selben Zug" verhindern soll.


Ursache A — der eigene neue Test ist falsch (behebbar im Netzstrang)

Site_CarriesDefinitionRole_ButDrawsAndProvidesNoPower_UntilCompletion scheitert bereits an der Platzierung, nicht an der Strombilanz:

f.SpawnBuilder(0, 19, 20);                                              // nur ein Bauarbeiter
f.Step(1);                                                              // kein HQ, kein Kraftwerk
Assert.That(f.Construction.TryPlaceBuilding(0, 4, 20, 20), Is.True);    // ← Expected True, But was False

Definition 4 ist die Raffinerie mit powerRequired: 20. ConstructionSystem.ValidatePlacement lehnt ab:

if (def.PowerRequired > 0 && eco.PowerProvided - eco.PowerRequired < def.PowerRequired)

0 - 0 = 0 < 20 → abgelehnt. Der Nachbartest PowerSite_ProvidesNothing_UntilCompletion läuft durch, weil Definition 5 (Kraftwerk) powerRequired: 0 trägt.

Behebung: ein HQ in die Fixture (das liefert 30), dann greift die Energieprüfung nicht mehr. Die Implementierung ist an dieser Stelle in Ordnung — der Test prüft nur etwas, das er sich selbst unmöglich gemacht hat.


Ursache B — eine Regression in der KI (nicht im Netzstrang behebbar)

Die anderen drei Fehler (ShortRun_CommandStream_CarriesBothSlotsAndTheOpeningLoop, AiBehaviorId_TracksWhatTheAiActuallyDoes, SkirmishAi_AimsARetreatingUnitAtItsPursuer) haben eine gemeinsame Wurzel.

SkirmishAiSystem erkennt Baustellen an der Rolle:

if (SimDefinitions.IsBuildingRole(u.Role)) {          // Zeile 228
    case UnitRole.Refinery when refineryRaw == 0: ...
    case UnitRole.Barracks  when barracksRaw == 0: ...
    case UnitRole.Power:     powerCompleted = true;
    continue;                                          // ← und hier ist Schluss
}
switch (u.Role) {
    case UnitRole.Unit:                                // Zeile 266
        if (_construction.TryGetSite(raw, ...))        // ← SO fand die KI ihre Baustellen
            sites.Add(...);                            //    Zeile 271

Vor 16.3 trug eine Baustelle UnitRole.Unit und landete in Zeile 266. Seit 16.3 trägt sie ihre Definitionsrolle und wird oben abgefangen. Drei Folgen:

  1. sites bleibt leer. Die Schleife ab Zeile 319, die den zugewiesenen Bauarbeiter in Chebyshev-Reichweite der Baustelle schickt, läuft nie. Ohne Bauarbeiter in Reichweite pausiert die Baustelle — es wird nie etwas fertig.
  2. Die Bauleiter zählt falsch. Eine Raffinerie im Bau setzt refineryRaw, eine Kaserne im Bau barracksRaw. Die KI hält die Leiter für abgearbeitet.
  3. powerCompleted = true durch ein Kraftwerk im Bau.

Daher fehlt QueueUnit im Eröffnungsloop: Es entsteht nie eine fertige Kaserne, aus der produziert werden könnte. Erwartet wird {PlaceBuilding, QueueUnit, Harvest}, gemessen {Move, PlaceBuilding, Harvest} — das passt genau zu diesem Bild.

Die Behebung ist klein, liegt aber in fremdem Terrain. Im Zensus muss zuerst das Baustellen-Register gefragt werden, dann die Rolle. _construction ist in SkirmishAiSystem bereits injiziert (Zeile 135/166), und dieser PR macht ConstructionSystem.IsActiveSite öffentlich — der Baustein ist also da. Aber Assets/_Project/Scripts/AI/SkirmishAiSystem.cs gehört exklusiv dem Einheitenstrang. Vom Netzstrang wird das nicht gebaut.


Was daraus folgt

Dieser PR kann erst gemergt werden, wenn der KI-Zensus nachgezogen ist. Das ist ein dritter Punkt für die Ansage in #70 — und der einzige, der 16.3 blockiert, während der DefensePlatform-Restbefund dort auch später behandelt werden kann.

Reihenfolge: Zensus in 13B nachziehen → dann #71 → dann, falls dann noch nötig, die Baseline-Neusetzung als eigener PR. Nicht umgekehrt.

@cubetribe

Copy link
Copy Markdown
Collaborator Author

Korrektur zur Baseline-Erwartung im PR-Text: Die vier roten Tests sind keine Golden-Byte-Verschiebungen, sondern eine KI-Regression aus der Rollenänderung (Diagnose vom Inhaber, am Code bestätigt):

SkirmishAiSystem.cs:266 erkennt Baustellen über case UnitRole.Unit: → mit def.Role bleibt sites leer, die KI schickt den Bauarbeiter nie zur Baustelle, nichts wird fertig, QueueUnit fehlt im Stream. Die Stelle gehört dem Einheitenstrang — Befund und Fix-Skizze liegen in Issue #70 (Kommentar vom 2026-08-09).

Damit steht dieser PR: Sim- und View-Teile wie beschrieben, KI-Regression bekannt und bei 13B eingeordnet. Baseline-Neusetzung bleibt zusätzlich nötig (Baustellen serialisieren die Definitionsrolle), aber sie ist nicht die Ursache der roten Tests.

Sprint 16.3: SpawnBuildingEntity assigns def.Role to unfinished sites.
The generic UnitRole.Unit slot is armed by the weapon-table fallback
(15 damage) and D-087 auto-acquisition fires it — every site shot.
Unarmed building roles carry AttackDamage 0, so the fallback dies
without a line in Combat/.

The three readers that resolved a site by its generic role are
compensated at the source:

- EconomySystem.RecomputePower skips sites through a bound IsActiveSite
  lookup (Func<EntityId,bool>, wired once by the ConstructionSystem
  constructor so no host can forget it; null-tolerant for
  construction-free rigs). A site neither provides nor draws power —
  otherwise a Power site would power up its own grid mid-build.
- UnitViewManager maps sites back to the effective view role
  UnitRole.Unit: the site keeps its low pad and no art prefab until
  completion, and the rebind trigger now follows the site-register flip
  (the role no longer changes at completion).
- VictorySystem excludes sites from the HQ scan: a half-built HQ would
  otherwise mask the D-077 last-HQ elimination. Building classification
  itself is untouched (sites counted as buildings before, via the site
  table).

SelectionManager.CopyMobileSelection now drops sites from mobile
command dispatch (they read as buildings) — intended: sites take no
move orders; selection itself is unchanged (player-id only), and the
command card resolves sites before the role check.

Known remainder, announced to the units track before this PR: a
DefensePlatform SITE stays armed (20 damage from the building
definition). A site filter in auto-acquisition lives in
Simulation/Combat/ and belongs to the units track.

Expected: golden-byte baselines move (site entities serialize the
definition role now). Baseline reset lands in a SEPARATE PR per the
standing rule and needs an SDK-8 environment.
@cubetribe
cubetribe force-pushed the feat/16-3-site-role branch from ff6f307 to 5c90d72 Compare August 9, 2026 17:54
cubetribe added a commit that referenced this pull request Aug 9, 2026
Sprint 16.4, D-024/D-096. The account gains an upper bound DERIVED from
the living building stock on every read - never stored (a stored cap
would be a state field and a format break): a completed HQ provides the
2.000 AE base per HQ, every completed Storage adds 2.000, sites hold
nothing (via the bound site lookup).

- DepositCapped(playerId, amount) is now the only income path: harvest
  deposits, production cancel, construction cancel and sell refunds all
  clamp at the ceiling - overflow is forfeit ("Ueberschuss verfaellt").
- An existing balance above the ceiling decays by 25% of the excess
  once per second (tick % 10, integer floor, minimum 1 AE). The decay
  IS the D-024 "25% loss on destruction" carried without an event: a
  destroyed or sold storage drops the ceiling and the decay is the
  loss. Stateless and restore-safe by construction.
- The destruction-rule wording is an owner decision (decay over a
  slot-bound one-time loss): the slot of a destroyed storage is not
  reconstructible inside the hard bounds (despawned entity, ownerless
  PlacementState, KillUnit in the units track).

This branch also replicates the 16.3 site-lookup mechanism
(BindSiteLookup / IsActiveSite) VERBATIM from PR #71: the capacity scan
must exclude sites once sites carry their definition role. Identical
hunks at identical locations - whichever PR merges first, the other
merges clean.

Expected: golden-byte baselines move (the Determinism10000 opening
starts 1.000 AE over the HQ ceiling and decays; deposits clamp).
Baseline reset lands in a SEPARATE PR per the standing rule and needs
an SDK-8 environment.
@cubetribe
cubetribe removed the request for review from travelhawk August 10, 2026 06:47
@cubetribe
cubetribe merged commit e4a4cb1 into main Aug 10, 2026
8 checks passed
@cubetribe
cubetribe deleted the feat/16-3-site-role branch August 10, 2026 07:24
cubetribe added a commit that referenced this pull request Aug 10, 2026
…nt 16.4) (#73)

* feat(economy): derived storage ceiling caps the AE account (#53)

Sprint 16.4, D-024/D-096. The account gains an upper bound DERIVED from
the living building stock on every read - never stored (a stored cap
would be a state field and a format break): a completed HQ provides the
2.000 AE base per HQ, every completed Storage adds 2.000, sites hold
nothing (via the bound site lookup).

- DepositCapped(playerId, amount) is now the only income path: harvest
  deposits, production cancel, construction cancel and sell refunds all
  clamp at the ceiling - overflow is forfeit ("Ueberschuss verfaellt").
- An existing balance above the ceiling decays by 25% of the excess
  once per second (tick % 10, integer floor, minimum 1 AE). The decay
  IS the D-024 "25% loss on destruction" carried without an event: a
  destroyed or sold storage drops the ceiling and the decay is the
  loss. Stateless and restore-safe by construction.
- The destruction-rule wording is an owner decision (decay over a
  slot-bound one-time loss): the slot of a destroyed storage is not
  reconstructible inside the hard bounds (despawned entity, ownerless
  PlacementState, KillUnit in the units track).

This branch also replicates the 16.3 site-lookup mechanism
(BindSiteLookup / IsActiveSite) VERBATIM from PR #71: the capacity scan
must exclude sites once sites carry their definition role. Identical
hunks at identical locations - whichever PR merges first, the other
merges clean.

Expected: golden-byte baselines move (the Determinism10000 opening
starts 1.000 AE over the HQ ceiling and decays; deposits clamp).
Baseline reset lands in a SEPARATE PR per the standing rule and needs
an SDK-8 environment.

* test(economy): cover capped deposits through real call paths
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant