app.hypergames.hypersdk 1.11.0-beta.6 → 1.11.0-beta.8
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 +14 -0
- package/Runtime/Internal/Core/HyperRuntime.cs +6 -13
- package/Runtime/Internal/Managers/DataManager.cs +1 -0
- package/Runtime/Internal/Managers/ReplayManager.cs +1 -0
- package/Runtime/Internal/Replay/ReplaySessionState.cs +48 -3
- package/Runtime/Public/Core/HyperSDK.cs +8 -0
- package/Runtime/Public/Replay/ReplayPlaybackEngine.cs +56 -1
- package/Runtime/Shared/Replay/IReplayPlaybackEngine.cs +3 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [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
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **replay:** mute audio while seeking ([5ab8cd1](https://github.com/mvmhyper/hyper-sdk/commit/5ab8cd1411493cb5add07b51dc190c85251f99d2))
|
|
7
|
+
|
|
8
|
+
# [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)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **replay:** stabilize scene reload seeking ([dd9ac63](https://github.com/mvmhyper/hyper-sdk/commit/dd9ac632d08349d9578f64ae0b2dc2ab905a26af))
|
|
14
|
+
|
|
1
15
|
# [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)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -123,8 +123,7 @@ namespace Hyper.Internal
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/// <summary>
|
|
126
|
-
/// Restarts replay playback while preserving
|
|
127
|
-
/// Uses the configured custom restart flow when available, otherwise reloads HyperLoader.
|
|
126
|
+
/// Restarts replay playback through HyperLoader while preserving replay session data.
|
|
128
127
|
/// </summary>
|
|
129
128
|
internal static void RestartReplaySession()
|
|
130
129
|
{
|
|
@@ -146,17 +145,9 @@ namespace Hyper.Internal
|
|
|
146
145
|
_isSoftRestarting = true;
|
|
147
146
|
Time.timeScale = 1f;
|
|
148
147
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
_replayCustomRestartPending = true;
|
|
153
|
-
_replayManager?.PrepareForRestart();
|
|
154
|
-
// Custom cleanup may use random streams. Rewind after cleanup, immediately before OnGameStart.
|
|
155
|
-
DeferReplayRandomReset();
|
|
156
|
-
_gameManager.ResetUIStateForCustomRestart();
|
|
157
|
-
_eventManager.InvokeCustomRestartEvent();
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
148
|
+
_replayCustomRestartPending = false;
|
|
149
|
+
_replayRandomResetPending = false;
|
|
150
|
+
_replayManager?.PrepareForRestart();
|
|
160
151
|
|
|
161
152
|
if (_host != null)
|
|
162
153
|
{
|
|
@@ -218,6 +209,7 @@ namespace Hyper.Internal
|
|
|
218
209
|
{
|
|
219
210
|
yield return UnloadAllScenesCoroutine();
|
|
220
211
|
|
|
212
|
+
Clear();
|
|
221
213
|
ClearAllDontDestroyOnLoadObjects();
|
|
222
214
|
SceneManager.LoadScene("HyperLoader");
|
|
223
215
|
}
|
|
@@ -451,6 +443,7 @@ namespace Hyper.Internal
|
|
|
451
443
|
_customLoadingProgress = 0f;
|
|
452
444
|
_replayRandomResetPending = false;
|
|
453
445
|
_replayCustomRestartPending = false;
|
|
446
|
+
_fixedSimulationTimeSeconds = 0d;
|
|
454
447
|
|
|
455
448
|
if (_singletonObject != null)
|
|
456
449
|
{
|
|
@@ -24,6 +24,7 @@ namespace Hyper.Internal
|
|
|
24
24
|
public static bool IsSeekRestartPending => IsSeeking && !_seekPlaybackReady;
|
|
25
25
|
public static float SeekTarget01 { get; private set; }
|
|
26
26
|
public static bool HasEnded { get; private set; }
|
|
27
|
+
public static float GameplayStartProgress01 { get; private set; }
|
|
27
28
|
|
|
28
29
|
internal static string EditorReplayFilePath =>
|
|
29
30
|
Path.Combine(Application.persistentDataPath, "hyper-replay.ndjson");
|
|
@@ -32,6 +33,8 @@ namespace Hyper.Internal
|
|
|
32
33
|
|
|
33
34
|
private static bool _seekPlaybackReady;
|
|
34
35
|
private static bool _watchAgainAutoStartPending;
|
|
36
|
+
private static bool _isSeekAudioMuted;
|
|
37
|
+
private static float _volumeBeforeSeek;
|
|
35
38
|
|
|
36
39
|
public static void Configure(
|
|
37
40
|
string playerName,
|
|
@@ -39,6 +42,7 @@ namespace Hyper.Internal
|
|
|
39
42
|
long originalRandomSeed,
|
|
40
43
|
string entryId = null)
|
|
41
44
|
{
|
|
45
|
+
RestoreAudioAfterSeek();
|
|
42
46
|
IsActive = true;
|
|
43
47
|
PlayerName = playerName ?? string.Empty;
|
|
44
48
|
AvatarUrl = avatarUrl ?? string.Empty;
|
|
@@ -54,6 +58,7 @@ namespace Hyper.Internal
|
|
|
54
58
|
_seekPlaybackReady = false;
|
|
55
59
|
_watchAgainAutoStartPending = false;
|
|
56
60
|
HasEnded = false;
|
|
61
|
+
GameplayStartProgress01 = 0f;
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
public static void SetMetadata(string playerName, string avatarUrl, long originalRandomSeed)
|
|
@@ -82,13 +87,19 @@ namespace Hyper.Internal
|
|
|
82
87
|
{
|
|
83
88
|
if (!IsActive) return;
|
|
84
89
|
if (IsSeeking && !_seekPlaybackReady) return;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
float rawProgress = Math.Max(0f, Math.Min(1f, progress));
|
|
91
|
+
Progress01 = GameplayStartProgress01 >= 1f
|
|
92
|
+
? 0f
|
|
93
|
+
: Math.Max(0f, Math.Min(1f,
|
|
94
|
+
(rawProgress - GameplayStartProgress01) / (1f - GameplayStartProgress01)));
|
|
95
|
+
|
|
96
|
+
bool hasReachedGameplayStart = rawProgress + 0.000001f >= GameplayStartProgress01;
|
|
97
|
+
if (IsSeeking && hasReachedGameplayStart && Progress01 >= SeekTarget01)
|
|
88
98
|
{
|
|
89
99
|
IsSeeking = false;
|
|
90
100
|
_seekPlaybackReady = false;
|
|
91
101
|
PlaybackSpeed = UserPlaybackSpeed;
|
|
102
|
+
RestoreAudioAfterSeek();
|
|
92
103
|
SeekCompleted?.Invoke(PlaybackSpeed);
|
|
93
104
|
}
|
|
94
105
|
}
|
|
@@ -107,6 +118,7 @@ namespace Hyper.Internal
|
|
|
107
118
|
Progress01 = 1f;
|
|
108
119
|
if (IsSeeking) PlaybackSpeed = UserPlaybackSpeed;
|
|
109
120
|
IsSeeking = false;
|
|
121
|
+
RestoreAudioAfterSeek();
|
|
110
122
|
HasEnded = true;
|
|
111
123
|
}
|
|
112
124
|
|
|
@@ -119,6 +131,7 @@ namespace Hyper.Internal
|
|
|
119
131
|
PlaybackSpeed = SanitizeSpeed(seekSpeed);
|
|
120
132
|
IsSeeking = true;
|
|
121
133
|
_seekPlaybackReady = false;
|
|
134
|
+
MuteAudioForSeek();
|
|
122
135
|
}
|
|
123
136
|
|
|
124
137
|
public static void ResetForSeekRestart()
|
|
@@ -136,9 +149,16 @@ namespace Hyper.Internal
|
|
|
136
149
|
_seekPlaybackReady = true;
|
|
137
150
|
}
|
|
138
151
|
|
|
152
|
+
internal static void EnsureAudioMutedDuringSeek()
|
|
153
|
+
{
|
|
154
|
+
if (_isSeekAudioMuted)
|
|
155
|
+
AudioListener.volume = 0f;
|
|
156
|
+
}
|
|
157
|
+
|
|
139
158
|
public static void ResetForWatchAgain()
|
|
140
159
|
{
|
|
141
160
|
if (!IsActive) return;
|
|
161
|
+
RestoreAudioAfterSeek();
|
|
142
162
|
Progress01 = 0f;
|
|
143
163
|
PlaybackSpeed = 1f;
|
|
144
164
|
UserPlaybackSpeed = 1f;
|
|
@@ -149,6 +169,12 @@ namespace Hyper.Internal
|
|
|
149
169
|
HasEnded = false;
|
|
150
170
|
}
|
|
151
171
|
|
|
172
|
+
public static void SetGameplayStartProgress(float progress)
|
|
173
|
+
{
|
|
174
|
+
if (!IsActive) return;
|
|
175
|
+
GameplayStartProgress01 = Math.Max(0f, Math.Min(1f, progress));
|
|
176
|
+
}
|
|
177
|
+
|
|
152
178
|
public static bool ConsumeWatchAgainAutoStart()
|
|
153
179
|
{
|
|
154
180
|
bool pending = _watchAgainAutoStartPending;
|
|
@@ -158,6 +184,7 @@ namespace Hyper.Internal
|
|
|
158
184
|
|
|
159
185
|
public static void Clear()
|
|
160
186
|
{
|
|
187
|
+
RestoreAudioAfterSeek();
|
|
161
188
|
IsActive = false;
|
|
162
189
|
PlayerName = string.Empty;
|
|
163
190
|
AvatarUrl = string.Empty;
|
|
@@ -173,6 +200,7 @@ namespace Hyper.Internal
|
|
|
173
200
|
_seekPlaybackReady = false;
|
|
174
201
|
_watchAgainAutoStartPending = false;
|
|
175
202
|
HasEnded = false;
|
|
203
|
+
GameplayStartProgress01 = 0f;
|
|
176
204
|
SeekCompleted = null;
|
|
177
205
|
}
|
|
178
206
|
|
|
@@ -181,6 +209,23 @@ namespace Hyper.Internal
|
|
|
181
209
|
return float.IsNaN(speed) || float.IsInfinity(speed) || speed <= 0f ? 1f : speed;
|
|
182
210
|
}
|
|
183
211
|
|
|
212
|
+
private static void MuteAudioForSeek()
|
|
213
|
+
{
|
|
214
|
+
if (_isSeekAudioMuted) return;
|
|
215
|
+
|
|
216
|
+
_volumeBeforeSeek = AudioListener.volume;
|
|
217
|
+
_isSeekAudioMuted = true;
|
|
218
|
+
AudioListener.volume = 0f;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private static void RestoreAudioAfterSeek()
|
|
222
|
+
{
|
|
223
|
+
if (!_isSeekAudioMuted) return;
|
|
224
|
+
|
|
225
|
+
AudioListener.volume = _volumeBeforeSeek;
|
|
226
|
+
_isSeekAudioMuted = false;
|
|
227
|
+
}
|
|
228
|
+
|
|
184
229
|
private static void ReleaseAvatarTexture()
|
|
185
230
|
{
|
|
186
231
|
if (AvatarTexture != null)
|
|
@@ -486,10 +486,15 @@ namespace Hyper
|
|
|
486
486
|
/// <summary>Fired when all replay activities have been dispatched.</summary>
|
|
487
487
|
event Action OnEnded;
|
|
488
488
|
|
|
489
|
+
/// <summary>Fired at the recorded boundary where game-specific playable gameplay began.</summary>
|
|
490
|
+
event Action OnGameplayReady;
|
|
491
|
+
|
|
489
492
|
bool IsPlaying { get; }
|
|
490
493
|
double DurationSeconds { get; }
|
|
491
494
|
double ElapsedSeconds { get; }
|
|
492
495
|
float Progress01 { get; }
|
|
496
|
+
bool IsSeeking { get; }
|
|
497
|
+
bool HasGameplayReadyMarker { get; }
|
|
493
498
|
|
|
494
499
|
/// <summary>
|
|
495
500
|
/// Optional visual lead time. Gameplay actions still fire at their recorded time.
|
|
@@ -685,11 +690,14 @@ namespace Hyper
|
|
|
685
690
|
public event Action<float, string> OnValueAction { add => _playback.OnValueAction += value; remove => _playback.OnValueAction -= value; }
|
|
686
691
|
public event Action<string> OnActionPreparing { add => _playback.OnActionPreparing += value; remove => _playback.OnActionPreparing -= value; }
|
|
687
692
|
public event Action OnEnded { add => _playback.OnGameEnded += value; remove => _playback.OnGameEnded -= value; }
|
|
693
|
+
public event Action OnGameplayReady { add => _playback.OnGameplayReady += value; remove => _playback.OnGameplayReady -= value; }
|
|
688
694
|
|
|
689
695
|
public bool IsPlaying => _playback.IsPlaying;
|
|
690
696
|
public double DurationSeconds => _playback.DurationSeconds;
|
|
691
697
|
public double ElapsedSeconds => _playback.ElapsedSeconds;
|
|
692
698
|
public float Progress01 => _playback.Progress01;
|
|
699
|
+
public bool IsSeeking => ReplaySessionState.IsSeeking;
|
|
700
|
+
public bool HasGameplayReadyMarker => _playback.HasGameplayReadyMarker;
|
|
693
701
|
public double ActionPreparationLeadSeconds
|
|
694
702
|
{
|
|
695
703
|
get => _playback.ActionPreparationLeadSeconds;
|
|
@@ -11,6 +11,7 @@ namespace Hyper
|
|
|
11
11
|
internal sealed class ReplayPlaybackEngine : IReplayPlaybackEngine
|
|
12
12
|
{
|
|
13
13
|
private const string GameStarted = "GS";
|
|
14
|
+
private const string GameplayReady = "GPR";
|
|
14
15
|
private const string GamePaused = "GP";
|
|
15
16
|
private const string GameResumed = "GR";
|
|
16
17
|
private const string GameEnded = "GE";
|
|
@@ -45,6 +46,7 @@ namespace Hyper
|
|
|
45
46
|
/// </summary>
|
|
46
47
|
public event Action<string> OnActionPreparing;
|
|
47
48
|
public event Action OnGameStarted;
|
|
49
|
+
public event Action OnGameplayReady;
|
|
48
50
|
public event Action OnGameEnded;
|
|
49
51
|
|
|
50
52
|
public bool IsPlaying { get; private set; }
|
|
@@ -55,6 +57,8 @@ namespace Hyper
|
|
|
55
57
|
/// The action itself is still dispatched at its recorded timestamp.
|
|
56
58
|
/// </summary>
|
|
57
59
|
public double ActionPreparationLeadSeconds { get; set; }
|
|
60
|
+
public bool HasGameplayReadyMarker { get; private set; }
|
|
61
|
+
public float GameplayStartProgress01 { get; private set; }
|
|
58
62
|
public float Progress01 => DurationSeconds <= 0d
|
|
59
63
|
? (IsPlaying ? 0f : 1f)
|
|
60
64
|
: Mathf.Clamp01((float)(ElapsedSeconds / DurationSeconds));
|
|
@@ -73,6 +77,11 @@ namespace Hyper
|
|
|
73
77
|
_completionPending = false;
|
|
74
78
|
_nextActionPrepared = false;
|
|
75
79
|
DurationSeconds = CalculateDuration(_ndjson);
|
|
80
|
+
double gameplayStartSeconds = CalculateGameplayStart(_ndjson, out bool hasGameplayReadyMarker);
|
|
81
|
+
HasGameplayReadyMarker = hasGameplayReadyMarker;
|
|
82
|
+
GameplayStartProgress01 = hasGameplayReadyMarker && DurationSeconds > 0d
|
|
83
|
+
? Mathf.Clamp01((float)(gameplayStartSeconds / DurationSeconds))
|
|
84
|
+
: 0f;
|
|
76
85
|
_hasNext = TryReadNext(out _next);
|
|
77
86
|
|
|
78
87
|
if (!_hasNext)
|
|
@@ -214,6 +223,9 @@ namespace Hyper
|
|
|
214
223
|
case GameStarted:
|
|
215
224
|
OnGameStarted?.Invoke();
|
|
216
225
|
break;
|
|
226
|
+
case GameplayReady:
|
|
227
|
+
OnGameplayReady?.Invoke();
|
|
228
|
+
break;
|
|
217
229
|
case GamePaused:
|
|
218
230
|
SkipRecordedPause(activity.Time);
|
|
219
231
|
break;
|
|
@@ -246,7 +258,50 @@ namespace Hyper
|
|
|
246
258
|
private static bool IsLifecycleAction(string action)
|
|
247
259
|
{
|
|
248
260
|
return action == GameStarted || action == GamePaused ||
|
|
249
|
-
action == GameResumed || action == GameEnded;
|
|
261
|
+
action == GameResumed || action == GameEnded || action == GameplayReady;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private static double CalculateGameplayStart(string ndjson, out bool found)
|
|
265
|
+
{
|
|
266
|
+
found = false;
|
|
267
|
+
if (string.IsNullOrWhiteSpace(ndjson)) return 0d;
|
|
268
|
+
|
|
269
|
+
int cursor = 0;
|
|
270
|
+
bool hasFirst = false;
|
|
271
|
+
double firstTime = 0d;
|
|
272
|
+
double removedPauses = 0d;
|
|
273
|
+
double? pauseStartedAt = null;
|
|
274
|
+
|
|
275
|
+
while (TryReadLine(ndjson, ref cursor, out string line))
|
|
276
|
+
{
|
|
277
|
+
if (!TryParse(line, out ReplayActivity activity)) continue;
|
|
278
|
+
if (!hasFirst)
|
|
279
|
+
{
|
|
280
|
+
hasFirst = true;
|
|
281
|
+
firstTime = activity.Time;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (activity.Kind != ReplayActivityKind.Action) continue;
|
|
285
|
+
if (activity.Action == GamePaused)
|
|
286
|
+
{
|
|
287
|
+
pauseStartedAt ??= activity.Time;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (activity.Action == GameResumed && pauseStartedAt.HasValue)
|
|
292
|
+
{
|
|
293
|
+
removedPauses += Math.Max(0d, activity.Time - pauseStartedAt.Value);
|
|
294
|
+
pauseStartedAt = null;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (activity.Action != GameplayReady) continue;
|
|
299
|
+
|
|
300
|
+
found = true;
|
|
301
|
+
return Math.Max(0d, activity.Time - firstTime - removedPauses);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return 0d;
|
|
250
305
|
}
|
|
251
306
|
|
|
252
307
|
private void SkipRecordedPause(double pauseStartedAt)
|
|
@@ -19,12 +19,15 @@ namespace Hyper.Shared
|
|
|
19
19
|
event Action<float, string> OnValueAction;
|
|
20
20
|
event Action<string> OnActionPreparing;
|
|
21
21
|
event Action OnGameStarted;
|
|
22
|
+
event Action OnGameplayReady;
|
|
22
23
|
event Action OnGameEnded;
|
|
23
24
|
|
|
24
25
|
bool IsPlaying { get; }
|
|
25
26
|
double DurationSeconds { get; }
|
|
26
27
|
double ElapsedSeconds { get; }
|
|
27
28
|
float Progress01 { get; }
|
|
29
|
+
bool HasGameplayReadyMarker { get; }
|
|
30
|
+
float GameplayStartProgress01 { get; }
|
|
28
31
|
double ActionPreparationLeadSeconds { get; set; }
|
|
29
32
|
|
|
30
33
|
void Begin(string ndjson);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "app.hypergames.hypersdk",
|
|
3
|
-
"version": "1.11.0-beta.
|
|
3
|
+
"version": "1.11.0-beta.8",
|
|
4
4
|
"displayName": "HyperSDK",
|
|
5
5
|
"description": "Official Unity SDK for Hyper: multiplayer battles, leaderboards, deterministic RNG, session management, tutorials, and WebGL optimizations for production-ready games.",
|
|
6
6
|
"unity": "6000.0",
|