Skip to content

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

  • DungeonRunService erstellt und startet Nakama-Online-Run-Sessions.
  • DungeonObjectiveTracker3D führt Boss- und Gegnerquoten zusammen und liefert einen unveränderlichen RunObjectiveSnapshot.
  • DungeonRun3DController erfasst Raum-, Kill-, Truhen-, Todes-, Shot-, Treffer-, Damage-, Power-up- und Bossereignisse explizit ueber RunMetricsTracker.
  • DungeonExitController3D schaltet nur den Ausgang frei. Die fachliche Abschlussentscheidung trifft ausschließlich IRunCompletionService.
  • Completion schreibt Replay-Chunks lokal (LocalReplayStore) und synchronisiert über PendingRunResultQueue nach Nakama (rpc_submit_run).
  • Nakama persistiert immutable Ergebnisse, History-Index, Finalisierungszeiger und Profilaggregat unter online_run_sessions und 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

  1. DungeonExitController3D wird durch das bestehende Objective-Event freigeschaltet.
  2. Beim Betreten prüft DungeonRun3DController den zentralen Objective-Snapshot.
  3. RunCompletionService.CompleteAsync wechselt atomar nach Finalizing.
  4. Objective- und Metrics-Snapshots werden eingefroren und die Laufzeit gestoppt.
  5. IRunAwardEvaluator bewertet das lokale Ergebnis; BootstrapBase registriert DevelopmentRunAwardEvaluator mit performance-medals-v2.
  6. Das lokale RunCompletedEvent wird veröffentlicht.
  7. PendingRunResultQueue übernimmt als Single Writer Replay-Upload, Pending-Leaderboard-Registrierung und Submission.
  8. NakamaRunSubmitPort ruft rpc_submit_run auf; bei Timeout fragt es rpc_get_run_submission für idempotente Wiederaufnahme ab.
  9. Nakama validiert Authentifizierung, Session/Ticket, Replay-Hash und Timeline und berechnet Score, Medaille und Eligibility serverseitig.
  10. 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 ConfirmationDialog und verwendet anschließend UserCancelled.
  • Das endgültige Player-Todessignal verwendet LivesDepleted ohne Umdeutung als Benutzerabbruch.
  • Technische Gründe Disconnected, ServerShutdown und InvalidRunState sind modelliert und können an denselben AbortAsync-Pfad angeschlossen werden.
  • AbortAsync erzwingt unabhängig vom Evaluator Award.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:

  1. Eine Implementierung von IRunAwardEvaluator unter src/HolodeckArena.Game3D/Features/Dungeons/Run/Completion hinzufügen.
  2. Nur RunAwardContext auswerten; keine Godot-Nodes oder UI-Zustände lesen.
  3. stabile RuleSetId, erhöhte RuleSetVersion und optional EvaluatedValues zurückgeben.
  4. die Registrierung in Core/Bootstrap/BootstrapBase.cs auf die neue Strategie umstellen.
  5. dieselbe Regelversion in der serverseitigen Scoring-/Medaillenstrategie implementieren und versionieren; Nakama berechnet das kanonische Ergebnis unabhängig vom Clientwert.
  6. 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

  1. Das Feld kompatibel an RunResultMetrics.cs anhängen und bei Bedarf SchemaVersion erhöhen.
  2. Eine explizite Erfassungsmethode in RunMetricsTracker.cs ergänzen.
  3. Am fachlichen Eventpunkt in DungeonRun3DController.cs aufrufen; nicht beim Result-Screen nachträglich aus Nodes lesen.
  4. Mapping in runResultModels.ts, runResultMapping.ts, submitOnlineRunRpc.ts und toRunResultSummary ergänzen, falls die Liste den Wert benötigt.
  5. 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.