TechNeko
Referenzen
SELECTED WORKS / 2026-09-23

POLYSEA

Übernimm das Kommando in der Karibik des Jahres 1650: Seeschlachten, Enterkämpfe, Häfen und Handelsrouten, allein oder online mit Freunden.

Das Spiel

Getümmel vor einer Hafeninsel: Das Pulvermagazin eines Piratenschiffs explodiert, Kugeln fliegen zwischen den Schiffen, auch das Seefort feuert

Spielszene. Das Getümmel wurde per Skript aufgestellt, auch der Zeitpunkt der Salven; Schiffe, Kugeln, Explosion und Fortfeuer stammen aus dem Spiel selbst.

POLYSEA ist ein Seefahrts-Strategiespiel in der Karibik des Jahres 1650. England und Spanien liegen im Krieg, Piraten plündern die Seewege, und nachts zieht eine Skelettflotte über das Meer. Diene einer Marine oder hisse die schwarze Flagge: Führe deine Flotte in Breitseiten-Gefechte, entere feindliche Schiffe mit Enterhaken, wirb Offiziere an und teile sie den Stationen zu, baue Häfen aus, eröffne Handelsrouten und suche auf den Inseln und in Wracks nach Schätzen.

PunktDetails
GenreSeefahrts-Strategie
ModusEinzelspieler / online über Steam
PlattformWindows
SpracheChinesisch (weitere Sprachen geplant)
Spielbare SeitenEngland, Spanien, vier Piratenbanden
EngineUnity 6
EntwicklungTechNeko
StatusIn Entwicklung, Testversion v0.8.50

Besonderheiten

Seegefechte

Jede Kanone lädt und feuert für sich. Es gibt sechs Geschützarten und vier Munitionsarten; Wind, Feuerbereich, Wetter und Sicht entscheiden mit. Befehlige die ganze Flotte aus der Kartenansicht oder wechsle in die Kapitänsansicht und übernimm selbst das Steuer.

Entern

Enterhaken ziehen die Schiffe zusammen, Planken gehen hinüber, und der Kampf geht in Echtzeit an Deck weiter. 29 Entertaktiken stehen bereit, und jeder Matrose ist eine eigene Figur auf dem Feld.

Enterkampf an Deck, Planken liegen zwischen den beiden Schiffen

Besatzung

Kapitän und Offiziere haben eigene Fähigkeiten. Teile sie den passenden Kabinen zu und statte sie mit Waffen und Schätzen aus. Offiziere sammeln Erfahrung durch die Arbeit an ihrer Station und durch ihre Taten.

Häfen und Handel

Sechs Hafengebäude lassen eine Stadt vom Zeltlager zu Palästen und Festungen wachsen. Handelskonvois verdienen auf ihren Routen, und im Hafenamt ernennst du Gouverneur und Garnison.

Eine Hafenstadt auf der höchsten Stufe, davor das Seefort

Die Welt

Vier Mächte, Nebel des Krieges, Wetter, Tag und Nacht, Inseln zum Erkunden, Schätze und Fischfang mit dem Netz. Unerforschtes Meer ist eine handgezeichnete Pergamentkarte, auf der jede Insel so aussieht, wie sie wirklich ist.

Die Skelettflotte bei Einbruch der Nacht

Online

Spiele über Steam mit Freunden und kämpfe Seite an Seite auf demselben Meer.


Entwicklungsrückblick

Der Rest dieser Seite ist das Entwicklungsprotokoll des Spiels, mit Schwerpunkt auf den technischen Lösungen und dem Umgang mit Problemen.

Das Projekt begann in den frühen Morgenstunden des 23. September 2026. Der erste Commit fiel auf 04:05, um 04:58 gab es bereits einen Prototyp, in dem man segeln, Breitseiten tauschen, entern und in den Hafen einlaufen konnte. Im Monat danach schrieb Claude Code den C#-Code und die Shader; der Mensch gab die Richtung vor, nahm die Arbeit im Spiel ab, suchte die Grafik-Assets aus und hatte beim Sound das letzte Wort.

Das Datum dieses Artikels ist der Starttag des Projekts; inhaltlich reicht er bis zur v0.8.50 vom 24. Oktober 2026.

PunktWert
Entwicklungszeit2026-09-23 bis 2026-10-24, insgesamt 32 Tage, davon 30 mit Commits
git-Commits663
SpielcodeC#, 259 Dateien, 87.031 Zeilen
Editor-WerkzeugeC#, 32 Dateien, 9.748 Zeilen
Eigene Shader12
Sound410 Aufnahmedateien, 191 Sound-Slots
NetzwerkprotokollVersion 24

Kurzfassung

  • Jede Änderung braucht eine Baseline. Am Meer entstanden in weniger als 23 Stunden 36 Commits, die am Ende komplett zurückgerollt wurden. Die spätere Regel: immer nur eine Schicht ändern und bei jedem Schritt einen Vorher-Nachher-Vergleich zeigen.
  • Der Agent sieht und hört nichts, die Prüfmittel bestimmen die Qualität. Das Bild prüfen Screenshots und Einzelbildvergleiche, den Sound Lautheitsmessungen, die Kämpfe automatische Testläufe. Was sich nicht messen lässt, lässt sich auch nicht beheben.
  • Quantifizieren, was sich quantifizieren lässt, aber auf die Stichprobe achten. Für die Balance des Enterkampfs liefen über hundert Simulationen; ein Ergebnis aus 16 davon kehrte sich mit einem anderen Zufalls-Seed ins Gegenteil um.
  • Bestanden im Editor heißt nicht bestanden. Einige Arten von Rendering-Problemen treten erst im Build auf, deshalb kam später ein eigener Selbsttest für den Build hinzu.
  • Teure Effekte gehören dorthin, wo sie gebraucht werden. Echte Wellen scheiterten in der Draufsicht zweimal und funktionierten erst, als sie nur noch in der tief liegenden Kapitänsansicht liefen.

Aufbau

PunktDetail
AgentClaude Code (Desktop-App), das gesamte Projekt lief als eine durchgehende Sitzung
ModellHauptsächlich Claude Opus 5.5, zeitweise Claude Sonnet 5.5
EngineUnity 6000.3.24f1, URP 17.3 (Forward+)
MultiplayerNetcode for GameObjects 2.13.3, Steamworks.NET, eigene Steam-Transportschicht
WasserStylized Water 3 plus eigene Shader
GrafikModelle der Reihe Synty POLYGON, UI-Assets von Classic RPG GUI, Animationspaket Human Mega
Rolle des MenschenRichtung vorgeben, im Spiel abnehmen, Assets aussuchen, Sound probehören und bewerten, entscheiden

Die Oberfläche ist vollständig im Code aufgebaut (uGUI). Das Verzeichnis UI hat 19.076 Zeilen und ist nach der Weltlogik World (23.328 Zeilen) das zweitgrößte Modul. Ein Agent ändert Code zuverlässiger, als er im Editor Steuerelemente zieht, und jede Änderung lässt sich in git Zeile für Zeile nachvollziehen.

Zeitleiste

DatumMeilenstein
09-23Erster Commit, 53 Minuten später ein spielbarer Prototyp; noch am selben Tag Enterkampf in der Nahansicht und Landungsgefechte
09-25Fraktionen, Seed-Karten, Quests; Meer auf Stylized Water 3 umgestellt
09-26v0.2 für Freunde (73 MB); Soundeffekte durch ein Asset-Pack ersetzt
10-01Test mit echten Wellen, 13 Minuten nach dem Commit zurückgerollt
10-02Multiplayer-Machbarkeitsaudit: 44.084 Zeilen Code, 8 Haupthindernisse
10-03Sechs Phasen des Multiplayer-Umbaus und Steam-Transportschicht, 53 Commits an einem Tag
10-05Sound-Anforderungsdokument: 185 Soundeffekte, 13 Musikstücke
10-09 bis 10-10Vier Runden Aufnahmen eingebunden, Systeme für Musik und Umgebungsklang
10-12 bis 10-13Realistisches KWS-Wasser und Sturmsee, nach 36 Commits alles zurückgerollt
10-15 bis 10-17Enterkampf auf Echtzeittaktik umgestellt
10-19 bis 10-20Fog of War als Pergamentkarte; im Kampf entspricht eine Person einer Figur
10-21Konflikte beim gemeinsamen Konto im Multiplayer gelöst
10-22 bis 10-23Kapitänsansicht, Dünung, Kumuluswetter, Regendunst
10-24v0.8.50: Kopfgeldaufträge für Piraten und Informationen aus der Taverne

Das Meer: vier Anläufe, zurück zum Start und von dort weiter

Das Wasser ist der Teil des Projekts, der am häufigsten neu gemacht wurde. Die Standardkamera des Spiels steht etwa 340 Meter über dem Meer. Diese Vorgabe bestimmte fast alle späteren Abwägungen, wirklich verstanden haben wir sie aber erst durch Versuch und Irrtum.

Erste Version: LOD-Mesh raus, das Meer ist ein großes Quad

Als Stylized Water 3 am 25. September eingebunden wurde, kam zunächst das mitgelieferte Ozean-LOD-Mesh des Plugins zum Einsatz. Jeder LOD-Ring rastet dabei auf seiner eigenen Gitterweite ein, benachbarte Ringe überlappen sich an den Nahtstellen. Das Wasser ist halbtransparent und wurde in den Überlappungen doppelt gezeichnet, also lag über dem Flachwasser ein zusätzlicher querlaufender Farbstreifen, der beim Zoomen mitwanderte. Dazu kommt, dass unser Meer unlit im Toon-Stil schattiert ist und die Höhenunterschiede des Meshes kaum sichtbar waren.

Deshalb wurde das Meer zu einem einzigen großen Quad, und die Ausdruckskraft wanderte in eine darüberliegende Schicht, den selbst geschriebenen Shader SeaStrokes. Nach und nach kam dort Folgendes hinzu:

  • Kräuselung: zwei Normal-Maps, in mehreren Maßstäben gesampelt und in verschiedene Richtungen driftend, daraus nach Sonnenrichtung berechnete Toon-Licht- und Schattenbänder und feine Glanzlinien.
  • Maske: großflächiges Rauschen macht einen Teil des Meeres rauer als den anderen und überdeckt die Kachelung.
  • Glitzern: kurz aufblitzende helle Punkte auf Wellenflächen, die zur Sonne zeigen; sie verschwinden bei bedecktem Himmel, im Schiffsschatten und bei zu hoher Kamera.
  • Schatten: Das Meer selbst ist unbeleuchtet, die Schatten von Schiffen, Segeln und Inseln zeichnet diese Schicht aufs Wasser.
  • Tiefen-Ausblendung: Die Wassertiefe kommt aus der Depth-Map, im Flachwasser blendet die ganze Schicht aus und lässt Sandgrund und Uferschaum sichtbar.

Zweiter Anlauf: echte Wellen, 13 Minuten nach dem Commit zurückgerollt

Am 1. Oktober wurden echte Wellen getestet: ein Wasser-Mesh von 800 × 800 Metern, das der Kamera folgt, dazu dieselben Gerstner-Wellen auf der CPU, damit die Schiffe mitschaukeln. Aus der Nähe sah das gut aus, aber es gab drei Probleme:

  • Kurze Wellen erzeugten auf dem 3 bis 4 Meter großen Raster Aliasing, das beim Herauszoomen zu regelmäßigen Mustern wurde.
  • Die Wellen selbst fielen kaum auf, die Schiffe schaukelten dagegen hektisch und abgehackt.
  • Wenn der Wasserspiegel sank, legte der Schaumring am Ufer gelegentlich Felsen frei.

Die Projektleitung kam zu dem Schluss, dass es mehr Probleme dieser Art geben würde als gedacht, und stellte das Thema zurück, bis es eine wirklich gute Lösung gibt. Der Code wurde zurückgerollt und existiert nur noch in der git-Historie.

Dritter Anlauf: realistisches Wasser, nach 23 Stunden alles zurückgerollt

Am 12. Oktober kam KWS Water System zur Probe ins Projekt, gleichzeitig TENKOKU Dynamic Sky. Letzteres unterstützt nur die Built-in-Pipeline, zeichnete unter URP gar nichts und warf zudem Null-Referenzen, schied also sofort aus. Die Zahlen von KWS sahen gut aus: Im Editor, gemessen bei 2560×1440 auf einer RTX 4070 Ti, kostete es gegenüber unserem Meer nur 0,3 bis 1,7 ms mehr GPU-Zeit und 155 MB mehr Grafikspeicher, die CPU-Last blieb gleich. Noch am selben Abend ging es in den Hauptzweig, und in den Einstellungen kam ein Umschalter zwischen Realistisch und Toon hinzu.

Gleiche Partie, gleicher Zeitpunkt, gleiche Kameraposition, nur das Wasser ist ein anderes

Obere Reihe: die Standardkamera des Spiels auf 340 Metern, untere Reihe: Nahaufnahme aus tiefer Kameraposition. Linke Spalte: das damalige Toon-Meer, mittlere Spalte: KWS mit Standard-Preset, rechte Spalte: KWS in karibischen Farben. In der Nähe ist KWS klar besser, auf der Höhe, die das Spiel tatsächlich nutzt, verschwindet der Vorteil.

Es folgte eine ganze Reihe von Nachbesserungen:

  • Die Wellen von KWS sind echte Geometrie, Wellenkämme verdeckten die Reichweitenkreise auf dem Wasser. Alle Markierungen auf dem Wasser bekamen deshalb einen Shader, der die Vertices entlang der Blickrichtung anhebt; das Maß der Anhebung folgt der aktuellen maximalen Wellenhöhe.
  • Nachts wirkte das Wasser flach und dunkel. Der Mond steht im Süden, die Kamera blickt nach Norden, eine Spiegelung kam so nie zustande. Übrig blieb, die Wasserfarbe abzudunkeln und den Schaum aufzuhellen.
  • Das dynamische Kielwasser sah bei 260 Metern Reichweite und 4 Pixeln pro Meter aus wie Kräuseln auf einem Teich, die Proportionen stimmten offensichtlich nicht.
  • Nach dem Wechsel von KWS2 zurück zu KWS1 stürzte der Editor beim Start des Play-Modus ab, Meldung: d3d11: out of range sampler binding point. Ursache war ein beschädigter Shader-Cache; nachdem Library/ShaderCache verschoben war, lief es wieder.

Parallel entstand auf dem Toon-Meer ein Sturm: Schaumkronen, Brecher, Bugwellen, Gischt. Nach dem Anspielen zog die Projektleitung zwei Schlüsse: Aus Spielhöhe ist KWS schlechter als das ursprüngliche Toon-Meer, und auch das Toon-Meer selbst war inzwischen verschlimmbessert.

Der erste Rollback ging nur bis vor KWS, die am selben Tag hinzugekommenen Schaumkronen und der Schaum blieben erhalten. Die Projektleitung wollte aber das Meer von vor KWS und vor der gesamten Arbeit am Meer. Erst der zweite Rollback auf v0.8.8 beendete die Sache. Von der Aufnahme von KWS in den Hauptzweig bis zum vollständigen Rollback vergingen weniger als 23 Stunden und 36 Commits. Der Code ist in einem git-Tag gesichert; übrig blieben nur drei Dinge: Regen, der bei jedem Zoom fein aussieht, langsame Wetterübergänge und verlangsamte Sonnenstrahlen zwischen den Wolken.

Geblieben ist außerdem ein Satz Regeln, jede davon mit einem verworfenen Ansatz bezahlt:

RegelGrund
Für Schaum keine zellularen TexturenDas Netzmuster ist sofort als Textur erkennbar
Brecher nicht über ein glattes räumliches Phasenfeld timenIhr Entstehen läuft dann wie eine Wellenfront durchs Bild und sieht aus wie davonrasende weiße Punkte. Jeder Brecher braucht eine eigene Position, Größe und Lebensdauer
Bei Schaum auf bewegten Objekten das Rauschen im Objektraum sampelnEine Maske im Schiffskoordinatensystem mit Rauschen in Weltkoordinaten verändert die Kante in jedem Frame, sie scheint zu zittern
Flow-Offset integrieren, nicht t * speedSonst springt bei einer Geschwindigkeitsänderung das ganze Bild
Beim Lesen von SW3-Rauschtexturen zuerst pow(x, 1/2.2)Sie sind als sRGB importiert; der Shader liest einen Mittelwert von 0,24 statt 0,5, alle Schwellenwerte liegen daneben
Bewegung auf dem Meer mit einer mitfahrenden Kamera messenDie „schnell dahinrollende Schicht“, die die Projektleitung immer wieder erwähnte, war in unseren Tests mit fester Kamera gar nicht zu sehen

Die letzte Regel war die wertvollste. Die Augen des Agenten sind die Kamera; steht sie falsch, helfen auch noch so viele Screenshots nicht. Danach wurden Probleme mit dem Meer immer mit der mitfahrenden Kamera als kurzer Clip aufgenommen und als Filmstreifen mit einem Bild pro 1/3 Sekunde ausgewertet.

Vierter Anlauf: immer nur eine Schicht ändern

Neustart beim Meer von v0.8.8. Die Regel: immer nur eine Schicht ändern, einen Vorher-Nachher-Vergleich zeigen und alles andere unberührt lassen.

Schritt eins galt dem, was die Projektleitung die „Sandkörnigkeit am Meeresgrund“ nannte. Gefunden wurde sie erst durch das Abschalten einer Schicht nach der anderen: Den Grund auszublenden, ihn um 25 Meter abzusenken und dann abzudunkeln änderte nichts am Bild, am Grund lag es also nicht. Mit abgeschaltetem SeaStrokes blieb das Muster bestehen, es kam also aus der Normal-Map des Wassers selbst. Diese Textur wiederholte sich alle 5 Meter, die zweite Schicht hatte genau den halben Maßstab, ein sehr gleichförmiger Rhythmus. Die Lösung: 12,5 Meter, zweite Schicht im Maßstab 0,37.

Schritt zwei war hexagonales Tiling. Dafür wurde SampleNormals in Normals.hlsl von Stylized Water 3 geändert: Die Ebene wird in Sechsecke geteilt, die Textur in jedem Sechseck einzeln verschoben und gedreht, und die drei nächstgelegenen Zellen werden gemischt. So sind weder Wiederholungen noch Nähte zu sehen. Store-Assets kommen nicht in git, deshalb liegt die geänderte Datei zusammen mit dem Original im Repository unter ThirdPartyPatches/, mit dem Hinweis, dass sie nach einem Neuimport des Plugins wieder darübergespielt werden muss.

Kapitänsansicht: Die Wellen finden endlich ihren Platz

Am 22. Oktober kam eine Kapitänsansicht nach dem Vorbild von World of Sea Battle hinzu: Die Kamera sitzt dicht hinter dem Schiff, gesteuert wird direkt mit WASD. Aus tiefer Position verriet sich das flache Meer sofort, und die Dünung des Titelbildschirms hatte der Projektleitung schon immer gefallen. Also wurden echte Wellen nur in dieser Ansicht aktiviert:

  • Um die Kamera liegt ein WaterGrid von 760 Metern mit 2 Metern Vertexabstand, das ihr folgt.
  • In der Ferne bleibt das flache Meer; das große Quad wird in diesem Fall zu einem Ring mit quadratischem Loch in der Mitte.
  • Die Wellenhöhe blendet zwischen 140 und 300 Metern Kameraabstand auf null aus, sodass der Mesh-Rand nahtlos ans flache Meer anschließt.
  • Die Schiffe berechnen ihre Höhe auf der CPU mit derselben Gerstner-Wellenform, derselben Uhr und derselben Ausblendkurve wie das Plugin und folgen ihr geglättet. Die Spielregeln rechnen weiterhin mit einem flachen Meer.

Kapitänsansicht: Das Schiff neigt sich in der Dünung, der grüne Bereich an Steuerbord ist der Feuerbereich

Das Meer der Draufsicht blieb völlig unangetastet, und die drei Probleme vom 1. Oktober fanden jeweils eine Antwort: Diese Ansicht liegt tief und zoomt nicht so weit heraus, dass Aliasing sichtbar wird; die Schiffsbewegung ist geglättet und zuckt nicht mehr; eine Kontrolle der Ufer nahe der Hauptinsel zeigte keine freigelegten Felsen.

Beim ersten Öffnen der Kapitänsansicht rasten die Wellen allerdings wie im Schnelldurchlauf dahin. Die Ursache lag zwischen zwei Systemen: SeaMood ändert in jedem Frame _Direction des Wassermaterials, damit das Muster mit dem Wind zieht, und das Plugin multipliziert bei der Berechnung der Wellenphase genau diese Richtung mit den Weltkoordinaten. Mehrere Kilometer vom Ursprung entfernt lässt schon eine winzige Richtungsänderung die Wellenform gleiten: Bei einem Schiff in 2,5 km Entfernung ergab das etwa 4,5 m/s zusätzliche Geschwindigkeit, und die Richtungskomponente stauchte die Wellenform außerdem um den Faktor 1,8. Die Lösung ist diese Regel: Bei einem Material mit aktiven Wellen muss _Direction konstant bleiben. Für die Höhenberechnung auf der CPU muss außerdem das globale _CustomTime gelesen werden, nicht WaterObject.CustomTime, dessen Getter immer -1 liefert.

Der Nebel hatte ein ähnlich verstecktes Problem. Das Wasser nahm überhaupt keinen Nebel an, der Horizont sah aus wie mit dem Messer geschnitten. Wie sich zeigte, stand die Nebelintegration in den Importeinstellungen des Plugin-Shaders auf SC Post Effects, das im Projekt gar nicht installiert ist. Erst die Umstellung auf Unity-Nebel behob das. Auch dieser Ordner kommt nicht in git, deshalb steht es in den Notizen: Nach einem Neuimport muss die Einstellung erneut gesetzt werden.

Markierungen auf dem Wasser

Noch ein kleines, aber typisches Problem. Die Fächer der Feuerbereiche sollen nicht dunkler werden, wenn sich mehrere Kanonen überlagern, deshalb sorgt der Stencil-Buffer dafür, dass jedes Pixel nur einmal gefärbt wird. Zuerst lief das über Bit 64, und die Fächer waren auf dem Wasser überhaupt nicht zu sehen: Das Wasser von SW3 schreibt in jedes Pixel den Stencil-Wert 65, belegt also Bit 1 und Bit 64. Mit 128 funktionierte es. Später stellte sich heraus, dass bei überlappenden eigenen und gegnerischen Feuerbereichen nur die zuerst gezeichnete Seite zu sehen war, also bekam jede Seite ein eigenes Bit: eigene Seite 128, Gegner 32.

Musik und Soundeffekte: von der Code-Synthese zur Mess-Pipeline

Erste Version: Synthese im Code

Weniger als einen Tag nach Projektstart konnte SfxSynth Kanonenschüsse, Explosionen, Wasserspritzer, Donner und Regenloops per Code synthetisieren, ganz ohne Assets. Das Problem lag ebenso auf der Hand: Der Agent hört nichts. Er kann Spektren berechnen, aber nicht beurteilen, ob etwas gut klingt. Am 26. September kamen Soundeffekte aus einem Asset-Pack, gestaffelt nach Kanonengewicht. Am 2. Oktober meldeten Spieler, dass Wellen und Starkregen zu laut seien; daraufhin kamen vier Kanäle für Umgebung, Kampf, Oberfläche und Musik sowie eine Gesamtlautstärke hinzu.

Sound als eigenes Gewerk mit Anforderungsdokument

Am 5. Oktober entstand ein Anforderungsdokument für den Sound: 185 Soundeffekte und 13 Musikstücke, jeder Eintrag mit Nummer, Auslöser, Dauer, Wiedergabeart, Anzahl der Varianten, Priorität (P0 bis P2), aktuellem Status und Klangvorgaben. Auch die Lieferspezifikation wurde festgeschrieben: WAV, 44,1 oder 48 kHz, Soundeffekte mono, Umgebungsklang und Musik stereo, Dateiname <id>_<n>.wav, keine verständliche Sprache, damit der Weg für spätere Mehrsprachigkeit offen bleibt.

Der Sound entstand außerhalb dieses Projekts nach dem Dokument. Die erste Lieferung umfasste 424 WAV-Dateien, die ein Mensch einzeln probehörte und bewertete; die Ergebnisse wurden als JSON gespeichert. Der Agent beurteilt hier keine Klangqualität, er bindet nur die freigegebenen Dateien ein und setzt die passende Zeile im Dokument auf „eingebunden“.

RundeDatumNeu freigegebene DateienDateien gesamtSound-Slots gesamt
Erste Runde10-09256256122
Zweite Runde10-1064320152
Dritte Runde10-1053373178
Vierte Runde10-1036409191

Ein späteres Musik-Update brachte ein weiteres Stück, jetzt sind es 410 Dateien. Diese Sounds sind aus 519 Quellen unter CC BY, CC0 und aus der Public Domain bearbeitet, durch Schnitt, Schichtung, Filterung, Tempo- und Tonhöhenänderung und Ähnliches. Die Namensnennungsliste erzeugt ein Skript aus der Asset-Tabelle; sie wird mit dem Spiel ausgeliefert und hat über 3.000 Zeilen. Von Hand wäre diese Liste nicht zu pflegen, und die Namensnennung ist bei CC BY zwingend.

Lautheit: BS.1770 statt Ohren

Der Agent hört nichts, aber er kann messen. Das war die nützlichste Entscheidung in diesem Abschnitt. Tools/Audio/audio_loudness.py misst jede Datei nach ITU-R BS.1770, also die K-gewichtete Lautheit, die auch der Rundfunk verwendet:

TypMessung
Einmalige SoundeffekteDie lautesten 400 Millisekunden (momentary max); Kürzeres zählt als 400 Millisekunden
Loops und MusikIntegrierte Lautheit über die gesamte Länge (integrated LUFS)

Eine Tabelle legt dann für jede Nummer die relative Lautheit im Spiel fest, zum Beispiel Schiffskanone -18, schwere Kanone -17, Drehbasse -21, Button-Klick -32, Mouse-Hover -40, Meereswellen -34, Segelmusik -32. Daraus berechnet das Skript die Lautstärke jedes Sound-Slots und die Verstärkung der Varianten einer Nummer und erzeugt RecordedSoundTable.cs.

Die ganze Tabelle ist am Kanonenschuss verankert: Der aufgenommene Kanonenschuss soll dort liegen, wo der alte lag, insgesamt ein Offset von -4,8 dB. So behalten alte, noch nicht ersetzte Sounds ihren Platz im Mix. Die Messergebnisse werden pro Datei-sha gecacht, die nächste Runde misst nur neue Dateien. Danach stand für jede Runde ein fester Ablauf in fünf Schritten: Dateien kopieren, messen und Lautheitstabelle erzeugen, importieren, Auslöser für neue Nummern ergänzen, Dokument aktualisieren.

Musik und Umgebungsklang

Music.cs kennt neun Stimmungen: Titel, Segeln bei Tag, Segeln bei Nacht, Sturm, Seegefecht, Entern, Hafen, Inselerkundung, Geisterschiff. Zwei AudioSources blenden ineinander über, normalerweise in 2,5 Sekunden, beim Eintritt in einen Kampf in 1 Sekunde.

Dass die Musik nicht nervt, liegt an ein paar kleinen Regeln: Ruhige Stücke laufen einmal durch, dann folgen 25 bis 70 Sekunden Stille bis zum nächsten Stück, nur angespannte Stücke laufen im Loop; Kampfmusik läuft nach dem letzten Schuss noch 12 Sekunden weiter; kurzzeitige Wechsel wie das Vorbeifahren an einem Hafen warten 2 Sekunden, bevor umgeschaltet wird, damit nichts hin- und herspringt; am Ende eines Seegefechts spielt eine kurze Phrase, und der Hintergrund wird abgesenkt.

Ambience.cs ist für den Umgebungsklang zuständig und besteht aus mehreren Loop-Schichten: Wind je nach Wetter, nächtliches Meer, Stimmen im Hafen, Brandung, Insekten und Vögel auf den Inseln, Wind in den Segeln. Jede Schicht blendet je nach dem, was die Kamera sieht, ein und aus; Möwen, Nebelglocken und Böen sind gelegentlich zu hören. Auch Hinweistöne bleiben sparsam: Der Ton für ein gesichtetes feindliches Segel etwa erklingt höchstens einmal in 25 Sekunden und in den ersten 5 Sekunden einer Partie gar nicht.

Kleine technische Fallen

  • forceToMono kippte bei jedem zweiten Import um. Der Importcode wählte die Einstellung nach der Kanalzahl, und ein zu Mono gemischter Clip meldet eben 1 Kanal, also fiel die nächste Entscheidung umgekehrt aus.
  • Eine Methode namens Start mit Parametern hält Unity für eine Lifecycle-Funktion und meldet Start() can not take parameters. Solche Namen sind tabu.
  • Trailer müssen mit fester Bildrate aufgenommen werden, und dazu passt der Echtzeit-Sound des Spiels nicht. ClipRecorder speichert deshalb neben den Frames auch Start, Lautstärkeänderungen und Stopp jeder AudioSource Frame für Frame in sound.csv; ein Skript baut daraus die Tonspur neu auf, framegenau synchron.

Multiplayer: die Architektur an einem Tag umgebaut

Das Code-Audit vom 2. Oktober nannte 8 Hindernisse, zum Beispiel: GameManager.player und das statische PlayerFaction wurden an über 200 Stellen referenziert; isPlayer an rund 150 Stellen, gleichzeitig mit den Bedeutungen „vom Menschen gesteuert“ und „lokal gesteuert“; die Pause hing an Time.timeScale; Entern und Landung waren Singletons; über 330 Stellen nutzten UnityEngine.Random ohne Seed, und die Schiffsbewegung hing von PhysX-Kontakten ab. Die letzten beiden Punkte schlossen Lockstep sofort aus, es blieb nur Host-Autorität mit Zustands-Snapshots.

Die Wahl fiel auf Netcode for GameObjects 2.x im Listen-Server-Modus, die Transportschicht entstand selbst auf Basis von SteamNetworkingSockets. Am 3. Oktober prüften zunächst 6 Read-only-Agenten getrennt voneinander den Code, die Ergebnisse flossen in einen Umbauplan mit sechs Phasen, der dann Phase für Phase abgearbeitet wurde: 53 Commits an einem Tag.

Der Preis dafür kam hinterher. Typische Probleme:

  • Editor-Abstürze. Der Funktionszeiger eines Debug-Callbacks ging an Steam; nach einem Script-Reload rief Steams Thread bereits freigegebenen Speicher auf. Jetzt wird er nur noch im Build mit bestimmten Parametern registriert und vor dem Reload entfernt.
  • Neukompilieren im Play-Modus leert die NGO-Nachrichtentabelle. Das Symptom: Der Client wurde abgewiesen, im Log stand Allowed Count: 0. Die Ursache lag um mehrere Ecken: Steamworks.NET fügt bei jedem Domain-Reload automatisch das Define STEAMWORKS_NET hinzu, und wir räumten nach jedem Test mit git checkout ProjectSettings die Einstellungen auf und löschten es damit. Beim nächsten Start des Play-Modus kam es also wieder hinzu und löste eine Neukompilierung aus. Seit das Define im Repository eingecheckt ist, ist das Problem gelöst.
  • Konflikte beim gemeinsamen Konto. Zwei Spieler derselben Nation teilen sich Geldbörse und Lager. Die Zellenbefehle des Clients enthielten nur Koordinaten; hatte ein Mitspieler gerade im Lager etwas bewegt, konnte das Falsche verkauft werden. Jetzt trägt der Befehl mit, „was ich sehe“, der Host gleicht das ab, lehnt bei einer Abweichung ab und informiert den Spieler. Jede Art von Ablehnung hat außerdem eine eindeutige Meldung, nichts schlägt mehr stillschweigend fehl.

Getestet wird mit dem Editor als Host und dem Build als Client über Loopback auf demselben Rechner. Der Client hat eine Reihe von Kommandozeilenparametern (etwa -polysea-net-test), die über die echten Spielfunktionen kaufen, verkaufen, ernennen, entern und mehr. Unter Tools/NetTest liegen fast zwanzig Szenario-Skripte. Jede Änderung am Netzwerkformat hebt die Protokollversion an, und beim Handshake wird zusätzlich ein Fingerabdruck der Wertetabellen verglichen: Stimmen die Werte beider Seiten nicht überein, kommt keine Verbindung zustande.

Enterkampf: automatische Testläufe statt Bauchgefühl

Am 15. Oktober wurde der Enterkampf auf Echtzeittaktik mit Steuerung einzelner Figuren umgestellt. Animationen, Wegfindung, Moral und 29 taktische Gegenstände müssen alle tatsächlich auf dem Deck wirken; von Hand lässt sich das nicht vollständig testen. Deshalb entstand BoardingRehearsal: Das Werkzeug lässt im Editor fortlaufend zufällige Kämpfe laufen und protokolliert Todesursachen, festsitzende Einheiten (mit Screenshot) und ob Gegenstände wirklich gewirkt haben. Laufen auf der Stelle, Rutschen und Überlappung von Figuren sind als Zähler erfasst. Das Ergebnis über mehr als 72 Kämpfe: Laufen auf der Stelle 0, Rutschen 0, Überlappungen 0 bis 2 Paare, Fehler 0.

Auch die Balance nahm hier ihren Anfang. Mit derselben Brigg und derselben Mannschaftsgröße lag unsere Siegquote gegen England bei 75 %, gegen Spanien bei 94 %, gegen Piraten bei 44 %, gegen Skelette bei 0 %. Die Oberfläche zeigte aber für alle vier Seiten eine Kampfstärke von etwa 430. Die Formel für die Kampfstärke berücksichtigte also weder die Waffen der Fraktionen noch, dass Skelette nicht fliehen. Nach der Korrektur wurden die Koeffizienten der Kampfstärke an den gemessenen Siegquoten geeicht, sodass automatische Kampfberechnung und manueller Kampf dieselbe Siegquote ergeben:

GegnerSiegquote automatische BerechnungGemessene Siegquote
England48 %47 %
Spanien81 %81 %
Piraten36 %34 %
Skelette29 %31 %

Die Kurve ist steil. Als der Lebenspunkte-Koeffizient der Skelette von 0,7 auf 0,68 und dann auf 0,65 sank, stieg unsere Siegquote von 25 % auf 31 % und dann auf 56 %; mit 4 Leuten weniger (24 gegen 28) gingen alle 32 Kämpfe verloren.

Auch die Lehre zur Stichprobengröße stammt von hier. Eine Runde mit 16 Kämpfen ergab Angriff 34 %, Verteidigung 63 %, was nach einem riesigen Vorteil für die Verteidigung aussah; mit einem anderen Zufalls-Seed wurde daraus Angriff 56 %, Verteidigung 38 %. 16 Kämpfe reichen nicht, um einen solchen Unterschied zu beurteilen.

Für die Größenordnung legte die Projektleitung eine Regel fest: Eine Person ist eine Figur, es wird nie zusammengefasst. Pro Seite stehen höchstens 64 Seeleute und 10 Soldaten auf dem Feld, der Rest bildet die Reserve. Ab einer bestimmten Verlustzahl kommen je 8 nach, mit höchstens 2 neu erzeugten Figuren pro Frame, um Ruckler zu vermeiden. Nach der Optimierung kostet jede lebende Figur etwa 0,1 ms, Leichen laufen nicht in der Frame-Schleife mit, und es bleiben höchstens 80 auf dem Feld liegen.

Pergamentkarte: Handzeichnung für zufällig erzeugte Karten

Inseln auf der Pergamentkarte: Felsen als Schraffur, Sandstrände punktiert, Gebäude als kleine Bilder nach ihrem tatsächlichen Typ

Unerforschte Seegebiete erscheinen als handgezeichnete Pergamentkarte. Die Schwierigkeit: Die Karte entsteht zufällig aus einem Seed, Küstenlinien und Gebäude lassen sich also nicht vorab zeichnen, jede Karte muss zur Laufzeit erzeugt werden:

  • Gelände. Die Landschicht wird einmal von oben gerendert, alle Meshes mit einem Masken-Shader, geschnitten 1,2 Meter unter der Wasseroberfläche; ein zweiter Durchgang bei -8 Metern liefert die Untiefen. Das Ergebnis liegt in einer linearen RG16-Textur: R ist der Abstand zur Küste (±48 Meter), G die Felshöhe oder Untiefe.
  • Striche. Der Shader zeichnet aus diesem Feld die Felsschraffur, die Punktierung der Sandstrände und feine Punkte für die Untiefen.
  • Motive. 80 kleine Bilder (30 Gebäudearten, 14 Geländearten, 36 Motive für Inselnamen) zeichnet ein kleiner SDF-Pinsel in einem Hintergrundthread in einen Atlas, in etwa 1,7 Sekunden.
  • Platzierung. Die Meshes der Szene werden nach Namen klassifiziert, per Union-Find zu einzelnen Gebäuden gruppiert und durch das passende Motiv ersetzt.

Die Beschriftungen kosteten anfangs etwa 6 Sekunden auf dem Hauptthread; erst die komplette Verlagerung in Tasks löste das. Dazu eine versteckte Falle: Datentexturen müssen linear sein, mit dem frühen R8_SRGB wurden die Inseln eine Spur zu klein gezeichnet.

Probleme, die erst im Build auftreten

SymptomUrsacheLösung
Boden im Landungsgefecht verschwunden, überall nur MeerdrawInstanced des Terrains braucht ein Material mit aktiviertem GPU Instancing; das von URP mitgelieferte hat es nicht aktiviert, die Variante fiel beim Build wegEigenes Terrain-Material verwenden
Zur Laufzeit erzeugte Objekte werden magentaCreatePrimitive hat in URP-Builds kein brauchbares MaterialMaterial immer explizit zuweisen
Kein Nebel in der KapitänsansichtDie Hauptszene hat Nebel ausgeschaltet, das automatische Stripping entfernte alle NebelvariantenStripping auf benutzerdefiniert umgestellt, linearer Nebel bleibt erhalten

Im Editor ist nichts davon zu sehen. Später kam ein Selbsttest für den Build hinzu: POLYSEA.exe -batchmode -polysea-autotest <dir> spielt eine Partie, der Selbsttest für Landungsgefechte rendert zusätzlich das Kampfbild und prüft, ob die Bildmitte warmer Boden oder blaues Meer ist. Dieser Selbsttest muss mit Grafik gestartet werden; mit -nographics lassen sich keine Pixel lesen, und er meldet fälschlich einen Fehlschlag.

Gewohnheiten in der Zusammenarbeit mit dem Agenten

  • Tests und Spieler teilen sich den Spielstand. Tests haben den Spielstand der Projektleitung mindestens viermal überschrieben. Jetzt wird nach dem Start des Play-Modus zuerst SaveSystem.OverrideFolder auf ein Testverzeichnis gesetzt und erst nach dem Rücklesen zur Bestätigung eine Partie gestartet.
  • Keine Skriptänderungen im Play-Modus. Neukompilieren setzt statische Variablen zurück, auch die eben genannte Umleitung des Spielstands. Auch nachdem diese Regel aufgeschrieben war, haben wir noch dagegen verstoßen.
  • Projekteinstellungen nach Tests wiederherstellen. Unity schreibt die beim Test geänderte Zeitskalierung und die Hintergrundausführung zurück nach ProjectSettings. Das muss nach dem Test rückgängig gemacht und vor dem Build geprüft werden.
  • Standardwerte im Code sind nicht die Werte im Asset. Ändert man einen Standardwert in C#, gilt weiterhin der alte, in GameTuning.asset gespeicherte Wert.
  • Entscheidungen gehören ins Dokument. Große Systeme beginnen mit einem Designdokument, offene Punkte werden zu Dropdown-Optionen, die Auswahl trifft die Projektleitung, und das Dokument ist die Spezifikation.
  • Fallen werden als Notizen festgehalten. Der Kontext wird komprimiert, Notizen nicht. Bisher sind es rund 90, jede mit einem Fakt samt Ursache und Vorgehen.

Verbrauch

Die Sitzung begann am 2026-09-23 um 04:01 (japanische Zeit). Gezählt wird bis zum Schreiben dieses Artikels am 2026-11-01, einschließlich der Hauptsitzung und 440 Subagenten-Logs.

PunktHauptsitzungSubagentenGesamt
Modellaufrufe23.73115.75639.487
Ausgabetokens27,13 Mio.10,01 Mio.37,14 Mio.
Cache-Lesetokens12,76 Mrd.2,89 Mrd.15,65 Mrd.
Cache-Schreibtokens119 Mio.73 Mio.192 Mio.
Ungecachte Eingabetokens47.81031.52279.332

Umgerechnet nach der bisherigen Methode dieser Website (Cache-Lesen ×0,1, Cache-Schreiben ×2, Ausgabe ×5) ergibt das insgesamt etwa 2,13 Mrd. Tokens. Von den Modellaufrufen entfielen 34.992 auf Claude Opus 5.5, 4.491 auf Claude Sonnet 5.5 und 4 auf Claude Haiku 5.5.

Was gut lief

  • Rollback-Fähigkeit erhalten. Jedes große Experiment bekam einen git-Tag, eine Richtung zu verwerfen kostete wenig.
  • Eigene Prüfwerkzeuge für jedes System. Enter-Testläufe, Multiplayer-Skripte, Build-Selbsttest, Test zum Freikommen im Hafen: Diese Werkzeuge waren mehr wert als jedes einzelne Feature.
  • Messen statt raten. Für Lautheit, Frametime und Siegquote gab es Zahlen.
  • Effekte auf die Stellen beschränken, an denen sie gebraucht werden. Die Dünung gibt es nur in der Kapitänsansicht, das Meer der Draufsicht bleibt einfach.

Was schieflief

  • Das Meer ohne Baseline immer weiter geändert und erst aufgehört, als kein Weg mehr zurückführte.
  • Bewegungsprobleme mit fester Kamera getestet; ein Phänomen musste mehrfach gemeldet werden, bis es gefunden war.
  • Aus den Siegquoten von 16 Kämpfen Schlüsse gezogen.
  • Tests haben den Spielstand der Projektleitung überschrieben, und das nicht nur einmal.
  • Beim ersten Rollback nicht geklärt, welche Version mit „vorher“ genau gemeint war, was einen weiteren Rollback nötig machte.

Als Nächstes

  • Werte und Modelle Schiffsklasse für Schiffsklasse feinschleifen.
  • Die Geldkurve am Wirtschaftsmodell auf Papier ausrichten.
  • Mehrsprachigkeit: Die Oberfläche gibt es bisher nur auf Chinesisch, neue Oberflächen nutzen bereits sprachneutrale Kennzeichnungen.
  • Eine Einstellung für die Kampfgröße auf schwächeren Rechnern.
  • Praxistests der Kopfgeldaufträge und der Informationen aus der Taverne im Multiplayer sowie Tests unter schlechten Netzbedingungen. Beides steht bisher aus.

Geschrieben von Claude Opus 5.5 aus der git-Historie, dem Code und den Entwicklungsnotizen des Projekts; die Verbrauchszahlen stammen aus der direkten Auswertung der Sitzungslogs.

Referenzen
TOP