Run Completion, Medaillen und Run-Historie
Diese Seite beschreibt den kanonischen Abschluss eines 3D-Dungeon-Runs. Die zentrale Domäne liegt in src/HolodeckArena.Game3D/Features/Dungeons/Run/Completion; Nakama persistiert Ergebnisse und Aggregate unter src/server/nakama/modules/src.
Bestandsaufnahme und Integrationspunkte
DungeonRunServiceerstellt und startet Nakama-Online-Run-Sessions.DungeonObjectiveTracker3Dführt Boss- und Gegnerquoten zusammen und liefert einen unveränderlichenRunObjectiveSnapshot.DungeonRun3DControllererfasst Raum-, Kill-, Truhen-, Todes-, Shot-, Treffer-, Damage-, Power-up- und Bossereignisse explizit ueberRunMetricsTracker.DungeonExitController3Dschaltet nur den Ausgang frei. Die fachliche Abschlussentscheidung trifft ausschließlichIRunCompletionService.- Completion schreibt Replay-Chunks lokal (
LocalReplayStore) und synchronisiert überPendingRunResultQueuenach Nakama (rpc_submit_run). - Nakama persistiert immutable Ergebnisse, History-Index, Finalisierungszeiger und Profilaggregat unter
online_run_sessionsund den Run-Result-Collections.
Fachliches Modell
Completion und Auszeichnung sind getrennt:
| CompletionStatus | Award | Sichtbarer Text |
|---|---|---|
| Aborted | beliebig, serverseitig auf None erzwungen | Abgebrochen |
| Completed | None | Erfolgreich abgeschlossen |
| Completed | Bronze | Mit Bronze abgeschlossen |
| Completed | Silver | Mit Silber abgeschlossen |
| Completed | Gold | Mit Gold abgeschlossen |
RunResult ist Schema-Version 2. Es speichert Run-/Spieler-/Seed-/Definition-IDs, Zeiten, Grund, Kategorie, Objective-Snapshot, gemeldete und optional verifizierte Metriken, Replay-/Party-/Team-IDs, Loadout-/Balanceinformationen, Scoreaufschlüsselung sowie ID und Version der Medaillenregeln.
Zustandsdiagramm
stateDiagram-v2
[*] --> Running
Running --> Finalizing: CompleteAsync oder AbortAsync
Running --> Running: Exit bei fehlenden Zielen
Finalizing --> Completed: erfolgreicher Run lokal finalisiert
Finalizing --> Aborted: Abbruch lokal finalisiert
Completed --> Persisted: Nakama bestätigt
Aborted --> Persisted: Nakama bestätigt
Completed --> PersistenceFailed: temporärer/permanenter Fehler
Aborted --> PersistenceFailed: temporärer/permanenter Fehler
PersistenceFailed --> Persisted: begrenzter Retry
RunCompletionService schützt den Übergang mit einem Lock und genau einem gespeicherten Finalisierungs-Task. Gleichzeitige Boss-, Exit-, Todes- oder UI-Ereignisse erhalten dasselbe Resultat und lösen nur einen Repository-Aufruf aus. Ein lokaler Abschluss wird bei einem Netzwerkfehler nicht zurückgerollt.
Die Resultatbildung liegt in der puren RunResultFactory: Score, Award, Dauer, Loadout-Snapshot und versionierte Balance-Metadaten entstehen dort ohne EventBus-, Queue- oder Persistenzzugriff. RunCompletionService liefert die Abschlusszeit über IGameClock, orchestriert Events und übergibt das Resultat an die Pending-Queue.
Die erlaubten Zustandswechsel liegen in HolodeckArena.Shared.Runs.RunLifecycle: Running → Finalizing → Completed/Aborted. Terminalzustände können nicht erneut geöffnet oder gegeneinander ausgetauscht werden. Replay-Prüfung ist zweistufig: ReplayIntegrityValidator erzeugt erst nach Hash-, Größen-, Format- und Binding-Prüfung ein ReplayArtifact; ReplayRuleValidator prüft anschließend Timeline und fachliche Ereignisregeln.
Erfolgreicher Ablauf
DungeonExitController3Dwird durch das bestehende Objective-Event freigeschaltet.- Beim Betreten prüft
DungeonRun3DControllerden zentralen Objective-Snapshot. RunCompletionService.CompleteAsyncwechselt atomar nachFinalizing.- Objective- und Metrics-Snapshots werden eingefroren und die Laufzeit gestoppt.
IRunAwardEvaluatorbewertet das lokale Ergebnis;BootstrapBaseregistriertDevelopmentRunAwardEvaluatormitperformance-medals-v2.- Das lokale
RunCompletedEventwird veröffentlicht. PendingRunResultQueueübernimmt als Single Writer Replay-Upload, Pending-Leaderboard-Registrierung und Submission.NakamaRunSubmitPortruftrpc_submit_runauf; bei Timeout fragt esrpc_get_run_submissionfür idempotente Wiederaufnahme ab.- Nakama validiert Authentifizierung, Session/Ticket, Replay-Hash und Timeline und berechnet Score, Medaille und Eligibility serverseitig.
- Resultat, History-Index, Profilaggregat und gegebenenfalls Manual-Leaderboard-Projektion werden persistiert; der Result-Screen zeigt das kanonische Ergebnis und den Synchronisationsstatus.
Abbruchablauf
- Der Button „Run aufgeben“ öffnet zuerst einen
ConfirmationDialogund verwendet anschließendUserCancelled. - Das endgültige Player-Todessignal verwendet
LivesDepletedohne Umdeutung als Benutzerabbruch. - Technische Gründe
Disconnected,ServerShutdownundInvalidRunStatesind modelliert und können an denselbenAbortAsync-Pfad angeschlossen werden. AbortAsyncerzwingt unabhängig vom EvaluatorAward.None, speichert alle bis dahin erfassten Metriken und erzeugt keinen offiziellen erfolgreichen Leaderboardeintrag.- Ein Abbruch vor dem eigentlichen Start der vorbereiteten Lounge-Session verwendet aus Kompatibilitätsgründen noch den alten
rpc_abort_run; ein gestarteter Gameplay-Run verwendet immer die neue zentrale Pipeline.
Medaillenstrategie erweitern
Die aktive lokale Implementierung ist DevelopmentRunAwardEvaluator.cs. Sie leitet Bronze/Silber/Gold über DevelopmentRunScoring.AwardFor(score) ab und kennzeichnet das Ergebnis mit RuleSet performance-medals-v2, Version 2. NoAwardEvaluator.cs bleibt als explizite Nullstrategie verfügbar. Im Online-Pfad bleibt die Nakama-Auswertung autoritativ und übernimmt keine Clientmedaille ungeprüft.
Für neue Bronze-/Silber-/Gold-Regeln:
- Eine Implementierung von
IRunAwardEvaluatoruntersrc/HolodeckArena.Game3D/Features/Dungeons/Run/Completionhinzufügen. - Nur
RunAwardContextauswerten; keine Godot-Nodes oder UI-Zustände lesen. - stabile
RuleSetId, erhöhteRuleSetVersionund optionalEvaluatedValueszurückgeben. - die Registrierung in
Core/Bootstrap/BootstrapBase.csauf die neue Strategie umstellen. - dieselbe Regelversion in der serverseitigen Scoring-/Medaillenstrategie implementieren und versionieren; Nakama berechnet das kanonische Ergebnis unabhängig vom Clientwert.
- Tests für alle Grenzwerte und alte Regelversionen ergänzen. Gespeicherte Resultate werden nie nachträglich neu bewertet.
Nakama-Persistenz
| Collection | Owner/Key | Zweck |
|---|---|---|
run_results |
Spieler / ResultId | vollständiges unveränderliches kanonisches Resultat |
run_finalizations |
Spieler / RunId | create-only Zeiger auf genau ein Resultat pro Run |
run_history_index |
Spieler / invertierter Zeitstempel + ResultId | absteigend paginierbarer Summary-Index |
player_run_profile |
Spieler / summary |
kompakte Zähler und letzter Run |
online_run_sessions |
Spieler / RunId | Online-Session und Statusübergänge |
Der Finalisierungszeiger verhindert Duplikate auch bei gleichzeitig gesendeten unterschiedlichen Result-IDs. Resultat, Zeiger, Index und Profil werden gemeinsam geschrieben. Der Profil-Write verwendet die Nakama-Objektversion und wird bei Concurrency-Konflikten höchstens dreimal neu berechnet. Wiederholte Requests liefern das bestehende Resultat und erhöhen keinen Zähler.
Profilregeln: TotalRuns immer +1; je nach Completion CompletedRuns oder AbortedRuns; je nach Award der passende Medaillenzähler; Spielzeit addieren; Zeit und ID des letzten Runs setzen.
RPCs und Pagination
rpc_submit_run: validiert Replay und Client-Claim und persistiert das autoritative Ergebnis idempotent.rpc_get_run_submission: liest den terminalen Submissionstatus für Retry/Recovery.rpc_register_pending_leaderboard: legt vor der längeren Replayvalidierung einen Pending-Eintrag an.rpc_get_run_history: liefert Summaries, Cursor und Filter für Completion, Award und Kategorie; PageSize 1 bis 100.rpc_get_run_result: lädt Details anhand ResultId.rpc_get_player_run_profile: lädt das kompakte Profilaggregat.
Die History ist über run_history_index nach FinishedAtUtc absteigend geordnet. Der Cursor ist opak und wird unverändert an Nakama zurückgegeben. Filter werden beim Scannen angewandt; die Implementierung liest in begrenzten Seiten weiter, bis die angeforderte Ergebniszahl erreicht oder der Cursor erschöpft ist.
Im Client kapselt NakamaRunHistoryService alle RPCs. RunHistoryViewModel lädt Profil plus erste Seite, hängt Folgeseiten an und lädt Details bei Auswahl. Eine reichhaltige Profilscene kann dieses ViewModel verwenden, ohne Nakama direkt aufzurufen.
Retry- und Outbox-Verhalten
Nach der lokalen Finalisierung existieren getrennte Zustände PersistencePending, Persisted und PersistenceFailed. Bei einem Fehler legt PendingRunResultQueue anhand ResultId dedupliziert ab. RetryPendingAsync:
- verwendet dieselbe ResultId,
- hat eine konfigurierbare maximale Versuchszahl,
- wartet mit begrenztem Backoff,
- entfernt erst nach bestätigter Persistenz,
- startet keine unendliche Hintergrundschleife.
Die Queue ist der Single-Writer-Sync-Pfad; Replay-Chunks und Pending-Einträge liegen unter user://replays (LocalReplayStore).
Neue Metrik ergänzen
- Das Feld kompatibel an
RunResultMetrics.csanhängen und bei BedarfSchemaVersionerhöhen. - Eine explizite Erfassungsmethode in
RunMetricsTracker.csergänzen. - Am fachlichen Eventpunkt in
DungeonRun3DController.csaufrufen; nicht beim Result-Screen nachträglich aus Nodes lesen. - Mapping in
runResultModels.ts,runResultMapping.ts,submitOnlineRunRpc.tsundtoRunResultSummaryergänzen, falls die Liste den Wert benötigt. - Migration/default 0 für ältere Payloads beibehalten und Domain-/RPC-Tests ergänzen.
Aktuell gemeldet sind Kills, Raeume, Truhen, Todesfaelle, Shots, Treffer, Damage, Power-ups sowie Objective- und Bossfortschritt. Diese Werte bleiben ReportedMetrics; VerifiedMetrics wird erst durch Game-Server oder Replay-Validierung vertrauenswuerdig.
Datei-Matrix für typische Änderungen
| Änderung | Primäre Dateien |
|---|---|
| Medaillenformel | IRunAwardEvaluator.cs, Evaluator-Klasse, BootstrapBase.cs, serverseitige Scoring-/Medaillenstrategie |
| neue Run-Metrik | RunResultMetrics.cs, RunMetricsTracker.cs, DungeonRun3DController.cs, runResultModels.ts, runResultMapping.ts, submitOnlineRunRpc.ts |
| neuer Abbruchgrund | RunEndReason.cs, auslösendes Gameplay-/Netzwerkereignis, RunResultPresentation.cs |
| History in UI | NakamaRunHistoryService.cs, NakamaRunResultRepository.cs, RunHistoryViewModel.cs, Godot-Control/Scene |
| Profilaggregat erweitern | PlayerRunProfile.cs, runResultModels.ts, projectPlayerRunProfile in runResultStorage.ts |
Tests und Diagnose
RunCompletionSystemTests.cs, RunBackendAndScoringTests.cs, RunLifecycleTests.cs, ReplayValidationPolicyTests.cs und ReplayPipelineRecoveryTests.cs decken Completion/Abort, Medaillen/Score, Zustandsinvarianten, Idempotenz, History, Pending/Retry und Replay-Recovery ab.
Strukturierte Lognamen sind RunFinalizationStarted, RunFinalizationRejected, RunCompleted, RunAborted, RunResultPersistStarted, RunResultPersistSucceeded, RunResultPersistFailed, RunResultRetryScheduled, DuplicateRunResultIgnored und PlayerRunProfileUpdated. Große Metrics- oder Replay-Payloads werden nicht geloggt.