• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

orion-ecs / keen-eye / 30113831081

24 Jul 2026 05:39PM UTC coverage: 64.224% (-0.07%) from 64.291%
30113831081

push

github

tyevco
fix(audio): Fix non-looping replay, WAV validation, paused-source recycling, double master volume (#1185,#1186,#1188,#1189)

- #1185: AudioSourceSystem now restarts a stopped backend source when State is set back to Playing (distinguishes a replay request from a natural finish via CurrentSound validity), so non-looping sources are replayable per the AudioSource.State contract.
- #1186: WavDecoder validates each chunk size against the remaining bytes and throws AudioLoadException on negative/oversized sizes, preventing the ArgumentOutOfRangeException slice crash and the negative-size infinite loop on untrusted files.
- #1188: SourcePool.Update only recycles Stopped sources (Paused treated as still-active), so Pause/PauseAll no longer kills pooled one-shots on the next frame.
- #1189: SilkAudioContext applies master + Master-channel volume only via the listener gain; ComputeEffectiveVolume no longer re-multiplies them into the per-source gain, eliminating the squared master volume and the new-vs-playing inconsistency.

Adds KeenEyes.Audio.Tests (AudioSourceSystem replay/finish, via fake IAudioDevice/IAudioContext) and KeenEyes.Audio.Silk.Tests (WavDecoder malformed-input validation). #1188/#1189 are OpenAL-device-bound and verified by code trace.

Closes #1185
Closes #1186
Closes #1188
Closes #1189

8612 of 12731 branches covered (67.65%)

Branch coverage included in aggregate %.

10 of 15 new or added lines in 4 files covered. (66.67%)

368 existing lines in 26 files now uncovered.

51312 of 80574 relevant lines covered (63.68%)

0.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.1
/src/KeenEyes.Animation/Systems/TweenSystem.cs
1
using System.Numerics;
2
using KeenEyes.Animation.Components;
3
using KeenEyes.Animation.Data;
4
using KeenEyes.Animation.Tweening;
5

6
namespace KeenEyes.Animation.Systems;
7

8
/// <summary>
9
/// System that updates tween interpolation for all tween components.
10
/// </summary>
11
/// <remarks>
12
/// <para>
13
/// This system updates TweenFloat, TweenVector2, TweenVector3, and TweenVector4
14
/// components, advancing their elapsed time and computing the current interpolated value.
15
/// </para>
16
/// <para>
17
/// The interpolated values can be read from CurrentValue and applied to other
18
/// components by user systems.
19
/// </para>
20
/// </remarks>
21
public sealed class TweenSystem : SystemBase
22
{
23
    /// <inheritdoc />
24
    public override void Update(float deltaTime)
25
    {
26
        UpdateFloatTweens(deltaTime);
1✔
27
        UpdateVector2Tweens(deltaTime);
1✔
28
        UpdateVector3Tweens(deltaTime);
1✔
29
        UpdateVector4Tweens(deltaTime);
1✔
30
    }
1✔
31

32
    private void UpdateFloatTweens(float deltaTime)
33
    {
34
        foreach (var entity in World.Query<TweenFloat>())
1✔
35
        {
36
            ref var tween = ref World.Get<TweenFloat>(entity);
1✔
37

38
            if (!tween.IsPlaying || tween.IsComplete)
1✔
39
            {
40
                continue;
41
            }
42

43
            tween.ElapsedTime += deltaTime;
1✔
44

45
            var t = ComputeProgress(tween.ElapsedTime, tween.Duration, tween.Loop, tween.PingPong, out var complete);
1✔
46
            tween.IsComplete = complete;
1✔
47

48
            if (complete && !tween.Loop)
1✔
49
            {
50
                tween.IsPlaying = false;
1✔
51
            }
52

53
            var easedT = Easing.Evaluate(tween.EaseType, t);
1✔
54
            tween.CurrentValue = tween.StartValue + (tween.EndValue - tween.StartValue) * easedT;
1✔
55
        }
56
    }
1✔
57

58
    private void UpdateVector2Tweens(float deltaTime)
59
    {
60
        foreach (var entity in World.Query<TweenVector2>())
1✔
61
        {
62
            ref var tween = ref World.Get<TweenVector2>(entity);
1✔
63

64
            if (!tween.IsPlaying || tween.IsComplete)
1✔
65
            {
66
                continue;
67
            }
68

69
            tween.ElapsedTime += deltaTime;
1✔
70

71
            var t = ComputeProgress(tween.ElapsedTime, tween.Duration, tween.Loop, tween.PingPong, out var complete);
1✔
72
            tween.IsComplete = complete;
1✔
73

74
            if (complete && !tween.Loop)
1✔
75
            {
UNCOV
76
                tween.IsPlaying = false;
×
77
            }
78

79
            var easedT = Easing.Evaluate(tween.EaseType, t);
1✔
80
            tween.CurrentValue = Vector2.Lerp(tween.StartValue, tween.EndValue, easedT);
1✔
81
        }
82
    }
1✔
83

84
    private void UpdateVector3Tweens(float deltaTime)
85
    {
86
        foreach (var entity in World.Query<TweenVector3>())
1✔
87
        {
88
            ref var tween = ref World.Get<TweenVector3>(entity);
1✔
89

90
            if (!tween.IsPlaying || tween.IsComplete)
1✔
91
            {
92
                continue;
93
            }
94

95
            tween.ElapsedTime += deltaTime;
1✔
96

97
            var t = ComputeProgress(tween.ElapsedTime, tween.Duration, tween.Loop, tween.PingPong, out var complete);
1✔
98
            tween.IsComplete = complete;
1✔
99

100
            if (complete && !tween.Loop)
1✔
101
            {
UNCOV
102
                tween.IsPlaying = false;
×
103
            }
104

105
            var easedT = Easing.Evaluate(tween.EaseType, t);
1✔
106
            tween.CurrentValue = Vector3.Lerp(tween.StartValue, tween.EndValue, easedT);
1✔
107
        }
108
    }
1✔
109

110
    private void UpdateVector4Tweens(float deltaTime)
111
    {
112
        foreach (var entity in World.Query<TweenVector4>())
1✔
113
        {
114
            ref var tween = ref World.Get<TweenVector4>(entity);
1✔
115

116
            if (!tween.IsPlaying || tween.IsComplete)
1✔
117
            {
118
                continue;
119
            }
120

121
            tween.ElapsedTime += deltaTime;
1✔
122

123
            var t = ComputeProgress(tween.ElapsedTime, tween.Duration, tween.Loop, tween.PingPong, out var complete);
1✔
124
            tween.IsComplete = complete;
1✔
125

126
            if (complete && !tween.Loop)
1✔
127
            {
UNCOV
128
                tween.IsPlaying = false;
×
129
            }
130

131
            var easedT = Easing.Evaluate(tween.EaseType, t);
1✔
132
            tween.CurrentValue = Vector4.Lerp(tween.StartValue, tween.EndValue, easedT);
1✔
133
        }
134
    }
1✔
135

136
    private static float ComputeProgress(float elapsed, float duration, bool loop, bool pingPong, out bool complete)
137
    {
138
        complete = false;
1✔
139

140
        if (duration <= 0f)
1✔
141
        {
142
            complete = true;
1✔
143
            return 1f;
1✔
144
        }
145

146
        if (elapsed >= duration)
1✔
147
        {
148
            if (loop)
1✔
149
            {
150
                // Keep the accumulated time monotonic and derive the wrapped phase locally.
151
                // Mutating elapsed here (elapsed %= duration) destroys cycle parity, which
152
                // turned ping-pong into a forward-only sawtooth.
153
                if (pingPong)
1✔
154
                {
155
                    return WrapMath.PingPong(elapsed, duration) / duration;
1✔
156
                }
157

158
                return WrapMath.Repeat(elapsed, duration) / duration;
1✔
159
            }
160

161
            complete = true;
1✔
162
            return 1f;
1✔
163
        }
164

165
        return elapsed / duration;
1✔
166
    }
167
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc