Files
lucashahmannandClaude Opus 5.5 e66f89f49e feat: ATM10-Statistikportal
Statistikportal für den All-the-Mods-10-Server (Minecraft 1.21.1, NeoForge 21.1):

- mod/: serverseitige NeoForge-Mod atmstats – Ereignisse, Live-Zähler, Servergesundheit,
  FTB-Dateien; Outbox auf der Festplatte, signierter gzip-Versand per HTTPS mit Backoff
- backend/: Fastify + PostgreSQL – idempotenter Ingest, Projektionen, Parser für SNBT,
  FTB Quests/Teams, world/stats, world/advancements und latest.log; Ranglisten, Snapshots,
  Retention, öffentliche API, SSE-Live-Updates, Admin mit argon2/CSRF, SFTP-Worker
- web/: Next.js-Dashboard – Übersicht, Ranglisten, Spielerprofile, ATM10, Server,
  Datenquellen, Verwaltung
- shared/: Protokoll v1 und API-Typen; tools/: Simulator, Log-Anonymisierer, RCON-Client
- deploy/: Docker Compose hinter Traefik, Backup; docs/: Architektur, Datenquellen, Betrieb

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:25 +02:00

370 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<uuid>.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: `<server>/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: <Grund>“). 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 <token>
X-ATM-Server: <serverId>
X-ATM-Timestamp: <unix ms>
X-ATM-Signature: hex(HMAC-SHA256(secret, timestamp + "." + sha256(body)))
Content-Encoding: gzip
{ "protocol": 1, "events": [ { "id": "<uuidv7>", "type": "...", "ts": <ms>, ...payload } ] }
→ 200 { "accepted": ["<id>", ...], "duplicate": ["<id>", ...], "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/<uuid>.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.
→ Geklärt am 13.10.2026 anhand der echten Dateien, siehe `docs/DATA-SOURCES.md`.
4. SFTP-Host/Port/Benutzer → `.env` des Betreibers (nicht im Repo).