`ensureSourceClockSet()` was intended to only run when the adjustable
source hasn't been set at all yet. As it turns out permitting it to run
unconditionally can break the state of the underlying interpolated
clock. This is caused by the following factors:
* While the decoupleable clock is running, its `CurrentTime` does not
come from either the source clock, or the internal stopwatch; it is
instead calculated using the base `InterpolatingFramedClock` logic.
* A source change of a decoupleable clock seeks the provided source
clock to the decoupleable's current time.
* When an interpolating clock is seeked (decoupleable clock is also
an interpolating one), its interpolation state
(`{Last,Current}InterpolatedTime`) are reset to 0.
* If the interpolating clock determines that its current time is too
far away from the source's time (which was set when the source is
changed), it will ignore the source and instead continue to use
its current time until the source clock has caught up.
Overall, the source change is not really necessary if a source is
already there. The only reason to ensure it was set was to make sure
the first seek of the gameplay clock wasn't performed in decoupled
mode. Therefore, add a guard to make sure the source is only set if
there isn't one already.
- If the storyboard ends after the beatmap, call updateCompletionState as if the score processor has completed at that time. (completionProgressDelegate is null here since earlier when the score processor actually completed, updateCompletionState returned after showing the skip overlay.)
- If the storyboard ends before the beatmap does, updateCompletionState simply returns and waits until the score processor is completed.
- If the storyboard and beatmap end at the exact same time, make sure updateCompletionState() is called only once by the score processor completion.
Co-Authored-By: Marlina José <marlina@umich.edu>