app.hypergames.hypersdk 1.11.0-beta.8 → 1.11.0

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 (42) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/Editor/Scripts/Inspectors/HyperSDKSettingsEditor.cs +15 -0
  3. package/HyperLoader.unity +2 -2
  4. package/Runtime/Internal/Bootstrap/HyperLoader.cs +5 -1
  5. package/Runtime/Internal/Core/HyperRuntime.cs +63 -1
  6. package/Runtime/Internal/Managers/BackendManager.cs +26 -3
  7. package/Runtime/Internal/Managers/DataManager.cs +8 -0
  8. package/Runtime/Internal/Managers/EventManager.cs +7 -1
  9. package/Runtime/Internal/Managers/GameManager.cs +1 -1
  10. package/Runtime/Internal/Managers/ReplayManager.cs +4 -1
  11. package/Runtime/Internal/Managers/TimeManager.cs +21 -6
  12. package/Runtime/Internal/Replay/ReplaySessionState.cs +15 -4
  13. package/Runtime/Internal/ScriptableObjects/HyperSDKSettings.cs +10 -0
  14. package/Runtime/Internal/UI/Components/ReplayController.cs +205 -27
  15. package/Runtime/Internal/UI/Components/ReplayPresentationCanvas.cs +1 -1
  16. package/Runtime/Internal/UI/Components/ReplaySeekOverlay.cs +18 -0
  17. package/Runtime/Internal/UI/Components/ReplaySeekOverlay.cs.meta +2 -0
  18. package/Runtime/Internal/UI/Modals/ReplayPausedModal.cs +12 -1
  19. package/Runtime/Internal/UI/Modals/StartingInstructionModal.cs +1 -1
  20. package/Runtime/Public/Replay/ReplayPlaybackEngine.cs +71 -2
  21. package/Runtime/Resources/Fonts/Wonder Boys/Wonder Boys.asset +394 -379
  22. package/Runtime/Resources/HyperSDKSettings.asset +2 -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/UIPrefabs/ReplayController.prefab +295 -15
  33. package/Runtime/Resources/UIPrefabs/ReplayPausedModal.prefab +149 -120
  34. package/Runtime/Resources/UIPrefabs/ReplaySeekOverlay.prefab +291 -6
  35. package/Samples~/LaneRunner/Prefabs/DoubleScorePickup.prefab +1 -1
  36. package/Samples~/LaneRunner/Prefabs/Obstacle.prefab +1 -1
  37. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerSpawner.cs +1 -1
  38. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerTrackItem.cs +9 -2
  39. package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerTournamentAdapter.cs +1 -0
  40. package/package.json +1 -1
  41. /package/Runtime/{Public → Shared}/PlayerDeviceClassification.cs +0 -0
  42. /package/Runtime/{Public → Shared}/PlayerDeviceClassification.cs.meta +0 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,191 @@
1
+ # [1.11.0](https://github.com/mvmhyper/hyper-sdk/compare/v1.10.0...v1.11.0) (2026-09-23)
2
+
3
+
4
+ ### Features
5
+
6
+ * **sdk:** promote replay support and stability improvements to stable ([#159](https://github.com/mvmhyper/hyper-sdk/issues/159)) ([8172b37](https://github.com/mvmhyper/hyper-sdk/commit/8172b376cc587d139a43c4f4b4204f5c29af7aef))
7
+
8
+ # [1.11.0-beta.33](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.32...v1.11.0-beta.33) (2026-09-11)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **ui:** preserve mobile landscape canvas scaling ([c8a3f27](https://github.com/mvmhyper/hyper-sdk/commit/c8a3f2793bddaf89971a33d8752546a502de80e4))
14
+
15
+ # [1.11.0-beta.32](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.31...v1.11.0-beta.32) (2026-09-11)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **ui:** set landscape canvas scaler match to 0.75 ([edc44eb](https://github.com/mvmhyper/hyper-sdk/commit/edc44ebcc229d68ed40eb60b05df3ed25c5053f8))
21
+
22
+ # [1.11.0-beta.31](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.30...v1.11.0-beta.31) (2026-09-09)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * prevent gameplay timers jumping after pause ([d9d9de4](https://github.com/mvmhyper/hyper-sdk/commit/d9d9de4b7c71a0493b379bfb3f8e7712785c1401))
28
+
29
+ # [1.11.0-beta.30](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.29...v1.11.0-beta.30) (2026-09-08)
30
+
31
+
32
+ ### Bug Fixes
33
+
34
+ * **replay:** improve seek slider handle usability ([10c4847](https://github.com/mvmhyper/hyper-sdk/commit/10c48474c6dff689903aae99bff40215152710f6))
35
+
36
+ # [1.11.0-beta.29](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.28...v1.11.0-beta.29) (2026-09-08)
37
+
38
+
39
+ ### Bug Fixes
40
+
41
+ * **replay:** simplify seek progress presentation ([16cf45a](https://github.com/mvmhyper/hyper-sdk/commit/16cf45afb3bf4fed8abc094ce2405237f0bf3b1d))
42
+
43
+ # [1.11.0-beta.28](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.27...v1.11.0-beta.28) (2026-09-08)
44
+
45
+
46
+ ### Features
47
+
48
+ * update replay playback speed options ([fc5f653](https://github.com/mvmhyper/hyper-sdk/commit/fc5f65350b31425c53a131b7c44e8780e1b0ce2d))
49
+
50
+ # [1.11.0-beta.27](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.26...v1.11.0-beta.27) (2026-09-08)
51
+
52
+
53
+ ### Features
54
+
55
+ * **replay:** increase seek reconstruction speed ([97ce729](https://github.com/mvmhyper/hyper-sdk/commit/97ce72978d64711e0463def42283704e5d880266))
56
+
57
+ # [1.11.0-beta.26](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.25...v1.11.0-beta.26) (2026-09-07)
58
+
59
+
60
+ ### Bug Fixes
61
+
62
+ * **sample:** refine replay integration template ([5a345df](https://github.com/mvmhyper/hyper-sdk/commit/5a345df87d19a6df11af4117fbeed1e21e8e9f9a))
63
+
64
+ # [1.11.0-beta.25](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.24...v1.11.0-beta.25) (2026-09-04)
65
+
66
+
67
+ ### Features
68
+
69
+ * **replay:** include final score metadata ([463dcad](https://github.com/mvmhyper/hyper-sdk/commit/463dcadd4df5e5684f3be328b0ec361f25f26f2a))
70
+
71
+ # [1.11.0-beta.24](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.23...v1.11.0-beta.24) (2026-09-04)
72
+
73
+
74
+ ### Bug Fixes
75
+
76
+ * **replay:** add 1.5x playback speed ([3f7875f](https://github.com/mvmhyper/hyper-sdk/commit/3f7875fcc4934ed01aaa521dce45327a177f67a6))
77
+
78
+ # [1.11.0-beta.23](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.22...v1.11.0-beta.23) (2026-09-03)
79
+
80
+
81
+ ### Bug Fixes
82
+
83
+ * **replay:** smooth read-only progress display ([ead4579](https://github.com/mvmhyper/hyper-sdk/commit/ead45798883b24e4b4354f3aab22400a1399e212))
84
+
85
+ # [1.11.0-beta.22](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.21...v1.11.0-beta.22) (2026-09-03)
86
+
87
+
88
+ ### Features
89
+
90
+ * **replay:** finalize playback controls and settings ([559a7e4](https://github.com/mvmhyper/hyper-sdk/commit/559a7e48d9998efa022fe87253645dc13f83e901))
91
+
92
+ # [1.11.0-beta.21](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.20...v1.11.0-beta.21) (2026-09-03)
93
+
94
+
95
+ ### Bug Fixes
96
+
97
+ * **replay:** align gameplay activities to fixed ticks ([1e681b9](https://github.com/mvmhyper/hyper-sdk/commit/1e681b9c7f314a48c417f6e76cb2d4024c890ad3))
98
+
99
+ # [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)
100
+
101
+
102
+ ### Bug Fixes
103
+
104
+ * **replay:** separate replay restart configuration ([de01d37](https://github.com/mvmhyper/hyper-sdk/commit/de01d37027fdde5d4f8803fa10e060d48a080316))
105
+
106
+ # [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)
107
+
108
+
109
+ ### Bug Fixes
110
+
111
+ * **replay:** stop seeking at requested progress ([54bf74a](https://github.com/mvmhyper/hyper-sdk/commit/54bf74a4b66b7c19e1398acd611a7d9557ccbcf3))
112
+
113
+ # [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)
114
+
115
+
116
+ ### Features
117
+
118
+ * **replay:** support custom replay restarts ([bf722ab](https://github.com/mvmhyper/hyper-sdk/commit/bf722abe48add18c356922c60231cb2017e28443))
119
+
120
+ # [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)
121
+
122
+
123
+ ### Features
124
+
125
+ * **replay:** refine seek loading progress ([3d01f94](https://github.com/mvmhyper/hyper-sdk/commit/3d01f94d7e6ce360a61dd3d257149cf7f3237bcb))
126
+
127
+ # [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)
128
+
129
+
130
+ ### Bug Fixes
131
+
132
+ * **replay:** show seek preparation progress ([60213f7](https://github.com/mvmhyper/hyper-sdk/commit/60213f784c5bff39da4634590545fe4d0e4c0911))
133
+
134
+ # [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)
135
+
136
+
137
+ ### Features
138
+
139
+ * **replay:** show seek completion progress ([4e7c1b8](https://github.com/mvmhyper/hyper-sdk/commit/4e7c1b807bd9f34b97477ef39aa9ca3c06316ef3))
140
+
141
+ # [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)
142
+
143
+
144
+ ### Bug Fixes
145
+
146
+ * **replay:** keep seek loader animating ([7e13938](https://github.com/mvmhyper/hyper-sdk/commit/7e13938ce718a238506c08c30744ff24ed7fa710))
147
+
148
+ # [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)
149
+
150
+
151
+ ### Bug Fixes
152
+
153
+ * **replay:** reduce default seek speed to 10x ([ccaf5b9](https://github.com/mvmhyper/hyper-sdk/commit/ccaf5b9807fd9439627072518317ad7f079e544c))
154
+
155
+ # [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)
156
+
157
+
158
+ ### Bug Fixes
159
+
160
+ * **replay:** reduce default seek speed ([d757710](https://github.com/mvmhyper/hyper-sdk/commit/d757710d107a0a496c9d7daa1434004a682bd355))
161
+
162
+ # [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)
163
+
164
+
165
+ ### Bug Fixes
166
+
167
+ * **replay:** apply 30x seek speed on beta ([564769e](https://github.com/mvmhyper/hyper-sdk/commit/564769e4764f27fd301f0e1dc52a4434635f93b7))
168
+ * **replay:** reduce default seek speed ([2cd0a34](https://github.com/mvmhyper/hyper-sdk/commit/2cd0a34c6390725d1c57fc3fa5f691f618c45083))
169
+
170
+
171
+ ### Features
172
+
173
+ * **replay:** increase default seek speed ([d1a5a46](https://github.com/mvmhyper/hyper-sdk/commit/d1a5a464c1e3e149255bf0d2d183439e9c29cfb3))
174
+
175
+ # [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)
176
+
177
+
178
+ ### Bug Fixes
179
+
180
+ * **replay:** reduce default seek speed ([f70551a](https://github.com/mvmhyper/hyper-sdk/commit/f70551aea942274ace1cb952ddae4e9edb0c1c12))
181
+
182
+ # [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)
183
+
184
+
185
+ ### Bug Fixes
186
+
187
+ * **replay:** show loader while exiting paused replay ([4a2e7a6](https://github.com/mvmhyper/hyper-sdk/commit/4a2e7a6045017ea8ed3ae3525271440dbf013e6c))
188
+
1
189
  # [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)
2
190
 
3
191
 
@@ -170,6 +170,21 @@ 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
+ "Replay Custom Restart",
176
+ "Use game-owned restart logic for replay Watch Again and seeking. " +
177
+ "Disabled uses HyperLoader to rebuild deterministic game state."),
178
+ settings.useCustomReplayRestartFlow);
179
+
180
+ EditorGUILayout.Space(12);
181
+ EditorGUILayout.LabelField("Replay Settings", EditorStyles.boldLabel);
182
+ settings.allowReplaySeeking = EditorGUILayout.Toggle(
183
+ new GUIContent(
184
+ "Allow Replay Seeking",
185
+ "Allow viewers to seek using the replay progress bar. " +
186
+ "Leave disabled until accelerated replay reconstruction is validated for this game."),
187
+ settings.allowReplaySeeking);
173
188
 
174
189
  EditorGUILayout.Space(12);
175
190
  // 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
@@ -448,7 +448,11 @@ namespace Hyper.Internal
448
448
  return false;
449
449
  }
450
450
 
451
- ReplaySessionState.SetMetadata(replay.PlayerName, replay.AvatarUrl, replay.Seed);
451
+ ReplaySessionState.SetMetadata(
452
+ replay.PlayerName,
453
+ replay.AvatarUrl,
454
+ replay.Seed,
455
+ replay.FinalScore);
452
456
  ReplaySessionState.SetReplayData(replay.ActivityNdjson);
453
457
  HyperDebug.Log($"Replay loaded from {path}");
454
458
  return true;
@@ -15,6 +15,8 @@ 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
 
@@ -22,6 +24,7 @@ namespace Hyper.Internal
22
24
  private static bool _isSoftRestarting;
23
25
  private static bool _replayRandomResetPending;
24
26
  private static bool _replayCustomRestartPending;
27
+ private static int _replayCustomRestartVersion;
25
28
  private static bool _customLoadingComplete;
26
29
  private static bool _customLoadingActive;
27
30
  private static float _customLoadingProgress;
@@ -123,7 +126,8 @@ namespace Hyper.Internal
123
126
  }
124
127
 
125
128
  /// <summary>
126
- /// Restarts replay playback through HyperLoader while preserving replay session data.
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.
127
131
  /// </summary>
128
132
  internal static void RestartReplaySession()
129
133
  {
@@ -147,8 +151,64 @@ namespace Hyper.Internal
147
151
 
148
152
  _replayCustomRestartPending = false;
149
153
  _replayRandomResetPending = false;
154
+ int restartVersion = ++_replayCustomRestartVersion;
150
155
  _replayManager?.PrepareForRestart();
151
156
 
157
+ if (_settings != null && _settings.useCustomReplayRestartFlow &&
158
+ _host != null && _gameManager != null && _eventManager != null)
159
+ {
160
+ _replayCustomRestartPending = true;
161
+ _replayRandomResetPending = true;
162
+ _gameManager.ResetUIStateForCustomRestart();
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
+ }
184
+ }
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;
152
212
  if (_host != null)
153
213
  {
154
214
  _host.StartCoroutine(RestartSessionCoroutine());
@@ -443,6 +503,7 @@ namespace Hyper.Internal
443
503
  _customLoadingProgress = 0f;
444
504
  _replayRandomResetPending = false;
445
505
  _replayCustomRestartPending = false;
506
+ _replayCustomRestartVersion++;
446
507
  _fixedSimulationTimeSeconds = 0d;
447
508
 
448
509
  if (_singletonObject != null)
@@ -504,6 +565,7 @@ namespace Hyper.Internal
504
565
  _customLoadingProgress = 0f;
505
566
  _replayRandomResetPending = false;
506
567
  _replayCustomRestartPending = false;
568
+ _replayCustomRestartVersion++;
507
569
  _fixedSimulationTimeSeconds = 0d;
508
570
  }
509
571
  }
@@ -179,7 +179,8 @@ namespace Hyper.Internal.Managers
179
179
  ReplaySessionState.SetMetadata(
180
180
  replay.PlayerName,
181
181
  replay.AvatarUrl,
182
- replay.Seed);
182
+ replay.Seed,
183
+ replay.FinalScore);
183
184
  ReplaySessionState.SetReplayData(replay.ActivityNdjson);
184
185
  HyperDebug.Log($"Replay downloaded for entry {entryId}.");
185
186
  onSuccess?.Invoke();
@@ -289,9 +290,10 @@ namespace Hyper.Internal.Managers
289
290
  string seedLine = reader.ReadLine();
290
291
  string nameLine = reader.ReadLine();
291
292
  string avatarLine = reader.ReadLine();
293
+ string scoreLine = reader.ReadLine();
292
294
  string activities = reader.ReadToEnd();
293
295
 
294
- if (seedLine == null || nameLine == null || avatarLine == null)
296
+ if (seedLine == null || nameLine == null || avatarLine == null || scoreLine == null)
295
297
  {
296
298
  error = "The replay response is missing required metadata.";
297
299
  return false;
@@ -302,6 +304,7 @@ namespace Hyper.Internal.Managers
302
304
  JArray seedData = JArray.Parse(seedLine);
303
305
  JArray nameData = JArray.Parse(nameLine);
304
306
  JArray avatarData = JArray.Parse(avatarLine);
307
+ JArray scoreData = JArray.Parse(scoreLine);
305
308
 
306
309
  if (seedData.Count != 1 ||
307
310
  seedData[0].Type != JTokenType.Integer ||
@@ -327,6 +330,18 @@ namespace Hyper.Internal.Managers
327
330
  return false;
328
331
  }
329
332
 
333
+ if (scoreData.Count != 1 ||
334
+ scoreData[0].Type != JTokenType.Integer ||
335
+ !int.TryParse(
336
+ scoreData[0].ToString(),
337
+ NumberStyles.Integer,
338
+ CultureInfo.InvariantCulture,
339
+ out int finalScore))
340
+ {
341
+ error = "Replay metadata contains an invalid final score.";
342
+ return false;
343
+ }
344
+
330
345
  if (string.IsNullOrWhiteSpace(activities))
331
346
  {
332
347
  error = "The replay response contains no gameplay activities.";
@@ -337,6 +352,7 @@ namespace Hyper.Internal.Managers
337
352
  seed,
338
353
  nameData[0].Value<string>() ?? string.Empty,
339
354
  avatarData[0].Value<string>() ?? string.Empty,
355
+ finalScore,
340
356
  activities);
341
357
 
342
358
  return true;
@@ -354,13 +370,20 @@ namespace Hyper.Internal.Managers
354
370
  internal long Seed { get; }
355
371
  internal string PlayerName { get; }
356
372
  internal string AvatarUrl { get; }
373
+ internal int FinalScore { get; }
357
374
  internal string ActivityNdjson { get; }
358
375
 
359
- internal ReplayResponse(long seed, string playerName, string avatarUrl, string activityNdjson)
376
+ internal ReplayResponse(
377
+ long seed,
378
+ string playerName,
379
+ string avatarUrl,
380
+ int finalScore,
381
+ string activityNdjson)
360
382
  {
361
383
  Seed = seed;
362
384
  PlayerName = playerName;
363
385
  AvatarUrl = avatarUrl;
386
+ FinalScore = finalScore;
364
387
  ActivityNdjson = activityNdjson;
365
388
  }
366
389
  }
@@ -375,6 +375,7 @@ namespace Hyper.Internal.Managers
375
375
  new object[] { HyperRandom.CurrentSeed }));
376
376
  _editorReplay.AppendLine(JsonConvert.SerializeObject(new object[] { "hypergamer" }));
377
377
  _editorReplay.AppendLine(JsonConvert.SerializeObject(new object[] { string.Empty }));
378
+ _editorReplayMetadataLength = _editorReplay.Length;
378
379
  #endif
379
380
  _replayRecorder?.StartSession();
380
381
  }
@@ -426,6 +427,7 @@ namespace Hyper.Internal.Managers
426
427
 
427
428
  #if UNITY_EDITOR
428
429
  private readonly StringBuilder _editorReplay = new StringBuilder(4096);
430
+ private int _editorReplayMetadataLength;
429
431
 
430
432
  private void CaptureEditorReplayActivity(object[] activity)
431
433
  {
@@ -434,6 +436,8 @@ namespace Hyper.Internal.Managers
434
436
 
435
437
  private void SaveEditorReplay()
436
438
  {
439
+ string finalScoreLine = JsonConvert.SerializeObject(new object[] { CurrentScore }) + Environment.NewLine;
440
+ _editorReplay.Insert(_editorReplayMetadataLength, finalScoreLine);
437
441
  try
438
442
  {
439
443
  File.WriteAllText(ReplaySessionState.EditorReplayFilePath, _editorReplay.ToString());
@@ -443,6 +447,10 @@ namespace Hyper.Internal.Managers
443
447
  {
444
448
  HyperDebug.LogWarning($"Unable to save Editor replay: {exception.Message}");
445
449
  }
450
+ finally
451
+ {
452
+ _editorReplay.Remove(_editorReplayMetadataLength, finalScoreLine.Length);
453
+ }
446
454
  }
447
455
  #endif
448
456
 
@@ -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();
@@ -1334,7 +1334,7 @@ namespace Hyper.Internal.Managers
1334
1334
  _canvasScaler = _canvas.GetComponent<CanvasScaler>();
1335
1335
  _canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
1336
1336
  _canvasScaler.referenceResolution = new Vector2(1080, 2160);
1337
- _canvasScaler.matchWidthOrHeight = IsPortrait() ? 1f : 0.593f;
1337
+ _canvasScaler.matchWidthOrHeight = IsPortrait() ? 1f : (PlayerDeviceClassification.PlayerDeviceIsPC ? 0.75f : 0.593f);
1338
1338
 
1339
1339
  CreateHyperCanvasSafeArea();
1340
1340
  CreateHyperCanvasBlockerUI();
@@ -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()
@@ -103,14 +103,28 @@ namespace Hyper.Internal.Managers
103
103
  if (Mathf.Approximately(Time.timeScale, 0f))
104
104
  return;
105
105
 
106
- float gameTimerDelta = _gameManager != null && _gameManager.IsReplayMode
107
- ? Time.fixedDeltaTime
108
- : Time.fixedUnscaledDeltaTime;
106
+ float gameTimerDelta = GetGameTimerDelta(
107
+ _gameManager != null && _gameManager.IsReplayMode,
108
+ Time.fixedDeltaTime,
109
+ Time.timeScale);
109
110
 
110
111
  UpdatePracticeTimer(gameTimerDelta);
111
112
  UpdateBattleTimer(gameTimerDelta);
112
113
  }
113
114
 
115
+ internal static float GetGameTimerDelta(
116
+ bool isReplayMode,
117
+ float fixedDeltaTime,
118
+ float timeScale)
119
+ {
120
+ if (timeScale <= 0f) return 0f;
121
+
122
+ // The first FixedUpdate after a pause can report the whole paused
123
+ // interval in fixedUnscaledDeltaTime. Derive one live simulation
124
+ // step instead, keeping real-time duration at custom time scales.
125
+ return isReplayMode ? fixedDeltaTime : fixedDeltaTime / timeScale;
126
+ }
127
+
114
128
  public void Reset()
115
129
  {
116
130
  _practiceTimer = 0;
@@ -299,9 +313,9 @@ namespace Hyper.Internal.Managers
299
313
  }
300
314
 
301
315
  _battleTimer -= delta;
302
- if (_battleTimer <= 0)
316
+ if (_battleTimer <= 0f || Mathf.Approximately(_battleTimer, 0f))
303
317
  {
304
- _battleTimer = 0;
318
+ _battleTimer = 0f;
305
319
  if (!_timeUpTriggered)
306
320
  {
307
321
  QueueTimeUp();
@@ -314,7 +328,8 @@ namespace Hyper.Internal.Managers
314
328
  {
315
329
  _battleTimer = _sessionTimer;
316
330
 
317
- if (_battleTimer <= 0 && !_timeUpTriggered)
331
+ if ((_battleTimer <= 0f || Mathf.Approximately(_battleTimer, 0f)) &&
332
+ !_timeUpTriggered)
318
333
  {
319
334
  QueueTimeUp();
320
335
  }
@@ -14,6 +14,7 @@ namespace Hyper.Internal
14
14
  public static string AvatarUrl { get; private set; }
15
15
  public static string EntryId { get; private set; }
16
16
  public static string ReplayNdjson { get; private set; }
17
+ public static int? FinalScore { get; private set; }
17
18
  public static Texture2D AvatarTexture { get; private set; }
18
19
  public static bool HasReplayData => !string.IsNullOrWhiteSpace(ReplayNdjson);
19
20
  public static long OriginalRandomSeed { get; private set; }
@@ -48,6 +49,7 @@ namespace Hyper.Internal
48
49
  AvatarUrl = avatarUrl ?? string.Empty;
49
50
  EntryId = entryId ?? string.Empty;
50
51
  ReplayNdjson = string.Empty;
52
+ FinalScore = null;
51
53
  ReleaseAvatarTexture();
52
54
  OriginalRandomSeed = originalRandomSeed;
53
55
  Progress01 = 0f;
@@ -61,13 +63,18 @@ namespace Hyper.Internal
61
63
  GameplayStartProgress01 = 0f;
62
64
  }
63
65
 
64
- public static void SetMetadata(string playerName, string avatarUrl, long originalRandomSeed)
66
+ public static void SetMetadata(
67
+ string playerName,
68
+ string avatarUrl,
69
+ long originalRandomSeed,
70
+ int finalScore)
65
71
  {
66
72
  if (!IsActive) return;
67
73
 
68
74
  PlayerName = playerName ?? string.Empty;
69
75
  AvatarUrl = avatarUrl ?? string.Empty;
70
76
  OriginalRandomSeed = originalRandomSeed;
77
+ FinalScore = finalScore;
71
78
  }
72
79
 
73
80
  public static void SetReplayData(string ndjson)
@@ -83,10 +90,10 @@ namespace Hyper.Internal
83
90
  AvatarTexture = texture;
84
91
  }
85
92
 
86
- public static void SetProgress(float progress)
93
+ public static bool SetProgress(float progress)
87
94
  {
88
- if (!IsActive) return;
89
- if (IsSeeking && !_seekPlaybackReady) return;
95
+ if (!IsActive) return false;
96
+ if (IsSeeking && !_seekPlaybackReady) return false;
90
97
  float rawProgress = Math.Max(0f, Math.Min(1f, progress));
91
98
  Progress01 = GameplayStartProgress01 >= 1f
92
99
  ? 0f
@@ -101,7 +108,10 @@ namespace Hyper.Internal
101
108
  PlaybackSpeed = UserPlaybackSpeed;
102
109
  RestoreAudioAfterSeek();
103
110
  SeekCompleted?.Invoke(PlaybackSpeed);
111
+ return true;
104
112
  }
113
+
114
+ return false;
105
115
  }
106
116
 
107
117
  public static void SetPlaybackSpeed(float speed)
@@ -190,6 +200,7 @@ namespace Hyper.Internal
190
200
  AvatarUrl = string.Empty;
191
201
  EntryId = string.Empty;
192
202
  ReplayNdjson = string.Empty;
203
+ FinalScore = null;
193
204
  ReleaseAvatarTexture();
194
205
  OriginalRandomSeed = 0;
195
206
  Progress01 = 0f;
@@ -76,6 +76,16 @@ namespace Hyper.Internal
76
76
  "instead of using default restart. Listen to HyperSDK.Event.OnCustomRestart in your game manager.")]
77
77
  public bool useCustomRestartFlow = false;
78
78
 
79
+ [Tooltip("Use the game-owned custom restart flow for replay Watch Again and seeking. " +
80
+ "Leave disabled to rebuild the game through HyperLoader for deterministic replay state.")]
81
+ public bool useCustomReplayRestartFlow = false;
82
+
83
+ [Space(20)]
84
+ [Header("Replay Settings")]
85
+ [Tooltip("Allow replay viewers to seek from the replay progress bar. " +
86
+ "Leave disabled until the game has been validated for accelerated replay reconstruction.")]
87
+ public bool allowReplaySeeking = false;
88
+
79
89
  #endregion
80
90
 
81
91
  #region Editor Testing