feat(ai): Goal-Katalog fuer die Skirmish-KI, erste Regel DefendHome - #96
Open
arn-c0de wants to merge 5 commits into
Open
feat(ai): Goal-Katalog fuer die Skirmish-KI, erste Regel DefendHome#96arn-c0de wants to merge 5 commits into
arn-c0de wants to merge 5 commits into
Conversation
Der Armeeschritt entschied in einer if-Kette, in der keine Verzweigung einen Namen hatte. Er waehlt jetzt je Einheit und Kadenz EIN Goal aus einer festen Prioritaetsliste — Retreat, Attack, Hold, Advance — und wendet dessen Wirkung aus einer Tabelle an. ES IST KEINE VERHALTENSAENDERUNG, und das ist der ganze Zweck des Schritts: dieselben vier Bedingungen in derselben Reihenfolge, dieselben Befehle. Der Nachweis ist kein Test, sondern eine Zahl — die kanonische Partie entscheidet auf demselben Tick mit demselben Endzustand (CanonicalAiOutcomeTests), und ein Laborlauf ist byte-identisch bis auf die gemessene Laufzeit. Deshalb kein AiBehaviorId.Revision-Bump. KEIN PROFILFELD, obwohl der Plan Prioritaeten im Profil vorsah. Ein angehaengtes Feld bewegt AiProfile.ProfileHash und damit den Verhaltensbezeichner, der in jedes Laborartefakt gedruckt wird — der Schritt waere dann nicht byte-identisch gewesen, und die Byte-Gleichheit ist genau das, was ihn auswertbar macht. Eine Aus-Stellung bringt jedes Modul mit, das eine REGEL bekommt, im PR, der ihm die Regel gibt. Ein Goal ist KEIN Zustand: es wird je Kadenz neu abgeleitet und nirgends gespeichert. Die KI bleibt eine reine Funktion des committeten Zustands, es entsteht kein Sidecar-Block, kein neues System, keine Aenderung der Tick-Reihenfolge. Zwei Klauseln, die beim Modularisieren leicht verlorengehen und beide tragend sind: - Retreat tritt fuer eine Einheit zurueck, die ANGEKOMMEN ist. Heimkommen beendet den Rueckzug; MS-1-Einheiten heilen nie, eine Regel, die sie bis zur Genesung festhaelt, staut die Verwundeten zuhause und fuellt nie wieder eine Welle. - Attack verlangt, nicht auf Rueckzug zu sein. Eine verwundete Einheit ausserhalb des Rings erfuellt sonst jede andere Haelfte des Marschtests, und der Rueckzug erreichte genau die Einheit nicht, fuer die es ihn gibt. 707/707 im Spiel.
Zwei optionale Naehte an der Skirmish-KI, die der ausgelieferte Pfad nie fuellt — MatchRunner uebergibt keine von beiden, die Referenzen bleiben null, und die Null-Pruefung ist der ganze Aufwand im Spiel. IAiGoalObserver laesst mitlesen, welches Goal eine Einheit bekommen hat UND mit welchen Zahlen die Bedingung entschieden hat: Leben gegen die Rueckzugsschwelle, Abstand gegen die Sammeltoleranz, Abstand zum HQ gegen den Ring. Jede Bedingung im Katalog ist ein ganzzahliger Vergleich, also ist der Abstand zum naechsten Goal exakte Arithmetik und keine Schaetzung. Es ist ein Rueckruf und kein Feld auf dem System: ein Puffer "was ich zuletzt entschieden habe" waere genau das Gedaechtnis, das der ganze Entwurf vermeidet, und das erste, was eine spaetere Regel lesen wollte. IAiGoalOverride erlaubt es, einem Goal von aussen vorzugreifen — als EINGABE der Entscheidung, wie das Profil, nicht als gespeicherter Zustand. Die Maske ersetzt die Wahl und nie die Wirkung: ein erzwungenes Goal erzeugt genau die Befehle, die dieses Goal immer erzeugt, damit kein Werkzeug ein Verhalten vorfuehren kann, fuer das die KI keinen Code hat. Wozu ueberhaupt: bisher war der einzige Weg, das WARUM einer Einheit zu zeigen, die Regeln neben der Aufzeichnung nachzubauen und das Ergebnis "hergeleitet" zu nennen. Ein Diagnosewerkzeug, das eine zweite, leicht abweichende Fassung der Regeln zeigt, ist schlimmer als eines, das nichts zeigt. WaveStrengthGate bekommt eine IsReady-Ueberladung, die die Schwelle herausgibt. Als Ueberladung und nicht als zweite Rechnung beim Aufrufer, damit der Vergleichsoperator weiterhin genau einmal existiert. Beide Naehte sind byte-identisch nachgewiesen: ein Lauf mit Beobachter und ein Lauf mit leerer Maske erreichen denselben Endzustand auf demselben Tick wie einer ohne. Dazu die Gegenprobe, dass eine Maske, die etwas benennt, die Partie tatsaechlich veraendert — sonst waere die erste Zusicherung leer. 715/715 im Spiel (707 plus acht neue). Keine der vier Baseline-Dateien angefasst.
Neues Goal DefendHome (r7 -> r8): wer noch im Sammelring steht und nicht zurueckzieht, marschiert zur statischen HQ-Zelle und zielt auf den naechsten sichtbaren bewaffneten Gegner. Wer draussen ist, marschiert weiter -- die r3-Regel "Einheiten draussen werden nie zurueckgerufen" bleibt, die Welle wird unterbrochen, nicht freigegeben. Der Defekt ist so alt wie der Sammelpunkt: eine angekommene Einheit bekommt absichtlich keinen Befehl und haengt allein an der D-087-Auto- Acquisition, die 6 bzw. 7 Zellen reicht -- der Sammelpunkt liegt 12 vom HQ. Gemessen: das Legions-HQ nimmt ueber 766 Ticks 327 Treffer, waehrend die eigenen Einheiten im Median 13 Zellen entfernt unter Hold stehen und keine einzige angreift. Profilfeld defendHomeCells (ausgeliefert 10, 0 = aus). Auf 0 beidseitig ergibt die kanonische Partie bitgenau r7: Tick 3213, 0xE002DD893916967B. Der Bezeichner bewegt beide Haelften -- die Revision, weil Entscheidungen sich aendern, und ProfileHash, weil die Regel mit ihrer Aus-Stellung ausgeliefert wird. Wehrlosigkeit im Beschussfenster 96 % -> 60 %, Partie 3213 -> 6490 Ticks, eigene Verluste 18 -> 60: die Regel verschiebt die Niederlage, sie wendet sie nicht ab. 31 Beurteilungen kosten 25 Befehle auf eine einzige Zielzelle -- das ist die Korrektur gegenueber DefendBase (Journal V002, +23 % Intents an einem beweglichen Ziel). Bei bedrohter Basis steht keine Einheit im Ring mehr unter Hold. Am Player angesehen: Nova.AiLab-goal-base-defense-r8-20260810 https://youtu.be/pD9NXtTutmM Im laufenden Spiel gesehen: nein.
IAiGoalObserver und IAiGoalOverride lagen in Nova.AI.Data. Nova.Gameplay und Nova.AI.Tests referenzieren aber nur Nova.AI, und dass niemand die beiden optionalen Konstruktorargumente uebergibt, hilft nicht: der Compiler muss die Parametertypen fuer die Ueberladungsaufloesung laden. Ergebnis war CS0012 in MatchRunner.cs und in den Unity-EditMode-Tests, also eine rote Startkompilierung — und weil -executeMethod danach laeuft, die Meldung "class 'DemoLauncher' could not be found". Der Demo-Start war nie kaputt, die Assembly war es. dotnet test hat das nicht gesehen und kann es nicht sehen: SimRunner und Labor ziehen alle Quellordner in eine Assembly, die Grenzen gibt es nur in Unity. Die Naehte gehoeren ohnehin zur Vertragsflaeche der KI und nicht zur Datenschicht: sie ziehen jetzt in Nova.AI um, die jeder referenziert, der die KI baut. Damit braucht das ausgelieferte Spiel fuer den Konstruktor keine Referenz auf die KI-Datenassembly. Die Worte, auf die sich drei Oberflaechen einigen muessen — GoalKind, Profile, AiBehaviorId — bleiben in Nova.AI.Data, wie es dort dokumentiert ist. Die Alternative waere eine Zeile in Nova.Gameplay.asmdef gewesen. Das ist fremdes Terrain und haette eine Rueckfrage gebraucht, nicht einen Commit. Geprueft: dotnet test 721/721 gruen. In Unity noch nicht nachkompiliert.
Beide beim Pruefen der Begruendung gefunden, nicht des Codes. Keine fuegt eine Zahl hinzu, also bewegt sich ProfileHash nicht und der Aus-Pfad (defendHomeCells: 0) bleibt bitgenau r7. 1. Die statische Zielzelle allein traegt das Argument nicht. Die Re-Issue-Unterdrueckung vergleicht den stehenden Befehl, und MovementSystem loescht genau den bei der Ankunft (UnitState.Stop()). Ein angekommener Verteidiger hatte damit nichts mehr zu vergleichen: die HQ-Zelle ging jede Kadenz erneut raus. Gemessen ein Move-Intent pro Kadenz fuer die Dauer der Belagerung, mit acht stehenden Einheiten, die jedes Mal wieder in IsMoving kippten -- die V002-Form eine Nummer kleiner. DefendHome verstummt jetzt selbst, sobald die Einheit daheim steht; dieselbe Stille, die Hold am Sammelpunkt hat. 2. Die Regel fragte, ob die Einheit gerade zurueckzieht. Diese Klausel konnte nur Verwundete treffen, die schon angekommen waren -- Laufende nimmt Retreat eine Zeile frueher. Ankommen beendet den Rueckzug aber nach der eigenen Regel der KI. Sie hat also nichts bewirkt ausser genau diese Einheiten an Hold zu geben: zwoelf Zellen draussen stehend, auf einen Verfolger zielend, den sie nicht erreichen, waehrend die Basis brennt, zu der sie zurueckgelaufen waren. Dazu zwei kleinere Sachen im selben Zug: CollectVisibleThreats laesst eine Entity-Id, die das Wire-Format nicht ausdruecken kann, jetzt aus (eine 0 gewaenne den Tie-Break auf die niedrigste Id und hiesse dann stillschweigend "kein Angriffsbefehl"), und der Goal-Katalog zaehlt an vier Stellen wieder richtig -- r8 hatte ein fuenftes Goal eingezogen und "die vier Bedingungen" stehen lassen. Zwei neue Tests, beide gegen den unkorrigierten Stand als rot nachgewiesen: AnArrivedDefenderIsNotSentHomeAgain und AWoundedUnitThatIsAlreadyHomeDefendsInsteadOfHolding. Der bestehende AHeldDefenceDoesNotProduceAnOrderEveryCadence konnte Punkt 1 nicht sehen -- alle Verteidiger teilen eine Zielzelle, die Wiederholung kostet also genau einen gruppierten Intent und liegt unter seiner Schranke. Der CHANGELOG-Eintrag sagt jetzt ausdruecklich, dass die Wirkungszahlen (96 % auf 60 %, 3.213 auf 6.490 Ticks, 18 auf 60 Verluste) am unkorrigierten DefendHome gemessen wurden und vor dem Merge neu zu messen sind. Ebenso notiert, nicht behoben: unterhalb der Squad-Schwelle laeuft der Armeeschritt gar nicht, also verteidigt niemand -- das ist eine Verhaltensausweitung mit eigener Messpflicht und gehoert in einen eigenen PR. Geprueft: dotnet test 722/722 gruen. Im laufenden Spiel nicht gesehen.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Was & Warum
Die Skirmish-KI benennt jetzt, was eine Einheit vorhat (
GoalKindstatt namenloser if-Kette), und die Form bekommt ihre erste Regel:DefendHome— wer noch im Sammelring steht, bricht ab und marschiert zum eigenen HQ, wenn dort ein sichtbarer bewaffneter Gegner innerhalb vondefendHomeCells(10, 0 = aus) steht. Vorher hing eine angekommene Einheit allein an der D-087-Auto-Acquisition (6–7 Zellen), während der Sammelpunkt zwölf Zellen vom HQ liegt — ein Angreifer an der Basis war ausserhalb jeder Reichweite.AiBehaviorId:r7→r8.Das ist die Grundlage, auf der die künftigen KI-Verhaltensweisen aufsetzen — ich lege gerade die Baselines dafür an; aktueller Stand und geplante Ausbaustufen sind im Labor einsehbar: https://github.com/arn-c0de/Nova.AiLab/blob/main/ROADMAP.md
Im laufenden Spiel gesehen: nein — im Labor gebaut und gemessen. Ersatznachweise: die Umbenennung ist verhaltensneutral (kanonische Partie unbewegt, Laborartefakte byte-identisch),
0xE002DD893916967B), 722/722 grün.Restrisiko:
Checkliste
dotnet test tools/Nova.SimRunner.Testslokal grün[Unreleased]in CHANGELOG.mdD-ID im DecisionLog— keine echte Entscheidung; die Goal-Priorität liegt bewusst in C# statt im Profil, damitProfileHashsich nicht bewegtExterne Beiträge