Manuelle serverseitige Leaderboards
Bestandsaufnahme vor dem Umbau
Die produktive Run-Pipeline ist rpc_submit_run in submitOnlineRunRpc.ts. Sie l?dt die serverseitig erzeugte Run-Session, pr?ft Ticket, Replay-Hash und Timeline, berechnet Score, Dauer, Accuracy, Damage Dealt, Damage Taken und Medaille in onlineRunValidation.ts und schreibt danach Run-Validation, kanonisches Ergebnis, Replay und Run-Historie in Nakama Storage Objects.
Vor diesem Umbau erstellte rpc_submit_run das offizielle Nakama-Leaderboard ?ber leaderboardCreate und schrieb mit leaderboardRecordWrite. Ein eigener Leaderboard-Read existierte weder im Nakama-Modul noch im 3D-Client. Die 3D-Completion-Anzeige verwendete nur die beim Write zur?ckgegebene Legacy-Rangnummer. Das eingefrorene 2D-Projekt wurde nicht untersucht oder ver?ndert.
Die Run-Historie enth?lt Run-/Result-ID, Owner, Seed, numerische Client-Run-Kategorie, Completion-Status, Zeiten, kanonische und gemeldete Metriken, Award sowie Replay-/Validierungsreferenzen. Die erg?nzenden Online-Run-Storage-Objekte enthalten Submission-ID, fachliche Kategorie, Seed-Modus, Balance-Version, Loadout und das vollst?ndige serverseitige Validation-Ergebnis. Kein einzelnes vorhandenes Objekt enth?lt jedoch alle Projektionsfelder, und Nakama Storage bietet f?r diese Objekte keine effiziente globale Window-Function-Abfrage. Deshalb wird ein eigenes SQL-Read-Model verwendet.
Ersetzt wurden die neuen prim?ren Aufrufe wie folgt:
- Write:
leaderboardCreate+leaderboardRecordWrite->upsertLeaderboardEntrynach erfolgreicher Replay-Validierung. - Read: Client-Reads -> authentifizierter RPC
leaderboard_get. - Create/List/Around Owner/Delete: Es gab daf?r keine 3D-Client-Aufrufe. Der neue Pfad ben?tigt sie nicht.
- Legacy-Nakama-Leaderboard-Writes und
leaderboard_publications-Storage sind entfernt; nur Manual SQL bleibt.
Architektur und Datenfluss
Replay + Run-Submission
-> bestehende serverseitige Run-Validierung und Metrikberechnung
-> idempotentes Upsert in leaderboard_run_entries
-> bestehende kanonische Run-Historie
-> leaderboard_get
-> NakamaLeaderboardRpcClient
Der Client kann keine Leaderboard-Werte schreiben. rpc_submit_run ignoriert den Client-Score f?r die Projektion und verwendet ausschlie?lich validation.authoritativeResult. Abgebrochene oder invalidierte Runs erreichen den Projektionszweig nicht.
Run-Historie (Nakama Storage) und Projektion (eigene SQL-Tabelle) k?nnen in der JavaScript-Runtime nicht in einer gemeinsamen Datenbanktransaktion geschrieben werden. Deshalb wird zuerst idempotent projiziert und erst danach die bestehende Run-Historie finalisiert. Ein Projektionsfehler setzt die vorhandene Submission auf TechnicalFailure; derselbe Request kann mit derselben Submission-ID wiederholt werden. UNIQUE(run_id), UNIQUE(submission_id) und ON CONFLICT (run_id) DO UPDATE verhindern Duplikate. Ein Konflikt darf nur denselben Owner und dieselbe Submission aktualisieren.
Tabelle und Indizes
leaderboard_definitions registriert ausschließlich Kontexte, die der Server beim Erstellen einer gültigen Online-Run-Session akzeptiert hat. Dadurch werden unbekannte Seeds abgelehnt, während bekannte Boards vor dem ersten Completion-Eintrag leer lesbar bleiben.
leaderboard_run_entries ist eine eigene Anwendungstabelle; Nakama-Kerntabellen werden weder ver?ndert noch abgefragt. Die Referenzmigration liegt unter src/server/nakama/migrations/001_manual_leaderboards.sql; die Runtime f?hrt dieselbe idempotente DDL beim Start aus.
Wichtige Spalten sind run_id, submission_id, owner_id, username_snapshot, Kontextfelder, alle f?nf Metriken, Waffen-Snapshots, Completion-/Validation-Zeit und die Flags is_valid/is_leaderboard_eligible. Checks sch?tzen negative Werte und Accuracy au?erhalb 0..1.
Der Definitionskatalog besitzt einen zusammengesetzten Primärschlüssel aus Seed, Kategorie und Versionen.
Die drei partiellen Indizes sind auf sichtbare, g?ltige Eintr?ge beschr?nkt:
idx_leaderboard_score_context: Kontext, Score absteigend und Dauer aufsteigend.idx_leaderboard_duration_context: Kontext, Dauer aufsteigend und Score absteigend.idx_leaderboard_owner_context: Owner plus vollst?ndiger Kontext f?r die eigene Platzierung.
Accuracy und Damage verwenden dieselbe enge Kontextselektion und werden vorerst nicht separat ?berindiziert. Bei realer Last sind Query-Pl?ne zu messen, bevor weitere Metrikindizes hinzukommen.
Kontext, Kategorien und Eligibility
Ein Board wird durch Seed-ID, Kategorie, Score-Version, Balance-Version und Metrik bestimmt. Unterst?tzte Kategorien sind developer, official_daily, official_weekly, official_monthly, official_seasonal, custom und community. Bestehende Online-Kategorien werden zentral abgebildet: official plus Seed-Modus wird zur passenden offiziellen Kategorie, open wird custom.
Ein Eintrag ist nur sichtbar, wenn er validiert, vollst?ndig abgeschlossen und leaderboard-f?hig ist; Seed und Kategorie m?ssen g?ltig sein, Metriken in ihren Grenzen liegen und Score-/Balance-Version m?ssen der serverseitigen Whitelist entsprechen. Kontextfilter verhindern, dass Developer, Custom oder Community in offiziellen Boards erscheinen. Verd?chtige Runs werden bereits durch Validation Findings invalidiert. Eine sp?tere Administration kann is_leaderboard_eligible ohne L?schen der Run-Historie auf false setzen.
Ranking
Zun?chst wird mit ROW_NUMBER() OVER (PARTITION BY owner_id ...) pro Owner der beste Run gew?hlt. Danach erh?lt jeder sichtbare Best-Run mit einem zweiten ROW_NUMBER() einen eindeutigen Platz. Vollst?ndige Gleichst?nde werden stabil ?ber run_id ASC aufgel?st. Falls fachlich sp?ter Gleichst?nde denselben Platz erhalten sollen, kann nur die ?u?ere Funktion auf RANK() oder DENSE_RANK() umgestellt werden.
- Score: Score desc, Dauer asc, Accuracy desc, Damage Taken asc, Validated At asc, Run-ID asc.
- Duration: Dauer asc, Score desc, Accuracy desc, Damage Taken asc, Validated At asc, Run-ID asc.
- Accuracy: Accuracy desc, Score desc, Dauer asc, Damage Taken asc, Validated At asc, Run-ID asc.
- Damage Dealt: Damage Dealt desc, Score desc, Dauer asc, Accuracy desc, Run-ID asc.
- Damage Taken: Damage Taken asc, Score desc, Dauer asc, Accuracy desc, Run-ID asc.
Top-N und die eigene Platzierung werden aus einem gemeinsamen gerankten CTE gelesen. Es wird nie das gesamte Board in Anwendungscode geladen. Ist der eigene Eintrag bereits in Top-N, wird er dort mit isCurrentPlayer=true markiert und currentPlayer.entry bleibt null.
RPC leaderboard_get
Der RPC verlangt eine Nakama-Session. Die Owner-ID stammt ausschlie?lich aus ctx.userId; ein ?bergebenes ownerId wird abgelehnt. Kategorien, Versionen und Metriken sind Whitelists. limit ist standardm??ig 10, mindestens 1 und maximal 100 (gr??ere positive Werte werden auf 100 normalisiert). Sortierausdr?cke stammen ausschlie?lich aus festen serverseitigen Definitionen und nie aus Clientdaten.
{
"seedId": "DAILY-2026-07-18",
"runCategory": "official_daily",
"scoreVersion": "performance-score-v2",
"balanceVersion": "weapon-balance-2026-07",
"metric": "score",
"limit": 10
}
Die Antwort enth?lt leaderboard, entries und currentPlayer. Kontrollierte Fehler haben die Form {"error":{"code":"...","message":"...","correlationId":"..."}}. Verwendet werden UNAUTHENTICATED, INVALID_REQUEST, UNKNOWN_SEED, UNKNOWN_LEADERBOARD, UNSUPPORTED_METRIC, UNSUPPORTED_SCORE_VERSION, UNSUPPORTED_BALANCE_VERSION, LEADERBOARD_UNAVAILABLE und INTERNAL_ERROR. SQL, Tabellennamen und Stack Traces werden nicht zur?ckgegeben; Logs enthalten nur Korrelation, User, Seed, Metrik und gek?rzte technische Fehlerdaten.
Es gibt aktuell kein zentrales RPC-Rate-Limiting im Projekt. Authentifizierung, Limit und Whitelists begrenzen den Aufruf; infrastrukturelles Rate-Limiting am Nakama/API-Gateway bleibt ein Deployment-Punkt.
Provider
Leaderboards werden ausschließlich über das Manual-SQL-Read-Model (leaderboard_run_entries) geschrieben und gelesen. Es gibt keinen LegacyNakama-Provider und keinen Dual-Write mehr.
Backfill und Legacy-Daten
Ein vollst?ndiger automatischer Backfill ist aus den vorhandenen Run-History-Summaries allein nicht zuverl?ssig m?glich: ?lteren Ergebnissen fehlen teilweise Submission-ID, fachliche Kategorie/Seed-Modus, Balance-Version, Accuracy, Damage, Waffen oder eindeutige Validierungsnachweise. Au?erdem f?hrt die neue Anwendungsschicht absichtlich keine SQL-Abfrage gegen interne Nakama-Storage- oder Leaderboard-Tabellen aus.
Der bereitgestellte Importkern backfillLeaderboardEntries nimmt ausschlie?lich bereits normalisierte, nachweislich validierte LeaderboardRunEntryWrite-Datens?tze entgegen. Er ist ?ber Run-ID/Submission-ID idempotent, unterst?tzt Dry Run und meldet read, migrated, skipped, failed und dryRun. Er ist absichtlich nicht als Client-RPC registriert.
Operatives Vorgehen:
- Validierte Runs ?ber die unterst?tzten Nakama Storage-/Admin-APIs exportieren und mit Online-Session/Validation-Daten zusammenf?hren.
- Nur vollst?ndig belegbare Eintr?ge normalisieren; fehlende oder widerspr?chliche Datens?tze als
skippedprotokollieren. - Zuerst Dry Run gegen
backfillLeaderboardEntries, Statistiken und Stichproben pr?fen. - Danach denselben Batch ohne Dry Run importieren; Wiederholungen sind sicher.
- Falls n?tig fehlende Legacy-Metadaten ?ber offizielle Nakama-Leaderboard-APIs exportieren, niemals ?ber interne Tabellen.
Mit Migration 002_reset_leaderboard_for_performance_score_v2.sql werden ausschließlich alte Zeilen der eigenen Projektionstabellen leaderboard_run_entries und leaderboard_definitions transaktional entfernt. Run-Historien, ursprüngliche Submissions, Validierungen und Replay-Daten bleiben unverändert. Die Migration ist idempotent; der Backfill und direkte Projektionseinträge akzeptieren ausschließlich performance-score-v2.
Die Bewertungsformel, Punkteaufschlüsselung und Versionswechsel sind in Run-Scoring dokumentiert.
Godot-Client und sp?tere UI
ILeaderboardClient und NakamaLeaderboardRpcClient sind in der 3D-DI registriert. Der Client verwendet ausschlie?lich leaderboard_get, die vorhandene Session und kontrollierte LeaderboardClientException-Fehler. Es wurde keine Szene und kein Button erg?nzt.
Eine sp?tere UI sollte ILeaderboardClient injizieren, mit Limit 10 laden, entries rendern, den Eintrag mit isCurrentPlayer hervorheben und currentPlayer.entry nur dann separat anzeigen, wenn isInDisplayedEntries=false. Loading-, Empty- und kontrollierte Error-States m?ssen ber?cksichtigt werden. Die UI darf keine Nakama-Leaderboard-SDK-Methode direkt verwenden.
Neue Metrik hinzuf?gen
LeaderboardMetric/Whitelist und Client-Wire-Mapping erweitern.- Eine feste Sortierdefinition in
leaderboardRankingOrdererg?nzen; keine Client-Sortierung ?bernehmen. - Erforderliche Spalte, Checks und bei gemessener Notwendigkeit einen Kontextindex erg?nzen.
- Row-Mapping und RPC-/Client-DTO pr?fen.
- Ranking-, Tie-Breaker-, RPC-, Eligibility- und Clienttests erg?nzen.
- Die sp?tere Clientdarstellung um Label und Formatierung erweitern.