10. Juli 2026 · Beta-Phase

Block #05 — Beta gestartet: Taktik, Portraits und 16.052 Spieler

Phase 3 ist abgeschlossen. Die Beta ist draussen. Ich hab zwei Tage lang gar nichts gemacht. Keine IDE, kein Code, kein GitHub. Counter-Strike und nichts anderes. Das war gut. Sehr gut sogar.

Dann hat es mich wieder gepackt und ich war zurueck. Wer haette das gedacht.

Die ersten Beta-Wochen waren intensiver als ich erwartet hatte — nicht wegen Bugs, sondern wegen Perspektive. Wenn man das eigene Spiel zum ersten Mal ohne Entwicklerbrille spielt aendert sich die Wahrnehmung fundamental. Dinge die man fuer gut genug gehalten hat sehen ploetzlich aus wie offene Baustellen. Das Taktikfeld zum Beispiel: Graue Kreise mit Namenskuerzeln. Funktioniert. Aber es fuehlt sich nicht nach Spielern an. Es fuehlt sich nach Schachfiguren an. Das war das erste was auf die Liste kam.

P1: Echte Portraits auf dem Taktikfeld

Das erste Beta-Feature: echte Spielerportraits. Das System ist direkt an das Datenbankfeld Player.PhotoPath in der MasterDB gebunden. Wenn ein Bild vorhanden ist wird es auf dem Taktikfeld als kreisfoermig zugeschnittenes Portrait angezeigt. Wenn kein Bild vorhanden ist gibt es den alten Token-Fallback — Namenskuerzel auf farbigem Hintergrund.

Die Bild-Verarbeitung passiert beim Import: PNG-Dateien aus dem Ordner Imports/Portraits/ werden beim MasterDB-Import eingelesen. Die technischen Anforderungen sind dokumentiert: PNG, 512×512 Pixel, quadratisches Format. Das Interface macht daraus einen 64px-Kreis auf dem Taktikfeld oder einen 128px-Kreis in der Spieler-Detailansicht. Das Portrait-Masking — das kreisfoermige Zuschneiden — laeuft zur Ladezeit einmal und wird als Textur gecacht. Kein Rechenaufwand waehrend des Renderings.

Der technische Aufwand war ueberschaubar. Die visuelle Auswirkung war unverhaeltnismaessig gross. Ploetzlich stehen auf dem Taktikfeld erkennbare Menschen und keine abstrakten Markierungen. Das ist der Unterschied zwischen einer Simulation die man bedient und einem Spiel das man wirklich spielt.

Freies Ziehen auf dem Taktikfeld

Freies Ziehen von Spieler-Icons auf dem Taktikfeld war lange auf der Feature-Liste. Das erste Design-Doc begann mit “sollte schnell gehen”. War es nicht.

Die technische Grundimplementierung war in Ordnung: GLFW-MouseDrag-Events abfangen, Position des angeklickten Spieler-Icons tracken, Render-Position anpassen. Das waren ein paar Stunden. Das eigentliche Problem war das Speichern.

Ein Spieler hat auf dem Taktikfeld eine Position die aus seiner Formations-Rolle kommt: Ein linker Mittelfeldspieler steht links im Mittelfeld. Die Formation bestimmt das. Wenn ich den Spieler jetzt frei positioniere — wohin speichere ich das? Die Formation kann ich nicht beschreiben, sie gilt fuer alle Karrieren die diese Formation benutzen. Die SaveDB muss die Override-Koordinaten speichern: “In dieser Karriere, in dieser Formation, steht Spieler X an Position Y.”

Das bedeutete eine neue EF Core Migration: AddTacticLayoutJson. Die SaveDB bekommt ein neues JSON-Feld das pro Formation und pro Spielstand die Override-Koordinaten speichert. Der FormationLayoutResolver kombiniert beim Rendern das Basis-Template der Formation mit den optionalen Override-Koordinaten aus dem Spielstand. Wenn keine Overrides vorhanden sind wird das Template verwendet. Wenn Overrides vorhanden sind gelten diese — transparent, keine sichtbare Unterschied im Handling.

Das Ergebnis: Man zieht einen Spieler an eine neue Position, speichert, laedt das Spiel neu — die Position ist noch da. Klingt selbstverstaendlich. Ohne das korrekte Datenmodell waere das Ziehen eine Geste ohne Konsequenz. Mit korrektem Speichern ist es ein echtes taktisches Werkzeug.

Formationsvarianten — Standard, Offensiv, Defensiv

Ein Team hat nicht eine Formation — es hat mehrere Varianten fuer verschiedene Situationen. Das war ein Feature das ich schon lange designt aber noch nicht implementiert hatte.

Drei Varianten pro Karriere: Standard fuer das normale Spiel. Offensiv fuer Spiele wo man ein Tor braucht — aggressivere Pressinglinie, hoehere Besetzung des letzten Drittels, anderes Tempo-Setting. Defensiv fuer Spiele wo man ein Ergebnis verteidigt — kompakteres Mittelfeld, niedrigere Pressinglinie, andere Spieleraufstellung.

Jede Variante traegt vollstaendig unabhaengige Daten: eigene Formation, eigene Pressing-Einstellungen, eigenes Spieltempo, eigene Startelf, eigene Layout-Overrides. Das ist deutlich mehr Zustand als urspruenglich geplant. Dafuer eine saubere EF Core Migration: AddTacticVariantsJson fuegt einen VariantsJson-Block zur TacticState-Entitaet hinzu, zusammen mit einem ActiveVariantKey-Feld das festhalt welche Variante gerade aktiv ist. Der Wechsel zwischen Varianten passiert in der Taktik-Ansicht mit einem Tap — man schaut auf das offensive Setup, prueft den Kader, wechselt vor dem Anpfiff oder in der Halbzeit.

Die Formationsbibliothek — zwoelf Formationen speichern

Direkt daran anschliessend: die Formationsbibliothek. Bis zu zwoelf vollstaendige Formationen pro Spielstand speicherbar. Auto-Namen nach der Formations-Notation (4-3-3, 4-2-3-1, 3-5-2 usw.) oder manuell vergeben fuer eigene benannte Setups. Varianten koennen gespeicherte Bibliotheks-Formationen mit einem Klick anwenden — man baut einmal eine perfekte 4-3-3-Variante fuer gegnerische 4-4-2-Setups und kann sie in jeder Saison aus der Bibliothek laden.

Neue EF Core Migration: AddSavedTacticFormationsJson. Das ist die dritte Migration in dieser Feature-Gruppe. Ich hab sie bewusst separat gehalten: Jede Migration hat eine klare, beschreibbare Aufgabe. Keine “AddTacticStuffJson” die drei verschiedene Dinge auf einmal hinzufuegt. Migrations sollen lesbar sein wenn man in einem Jahr die Entwicklungsgeschichte nachvollzieht.

Deep-Talent-MasterDB — 36 Ligen, 16.052 Spieler

Die alte MasterDB war ausreichend fuer die Entwicklungsphase. Fuer eine Beta die andere spielen sollen war sie es nicht. Zu wenig Ligen, zu wenig geografische Breite, zu wenig Spieler fuer eine richtige Spielwelt.

Ich hab Wochen damit verbracht die Deep-Talent-MasterDB aufzubauen: 36 Ligen aus mehr als 20 Laendern, vier bis fuenf Wettbewerbsebenen pro Land, 16.052 Spieler mit vollstaendigen Attribut-Profilen. Das ist jetzt der offizielle Projektstandard gespeichert unter Data/masterdb.sqlite.

Die Datenqualitaet war genauso aufwendig wie die Datenmenge. Attribut-Verteilungen muessen realistisch sein: Ein Bundesliga-Kader soll sich statistisch anders anfuehlen als ein Drittliga-Kader — nicht nur in der Gesamtstaerke, sondern in der Tiefe des Kaders, in der Talent-Verteilung, in den Persoenlichkeitswerten. Ich hab Export-Skripte geschrieben die die statistischen Verteilungen der DB pruefen und Ausreisser markieren. Dann manuell korrigiert. Dann nochmal geprueft.

Spielwelt-Selektion — nicht alle 36 Ligen muessen aktiv sein

Mit 36 Ligen kam sofort eine neue Frage: Braucht jede Karriere wirklich alle 36 Ligen? Die Simulation ist performant, aber viele Spieler interessieren sich vielleicht nur fuer die Top-Ligen Europas. Und auf schwacherer Hardware ist weniger Last eindeutig besser.

Die Spielwelt-Selektion loest das: Beim Start einer neuen Karriere waehlt der Spieler welche Laender und Ligen aktiv sein sollen. Was nicht aktiv ist wird physisch nicht in den Spielstand uebernommen — keine versteckten Daten, keine Ligen die im Hintergrund simuliert werden ohne dass jemand davon profitiert. Drei Service-Klassen verwalten das: SimulationWorldCatalogService stellt die vollstaendige Liste aller verfuegbaren Ligen bereit. SimulationWorldSelectionService verwaltet die Auswahl des Spielers. SimulationWorldPruner entfernt alle nicht-ausgewaehlten Daten beim Erstellen der SaveDB.

Eine Weltzusammenfassung zeigt vor der Bestaetigung: Vereinsanzahl, Spieleranzahl, Ligaanzahl, geschaetzte Ladezeit fuer Spieltage — damit der Spieler weiss womit er es zu tun hat bevor er bestaetigt.

102 von 102 Tests gruen nach dem vollstaendigen System-Rollout. Editor-Smoke und UI-Smoke verifiziert. Das war der Stand nach der ersten Beta-Welle. Die naechste Phase ist die groesste visuelle Arbeit des Projekts: der Visual Track.

Kommentar schreiben