app.hypergames.hypersdk 1.11.0-beta.2 → 1.11.0-beta.20

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/Editor/Scripts/Inspectors/HyperSDKSettingsEditor.cs +6 -0
  3. package/HyperLoader.unity +2 -2
  4. package/Runtime/Internal/Core/HyperRuntime.cs +104 -9
  5. package/Runtime/Internal/Managers/DataManager.cs +2 -1
  6. package/Runtime/Internal/Managers/EventManager.cs +7 -1
  7. package/Runtime/Internal/Managers/GameManager.cs +88 -51
  8. package/Runtime/Internal/Managers/ReplayManager.cs +5 -1
  9. package/Runtime/Internal/Managers/TimeManager.cs +25 -8
  10. package/Runtime/Internal/Replay/ReplaySessionState.cs +66 -6
  11. package/Runtime/Internal/ScriptableObjects/HyperSDKSettings.cs +4 -0
  12. package/Runtime/Internal/UI/Components/ReplayController.cs +134 -11
  13. package/Runtime/Internal/UI/Components/ReplaySeekOverlay.cs +18 -0
  14. package/Runtime/Internal/UI/Components/ReplaySeekOverlay.cs.meta +2 -0
  15. package/Runtime/Internal/UI/Modals/ReplayPausedModal.cs +145 -0
  16. package/Runtime/Internal/UI/Modals/ReplayPausedModal.cs.meta +2 -0
  17. package/Runtime/Internal/UI/Modals/StartingInstructionModal.cs +1 -0
  18. package/Runtime/Public/Core/HyperSDK.cs +16 -0
  19. package/Runtime/Public/Replay/ReplayPlaybackEngine.cs +56 -1
  20. package/Runtime/Resources/Fonts/Nunito/Nunito-Bold SDF.asset +500 -457
  21. package/Runtime/Resources/Fonts/Wonder Boys/Wonder Boys.asset +394 -379
  22. package/Runtime/Resources/HyperSDKSettings.asset +1 -0
  23. package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Normal.png +0 -0
  24. package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Normal.png.meta +156 -0
  25. package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Pressed.png +0 -0
  26. package/Runtime/Resources/Sprites/Buttons/Hyper Long White Button_Pressed.png.meta +156 -0
  27. package/Runtime/Resources/Sprites/Circle Loader/Property 1=1.png.meta +7 -7
  28. package/Runtime/Resources/Sprites/Circle Loader/Property 1=2.png.meta +7 -7
  29. package/Runtime/Resources/Sprites/Circle Loader/Property 1=3.png.meta +7 -7
  30. package/Runtime/Resources/Sprites/Circle Loader/Property 1=4.png.meta +7 -7
  31. package/Runtime/Resources/Sprites/Circle Loader/Property 1=5.png.meta +7 -7
  32. package/Runtime/Resources/Sprites/Replays/Replay.png +0 -0
  33. package/Runtime/Resources/Sprites/Replays/{Recording.png.meta → Replay.png.meta} +9 -9
  34. package/Runtime/Resources/UIPrefabs/ControlBar.prefab +5 -5
  35. package/Runtime/Resources/UIPrefabs/ReplayController.prefab +305 -183
  36. package/Runtime/Resources/UIPrefabs/ReplayPausedModal.prefab +2194 -0
  37. package/Runtime/Resources/UIPrefabs/ReplayPausedModal.prefab.meta +7 -0
  38. package/Runtime/Resources/UIPrefabs/ReplaySeekOverlay.prefab +291 -6
  39. package/Runtime/Shared/Replay/IReplayPlaybackEngine.cs +3 -0
  40. package/Samples~/LaneRunner/LaneRunnerSample.unity +4 -3
  41. package/Samples~/LaneRunner/Prefabs/DoubleScorePickup.prefab +1 -1
  42. package/Samples~/LaneRunner/Prefabs/Obstacle.prefab +1 -1
  43. package/Samples~/LaneRunner/README.md +8 -1
  44. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerPlayer.cs +18 -4
  45. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerSpawner.cs +3 -1
  46. package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerTournamentAdapter.cs +8 -0
  47. package/package.json +1 -1
  48. package/Runtime/Resources/Sprites/Replays/Recording.png +0 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,142 @@
1
+ # [1.11.0-beta.20](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.19...v1.11.0-beta.20) (2026-09-02)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **replay:** separate replay restart configuration ([de01d37](https://github.com/mvmhyper/hyper-sdk/commit/de01d37027fdde5d4f8803fa10e060d48a080316))
7
+
8
+ # [1.11.0-beta.19](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.18...v1.11.0-beta.19) (2026-09-01)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **replay:** stop seeking at requested progress ([54bf74a](https://github.com/mvmhyper/hyper-sdk/commit/54bf74a4b66b7c19e1398acd611a7d9557ccbcf3))
14
+
15
+ # [1.11.0-beta.18](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.17...v1.11.0-beta.18) (2026-09-01)
16
+
17
+
18
+ ### Features
19
+
20
+ * **replay:** support custom replay restarts ([bf722ab](https://github.com/mvmhyper/hyper-sdk/commit/bf722abe48add18c356922c60231cb2017e28443))
21
+
22
+ # [1.11.0-beta.17](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.16...v1.11.0-beta.17) (2026-09-01)
23
+
24
+
25
+ ### Features
26
+
27
+ * **replay:** refine seek loading progress ([3d01f94](https://github.com/mvmhyper/hyper-sdk/commit/3d01f94d7e6ce360a61dd3d257149cf7f3237bcb))
28
+
29
+ # [1.11.0-beta.16](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.15...v1.11.0-beta.16) (2026-08-31)
30
+
31
+
32
+ ### Bug Fixes
33
+
34
+ * **replay:** show seek preparation progress ([60213f7](https://github.com/mvmhyper/hyper-sdk/commit/60213f784c5bff39da4634590545fe4d0e4c0911))
35
+
36
+ # [1.11.0-beta.15](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.14...v1.11.0-beta.15) (2026-08-31)
37
+
38
+
39
+ ### Features
40
+
41
+ * **replay:** show seek completion progress ([4e7c1b8](https://github.com/mvmhyper/hyper-sdk/commit/4e7c1b807bd9f34b97477ef39aa9ca3c06316ef3))
42
+
43
+ # [1.11.0-beta.14](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.13...v1.11.0-beta.14) (2026-08-31)
44
+
45
+
46
+ ### Bug Fixes
47
+
48
+ * **replay:** keep seek loader animating ([7e13938](https://github.com/mvmhyper/hyper-sdk/commit/7e13938ce718a238506c08c30744ff24ed7fa710))
49
+
50
+ # [1.11.0-beta.13](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.12...v1.11.0-beta.13) (2026-08-31)
51
+
52
+
53
+ ### Bug Fixes
54
+
55
+ * **replay:** reduce default seek speed to 10x ([ccaf5b9](https://github.com/mvmhyper/hyper-sdk/commit/ccaf5b9807fd9439627072518317ad7f079e544c))
56
+
57
+ # [1.11.0-beta.12](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.11...v1.11.0-beta.12) (2026-08-31)
58
+
59
+
60
+ ### Bug Fixes
61
+
62
+ * **replay:** reduce default seek speed ([d757710](https://github.com/mvmhyper/hyper-sdk/commit/d757710d107a0a496c9d7daa1434004a682bd355))
63
+
64
+ # [1.11.0-beta.11](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.10...v1.11.0-beta.11) (2026-08-30)
65
+
66
+
67
+ ### Bug Fixes
68
+
69
+ * **replay:** apply 30x seek speed on beta ([564769e](https://github.com/mvmhyper/hyper-sdk/commit/564769e4764f27fd301f0e1dc52a4434635f93b7))
70
+ * **replay:** reduce default seek speed ([2cd0a34](https://github.com/mvmhyper/hyper-sdk/commit/2cd0a34c6390725d1c57fc3fa5f691f618c45083))
71
+
72
+
73
+ ### Features
74
+
75
+ * **replay:** increase default seek speed ([d1a5a46](https://github.com/mvmhyper/hyper-sdk/commit/d1a5a464c1e3e149255bf0d2d183439e9c29cfb3))
76
+
77
+ # [1.11.0-beta.10](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.9...v1.11.0-beta.10) (2026-08-30)
78
+
79
+
80
+ ### Bug Fixes
81
+
82
+ * **replay:** reduce default seek speed ([f70551a](https://github.com/mvmhyper/hyper-sdk/commit/f70551aea942274ace1cb952ddae4e9edb0c1c12))
83
+
84
+ # [1.11.0-beta.9](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.8...v1.11.0-beta.9) (2026-08-30)
85
+
86
+
87
+ ### Bug Fixes
88
+
89
+ * **replay:** show loader while exiting paused replay ([4a2e7a6](https://github.com/mvmhyper/hyper-sdk/commit/4a2e7a6045017ea8ed3ae3525271440dbf013e6c))
90
+
91
+ # [1.11.0-beta.8](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.7...v1.11.0-beta.8) (2026-08-30)
92
+
93
+
94
+ ### Bug Fixes
95
+
96
+ * **replay:** mute audio while seeking ([5ab8cd1](https://github.com/mvmhyper/hyper-sdk/commit/5ab8cd1411493cb5add07b51dc190c85251f99d2))
97
+
98
+ # [1.11.0-beta.7](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.6...v1.11.0-beta.7) (2026-08-30)
99
+
100
+
101
+ ### Bug Fixes
102
+
103
+ * **replay:** stabilize scene reload seeking ([dd9ac63](https://github.com/mvmhyper/hyper-sdk/commit/dd9ac632d08349d9578f64ae0b2dc2ab905a26af))
104
+
105
+ # [1.11.0-beta.6](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.5...v1.11.0-beta.6) (2026-08-29)
106
+
107
+
108
+ ### Bug Fixes
109
+
110
+ * **timer:** restore gameplay timers after pause ([ca82d57](https://github.com/mvmhyper/hyper-sdk/commit/ca82d57df87618807cbae611c0a9d6acc14f8470))
111
+
112
+ # [1.11.0-beta.5](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.4...v1.11.0-beta.5) (2026-08-28)
113
+
114
+
115
+ ### Bug Fixes
116
+
117
+ * **replay:** wait for custom restart readiness ([43f1aeb](https://github.com/mvmhyper/hyper-sdk/commit/43f1aeb6a127c8c12dbb8379e7230d64ef5e0763))
118
+
119
+
120
+ ### Features
121
+
122
+ * **replay:** add replay pause controls ([b9fb470](https://github.com/mvmhyper/hyper-sdk/commit/b9fb470e65c69e66a8a9611b0caa7e5417b1e4bf))
123
+ * **replay:** update replay control bar assets ([79ce8c0](https://github.com/mvmhyper/hyper-sdk/commit/79ce8c0f1b68129ae5b8bcf18a302117cf11c885))
124
+
125
+ # [1.11.0-beta.4](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.3...v1.11.0-beta.4) (2026-08-28)
126
+
127
+
128
+ ### Bug Fixes
129
+
130
+ * **replay:** align recording with fixed simulation ([ae2b322](https://github.com/mvmhyper/hyper-sdk/commit/ae2b32216a0a9ea125572d788eee3bfd846414b0))
131
+
132
+ # [1.11.0-beta.3](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.2...v1.11.0-beta.3) (2026-08-28)
133
+
134
+
135
+ ### Bug Fixes
136
+
137
+ * **replay:** reset random state before playback restart ([526a4d9](https://github.com/mvmhyper/hyper-sdk/commit/526a4d98c9b22241e3c52bb6837145bad999fb2b))
138
+ * **sample:** support lane runner custom restart ([49da385](https://github.com/mvmhyper/hyper-sdk/commit/49da385aaa917de438672a8630d4fe7927741b2a))
139
+
1
140
  # [1.11.0-beta.2](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.1...v1.11.0-beta.2) (2026-08-27)
2
141
 
3
142
 
@@ -170,6 +170,12 @@ namespace Hyper.Editor
170
170
  settings.useCustomRestartFlow = EditorGUILayout.Toggle(
171
171
  new GUIContent("Use Custom Restart Flow"),
172
172
  settings.useCustomRestartFlow);
173
+ settings.useCustomReplayRestartFlow = EditorGUILayout.Toggle(
174
+ new GUIContent(
175
+ "Use Custom Replay Restart Flow",
176
+ "Use game-owned restart logic for replay Watch Again and seeking. " +
177
+ "Disabled uses HyperLoader to rebuild deterministic game state."),
178
+ settings.useCustomReplayRestartFlow);
173
179
 
174
180
  EditorGUILayout.Space(12);
175
181
  // Draw Editor Testing
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.112, 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
@@ -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.112, 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
@@ -15,11 +15,16 @@ namespace Hyper.Internal
15
15
  /// </summary>
16
16
  internal static class HyperRuntime
17
17
  {
18
+ private const float ReplayCustomRestartTimeoutSeconds = 10f;
19
+
18
20
  private static HyperRuntimeHost _host;
19
21
  private static GameObject _singletonObject;
20
22
 
21
23
  private static bool _isInitialized;
22
24
  private static bool _isSoftRestarting;
25
+ private static bool _replayRandomResetPending;
26
+ private static bool _replayCustomRestartPending;
27
+ private static int _replayCustomRestartVersion;
23
28
  private static bool _customLoadingComplete;
24
29
  private static bool _customLoadingActive;
25
30
  private static float _customLoadingProgress;
@@ -32,6 +37,7 @@ namespace Hyper.Internal
32
37
  private static EventManager _eventManager;
33
38
  private static TimerManager _timerManager;
34
39
  private static ReplayManager _replayManager;
40
+ private static double _fixedSimulationTimeSeconds;
35
41
 
36
42
  internal static bool IsInitialized => _isInitialized;
37
43
  internal static bool IsSoftRestarting
@@ -39,6 +45,7 @@ namespace Hyper.Internal
39
45
  get => _isSoftRestarting;
40
46
  set => _isSoftRestarting = value;
41
47
  }
48
+ internal static bool IsReplayCustomRestartPending => _replayCustomRestartPending;
42
49
  internal static bool CustomLoadingComplete
43
50
  {
44
51
  get => _customLoadingComplete;
@@ -58,6 +65,7 @@ namespace Hyper.Internal
58
65
  internal static EventManager EventManager => _eventManager;
59
66
  internal static TimerManager Timer => _timerManager;
60
67
  internal static ReplayManager ReplayManager => _replayManager;
68
+ internal static double FixedSimulationTimeSeconds => _fixedSimulationTimeSeconds;
61
69
 
62
70
  internal static event Action CustomLoadingStarted
63
71
  {
@@ -118,8 +126,8 @@ namespace Hyper.Internal
118
126
  }
119
127
 
120
128
  /// <summary>
121
- /// Restarts replay playback while preserving its downloaded data and original seed.
122
- /// Uses the configured custom restart flow when available, otherwise reloads HyperLoader.
129
+ /// Restarts replay playback while preserving replay session data. Uses the game-owned
130
+ /// replay restart flow only when explicitly enabled and otherwise reloads HyperLoader.
123
131
  /// </summary>
124
132
  internal static void RestartReplaySession()
125
133
  {
@@ -141,17 +149,66 @@ namespace Hyper.Internal
141
149
  _isSoftRestarting = true;
142
150
  Time.timeScale = 1f;
143
151
 
144
- if (_settings != null && _settings.useCustomRestartFlow &&
145
- _gameManager != null && _eventManager != null)
152
+ _replayCustomRestartPending = false;
153
+ _replayRandomResetPending = false;
154
+ int restartVersion = ++_replayCustomRestartVersion;
155
+ _replayManager?.PrepareForRestart();
156
+
157
+ if (_settings != null && _settings.useCustomReplayRestartFlow &&
158
+ _host != null && _gameManager != null && _eventManager != null)
146
159
  {
147
- _replayManager?.PrepareForRestart();
148
- ResetReplayRandomState();
160
+ _replayCustomRestartPending = true;
161
+ _replayRandomResetPending = true;
149
162
  _gameManager.ResetUIStateForCustomRestart();
150
- _eventManager.InvokeCustomRestartEvent();
151
- _isSoftRestarting = false;
152
- return;
163
+
164
+ try
165
+ {
166
+ if (!_eventManager.InvokeCustomRestartEvent())
167
+ {
168
+ HyperDebug.LogWarning(
169
+ "[Replay] Custom restart has no handler; falling back to HyperLoader.");
170
+ BeginReplaySceneRestart(restartVersion);
171
+ return;
172
+ }
173
+
174
+ _host.StartCoroutine(WaitForReplayCustomRestart(restartVersion));
175
+ return;
176
+ }
177
+ catch (Exception exception)
178
+ {
179
+ HyperDebug.LogWarning(
180
+ $"[Replay] Custom restart failed; falling back to HyperLoader: {exception.Message}");
181
+ BeginReplaySceneRestart(restartVersion);
182
+ return;
183
+ }
153
184
  }
154
185
 
186
+ BeginReplaySceneRestart(restartVersion);
187
+ }
188
+
189
+ private static IEnumerator WaitForReplayCustomRestart(int restartVersion)
190
+ {
191
+ float timeoutAt = Time.realtimeSinceStartup + ReplayCustomRestartTimeoutSeconds;
192
+ while (_replayCustomRestartPending && restartVersion == _replayCustomRestartVersion &&
193
+ Time.realtimeSinceStartup < timeoutAt)
194
+ {
195
+ yield return null;
196
+ }
197
+
198
+ if (!_replayCustomRestartPending || restartVersion != _replayCustomRestartVersion) yield break;
199
+
200
+ HyperDebug.LogWarning(
201
+ $"[Replay] Custom restart did not complete within {ReplayCustomRestartTimeoutSeconds:0} seconds; " +
202
+ "falling back to HyperLoader.");
203
+ BeginReplaySceneRestart(restartVersion);
204
+ }
205
+
206
+ private static void BeginReplaySceneRestart(int restartVersion)
207
+ {
208
+ if (restartVersion != _replayCustomRestartVersion) return;
209
+
210
+ _replayCustomRestartPending = false;
211
+ _replayRandomResetPending = false;
155
212
  if (_host != null)
156
213
  {
157
214
  _host.StartCoroutine(RestartSessionCoroutine());
@@ -176,6 +233,34 @@ namespace Hyper.Internal
176
233
  HyperRandom.Init(ReplaySessionState.OriginalRandomSeed);
177
234
  }
178
235
 
236
+ internal static void ApplyPendingReplayRandomReset()
237
+ {
238
+ if (!_replayRandomResetPending) return;
239
+
240
+ _replayRandomResetPending = false;
241
+ ResetReplayRandomState();
242
+ }
243
+
244
+ internal static void DeferReplayRandomReset()
245
+ {
246
+ _replayRandomResetPending = true;
247
+ }
248
+
249
+ internal static void CompleteCustomRestart()
250
+ {
251
+ if (!_replayCustomRestartPending || _host == null) return;
252
+
253
+ _replayCustomRestartPending = false;
254
+ _host.StartCoroutine(CompleteReplayCustomRestartNextFrame());
255
+ }
256
+
257
+ private static IEnumerator CompleteReplayCustomRestartNextFrame()
258
+ {
259
+ yield return null;
260
+ _gameManager?.CompleteReplayCustomRestart();
261
+ _isSoftRestarting = false;
262
+ }
263
+
179
264
  /// <summary>
180
265
  /// Coroutine-based restart that properly unloads all scenes before reloading.
181
266
  /// Handles async scene operations and ensures clean state before restart.
@@ -184,6 +269,7 @@ namespace Hyper.Internal
184
269
  {
185
270
  yield return UnloadAllScenesCoroutine();
186
271
 
272
+ Clear();
187
273
  ClearAllDontDestroyOnLoadObjects();
188
274
  SceneManager.LoadScene("HyperLoader");
189
275
  }
@@ -381,6 +467,7 @@ namespace Hyper.Internal
381
467
  internal static void FixedTick()
382
468
  {
383
469
  if (!_isInitialized) return;
470
+ _fixedSimulationTimeSeconds += Time.fixedDeltaTime;
384
471
  _replayManager?.FixedTick();
385
472
  _timerManager?.FixedTick();
386
473
  }
@@ -414,6 +501,10 @@ namespace Hyper.Internal
414
501
  _customLoadingComplete = false;
415
502
  _customLoadingActive = false;
416
503
  _customLoadingProgress = 0f;
504
+ _replayRandomResetPending = false;
505
+ _replayCustomRestartPending = false;
506
+ _replayCustomRestartVersion++;
507
+ _fixedSimulationTimeSeconds = 0d;
417
508
 
418
509
  if (_singletonObject != null)
419
510
  {
@@ -472,6 +563,10 @@ namespace Hyper.Internal
472
563
  _customLoadingComplete = false;
473
564
  _customLoadingActive = false;
474
565
  _customLoadingProgress = 0f;
566
+ _replayRandomResetPending = false;
567
+ _replayCustomRestartPending = false;
568
+ _replayCustomRestartVersion++;
569
+ _fixedSimulationTimeSeconds = 0d;
475
570
  }
476
571
  }
477
572
  }
@@ -219,6 +219,7 @@ namespace Hyper.Internal.Managers
219
219
  internal void ConfigurePlayerSoundSetting()
220
220
  {
221
221
  SetSound(GetSound());
222
+ ReplaySessionState.EnsureAudioMutedDuringSeek();
222
223
  if (_gameManager == null) _gameManager = HyperRuntime.GameManager;
223
224
  }
224
225
 
@@ -462,7 +463,7 @@ namespace Hyper.Internal.Managers
462
463
 
463
464
  private static double GetSimulationTimeSeconds()
464
465
  {
465
- return Time.fixedTimeAsDouble;
466
+ return HyperRuntime.FixedSimulationTimeSeconds;
466
467
  }
467
468
 
468
469
  private ScoreData GetPooledScoreData()
@@ -79,7 +79,13 @@ namespace Hyper.Internal.Managers
79
79
  internal void InvokeGameEndedEvent() => OnGameEnded?.Invoke();
80
80
  internal void InvokeLivesExhaustedEvent() => OnLivesExhausted?.Invoke();
81
81
  internal void InvokeHealthExhaustedEvent() => OnHealthExhausted?.Invoke();
82
- internal void InvokeCustomRestartEvent() => OnCustomRestart?.Invoke();
82
+ internal bool InvokeCustomRestartEvent()
83
+ {
84
+ Action handler = OnCustomRestart;
85
+ if (handler == null) return false;
86
+ handler.Invoke();
87
+ return true;
88
+ }
83
89
  internal void InvokeConnectionLostEvent() => OnInternetConnectionLost?.Invoke();
84
90
  internal void InvokeConnectionFoundEvent() => OnInternetConnectionFound?.Invoke();
85
91
  internal void InvokeBackendSocketManuallyClosedEvent() => OnBackendSocketManuallyClosed?.Invoke();
@@ -191,6 +191,41 @@ namespace Hyper.Internal.Managers
191
191
  Resources.UnloadUnusedAssets();
192
192
  }
193
193
 
194
+ internal void PauseReplay()
195
+ {
196
+ if (!IsReplayMode || !_hasGameStarted || _isPaused || _hasGameEnded)
197
+ {
198
+ return;
199
+ }
200
+
201
+ _isPaused = true;
202
+ _savedReplayTimeScale = Time.timeScale > 0f
203
+ ? Time.timeScale
204
+ : ReplaySessionState.PlaybackSpeed;
205
+ Time.timeScale = 0f;
206
+ HyperRuntime.Timer.PauseGameplayTimers();
207
+
208
+ _previousTimeScale = 0f;
209
+ _eventManager.InvokeTimeScaleFrozenEvent();
210
+ ReplayPresentationCanvas.SetSeekingLayer(false);
211
+ SetBlockerActive(true);
212
+ ShowReplayPauseModal();
213
+ }
214
+
215
+ internal void ResumeReplay()
216
+ {
217
+ if (!IsReplayMode || !_isPaused || _hasGameEnded) return;
218
+
219
+ _isPaused = false;
220
+ HidePauseModals();
221
+ Time.timeScale = _savedReplayTimeScale > 0f ? _savedReplayTimeScale : 1f;
222
+ HyperRuntime.Timer.ResumeGameTimersAfterPause();
223
+
224
+ _previousTimeScale = Time.timeScale;
225
+ _eventManager.InvokeTimeScaleResumedEvent();
226
+ ReplayPresentationCanvas.SetSeekingLayer(ReplaySessionState.IsSeeking);
227
+ }
228
+
194
229
  internal void ResumeGame()
195
230
  {
196
231
  _isPaused = false;
@@ -222,51 +257,7 @@ namespace Hyper.Internal.Managers
222
257
  private void HandleReplayFocus(bool hasFocus)
223
258
  {
224
259
  if (!_hasGameStarted || _hasGameEnded) return;
225
-
226
- if (!hasFocus && _replayFocusResumeCoroutine != null)
227
- {
228
- StopCoroutine(_replayFocusResumeCoroutine);
229
- _replayFocusResumeCoroutine = null;
230
- }
231
-
232
- if (!hasFocus && !_replayFocusSuspended)
233
- {
234
- _replayFocusSuspended = true;
235
- _savedReplayTimeScale = Time.timeScale;
236
- Time.timeScale = 0f;
237
- HyperRuntime.Timer.PauseGameplayTimers();
238
-
239
- // WebGL may stop producing frames immediately after focus loss,
240
- // so suspend replay timing here instead of waiting for Update.
241
- _previousTimeScale = 0f;
242
- _eventManager.InvokeTimeScaleFrozenEvent();
243
- HyperDebug.Log("Time scale frozen event invoked");
244
- }
245
- else if (hasFocus && _replayFocusSuspended)
246
- {
247
- if (_replayFocusResumeCoroutine == null)
248
- {
249
- _replayFocusResumeCoroutine = StartCoroutine(ResumeReplayAfterFocus());
250
- }
251
- }
252
- }
253
-
254
- private IEnumerator ResumeReplayAfterFocus()
255
- {
256
- // WebGL reports focus before Unity refreshes its unscaled clock. Keep
257
- // gameplay and replay frozen briefly while session time continues.
258
- yield return new WaitForSecondsRealtime(1f);
259
-
260
- _replayFocusResumeCoroutine = null;
261
- if (!_replayFocusSuspended || _hasGameEnded) yield break;
262
-
263
- _replayFocusSuspended = false;
264
- Time.timeScale = _savedReplayTimeScale > 0f ? _savedReplayTimeScale : 1f;
265
- HyperRuntime.Timer.ResumeGameTimersAfterPause();
266
-
267
- _previousTimeScale = Time.timeScale;
268
- _eventManager.InvokeTimeScaleResumedEvent();
269
- HyperDebug.Log("Time scale resumed event invoked");
260
+ if (!hasFocus) PauseReplay();
270
261
  }
271
262
 
272
263
  void HandlePauseState(bool shouldPause)
@@ -552,7 +543,7 @@ namespace Hyper.Internal.Managers
552
543
 
553
544
  internal void ShowStartGameDeadline(int timeLeft)
554
545
  {
555
- if (_hasGameStarted || !IsInGameScene) return;
546
+ if (_hasGameStarted || !IsInGameScene || IsReplayMode) return;
556
547
 
557
548
  EnsureStartGameDeadlineBar();
558
549
  _eventManager.OnSessionAboutToExpire += ChangeDeadlineColorToRed;
@@ -728,7 +719,6 @@ namespace Hyper.Internal.Managers
728
719
  private bool _isPortrait;
729
720
  private float _previousTimeScale = 1f;
730
721
  private float _savedTimeScaleBeforePause = 1f;
731
- private bool _replayFocusSuspended;
732
722
  private float _savedReplayTimeScale = 1f;
733
723
 
734
724
  #endregion
@@ -1025,6 +1015,8 @@ namespace Hyper.Internal.Managers
1025
1015
  if (_gameStartRequested || _hasGameStarted || _hasGameEnded || !IsInGameScene) return;
1026
1016
  _gameStartRequested = true;
1027
1017
 
1018
+ HyperRuntime.ApplyPendingReplayRandomReset();
1019
+
1028
1020
  HideStartGameDeadlineSnackBar();
1029
1021
  _dataManager.UpdateScore(0);
1030
1022
  HyperRuntime.Timer.StartActiveGameTimer();
@@ -1062,7 +1054,7 @@ namespace Hyper.Internal.Managers
1062
1054
 
1063
1055
  /// <summary>
1064
1056
  /// Resets SDK UI state for custom restart flow.
1065
- /// Called after OnCustomRestart event is fired to prepare the SDK for the developer's restart logic.
1057
+ /// Called before OnCustomRestart so the developer's cleanup starts from reset SDK state.
1066
1058
  /// </summary>
1067
1059
  internal void ResetUIStateForCustomRestart()
1068
1060
  {
@@ -1075,7 +1067,6 @@ namespace Hyper.Internal.Managers
1075
1067
  _gameStartRequested = false;
1076
1068
  _hasGameEnded = false;
1077
1069
  _replayEndHandled = false;
1078
- _replayFocusSuspended = false;
1079
1070
  _isPaused = false;
1080
1071
  _isFrozenBeforePause = false;
1081
1072
 
@@ -1086,6 +1077,12 @@ namespace Hyper.Internal.Managers
1086
1077
  ShowControlBarElements();
1087
1078
  _replayControllerComponent?.ResetForReplayRestart();
1088
1079
 
1080
+ if (IsReplayMode && HyperRuntime.IsReplayCustomRestartPending)
1081
+ {
1082
+ HideStartingInstructionModal();
1083
+ return;
1084
+ }
1085
+
1089
1086
  if (IsReplayMode && ReplaySessionState.IsSeeking)
1090
1087
  {
1091
1088
  HideStartingInstructionModal();
@@ -1097,6 +1094,20 @@ namespace Hyper.Internal.Managers
1097
1094
  }
1098
1095
  }
1099
1096
 
1097
+ internal void CompleteReplayCustomRestart()
1098
+ {
1099
+ if (!IsReplayMode || _gameStartRequested || _hasGameStarted || _hasGameEnded) return;
1100
+
1101
+ if (ReplaySessionState.IsSeeking)
1102
+ {
1103
+ StartCoroutine(StartSeekingReplayWhenReady());
1104
+ }
1105
+ else
1106
+ {
1107
+ ShowStartingInstructionModal();
1108
+ }
1109
+ }
1110
+
1100
1111
  #endregion
1101
1112
 
1102
1113
  #region Game End Logic
@@ -1138,7 +1149,6 @@ namespace Hyper.Internal.Managers
1138
1149
 
1139
1150
  _replayEndHandled = true;
1140
1151
  _hasGameEnded = true;
1141
- _replayFocusSuspended = false;
1142
1152
  Time.timeScale = 1f;
1143
1153
  _eventManager.InvokeGameEndedEvent();
1144
1154
  HyperRuntime.Timer.StopAllTimers();
@@ -1160,10 +1170,12 @@ namespace Hyper.Internal.Managers
1160
1170
  _eventManager.InvokeGameEndedEvent();
1161
1171
  }
1162
1172
 
1173
+ _isPaused = false;
1163
1174
  Time.timeScale = 1f;
1164
1175
  HyperRuntime.Timer.StopAllTimers();
1165
1176
  _dataManager.StopCapturingScores();
1166
1177
  HyperRuntime.BackendManager.StopSendingScores();
1178
+ HidePauseModals();
1167
1179
  _replayControllerComponent?.SetReplayEnded();
1168
1180
  ReplaySessionState.Clear();
1169
1181
  ReplayPresentationCanvas.DestroyCurrent();
@@ -1274,8 +1286,8 @@ namespace Hyper.Internal.Managers
1274
1286
  private GameObject _replayControllerObject;
1275
1287
  private ReplayController _replayControllerComponent;
1276
1288
  private GameObject _replayOverModal;
1289
+ private GameObject _replayPausedModal;
1277
1290
  private Coroutine _replayConnectionCheckCoroutine;
1278
- private Coroutine _replayFocusResumeCoroutine;
1279
1291
 
1280
1292
  private GameObject _connectionStatusBar;
1281
1293
  private GameObject _startGameDeadlineStatusBar;
@@ -1505,6 +1517,30 @@ namespace Hyper.Internal.Managers
1505
1517
  _replayOverModal.transform.SetAsLastSibling();
1506
1518
  }
1507
1519
 
1520
+ private void ShowReplayPauseModal()
1521
+ {
1522
+ if (_replayPausedModal == null)
1523
+ {
1524
+ if (_canvas == null) CreateCanvas();
1525
+ GameObject prefab = Resources.Load<GameObject>("UIPrefabs/ReplayPausedModal");
1526
+ if (prefab == null)
1527
+ {
1528
+ HyperDebug.LogWarning("[GameManager] ReplayPausedModal prefab not found.");
1529
+ ResumeReplay();
1530
+ return;
1531
+ }
1532
+
1533
+ _replayPausedModal = InstantiateModal(prefab);
1534
+ if (_replayPausedModal.GetComponent<ReplayPausedModal>() == null)
1535
+ {
1536
+ _replayPausedModal.AddComponent<ReplayPausedModal>();
1537
+ }
1538
+ }
1539
+
1540
+ _replayPausedModal.SetActive(true);
1541
+ _replayPausedModal.transform.SetAsLastSibling();
1542
+ }
1543
+
1508
1544
  private void EnsureConnectionStatusBar()
1509
1545
  {
1510
1546
  if (_connectionStatusBar != null) return;
@@ -1753,6 +1789,7 @@ namespace Hyper.Internal.Managers
1753
1789
  internal void HidePauseModals()
1754
1790
  {
1755
1791
  SetBlockerActive(false);
1792
+ if (_replayPausedModal != null) _replayPausedModal.SetActive(false);
1756
1793
  if (_battleGamePausedModal != null) _battleGamePausedModal.SetActive(false);
1757
1794
  if (_practicePausedModal != null) _practicePausedModal.SetActive(false);
1758
1795
  if (_battleGamePausedLandscapeModal != null) _battleGamePausedLandscapeModal.SetActive(false);
@@ -36,7 +36,10 @@ namespace Hyper.Internal.Managers
36
36
  {
37
37
  if (_playback == null || !_playback.IsPlaying) return;
38
38
  _playback.Tick();
39
- ReplaySessionState.SetProgress(_playback.Progress01);
39
+ if (ReplaySessionState.SetProgress(_playback.Progress01))
40
+ {
41
+ UnityEngine.Time.timeScale = ReplaySessionState.PlaybackSpeed;
42
+ }
40
43
  }
41
44
 
42
45
  internal void PrepareForRestart()
@@ -74,6 +77,7 @@ namespace Hyper.Internal.Managers
74
77
  }
75
78
 
76
79
  _playback.Begin(ReplaySessionState.ReplayNdjson);
80
+ ReplaySessionState.SetGameplayStartProgress(_playback.GameplayStartProgress01);
77
81
  }
78
82
 
79
83
  private void HandleGameEnded()