docs: Design-Spezifikation für ATM10-Statistikportal

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
lucashahmannandClaude Opus 5.5 committed 2026-10-13 01:26:40 +02:00
commit 0a28ab2cd4
2 files changed
+381

No files matched your search

@@ -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/<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.
4. SFTP-Host/Port/Benutzer → `.env` des Betreibers (nicht im Repo).