From 8c673dbb99b2c15b2630531d5659699ea32cac4f Mon Sep 17 00:00:00 2001 From: Kenneth Enow Date: Sun, 2 Aug 2026 23:49:57 -0400 Subject: [PATCH] Add grade and score to the results QR link The results web page needs the letter grade and numeric score from the shared link, not only the BST start and placed sequences. TreeShareConfig: adds editable query keys for grade and score, and moves the default viewer path from /view to /results. Both accessors fall back to the canonical key name when the asset field is left blank, matching how start and placed already behave. TreeRunTracker.BuildViewerUrl: now takes the final Grade and score and appends grade and score params. The score is formatted with invariant culture so a machine with a comma decimal separator cannot corrupt the link. TreeResultQrDisplay.PrepareResultQrCode: now takes the run's RhythmScoreTracker, which is the same instance the result screen shows, so the QR and the on-screen stats can never disagree. A null tracker logs an error, finishes level tracking, and shows the QR fallback rather than encoding an incomplete link. ResultScreenController: passes the score tracker it already has to the QR prepare call. TreeShareConfig.asset: sets the new grade and score keys and the /results base URL for this project. Co-authored-by: Cursor --- AI/CHANGELOG.md | 7 ++++++ Assets/Integration/TreeResultQrDisplay.cs | 17 +++++++++++-- Assets/Integration/TreeRunTracker.cs | 17 ++++++++++--- Assets/Integration/TreeShareConfig.cs | 25 ++++++++++++++++--- .../Configs/TreeShareConfig.asset | 4 ++- Assets/UI/Style1/ResultScreenController.cs | 2 +- 6 files changed, 60 insertions(+), 12 deletions(-) diff --git a/AI/CHANGELOG.md b/AI/CHANGELOG.md index 046661e..ae545fb 100644 --- a/AI/CHANGELOG.md +++ b/AI/CHANGELOG.md @@ -43,6 +43,13 @@ - **Impact:** Lesson copy stays on the side panels; timing feedback looks like the level. - **Refs:** `TutorialController.cs`, `TreeRhythmController.cs`, `Assets/Scenes/Tutorial.unity` +### Changed — Results QR link carries grade and score +- **Scope:** component:ResultScreen · integration +- **What:** The share link now encodes four params (`?start=…&placed=…&grade=…&score=…`). `TreeRunTracker.BuildViewerUrl` takes the final `Grade` and score, `TreeResultQrDisplay.PrepareResultQrCode` takes the run's `RhythmScoreTracker`, and both new query keys are editable on `TreeShareConfig`. Default viewer path moves from `/view` to `/results`. +- **Why:** The results web page needs the letter grade and numeric score from the link itself, not only the BST sequences. +- **Impact:** `PrepareResultQrCode` and `BuildViewerUrl` gained parameters; the result screen passes the score tracker it already displays. A missing tracker logs an error and shows the QR fallback instead of an incomplete link. Set `_viewerBaseUrl` per environment on the config asset. +- **Refs:** `TreeShareConfig.cs`, `TreeRunTracker.cs`, `TreeResultQrDisplay.cs`, `ResultScreenController.cs`, `TreeShareConfig.asset` + ### Fixed — Player-side synthwave reacts to the song again in VS Bot / 2P - **Scope:** component:PlayerVsBot - **What:** `TwoPlayerGameCoordinator` now pulses P1's synthwave floor from P1 `OnBeat` (P2 already had this) and retargets the shared `AudioFFTAnalyzer` to P1's playing `AudioSource`. P2 `PlaySync` never starts a clip, so the analyzer had been listening to a silent source — hills stayed flat on the human side while the bot side still kicked from its beat wire. diff --git a/Assets/Integration/TreeResultQrDisplay.cs b/Assets/Integration/TreeResultQrDisplay.cs index 8902ee6..5218301 100644 --- a/Assets/Integration/TreeResultQrDisplay.cs +++ b/Assets/Integration/TreeResultQrDisplay.cs @@ -59,8 +59,12 @@ public void SetTracker(TreeRunTracker tracker) * assigns it to the results-screen RawImage. Safe to call once per run; * subsequent calls for the same run are no-ops until . * + * + * Same tracker the result screen displays; supplies the grade and total + * score encoded in the link. + * */ - public void PrepareResultQrCode() + public void PrepareResultQrCode(RhythmScoreTracker score) { if (_preparedForCurrentRun) return; @@ -72,6 +76,15 @@ public void PrepareResultQrCode() return; } + if (score == null) + { + Debug.LogError("[TreeResultQrDisplay] Missing RhythmScoreTracker — cannot encode grade and score."); + _tracker.FinishLevel(); + ShowFallback(); + _preparedForCurrentRun = true; + return; + } + if (_qrRawImage == null) { Debug.LogError("[TreeResultQrDisplay] Missing QR RawImage reference."); @@ -81,7 +94,7 @@ public void PrepareResultQrCode() } _tracker.FinishLevel(); - _lastUrl = _tracker.BuildViewerUrl(); + _lastUrl = _tracker.BuildViewerUrl(score.CurrentGrade, score.TotalScore); if (string.IsNullOrEmpty(_lastUrl)) { Debug.LogError("[TreeResultQrDisplay] Viewer URL was empty (check TreeShareConfig)."); diff --git a/Assets/Integration/TreeRunTracker.cs b/Assets/Integration/TreeRunTracker.cs index 49e04f5..c0f73ac 100644 --- a/Assets/Integration/TreeRunTracker.cs +++ b/Assets/Integration/TreeRunTracker.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using UnityEngine; /** @@ -7,7 +8,8 @@ * * Holds the starting tree's original insertion order plus every value the * player successfully places, in placement order. Used by - * to build a seemytree viewer URL. + * to build a results viewer URL of the form + * ?start=…&placed=…&grade=…&score=…. * * Attach beside on each GameController. * Does not use DontDestroyOnLoad — lives for the GameScene level only. @@ -123,12 +125,14 @@ public void ResetTracking() } /** - * Builds the seemytree viewer URL for the current run. + * Builds the results viewer URL for the current run. * Empty placement lists are valid and encode as placed=. * Base URL and query names come from . * + * Final letter grade shown on the result screen. + * Final score from . */ - public string BuildViewerUrl() + public string BuildViewerUrl(Grade grade, long score) { if (_shareConfig == null) { @@ -141,12 +145,17 @@ public string BuildViewerUrl() string startEncoded = Uri.EscapeDataString(startJoined); string placedEncoded = Uri.EscapeDataString(placedJoined); + string gradeEncoded = Uri.EscapeDataString(grade.ToString()); + string scoreEncoded = Uri.EscapeDataString(score.ToString(CultureInfo.InvariantCulture)); string baseUrl = _shareConfig.ViewerBaseUrl; string startKey = _shareConfig.StartParamName; string placedKey = _shareConfig.PlacedParamName; + string gradeKey = _shareConfig.GradeParamName; + string scoreKey = _shareConfig.ScoreParamName; - return $"{baseUrl}?{startKey}={startEncoded}&{placedKey}={placedEncoded}"; + return $"{baseUrl}?{startKey}={startEncoded}&{placedKey}={placedEncoded}" + + $"&{gradeKey}={gradeEncoded}&{scoreKey}={scoreEncoded}"; } #endregion diff --git a/Assets/Integration/TreeShareConfig.cs b/Assets/Integration/TreeShareConfig.cs index 3c31a9b..f00e03e 100644 --- a/Assets/Integration/TreeShareConfig.cs +++ b/Assets/Integration/TreeShareConfig.cs @@ -8,9 +8,12 @@ [CreateAssetMenu(menuName = "Treeformance/TreeShareConfig", fileName = "TreeShareConfig")] public class TreeShareConfig : ScriptableObject { + /** Used when the asset leaves _viewerBaseUrl blank. */ + private const string DefaultViewerBaseUrl = "https://seemytree.netlify.app/results"; + [Header("Viewer")] - [Tooltip("Base URL for the tree viewer (no query string). Example: https://seemytree.netlify.app/view")] - [SerializeField] private string _viewerBaseUrl = "https://seemytree.netlify.app/view"; + [Tooltip("Base URL for the results viewer (no query string). Example: https://seemytree.netlify.app/results")] + [SerializeField] private string _viewerBaseUrl = DefaultViewerBaseUrl; [Header("Query Parameter Names")] [Tooltip("Query key for the starting BST insertion order.")] @@ -19,10 +22,16 @@ public class TreeShareConfig : ScriptableObject [Tooltip("Query key for successful player placements in order.")] [SerializeField] private string _placedParamName = "placed"; - /** Host + path used before ?start=…&placed=…. */ + [Tooltip("Query key for the final letter grade (SS, S, A, B, C, D, F).")] + [SerializeField] private string _gradeParamName = "grade"; + + [Tooltip("Query key for the final numeric score.")] + [SerializeField] private string _scoreParamName = "score"; + + /** Host + path used before ?start=…&placed=…&grade=…&score=…. */ public string ViewerBaseUrl => string.IsNullOrWhiteSpace(_viewerBaseUrl) - ? "https://seemytree.netlify.app/view" + ? DefaultViewerBaseUrl : _viewerBaseUrl.TrimEnd('/'); /** Query parameter name for the starting insertion sequence. */ @@ -32,4 +41,12 @@ public class TreeShareConfig : ScriptableObject /** Query parameter name for successful placements. */ public string PlacedParamName => string.IsNullOrWhiteSpace(_placedParamName) ? "placed" : _placedParamName; + + /** Query parameter name for the final letter grade. */ + public string GradeParamName => + string.IsNullOrWhiteSpace(_gradeParamName) ? "grade" : _gradeParamName; + + /** Query parameter name for the final numeric score. */ + public string ScoreParamName => + string.IsNullOrWhiteSpace(_scoreParamName) ? "score" : _scoreParamName; } diff --git a/Assets/ScriptableObjects/Configs/TreeShareConfig.asset b/Assets/ScriptableObjects/Configs/TreeShareConfig.asset index 968359e..2bcd262 100644 --- a/Assets/ScriptableObjects/Configs/TreeShareConfig.asset +++ b/Assets/ScriptableObjects/Configs/TreeShareConfig.asset @@ -12,6 +12,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 12b30bbd212e049eb9d2fa93b78b1a36, type: 3} m_Name: TreeShareConfig m_EditorClassIdentifier: - _viewerBaseUrl: https://seemytree.netlify.app/view + _viewerBaseUrl: https://seemytree.netlify.app/results _startParamName: start _placedParamName: placed + _gradeParamName: grade + _scoreParamName: score diff --git a/Assets/UI/Style1/ResultScreenController.cs b/Assets/UI/Style1/ResultScreenController.cs index a6e1cae..9928b0a 100644 --- a/Assets/UI/Style1/ResultScreenController.cs +++ b/Assets/UI/Style1/ResultScreenController.cs @@ -249,7 +249,7 @@ public async UniTask ShowAsync(RhythmScoreTracker score, IMenuInputProvider inpu _treeQrDisplay = GetComponent(); try { - _treeQrDisplay?.PrepareResultQrCode(); + _treeQrDisplay?.PrepareResultQrCode(score); } catch (Exception ex) {