diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6ac279..ed056ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/docs/production/DecisionLog.md b/docs/production/DecisionLog.md
index 8d76f21..4c4e8b5 100644
--- a/docs/production/DecisionLog.md
+++ b/docs/production/DecisionLog.md
@@ -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
@@ -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.
@@ -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 |
diff --git a/docs/production/hashkrieg/13-15_Parallelbetrieb.md b/docs/production/hashkrieg/13-15_Parallelbetrieb.md
index 0f84430..d522823 100644
--- a/docs/production/hashkrieg/13-15_Parallelbetrieb.md
+++ b/docs/production/hashkrieg/13-15_Parallelbetrieb.md
@@ -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
@@ -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 |
@@ -107,7 +108,7 @@ 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 |
@@ -115,6 +116,7 @@ zusätzlich: **Verhalten ändern ja, Vertrag ändern nur nach Absprache.**
| `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
@@ -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) |
diff --git a/tools/Nova.SimRunner.Tests/CanonicalAiOutcomeTests.cs b/tools/Nova.SimRunner.Tests/CanonicalAiOutcomeTests.cs
new file mode 100644
index 0000000..262ead1
--- /dev/null
+++ b/tools/Nova.SimRunner.Tests/CanonicalAiOutcomeTests.cs
@@ -0,0 +1,76 @@
+using NUnit.Framework;
+using Nova.AI.Data;
+
+namespace Nova.SimRunner.Tests
+{
+ ///
+ /// 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).
+ ///
+ /// Why this is not in 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
+ /// .
+ ///
+ ///
+ /// The split does not lose the diagnosis, because this test reads the
+ /// identifier too. Read the two results together:
+ ///
+ ///
+ /// - outcome moved, identifier movedthe AI
+ /// changed. The AI strand bumps AiBehaviorId.Revision, writes the
+ /// journal entry in tools/Nova.AiLab/reports/behavior-log.md, and
+ /// updates the numbers below in the same commit.
+ /// - outcome moved, identifier unchangedthe
+ /// 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.
+ /// - outcome UNDECIDED (tick 0)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.
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ [TestFixture]
+ public sealed class CanonicalAiOutcomeTests
+ {
+ /// Decided tick of the canonical AI match, last moved by: Sprint 15 (r5).
+ private const uint PinnedDecidedTick = 2548u;
+
+ /// End-state hash of the canonical AI match, last moved by: Sprint 15 (r5).
+ 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");
+ });
+ }
+ }
+}
diff --git a/tools/Nova.SimRunner.Tests/SkirmishAiTests.cs b/tools/Nova.SimRunner.Tests/SkirmishAiTests.cs
index c2e375f..3357d57 100644
--- a/tools/Nova.SimRunner.Tests/SkirmishAiTests.cs
+++ b/tools/Nova.SimRunner.Tests/SkirmishAiTests.cs
@@ -40,7 +40,8 @@ namespace Nova.SimRunner.Tests
[TestFixture]
public sealed class SkirmishAiTests
{
- private const ulong Seed = 0xA17E57DE57UL;
+ /// Seed of the canonical AI match. Internal since D-101: CanonicalAiOutcomeTests pins its outcome.
+ internal const ulong Seed = 0xA17E57DE57UL;
private const byte HumanSlot = 0;
private const byte AiSlot = 1;
private const ushort MapWidth = 128;
@@ -53,13 +54,13 @@ public sealed class SkirmishAiTests
/// at tick 2242, so 6.000 ticks is a ~2.7x margin — comfortably sane,
/// and exact because the whole loop is deterministic.
///
- private const int EndToEndBudgetTicks = 6000;
+ internal const int EndToEndBudgetTicks = 6000;
// ----------------------------------------------------------------
// The AI host (mirror of MatchRunner's skirmish wiring)
// ----------------------------------------------------------------
- private sealed class AiHost
+ internal sealed class AiHost
{
public SimulationKernel Kernel;
public EntityManager Entities;
@@ -250,7 +251,7 @@ private static void ApplyOpeningPosition(AiHost host)
}
}
- private static AiHost BuildMatch(ulong seed, AiProfile? profile = null)
+ internal static AiHost BuildMatch(ulong seed, AiProfile? profile = null)
{
AiHost host = BuildAiHost(seed, profile);
ApplyOpeningPosition(host);
@@ -417,22 +418,29 @@ public void SkirmishAi_EndToEnd_IsByteDeterministicAcrossRuns()
// ----------------------------------------------------------------
///
- /// Pins TOGETHER with what the AI actually
- /// does. Either half alone is useless: the identifier's profile hash
- /// catches changed numbers but never a changed rule, and the end state
- /// catches a changed rule but does not know the identifier exists.
+ /// Pins — the identifier that says WHICH AI
+ /// this is. It carries the profile hash and the revision, so it catches
+ /// changed numbers, and the revision catches a changed rule that the
+ /// author declared.
///
- /// WHEN THIS GOES RED — and only then read on, because the failure
- /// message is the procedure:
- ///
- ///
- /// - Was the behaviour change intended? If not, fix the code. The
- /// test just told you the AI plays differently than you thought.
- /// - If it was: bump AiBehaviorId.Revision, add its line to
- /// the history in that file, write the journal entry in
+ /// WHEN THIS GOES RED: the AI's profile numbers or its revision moved.
+ /// Bump AiBehaviorId.Revision, add its line to the history in
+ /// that file, write the journal entry in
/// tools/Nova.AiLab/reports/behavior-log.md — measured values,
- /// better AND worse — and only then update the numbers below.
- ///
+ /// better AND worse — and only then update the string below.
+ ///
+ ///
+ /// THE OTHER HALF LIVES ELSEWHERE (D-101, 2026-08-09). This pin used to
+ /// also assert the decided tick and the end-state hash of the canonical
+ /// AI match. Those two numbers move on ANY simulation change, not only
+ /// on an AI change — every package of Sprint 16 tripped them — and the
+ /// procedure above then pointed the wrong strand at the wrong journal.
+ /// They now live in CanonicalAiOutcomeTests, which the maintainer
+ /// strand owns. The diagnosis stays intact because that test reads this
+ /// identifier: outcome moved AND identifier moved means the AI changed;
+ /// outcome moved and identifier unchanged means the simulation under it
+ /// changed.
+ ///
///
/// This is NOT one of the four determinism baselines and must not be
/// treated as one: those live in their own files and separate a
@@ -441,21 +449,10 @@ public void SkirmishAi_EndToEnd_IsByteDeterministicAcrossRuns()
///
///
[Test]
- public void AiBehaviorId_TracksWhatTheAiActuallyDoes()
+ public void AiBehaviorId_TracksWhichAiThisIs()
{
- AiHost host = BuildMatch(Seed);
- uint decided = host.RunUntilDecided(EndToEndBudgetTicks);
- ulong endState = host.Kernel.CalculateStateHash();
-
- Assert.Multiple(() =>
- {
- Assert.That(AiBehaviorId.Value, Is.EqualTo("r5.779A1B5B"),
- "the AI identifier changed — bump the revision and write the journal entry");
- Assert.That(decided, Is.EqualTo(2548u),
- "the AI decides the canonical match on a different tick than the pinned one");
- Assert.That($"0x{endState:X16}", Is.EqualTo("0x14472B2B943ED2BB"),
- "same identifier, different end state: behaviour moved without the revision moving");
- });
+ Assert.That(AiBehaviorId.Value, Is.EqualTo("r5.779A1B5B"),
+ "the AI identifier changed — bump the revision and write the journal entry");
}
// ----------------------------------------------------------------