app.hypergames.hypersdk 1.4.13 → 1.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## [1.4.15](https://github.com/mvmhyper/hyper-sdk/compare/v1.4.14...v1.4.15) (2026-01-06)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * trigger patch release ([f371d9b](https://github.com/mvmhyper/hyper-sdk/commit/f371d9b4ad029d893fb5d85cceb710d2fe831820))
7
+
8
+ ## [1.4.14](https://github.com/mvmhyper/hyper-sdk/compare/v1.4.13...v1.4.14) (2026-01-06)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * migrate final submit to websockets; refresh CSRF on freeze-resume ([8b445e7](https://github.com/mvmhyper/hyper-sdk/commit/8b445e78d677507cc2ef7bce54d815df0ee0f58b))
14
+ * Minor Bug fixes and improvements. ([a5004b5](https://github.com/mvmhyper/hyper-sdk/commit/a5004b5d054be4e5a432f30a6abcfad4287f70b7))
15
+
1
16
  ## [1.4.13](https://github.com/mvmhyper/hyper-sdk/compare/v1.4.12...v1.4.13) (2026-01-01)
2
17
 
3
18
 
@@ -55,35 +55,58 @@ namespace Hyper.Editor
55
55
  return;
56
56
  }
57
57
 
58
+ var currentVersion = packageInfo.version;
58
59
  var previousVersion = EditorPrefs.GetString(InstallerPrefsKey, string.Empty);
59
60
  var sceneExists = File.Exists(HyperLoaderSceneDestination);
60
61
  var templateExists = Directory.Exists(WebGLTemplateDestination);
61
62
  var configExists = File.Exists(HyperGameContentConfigDestination);
62
63
  var settingsExists = File.Exists(HyperSDKSettingsDestination);
63
64
 
64
- // Only run if version changed or files are missing
65
- if (previousVersion == packageInfo.version &&
66
- sceneExists &&
67
- templateExists &&
68
- configExists &&
69
- settingsExists)
70
- {
71
- return;
72
- }
73
-
65
+ // Check what kind of version change we are dealing with
66
+ bool isMajorVersionChange = HasMajorVersionChanged(previousVersion, currentVersion);
67
+ bool isMajorOrMinorVersionChange = HasMajorOrMinorVersionChanged(previousVersion, currentVersion);
68
+
74
69
  var packageRoot = packageInfo.resolvedPath;
75
70
  var hyperLoaderSource = Path.Combine(packageRoot, HyperLoaderSceneRelativePath);
76
71
  var templateSource = Path.Combine(packageRoot, WebGLTemplateRelativePath);
77
72
  var hyperGameContentConfigSource = Path.Combine(packageRoot, HyperGameContentConfigRelativePath);
78
73
  var hyperSDKSettingsSource = Path.Combine(packageRoot, HyperSDKSettingsRelativePath);
79
74
 
80
- CopyFileWithoutMeta(hyperLoaderSource, HyperLoaderSceneDestination);
81
- CopyTemplate(templateSource);
82
- CopyFileWithoutMeta(hyperGameContentConfigSource, HyperGameContentConfigDestination);
83
- CopyFileWithoutMeta(hyperSDKSettingsSource, HyperSDKSettingsDestination);
75
+ bool anyFileCopied = false;
76
+
77
+ if (!sceneExists || isMajorVersionChange)
78
+ {
79
+ CopyFileWithoutMeta(hyperLoaderSource, HyperLoaderSceneDestination);
80
+ anyFileCopied = true;
81
+ }
82
+
83
+ if (!templateExists || isMajorOrMinorVersionChange)
84
+ {
85
+ CopyTemplate(templateSource);
86
+ anyFileCopied = true;
87
+ }
88
+
89
+ if (!configExists || isMajorOrMinorVersionChange)
90
+ {
91
+ CopyFileWithoutMeta(hyperGameContentConfigSource, HyperGameContentConfigDestination);
92
+ anyFileCopied = true;
93
+ }
84
94
 
85
- EditorPrefs.SetString(InstallerPrefsKey, packageInfo.version);
86
- AssetDatabase.Refresh();
95
+ if (!settingsExists || isMajorOrMinorVersionChange)
96
+ {
97
+ CopyFileWithoutMeta(hyperSDKSettingsSource, HyperSDKSettingsDestination);
98
+ anyFileCopied = true;
99
+ }
100
+
101
+ // Always update the stored version, even if no files were copied
102
+ if (previousVersion != currentVersion)
103
+ {
104
+ EditorPrefs.SetString(InstallerPrefsKey, currentVersion);
105
+ if (anyFileCopied)
106
+ {
107
+ AssetDatabase.Refresh();
108
+ }
109
+ }
87
110
  }
88
111
 
89
112
  private static void CopyFileWithoutMeta(string sourcePath, string destinationPath)
@@ -143,6 +166,82 @@ namespace Hyper.Editor
143
166
  FileUtil.DeleteFileOrDirectory(meta);
144
167
  }
145
168
  }
169
+
170
+ /// <summary>
171
+ /// Parses a semantic version string (e.g., "1.4.5") and returns major, minor, patch components.
172
+ /// Returns (0, 0, 0) if version string is invalid or empty.
173
+ /// </summary>
174
+ private static (int major, int minor, int patch) ParseVersion(string version)
175
+ {
176
+ if (string.IsNullOrEmpty(version))
177
+ {
178
+ return (0, 0, 0);
179
+ }
180
+
181
+ // Remove any pre-release or build metadata (e.g., "1.4.5-preview.1" -> "1.4.5")
182
+ var cleanVersion = version.Split('-')[0].Split('+')[0];
183
+
184
+ var parts = cleanVersion.Split('.');
185
+ if (parts.Length < 2)
186
+ {
187
+ return (0, 0, 0);
188
+ }
189
+
190
+ int major = int.TryParse(parts[0], out int m) ? m : 0;
191
+ int minor = int.TryParse(parts[1], out int mi) ? mi : 0;
192
+ int patch = parts.Length > 2 && int.TryParse(parts[2], out int p) ? p : 0;
193
+
194
+ return (major, minor, patch);
195
+ }
196
+
197
+ /// <summary>
198
+ /// Returns true if the version change represents a major or minor version change (not just patch).
199
+ /// Examples:
200
+ /// - 1.4.5 -> 1.4.6: false (patch change)
201
+ /// - 1.4.5 -> 1.5.0: true (minor change)
202
+ /// - 1.4.5 -> 2.0.0: true (major change)
203
+ /// - Empty -> 1.0.0: true (first install)
204
+ /// </summary>
205
+ private static bool HasMajorOrMinorVersionChanged(string previousVersion, string currentVersion)
206
+ {
207
+ // First install or version is empty
208
+ if (string.IsNullOrEmpty(previousVersion))
209
+ {
210
+ return true;
211
+ }
212
+
213
+ var (prevMajor, prevMinor, _) = ParseVersion(previousVersion);
214
+ var (currMajor, currMinor, _) = ParseVersion(currentVersion);
215
+
216
+ // Major version changed
217
+ if (currMajor != prevMajor)
218
+ {
219
+ return true;
220
+ }
221
+
222
+ // Minor version changed
223
+ if (currMinor != prevMinor)
224
+ {
225
+ return true;
226
+ }
227
+
228
+ // Only patch version changed (or no change)
229
+ return false;
230
+ }
231
+
232
+ private static bool HasMajorVersionChanged(string previousVersion, string currentVersion)
233
+ {
234
+ if(string.IsNullOrEmpty(previousVersion))
235
+ {
236
+ return true;
237
+ }
238
+
239
+ var (prevMajor, _, _) = ParseVersion(previousVersion);
240
+ var (currMajor, _, _) = ParseVersion(currentVersion);
241
+
242
+ // Only major version
243
+ return currMajor != prevMajor;
244
+ }
146
245
  }
147
246
  }
148
247
 
@@ -563,7 +563,7 @@ namespace Hyper.Editor
563
563
  isOptimal = status.hyperLoaderAtIndex0,
564
564
  current = $"• Current: {(status.hyperLoaderAtIndex0 ? "HyperLoader at index 0" : "HyperLoader not at index 0")}",
565
565
  recommended = "• Recommended: HyperLoader at build index 0",
566
- description = "HyperLoader scene must be at build index 0 in Build Settings. This ensures proper SDK initialization before your game starts.",
566
+ description = "HyperLoader scene must be at build index 0 in Build Settings (from Assets/HyperSDK/HyperLoader.unity). This ensures proper SDK initialization before your game starts.",
567
567
  priority = SettingPriority.Critical,
568
568
  type = SettingType.HyperLoaderSceneIndex
569
569
  };
@@ -2743,7 +2743,7 @@ namespace Hyper.Editor
2743
2743
  // Property may not exist in this Unity version
2744
2744
  }
2745
2745
 
2746
- // Check if HyperLoader is at build index 0
2746
+ // Check if HyperLoader is at build index 0 (must be from Assets/HyperSDK/, not Packages/)
2747
2747
  bool hyperLoaderAt0 = false;
2748
2748
  try
2749
2749
  {
@@ -2751,8 +2751,8 @@ namespace Hyper.Editor
2751
2751
  if (scenes != null && scenes.Length > 0 && scenes[0].enabled)
2752
2752
  {
2753
2753
  string firstScenePath = scenes[0].path;
2754
- hyperLoaderAt0 = firstScenePath.Contains("HyperLoader") ||
2755
- firstScenePath.EndsWith("HyperLoader.unity", System.StringComparison.OrdinalIgnoreCase);
2754
+ // Only accept the HyperLoader scene from Assets/HyperSDK/, not from Packages/
2755
+ hyperLoaderAt0 = string.Equals(firstScenePath, HyperConstants.HYPER_LOADER_SCENE_PATH, StringComparison.OrdinalIgnoreCase);
2756
2756
  }
2757
2757
  }
2758
2758
  catch (System.Exception)
package/HyperLoader.unity CHANGED
@@ -304,7 +304,7 @@ RectTransform:
304
304
  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
305
305
  m_AnchorMin: {x: 0, y: 1}
306
306
  m_AnchorMax: {x: 0, y: 1}
307
- m_AnchoredPosition: {x: 489.1766, y: -280.1698}
307
+ m_AnchoredPosition: {x: 489.20746, y: -280.1698}
308
308
  m_SizeDelta: {x: 1044.9, y: 239.347}
309
309
  m_Pivot: {x: 0.5, y: 0.5}
310
310
  --- !u!114 &751153944
@@ -840,7 +840,7 @@ GameObject:
840
840
  m_Component:
841
841
  - component: {fileID: 1064166296}
842
842
  m_Layer: 0
843
- m_Name: __HyperSDK_BuildMarker_639020542296075009
843
+ m_Name: __HyperSDK_BuildMarker_639030030877789930
844
844
  m_TagString: Untagged
845
845
  m_Icon: {fileID: 0}
846
846
  m_NavMeshLayer: 0
@@ -1092,7 +1092,7 @@ RectTransform:
1092
1092
  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
1093
1093
  m_AnchorMin: {x: 0, y: 1}
1094
1094
  m_AnchorMax: {x: 0, y: 1}
1095
- m_AnchoredPosition: {x: 489.1766, y: -133.2274}
1095
+ m_AnchoredPosition: {x: 489.20746, y: -133.2274}
1096
1096
  m_SizeDelta: {x: 875, y: 54.5378}
1097
1097
  m_Pivot: {x: 0.5, y: 0.5}
1098
1098
  --- !u!1 &2874543288936963705
@@ -42,7 +42,7 @@ namespace Hyper.Internal.Managers
42
42
 
43
43
  #region Public Properties (Internal API)
44
44
 
45
- public int scoreSubmissionTimeout { get; private set; } = 3;
45
+ public int scoreSubmissionTimeout { get; private set; } = 4;
46
46
  public int CurrentRetryCount => _currentRetryCount;
47
47
  public bool isSocketConnected { get; private set; }
48
48
  public bool tutorialFetchSuccess { get; private set; }
@@ -373,6 +373,7 @@ namespace Hyper.Internal.Managers
373
373
  };
374
374
 
375
375
  string json = JsonConvert.SerializeObject(message);
376
+ HyperDebug.Log($"Sending game resumed message: {json}");
376
377
  SendWebSocketMessage(json);
377
378
  }
378
379
  catch (Exception ex)
@@ -381,7 +382,7 @@ namespace Hyper.Internal.Managers
381
382
  }
382
383
 
383
384
  float elapsed = 0f;
384
- const float timeout = 2f;
385
+ const float timeout = 1f;
385
386
 
386
387
  while (!_gameResumed && elapsed < timeout)
387
388
  {
@@ -390,7 +391,7 @@ namespace Hyper.Internal.Managers
390
391
  }
391
392
  }
392
393
 
393
- yield return new WaitForSecondsRealtime(2f);
394
+ yield return new WaitForSecondsRealtime(1.5f);
394
395
 
395
396
  _sendGameResumedCoroutine = null;
396
397
  }
@@ -529,6 +530,12 @@ namespace Hyper.Internal.Managers
529
530
  _eventManager?.InvokeWebSocketScoreSubmitted(_lastScoreData);
530
531
  }
531
532
 
533
+ if (_isFinalScoreSubmission)
534
+ {
535
+ _finalScoreSubmitted = true;
536
+ _isFinalScoreSubmission = false;
537
+ }
538
+
532
539
  TryUpdateCSRFToken(message);
533
540
  }
534
541
 
@@ -621,22 +628,22 @@ namespace Hyper.Internal.Managers
621
628
 
622
629
  #endregion
623
630
 
624
- #region Score Submission (HTTPS)
631
+ #region Final Score Submission
625
632
 
626
- private Coroutine _endGameCoroutine;
633
+ private Coroutine _finalScoreSubmissionCoroutine;
627
634
 
628
635
  public void SubmitScore()
629
636
  {
630
- if (_endGameCoroutine != null)
637
+ if (_finalScoreSubmissionCoroutine != null)
631
638
  {
632
639
  HyperDebug.LogError("Score submission already in progress");
633
640
  return;
634
641
  }
635
642
 
636
643
  #if UNITY_WEBGL && !UNITY_EDITOR
637
- _endGameCoroutine = StartCoroutine(SubmitScoreCoroutine());
644
+ _finalScoreSubmissionCoroutine = StartCoroutine(FinalScoreSubmissionCoroutine());
638
645
  #else
639
- _endGameCoroutine = StartCoroutine(SubmitScoreCoroutineDemo());
646
+ _finalScoreSubmissionCoroutine = StartCoroutine(FinalScoreSubmissionCoroutineDemo());
640
647
  #endif
641
648
  }
642
649
 
@@ -649,27 +656,34 @@ namespace Hyper.Internal.Managers
649
656
  return;
650
657
  }
651
658
 
652
- if (_endGameCoroutine != null)
659
+ if (_finalScoreSubmissionCoroutine != null)
653
660
  {
654
- StopCoroutine(_endGameCoroutine);
661
+ StopCoroutine(_finalScoreSubmissionCoroutine);
655
662
  }
656
663
 
657
- _endGameCoroutine = null;
664
+ _finalScoreSubmissionCoroutine = null;
658
665
 
659
666
  #if UNITY_WEBGL && !UNITY_EDITOR
660
- _endGameCoroutine = StartCoroutine(SubmitScoreCoroutine());
667
+ _finalScoreSubmissionCoroutine = StartCoroutine(FinalScoreSubmissionCoroutine());
661
668
  #else
662
- _endGameCoroutine = StartCoroutine(SubmitScoreCoroutineDemo());
669
+ _finalScoreSubmissionCoroutine = StartCoroutine(FinalScoreSubmissionCoroutineDemo());
663
670
  #endif
664
671
  }
665
672
 
666
- private IEnumerator SubmitScoreCoroutine()
673
+ private IEnumerator FinalScoreSubmissionCoroutine()
667
674
  {
668
675
  _gameManager?.GameOverModalComponent?.Init();
669
676
  _currentRetryCount = 0;
670
677
 
671
678
  while (_currentRetryCount < MAX_RETRY_COUNT)
672
679
  {
680
+ if (_hasSessionEnded)
681
+ {
682
+ _gameManager?.ShowRetryTimeExpiredModal();
683
+ _finalScoreSubmissionCoroutine = null;
684
+ yield break;
685
+ }
686
+
673
687
  var finalScoreData = _dataManager?.CurrentScoreData;
674
688
  finalScoreData.isFinal = true;
675
689
 
@@ -698,55 +712,100 @@ namespace Hyper.Internal.Managers
698
712
  yield return _sendGameResumedCoroutine;
699
713
  }
700
714
 
701
- object encryptedObject = JsonConvert.DeserializeObject(encryptedScoreData);
702
- var payload = new { encryptedData = encryptedObject };
703
- string jsonData = JsonConvert.SerializeObject(payload);
715
+ if (!isSocketConnected)
716
+ {
717
+ HyperDebug.LogError("WebSocket not connected - cannot submit final score");
718
+ _currentRetryCount++;
719
+ _gameManager?.GameOverModalComponent?.UpdateAttemptCount(_currentRetryCount, MAX_RETRY_COUNT);
704
720
 
705
- string url = GetServerURL($"sessions/{_cachedSessionId}/scores");
721
+ if (_currentRetryCount < MAX_RETRY_COUNT)
722
+ {
723
+ yield return new WaitForSecondsRealtime(1f);
724
+ }
725
+ continue;
726
+ }
706
727
 
707
- using (UnityWebRequest request = new UnityWebRequest(url, "POST"))
728
+ // Sending final score via WebSocket
729
+ _finalScoreSubmitted = false;
730
+ _isFinalScoreSubmission = true;
731
+ _lastSentBatch = new List<ScoreData> { finalScoreData };
732
+
733
+ bool sendFailed = false;
734
+ try
708
735
  {
709
- byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonData);
710
- request.uploadHandler = new UploadHandlerRaw(bodyRaw);
711
- request.downloadHandler = new DownloadHandlerBuffer();
712
- request.SetRequestHeader("Content-Type", "application/json");
713
- request.SetRequestHeader("x-csrf-token", _csrfToken);
714
- request.timeout = scoreSubmissionTimeout;
736
+ var message = new WebSocketScoreMessage(_cachedSessionId, _csrfToken, encryptedScoreData);
737
+ string json = JsonConvert.SerializeObject(message);
738
+ SendWebSocketMessage(json);
739
+ }
740
+ catch (Exception ex)
741
+ {
742
+ HyperDebug.LogError($"Failed to send final score via WebSocket: {ex.Message}");
743
+ _isFinalScoreSubmission = false;
744
+ sendFailed = true;
745
+ }
715
746
 
716
- yield return request.SendWebRequest();
747
+ if (sendFailed)
748
+ {
749
+ _currentRetryCount++;
750
+ _gameManager?.GameOverModalComponent?.UpdateAttemptCount(_currentRetryCount, MAX_RETRY_COUNT);
717
751
 
718
- if (request.result == UnityWebRequest.Result.Success)
752
+ if (_currentRetryCount < MAX_RETRY_COUNT)
719
753
  {
720
- _gameManager?.GameOverModalComponent?.ScoreHasBeenSubmitted();
721
- // TryUpdateCSRFToken(request.downloadHandler.text);
722
- StopWebSocket();
723
- _endGameCoroutine = null;
724
- yield break;
754
+ yield return new WaitForSecondsRealtime(1f);
725
755
  }
726
- else if (_hasSessionEnded)
756
+ continue;
757
+ }
758
+
759
+ // Waiting for WebSocket response
760
+ float elapsed = 0f;
761
+ float timeout = scoreSubmissionTimeout;
762
+
763
+ while (!_finalScoreSubmitted && elapsed < timeout)
764
+ {
765
+ if (_hasSessionEnded)
727
766
  {
728
767
  _gameManager?.ShowRetryTimeExpiredModal();
729
- _endGameCoroutine = null;
768
+ _isFinalScoreSubmission = false;
769
+ _finalScoreSubmissionCoroutine = null;
730
770
  yield break;
731
771
  }
732
- else
733
- {
734
- _currentRetryCount++;
735
- _gameManager?.GameOverModalComponent?.UpdateAttemptCount(_currentRetryCount, MAX_RETRY_COUNT);
772
+ yield return null;
773
+ elapsed += Time.unscaledDeltaTime;
774
+ }
736
775
 
737
- if (_currentRetryCount < MAX_RETRY_COUNT)
738
- {
739
- yield return new WaitForSecondsRealtime(1f);
740
- }
776
+ if (_finalScoreSubmitted)
777
+ {
778
+ _gameManager?.GameOverModalComponent?.ScoreHasBeenSubmitted();
779
+ StopWebSocket();
780
+ _finalScoreSubmissionCoroutine = null;
781
+ yield break;
782
+ }
783
+ else if (_hasSessionEnded)
784
+ {
785
+ _gameManager?.ShowRetryTimeExpiredModal();
786
+ _isFinalScoreSubmission = false;
787
+ _finalScoreSubmissionCoroutine = null;
788
+ yield break;
789
+ }
790
+ else
791
+ {
792
+ _isFinalScoreSubmission = false;
793
+ _currentRetryCount++;
794
+ _gameManager?.GameOverModalComponent?.UpdateAttemptCount(_currentRetryCount, MAX_RETRY_COUNT);
795
+
796
+ if (_currentRetryCount < MAX_RETRY_COUNT)
797
+ {
798
+ yield return new WaitForSecondsRealtime(1f);
741
799
  }
742
800
  }
743
801
  }
744
802
 
803
+ _isFinalScoreSubmission = false;
745
804
  _gameManager?.GameOverModalComponent?.AllAttemptsHaveFailed();
746
- _endGameCoroutine = null;
805
+ _finalScoreSubmissionCoroutine = null;
747
806
  }
748
807
 
749
- private IEnumerator SubmitScoreCoroutineDemo()
808
+ private IEnumerator FinalScoreSubmissionCoroutineDemo()
750
809
  {
751
810
  if (_dataManager == null) _dataManager = HyperRuntime.DataManager;
752
811
  if (_eventManager == null) _eventManager = HyperRuntime.EventManager;
@@ -761,7 +820,7 @@ namespace Hyper.Internal.Managers
761
820
 
762
821
  _gameManager.GameOverModalComponent.ScoreHasBeenSubmitted();
763
822
  StopWebSocket();
764
- _endGameCoroutine = null;
823
+ _finalScoreSubmissionCoroutine = null;
765
824
  }
766
825
 
767
826
  #endregion
@@ -772,6 +831,8 @@ namespace Hyper.Internal.Managers
772
831
  private List<ScoreData> _lastSentBatch = new List<ScoreData>();
773
832
  private ScoreData _lastScoreData;
774
833
  private readonly WaitForSeconds _scoreSendInterval = new WaitForSeconds(1f);
834
+ private bool _isFinalScoreSubmission;
835
+ private bool _finalScoreSubmitted;
775
836
 
776
837
  public void StartSendingScores()
777
838
  {
@@ -45,6 +45,8 @@ namespace Hyper.Internal.Managers
45
45
  internal event Action OnTutorialFetchFailed;
46
46
  internal event Action OnTutorialDoesNotExist;
47
47
  internal event Action<ScoreData> OnLastSubmittedScoreReceived;
48
+ internal event Action OnTimeScaleFrozen; // Fired when Time.timeScale becomes 0
49
+ internal event Action OnTimeScaleResumed; // Fired when Time.timeScale becomes 1 (from any value)
48
50
 
49
51
  #endregion
50
52
 
@@ -68,6 +70,8 @@ namespace Hyper.Internal.Managers
68
70
  internal void InvokeTutorialFetchFailedEvent() => OnTutorialFetchFailed?.Invoke();
69
71
  internal void InvokeTutorialNotExistsEvent() => OnTutorialDoesNotExist?.Invoke();
70
72
  internal void InvokeWebSocketScoreSubmitted(ScoreData scoreData) => OnLastSubmittedScoreReceived?.Invoke(scoreData);
73
+ internal void InvokeTimeScaleFrozenEvent() => OnTimeScaleFrozen?.Invoke();
74
+ internal void InvokeTimeScaleResumedEvent() => OnTimeScaleResumed?.Invoke();
71
75
 
72
76
  #endregion
73
77
  }
@@ -158,12 +158,6 @@ namespace Hyper.Internal.Managers
158
158
  {
159
159
  if (!_hasGameStarted || _isPaused || _hasGameEnded) return;
160
160
 
161
- if (_backendManager.isSocketConnected)
162
- {
163
- _dataManager.StopCapturingScores();
164
- _backendManager.StopSendingScores();
165
- }
166
-
167
161
  _isPaused = true;
168
162
  _isFrozenBeforePause = Time.timeScale == 0;
169
163
  Time.timeScale = 0;
@@ -177,11 +171,6 @@ namespace Hyper.Internal.Managers
177
171
 
178
172
  internal void ResumeGame()
179
173
  {
180
- if (_backendManager.isSocketConnected)
181
- {
182
- _backendManager.SendGameResumed();
183
- }
184
-
185
174
  _isPaused = false;
186
175
 
187
176
  if (!_isFrozenBeforePause)
@@ -212,9 +201,37 @@ namespace Hyper.Internal.Managers
212
201
  {
213
202
  PauseGame();
214
203
  }
215
- else if (!shouldPause && _isPaused)
204
+ }
205
+
206
+ void Update()
207
+ {
208
+ float currentTimeScale = Time.timeScale;
209
+ if (currentTimeScale != _previousTimeScale)
216
210
  {
217
- // ResumeGame();
211
+ // detect intentional game-freeze states
212
+ if (currentTimeScale == 0f && _previousTimeScale != 0f)
213
+ {
214
+ _eventManager.InvokeTimeScaleFrozenEvent();
215
+ HyperDebug.Log("Time scale frozen event invoked");
216
+
217
+ if (_backendManager.isSocketConnected)
218
+ {
219
+ _dataManager.StopCapturingScores();
220
+ _backendManager.StopSendingScores();
221
+ }
222
+ }
223
+ else if (currentTimeScale == 1f && _previousTimeScale != 1f)
224
+ {
225
+ _eventManager.InvokeTimeScaleResumedEvent();
226
+ HyperDebug.Log("Time scale resumed event invoked");
227
+
228
+ if (_backendManager.isSocketConnected && !_hasGameEnded && _hasGameStarted)
229
+ {
230
+ _backendManager.SendGameResumed();
231
+ }
232
+ }
233
+
234
+ _previousTimeScale = currentTimeScale;
218
235
  }
219
236
  }
220
237
 
@@ -459,6 +476,8 @@ namespace Hyper.Internal.Managers
459
476
 
460
477
  internal void ShowGameOverModal()
461
478
  {
479
+ HidePauseModals();
480
+
462
481
  if (IsBattleMode)
463
482
  {
464
483
  EnsureBattleGameOverModal();
@@ -469,7 +488,7 @@ namespace Hyper.Internal.Managers
469
488
  EnsurePracticeGameOverModal();
470
489
  _practiceGameOverModal.SetActive(true);
471
490
  }
472
-
491
+
473
492
  SetBlockerActive(true);
474
493
  }
475
494
 
@@ -558,6 +577,7 @@ namespace Hyper.Internal.Managers
558
577
  private bool _hasGameEnded;
559
578
  private bool _isFrozenBeforePause;
560
579
  private bool _isPortrait;
580
+ private float _previousTimeScale = 1f;
561
581
 
562
582
  #endregion
563
583
 
@@ -769,6 +789,7 @@ namespace Hyper.Internal.Managers
769
789
  private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
770
790
  {
771
791
  Time.timeScale = 1;
792
+ _previousTimeScale = 1f; // Sync tracking on scene load
772
793
  _dataManager.ConfigurePlayerSoundSetting();
773
794
 
774
795
  string menuSceneName = HyperRuntime.GameContent.IsPracticeMenu ?
@@ -808,6 +829,7 @@ namespace Hyper.Internal.Managers
808
829
  _hasGameEnded = false;
809
830
  _isPaused = false;
810
831
  _isFrozenBeforePause = false;
832
+ _previousTimeScale = Time.timeScale; // Sync with actual time scale on scene load
811
833
  }
812
834
 
813
835
  private void InitializeSceneUI()
@@ -83,7 +83,7 @@ namespace Hyper.Internal
83
83
 
84
84
  // Animate to original position
85
85
  rectTransform.DOAnchorPosY(originalAnchoredPosition.y, 0.5f)
86
- .SetEase(Animation.HyperTween.Ease.OutQuad)
86
+ .SetEase(HyperTween.Ease.OutQuad)
87
87
  .SetUpdate(true);
88
88
  }
89
89
 
@@ -103,7 +103,7 @@ namespace Hyper.Internal
103
103
 
104
104
  // Animate to off-screen position
105
105
  rectTransform.DOAnchorPosY(offScreenY, 0.5f)
106
- .SetEase(Animation.HyperTween.Ease.InQuad)
106
+ .SetEase(HyperTween.Ease.InQuad)
107
107
  .SetUpdate(true)
108
108
  .OnComplete(() =>
109
109
  {