Seekampf 2.0 — API-Spezifikation

Das gesamte Spiel ist über diese API spielbar (Web-Client & externe Bots). Basis: REST + JSON unter /api/v1, plus WebSocket für Live-Kämpfe & Events. FastAPI liefert zusätzlich automatische OpenAPI/Swagger-Doku unter /docs. Status: Bereit für Umsetzung · 2026-07-02


1. Konventionen


2. Auth & Account

POST /auth/register        {name, email, password} → Account + Startinsel (Login erst nach E-Mail-Bestätigung!)
POST /auth/login           {name|email, password}  → {access, refresh}; 403 email_not_verified vor Bestätigung
POST /auth/refresh         {refresh}               → {access}
POST /auth/logout
POST /auth/verify-email    {token}                 → E-Mail bestätigen (schaltet Login frei)
POST /auth/resend-verification {email}             → Bestätigungsmail erneut senden (neutrale Antwort)
POST /auth/forgot-password {email}                 → Reset-Mail (SMTP, siehe ARCHITEKTUR §3a)
POST /auth/reset-password  {token, new_password}   → Passwort neu setzen
POST /auth/change-password {old_password, new_password} → Passwort ändern (eingeloggt)
GET  /me                                           → Profil, Punkte, Allianz, Bio
                                                      — `punkte` hier LIVE (einzige
                                                      Ausnahme, Dennis-Feedback
                                                      2026-07-06: "Live nur für
                                                      einen selber")
POST /me/profile           {bio}                   → kurzes Spieler-Profil setzen
GET  /users/{user_id}/profile                       → öffentliches Profil (Bio,
                                                      Punkte, Inselliste Name/
                                                      Koordinaten/Punkte/
                                                      Tageszuwachs — KEINE
                                                      Rohstoffe/Gebäude/Truppen;
                                                      zusätzlich: `online` (bool,
                                                      letzte Aktivität ≤
                                                      ONLINE_SCHWELLE_MIN=10 Min.),
                                                      `allianz` ({id,tag,name} oder
                                                      null), `quests_erfuellt`
                                                      (öffentlich sichtbare
                                                      Auszeichnungen, siehe §12).
                                                      `punkte` (Gesamt UND je Insel
                                                      in `inseln`) kommt aus dem
                                                      täglichen Tages-Snapshot,
                                                      NICHT live — gilt auch fürs
                                                      EIGENE Profil unter dieser
                                                      Route (nur `/me` ist live).
                                                      Jede Insel in `inseln` hat
                                                      zusätzlich ein `differenz`-Feld
                                                      (Punkte-Zu-/Abnahme seit
                                                      dem letzten Tages-Snapshot,
                                                      `null` vor dem ersten
                                                      Snapshot der Insel).
GET  /users/lookup         ?name=                   → wie oben, per Name gesucht
GET  /me/islands                                   → Liste eigener Inseln
POST /me/current-island    {island_id}             → aktive Insel wechseln
# API-Keys für Bots
GET    /me/apikeys
POST   /me/apikeys         {label, scopes}         → einmalig Klartext-Key
DELETE /me/apikeys/{id}
# DSGVO
GET    /me/export                                  → Datenexport (Auskunft, JSON)
DELETE /me                 {password}              → Account löschen (Recht auf Vergessenwerden)

2a. Rechtliche Seiten (statisch, ohne Login)

GET /legal/impressum           → Impressum (HTML/Markdown)
GET /legal/datenschutz         → Datenschutzerklärung
GET /legal/regeln              → Spielregeln

Im Footer des Web-Clients verlinkt. Inhalte editierbar (Template/Markdown), Werte liefert Dennis.

3. Insel & Rohstoffe

GET /islands/{id}                → Insel-Detail (Rohstoffe live, Produktion/h,
                                   Lager, Gebäude+Stufen, Garnison, Schiffe,
                                   laufende Aufträge, Punkte)
GET /islands/{id}/resources      → aktueller Bestand + Produktion + Kapazität

4. Gebäude & Warteschlange

GET  /islands/{id}/buildings                         → alle Typen + Stufen
                                                       (haupthaus_ab, verfuegbar)
GET  /islands/{id}/buildings/{type}/detail            → Beschreibung + volle
                                                       Stufentabelle (Kosten/Zeit/
                                                       Effekt/Freischaltung)
POST /islands/{id}/buildings/{type}/upgrade          → in Bau-Warteschlange (max 3)
GET  /islands/{id}/construction                       → aktiver Bau + Queue
DELETE /islands/{id}/construction/{orderId}           → Auftrag abbrechen
POST /islands/{id}/rename  {name}                     → Insel umbenennen

Fehler wenn: Voraussetzung (Haupthaus-Stufe — Kaserne ab 5, Hafen ab 7, Labor ab 10, Marktplatz ab 10, Steinmauer/Wachturm ab 1) fehlt, Rohstoffe fehlen, Queue voll, Max-Stufe erreicht, oder das Gebäude deaktiviert ist (aktuell: Marktplatz, Labor — 409 building_disabled, siehe balancing.DEAKTIVIERTE_GEBAEUDE; Code/Daten bleiben vollständig erhalten).

5. Militär (Kaserne/Hafen) & Forschung

POST /islands/{id}/training      {facility: kaserne|hafen, item, count}
GET  /islands/{id}/training       → aktive Ausbildung + Queue
DELETE /islands/{id}/training/{orderId}
GET  /islands/{id}/research
POST /islands/{id}/research/{tech}/upgrade

Baubarkeit prüft Kaserne-/Hafen-Stufe (z. B. Koloschiff = Hafen 20). Forschung (speer/schild/bogen/segel/spionage/engineering, je Stufe +10 % Effekt auf Angriff/Verteidigung/Schiffsgeschwindigkeit/Spionage-Chance/ Bauzeit) ist aktuell deaktiviert: POST /research/{tech}/upgrade → 409 feature_disabled, und research.forschung_stufen() liefert serverseitig nur noch 0 zurück (wirkt sich also auch nirgends mehr aus) — Code/Daten bleiben vollständig erhalten, System ist für eine Reaktivierung vorbereitet.

6. Karte / Ocean View

GET /map/sector/{x}/{y}          → 5×5 Inseln des Sektors (Basis-Infos:
                                   Besitzer/Name/Allianz/Punkte; KEINE Truppen/Rohstoffe)
GET /map/region/{x}/{y}          → 3×3-Sektoren-Ausschnitt (15×15 Inseln) in
                                   einer Antwort — fürs Ozean-Raster
GET /map/island/{x}/{y}/{z}      → öffentliche Hover-Infos einer Insel; für
                                   eingeloggte Betrachter zusätzlich
                                   `eigene_berichte` — letzte 10, neueste
                                   zuerst (Dennis-Feedback 2026-07-08):
                                   eigene Angriffe auf GENAU diese Insel UND
                                   eingehende Angriffe vom aktuellen Besitzer
                                   dieser Insel gegen eine eigene Insel;
                                   ungefiltert nach `Message.hidden` — im
                                   Postfach ausgeblendete Berichte bleiben
                                   hier trotzdem sichtbar
# Detail-Infos (Truppen/Rohstoffe) NUR via Spionage (siehe Kampfbericht)

punkte in ALLEN drei Endpunkten oben (Sektor/Region/Insel) kommt aus dem täglichen Tages-Snapshot, NICHT live (Dennis-Feedback 2026-07-06: "in der öffentlichen Insel-Ansicht auch") — eine brandneue Insel ohne bisherigen Snapshot zeigt live, sonst stünde sie permanent mit 0 Punkten da.

7. Flotten & Missionen

POST /fleets   {origin_island_id, mission_type, target:{x,y,z},
                ships:{type:count}, units:{type:count}, resources:{...},
                razzia_ziel?}       (razzia_ziel nur für mission_type=attack)
               → berechnet Fahrzeit/Ankunft; validiert Ladevolumen & Kapazität
GET  /fleets                      → eigene Flotten (outbound/returning) + Status
GET  /fleets/incoming             → auf eigene Inseln anfliegende Flotten,
                                   eigene UND fremde (Sichtbarkeit/Details je
                                   Wachturm-Stufe DER ZIELINSEL — Dennis-
                                   Feedback 2026-07-07: auch eigene Flotten
                                   erst innerhalb der Sichtweite, vorher immer
                                   sichtbar) + eigene Flotten, die eine eigene
                                   Insel gerade verlassen haben
                                   ({abfahrend: true, herkunft_insel_id,
                                   herkunft_koordinaten, sichtbar_bis} —
                                   sichtbar exakt solange, wie sie auch
                                   zurückgerufen werden könnte) +
                                   vorbeifahrende fremde Flotten
                                   ({vorbeifahrend: true, ziel_insel_id} —
                                   nur "da fliegt was", keine weiteren Details)
POST /fleets/{id}/recall          → Rückruf (nur solange in Wachturm-Sichtweite)

mission_type (Dennis-Feedback 2026-07-07): nur noch "attack" oder "handel" — die konkrete Abwicklung (in GET /fleets als mission sichtbar: attack/colonize/spy/transport) ergibt sich automatisch aus der Fracht: - attack + NUR Spähschiffe an Bord (keine Einheiten/Rohstoffe) → Spionage — geht auf JEDE existierende Insel, auch unbewohnte (eine Ruine kann noch eine Restgarnison/Rohstoffe haben). Nur echtes leeres Wasser (kein Inselplatz) ist kein gültiges Ziel. - attack + Kolonisationsschiff an Bord + Ziel ist eine FREIE Insel (owner = niemand) → Kolonisation. Ist das Ziel bewohnt, bleibt es ein normaler Angriff — das mitgeführte Kolonisationsschiff ist dann Teil der Eroberungs-Mechanik (gelingt die Eroberung, wird es verbraucht, genau wie bei einer normalen Kolonisation), NICHT der Kolonisation einer freien Insel. - attack sonst → normaler Angriff/Plünderung. - handel → Transport: Einheiten/Rohstoffe an eine EXISTIERENDE Insel liefern (bewohnt oder unbewohnt/Ruine — z. B. um eine Ruine als Vorposten zu bestücken). Einheiten an eine BEWOHNTE fremde Insel nur bei eigener/Allianz- Insel (D-Reinforce); an unbewohnte Inseln immer erlaubt. Rohstoffe an jede existierende Insel.

Regeln: nur Einheiten+Rohstoffe transportierbar (keine Schiffe verlegen); Rückkehr = gleiche Dauer; Beute fährt automatisch zur Heimatinsel; per handel verschickte Einheiten gehen dauerhaft in die Garnison der Zielinsel über. Kolonisation zielt NUR auf eine existierende, unbewohnte Insel (owner = niemand) — leeres Wasser ohne Inselplatz ist nicht kolonisierbar. Der Weltgenerator legt regelmäßig neue unbewohnte Inseln an (siehe ARCHITEKTUR). razzia_ziel (Gebäudetyp) ist optional — ohne Angabe raziert der Server bei Sieg automatisch das Haupthaus.

8. Kampf (Live)

GET /battles/{id}                 → Bericht (Sichtbarkeit je Rolle:
                                   Angreifer rudimentär / Verteidiger vollständig)
WS  /ws/battles/{id}              → Live-Playback-Frames (15 Min), rollenabhängig

9. Allianzen

POST /alliances                {name, tag}
GET  /alliances/{id}            → Info, Mitglieder (inkl. Inselanzahl je
                                   Mitglied), Diplomatie, Punkte
POST /alliances/{id}/invite     {user_id}      (leader/diplomat)
POST /alliances/{id}/join | /leave | /kick
POST /alliances/{id}/roles      {user_id, role}
POST /alliances/{id}/kasse/deposit | /withdraw  {island_id, resources}
                                   → {id, richtung, resources, fertig_at}
                                   (withdraw zusätzlich: kasse-Zwischenstand)
GET  /alliances/{id}/kasse/log  ?limit&offset (Default 15/0, Dennis-Feedback
                                   2026-07-09) → ABGESCHLOSSENE Ein-/
                                   Auszahlungen (wer, wann, Richtung, Menge),
                                   neueste zuerst — für jedes Mitglied
                                   einsehbar. Bleibt eine reine Liste (kein
                                   Gesamt-Zähler) — eine volle Seite
                                   (Länge == limit) heißt "es könnte mehr
                                   geben".
POST /alliances/{id}/diplomacy  {other_alliance_id, type: krieg|buendnis|nap|neutral}
POST /alliances/{id}/broadcast  {message}       (Rundbrief/Chat)
GET  /users/lookup             ?name=           → öffentliches Profil (für Einladungen; siehe §2)

Diplomatie-Semantik: krieg wirkt einseitig & sofort. buendnis/nap sind Angebote — aktiv, sobald die Gegenseite denselben Typ setzt. neutral kündigt Angebote/Bündnisse/NAPs sofort; ein laufender Krieg endet erst, wenn BEIDE Seiten neutral setzen (Friedensangebot wird protokolliert). handel-Missionen (§7) mit Einheiten sind zu einer BEWOHNTEN fremden Insel nur bei eigener Insel/Allianzmitglied erlaubt (§2.7 D-reinforce), zu unbewohnten Inseln immer. Die punkte je Mitglied in der Mitgliederliste kommen aus dem täglichen Tages-Snapshot, NICHT live (Dennis-Feedback 2026-07-03: "Live nur oben für einen selber") — Name/Inselanzahl/Rolle bleiben live.

24h-Sperrzeit (Dennis-Feedback 2026-07-04): Sobald eine Diplomatie- Beziehung einen settled Zustand erreicht (Krieg erklärt, Bündnis/NAP bestätigt, Frieden geschlossen, Bündnis/NAP gekündigt), ist ein Wechsel in einen ANDEREN Typ für ALLIANZ["diplomatie_sperre_stunden"] (Default: 24h) gesperrt — verhindert Hin- und Her-Wechseln zwischen den Zuständen im Minutentakt. Ein Versuch während der Sperre liefert 409 diplomacy_locked mit dem Zeitpunkt, ab dem wieder geändert werden darf. Laufende Angebote/ Friedensverhandlungen (Status angeboten/friede_angeboten) sind von der Sperre NICHT betroffen — Annehmen/Ablehnen/Zurückziehen geht immer sofort. Eine gekündigte Beziehung wird intern als neutral mit Sperrfrist gespeichert (statt gelöscht) und verschwindet aus der Diplomatie-Liste, sobald die Sperre abgelaufen ist.

Kasse-Transferzeit (Dennis-Feedback 2026-07-06): Ein-/Auszahlung wirken NICHT mehr sofort, sondern erst nach ALLIANZ["kasse_transferzeit_h"] (Default 2h, unter Turbo TURBO_FAKTOR-mal kürzer wie überall sonst). Einzahlung: Rohstoffe werden SOFORT von der Insel abgezogen, kommen aber erst nach der Transferzeit in der Kasse an. Auszahlung: der Betrag wird SOFORT aus der Kasse gebucht (reserviert, kein Doppel-Ausgeben), kommt aber erst nach der Transferzeit auf der Zielinsel an — Lagerkapazität wird ERST BEI ANKUNFT geprüft (kann sich in 2h ändern), was dann nicht passt, geht zurück in die Kasse. GET /alliances/{id} liefert für Mitglieder zusätzlich kasse_unterwegs: [{id, richtung, resources, spieler, fertig_at}] — alle noch nicht abgeschlossenen Transfers. GET .../kasse/log zeigt weiterhin nur ABGESCHLOSSENE Bewegungen (mit der tatsächlich angekommenen Menge, bei Auszahlung ggf. weniger als angefordert wegen Lagerkapazität).

10. Marktplatz & Handel

GET  /market/offers            ?want=&offer=      → Angebote filtern
POST /market/offers            {offer_res, offer_amount, want_res, want_amount}
POST /market/offers/{id}/accept {from_island_id}  → erzeugt Transport-Fleet
DELETE /market/offers/{id}
# Freier (riskanter) Versand:
POST /transfers                {from_island_id, target:{x,y,z}, resources}
# Handelsrouten (wiederkehrend, eigene Inseln):
GET/POST/DELETE /trade-routes      POST: {from_island_id, to_island_id,
                                          resources, interval_h}

Marktplatz/Marktangebote sind aktuell deaktiviert (POST /islands/{id}/ buildings/marktplatz/upgrade → 409 building_disabled; POST /market/offers → 409 feature_disabled) — Endpunkte, Code und Daten bleiben vollständig erhalten, siehe balancing.DEAKTIVIERTE_GEBAEUDE. Markt-Semantik (F16b- Defaults, Werte in balancing.py → MARKT), falls/wenn reaktiviert: Angebot braucht Marktplatz ≥ 1; angebotene Menge + Gebühr wird beim Einstellen hinterlegt (Escrow — das Verhältnis ist abgesichert). Kapazität je Angebot/ Annahme = 500 × Marktplatz-Stufe; max. Angebote je Insel = 2 + Stufe; Gebühr = 10 % − 0,4 %/Stufe. Lieferungen fahren als abstrakte Markt- Handelsschiffe (4 Knoten, echte Fahrzeit) — keine eigenen Schiffe nötig. Zurückziehen/Ablauf erstattet die Menge, die Gebühr verfällt.

Freier Versand (/transfers) nutzt EIGENE Handelsschiffe (automatisch gewählt, größte zuerst) — ohne jede Absicherung. Handelsrouten liefern mit eigenen Handelsschiffen; fehlen Schiffe/Rohstoffe, wird der Lauf übersprungen. Beide sind AKTIV, unabhängig vom deaktivierten Marktplatz.

11. Postfach

GET  /messages                 ?folder=inbox|combat
POST /messages                 {recipient, subject, body}
POST /messages/{id}/read
DELETE /messages/{id}

DELETE /messages/{id}: bei folder=combat (Kampf-/Spionageberichte) NUR ein Soft-Hide (hidden=true, verschwindet aus GET /messages) — Kampfberichte werden nie wirklich gelöscht, sie bleiben für spätere Statistiken im System (GRUNDBEDINGUNGEN §2.6b). Bei folder=inbox (normale Nachrichten) echtes Löschen der Zeile.

11a. Forum (öffentlich + Allianz-intern)

GET  /forum/threads            ?sichtbarkeit=oeffentlich|allianz  (optional; ohne
                                Parameter beide, je nach Sichtbarkeitsregel gemischt)
                                &limit=&offset=
POST /forum/threads            {titel, body, sichtbarkeit?: "oeffentlich"|"allianz"}
                                sichtbarkeit-Default: "oeffentlich"
GET  /forum/threads/{id}       ?limit=&offset=   → Thema + alle Beiträge
POST /forum/threads/{id}/posts {body}            → Antwort auf ein Thema
POST /forum/posts/{id}/report                     → melden (jeder eingeloggte Spieler)
DELETE /forum/posts/{id}                          (nur is_admin)
DELETE /forum/threads/{id}                        (nur is_admin)

Ein "unfassbar vereinfachtes" In-Game-Brett (Dennis-Feedback 2026-07-05) — kein Editieren, keine Kategorien/Unterforen, nur Themen + Antworten. Nur eingeloggt erreichbar (kein öffentlicher HTTP-Endpunkt wie bei §2a).

Sichtbarkeit: jedes Thema ist "oeffentlich" (für alle sichtbar) oder "allianz" (nur für Mitglieder GENAU dieser Allianz + Admin). Ein allianzinternes Thema anlegen ohne eigene Allianz → 403 not_member. Zugriff auf ein fremdes Allianz-Thema (View/Antworten/Melden) → 404 not_found (keine Info-Preisgabe über die Existenz, analog zu fremden Inseln). Admins sehen und moderieren alle Themen unabhängig von eigener Allianz-Mitgliedschaft.

Moderation: POST /forum/posts/{id}/report ist idempotent (mehrfaches Melden durch denselben Spieler erzeugt keinen zweiten Eintrag) und für jeden eingeloggten Spieler erlaubt — nur klein sichtbar im Frontend, kein Sofort-Effekt. Die Felder gemeldet (Themenliste) und melde_anzahl (Beitrag-Detail) kommen von der API nur mit, wenn der Aufrufer is_admin ist — normale Spieler sehen nie, ob/wie oft etwas gemeldet wurde. Admin kann jeden Beitrag oder jedes ganze Thema löschen (protokolliert im Admin-Audit-Log); löscht man den letzten Beitrag eines Themas, verschwindet das dann leere Thema automatisch mit.

Link-Verbot: Titel und Beitragstext (Thema wie Antwort) werden serverseitig gegen eine Link-Heuristik geprüft (Protokoll-URLs, www., wortartige Domain.TLD-Muster ohne Leerzeichen) — bei Treffer 422 link_verboten. Eine Heuristik, kein vollständiger Parser; erkennt nicht jede Verschleierung, löst aber nicht bei normalen Satzenden aus.

12. Ranglisten, Quests

GET /rankings/players          → nach Gesamtpunkten
GET /rankings/alliances        → nach Punkten
GET /stats/overview            → öffentlich, KEIN Login nötig — Server-Kennzahlen
                                  (Spieler/Inseln/Allianzen/Flotten unterwegs),
                                  Top-10-Spieler/Top-5-Allianzen, weltweiter
                                  Einheiten-/Schiffsbestand (Häfen) UND was
                                  gerade auf See ist (Schiffe + Einheiten in
                                  Flotten). Auch als Seite unter /statistik
                                  (HTML, von der Startseite verlinkt).
GET /quests                    → Tutorial + Meilensteine + heutige Dailies + Fortschritt
POST /quests/{id}/claim        → Belohnung (Rohstoffe) abholen

Rangliste live vs. eingefroren (Dennis-Feedback 2026-07-03): Rang/Punkte/ Differenz kommen aus dem täglichen Snapshot und ändern sich erst beim nächsten Tageswechsel. Name/Inseln/Allianz werden dagegen bei JEDEM Abruf frisch aus der Datenbank nachgeladen (live) — ein Allianzwechsel oder gelöschter Account ist also sofort sichtbar, nur die Punktzahl/der Rang selbst bleibt bis Mitternacht fix. Gelöschte Spieler/Allianzen werden aus dem Snapshot entfernt, sobald sie beim nächsten Abruf nicht mehr existieren. GET /rankings/players liefert außerdem online: bool je Spieler (Dennis- Feedback 2026-07-08) — ebenfalls live (letzte Aktivität < ONLINE_SCHWELLE_MIN Minuten), analog zum online-Feld im öffentlichen Profil (§ Nutzer); die genaue letzte Aktivität selbst bleibt privat, nur der Bool wird ausgeliefert. Tageswechsel = Mitternacht in der Server-Zeitzone Europe/Berlin (DST- sicher via app.core.timeutil.server_datum()), NICHT UTC-Mitternacht — Fix 2026-07-05, vorher wechselte der Snapshot erst 1–2h nach der deutschen Mitternacht, was wie ein Berechnungsfehler wirkte.

Öffentliche Auszeichnungen (Dennis-Feedback 2026-07-04): quests_erfuellt im öffentlichen Profil (§2) zeigt NUR Quests aus einer fest hinterlegten Whitelist, die nichts über die eigene Insel oder deren Ausbaustufen verrät (z. B. Allianz beigetreten, Kampf gewonnen, Insel erobert, geplündert, spioniert, Kontoalter, sowie die drei Lieferungs-/Kolonisations-Quests unten) — Gebäude-/Forschungs-/Punkte-Quests bleiben privat.

Meilensteine M13–M15 (neu, Dennis-Feedback 2026-07-03/04): - M13 Truppentransport — 1.000 Steinewerfer in EINER Lieferung verschickt (Maximum je Einzellieferung zählt). - M14 Großhändler — 100.000 Rohstoffe INSGESAMT verschickt (Summe über alle Lieferungen — nicht auf einmal, bewusst andere Semantik als M13). - M15 Gescheiterte Landung — 100 gescheiterte Kolonisationsversuche erlebt.

Dailies-Pool auf 15 Aufgaben erweitert (balancing.DAILIES["pool"]); pro Tag werden weiterhin 3 davon angeboten, deterministisch per HMAC(Server-Seed, Datum) ausgewählt — für alle Spieler am selben Tag garantiert identisch.

13. Admin

Interne Admin-/Testhilfe-Endpunkte (nur is_admin) — bewusst NICHT Teil dieser öffentlichen API-Dokumentation und aus der automatischen OpenAPI/ Swagger-Doku (/docs) ausgeschlossen (Dennis-Feedback 2026-07-08).

14. WebSocket-Kanäle

WS /ws                          → Benachrichtigungen (Auftrag fertig, Flotte
                                  angekommen, Angriff im Anflug, neue Nachricht)
WS /ws/battles/{id}             → Live-Kampf-Playback

Auth beim WS-Handshake via ?token= (JWT) oder ?api_key=.


14a. Dynamische Payloads & Events (vollständige Referenz)

Viele Antworten sehen je nach Rolle/Status/Admin-Recht unterschiedlich aus — Felder werden nicht nur mit null befüllt, sondern weggelassen. Diese Referenz listet ALLE Varianten, damit ein Bot nicht raten muss, welche Felder auftauchen können. (Dennis-Feedback 2026-07-06: "was alles für Informationen übertragen werden könnte", damit ein Bot genau weiß, was passieren kann.)

14a.1 Message.payload (Postfach, §11) — je nach Nachrichtentyp

Kampfbericht — Angreifer, Sieg:

{
  "battle_id": 42, "rolle": "angreifer", "sieg": true,
  "insel_name": "Nebelinsel", "insel_koordinaten": "12:34:5",
  "eingesetzt": {"angreifer": {"units": {}, "ships": {}},
                "verteidiger": {"units": {}, "ships": {}}},
  "verluste": {"angreifer": {}, "verteidiger": {}},
  "razzia": {"goldmine": 1},
  "loot": {"gold": 120, "stein": 40},
  "erobert": false,
  "aufklaerung": null,
  "aufklaerung_status": "kein_spaehschiff",
  "rng": {"eroberung": {"wurf": 0.71, "schwelle": 0.5}},
  "text_voll": "…"
}

insel_name/insel_koordinaten (Dennis-Feedback 2026-07-08) stehen in JEDER Kampfbericht-Variante (Angreifer Sieg/Niederlage, Verteidiger) sowie in GET /battles/{id} — Inselnamen sind ohnehin überall öffentlich sichtbar (GET /map/island), keine zusätzliche Info-Preisgabe. aufklaerung ist entweder null oder ein Schnappschuss (siehe 14a.2). aufklaerung_status: "kein_spaehschiff" (kein Spähschiff dabei) | "erfolgreich" | "fehlgeschlagen" (Würfel nicht getroffen). rng-Keys variieren je nachdem, welche Zufallsentscheidungen im Kampf gefallen sind (angriffsaufklaerung, eroberung, …), jeweils {wurf, schwelle}. aufklaerung.rohstoffe wird NACH der Plünderung erfasst (Dennis-Feedback 2026-07-07) — zeigt bei erfolgreicher Zusatz-Aufklärung also den Bestand ABZÜGLICH loot, nicht den Stand vor dem Angriff.

Kampfbericht — Angreifer, Niederlage (Dennis-Feedback 2026-07-09: "auch ohne Spähschiff Informationen über den Gegner ... wie der Kampf lief" — eingesetzt/verluste beider Seiten sind jetzt AUCH bei Niederlage sichtbar, nicht mehr nur bei Sieg):

{
  "battle_id": 42, "rolle": "angreifer", "sieg": false,
  "insel_name": "Nebelinsel", "insel_koordinaten": "12:34:5",
  "eingesetzt": {"angreifer": {"units": {}, "ships": {}},
                "verteidiger": {"units": {}, "ships": {}}},
  "verluste": {"angreifer": {}, "verteidiger": {}},
  "verteidiger_name": "SpielerName",
  "text_voll": "…"
}

Kein razzia/loot/aufklaerung (die gibt's nur bei Sieg) — die Zusatz-Aufklärung (Gebäude/Rohstoffe/Schiffsbestand der Insel) bleibt weiterhin exklusiv an Spähschiff + Sieg gebunden, NUR der reine Kampfverlauf (wer mit wieviel eingesetzt/verloren hat) ist jetzt immer sichtbar. verteidiger_name ist null, wenn das Ziel unbewohnt war.

Kampfbericht — Verteidiger (immer vollständig, unabhängig vom Ausgang):

{
  "battle_id": 42, "rolle": "verteidiger", "sieg": true,
  "insel_name": "Nebelinsel", "insel_koordinaten": "12:34:5",
  "eingesetzt": {"angreifer": {}, "verteidiger": {}},
  "verluste": {"angreifer": {}, "verteidiger": {}},
  "razzia": {}, "loot": {}, "angreifer": "SpielerName", "text_voll": "…"
}

Spionage — Ergebnis versenkt:

{"typ": "spionage", "ergebnis": "versenkt", "ziel": "12:34:5",
 "insel_name": "Nebelinsel",
 "rng": {"chance": 0.3, "wurf": 0.81, "entdeckt": true, "versenkt": true}}

insel_name (Dennis-Feedback 2026-07-08) steht in allen drei typ: "spionage"-Varianten (versenkt/erfolg/fehlschlag) — NICHT in den spionage_abwehr-Varianten (die betreffen die eigene Insel des Empfängers, der kennt sie bereits). Spionage-Abwehr — Ziel wurde ausspioniert, Späher versenkt:

{"typ": "spionage_abwehr", "ergebnis": "versenkt", "angreifer": "SpielerName"}

Spionage — Erfolg:

{"typ": "spionage", "ergebnis": "erfolg", "ziel": "12:34:5",
 "bericht": {"info_tiefe": 3, "rohstoffe": {"gold": 900, "stein": 400, "holz": 200},
            "gebaeude": {"haupthaus": 5}, "truppen": {"steinewerfer": 20}},
 "rng": {"chance": 0.6, "wurf": 0.22}}

bericht ist nach info_tiefe gestaffelt — Felder kommen ERST ab der jeweiligen Tiefe dazu (niedrigere Tiefe = weniger Felder, nicht null): info_tiefe 1: nur rohstoffe. ≥2: zusätzlich gebaeude. ≥3: zusätzlich truppen. ≥4: zusätzlich schiffe, forschung. Spionage — Fehlschlag (kein bericht-Feld):

{"typ": "spionage", "ergebnis": "fehlschlag", "ziel": "12:34:5", "rng": {...}}

Spionage-Abwehr — nur gesichtet (Angreifer bleibt anonym, kein angreifer-Feld):

{"typ": "spionage_abwehr", "ergebnis": "gesichtet"}

Allianz-Einladung: {"alliance_id": 3, "invite": true} Allianz-Rundbrief: {"alliance_id": 3, "rundbrief": true}

14a.2 Schnappschuss-Shape (in aufklaerung und Spionage-bericht verwendet)

{"rohstoffe": {"gold": 900, "stein": 400, "holz": 200},
 "gebaeude": {"haupthaus": 5, "goldmine": 3},
 "truppen": {"steinewerfer": 20}, "schiffe": {"spaehschiff": 1},
 "forschung": {"segel": 2}}

Welche Top-Level-Keys vorhanden sind, hängt vom Kontext ab (Aufklärung durch Spähschiff im Angriff vs. gestufter Spionagebericht, siehe oben).

14a.3 Gesperrter Bericht bei laufendem Live-Kampf (§8)

Solange now < battle.end_at, wird JEDES Kampf-/Spionage-Payload (egal welche Variante oben) beim Abruf über GET /messages/GET /messages/{id} auf genau diese 4 Felder reduziert — der volle Body/Payload kommt erst nach Kampfende:

{"battle_id": 42, "rolle": "angreifer", "fertig": false,
 "end_at": "2026-07-06T14:32:00Z"}

GET /battles/{id} ist vor Kampfende analog gesperrt:

{"id": 42, "start_at": "...", "end_at": "...", "fertig": false,
 "hinweis": "Der Kampf läuft noch — voller Bericht ab Kampfende abrufbar.",
 "rolle": "angreifer"}

14a.4 GET /battles/{id} — volle Ansicht nach Kampfende (§8)

Alle Varianten haben zusätzlich insel_name/insel_koordinaten (§14a.1). Verteidiger (immer voll): id, start_at, end_at, rolle:"verteidiger", ergebnis, timeline, eingesetzt, verluste, razzia, loot, conquered, angreifer_name. Angreifer + Sieg: id, start_at, end_at, rolle:"angreifer", ergebnis, timeline, eingesetzt, verluste, razzia, loot, conquered, aufklaerung, aufklaerung_status, rng, verteidiger_name. Angreifer + Niederlage (Dennis-Feedback 2026-07-09, NICHT mehr rudimentär — nur die Zusatz-Aufklärung fehlt): id, start_at, end_at, rolle:"angreifer", ergebnis, timeline, eingesetzt, verluste, verteidiger_name, hinweis. Kein razzia/loot/conquered/aufklaerung (die gibt's nur bei Sieg). verteidiger_name ist null bei unbewohntem Ziel.

timeline-Einträge (phase: "see"|"land") tragen seit 2026-07-09 zusätzlich angriffsstaerke/verteidigungsstaerke (die tatsächlich verglichenen Werte, die über Sieg/Niederlage entscheiden — VOR den Verlusten). Der "land"- Eintrag trägt außerdem basis_inselverteidigung (0 oder KAMPF ["basis_inselverteidigung"]=50 — 50 gilt bereits ab 1 verteidigender Einheit, 0 nur bei einer komplett leeren Zielinsel). Ältere, vor diesem Datum gespeicherte Battles haben diese Felder nicht (fehlen einfach, kein null).

14a.5 WS /ws — genau 4 Event-Typen (kein weiterer)

{"typ": "verbunden"}
{"typ": "nachricht", "id": 7, "folder": "inbox", "subject": "Hallo"}
{"typ": "flotte_ankunft", "fleet_id": 12, "state": "outbound|returning|done"}
{"typ": "angriff_im_anflug", "ziel": "12:34:5", "mission": "attack",
 "arrive_at": "2026-07-06T14:00:00Z"}

14a.6 WS /ws/battles/{id} — Live-Playback-Frames

Drei Phasen nacheinander, je mit eigener Zeitscheibe (KAMPF["phase_anteile"]): see (Seegefecht) → land (Landkampf) → belagerung (Razzia/Gebäudezerstörung, nur wenn der Angreifer gewinnt). Ein Feld erscheint in JEDEM Frame ab dem Zeitpunkt, an dem es das erste Mal relevant wird (nicht erst während seiner eigenen Phase) — Werte VOR ihrer Phase = Ausgangsbestand, NACH ihrer Phase = Endbestand (eingefroren), NUR während ihrer eigenen Phase wird interpoliert.

{"t": 45, "fortschritt": 0.3, "phase": "land",
 "angreifer_schiffe": {"kleines_kriegsschiff": 4},
 "angreifer_einheiten": {"bogenschuetze": 12}}

angreifer_schiffe/verteidiger_schiffe erscheinen NUR, wenn die jeweilige Seite tatsächlich Schiffe eingesetzt hat (der Verteidiger also NUR mit stationierten Kriegsschiffen) — schickt der Angreifer Schiffe gegen eine Insel ohne gegnerische Marine, laufen sie unbehelligt ein und bleiben trotzdem sichtbar (keine Verluste, aber kein leeres Feld).

Rollenabhängig — der Angreifer bekommt verteidiger_schiffe/ verteidiger_einheiten NIE (nicht nur null, komplett weggelassen), der Verteidiger sieht beide Seiten. Beim letzten Frame (fortschritt >= 1) kommt zusätzlich ergebnis: "attacker_win"|"defender_win" dazu — für den Verteidiger immer, für den Angreifer NUR bei attacker_win (bei eigener Niederlage bleibt ergebnis im Live-Stream weg, genau wie im Bericht).

Während der belagerung-Phase (nur bei Angreifer-Sieg) zusätzlich, für BEIDE Rollen sichtbar (keine Truppen-/Schiffs-Info, daher keine Geheimhaltung):

{"t": 270, "fortschritt": 0.9, "phase": "belagerung",
 "belagerung_ziel": "haupthaus", "belagerung_stufe": 2}

belagerung_stufe zählt live vom Ausgangswert auf den im Kampfbericht protokollierten Endwert (razzia) herunter.

14a.7 Rollen-/Admin-abhängige Felder außerhalb von Kampf/Spionage


15. Beispiel

Angriff starten: (Typ-Schlüssel sind IMMER Deutsch, siehe balancing.SCHIFFE/ EINHEITEN — z. B. kleines_kriegsschiff, nicht small_warship)

POST /api/v1/fleets
X-API-Key: sk_live_...
{
  "origin_island_id": 12,
  "mission_type": "attack",
  "target": { "x": 20, "y": 8, "z": 20 },
  "ships": { "kleines_kriegsschiff": 10, "spaehschiff": 1, "kleines_handelsschiff": 5 },
  "units": { "bogenschuetze": 80, "steinewerfer": 40 },
  "razzia_ziel": "goldmine"
}
→ 201 {
  "id": 88, "mission": "attack", "origin_island_id": 12,
  "target": { "x": 20, "y": 8, "z": 20 },
  "ships": { "kleines_kriegsschiff": 10, "spaehschiff": 1, "kleines_handelsschiff": 5 },
  "units": { "bogenschuetze": 80, "steinewerfer": 40 }, "resources": {}, "loot": {},
  "state": "outbound", "speed_knots": 10, "distanz_felder": 42,
  "depart_at": "2026-07-02T18:20:00Z", "arrive_at": "2026-07-02T18:40:00Z",
  "return_at": null, "recallable_until": "2026-07-02T18:30:00Z"
}

mission im Response ist die vom Server aus der Fracht ABGELEITETE konkrete Mission (hier attack, da weder nur Spähschiffe noch ein Kolonisationsschiff an Bord sind) — siehe §7.

zurück zu Seekampf