Skip to content

Repository files navigation

Nova.AiLab — KI-Simulationslabor

Zusehen

Video: die Oberfläche im Betrieb, mit einem Gefecht der beiden Armeen

Das Labor beim Zusehen — Laufrouten, Gefecht, Ereignisprotokoll

Was da läuft, ist player.html aus einem Laborlauf: die Karte tickgenau vor- und zurückspulbar, jede Einheit anklickbar, ihre Laufroute gezeichnet, Treffer als rote Ringe, Sterben als verblassendes Kreuz — und daneben das vollständige Ereignisprotokoll der Partie. Womit man so einen Lauf erzeugt, steht unter Start — oder ohne Kommandozeile mit ./lab-gui.sh.

Und was es nicht ist: ein Nachweis. Ein Laborlauf ist Diagnose. Was nicht im laufenden Spiel gesehen wurde, steht als ungesehen im PR-Text — das gilt auch für alles, was in diesem Video überzeugend aussieht.

Das Labor ist aus dem Fork (arn-c0de/Project_Nova) herausgelöst und liegt jetzt in einem eigenen Repository — neben dem Spiel-Checkout, nicht mehr darin.

Der Gewinn ist kein Ordnungsgewinn, sondern ein Messgewinn: Solange das Labor in einem Branch lag, konnte es nur diesen einen Branch messen. Jeder andere hätte erst mit dem Werkzeug bestückt werden müssen — und ein Branch, in den man das Messwerkzeug einbaut, ist nicht mehr der Branch, den man messen wollte. Von aussen misst es, was drüben ausgecheckt ist: jeder Branch lässt sich gegentesten, ohne Hin- und Herbauen. git checkout drüben genügt, hier ändert sich nichts.

Wohin Was dort steht
reports/README.md die Gesamtübersicht: jeder archivierte Laborlauf eine Zeile, der Verlauf innerhalb der aktuellen Definitionstabelle, Links in die Historie
reports/latest.md der zuletzt vermessene Lauf vollständig — Partie, Kandidatenprofile, Gegentabelle, Belagerung, Bewegung. Ohne Browser lesbar
reports/behavior-log.md das Verhaltensjournal, von Hand geführt: was geändert wurde, was besser und was schlechter wurde, und was schon widerlegt ist. Vor jeder neuen Änderung lesen
NEXT-STEPS.md was als nächstes ansteht — sortiert danach, was ein Spieler in einer Partie merkt, nicht nach Laborkennzahl. Dazu die begründete Liste dessen, was man nicht anfangen sollte

Die interaktive Fassung derselben Zahlen ist out/dashboard.html — sie braucht einen Browser, die drei Seiten oben nicht.

Werkzeug, kein Beitrag — und seit dem Ausbau auch räumlich:

ProjectNova - HASHKRIEG/
├── Project_Nova/        das Spiel, in irgendeinem Branch
└── Nova.AiLab/          dieses Labor, eigenes Repo

Drei Repositories, die dabei auseinanderzuhalten sind:

Repository Rolle
VibecodingGermany/Project_Nova das Spiel selbst — Ziel jedes Pull Requests. Wird nur gefetcht, nie dorthin gepusht
arn-c0de/Project_Nova mein Fork davon, üblicherweise der Checkout unter Project_Nova/ — von hier geht ein PR nach oben
arn-c0de/Nova.AiLab dieses Labor. Kein Beitrag zum Spiel, sondern das Werkzeug, das es vermisst

Welcher Checkout gemessen wird, entscheidet die MSBuild-Eigenschaft NovaRepo (Vorgabe: ../Project_Nova). Sie bestimmt beides zugleich — welche Quelldateien einkompiliert werden und welchen Commit die Artefakte als Herkunft tragen. Die beiden können deshalb nicht auseinanderlaufen:

./lab.sh                                   # misst ../Project_Nova
./lab.sh --repo /pfad/zu/zweitem/checkout  # oder ein `git worktree`
NovaRepo=/pfad/zu/checkout ./lab.sh        # dasselbe über die Umgebung

Ein zweiter Checkout (oder ein git worktree) ist der bequeme Weg, zwei Branches nebeneinander zu messen, ohne dauernd umzuschalten.

Wem was gehört: Das Labor gehört mir (LICENSE) — der Maintainer und die Mitwirkenden von Project Nova dürfen es benutzen, um Branches zu vermessen, nur nicht weitergeben. Was ich am Spiel ändere und per Pull Request einreiche, richtet sich dagegen nach den Bedingungen des Hauptrepos, und zwar nur der eingereichte Diff. Beide Richtungen und ihre Grenze stehen in CONTRIBUTIONS.md.

Was im Spiel-Repo bleibt: die In-Game-Debughilfen, weil sie Spielcode sind — der Bezeichner im F3-Panel, das Aufdecken der Karte, der Zeitraffer. Die liegen weiterhin auf lab/ai-simulation im Fork und gehören in keinen feat/-Branch. Plan und Begründung des Labors: docs/feature-ideas/AiSimulationEnvironment.md.

Was nicht mitversioniert wird: out/ — das ist der jeweils letzte rohe Lauf, und er ist reproduzierbar. Die verdichteten Messblöcke unter reports/data/ sind die Quelle, aus der jeder Bericht jederzeit neu entsteht; die sind versioniert. Nach einem Merge-Fenster des Maintainers ist eine alte Messmenge ohnehin nicht mehr vergleichbar, und der Bericht sagt das selbst, statt über die Grenze hinweg zu vergleichen.

Ein grüner Laborlauf ist Diagnose, kein Nachweis. Was nicht im laufenden Spiel gesehen wurde, steht als ungesehen im PR-Text.

Start

export DOTNET_ROOT="$PWD/.dotnet"; export PATH="$DOTNET_ROOT:$PATH"   # falls dotnet nicht im PATH ist

# eine KI-gegen-KI-Partie, mit Metriken und Artefakten
dotnet run --project Nova.AiLab -c Release -- match --trace-every 100 --out out/run1

# zwei Läufe desselben Specs, Hash-Ketten verglichen
dotnet run --project Nova.AiLab -c Release -- match --repeat 2 --hash-every 100

# die laufende Partie im Terminal mitsehen
dotnet run --project Nova.AiLab -c Release -- match --watch

# aufzeichnen und danach im Browser zurückspulen: out/run1/player.html öffnen
# — dort eine Einheit anklicken und ihre Laufroute, Angriffe und ihren Tod verfolgen
dotnet run --project Nova.AiLab -c Release -- match --view-every 25 --fog --out out/run1

# dasselbe ohne Kommandozeile: Branch wählen, messen, Player öffnen, zwei Läufe
# nebeneinanderlegen, Historie durchsehen — alles in einer lokalen Seite
./lab-gui.sh

# eine verbesserte Ansicht auf ALTE Läufe legen: schreibt nur player.html neu,
# die gemessenen Daten daneben bleiben unangetastet
dotnet run --project Nova.AiLab -c Release -- player --out out

# Seed-Matrix über alle Kerne, jeder 20. Lauf doppelt zur Selbstkontrolle
dotnet run --project Nova.AiLab -c Release -- sweep --seeds 24 --out out/sweep

# die Gegentabelle: 576 Duelle in Sekunden (Issues 01/02)
dotnet run --project Nova.AiLab -c Release -- duel --out out/duel

# die vier Bewegungsszenarien (Issue 03)
dotnet run --project Nova.AiLab -c Release -- movement --out out/movement

# alle Kandidatenprofile gegen die eingefrorene Referenz: out/compare/report.html
dotnet run --project Nova.AiLab -c Release -- compare --out out/compare

# gegen eine archivierte Ergebnismenge — verweigert bei fremdem Commit oder Definitionstabelle
dotnet run --project Nova.AiLab -c Release -- compare --against out/alt/resultset.json --out out/compare2

# vier Slots (die Karte hat vier Eckplätze)
dotnet run --project Nova.AiLab -c Release -- match --slots 4

dotnet test Nova.AiLab.Tests/Nova.AiLab.Tests.csproj -c Release

# alle vier Laufarten messen und alle Berichte schreiben — ein Kommando
./lab.sh

# nur die Berichte aus dem vorhandenen Lauf: dashboard.html + reports/
python3 Nova.AiLab/report/build_reports.py out

# nur die Markdown-Berichte neu rendern, ohne zu messen (nach Formatänderung)
python3 Nova.AiLab/report/build_reports.py --regenerate

# nur die eine Seite: out/dashboard.html
python3 Nova.AiLab/report/build_dashboard.py Nova.AiLab/out

Auf dem Telefon (Termux, arm64)

Das Labor läuft vollständig unter Termux — nicht nur das Zurückspulen fertiger Läufe, sondern das Messen selbst. Es braucht ein SDK für die richtige Architektur, und das steht im Termux-Hauptrepo:

pkg install dotnet-sdk-8.0     # nativ aarch64, RID linux-bionic-arm64
./lab.sh                       # misst ../Project_Nova wie überall sonst

Das .dotnet/ im Spiel-Checkout hilft dabei nicht: es ist ein x86-64-Build und bricht hier mit Exec format error ab. Schlimmer als nutzlos war es, solange lab.sh es blind in den PATH gehängt hat — dann verdeckte ein SDK, das nicht startet, eines, das gestartet wäre. lab.sh nimmt es seitdem nur noch, wenn es sich auch ausführen lässt.

Was das Gerät leistet (8 Kerne, Snapdragon-Klasse): eine Partie 1,5 s, ein vollständiger ./lab.sh mit allen vier Laufarten und 23 Kandidatenprofilen 2,5 min, die 655 Tests von Nova.SimRunner.Tests 108 s.

Und das ist die interessante Zahl: die 655 schliessen die vier Baseline-Dateien ein, deren Golden-Bytes und Hashes auf x86 entstanden sind. Dass sie auf arm64/bionic grün bleiben, ist der Nachweis, den die Festkomma-Regel verlangt — beide Architekturen rechnen bitgleich, und ein auf dem Telefon gemessener Lauf ist mit einem auf dem Rechner gemessenen vergleichbar.

Zwei Stolpersteine bleiben. Das global.json des Spiels pinnt SDK 8.0.318 mit rollForward: disable, Termux liefert 8.0.129: dotnet test drüben läuft nur aus einem Arbeitsverzeichnis ausserhalb des Checkouts, sonst greift der Pin. Und Unity gibt es hier nicht — die gespielte Beobachtung, die jeder verhaltensändernde PR verlangt, kommt weiterhin nur von einem echten Build. Ein grüner Laborlauf auf dem Telefon ändert daran nichts.

Zwei Fassungen desselben Laufs

Fassung Wo Wofür
interaktiv out/dashboard.html Kurven mit Fadenkreuz, Heatmap mit Abstandsdetail, Scrubber — braucht einen Browser
lesbar reports/README.md, reports/latest.md, reports/runs/<id>.md dieselben Zahlen als Markdown: auf GitHub direkt lesbar, ohne Download, ohne Server

reports/data/<id>.json ist die Quelle, die Markdown-Dateien sind Ableitung: ein Lauf wird an seinem Fingerabdruck erkannt (zweimal derselbe Lauf ergibt keinen zweiten Eintrag), und nach einer Formatänderung entsteht die ganze Historie mit --regenerate neu, ohne dass etwas nachgemessen werden muss. latest.md ist immer der zuletzt vermessene Lauf, README.md die Gesamtübersicht über alle.

Wechselt die Definitionstabelle, teilt sich die Historie: die Übersicht sagt das selbst hin und zeichnet den Verlauf nur innerhalb der aktuellen Tabelle. Über ein Merge-Fenster hinweg wird nicht verglichen, auch nicht als Kurve.

Neue Dateien unter Nova.AiLab/ hält .git/info/exclude aus git status heraus — ein neuer Bericht braucht deshalb git add -f, sonst fällt er still unter den Tisch.

Was hier liegt

Ein Ordner je Laufart — man findet alles über das Kommando, das man gerade fährt. Alle Dateien liegen im selben Namespace Nova.AiLab; die Ordner gliedern, sie trennen nicht. (Ein Nova.AiLab.Movement neben dem benutzten Nova.Simulation.Movement wäre eine Namenskollision, die man sich einhandelt, ohne etwas dafür zu bekommen.)

Match/ — eine Partie fahren

Datei Inhalt
MatchSpec.cs / SpecFile.cs Eingabevertrag (§3.2) und sein JSON-Leser — unbekannte Schlüssel sind Fehler, keine Vorgabewerte
CanonicalOpening.cs die D-077-Startaufstellung aus MatchBootstrap, Spawnreihenfolge inbegriffen
MultiSlotAiHost.cs der Match-Host: MatchRunner.InitializeMatch von einem KI-Slot auf N verallgemeinert, sonst nichts
CountingAiPeerTransport.cs zählt Intent-Verdikte — die einzige Stelle, an der intentsRejected ehrlich entsteht
MatchRun.cs fährt eine Partie, liefert Outcome, Entscheidungstick, Hash-Kette, Trace
RunArtifacts.cs result.json, trace.ndjson, hashchain.json, view.ndjson, tracks.ndjson, events.ndjson, units.json, player.html

Metrics/ — messen, ohne einzugreifen

Datei Inhalt
SlotMetrics.cs / TraceCollector.cs der Metrikkatalog aus §3.3, reiner Beobachter, nur Ganzzahlen
DebugEventLog.cs je Einheit und Tick: Spawn, Tod, Schaden, Befehl, Ziel, Angriff, Ernte, Bau, Steckenbleiben. Der Verursacher ist hergeleitet und als hergeleitet gekennzeichnet — notes/schadensquelle.md
RouteMetrics.cs eine Zeile je Einheit: Umwegfaktor, Stillstand trotz Moving, Ziel- und Befehlswechsel, Schaden. Kein double, auch nicht in der Wurzel

View/ — hinsehen

Datei Inhalt
ViewFrame.cs / ViewRecorder.cs die Sichtframes aus §3.4 — Tätigkeit, nicht nur Position; reiner Beobachter. Seit der Laufroutenarbeit trägt jede Zeile die Entity-ID als zehnte Spalte, angehängt statt eingeschoben
EntityTrackRecorder.cs die Positionsspur, jeder Tick: Delta gegen die letzte Position, Keyframe alle 500 Ticks. Die Route ist absichtlich feiner als das Bild
TerminalView.cs ANSI-Liveansicht, beantwortet „läuft gerade etwas schief?"
HtmlPlayer.cs eine selbstständige Seite mit canvas: Scrubber, Einzeltick, Ebenen — dazu Einheitenliste, Spur der Auswahl, Detailfeld und Ereignisband. Kein Build, kein Server. Jede Einheit trägt das Symbol ihrer Rolle, nicht mehr eine von fünf Formen; herausgezoomt weichen die Symbole wieder Punkten, weil eine Silhouette bei drei Pixeln nichts mehr sagt. Die Anzeigetafel je Sitz steht als Karte neben der Karte, in der Breite, die ein quadratisches Feld ohnehin übrig lässt: Kampfstärke und ihr Anteil, Welle, Armee, Arbeiter, Gebäude, Gesundheit, AE, Strom, Sicht, gebaut, verloren, Schaden. Das Detailfeld sagt in einem Satz, was die Einheit gerade tut — wen sie angreift (mit Rolle, Besitzer und Entfernung), wer auf sie schiesst, ob sie erntet, trägt, klemmt oder zur eigenen Basis läuft
report/uikit/ die gemeinsame Oberflächensprache: tokens.css (Farben, Abstände, Bausteine, die acht Sitzfarben) und icons.js (ein Symbolsatz für DOM und Leinwand). Wird in Player, Steuerseite und Dashboard eingesetzt statt verlinkt — der Player muss eine einzelne kopierbare Datei bleiben. Eine Rolle ohne Symbol lässt UiKitTests scheitern, nicht den Screenshot drei Wochen später

Sweep/ — dieselbe Spec über viele Seeds

Datei Inhalt
SeedSeries.cs / SweepRunner.cs Parallellauf mit Determinismus-Stichprobe (jeder 20. Lauf doppelt)

Duel/ — die Gegentabelle

Datei Inhalt
DuelArena.cs / DuelTable.cs AE-Parität, drei Abstände, beide Richtungen, Belagerung

Movement/ — die vier Bewegungsszenarien

Datei Inhalt
MovementScenarios.cs arrival, blocking, standoff, detour — Hindernisse sind Daten, nicht Code

Compare/ — Kandidat gegen Referenz

Datei Inhalt
LabProfiles.cs die Kandidatenprofile — heute die einzige Achse mit echter Varianz
TournamentRunner.cs jeder Kandidat gegen die Referenz, in beiden Fraktionsrollen
ResultSet.cs / ResultSetFile.cs Ergebnismenge mit Herkunft; verweigert den Vergleich, statt Unvergleichbares zu mischen
ComparisonReport.cs der Bericht — Kennzahlen nebeneinander, keine Rangliste
PrDraft.cs PR-Entwurf mit ausschliesslich Gemessenem; Beobachtungsabschnitt bleibt leer

Cli/ — die Kommandozeile

Datei Inhalt
Usage.cs der Hilfetext — die einzige Stelle, an der ein Modus in Worten steht
Options.cs alle Flags; Spec-Datei als Basis, explizite Flags überschreiben sie
MatchCommand.csCompareCommand.cs je ein Modus, je eine Datei: match, sweep, duel, movement, compare

Wurzel und report/

Datei Inhalt
Program.cs nur Main und die Modus-Weiche — rund 50 Zeilen, sonst nichts
lab.sh messen und berichten in einem Kommando; --reports-only, --regenerate
lab-gui.sh die Steuerseite: Branch wählen, messen, Player öffnen, Historie ansehen, zwei Läufe nebeneinanderlegen — alles im Browser. --port, --repo, --no-browser
report/gui_server.py die kleinstmögliche Gegenstelle dazu: nur Standardbibliothek, nur an 127.0.0.1. Ein fremder Branch wird nie im Arbeitscheckout ausgecheckt, sondern in einem git worktree unter .worktrees/ gemessen
report/gui.tpl.html die Seite dazu, in derselben Machart wie die anderen: eine Datei, kein Build, kein Netzzugriff
report/lab_data.py liest die Artefakte aller vier Laufarten und verdichtet sie zu einem Datenblock — die gemeinsame Quelle beider Berichtsformen, dazu Herkunft und Fingerabdruck eines Laufs
report/build_dashboard.py bettet diesen Block in die Seite out/dashboard.html — Kurven, Gegentabelle als Heatmap, Belagerung, Bewegung. Verdichtet nur, rechnet nichts dazu und vergibt keine Note
report/dashboard.tpl.html die Seite dazu: eine Datei, kein Build, kein Server, kein Netzzugriff
report/markdown_report.py derselbe Block als Markdown: ein Bericht je Lauf, eine Gesamtübersicht, Kurven als Mermaid. assert_no_ranking() hält maschinell fest, dass keine Tabellenzeile eine Note trägt
report/build_reports.py der Einstieg: archiviert den Lauf unter reports/data/, schreibt Seite und Markdown-Satz, entfernt Berichte ohne Messblock. --regenerate rendert die Historie neu, ohne zu messen
reports/ das Ergebnis: README.md (Gesamtübersicht), latest.md, runs/<id>.md, data/<id>.json

Das Testprojekt ../Nova.AiLab.Tests/ zieht diese Ordner mit einem Glob ein; eine neue Datei steht damit automatisch unter Test. Ausgenommen sind nur Program.cs und Cli/ (Einstiegspunkt und sein Drumherum) sowie bin/, obj/ (generierter Code).

Zwei Dinge, die man wissen muss, bevor man Zahlen liest

Der Seed ändert die Partie nicht. Kein Simulationssystem zieht aus dem Kernel-PRNG; der Seed geht in Zustands-Hash und Snapshot, sonst nirgendwohin. Ein Sweep über 24 Seeds ist eine Beobachtung. Der Sweep sagt das selbst hin, wenn alle Läufe gleich ausgehen — nicht überlesen.

Messen darf nichts kosten. Trace-Collector und Intent-Zählung sind reine Beobachter, und zwei Tests halten fest, dass ein Lauf mit und ohne sie dieselbe Hash-Kette liefert. Wenn diese Tests je rot werden, sind alle damit erhobenen Zahlen wertlos — nicht nur die neuen.

Die eine Regel, die dieses Labor trägt

Der Host muss dieselbe Partie sein wie das Spiel. Ein abgedrifteter Harness misst etwas, das es nicht gibt — und meldet dabei weiter saubere Zahlen. Nova.AiLab.Tests prüft das gegen einen handgespiegelten AiHost aus SkirmishAiTests.cs und vergleicht Zustands-Hashes, keine abgeschriebenen Konstanten. Wenn diese Suite rot wird, ist nicht der Test kaputt, sondern die Spiegelung: dann zuerst MatchRunner.InitializeMatch und MatchBootstrap nachziehen, nicht die Erwartung anpassen.

Die Tick-Reihenfolge ist Vertrag, kein Implementierungsdetail. Neue Systeme werden eingeordnet, nicht angehängt, und das Einordnen ist eine Absprache.

About

Deterministic AI simulation and diagnostics lab for Project Nova / HASHKRIEG — branch-independent matches, replays, metrics, movement tests, combat analysis and comparisons.

Topics

Resources

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages