From 0a28ab2cd462b9699e14665d51de6135b8a6c89d Mon Sep 17 00:00:00 2001 From: Lucas Hahmann Date: Tue, 13 Oct 2026 01:26:40 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20Design-Spezifikation=20f=C3=BCr=20ATM10?= =?UTF-8?q?-Statistikportal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .gitignore | 13 + .../specs/2026-10-13-atm10-stats-design.md | 368 ++++++++++++++++++ 2 files changed, 381 insertions(+) create mode 100644 .gitignore create mode 100644 docs/superpowers/specs/2026-10-13-atm10-stats-design.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b197ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Enthält echte Spielerdaten und IP-Adressen – nie versionieren +server-snapshot/ + +node_modules/ +.next/ +dist/ +build/ +.gradle/ +mod/run/ +*.log +.env +.env.* +!.env.example diff --git a/docs/superpowers/specs/2026-10-13-atm10-stats-design.md b/docs/superpowers/specs/2026-10-13-atm10-stats-design.md new file mode 100644 index 0000000..adc9909 --- /dev/null +++ b/docs/superpowers/specs/2026-10-13-atm10-stats-design.md @@ -0,0 +1,368 @@ +# ATM10 Stats – Design-Spezifikation + +Stand: 2026-10-13 · Status: zur Freigabe + +## 1. Ziel + +Ein öffentliches, modernes Statistikportal für den bei Mine-Hoster gehosteten +All-The-Mods-10-Server (ATM10). Vorbild für Inhalt und Anmutung ist +craftattack.one/stats (Kennzahlen, „Gerade online“, Top-10-Listen); das Portal +geht darüber hinaus mit Spielerprofilen, historischen Diagrammen und +ATM10-spezifischen Auswertungen (Mods, FTB Quests, Servergesundheit). + +Erfolgskriterien: + +- Spieler sehen ihre echten Werte innerhalb weniger Sekunden (Ereignisse) bzw. + ≤ 5 Minuten (Zähler) nach dem Geschehen. +- Keine erfundenen Daten: Jede angezeigte Kennzahl hat eine dokumentierte, + nachprüfbare Quelle. +- Die Mod verursacht keine messbare Tick-Last; kein Datenverlust bei Neustarts + von Gameserver oder Backend. +- Betrieb per Docker Compose hinter dem vorhandenen Traefik in der DMZ. + +## 2. Verifizierte Ausgangslage + +Quelle: `server-snapshot/` (vom Betreiber per SFTP bereitgestellt). + +| Fakt | Beleg | +|---|---| +| Minecraft 1.21.1 | `DataVersion: 3955` in `world/stats/*.json` | +| NeoForge 21.1.x, Java 21 | Modliste (`*-neoforge-1.21.1-*`), MC 1.21.1 setzt Java 21 voraus. Exakte NeoForge-/Java-Version: fehlt im Log-Ausschnitt → wird von der Mod beim Start ermittelt und gemeldet | +| 464 Mod-JARs | `mod-list.txt` | +| FTB Quests 2101.1.36, FTB Teams 2101.1.11, FTB Chunks, FTB Ranks, FTB Essentials | `mod-list.txt` | +| Quest-Fortschritt pro **Team** als SNBT (`task_progress`, `completed`, `started`, `claimed_rewards`, `completion_count`) | `world-ftbquests/*.snbt` | +| Spark 1.10.124 installiert | `mod-list.txt` (wird **nicht** als API genutzt) | +| Log enthält Login (UUID + IP), Join/Leave, Advancements/Goals/Challenges im Klartext, „Can't keep up“ | `latest.log` | +| Server läuft mit ~2,2 GB Heap und hat Lags bis 10 s | `latest.log` (AllTheLeaks, Can't keep up) | +| Mine-Hoster: SFTP-Vollzugriff, eigene Mods in `mods/` möglich | Mine-Hoster-Support (SFTP-, Mods-, Modpack-Artikel) | +| Ausgehendes HTTPS vom Gameserver | **unbekannt** → Hybrid-Design (Push + SFTP-Pull) | + +Mod-spezifische Zähler, die bereits in Vanilla-Stats auftauchen: +`apotheosis:world_tiers_activated`, `lootr:looted_stat`, +`waystones:waystone_activated`. Item-/Block-Statistiken enthalten Einträge aus +u. a. mekanism, powah, mysticalagriculture, ars_nouveau, irons_spellbooks, +modern_industrialization, oritech, create, refinedstorage, draconicevolution, +silentgear, occultism, theurgy, xycraft_world, minecolonies. + +Nicht installiert (daher keine Integration): Blood Magic, Botania. + +Datenschutz: `latest.log` enthält Spieler-IP-Adressen. `server-snapshot/` wird +nicht versioniert; Testfixtures aus dem Log werden anonymisiert (IP entfernt). + +## 3. Architektur + +``` + Mine-Hoster (ATM10, NeoForge 1.21.1) Eigener Server in der DMZ + ┌──────────────────────────────┐ HTTPS POST OPNsense → Traefik → Docker Compose + │ atmstats (serverseitige Mod) │ ──────────────▶ ┌────────────────────────────────┐ + │ Event-Hooks, Outbox-Journal │ Bearer + HMAC │ api Fastify/TS: ingest, │ + │ async Sender mit Backoff │ │ REST, SSE, admin │ + └──────────────────────────────┘ │ worker SFTP-Pull, Snapshots, │ + ▲ world/stats, world/ftbquests, │ Retention │ + │ config/ftbquests, logs │ web Next.js (App Router) │ + └───────────── SFTP-Pull ◀────────────────│ db PostgreSQL 16 │ + │ backup pg_dump täglich │ + └────────────────────────────────┘ +``` + +Monorepo: + +``` +mod/ NeoForge-Mod (Gradle, Java 21, ModDevGradle) +backend/ api + worker (TypeScript, Fastify, gemeinsamer Code, zwei Entrypoints) +web/ Next.js-Frontend (TypeScript) +shared/ gemeinsame TS-Typen + Zod-Schemas (Ingest-Protokoll, API-DTOs) +deploy/ docker-compose.yml, .env.example, Backup-Skript +tools/ Simulator (sendet realistische Mod-Batches), Fixture-Anonymisierer +docs/ README-Ergänzungen, ARCHITECTURE, DATA-SOURCES, OPERATIONS +``` + +### 3.1 Datenquellen und Zuständigkeit + +| Daten | Primärquelle | Fallback | +|---|---|---| +| Online-Status, Sessions | Mod: Login/Logout-Events | SFTP: Log-Parser (`joined/left the game`, `UUID of player`) | +| Gesamtzähler (Stats) | SFTP: `world/stats/.json` (maßgeblich, absolut) | — | +| Zähler zwischen Speicherungen | Mod: 5-min-Snapshot der Live-`ServerStatsCounter` online Spieler (absolute Werte) | — | +| Tode (Ursache, Killer, Dimension, Position grob) | Mod: `LivingDeathEvent` | Log: nicht verlässlich (Death-Messages lokalisiert/mod-abhängig) → nur Zähler aus Stats | +| Spieler-/Boss-Kills mit Waffe/Dimension | Mod: `LivingDeathEvent` mit Spieler als Quelle | Stats `minecraft:killed` (nur Zähler) | +| Advancements | Mod: `AdvancementEvent.AdvancementEarnEvent` | Log-Parser (`has made the advancement/reached the goal/completed the challenge`) | +| Dimensionswechsel | Mod: `PlayerChangedDimensionEvent` | — | +| FTB-Quest-Fortschritt | Mod liest `world/ftbquests/*.snbt` (eigener Thread, nur bei Änderung) | SFTP: dieselben Dateien | +| Quest-Definitionen (Kapitel, Titel) | Mod liest `config/ftbquests/quests/**` beim Start | SFTP | +| Teams | SNBT-Dateien von FTB Teams/Quests (`world/ftbteams/` wird beim Bau verifiziert) | SFTP | +| Versionen, Modliste | Mod: `ModList`, `SharedConstants`, `System.getProperty("java.version")` beim Start | Log-Header (falls vorhanden) | +| Servergesundheit (MSPT/TPS, Spieler, geladene Chunks, Heap) | Mod: eigene Messung über `ServerTickEvent.Pre/Post`, 60-s-Aggregat | Log: „Can't keep up“-Zählung | + +Konfliktregel für Zähler: Werte sind **absolut** (nicht Deltas). `stat_current` +übernimmt pro Schlüssel den Wert mit dem neuesten Beobachtungszeitpunkt; +ein kleinerer Wert ersetzt einen größeren nur, wenn er aus einer neueren +Stats-Datei stammt (z. B. nach Admin-Reset). Damit sind wiederholte und +verspätete Lieferungen unschädlich. + +Quest-Texte: Titel können als Übersetzungsschlüssel vorliegen +(`ftbquestslangsplitter` ist installiert). Die Mod liest zusätzlich +`config/ftbquests/quests/lang/en_us.snbt` bzw. die Lang-Dateien, falls +vorhanden; sonst wird die Quest-ID angezeigt. Exakte Pfade werden in der +Umsetzung anhand einer Kopie von `config/ftbquests/` verifiziert, bevor der +Parser fest verdrahtet wird. + +### 3.2 Bewusst nicht unterstützt + +Ohne verlässliche, dokumentierte serverseitige Quelle oder nur mit +ungeprüften Mod-Interna erreichbar; wird im Portal unter „Datenquellen“ +transparent ausgewiesen: + +- Energieproduktion/-verbrauch von Maschinen (Mekanism, Powah …) +- Inhalte von AE2-/Refined-Storage-Netzwerken +- Mana, Zauber, Source (Ars Nouveau, Iron's Spellbooks), Occultism-Rituale +- MineColonies-Kolonien +- exakte Spielerposition/Heatmaps (Datenschutz + Last) + +Spätere Erweiterung ist über das Ereignisprotokoll möglich (neue Event-Typen), +jeweils erst nach Prüfung der jeweiligen Mod-API gegen die installierte Version. + +## 4. Mod `atmstats` + +- NeoForge 21.1.x (Build gegen die zur ATM10-Version passende Version), + Java 21, ModDevGradle. Mod-ID `atmstats`. +- Rein serverseitig: `displayTest = "IGNORE_ALL_VERSION"` in + `neoforge.mods.toml`; Clients brauchen die Mod nicht. Keine Registrierungen + (Blöcke, Items, Netzwerkpakete). +- Keine Compile-Abhängigkeit auf andere Mods. + +### 4.1 Threading und Last + +- Event-Handler auf dem Server-Thread erzeugen nur kleine, unveränderliche + Event-Objekte und legen sie in eine `ConcurrentLinkedQueue` (O(1), keine + I/O, keine Serialisierung). +- Stats-Snapshot alle 5 min (konfigurierbar): auf dem Server-Thread werden je + online Spieler die Zählerwerte in ein `Object2IntMap` kopiert; Serialisierung + und Versand erfolgen im Mod-eigenen Daemon-Thread. Der Snapshot wird über + mehrere Ticks verteilt, falls viele Spieler online sind (max. 2 Spieler/Tick). +- Ein einzelner Daemon-Worker-Thread: Queue → Journal → Sender. + SNBT-Dateilesen ebenfalls dort (nur bei geänderter mtime, alle 2 min). +- Tick-Messung: `System.nanoTime()` in Pre/Post, Aggregat (avg/p95/max MSPT) + im Ringpuffer, Versand einmal pro Minute. + +### 4.2 Zuverlässigkeit + +- Event-ID: UUIDv7 (zeitlich sortierbar), erzeugt beim Entstehen des Events. +- Outbox-Journal: `/atmstats/outbox/` als Segmentdateien + (JSON Lines, fsync beim Rotieren, max. 1 MB/Segment). Ein Segment wird erst + gelöscht, wenn das Backend alle IDs bestätigt hat. +- Begrenzung: max. 50 MB bzw. 7 Tage; bei Überschreitung werden die ältesten + **Stats-Snapshots** zuerst verworfen (sind durch spätere ersetzbar), Ereignisse + zuletzt; Verwerfen wird geloggt. +- Beim Serverstopp (`ServerStoppingEvent`): Queue synchron ins Journal flushen + (Zeitlimit 2 s), Session-Ende für alle online Spieler erzeugen. +- Beim Start: Journal-Segmente werden erneut gesendet; ein `server_start`-Event + ermöglicht dem Backend, offene Sessions eines Absturzes zu schließen. +- Versand: `java.net.http.HttpClient` (HTTP/2, TLS-Verifikation an), Batches + bis 500 Events/256 KB, gzip, Timeout 10 s. Backoff exponentiell + 1 s → 5 min mit Jitter. Bei 4xx (außer 408/429) wird der Batch nicht endlos + wiederholt, sondern in `outbox/rejected/` abgelegt und geloggt. +- Startup-Selbsttest: `GET /api/ingest/ping` → Ergebnis ins Log + („atmstats: backend reachable / NOT reachable: “). Damit wird die + offene Frage „ausgehendes HTTPS erlaubt?“ beim ersten Start beantwortet. + +### 4.3 Konfiguration (`config/atmstats-server.toml`, NeoForge ModConfigSpec) + +`enabled`, `endpoint`, `serverId`, `token`, `hmacSecret`, +`statsSnapshotMinutes` (5), `questScanMinutes` (2), `healthIntervalSeconds` +(60), `maxOutboxMb` (50). Ohne Token bleibt die Mod passiv (nur Log-Hinweis). + +### 4.4 Protokoll (Version 1) + +``` +POST /api/ingest +Authorization: Bearer +X-ATM-Server: +X-ATM-Timestamp: +X-ATM-Signature: hex(HMAC-SHA256(secret, timestamp + "." + sha256(body))) +Content-Encoding: gzip +{ "protocol": 1, "events": [ { "id": "", "type": "...", "ts": , ...payload } ] } +→ 200 { "accepted": ["", ...], "duplicate": ["", ...], "rejected": [{ "id", "reason" }] } +``` + +Event-Typen: `server_start` (Versionen, Modliste), `server_stop`, +`player_join`, `player_leave`, `player_death`, `entity_kill`, `advancement`, +`dimension_change`, `stats_snapshot` (absolute Werte je Kategorie), +`quest_progress` (Team-ID, abgeschlossene Quest-IDs mit Zeitstempel, +Task-Fortschritt-Zähler), `quest_defs` (Kapitel/Quests/Titel, nur bei Änderung, +mit Hash), `team_info`, `health`. Schemas als Zod in `shared/` und als +JSON-Schema-Testfixtures, gegen die auch die Java-Serialisierung getestet wird. + +## 5. Backend + +TypeScript, Node 22 LTS, Fastify, Zod, `pg` mit SQL (kein ORM), +Migrationen mit `node-pg-migrate`. Zwei Prozesse aus einer Codebasis: + +**api** +- `GET /api/ingest/ping` (authentifiziert), `POST /api/ingest`. + Prüft Token (konstante Zeit), Zeitfenster ±5 min, HMAC; Rate-Limit; + Body-Limit 2 MB entpackt. Verarbeitung pro Batch in einer Transaktion: + `INSERT INTO events … ON CONFLICT (event_id) DO NOTHING`, nur neu + eingefügte Events werden projiziert (sessions, deaths, stat_current …). + Danach Benachrichtigung via `pg_notify('atm_live', …)`. +- Öffentlich: `GET /api/overview`, `/api/online`, + `/api/leaderboards` (Liste der Boards), `/api/leaderboards/:board?period=all|30d|7d`, + `/api/players?search=`, `/api/players/:uuid`, + `/api/players/:uuid/history?metric=&range=`, `/api/players/:uuid/items?mod=`, + `/api/mods` (Modliste + Aktivität je Mod), `/api/quests` (Kapitelfortschritt), + `/api/server` (Versionen, Health-Verlauf), `/api/sources` (Datenquellen-Status: + letzter Push, letzter SFTP-Sync), `/api/avatar/:uuid` (Proxy-Cache für + Spielerköpfe, 24 h; keine Besucher-IPs an Dritte). +- `GET /api/live` (SSE): abonniert `LISTEN atm_live`; Events `online`, + `death`, `advancement`, `quest`, `counters`; Heartbeat 25 s, + `Last-Event-ID`-Resume aus einem In-Memory-Ringpuffer (500 Einträge). +- Admin (`/api/admin/*`): Login mit Benutzer/Passwort (argon2id), + Session-Cookie (httpOnly, Secure, SameSite=Strict), CSRF-Token im Header, + Login-Rate-Limit. Funktionen: Status Mod/SFTP, letzte Fehler, manueller + Sync, Spieler ausblenden/einblenden, Ingest-Token rotieren (Anzeige einmalig). + Erster Admin wird per CLI (`npm run admin:create`) angelegt. +- Ausgeblendete Spieler erscheinen in keiner öffentlichen Antwort. +- Sicherheitsheader (über Fastify-Helmet), CORS nur eigene Domain. + +**worker** +- SFTP-Pull (ssh2-sftp-client) alle 15 min (konfigurierbar): Verzeichnislisten + von `world/stats`, `world/advancements`, `world/ftbquests`, + `config/ftbquests/quests`, `logs/latest.log`. Herunterladen nur bei geänderter + Größe/mtime (Zustand in `sync_state`); Log inkrementell ab Byte-Offset, + Rotation erkannt über kleineren Größenwert. Ergebnisse durchlaufen dieselben + Projektionen wie Ingest (synthetische, deterministische Event-IDs aus + Datei+Inhalt-Hash → idempotent). +- Advancements-Dateien (`world/advancements/.json`) liefern die + vollständige Liste erreichter Advancements inkl. Zeitpunkten. +- Snapshot-Job: stündlich `stat_snapshots` (Auswahl relevanter Metriken je + Spieler), täglich um 00:05 Tageswerte; Retention: stündlich 30 Tage, + täglich unbegrenzt; `server_health` Minutenwerte 14 Tage, danach + Stundenaggregate. +- Sitzungsbereinigung: offene Sessions ohne Lebenszeichen > 10 min nach + `server_start` werden mit letztem bekannten Zeitpunkt geschlossen. +- Job-Locking über `pg_try_advisory_lock`, damit Jobs nie doppelt laufen. + +Der Worker ist optional: ohne SFTP-Zugangsdaten läuft das System nur mit Push. +Ohne erreichbaren Push läuft es nur mit Pull (dann Online-Status und Tode mit +bis zu 15 min Verzögerung bzw. nur als Zähler). + +## 6. Datenbank + +Kern-Tabellen (Details in Migrationen): + +- `players(uuid PK, name, first_seen, last_seen, hidden bool)` +- `name_history(uuid, name, seen_at)` +- `teams(team_id PK, name, type)`, `team_members(team_id, uuid)` +- `events(event_id PK, server_id, type, ts, received_at, source 'push'|'sftp', payload jsonb)` +- `sessions(id, uuid, started_at, ended_at, end_reason)` +- `stat_current(uuid, category, key, value bigint, observed_at, source, PK(uuid, category, key))` +- `stat_snapshots(uuid, metric, bucket timestamptz, granularity 'hour'|'day', value bigint)` +- `deaths(event_id, uuid, ts, dimension, cause, killer_type, killer_name)` +- `kills(event_id, uuid, ts, dimension, victim_type, is_boss, weapon)` +- `advancements(uuid, advancement_id, achieved_at, title, PK(uuid, advancement_id))` +- `quest_defs(quest_id PK, chapter_id, chapter_title, title, def_hash)` +- `quest_progress(team_id, quest_id, completed_at, PK(team_id, quest_id))` +- `server_info(id, started_at, mc_version, neoforge_version, java_version, mods jsonb)` +- `server_health(bucket, mspt_avg, mspt_p95, mspt_max, tps, players, chunks, heap_used_mb, heap_max_mb)` +- `sync_state(path PK, size, mtime, offset, hash, synced_at)` +- `admin_users`, `admin_sessions`, `ingest_tokens(id, hash, created_at, revoked_at)` + +Ranglisten werden aus `stat_current` mit Indizes auf `(category, key, value DESC)` +berechnet. Abgeleitete Metriken (z. B. Gesamtstrecke = Summe aller `*_one_cm`, +Items je Mod = Summe nach Namespace) liegen als SQL-Views vor. Eine +Materialized View `leaderboard_cache` wird höchstens alle 60 s +`REFRESH … CONCURRENTLY` aktualisiert. + +## 7. Ranglisten (Start-Set, alle aus belegten Quellen) + +| Board | Metrik | +|---|---| +| Dauerzocker | `minecraft:custom/minecraft:play_time` (Ticks → h) | +| Friedhof | `minecraft:custom/minecraft:deaths` | +| Monsterjäger | `minecraft:custom/minecraft:mob_kills` | +| Abrissbirne | Summe `minecraft:mined/*` | +| Weltenbummler | Summe `minecraft:custom/*_one_cm` (→ km) | +| Flieger | `minecraft:custom/minecraft:fly_one_cm` | +| Diamantenbank | `minecraft:mined/minecraft:diamond_ore` + `deepslate_diamond_ore` (Abbau, nicht Besitz – so ausgewiesen) | +| Techniker | Summe `crafted`+`used` in Tech-Namespaces (mekanism, mekanismgenerators, powah, modern_industrialization, oritech, create, immersiveengineering, appliedenergistics2, refinedstorage, draconicevolution, integrateddynamics, pneumaticcraft) | +| Magier | dito Magie-Namespaces (ars_nouveau, ars_elemental, ars_additions, irons_spellbooks, occultism, theurgy, rootsclassic, forbidden_arcanus, evilcraft, reliquary) | +| Farmer | dito (mysticalagriculture, mysticalagradditions, productivebees, botanypots, pamhc2*, farmersdelight, herbsandharvest) | +| Questmeister | Anzahl abgeschlossener Quests des Teams (Teamleaderboard + Spieler über Mitgliedschaft) | +| Abenteurer | Anzahl Advancements | +| Schatzjäger | `lootr:looted_stat` | +| Weltenstufe | `apotheosis:world_tiers_activated` | + +Die Namespace-Kategorien liegen in einer editierbaren Konfigurationsdatei +(`backend/config/mod-categories.json`) und werden mit der echten Modliste +abgeglichen; Namespaces ohne installierte Mod werden ignoriert und geloggt. + +## 8. Frontend + +Next.js (App Router), TypeScript, Tailwind CSS, Recharts. Dunkles, +eigenständiges Gaming-Design (kein Nachbau von craftattack-Assets), responsiv, +zugänglich (Kontrast, Tastatur, `prefers-reduced-motion`). Server-Rendering mit +kurzer Revalidierung; Live-Teile clientseitig per SSE. + +Seiten: +- `/` Übersicht: Kennzahlen (Online, Spielstunden gesamt, Tode, Quests, + Advancements), „Gerade online“, Live-Ticker, Top-10-Karten. +- `/leaderboards` und `/leaderboards/[board]` (Zeitraum all/30d/7d aus Snapshots). +- `/players` (Suche) und `/players/[name]`: Kopf, Spielzeit, Sessions-Heatmap + (Wochentag × Stunde), Verlaufsdiagramme, Bewegung nach Art, Kampf + (Kills nach Mob, Todesursachen), Items nach Mod (Tabs mined/crafted/used/picked_up), + Advancements, Team-Quests, Rangpositionen. +- `/atm10`: Aktivität je Mod/Kategorie, Quest-Kapitelfortschritt, meistgenutzte + Mods. +- `/server`: Versionen (MC, NeoForge, Java, ATM10-Modliste mit Suche), TPS/MSPT-, + Spieler- und Heap-Verlauf. +- `/sources`: Datenquellen, Aktualität, Einschränkungen (aus `DATA-SOURCES`). +- `/admin`: Login, Status, Sync, Spieler ausblenden, Token-Rotation. + +## 9. Deployment und Betrieb + +- `deploy/docker-compose.yml`: `db` (postgres:16-alpine, Volume), `api`, + `worker`, `web`, `backup`. Nur `web` und `api` hängen am externen + Traefik-Netz (`TRAEFIK_NETWORK`); `db` nur im internen Netz. +- Traefik-Labels: Router `Host(${DOMAIN}) && PathPrefix(/api)` → api, + `Host(${DOMAIN})` → web; Entrypoint/Cert-Resolver per `.env`. Optionale + Middleware `ipAllowList` für `/api/ingest` (`INGEST_ALLOWED_IPS`). +- OPNsense: eingehend 443 → Traefik (bereits vorhanden); für den Pull-Fallback + ausgehend DMZ-Host → Mine-Hoster-SFTP-Host/-Port. +- Healthchecks für alle Container; `api` und `worker` warten auf DB und führen + Migrationen beim Start aus (Advisory-Lock). +- Backup: täglicher `pg_dump -Fc` mit 14-Tage-Rotation in ein Volume; + Restore-Anleitung in `OPERATIONS.md`. +- Logs strukturiert (pino, JSON), keine Tokens/IPs im Log. + +## 10. Tests + +- Mod (JUnit 5, ohne Minecraft-Laufzeit): Journal (Schreiben, Rotation, Recovery + nach abgeschnittener letzter Zeile), Backoff, Batch-Bildung, HMAC, JSON gegen + Schema-Fixtures, SNBT-Parser an den echten Quest-Dateien. Minecraft-abhängiger + Code ist dünn und von der Logik getrennt. Zusätzlich manueller Testlauf auf + einem lokalen NeoForge-Server (`runServer`). +- Backend (Vitest + Postgres im Compose-Testprofil): Ingest-Auth (falsches + Token, abgelaufener Zeitstempel, falsche Signatur), Idempotenz (gleicher Batch + doppelt), Projektionen, Stats-Parser an echten `world/stats`-Dateien, + Log-Parser an anonymisiertem Log, Ranglisten-SQL, SSE. +- Frontend: Komponententests (Vitest + Testing Library) und Playwright-Smoke-Test + gegen den Compose-Stack mit Simulator-Daten. +- Simulator (`tools/simulator`): erzeugt signierte Batches im echten Protokoll. + +## 11. Dokumentation + +`README.md` (Überblick, Schnellstart), `docs/ARCHITECTURE.md`, +`docs/DATA-SOURCES.md` (jede Kennzahl → Quelle, Aktualität, Einschränkungen), +`docs/OPERATIONS.md` (Deployment Traefik/OPNsense, Mod-Installation per +Mine-Hoster-SFTP, Konfiguration, Token-Rotation, Backup/Restore, +Neustartverhalten, Fehlerbilder), `mod/README.md`. + +## 12. Offene Punkte, die in der Umsetzung geklärt werden + +1. Exakte NeoForge-Version des Servers → Mod-Build gegen 21.1.x; Kompatibilität + innerhalb 21.1 ist gegeben, finale Version per Startup-Log bestätigen. +2. Ausgehendes HTTPS bei Mine-Hoster → Selbsttest beim ersten Start. +3. Struktur von `config/ftbquests/quests/` und `world/ftbteams/` → Betreiber + stellt Kopie bereit, bevor Quest-Titel/Team-Namen-Parser finalisiert werden; + bis dahin werden Quest-IDs angezeigt. +4. SFTP-Host/Port/Benutzer → `.env` des Betreibers (nicht im Repo).