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

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 (115) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/Editor/Scripts/Setup/HyperSDKInstaller.cs +42 -0
  3. package/Editor/Scripts/Tools/HyperGameModeMenu.cs +27 -7
  4. package/Runtime/Internal/AssemblyInfo.cs +1 -0
  5. package/Runtime/Internal/Bootstrap/HyperLoader.cs +186 -8
  6. package/Runtime/Internal/Core/HyperRuntime.cs +82 -0
  7. package/Runtime/Internal/Core/HyperRuntimeHost.cs +6 -0
  8. package/Runtime/Internal/Data/URLData.cs +18 -1
  9. package/Runtime/Internal/Managers/BackendManager.cs +281 -24
  10. package/Runtime/Internal/Managers/DataManager.cs +207 -21
  11. package/Runtime/Internal/Managers/GameManager.cs +365 -17
  12. package/Runtime/Internal/Managers/ReplayManager.cs +117 -0
  13. package/Runtime/Internal/Managers/ReplayManager.cs.meta +11 -0
  14. package/Runtime/Internal/Managers/TimeManager.cs +54 -12
  15. package/Runtime/Internal/Replay/ReplayActivityRecorder.cs +182 -0
  16. package/Runtime/Internal/Replay/ReplayActivityRecorder.cs.meta +2 -0
  17. package/Runtime/Internal/Replay/ReplaySessionState.cs +181 -0
  18. package/Runtime/Internal/Replay/ReplaySessionState.cs.meta +2 -0
  19. package/Runtime/Internal/Replay.meta +8 -0
  20. package/Runtime/Internal/ScriptableObjects/HyperSDKSettings.cs +5 -0
  21. package/Runtime/Internal/UI/Animation/LoadingTextAnimation.cs +15 -1
  22. package/Runtime/Internal/UI/Components/ControlBar.cs +25 -2
  23. package/Runtime/Internal/UI/Components/PauseButton.cs +4 -1
  24. package/Runtime/Internal/UI/Components/ReplayController.cs +527 -0
  25. package/Runtime/Internal/UI/Components/ReplayController.cs.meta +2 -0
  26. package/Runtime/Internal/UI/Components/ReplayMarkerPulse.cs +89 -0
  27. package/Runtime/Internal/UI/Components/ReplayMarkerPulse.cs.meta +2 -0
  28. package/Runtime/Internal/UI/Components/ReplayPresentationCanvas.cs +183 -0
  29. package/Runtime/Internal/UI/Components/ReplayPresentationCanvas.cs.meta +2 -0
  30. package/Runtime/Internal/UI/Modals/LoadingFailedModal.cs +12 -0
  31. package/Runtime/Internal/UI/Modals/ReplayOverModal.cs +61 -0
  32. package/Runtime/Internal/UI/Modals/ReplayOverModal.cs.meta +2 -0
  33. package/Runtime/Internal/UI/Modals/StartingInstructionModal.cs +38 -29
  34. package/Runtime/Public/AssemblyInfo.cs +3 -0
  35. package/Runtime/Public/AssemblyInfo.cs.meta +2 -0
  36. package/Runtime/Public/Core/HyperSDK.cs +305 -109
  37. package/Runtime/Public/Hyper.asmdef +2 -1
  38. package/Runtime/Public/Replay/ReplayPlaybackEngine.cs +555 -0
  39. package/Runtime/Public/Replay/ReplayPlaybackEngine.cs.meta +2 -0
  40. package/Runtime/Public/Replay.meta +8 -0
  41. package/Runtime/Resources/Fonts/Nunito/Nunito-Light SDF.asset +528 -521
  42. package/Runtime/Resources/Materials/HyperReplayScanlines.preset +340 -0
  43. package/Runtime/Resources/Materials/HyperReplayScanlines.preset.meta +8 -0
  44. package/Runtime/Resources/Materials/ReplayScanlineOverlay.mat +55 -0
  45. package/Runtime/Resources/Materials/ReplayScanlineOverlay.mat.meta +8 -0
  46. package/Runtime/Resources/Shaders/ReplayScanlineOverlay.shader +150 -0
  47. package/Runtime/Resources/Shaders/ReplayScanlineOverlay.shader.meta +10 -0
  48. package/Runtime/Resources/Sprites/Replays/Fallback avatar.png +0 -0
  49. package/Runtime/Resources/Sprites/Replays/Fallback avatar.png.meta +156 -0
  50. package/Runtime/Resources/Sprites/Replays/Recording.png +0 -0
  51. package/Runtime/Resources/Sprites/Replays/Recording.png.meta +156 -0
  52. package/Runtime/Resources/Sprites/Replays/Replay frame.png +0 -0
  53. package/Runtime/Resources/Sprites/Replays/Replay frame.png.meta +156 -0
  54. package/Runtime/Resources/Sprites/Replays/Replay modal icon.png +0 -0
  55. package/Runtime/Resources/Sprites/Replays/Replay modal icon.png.meta +156 -0
  56. package/Runtime/Resources/Sprites/Replays.meta +8 -0
  57. package/Runtime/Resources/UIPrefabs/ControlBar.prefab +169 -2
  58. package/Runtime/Resources/UIPrefabs/GamePausedModal_Battle.prefab +1 -1
  59. package/Runtime/Resources/UIPrefabs/ReplayController.prefab +1269 -0
  60. package/Runtime/Resources/UIPrefabs/ReplayController.prefab.meta +7 -0
  61. package/Runtime/Resources/UIPrefabs/ReplayInputBlocker.prefab +90 -0
  62. package/Runtime/Resources/UIPrefabs/ReplayInputBlocker.prefab.meta +7 -0
  63. package/Runtime/Resources/UIPrefabs/ReplayOverModal.prefab +958 -0
  64. package/Runtime/Resources/UIPrefabs/ReplayOverModal.prefab.meta +7 -0
  65. package/Runtime/Resources/UIPrefabs/ReplaySeekOverlay.prefab +189 -0
  66. package/Runtime/Resources/UIPrefabs/ReplaySeekOverlay.prefab.meta +7 -0
  67. package/Runtime/Resources/UIPrefabs/SnackBar.prefab +2 -2
  68. package/Runtime/Shared/AssemblyInfo.cs +5 -0
  69. package/Runtime/Shared/AssemblyInfo.cs.meta +11 -0
  70. package/Runtime/Shared/DTOs/Enums.cs +8 -3
  71. package/Runtime/Shared/DTOs/Score/ScoreSnapshot.cs +13 -1
  72. package/Runtime/Shared/Replay/IReplayPlaybackEngine.cs +37 -0
  73. package/Runtime/Shared/Replay/IReplayPlaybackEngine.cs.meta +11 -0
  74. package/Runtime/Shared/Replay.meta +8 -0
  75. package/Samples~/LaneRunner/LaneRunnerSample.unity +1603 -0
  76. package/Samples~/LaneRunner/LaneRunnerSample.unity.meta +7 -0
  77. package/Samples~/LaneRunner/Materials/DoubleScore.mat +86 -0
  78. package/Samples~/LaneRunner/Materials/DoubleScore.mat.meta +8 -0
  79. package/Samples~/LaneRunner/Materials/Ground.mat +84 -0
  80. package/Samples~/LaneRunner/Materials/Ground.mat.meta +8 -0
  81. package/Samples~/LaneRunner/Materials/Lane.mat +84 -0
  82. package/Samples~/LaneRunner/Materials/Lane.mat.meta +8 -0
  83. package/Samples~/LaneRunner/Materials/LaneMarker.mat +84 -0
  84. package/Samples~/LaneRunner/Materials/LaneMarker.mat.meta +8 -0
  85. package/Samples~/LaneRunner/Materials/Obstacle.mat +84 -0
  86. package/Samples~/LaneRunner/Materials/Obstacle.mat.meta +8 -0
  87. package/Samples~/LaneRunner/Materials/Player.mat +84 -0
  88. package/Samples~/LaneRunner/Materials/Player.mat.meta +8 -0
  89. package/Samples~/LaneRunner/Materials.meta +8 -0
  90. package/Samples~/LaneRunner/Prefabs/DoubleScorePickup.prefab +152 -0
  91. package/Samples~/LaneRunner/Prefabs/DoubleScorePickup.prefab.meta +7 -0
  92. package/Samples~/LaneRunner/Prefabs/Obstacle.prefab +152 -0
  93. package/Samples~/LaneRunner/Prefabs/Obstacle.prefab.meta +7 -0
  94. package/Samples~/LaneRunner/Prefabs.meta +8 -0
  95. package/Samples~/LaneRunner/README.md +35 -0
  96. package/Samples~/LaneRunner/README.md.meta +7 -0
  97. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerCommand.cs +9 -0
  98. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerCommand.cs.meta +2 -0
  99. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerGame.cs +93 -0
  100. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerGame.cs.meta +2 -0
  101. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerPlayer.cs +155 -0
  102. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerPlayer.cs.meta +2 -0
  103. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerSpawner.cs +110 -0
  104. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerSpawner.cs.meta +2 -0
  105. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerTrackItem.cs +41 -0
  106. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerTrackItem.cs.meta +2 -0
  107. package/Samples~/LaneRunner/Scripts/Gameplay.meta +8 -0
  108. package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerReplayAdapter.cs +62 -0
  109. package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerReplayAdapter.cs.meta +2 -0
  110. package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerTournamentAdapter.cs +64 -0
  111. package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerTournamentAdapter.cs.meta +2 -0
  112. package/Samples~/LaneRunner/Scripts/Hyper.meta +8 -0
  113. package/Samples~/LaneRunner/Scripts.meta +8 -0
  114. package/Tests.meta +8 -0
  115. package/package.json +8 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,37 @@
1
+ # [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
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **replay:** respect custom restart flows ([7470a17](https://github.com/mvmhyper/hyper-sdk/commit/7470a17c60534465500a202c24b0e360f61850fc))
7
+ * **replay:** shorten automatic replay start ([d05dc92](https://github.com/mvmhyper/hyper-sdk/commit/d05dc9267fbf05d392135eac3bb7c4f69b786eff))
8
+ * **timer:** align game time with fixed simulation ([b690d86](https://github.com/mvmhyper/hyper-sdk/commit/b690d861e178f05b21f1a7fbd87e255d97008a8b))
9
+
10
+ # [1.11.0-beta.1](https://github.com/mvmhyper/hyper-sdk/compare/v1.10.0...v1.11.0-beta.1) (2026-08-26)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **replay:** add player handle font glyph ([5ba9f0d](https://github.com/mvmhyper/hyper-sdk/commit/5ba9f0d0c3e207b4ff0a4b4f16ffe53bd215ea14))
16
+ * **replay:** handle backend loading failures ([1d299cf](https://github.com/mvmhyper/hyper-sdk/commit/1d299cf7d1002d5632cd0b1da2c928405b50e56a))
17
+ * **replay:** harden web replay playback ([96f0521](https://github.com/mvmhyper/hyper-sdk/commit/96f05215f6a483306622b632366de20b58ede6e9))
18
+ * **replay:** preserve recorded action timing ([48ff268](https://github.com/mvmhyper/hyper-sdk/commit/48ff268c16b0ddd5fd4e81e0903d4b54b05d4a1c))
19
+ * **replay:** refine replay ui behavior ([8a9364f](https://github.com/mvmhyper/hyper-sdk/commit/8a9364fb19f6ccd6df31a396bf7fef30c5f66040))
20
+ * **score:** preserve latest final snapshot time ([b9242cd](https://github.com/mvmhyper/hyper-sdk/commit/b9242cd5478702621fbc30a1bd21a972bbf9fb4c))
21
+
22
+
23
+ ### Features
24
+
25
+ * **replay:** add math rush replay integration ([394e8b0](https://github.com/mvmhyper/hyper-sdk/commit/394e8b0591c99899b5f2433bbadf159aa4631d47))
26
+ * **replay:** add replay activity recording ([8b87b42](https://github.com/mvmhyper/hyper-sdk/commit/8b87b425440ebfa4d08fcc9d62586afde58fda33))
27
+ * **replay:** add replay playback scheduling ([40c9c83](https://github.com/mvmhyper/hyper-sdk/commit/40c9c83a886b06434ee886fa85f490ca74710ac6))
28
+ * **replay:** add replay ui flows ([e287986](https://github.com/mvmhyper/hyper-sdk/commit/e287986222763c814a299cfcb81d1cd14bcf49e9))
29
+ * **replay:** add replay visual assets ([4c61645](https://github.com/mvmhyper/hyper-sdk/commit/4c616458155e04444feac9fb187b63f646035f7a))
30
+ * **replay:** add stable replay launch mode ([fbfd8fc](https://github.com/mvmhyper/hyper-sdk/commit/fbfd8fcdc5a7119f7c3655f1a033705ccd47608f))
31
+ * **replay:** align playback with fixed simulation ([8e4051d](https://github.com/mvmhyper/hyper-sdk/commit/8e4051d5d41fe3dbc4d050ee4f961ae6d690d1bd))
32
+ * **replay:** finalize playback simulation and seeking ([de3b6e7](https://github.com/mvmhyper/hyper-sdk/commit/de3b6e79be4ecde5abc4bb20190e80f12fbe3598))
33
+ * **replay:** load replay data from backend ([f89c067](https://github.com/mvmhyper/hyper-sdk/commit/f89c067b6e55074c08f75e34c0d378af969b60c5))
34
+
1
35
  # [1.10.0](https://github.com/mvmhyper/hyper-sdk/compare/v1.9.1...v1.10.0) (2026-08-10)
2
36
 
3
37
 
@@ -20,9 +20,51 @@ namespace Hyper.Editor
20
20
  private const string HyperSDKSettingsRelativePath = "Runtime/Resources/HyperSDKSettings.asset";
21
21
  private const string HyperGameContentConfigDestination = "Assets/HyperSDK/Runtime/Resources/HyperGameContentConfig.asset";
22
22
  private const string HyperSDKSettingsDestination = "Assets/HyperSDK/Runtime/Resources/HyperSDKSettings.asset";
23
+ private const string LaneRunnerSampleRelativePath = "Samples~/LaneRunner";
24
+ private const string LaneRunnerSampleDestination = "Assets/Samples/HyperSDK/Lane Runner";
23
25
 
24
26
  private static bool _hasChecked = false;
25
27
 
28
+ [MenuItem("Hyper/Samples/Import Lane Runner", priority = 100)]
29
+ private static void ImportLaneRunnerSample()
30
+ {
31
+ var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(HyperSDKInstaller).Assembly);
32
+ if (packageInfo == null || string.IsNullOrEmpty(packageInfo.resolvedPath) ||
33
+ packageInfo.resolvedPath.Replace('\\', '/').Contains("/Assets/"))
34
+ {
35
+ var developmentSample = AssetDatabase.LoadAssetAtPath<SceneAsset>(
36
+ "Assets/SampleGame (Lane Runner)/LaneRunnerSample.unity");
37
+ Selection.activeObject = developmentSample;
38
+ EditorGUIUtility.PingObject(developmentSample);
39
+ return;
40
+ }
41
+
42
+ string sourcePath = Path.Combine(packageInfo.resolvedPath, LaneRunnerSampleRelativePath);
43
+
44
+ if (!Directory.Exists(sourcePath))
45
+ {
46
+ EditorUtility.DisplayDialog("Lane Runner Sample",
47
+ "The Lane Runner sample was not found in this HyperSDK installation.", "OK");
48
+ return;
49
+ }
50
+
51
+ if (Directory.Exists(LaneRunnerSampleDestination))
52
+ {
53
+ EditorUtility.DisplayDialog("Lane Runner Sample",
54
+ "Lane Runner is already imported at Assets/Samples/HyperSDK/Lane Runner.", "OK");
55
+ return;
56
+ }
57
+
58
+ Directory.CreateDirectory(Path.GetDirectoryName(LaneRunnerSampleDestination));
59
+ FileUtil.CopyFileOrDirectory(sourcePath, LaneRunnerSampleDestination);
60
+ AssetDatabase.Refresh();
61
+
62
+ var scene = AssetDatabase.LoadAssetAtPath<SceneAsset>(
63
+ LaneRunnerSampleDestination + "/LaneRunnerSample.unity");
64
+ Selection.activeObject = scene;
65
+ EditorGUIUtility.PingObject(scene);
66
+ }
67
+
26
68
  static HyperSDKInstaller()
27
69
  {
28
70
  // Only register delayCall once per domain reload
@@ -8,11 +8,12 @@ namespace Hyper.Editor
8
8
  {
9
9
  static class HyperGameModeMenu
10
10
  {
11
- private enum DevMode { Practice = 0, Battle = 1 }
11
+ private enum DevMode { Practice = 0, Battle = 1, Replay = 2 }
12
12
  private const string PrefKey = "Hyper.DevMode";
13
13
 
14
14
  private const string BattlePath = "Hyper/Battle Game Mode";
15
15
  private const string PracticePath = "Hyper/Practice Game Mode";
16
+ private const string ReplayPath = "Hyper/Replay Game Mode";
16
17
 
17
18
  private static HyperSDKSettings _settings;
18
19
 
@@ -26,10 +27,14 @@ namespace Hyper.Editor
26
27
  }
27
28
 
28
29
  private static DevMode FromSettings(GameMode gm)
29
- => gm == GameMode.Battle ? DevMode.Battle : DevMode.Practice;
30
-
31
- private static GameMode ToSettings(DevMode dm)
32
- => dm == DevMode.Battle ? GameMode.Battle : GameMode.Practice;
30
+ {
31
+ switch (gm)
32
+ {
33
+ case GameMode.Battle: return DevMode.Battle;
34
+ case GameMode.Replay: return DevMode.Replay;
35
+ default: return DevMode.Practice;
36
+ }
37
+ }
33
38
 
34
39
  private static DevMode Current
35
40
  {
@@ -44,8 +49,12 @@ namespace Hyper.Editor
44
49
  {
45
50
  if (TryLoadSettings(out var s))
46
51
  {
47
- if (value == DevMode.Battle) s.SetModeAsBattle();
48
- else s.SetModeAsPractice();
52
+ switch (value)
53
+ {
54
+ case DevMode.Battle: s.SetModeAsBattle(); break;
55
+ case DevMode.Replay: s.SetModeAsReplay(); break;
56
+ default: s.SetModeAsPractice(); break;
57
+ }
49
58
 
50
59
  EditorUtility.SetDirty(s);
51
60
  AssetDatabase.SaveAssets();
@@ -84,6 +93,16 @@ namespace Hyper.Editor
84
93
  return !EditorApplication.isPlaying;
85
94
  }
86
95
 
96
+ [MenuItem(ReplayPath, priority = 300)]
97
+ private static void SetReplay() => SetMode(DevMode.Replay);
98
+
99
+ [MenuItem(ReplayPath, validate = true)]
100
+ private static bool ValidateReplay()
101
+ {
102
+ Menu.SetChecked(ReplayPath, Current == DevMode.Replay);
103
+ return !EditorApplication.isPlaying;
104
+ }
105
+
87
106
  #endregion
88
107
 
89
108
  private static void SetMode(DevMode mode)
@@ -94,6 +113,7 @@ namespace Hyper.Editor
94
113
 
95
114
  Menu.SetChecked(BattlePath, mode == DevMode.Battle);
96
115
  Menu.SetChecked(PracticePath, mode == DevMode.Practice);
116
+ Menu.SetChecked(ReplayPath, mode == DevMode.Replay);
97
117
 
98
118
  HyperDebug.LogSuccess($"Game Mode set to: {mode}");
99
119
  }
@@ -2,4 +2,5 @@ using System.Runtime.CompilerServices;
2
2
 
3
3
  [assembly: InternalsVisibleTo("Hyper")]
4
4
  [assembly: InternalsVisibleTo("Hyper.Editor")]
5
+ [assembly: InternalsVisibleTo("Hyper.Tests.EditMode")]
5
6
 
@@ -1,6 +1,9 @@
1
1
  using Hyper.Internal.Managers;
2
2
  using System;
3
3
  using System.Collections;
4
+ #if UNITY_EDITOR
5
+ using System.IO;
6
+ #endif
4
7
  using TMPro;
5
8
  using UnityEngine;
6
9
  using UnityEngine.SceneManagement;
@@ -60,6 +63,11 @@ namespace Hyper.Internal
60
63
  InitializeComponents();
61
64
  LoadConfiguration();
62
65
  SetupSplashScreen();
66
+
67
+ if (ReplaySessionState.IsSeeking)
68
+ {
69
+ SetLoaderCanvasVisible(false);
70
+ }
63
71
  }
64
72
 
65
73
  private void Start()
@@ -74,6 +82,15 @@ namespace Hyper.Internal
74
82
 
75
83
  #endregion
76
84
 
85
+ private void SetLoaderCanvasVisible(bool isVisible)
86
+ {
87
+ Canvas[] canvases = GetComponentsInChildren<Canvas>(true);
88
+ for (int i = 0; i < canvases.Length; i++)
89
+ {
90
+ canvases[i].enabled = isVisible;
91
+ }
92
+ }
93
+
77
94
  #region Initialization
78
95
 
79
96
  private void InitializeComponents()
@@ -115,7 +132,7 @@ namespace Hyper.Internal
115
132
  private static T LoadResourceFromAssets<T>(string resourceName) where T : UnityEngine.Object
116
133
  {
117
134
  T[] allResources = Resources.LoadAll<T>(string.Empty);
118
-
135
+
119
136
  if (allResources == null || allResources.Length == 0)
120
137
  {
121
138
  return Resources.Load<T>(resourceName);
@@ -247,9 +264,13 @@ namespace Hyper.Internal
247
264
  {
248
265
  sdkInitialized = true;
249
266
 
250
- if (_eventManager == null) _eventManager = HyperRuntime.EventManager;
251
- if (_gameManager == null) _gameManager = HyperRuntime.GameManager;
252
- if (_backendManager == null) _backendManager = HyperRuntime.BackendManager;
267
+ // Bootstrap retries recreate the runtime managers. Always bind the
268
+ // current instances rather than retaining objects queued for destruction.
269
+ _eventManager = HyperRuntime.EventManager;
270
+ _gameManager = HyperRuntime.GameManager;
271
+ _backendManager = HyperRuntime.BackendManager;
272
+ UpdateLoadingTextForMode();
273
+ UpdateHintVisibilityForMode();
253
274
  SubscribeToEvents();
254
275
  });
255
276
 
@@ -264,14 +285,23 @@ namespace Hyper.Internal
264
285
  _loadingBarSlider.value = _bootstrapProgress;
265
286
 
266
287
  // Start tutorial discovery early, but never wait for it before loading gameplay.
267
- HyperRuntime.BackendManager.GetTutorial(null);
288
+ if (!_gameManager.IsReplayMode)
289
+ {
290
+ HyperRuntime.BackendManager.GetTutorial(null);
291
+ }
268
292
 
269
293
  // Step 2: Handle Backend/PVP or Practice mode initialization
270
- if (_gameManager.Config.IsBackendGame && _gameManager.Config.isPVP)
294
+ if (_gameManager.IsReplayMode)
295
+ {
296
+ bool replayInitialized = false;
297
+ yield return InitializeReplay(success => replayInitialized = success);
298
+ if (!replayInitialized) yield break;
299
+ }
300
+ else if (_gameManager.Config.IsBackendGame && _gameManager.Config.isPVP)
271
301
  {
272
302
  yield return InitializeBackendGame();
273
303
  }
274
- else if (_gameManager.Config.GameMode == GameMode.Battle)
304
+ else if (_gameManager.IsBattleMode)
275
305
  {
276
306
  InitializeNonBackendBattle();
277
307
  }
@@ -328,6 +358,117 @@ namespace Hyper.Internal
328
358
  _loadingBarSlider.value = _bootstrapProgress;
329
359
  }
330
360
 
361
+ private IEnumerator InitializeReplay(Action<bool> onComplete)
362
+ {
363
+ _targetSceneName = _gameContentConfig.GameScene?.SceneName;
364
+
365
+ if (!ReplaySessionState.HasReplayData)
366
+ {
367
+ #if UNITY_EDITOR
368
+ if (string.IsNullOrWhiteSpace(_gameManager.Config.EntryId))
369
+ {
370
+ if (TryLoadEditorReplay(out string editorReplayError))
371
+ {
372
+ _backendManager.RegisterSeed(GetRandomSeed());
373
+ onComplete?.Invoke(true);
374
+ yield break;
375
+ }
376
+
377
+ HandleReplayLoadingFailure(editorReplayError, onComplete);
378
+ yield break;
379
+ }
380
+ #endif
381
+ bool replayDownloaded = false;
382
+ string replayError = null;
383
+ yield return _backendManager.FetchReplay(
384
+ _gameManager.Config.EntryId,
385
+ () => replayDownloaded = true,
386
+ error => replayError = error);
387
+
388
+ if (!replayDownloaded || !ReplaySessionState.HasReplayData)
389
+ {
390
+ HandleReplayLoadingFailure($"Unable to load replay: {replayError}", onComplete);
391
+ yield break;
392
+ }
393
+
394
+ }
395
+
396
+ if (ReplaySessionState.AvatarTexture == null &&
397
+ !string.IsNullOrWhiteSpace(ReplaySessionState.AvatarUrl))
398
+ {
399
+ Texture2D downloadedAvatar = null;
400
+ string avatarError = null;
401
+
402
+ yield return _backendManager.FetchReplayAvatar(
403
+ ReplaySessionState.AvatarUrl,
404
+ texture => downloadedAvatar = texture,
405
+ error => avatarError = error);
406
+
407
+ if (downloadedAvatar != null)
408
+ {
409
+ Texture2D circularAvatar = ReplayController.CreateCircularAvatar(
410
+ downloadedAvatar,
411
+ ReplayController.AvatarTextureSize);
412
+
413
+ Destroy(downloadedAvatar);
414
+ ReplaySessionState.SetAvatarTexture(circularAvatar);
415
+ }
416
+ else
417
+ {
418
+ HyperDebug.LogWarning(
419
+ $"Unable to load replay avatar. Using fallback avatar. Error: {avatarError}");
420
+ }
421
+ }
422
+
423
+ _backendManager.RegisterSeed(GetRandomSeed());
424
+ _bootstrapProgress = _progressAfterBackend;
425
+ _loadingBarSlider.value = _bootstrapProgress;
426
+ onComplete?.Invoke(true);
427
+ }
428
+
429
+ #if UNITY_EDITOR
430
+ private bool TryLoadEditorReplay(out string error)
431
+ {
432
+ error = null;
433
+ string path = ReplaySessionState.EditorReplayFilePath;
434
+ if (!File.Exists(path))
435
+ {
436
+ error = $"No Editor replay has been recorded at {path}.";
437
+ return false;
438
+ }
439
+
440
+ try
441
+ {
442
+ string ndjson = File.ReadAllText(path);
443
+ if (!BackendManager.TryParseReplayResponse(
444
+ ndjson,
445
+ out BackendManager.ReplayResponse replay,
446
+ out error))
447
+ {
448
+ return false;
449
+ }
450
+
451
+ ReplaySessionState.SetMetadata(replay.PlayerName, replay.AvatarUrl, replay.Seed);
452
+ ReplaySessionState.SetReplayData(replay.ActivityNdjson);
453
+ HyperDebug.Log($"Replay loaded from {path}");
454
+ return true;
455
+ }
456
+ catch (Exception exception)
457
+ {
458
+ error = $"Unable to load Editor replay: {exception.Message}";
459
+ return false;
460
+ }
461
+ }
462
+ #endif
463
+
464
+ private void HandleReplayLoadingFailure(string error, Action<bool> onComplete)
465
+ {
466
+ HyperDebug.LogError(error);
467
+ onComplete?.Invoke(false);
468
+ _gameManager.ShowLoadingFailedModal(true);
469
+ _gameManager.LoadingFailedModalComponent?.AllAttemptsHaveFailed();
470
+ }
471
+
331
472
  private void InitializeNonBackendBattle()
332
473
  {
333
474
  _targetSceneName = _gameContentConfig.GameScene?.SceneName;
@@ -377,8 +518,21 @@ namespace Hyper.Internal
377
518
 
378
519
  private long GetRandomSeed()
379
520
  {
521
+ if (_gameManager.IsReplayMode)
522
+ {
523
+ if (ReplaySessionState.OriginalRandomSeed != 0)
524
+ {
525
+ return ReplaySessionState.OriginalRandomSeed;
526
+ }
527
+
528
+ if (HyperRuntime.Settings != null && HyperRuntime.Settings._settings != null)
529
+ {
530
+ return HyperRuntime.Settings._settings.RandomSeed;
531
+ }
532
+ }
533
+
380
534
  #if UNITY_EDITOR
381
- // In editor, use the seed from editor simulation settings if available
535
+ // In editor, use the seed from editor simulation settings if available.
382
536
  if (HyperRuntime.Settings != null && HyperRuntime.Settings._settings != null)
383
537
  {
384
538
  return HyperRuntime.Settings._settings.RandomSeed;
@@ -464,6 +618,30 @@ namespace Hyper.Internal
464
618
 
465
619
  #region UI Control
466
620
 
621
+ private void UpdateLoadingTextForMode()
622
+ {
623
+ if (_gameManager == null) return;
624
+
625
+ SetLoadingText(_gameManager.IsReplayMode ? "LOADING REPLAY" : "LAUNCHING GAME");
626
+ }
627
+
628
+ private void SetLoadingText(string text)
629
+ {
630
+ if (launchingGameAnimation == null) return;
631
+
632
+ LoadingTextAnimation animation =
633
+ launchingGameAnimation.GetComponentInChildren<LoadingTextAnimation>(true);
634
+ animation?.SetBaseText(text);
635
+ }
636
+
637
+ private void UpdateHintVisibilityForMode()
638
+ {
639
+ if (hintContainer != null && _gameManager != null && _gameManager.IsReplayMode)
640
+ {
641
+ hintContainer.SetActive(false);
642
+ }
643
+ }
644
+
467
645
  private void ShowLoadingScreenElements(bool show)
468
646
  {
469
647
  if (launchingGameAnimation != null)
@@ -4,6 +4,7 @@ using System.Collections.Generic;
4
4
  using UnityEngine;
5
5
  using UnityEngine.SceneManagement;
6
6
  using Hyper.Internal.Managers;
7
+ using Hyper.Shared;
7
8
 
8
9
  namespace Hyper.Internal
9
10
  {
@@ -30,6 +31,7 @@ namespace Hyper.Internal
30
31
  private static DataManager _dataManager;
31
32
  private static EventManager _eventManager;
32
33
  private static TimerManager _timerManager;
34
+ private static ReplayManager _replayManager;
33
35
 
34
36
  internal static bool IsInitialized => _isInitialized;
35
37
  internal static bool IsSoftRestarting
@@ -55,6 +57,7 @@ namespace Hyper.Internal
55
57
  internal static DataManager DataManager => _dataManager;
56
58
  internal static EventManager EventManager => _eventManager;
57
59
  internal static TimerManager Timer => _timerManager;
60
+ internal static ReplayManager ReplayManager => _replayManager;
58
61
 
59
62
  internal static event Action CustomLoadingStarted
60
63
  {
@@ -114,6 +117,65 @@ namespace Hyper.Internal
114
117
  }
115
118
  }
116
119
 
120
+ /// <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.
123
+ /// </summary>
124
+ internal static void RestartReplaySession()
125
+ {
126
+ ReplaySessionState.ResetForWatchAgain();
127
+ RestartReplay();
128
+ }
129
+
130
+ internal static void SeekReplaySession(float targetProgress, float resumeSpeed, float seekSpeed)
131
+ {
132
+ if (!ReplaySessionState.IsActive || ReplaySessionState.HasEnded) return;
133
+
134
+ ReplaySessionState.RequestSeek(targetProgress, resumeSpeed, seekSpeed);
135
+ ReplaySessionState.ResetForSeekRestart();
136
+ RestartReplay();
137
+ }
138
+
139
+ private static void RestartReplay()
140
+ {
141
+ _isSoftRestarting = true;
142
+ Time.timeScale = 1f;
143
+
144
+ if (_settings != null && _settings.useCustomRestartFlow &&
145
+ _gameManager != null && _eventManager != null)
146
+ {
147
+ _replayManager?.PrepareForRestart();
148
+ ResetReplayRandomState();
149
+ _gameManager.ResetUIStateForCustomRestart();
150
+ _eventManager.InvokeCustomRestartEvent();
151
+ _isSoftRestarting = false;
152
+ return;
153
+ }
154
+
155
+ if (_host != null)
156
+ {
157
+ _host.StartCoroutine(RestartSessionCoroutine());
158
+ }
159
+ else
160
+ {
161
+ ClearAllDontDestroyOnLoadObjects();
162
+ SceneManager.LoadScene("HyperLoader");
163
+ }
164
+ }
165
+
166
+ internal static void ResetReplayRandomState()
167
+ {
168
+ int originalSeed = unchecked((int)ReplaySessionState.OriginalRandomSeed);
169
+ if (HyperRandom.CurrentSeed == originalSeed)
170
+ {
171
+ // Preserve references held by game code while rewinding every stream.
172
+ HyperRandom.ResetAll();
173
+ return;
174
+ }
175
+
176
+ HyperRandom.Init(ReplaySessionState.OriginalRandomSeed);
177
+ }
178
+
117
179
  /// <summary>
118
180
  /// Coroutine-based restart that properly unloads all scenes before reloading.
119
181
  /// Handles async scene operations and ensures clean state before restart.
@@ -302,6 +364,9 @@ namespace Hyper.Internal
302
364
  _gameManager = _singletonObject.AddComponent<GameManager>();
303
365
  _gameManager.Initialize();
304
366
 
367
+ _replayManager = new ReplayManager();
368
+ _replayManager.Initialize(_eventManager);
369
+
305
370
  _timerManager = new TimerManager();
306
371
  _timerManager.Initialize();
307
372
  _timerManager.InjectEventHooks();
@@ -313,6 +378,13 @@ namespace Hyper.Internal
313
378
  _timerManager?.Update();
314
379
  }
315
380
 
381
+ internal static void FixedTick()
382
+ {
383
+ if (!_isInitialized) return;
384
+ _replayManager?.FixedTick();
385
+ _timerManager?.FixedTick();
386
+ }
387
+
316
388
  internal static void Clear()
317
389
  {
318
390
  if (_host == null)
@@ -323,6 +395,9 @@ namespace Hyper.Internal
323
395
 
324
396
  _timerManager = null;
325
397
 
398
+ _replayManager?.Cleanup();
399
+ _replayManager = null;
400
+
326
401
  _dataManager?.Cleanup();
327
402
  _dataManager = null;
328
403
 
@@ -364,6 +439,11 @@ namespace Hyper.Internal
364
439
  {
365
440
  if (obj.scene.name == "DontDestroyOnLoad" && obj.transform.parent == null)
366
441
  {
442
+ if (ReplaySessionState.IsSeeking && ReplayPresentationCanvas.IsPresentationRoot(obj))
443
+ {
444
+ continue;
445
+ }
446
+
367
447
  UnityEngine.Object.Destroy(obj);
368
448
  }
369
449
  }
@@ -372,6 +452,7 @@ namespace Hyper.Internal
372
452
  internal static void OnHostDestroyed(HyperRuntimeHost host)
373
453
  {
374
454
  if (_host != host) return;
455
+ _replayManager?.Cleanup();
375
456
  ResetStaticState();
376
457
  }
377
458
 
@@ -385,6 +466,7 @@ namespace Hyper.Internal
385
466
  _gameManager = null;
386
467
  _backendManager = null;
387
468
  _dataManager = null;
469
+ _replayManager = null;
388
470
  _gameContent = null;
389
471
  _isInitialized = false;
390
472
  _customLoadingComplete = false;
@@ -6,6 +6,7 @@ namespace Hyper.Internal
6
6
  /// Lightweight MonoBehaviour that allows the internal runtime to participate in
7
7
  /// Unity's update loop without exposing managed objects to public assemblies.
8
8
  /// </summary>
9
+ [DefaultExecutionOrder(-10000)]
9
10
  internal sealed class HyperRuntimeHost : MonoBehaviour
10
11
  {
11
12
  private void Awake()
@@ -18,6 +19,11 @@ namespace Hyper.Internal
18
19
  HyperRuntime.Tick();
19
20
  }
20
21
 
22
+ private void FixedUpdate()
23
+ {
24
+ HyperRuntime.FixedTick();
25
+ }
26
+
21
27
  private void OnDestroy()
22
28
  {
23
29
  HyperRuntime.OnHostDestroyed(this);
@@ -27,6 +27,11 @@ namespace Hyper.Internal
27
27
  /// </summary>
28
28
  public bool isPVP => GameMode == GameMode.Battle;
29
29
 
30
+ /// <summary>
31
+ /// Returns true for modes that use battle gameplay, scenes, and timers.
32
+ /// </summary>
33
+ public bool IsBattleGameplay => GameMode == GameMode.Battle || GameMode == GameMode.Replay;
34
+
30
35
  #endregion
31
36
 
32
37
  #region Internal Fields
@@ -114,5 +119,17 @@ namespace Hyper.Internal
114
119
  {
115
120
  GameMode = GameMode.Practice;
116
121
  }
122
+
123
+ public void SetModeAsReplay()
124
+ {
125
+ GameMode = GameMode.Replay;
126
+ }
127
+
128
+ internal void SetModeFromLaunchFlags(bool isPVP, bool isReplay)
129
+ {
130
+ GameMode = isReplay
131
+ ? GameMode.Replay
132
+ : isPVP ? GameMode.Battle : GameMode.Practice;
133
+ }
117
134
  }
118
- }
135
+ }