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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,20 @@ die Versionierung folgt (in der aktuellen Doku-Phase) dem Dokumentationsstand de
gespulte Partie ohne dieses Etikett nichts wert ist.

### Geändert
- **Der Ausgangspin der kanonischen KI-Partie ist vom Identitätspin getrennt
(D-101).** `SkirmishAiTests` pinnte Kennung, Entscheidungstick und
Endzustands-Hash in einer Zusicherung. Die beiden Zahlen bewegen sich aber bei
jeder Änderung an der Simulation, in der die KI spielt — im ersten
Wirtschaftssprint riss das jedes Paket, ohne dass eine Zeile KI-Code berührt
war, und die im Test hinterlegte Prozedur schickte den falschen Strang ins
Verhaltensjournal. Entscheidungstick und Endzustand liegen jetzt in
`CanonicalAiOutcomeTests` beim Maintainer-Strang, die Kennung bleibt beim
Einheitenstrang. Die Diagnose bleibt erhalten: der neue Test liest die Kennung
mit und unterscheidet im Fehlertext zwischen KI- und Simulationsänderung. Neu
ist eine Zusicherung, die es vorher nicht gab — eine **unentschiedene** Partie
gilt als Defekt und nicht als verschobener Pin. Dazu bekommt
`tools/Nova.SimRunner.Tests/` erstmals eine Eigentümerzeile in der
Schreibhoheitstabelle: geteilt je Datei, fremde Testdateien nur nach Ansage.
- **Angeschlagene KI-Einheiten drehen ab (Einheitenstrang, KI-Verhalten `r4`):**
Wer die KI angriff, merkte nichts davon — angeschlagene Einheiten kämpften bis
zum letzten Lebenspunkt. Eine Einheit unter 60 % Leben, in deren Nähe (8
Expand Down
71 changes: 70 additions & 1 deletion docs/production/DecisionLog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Decision Log

**Version:** 1.32.0 | **Status:** aktiv (laufend) | **Verantwortungsbereich:** Game Director / Lead Technical Director / Project Owner | **Sprint:** 16
**Version:** 1.33.0 | **Status:** aktiv (laufend) | **Verantwortungsbereich:** Game Director / Lead Technical Director / Project Owner | **Sprint:** 16

## Zweck

Expand Down Expand Up @@ -2924,6 +2924,74 @@ Vertriebsweg und setzt sich später neben `install_hash`, ohne Tabellen,
Sperrarten oder Bedienweg zu ändern. Q-041 bleibt bis zur Öffnung der Beta
offen.

---

### D-101 | verbindlich | Sprint 16 (der Ausgangspin der kanonischen KI-Partie wird vom Identitätspin getrennt)

**Status:** Inhaberentscheidung vom 2026-08-09, umgesetzt im selben Zug.

**Kontext:** `SkirmishAiTests.AiBehaviorId_TracksWhatTheAiActuallyDoes` pinnte
drei Werte in einer Zusicherung: die KI-Kennung `AiBehaviorId.Value`, den
Entscheidungstick der kanonischen Partie und deren Endzustands-Hash. Die
Kopplung war beabsichtigt — der Kommentar begründet sie damit, dass die Kennung
geänderte Zahlen fängt und der Endzustand geänderte Regeln.

Der erste Wirtschaftssprint hat gezeigt, dass die Kopplung an der falschen Naht
liegt: Entscheidungstick und Endzustand bewegen sich bei **jeder** Änderung an
der Simulation, in der die KI spielt. Paket 16.1 verschob den Endzustand, 16.2
zusätzlich den Tick — beide fassen keine Zeile KI-Code an. Die im Test
hinterlegte Prozedur schickte den Netzstrang damit in Arns Verhaltensjournal,
also in das falsche Buch. Der Test liegt zudem in
`tools/Nova.SimRunner.Tests/`, einem Verzeichnis, das in der
Schreibhoheitstabelle gar keinen Eigentümer hatte.

**Alternativen:**

1. **Arn zieht den Pin nach jedem Merge des Netzstrangs nach.** Verworfen: bei
zehn Paketen in Sprint 16 sind das zehn Runden Ping-Pong über einen
Fork-PR-Weg, und jede blockiert einen fertigen Maintainer-PR an einer
Zahl, die niemand aus seinem Strang bewegt hat.
2. **Der Netzstrang aktualisiert die Zahlen selbst, der Test bleibt, wo er
ist.** Verworfen: dann schreibt ein Strang regelmäßig in die Testdatei des
anderen, ohne dass die Tabelle das deckt — genau die Unklarheit, die den
Fall erzeugt hat. Ausserdem bliebe die irreführende Prozedur im Kommentar
stehen.
3. **Den Ausgangspin ersatzlos streichen.** Verworfen: er ist der einzige Test,
der bemerkt, dass die KI die kanonische Partie überhaupt nicht mehr
entscheidet. Genau das ist in Paket 16.3 passiert (Entscheidungstick 0), und
ohne den Pin wäre es niemandem aufgefallen.
4. **ANGENOMMEN: der Pin wird geteilt.** Die Kennung bleibt beim
Einheitenstrang (`SkirmishAiTests.AiBehaviorId_TracksWhichAiThisIs`),
Entscheidungstick und Endzustand ziehen in `CanonicalAiOutcomeTests` um,
das der Netzstrang besitzt.

**Entscheidung:**

1. `CanonicalAiOutcomeTests` (neu, Netzstrang) pinnt Entscheidungstick und
Endzustands-Hash der kanonischen KI-Partie.
2. `SkirmishAiTests` behält die Kennung und deren Prozedur (Revision bumpen,
Journaleintrag) unverändert.
3. Der Ausgangstest **liest die Kennung mit** und macht die Unterscheidung im
Fehlertext: Ausgang bewegt **und** Kennung bewegt heisst KI-Änderung;
Ausgang bewegt und Kennung unverändert heisst Simulationsänderung.
4. **Unentschieden ist ein Defekt, kein verschobener Pin.** Der Ausgangstest
sichert das zuerst und getrennt zu, damit der Fehlertext beide Fälle
auseinanderhält.
5. `tools/Nova.SimRunner.Tests/` bekommt eine Eigentümerzeile: geteilt je
Datei, fremde Testdateien nur nach Ansage. Der KI-Harness (`AiHost`,
`BuildMatch`) wird Vertragsfläche und dafür `internal`.

**Begründung:** Die Kennung beantwortet „welche KI ist das", der Ausgang
beantwortet „hat sich irgendetwas bewegt". Das sind zwei Fragen, und nur die
erste gehört dem Einheitenstrang. Die Diagnose, die die Kopplung leistete, geht
nicht verloren — sie wandert aus der Zusicherung in den Fehlertext, wo sie
ohnehin hingehört: gelesen wird sie erst, wenn der Test rot ist.

**Konsequenzen:** Vier Deklarationen in `SkirmishAiTests` werden `internal`,
die Zusicherung dort schrumpft auf eine Zeile, der erklärende Kommentar
verweist auf die neue Datei. Der Einheitenstrang ist zu informieren (Issue #75).
Wer den Harness umbenennt, sagt es an. Keine Baseline-Datei ist berührt.

## Offene Punkte

- Alle Sprint-4-Review-Befunde (105, davon 9 kritisch): 7 entscheidungsbedürftige kritische Befunde sind durch D-043–D-052 entschieden.
Expand Down Expand Up @@ -3004,6 +3072,7 @@ offen.

| Version | Datum | Änderung | Autor |
|---|---|---|---|
| 1.33.0 | 2026-08-09 | D-101 aufgenommen: der Ausgangspin der kanonischen KI-Partie (Entscheidungstick, Endzustand) wird vom Identitätspin getrennt und zieht in eine Maintainer-Datei; `tools/Nova.SimRunner.Tests/` bekommt erstmals eine Eigentümerzeile | Project Owner / Orchestrator |
| 1.0.0 | 2026-07-21 | D-001 bis D-005 aus Sprint 0 protokolliert | Game Director |
| 1.1.0 | 2026-07-21 | D-006 (Unity 6.3 LTS + URP bestätigt) aus Sprint-1-Validierung | Lead Technical Director |
| 1.2.0 | 2026-07-21 | D-007 bis D-019: verbindliche Game-Design-Grundlagen (Q-001–Q-012, Q-016, Q-017) | Game Director |
Expand Down
7 changes: 5 additions & 2 deletions docs/production/hashkrieg/13-15_Parallelbetrieb.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Parallelbetrieb Sprint 13–18 — zwei Stränge, eine Simulation

**Version:** 1.3.1 | **Status:** verbindlich ab Merge des Sprint-13.0-PR | **Verantwortungsbereich:** Maintainers und Strangverantwortliche | **Sprint:** 13–18 | **Gilt für:** [13](13_Sprint_Netzpartie.md), [13B](13B_Sprint_Einheitenverhalten.md), [14](14_Sprint_Lobby.md), [15](15_Sprint_Netzstabilitaet.md), [16](16_Sprint_Wirtschaft.md), [18](18_Sprint_Befehl_und_Auswahl.md) | **Leitsatz:** getrennte Ordner sind billig, getrennte Determinismus-Zustände nicht
**Version:** 1.4.0 | **Status:** verbindlich ab Merge des Sprint-13.0-PR | **Verantwortungsbereich:** Maintainers und Strangverantwortliche | **Sprint:** 13–18 | **Gilt für:** [13](13_Sprint_Netzpartie.md), [13B](13B_Sprint_Einheitenverhalten.md), [14](14_Sprint_Lobby.md), [15](15_Sprint_Netzstabilitaet.md), [16](16_Sprint_Wirtschaft.md), [18](18_Sprint_Befehl_und_Auswahl.md) | **Leitsatz:** getrennte Ordner sind billig, getrennte Determinismus-Zustände nicht

## Warum es dieses Dokument gibt

Expand Down Expand Up @@ -85,6 +85,7 @@ Freiraum.
| `Scripts/Simulation/Replays/`, `Snapshots/` | **niemand ohne D-ID** | Speicherformat und Fingerprint — Änderung ist eine Inhaberentscheidung |
| `Scripts/Simulation/State/` — **Layout und Serialisierung** | **niemand ohne D-ID** | Feldbestand, Feldreihenfolge, `StateVersion`, Blockformat. Das ist der Teil, der Snapshots und Replays unlesbar macht |
| `Scripts/Simulation/State/` — **Befehlsanwendung** (`UnitCommandStateView`) | **Netzstrang** | mit D-095 aus dem Frost gelöst: *was* ein bestehender `CommandKind` in den Zustand schreibt, ist Verhalten, nicht Format. **Kein neuer `CommandKind`** — das Register bleibt eingefroren |
| `tools/Nova.SimRunner.Tests/` | **geteilt — je Datei** | jeder Strang besitzt die Testdateien zu seinem Gegenstand. Änderungen an einer fremden Testdatei werden **vorher angesagt**. Zwei Pins sind namentlich zugeordnet: `SkirmishAiTests.AiBehaviorId_TracksWhichAiThisIs` gehört dem Einheitenstrang, `CanonicalAiOutcomeTests` dem Netzstrang (D-101) |
| `CHANGELOG.md` | **serialisiert** | ein Eintrag pro PR, Konflikte löst der Mergende |
| `docs/production/hashkrieg/` | **Maintainer** | Planungsstand; Befunde kommen per Mail oder Issue, nicht per PR |

Expand All @@ -107,14 +108,15 @@ D-095 schreibt diese bereits gelebte Linie nur auf.

### Vertragsflächen in fremdem Besitz

Vier Flächen gehören einem Strang, werden aber vom anderen konsumiert. Dort gilt
Fünf Flächen gehören einem Strang, werden aber vom anderen konsumiert. Dort gilt
zusätzlich: **Verhalten ändern ja, Vertrag ändern nur nach Absprache.**

| Fläche | Eigentümer | Konsument | Was ohne Absprache nicht geht |
|---|---|---|---|
| `Pathfinding.CostField` | Einheitenstrang | `ConstructionSystem` (Platzierungsprüfung, Sprint 16) | Signatur oder Begehbarkeits-Semantik von `IsWalkable` ändern. Flow-Field-Erzeugung und Pathfinding-Interna sind frei |
| `FogOfWarSystem.GetTeamView` | Netzstrang | `CombatSystem` (Zielerlaubnis) | Rückgabeform oder Sichtbarkeitsregel ändern, ohne den Einheitenstrang zu informieren |
| `WeaponProfiles`-Slot `UnitRole.Unit` | Einheitenstrang | `ConstructionSystem` (Baustellen tragen heute diese Rolle) | Sprint 16 löst die Kopplung auf, indem die Baustelle `def.Role` statt `UnitRole.Unit` bekommt. Bis dahin gilt: der Fallback-Schaden von 15 ist **keine** Baustellenregel, sondern ein Nebeneffekt. Wer den Slot umwidmet, sagt es an |
| `SkirmishAiTests.AiHost` / `BuildMatch` | Einheitenstrang | `CanonicalAiOutcomeTests` (Netzstrang) | Umbenennen oder Signatur ändern, ohne es anzusagen — der Ausgangspin baut darauf auf. Der Inhalt des Harness ist frei |
| `UnitState.AttackTarget` | Einheitenstrang (`CombatSystem`) | `UnitCommandStateView` (`Stop` löscht es, Sprint 16) | das Feld **löschen** darf der Netzstrang. Eine Regel, *wann automatisch neu erfasst wird* (D-087, Auto-Zielerfassung), gehört dem Einheitenstrang. Ein „Feuer einstellen" ist deshalb kein Netzstrang-Paket |

## Neue Systeme — wer die Tick-Reihenfolge setzt
Expand Down Expand Up @@ -291,6 +293,7 @@ zusammen gespielt wurden, sind zwei Behauptungen.

| Version | Datum | Änderung | Autor |
|---|---|---|---|
| 1.4.0 | 2026-08-09 | **D-101:** `tools/Nova.SimRunner.Tests/` bekommt eine Eigentümerzeile — geteilt je Datei, fremde Testdateien nur nach Ansage. Der Ausgangspin der kanonischen KI-Partie ist vom Identitätspin getrennt: `CanonicalAiOutcomeTests` (Netzstrang) hält Entscheidungstick und Endzustand, `SkirmishAiTests` die Kennung. Der KI-Harness ist damit Vertragsfläche | Orchestrator |
| 1.3.1 | 2026-08-09 | Zugangsmodell nachgezogen: der externe Beitragende erhält einen Collaborator-Eintrag auf der Stufe **Triage**, damit ihn Issues erreichen können. Der Code-Weg bleibt Fork-only, die Merge-Sperre unberührt | Orchestrator |
| 1.3.0 | 2026-08-09 | **D-095:** Trennung von „Verhaltensraum" auf „Dateihoheit" umgestellt — Sprint 16 läuft parallel zu 13B statt dahinter. `Simulation/State/` in Layout (weiter eingefroren) und Befehlsanwendung (Eigentümer des jeweiligen Befehls) getrennt. Zwei Vertragsflächen ergänzt (`WeaponProfiles`-Slot `UnitRole.Unit`, `UnitState.AttackTarget`). Merge-Fenster auf einen Strang je Fenster verschärft. Abschnitte „Definitions-Hash" und „kanonische Startaufstellung an vier Stellen" ergänzt. Plattform-Abschnitt berichtigt: der Linux-Build existiert seit `e15f5e6`, die offene Bringschuld ist stattdessen ein `NovaBuildCommit`-Leser im Spiel | Orchestrator |
| 1.2.2 | 2026-08-09 | Zwei Pfade nachgetragen, die der Einheitenstrang in der Praxis braucht und die die Tabelle nicht kannte: `tools/Nova.AiLab/` samt Tests (Messwerkzeug, kein Spielcode) und `Presentation/UI/DebugHud.cs` als ausdrückliche Ausnahme aus `Presentation/`. Beide Lücken lagen im Dokument, nicht im Verhalten des Beitragenden | Producer / Agent (Umsetzung) |
Expand Down
76 changes: 76 additions & 0 deletions tools/Nova.SimRunner.Tests/CanonicalAiOutcomeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using NUnit.Framework;
using Nova.AI.Data;

namespace Nova.SimRunner.Tests
{
/// <summary>
/// Pins the OUTCOME of the canonical AI match: the tick it is decided on
/// and the end-state hash. Owned by the maintainer strand (D-101).
/// <para>
/// Why this is not in <see cref="SkirmishAiTests"/> any more: these two
/// numbers move on ANY change to the simulation the AI plays in — economy,
/// construction, production, vision — not only on a change to the AI
/// itself. They sat in the AI suite until 2026-08-09, where every package
/// of Sprint 16 tripped them and the failure message sent the maintainer
/// strand to the AI behaviour journal, which is the wrong book. The
/// identifier pin — "which AI is this" — stayed where it belongs, in
/// <see cref="SkirmishAiTests.AiBehaviorId_TracksWhichAiThisIs"/>.
/// </para>
/// <para>
/// The split does not lose the diagnosis, because this test reads the
/// identifier too. Read the two results together:
/// </para>
/// <list type="table">
/// <item><term>outcome moved, identifier moved</term><description>the AI
/// changed. The AI strand bumps <c>AiBehaviorId.Revision</c>, writes the
/// journal entry in <c>tools/Nova.AiLab/reports/behavior-log.md</c>, and
/// updates the numbers below in the same commit.</description></item>
/// <item><term>outcome moved, identifier unchanged</term><description>the
/// simulation under the AI changed. The strand that changed it updates the
/// numbers below and says so in its PR. No journal entry, no revision
/// bump — nothing about the AI moved.</description></item>
/// <item><term>outcome UNDECIDED (tick 0)</term><description>not a moved
/// pin. The AI no longer finishes the match inside the budget, which means
/// something broke its loop. Fix the cause; do not update the
/// numbers.</description></item>
/// </list>
/// <para>
/// This is NOT one of the four determinism baselines: those live in their
/// own files and force a behaviour PR and a baseline PR apart. This pin
/// belongs WITH the change that moved it and is updated in the same commit.
/// </para>
/// </summary>
[TestFixture]
public sealed class CanonicalAiOutcomeTests
{
/// <summary>Decided tick of the canonical AI match, last moved by: Sprint 15 (r5).</summary>
private const uint PinnedDecidedTick = 2548u;

/// <summary>End-state hash of the canonical AI match, last moved by: Sprint 15 (r5).</summary>
private const string PinnedEndState = "0x14472B2B943ED2BB";

[Test]
public void CanonicalAiMatch_DecidesOnThePinnedTick_WithThePinnedEndState()
{
SkirmishAiTests.AiHost host = SkirmishAiTests.BuildMatch(SkirmishAiTests.Seed);
uint decided = host.RunUntilDecided(SkirmishAiTests.EndToEndBudgetTicks);
ulong endState = host.Kernel.CalculateStateHash();

// Undecided is a defect, not a moved pin — it is asserted first and
// on its own so the failure says which of the two happened.
Assert.That(decided, Is.Not.Zero,
$"the AI did not decide the canonical match within {SkirmishAiTests.EndToEndBudgetTicks} ticks — "
+ "its loop is broken, not merely moved. Fix the cause instead of updating the pin.");

Assert.Multiple(() =>
{
Assert.That(decided, Is.EqualTo(PinnedDecidedTick),
"the canonical AI match is decided on a different tick than the pinned one — "
+ $"AI identifier is {AiBehaviorId.Value}; if that string is unchanged, the simulation moved, not the AI");
Assert.That($"0x{endState:X16}", Is.EqualTo(PinnedEndState),
"the canonical AI match ends in a different state than the pinned one — "
+ $"AI identifier is {AiBehaviorId.Value}; if that string is unchanged, the simulation moved, not the AI");
});
}
}
}
Loading
Loading