Wofür: Es fährt die KI-Partie von HashKrieg ohne Unity, tausendfach, und schreibt auf, was dabei passiert — damit eine Änderung am KI-Verhalten in Sekunden beurteilbar ist statt in gespielten Partien.
Warum überhaupt: Weil man einer KI beim Zusehen alles zutraut. Das Labor liefert Zahlen, die zwei Läufe vergleichbar machen, statt Eindrücke — und sagt selbst, wo sie nichts beweisen.
Wie: ./lab.sh messen → reports/latest.md lesen →
out/player.html hinsehen. Ohne Kommandozeile: ./lab-gui.sh.
Tip
Neu hier? → START-HIER.md — eine Seite: wofür, warum,
wie, und welches der Dokumente man wann aufmacht. Wer bei null anfängt
(auch als Agent), nimmt UEBERGABE.md: beide Repositories,
Scope, Messen, Testen, Fallen. Diese Seite hier ist der Aufbau im Detail:
jede Datei, jedes Kommando.
Sortiert danach, was ein Spieler in einer Partie merkt — nicht nach
Aufwand und nicht nach Laborkennzahl. Vollständig, mit Aus-Stellung,
Reihenfolgeregeln und Rückfragen: ROADMAP.md.
| # | Ziel | Was der Spieler merken soll |
|---|---|---|
| 1 | Sammeln abbrechen, wenn die Basis brennt | Sie lässt ihr HQ nicht mehr zusammenschiessen, während neun Einheiten wartend danebenstehen |
| 2 | Goal-System als Form (verhaltensneutral) | Nichts — und genau das ist der Nachweis. Danach ist jedes Verhalten ein benanntes Modul statt eines if-Zweigs |
| 3 | Nachschub hinter der laufenden Welle | Neue Einheiten laufen dem Gefecht hinterher statt zu warten — und bei gebrochener Welle gar nicht |
| 4 | Zweites lohnendes Ziel | Sie geht auch auf Harvester und Refinery, nicht nur stur aufs Headquarter |
| 5 | Wellengrösse nach Lage | Kleiner Stoss gegen eine kleine Gruppe, Grossangriff gegen eine verteidigte Basis. Nicht mehr jede Welle gleich gross |
| 6 | Basis ausbauen, Fahrzeuge kaufen | Sie baut bis zum Fahrzeugwerk weiter und gibt ihre Punkte aus, statt auf 17.000 AE zu sitzen |
| 7 | Armee-Stärkeziel statt Kopfzahl | Grössere Armeen, weil Stärke zählt und nicht die Anzahl |
| 8 | Anmarsch nach Kosten, dann Flanke | Der Angriff läuft nicht mehr zweimal dieselbe Linie — und teilt sich auf zwei Wege, wenn das aus ihrer Sicht die Siegchance hebt |
| 9 | Basisverteidigung, zweiter Anlauf | Ein früher Konter trifft nicht mehr eine wartende Armee |
| 10 | Abstandhalten plus Aufklärung | Artillerie läuft nicht mehr auf Tuchfühlung heran und stirbt an Infanterie |
| 11 | Schwierigkeitsgrade | Drei Zahlensätze, keine zweite KI |
Dazu drei Laborarbeiten, die vorher fällig sind: eine Wellenmetrik (sonst
ist „verschieden grosse Wellen" unbelegbar), ein Anmarschszenario gegen stehende
Verteidigung (sonst ist die Flanke nur plausibel) und das Admin-Panel, in
dem man je Einheit sieht, was sie vorhat, was sie vorhatte, was sie als nächstes
tut — und es übersteuern kann: GOALS.md.
Important
Punkt 5 bis 7 hängen an einer Rückfrage, die nicht unsere Zahl ist. Das
Stärke-Wellentor ist gebaut und gemergt (r6, #72) — und im ausgelieferten
Spiel wirkungslos, weil die Armeeobergrenze bei 12 steht und der
Erreichbarkeitsdeckel daraus wieder „sammle die ganze Armee" macht. Die 12
steht in MatchRunner.cs:254, also im Netzstrang. Einseitig gemessen wirkt
30: die Legion entscheidet früher (5.005 statt 5.773 Ticks) bei 23 statt 51
eigenen Verlusten. Anheben ohne das Tor geht in die andere Richtung
(51 → 64). Details: ROADMAP.md §4.
▶ Video: die ganze Partie einmal in normalem Tempo — wie sich die NPC heute verhalten
Eine ganze Partie, ungeschnitten und ohne Zeitraffer durchlaufen. Nicht als
Vorführung, sondern als Vergleichspunkt: Wellenrhythmus, Anmarsch, Rückzug
und Zielwahl in Bewegung, so wie sie vor den nächsten Änderungen aussehen. Wenn
ROADMAP 1, 3 und 5 gebaut sind, wird gegen genau diese Aufnahme
verglichen — was sich verändert hat, sieht man dann, ohne eine Zahl zu lesen.
Punkt 1 ist inzwischen gebaut, und der Vergleich ist gezogen: die zweite
Aufnahme unten zeigt denselben Fall mit DefendHome (B004).
| Aufnahme | Nova.AiLab-Behavior-a6-20260809-181200-feat_ai-strength-wave-gate-308d8cf |
|---|---|
| KI-Verhalten | r6.E34435F9 |
| Branch / Commit | feat/ai-strength-wave-gate · 308d8cf |
| Aufgenommen | 2026-08-09, 18:12 — Laborlauf a6 aus lab-gui.sh |
| Gehört zu | Journal B003 — dieselbe Aufnahmereihe (18:10/18:11/18:12) |
Das Video zeigt einen bekannten Defekt, und zwar absichtlich. Aus genau dieser Reihe stammt die Beobachtung in B003: „Einheiten greifen Headquarter an, aber die roten NPC sammeln sich an ihrem Armee-Sammelpunkt, anstatt erst die an ihrem Headquarter zu bekämpfen." Nachgemessen liegt das HQ über 3.169 Ticks unter Feuer, davon 19 % wehrlos. Das abzustellen ist ROADMAP Punkt 1 — und diese Aufnahme ist der Beleg dafür, wie es vorher aussah.
Zur Aufnahme gehört der Stand, an dem sie entstanden ist — deshalb steht er oben in der Tabelle und im Dateinamen. Ohne ihn ist ein Video später nicht zuzuordnen und als Vergleich wertlos. Und auch das hier bleibt Diagnose, kein Nachweis: eine Aufzeichnung des Labors ist keine gespielte Partie.
▶ Video: derselbe Fall, nachher — die Basisverteidigung greift (DefendHome, r8)
Das ist die Gegenaufnahme zu der von oben, und sie ist der Grund, aus dem die erste aufgehoben wurde. Dort sammeln die roten NPC am Sammelpunkt, während ihr Hauptquartier fällt (Journal B003). Hier brechen sie ab und laufen heim. Zu sehen, ohne eine Zahl zu lesen — genau der Vergleich, den der Absatz über der ersten Aufnahme angekündigt hat, jetzt wo ROADMAP Punkt 1 gebaut ist.
| Aufnahme | Nova.AiLab-goal-base-defense-r8-20260810 |
|---|---|
| KI-Verhalten | r8.1E6E7AE3 |
| Branch / Lauf | feat/ai-goal-system · out/gui/20260810-203239-feat_ai-goal-system-dedd53b |
| Aufgenommen | 2026-08-10 — Laborlauf aus lab-gui.sh, im Player angesehen |
| Gehört zu | Journal B004 (Beobachtung) und V008 (Messung) |
DefendHomegibt es erst abr8— deshalb steht der Stand im Namen. Inr7existiert das Goal nicht, und dieselbe Szene ist dort der Defekt aus B003. Von allen archivierten Läufen trägt genau einerr8und einDefendHomeingoals.ndjson, und das ist der oben genannte; ohne diese Zuordnung wäre die Aufnahme später nicht mehr einzuordnen.
Was man sieht, und was das wert ist. Man sieht die Regel arbeiten: die Wartenden lösen sich vom Sammelpunkt und marschieren zum eigenen HQ. Das ist mehr als eine Kennzahl — es ist der Fall, den B003 beschrieben hat, in Bewegung und nachprüfbar. Es ist trotzdem kein Nachweis im Sinne dieses Repos: eine Aufzeichnung des Labors ist keine gespielte Partie, und der Abschnitt „Im laufenden Spiel gesehen" im PR-Text bleibt leer, bis jemand sie in Unity gespielt hat. Was die Messung dazu sagt — Wehrlosigkeit 96 % → 60 %, Partie 3.213 → 6.490 Ticks, und die Legion verliert sie trotzdem — steht in V008.
▶ Video: die Oberfläche im Betrieb, mit einem Gefecht der beiden Armeen
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 checkoutdrü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 |
| ROADMAP.md | was gebaut ist, was als nächstes kommt, in welcher Reihenfolge und was Rückfrage an den Maintainer ist — die einzige Nummerierung |
| NEXT-STEPS.md | warum die Punkte so eingeordnet sind: sieben Beobachtungen, sortiert danach, was ein Spieler in einer Partie merkt |
| KAMPFSTAERKE.md · VERTEIDIGUNG.md · GOALS.md | die Detailpläne: Kampfpunkte und Wellengrösse · Basisverteidigung · Goal-System, Flanke und Admin-Panel |
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/HashKrieg |
das Spiel selbst — Ziel jedes Pull Requests. Wird nur gefetcht, nie dorthin gepusht. Vormals VibecodingGermany/Project_Nova; der Fork und der Checkout daneben heissen weiterhin Project_Nova |
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 UmgebungEin 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 HashKrieg
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.
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 outDas 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 sonstDas .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.
| 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.
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.)
| 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 |
| 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 |
| 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 |
| Datei | Inhalt |
|---|---|
SeedSeries.cs / SweepRunner.cs |
Parallellauf mit Determinismus-Stichprobe (jeder 20. Lauf doppelt) |
| Datei | Inhalt |
|---|---|
DuelArena.cs / DuelTable.cs |
AE-Parität, drei Abstände, beide Richtungen, Belagerung |
| Datei | Inhalt |
|---|---|
MovementScenarios.cs |
arrival, blocking, standoff, detour — Hindernisse sind Daten, nicht Code |
| 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 |
| 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.cs … CompareCommand.cs |
je ein Modus, je eine Datei: match, sweep, duel, movement, compare |
| 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).
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.
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.
