Fix negative speeds in state machines

look commit by commit potentially, last couple of commits are running core generator

& its quite a small change code wise, so it'd be good to get feedback on the actual change sooner rather than later

there are at least a couple of changes in there that should make you question if this is a good idea so i expect some feedback.

add to cpp: (i just got it working in the viewer, probably broke some things)
- [x] negative time fix
- [x] combine state speed to determine time for state
- [x] no longer carry spilled time into new advances

add test
- [x] negative time fix to state machines
- [x] negative combined time state fix to state machines
- [ ] no longer carry spilled time into new advances @luigi-rosso i tried adding tests for this, but i was not really able to construct a state machine instance (with an animations, an animation state and the right transition) in tests I had to add a bunch of public methods all over the place, can you show me the way? probably not a blocker for merging this)

apply changes to rive_flutter
- [x] run script (ran both core generator and core generator runtime, the runtime one wanted to remove a load of comments so i didnt let those bits be committed. but still.. annoying)

Diffs=
bc6c6f467 Fix negative speeds in state machines (#4887)
diff --git a/.rive_head b/.rive_head
index c2ac205..3727ac5 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-040a27c1c53dfb53ffce3a8b2503124fe1a2f068
+bc6c6f467b8f1e0f794be093a514768bd068088a
diff --git a/include/rive/animation/animation_state_instance.hpp b/include/rive/animation/animation_state_instance.hpp
index 379f5d3..689b8ce 100644
--- a/include/rive/animation/animation_state_instance.hpp
+++ b/include/rive/animation/animation_state_instance.hpp
@@ -23,6 +23,7 @@
     void apply(float mix) override;
 
     bool keepGoing() const override;
+    void clearSpilledTime() override;
 
     const LinearAnimationInstance* animationInstance() const { return &m_AnimationInstance; }
 
diff --git a/include/rive/animation/linear_animation.hpp b/include/rive/animation/linear_animation.hpp
index 309c6d7..6d8aeb5 100644
--- a/include/rive/animation/linear_animation.hpp
+++ b/include/rive/animation/linear_animation.hpp
@@ -27,9 +27,14 @@
 
     StatusCode import(ImportStack& importStack) override;
 
+    float durationSeconds() const;
+    /// Returns the start time/ end time of the animation in seconds
     float startSeconds() const;
     float endSeconds() const;
-    float durationSeconds() const;
+
+    /// Returns the start time/ end time of the animation in seconds, considering speed
+    float startTime() const;
+    float endTime() const;
 
     /// Convert a global clock to local seconds (takes into consideration
     /// work area start/end, speed, looping).
diff --git a/include/rive/animation/linear_animation_instance.hpp b/include/rive/animation/linear_animation_instance.hpp
index e430995..3e645ab 100644
--- a/include/rive/animation/linear_animation_instance.hpp
+++ b/include/rive/animation/linear_animation_instance.hpp
@@ -23,7 +23,7 @@
     int m_LoopValue = -1;
 
 public:
-    LinearAnimationInstance(const LinearAnimation*, ArtboardInstance*);
+    LinearAnimationInstance(const LinearAnimation*, ArtboardInstance*, float speedMultiplier = 1.0);
     LinearAnimationInstance(LinearAnimationInstance const&);
     ~LinearAnimationInstance() override;
 
@@ -31,6 +31,8 @@
     // animation will continue to animate after this advance.
     bool advance(float seconds);
 
+    void clearSpilledTime() { m_SpilledTime = 0; }
+
     // Returns a pointer to the instance's animation
     const LinearAnimation* animation() const { return m_Animation; }
 
@@ -76,7 +78,7 @@
     uint32_t fps() const;
     uint32_t duration() const;
     float speed() const;
-    float startSeconds() const;
+    float startTime() const;
 
     // Returns either the animation's default or overridden loop values
     Loop loop() const override { return (Loop)loopValue(); }
@@ -87,6 +89,7 @@
     bool isTranslucent() const override;
     bool advanceAndApply(float seconds) override;
     std::string name() const override;
+    void reset(float speedMultiplier);
 };
 } // namespace rive
 #endif
diff --git a/include/rive/animation/state_instance.hpp b/include/rive/animation/state_instance.hpp
index 3465a43..549555d 100644
--- a/include/rive/animation/state_instance.hpp
+++ b/include/rive/animation/state_instance.hpp
@@ -27,6 +27,7 @@
     /// Returns true when the State Machine needs to keep advancing this
     /// state.
     virtual bool keepGoing() const = 0;
+    virtual void clearSpilledTime() {}
 
     const LayerState* state() const;
 };
diff --git a/src/animation/animation_state_instance.cpp b/src/animation/animation_state_instance.cpp
index 1c4063e..a2ed1ed 100644
--- a/src/animation/animation_state_instance.cpp
+++ b/src/animation/animation_state_instance.cpp
@@ -15,7 +15,9 @@
     // SystemStateInstance (basically a no-op StateMachine state) which would
     // cause bad casts in parts of the code where we assumed AnimationStates
     // would have create AnimationStateInstances.
-    m_AnimationInstance(state->animation() ? state->animation() : &emptyAnimation, instance),
+    m_AnimationInstance(state->animation() ? state->animation() : &emptyAnimation,
+                        instance,
+                        state->speed()),
     m_KeepGoing(true)
 {}
 
@@ -28,4 +30,5 @@
 
 void AnimationStateInstance::apply(float mix) { m_AnimationInstance.apply(mix); }
 
-bool AnimationStateInstance::keepGoing() const { return m_KeepGoing; }
\ No newline at end of file
+bool AnimationStateInstance::keepGoing() const { return m_KeepGoing; }
+void AnimationStateInstance::clearSpilledTime() { m_AnimationInstance.clearSpilledTime(); }
\ No newline at end of file
diff --git a/src/animation/linear_animation.cpp b/src/animation/linear_animation.cpp
index 3bbdc4a..f9ecb39 100644
--- a/src/animation/linear_animation.cpp
+++ b/src/animation/linear_animation.cpp
@@ -3,6 +3,7 @@
 #include "rive/artboard.hpp"
 #include "rive/importers/artboard_importer.hpp"
 #include "rive/importers/import_stack.hpp"
+#include <cmath>
 
 using namespace rive;
 
@@ -77,7 +78,10 @@
 {
     return (enableWorkArea() ? workEnd() : duration()) / (float)fps();
 }
-float LinearAnimation::durationSeconds() const { return endSeconds() - startSeconds(); }
+
+float LinearAnimation::startTime() const { return (speed() >= 0) ? startSeconds() : endSeconds(); }
+float LinearAnimation::endTime() const { return (speed() >= 0) ? endSeconds() : startSeconds(); }
+float LinearAnimation::durationSeconds() const { return std::abs(endSeconds() - startSeconds()); }
 
 // Matches Dart modulus: https://api.dart.dev/stable/2.19.0/dart-core/double/operator_modulo.html
 static float positiveMod(float value, float range)
@@ -96,13 +100,13 @@
     switch (loop())
     {
         case Loop::oneShot:
-            return seconds + startSeconds();
+            return seconds + startTime();
         case Loop::loop:
-            return positiveMod(seconds, (endSeconds() - startSeconds())) + startSeconds();
+            return positiveMod(seconds, (durationSeconds())) + startTime();
         case Loop::pingPong:
-            float localTime = positiveMod(seconds, (endSeconds() - startSeconds()));
-            int direction = ((int)(seconds / (endSeconds() - startSeconds()))) % 2;
-            return direction == 0 ? localTime + startSeconds() : endSeconds() - localTime;
+            float localTime = positiveMod(seconds, (durationSeconds()));
+            int direction = ((int)(seconds / (durationSeconds()))) % 2;
+            return direction == 0 ? localTime + startTime() : endTime() - localTime;
     }
     RIVE_UNREACHABLE();
 }
\ No newline at end of file
diff --git a/src/animation/linear_animation_instance.cpp b/src/animation/linear_animation_instance.cpp
index 2e4b0f1..0e9ea17 100644
--- a/src/animation/linear_animation_instance.cpp
+++ b/src/animation/linear_animation_instance.cpp
@@ -8,10 +8,11 @@
 using namespace rive;
 
 LinearAnimationInstance::LinearAnimationInstance(const LinearAnimation* animation,
-                                                 ArtboardInstance* instance) :
+                                                 ArtboardInstance* instance,
+                                                 float speedMultiplier) :
     Scene(instance),
     m_Animation((assert(animation != nullptr), animation)),
-    m_Time(animation->enableWorkArea() ? (float)animation->workStart() / animation->fps() : 0),
+    m_Time((speedMultiplier >= 0) ? animation->startTime() : animation->endTime()),
     m_TotalTime(0.0f),
     m_LastTotalTime(0.0f),
     m_SpilledTime(0.0f),
@@ -173,13 +174,18 @@
     m_Direction = 1;
 }
 
+void LinearAnimationInstance::reset(float speedMultiplier = 1.0)
+{
+    m_Time = (speedMultiplier >= 0) ? m_Animation->startTime() : m_Animation->endTime();
+}
+
 uint32_t LinearAnimationInstance::fps() const { return m_Animation->fps(); }
 
 uint32_t LinearAnimationInstance::duration() const { return m_Animation->duration(); }
 
 float LinearAnimationInstance::speed() const { return m_Animation->speed(); }
 
-float LinearAnimationInstance::startSeconds() const { return m_Animation->startSeconds(); }
+float LinearAnimationInstance::startTime() const { return m_Animation->startTime(); }
 
 std::string LinearAnimationInstance::name() const { return m_Animation->name(); }
 
diff --git a/src/animation/state_machine_instance.cpp b/src/animation/state_machine_instance.cpp
index ef70627..544aa9a 100644
--- a/src/animation/state_machine_instance.cpp
+++ b/src/animation/state_machine_instance.cpp
@@ -88,7 +88,7 @@
     {
         m_StateChangedOnAdvance = false;
 
-        if (m_CurrentState != nullptr)
+        if (m_CurrentState != nullptr && m_CurrentState->keepGoing())
         {
             m_CurrentState->advance(seconds, inputs);
         }
@@ -115,6 +115,8 @@
 
         apply();
 
+        m_CurrentState->clearSpilledTime();
+
         return m_Mix != 1.0f || m_WaitingForExit ||
                (m_CurrentState != nullptr && m_CurrentState->keepGoing());
     }
diff --git a/src/animation/state_transition.cpp b/src/animation/state_transition.cpp
index caffa41..5efa228 100644
--- a/src/animation/state_transition.cpp
+++ b/src/animation/state_transition.cpp
@@ -99,6 +99,7 @@
         auto exitAnimation = exitTimeAnimation(stateFrom);
         if (exitAnimation != nullptr)
         {
+            // TODO: needs a looking for speed
             start = absolute ? exitAnimation->startSeconds() : 0.0f;
             animationDuration = exitAnimation->durationSeconds();
         }
@@ -176,6 +177,7 @@
                 exitTime += std::floor(lastTime / duration) * duration;
             }
 
+            // TODO: needs a looking for speed
             if (time < exitTime)
             {
                 return AllowTransition::waitingForExit;
diff --git a/test/animation_state_instance_test.cpp b/test/animation_state_instance_test.cpp
index cdced64..af648d4 100644
--- a/test/animation_state_instance_test.cpp
+++ b/test/animation_state_instance_test.cpp
@@ -140,3 +140,117 @@
     delete animationState;
     delete linearAnimation;
 }
+
+TEST_CASE("AnimationStateInstance starts a positive animation at the beginning", "[animation]")
+{
+    rive::NoOpFactory emptyFactory;
+    // For each of these tests, we cons up a dummy artboard/instance
+    // just to make the animations happy.
+    rive::Artboard ab(&emptyFactory);
+    auto abi = ab.instance();
+
+    rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+    // duration in seconds is 5
+    linearAnimation->duration(10);
+    linearAnimation->fps(2);
+
+    rive::AnimationState* animationState = new rive::AnimationState();
+    animationState->animation(linearAnimation);
+
+    rive::AnimationStateInstance* animationStateInstance =
+        new rive::AnimationStateInstance(animationState, abi.get());
+
+    // backwards 2 seconds from 5.
+    REQUIRE(animationStateInstance->animationInstance()->time() == 0.0);
+
+    delete animationStateInstance;
+    delete animationState;
+    delete linearAnimation;
+}
+
+TEST_CASE("AnimationStateInstance starts a negative animation at the end", "[animation]")
+{
+    rive::NoOpFactory emptyFactory;
+    // For each of these tests, we cons up a dummy artboard/instance
+    // just to make the animations happy.
+    rive::Artboard ab(&emptyFactory);
+    auto abi = ab.instance();
+
+    rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+    // duration in seconds is 5
+    linearAnimation->duration(10);
+    linearAnimation->fps(2);
+    linearAnimation->speed(-1);
+
+    rive::AnimationState* animationState = new rive::AnimationState();
+    animationState->animation(linearAnimation);
+
+    rive::AnimationStateInstance* animationStateInstance =
+        new rive::AnimationStateInstance(animationState, abi.get());
+
+    // backwards 2 seconds from 5.
+    REQUIRE(animationStateInstance->animationInstance()->time() == 5.0);
+
+    delete animationStateInstance;
+    delete animationState;
+    delete linearAnimation;
+}
+
+TEST_CASE("AnimationStateInstance with negative speed starts a positive animation at the end",
+          "[animation]")
+{
+    rive::NoOpFactory emptyFactory;
+    // For each of these tests, we cons up a dummy artboard/instance
+    // just to make the animations happy.
+    rive::Artboard ab(&emptyFactory);
+    auto abi = ab.instance();
+
+    rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+    // duration in seconds is 5
+    linearAnimation->duration(10);
+    linearAnimation->fps(2);
+
+    rive::AnimationState* animationState = new rive::AnimationState();
+    animationState->speed(-1);
+    animationState->animation(linearAnimation);
+
+    rive::AnimationStateInstance* animationStateInstance =
+        new rive::AnimationStateInstance(animationState, abi.get());
+
+    // backwards 2 seconds from 5.
+    REQUIRE(animationStateInstance->animationInstance()->time() == 5.0);
+
+    delete animationStateInstance;
+    delete animationState;
+    delete linearAnimation;
+}
+
+TEST_CASE("AnimationStateInstance with negative speed starts a negative animation at the beginning",
+          "[animation]")
+{
+    rive::NoOpFactory emptyFactory;
+    // For each of these tests, we cons up a dummy artboard/instance
+    // just to make the animations happy.
+    rive::Artboard ab(&emptyFactory);
+    auto abi = ab.instance();
+
+    rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+    // duration in seconds is 5
+    linearAnimation->duration(10);
+    linearAnimation->fps(2);
+    linearAnimation->speed(-1);
+
+    rive::AnimationState* animationState = new rive::AnimationState();
+    animationState->speed(-1);
+    animationState->animation(linearAnimation);
+
+    rive::AnimationStateInstance* animationStateInstance =
+        new rive::AnimationStateInstance(animationState, abi.get());
+
+    // backwards 2 seconds from 5.
+    REQUIRE(animationStateInstance->animationInstance()->time() == 0.0);
+
+    delete animationStateInstance;
+    delete animationState;
+    delete linearAnimation;
+}
\ No newline at end of file
diff --git a/test/linear_animation_test.cpp b/test/linear_animation_test.cpp
new file mode 100644
index 0000000..5d61c8f
--- /dev/null
+++ b/test/linear_animation_test.cpp
@@ -0,0 +1,53 @@
+#include <rive/artboard.hpp>
+#include <rive/animation/linear_animation.hpp>
+#include "utils/no_op_factory.hpp"
+#include <catch.hpp>
+#include <cstdio>
+
+TEST_CASE("LinearAnimation with positive speed have normal start and end seconds", "[animation]")
+{
+    rive::NoOpFactory emptyFactory;
+    // For each of these tests, we cons up a dummy artboard/instance
+    // just to make the animations happy.
+    rive::Artboard ab(&emptyFactory);
+    auto abi = ab.instance();
+
+    rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+    // duration in seconds is 5
+    linearAnimation->duration(10);
+    linearAnimation->fps(2);
+    linearAnimation->speed(1);
+
+    REQUIRE(linearAnimation->startSeconds() == 0.0);
+    REQUIRE(linearAnimation->endSeconds() == 5.0);
+
+    REQUIRE(linearAnimation->startTime() == 0.0);
+    REQUIRE(linearAnimation->endTime() == 5.0);
+    REQUIRE(linearAnimation->durationSeconds() == 5.0);
+
+    delete linearAnimation;
+}
+
+TEST_CASE("LinearAnimation with negative speed have reversed start and end seconds", "[animation]")
+{
+    rive::NoOpFactory emptyFactory;
+    // For each of these tests, we cons up a dummy artboard/instance
+    // just to make the animations happy.
+    rive::Artboard ab(&emptyFactory);
+    auto abi = ab.instance();
+
+    rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+    // duration in seconds is 5
+    linearAnimation->duration(10);
+    linearAnimation->fps(2);
+    linearAnimation->speed(-1);
+
+    REQUIRE(linearAnimation->startSeconds() == 0.0);
+    REQUIRE(linearAnimation->endSeconds() == 5.0);
+    REQUIRE(linearAnimation->startTime() == 5.0);
+    REQUIRE(linearAnimation->endTime() == 0.0);
+
+    REQUIRE(linearAnimation->durationSeconds() == 5.0);
+
+    delete linearAnimation;
+}