From 37adf790ee91c3b2c3510e01087a74c9aa61f811 Mon Sep 17 00:00:00 2001
From: Luigi Rosso <luigi@rive.app>
Date: Fri, 7 May 2021 19:41:50 -0700
Subject: [PATCH] Blend states!

---
 example/assets/liquid_download.riv            | Bin 0 -> 7127 bytes
 example/lib/liquid_download.dart              |  94 +++
 example/lib/main.dart                         |  15 +
 lib/src/blend_animations.dart                 |  20 +
 lib/src/core/core.dart                        |   1 +
 .../core/importers/layer_state_importer.dart  |  24 +
 .../state_machine_layer_importer.dart         |  10 +-
 .../animation/blend_animation_1d_base.dart    |  37 ++
 .../animation/blend_animation_base.dart       |  38 ++
 .../blend_animation_direct_base.dart          |  39 ++
 .../animation/blend_state_1d_base.dart        |  46 ++
 .../generated/animation/blend_state_base.dart |  19 +
 .../animation/blend_state_direct_base.dart    |  22 +
 .../blend_state_transition_base.dart          |  44 ++
 lib/src/generated/rive_core_context.dart      | 562 ++++++++++++++----
 lib/src/rive_core/animation/animation.dart    |   4 +
 .../rive_core/animation/animation_state.dart  |  33 +
 .../animation/animation_state_instance.dart   |  25 +
 lib/src/rive_core/animation/any_state.dart    |   6 +-
 .../rive_core/animation/blend_animation.dart  |  52 ++
 .../animation/blend_animation_1d.dart         |   9 +
 .../animation/blend_animation_direct.dart     |  30 +
 lib/src/rive_core/animation/blend_state.dart  |  20 +
 .../rive_core/animation/blend_state_1d.dart   |  24 +
 .../animation/blend_state_1d_instance.dart    |  82 +++
 .../animation/blend_state_direct.dart         |   9 +
 .../blend_state_direct_instance.dart          |  24 +
 .../animation/blend_state_instance.dart       |  59 ++
 .../animation/blend_state_transition.dart     |  31 +
 .../animation/cubic_interpolator.dart         |  21 +
 lib/src/rive_core/animation/entry_state.dart  |   6 +-
 lib/src/rive_core/animation/exit_state.dart   |   6 +-
 lib/src/rive_core/animation/interpolator.dart |   3 +
 lib/src/rive_core/animation/keyed_object.dart |  24 +
 .../rive_core/animation/keyed_property.dart   |  38 ++
 lib/src/rive_core/animation/keyframe.dart     |  16 +
 .../rive_core/animation/keyframe_color.dart   |   4 +
 .../rive_core/animation/keyframe_double.dart  |   3 +
 lib/src/rive_core/animation/keyframe_id.dart  |   1 +
 .../animation/keyframe_interpolation.dart     |  13 +-
 lib/src/rive_core/animation/layer_state.dart  |   9 +
 .../rive_core/animation/linear_animation.dart |  28 +
 .../animation/linear_animation_instance.dart  |  32 +
 lib/src/rive_core/animation/loop.dart         |  13 +-
 .../rive_core/animation/state_instance.dart   |  33 +
 .../rive_core/animation/state_machine.dart    |   2 +
 .../animation/state_machine_bool.dart         |   2 +
 .../animation/state_machine_component.dart    |   9 +
 .../animation/state_machine_input.dart        |   3 +
 .../animation/state_machine_layer.dart        |   7 +
 .../state_machine_layer_component.dart        |   5 +
 .../animation/state_machine_number.dart       |   2 +
 .../animation/state_machine_trigger.dart      |   2 +
 .../rive_core/animation/state_transition.dart | 106 +++-
 .../animation/transition_bool_condition.dart  |   1 +
 .../animation/transition_condition.dart       |   8 +-
 .../transition_number_condition.dart          |   3 +
 .../transition_trigger_condition.dart         |   3 +
 .../animation/transition_value_condition.dart |   5 +-
 lib/src/rive_core/artboard.dart               |  86 +++
 lib/src/rive_core/backboard.dart              |   2 +
 lib/src/rive_core/bones/bone.dart             |   8 +
 lib/src/rive_core/bones/cubic_weight.dart     |   4 +
 lib/src/rive_core/bones/skin.dart             |  17 +
 lib/src/rive_core/bones/skinnable.dart        |   9 +
 lib/src/rive_core/bones/tendon.dart           |   9 +-
 lib/src/rive_core/bones/weight.dart           |  10 +-
 lib/src/rive_core/bones/weighted_vertex.dart  |  31 +-
 lib/src/rive_core/component.dart              |  68 ++-
 lib/src/rive_core/component_dirt.dart         |  27 +
 lib/src/rive_core/component_flags.dart        |   4 +
 lib/src/rive_core/container_component.dart    |  14 +
 lib/src/rive_core/draw_rules.dart             |   5 +
 lib/src/rive_core/draw_target.dart            |   4 +
 lib/src/rive_core/drawable.dart               |  20 +
 lib/src/rive_core/event.dart                  |   2 +
 lib/src/rive_core/math/aabb.dart              |  34 +-
 lib/src/rive_core/math/circle_constant.dart   |   2 +
 lib/src/rive_core/math/mat2d.dart             |  16 +
 lib/src/rive_core/math/segment2d.dart         |  33 +-
 .../rive_core/math/transform_components.dart  |   4 +
 lib/src/rive_core/math/vec2d.dart             |  13 +-
 lib/src/rive_core/node.dart                   |   2 +
 .../rive_core/rive_animation_controller.dart  |   7 +
 .../rive_format_error_exception.dart          |   1 +
 .../rive_unsupported_version_exception.dart   |   3 +
 lib/src/rive_core/runtime/runtime_header.dart |  29 +-
 lib/src/rive_core/shapes/clipping_shape.dart  |  13 +
 .../shapes/cubic_asymmetric_vertex.dart       |   3 +
 .../shapes/cubic_detached_vertex.dart         |  24 +-
 .../shapes/cubic_mirrored_vertex.dart         |   5 +
 lib/src/rive_core/shapes/cubic_vertex.dart    |   6 +
 lib/src/rive_core/shapes/ellipse.dart         |  54 +-
 lib/src/rive_core/shapes/paint/fill.dart      |  11 +-
 .../rive_core/shapes/paint/gradient_stop.dart |   4 +
 .../shapes/paint/linear_gradient.dart         |  33 +
 .../shapes/paint/radial_gradient.dart         |   2 +
 .../rive_core/shapes/paint/shape_paint.dart   |  18 +
 .../shapes/paint/shape_paint_mutator.dart     |   6 +
 .../rive_core/shapes/paint/solid_color.dart   |  17 +-
 lib/src/rive_core/shapes/paint/stroke.dart    |  14 +-
 lib/src/rive_core/shapes/paint/trim_path.dart |  18 +-
 .../shapes/paint/trim_path_drawing.dart       |  35 +-
 lib/src/rive_core/shapes/parametric_path.dart |   6 +
 lib/src/rive_core/shapes/path.dart            |  51 ++
 lib/src/rive_core/shapes/path_composer.dart   |  17 +
 lib/src/rive_core/shapes/path_vertex.dart     |   8 +
 lib/src/rive_core/shapes/points_path.dart     |  20 +
 lib/src/rive_core/shapes/polygon.dart         |   3 +
 lib/src/rive_core/shapes/rectangle.dart       |   8 +-
 lib/src/rive_core/shapes/shape.dart           |  60 +-
 .../shapes/shape_paint_container.dart         |  18 +
 lib/src/rive_core/shapes/star.dart            |   2 +
 lib/src/rive_core/shapes/straight_vertex.dart |   7 +
 lib/src/rive_core/shapes/triangle.dart        |   3 +
 .../rive_core/state_machine_controller.dart   | 188 +++---
 lib/src/rive_core/state_transition_flags.dart |  10 +
 lib/src/rive_core/transform_component.dart    |  15 +
 lib/src/rive_file.dart                        |  18 +-
 lib/src/state_machine_controller.dart         |   9 -
 120 files changed, 2661 insertions(+), 296 deletions(-)
 create mode 100644 example/assets/liquid_download.riv
 create mode 100644 example/lib/liquid_download.dart
 create mode 100644 lib/src/blend_animations.dart
 create mode 100644 lib/src/generated/animation/blend_animation_1d_base.dart
 create mode 100644 lib/src/generated/animation/blend_animation_base.dart
 create mode 100644 lib/src/generated/animation/blend_animation_direct_base.dart
 create mode 100644 lib/src/generated/animation/blend_state_1d_base.dart
 create mode 100644 lib/src/generated/animation/blend_state_base.dart
 create mode 100644 lib/src/generated/animation/blend_state_direct_base.dart
 create mode 100644 lib/src/generated/animation/blend_state_transition_base.dart
 create mode 100644 lib/src/rive_core/animation/animation_state_instance.dart
 create mode 100644 lib/src/rive_core/animation/blend_animation.dart
 create mode 100644 lib/src/rive_core/animation/blend_animation_1d.dart
 create mode 100644 lib/src/rive_core/animation/blend_animation_direct.dart
 create mode 100644 lib/src/rive_core/animation/blend_state.dart
 create mode 100644 lib/src/rive_core/animation/blend_state_1d.dart
 create mode 100644 lib/src/rive_core/animation/blend_state_1d_instance.dart
 create mode 100644 lib/src/rive_core/animation/blend_state_direct.dart
 create mode 100644 lib/src/rive_core/animation/blend_state_direct_instance.dart
 create mode 100644 lib/src/rive_core/animation/blend_state_instance.dart
 create mode 100644 lib/src/rive_core/animation/blend_state_transition.dart
 create mode 100644 lib/src/rive_core/animation/state_instance.dart

diff --git a/example/assets/liquid_download.riv b/example/assets/liquid_download.riv
new file mode 100644
index 0000000000000000000000000000000000000000..8c933a431c9a125654b46f2bd50b09bb634911cb
GIT binary patch
literal 7127
zcmdT}du&@*89(>hZrxX2OWL?KNt!Mt>oTpG389M@$G%D0RL#?Ela{U{52rIr<KQ^k
zgm{FG2{ekRR;^jL6@uurim@^YX<|^+&I5^RDU)ClNc{sQ9viPQAyB3L6Mo-$-Rma_
z@DGvVobP;(^S#e^;-Q|yU2ECd+ZK)IjZ4Oh2BV)X%y4_d$<$b4G&!-Bv2&J}KA7p=
zU_BTcf2eM6GBG#fVHHHYiLuRA34Jh^yHwz<#PoD*Je5d#44~f+1HJefD|1(7q3Z@l
zQxAGfw!^)iR&@i3_;kwS8rr<ne>M7^cQ~Al@!_2G(6-Jyu0{X(iz{JP;TddL#t*yO
zGdRoGd}z*q79M_#IGc`j^JBcslX1+tY`A)P`5R%Juo%w4Fg6n*%xgGtNS!!jpIFhE
zcw~AiF*;Fao6a^JeYMIeIq>)07IfH>OChW;&9bp<=w|8lP?WJ(vaK(xo}6q?Lya$o
zhxVN=zZU)Noo|v0qDrBtus)njCLZzVz<dzVE~v^fY;!gY;rj}c`>M$Wpm{&rL%!mS
zeqtoo<}msN<1SCtvHdXRpfcqk_ic)Y)I>-(_Rpna$vP{Z9G{ALSlMIoFNMnK!;yY&
z_r|WNsrbxnOnA1wk}=b&q7R5ZBt*O1CDFO@@!0IFEsWB*JS&&8g*8CfyAcR`Hv(b3
z4R0@ga+wnT0D>N$K2aB)i6iHew%bt;*)ke46gsSIJ)FF}cYA1-K6n7v9Eg8vE<OQK
z&E^-Vv>u=Y+8}m?X-#!TR@&TE-E;Qg*M0B&>_|HLyO-8Qa?s6Vx4#Z&pG!yY8M{3n
z`eIse=S8otZ2qX=hMc_Iks{pSu>|)=@^SA!`>-Xry(QHVwkD%Y@2YMdyC}F1-Yo6u
zI5hIg?#N-Pk>P8@-whR{j)405)kEKm{3NeL?h7vG>M&zJjj;OMQ-!|<&+Mp)uw9-e
zo}qmWzgdh^<k!h~MD`+3!CPNR=M7X_PN0It0~Lh1MFK_WB7q847Y!8WE*vO+cHuw;
ztBVGT3tw@dZ0f>+;xzvzP&Q3k1*(k)s=wjaXL@;wTIdO=&J()eac|AH7ik>(GirUH
z;jPcLEVD{nD~TywX$_5r4Kz7{HUd11RVz~SZuGs5-A1!<k7L&>cb$8sc`2*%?$+~N
zCwHTh&L95M3$^XcAAabi7W@fanC%MzYI?bYAZl|R8=7{vg|F}U8f|R<Vp%9myBSEi
z0ZAGe@oo==Kl8UA2`TfC$vNhUU_KO?T=*;<BN;#V<A*ilz3=!X&0GDS53$--cW3Nm
zV$ZMKZAOsYciGi$FkfIT+uD+3HP(XJX>^l7te?Xg+p}V6cJ(R5%Y^U+yg*g}dCFz;
zoW>fcCZIL}H4aR(`Fjh?1+zPN>#u37h(_#gZ7GpNZ2qf~2zLRkI1%6V1*#ye412*&
zvuju>F#w>q`^M5~QguXg2-TmYBW^4mAlx|@C`Y=VIe(s@Zxe<axV5DmFv;xCmdW`>
zu*--=sNxVTMRCD{>KLbB@(QtYHy+6lAxu`MVmPR=ge8pqoPd$;$v<q74luipKnPm0
zG~y^A?i_g*xP5^(hpQibFF_85tD8^#P<xT@=67|eCX%i$fi%hk#NO=sxH93g2>5;(
zhZjtRxMYIX_K$FBL9{Q>ASGNR@yEam$!6El?m*ys<mvAdbbt5c<Rn39or^*)XF9$L
zrUJ=HNlod*<vpNG;hKD+`;KF8YE4FqYC^H$0hTu0SX$DYd2Jwd&XL0qAI|Cv+$r6C
zoZC$!2+;EeQdF*aJ<9b3^2g(U9t?qriZYi;B2z$)f_D8czjcKlhxK0u%Q}!PmGZ!0
z95@CX6{xHR5;=_D5unB?tJ|G;U!dOUwuvm`3k8tEDdkhOahJzd&r7Zg3ROufh~a9}
znsQSKL?-G3dCf?w+E%ZXI5{&FOU2Ol(Di~DO|RURP^DX56-4Qdw=1Kom5WX$N~#=(
zqbNm}f}WgG6=k_7M4J)?=L@JJqay~C?v0#L6-W(G$88kV$TR0(rR-?tCdv|#KB0^t
z_Y&9PRdD$Ns-)`_7e$#wIX3LjlYAY$MgY2Uv$WDtu=QFkAB7ywB)uriaF0;Y7f|&}
z+UOz_+Q{n7(nfOHx@2+5$}PkNO`rh98Rc%*Y$-I7m3bSM*S^yx3)2Qrh}>iQ-u^U&
zj`FPb@fYb1-qbTafox2kj8Bj9q~w>eNK1Knf|wp4;G-!2*n1^{(BQrVz~;|T9?46a
zHb^zz6>y%+T75MDPXn?R^n)Bh@`4qihcHIM$ZUR~xRG@8#tFv-rCjl(SKg5cr8Gh+
zDU8f|Fv*<Pu7rZzk-`A&O7fm=V&ntvMxBiiBy)-f)(L8j+TkUcF2KvMD{=|?0;R%Q
zUVySe;xva(B~aVCp))sVZy3CpP}F2}7@|{Sy+LhU_6F()1(3C^o@i<`nG%g@r<R5B
z3H;6Gk|lVpog|LNBprCMr9%(U5qud_Z0Yf)5`Z3={1A)i<iqpv#}D0A9#{k*!)mM#
zNkfEGKE4R4a(wZtWRV>ZItOpvOy`=Q8Xf5@(~0X(YlP1>P4_4jkld8wj5?{*6}OG7
zbxIPq<~D5kf$CFN9QUgzZPi&wDQB|q1G3Xaxn!rKC)9)Se$^F7u$|Wy;SM@+vSZL~
zO{Dy`US)P;nD3dGitV9E-Z7F>rU;9iy+maaVKeebkr!<sDu_0BPKISA6c3~=oAO_$
z%?hFlVjWYNE!Zm3dg$uHTnC~R^#wV5fm}vRnJ<I1wZ+kZ^yCEcUYvtrX$7qXwLKJ@
zjq%z(<x+30|Lx<b$SdqWdDN-Ng$Bd_{yE`zxv6ql)w}Kg^3?`Q^_varoEMuK|LBdS
zyh2kf5=wPNnv>BM=w;!28x?zxtdN{~Yn}(Ynga{I-FR(V1<|^1@e$u)RU!SQ<w7i(
zyBZ!cVujBEB^Hl$-V|vfaJ;%yKVHR~D}!ZKFD_`E|GlNIB8}BkORhT|57c7mpzP);
z<anCq;~{ruJWOj_Hy({86Y4t?eZ6GOIAX$d%r&*G75&q(eRI<j$=HO=&1YNnZy`m_
z-%QTA2pcV}Ud6@keZsJTj{z7+CQc;r#Q_7bj*riEz41p+<2y6_`*_b2M&?M(?t9Ac
zu<(Lmu+C>e1JF&N`LyBxy73R!qA|gGSl@!-#s&cT?ydV-!+6ecT@WIBpEt})hRN{P
ddkJD7VGT)OPa7MF8?ZsJOTr<JWF4%X{R_1nB3=Lh

literal 0
HcmV?d00001

diff --git a/example/lib/liquid_download.dart b/example/lib/liquid_download.dart
new file mode 100644
index 0000000..3957677
--- /dev/null
+++ b/example/lib/liquid_download.dart
@@ -0,0 +1,94 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:rive/rive.dart';
+
+/// An example showing how to drive a StateMachine via a trigger and number
+/// input.
+class LiquidDownload extends StatefulWidget {
+  const LiquidDownload({Key? key}) : super(key: key);
+
+  @override
+  _LiquidDownloadState createState() => _LiquidDownloadState();
+}
+
+class _LiquidDownloadState extends State<LiquidDownload> {
+  /// Tracks if the animation is playing by whether controller is running.
+  bool get isPlaying => _controller?.isActive ?? false;
+
+  Artboard? _riveArtboard;
+  StateMachineController? _controller;
+  SMIInput<bool>? _start;
+  SMIInput<double>? _progress;
+
+  @override
+  void initState() {
+    super.initState();
+
+    // Load the animation file from the bundle, note that you could also
+    // download this. The RiveFile just expects a list of bytes.
+    rootBundle.load('assets/liquid_download.riv').then(
+      (data) async {
+        // Load the RiveFile from the binary data.
+        final file = RiveFile.import(data);
+
+        // The artboard is the root of the animation and gets drawn in the
+        // Rive widget.
+        final artboard = file.mainArtboard;
+        var controller =
+            StateMachineController.fromArtboard(artboard, 'Download');
+        if (controller != null) {
+          artboard.addController(controller);
+          _start = controller.findInput('Download');
+          _progress = controller.findInput('Progress');
+        }
+        setState(() => _riveArtboard = artboard);
+      },
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      backgroundColor: Colors.grey,
+      appBar: AppBar(
+        title: const Text('Liquid Download'),
+      ),
+      body: Center(
+        child: _riveArtboard == null
+            ? const SizedBox()
+            : GestureDetector(
+                onTapDown: (_) => _start?.value = true,
+                child: Column(
+                  children: [
+                    const SizedBox(height: 10),
+                    const Text(
+                      'Press to activate, slide for progress...',
+                      style: TextStyle(
+                        fontSize: 18,
+                      ),
+                    ),
+                    Slider(
+                      value: _progress!.value,
+                      min: 0,
+                      max: 100,
+                      label: _progress!.value.round().toString(),
+                      onChanged: (double value) {
+                        setState(() {
+                          _progress!.value = value;
+                        });
+                      },
+                    ),
+                    const SizedBox(height: 10),
+                    Expanded(
+                      child: Rive(
+                        artboard: _riveArtboard!,
+                      ),
+                    ),
+                  ],
+                ),
+              ),
+      ),
+    );
+  }
+}
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 2434d5e..27fe750 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,6 +1,7 @@
 import 'package:flutter/material.dart';
 import 'package:rive_example/example_animation.dart';
 import 'package:rive_example/example_state_machine.dart';
+import 'package:rive_example/liquid_download.dart';
 import 'package:rive_example/little_machine.dart';
 import 'package:rive_example/state_machine_skills.dart';
 
@@ -73,6 +74,20 @@ class Home extends StatelessWidget {
                 );
               },
             ),
+            const SizedBox(
+              height: 10,
+            ),
+            ElevatedButton(
+              child: const Text('Liquid Download'),
+              onPressed: () {
+                Navigator.push(
+                  context,
+                  MaterialPageRoute<void>(
+                    builder: (context) => const LiquidDownload(),
+                  ),
+                );
+              },
+            ),
           ],
         ),
       ),
diff --git a/lib/src/blend_animations.dart b/lib/src/blend_animations.dart
new file mode 100644
index 0000000..8566e82
--- /dev/null
+++ b/lib/src/blend_animations.dart
@@ -0,0 +1,20 @@
+import 'dart:collection';
+
+import 'package:rive/src/rive_core/animation/blend_animation.dart';
+
+class BlendAnimations<T extends BlendAnimation> extends ListBase<T> {
+  final List<T?> _values = [];
+  List<T> get values => _values.cast<T>();
+
+  @override
+  int get length => _values.length;
+
+  @override
+  set length(int value) => _values.length = value;
+
+  @override
+  T operator [](int index) => _values[index]!;
+
+  @override
+  void operator []=(int index, T value) => _values[index] = value;
+}
diff --git a/lib/src/core/core.dart b/lib/src/core/core.dart
index 330f3b6..ed8a5d1 100644
--- a/lib/src/core/core.dart
+++ b/lib/src/core/core.dart
@@ -3,6 +3,7 @@ import 'dart:collection';
 import 'package:rive/src/rive_core/runtime/exceptions/rive_format_error_exception.dart';
 export 'package:rive/src/animation_list.dart';
 export 'package:rive/src/state_machine_components.dart';
+export 'package:rive/src/blend_animations.dart';
 export 'package:rive/src/state_transition_conditions.dart';
 export 'package:rive/src/state_transitions.dart';
 export 'package:rive/src/container_children.dart';
diff --git a/lib/src/core/importers/layer_state_importer.dart b/lib/src/core/importers/layer_state_importer.dart
index e06088c..3dd8e8f 100644
--- a/lib/src/core/importers/layer_state_importer.dart
+++ b/lib/src/core/importers/layer_state_importer.dart
@@ -1,4 +1,8 @@
 import 'package:rive/src/core/importers/artboard_import_stack_object.dart';
+import 'package:rive/src/rive_core/animation/blend_animation.dart';
+import 'package:rive/src/rive_core/animation/blend_state.dart';
+import 'package:rive/src/rive_core/animation/blend_state_direct.dart';
+import 'package:rive/src/rive_core/animation/blend_state_transition.dart';
 import 'package:rive/src/rive_core/animation/layer_state.dart';
 import 'package:rive/src/rive_core/animation/state_transition.dart';
 
@@ -10,4 +14,24 @@ class LayerStateImporter extends ArtboardImportStackObject {
     state.context.addObject(transition);
     state.internalAddTransition(transition);
   }
+
+  bool addBlendAnimation(BlendAnimation blendAnimation) {
+    if (state is BlendStateDirect) {
+      var blendState = state as BlendStateDirect;
+      for (final transition
+          in state.transitions.whereType<BlendStateTransition>()) {
+        if (transition.exitBlendAnimationId >= 0 &&
+            transition.exitBlendAnimationId < blendState.animations.length) {
+          transition.exitBlendAnimation =
+              blendState.animations[transition.exitBlendAnimationId];
+        }
+      }
+    }
+    if (state is BlendState) {
+      (state as BlendState).internalAddAnimation(blendAnimation);
+      return true;
+    }
+
+    return false;
+  }
 }
diff --git a/lib/src/core/importers/state_machine_layer_importer.dart b/lib/src/core/importers/state_machine_layer_importer.dart
index 1c4f56f..fbe7e48 100644
--- a/lib/src/core/importers/state_machine_layer_importer.dart
+++ b/lib/src/core/importers/state_machine_layer_importer.dart
@@ -1,13 +1,11 @@
 import 'package:rive/rive.dart';
 import 'package:rive/src/core/core.dart';
-import 'package:rive/src/rive_core/animation/animation_state.dart';
 import 'package:rive/src/rive_core/animation/layer_state.dart';
 import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
 
 class StateMachineLayerImporter extends ImportStackObject {
   final StateMachineLayer layer;
-  final ArtboardImporter artboardImporter;
-  StateMachineLayerImporter(this.layer, this.artboardImporter);
+  StateMachineLayerImporter(this.layer);
 
   final List<LayerState> importedStates = [];
 
@@ -27,12 +25,6 @@ class StateMachineLayerImporter extends ImportStackObject {
     assert(!_resolved);
     _resolved = true;
     for (final state in importedStates) {
-      if (state is AnimationState) {
-        int artboardAnimationIndex = state.animationId;
-        assert(artboardAnimationIndex >= 0 &&
-            artboardAnimationIndex < artboardImporter.animations.length);
-        state.animation = artboardImporter.animations[artboardAnimationIndex];
-      }
       for (final transition in state.transitions) {
         // At import time the stateToId is an index relative to the entire layer
         // (which state in this layer). We can use that to find the matching
diff --git a/lib/src/generated/animation/blend_animation_1d_base.dart b/lib/src/generated/animation/blend_animation_1d_base.dart
new file mode 100644
index 0000000..1c5a102
--- /dev/null
+++ b/lib/src/generated/animation/blend_animation_1d_base.dart
@@ -0,0 +1,37 @@
+/// Core automatically generated
+/// lib/src/generated/animation/blend_animation_1d_base.dart.
+/// Do not modify manually.
+
+import 'package:rive/src/generated/animation/blend_animation_base.dart';
+import 'package:rive/src/rive_core/animation/blend_animation.dart';
+
+abstract class BlendAnimation1DBase extends BlendAnimation {
+  static const int typeKey = 75;
+  @override
+  int get coreType => BlendAnimation1DBase.typeKey;
+  @override
+  Set<int> get coreTypes =>
+      {BlendAnimation1DBase.typeKey, BlendAnimationBase.typeKey};
+
+  /// --------------------------------------------------------------------------
+  /// Value field with key 166.
+  static const double valueInitialValue = 0;
+  double _value = valueInitialValue;
+  static const int valuePropertyKey = 166;
+  double get value => _value;
+
+  /// Change the [_value] field value.
+  /// [valueChanged] will be invoked only if the field's value has changed.
+  set value(double value) {
+    if (_value == value) {
+      return;
+    }
+    double from = _value;
+    _value = value;
+    if (hasValidated) {
+      valueChanged(from, value);
+    }
+  }
+
+  void valueChanged(double from, double to);
+}
diff --git a/lib/src/generated/animation/blend_animation_base.dart b/lib/src/generated/animation/blend_animation_base.dart
new file mode 100644
index 0000000..984c39b
--- /dev/null
+++ b/lib/src/generated/animation/blend_animation_base.dart
@@ -0,0 +1,38 @@
+/// Core automatically generated
+/// lib/src/generated/animation/blend_animation_base.dart.
+/// Do not modify manually.
+
+import 'package:rive/src/core/core.dart';
+
+abstract class BlendAnimationBase<T extends CoreContext> extends Core<T> {
+  static const int typeKey = 74;
+  @override
+  int get coreType => BlendAnimationBase.typeKey;
+  @override
+  Set<int> get coreTypes => {BlendAnimationBase.typeKey};
+
+  /// --------------------------------------------------------------------------
+  /// AnimationId field with key 165.
+  static const int animationIdInitialValue = -1;
+  int _animationId = animationIdInitialValue;
+  static const int animationIdPropertyKey = 165;
+
+  /// Id of the animation this BlendAnimation references.
+  int get animationId => _animationId;
+
+  /// Change the [_animationId] field value.
+  /// [animationIdChanged] will be invoked only if the field's value has
+  /// changed.
+  set animationId(int value) {
+    if (_animationId == value) {
+      return;
+    }
+    int from = _animationId;
+    _animationId = value;
+    if (hasValidated) {
+      animationIdChanged(from, value);
+    }
+  }
+
+  void animationIdChanged(int from, int to);
+}
diff --git a/lib/src/generated/animation/blend_animation_direct_base.dart b/lib/src/generated/animation/blend_animation_direct_base.dart
new file mode 100644
index 0000000..12feb5b
--- /dev/null
+++ b/lib/src/generated/animation/blend_animation_direct_base.dart
@@ -0,0 +1,39 @@
+/// Core automatically generated
+/// lib/src/generated/animation/blend_animation_direct_base.dart.
+/// Do not modify manually.
+
+import 'package:rive/src/generated/animation/blend_animation_base.dart';
+import 'package:rive/src/rive_core/animation/blend_animation.dart';
+
+abstract class BlendAnimationDirectBase extends BlendAnimation {
+  static const int typeKey = 77;
+  @override
+  int get coreType => BlendAnimationDirectBase.typeKey;
+  @override
+  Set<int> get coreTypes =>
+      {BlendAnimationDirectBase.typeKey, BlendAnimationBase.typeKey};
+
+  /// --------------------------------------------------------------------------
+  /// InputId field with key 168.
+  static const int inputIdInitialValue = -1;
+  int _inputId = inputIdInitialValue;
+  static const int inputIdPropertyKey = 168;
+
+  /// Id of the input that drives the direct mix value for this animation.
+  int get inputId => _inputId;
+
+  /// Change the [_inputId] field value.
+  /// [inputIdChanged] will be invoked only if the field's value has changed.
+  set inputId(int value) {
+    if (_inputId == value) {
+      return;
+    }
+    int from = _inputId;
+    _inputId = value;
+    if (hasValidated) {
+      inputIdChanged(from, value);
+    }
+  }
+
+  void inputIdChanged(int from, int to);
+}
diff --git a/lib/src/generated/animation/blend_state_1d_base.dart b/lib/src/generated/animation/blend_state_1d_base.dart
new file mode 100644
index 0000000..c917de4
--- /dev/null
+++ b/lib/src/generated/animation/blend_state_1d_base.dart
@@ -0,0 +1,46 @@
+/// Core automatically generated
+/// lib/src/generated/animation/blend_state_1d_base.dart.
+/// Do not modify manually.
+
+import 'package:rive/src/generated/animation/blend_state_base.dart';
+import 'package:rive/src/generated/animation/layer_state_base.dart';
+import 'package:rive/src/generated/animation/state_machine_layer_component_base.dart';
+import 'package:rive/src/rive_core/animation/blend_animation_1d.dart';
+import 'package:rive/src/rive_core/animation/blend_state.dart';
+
+abstract class BlendState1DBase extends BlendState<BlendAnimation1D> {
+  static const int typeKey = 76;
+  @override
+  int get coreType => BlendState1DBase.typeKey;
+  @override
+  Set<int> get coreTypes => {
+        BlendState1DBase.typeKey,
+        BlendStateBase.typeKey,
+        LayerStateBase.typeKey,
+        StateMachineLayerComponentBase.typeKey
+      };
+
+  /// --------------------------------------------------------------------------
+  /// InputId field with key 167.
+  static const int inputIdInitialValue = -1;
+  int _inputId = inputIdInitialValue;
+  static const int inputIdPropertyKey = 167;
+
+  /// Id of the input that drives the mix value for this blend state.
+  int get inputId => _inputId;
+
+  /// Change the [_inputId] field value.
+  /// [inputIdChanged] will be invoked only if the field's value has changed.
+  set inputId(int value) {
+    if (_inputId == value) {
+      return;
+    }
+    int from = _inputId;
+    _inputId = value;
+    if (hasValidated) {
+      inputIdChanged(from, value);
+    }
+  }
+
+  void inputIdChanged(int from, int to);
+}
diff --git a/lib/src/generated/animation/blend_state_base.dart b/lib/src/generated/animation/blend_state_base.dart
new file mode 100644
index 0000000..1f97b13
--- /dev/null
+++ b/lib/src/generated/animation/blend_state_base.dart
@@ -0,0 +1,19 @@
+/// Core automatically generated
+/// lib/src/generated/animation/blend_state_base.dart.
+/// Do not modify manually.
+
+import 'package:rive/src/generated/animation/layer_state_base.dart';
+import 'package:rive/src/generated/animation/state_machine_layer_component_base.dart';
+import 'package:rive/src/rive_core/animation/layer_state.dart';
+
+abstract class BlendStateBase extends LayerState {
+  static const int typeKey = 72;
+  @override
+  int get coreType => BlendStateBase.typeKey;
+  @override
+  Set<int> get coreTypes => {
+        BlendStateBase.typeKey,
+        LayerStateBase.typeKey,
+        StateMachineLayerComponentBase.typeKey
+      };
+}
diff --git a/lib/src/generated/animation/blend_state_direct_base.dart b/lib/src/generated/animation/blend_state_direct_base.dart
new file mode 100644
index 0000000..684af45
--- /dev/null
+++ b/lib/src/generated/animation/blend_state_direct_base.dart
@@ -0,0 +1,22 @@
+/// Core automatically generated
+/// lib/src/generated/animation/blend_state_direct_base.dart.
+/// Do not modify manually.
+
+import 'package:rive/src/generated/animation/blend_state_base.dart';
+import 'package:rive/src/generated/animation/layer_state_base.dart';
+import 'package:rive/src/generated/animation/state_machine_layer_component_base.dart';
+import 'package:rive/src/rive_core/animation/blend_animation_direct.dart';
+import 'package:rive/src/rive_core/animation/blend_state.dart';
+
+abstract class BlendStateDirectBase extends BlendState<BlendAnimationDirect> {
+  static const int typeKey = 73;
+  @override
+  int get coreType => BlendStateDirectBase.typeKey;
+  @override
+  Set<int> get coreTypes => {
+        BlendStateDirectBase.typeKey,
+        BlendStateBase.typeKey,
+        LayerStateBase.typeKey,
+        StateMachineLayerComponentBase.typeKey
+      };
+}
diff --git a/lib/src/generated/animation/blend_state_transition_base.dart b/lib/src/generated/animation/blend_state_transition_base.dart
new file mode 100644
index 0000000..8d11f38
--- /dev/null
+++ b/lib/src/generated/animation/blend_state_transition_base.dart
@@ -0,0 +1,44 @@
+/// Core automatically generated
+/// lib/src/generated/animation/blend_state_transition_base.dart.
+/// Do not modify manually.
+
+import 'package:rive/src/generated/animation/state_machine_layer_component_base.dart';
+import 'package:rive/src/generated/animation/state_transition_base.dart';
+import 'package:rive/src/rive_core/animation/state_transition.dart';
+
+abstract class BlendStateTransitionBase extends StateTransition {
+  static const int typeKey = 78;
+  @override
+  int get coreType => BlendStateTransitionBase.typeKey;
+  @override
+  Set<int> get coreTypes => {
+        BlendStateTransitionBase.typeKey,
+        StateTransitionBase.typeKey,
+        StateMachineLayerComponentBase.typeKey
+      };
+
+  /// --------------------------------------------------------------------------
+  /// ExitBlendAnimationId field with key 171.
+  static const int exitBlendAnimationIdInitialValue = -1;
+  int _exitBlendAnimationId = exitBlendAnimationIdInitialValue;
+  static const int exitBlendAnimationIdPropertyKey = 171;
+
+  /// Id of the state the blend state animation used for exit time calculation.
+  int get exitBlendAnimationId => _exitBlendAnimationId;
+
+  /// Change the [_exitBlendAnimationId] field value.
+  /// [exitBlendAnimationIdChanged] will be invoked only if the field's value
+  /// has changed.
+  set exitBlendAnimationId(int value) {
+    if (_exitBlendAnimationId == value) {
+      return;
+    }
+    int from = _exitBlendAnimationId;
+    _exitBlendAnimationId = value;
+    if (hasValidated) {
+      exitBlendAnimationIdChanged(from, value);
+    }
+  }
+
+  void exitBlendAnimationIdChanged(int from, int to);
+}
diff --git a/lib/src/generated/rive_core_context.dart b/lib/src/generated/rive_core_context.dart
index fb6dd91..7645d98 100644
--- a/lib/src/generated/rive_core_context.dart
+++ b/lib/src/generated/rive_core_context.dart
@@ -8,6 +8,12 @@ import 'package:rive/src/core/field_types/core_uint_type.dart';
 import 'package:rive/src/generated/animation/animation_base.dart';
 import 'package:rive/src/generated/animation/animation_state_base.dart';
 import 'package:rive/src/generated/animation/any_state_base.dart';
+import 'package:rive/src/generated/animation/blend_animation_1d_base.dart';
+import 'package:rive/src/generated/animation/blend_animation_base.dart';
+import 'package:rive/src/generated/animation/blend_animation_direct_base.dart';
+import 'package:rive/src/generated/animation/blend_state_1d_base.dart';
+import 'package:rive/src/generated/animation/blend_state_direct_base.dart';
+import 'package:rive/src/generated/animation/blend_state_transition_base.dart';
 import 'package:rive/src/generated/animation/cubic_interpolator_base.dart';
 import 'package:rive/src/generated/animation/entry_state_base.dart';
 import 'package:rive/src/generated/animation/exit_state_base.dart';
@@ -70,6 +76,11 @@ import 'package:rive/src/generated/transform_component_base.dart';
 import 'package:rive/src/rive_core/animation/animation.dart';
 import 'package:rive/src/rive_core/animation/animation_state.dart';
 import 'package:rive/src/rive_core/animation/any_state.dart';
+import 'package:rive/src/rive_core/animation/blend_animation_1d.dart';
+import 'package:rive/src/rive_core/animation/blend_animation_direct.dart';
+import 'package:rive/src/rive_core/animation/blend_state_1d.dart';
+import 'package:rive/src/rive_core/animation/blend_state_direct.dart';
+import 'package:rive/src/rive_core/animation/blend_state_transition.dart';
 import 'package:rive/src/rive_core/animation/cubic_interpolator.dart';
 import 'package:rive/src/rive_core/animation/entry_state.dart';
 import 'package:rive/src/rive_core/animation/exit_state.dart';
@@ -129,6 +140,8 @@ class RiveCoreContext {
         return AnimationState();
       case KeyedObjectBase.typeKey:
         return KeyedObject();
+      case BlendAnimationDirectBase.typeKey:
+        return BlendAnimationDirect();
       case StateMachineNumberBase.typeKey:
         return StateMachineNumber();
       case TransitionTriggerConditionBase.typeKey:
@@ -161,10 +174,18 @@ class RiveCoreContext {
         return LinearAnimation();
       case StateMachineTriggerBase.typeKey:
         return StateMachineTrigger();
+      case BlendStateDirectBase.typeKey:
+        return BlendStateDirect();
       case ExitStateBase.typeKey:
         return ExitState();
+      case BlendAnimation1DBase.typeKey:
+        return BlendAnimation1D();
+      case BlendState1DBase.typeKey:
+        return BlendState1D();
       case TransitionBoolConditionBase.typeKey:
         return TransitionBoolCondition();
+      case BlendStateTransitionBase.typeKey:
+        return BlendStateTransition();
       case StateMachineBoolBase.typeKey:
         return StateMachineBool();
       case LinearGradientBase.typeKey:
@@ -262,6 +283,16 @@ class RiveCoreContext {
           object.objectId = value;
         }
         break;
+      case BlendAnimationBase.animationIdPropertyKey:
+        if (object is BlendAnimationBase && value is int) {
+          object.animationId = value;
+        }
+        break;
+      case BlendAnimationDirectBase.inputIdPropertyKey:
+        if (object is BlendAnimationDirectBase && value is int) {
+          object.inputId = value;
+        }
+        break;
       case StateMachineComponentBase.namePropertyKey:
         if (object is StateMachineComponentBase && value is String) {
           object.name = value;
@@ -402,6 +433,21 @@ class RiveCoreContext {
           object.enableWorkArea = value;
         }
         break;
+      case BlendAnimation1DBase.valuePropertyKey:
+        if (object is BlendAnimation1DBase && value is double) {
+          object.value = value;
+        }
+        break;
+      case BlendState1DBase.inputIdPropertyKey:
+        if (object is BlendState1DBase && value is int) {
+          object.inputId = value;
+        }
+        break;
+      case BlendStateTransitionBase.exitBlendAnimationIdPropertyKey:
+        if (object is BlendStateTransitionBase && value is int) {
+          object.exitBlendAnimationId = value;
+        }
+        break;
       case StateMachineBoolBase.valuePropertyKey:
         if (object is StateMachineBoolBase && value is bool) {
           object.value = value;
@@ -846,6 +892,8 @@ class RiveCoreContext {
       case DrawTargetBase.placementValuePropertyKey:
       case AnimationStateBase.animationIdPropertyKey:
       case KeyedObjectBase.objectIdPropertyKey:
+      case BlendAnimationBase.animationIdPropertyKey:
+      case BlendAnimationDirectBase.inputIdPropertyKey:
       case TransitionConditionBase.inputIdPropertyKey:
       case KeyedPropertyBase.propertyKeyPropertyKey:
       case KeyFrameBase.framePropertyKey:
@@ -862,6 +910,8 @@ class RiveCoreContext {
       case LinearAnimationBase.loopValuePropertyKey:
       case LinearAnimationBase.workStartPropertyKey:
       case LinearAnimationBase.workEndPropertyKey:
+      case BlendState1DBase.inputIdPropertyKey:
+      case BlendStateTransitionBase.exitBlendAnimationIdPropertyKey:
       case StrokeBase.capPropertyKey:
       case StrokeBase.joinPropertyKey:
       case TrimPathBase.modeValuePropertyKey:
@@ -889,6 +939,7 @@ class RiveCoreContext {
       case CubicInterpolatorBase.y2PropertyKey:
       case KeyFrameDoubleBase.valuePropertyKey:
       case LinearAnimationBase.speedPropertyKey:
+      case BlendAnimation1DBase.valuePropertyKey:
       case LinearGradientBase.startXPropertyKey:
       case LinearGradientBase.startYPropertyKey:
       case LinearGradientBase.endXPropertyKey:
@@ -990,6 +1041,10 @@ class RiveCoreContext {
         return (object as AnimationStateBase).animationId;
       case KeyedObjectBase.objectIdPropertyKey:
         return (object as KeyedObjectBase).objectId;
+      case BlendAnimationBase.animationIdPropertyKey:
+        return (object as BlendAnimationBase).animationId;
+      case BlendAnimationDirectBase.inputIdPropertyKey:
+        return (object as BlendAnimationDirectBase).inputId;
       case TransitionConditionBase.inputIdPropertyKey:
         return (object as TransitionConditionBase).inputId;
       case KeyedPropertyBase.propertyKeyPropertyKey:
@@ -1022,6 +1077,10 @@ class RiveCoreContext {
         return (object as LinearAnimationBase).workStart;
       case LinearAnimationBase.workEndPropertyKey:
         return (object as LinearAnimationBase).workEnd;
+      case BlendState1DBase.inputIdPropertyKey:
+        return (object as BlendState1DBase).inputId;
+      case BlendStateTransitionBase.exitBlendAnimationIdPropertyKey:
+        return (object as BlendStateTransitionBase).exitBlendAnimationId;
       case StrokeBase.capPropertyKey:
         return (object as StrokeBase).cap;
       case StrokeBase.joinPropertyKey:
@@ -1080,6 +1139,8 @@ class RiveCoreContext {
         return (object as KeyFrameDoubleBase).value;
       case LinearAnimationBase.speedPropertyKey:
         return (object as LinearAnimationBase).speed;
+      case BlendAnimation1DBase.valuePropertyKey:
+        return (object as BlendAnimation1DBase).value;
       case LinearGradientBase.startXPropertyKey:
         return (object as LinearGradientBase).startX;
       case LinearGradientBase.startYPropertyKey:
@@ -1237,13 +1298,19 @@ class RiveCoreContext {
   static void setString(Core object, int propertyKey, String value) {
     switch (propertyKey) {
       case ComponentBase.namePropertyKey:
-        if (object is ComponentBase) object.name = value;
+        if (object is ComponentBase) {
+          object.name = value;
+        }
         break;
       case StateMachineComponentBase.namePropertyKey:
-        if (object is StateMachineComponentBase) object.name = value;
+        if (object is StateMachineComponentBase) {
+          object.name = value;
+        }
         break;
       case AnimationBase.namePropertyKey:
-        if (object is AnimationBase) object.name = value;
+        if (object is AnimationBase) {
+          object.name = value;
+        }
         break;
     }
   }
@@ -1251,121 +1318,219 @@ class RiveCoreContext {
   static void setUint(Core object, int propertyKey, int value) {
     switch (propertyKey) {
       case ComponentBase.parentIdPropertyKey:
-        if (object is ComponentBase) object.parentId = value;
+        if (object is ComponentBase) {
+          object.parentId = value;
+        }
         break;
       case DrawTargetBase.drawableIdPropertyKey:
-        if (object is DrawTargetBase) object.drawableId = value;
+        if (object is DrawTargetBase) {
+          object.drawableId = value;
+        }
         break;
       case DrawTargetBase.placementValuePropertyKey:
-        if (object is DrawTargetBase) object.placementValue = value;
+        if (object is DrawTargetBase) {
+          object.placementValue = value;
+        }
         break;
       case AnimationStateBase.animationIdPropertyKey:
-        if (object is AnimationStateBase) object.animationId = value;
+        if (object is AnimationStateBase) {
+          object.animationId = value;
+        }
         break;
       case KeyedObjectBase.objectIdPropertyKey:
-        if (object is KeyedObjectBase) object.objectId = value;
+        if (object is KeyedObjectBase) {
+          object.objectId = value;
+        }
+        break;
+      case BlendAnimationBase.animationIdPropertyKey:
+        if (object is BlendAnimationBase) {
+          object.animationId = value;
+        }
+        break;
+      case BlendAnimationDirectBase.inputIdPropertyKey:
+        if (object is BlendAnimationDirectBase) {
+          object.inputId = value;
+        }
         break;
       case TransitionConditionBase.inputIdPropertyKey:
-        if (object is TransitionConditionBase) object.inputId = value;
+        if (object is TransitionConditionBase) {
+          object.inputId = value;
+        }
         break;
       case KeyedPropertyBase.propertyKeyPropertyKey:
-        if (object is KeyedPropertyBase) object.propertyKey = value;
+        if (object is KeyedPropertyBase) {
+          object.propertyKey = value;
+        }
         break;
       case KeyFrameBase.framePropertyKey:
-        if (object is KeyFrameBase) object.frame = value;
+        if (object is KeyFrameBase) {
+          object.frame = value;
+        }
         break;
       case KeyFrameBase.interpolationTypePropertyKey:
-        if (object is KeyFrameBase) object.interpolationType = value;
+        if (object is KeyFrameBase) {
+          object.interpolationType = value;
+        }
         break;
       case KeyFrameBase.interpolatorIdPropertyKey:
-        if (object is KeyFrameBase) object.interpolatorId = value;
+        if (object is KeyFrameBase) {
+          object.interpolatorId = value;
+        }
         break;
       case KeyFrameIdBase.valuePropertyKey:
-        if (object is KeyFrameIdBase) object.value = value;
+        if (object is KeyFrameIdBase) {
+          object.value = value;
+        }
         break;
       case TransitionValueConditionBase.opValuePropertyKey:
-        if (object is TransitionValueConditionBase) object.opValue = value;
+        if (object is TransitionValueConditionBase) {
+          object.opValue = value;
+        }
         break;
       case StateTransitionBase.stateToIdPropertyKey:
-        if (object is StateTransitionBase) object.stateToId = value;
+        if (object is StateTransitionBase) {
+          object.stateToId = value;
+        }
         break;
       case StateTransitionBase.flagsPropertyKey:
-        if (object is StateTransitionBase) object.flags = value;
+        if (object is StateTransitionBase) {
+          object.flags = value;
+        }
         break;
       case StateTransitionBase.durationPropertyKey:
-        if (object is StateTransitionBase) object.duration = value;
+        if (object is StateTransitionBase) {
+          object.duration = value;
+        }
         break;
       case StateTransitionBase.exitTimePropertyKey:
-        if (object is StateTransitionBase) object.exitTime = value;
+        if (object is StateTransitionBase) {
+          object.exitTime = value;
+        }
         break;
       case LinearAnimationBase.fpsPropertyKey:
-        if (object is LinearAnimationBase) object.fps = value;
+        if (object is LinearAnimationBase) {
+          object.fps = value;
+        }
         break;
       case LinearAnimationBase.durationPropertyKey:
-        if (object is LinearAnimationBase) object.duration = value;
+        if (object is LinearAnimationBase) {
+          object.duration = value;
+        }
         break;
       case LinearAnimationBase.loopValuePropertyKey:
-        if (object is LinearAnimationBase) object.loopValue = value;
+        if (object is LinearAnimationBase) {
+          object.loopValue = value;
+        }
         break;
       case LinearAnimationBase.workStartPropertyKey:
-        if (object is LinearAnimationBase) object.workStart = value;
+        if (object is LinearAnimationBase) {
+          object.workStart = value;
+        }
         break;
       case LinearAnimationBase.workEndPropertyKey:
-        if (object is LinearAnimationBase) object.workEnd = value;
+        if (object is LinearAnimationBase) {
+          object.workEnd = value;
+        }
+        break;
+      case BlendState1DBase.inputIdPropertyKey:
+        if (object is BlendState1DBase) {
+          object.inputId = value;
+        }
+        break;
+      case BlendStateTransitionBase.exitBlendAnimationIdPropertyKey:
+        if (object is BlendStateTransitionBase) {
+          object.exitBlendAnimationId = value;
+        }
         break;
       case StrokeBase.capPropertyKey:
-        if (object is StrokeBase) object.cap = value;
+        if (object is StrokeBase) {
+          object.cap = value;
+        }
         break;
       case StrokeBase.joinPropertyKey:
-        if (object is StrokeBase) object.join = value;
+        if (object is StrokeBase) {
+          object.join = value;
+        }
         break;
       case TrimPathBase.modeValuePropertyKey:
-        if (object is TrimPathBase) object.modeValue = value;
+        if (object is TrimPathBase) {
+          object.modeValue = value;
+        }
         break;
       case FillBase.fillRulePropertyKey:
-        if (object is FillBase) object.fillRule = value;
+        if (object is FillBase) {
+          object.fillRule = value;
+        }
         break;
       case PathBase.pathFlagsPropertyKey:
-        if (object is PathBase) object.pathFlags = value;
+        if (object is PathBase) {
+          object.pathFlags = value;
+        }
         break;
       case DrawableBase.blendModeValuePropertyKey:
-        if (object is DrawableBase) object.blendModeValue = value;
+        if (object is DrawableBase) {
+          object.blendModeValue = value;
+        }
         break;
       case DrawableBase.drawableFlagsPropertyKey:
-        if (object is DrawableBase) object.drawableFlags = value;
+        if (object is DrawableBase) {
+          object.drawableFlags = value;
+        }
         break;
       case WeightBase.valuesPropertyKey:
-        if (object is WeightBase) object.values = value;
+        if (object is WeightBase) {
+          object.values = value;
+        }
         break;
       case WeightBase.indicesPropertyKey:
-        if (object is WeightBase) object.indices = value;
+        if (object is WeightBase) {
+          object.indices = value;
+        }
         break;
       case CubicWeightBase.inValuesPropertyKey:
-        if (object is CubicWeightBase) object.inValues = value;
+        if (object is CubicWeightBase) {
+          object.inValues = value;
+        }
         break;
       case CubicWeightBase.inIndicesPropertyKey:
-        if (object is CubicWeightBase) object.inIndices = value;
+        if (object is CubicWeightBase) {
+          object.inIndices = value;
+        }
         break;
       case CubicWeightBase.outValuesPropertyKey:
-        if (object is CubicWeightBase) object.outValues = value;
+        if (object is CubicWeightBase) {
+          object.outValues = value;
+        }
         break;
       case CubicWeightBase.outIndicesPropertyKey:
-        if (object is CubicWeightBase) object.outIndices = value;
+        if (object is CubicWeightBase) {
+          object.outIndices = value;
+        }
         break;
       case ClippingShapeBase.sourceIdPropertyKey:
-        if (object is ClippingShapeBase) object.sourceId = value;
+        if (object is ClippingShapeBase) {
+          object.sourceId = value;
+        }
         break;
       case ClippingShapeBase.fillRulePropertyKey:
-        if (object is ClippingShapeBase) object.fillRule = value;
+        if (object is ClippingShapeBase) {
+          object.fillRule = value;
+        }
         break;
       case PolygonBase.pointsPropertyKey:
-        if (object is PolygonBase) object.points = value;
+        if (object is PolygonBase) {
+          object.points = value;
+        }
         break;
       case DrawRulesBase.drawTargetIdPropertyKey:
-        if (object is DrawRulesBase) object.drawTargetId = value;
+        if (object is DrawRulesBase) {
+          object.drawTargetId = value;
+        }
         break;
       case TendonBase.boneIdPropertyKey:
-        if (object is TendonBase) object.boneId = value;
+        if (object is TendonBase) {
+          object.boneId = value;
+        }
         break;
     }
   }
@@ -1373,205 +1538,344 @@ class RiveCoreContext {
   static void setDouble(Core object, int propertyKey, double value) {
     switch (propertyKey) {
       case StateMachineNumberBase.valuePropertyKey:
-        if (object is StateMachineNumberBase) object.value = value;
+        if (object is StateMachineNumberBase) {
+          object.value = value;
+        }
         break;
       case TransitionNumberConditionBase.valuePropertyKey:
-        if (object is TransitionNumberConditionBase) object.value = value;
+        if (object is TransitionNumberConditionBase) {
+          object.value = value;
+        }
         break;
       case CubicInterpolatorBase.x1PropertyKey:
-        if (object is CubicInterpolatorBase) object.x1 = value;
+        if (object is CubicInterpolatorBase) {
+          object.x1 = value;
+        }
         break;
       case CubicInterpolatorBase.y1PropertyKey:
-        if (object is CubicInterpolatorBase) object.y1 = value;
+        if (object is CubicInterpolatorBase) {
+          object.y1 = value;
+        }
         break;
       case CubicInterpolatorBase.x2PropertyKey:
-        if (object is CubicInterpolatorBase) object.x2 = value;
+        if (object is CubicInterpolatorBase) {
+          object.x2 = value;
+        }
         break;
       case CubicInterpolatorBase.y2PropertyKey:
-        if (object is CubicInterpolatorBase) object.y2 = value;
+        if (object is CubicInterpolatorBase) {
+          object.y2 = value;
+        }
         break;
       case KeyFrameDoubleBase.valuePropertyKey:
-        if (object is KeyFrameDoubleBase) object.value = value;
+        if (object is KeyFrameDoubleBase) {
+          object.value = value;
+        }
         break;
       case LinearAnimationBase.speedPropertyKey:
-        if (object is LinearAnimationBase) object.speed = value;
+        if (object is LinearAnimationBase) {
+          object.speed = value;
+        }
+        break;
+      case BlendAnimation1DBase.valuePropertyKey:
+        if (object is BlendAnimation1DBase) {
+          object.value = value;
+        }
         break;
       case LinearGradientBase.startXPropertyKey:
-        if (object is LinearGradientBase) object.startX = value;
+        if (object is LinearGradientBase) {
+          object.startX = value;
+        }
         break;
       case LinearGradientBase.startYPropertyKey:
-        if (object is LinearGradientBase) object.startY = value;
+        if (object is LinearGradientBase) {
+          object.startY = value;
+        }
         break;
       case LinearGradientBase.endXPropertyKey:
-        if (object is LinearGradientBase) object.endX = value;
+        if (object is LinearGradientBase) {
+          object.endX = value;
+        }
         break;
       case LinearGradientBase.endYPropertyKey:
-        if (object is LinearGradientBase) object.endY = value;
+        if (object is LinearGradientBase) {
+          object.endY = value;
+        }
         break;
       case LinearGradientBase.opacityPropertyKey:
-        if (object is LinearGradientBase) object.opacity = value;
+        if (object is LinearGradientBase) {
+          object.opacity = value;
+        }
         break;
       case StrokeBase.thicknessPropertyKey:
-        if (object is StrokeBase) object.thickness = value;
+        if (object is StrokeBase) {
+          object.thickness = value;
+        }
         break;
       case GradientStopBase.positionPropertyKey:
-        if (object is GradientStopBase) object.position = value;
+        if (object is GradientStopBase) {
+          object.position = value;
+        }
         break;
       case TrimPathBase.startPropertyKey:
-        if (object is TrimPathBase) object.start = value;
+        if (object is TrimPathBase) {
+          object.start = value;
+        }
         break;
       case TrimPathBase.endPropertyKey:
-        if (object is TrimPathBase) object.end = value;
+        if (object is TrimPathBase) {
+          object.end = value;
+        }
         break;
       case TrimPathBase.offsetPropertyKey:
-        if (object is TrimPathBase) object.offset = value;
+        if (object is TrimPathBase) {
+          object.offset = value;
+        }
         break;
       case TransformComponentBase.rotationPropertyKey:
-        if (object is TransformComponentBase) object.rotation = value;
+        if (object is TransformComponentBase) {
+          object.rotation = value;
+        }
         break;
       case TransformComponentBase.scaleXPropertyKey:
-        if (object is TransformComponentBase) object.scaleX = value;
+        if (object is TransformComponentBase) {
+          object.scaleX = value;
+        }
         break;
       case TransformComponentBase.scaleYPropertyKey:
-        if (object is TransformComponentBase) object.scaleY = value;
+        if (object is TransformComponentBase) {
+          object.scaleY = value;
+        }
         break;
       case TransformComponentBase.opacityPropertyKey:
-        if (object is TransformComponentBase) object.opacity = value;
+        if (object is TransformComponentBase) {
+          object.opacity = value;
+        }
         break;
       case NodeBase.xPropertyKey:
-        if (object is NodeBase) object.x = value;
+        if (object is NodeBase) {
+          object.x = value;
+        }
         break;
       case NodeBase.yPropertyKey:
-        if (object is NodeBase) object.y = value;
+        if (object is NodeBase) {
+          object.y = value;
+        }
         break;
       case PathVertexBase.xPropertyKey:
-        if (object is PathVertexBase) object.x = value;
+        if (object is PathVertexBase) {
+          object.x = value;
+        }
         break;
       case PathVertexBase.yPropertyKey:
-        if (object is PathVertexBase) object.y = value;
+        if (object is PathVertexBase) {
+          object.y = value;
+        }
         break;
       case StraightVertexBase.radiusPropertyKey:
-        if (object is StraightVertexBase) object.radius = value;
+        if (object is StraightVertexBase) {
+          object.radius = value;
+        }
         break;
       case CubicAsymmetricVertexBase.rotationPropertyKey:
-        if (object is CubicAsymmetricVertexBase) object.rotation = value;
+        if (object is CubicAsymmetricVertexBase) {
+          object.rotation = value;
+        }
         break;
       case CubicAsymmetricVertexBase.inDistancePropertyKey:
-        if (object is CubicAsymmetricVertexBase) object.inDistance = value;
+        if (object is CubicAsymmetricVertexBase) {
+          object.inDistance = value;
+        }
         break;
       case CubicAsymmetricVertexBase.outDistancePropertyKey:
-        if (object is CubicAsymmetricVertexBase) object.outDistance = value;
+        if (object is CubicAsymmetricVertexBase) {
+          object.outDistance = value;
+        }
         break;
       case ParametricPathBase.widthPropertyKey:
-        if (object is ParametricPathBase) object.width = value;
+        if (object is ParametricPathBase) {
+          object.width = value;
+        }
         break;
       case ParametricPathBase.heightPropertyKey:
-        if (object is ParametricPathBase) object.height = value;
+        if (object is ParametricPathBase) {
+          object.height = value;
+        }
         break;
       case ParametricPathBase.originXPropertyKey:
-        if (object is ParametricPathBase) object.originX = value;
+        if (object is ParametricPathBase) {
+          object.originX = value;
+        }
         break;
       case ParametricPathBase.originYPropertyKey:
-        if (object is ParametricPathBase) object.originY = value;
+        if (object is ParametricPathBase) {
+          object.originY = value;
+        }
         break;
       case RectangleBase.cornerRadiusTLPropertyKey:
-        if (object is RectangleBase) object.cornerRadiusTL = value;
+        if (object is RectangleBase) {
+          object.cornerRadiusTL = value;
+        }
         break;
       case RectangleBase.cornerRadiusTRPropertyKey:
-        if (object is RectangleBase) object.cornerRadiusTR = value;
+        if (object is RectangleBase) {
+          object.cornerRadiusTR = value;
+        }
         break;
       case RectangleBase.cornerRadiusBLPropertyKey:
-        if (object is RectangleBase) object.cornerRadiusBL = value;
+        if (object is RectangleBase) {
+          object.cornerRadiusBL = value;
+        }
         break;
       case RectangleBase.cornerRadiusBRPropertyKey:
-        if (object is RectangleBase) object.cornerRadiusBR = value;
+        if (object is RectangleBase) {
+          object.cornerRadiusBR = value;
+        }
         break;
       case CubicMirroredVertexBase.rotationPropertyKey:
-        if (object is CubicMirroredVertexBase) object.rotation = value;
+        if (object is CubicMirroredVertexBase) {
+          object.rotation = value;
+        }
         break;
       case CubicMirroredVertexBase.distancePropertyKey:
-        if (object is CubicMirroredVertexBase) object.distance = value;
+        if (object is CubicMirroredVertexBase) {
+          object.distance = value;
+        }
         break;
       case PolygonBase.cornerRadiusPropertyKey:
-        if (object is PolygonBase) object.cornerRadius = value;
+        if (object is PolygonBase) {
+          object.cornerRadius = value;
+        }
         break;
       case StarBase.innerRadiusPropertyKey:
-        if (object is StarBase) object.innerRadius = value;
+        if (object is StarBase) {
+          object.innerRadius = value;
+        }
         break;
       case CubicDetachedVertexBase.inRotationPropertyKey:
-        if (object is CubicDetachedVertexBase) object.inRotation = value;
+        if (object is CubicDetachedVertexBase) {
+          object.inRotation = value;
+        }
         break;
       case CubicDetachedVertexBase.inDistancePropertyKey:
-        if (object is CubicDetachedVertexBase) object.inDistance = value;
+        if (object is CubicDetachedVertexBase) {
+          object.inDistance = value;
+        }
         break;
       case CubicDetachedVertexBase.outRotationPropertyKey:
-        if (object is CubicDetachedVertexBase) object.outRotation = value;
+        if (object is CubicDetachedVertexBase) {
+          object.outRotation = value;
+        }
         break;
       case CubicDetachedVertexBase.outDistancePropertyKey:
-        if (object is CubicDetachedVertexBase) object.outDistance = value;
+        if (object is CubicDetachedVertexBase) {
+          object.outDistance = value;
+        }
         break;
       case ArtboardBase.widthPropertyKey:
-        if (object is ArtboardBase) object.width = value;
+        if (object is ArtboardBase) {
+          object.width = value;
+        }
         break;
       case ArtboardBase.heightPropertyKey:
-        if (object is ArtboardBase) object.height = value;
+        if (object is ArtboardBase) {
+          object.height = value;
+        }
         break;
       case ArtboardBase.xPropertyKey:
-        if (object is ArtboardBase) object.x = value;
+        if (object is ArtboardBase) {
+          object.x = value;
+        }
         break;
       case ArtboardBase.yPropertyKey:
-        if (object is ArtboardBase) object.y = value;
+        if (object is ArtboardBase) {
+          object.y = value;
+        }
         break;
       case ArtboardBase.originXPropertyKey:
-        if (object is ArtboardBase) object.originX = value;
+        if (object is ArtboardBase) {
+          object.originX = value;
+        }
         break;
       case ArtboardBase.originYPropertyKey:
-        if (object is ArtboardBase) object.originY = value;
+        if (object is ArtboardBase) {
+          object.originY = value;
+        }
         break;
       case BoneBase.lengthPropertyKey:
-        if (object is BoneBase) object.length = value;
+        if (object is BoneBase) {
+          object.length = value;
+        }
         break;
       case RootBoneBase.xPropertyKey:
-        if (object is RootBoneBase) object.x = value;
+        if (object is RootBoneBase) {
+          object.x = value;
+        }
         break;
       case RootBoneBase.yPropertyKey:
-        if (object is RootBoneBase) object.y = value;
+        if (object is RootBoneBase) {
+          object.y = value;
+        }
         break;
       case SkinBase.xxPropertyKey:
-        if (object is SkinBase) object.xx = value;
+        if (object is SkinBase) {
+          object.xx = value;
+        }
         break;
       case SkinBase.yxPropertyKey:
-        if (object is SkinBase) object.yx = value;
+        if (object is SkinBase) {
+          object.yx = value;
+        }
         break;
       case SkinBase.xyPropertyKey:
-        if (object is SkinBase) object.xy = value;
+        if (object is SkinBase) {
+          object.xy = value;
+        }
         break;
       case SkinBase.yyPropertyKey:
-        if (object is SkinBase) object.yy = value;
+        if (object is SkinBase) {
+          object.yy = value;
+        }
         break;
       case SkinBase.txPropertyKey:
-        if (object is SkinBase) object.tx = value;
+        if (object is SkinBase) {
+          object.tx = value;
+        }
         break;
       case SkinBase.tyPropertyKey:
-        if (object is SkinBase) object.ty = value;
+        if (object is SkinBase) {
+          object.ty = value;
+        }
         break;
       case TendonBase.xxPropertyKey:
-        if (object is TendonBase) object.xx = value;
+        if (object is TendonBase) {
+          object.xx = value;
+        }
         break;
       case TendonBase.yxPropertyKey:
-        if (object is TendonBase) object.yx = value;
+        if (object is TendonBase) {
+          object.yx = value;
+        }
         break;
       case TendonBase.xyPropertyKey:
-        if (object is TendonBase) object.xy = value;
+        if (object is TendonBase) {
+          object.xy = value;
+        }
         break;
       case TendonBase.yyPropertyKey:
-        if (object is TendonBase) object.yy = value;
+        if (object is TendonBase) {
+          object.yy = value;
+        }
         break;
       case TendonBase.txPropertyKey:
-        if (object is TendonBase) object.tx = value;
+        if (object is TendonBase) {
+          object.tx = value;
+        }
         break;
       case TendonBase.tyPropertyKey:
-        if (object is TendonBase) object.ty = value;
+        if (object is TendonBase) {
+          object.ty = value;
+        }
         break;
     }
   }
@@ -1579,13 +1883,19 @@ class RiveCoreContext {
   static void setColor(Core object, int propertyKey, int value) {
     switch (propertyKey) {
       case KeyFrameColorBase.valuePropertyKey:
-        if (object is KeyFrameColorBase) object.value = value;
+        if (object is KeyFrameColorBase) {
+          object.value = value;
+        }
         break;
       case SolidColorBase.colorValuePropertyKey:
-        if (object is SolidColorBase) object.colorValue = value;
+        if (object is SolidColorBase) {
+          object.colorValue = value;
+        }
         break;
       case GradientStopBase.colorValuePropertyKey:
-        if (object is GradientStopBase) object.colorValue = value;
+        if (object is GradientStopBase) {
+          object.colorValue = value;
+        }
         break;
     }
   }
@@ -1593,25 +1903,39 @@ class RiveCoreContext {
   static void setBool(Core object, int propertyKey, bool value) {
     switch (propertyKey) {
       case LinearAnimationBase.enableWorkAreaPropertyKey:
-        if (object is LinearAnimationBase) object.enableWorkArea = value;
+        if (object is LinearAnimationBase) {
+          object.enableWorkArea = value;
+        }
         break;
       case StateMachineBoolBase.valuePropertyKey:
-        if (object is StateMachineBoolBase) object.value = value;
+        if (object is StateMachineBoolBase) {
+          object.value = value;
+        }
         break;
       case ShapePaintBase.isVisiblePropertyKey:
-        if (object is ShapePaintBase) object.isVisible = value;
+        if (object is ShapePaintBase) {
+          object.isVisible = value;
+        }
         break;
       case StrokeBase.transformAffectsStrokePropertyKey:
-        if (object is StrokeBase) object.transformAffectsStroke = value;
+        if (object is StrokeBase) {
+          object.transformAffectsStroke = value;
+        }
         break;
       case PointsPathBase.isClosedPropertyKey:
-        if (object is PointsPathBase) object.isClosed = value;
+        if (object is PointsPathBase) {
+          object.isClosed = value;
+        }
         break;
       case RectangleBase.linkCornerRadiusPropertyKey:
-        if (object is RectangleBase) object.linkCornerRadius = value;
+        if (object is RectangleBase) {
+          object.linkCornerRadius = value;
+        }
         break;
       case ClippingShapeBase.isVisiblePropertyKey:
-        if (object is ClippingShapeBase) object.isVisible = value;
+        if (object is ClippingShapeBase) {
+          object.isVisible = value;
+        }
         break;
     }
   }
diff --git a/lib/src/rive_core/animation/animation.dart b/lib/src/rive_core/animation/animation.dart
index a1afb27..0fabb31 100644
--- a/lib/src/rive_core/animation/animation.dart
+++ b/lib/src/rive_core/animation/animation.dart
@@ -1,5 +1,6 @@
 import 'package:rive/src/core/core.dart';
 import 'package:rive/src/rive_core/artboard.dart';
+
 import 'package:rive/src/generated/animation/animation_base.dart';
 export 'package:rive/src/generated/animation/animation_base.dart';
 
@@ -17,10 +18,13 @@ class Animation extends AnimationBase<RuntimeArtboard> {
 
   @override
   void onAddedDirty() {}
+
   @override
   void onAdded() {}
+
   @override
   bool validate() => super.validate() && _artboard != null;
+
   @override
   void nameChanged(String from, String to) {}
 }
diff --git a/lib/src/rive_core/animation/animation_state.dart b/lib/src/rive_core/animation/animation_state.dart
index 5cb2c3f..f0805fa 100644
--- a/lib/src/rive_core/animation/animation_state.dart
+++ b/lib/src/rive_core/animation/animation_state.dart
@@ -1,5 +1,8 @@
 import 'package:rive/src/core/core.dart';
+import 'package:rive/src/rive_core/animation/animation_state_instance.dart';
 import 'package:rive/src/rive_core/animation/linear_animation.dart';
+import 'package:rive/src/rive_core/animation/state_instance.dart';
+import 'package:rive/src/rive_core/artboard.dart';
 import 'package:rive/src/generated/animation/animation_state_base.dart';
 export 'package:rive/src/generated/animation/animation_state_base.dart';
 
@@ -15,6 +18,7 @@ class AnimationState extends AnimationStateBase {
     if (_animation == value) {
       return;
     }
+
     _animation = value;
     animationId = value?.id ?? Core.missingId;
   }
@@ -23,4 +27,33 @@ class AnimationState extends AnimationStateBase {
   void animationIdChanged(int from, int to) {
     animation = id == Core.missingId ? null : context.resolve(to);
   }
+
+  @override
+  StateInstance makeInstance() {
+    if (animation == null) {
+      // Failed to load at runtime/some new type we don't understand.
+      return SystemStateInstance(this);
+    }
+
+    return AnimationStateInstance(this);
+  }
+
+  // We keep the importer code here so that we can inject this for runtime.
+  // #2690
+  @override
+  bool import(ImportStack stack) {
+    var importer = stack.latest<ArtboardImporter>(ArtboardBase.typeKey);
+    if (importer == null) {
+      return false;
+    }
+
+    if (animationId >= 0 && animationId < importer.artboard.animations.length) {
+      var found = importer.artboard.animations[animationId];
+      if (found is LinearAnimation) {
+        animation = found;
+      }
+    }
+
+    return super.import(stack);
+  }
 }
diff --git a/lib/src/rive_core/animation/animation_state_instance.dart b/lib/src/rive_core/animation/animation_state_instance.dart
new file mode 100644
index 0000000..906fe0b
--- /dev/null
+++ b/lib/src/rive_core/animation/animation_state_instance.dart
@@ -0,0 +1,25 @@
+import 'package:rive/src/core/core.dart';
+import 'package:rive/src/rive_core/animation/animation_state.dart';
+import 'package:rive/src/rive_core/animation/linear_animation_instance.dart';
+import 'package:rive/src/rive_core/animation/state_instance.dart';
+
+/// Simple wrapper around [LinearAnimationInstance] making it compatible with
+/// the [StateMachine]'s [StateInstance] interface.
+class AnimationStateInstance extends StateInstance {
+  final LinearAnimationInstance animationInstance;
+
+  AnimationStateInstance(AnimationState state)
+      : assert(state.animation != null),
+        animationInstance = LinearAnimationInstance(state.animation!),
+        super(state);
+
+  @override
+  void advance(double seconds, _) => animationInstance.advance(seconds);
+
+  @override
+  void apply(CoreContext core, double mix) => animationInstance.animation
+      .apply(animationInstance.time, coreContext: core, mix: mix);
+
+  @override
+  bool get keepGoing => animationInstance.keepGoing;
+}
diff --git a/lib/src/rive_core/animation/any_state.dart b/lib/src/rive_core/animation/any_state.dart
index 116405f..d1d2376 100644
--- a/lib/src/rive_core/animation/any_state.dart
+++ b/lib/src/rive_core/animation/any_state.dart
@@ -1,4 +1,8 @@
+import 'package:rive/src/rive_core/animation/state_instance.dart';
 import 'package:rive/src/generated/animation/any_state_base.dart';
 export 'package:rive/src/generated/animation/any_state_base.dart';
 
-class AnyState extends AnyStateBase {}
+class AnyState extends AnyStateBase {
+  @override
+  StateInstance makeInstance() => SystemStateInstance(this);
+}
diff --git a/lib/src/rive_core/animation/blend_animation.dart b/lib/src/rive_core/animation/blend_animation.dart
new file mode 100644
index 0000000..3b0fee1
--- /dev/null
+++ b/lib/src/rive_core/animation/blend_animation.dart
@@ -0,0 +1,52 @@
+import 'package:rive/src/core/core.dart';
+import 'package:rive/src/rive_core/animation/layer_state.dart';
+import 'package:rive/src/rive_core/animation/linear_animation.dart';
+import 'package:rive/src/rive_core/artboard.dart';
+import 'package:rive/src/generated/animation/blend_animation_base.dart';
+import 'package:rive/src/generated/artboard_base.dart';
+export 'package:rive/src/generated/animation/blend_animation_base.dart';
+
+abstract class BlendAnimation extends BlendAnimationBase {
+  LinearAnimation? _animation;
+  LinearAnimation? get animation => _animation;
+
+  @override
+  void animationIdChanged(int from, int to) {
+    _animation = context.resolve(to);
+  }
+
+  @override
+  void onAdded() {}
+
+  @override
+  void onRemoved() {
+    super.onRemoved();
+  }
+
+  @override
+  void onAddedDirty() {}
+
+  @override
+  bool import(ImportStack importStack) {
+    var importer =
+        importStack.latest<LayerStateImporter>(LayerStateBase.typeKey);
+    if (importer == null || !importer.addBlendAnimation(this)) {
+      return false;
+    }
+    var artboardImporter =
+        importStack.latest<ArtboardImporter>(ArtboardBase.typeKey);
+    if (artboardImporter == null) {
+      return false;
+    }
+
+    if (animationId >= 0 &&
+        animationId < artboardImporter.artboard.animations.length) {
+      var found = artboardImporter.artboard.animations[animationId];
+      if (found is LinearAnimation) {
+        _animation = found;
+      }
+    }
+
+    return super.import(importStack);
+  }
+}
diff --git a/lib/src/rive_core/animation/blend_animation_1d.dart b/lib/src/rive_core/animation/blend_animation_1d.dart
new file mode 100644
index 0000000..99d8a52
--- /dev/null
+++ b/lib/src/rive_core/animation/blend_animation_1d.dart
@@ -0,0 +1,9 @@
+import 'package:rive/src/generated/animation/blend_animation_1d_base.dart';
+export 'package:rive/src/generated/animation/blend_animation_1d_base.dart';
+
+class BlendAnimation1D extends BlendAnimation1DBase {
+  @override
+  void valueChanged(double from, double to) {
+    // TODO: implement valueChanged
+  }
+}
diff --git a/lib/src/rive_core/animation/blend_animation_direct.dart b/lib/src/rive_core/animation/blend_animation_direct.dart
new file mode 100644
index 0000000..8fe9e8e
--- /dev/null
+++ b/lib/src/rive_core/animation/blend_animation_direct.dart
@@ -0,0 +1,30 @@
+import 'package:rive/src/core/core.dart';
+import 'package:rive/src/rive_core/animation/state_machine.dart';
+import 'package:rive/src/rive_core/animation/state_machine_number.dart';
+import 'package:rive/src/generated/animation/blend_animation_direct_base.dart';
+export 'package:rive/src/generated/animation/blend_animation_direct_base.dart';
+
+class BlendAnimationDirect extends BlendAnimationDirectBase {
+  StateMachineNumber? _input;
+  StateMachineNumber? get input => _input;
+
+  @override
+  void inputIdChanged(int from, int to) {}
+
+  @override
+  bool import(ImportStack stack) {
+    var importer = stack.latest<StateMachineImporter>(StateMachineBase.typeKey);
+    if (importer == null) {
+      return false;
+    }
+    if (inputId >= 0 && inputId < importer.machine.inputs.length) {
+      var found = importer.machine.inputs[inputId];
+      if (found is StateMachineNumber) {
+        _input = found;
+        inputId = found.id;
+      }
+    }
+
+    return super.import(stack);
+  }
+}
diff --git a/lib/src/rive_core/animation/blend_state.dart b/lib/src/rive_core/animation/blend_state.dart
new file mode 100644
index 0000000..e6a157b
--- /dev/null
+++ b/lib/src/rive_core/animation/blend_state.dart
@@ -0,0 +1,20 @@
+import 'package:rive/src/core/core.dart';
+import 'package:rive/src/rive_core/animation/blend_animation.dart';
+import 'package:rive/src/generated/animation/blend_state_base.dart';
+export 'package:rive/src/generated/animation/blend_state_base.dart';
+
+//
+abstract class BlendState<T extends BlendAnimation> extends BlendStateBase {
+  final BlendAnimations<T> _animations = BlendAnimations<T>();
+  BlendAnimations<T> get animations => _animations;
+
+  void internalAddAnimation(T animation) {
+    assert(!_animations.contains(animation),
+        'shouln\'t already contain the animation');
+    _animations.add(animation);
+  }
+
+  void internalRemoveAnimation(T animation) {
+    _animations.remove(animation);
+  }
+}
diff --git a/lib/src/rive_core/animation/blend_state_1d.dart b/lib/src/rive_core/animation/blend_state_1d.dart
new file mode 100644
index 0000000..dd8859d
--- /dev/null
+++ b/lib/src/rive_core/animation/blend_state_1d.dart
@@ -0,0 +1,24 @@
+import 'package:rive/src/rive_core/animation/blend_state_1d_instance.dart';
+import 'package:rive/src/rive_core/animation/state_instance.dart';
+import 'package:rive/src/rive_core/animation/state_machine_number.dart';
+import 'package:rive/src/generated/animation/blend_state_1d_base.dart';
+export 'package:rive/src/generated/animation/blend_state_1d_base.dart';
+
+class BlendState1D extends BlendState1DBase {
+  StateMachineNumber? _input;
+  StateMachineNumber? get input => _input;
+
+  @override
+  void inputIdChanged(int from, int to) {
+    _input = context.resolve<StateMachineNumber>(to);
+  }
+
+  @override
+  void onAddedDirty() {
+    super.onAddedDirty();
+    _input = context.resolve<StateMachineNumber>(inputId);
+  }
+
+  @override
+  StateInstance makeInstance() => BlendState1DInstance(this);
+}
diff --git a/lib/src/rive_core/animation/blend_state_1d_instance.dart b/lib/src/rive_core/animation/blend_state_1d_instance.dart
new file mode 100644
index 0000000..29dd85b
--- /dev/null
+++ b/lib/src/rive_core/animation/blend_state_1d_instance.dart
@@ -0,0 +1,82 @@
+import 'dart:collection';
+
+import 'package:rive/src/rive_core/animation/blend_animation_1d.dart';
+import 'package:rive/src/rive_core/animation/blend_state_1d.dart';
+import 'package:rive/src/rive_core/animation/blend_state_instance.dart';
+
+/// [BlendState1D] mixing logic that runs inside the [StateMachine].
+class BlendState1DInstance
+    extends BlendStateInstance<BlendState1D, BlendAnimation1D> {
+  BlendState1DInstance(BlendState1D state) : super(state) {
+    animationInstances.sort(
+        (a, b) => a.blendAnimation.value.compareTo(b.blendAnimation.value));
+  }
+
+  /// Binary find the closest animation index.
+  int animationIndex(double value) {
+    int idx = 0;
+    int mid = 0;
+    double closestValue = 0;
+    int start = 0;
+    int end = animationInstances.length - 1;
+
+    while (start <= end) {
+      mid = (start + end) >> 1;
+      closestValue = animationInstances[mid].blendAnimation.value;
+      if (closestValue < value) {
+        start = mid + 1;
+      } else if (closestValue > value) {
+        end = mid - 1;
+      } else {
+        idx = start = mid;
+        break;
+      }
+
+      idx = start;
+    }
+    return idx;
+  }
+
+  BlendStateAnimationInstance<BlendAnimation1D>? _from;
+  BlendStateAnimationInstance<BlendAnimation1D>? _to;
+
+  @override
+  void advance(double seconds, HashMap<int, dynamic> inputValues) {
+    super.advance(seconds, inputValues);
+    dynamic inputValue = inputValues[(state as BlendState1D).inputId];
+    var value = (inputValue is double
+            ? inputValue
+            : (state as BlendState1D).input?.value) ??
+        0;
+    int index = animationIndex(value);
+    _to = index >= 0 && index < animationInstances.length
+        ? animationInstances[index]
+        : null;
+    _from = index - 1 >= 0 && index - 1 < animationInstances.length
+        ? animationInstances[index - 1]
+        : null;
+
+    double mix, mixFrom;
+    if (_to == null ||
+        _from == null ||
+        _to!.blendAnimation.value == _from!.blendAnimation.value) {
+      mix = mixFrom = 1;
+    } else {
+      mix = (value - _from!.blendAnimation.value) /
+          (_to!.blendAnimation.value - _from!.blendAnimation.value);
+      mixFrom = 1.0 - mix;
+    }
+
+    var toValue = _to?.blendAnimation.value;
+    var fromValue = _from?.blendAnimation.value;
+    for (final animation in animationInstances) {
+      if (animation.blendAnimation.value == toValue) {
+        animation.mix = mix;
+      } else if (animation.blendAnimation.value == fromValue) {
+        animation.mix = mixFrom;
+      } else {
+        animation.mix = 0;
+      }
+    }
+  }
+}
diff --git a/lib/src/rive_core/animation/blend_state_direct.dart b/lib/src/rive_core/animation/blend_state_direct.dart
new file mode 100644
index 0000000..9f62c40
--- /dev/null
+++ b/lib/src/rive_core/animation/blend_state_direct.dart
@@ -0,0 +1,9 @@
+import 'package:rive/src/rive_core/animation/blend_state_direct_instance.dart';
+import 'package:rive/src/rive_core/animation/state_instance.dart';
+import 'package:rive/src/generated/animation/blend_state_direct_base.dart';
+export 'package:rive/src/generated/animation/blend_state_direct_base.dart';
+
+class BlendStateDirect extends BlendStateDirectBase {
+  @override
+  StateInstance makeInstance() => BlendStateDirectInstance(this);
+}
diff --git a/lib/src/rive_core/animation/blend_state_direct_instance.dart b/lib/src/rive_core/animation/blend_state_direct_instance.dart
new file mode 100644
index 0000000..dde8470
--- /dev/null
+++ b/lib/src/rive_core/animation/blend_state_direct_instance.dart
@@ -0,0 +1,24 @@
+import 'dart:collection';
+
+import 'package:rive/src/rive_core/animation/blend_animation_direct.dart';
+import 'package:rive/src/rive_core/animation/blend_state_direct.dart';
+import 'package:rive/src/rive_core/animation/blend_state_instance.dart';
+
+/// [BlendStateDirect] mixing logic that runs inside the [StateMachine].
+class BlendStateDirectInstance
+    extends BlendStateInstance<BlendStateDirect, BlendAnimationDirect> {
+  BlendStateDirectInstance(BlendStateDirect state) : super(state);
+
+  @override
+  void advance(double seconds, HashMap<int, dynamic> inputValues) {
+    super.advance(seconds, inputValues);
+    for (final animation in animationInstances) {
+      dynamic inputValue = inputValues[animation.blendAnimation.inputId];
+      var value = (inputValue is double
+              ? inputValue
+              : animation.blendAnimation.input?.value) ??
+          0;
+      animation.mix = value / 100;
+    }
+  }
+}
diff --git a/lib/src/rive_core/animation/blend_state_instance.dart b/lib/src/rive_core/animation/blend_state_instance.dart
new file mode 100644
index 0000000..6076f16
--- /dev/null
+++ b/lib/src/rive_core/animation/blend_state_instance.dart
@@ -0,0 +1,59 @@
+import 'dart:collection';
+
+import 'package:rive/src/core/core.dart';
+import 'package:flutter/foundation.dart';
+import 'package:rive/src/rive_core/animation/blend_animation.dart';
+import 'package:rive/src/rive_core/animation/blend_state.dart';
+import 'package:rive/src/rive_core/animation/linear_animation_instance.dart';
+import 'package:rive/src/rive_core/animation/state_instance.dart';
+
+/// Individual animation in a blend state instance.
+class BlendStateAnimationInstance<T extends BlendAnimation> {
+  final T blendAnimation;
+  final LinearAnimationInstance animationInstance;
+  double mix = 0;
+
+  BlendStateAnimationInstance(this.blendAnimation)
+      : animationInstance = LinearAnimationInstance(blendAnimation.animation!);
+}
+
+/// Generic blend state instance which works for [BlendState<BlendAnimation>]s
+/// where T represents the BlendState and K the BlendAnimation.
+abstract class BlendStateInstance<T extends BlendState<K>,
+    K extends BlendAnimation> extends StateInstance {
+  final List<BlendStateAnimationInstance<K>> animationInstances;
+  BlendStateInstance(T state)
+      : animationInstances = state.animations
+            .where((animation) => animation.animation != null)
+            .map((animation) => BlendStateAnimationInstance(animation))
+            .toList(growable: false),
+        super(state);
+
+  bool _keepGoing = true;
+  @override
+  bool get keepGoing => _keepGoing;
+
+  @mustCallSuper
+  @override
+  void advance(double seconds, HashMap<int, dynamic> inputValues) {
+    _keepGoing = false;
+    // Advance all the animations in the blend state
+    for (final animation in animationInstances) {
+      if (animation.animationInstance.advance(seconds) && !keepGoing) {
+        _keepGoing = true;
+      }
+    }
+  }
+
+  @override
+  void apply(CoreContext core, double mix) {
+    for (final animation in animationInstances) {
+      double m = mix * animation.mix;
+      if (m == 0) {
+        continue;
+      }
+      animation.animationInstance.animation
+          .apply(animation.animationInstance.time, coreContext: core, mix: m);
+    }
+  }
+}
diff --git a/lib/src/rive_core/animation/blend_state_transition.dart b/lib/src/rive_core/animation/blend_state_transition.dart
new file mode 100644
index 0000000..6b76d5c
--- /dev/null
+++ b/lib/src/rive_core/animation/blend_state_transition.dart
@@ -0,0 +1,31 @@
+import 'package:rive/src/rive_core/animation/blend_animation.dart';
+import 'package:rive/src/rive_core/animation/blend_state_instance.dart';
+import 'package:rive/src/rive_core/animation/layer_state.dart';
+import 'package:rive/src/rive_core/animation/linear_animation.dart';
+import 'package:rive/src/rive_core/animation/linear_animation_instance.dart';
+import 'package:rive/src/rive_core/animation/state_instance.dart';
+import 'package:rive/src/generated/animation/blend_state_transition_base.dart';
+export 'package:rive/src/generated/animation/blend_state_transition_base.dart';
+
+class BlendStateTransition extends BlendStateTransitionBase {
+  BlendAnimation? exitBlendAnimation;
+
+  @override
+  LinearAnimationInstance? exitTimeAnimationInstance(StateInstance stateFrom) {
+    if (stateFrom is BlendStateInstance) {
+      for (final blendAnimation in stateFrom.animationInstances) {
+        if (blendAnimation.blendAnimation == exitBlendAnimation) {
+          return blendAnimation.animationInstance;
+        }
+      }
+    }
+    return null;
+  }
+
+  @override
+  LinearAnimation? exitTimeAnimation(LayerState stateFrom) =>
+      exitBlendAnimation?.animation;
+
+  @override
+  void exitBlendAnimationIdChanged(int from, int to) {}
+}
diff --git a/lib/src/rive_core/animation/cubic_interpolator.dart b/lib/src/rive_core/animation/cubic_interpolator.dart
index c972192..8216745 100644
--- a/lib/src/rive_core/animation/cubic_interpolator.dart
+++ b/lib/src/rive_core/animation/cubic_interpolator.dart
@@ -1,14 +1,18 @@
 import 'dart:typed_data';
+
 import 'package:rive/src/core/core.dart';
 import 'package:rive/src/rive_core/animation/interpolator.dart';
 import 'package:rive/src/rive_core/artboard.dart';
 import 'package:rive/src/generated/animation/cubic_interpolator_base.dart';
 
 const int newtonIterations = 4;
+
+// Implements https://github.com/gre/bezier-easing/blob/master/src/index.js
 const double newtonMinSlope = 0.001;
 const double sampleStepSize = 1.0 / (splineTableSize - 1.0);
 const int splineTableSize = 11;
 const int subdivisionMaxIterations = 10;
+
 const double subdivisionPrecision = 0.0000001;
 double _calcBezier(double aT, double aA1, double aA2) {
   return (((1.0 - 3.0 * aA2 + 3.0 * aA1) * aT + (3.0 * aA2 - 6.0 * aA1)) * aT +
@@ -16,14 +20,17 @@ double _calcBezier(double aT, double aA1, double aA2) {
       aT;
 }
 
+// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
 double _getSlope(double aT, double aA1, double aA2) {
   return 3.0 * (1.0 - 3.0 * aA2 + 3.0 * aA1) * aT * aT +
       2.0 * (3.0 * aA2 - 6.0 * aA1) * aT +
       (3.0 * aA1);
 }
 
+// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
 class CubicInterpolator extends CubicInterpolatorBase implements Interpolator {
   _CubicEase _ease = _CubicEase.make(0.42, 0, 0.58, 1);
+
   @override
   bool equalParameters(Interpolator other) {
     if (other is CubicInterpolator) {
@@ -37,16 +44,22 @@ class CubicInterpolator extends CubicInterpolatorBase implements Interpolator {
 
   @override
   void onAdded() => _updateStoredCubic();
+
   @override
   void onAddedDirty() {}
+
   @override
   double transform(double value) => _ease.transform(value);
+
   @override
   void x1Changed(double from, double to) => _updateStoredCubic();
+
   @override
   void x2Changed(double from, double to) => _updateStoredCubic();
+
   @override
   void y1Changed(double from, double to) => _updateStoredCubic();
+
   @override
   void y2Changed(double from, double to) => _updateStoredCubic();
   void _updateStoredCubic() {
@@ -60,6 +73,7 @@ class CubicInterpolator extends CubicInterpolatorBase implements Interpolator {
       return false;
     }
     artboardHelper.addComponent(this);
+
     return super.import(stack);
   }
 }
@@ -68,23 +82,29 @@ class _Cubic extends _CubicEase {
   final Float64List _values = Float64List(splineTableSize);
   final double x1, y1, x2, y2;
   _Cubic(this.x1, this.y1, this.x2, this.y2) {
+    // Precompute values table
     for (int i = 0; i < splineTableSize; ++i) {
       _values[i] = _calcBezier(i * sampleStepSize, x1, x2);
     }
   }
+
   double getT(double x) {
     double intervalStart = 0.0;
     int currentSample = 1;
     int lastSample = splineTableSize - 1;
+
     for (;
         currentSample != lastSample && _values[currentSample] <= x;
         ++currentSample) {
       intervalStart += sampleStepSize;
     }
     --currentSample;
+
+    // Interpolate to provide an initial guess for t
     var dist = (x - _values[currentSample]) /
         (_values[currentSample + 1] - _values[currentSample]);
     var guessForT = intervalStart + dist * sampleStepSize;
+
     var initialSlope = _getSlope(guessForT, x1, x2);
     if (initialSlope >= newtonMinSlope) {
       for (int i = 0; i < newtonIterations; ++i) {
@@ -124,6 +144,7 @@ class _Cubic extends _CubicEase {
 
 abstract class _CubicEase {
   double transform(double t);
+
   static _CubicEase make(double x1, double y1, double x2, double y2) {
     if (x1 == y1 && x2 == y2) {
       return _LinearCubicEase();
diff --git a/lib/src/rive_core/animation/entry_state.dart b/lib/src/rive_core/animation/entry_state.dart
index 81f9c97..d868ef3 100644
--- a/lib/src/rive_core/animation/entry_state.dart
+++ b/lib/src/rive_core/animation/entry_state.dart
@@ -1,4 +1,8 @@
+import 'package:rive/src/rive_core/animation/state_instance.dart';
 import 'package:rive/src/generated/animation/entry_state_base.dart';
 export 'package:rive/src/generated/animation/entry_state_base.dart';
 
-class EntryState extends EntryStateBase {}
+class EntryState extends EntryStateBase {
+  @override
+  StateInstance makeInstance() => SystemStateInstance(this);
+}
diff --git a/lib/src/rive_core/animation/exit_state.dart b/lib/src/rive_core/animation/exit_state.dart
index a8e6e1c..fabdec2 100644
--- a/lib/src/rive_core/animation/exit_state.dart
+++ b/lib/src/rive_core/animation/exit_state.dart
@@ -1,4 +1,8 @@
+import 'package:rive/src/rive_core/animation/state_instance.dart';
 import 'package:rive/src/generated/animation/exit_state_base.dart';
 export 'package:rive/src/generated/animation/exit_state_base.dart';
 
-class ExitState extends ExitStateBase {}
+class ExitState extends ExitStateBase {
+  @override
+  StateInstance makeInstance() => SystemStateInstance(this);
+}
diff --git a/lib/src/rive_core/animation/interpolator.dart b/lib/src/rive_core/animation/interpolator.dart
index 88d4e4c..8526b5b 100644
--- a/lib/src/rive_core/animation/interpolator.dart
+++ b/lib/src/rive_core/animation/interpolator.dart
@@ -1,5 +1,8 @@
 abstract class Interpolator {
   int get id;
+
+  /// Convert a linear interpolation factor to an eased one.
   double transform(double value);
+
   bool equalParameters(Interpolator other);
 }
diff --git a/lib/src/rive_core/animation/keyed_object.dart b/lib/src/rive_core/animation/keyed_object.dart
index d752b70..5778cf7 100644
--- a/lib/src/rive_core/animation/keyed_object.dart
+++ b/lib/src/rive_core/animation/keyed_object.dart
@@ -1,7 +1,9 @@
 import 'dart:collection';
+
 import 'package:rive/src/core/core.dart';
 import 'package:rive/src/rive_core/animation/keyed_property.dart';
 import 'package:rive/src/rive_core/component.dart';
+
 import 'package:rive/src/generated/animation/keyed_object_base.dart';
 import 'linear_animation.dart';
 export 'package:rive/src/generated/animation/keyed_object_base.dart';
@@ -9,20 +11,26 @@ export 'package:rive/src/generated/animation/keyed_object_base.dart';
 class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
   final HashMap<int, KeyedProperty> _keyedProperties =
       HashMap<int, KeyedProperty>();
+
   Iterable<KeyedProperty> get keyedProperties => _keyedProperties.values;
+
   @override
   void onAddedDirty() {}
+
   @override
   void onAdded() {}
+
   @override
   bool validate() {
     if (!super.validate()) {
       return false;
     }
+
     var component = context.resolve<Component>(objectId);
     if (component == null) {
       return false;
     }
+
     return true;
   }
 
@@ -33,14 +41,22 @@ class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
 
   bool isValidKeyedProperty(KeyedProperty property) {
     var value = _keyedProperties[property.propertyKey];
+
+    // If the property is already keyed, that's ok just make sure the
+    // KeyedObject matches.
     if (value != null && value != property) {
       return false;
     }
     return true;
   }
 
+  /// Called by rive_core to add a KeyedProperty to the animation. This should
+  /// be @internal when it's supported.
   bool internalAddKeyedProperty(KeyedProperty property) {
     var value = _keyedProperties[property.propertyKey];
+
+    // If the property is already keyed, that's ok just make sure the
+    // KeyedObject matches.
     if (value != null && value != property) {
       return false;
     }
@@ -48,11 +64,17 @@ class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
     return true;
   }
 
+  /// Called by rive_core to remove a KeyedObject to the animation. This should
+  /// be @internal when it's supported.
   bool internalRemoveKeyedProperty(KeyedProperty property) {
     var removed = _keyedProperties.remove(property.propertyKey);
+
     if (_keyedProperties.isEmpty) {
+      // Remove this keyed property.
       context.removeObject(this);
     }
+    // assert(removed == null || removed == property,
+    //     '$removed was not $property or null');
     return removed != null;
   }
 
@@ -68,6 +90,7 @@ class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
 
   @override
   void objectIdChanged(int from, int to) {}
+
   @override
   bool import(ImportStack stack) {
     var animationHelper =
@@ -76,6 +99,7 @@ class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
       return false;
     }
     animationHelper.addKeyedObject(this);
+
     return super.import(stack);
   }
 }
diff --git a/lib/src/rive_core/animation/keyed_property.dart b/lib/src/rive_core/animation/keyed_property.dart
index 5a9cec8..13e3f07 100644
--- a/lib/src/rive_core/animation/keyed_property.dart
+++ b/lib/src/rive_core/animation/keyed_property.dart
@@ -1,6 +1,7 @@
 import 'package:rive/src/core/core.dart';
 import 'package:rive/src/rive_core/animation/keyed_object.dart';
 import 'package:rive/src/rive_core/animation/keyframe.dart';
+
 import 'package:rive/src/generated/animation/keyed_property_base.dart';
 export 'package:rive/src/generated/animation/keyed_property_base.dart';
 
@@ -12,6 +13,8 @@ class KeyFrameList<T extends KeyFrameInterface> {
   List<T> _keyframes = [];
   Iterable<T> get keyframes => _keyframes;
   set keyframes(Iterable<T> frames) => _keyframes = frames.toList();
+
+  /// Get the keyframe immediately following the provided one.
   T? after(T keyframe) {
     var index = _keyframes.indexOf(keyframe);
     if (index != -1 && index + 1 < _keyframes.length) {
@@ -20,12 +23,15 @@ class KeyFrameList<T extends KeyFrameInterface> {
     return null;
   }
 
+  /// Find the index in the keyframe list of a specific time frame.
   int indexOfFrame(int frame) {
     int idx = 0;
+    // Binary find the keyframe index.
     int mid = 0;
     int closestFrame = 0;
     int start = 0;
     int end = _keyframes.length - 1;
+
     while (start <= end) {
       mid = (start + end) >> 1;
       closestFrame = _keyframes[mid].frame;
@@ -37,6 +43,7 @@ class KeyFrameList<T extends KeyFrameInterface> {
         idx = start = mid;
         break;
       }
+
       idx = start;
     }
     return idx;
@@ -49,13 +56,17 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
     with KeyFrameList<KeyFrame> {
   @override
   void onAdded() {}
+
   @override
   void onAddedDirty() {}
+
   @override
   void onRemoved() {
     super.onRemoved();
   }
 
+  /// Called by rive_core to add a KeyFrame to this KeyedProperty. This should
+  /// be @internal when it's supported.
   bool internalAddKeyFrame(KeyFrame frame) {
     if (_keyframes.contains(frame)) {
       return false;
@@ -65,44 +76,64 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
     return true;
   }
 
+  /// Called by rive_core to remove a KeyFrame from this KeyedProperty. This
+  /// should be @internal when it's supported.
   bool internalRemoveKeyFrame(KeyFrame frame) {
     var removed = _keyframes.remove(frame);
     if (_keyframes.isEmpty) {
+      // If they keyframes are now empty, we might want to remove this keyed
+      // property. Wait for any other pending changes to complete before
+      // checking.
       context.dirty(_checkShouldRemove);
     }
+
     return removed;
   }
 
   void _checkShouldRemove() {
     if (_keyframes.isEmpty) {
+      // Remove this keyed property.
       context.removeObject(this);
     }
   }
 
+  /// Called by keyframes when their time value changes. This is a pretty rare
+  /// operation, usually occurs when a user moves a keyframe. Meaning: this
+  /// shouldn't make it into the runtimes unless we want to allow users moving
+  /// keyframes around at runtime via code for some reason.
   void markKeyFrameOrderDirty() {
     context.dirty(_sortAndValidateKeyFrames);
   }
 
   void _sortAndValidateKeyFrames() {
     sort();
+
     for (int i = 0; i < _keyframes.length - 1; i++) {
       var a = _keyframes[i];
       var b = _keyframes[i + 1];
       if (a.frame == b.frame) {
+        // N.B. this removes it from the list too.
         context.removeObject(a);
+        // Repeat current.
         i--;
       }
     }
   }
 
+  /// Number of keyframes for this keyed property.
   int get numFrames => _keyframes.length;
+
   KeyFrame getFrameAt(int index) => _keyframes[index];
+
   int closestFrameIndex(double seconds) {
     int idx = 0;
+    // Binary find the keyframe index (use timeInSeconds here as opposed to the
+    // finder above which operates in frames).
     int mid = 0;
     double closestSeconds = 0;
     int start = 0;
     int end = _keyframes.length - 1;
+
     while (start <= end) {
       mid = (start + end) >> 1;
       closestSeconds = _keyframes[mid].seconds;
@@ -123,10 +154,12 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
     if (_keyframes.isEmpty) {
       return;
     }
+
     int idx = closestFrameIndex(seconds);
     int pk = propertyKey;
     if (idx == 0) {
       var first = _keyframes[0];
+
       first.apply(object, pk, mix);
     } else {
       if (idx < _keyframes.length) {
@@ -135,6 +168,8 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
         if (seconds == toFrame.seconds) {
           toFrame.apply(object, pk, mix);
         } else {
+          /// Equivalent to fromFrame.interpolation ==
+          /// KeyFrameInterpolation.hold.
           if (fromFrame.interpolationType == 0) {
             fromFrame.apply(object, pk, mix);
           } else {
@@ -143,6 +178,7 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
         }
       } else {
         var last = _keyframes[idx - 1];
+
         last.apply(object, pk, mix);
       }
     }
@@ -150,6 +186,7 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
 
   @override
   void propertyKeyChanged(int from, int to) {}
+
   @override
   bool import(ImportStack stack) {
     var importer = stack.latest<KeyedObjectImporter>(KeyedObjectBase.typeKey);
@@ -157,6 +194,7 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
       return false;
     }
     importer.addKeyedProperty(this);
+
     return super.import(stack);
   }
 }
diff --git a/lib/src/rive_core/animation/keyframe.dart b/lib/src/rive_core/animation/keyframe.dart
index 49a1747..9d82a62 100644
--- a/lib/src/rive_core/animation/keyframe.dart
+++ b/lib/src/rive_core/animation/keyframe.dart
@@ -3,14 +3,18 @@ import 'package:rive/src/rive_core/animation/interpolator.dart';
 import 'package:rive/src/rive_core/animation/keyed_property.dart';
 import 'package:rive/src/rive_core/animation/keyframe_interpolation.dart';
 import 'package:rive/src/rive_core/animation/linear_animation.dart';
+
 import 'package:rive/src/generated/animation/keyframe_base.dart';
+
 export 'package:rive/src/generated/animation/keyframe_base.dart';
 
 abstract class KeyFrame extends KeyFrameBase<RuntimeArtboard>
     implements KeyFrameInterface {
   double _timeInSeconds = 0;
   double get seconds => _timeInSeconds;
+
   bool get canInterpolate => true;
+
   KeyFrameInterpolation get interpolation =>
       KeyFrameInterpolation.values[interpolationType];
   set interpolation(KeyFrameInterpolation value) {
@@ -19,13 +23,17 @@ abstract class KeyFrame extends KeyFrameBase<RuntimeArtboard>
 
   @override
   void interpolationTypeChanged(int from, int to) {}
+
   @override
   void interpolatorIdChanged(int from, int to) {
+    // This might resolve to null during a load or if context isn't available
+    // yet so we also do this in onAddedDirty.
     interpolator = context.resolve(to);
   }
 
   @override
   void onAdded() {}
+
   void computeSeconds(LinearAnimation animation) {
     _timeInSeconds = frame / animation.fps;
   }
@@ -44,15 +52,22 @@ abstract class KeyFrame extends KeyFrameBase<RuntimeArtboard>
 
   @override
   void frameChanged(int from, int to) {}
+
+  /// Apply the value of this keyframe to the object's property.
   void apply(Core object, int propertyKey, double mix);
+
+  /// Interpolate the value between this keyframe and the next and apply it to
+  /// the object's property.
   void applyInterpolation(Core object, int propertyKey, double seconds,
       covariant KeyFrame nextFrame, double mix);
+
   Interpolator? _interpolator;
   Interpolator? get interpolator => _interpolator;
   set interpolator(Interpolator? value) {
     if (_interpolator == value) {
       return;
     }
+
     _interpolator = value;
     interpolatorId = value?.id ?? Core.missingId;
   }
@@ -65,6 +80,7 @@ abstract class KeyFrame extends KeyFrameBase<RuntimeArtboard>
       return false;
     }
     keyedPropertyHelper.addKeyFrame(this);
+
     return super.import(importStack);
   }
 }
diff --git a/lib/src/rive_core/animation/keyframe_color.dart b/lib/src/rive_core/animation/keyframe_color.dart
index a62a0cd..94c8393 100644
--- a/lib/src/rive_core/animation/keyframe_color.dart
+++ b/lib/src/rive_core/animation/keyframe_color.dart
@@ -1,4 +1,5 @@
 import 'dart:ui';
+
 import 'package:rive/src/core/core.dart';
 import 'package:rive/src/generated/animation/keyframe_color_base.dart';
 import 'package:rive/src/generated/rive_core_context.dart';
@@ -22,13 +23,16 @@ class KeyFrameColor extends KeyFrameColorBase {
   @override
   void apply(Core<CoreContext> object, int propertyKey, double mix) =>
       _apply(object, propertyKey, mix, value);
+
   @override
   void applyInterpolation(Core<CoreContext> object, int propertyKey,
       double currentTime, KeyFrameColor nextFrame, double mix) {
     var f = (currentTime - seconds) / (nextFrame.seconds - seconds);
+
     if (interpolator != null) {
       f = interpolator!.transform(f);
     }
+
     var color = Color.lerp(Color(value), Color(nextFrame.value), f);
     if (color != null) {
       _apply(object, propertyKey, mix, color.value);
diff --git a/lib/src/rive_core/animation/keyframe_double.dart b/lib/src/rive_core/animation/keyframe_double.dart
index 2d902f4..4dab3c9 100644
--- a/lib/src/rive_core/animation/keyframe_double.dart
+++ b/lib/src/rive_core/animation/keyframe_double.dart
@@ -22,13 +22,16 @@ class KeyFrameDouble extends KeyFrameDoubleBase {
   @override
   void apply(Core<CoreContext> object, int propertyKey, double mix) =>
       _apply(object, propertyKey, mix, value);
+
   @override
   void applyInterpolation(Core<CoreContext> object, int propertyKey,
       double currentTime, KeyFrameDouble nextFrame, double mix) {
     var f = (currentTime - seconds) / (nextFrame.seconds - seconds);
+
     if (interpolator != null) {
       f = interpolator!.transform(f);
     }
+
     _apply(object, propertyKey, mix, value + (nextFrame.value - value) * f);
   }
 
diff --git a/lib/src/rive_core/animation/keyframe_id.dart b/lib/src/rive_core/animation/keyframe_id.dart
index 235029c..fa6accb 100644
--- a/lib/src/rive_core/animation/keyframe_id.dart
+++ b/lib/src/rive_core/animation/keyframe_id.dart
@@ -6,6 +6,7 @@ export 'package:rive/src/generated/animation/keyframe_id_base.dart';
 class KeyFrameId extends KeyFrameIdBase {
   @override
   bool get canInterpolate => false;
+
   @override
   void apply(Core<CoreContext> object, int propertyKey, double mix) {
     RiveCoreContext.setUint(object, propertyKey, value);
diff --git a/lib/src/rive_core/animation/keyframe_interpolation.dart b/lib/src/rive_core/animation/keyframe_interpolation.dart
index 015c144..9ab7930 100644
--- a/lib/src/rive_core/animation/keyframe_interpolation.dart
+++ b/lib/src/rive_core/animation/keyframe_interpolation.dart
@@ -1 +1,12 @@
-enum KeyFrameInterpolation { hold, linear, cubic }
+/// The type of interpolation used for a keyframe.
+enum KeyFrameInterpolation {
+  /// Hold the incoming value until the next keyframe is reached.
+  hold,
+
+  /// Linearly interpolate from the incoming to the outgoing value.
+  linear,
+
+  /// Cubicly interpolate from incoming to outgoing value based on the
+  /// [CubicInterpolator]'s parameters.
+  cubic,
+}
diff --git a/lib/src/rive_core/animation/layer_state.dart b/lib/src/rive_core/animation/layer_state.dart
index 9dfece1..7f3a1fb 100644
--- a/lib/src/rive_core/animation/layer_state.dart
+++ b/lib/src/rive_core/animation/layer_state.dart
@@ -1,4 +1,5 @@
 import 'package:rive/src/core/core.dart';
+import 'package:rive/src/rive_core/animation/state_instance.dart';
 import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
 import 'package:rive/src/rive_core/animation/state_transition.dart';
 import 'package:rive/src/generated/animation/layer_state_base.dart';
@@ -7,11 +8,16 @@ export 'package:rive/src/generated/animation/layer_state_base.dart';
 abstract class LayerState extends LayerStateBase {
   final StateTransitions _transitions = StateTransitions();
   StateTransitions get transitions => _transitions;
+
   @override
   void onAdded() {}
+
   @override
   void onAddedDirty() {}
+
   void internalAddTransition(StateTransition transition) {
+    assert(!_transitions.contains(transition),
+        'shouldn\'t already contain the transition');
     _transitions.add(transition);
   }
 
@@ -24,6 +30,8 @@ abstract class LayerState extends LayerStateBase {
     super.onRemoved();
   }
 
+  StateInstance makeInstance();
+
   @override
   bool import(ImportStack stack) {
     var importer =
@@ -32,6 +40,7 @@ abstract class LayerState extends LayerStateBase {
       return false;
     }
     importer.addState(this);
+
     return super.import(stack);
   }
 }
diff --git a/lib/src/rive_core/animation/linear_animation.dart b/lib/src/rive_core/animation/linear_animation.dart
index df94bef..b774e5e 100644
--- a/lib/src/rive_core/animation/linear_animation.dart
+++ b/lib/src/rive_core/animation/linear_animation.dart
@@ -1,4 +1,5 @@
 import 'dart:collection';
+
 import 'package:rive/src/core/core.dart';
 import 'package:rive/src/rive_core/animation/keyed_object.dart';
 import 'package:rive/src/rive_core/animation/loop.dart';
@@ -7,8 +8,16 @@ import 'package:rive/src/generated/animation/linear_animation_base.dart';
 export 'package:rive/src/generated/animation/linear_animation_base.dart';
 
 class LinearAnimation extends LinearAnimationBase {
+  /// Map objectId to KeyedObject. N.B. this is the id of the object that we
+  /// want to key in core, not of the KeyedObject. It's a clear way to see if an
+  /// object is keyed in this animation.
   final _keyedObjects = HashMap<int, KeyedObject>();
+
+  /// The metadata for the objects that are keyed in this animation.
   Iterable<KeyedObject> get keyedObjects => _keyedObjects.values;
+
+  /// Called by rive_core to add a KeyedObject to the animation. This should be
+  /// @internal when it's supported.
   bool internalAddKeyedObject(KeyedObject object) {
     if (internalCheckAddKeyedObject(object)) {
       _keyedObjects[object.objectId] = object;
@@ -19,6 +28,9 @@ class LinearAnimation extends LinearAnimationBase {
 
   bool internalCheckAddKeyedObject(KeyedObject object) {
     var value = _keyedObjects[object.objectId];
+
+    // If the object is already keyed, that's ok just make sure the KeyedObject
+    // matches.
     if (value != null && value != object) {
       return false;
     }
@@ -29,6 +41,13 @@ class LinearAnimation extends LinearAnimationBase {
   double get endSeconds =>
       (enableWorkArea ? workEnd : duration).toDouble() / fps;
   double get durationSeconds => endSeconds - startSeconds;
+
+  /// Pass in a different [core] context if you want to apply the animation to a
+  /// different instance. This isn't meant to be used yet but left as mostly a
+  /// note to remember that at runtime we have to support applying animations to
+  /// instances. We do a nice job of not duping all that data at runtime (so
+  /// animations exist once but entire Rive file can be instanced multiple times
+  /// playing different positions).
   void apply(double time, {required CoreContext coreContext, double mix = 1}) {
     for (final keyedObject in _keyedObjects.values) {
       keyedObject.apply(time, mix, coreContext);
@@ -37,20 +56,28 @@ class LinearAnimation extends LinearAnimationBase {
 
   Loop get loop => Loop.values[loopValue];
   set loop(Loop value) => loopValue = value.index;
+
   @override
   void durationChanged(int from, int to) {}
+
   @override
   void enableWorkAreaChanged(bool from, bool to) {}
+
   @override
   void fpsChanged(int from, int to) {}
+
   @override
   void loopValueChanged(int from, int to) {}
+
   @override
   void speedChanged(double from, double to) {}
+
   @override
   void workEndChanged(int from, int to) {}
+
   @override
   void workStartChanged(int from, int to) {}
+
   @override
   bool import(ImportStack stack) {
     var artboardImporter = stack.latest<ArtboardImporter>(ArtboardBase.typeKey);
@@ -58,6 +85,7 @@ class LinearAnimation extends LinearAnimationBase {
       return false;
     }
     artboardImporter.addAnimation(this);
+
     return super.import(stack);
   }
 }
diff --git a/lib/src/rive_core/animation/linear_animation_instance.dart b/lib/src/rive_core/animation/linear_animation_instance.dart
index 14655b2..49201ce 100644
--- a/lib/src/rive_core/animation/linear_animation_instance.dart
+++ b/lib/src/rive_core/animation/linear_animation_instance.dart
@@ -11,47 +11,73 @@ class LinearAnimationInstance {
   bool get didLoop => _didLoop;
   double _spilledTime = 0;
   double get spilledTime => _spilledTime;
+
   double get totalTime => _totalTime;
   double get lastTotalTime => _lastTotalTime;
+
   LinearAnimationInstance(this.animation)
       : _time =
             (animation.enableWorkArea ? animation.workStart : 0).toDouble() /
                 animation.fps;
+
+  /// Note that when time is set, the direction will be changed to 1
   set time(double value) {
     if (_time == value) {
       return;
     }
+    // Make sure to keep last and total in relative lockstep so state machines
+    // can track change even when setting time.
     var diff = _totalTime - _lastTotalTime;
     _time = _totalTime = value;
     _lastTotalTime = _totalTime - diff;
     _direction = 1;
   }
 
+  /// Returns the current time position of the animation in seconds
   double get time => _time;
+
+  /// Direction should only be +1 or -1
   set direction(int value) => _direction = value == -1 ? -1 : 1;
+
+  /// Returns the animation's play direction: 1 for forwards, -1 for backwards
   int get direction => _direction;
+
+  /// Returns the end time of the animation in seconds
   double get endTime =>
       (animation.enableWorkArea ? animation.workEnd : animation.duration)
           .toDouble() /
       animation.fps;
+
+  /// Returns the start time of the animation in seconds
   double get startTime =>
       (animation.enableWorkArea ? animation.workStart : 0).toDouble() /
       animation.fps;
+
   double get progress => (_time - startTime) / (endTime - startTime);
+
+  /// Resets the animation to the starting frame
   void reset() => _time = startTime;
+
+  /// Whether the controller driving this animation should keep requesting
+  /// frames be drawn.
   bool get keepGoing => animation.loop != Loop.oneShot || !_didLoop;
+
   bool advance(double elapsedSeconds) {
     var deltaSeconds = elapsedSeconds * animation.speed * _direction;
     _lastTotalTime = _totalTime;
     _totalTime += deltaSeconds;
     _time += deltaSeconds;
+
     double frames = _time * animation.fps;
+
     var start = animation.enableWorkArea ? animation.workStart : 0;
     var end = animation.enableWorkArea ? animation.workEnd : animation.duration;
     var range = end - start;
+
     bool keepGoing = true;
     _didLoop = false;
     _spilledTime = 0;
+
     switch (animation.loop) {
       case Loop.oneShot:
         if (frames > end) {
@@ -72,6 +98,7 @@ class LinearAnimationInstance {
         }
         break;
       case Loop.pingPong:
+        // ignore: literal_only_boolean_expressions
         while (true) {
           if (_direction == 1 && frames >= end) {
             _spilledTime = (frames - end) / animation.fps;
@@ -86,6 +113,11 @@ class LinearAnimationInstance {
             _time = frames / animation.fps;
             _didLoop = true;
           } else {
+            // we're within the range, we can stop fixing. We do this in a
+            // loop to fix conditions when time has advanced so far that we've
+            // ping-ponged back and forth a few times in a single frame. We
+            // want to accomodate for this in cases where animations are not
+            // advanced on regular intervals.
             break;
           }
         }
diff --git a/lib/src/rive_core/animation/loop.dart b/lib/src/rive_core/animation/loop.dart
index c0f3046..702dc26 100644
--- a/lib/src/rive_core/animation/loop.dart
+++ b/lib/src/rive_core/animation/loop.dart
@@ -1 +1,12 @@
-enum Loop { oneShot, loop, pingPong }
+/// Loop options for linear animations.
+enum Loop {
+  /// Play until the duration or end of work area of the animation.
+  oneShot,
+
+  /// Play until the duration or end of work area of the animation and then go
+  /// back to the start (0 seconds).
+  loop,
+
+  /// Play to the end of the duration/work area and then play back.
+  pingPong,
+}
diff --git a/lib/src/rive_core/animation/state_instance.dart b/lib/src/rive_core/animation/state_instance.dart
new file mode 100644
index 0000000..1707048
--- /dev/null
+++ b/lib/src/rive_core/animation/state_instance.dart
@@ -0,0 +1,33 @@
+import 'dart:collection';
+
+import 'package:rive/src/core/core.dart';
+import 'package:rive/src/rive_core/animation/layer_state.dart';
+
+/// Represents the instance of a [LayerState] which is being used in a
+/// [LayerController] of a [StateMachineController]. Abstract representation of
+/// an Animation (for [AnimationState]) or set of Animations in the case of a
+/// [BlendState].
+abstract class StateInstance {
+  final LayerState state;
+
+  StateInstance(this.state);
+
+  void advance(double seconds, HashMap<int, dynamic> inputValues);
+  void apply(CoreContext core, double mix);
+
+  bool get keepGoing;
+}
+
+/// A single one of these is created per Layer which just represents/wraps the
+/// AnyState but conforms to the instance interface.
+class SystemStateInstance extends StateInstance {
+  SystemStateInstance(LayerState state) : super(state);
+  @override
+  void advance(double seconds, HashMap<int, dynamic> inputValues) {}
+
+  @override
+  void apply(CoreContext core, double mix) {}
+
+  @override
+  bool get keepGoing => false;
+}
diff --git a/lib/src/rive_core/animation/state_machine.dart b/lib/src/rive_core/animation/state_machine.dart
index e4009a9..9fa71e4 100644
--- a/lib/src/rive_core/animation/state_machine.dart
+++ b/lib/src/rive_core/animation/state_machine.dart
@@ -10,6 +10,7 @@ class StateMachine extends StateMachineBase {
       StateMachineComponents<StateMachineInput>();
   final StateMachineComponents<StateMachineLayer> layers =
       StateMachineComponents<StateMachineLayer>();
+
   @override
   bool import(ImportStack stack) {
     var artboardImporter = stack.latest<ArtboardImporter>(ArtboardBase.typeKey);
@@ -17,6 +18,7 @@ class StateMachine extends StateMachineBase {
       return false;
     }
     artboardImporter.addStateMachine(this);
+
     return super.import(stack);
   }
 }
diff --git a/lib/src/rive_core/animation/state_machine_bool.dart b/lib/src/rive_core/animation/state_machine_bool.dart
index 36601c3..3288484 100644
--- a/lib/src/rive_core/animation/state_machine_bool.dart
+++ b/lib/src/rive_core/animation/state_machine_bool.dart
@@ -4,8 +4,10 @@ export 'package:rive/src/generated/animation/state_machine_bool_base.dart';
 class StateMachineBool extends StateMachineBoolBase {
   @override
   void valueChanged(bool from, bool to) {}
+
   @override
   bool isValidType<T>() => T == bool;
+
   @override
   dynamic get controllerValue => value;
 }
diff --git a/lib/src/rive_core/animation/state_machine_component.dart b/lib/src/rive_core/animation/state_machine_component.dart
index 710bf83..f98295d 100644
--- a/lib/src/rive_core/animation/state_machine_component.dart
+++ b/lib/src/rive_core/animation/state_machine_component.dart
@@ -1,9 +1,11 @@
 import 'dart:collection';
+
 import 'package:rive/src/core/core.dart';
 import 'package:rive/src/rive_core/animation/state_machine.dart';
 import 'package:rive/src/generated/animation/state_machine_component_base.dart';
 export 'package:rive/src/generated/animation/state_machine_component_base.dart';
 
+/// Implemented by state machine inputs and layers.
 abstract class StateMachineComponent extends StateMachineComponentBase {
   StateMachine? _stateMachine;
   StateMachine? get stateMachine => _stateMachine;
@@ -15,16 +17,22 @@ abstract class StateMachineComponent extends StateMachineComponentBase {
       machineComponentList(_stateMachine!).remove(this);
     }
     _stateMachine = machine;
+
     if (_stateMachine != null) {
       machineComponentList(_stateMachine!).add(this);
     }
   }
 
+  // Intentionally using ListBase instead of FractionallyIndexedList here as
+  // it's more compatible with runtime.
   ListBase<StateMachineComponent> machineComponentList(StateMachine machine);
+
   @override
   void nameChanged(String from, String to) {}
+
   @override
   void onAddedDirty() {}
+
   @override
   void onRemoved() {
     super.onRemoved();
@@ -39,6 +47,7 @@ abstract class StateMachineComponent extends StateMachineComponentBase {
       return false;
     }
     importer.addMachineComponent(this);
+
     return super.import(importStack);
   }
 }
diff --git a/lib/src/rive_core/animation/state_machine_input.dart b/lib/src/rive_core/animation/state_machine_input.dart
index 2f9ff4b..ef801d8 100644
--- a/lib/src/rive_core/animation/state_machine_input.dart
+++ b/lib/src/rive_core/animation/state_machine_input.dart
@@ -1,4 +1,5 @@
 import 'dart:collection';
+
 import 'package:rive/src/rive_core/animation/state_machine.dart';
 import 'package:rive/src/rive_core/animation/state_machine_component.dart';
 import 'package:rive/src/generated/animation/state_machine_input_base.dart';
@@ -6,9 +7,11 @@ export 'package:rive/src/generated/animation/state_machine_input_base.dart';
 
 abstract class StateMachineInput extends StateMachineInputBase {
   static final StateMachineInput unknown = _StateMachineUnknownInput();
+
   @override
   ListBase<StateMachineComponent> machineComponentList(StateMachine machine) =>
       machine.inputs;
+
   bool isValidType<T>() => false;
   dynamic get controllerValue => null;
 }
diff --git a/lib/src/rive_core/animation/state_machine_layer.dart b/lib/src/rive_core/animation/state_machine_layer.dart
index f9caa2c..c9f93b5 100644
--- a/lib/src/rive_core/animation/state_machine_layer.dart
+++ b/lib/src/rive_core/animation/state_machine_layer.dart
@@ -1,4 +1,5 @@
 import 'dart:collection';
+
 import 'package:rive/src/rive_core/animation/any_state.dart';
 import 'package:rive/src/rive_core/animation/entry_state.dart';
 import 'package:rive/src/rive_core/animation/exit_state.dart';
@@ -13,12 +14,17 @@ class StateMachineLayer extends StateMachineLayerBase {
   LayerState? _entryState;
   LayerState? _anyState;
   LayerState? _exitState;
+
   LayerState? get entryState => _entryState;
   LayerState? get anyState => _anyState;
   LayerState? get exitState => _exitState;
+
   @override
   ListBase<StateMachineComponent> machineComponentList(StateMachine machine) =>
       machine.layers;
+
+  /// Called by rive_core to add a LayerState to the StateMachineLayer. This
+  /// should be @internal when it's supported.
   bool internalAddState(LayerState state) {
     switch (state.coreType) {
       case AnyStateBase.typeKey:
@@ -31,6 +37,7 @@ class StateMachineLayer extends StateMachineLayerBase {
         _entryState = state;
         break;
     }
+
     return true;
   }
 }
diff --git a/lib/src/rive_core/animation/state_machine_layer_component.dart b/lib/src/rive_core/animation/state_machine_layer_component.dart
index 0b41057..34e9851 100644
--- a/lib/src/rive_core/animation/state_machine_layer_component.dart
+++ b/lib/src/rive_core/animation/state_machine_layer_component.dart
@@ -1,4 +1,9 @@
+// We really want this file to import core for the flutter runtime, so make the
+// linter happy...
+
+// ignore: unused_import
 import 'package:rive/src/core/core.dart';
+
 import 'package:rive/src/generated/animation/state_machine_layer_component_base.dart';
 export 'package:rive/src/generated/animation/state_machine_layer_component_base.dart';
 
diff --git a/lib/src/rive_core/animation/state_machine_number.dart b/lib/src/rive_core/animation/state_machine_number.dart
index 26dfe13..db019db 100644
--- a/lib/src/rive_core/animation/state_machine_number.dart
+++ b/lib/src/rive_core/animation/state_machine_number.dart
@@ -4,8 +4,10 @@ export 'package:rive/src/generated/animation/state_machine_number_base.dart';
 class StateMachineNumber extends StateMachineNumberBase {
   @override
   void valueChanged(double from, double to) {}
+
   @override
   bool isValidType<T>() => T == double;
+
   @override
   dynamic get controllerValue => value;
 }
diff --git a/lib/src/rive_core/animation/state_machine_trigger.dart b/lib/src/rive_core/animation/state_machine_trigger.dart
index b265ced..805112a 100644
--- a/lib/src/rive_core/animation/state_machine_trigger.dart
+++ b/lib/src/rive_core/animation/state_machine_trigger.dart
@@ -4,6 +4,7 @@ export 'package:rive/src/generated/animation/state_machine_trigger_base.dart';
 class StateMachineTrigger extends StateMachineTriggerBase {
   bool _triggered = false;
   bool get triggered => _triggered;
+
   void fire() {
     _triggered = true;
   }
@@ -14,6 +15,7 @@ class StateMachineTrigger extends StateMachineTriggerBase {
 
   @override
   bool isValidType<T>() => T == bool;
+
   @override
   dynamic get controllerValue => _triggered;
 }
diff --git a/lib/src/rive_core/animation/state_transition.dart b/lib/src/rive_core/animation/state_transition.dart
index 6ff0b02..32dc7b3 100644
--- a/lib/src/rive_core/animation/state_transition.dart
+++ b/lib/src/rive_core/animation/state_transition.dart
@@ -1,24 +1,40 @@
+import 'dart:collection';
+
 import 'package:rive/src/core/core.dart';
 import 'package:rive/src/rive_core/animation/animation_state.dart';
+import 'package:rive/src/rive_core/animation/animation_state_instance.dart';
 import 'package:rive/src/rive_core/animation/layer_state.dart';
+import 'package:rive/src/rive_core/animation/linear_animation.dart';
+import 'package:rive/src/rive_core/animation/linear_animation_instance.dart';
+import 'package:rive/src/rive_core/animation/state_instance.dart';
 import 'package:rive/src/rive_core/animation/transition_condition.dart';
+import 'package:rive/src/rive_core/animation/transition_trigger_condition.dart';
 import 'package:rive/src/generated/animation/state_transition_base.dart';
 import 'package:rive/src/rive_core/state_transition_flags.dart';
 export 'package:rive/src/generated/animation/state_transition_base.dart';
 
+enum AllowTransition { no, waitingForExit, yes }
+
 class StateTransition extends StateTransitionBase {
   final StateTransitionConditions conditions = StateTransitionConditions();
   LayerState? stateTo;
   static final StateTransition unknown = StateTransition();
+
   @override
   bool validate() {
-    return super.validate() && stateTo != null;
+    return super.validate() &&
+
+        // need this last so runtimes get it which makes the whole
+        // allowTransitionFrom thing above a little weird.
+        stateTo != null;
   }
 
   @override
   void onAdded() {}
+
   @override
   void onAddedDirty() {}
+
   @override
   void onRemoved() {
     super.onRemoved();
@@ -27,6 +43,11 @@ class StateTransition extends StateTransitionBase {
   bool get isDisabled => (flags & StateTransitionFlags.disabled) != 0;
   bool get pauseOnExit => (flags & StateTransitionFlags.pauseOnExit) != 0;
   bool get enableExitTime => (flags & StateTransitionFlags.enableExitTime) != 0;
+
+  /// The amount of time to mix the outgoing animation onto the incoming one
+  /// when changing state. Only applies when going out from an AnimationState.
+  /// [stateFrom] must be provided as at runtime we don't store the reference to
+  /// the state this transition comes from.
   double mixTime(LayerState stateFrom) {
     if (duration == 0) {
       return 0;
@@ -42,14 +63,30 @@ class StateTransition extends StateTransitionBase {
     }
   }
 
+  /// Provide the animation instance to use for computing percentage durations
+  /// for exit time.
+  LinearAnimationInstance? exitTimeAnimationInstance(StateInstance stateFrom) =>
+      stateFrom is AnimationStateInstance ? stateFrom.animationInstance : null;
+
+  /// Provide the animation to use for computing percentage durations for exit
+  /// time.
+  LinearAnimation? exitTimeAnimation(LayerState stateFrom) =>
+      stateFrom is AnimationState ? stateFrom.animation : null;
+
+  /// Computes the exit time in seconds of the [stateFrom]. Set [absolute] to
+  /// true if you want the returned time to be relative to the entire animation.
+  /// Set [absolute] to false if you want it relative to the work area.
   double exitTimeSeconds(LayerState stateFrom, {bool absolute = false}) {
     if ((flags & StateTransitionFlags.exitTimeIsPercentage) != 0) {
       var animationDuration = 0.0;
       var start = 0.0;
-      if (stateFrom is AnimationState) {
-        start = absolute ? stateFrom.animation?.startSeconds ?? 0 : 0;
-        animationDuration = stateFrom.animation?.durationSeconds ?? 0;
+
+      var exitAnimation = exitTimeAnimation(stateFrom);
+      if (exitAnimation != null) {
+        start = absolute ? exitAnimation.startSeconds : 0;
+        animationDuration = exitAnimation.durationSeconds;
       }
+
       return start + exitTime / 100 * animationDuration;
     } else {
       return exitTime / 1000;
@@ -64,28 +101,89 @@ class StateTransition extends StateTransitionBase {
       return false;
     }
     importer.addTransition(this);
+
     return super.import(importStack);
   }
 
+  /// Called by rive_core to add a [TransitionCondition] to this
+  /// [StateTransition]. This should be @internal when it's supported.
   bool internalAddCondition(TransitionCondition condition) {
     if (conditions.contains(condition)) {
       return false;
     }
     conditions.add(condition);
+
     return true;
   }
 
+  /// Called by rive_core to remove a [TransitionCondition] from this
+  /// [StateTransition]. This should be @internal when it's supported.
   bool internalRemoveCondition(TransitionCondition condition) {
     var removed = conditions.remove(condition);
+
     return removed;
   }
 
   @override
   void flagsChanged(int from, int to) {}
+
   @override
   void durationChanged(int from, int to) {}
+
   @override
   void exitTimeChanged(int from, int to) {}
+
   @override
   void stateToIdChanged(int from, int to) {}
+
+  /// Returns true when this transition can be taken from [stateFrom] with the
+  /// given [inputValues].
+  AllowTransition allowed(StateInstance stateFrom,
+      HashMap<int, dynamic> inputValues, bool ignoreTriggers) {
+    if (isDisabled) {
+      return AllowTransition.no;
+    }
+    for (final condition in conditions) {
+      if ((ignoreTriggers && condition is TransitionTriggerCondition) ||
+          !condition.evaluate(inputValues)) {
+        return AllowTransition.no;
+      }
+    }
+    // For now we only enable exit time from AnimationStates, do we want to
+    // enable this for BlendStates? How would that work?
+    if (enableExitTime) {
+      var exitAnimation = exitTimeAnimationInstance(stateFrom);
+      if (exitAnimation != null) {
+        // Exit time is specified in a value less than a single loop, so we
+        // want to allow exiting regardless of which loop we're on. To do that
+        // we bring the exit time up to the loop our lastTime is at.
+        var lastTime = exitAnimation.lastTotalTime;
+        var time = exitAnimation.totalTime;
+        var exitTime = exitTimeSeconds(stateFrom.state);
+        var animationFrom = exitAnimation.animation;
+        if (exitTime < animationFrom.durationSeconds) {
+          // Get exit time relative to the loop lastTime was in.
+          exitTime += (lastTime / animationFrom.durationSeconds).floor() *
+              animationFrom.durationSeconds;
+        }
+
+        if (time < exitTime) {
+          return AllowTransition.waitingForExit;
+        }
+      }
+    }
+    return AllowTransition.yes;
+  }
+
+  bool applyExitCondition(StateInstance stateFrom) {
+    // Hold exit time when the user has set to pauseOnExit on this condition
+    // (only valid when exiting from an Animation).
+    bool useExitTime = enableExitTime && stateFrom is AnimationStateInstance;
+    if (pauseOnExit && useExitTime) {
+      stateFrom.animationInstance.time =
+          exitTimeSeconds(stateFrom.state, absolute: true);
+      return true;
+    }
+    return useExitTime;
+  }
 }
diff --git a/lib/src/rive_core/animation/transition_bool_condition.dart b/lib/src/rive_core/animation/transition_bool_condition.dart
index 3161e0d..5e58c47 100644
--- a/lib/src/rive_core/animation/transition_bool_condition.dart
+++ b/lib/src/rive_core/animation/transition_bool_condition.dart
@@ -7,6 +7,7 @@ export 'package:rive/src/generated/animation/transition_bool_condition_base.dart
 class TransitionBoolCondition extends TransitionBoolConditionBase {
   @override
   bool validate() => super.validate() && (input is StateMachineBool);
+
   @override
   bool evaluate(HashMap<int, dynamic> values) {
     if (input is! StateMachineBool) {
diff --git a/lib/src/rive_core/animation/transition_condition.dart b/lib/src/rive_core/animation/transition_condition.dart
index 79bb752..d5f8251 100644
--- a/lib/src/rive_core/animation/transition_condition.dart
+++ b/lib/src/rive_core/animation/transition_condition.dart
@@ -1,4 +1,5 @@
 import 'dart:collection';
+
 import 'package:rive/src/core/core.dart';
 import 'package:rive/src/rive_core/animation/state_machine_input.dart';
 import 'package:rive/src/rive_core/animation/state_transition.dart';
@@ -11,7 +12,7 @@ enum TransitionConditionOp {
   lessThanOrEqual,
   greaterThanOrEqual,
   lessThan,
-  greaterThan
+  greaterThan,
 }
 
 abstract class TransitionCondition extends TransitionConditionBase {
@@ -21,7 +22,9 @@ abstract class TransitionCondition extends TransitionConditionBase {
     if (_input == value) {
       return;
     }
+
     _input = value;
+
     inputId = _input.id;
   }
 
@@ -32,12 +35,14 @@ abstract class TransitionCondition extends TransitionConditionBase {
 
   @override
   void onAdded() {}
+
   @override
   void onAddedDirty() {
     input = context.resolveWithDefault(inputId, StateMachineInput.unknown);
   }
 
   bool evaluate(HashMap<int, dynamic> values);
+
   @override
   bool import(ImportStack importStack) {
     var importer = importStack
@@ -46,6 +51,7 @@ abstract class TransitionCondition extends TransitionConditionBase {
       return false;
     }
     importer.addCondition(this);
+
     return super.import(importStack);
   }
 }
diff --git a/lib/src/rive_core/animation/transition_number_condition.dart b/lib/src/rive_core/animation/transition_number_condition.dart
index a7fb680..44a9dc4 100644
--- a/lib/src/rive_core/animation/transition_number_condition.dart
+++ b/lib/src/rive_core/animation/transition_number_condition.dart
@@ -1,4 +1,5 @@
 import 'dart:collection';
+
 import 'package:rive/src/rive_core/animation/state_machine_number.dart';
 import 'package:rive/src/rive_core/animation/transition_condition.dart';
 import 'package:rive/src/generated/animation/transition_number_condition_base.dart';
@@ -7,8 +8,10 @@ export 'package:rive/src/generated/animation/transition_number_condition_base.da
 class TransitionNumberCondition extends TransitionNumberConditionBase {
   @override
   void valueChanged(double from, double to) {}
+
   @override
   bool validate() => super.validate() && (input is StateMachineNumber);
+
   @override
   bool evaluate(HashMap<int, dynamic> values) {
     if (input is! StateMachineNumber) {
diff --git a/lib/src/rive_core/animation/transition_trigger_condition.dart b/lib/src/rive_core/animation/transition_trigger_condition.dart
index 670c29a..124a2e2 100644
--- a/lib/src/rive_core/animation/transition_trigger_condition.dart
+++ b/lib/src/rive_core/animation/transition_trigger_condition.dart
@@ -1,4 +1,5 @@
 import 'dart:collection';
+
 import 'package:rive/src/rive_core/animation/state_machine_trigger.dart';
 import 'package:rive/src/generated/animation/transition_trigger_condition_base.dart';
 export 'package:rive/src/generated/animation/transition_trigger_condition_base.dart';
@@ -6,6 +7,7 @@ export 'package:rive/src/generated/animation/transition_trigger_condition_base.d
 class TransitionTriggerCondition extends TransitionTriggerConditionBase {
   @override
   bool validate() => super.validate() && (input is StateMachineTrigger);
+
   @override
   bool evaluate(HashMap<int, dynamic> values) {
     if (input is! StateMachineTrigger) {
@@ -16,6 +18,7 @@ class TransitionTriggerCondition extends TransitionTriggerConditionBase {
       values[input.id] = false;
       return true;
     }
+
     var triggerInput = input as StateMachineTrigger;
     if (triggerInput.triggered) {
       return true;
diff --git a/lib/src/rive_core/animation/transition_value_condition.dart b/lib/src/rive_core/animation/transition_value_condition.dart
index 9c6fd1b..2a68b87 100644
--- a/lib/src/rive_core/animation/transition_value_condition.dart
+++ b/lib/src/rive_core/animation/transition_value_condition.dart
@@ -4,6 +4,9 @@ export 'package:rive/src/generated/animation/transition_value_condition_base.dar
 
 abstract class TransitionValueCondition extends TransitionValueConditionBase {
   TransitionConditionOp get op => TransitionConditionOp.values[opValue];
+
   @override
-  void opValueChanged(int from, int to) {}
+  void opValueChanged(int from, int to) {
+    // TODO: implement opValueChanged
+  }
 }
diff --git a/lib/src/rive_core/artboard.dart b/lib/src/rive_core/artboard.dart
index a31751b..b9b45b5 100644
--- a/lib/src/rive_core/artboard.dart
+++ b/lib/src/rive_core/artboard.dart
@@ -1,4 +1,5 @@
 import 'dart:ui';
+
 import 'package:rive/src/core/core.dart';
 import 'package:rive/src/rive_core/animation/animation.dart';
 import 'package:rive/src/rive_core/component.dart';
@@ -12,34 +13,52 @@ import 'package:rive/src/rive_core/rive_animation_controller.dart';
 import 'package:rive/src/rive_core/shapes/paint/shape_paint_mutator.dart';
 import 'package:rive/src/rive_core/shapes/shape_paint_container.dart';
 import 'package:rive/src/utilities/dependency_sorter.dart';
+
 import 'package:rive/src/generated/artboard_base.dart';
+
 export 'package:rive/src/generated/artboard_base.dart';
 
 class Artboard extends ArtboardBase with ShapePaintContainer {
+  /// Artboard are one of the few (only?) components that can be orphaned.
   @override
   bool get canBeOrphaned => true;
+
   final Path path = Path();
   List<Component> _dependencyOrder = [];
   final List<Drawable> _drawables = [];
   final List<DrawRules> _rules = [];
   List<DrawTarget> _sortedDrawRules = [];
+
   final Set<Component> _components = {};
+
   List<Drawable> get drawables => _drawables;
+
   final AnimationList _animations = AnimationList();
+
+  /// List of animations in this artboard.
   AnimationList get animations => _animations;
+
+  /// Does this artboard have animations?
   bool get hasAnimations => _animations.isNotEmpty;
+
   int _dirtDepth = 0;
   int _dirt = 255;
+
   void forEachComponent(void Function(Component) callback) =>
       _components.forEach(callback);
+
   @override
   Artboard get artboard => this;
+
   Vec2D get originWorld {
     return Vec2D.fromValues(x + width * originX, y + height * originY);
   }
 
+  /// Walk the dependency tree and update components in order. Returns true if
+  /// any component updated.
   bool updateComponents() {
     bool didUpdate = false;
+
     if ((_dirt & ComponentDirt.drawOrder) != 0) {
       sortDrawOrder();
       _dirt &= ~ComponentDirt.drawOrder;
@@ -51,6 +70,8 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
       int count = _dependencyOrder.length;
       while ((_dirt & ComponentDirt.components) != 0 && step < maxSteps) {
         _dirt &= ~ComponentDirt.components;
+        // Track dirt depth here so that if something else marks
+        // dirty, we restart.
         for (int i = 0; i < count; i++) {
           Component component = _dependencyOrder[i];
           _dirtDepth = i;
@@ -71,6 +92,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
     return didUpdate;
   }
 
+  /// Update any dirty components in this artboard.
   bool advance(double elapsedSeconds) {
     bool didUpdate = false;
     for (final controller in _animationControllers) {
@@ -93,6 +115,10 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
       context.markNeedsAdvance();
       _dirt |= ComponentDirt.components;
     }
+
+    /// If the order of the component is less than the current dirt depth,
+    /// update the dirt depth so that the update loop can break out early and
+    /// re-run (something up the tree is dirty).
     if (component.graphOrder < _dirtDepth) {
       _dirtDepth = component.graphOrder;
     }
@@ -100,17 +126,24 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
 
   @override
   bool resolveArtboard() => true;
+
+  /// Sort the DAG for resolution in order of dependencies such that dependent
+  /// compnents process after their dependencies.
   void sortDependencies() {
     var optimistic = DependencySorter<Component>();
     var order = optimistic.sort(this);
     if (order.isEmpty) {
+      // cycle detected, use a more robust solver
       var robust = TarjansDependencySorter<Component>();
       order = robust.sort(this);
     }
+
     _dependencyOrder = order;
     for (final component in _dependencyOrder) {
       component.graphOrder = graphOrder++;
+      // component.dirt = 255;
     }
+
     _dirt |= ComponentDirt.components;
   }
 
@@ -145,16 +178,23 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
     return Vec2D.add(Vec2D(), worldTranslation, wt);
   }
 
+  /// Adds a component to the artboard. Good place for the artboard to check for
+  /// components it'll later need to do stuff with (like draw them or sort them
+  /// when the draw order changes).
   void addComponent(Component component) {
     if (!_components.add(component)) {
       return;
     }
   }
 
+  /// Remove a component from the artboard and its various tracked lists of
+  /// components.
   void removeComponent(Component component) {
     _components.remove(component);
   }
 
+  /// Let the artboard know that the drawables need to be resorted before
+  /// drawing next.
   void markDrawOrderDirty() {
     if ((dirt & ComponentDirt.drawOrder) == 0) {
       context.markNeedsAdvance();
@@ -162,13 +202,25 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
     }
   }
 
+  /// Draw the drawable components in this artboard.
   void draw(Canvas canvas) {
     canvas.save();
     canvas.clipRect(Rect.fromLTWH(0, 0, width, height));
+    // Get into artboard's world space. This is because the artboard draws
+    // components in the artboard's space (in component lingo we call this world
+    // space). The artboards themselves are drawn in the editor's world space,
+    // which is the world space that is used by stageItems. This is a little
+    // confusing and perhaps we should find a better wording for the transform
+    // spaces. We used "world space" in components as that's the game engine
+    // ratified way of naming the top-most transformation. Perhaps we should
+    // rename those to artboardTransform and worldTransform is only reserved for
+    // stageItems? The other option is to stick with 'worldTransform' in
+    // components and use 'editor or stageTransform' for stageItems.
     canvas.translate(width * originX, height * originY);
     for (final fill in fills) {
       fill.draw(canvas, path);
     }
+
     for (var drawable = _firstDrawable;
         drawable != null;
         drawable = drawable.prev) {
@@ -180,8 +232,10 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
     canvas.restore();
   }
 
+  /// Our world transform is always the identity. Artboard defines world space.
   @override
   Mat2D get worldTransform => Mat2D();
+
   @override
   void originXChanged(double from, double to) {
     addDirt(ComponentDirt.worldTransform);
@@ -192,20 +246,31 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
     addDirt(ComponentDirt.worldTransform);
   }
 
+  /// Called by rive_core to add an Animation to an Artboard. This should be
+  /// @internal when it's supported.
   bool internalAddAnimation(Animation animation) {
     if (_animations.contains(animation)) {
       return false;
     }
     _animations.add(animation);
+
     return true;
   }
 
+  /// Called by rive_core to remove an Animation from an Artboard. This should
+  /// be @internal when it's supported.
   bool internalRemoveAnimation(Animation animation) {
     bool removed = _animations.remove(animation);
+
     return removed;
   }
 
+  /// The animation controllers that are called back whenever the artboard
+  /// advances.
   final Set<RiveAnimationController> _animationControllers = {};
+
+  /// Add an animation controller to this artboard. Playing will be scheduled if
+  /// it's already playing.
   bool addController(RiveAnimationController controller) {
     if (_animationControllers.contains(controller) ||
         !controller.init(context)) {
@@ -219,6 +284,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
     return true;
   }
 
+  /// Remove an animation controller form this artboard.
   bool removeController(RiveAnimationController controller) {
     if (_animationControllers.remove(controller)) {
       controller.isActiveChanged.removeListener(_onControllerPlayingChanged);
@@ -229,25 +295,37 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
   }
 
   void _onControllerPlayingChanged() => context.markNeedsAdvance();
+
   @override
   void onFillsChanged() {}
+
   @override
   void onPaintMutatorChanged(ShapePaintMutator mutator) {}
+
   @override
   void onStrokesChanged() {}
+
   @override
   Vec2D get worldTranslation => Vec2D();
+
   Drawable? _firstDrawable;
+
   void computeDrawOrder() {
     _drawables.clear();
     _rules.clear();
     buildDrawOrder(_drawables, null, _rules);
+
+    // Build rule dependencies. In practice this'll need to happen anytime a
+    // target drawable is changed or rule is added/removed.
     var root = DrawTarget();
+    // Make sure all dependents are empty.
     for (final nodeRules in _rules) {
       for (final target in nodeRules.targets) {
         target.dependents.clear();
       }
     }
+
+    // Now build up the dependencies.
     for (final nodeRules in _rules) {
       for (final target in nodeRules.targets) {
         root.dependents.add(target);
@@ -259,19 +337,25 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
         }
       }
     }
+
     var sorter = DependencySorter<Component>();
+
     _sortedDrawRules = sorter.sort(root).cast<DrawTarget>().skip(1).toList();
+
     sortDrawOrder();
   }
 
   void sortDrawOrder() {
+    // Clear out rule first/last items.
     for (final rule in _sortedDrawRules) {
       rule.first = rule.last = null;
     }
+
     _firstDrawable = null;
     Drawable? lastDrawable;
     for (final drawable in _drawables) {
       var rules = drawable.flattenedDrawRules;
+
       var target = rules?.activeTarget;
       if (target != null) {
         if (target.first == null) {
@@ -294,6 +378,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
         }
       }
     }
+
     for (final rule in _sortedDrawRules) {
       if (rule.first == null) {
         continue;
@@ -323,6 +408,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
           break;
       }
     }
+
     _firstDrawable = lastDrawable;
   }
 }
diff --git a/lib/src/rive_core/backboard.dart b/lib/src/rive_core/backboard.dart
index ebda29f..5b3607a 100644
--- a/lib/src/rive_core/backboard.dart
+++ b/lib/src/rive_core/backboard.dart
@@ -3,8 +3,10 @@ export 'package:rive/src/generated/backboard_base.dart';
 
 class Backboard extends BackboardBase {
   static final Backboard unknown = Backboard();
+
   @override
   void onAdded() {}
+
   @override
   void onAddedDirty() {}
 }
diff --git a/lib/src/rive_core/bones/bone.dart b/lib/src/rive_core/bones/bone.dart
index 8dd6359..57e2885 100644
--- a/lib/src/rive_core/bones/bone.dart
+++ b/lib/src/rive_core/bones/bone.dart
@@ -22,6 +22,9 @@ class Bone extends BoneBase {
     return null;
   }
 
+  /// Iterate through the child bones. [BoneCallback] returns false if iteration
+  /// can stop. Returns false if iteration stopped, true if it made it through
+  /// the whole list.
   bool forEachBone(BoneCallback callback) {
     for (final child in children) {
       if (child.coreType == BoneBase.typeKey) {
@@ -35,6 +38,7 @@ class Bone extends BoneBase {
 
   @override
   double get x => (parent as Bone).length;
+
   @override
   set x(double value) {
     throw UnsupportedError('not expected to set x on a bone.');
@@ -42,6 +46,7 @@ class Bone extends BoneBase {
 
   @override
   double get y => 0;
+
   @override
   set y(double value) {
     throw UnsupportedError('not expected to set y on a bone.');
@@ -49,6 +54,9 @@ class Bone extends BoneBase {
 
   @override
   bool validate() {
+    // Bones are only valid if they're parented to other bones. RootBones are a
+    // special case, but they inherit from bone so we check the concrete type
+    // here to make sure we evalute this check only for non-root bones.
     return super.validate() && (coreType != BoneBase.typeKey || parent is Bone);
   }
 }
diff --git a/lib/src/rive_core/bones/cubic_weight.dart b/lib/src/rive_core/bones/cubic_weight.dart
index f673ec0..45a0ee0 100644
--- a/lib/src/rive_core/bones/cubic_weight.dart
+++ b/lib/src/rive_core/bones/cubic_weight.dart
@@ -5,12 +5,16 @@ export 'package:rive/src/generated/bones/cubic_weight_base.dart';
 class CubicWeight extends CubicWeightBase {
   final Vec2D inTranslation = Vec2D();
   final Vec2D outTranslation = Vec2D();
+
   @override
   void inIndicesChanged(int from, int to) {}
+
   @override
   void inValuesChanged(int from, int to) {}
+
   @override
   void outIndicesChanged(int from, int to) {}
+
   @override
   void outValuesChanged(int from, int to) {}
 }
diff --git a/lib/src/rive_core/bones/skin.dart b/lib/src/rive_core/bones/skin.dart
index 1f8fba6..55dc1fe 100644
--- a/lib/src/rive_core/bones/skin.dart
+++ b/lib/src/rive_core/bones/skin.dart
@@ -1,24 +1,37 @@
 import 'dart:typed_data';
+
 import 'package:rive/src/rive_core/bones/skinnable.dart';
 import 'package:rive/src/rive_core/bones/tendon.dart';
 import 'package:rive/src/rive_core/component.dart';
 import 'package:rive/src/rive_core/math/mat2d.dart';
 import 'package:rive/src/rive_core/shapes/path_vertex.dart';
+
 import 'package:rive/src/generated/bones/skin_base.dart';
 export 'package:rive/src/generated/bones/skin_base.dart';
 
+/// Represents a skin deformation of either a Path or an Image Mesh connected to
+/// a set of bones.
 class Skin extends SkinBase {
   final List<Tendon> _tendons = [];
   List<Tendon> get tendons => _tendons;
   Float32List _boneTransforms = Float32List(0);
   final Mat2D _worldTransform = Mat2D();
+
   @override
   void onDirty(int mask) {
+    // When the skin is dirty the deformed skinnable will need to regenerate its
+    // drawing commands.
+
+    // TODO: rename path to topology/surface something common between path &
+    // mesh.
     (parent as Skinnable).markSkinDirty();
   }
 
   @override
   void update(int dirt) {
+    // Any dirt here indicates that the transforms needs to be rebuilt. This
+    // should only be worldTransform from the bones (recursively passed down) or
+    // ComponentDirt.path from the PointsPath (set explicitly).
     var size = (_tendons.length + 1) * 6;
     if (_boneTransforms.length != size) {
       _boneTransforms = Float32List(size);
@@ -29,6 +42,7 @@ class Skin extends SkinBase {
       _boneTransforms[4] = 0;
       _boneTransforms[5] = 0;
     }
+
     var temp = Mat2D();
     var bidx = 6;
     for (final tendon in _tendons) {
@@ -79,6 +93,8 @@ class Skin extends SkinBase {
   @override
   void buildDependencies() {
     super.buildDependencies();
+    // A skin depends on all its bones. N.B. that we don't depend on the parent
+    // skinnable. The skinnable depends on us.
     for (final tendon in _tendons) {
       tendon.bone?.addDependent(this);
     }
@@ -110,6 +126,7 @@ class Skin extends SkinBase {
           markRebuildDependencies();
         }
         parent?.markRebuildDependencies();
+
         break;
     }
   }
diff --git a/lib/src/rive_core/bones/skinnable.dart b/lib/src/rive_core/bones/skinnable.dart
index 4054abd..36fd0a5 100644
--- a/lib/src/rive_core/bones/skinnable.dart
+++ b/lib/src/rive_core/bones/skinnable.dart
@@ -1,18 +1,27 @@
 import 'package:rive/src/rive_core/bones/skin.dart';
 import 'package:rive/src/rive_core/component.dart';
 
+/// An abstraction to give a common interface to any container component that
+/// can contain a skin to bind bones to.
 abstract class Skinnable {
+  // _skin is null when this object isn't connected to bones.
   Skin? _skin;
   Skin? get skin => _skin;
+
   void appendChild(Component child);
+
+  // ignore: use_setters_to_change_properties
   void addSkin(Skin skin) {
+    // Notify old skin/maybe support multiple skins in the future?
     _skin = skin;
+
     markSkinDirty();
   }
 
   void removeSkin(Skin skin) {
     if (_skin == skin) {
       _skin = null;
+
       markSkinDirty();
     }
   }
diff --git a/lib/src/rive_core/bones/tendon.dart b/lib/src/rive_core/bones/tendon.dart
index 05466a1..84a3d14 100644
--- a/lib/src/rive_core/bones/tendon.dart
+++ b/lib/src/rive_core/bones/tendon.dart
@@ -8,6 +8,7 @@ class Tendon extends TendonBase {
   Mat2D? _inverseBind;
   SkeletalComponent? _bone;
   SkeletalComponent? get bone => _bone;
+
   Mat2D get inverseBind {
     if (_inverseBind == null) {
       _inverseBind = Mat2D();
@@ -17,11 +18,16 @@ class Tendon extends TendonBase {
   }
 
   @override
-  void boneIdChanged(int from, int to) {}
+  void boneIdChanged(int from, int to) {
+    // This never happens, or at least it should only happen prior to an
+    // onAddedDirty call.
+  }
+
   @override
   void onAddedDirty() {
     super.onAddedDirty();
     _bone = context.resolve(boneId);
+
     _bind[0] = xx;
     _bind[1] = xy;
     _bind[2] = yx;
@@ -32,6 +38,7 @@ class Tendon extends TendonBase {
 
   @override
   void update(int dirt) {}
+
   @override
   void txChanged(double from, double to) {
     _bind[4] = to;
diff --git a/lib/src/rive_core/bones/weight.dart b/lib/src/rive_core/bones/weight.dart
index a262d67..af4cb82 100644
--- a/lib/src/rive_core/bones/weight.dart
+++ b/lib/src/rive_core/bones/weight.dart
@@ -1,4 +1,5 @@
 import 'dart:typed_data';
+
 import 'package:rive/src/rive_core/math/mat2d.dart';
 import 'package:rive/src/rive_core/math/vec2d.dart';
 import 'package:rive/src/generated/bones/weight_base.dart';
@@ -6,12 +7,18 @@ export 'package:rive/src/generated/bones/weight_base.dart';
 
 class Weight extends WeightBase {
   final Vec2D translation = Vec2D();
+
   @override
   void indicesChanged(int from, int to) {}
+
   @override
-  void update(int dirt) {}
+  void update(int dirt) {
+    // Intentionally empty. Weights don't update.
+  }
+
   @override
   void valuesChanged(int from, int to) {}
+
   static void deform(double x, double y, int indices, int weights, Mat2D world,
       Float32List boneTransforms, Vec2D result) {
     double xx = 0, xy = 0, yx = 0, yy = 0, tx = 0, ty = 0;
@@ -22,6 +29,7 @@ class Weight extends WeightBase {
       if (weight == 0) {
         continue;
       }
+
       double normalizedWeight = weight / 255;
       var index = encodedWeightValue(i, indices);
       var startBoneTransformIndex = index * 6;
diff --git a/lib/src/rive_core/bones/weighted_vertex.dart b/lib/src/rive_core/bones/weighted_vertex.dart
index ec0aa30..473cd67 100644
--- a/lib/src/rive_core/bones/weighted_vertex.dart
+++ b/lib/src/rive_core/bones/weighted_vertex.dart
@@ -1,19 +1,31 @@
+/// Helper to abstract changing weighted values on a vertex.
 abstract class WeightedVertex {
   int get weights;
   int get weightIndices;
   set weights(int value);
   set weightIndices(int value);
+
+  /// Set the weight of this vertex for a specific tendon.
   void setWeight(int tendonIndex, int tendonCount, double weight) {
     int tendonWeightIndex =
         _setTendonWeight(tendonIndex, (weight.clamp(0, 1) * 255).round());
+
+    // re-normalize the list such that only bones with value are at the
+    // start and they sum to 100%, if any need to change make sure to give
+    // priority (not change) tendonIndex which we just tried to set.
+
     var tendonWeights = _tendonWeights;
     int totalWeight = tendonWeights.fold(
         0, (value, tendonWeight) => value + tendonWeight.weight);
     var vertexTendons =
         tendonWeights.where((tendonWeight) => tendonWeight.tendon != 0);
+
     const maxWeight = 255;
+
     var remainder = maxWeight - totalWeight;
     if (vertexTendons.length == 1) {
+      // User is specifically setting a single tendon to a value, just pick
+      // the next one up (modulate by the total number of tendons).
       var patchTendonIndex = (tendonIndex + 1) % tendonCount;
       _setTendonWeight(
           patchTendonIndex, tendonCount == 1 ? maxWeight : remainder);
@@ -40,6 +52,8 @@ abstract class WeightedVertex {
 
   void _sortWeights() {
     var tendonWeights = _tendonWeights;
+    // Sort weights such that tendons with value show up first and any with no
+    // value (0 weight) are cleared to the 0 (no) tendon.
     tendonWeights.sort((a, b) => b.weight.compareTo(a.weight));
     for (int i = 0; i < tendonWeights.length; i++) {
       final tw = tendonWeights[i];
@@ -54,14 +68,16 @@ abstract class WeightedVertex {
         _WeightHelper(2, (weightIndices >> 16) & 0xFF, _getRawWeight(2)),
         _WeightHelper(3, (weightIndices >> 24) & 0xFF, _getRawWeight(3))
       ];
+
   int _setTendonWeight(int tendonIndex, int weight) {
     var indices = weightIndices;
     var bonesIndices = [
       indices & 0xFF,
       (indices >> 8) & 0xFF,
       (indices >> 16) & 0xFF,
-      (indices >> 24) & 0xFF
+      (indices >> 24) & 0xFF,
     ];
+
     int setWeightIndex = -1;
     for (int i = 0; i < 4; i++) {
       if (bonesIndices[i] == tendonIndex + 1) {
@@ -70,10 +86,14 @@ abstract class WeightedVertex {
         break;
       }
     }
+
+    // This bone wasn't weighted for this vertex, go find the bone with the
+    // least weight (or a 0 bone) and use it.
     if (setWeightIndex == -1) {
       int lowestWeight = double.maxFinite.toInt();
       for (int i = 0; i < 4; i++) {
         if (bonesIndices[i] == 0) {
+          // this isn't set to a bone yet, use it!
           setWeightIndex = i;
           break;
         }
@@ -83,16 +103,21 @@ abstract class WeightedVertex {
           lowestWeight = weight;
         }
       }
+
       _setTendonIndex(setWeightIndex, tendonIndex + 1);
       _rawSetWeight(setWeightIndex, weight);
     }
     return setWeightIndex;
   }
 
+  /// [tendonIndex] of 0 means no bound tendon, when bound to an actual tendon,
+  /// it should be set to the skin's tendon's index + 1.
   void _setTendonIndex(int weightIndex, int tendonIndex) {
     assert(weightIndex < 4 && weightIndex >= 0);
     var indexValues = weightIndices;
+    // Clear the bits for this weight value.
     indexValues &= ~(0xFF << (weightIndex * 8));
+    // Set the bits for this weight value.
     weightIndices = indexValues | (tendonIndex << (weightIndex * 8));
   }
 
@@ -104,11 +129,14 @@ abstract class WeightedVertex {
   void _rawSetWeight(int weightIndex, int weightValue) {
     assert(weightIndex < 4 && weightIndex >= 0);
     var weightValues = weights;
+    // Clear the bits for this weight value.
     weightValues &= ~(0xFF << (weightIndex * 8));
+    // Set the bits for this weight value.
     weights = weightValues | (weightValue << (weightIndex * 8));
   }
 
   int _getRawWeight(int weightIndex) => (weights >> (weightIndex * 8)) & 0xFF;
+
   double getWeight(int tendonIndex) {
     for (int i = 0; i < 4; i++) {
       if (getTendon(i) == tendonIndex + 1) {
@@ -123,5 +151,6 @@ class _WeightHelper {
   final int index;
   final int tendon;
   int weight;
+
   _WeightHelper(this.index, this.tendon, this.weight);
 }
diff --git a/lib/src/rive_core/component.dart b/lib/src/rive_core/component.dart
index abe65ef..82e7903 100644
--- a/lib/src/rive_core/component.dart
+++ b/lib/src/rive_core/component.dart
@@ -2,6 +2,7 @@ import 'package:rive/src/core/core.dart';
 import 'package:flutter/foundation.dart';
 import 'package:rive/src/rive_core/artboard.dart';
 import 'package:rive/src/rive_core/container_component.dart';
+
 import 'package:rive/src/generated/component_base.dart';
 import 'package:rive/src/utilities/dependency_sorter.dart';
 import 'package:rive/src/utilities/tops.dart';
@@ -11,20 +12,36 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
     implements DependencyGraphNode<Component>, Parentable<Component> {
   Artboard? _artboard;
   dynamic _userData;
+
+  /// Override to true if you want some object inheriting from Component to not
+  /// have a parent. Most objects will validate that they have a parent during
+  /// the onAdded callback otherwise they are considered invalid and are culled
+  /// from core.
   bool get canBeOrphaned => false;
+
+  // Used during update process.
   int graphOrder = 0;
   int dirt = 0xFFFF;
+
+  // This is really only for sanity and earlying out of recursive loops.
   static const int maxTreeDepth = 5000;
+
   bool addDirt(int value, {bool recurse = false}) {
     if ((dirt & value) == value) {
+      // Already marked.
       return false;
     }
+
+    // Make sure dirt is set before calling anything that can set more dirt.
     dirt |= value;
+
     onDirty(dirt);
     artboard?.onComponentDirty(this);
+
     if (!recurse) {
       return true;
     }
+
     for (final d in dependents) {
       d.addDirt(value, recurse: recurse);
     }
@@ -33,7 +50,12 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
 
   void onDirty(int mask) {}
   void update(int dirt);
+
+  /// The artboard this component belongs to.
   Artboard? get artboard => _artboard;
+
+  // Note that this isn't a setter as we don't want anything externally changing
+  // the artboard.
   void _changeArtboard(Artboard? value) {
     if (_artboard == value) {
       return;
@@ -43,8 +65,14 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
     _artboard?.addComponent(this);
   }
 
+  /// Called whenever we're resolving the artboard, we piggy back on that
+  /// process to visit ancestors in the tree. This is a good opportunity to
+  /// check if we have an ancestor of a specific type. For example, a Path needs
+  /// to know which Shape it's within.
   @mustCallSuper
   void visitAncestor(Component ancestor) {}
+
+  /// Find the artboard in the hierarchy.
   bool resolveArtboard() {
     int sanity = maxTreeDepth;
     for (Component? curr = this;
@@ -71,6 +99,7 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
   }
 
   void userDataChanged(dynamic from, dynamic to) {}
+
   @override
   void parentIdChanged(int from, int to) {
     parent = context.resolve(to);
@@ -79,6 +108,7 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
   ContainerComponent? _parent;
   @override
   ContainerComponent? get parent => _parent;
+
   set parent(ContainerComponent? value) {
     if (_parent == value) {
       return;
@@ -93,28 +123,40 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
   void parentChanged(ContainerComponent? from, ContainerComponent? to) {
     from?.children.remove(this);
     from?.childRemoved(this);
+
     to?.children.add(this);
     to?.childAdded(this);
+
+    // We need to resolve our artboard.
     markRebuildDependencies();
   }
 
+  /// Components that depend on this component.
   final Set<Component> _dependents = {};
+
+  /// Components that this component depends on.
   final Set<Component> _dependsOn = {};
+
   @override
   Set<Component> get dependents => _dependents;
+
   bool addDependent(Component dependent) {
     assert(artboard == dependent.artboard,
         'Components must be in the same artboard.');
+
     if (!_dependents.add(dependent)) {
       return false;
     }
     dependent._dependsOn.add(this);
+
     return true;
   }
 
   bool isValidParent(Component parent) => parent is ContainerComponent;
+
   void markRebuildDependencies() {
     if (!context.markDependenciesDirty(this)) {
+      // no context, or already dirty.
       return;
     }
     for (final dependent in _dependents) {
@@ -128,11 +170,18 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
       parentDep._dependents.remove(this);
     }
     _dependsOn.clear();
+    // by default a component depends on nothing (likely it will depend on the
+    // parent but we leave that for specific implementations to supply).
   }
 
+  /// Something we depend on has been removed. It's important to clear out any
+  /// stored references to that dependency so it can be garbage collected (if
+  /// necessary).
   void onDependencyRemoved(Component dependent) {}
+
   @override
   void onAdded() {}
+
   @override
   void onAddedDirty() {
     if (parentId != Core.missingId) {
@@ -140,6 +189,11 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
     }
   }
 
+  /// When a component has been removed from the Core Context, we clean up any
+  /// dangling references left on the parent and on any other dependent
+  /// component. It's important for specialization of Component to respond to
+  /// override [onDependencyRemoved] and clean up any further stored references
+  /// to that component (for example the target of a Constraint).
   @override
   @mustCallSuper
   void onRemoved() {
@@ -148,14 +202,22 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
       parentDep._dependents.remove(this);
     }
     _dependsOn.clear();
+
     for (final dependent in _dependents) {
       dependent.onDependencyRemoved(this);
     }
     _dependents.clear();
+
+    // silently clear from the parent in order to not cause any further undo
+    // stack changes
     if (parent != null) {
       parent!.children.remove(this);
       parent!.childRemoved(this);
     }
+
+    // The artboard containing this component will need its dependencies
+    // re-sorted.
+
     if (artboard != null) {
       context.markDependencyOrderDirty();
       _changeArtboard(null);
@@ -168,7 +230,10 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
   }
 
   @override
-  void nameChanged(String from, String to) {}
+  void nameChanged(String from, String to) {
+    /// Changing name doesn't really do anything.
+  }
+
   @override
   bool import(ImportStack stack) {
     var artboardImporter = stack.latest<ArtboardImporter>(ArtboardBase.typeKey);
@@ -176,6 +241,7 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
       return false;
     }
     artboardImporter.addComponent(this);
+
     return super.import(stack);
   }
 }
diff --git a/lib/src/rive_core/component_dirt.dart b/lib/src/rive_core/component_dirt.dart
index ddda4d6..f30bd03 100644
--- a/lib/src/rive_core/component_dirt.dart
+++ b/lib/src/rive_core/component_dirt.dart
@@ -1,14 +1,41 @@
 class ComponentDirt {
   static const int dependents = 1 << 0;
+
+  /// General flag for components are dirty (if this is up, the update cycle
+  /// runs). It gets automatically applied with any other dirt.
   static const int components = 1 << 1;
+
+  /// Draw order needs to be re-computed.
   static const int drawOrder = 1 << 2;
+
+  /// Draw order needs to be re-computed.
   static const int naturalDrawOrder = 1 << 3;
+
+  /// Path is dirty and needs to be rebuilt.
   static const int path = 1 << 4;
+
+  /// Vertices have changed, re-order cached lists.
   static const int vertices = 1 << 5;
+
+  /// Used by any component that needs to recompute their local transform.
+  /// Usually components that have their transform dirty will also have their
+  /// worldTransform dirty.
   static const int transform = 1 << 6;
+
+  /// Used by any component that needs to update its world transform.
   static const int worldTransform = 1 << 7;
+
+  /// Dirt used to mark some stored paint needs to be rebuilt or that we just
+  /// want to trigger an update cycle so painting occurs.
   static const int paint = 1 << 8;
+
+  /// Used by the gradients track when the stops need to be re-ordered.
   static const int stops = 1 << 9;
+
+  /// Used by ClippingShape to help Shape know when to recalculate its list of
+  /// clipping sources.
   static const int clip = 1 << 10;
+
+  /// Set when blend modes need to be updated.
   static const int blendMode = 1 << 11;
 }
diff --git a/lib/src/rive_core/component_flags.dart b/lib/src/rive_core/component_flags.dart
index a999c6b..0b48151 100644
--- a/lib/src/rive_core/component_flags.dart
+++ b/lib/src/rive_core/component_flags.dart
@@ -1,4 +1,8 @@
 class ComponentFlags {
+  /// Whether the component should be drawn (at runtime this only used by
+  /// drawables and paths).
   static const int hidden = 1 << 0;
+
+  // Whether the component was locked for editing in the editor.
   static const int locked = 1 << 1;
 }
diff --git a/lib/src/rive_core/container_component.dart b/lib/src/rive_core/container_component.dart
index 4ab8e17..8d0fb6c 100644
--- a/lib/src/rive_core/container_component.dart
+++ b/lib/src/rive_core/container_component.dart
@@ -19,7 +19,11 @@ abstract class ContainerComponent extends ContainerComponentBase {
 
   @mustCallSuper
   void childAdded(Component child) {}
+
   void childRemoved(Component child) {}
+
+  // Make sure that the current function can be applied to the current
+  // [Component], before descending onto all the children.
   bool forAll(DescentCallback cb) {
     if (cb(this) == false) {
       return false;
@@ -28,17 +32,27 @@ abstract class ContainerComponent extends ContainerComponentBase {
     return true;
   }
 
+  // Recursively descend onto all the children in the hierarchy tree.
+  // If the callback returns false, it won't recurse down a particular branch.
   void forEachChild(DescentCallback cb) {
     for (final child in children) {
       if (cb(child) == false) {
         continue;
       }
+
+      // TODO: replace with a more robust check.
       if (child is ContainerComponent) {
         child.forEachChild(cb);
       }
     }
   }
 
+  /// Recursive version of [Component.remove]. This should only be called when
+  /// you know this is the only part of the branch you are removing in your
+  /// operation. If your operation could remove items from the same branch
+  /// multiple times, you should consider building up a list of the individual
+  /// items to remove and then remove them individually to avoid calling remove
+  /// multiple times on children.
   void removeRecursive() {
     Set<Component> deathRow = {this};
     forEachChild((child) => deathRow.add(child));
diff --git a/lib/src/rive_core/draw_rules.dart b/lib/src/rive_core/draw_rules.dart
index 7a11973..58ef46d 100644
--- a/lib/src/rive_core/draw_rules.dart
+++ b/lib/src/rive_core/draw_rules.dart
@@ -7,10 +7,12 @@ export 'package:rive/src/generated/draw_rules_base.dart';
 class DrawRules extends DrawRulesBase {
   final Set<DrawTarget> _targets = {};
   Set<DrawTarget> get targets => _targets;
+
   DrawTarget? _activeTarget;
   DrawTarget? get activeTarget => _activeTarget;
   set activeTarget(DrawTarget? value) =>
       drawTargetId = value?.id ?? Core.missingId;
+
   @override
   void drawTargetIdChanged(int from, int to) {
     _activeTarget = context.resolve(to);
@@ -25,12 +27,14 @@ class DrawRules extends DrawRulesBase {
 
   @override
   void update(int dirt) {}
+
   @override
   void childAdded(Component child) {
     super.childAdded(child);
     switch (child.coreType) {
       case DrawTargetBase.typeKey:
         _targets.add(child as DrawTarget);
+
         break;
     }
   }
@@ -44,6 +48,7 @@ class DrawRules extends DrawRulesBase {
         if (_targets.isEmpty) {
           remove();
         }
+
         break;
     }
   }
diff --git a/lib/src/rive_core/draw_target.dart b/lib/src/rive_core/draw_target.dart
index 42dd856..f155339 100644
--- a/lib/src/rive_core/draw_target.dart
+++ b/lib/src/rive_core/draw_target.dart
@@ -6,14 +6,17 @@ export 'package:rive/src/generated/draw_target_base.dart';
 enum DrawTargetPlacement { before, after }
 
 class DrawTarget extends DrawTargetBase {
+  // Store first and last drawables that are affected by this target.
   Drawable? first;
   Drawable? last;
+
   Drawable? _drawable;
   Drawable? get drawable => _drawable;
   set drawable(Drawable? value) {
     if (_drawable == value) {
       return;
     }
+
     _drawable = value;
     drawableId = value?.id ?? Core.missingId;
   }
@@ -21,6 +24,7 @@ class DrawTarget extends DrawTargetBase {
   DrawTargetPlacement get placement =>
       DrawTargetPlacement.values[placementValue];
   set placement(DrawTargetPlacement value) => placementValue = value.index;
+
   @override
   void drawableIdChanged(int from, int to) {
     drawable = context.resolve(to);
diff --git a/lib/src/rive_core/drawable.dart b/lib/src/rive_core/drawable.dart
index 60a3d9f..2f15f9f 100644
--- a/lib/src/rive_core/drawable.dart
+++ b/lib/src/rive_core/drawable.dart
@@ -1,4 +1,5 @@
 import 'dart:ui';
+
 import 'package:rive/src/rive_core/component_dirt.dart';
 import 'package:rive/src/rive_core/component_flags.dart';
 import 'package:rive/src/rive_core/container_component.dart';
@@ -9,23 +10,37 @@ import 'package:rive/src/rive_core/transform_component.dart';
 export 'package:rive/src/generated/drawable_base.dart';
 
 abstract class Drawable extends DrawableBase {
+  /// Flattened rules inherited from parents (or self) so we don't have to look
+  /// up the tree when re-sorting.
   DrawRules? flattenedDrawRules;
+
+  /// The previous drawable in the draw order.
   Drawable? prev;
+
+  /// The next drawable in the draw order.
   Drawable? next;
+
   @override
   void buildDrawOrder(
       List<Drawable> drawables, DrawRules? rules, List<DrawRules> allRules) {
     flattenedDrawRules = drawRules ?? rules;
+
     drawables.add(this);
+
     super.buildDrawOrder(drawables, rules, allRules);
   }
 
+  /// Draw the contents of this drawable component in world transform space.
   void draw(Canvas canvas);
+
   BlendMode get blendMode => BlendMode.values[blendModeValue];
   set blendMode(BlendMode value) => blendModeValue = value.index;
+
   @override
   void blendModeValueChanged(int from, int to) {}
+
   List<ClippingShape> _clippingShapes = [];
+
   bool clip(Canvas canvas) {
     if (_clippingShapes.isEmpty) {
       return false;
@@ -43,6 +58,8 @@ abstract class Drawable extends DrawableBase {
   @override
   void parentChanged(ContainerComponent? from, ContainerComponent? to) {
     super.parentChanged(from, to);
+    // Make sure we re-compute clipping shapes when we change parents. Issue
+    // #1586
     addDirt(ComponentDirt.clip);
   }
 
@@ -50,6 +67,7 @@ abstract class Drawable extends DrawableBase {
   void update(int dirt) {
     super.update(dirt);
     if (dirt & ComponentDirt.clip != 0) {
+      // Find clip in parents.
       List<ClippingShape> clippingShapes = [];
       for (ContainerComponent? p = this; p != null; p = p.parent) {
         if (p is TransformComponent) {
@@ -62,7 +80,9 @@ abstract class Drawable extends DrawableBase {
     }
   }
 
+  // When drawable flags change, repaint.
   @override
   void drawableFlagsChanged(int from, int to) => addDirt(ComponentDirt.paint);
+
   bool get isHidden => (drawableFlags & ComponentFlags.hidden) != 0;
 }
diff --git a/lib/src/rive_core/event.dart b/lib/src/rive_core/event.dart
index a8f87db..d369c86 100644
--- a/lib/src/rive_core/event.dart
+++ b/lib/src/rive_core/event.dart
@@ -1,6 +1,8 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 
+// Just a way to get around the protected notifyListeners so we can use trigger
+// multiple events from a single object.
 class Event extends ChangeNotifier {
   void notify() => notifyListeners();
 }
diff --git a/lib/src/rive_core/math/aabb.dart b/lib/src/rive_core/math/aabb.dart
index 9a291bf..a26a175 100644
--- a/lib/src/rive_core/math/aabb.dart
+++ b/lib/src/rive_core/math/aabb.dart
@@ -5,16 +5,19 @@ import 'package:rive/src/rive_core/math/vec2d.dart';
 
 class AABB {
   Float32List _buffer;
+
   Float32List get values {
     return _buffer;
   }
 
   Vec2D get topLeft => minimum;
+
   Vec2D get topRight {
     return Vec2D.fromValues(_buffer[2], _buffer[1]);
   }
 
   Vec2D get bottomRight => maximum;
+
   Vec2D get bottomLeft {
     return Vec2D.fromValues(_buffer[0], _buffer[3]);
   }
@@ -31,10 +34,14 @@ class AABB {
   double get maxX => _buffer[2];
   double get minY => _buffer[1];
   double get maxY => _buffer[3];
+
   AABB() : _buffer = Float32List.fromList([0.0, 0.0, 0.0, 0.0]);
+
   AABB.clone(AABB a) : _buffer = Float32List.fromList(a.values);
+
   AABB.fromValues(double a, double b, double c, double d)
       : _buffer = Float32List.fromList([a, b, c, d]);
+
   AABB.empty()
       : _buffer = Float32List.fromList([
           double.maxFinite,
@@ -42,6 +49,7 @@ class AABB {
           -double.maxFinite,
           -double.maxFinite
         ]);
+
   factory AABB.expand(AABB from, double amount) {
     var aabb = AABB.clone(from);
     if (aabb.width < amount) {
@@ -54,6 +62,7 @@ class AABB {
     }
     return aabb;
   }
+
   factory AABB.pad(AABB from, double amount) {
     var aabb = AABB.clone(from);
     aabb[0] -= amount;
@@ -62,7 +71,9 @@ class AABB {
     aabb[3] += amount;
     return aabb;
   }
+
   bool get isEmpty => !AABB.isValid(this);
+
   Vec2D includePoint(Vec2D point, Mat2D? transform) {
     var transformedPoint = transform == null
         ? point
@@ -90,12 +101,15 @@ class AABB {
 
   AABB.fromMinMax(Vec2D min, Vec2D max)
       : _buffer = Float32List.fromList([min[0], min[1], max[0], max[1]]);
+
   static bool areEqual(AABB a, AABB b) {
     return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3];
   }
 
   double get width => _buffer[2] - _buffer[0];
+
   double get height => _buffer[3] - _buffer[1];
+
   double operator [](int idx) {
     return _buffer[idx];
   }
@@ -162,14 +176,18 @@ class AABB {
   static bool testOverlap(AABB a, AABB b) {
     double d1x = b[0] - a[2];
     double d1y = b[1] - a[3];
+
     double d2x = a[0] - b[2];
     double d2y = a[1] - b[3];
+
     if (d1x > 0.0 || d1y > 0.0) {
       return false;
     }
+
     if (d2x > 0.0 || d2y > 0.0) {
       return false;
     }
+
     return true;
   }
 
@@ -181,6 +199,7 @@ class AABB {
 
   AABB translate(Vec2D vec) => AABB.fromValues(_buffer[0] + vec[0],
       _buffer[1] + vec[1], _buffer[2] + vec[0], _buffer[3] + vec[1]);
+
   @override
   String toString() {
     return _buffer.toString();
@@ -195,16 +214,23 @@ class AABB {
     ], transform: matrix);
   }
 
-  factory AABB.fromPoints(Iterable<Vec2D> points,
-      {Mat2D? transform, double expand = 0}) {
+  /// Compute an AABB from a set of points with an optional [transform] to apply
+  /// before computing.
+  factory AABB.fromPoints(
+    Iterable<Vec2D> points, {
+    Mat2D? transform,
+    double expand = 0,
+  }) {
     double minX = double.maxFinite;
     double minY = double.maxFinite;
     double maxX = -double.maxFinite;
     double maxY = -double.maxFinite;
+
     for (final point in points) {
       var p = transform == null
           ? point
           : Vec2D.transformMat2D(Vec2D(), point, transform);
+
       double x = p[0];
       double y = p[1];
       if (x < minX) {
@@ -213,6 +239,7 @@ class AABB {
       if (y < minY) {
         minY = y;
       }
+
       if (x > maxX) {
         maxX = x;
       }
@@ -220,6 +247,8 @@ class AABB {
         maxY = y;
       }
     }
+
+    // Make sure the box is at least this wide/high
     if (expand != 0) {
       double width = maxX - minX;
       double diff = expand - width;
@@ -230,6 +259,7 @@ class AABB {
       }
       double height = maxY - minY;
       diff = expand - height;
+
       if (diff > 0) {
         diff /= 2;
         minY -= diff;
diff --git a/lib/src/rive_core/math/circle_constant.dart b/lib/src/rive_core/math/circle_constant.dart
index 70e7187..4710375 100644
--- a/lib/src/rive_core/math/circle_constant.dart
+++ b/lib/src/rive_core/math/circle_constant.dart
@@ -1,2 +1,4 @@
+/// Use this for perfect rounded corners.
+/// https://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves
 const circleConstant = 0.552284749831;
 const icircleConstant = 1 - circleConstant;
diff --git a/lib/src/rive_core/math/mat2d.dart b/lib/src/rive_core/math/mat2d.dart
index 24a0753..e49a6ef 100644
--- a/lib/src/rive_core/math/mat2d.dart
+++ b/lib/src/rive_core/math/mat2d.dart
@@ -1,8 +1,11 @@
 import 'dart:math';
 import 'dart:typed_data';
+
 import 'package:rive/src/rive_core/math/transform_components.dart';
 import 'package:rive/src/rive_core/math/vec2d.dart';
 
+/// Can't make this constant so we override and disable changing values so we'll
+/// throw if something tries to change the identity.
 class _Identity extends Mat2D {
   @override
   void operator []=(int index, double value) => throw UnsupportedError(
@@ -12,6 +15,7 @@ class _Identity extends Mat2D {
 class Mat2D {
   static final Mat2D identity = _Identity();
   final Float32List _buffer;
+
   Float32List get values {
     return _buffer;
   }
@@ -46,15 +50,20 @@ class Mat2D {
   }
 
   Mat2D() : _buffer = Float32List.fromList([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
+
   Mat2D.fromTranslation(Vec2D translation)
       : _buffer = Float32List.fromList(
             [1.0, 0.0, 0.0, 1.0, translation[0], translation[1]]);
+
   Mat2D.fromScaling(Vec2D scaling)
       : _buffer = Float32List.fromList([scaling[0], 0, 0, scaling[1], 0, 0]);
+
   Mat2D.fromMat4(Float64List mat4)
       : _buffer = Float32List.fromList(
             [mat4[0], mat4[1], mat4[4], mat4[5], mat4[12], mat4[13]]);
+
   Mat2D.clone(Mat2D copy) : _buffer = Float32List.fromList(copy.values);
+
   static Mat2D fromRotation(Mat2D o, double rad) {
     double s = sin(rad);
     double c = cos(rad);
@@ -163,11 +172,13 @@ class Mat2D {
 
   static bool invert(Mat2D o, Mat2D a) {
     double aa = a[0], ab = a[1], ac = a[2], ad = a[3], atx = a[4], aty = a[5];
+
     double det = aa * ad - ab * ac;
     if (det == 0.0) {
       return false;
     }
     det = 1.0 / det;
+
     o[0] = ad * det;
     o[1] = -ab * det;
     o[2] = -ac * det;
@@ -181,6 +192,7 @@ class Mat2D {
     double x = m[0];
     double y = m[1];
     s[0] = x.sign * sqrt(x * x + y * y);
+
     x = m[2];
     y = m[3];
     s[1] = y.sign * sqrt(x * x + y * y);
@@ -203,11 +215,13 @@ class Mat2D {
 
   static void decompose(Mat2D m, TransformComponents result) {
     double m0 = m[0], m1 = m[1], m2 = m[2], m3 = m[3];
+
     double rotation = atan2(m1, m0);
     double denom = m0 * m0 + m1 * m1;
     double scaleX = sqrt(denom);
     double scaleY = (scaleX == 0) ? 0 : ((m0 * m3 - m2 * m1) / scaleX);
     double skewX = atan2(m0 * m2 + m1 * m3, denom);
+
     result[0] = m[4];
     result[1] = m[5];
     result[2] = scaleX;
@@ -218,6 +232,7 @@ class Mat2D {
 
   static void compose(Mat2D m, TransformComponents result) {
     double r = result[4];
+
     if (r != 0.0) {
       Mat2D.fromRotation(m, r);
     } else {
@@ -226,6 +241,7 @@ class Mat2D {
     m[4] = result[0];
     m[5] = result[1];
     Mat2D.scale(m, m, result.scale);
+
     double sk = result[5];
     if (sk != 0.0) {
       m[2] = m[0] * sk + m[2];
diff --git a/lib/src/rive_core/math/segment2d.dart b/lib/src/rive_core/math/segment2d.dart
index 04d48dd..c44a213 100644
--- a/lib/src/rive_core/math/segment2d.dart
+++ b/lib/src/rive_core/math/segment2d.dart
@@ -1,18 +1,39 @@
 import 'package:rive/src/rive_core/math/vec2d.dart';
 
+/// Result of projecting a point onto a segment.
 class ProjectionResult {
+  /// The distance factor from 0-1 along the segment starting at the
+  /// [Segment2D.start].
   final double t;
+
+  /// The actual 2d point in the same space as [Segment2D.start] and
+  /// [Segment2D.end].
   final Vec2D point;
+
   ProjectionResult(this.t, this.point);
 }
 
+/// A line segment with a discrete [start] and [end].
 class Segment2D {
+  /// The starting point of this line segment.
   final Vec2D start;
+
+  /// The ending point of this line segment.
   final Vec2D end;
+
+  /// Difference from start to end. Nullable so we can compute it only when we
+  /// need it.
   Vec2D? diff;
+
+  /// The squared length of this segment.
   double lengthSquared = 0;
+
   Segment2D(this.start, this.end);
+
+  /// Find where the given [point] lies on this segment.
   ProjectionResult projectPoint(Vec2D point, {bool clamp = true}) {
+    // We cache these internally so we can call projectPoint multiple times in
+    // succession performantly.
     if (diff == null) {
       diff = Vec2D.subtract(Vec2D(), start, end);
       lengthSquared = Vec2D.squaredLength(diff!);
@@ -23,7 +44,9 @@ class Segment2D {
     double t = ((point[0] - start[0]) * (end[0] - start[0]) +
             (point[1] - start[1]) * (end[1] - start[1])) /
         lengthSquared;
+
     if (clamp) {
+      // Clamp at edges.
       if (t < 0.0) {
         return ProjectionResult(0, start);
       }
@@ -31,9 +54,13 @@ class Segment2D {
         return ProjectionResult(1, end);
       }
     }
+
     return ProjectionResult(
-        t,
-        Vec2D.fromValues(start[0] + t * (end[0] - start[0]),
-            start[1] + t * (end[1] - start[1])));
+      t,
+      Vec2D.fromValues(
+        start[0] + t * (end[0] - start[0]),
+        start[1] + t * (end[1] - start[1]),
+      ),
+    );
   }
 }
diff --git a/lib/src/rive_core/math/transform_components.dart b/lib/src/rive_core/math/transform_components.dart
index 34bc35c..6756270 100644
--- a/lib/src/rive_core/math/transform_components.dart
+++ b/lib/src/rive_core/math/transform_components.dart
@@ -1,9 +1,11 @@
 import 'dart:math';
 import 'dart:typed_data';
+
 import 'package:rive/src/rive_core/math/vec2d.dart';
 
 class TransformComponents {
   final Float32List _buffer;
+
   Float32List get values {
     return _buffer;
   }
@@ -18,8 +20,10 @@ class TransformComponents {
 
   TransformComponents()
       : _buffer = Float32List.fromList([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
+
   TransformComponents.clone(TransformComponents copy)
       : _buffer = Float32List.fromList(copy.values);
+
   double get x {
     return _buffer[0];
   }
diff --git a/lib/src/rive_core/math/vec2d.dart b/lib/src/rive_core/math/vec2d.dart
index ddefa8f..7b5042d 100644
--- a/lib/src/rive_core/math/vec2d.dart
+++ b/lib/src/rive_core/math/vec2d.dart
@@ -5,6 +5,7 @@ import 'package:rive/src/utilities/utilities.dart';
 
 class Vec2D {
   final Float32List _buffer;
+
   Float32List get values {
     return _buffer;
   }
@@ -18,8 +19,11 @@ class Vec2D {
   }
 
   Vec2D() : _buffer = Float32List.fromList([0.0, 0.0]);
+
   Vec2D.clone(Vec2D copy) : _buffer = Float32List.fromList(copy._buffer);
+
   Vec2D.fromValues(double x, double y) : _buffer = Float32List.fromList([x, y]);
+
   static void copy(Vec2D o, Vec2D a) {
     o[0] = a[0];
     o[1] = a[1];
@@ -99,6 +103,7 @@ class Vec2D {
   static Vec2D negate(Vec2D result, Vec2D a) {
     result[0] = -1 * a[0];
     result[1] = -1 * a[1];
+
     return result;
   }
 
@@ -143,9 +148,12 @@ class Vec2D {
     if (t >= 1) {
       return Vec2D.squaredDistance(segmentPoint2, pt);
     }
+
     Vec2D ptOnSeg = Vec2D.fromValues(
-        segmentPoint1[0] + t * (segmentPoint2[0] - segmentPoint1[0]),
-        segmentPoint1[1] + t * (segmentPoint2[1] - segmentPoint1[1]));
+      segmentPoint1[0] + t * (segmentPoint2[0] - segmentPoint1[0]),
+      segmentPoint1[1] + t * (segmentPoint2[1] - segmentPoint1[1]),
+    );
+
     return Vec2D.squaredDistance(ptOnSeg, pt);
   }
 
@@ -165,6 +173,7 @@ class Vec2D {
   @override
   bool operator ==(Object o) =>
       o is Vec2D && _buffer[0] == o[0] && _buffer[1] == o[1];
+
   @override
   int get hashCode => szudzik(_buffer[0].hashCode, _buffer[1].hashCode);
 }
diff --git a/lib/src/rive_core/node.dart b/lib/src/rive_core/node.dart
index eaf97b2..222e777 100644
--- a/lib/src/rive_core/node.dart
+++ b/lib/src/rive_core/node.dart
@@ -7,6 +7,8 @@ class _UnknownNode extends Node {}
 
 class Node extends NodeBase {
   static final Node unknown = _UnknownNode();
+
+  /// Sets the position of the Node
   set translation(Vec2D pos) {
     x = pos[0];
     y = pos[1];
diff --git a/lib/src/rive_core/rive_animation_controller.dart b/lib/src/rive_core/rive_animation_controller.dart
index e64948e..007cf0e 100644
--- a/lib/src/rive_core/rive_animation_controller.dart
+++ b/lib/src/rive_core/rive_animation_controller.dart
@@ -1,9 +1,12 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/widgets.dart';
 
+/// Abstraction for receiving a per frame callback while isPlaying is true to
+/// apply animation based on an elapsed amount of time.
 abstract class RiveAnimationController<T> {
   final _isActive = ValueNotifier<bool>(false);
   ValueListenable<bool> get isActiveChanged => _isActive;
+
   bool get isActive => _isActive.value;
   set isActive(bool value) {
     if (_isActive.value != value) {
@@ -20,7 +23,11 @@ abstract class RiveAnimationController<T> {
   void onActivate() {}
   @protected
   void onDeactivate() {}
+
+  /// Apply animation to objects registered in [core]. Note that a [core]
+  /// context is specified as animations can be applied to instances.
   void apply(T core, double elapsedSeconds);
+
   bool init(T core) => true;
   void dispose() {}
 }
diff --git a/lib/src/rive_core/runtime/exceptions/rive_format_error_exception.dart b/lib/src/rive_core/runtime/exceptions/rive_format_error_exception.dart
index efda20a..e90bb00 100644
--- a/lib/src/rive_core/runtime/exceptions/rive_format_error_exception.dart
+++ b/lib/src/rive_core/runtime/exceptions/rive_format_error_exception.dart
@@ -1,5 +1,6 @@
 import 'package:meta/meta.dart';
 
+/// Thrown when a file being read doesn't match the Rive format.
 @immutable
 class RiveFormatErrorException implements Exception {
   final String cause;
diff --git a/lib/src/rive_core/runtime/exceptions/rive_unsupported_version_exception.dart b/lib/src/rive_core/runtime/exceptions/rive_unsupported_version_exception.dart
index 52f6c7d..b505940 100644
--- a/lib/src/rive_core/runtime/exceptions/rive_unsupported_version_exception.dart
+++ b/lib/src/rive_core/runtime/exceptions/rive_unsupported_version_exception.dart
@@ -1,5 +1,7 @@
 import 'package:meta/meta.dart';
 
+/// Error that occurs when a file being loaded doesn't match the importer's
+/// supported version.
 @immutable
 class RiveUnsupportedVersionException implements Exception {
   final int majorVersion;
@@ -8,6 +10,7 @@ class RiveUnsupportedVersionException implements Exception {
   final int fileMinorVersion;
   const RiveUnsupportedVersionException(this.majorVersion, this.minorVersion,
       this.fileMajorVersion, this.fileMinorVersion);
+
   @override
   String toString() {
     return 'File contains version $fileMajorVersion.$fileMinorVersion. '
diff --git a/lib/src/rive_core/runtime/runtime_header.dart b/lib/src/rive_core/runtime/runtime_header.dart
index b5c6565..acf01c1 100644
--- a/lib/src/rive_core/runtime/runtime_header.dart
+++ b/lib/src/rive_core/runtime/runtime_header.dart
@@ -1,11 +1,15 @@
 import 'dart:collection';
+
 import 'package:rive/src/rive_core/runtime/exceptions/rive_format_error_exception.dart';
 import 'package:rive/src/rive_core/runtime/exceptions/rive_unsupported_version_exception.dart';
 import 'package:rive/src/utilities/binary_buffer/binary_reader.dart';
 
+/// Stores the minor and major version of Rive. Versions with the same major
+/// value are backwards and forwards compatible.
 class RuntimeVersion {
   final int major;
   final int minor;
+
   const RuntimeVersion(this.major, this.minor);
   String versionString() {
     return '$major.$minor';
@@ -17,19 +21,26 @@ const riveVersion = RuntimeVersion(7, 0);
 class RuntimeHeader {
   static const String fingerprint = 'RIVE';
   final RuntimeVersion version;
+
   final int fileId;
+
   final HashMap<int, int> propertyToFieldIndex;
-  RuntimeHeader(
-      {required this.fileId,
-      required this.version,
-      required this.propertyToFieldIndex});
+
+  RuntimeHeader({
+    required this.fileId,
+    required this.version,
+    required this.propertyToFieldIndex,
+  });
+
   factory RuntimeHeader.read(BinaryReader reader) {
     var fingerprint = RuntimeHeader.fingerprint.codeUnits;
+
     for (int i = 0; i < fingerprint.length; i++) {
       if (reader.readUint8() != fingerprint[i]) {
         throw const RiveFormatErrorException('Fingerprint doesn\'t match.');
       }
     }
+
     int readMajorVersion = reader.readVarUint();
     int readMinorVersion = reader.readVarUint();
     if (readMajorVersion > riveVersion.major) {
@@ -40,7 +51,9 @@ class RuntimeHeader {
       reader.readVarUint();
     }
     int fileId = reader.readVarUint();
+
     var propertyFields = HashMap<int, int>();
+
     var propertyKeys = <int>[];
     for (int propertyKey = reader.readVarUint();
         propertyKey != 0;
@@ -58,9 +71,11 @@ class RuntimeHeader {
       propertyFields[propertyKey] = fieldIndex;
       currentBit += 2;
     }
+
     return RuntimeHeader(
-        fileId: fileId,
-        version: RuntimeVersion(readMajorVersion, readMinorVersion),
-        propertyToFieldIndex: propertyFields);
+      fileId: fileId,
+      version: RuntimeVersion(readMajorVersion, readMinorVersion),
+      propertyToFieldIndex: propertyFields,
+    );
   }
 }
diff --git a/lib/src/rive_core/shapes/clipping_shape.dart b/lib/src/rive_core/shapes/clipping_shape.dart
index f8258cd..ef0da44 100644
--- a/lib/src/rive_core/shapes/clipping_shape.dart
+++ b/lib/src/rive_core/shapes/clipping_shape.dart
@@ -1,4 +1,5 @@
 import 'dart:ui';
+
 import 'package:rive/src/rive_core/component_dirt.dart';
 import 'package:rive/src/rive_core/node.dart';
 import 'package:rive/src/rive_core/shapes/shape.dart';
@@ -10,19 +11,25 @@ class ClippingShape extends ClippingShapeBase {
   final List<Shape> _shapes = [];
   PathFillType get fillType => PathFillType.values[fillRule];
   set fillType(PathFillType type) => fillRule = type.index;
+
   Node _source = Node.unknown;
   Node get source => _source;
   set source(Node value) {
     if (_source == value) {
       return;
     }
+
     _source = value;
     sourceId = value.id;
   }
 
   @override
   void fillRuleChanged(int from, int to) {
+    // In the future, if clipOp can change at runtime (animation), we may want
+    // the shapes that use this as a clipping source to make them depend on this
+    // clipping shape so we can add dirt to them directly.
     parent?.addDirt(ComponentDirt.clip, recurse: true);
+
     addDirt(ComponentDirt.path);
   }
 
@@ -44,10 +51,13 @@ class ClippingShape extends ClippingShapeBase {
     _source.forAll((component) {
       if (component is Shape) {
         _shapes.add(component);
+        //component.addDependent(this);
         component.pathComposer.addDependent(this);
       }
       return true;
     });
+
+    // make sure we rebuild the clipping path.
     addDirt(ComponentDirt.path);
   }
 
@@ -60,6 +70,8 @@ class ClippingShape extends ClippingShapeBase {
   @override
   void update(int dirt) {
     if (dirt & (ComponentDirt.worldTransform | ComponentDirt.path) != 0) {
+      // Build the clipping path as one of our dependent shapes changes or we
+      // added a shape.
       clippingPath.reset();
       clippingPath.fillType = fillType;
       for (final shape in _shapes) {
@@ -75,6 +87,7 @@ class ClippingShape extends ClippingShapeBase {
 
   @override
   void isVisibleChanged(bool from, bool to) {
+    // Redraw
     _source.addDirt(ComponentDirt.paint);
   }
 }
diff --git a/lib/src/rive_core/shapes/cubic_asymmetric_vertex.dart b/lib/src/rive_core/shapes/cubic_asymmetric_vertex.dart
index fec3ba2..d90304a 100644
--- a/lib/src/rive_core/shapes/cubic_asymmetric_vertex.dart
+++ b/lib/src/rive_core/shapes/cubic_asymmetric_vertex.dart
@@ -1,4 +1,5 @@
 import 'dart:math';
+
 import 'package:rive/src/core/core.dart';
 import 'package:rive/src/rive_core/component_dirt.dart';
 import 'package:rive/src/rive_core/math/vec2d.dart';
@@ -10,8 +11,10 @@ class CubicAsymmetricVertex extends CubicAsymmetricVertexBase {
   CubicAsymmetricVertex.procedural() {
     InternalCoreHelper.markValid(this);
   }
+
   Vec2D? _inPoint;
   Vec2D? _outPoint;
+
   @override
   Vec2D get outPoint {
     return _outPoint ??= Vec2D.add(
diff --git a/lib/src/rive_core/shapes/cubic_detached_vertex.dart b/lib/src/rive_core/shapes/cubic_detached_vertex.dart
index 7566bac..8e8d4c0 100644
--- a/lib/src/rive_core/shapes/cubic_detached_vertex.dart
+++ b/lib/src/rive_core/shapes/cubic_detached_vertex.dart
@@ -1,4 +1,5 @@
 import 'dart:math';
+
 import 'package:rive/src/core/core.dart';
 import 'package:rive/src/rive_core/component_dirt.dart';
 import 'package:rive/src/rive_core/math/vec2d.dart';
@@ -8,16 +9,18 @@ export 'package:rive/src/generated/shapes/cubic_detached_vertex_base.dart';
 class CubicDetachedVertex extends CubicDetachedVertexBase {
   Vec2D? _inPoint;
   Vec2D? _outPoint;
+
   CubicDetachedVertex();
-  CubicDetachedVertex.fromValues(
-      {required double x,
-      required double y,
-      double? inX,
-      double? inY,
-      double? outX,
-      double? outY,
-      Vec2D? inPoint,
-      Vec2D? outPoint}) {
+  CubicDetachedVertex.fromValues({
+    required double x,
+    required double y,
+    double? inX,
+    double? inY,
+    double? outX,
+    double? outY,
+    Vec2D? inPoint,
+    Vec2D? outPoint,
+  }) {
     InternalCoreHelper.markValid(this);
     this.x = x;
     this.y = y;
@@ -25,12 +28,14 @@ class CubicDetachedVertex extends CubicDetachedVertexBase {
     this.outPoint =
         Vec2D.fromValues(outX ?? outPoint![0], outY ?? outPoint![1]);
   }
+
   @override
   Vec2D get outPoint => _outPoint ??= Vec2D.add(
       Vec2D(),
       translation,
       Vec2D.fromValues(
           cos(outRotation) * outDistance, sin(outRotation) * outDistance));
+
   @override
   set outPoint(Vec2D value) {
     _outPoint = Vec2D.clone(value);
@@ -42,6 +47,7 @@ class CubicDetachedVertex extends CubicDetachedVertexBase {
       translation,
       Vec2D.fromValues(
           cos(inRotation) * inDistance, sin(inRotation) * inDistance));
+
   @override
   set inPoint(Vec2D value) {
     _inPoint = Vec2D.clone(value);
diff --git a/lib/src/rive_core/shapes/cubic_mirrored_vertex.dart b/lib/src/rive_core/shapes/cubic_mirrored_vertex.dart
index e6aa9bc..31f5514 100644
--- a/lib/src/rive_core/shapes/cubic_mirrored_vertex.dart
+++ b/lib/src/rive_core/shapes/cubic_mirrored_vertex.dart
@@ -1,4 +1,5 @@
 import 'dart:math';
+
 import 'package:rive/src/core/core.dart';
 import 'package:rive/src/rive_core/component_dirt.dart';
 import 'package:rive/src/rive_core/math/vec2d.dart';
@@ -7,11 +8,15 @@ export 'package:rive/src/generated/shapes/cubic_mirrored_vertex_base.dart';
 
 class CubicMirroredVertex extends CubicMirroredVertexBase {
   CubicMirroredVertex();
+
+  /// Makes a vertex that is disconnected from core.
   CubicMirroredVertex.procedural() {
     InternalCoreHelper.markValid(this);
   }
+
   Vec2D? _inPoint;
   Vec2D? _outPoint;
+
   @override
   Vec2D get outPoint {
     return _outPoint ??= Vec2D.add(Vec2D(), translation,
diff --git a/lib/src/rive_core/shapes/cubic_vertex.dart b/lib/src/rive_core/shapes/cubic_vertex.dart
index f97e437..25d1bb8 100644
--- a/lib/src/rive_core/shapes/cubic_vertex.dart
+++ b/lib/src/rive_core/shapes/cubic_vertex.dart
@@ -1,4 +1,5 @@
 import 'dart:typed_data';
+
 import 'package:rive/src/rive_core/bones/weight.dart';
 import 'package:rive/src/rive_core/math/mat2d.dart';
 import 'package:rive/src/rive_core/math/vec2d.dart';
@@ -8,15 +9,20 @@ export 'package:rive/src/generated/shapes/cubic_vertex_base.dart';
 abstract class CubicVertex extends CubicVertexBase {
   Vec2D get outPoint;
   Vec2D get inPoint;
+
   set outPoint(Vec2D value);
   set inPoint(Vec2D value);
+
   @override
   Vec2D get renderTranslation => weight?.translation ?? super.renderTranslation;
+
   Vec2D get renderIn => weight?.inTranslation ?? inPoint;
   Vec2D get renderOut => weight?.outTranslation ?? outPoint;
+
   @override
   void deform(Mat2D world, Float32List boneTransforms) {
     super.deform(world, boneTransforms);
+
     Weight.deform(outPoint[0], outPoint[1], weight!.outIndices,
         weight!.outValues, world, boneTransforms, weight!.outTranslation);
     Weight.deform(inPoint[0], inPoint[1], weight!.inIndices, weight!.inValues,
diff --git a/lib/src/rive_core/shapes/ellipse.dart b/lib/src/rive_core/shapes/ellipse.dart
index 0f76b63..3afdfc7 100644
--- a/lib/src/rive_core/shapes/ellipse.dart
+++ b/lib/src/rive_core/shapes/ellipse.dart
@@ -2,6 +2,7 @@ import 'package:rive/src/rive_core/math/circle_constant.dart';
 import 'package:rive/src/rive_core/shapes/cubic_detached_vertex.dart';
 import 'package:rive/src/rive_core/shapes/path_vertex.dart';
 import 'package:rive/src/generated/shapes/ellipse_base.dart';
+
 export 'package:rive/src/generated/shapes/ellipse_base.dart';
 
 class Ellipse extends EllipseBase {
@@ -9,35 +10,40 @@ class Ellipse extends EllipseBase {
   List<PathVertex> get vertices {
     double ox = -originX * width + radiusX;
     double oy = -originY * height + radiusY;
+
     return [
       CubicDetachedVertex.fromValues(
-          x: ox,
-          y: oy - radiusY,
-          inX: ox - radiusX * circleConstant,
-          inY: oy - radiusY,
-          outX: ox + radiusX * circleConstant,
-          outY: oy - radiusY),
+        x: ox,
+        y: oy - radiusY,
+        inX: ox - radiusX * circleConstant,
+        inY: oy - radiusY,
+        outX: ox + radiusX * circleConstant,
+        outY: oy - radiusY,
+      ),
       CubicDetachedVertex.fromValues(
-          x: ox + radiusX,
-          y: oy,
-          inX: ox + radiusX,
-          inY: oy + circleConstant * -radiusY,
-          outX: ox + radiusX,
-          outY: oy + circleConstant * radiusY),
+        x: ox + radiusX,
+        y: oy,
+        inX: ox + radiusX,
+        inY: oy + circleConstant * -radiusY,
+        outX: ox + radiusX,
+        outY: oy + circleConstant * radiusY,
+      ),
       CubicDetachedVertex.fromValues(
-          x: ox,
-          y: oy + radiusY,
-          inX: ox + radiusX * circleConstant,
-          inY: oy + radiusY,
-          outX: ox - radiusX * circleConstant,
-          outY: oy + radiusY),
+        x: ox,
+        y: oy + radiusY,
+        inX: ox + radiusX * circleConstant,
+        inY: oy + radiusY,
+        outX: ox - radiusX * circleConstant,
+        outY: oy + radiusY,
+      ),
       CubicDetachedVertex.fromValues(
-          x: ox - radiusX,
-          y: oy,
-          inX: ox - radiusX,
-          inY: oy + radiusY * circleConstant,
-          outX: ox - radiusX,
-          outY: oy - radiusY * circleConstant)
+        x: ox - radiusX,
+        y: oy,
+        inX: ox - radiusX,
+        inY: oy + radiusY * circleConstant,
+        outX: ox - radiusX,
+        outY: oy - radiusY * circleConstant,
+      ),
     ];
   }
 
diff --git a/lib/src/rive_core/shapes/paint/fill.dart b/lib/src/rive_core/shapes/paint/fill.dart
index 410a3ca..c65f581 100644
--- a/lib/src/rive_core/shapes/paint/fill.dart
+++ b/lib/src/rive_core/shapes/paint/fill.dart
@@ -1,19 +1,28 @@
 import 'dart:ui';
+
 import 'package:rive/src/rive_core/component_dirt.dart';
 import 'package:rive/src/rive_core/shapes/shape_paint_container.dart';
 import 'package:rive/src/generated/shapes/paint/fill_base.dart';
 export 'package:rive/src/generated/shapes/paint/fill_base.dart';
 
+/// A fill Shape painter.
 class Fill extends FillBase {
   @override
   Paint makePaint() => Paint()..style = PaintingStyle.fill;
+
   PathFillType get fillType => PathFillType.values[fillRule];
   set fillType(PathFillType type) => fillRule = type.index;
+
   @override
   void fillRuleChanged(int from, int to) =>
       parent?.addDirt(ComponentDirt.paint);
+
   @override
-  void update(int dirt) {}
+  void update(int dirt) {
+    // Intentionally empty, fill doesn't update.
+    // Because Fill never adds dependencies, it'll also never get called.
+  }
+
   @override
   void onAdded() {
     super.onAdded();
diff --git a/lib/src/rive_core/shapes/paint/gradient_stop.dart b/lib/src/rive_core/shapes/paint/gradient_stop.dart
index 7bb440c..821c32b 100644
--- a/lib/src/rive_core/shapes/paint/gradient_stop.dart
+++ b/lib/src/rive_core/shapes/paint/gradient_stop.dart
@@ -1,7 +1,9 @@
 import 'dart:ui' as ui;
+
 import 'package:rive/src/rive_core/container_component.dart';
 import 'package:rive/src/generated/shapes/paint/gradient_stop_base.dart';
 import 'package:rive/src/rive_core/shapes/paint/linear_gradient.dart';
+
 export 'package:rive/src/generated/shapes/paint/gradient_stop_base.dart';
 
 class GradientStop extends GradientStopBase {
@@ -24,8 +26,10 @@ class GradientStop extends GradientStopBase {
 
   @override
   void update(int dirt) {}
+
   @override
   bool validate() => super.validate() && _gradient != null;
+
   @override
   void parentChanged(ContainerComponent? from, ContainerComponent? to) {
     super.parentChanged(from, to);
diff --git a/lib/src/rive_core/shapes/paint/linear_gradient.dart b/lib/src/rive_core/shapes/paint/linear_gradient.dart
index c2accbf..b4102c1 100644
--- a/lib/src/rive_core/shapes/paint/linear_gradient.dart
+++ b/lib/src/rive_core/shapes/paint/linear_gradient.dart
@@ -1,15 +1,24 @@
 import 'dart:ui' as ui;
+
 import 'package:meta/meta.dart';
 import 'package:rive/src/rive_core/component.dart';
 import 'package:rive/src/rive_core/component_dirt.dart';
 import 'package:rive/src/rive_core/math/vec2d.dart';
 import 'package:rive/src/rive_core/shapes/paint/gradient_stop.dart';
 import 'package:rive/src/rive_core/shapes/paint/shape_paint_mutator.dart';
+import 'package:rive/src/rive_core/shapes/shape.dart';
 import 'package:rive/src/generated/shapes/paint/linear_gradient_base.dart';
 export 'package:rive/src/generated/shapes/paint/linear_gradient_base.dart';
 
+/// A core linear gradient. Can be added as a child to a [Shape]'s [Fill] or
+/// [Stroke] to paint that Fill or Stroke with a gradient. This is the
+/// foundation for the RadialGradient which is very similar but also has a
+/// radius value.
 class LinearGradient extends LinearGradientBase with ShapePaintMutator {
+  /// Stored list of core gradient stops are in the hierarchy as children of
+  /// this container.
   final List<GradientStop> gradientStops = [];
+
   bool _paintsInWorldSpace = true;
   bool get paintsInWorldSpace => _paintsInWorldSpace;
   set paintsInWorldSpace(bool value) {
@@ -22,8 +31,11 @@ class LinearGradient extends LinearGradientBase with ShapePaintMutator {
 
   Vec2D get start => Vec2D.fromValues(startX, startY);
   Vec2D get end => Vec2D.fromValues(endX, endY);
+
   ui.Offset get startOffset => ui.Offset(startX, startY);
   ui.Offset get endOffset => ui.Offset(endX, endY);
+
+  /// Gradients depends on their shape.
   @override
   void buildDependencies() {
     super.buildDependencies();
@@ -35,6 +47,7 @@ class LinearGradient extends LinearGradientBase with ShapePaintMutator {
     super.childAdded(child);
     if (child is GradientStop && !gradientStops.contains(child)) {
       gradientStops.add(child);
+
       markStopsDirty();
     }
   }
@@ -44,31 +57,48 @@ class LinearGradient extends LinearGradientBase with ShapePaintMutator {
     super.childRemoved(child);
     if (child is GradientStop && gradientStops.contains(child)) {
       gradientStops.remove(child);
+
       markStopsDirty();
     }
   }
 
+  /// Mark the gradient stops as changed. This will re-sort the stops and
+  /// rebuild the necessary gradients in the next update cycle.
   void markStopsDirty() => addDirt(ComponentDirt.stops | ComponentDirt.paint);
+
+  /// Mark the gradient as needing to be rebuilt. This is a more efficient
+  /// version of markStopsDirty as it won't re-sort the stops.
   void markGradientDirty() => addDirt(ComponentDirt.paint);
+
   @override
   void update(int dirt) {
+    // Do the stops need to be re-ordered?
     bool stopsChanged = dirt & ComponentDirt.stops != 0;
     if (stopsChanged) {
       gradientStops.sort((a, b) => a.position.compareTo(b.position));
     }
+
     bool worldTransformed = dirt & ComponentDirt.worldTransform != 0;
     bool localTransformed = dirt & ComponentDirt.transform != 0;
+
+    // We rebuild the gradient if the gradient is dirty or we paint in world
+    // space and the world space transform has changed, or the local transform
+    // has changed. Local transform changes when a stop moves in local space.
     var rebuildGradient = dirt & ComponentDirt.paint != 0 ||
         localTransformed ||
         (paintsInWorldSpace && worldTransformed);
     if (rebuildGradient) {
+      // build up the color and positions lists
       var colors = <ui.Color>[];
       var colorPositions = <double>[];
       for (final stop in gradientStops) {
         colors.add(stop.color);
         colorPositions.add(stop.position);
       }
+      // Check if we need to update the world space gradient.
       if (paintsInWorldSpace) {
+        // Get the start and end of the gradient in world coordinates (world
+        // transform of the shape).
         var world = shapePaintContainer!.worldTransform;
         var worldStart = Vec2D.transformMat2D(Vec2D(), start, world);
         var worldEnd = Vec2D.transformMat2D(Vec2D(), end, world);
@@ -85,6 +115,7 @@ class LinearGradient extends LinearGradientBase with ShapePaintMutator {
   ui.Gradient makeGradient(ui.Offset start, ui.Offset end,
           List<ui.Color> colors, List<double> colorPositions) =>
       ui.Gradient.linear(start, end, colors, colorPositions);
+
   @override
   void startXChanged(double from, double to) {
     addDirt(ComponentDirt.transform);
@@ -114,6 +145,8 @@ class LinearGradient extends LinearGradientBase with ShapePaintMutator {
   @override
   void opacityChanged(double from, double to) {
     syncColor();
+    // We don't need to rebuild anything, just let our shape know we should
+    // repaint.
     shapePaintContainer!.addDirt(ComponentDirt.paint);
   }
 
diff --git a/lib/src/rive_core/shapes/paint/radial_gradient.dart b/lib/src/rive_core/shapes/paint/radial_gradient.dart
index d405a3a..1c5baf4 100644
--- a/lib/src/rive_core/shapes/paint/radial_gradient.dart
+++ b/lib/src/rive_core/shapes/paint/radial_gradient.dart
@@ -3,6 +3,8 @@ import 'package:rive/src/generated/shapes/paint/radial_gradient_base.dart';
 export 'package:rive/src/generated/shapes/paint/radial_gradient_base.dart';
 
 class RadialGradient extends RadialGradientBase {
+  /// We override the make gradient operation to create a radial gradient
+  /// instead of a linear one.
   @override
   ui.Gradient makeGradient(ui.Offset start, ui.Offset end,
           List<ui.Color> colors, List<double> colorPositions) =>
diff --git a/lib/src/rive_core/shapes/paint/shape_paint.dart b/lib/src/rive_core/shapes/paint/shape_paint.dart
index c5b0ea4..4c89b7a 100644
--- a/lib/src/rive_core/shapes/paint/shape_paint.dart
+++ b/lib/src/rive_core/shapes/paint/shape_paint.dart
@@ -1,34 +1,48 @@
 import 'dart:ui';
+
 import 'package:meta/meta.dart';
 import 'package:rive/src/rive_core/component.dart';
 import 'package:rive/src/rive_core/component_dirt.dart';
 import 'package:rive/src/rive_core/container_component.dart';
 import 'package:rive/src/rive_core/shapes/paint/shape_paint_mutator.dart';
+import 'package:rive/src/rive_core/shapes/shape.dart';
 import 'package:rive/src/rive_core/shapes/shape_paint_container.dart';
 import 'package:rive/src/generated/shapes/paint/shape_paint_base.dart';
 export 'package:rive/src/generated/shapes/paint/shape_paint_base.dart';
 
+/// Generic ShapePaint that abstracts Stroke and Fill. Automatically hooks up
+/// parent [Shape] to child [ShapePaintMutator]s.
 abstract class ShapePaint extends ShapePaintBase {
   late Paint _paint;
   Paint get paint => _paint;
   ShapePaintMutator? _paintMutator;
   ShapePaintContainer? get shapePaintContainer =>
       parent is ShapePaintContainer ? parent as ShapePaintContainer : null;
+
   ShapePaint() {
     _paint = makePaint();
   }
+
   BlendMode get blendMode => _paint.blendMode;
   set blendMode(BlendMode value) => _paint.blendMode = value;
+
   double get renderOpacity => _paintMutator!.renderOpacity;
   set renderOpacity(double value) => _paintMutator!.renderOpacity = value;
+
   ShapePaintMutator? get paintMutator => _paintMutator;
+
   void _changeMutator(ShapePaintMutator? mutator) {
     _paint = makePaint();
     _paintMutator = mutator;
   }
 
+  /// Implementing classes are expected to override this to create a paint
+  /// object. This gets called whenever the mutator is changed in order to not
+  /// require each mutator to manually reset the paint to some canonical state.
+  /// Instead, we simply blow out the old one and make a new one.
   @protected
   Paint makePaint();
+
   @override
   void childAdded(Component child) {
     super.childAdded(child);
@@ -53,6 +67,7 @@ abstract class ShapePaint extends ShapePaintBase {
       super.validate() &&
       parent is ShapePaintContainer &&
       _paintMutator != null;
+
   @override
   void isVisibleChanged(bool from, bool to) {
     shapePaintContainer?.addDirt(ComponentDirt.paint);
@@ -61,6 +76,8 @@ abstract class ShapePaint extends ShapePaintBase {
   @override
   void childRemoved(Component child) {
     super.childRemoved(child);
+    // Make sure to clean up any references so that they can be garbage
+    // collected.
     if (child is ShapePaintMutator &&
         _paintMutator == child as ShapePaintMutator) {
       _changeMutator(null);
@@ -69,5 +86,6 @@ abstract class ShapePaint extends ShapePaintBase {
 
   void _initMutator() =>
       _paintMutator?.initializePaintMutator(shapePaintContainer!, paint);
+
   void draw(Canvas canvas, Path path);
 }
diff --git a/lib/src/rive_core/shapes/paint/shape_paint_mutator.dart b/lib/src/rive_core/shapes/paint/shape_paint_mutator.dart
index 53544ca..36a6317 100644
--- a/lib/src/rive_core/shapes/paint/shape_paint_mutator.dart
+++ b/lib/src/rive_core/shapes/paint/shape_paint_mutator.dart
@@ -1,12 +1,17 @@
 import 'dart:ui';
+
 import 'package:flutter/material.dart';
 import 'package:rive/src/rive_core/shapes/shape_paint_container.dart';
 
 abstract class ShapePaintMutator {
   ShapePaintContainer? _shapePaintContainer;
   Paint _paint = Paint();
+
+  /// The container is usually either a Shape or an Artboard, basically any of
+  /// the various ContainerComponents that can contain Fills or Strokes.
   ShapePaintContainer? get shapePaintContainer => _shapePaintContainer;
   Paint get paint => _paint;
+
   double _renderOpacity = 1;
   double get renderOpacity => _renderOpacity;
   set renderOpacity(double value) {
@@ -18,6 +23,7 @@ abstract class ShapePaintMutator {
 
   @protected
   void syncColor();
+
   @mustCallSuper
   void initializePaintMutator(ShapePaintContainer container, Paint paint) {
     _shapePaintContainer = container;
diff --git a/lib/src/rive_core/shapes/paint/solid_color.dart b/lib/src/rive_core/shapes/paint/solid_color.dart
index 6ed8aac..cbe9b34 100644
--- a/lib/src/rive_core/shapes/paint/solid_color.dart
+++ b/lib/src/rive_core/shapes/paint/solid_color.dart
@@ -1,10 +1,12 @@
 import 'dart:ui';
+
 import 'package:rive/src/rive_core/component_dirt.dart';
 import 'package:rive/src/rive_core/shapes/paint/shape_paint.dart';
 import 'package:rive/src/rive_core/shapes/paint/shape_paint_mutator.dart';
 import 'package:rive/src/generated/shapes/paint/solid_color_base.dart';
 export 'package:rive/src/generated/shapes/paint/solid_color_base.dart';
 
+/// A solid color painter for a shape. Works for both Fill and Stroke.
 class SolidColor extends SolidColorBase with ShapePaintMutator {
   Color get color => Color(colorValue);
   set color(Color c) {
@@ -13,12 +15,24 @@ class SolidColor extends SolidColorBase with ShapePaintMutator {
 
   @override
   void colorValueChanged(int from, int to) {
+    // Since all we need to do is set the color on the paint, we can just do
+    // this whenever it changes as it's such a lightweight operation. We don't
+    // need to schedule it for the next update cycle, which saves us from adding
+    // SolidColor to the dependencies graph.
     syncColor();
+
+    // Since we're not in the dependency tree, chuck dirt onto the shape, which
+    // is. This just ensures we'll paint as soon as possible to show the updated
+    // color.
     shapePaintContainer?.addDirt(ComponentDirt.paint);
   }
 
   @override
-  void update(int dirt) {}
+  void update(int dirt) {
+    // Intentionally empty. SolidColor doesn't need an update cycle and doesn't
+    // depend on anything.
+  }
+
   @override
   void syncColor() {
     paint.color = color
@@ -27,6 +41,7 @@ class SolidColor extends SolidColorBase with ShapePaintMutator {
 
   @override
   bool validate() => super.validate() && parent is ShapePaint;
+
   @override
   void onAdded() {
     super.onAdded();
diff --git a/lib/src/rive_core/shapes/paint/stroke.dart b/lib/src/rive_core/shapes/paint/stroke.dart
index ef78ccd..6382f52 100644
--- a/lib/src/rive_core/shapes/paint/stroke.dart
+++ b/lib/src/rive_core/shapes/paint/stroke.dart
@@ -6,9 +6,12 @@ import 'package:rive/src/rive_core/shapes/shape_paint_container.dart';
 import 'package:rive/src/generated/shapes/paint/stroke_base.dart';
 export 'package:rive/src/generated/shapes/paint/stroke_base.dart';
 
+/// A stroke Shape painter.
 class Stroke extends StrokeBase {
   StrokeEffect? _effect;
   StrokeEffect? get effect => _effect;
+
+  // Should be @internal when supported.
   // ignore: use_setters_to_change_properties
   void addStrokeEffect(StrokeEffect effect) {
     _effect = effect;
@@ -26,10 +29,13 @@ class Stroke extends StrokeBase {
     ..strokeCap = strokeCap
     ..strokeJoin = strokeJoin
     ..strokeWidth = thickness;
+
   StrokeCap get strokeCap => StrokeCap.values[cap];
   set strokeCap(StrokeCap value) => cap = value.index;
+
   StrokeJoin get strokeJoin => StrokeJoin.values[join];
   set strokeJoin(StrokeJoin value) => join = value.index;
+
   @override
   void capChanged(int from, int to) {
     paint.strokeCap = StrokeCap.values[to];
@@ -57,7 +63,11 @@ class Stroke extends StrokeBase {
   }
 
   @override
-  void update(int dirt) {}
+  void update(int dirt) {
+    // Intentionally empty, fill doesn't update.
+    // Because Fill never adds dependencies, it'll also never get called.
+  }
+
   @override
   void onAdded() {
     super.onAdded();
@@ -67,11 +77,13 @@ class Stroke extends StrokeBase {
   }
 
   void invalidateEffects() => _effect?.invalidateEffect();
+
   @override
   void draw(Canvas canvas, Path path) {
     if (!isVisible) {
       return;
     }
+
     canvas.drawPath(_effect?.effectPath(path) ?? path, paint);
   }
 }
diff --git a/lib/src/rive_core/shapes/paint/trim_path.dart b/lib/src/rive_core/shapes/paint/trim_path.dart
index bca13f7..a97115e 100644
--- a/lib/src/rive_core/shapes/paint/trim_path.dart
+++ b/lib/src/rive_core/shapes/paint/trim_path.dart
@@ -1,12 +1,18 @@
 import 'dart:ui';
+
 import 'package:rive/src/rive_core/component_dirt.dart';
 import 'package:rive/src/rive_core/shapes/paint/stroke.dart';
 import 'package:rive/src/rive_core/shapes/paint/stroke_effect.dart';
 import 'package:rive/src/rive_core/shapes/paint/trim_path_drawing.dart';
+
 import 'package:rive/src/generated/shapes/paint/trim_path_base.dart';
 export 'package:rive/src/generated/shapes/paint/trim_path_base.dart';
 
-enum TrimPathMode { none, sequential, synchronized }
+enum TrimPathMode {
+  none,
+  sequential,
+  synchronized,
+}
 
 class TrimPath extends TrimPathBase implements StrokeEffect {
   final Path _trimmedPath = Path();
@@ -20,10 +26,12 @@ class TrimPath extends TrimPathBase implements StrokeEffect {
     var isSequential = mode == TrimPathMode.sequential;
     double renderStart = start.clamp(0, 1).toDouble();
     double renderEnd = end.clamp(0, 1).toDouble();
+
     bool inverted = renderStart > renderEnd;
     if ((renderStart - renderEnd).abs() != 1.0) {
       renderStart = (renderStart + offset) % 1.0;
       renderEnd = (renderEnd + offset) % 1.0;
+
       if (renderStart < 0) {
         renderStart += 1.0;
       }
@@ -49,8 +57,10 @@ class TrimPath extends TrimPathBase implements StrokeEffect {
   }
 
   Stroke? get stroke => parent as Stroke?;
+
   TrimPathMode get mode => TrimPathMode.values[modeValue];
   set mode(TrimPathMode value) => modeValue = value.index;
+
   @override
   void invalidateEffect() {
     _renderPath = null;
@@ -59,14 +69,19 @@ class TrimPath extends TrimPathBase implements StrokeEffect {
 
   @override
   void endChanged(double from, double to) => invalidateEffect();
+
   @override
   void modeValueChanged(int from, int to) => invalidateEffect();
+
   @override
   void offsetChanged(double from, double to) => invalidateEffect();
+
   @override
   void startChanged(double from, double to) => invalidateEffect();
+
   @override
   void update(int dirt) {}
+
   @override
   void onAdded() {
     super.onAdded();
@@ -77,6 +92,7 @@ class TrimPath extends TrimPathBase implements StrokeEffect {
   @override
   void onRemoved() {
     stroke?.removeStrokeEffect(this);
+
     super.onRemoved();
   }
 }
diff --git a/lib/src/rive_core/shapes/paint/trim_path_drawing.dart b/lib/src/rive_core/shapes/paint/trim_path_drawing.dart
index eb08e56..5baf13b 100644
--- a/lib/src/rive_core/shapes/paint/trim_path_drawing.dart
+++ b/lib/src/rive_core/shapes/paint/trim_path_drawing.dart
@@ -8,18 +8,27 @@ class _FirstExtractedPath {
   _FirstExtractedPath(this.path, this.metric, this.length);
 }
 
+// Returns the path it last extracted from (actually the metrics for that path).
 _FirstExtractedPath? _appendPathSegmentSequential(
-    Iterable<PathMetric> metrics, Path result, double start, double stop,
-    {_FirstExtractedPath? first}) {
+  Iterable<PathMetric> metrics,
+  Path result,
+  double start,
+  double stop, {
+  _FirstExtractedPath? first,
+}) {
   double nextOffset = 0;
   double offset = 0;
   for (final metric in metrics) {
     nextOffset += metric.length;
     if (start < nextOffset) {
+      // Store the last metric extracted from so next ops can use it.
       var st = max(0.0, start - offset);
       var et = min(metric.length, stop - offset);
       var extractLength = et - st;
       Path extracted = metric.extractPath(st, et);
+
+      // If we're re-extracting from the first path, make it look
+      // contiguous.
       if (first == null) {
         // ignore: parameter_assignments
         first = _FirstExtractedPath(extracted, metric, extractLength);
@@ -31,11 +40,13 @@ _FirstExtractedPath? _appendPathSegmentSequential(
           result.addPath(extracted, Offset.zero);
         }
       } else {
+        // If we extracted this whole sub-path, close it.
         if (metric.isClosed && extractLength == metric.length) {
           extracted.close();
         }
         result.addPath(extracted, Offset.zero);
       }
+
       if (stop < nextOffset) {
         break;
       }
@@ -46,11 +57,16 @@ _FirstExtractedPath? _appendPathSegmentSequential(
 }
 
 void _appendPathSegmentSync(
-    PathMetric metric, Path to, double start, double stop,
-    {bool startWithMoveTo = true}) {
+  PathMetric metric,
+  Path to,
+  double start,
+  double stop, {
+  bool startWithMoveTo = true,
+}) {
   double nextOffset = metric.length;
   if (start < nextOffset) {
     Path extracted = metric.extractPath(start, stop);
+
     if (startWithMoveTo) {
       to.addPath(extracted, Offset.zero);
     } else {
@@ -61,11 +77,13 @@ void _appendPathSegmentSync(
 
 void _trimPathSequential(
     Path path, Path result, double startT, double stopT, bool complement) {
+  // Measure length of all the contours.
   var metrics = path.computeMetrics().toList(growable: false);
   double totalLength = 0.0;
   for (final metric in metrics) {
     totalLength += metric.length;
   }
+
   double trimStart = totalLength * startT;
   double trimStop = totalLength * stopT;
   _FirstExtractedPath? first;
@@ -79,7 +97,12 @@ void _trimPathSequential(
           first: first);
     }
   } else if (trimStart < trimStop) {
-    first = _appendPathSegmentSequential(metrics, result, trimStart, trimStop);
+    first = _appendPathSegmentSequential(
+      metrics,
+      result,
+      trimStart,
+      trimStop,
+    );
   }
   if (first != null) {
     if (first.length == first.metric.length) {
@@ -102,6 +125,8 @@ void _trimPathSync(
         _appendPathSegmentSync(metric, result, trimStop, length);
       }
       if (trimStart > 0.0) {
+        // Make sure to connect the two paths (startWithMoveTo false) if we
+        // extracted the start. Force start with a move if the path is open.
         _appendPathSegmentSync(metric, result, 0.0, trimStart,
             startWithMoveTo: !extractStart || !metric.isClosed);
       }
diff --git a/lib/src/rive_core/shapes/parametric_path.dart b/lib/src/rive_core/shapes/parametric_path.dart
index 7f38e77..266e548 100644
--- a/lib/src/rive_core/shapes/parametric_path.dart
+++ b/lib/src/rive_core/shapes/parametric_path.dart
@@ -5,14 +5,19 @@ export 'package:rive/src/generated/shapes/parametric_path_base.dart';
 abstract class ParametricPath extends ParametricPathBase {
   @override
   bool get isClosed => true;
+
   @override
   Mat2D get pathTransform => worldTransform;
+
   @override
   Mat2D get inversePathTransform => inverseWorldTransform;
+
   @override
   void widthChanged(double from, double to) => markPathDirty();
+
   @override
   void heightChanged(double from, double to) => markPathDirty();
+
   @override
   void xChanged(double from, double to) {
     super.xChanged(from, to);
@@ -45,6 +50,7 @@ abstract class ParametricPath extends ParametricPathBase {
 
   @override
   void originXChanged(double from, double to) => markPathDirty();
+
   @override
   void originYChanged(double from, double to) => markPathDirty();
 }
diff --git a/lib/src/rive_core/shapes/path.dart b/lib/src/rive_core/shapes/path.dart
index 451993e..26bfe2f 100644
--- a/lib/src/rive_core/shapes/path.dart
+++ b/lib/src/rive_core/shapes/path.dart
@@ -1,5 +1,6 @@
 import 'dart:math';
 import 'dart:ui' as ui;
+
 import 'package:rive/src/rive_core/component.dart';
 import 'package:rive/src/rive_core/component_dirt.dart';
 import 'package:rive/src/rive_core/component_flags.dart';
@@ -13,8 +14,11 @@ import 'package:rive/src/rive_core/shapes/straight_vertex.dart';
 import 'package:rive/src/generated/shapes/path_base.dart';
 export 'package:rive/src/generated/shapes/path_base.dart';
 
+/// An abstract low level path that gets implemented by parametric and point
+/// based paths.
 abstract class Path extends PathBase {
   final Mat2D _inverseWorldTransform = Mat2D();
+
   final RenderPath _renderPath = RenderPath();
   ui.Path get uiPath {
     if (!_isValid) {
@@ -24,12 +28,17 @@ abstract class Path extends PathBase {
   }
 
   bool _isValid = false;
+
   bool get isClosed;
+
   Shape? _shape;
+
   Shape? get shape => _shape;
+
   Mat2D get pathTransform;
   Mat2D get inversePathTransform;
   Mat2D get inverseWorldTransform => _inverseWorldTransform;
+
   @override
   bool resolveArtboard() {
     _changeShape(null);
@@ -55,7 +64,10 @@ abstract class Path extends PathBase {
 
   @override
   void onRemoved() {
+    // We're no longer a child of the shape we may have been under, make sure to
+    // let it know we're gone.
     _changeShape(null);
+
     super.onRemoved();
   }
 
@@ -63,7 +75,12 @@ abstract class Path extends PathBase {
   void updateWorldTransform() {
     super.updateWorldTransform();
     _shape?.pathChanged(this);
+
+    // Paths store their inverse world so that it's available for skinning and
+    // other operations that occur at runtime.
     if (!Mat2D.invert(_inverseWorldTransform, pathTransform)) {
+      // If for some reason the inversion fails (like we have a 0 scale) just
+      // store the identity.
       Mat2D.setIdentity(_inverseWorldTransform);
     }
   }
@@ -71,11 +88,16 @@ abstract class Path extends PathBase {
   @override
   void update(int dirt) {
     super.update(dirt);
+
     if (dirt & ComponentDirt.path != 0) {
       _buildPath();
     }
   }
 
+  /// Subclasses should call this whenever a parameter that affects the topology
+  /// of the path changes in order to allow the system to rebuild the parametric
+  /// path.
+  /// should @internal when supported
   void markPathDirty() {
     addDirt(ComponentDirt.path);
     _isValid = false;
@@ -83,6 +105,7 @@ abstract class Path extends PathBase {
   }
 
   List<PathVertex> get vertices;
+
   bool _buildPath() {
     _isValid = true;
     _renderPath.reset();
@@ -91,12 +114,15 @@ abstract class Path extends PathBase {
     if (length < 2) {
       return false;
     }
+
     var firstPoint = vertices.first;
     double outX, outY;
     bool prevIsCubic;
+
     double startX, startY;
     double startInX, startInY;
     bool startIsCubic;
+
     if (firstPoint is CubicVertex) {
       startIsCubic = prevIsCubic = true;
       var inPoint = firstPoint.renderIn;
@@ -112,29 +138,39 @@ abstract class Path extends PathBase {
     } else {
       startIsCubic = prevIsCubic = false;
       var point = firstPoint as StraightVertex;
+
       var radius = point.radius;
       if (radius > 0) {
         var prev = vertices[length - 1];
+
         var pos = point.renderTranslation;
+
         var toPrev = Vec2D.subtract(Vec2D(),
             prev is CubicVertex ? prev.renderOut : prev.renderTranslation, pos);
         var toPrevLength = Vec2D.length(toPrev);
         toPrev[0] /= toPrevLength;
         toPrev[1] /= toPrevLength;
+
         var next = vertices[1];
+
         var toNext = Vec2D.subtract(Vec2D(),
             next is CubicVertex ? next.renderIn : next.renderTranslation, pos);
         var toNextLength = Vec2D.length(toNext);
         toNext[0] /= toNextLength;
         toNext[1] /= toNextLength;
+
         var renderRadius = min(toPrevLength, min(toNextLength, radius));
+
         var translation = Vec2D.scaleAndAdd(Vec2D(), pos, toPrev, renderRadius);
         _renderPath.moveTo(startInX = startX = translation[0],
             startInY = startY = translation[1]);
+
         var outPoint = Vec2D.scaleAndAdd(
             Vec2D(), pos, toPrev, icircleConstant * renderRadius);
+
         var inPoint = Vec2D.scaleAndAdd(
             Vec2D(), pos, toNext, icircleConstant * renderRadius);
+
         var posNext = Vec2D.scaleAndAdd(Vec2D(), pos, toNext, renderRadius);
         _renderPath.cubicTo(outPoint[0], outPoint[1], inPoint[0], inPoint[1],
             outX = posNext[0], outY = posNext[1]);
@@ -146,28 +182,35 @@ abstract class Path extends PathBase {
         _renderPath.moveTo(startInX = startX = outX, startInY = startY = outY);
       }
     }
+
     for (int i = 1; i < length; i++) {
       var vertex = vertices[i];
+
       if (vertex is CubicVertex) {
         var inPoint = vertex.renderIn;
         var translation = vertex.renderTranslation;
         _renderPath.cubicTo(
             outX, outY, inPoint[0], inPoint[1], translation[0], translation[1]);
+
         prevIsCubic = true;
         var outPoint = vertex.renderOut;
         outX = outPoint[0];
         outY = outPoint[1];
       } else {
         var point = vertex as StraightVertex;
+
         var radius = point.radius;
         if (radius > 0) {
           var pos = point.renderTranslation;
+
           var toPrev =
               Vec2D.subtract(Vec2D(), Vec2D.fromValues(outX, outY), pos);
           var toPrevLength = Vec2D.length(toPrev);
           toPrev[0] /= toPrevLength;
           toPrev[1] /= toPrevLength;
+
           var next = vertices[(i + 1) % length];
+
           var toNext = Vec2D.subtract(
               Vec2D(),
               next is CubicVertex ? next.renderIn : next.renderTranslation,
@@ -175,7 +218,9 @@ abstract class Path extends PathBase {
           var toNextLength = Vec2D.length(toNext);
           toNext[0] /= toNextLength;
           toNext[1] /= toNextLength;
+
           var renderRadius = min(toPrevLength, min(toNextLength, radius));
+
           var translation =
               Vec2D.scaleAndAdd(Vec2D(), pos, toPrev, renderRadius);
           if (prevIsCubic) {
@@ -184,10 +229,13 @@ abstract class Path extends PathBase {
           } else {
             _renderPath.lineTo(translation[0], translation[1]);
           }
+
           var outPoint = Vec2D.scaleAndAdd(
               Vec2D(), pos, toPrev, icircleConstant * renderRadius);
+
           var inPoint = Vec2D.scaleAndAdd(
               Vec2D(), pos, toNext, icircleConstant * renderRadius);
+
           var posNext = Vec2D.scaleAndAdd(Vec2D(), pos, toNext, renderRadius);
           _renderPath.cubicTo(outPoint[0], outPoint[1], inPoint[0], inPoint[1],
               outX = posNext[0], outY = posNext[1]);
@@ -197,6 +245,7 @@ abstract class Path extends PathBase {
           var x = translation[0];
           var y = translation[1];
           _renderPath.cubicTo(outX, outY, x, y, x, y);
+
           prevIsCubic = false;
           outX = x;
           outY = y;
@@ -219,12 +268,14 @@ abstract class Path extends PathBase {
 
   @override
   void pathFlagsChanged(int from, int to) => markPathDirty();
+
   bool get isHidden => (pathFlags & ComponentFlags.hidden) != 0;
 }
 
 class RenderPath {
   final ui.Path _uiPath = ui.Path();
   ui.Path get uiPath => _uiPath;
+
   void reset() {
     _uiPath.reset();
   }
diff --git a/lib/src/rive_core/shapes/path_composer.dart b/lib/src/rive_core/shapes/path_composer.dart
index 59e31c5..c85e6b9 100644
--- a/lib/src/rive_core/shapes/path_composer.dart
+++ b/lib/src/rive_core/shapes/path_composer.dart
@@ -1,22 +1,36 @@
 import 'dart:ui' as ui;
+
 import 'package:rive/src/rive_core/artboard.dart';
 import 'package:rive/src/rive_core/component.dart';
 import 'package:rive/src/rive_core/component_dirt.dart';
 import 'package:rive/src/rive_core/math/mat2d.dart';
 import 'package:rive/src/rive_core/shapes/shape.dart';
 
+/// The PathComposer builds the desired world and local paths for the shapes and
+/// their fills/strokes. It guarantees that one of local or world path is always
+/// available. If the Shape only wants a local path, we'll only build a local
+/// one. If the Shape only wants a world path, we'll build only that world path.
+/// If it wants both, we build both. If it wants none, we still build a world
+/// path.
 class PathComposer extends Component {
   final Shape shape;
   PathComposer(this.shape);
+
   @override
   Artboard? get artboard => shape.artboard;
+
   final ui.Path worldPath = ui.Path();
   final ui.Path localPath = ui.Path();
   ui.Path _fillPath = ui.Path();
   ui.Path get fillPath => _fillPath;
+
   void _recomputePath() {
+    // No matter what we'll need some form of a world path to get our bounds.
+    // Let's optimize how we build it.
     var buildLocalPath = shape.wantLocalPath;
     var buildWorldPath = shape.wantWorldPath || !buildLocalPath;
+
+    // The fill path will be whichever one of these two is available.
     if (buildLocalPath) {
       localPath.reset();
       var world = shape.worldTransform;
@@ -49,6 +63,9 @@ class PathComposer extends Component {
   @override
   void buildDependencies() {
     super.buildDependencies();
+
+    // We depend on the shape and all of its paths so that we can update after
+    // all of them.
     shape.addDependent(this);
     for (final path in shape.paths) {
       path.addDependent(this);
diff --git a/lib/src/rive_core/shapes/path_vertex.dart b/lib/src/rive_core/shapes/path_vertex.dart
index 7f4848a..d2e9508 100644
--- a/lib/src/rive_core/shapes/path_vertex.dart
+++ b/lib/src/rive_core/shapes/path_vertex.dart
@@ -1,4 +1,5 @@
 import 'dart:typed_data';
+
 import 'package:rive/src/rive_core/bones/weight.dart';
 import 'package:rive/src/rive_core/component.dart';
 import 'package:rive/src/rive_core/math/mat2d.dart';
@@ -10,12 +11,16 @@ export 'package:rive/src/generated/shapes/path_vertex_base.dart';
 abstract class PathVertex<T extends Weight> extends PathVertexBase {
   T? _weight;
   T? get weight => _weight;
+
   Path? get path => parent as Path?;
+
   @override
   void update(int dirt) {}
+
   final Vec2D _renderTranslation = Vec2D();
   Vec2D get translation => Vec2D.fromValues(x, y);
   Vec2D get renderTranslation => _renderTranslation;
+
   set translation(Vec2D value) {
     x = value[0];
     y = value[1];
@@ -31,12 +36,14 @@ abstract class PathVertex<T extends Weight> extends PathVertexBase {
   @override
   void xChanged(double from, double to) {
     _renderTranslation[0] = to;
+
     path?.markPathDirty();
   }
 
   @override
   void yChanged(double from, double to) {
     _renderTranslation[1] = to;
+
     path?.markPathDirty();
   }
 
@@ -61,6 +68,7 @@ abstract class PathVertex<T extends Weight> extends PathVertexBase {
     }
   }
 
+  /// Deform only gets called when we are weighted.
   void deform(Mat2D world, Float32List boneTransforms) {
     Weight.deform(x, y, weight!.indices, weight!.values, world, boneTransforms,
         _weight!.translation);
diff --git a/lib/src/rive_core/shapes/points_path.dart b/lib/src/rive_core/shapes/points_path.dart
index 81b4468..be58fde 100644
--- a/lib/src/rive_core/shapes/points_path.dart
+++ b/lib/src/rive_core/shapes/points_path.dart
@@ -4,20 +4,29 @@ import 'package:rive/src/rive_core/component_dirt.dart';
 import 'package:rive/src/rive_core/math/mat2d.dart';
 import 'package:rive/src/rive_core/shapes/path_vertex.dart';
 import 'package:rive/src/generated/shapes/points_path_base.dart';
+
 export 'package:rive/src/generated/shapes/points_path_base.dart';
 
 class PointsPath extends PointsPathBase with Skinnable {
   final List<PathVertex> _vertices = [];
+
   PointsPath() {
     isClosed = false;
   }
+
+  // When bound to bones pathTransform should be the identity as it'll already
+  // be in world space.
   @override
   Mat2D get pathTransform => skin != null ? Mat2D.identity : worldTransform;
+
+  // When bound to bones inversePathTransform should be the identity.
   @override
   Mat2D get inversePathTransform =>
       skin != null ? Mat2D() : inverseWorldTransform;
+
   @override
   List<PathVertex> get vertices => _vertices;
+
   @override
   void childAdded(Component child) {
     super.childAdded(child);
@@ -44,22 +53,33 @@ class PointsPath extends PointsPathBase with Skinnable {
   @override
   void buildDependencies() {
     super.buildDependencies();
+
+    // Depend on the skin, if we have it. This works because the skin is not a
+    // node so we have no dependency on our parent yet (which would cause a
+    // dependency cycle).
     skin?.addDependent(this);
   }
 
   @override
   void markPathDirty() {
+    // Make sure the skin gets marked dirty too.
     skin?.addDirt(ComponentDirt.path);
     super.markPathDirty();
   }
 
   @override
   void markSkinDirty() => super.markPathDirty();
+
   @override
   void update(int dirt) {
     if (dirt & ComponentDirt.path != 0) {
+      // Before calling super (which will build the path) make sure to deform
+      // things if necessary. We depend on the skin which assures us that the
+      // boneTransforms are up to date.
       skin?.deform(_vertices);
     }
+    // Finally call super.update so the path commands can actually be rebuilt
+    // (when ComponentDirt.path is set).
     super.update(dirt);
   }
 }
diff --git a/lib/src/rive_core/shapes/polygon.dart b/lib/src/rive_core/shapes/polygon.dart
index bdfcb3c..07d7904 100644
--- a/lib/src/rive_core/shapes/polygon.dart
+++ b/lib/src/rive_core/shapes/polygon.dart
@@ -1,4 +1,5 @@
 import 'dart:math';
+
 import 'package:rive/src/rive_core/shapes/path_vertex.dart';
 import 'package:rive/src/rive_core/bones/weight.dart';
 import 'package:rive/src/rive_core/shapes/straight_vertex.dart';
@@ -8,8 +9,10 @@ export 'package:rive/src/generated/shapes/polygon_base.dart';
 class Polygon extends PolygonBase {
   @override
   void cornerRadiusChanged(double from, double to) => markPathDirty();
+
   @override
   void pointsChanged(int from, int to) => markPathDirty();
+
   @override
   List<PathVertex<Weight>> get vertices {
     var vertexList = <PathVertex<Weight>>[];
diff --git a/lib/src/rive_core/shapes/rectangle.dart b/lib/src/rive_core/shapes/rectangle.dart
index b73c118..b72149c 100644
--- a/lib/src/rive_core/shapes/rectangle.dart
+++ b/lib/src/rive_core/shapes/rectangle.dart
@@ -4,10 +4,12 @@ import 'package:rive/src/generated/shapes/rectangle_base.dart';
 export 'package:rive/src/generated/shapes/rectangle_base.dart';
 
 class Rectangle extends RectangleBase {
+  //
   @override
   List<PathVertex> get vertices {
     double ox = -originX * width;
     double oy = -originY * height;
+
     return [
       StraightVertex.procedural()
         ..x = ox
@@ -24,18 +26,22 @@ class Rectangle extends RectangleBase {
       StraightVertex.procedural()
         ..x = ox
         ..y = oy + height
-        ..radius = linkCornerRadius ? cornerRadiusTL : cornerRadiusBL
+        ..radius = linkCornerRadius ? cornerRadiusTL : cornerRadiusBL,
     ];
   }
 
   @override
   void cornerRadiusTLChanged(double from, double to) => markPathDirty();
+
   @override
   void cornerRadiusTRChanged(double from, double to) => markPathDirty();
+
   @override
   void cornerRadiusBLChanged(double from, double to) => markPathDirty();
+
   @override
   void cornerRadiusBRChanged(double from, double to) => markPathDirty();
+
   @override
   void linkCornerRadiusChanged(bool from, bool to) {
     markPathDirty();
diff --git a/lib/src/rive_core/shapes/shape.dart b/lib/src/rive_core/shapes/shape.dart
index f1c9231..59dfc45 100644
--- a/lib/src/rive_core/shapes/shape.dart
+++ b/lib/src/rive_core/shapes/shape.dart
@@ -1,4 +1,5 @@
 import 'dart:ui' as ui;
+
 import 'package:rive/src/rive_core/component_dirt.dart';
 import 'package:rive/src/rive_core/shapes/paint/linear_gradient.dart' as core;
 import 'package:rive/src/rive_core/shapes/paint/shape_paint_mutator.dart';
@@ -12,17 +13,21 @@ export 'package:rive/src/generated/shapes/shape_base.dart';
 
 class Shape extends ShapeBase with ShapePaintContainer {
   final Set<Path> paths = {};
+
   bool _wantWorldPath = false;
   bool _wantLocalPath = false;
   bool get wantWorldPath => _wantWorldPath;
   bool get wantLocalPath => _wantLocalPath;
   bool _fillInWorld = false;
   bool get fillInWorld => _fillInWorld;
+
   late PathComposer pathComposer;
   Shape() {
     pathComposer = PathComposer(this);
   }
+
   ui.Path get fillPath => pathComposer.fillPath;
+
   bool addPath(Path path) {
     paintChanged();
     return paths.add(path);
@@ -30,17 +35,29 @@ class Shape extends ShapeBase with ShapePaintContainer {
 
   void _markComposerDirty() {
     pathComposer.addDirt(ComponentDirt.path, recurse: true);
+    // Stroke effects need to be rebuilt whenever the path composer rebuilds the
+    // compound path.
     invalidateStrokeEffects();
   }
 
   void pathChanged(Path path) => _markComposerDirty();
+
   void paintChanged() {
     addDirt(ComponentDirt.path);
     _markBlendModeDirty();
     _markRenderOpacityDirty();
+
+    // Add world transform dirt to the direct dependents (don't recurse) as
+    // things like ClippingShape directly depend on their referenced Shape. This
+    // allows them to recompute any stored values which can change when the
+    // transformAffectsStroke property changes (whether the path is in world
+    // space or not). Consider using a different dirt type if this pattern is
+    // repeated.
     for (final d in dependents) {
       d.addDirt(ComponentDirt.worldTransform);
     }
+
+    // Path composer needs to update if we update the types of paths we want.
     _markComposerDirty();
   }
 
@@ -59,6 +76,9 @@ class Shape extends ShapeBase with ShapePaintContainer {
   @override
   void update(int dirt) {
     super.update(dirt);
+
+    // When the paint gets marked dirty, we need to sync the blend mode with the
+    // paints.
     if (dirt & ComponentDirt.blendMode != 0) {
       for (final fill in fills) {
         fill.blendMode = blendMode;
@@ -67,6 +87,10 @@ class Shape extends ShapeBase with ShapePaintContainer {
         stroke.blendMode = blendMode;
       }
     }
+
+    // RenderOpacity gets updated with the worldTransform (accumulates through
+    // hierarchy), so if we see worldTransform is dirty, update our internal
+    // render opacities.
     if (dirt & ComponentDirt.worldTransform != 0) {
       for (final fill in fills) {
         fill.renderOpacity = renderOpacity;
@@ -75,7 +99,11 @@ class Shape extends ShapeBase with ShapePaintContainer {
         stroke.renderOpacity = renderOpacity;
       }
     }
+    // We update before the path composer so let's get our ducks in a row, what
+    // do we want? PathComposer depends on us so we're safe to update our
+    // desires here.
     if (dirt & ComponentDirt.path != 0) {
+      // Recompute which paths we want.
       _wantWorldPath = false;
       _wantLocalPath = false;
       for (final stroke in strokes) {
@@ -85,20 +113,33 @@ class Shape extends ShapeBase with ShapePaintContainer {
           _wantWorldPath = true;
         }
       }
+
+      // Update the gradients' paintsInWorldSpace properties based on whether
+      // the path we'll be feeding that at draw time is in world or local space.
+      // This is a good opportunity to do it as gradients depend on us so
+      // they'll update after us.
+
+      // We optmistically first fill in the space we know the stroke will be in.
       _fillInWorld = _wantWorldPath || !_wantLocalPath;
+
+      // Gradients almost always fill in local space, unless they are bound to
+      // bones.
       var mustFillLocal = fills.firstWhereOrNull(
-              (fill) => fill.paintMutator is core.LinearGradient) !=
+            (fill) => fill.paintMutator is core.LinearGradient,
+          ) !=
           null;
       if (mustFillLocal) {
         _fillInWorld = false;
         _wantLocalPath = true;
       }
+
       for (final fill in fills) {
         var mutator = fill.paintMutator;
         if (mutator is core.LinearGradient) {
           mutator.paintsInWorldSpace = _fillInWorld;
         }
       }
+
       for (final stroke in strokes) {
         var mutator = stroke.paintMutator;
         if (mutator is core.LinearGradient) {
@@ -115,6 +156,7 @@ class Shape extends ShapeBase with ShapePaintContainer {
 
   @override
   void blendModeValueChanged(int from, int to) => _markBlendModeDirty();
+
   @override
   void draw(ui.Canvas canvas) {
     bool clipped = clip(canvas);
@@ -129,12 +171,20 @@ class Shape extends ShapeBase with ShapePaintContainer {
     if (!_fillInWorld) {
       canvas.restore();
     }
+
+    // Strokes are slightly more complicated, they may want a local path. Note
+    // that we've already built this up during our update and processed any
+    // gradients to have their offsets in the correct transform space (see our
+    // update method).
     for (final stroke in strokes) {
+      // stroke.draw(canvas, _pathComposer);
       var transformAffectsStroke = stroke.transformAffectsStroke;
       var path = transformAffectsStroke
           ? pathComposer.localPath
           : pathComposer.worldPath;
+
       if (transformAffectsStroke) {
+        // Get into world space.
         canvas.save();
         canvas.transform(worldTransform.mat4);
         stroke.draw(canvas, path);
@@ -143,6 +193,7 @@ class Shape extends ShapeBase with ShapePaintContainer {
         stroke.draw(canvas, path);
       }
     }
+
     if (clipped) {
       canvas.restore();
     }
@@ -150,15 +201,22 @@ class Shape extends ShapeBase with ShapePaintContainer {
 
   void _markBlendModeDirty() => addDirt(ComponentDirt.blendMode);
   void _markRenderOpacityDirty() => addDirt(ComponentDirt.worldTransform);
+
   @override
   void onPaintMutatorChanged(ShapePaintMutator mutator) {
+    // The transform affects stroke property may have changed as we have a new
+    // mutator.
     paintChanged();
   }
 
   @override
   void onStrokesChanged() => paintChanged();
+
   @override
   void onFillsChanged() => paintChanged();
+
+  /// Since the PathComposer isn't in core, we need to let it know when to proxy
+  /// build dependencies.
   @override
   void buildDependencies() {
     super.buildDependencies();
diff --git a/lib/src/rive_core/shapes/shape_paint_container.dart b/lib/src/rive_core/shapes/shape_paint_container.dart
index d24944e..a570b43 100644
--- a/lib/src/rive_core/shapes/shape_paint_container.dart
+++ b/lib/src/rive_core/shapes/shape_paint_container.dart
@@ -1,19 +1,33 @@
 import 'package:rive/src/rive_core/component.dart';
 import 'package:rive/src/rive_core/math/mat2d.dart';
 import 'package:rive/src/rive_core/math/vec2d.dart';
+
 import 'package:rive/src/rive_core/shapes/paint/fill.dart';
 import 'package:meta/meta.dart';
 import 'package:rive/src/rive_core/shapes/paint/shape_paint_mutator.dart';
 import 'package:rive/src/rive_core/shapes/paint/stroke.dart';
 
+/// An abstraction to give a common interface to any component that can contain
+/// fills and strokes.
 abstract class ShapePaintContainer {
   final Set<Fill> fills = {};
+
   final Set<Stroke> strokes = {};
+
+  /// Called whenever a new paint mutator is added/removed from the shape paints
+  /// (for example a linear gradient is added to a stroke).
   void onPaintMutatorChanged(ShapePaintMutator mutator);
+
+  /// Called when a fill is added or removed.
   @protected
   void onFillsChanged();
+
+  /// Called when a stroke is added or remoevd.
   @protected
   void onStrokesChanged();
+
+  /// Called whenever the compound path for this shape is changed so that the
+  /// effects can be invalidated on all the strokes.
   void invalidateStrokeEffects() {
     for (final stroke in strokes) {
       stroke.invalidateEffects();
@@ -52,7 +66,11 @@ abstract class ShapePaintContainer {
     return false;
   }
 
+  /// These usually gets auto implemented as this mixin is meant to be added to
+  /// a ComponentBase. This way the implementor doesn't need to cast
+  /// ShapePaintContainer to ContainerComponent/Shape/Artboard/etc.
   bool addDirt(int value, {bool recurse = false});
+
   bool addDependent(Component dependent);
   void appendChild(Component child);
   Mat2D get worldTransform;
diff --git a/lib/src/rive_core/shapes/star.dart b/lib/src/rive_core/shapes/star.dart
index bd5c784..23a977c 100644
--- a/lib/src/rive_core/shapes/star.dart
+++ b/lib/src/rive_core/shapes/star.dart
@@ -1,4 +1,5 @@
 import 'dart:math';
+
 import 'package:rive/src/rive_core/bones/weight.dart';
 import 'package:rive/src/rive_core/shapes/path_vertex.dart';
 import 'package:rive/src/rive_core/shapes/straight_vertex.dart';
@@ -8,6 +9,7 @@ export 'package:rive/src/generated/shapes/star_base.dart';
 class Star extends StarBase {
   @override
   void innerRadiusChanged(double from, double to) => markPathDirty();
+
   @override
   List<PathVertex<Weight>> get vertices {
     var actualPoints = points * 2;
diff --git a/lib/src/rive_core/shapes/straight_vertex.dart b/lib/src/rive_core/shapes/straight_vertex.dart
index ad3a715..55a130f 100644
--- a/lib/src/rive_core/shapes/straight_vertex.dart
+++ b/lib/src/rive_core/shapes/straight_vertex.dart
@@ -6,13 +6,20 @@ import 'package:rive/src/generated/shapes/straight_vertex_base.dart';
 export 'package:rive/src/generated/shapes/straight_vertex_base.dart';
 
 class StraightVertex extends StraightVertexBase {
+  /// Nullable because not all vertices have weight, they only have it when the
+  /// shape they are in is bound to bones.
   Weight? _weight;
+
   StraightVertex();
+
+  /// Makes a vertex that is disconnected from core.
   StraightVertex.procedural() {
     InternalCoreHelper.markValid(this);
   }
+
   @override
   String toString() => 'x[$x], y[$y], r[$radius]';
+
   @override
   void radiusChanged(double from, double to) {
     path?.markPathDirty();
diff --git a/lib/src/rive_core/shapes/triangle.dart b/lib/src/rive_core/shapes/triangle.dart
index 1ce3a5e..b218f63 100644
--- a/lib/src/rive_core/shapes/triangle.dart
+++ b/lib/src/rive_core/shapes/triangle.dart
@@ -1,6 +1,8 @@
 import 'package:rive/src/rive_core/shapes/path_vertex.dart';
 import 'package:rive/src/rive_core/shapes/straight_vertex.dart';
 import 'package:rive/src/generated/shapes/triangle_base.dart';
+
+/// Export the Base class for external use (e.g. rive.dart)
 export 'package:rive/src/generated/shapes/triangle_base.dart';
 
 class Triangle extends TriangleBase {
@@ -8,6 +10,7 @@ class Triangle extends TriangleBase {
   List<PathVertex> get vertices {
     double ox = -originX * width;
     double oy = -originY * height;
+
     return [
       StraightVertex.procedural()
         ..x = ox + width / 2
diff --git a/lib/src/rive_core/state_machine_controller.dart b/lib/src/rive_core/state_machine_controller.dart
index b1e395c..8533202 100644
--- a/lib/src/rive_core/state_machine_controller.dart
+++ b/lib/src/rive_core/state_machine_controller.dart
@@ -1,9 +1,12 @@
 import 'dart:collection';
+
 import 'package:rive/src/core/core.dart';
 import 'package:flutter/foundation.dart';
-import 'package:rive/src/rive_core/animation/animation_state.dart';
+import 'package:rive/src/rive_core/animation/animation_state_instance.dart';
+import 'package:rive/src/rive_core/animation/any_state.dart';
 import 'package:rive/src/rive_core/animation/layer_state.dart';
-import 'package:rive/src/rive_core/animation/linear_animation_instance.dart';
+import 'package:rive/src/rive_core/animation/linear_animation.dart';
+import 'package:rive/src/rive_core/animation/state_instance.dart';
 import 'package:rive/src/rive_core/animation/state_machine.dart';
 import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
 import 'package:rive/src/rive_core/animation/state_transition.dart';
@@ -11,21 +14,28 @@ import 'package:rive/src/rive_core/rive_animation_controller.dart';
 
 class LayerController {
   final StateMachineLayer layer;
-  LayerState? _currentState;
-  LayerState? _stateFrom;
+  final StateInstance anyStateInstance;
+
+  StateInstance? _currentState;
+  StateInstance? _stateFrom;
   bool _holdAnimationFrom = false;
-  LinearAnimationInstance? _animationInstanceFrom;
   StateTransition? _transition;
   double _mix = 1.0;
-  LinearAnimationInstance? _animationInstance;
-  LayerController(this.layer) {
+
+  LayerController(this.layer)
+      : assert(layer.anyState != null),
+        anyStateInstance = layer.anyState!.makeInstance() {
     _changeState(layer.entryState);
   }
+
   bool _changeState(LayerState? state, {StateTransition? transition}) {
-    if (state == _currentState) {
+    assert(state is! AnyState,
+        'We don\'t allow making the AnyState an active state.');
+    if (state == _currentState?.state) {
       return false;
     }
-    _currentState = state;
+
+    _currentState = state?.makeInstance();
     return true;
   }
 
@@ -33,11 +43,17 @@ class LayerController {
     _changeState(null);
   }
 
+  bool get isTransitioning =>
+      _transition != null &&
+      _stateFrom != null &&
+      _transition!.duration != 0 &&
+      _mix != 1;
+
   void _updateMix(double elapsedSeconds) {
     if (_transition != null &&
         _stateFrom != null &&
         _transition!.duration != 0) {
-      _mix = (_mix + elapsedSeconds / _transition!.mixTime(_stateFrom!))
+      _mix = (_mix + elapsedSeconds / _transition!.mixTime(_stateFrom!.state))
           .clamp(0, 1)
           .toDouble();
     } else {
@@ -45,103 +61,112 @@ class LayerController {
     }
   }
 
+  void _apply(CoreContext core) {
+    if (_holdAnimation != null) {
+      _holdAnimation!.apply(_holdTime, coreContext: core, mix: _holdMix);
+      _holdAnimation = null;
+    }
+
+    if (_stateFrom != null && _mix < 1) {
+      _stateFrom!.apply(core, 1 - _mix);
+    }
+    if (_currentState != null) {
+      _currentState!.apply(core, _mix);
+    }
+  }
+
   bool apply(StateMachineController machineController, CoreContext core,
       double elapsedSeconds, HashMap<int, dynamic> inputValues) {
-    if (_animationInstance != null) {
-      _animationInstance!.advance(elapsedSeconds);
+    if (_currentState != null) {
+      _currentState!.advance(elapsedSeconds, inputValues);
     }
+
     _updateMix(elapsedSeconds);
-    if (_animationInstanceFrom != null && _mix < 1) {
+
+    if (_stateFrom != null && _mix < 1) {
+      // This didn't advance during our updateState, but it should now that we
+      // realize we need to mix it in.
       if (!_holdAnimationFrom) {
-        _animationInstanceFrom!.advance(elapsedSeconds);
+        _stateFrom!.advance(elapsedSeconds, inputValues);
       }
     }
-    for (int i = 0; updateState(inputValues); i++) {
-      machineController.advanceInputs();
+
+    for (int i = 0; updateState(inputValues, i != 0); i++) {
+      _apply(core);
+
       if (i == 100) {
+        // Escape hatch, let the user know their logic is causing some kind of
+        // recursive condition.
         print('StateMachineController.apply exceeded max iterations.');
+
         return false;
       }
     }
-    if (_animationInstanceFrom != null && _mix < 1) {
-      _animationInstanceFrom!.animation.apply(_animationInstanceFrom!.time,
-          mix: 1 - _mix, coreContext: core);
-    }
-    if (_animationInstance != null) {
-      _animationInstance!.animation
-          .apply(_animationInstance!.time, mix: _mix, coreContext: core);
-    }
-    return _mix != 1 || (_animationInstance?.keepGoing ?? false);
+
+    _apply(core);
+
+    return _mix != 1 || _waitingForExit || (_currentState?.keepGoing ?? false);
   }
 
-  bool updateState(HashMap<int, dynamic> inputValues) {
-    if (tryChangeState(layer.anyState, inputValues)) {
+  bool _waitingForExit = false;
+  LinearAnimation? _holdAnimation;
+  double _holdTime = 0;
+  double _holdMix = 0;
+
+  bool updateState(HashMap<int, dynamic> inputValues, bool ignoreTriggers) {
+    if (isTransitioning) {
+      return false;
+    }
+    _waitingForExit = false;
+    if (tryChangeState(anyStateInstance, inputValues, ignoreTriggers)) {
       return true;
     }
-    return tryChangeState(_currentState, inputValues);
+
+    return tryChangeState(_currentState, inputValues, ignoreTriggers);
   }
 
-  bool tryChangeState(
-      LayerState? stateFrom, HashMap<int, dynamic> inputValues) {
+  bool tryChangeState(StateInstance? stateFrom,
+      HashMap<int, dynamic> inputValues, bool ignoreTriggers) {
     if (stateFrom == null) {
       return false;
     }
-    for (final transition in stateFrom.transitions) {
-      if (transition.isDisabled) {
-        continue;
-      }
-      bool valid = true;
-      for (final condition in transition.conditions) {
-        if (!condition.evaluate(inputValues)) {
-          valid = false;
-          break;
-        }
-      }
-      if (valid && stateFrom is AnimationState && transition.enableExitTime) {
-        var fromAnimation = stateFrom.animation!;
-        if (_animationInstance != null &&
-            fromAnimation == _animationInstance!.animation) {
-          var lastTime = _animationInstance!.lastTotalTime;
-          var time = _animationInstance!.totalTime;
-          var exitTime = transition.exitTimeSeconds(stateFrom);
-          if (exitTime < fromAnimation.durationSeconds) {
-            exitTime += (lastTime / fromAnimation.durationSeconds).floor() *
-                fromAnimation.durationSeconds;
-          }
-          if (time < exitTime) {
-            valid = false;
-          }
-        }
-      }
-      if (valid && _changeState(transition.stateTo, transition: transition)) {
+
+    for (final transition in stateFrom.state.transitions) {
+      var allowed = transition.allowed(stateFrom, inputValues, ignoreTriggers);
+      if (allowed == AllowTransition.yes &&
+          _changeState(transition.stateTo, transition: transition)) {
+        // Take transition
         _transition = transition;
         _stateFrom = stateFrom;
-        if (transition.pauseOnExit &&
-            transition.enableExitTime &&
-            _animationInstance != null) {
-          _animationInstance!.time =
-              transition.exitTimeSeconds(stateFrom, absolute: true);
+
+        // If we had an exit time and wanted to pause on exit, make sure to hold
+        // the exit time. Delegate this to the transition by telling it that it
+        // was completed.
+        if (transition.applyExitCondition(stateFrom)) {
+          // Make sure we apply this state.
+          var inst = (stateFrom as AnimationStateInstance).animationInstance;
+          _holdAnimation = inst.animation;
+          _holdTime = inst.time;
+          _holdMix = _mix;
         }
+
+        // Keep mixing last animation that was mixed in.
         if (_mix != 0) {
           _holdAnimationFrom = transition.pauseOnExit;
-          _animationInstanceFrom = _animationInstance;
         }
-        if (_currentState is AnimationState) {
-          var animationState = _currentState as AnimationState;
-          var spilledTime = _animationInstanceFrom?.spilledTime ?? 0;
-          if (animationState.animation != null) {
-            _animationInstance =
-                LinearAnimationInstance(animationState.animation!);
-            _animationInstance!.advance(spilledTime);
-          } else {
-            _animationInstance = null;
-          }
-          _mix = 0;
-          _updateMix(0.0);
-        } else {
-          _animationInstance = null;
+        if (stateFrom is AnimationStateInstance) {
+          var spilledTime = stateFrom.animationInstance.spilledTime;
+          _currentState?.advance(spilledTime, inputValues);
         }
+
+        _mix = 0;
+        _updateMix(0);
+        // Make sure to reset _waitingForExit to false if we succeed at taking a
+        // transition.
+        _waitingForExit = false;
         return true;
+      } else if (allowed == AllowTransition.waitingForExit) {
+        _waitingForExit = true;
       }
     }
     return false;
@@ -153,6 +178,7 @@ class StateMachineController extends RiveAnimationController<CoreContext> {
   final inputValues = HashMap<int, dynamic>();
   StateMachineController(this.stateMachine);
   final layerControllers = <LayerController>[];
+
   void _clearLayerControllers() {
     for (final layer in layerControllers) {
       layer.dispose();
@@ -163,10 +189,14 @@ class StateMachineController extends RiveAnimationController<CoreContext> {
   @override
   bool init(CoreContext core) {
     _clearLayerControllers();
+
     for (final layer in stateMachine.layers) {
       layerControllers.add(LayerController(layer));
     }
+
+    // Make sure triggers are all reset.
     advanceInputs();
+
     return super.init(core);
   }
 
@@ -178,6 +208,7 @@ class StateMachineController extends RiveAnimationController<CoreContext> {
 
   @protected
   void advanceInputs() {}
+
   @override
   void apply(CoreContext core, double elapsedSeconds) {
     bool keepGoing = false;
@@ -186,6 +217,7 @@ class StateMachineController extends RiveAnimationController<CoreContext> {
         keepGoing = true;
       }
     }
+    advanceInputs();
     isActive = keepGoing;
   }
 }
diff --git a/lib/src/rive_core/state_transition_flags.dart b/lib/src/rive_core/state_transition_flags.dart
index b6f0afd..d30f426 100644
--- a/lib/src/rive_core/state_transition_flags.dart
+++ b/lib/src/rive_core/state_transition_flags.dart
@@ -1,7 +1,17 @@
 class StateTransitionFlags {
+  /// Whether the transition is disabled.
   static const int disabled = 1 << 0;
+
+  /// Whether the transition duration is a percentage or time in ms.
   static const int durationIsPercentage = 1 << 1;
+
+  /// Whether exit time is enabled.
   static const int enableExitTime = 1 << 2;
+
+  /// Whether the exit time is a percentage or time in ms.
   static const int exitTimeIsPercentage = 1 << 3;
+
+  /// Whether the animation is held at exit or if it keeps advancing during
+  /// mixing.
   static const int pauseOnExit = 1 << 4;
 }
diff --git a/lib/src/rive_core/transform_component.dart b/lib/src/rive_core/transform_component.dart
index f4a4d03..b3bc8a2 100644
--- a/lib/src/rive_core/transform_component.dart
+++ b/lib/src/rive_core/transform_component.dart
@@ -12,21 +12,29 @@ import 'package:rive/src/generated/transform_component_base.dart';
 export 'package:rive/src/generated/transform_component_base.dart';
 
 abstract class TransformComponent extends TransformComponentBase {
+  /// Draw rules saved against this transform component, inherited by children.
   DrawRules? _drawRules;
+
   DrawRules? get drawRules => _drawRules;
+
   final List<ClippingShape> _clippingShapes = [];
   Iterable<ClippingShape> get clippingShapes => _clippingShapes;
+
   double _renderOpacity = 0;
   double get renderOpacity => _renderOpacity;
+
   final Mat2D worldTransform = Mat2D();
   final Mat2D transform = Mat2D();
+
   Vec2D get translation => Vec2D.fromValues(x, y);
   Vec2D get worldTranslation =>
       Vec2D.fromValues(worldTransform[4], worldTransform[5]);
+
   double get x;
   double get y;
   set x(double value);
   set y(double value);
+
   @override
   void update(int dirt) {
     if (dirt & ComponentDirt.transform != 0) {
@@ -45,10 +53,14 @@ abstract class TransformComponent extends TransformComponentBase {
     }
     transform[4] = x;
     transform[5] = y;
+
     Mat2D.scaleByValues(transform, scaleX, scaleY);
   }
 
+  // TODO: when we have layer effect renderers, this will need to render 1 for
+  // layer effects.
   double get childOpacity => _renderOpacity;
+
   Vec2D get scale => Vec2D.fromValues(scaleX, scaleY);
   set scale(Vec2D value) {
     scaleX = value[0];
@@ -70,6 +82,7 @@ abstract class TransformComponent extends TransformComponentBase {
   void calculateWorldTransform() {
     var parent = this.parent;
     final chain = <TransformComponent>[this];
+
     while (parent != null) {
       if (parent is TransformComponent) {
         chain.insert(0, parent);
@@ -131,10 +144,12 @@ abstract class TransformComponent extends TransformComponentBase {
     switch (child.coreType) {
       case DrawRulesBase.typeKey:
         _drawRules = child as DrawRules;
+
         break;
       case ClippingShapeBase.typeKey:
         _clippingShapes.add(child as ClippingShape);
         addDirt(ComponentDirt.clip, recurse: true);
+
         break;
     }
   }
diff --git a/lib/src/rive_file.dart b/lib/src/rive_file.dart
index 3018e32..3dde563 100644
--- a/lib/src/rive_file.dart
+++ b/lib/src/rive_file.dart
@@ -6,6 +6,7 @@ import 'package:rive/src/core/core.dart';
 import 'package:rive/src/core/field_types/core_field_type.dart';
 import 'package:rive/src/generated/animation/animation_state_base.dart';
 import 'package:rive/src/generated/animation/any_state_base.dart';
+import 'package:rive/src/generated/animation/blend_state_transition_base.dart';
 import 'package:rive/src/generated/animation/entry_state_base.dart';
 import 'package:rive/src/generated/animation/exit_state_base.dart';
 import 'package:rive/src/generated/animation/keyed_property_base.dart';
@@ -23,6 +24,8 @@ import 'package:rive/src/rive_core/runtime/exceptions/rive_format_error_exceptio
 import 'package:rive/src/rive_core/runtime/runtime_header.dart';
 import 'package:rive/src/utilities/binary_buffer/binary_reader.dart';
 
+import 'generated/animation/blend_state_1d_base.dart';
+import 'generated/animation/blend_state_direct_base.dart';
 import 'rive_core/animation/state_transition.dart';
 
 Core<CoreContext>? _readRuntimeObject(
@@ -140,27 +143,26 @@ class RiveFile {
           stackObject = StateMachineImporter(object as StateMachine);
           break;
         case StateMachineLayerBase.typeKey:
-          {
-            // Needs artboard importer to resolve linear animations.
-            var artboardImporter = importStack
-                .requireLatest<ArtboardImporter>(ArtboardBase.typeKey);
-            stackObject = StateMachineLayerImporter(
-                object as StateMachineLayer, artboardImporter);
-            break;
-          }
+          stackObject = StateMachineLayerImporter(object as StateMachineLayer);
+          break;
+
         case EntryStateBase.typeKey:
         case AnyStateBase.typeKey:
         case ExitStateBase.typeKey:
         case AnimationStateBase.typeKey:
+        case BlendStateDirectBase.typeKey:
+        case BlendState1DBase.typeKey:
           stackObject = LayerStateImporter(object as LayerState);
           stackType = LayerStateBase.typeKey;
           break;
         case StateTransitionBase.typeKey:
+        case BlendStateTransitionBase.typeKey:
           {
             var stateMachineImporter = importStack
                 .requireLatest<StateMachineImporter>(StateMachineBase.typeKey);
             stackObject = StateTransitionImporter(
                 object as StateTransition, stateMachineImporter);
+            stackType = StateTransitionBase.typeKey;
             break;
           }
         default:
diff --git a/lib/src/state_machine_controller.dart b/lib/src/state_machine_controller.dart
index 65ce821..cec66d8 100644
--- a/lib/src/state_machine_controller.dart
+++ b/lib/src/state_machine_controller.dart
@@ -1,5 +1,4 @@
 import 'package:flutter/foundation.dart';
-import 'package:rive/src/core/core.dart';
 import 'package:rive/src/generated/animation/state_machine_bool_base.dart';
 import 'package:rive/src/generated/animation/state_machine_number_base.dart';
 import 'package:rive/src/generated/animation/state_machine_trigger_base.dart';
@@ -160,14 +159,6 @@ class StateMachineController extends core.StateMachineController {
     return null;
   }
 
-  @override
-  void apply(CoreContext core, double elapsedSeconds) {
-    super.apply(core, elapsedSeconds);
-    for (final input in _inputs) {
-      input.advance();
-    }
-  }
-
   @override
   void advanceInputs() {
     for (final input in _inputs) {