Repository: nfrechette/acl (cpp)

Issue:
Unify naming convention to something closer to the C++ stdlib
Change CamelCase class/struct names to lowest_case equivalents and typedef the old names. The old names will be removed in 2.0.


Discussion:

Patch:
diff --git a/cmake/CMakeCompiler.cmake b/cmake/CMakeCompiler.cmake
--- a/cmake/CMakeCompiler.cmake
+++ b/cmake/CMakeCompiler.cmake
@@ -60,6 +60,12 @@ macro(setup_default_compiler_flags _project_name)
 		target_compile_options(${_project_name} PRIVATE -Wshadow)			# Enable shadowing warnings
 		target_compile_options(${_project_name} PRIVATE -Werror)			# Treat warnings as errors
 
-		target_compile_options(${_project_name} PRIVATE -g)					# Enable debug symbols
+		if (PLATFORM_EMSCRIPTEN)
+			# Remove '-g' from compilation flags since it sometimes crashes the compiler
+			string(REPLACE "-g" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
+			string(REPLACE "-g" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
+		else()
+			target_compile_options(${_project_name} PRIVATE -g)					# Enable debug symbols
+		endif()
 	endif()
 endmacro()
diff --git a/docs/README.md b/docs/README.md
--- a/docs/README.md
+++ b/docs/README.md
@@ -8,13 +8,9 @@ ACL aims to support a few core algorithms that are well suited for production us
 
 *  [Handling asserts](handling_asserts.md)
 *  [Implementing an allocator](implementing_an_allocator.md)
-*  [Creating a skeleton](creating_a_skeleton.md)
-*  [Creating a raw clip](creating_a_raw_clip.md)
-*  [Compressing a raw clip](compressing_a_raw_clip.md)
-*  [Decompressing a clip](decompressing_a_clip.md)
 *  [Creating a raw track list](creating_a_raw_track_list.md)
-*  [Compressing scalar tracks](compressing_scalar_tracks.md)
-*  [Decompressing scalar tracks](decompressing_a_track_list.md)
+*  [Compressing tracks](compressing_raw_tracks.md)
+*  [Decompressing tracks](decompressing_a_track_list.md)
 *  [Other considerations](misc_integration_details.md)
 
 See how it's integrated into Unreal Engine 4 [here](https://github.com/nfrechette/acl-ue4-plugin)!
diff --git a/docs/compressing_a_raw_clip.md b/docs/compressing_raw_tracks.md
similarity index 52%
rename from docs/compressing_a_raw_clip.md
rename to docs/compressing_raw_tracks.md
--- a/docs/compressing_a_raw_clip.md
+++ b/docs/compressing_raw_tracks.md
@@ -1,23 +1,42 @@
-# Compressing a raw clip
+# Compressing scalar tracks
 
-Once you have created a [raw animation clip instance](creating_a_raw_clip.md) and an [allocator instance](implementing_an_allocator.md), you are ready to compress it. In order to do so, you simply have to pick the algorithm you will use and a set of compression settings.
+Once you have created a [raw track list](creating_a_raw_track_list.md) and an [allocator instance](implementing_an_allocator.md), you are ready to compress it.
 
 For now, we only implement a single algorithm: [uniformly sampled](algorithm_uniformly_sampled.md). This is a simple and excellent algorithm to use for everyday animation clips.
 
+## Compressing scalar tracks
+
+*Compression settings are currently required as an argument but not used by scalar tracks. It is a placeholder.*
+*Segmenting is currently not supported by scalar tracks.*
+
+```c++
+#include <acl/compression/compress.h>
+
+using namespace acl;
+
+compression_settings settings;
+
+OutputStats stats;
+compressed_tracks* out_compressed_tracks = nullptr;
+ErrorResult result = compress_track_list(allocator, raw_track_list, settings, out_compressed_tracks, stats);
+```
+
+## Compressing transform tracks
+
 The compression level used will dictate how much time to spend optimizing the variable bit rates. Lower levels are faster but produce a larger compressed size.
 
 While we support various [rotation and vector quantization formats](rotation_and_vector_formats.md), the *variable* variants are generally the best. It is safe to use them for all your clips but if you do happen to run into issues with some exotic clips, you can easily fallback to less aggressive variants.
 
 Selecting the right [error metric](error_metrics.md) is important and you will want to carefully pick the one that best approximates how your game engine performs skinning.
 
-The last important setting to choose is the `error_threshold`. This is used in conjunction with the error metric and the virtual vertex distance from the [skeleton](creating_a_skeleton.md) in order to guarantee that a certain quality is maintained. A default value of **0.01cm** is safe to use and it most likely should never be changed unless the units you are using differ. If you do run into issues where compression artifacts are visible, in all likelihood the virtual vertex distance used on the problematic bones is not conservative enough.
+The last important setting to choose is the `error_threshold`. This is used in conjunction with the error metric and the virtual vertex distance (shell distance) in order to guarantee that a certain quality is maintained. A default value of **0.01cm** is safe to use and it most likely should never be changed unless the units you are using differ. If you do run into issues where compression artifacts are visible, in all likelihood the virtual vertex distance used on the problematic bones is not conservative enough.
 
 ```c++
-#include <acl/algorithm/uniformly_sampled/encoder.h>
+#include <acl/compression/compress.h>
 
 using namespace acl;
 
-CompressionSettings settings;
+compression_settings settings;
 settings.level = compression_level8::medium;
 settings.rotation_format = rotation_format8::quatf_drop_w_variable;
 settings.translation_format = vector_format8::vector3f_variable;
@@ -26,9 +45,9 @@ settings.scale_format = vector_format8::vector3f_variable;
 qvvf_transform_error_metric error_metric;
 settings.error_metric = &error_metric;
 
-OutputStats stats;
-CompressedClip* compressed_clip = nullptr;
-ErrorResult error_result = uniformly_sampled::compress_clip(allocator, raw_clip, settings, compressed_clip, stats);
+output_stats stats;
+compressed_tracks* out_compressed_tracks = nullptr;
+error_result result = compress_track_list(allocator, raw_track_list, settings, out_compressed_tracks, stats);
 ```
 
 You can also query the current default and recommended settings with this function: `get_default_compression_settings()`.
diff --git a/docs/compressing_scalar_tracks.md b/docs/compressing_scalar_tracks.md
deleted file mode 100644
--- a/docs/compressing_scalar_tracks.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# Compressing scalar tracks
-
-Once you have created a [raw track list](creating_a_raw_track_list.md) and an [allocator instance](implementing_an_allocator.md), you are ready to compress it.
-
-For now, we only implement a single algorithm: [uniformly sampled](algorithm_uniformly_sampled.md). This is a simple and excellent algorithm to use for everyday animation clips. Segmenting is currently not supported.
-
-Compression settings are currently required as an argument but not used. It is a placeholder.
-
-```c++
-#include <acl/compression/compress.h>
-
-using namespace acl;
-
-compression_settings settings;
-
-OutputStats stats;
-compressed_tracks* out_compressed_tracks = nullptr;
-ErrorResult error_result = compress_track_list(allocator, raw_track_list, settings, out_compressed_tracks, stats);
-```
diff --git a/docs/creating_a_raw_clip.md b/docs/creating_a_raw_clip.md
deleted file mode 100644
--- a/docs/creating_a_raw_clip.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# Creating a raw clip
-
-Once you have [created a rigid skeleton](creating_a_skeleton.md), the next step is to create a raw clip and populate it with the data we will want to compress. Doing so is fairly straight forward and you will only need a few things:
-
-*  [An allocator instance](implementing_an_allocator.md)
-*  [A rigid skeleton](creating_a_skeleton.md)
-*  The number of samples per track (each track has the same number of samples)
-*  The rate at which samples are recorded (e.g. 30 == 30 FPS)
-*  An optional string for the clip name
-
-```c++
-uint32_t num_samples_per_track = 20;
-float sample_rate = 30.0f;
-String name(allocator, "Run Cycle");
-AnimationClip clip(allocator, skeleton, num_samples_per_track, sample_rate, name);
-```
-
-Once you have created an instance, simply populate the track data. Note that for now, even if you have no scale data, you have to populate the scale track with the default scale value that your engine expects.
-
-```c++
-AnimatedBone& bone = clip.get_animated_bone(bone_index);
-for (uint32_t sample_index = 0; sample_index < num_samples_per_track; ++sample_index)
-{
-    bone.rotation_track.set_sample(sample_index, quat_identity_64());
-    bone.translation_track.set_sample(sample_index, vector_zero_64());
-    bone.scale_track.set_sample(sample_index, vector_set(1.0));
-}
-```
-
-Once your raw clip has been populated with data, it is ready for [compression](compressing_a_raw_clip.md). The data contained within the `AnimationClip` will be read-only.
-
-*Note: The current API is subject to change for **v2.0**. As it stands right now, it forces you to duplicate the memory of the raw clip by copying everything within the library instance. A future API will allow the game engine to own the raw clip memory and have ACL simply reference it directly, avoiding the overhead of a copy.*
-
-## Additive animation clips
-
-If the clip you are compressing is an additive clip, you will also need to create an instance for the base clip. Once you have both clip instances, you link them together by calling `set_additive_base(..)` on the additive clip. This will allow you to specify the clip instance that represents the base clip as well as the [format](additive_clips.md) used by the additive clip.
-
-The library assumes that the raw clip data has already been transformed to be in additive or relative space.
-
-## Re-ordering or stripping bones
-
-Sometimes it is desirable to re-order the bones being outputted or strip them altogether. This could be to facilitate LOD support or various forms of skeleton changes without needing to re-import the clips. This is easily achieved by setting the desired `output_index` on each `AnimatedBone` contained in an `AnimationClip`. The default value is the bone index. You can use `k_invalid_bone_index` to strip the bone from the final compressed output.
-
-*Note that each `output_index` needs to be unique and there can be no gaps. If **20** bones are outputted, the indices must run from **[0 .. 20)**.*
\ No newline at end of file
diff --git a/docs/creating_a_raw_track_list.md b/docs/creating_a_raw_track_list.md
--- a/docs/creating_a_raw_track_list.md
+++ b/docs/creating_a_raw_track_list.md
@@ -24,12 +24,20 @@ Once you have created an instance, simply populate the track data. Note that at
 Each track requires a track description. It contains a number of important properties:
 
 *  `output_index`: after compression, this is the index of the track. This allows re-ordering for LOD processing and other similar use cases.
+*  `parent_index`: for transform tracks, indicates the parent transform index it is relative to (in local space of).
 *  `precision`: the precision we aim to attain when optimizing the bit rate. The resulting compression error is nearly guaranteed to be below this threshold.
+*  `shell_distance`: for transform tracks, indicates the distance at which we measure the error, see [error metric function](error_metrics.md).
 
 ```c++
 track_desc_scalarf desc0;
 desc0.output_index = 0;
 desc0.precision = 0.001F;
+
+track_desc_transformf desc1;
+desc1.output_index = 0;
+desc1.parent_index = 0;
+desc1.precision = 0.01F;
+desc1.shell_distance = 3.0F;
 ```
 
 Tracks can be created in one of four ways:
@@ -51,7 +59,19 @@ raw_track0[3] = rtm::float3f{ 4.5F, 91.13F, 41.135F };
 raw_track_list[0] = std::move(raw_track0);
 ```
 
-Once your raw track list has been populated with data, it is ready for [compression](compressing_scalar_tracks.md). The data contained within the `track_array` will be read-only.
+Once your raw track list has been populated with data, it is ready for [compression](compressing_raw_tracks.md). The data contained within the `track_array` will be read-only.
+
+## Additive animation clips
+
+If the clip you are compressing is an additive clip, you will also need to create an instance for the base clip. Once you have both clip instances, you can compress them together with `compress_track_list(..)`. This will allow you to specify the clip instance that represents the base clip as well as the [format](additive_clips.md) used by the additive clip.
+
+The library assumes that the raw clip data has already been transformed to be in additive or relative space.
+
+## Re-ordering or stripping bones
+
+Sometimes it is desirable to re-order the bones being outputted or strip them altogether. This could be to facilitate LOD support or various forms of skeleton changes without needing to re-import the clips. This is easily achieved by setting the desired `output_index` on each `AnimatedBone` contained in an `AnimationClip`. The default value is the bone index. You can use `k_invalid_bone_index` to strip the bone from the final compressed output.
+
+*Note that each `output_index` needs to be unique and there can be no gaps. If **20** bones are outputted, the indices must run from **[0 .. 20)**.*
 
 ## Compressing morph target blend weights
 
diff --git a/docs/creating_a_skeleton.md b/docs/creating_a_skeleton.md
deleted file mode 100644
--- a/docs/creating_a_skeleton.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# Creating a rigid skeleton
-
-After you have found an [allocator instance](implementing_an_allocator.md) to use, you will need to create a `RigidSkeleton` instance. A skeleton is a tree made up of several bones. At the root lies at least one bone with no parent. Each bone that follows can have any number of children but a single parent. You will only need a few things per bone:
-
-*  A virtual vertex distance
-*  A parent bone index
-*  An optional bind transform
-*  An optional string for the bone name
-
-The virtual vertex distance is used by the [error metric function](error_metrics.md). It will measure the error of a vertex at that particular distance from the bone in object space. This distance should be large enough to contain the vast majority of the visible vertices skinned on that particular bone. The algorithm ensures that all vertices contained up to this distance will have an error lower than the supplied error threshold in the `CompressionSettings`. It is generally sufficient for this value to be approximate and it is often safe to use the same value for every bone in humanoid characters. A value of **3cm** is good enough for cinematographic quality for most characters. While visible vertices around the torso will often be further away than this, finger tips and facial bones will be closer than that. Because the compressed track data is stored in relative space of the parent bone, any error will accumulate down the hierarchy. This means that in order to keep the leaf bones within that accuracy threshold, all the parent bones in that chain will require even higher accuracy.
-
-The bind transform is present mainly for debugging purposes and it is otherwise not used by the library. The bone name is present exclusively for debugging purposes as well.
-
-```c++
-RigidBone bones[num_bones];
-for (int bone_index = 0; bone_index < num_bones; ++bone_index)
-{
-    if (bone_index != 0)
-        bones[bone_index].parent_index = bone_index - 1;	// Single bone chain
-
-    bones[bone_index].vertex_distance = 3.0f;
-}
-
-RigidSkeleton(allocator, bones, num_bones);
-```
-
-*Note: The current API is subject to change for v2.0.*
diff --git a/docs/decompressing_a_clip.md b/docs/decompressing_a_clip.md
deleted file mode 100644
--- a/docs/decompressing_a_clip.md
+++ /dev/null
@@ -1,33 +0,0 @@
-# Decompressing a clip
-
-Once you have a [compressed clip](compressing_a_raw_clip.md), the first order of business to decompress it is to select a set of `DecompressionSettings`. The decompression settings `struct` is populated with `constexpr` functions that can be overridden to turn on or off the code generation per feature. For example, the default decompression settings disable all the code for packing formats that are not variable. For the uniformly sampled algorithm, see [here](../includes/acl/algorithm/uniformly_sampled/decoder.h) for details.
-
-The next thing you need is a `DecompressionContext` instance. This will allow you to actually decompress poses, bones, and tracks. You can also use it to seek arbitrarily in the clip. For safety, the decompression context is templated with the decompression settings.
-
-```c++
-using namespace acl;
-using namespace acl::uniformly_sampled;
-
-DecompressionContext<DefaultDecompressionSettings> context;
-
-context.initialize(*compressed_clip);
-context.seek(sample_time, sample_rounding_policy::none);
-
-context.decompress_bone(bone_index, &rotation, &translation, &scale);
-```
-
-As shown, a context must be initialized with a compressed clip instance. Some context objects such as the one used by uniform sampling can be re-used by any compressed clip and does not need to be re-created while others might require this. In order to detect when this might be required, the function `is_dirty(const CompressedClip& clip)` is provided. Some context objects cannot be created on the stack and must be dynamically allocated with an allocator instance. The functions `make_decompression_context(...)` are provided for this purpose.
-
-You can seek anywhere in a clip but you will need to handle looping manually in your game engine. When seeking, you must also provide a `sample_rounding_policy` to dictate how the interpolation is to be performed. See [here](../includes/acl/core/interpolation_utils.h) for details.
-
-Every decompression function supported by the context is prefixed with `decompress_`. Uniform sampling supports decompressing a whole pose with a custom `OutputWriter` for optimized pose writing. You can implement your own and coerce to your own math types. The type is templated on the `decompress_pose` function in order to be easily inlined.
-
-```c++
-Transform_32* transforms = new Transform_32[num_bones];
-DefaultOutputWriter pose_writer(transforms, num_bones);
-context.decompress_pose(pose_writer);
-```
-
-## Floating point exceptions
-
-For performance reasons, the decompression code assumes that the caller has already disabled all floating point exceptions. This avoids the need to save/restore them with every call. ACL provides helpers in [acl/core/floating_point_exceptions.h](..\includes\acl\core\floating_point_exceptions.h) to assist and optionally this behavior can be controlled by overriding `DecompressionSettings::disable_fp_exeptions()`.
diff --git a/docs/decompressing_a_track_list.md b/docs/decompressing_a_track_list.md
--- a/docs/decompressing_a_track_list.md
+++ b/docs/decompressing_a_track_list.md
@@ -1,6 +1,6 @@
 # Decompressing a track list
 
-Once you have a [compressed track list](compressing_scalar_tracks.md), the first order of business to decompress it is to select a set of `decompression_settings`. The decompression settings `struct` is populated with `constexpr` functions that can be overridden to turn on or off the code generation per feature.
+Once you have a [compressed track list](compressing_raw_tracks.md), the first order of business to decompress it is to select a set of `decompression_settings`. The decompression settings `struct` is populated with `constexpr` functions that can be overridden to turn on or off the code generation per feature.
 
 The next thing you need is a `decompression_context` instance. This will allow you to actually decompress tracks. You can also use it to seek arbitrarily in the list. For safety, the decompression context is templated with the decompression settings.
 
diff --git a/docs/error_metrics.md b/docs/error_metrics.md
--- a/docs/error_metrics.md
+++ b/docs/error_metrics.md
@@ -1,6 +1,6 @@
 # Transform error metric
 
-A proper error metric is central to every animation compression algorithm and ACL is no different. The general technique implemented is the one described on this [blog post](https://nfrechette.github.io/2016/11/01/anim_compression_accuracy/) and the various implementations as well as the interfaces live [here](../includes/acl/compression/skeleton_error_metric.h).
+A proper error metric is central to every animation compression algorithm and ACL is no different. The general technique implemented is the one described on this [blog post](https://nfrechette.github.io/2016/11/01/anim_compression_accuracy/) and the various implementations as well as the interfaces live [here](../includes/acl/compression/transform_error_metrics.h).
 
 Some care must be taken when selecting which error metric to use. If the error it calculates isn't representative of how it would be calculated in the host game engine, the resulting visual fidelity might suffer. ACL implements a number of popular implementations and you are free to implement and use your own. The `qvvf_transform_error_metric` error metric is a sensible default.
 
diff --git a/external/sjson-cpp b/external/sjson-cpp
--- a/external/sjson-cpp
+++ b/external/sjson-cpp
@@ -1 +1 @@
-Subproject commit d6300b20f13d7752c24f25a1e2094fa5ead44c7d
+Subproject commit cc08f4ce82cedd0cdaeff02722e8b361db514920
diff --git a/includes/acl/algorithm/uniformly_sampled/decoder.h b/includes/acl/algorithm/uniformly_sampled/decoder.h
deleted file mode 100644
--- a/includes/acl/algorithm/uniformly_sampled/decoder.h
+++ /dev/null
@@ -1,923 +0,0 @@
-#pragma once
-
-////////////////////////////////////////////////////////////////////////////////
-// The MIT License (MIT)
-//
-// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-////////////////////////////////////////////////////////////////////////////////
-
-#include "acl/core/bitset.h"
-#include "acl/core/bit_manip_utils.h"
-#include "acl/core/impl/compiler_utils.h"
-#include "acl/core/compressed_clip.h"
-#include "acl/core/floating_point_exceptions.h"
-#include "acl/core/iallocator.h"
-#include "acl/core/interpolation_utils.h"
-#include "acl/core/range_reduction_types.h"
-#include "acl/core/utils.h"
-#include "acl/math/quat_packing.h"
-#include "acl/decompression/impl/decompress_data.h"
-#include "acl/decompression/output_writer.h"
-
-#include <rtm/quatf.h>
-#include <rtm/vector4f.h>
-
-#include <cstdint>
-
-ACL_IMPL_FILE_PRAGMA_PUSH
-
-//////////////////////////////////////////////////////////////////////////
-// See encoder for details
-//////////////////////////////////////////////////////////////////////////
-
-namespace acl
-{
-	namespace uniformly_sampled
-	{
-		// 2 ways to encore a track as default: a bitset or omit the track
-		// the second method requires a track id to be present to distinguish the
-		// remaining tracks.
-		// For a character, about 50-90 tracks are animated.
-		// We ideally want to support more than 255 tracks or bones.
-		// 50 * 16 bits = 100 bytes
-		// 90 * 16 bits = 180 bytes
-		// On the other hand, a character has about 140-180 bones, or 280-360 tracks (rotation/translation only)
-		// 280 * 1 bit = 35 bytes
-		// 360 * 1 bit = 45 bytes
-		// It is obvious that storing a bitset is much more compact
-		// A bitset also allows us to process and write track values in the order defined when compressed
-		// unlike the track id method which makes it impossible to know which values are default until
-		// everything has been decompressed (at which point everything else is default).
-		// For the track id method to be more compact, an unreasonable small number of tracks would need to be
-		// animated or constant compared to the total possible number of tracks. Those are likely to be rare.
-
-		namespace acl_impl
-		{
-			RTM_DISABLE_SECURITY_COOKIE_CHECK inline rtm::quatf RTM_SIMD_CALL quat_lerp_no_normalization(rtm::quatf_arg0 start, rtm::quatf_arg1 end, float alpha) RTM_NO_EXCEPT
-			{
-				using namespace rtm;
-
-#if defined(RTM_SSE2_INTRINSICS)
-				// Calculate the vector4 dot product: dot(start, end)
-				__m128 dot;
-#if defined(RTM_SSE4_INTRINSICS)
-				// The dpps instruction isn't as accurate but we don't care here, we only need the sign of the
-				// dot product. If both rotations are on opposite ends of the hypersphere, the result will be
-				// very negative. If we are on the edge, the rotations are nearly opposite but not quite which
-				// means that the linear interpolation here will have terrible accuracy to begin with. It is designed
-				// for interpolating rotations that are reasonably close together. The bias check is mainly necessary
-				// because the W component is often kept positive which flips the sign.
-				// Using the dpps instruction reduces the number of registers that we need and helps the function get
-				// inlined.
-				dot = _mm_dp_ps(start, end, 0xFF);
-#else
-				{
-					__m128 x2_y2_z2_w2 = _mm_mul_ps(start, end);
-					__m128 z2_w2_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 3, 2));
-					__m128 x2z2_y2w2_0_0 = _mm_add_ps(x2_y2_z2_w2, z2_w2_0_0);
-					__m128 y2w2_0_0_0 = _mm_shuffle_ps(x2z2_y2w2_0_0, x2z2_y2w2_0_0, _MM_SHUFFLE(0, 0, 0, 1));
-					__m128 x2y2z2w2_0_0_0 = _mm_add_ps(x2z2_y2w2_0_0, y2w2_0_0_0);
-					// Shuffle the dot product to all SIMD lanes, there is no _mm_and_ss and loading
-					// the constant from memory with the 'and' instruction is faster, it uses fewer registers
-					// and fewer instructions
-					dot = _mm_shuffle_ps(x2y2z2w2_0_0_0, x2y2z2w2_0_0_0, _MM_SHUFFLE(0, 0, 0, 0));
-				}
-#endif
-
-				// Calculate the bias, if the dot product is positive or zero, there is no bias
-				// but if it is negative, we want to flip the 'end' rotation XYZW components
-				__m128 bias = _mm_and_ps(dot, _mm_set_ps1(-0.0F));
-
-				// Lerp the rotation after applying the bias
-				// ((1.0 - alpha) * start) + (alpha * (end ^ bias)) == (start - alpha * start) + (alpha * (end ^ bias))
-				__m128 alpha_ = _mm_set_ps1(alpha);
-				__m128 interpolated_rotation = _mm_add_ps(_mm_sub_ps(start, _mm_mul_ps(alpha_, start)), _mm_mul_ps(alpha_, _mm_xor_ps(end, bias)));
-
-				// Due to the interpolation, the result might not be anywhere near normalized!
-				// Make sure to normalize afterwards before using
-				return interpolated_rotation;
-#elif defined (RTM_NEON64_INTRINSICS)
-				// On ARM64 with NEON, we load 1.0 once and use it twice which is faster than
-				// using a AND/XOR with the bias (same number of instructions)
-				float dot = vector_dot(start, end);
-				float bias = dot >= 0.0F ? 1.0F : -1.0F;
-
-				// ((1.0 - alpha) * start) + (alpha * (end * bias)) == (start - alpha * start) + (alpha * (end * bias))
-				vector4f interpolated_rotation = vector_mul_add(vector_mul(end, bias), alpha, vector_neg_mul_sub(start, alpha, start));
-
-				// Due to the interpolation, the result might not be anywhere near normalized!
-				// Make sure to normalize afterwards before using
-				return interpolated_rotation;
-#elif defined(RTM_NEON_INTRINSICS)
-				// Calculate the vector4 dot product: dot(start, end)
-				float32x4_t x2_y2_z2_w2 = vmulq_f32(start, end);
-				float32x2_t x2_y2 = vget_low_f32(x2_y2_z2_w2);
-				float32x2_t z2_w2 = vget_high_f32(x2_y2_z2_w2);
-				float32x2_t x2z2_y2w2 = vadd_f32(x2_y2, z2_w2);
-				float32x2_t x2y2z2w2 = vpadd_f32(x2z2_y2w2, x2z2_y2w2);
-
-				// Calculate the bias, if the dot product is positive or zero, there is no bias
-				// but if it is negative, we want to flip the 'end' rotation XYZW components
-				// On ARM-v7-A, the AND/XOR trick is faster than the cmp/fsel
-				uint32x2_t bias = vand_u32(vreinterpret_u32_f32(x2y2z2w2), vdup_n_u32(0x80000000));
-
-				// Lerp the rotation after applying the bias
-				// ((1.0 - alpha) * start) + (alpha * (end ^ bias)) == (start - alpha * start) + (alpha * (end ^ bias))
-				float32x4_t end_biased = vreinterpretq_f32_u32(veorq_u32(vreinterpretq_u32_f32(end), vcombine_u32(bias, bias)));
-				float32x4_t interpolated_rotation = vmlaq_n_f32(vmlsq_n_f32(start, start, alpha), end_biased, alpha);
-
-				// Due to the interpolation, the result might not be anywhere near normalized!
-				// Make sure to normalize afterwards before using
-				return interpolated_rotation;
-#else
-				// To ensure we take the shortest path, we apply a bias if the dot product is negative
-				vector4f start_vector = quat_to_vector(start);
-				vector4f end_vector = quat_to_vector(end);
-				float dot = vector_dot(start_vector, end_vector);
-				float bias = dot >= 0.0F ? 1.0F : -1.0F;
-				// ((1.0 - alpha) * start) + (alpha * (end * bias)) == (start - alpha * start) + (alpha * (end * bias))
-				vector4f interpolated_rotation = vector_mul_add(vector_mul(end_vector, bias), alpha, vector_neg_mul_sub(start_vector, alpha, start_vector));
-
-				// Due to the interpolation, the result might not be anywhere near normalized!
-				// Make sure to normalize afterwards before using
-				return vector_to_quat(interpolated_rotation);
-#endif
-			}
-
-			constexpr size_t k_cache_line_size = 64;
-
-			struct alignas(k_cache_line_size) DecompressionContext
-			{
-				// Clip related data							//   offsets
-				const CompressedClip* clip;						//   0 |   0
-
-				const uint32_t* constant_tracks_bitset;			//   4 |   8
-				const uint8_t* constant_track_data;				//   8 |  16
-				const uint32_t* default_tracks_bitset;			//  12 |  24
-
-				const uint8_t* clip_range_data;					//  16 |  32
-
-				float clip_duration;							//  20 |  40
-
-				BitSetDescription bitset_desc;					//  24 |  44
-
-				uint32_t clip_hash;								//  28 |  48
-
-				range_reduction_flags8 range_reduction;			//  32 |  52
-				uint8_t num_rotation_components;				//  33 |  53
-
-				uint8_t padding0[2];							//  34 |  54
-
-				// Seeking related data
-				const uint8_t* format_per_track_data[2];		//  36 |  56
-				const uint8_t* segment_range_data[2];			//  44 |  72
-				const uint8_t* animated_track_data[2];			//  52 |  88
-
-				uint32_t key_frame_bit_offsets[2];				//  60 | 104
-
-				float interpolation_alpha;						//  68 | 112
-				float sample_time;								//  76 | 120
-
-				uint8_t padding1[sizeof(void*) == 4 ? 52 : 4];	//  80 | 124
-
-				//									Total size:	   128 | 128
-			};
-
-			static_assert(sizeof(DecompressionContext) == 128, "Unexpected size");
-
-			struct alignas(k_cache_line_size) SamplingContext
-			{
-				static constexpr size_t k_num_samples_to_interpolate = 2;
-
-				inline static rtm::quatf RTM_SIMD_CALL interpolate_rotation(rtm::quatf_arg0 rotation0, rtm::quatf_arg1 rotation1, float interpolation_alpha)
-				{
-					return rtm::quat_lerp(rotation0, rotation1, interpolation_alpha);
-				}
-
-				inline static rtm::quatf RTM_SIMD_CALL interpolate_rotation_no_normalization(rtm::quatf_arg0 rotation0, rtm::quatf_arg1 rotation1, float interpolation_alpha)
-				{
-					return quat_lerp_no_normalization(rotation0, rotation1, interpolation_alpha);
-				}
-
-				inline static rtm::quatf RTM_SIMD_CALL interpolate_rotation(rtm::quatf_arg0 rotation0, rtm::quatf_arg1 rotation1, rtm::quatf_arg2 rotation2, rtm::quatf_arg3 rotation3, float interpolation_alpha)
-				{
-					(void)rotation1;
-					(void)rotation2;
-					(void)rotation3;
-					(void)interpolation_alpha;
-					return rotation0;	// Not implemented, we use linear interpolation
-				}
-
-				inline static rtm::vector4f RTM_SIMD_CALL interpolate_vector4(rtm::vector4f_arg0 vector0, rtm::vector4f_arg1 vector1, float interpolation_alpha)
-				{
-					return rtm::vector_lerp(vector0, vector1, interpolation_alpha);
-				}
-
-				inline static rtm::vector4f RTM_SIMD_CALL interpolate_vector4(rtm::vector4f_arg0 vector0, rtm::vector4f_arg1 vector1, rtm::vector4f_arg2 vector2, rtm::vector4f_arg3 vector3, float interpolation_alpha)
-				{
-					(void)vector1;
-					(void)vector2;
-					(void)vector3;
-					(void)interpolation_alpha;
-					return vector0;		// Not implemented, we use linear interpolation
-				}
-
-				//														//   offsets
-				uint32_t track_index;									//   0 |   0
-				uint32_t constant_track_data_offset;					//   4 |   4
-				uint32_t clip_range_data_offset;						//   8 |   8
-
-				uint32_t format_per_track_data_offset;					//  12 |  12
-				uint32_t segment_range_data_offset;						//  16 |  16
-
-				uint32_t key_frame_bit_offsets[2];						//  20 |  20
-
-				uint8_t padding[4];										//  28 |  28
-
-				rtm::vector4f vectors[k_num_samples_to_interpolate];	//  32 |  32
-
-				//											Total size:	    64 |  64
-			};
-
-			static_assert(sizeof(SamplingContext) == 64, "Unexpected size");
-
-			// We use adapters to wrap the DecompressionSettings
-			// This allows us to re-use the code for skipping and decompressing Vector3 samples
-			// Code generation will generate specialized code for each specialization
-			template<class SettingsType>
-			struct TranslationDecompressionSettingsAdapter
-			{
-				explicit TranslationDecompressionSettingsAdapter(const SettingsType& settings_) : settings(settings_) {}
-
-				constexpr range_reduction_flags8 get_range_reduction_flag() const { return range_reduction_flags8::translations; }
-				rtm::vector4f RTM_SIMD_CALL get_default_value() const { return rtm::vector_zero(); }
-				vector_format8 get_vector_format(const ClipHeader& header) const { return settings.get_translation_format(header.translation_format); }
-				bool is_vector_format_supported(vector_format8 format) const { return settings.is_translation_format_supported(format); }
-				bool are_range_reduction_flags_supported(range_reduction_flags8 flags) const { return settings.are_range_reduction_flags_supported(flags); }
-
-				SettingsType settings;
-			};
-
-			template<class SettingsType>
-			struct ScaleDecompressionSettingsAdapter
-			{
-				explicit ScaleDecompressionSettingsAdapter(const SettingsType& settings_, const ClipHeader& header)
-					: settings(settings_)
-					, default_scale(header.default_scale ? rtm::vector_set(1.0F) : rtm::vector_zero())
-				{}
-
-				constexpr range_reduction_flags8 get_range_reduction_flag() const { return range_reduction_flags8::scales; }
-				rtm::vector4f RTM_SIMD_CALL get_default_value() const { return default_scale; }
-				vector_format8 get_vector_format(const ClipHeader& header) const { return settings.get_scale_format(header.scale_format); }
-				bool is_vector_format_supported(vector_format8 format) const { return settings.is_scale_format_supported(format); }
-				bool are_range_reduction_flags_supported(range_reduction_flags8 flags) const { return settings.are_range_reduction_flags_supported(flags); }
-
-				SettingsType settings;
-				uint8_t padding[get_required_padding<SettingsType, rtm::vector4f>()];
-				rtm::vector4f default_scale;
-			};
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Deriving from this struct and overriding these constexpr functions
-		// allow you to control which code is stripped for maximum performance.
-		// With these, you can:
-		//    - Support only a subset of the formats and statically strip the rest
-		//    - Force a single format and statically strip the rest
-		//    - Decide all of this at runtime by not making the overrides constexpr
-		//
-		// By default, all formats are supported.
-		//////////////////////////////////////////////////////////////////////////
-		struct DecompressionSettings
-		{
-			constexpr bool is_rotation_format_supported(rotation_format8 /*format*/) const { return true; }
-			constexpr bool is_translation_format_supported(vector_format8 /*format*/) const { return true; }
-			constexpr bool is_scale_format_supported(vector_format8 /*format*/) const { return true; }
-			constexpr rotation_format8 get_rotation_format(rotation_format8 format) const { return format; }
-			constexpr vector_format8 get_translation_format(vector_format8 format) const { return format; }
-			constexpr vector_format8 get_scale_format(vector_format8 format) const { return format; }
-
-			constexpr bool are_range_reduction_flags_supported(range_reduction_flags8 /*flags*/) const { return true; }
-			constexpr range_reduction_flags8 get_range_reduction(range_reduction_flags8 flags) const { return flags; }
-
-			// Whether to explicitly disable floating point exceptions during decompression.
-			// This has a cost, exceptions are usually disabled globally and do not need to be
-			// explicitly disabled during decompression.
-			// We assume that floating point exceptions are already disabled by the caller.
-			constexpr bool disable_fp_exeptions() const { return false; }
-
-			// Whether rotations should be normalized before being output or not. Some animation
-			// runtimes will normalize in a separate step and do not need the explicit normalization.
-			// Enabled by default for safety.
-			constexpr bool normalize_rotations() const { return true; }
-		};
-
-		//////////////////////////////////////////////////////////////////////////
-		// These are debug settings, everything is enabled and nothing is stripped.
-		// It will have the worst performance but allows every feature.
-		//////////////////////////////////////////////////////////////////////////
-		struct DebugDecompressionSettings : public DecompressionSettings {};
-
-		//////////////////////////////////////////////////////////////////////////
-		// These are the default settings. Only the generally optimal settings
-		// are enabled and will offer the overall best performance.
-		//
-		// Note: Segment range reduction supports all_tracks or none because it can
-		// be disabled if there is a single segment.
-		//////////////////////////////////////////////////////////////////////////
-		struct DefaultDecompressionSettings : public DecompressionSettings
-		{
-			constexpr bool is_rotation_format_supported(rotation_format8 format) const { return format == rotation_format8::quatf_drop_w_variable; }
-			constexpr bool is_translation_format_supported(vector_format8 format) const { return format == vector_format8::vector3f_variable; }
-			constexpr bool is_scale_format_supported(vector_format8 format) const { return format == vector_format8::vector3f_variable; }
-			constexpr rotation_format8 get_rotation_format(rotation_format8 /*format*/) const { return rotation_format8::quatf_drop_w_variable; }
-			constexpr vector_format8 get_translation_format(vector_format8 /*format*/) const { return vector_format8::vector3f_variable; }
-			constexpr vector_format8 get_scale_format(vector_format8 /*format*/) const { return vector_format8::vector3f_variable; }
-
-			constexpr range_reduction_flags8 get_range_reduction(range_reduction_flags8 /*flags*/) const { return range_reduction_flags8::all_tracks; }
-		};
-
-		//////////////////////////////////////////////////////////////////////////
-		// Decompression context for the uniformly sampled algorithm. The context
-		// allows various decompression actions to be performed in a clip.
-		//
-		// Both the constructor and destructor are public because it is safe to place
-		// instances of this context on the stack or as member variables.
-		//
-		// This compression algorithm is the simplest by far and as such it offers
-		// the fastest compression and decompression. Every sample is retained and
-		// every track has the same number of samples playing back at the same
-		// sample rate. This means that when we sample at a particular time within
-		// the clip, we can trivially calculate the offsets required to read the
-		// desired data. All the data is sorted in order to ensure all reads are
-		// as contiguous as possible for optimal cache locality during decompression.
-		//////////////////////////////////////////////////////////////////////////
-		template<class DecompressionSettingsType>
-		class DecompressionContext
-		{
-		public:
-			static_assert(std::is_base_of<DecompressionSettings, DecompressionSettingsType>::value, "DecompressionSettingsType must derive from DecompressionSettings!");
-
-			//////////////////////////////////////////////////////////////////////////
-			// An alias to the decompression settings type.
-			using SettingsType = DecompressionSettingsType;
-
-			//////////////////////////////////////////////////////////////////////////
-			// Constructs a context instance.
-			// The default constructor for the DecompressionSettingsType will be used.
-			DecompressionContext();
-
-			//////////////////////////////////////////////////////////////////////////
-			// Constructs a context instance from a settings instance.
-			DecompressionContext(const DecompressionSettingsType& settings);
-
-			//////////////////////////////////////////////////////////////////////////
-			// Returns the compressed clip bound to this context instance.
-			const CompressedClip* get_compressed_clip() const { return m_context.clip; }
-
-			//////////////////////////////////////////////////////////////////////////
-			// Initializes the context instance to a particular compressed clip
-			void initialize(const CompressedClip& clip);
-
-			//////////////////////////////////////////////////////////////////////////
-			// Returns true if this context instance is bound to a compressed clip, false otherwise.
-			bool is_initialized() const { return m_context.clip != nullptr; }
-
-			//////////////////////////////////////////////////////////////////////////
-			// Returns true if this context instance is bound to the specified compressed clip, false otherwise.
-			bool is_dirty(const CompressedClip& clip);
-
-			//////////////////////////////////////////////////////////////////////////
-			// Seeks within the compressed clip to a particular point in time
-			void seek(float sample_time, sample_rounding_policy rounding_policy);
-
-			//////////////////////////////////////////////////////////////////////////
-			// Decompress a full pose at the current sample time.
-			// The OutputWriterType allows complete control over how the pose is written out
-			template<class OutputWriterType>
-			void decompress_pose(OutputWriterType& writer);
-
-			//////////////////////////////////////////////////////////////////////////
-			// Decompress a single bone at the current sample time.
-			// Each track entry is optional
-			void decompress_bone(uint16_t sample_bone_index, rtm::quatf* out_rotation, rtm::vector4f* out_translation, rtm::vector4f* out_scale);
-
-		private:
-			DecompressionContext(const DecompressionContext& other) = delete;
-			DecompressionContext& operator=(const DecompressionContext& other) = delete;
-
-			// Internal context data
-			acl_impl::DecompressionContext m_context;
-
-			// The static settings used to strip out code at runtime
-			DecompressionSettingsType m_settings;
-
-			// Ensure we have non-zero padding to avoid compiler warnings
-			static constexpr size_t k_padding_size = alignof(acl_impl::DecompressionContext) - sizeof(DecompressionSettingsType);
-			uint8_t m_padding[k_padding_size != 0 ? k_padding_size : alignof(acl_impl::DecompressionContext)];
-		};
-
-		//////////////////////////////////////////////////////////////////////////
-		// Allocates and constructs an instance of the decompression context
-		template<class DecompressionSettingsType>
-		inline DecompressionContext<DecompressionSettingsType>* make_decompression_context(IAllocator& allocator)
-		{
-			return allocate_type<DecompressionContext<DecompressionSettingsType>>(allocator);
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Allocates and constructs an instance of the decompression context
-		template<class DecompressionSettingsType>
-		inline DecompressionContext<DecompressionSettingsType>* make_decompression_context(IAllocator& allocator, const DecompressionSettingsType& settings)
-		{
-			return allocate_type<DecompressionContext<DecompressionSettingsType>>(allocator, settings);
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-
-		template<class DecompressionSettingsType>
-		inline DecompressionContext<DecompressionSettingsType>::DecompressionContext()
-			: m_context()
-			, m_settings()
-		{
-			m_context.clip = nullptr;		// Only member used to detect if we are initialized
-		}
-
-		template<class DecompressionSettingsType>
-		inline DecompressionContext<DecompressionSettingsType>::DecompressionContext(const DecompressionSettingsType& settings)
-			: m_context()
-			, m_settings(settings)
-		{
-			m_context.clip = nullptr;		// Only member used to detect if we are initialized
-		}
-
-		template<class DecompressionSettingsType>
-		inline void DecompressionContext<DecompressionSettingsType>::initialize(const CompressedClip& clip)
-		{
-			ACL_ASSERT(clip.is_valid(false).empty(), "CompressedClip is not valid");
-			ACL_ASSERT(clip.get_algorithm_type() == algorithm_type8::uniformly_sampled, "Invalid algorithm type [%s], expected [%s]", get_algorithm_name(clip.get_algorithm_type()), get_algorithm_name(algorithm_type8::uniformly_sampled));
-
-			const ClipHeader& header = get_clip_header(clip);
-
-			const rotation_format8 rotation_format = m_settings.get_rotation_format(header.rotation_format);
-			const vector_format8 translation_format = m_settings.get_translation_format(header.translation_format);
-			const vector_format8 scale_format = m_settings.get_scale_format(header.scale_format);
-
-			ACL_ASSERT(rotation_format == header.rotation_format, "Statically compiled rotation format (%s) differs from the compressed rotation format (%s)!", get_rotation_format_name(rotation_format), get_rotation_format_name(header.rotation_format));
-			ACL_ASSERT(m_settings.is_rotation_format_supported(rotation_format), "Rotation format (%s) isn't statically supported!", get_rotation_format_name(rotation_format));
-			ACL_ASSERT(translation_format == header.translation_format, "Statically compiled translation format (%s) differs from the compressed translation format (%s)!", get_vector_format_name(translation_format), get_vector_format_name(header.translation_format));
-			ACL_ASSERT(m_settings.is_translation_format_supported(translation_format), "Translation format (%s) isn't statically supported!", get_vector_format_name(translation_format));
-			ACL_ASSERT(scale_format == header.scale_format, "Statically compiled scale format (%s) differs from the compressed scale format (%s)!", get_vector_format_name(scale_format), get_vector_format_name(header.scale_format));
-			ACL_ASSERT(m_settings.is_scale_format_supported(scale_format), "Scale format (%s) isn't statically supported!", get_vector_format_name(scale_format));
-
-			m_context.clip = &clip;
-			m_context.clip_hash = clip.get_hash();
-			m_context.clip_duration = calculate_duration(header.num_samples, header.sample_rate);
-			m_context.sample_time = -1.0F;
-			m_context.default_tracks_bitset = header.get_default_tracks_bitset();
-
-			m_context.constant_tracks_bitset = header.get_constant_tracks_bitset();
-			m_context.constant_track_data = header.get_constant_track_data();
-			m_context.clip_range_data = header.get_clip_range_data();
-
-			for (uint8_t key_frame_index = 0; key_frame_index < 2; ++key_frame_index)
-			{
-				m_context.format_per_track_data[key_frame_index] = nullptr;
-				m_context.segment_range_data[key_frame_index] = nullptr;
-				m_context.animated_track_data[key_frame_index] = nullptr;
-			}
-
-			const uint32_t num_tracks_per_bone = header.has_scale ? 3 : 2;
-			m_context.bitset_desc = BitSetDescription::make_from_num_bits(header.num_bones * num_tracks_per_bone);
-
-			range_reduction_flags8 range_reduction = range_reduction_flags8::none;
-			if (is_rotation_format_variable(rotation_format))
-				range_reduction |= range_reduction_flags8::rotations;
-			if (is_vector_format_variable(translation_format))
-				range_reduction |= range_reduction_flags8::translations;
-			if (is_vector_format_variable(scale_format))
-				range_reduction |= range_reduction_flags8::scales;
-
-			m_context.range_reduction = m_settings.get_range_reduction(range_reduction);
-
-			ACL_ASSERT((m_context.range_reduction & range_reduction) == range_reduction, "Statically compiled range reduction flags (%u) differ from the compressed flags (%u)!", static_cast< uint32_t >( m_context.range_reduction ), static_cast< uint32_t >( range_reduction ) );
-			ACL_ASSERT(m_settings.are_range_reduction_flags_supported(m_context.range_reduction), "Range reduction flags (%u) aren't statically supported!", static_cast< uint32_t >( m_context.range_reduction ) );
-
-			m_context.num_rotation_components = rotation_format == rotation_format8::quatf_full ? 4 : 3;
-		}
-
-		template<class DecompressionSettingsType>
-		inline bool DecompressionContext<DecompressionSettingsType>::is_dirty(const CompressedClip& clip)
-		{
-			if (m_context.clip != &clip)
-				return true;
-
-			if (m_context.clip_hash != clip.get_hash())
-				return true;
-
-			return false;
-		}
-
-		template<class DecompressionSettingsType>
-		inline void DecompressionContext<DecompressionSettingsType>::seek(float sample_time, sample_rounding_policy rounding_policy)
-		{
-			ACL_ASSERT(m_context.clip != nullptr, "Context is not initialized");
-
-			// Clamp for safety, the caller should normally handle this but in practice, it often isn't the case
-			// TODO: Make it optional via DecompressionSettingsType?
-			sample_time = rtm::scalar_clamp(sample_time, 0.0F, m_context.clip_duration);
-
-			if (m_context.sample_time == sample_time)
-				return;
-
-			m_context.sample_time = sample_time;
-
-			const ClipHeader& header = get_clip_header(*m_context.clip);
-
-			uint32_t key_frame0;
-			uint32_t key_frame1;
-			find_linear_interpolation_samples_with_sample_rate(header.num_samples, header.sample_rate, sample_time, rounding_policy, key_frame0, key_frame1, m_context.interpolation_alpha);
-
-			uint32_t segment_key_frame0;
-			uint32_t segment_key_frame1;
-
-			const SegmentHeader* segment_header0;
-			const SegmentHeader* segment_header1;
-
-			const SegmentHeader* segment_headers = header.get_segment_headers();
-			const uint32_t num_segments = header.num_segments;
-
-			if (num_segments == 1)
-			{
-				// Key frame 0 and 1 are in the only segment present
-				// This is a really common case and when it happens, we don't store the segment start index (zero)
-				segment_header0 = segment_headers;
-				segment_key_frame0 = key_frame0;
-
-				segment_header1 = segment_headers;
-				segment_key_frame1 = key_frame1;
-			}
-			else
-			{
-				const uint32_t* segment_start_indices = header.get_segment_start_indices();
-
-				// See segment_streams(..) for implementation details. This implementation is directly tied to it.
-				const uint32_t approx_num_samples_per_segment = header.num_samples / num_segments;	// TODO: Store in header?
-				const uint32_t approx_segment_index = key_frame0 / approx_num_samples_per_segment;
-
-				uint32_t segment_index0 = 0;
-				uint32_t segment_index1 = 0;
-
-				// Our approximate segment guess is just that, a guess. The actual segments we need could be just before or after.
-				// We start looking one segment earlier and up to 2 after. If we have too few segments after, we will hit the
-				// sentinel value of 0xFFFFFFFF and exit the loop.
-				// TODO: Can we do this with SIMD? Load all 4 values, set key_frame0, compare, move mask, count leading zeroes
-				const uint32_t start_segment_index = approx_segment_index > 0 ? (approx_segment_index - 1) : 0;
-				const uint32_t end_segment_index = start_segment_index + 4;
-
-				for (uint32_t segment_index = start_segment_index; segment_index < end_segment_index; ++segment_index)
-				{
-					if (key_frame0 < segment_start_indices[segment_index])
-					{
-						// We went too far, use previous segment
-						ACL_ASSERT(segment_index > 0, "Invalid segment index: %u", segment_index);
-						segment_index0 = segment_index - 1;
-						segment_index1 = key_frame1 < segment_start_indices[segment_index] ? segment_index0 : segment_index;
-						break;
-					}
-				}
-
-				segment_header0 = segment_headers + segment_index0;
-				segment_header1 = segment_headers + segment_index1;
-
-				segment_key_frame0 = key_frame0 - segment_start_indices[segment_index0];
-				segment_key_frame1 = key_frame1 - segment_start_indices[segment_index1];
-			}
-
-			m_context.format_per_track_data[0] = header.get_format_per_track_data(*segment_header0);
-			m_context.format_per_track_data[1] = header.get_format_per_track_data(*segment_header1);
-			m_context.segment_range_data[0] = header.get_segment_range_data(*segment_header0);
-			m_context.segment_range_data[1] = header.get_segment_range_data(*segment_header1);
-			m_context.animated_track_data[0] = header.get_track_data(*segment_header0);
-			m_context.animated_track_data[1] = header.get_track_data(*segment_header1);
-
-			m_context.key_frame_bit_offsets[0] = segment_key_frame0 * segment_header0->animated_pose_bit_size;
-			m_context.key_frame_bit_offsets[1] = segment_key_frame1 * segment_header1->animated_pose_bit_size;
-		}
-
-		template<class DecompressionSettingsType>
-		template<class OutputWriterType>
-		inline void DecompressionContext<DecompressionSettingsType>::decompress_pose(OutputWriterType& writer)
-		{
-			static_assert(std::is_base_of<OutputWriter, OutputWriterType>::value, "OutputWriterType must derive from OutputWriter");
-
-			using namespace acl::acl_impl;
-
-			ACL_ASSERT(m_context.clip != nullptr, "Context is not initialized");
-			ACL_ASSERT(m_context.sample_time >= 0.0f, "Context not set to a valid sample time");
-
-			// Due to the SIMD operations, we sometimes overflow in the SIMD lanes not used.
-			// Disable floating point exceptions to avoid issues.
-			fp_environment fp_env;
-			if (m_settings.disable_fp_exeptions())
-				disable_fp_exceptions(fp_env);
-
-			const ClipHeader& header = get_clip_header(*m_context.clip);
-
-			const acl_impl::TranslationDecompressionSettingsAdapter<DecompressionSettingsType> translation_adapter(m_settings);
-			const acl_impl::ScaleDecompressionSettingsAdapter<DecompressionSettingsType> scale_adapter(m_settings, header);
-
-			const rtm::vector4f default_scale = scale_adapter.get_default_value();
-
-			acl_impl::SamplingContext sampling_context;
-			sampling_context.track_index = 0;
-			sampling_context.constant_track_data_offset = 0;
-			sampling_context.clip_range_data_offset = 0;
-			sampling_context.format_per_track_data_offset = 0;
-			sampling_context.segment_range_data_offset = 0;
-			sampling_context.key_frame_bit_offsets[0] = m_context.key_frame_bit_offsets[0];
-			sampling_context.key_frame_bit_offsets[1] = m_context.key_frame_bit_offsets[1];
-
-			sampling_context.vectors[0] = default_scale;	// Init with something to avoid GCC warning
-			sampling_context.vectors[1] = default_scale;	// Init with something to avoid GCC warning
-
-			const uint16_t num_bones = header.num_bones;
-			for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
-			{
-				if (writer.skip_all_bone_rotations() || writer.skip_bone_rotation(bone_index))
-					skip_over_rotation(m_settings, header, m_context, sampling_context);
-				else
-				{
-					const rtm::quatf rotation = decompress_and_interpolate_rotation(m_settings, header, m_context, sampling_context);
-					writer.write_bone_rotation(bone_index, rotation);
-				}
-
-				if (writer.skip_all_bone_translations() || writer.skip_bone_translation(bone_index))
-					skip_over_vector(translation_adapter, header, m_context, sampling_context);
-				else
-				{
-					const rtm::vector4f translation = decompress_and_interpolate_vector(translation_adapter, header, m_context, sampling_context);
-					writer.write_bone_translation(bone_index, translation);
-				}
-
-				if (writer.skip_all_bone_scales() || writer.skip_bone_scale(bone_index))
-				{
-					if (header.has_scale)
-						skip_over_vector(scale_adapter, header, m_context, sampling_context);
-				}
-				else
-				{
-					const rtm::vector4f scale = header.has_scale ? decompress_and_interpolate_vector(scale_adapter, header, m_context, sampling_context) : default_scale;
-					writer.write_bone_scale(bone_index, scale);
-				}
-			}
-
-			if (m_settings.disable_fp_exeptions())
-				restore_fp_exceptions(fp_env);
-		}
-
-		template<class DecompressionSettingsType>
-		inline void DecompressionContext<DecompressionSettingsType>::decompress_bone(uint16_t sample_bone_index, rtm::quatf* out_rotation, rtm::vector4f* out_translation, rtm::vector4f* out_scale)
-		{
-			using namespace acl::acl_impl;
-
-			ACL_ASSERT(m_context.clip != nullptr, "Context is not initialized");
-			ACL_ASSERT(m_context.sample_time >= 0.0f, "Context not set to a valid sample time");
-
-			// Due to the SIMD operations, we sometimes overflow in the SIMD lanes not used.
-			// Disable floating point exceptions to avoid issues.
-			fp_environment fp_env;
-			if (m_settings.disable_fp_exeptions())
-				disable_fp_exceptions(fp_env);
-
-			const ClipHeader& header = get_clip_header(*m_context.clip);
-
-			const acl_impl::TranslationDecompressionSettingsAdapter<DecompressionSettingsType> translation_adapter(m_settings);
-			const acl_impl::ScaleDecompressionSettingsAdapter<DecompressionSettingsType> scale_adapter(m_settings, header);
-
-			acl_impl::SamplingContext sampling_context;
-			sampling_context.key_frame_bit_offsets[0] = m_context.key_frame_bit_offsets[0];
-			sampling_context.key_frame_bit_offsets[1] = m_context.key_frame_bit_offsets[1];
-
-			const rotation_format8 rotation_format = m_settings.get_rotation_format(header.rotation_format);
-			const vector_format8 translation_format = m_settings.get_translation_format(header.translation_format);
-			const vector_format8 scale_format = m_settings.get_scale_format(header.scale_format);
-
-			const bool are_all_tracks_variable = is_rotation_format_variable(rotation_format) && is_vector_format_variable(translation_format) && is_vector_format_variable(scale_format);
-			if (!are_all_tracks_variable)
-			{
-				// Slow path, not optimized yet because it's more complex and shouldn't be used in production anyway
-				sampling_context.track_index = 0;
-				sampling_context.constant_track_data_offset = 0;
-				sampling_context.clip_range_data_offset = 0;
-				sampling_context.format_per_track_data_offset = 0;
-				sampling_context.segment_range_data_offset = 0;
-
-				for (uint16_t bone_index = 0; bone_index < sample_bone_index; ++bone_index)
-				{
-					skip_over_rotation(m_settings, header, m_context, sampling_context);
-					skip_over_vector(translation_adapter, header, m_context, sampling_context);
-
-					if (header.has_scale)
-						skip_over_vector(scale_adapter, header, m_context, sampling_context);
-				}
-			}
-			else
-			{
-				const uint32_t num_tracks_per_bone = header.has_scale ? 3 : 2;
-				const uint32_t track_index = sample_bone_index * num_tracks_per_bone;
-				uint32_t num_default_rotations = 0;
-				uint32_t num_default_translations = 0;
-				uint32_t num_default_scales = 0;
-				uint32_t num_constant_rotations = 0;
-				uint32_t num_constant_translations = 0;
-				uint32_t num_constant_scales = 0;
-
-				if (header.has_scale)
-				{
-					uint32_t rotation_track_bit_mask = 0x92492492;		// b100100100..
-					uint32_t translation_track_bit_mask = 0x49249249;	// b010010010..
-					uint32_t scale_track_bit_mask = 0x24924924;			// b001001001..
-
-					const uint32_t last_offset = track_index / 32;
-					uint32_t offset = 0;
-					for (; offset < last_offset; ++offset)
-					{
-						const uint32_t default_value = m_context.default_tracks_bitset[offset];
-						num_default_rotations += count_set_bits(default_value & rotation_track_bit_mask);
-						num_default_translations += count_set_bits(default_value & translation_track_bit_mask);
-						num_default_scales += count_set_bits(default_value & scale_track_bit_mask);
-
-						const uint32_t constant_value = m_context.constant_tracks_bitset[offset];
-						num_constant_rotations += count_set_bits(constant_value & rotation_track_bit_mask);
-						num_constant_translations += count_set_bits(constant_value & translation_track_bit_mask);
-						num_constant_scales += count_set_bits(constant_value & scale_track_bit_mask);
-
-						// Because the number of tracks in a 32bit word isn't a multiple of the number of tracks we have (3),
-						// we have to rotate the masks left
-						rotation_track_bit_mask = rotate_bits_left(rotation_track_bit_mask, 2);
-						translation_track_bit_mask = rotate_bits_left(translation_track_bit_mask, 2);
-						scale_track_bit_mask = rotate_bits_left(scale_track_bit_mask, 2);
-					}
-
-					const uint32_t remaining_tracks = track_index % 32;
-					if (remaining_tracks != 0)
-					{
-						const uint32_t not_up_to_track_mask = ((1 << (32 - remaining_tracks)) - 1);
-						const uint32_t default_value = and_not(not_up_to_track_mask, m_context.default_tracks_bitset[offset]);
-						num_default_rotations += count_set_bits(default_value & rotation_track_bit_mask);
-						num_default_translations += count_set_bits(default_value & translation_track_bit_mask);
-						num_default_scales += count_set_bits(default_value & scale_track_bit_mask);
-
-						const uint32_t constant_value = and_not(not_up_to_track_mask, m_context.constant_tracks_bitset[offset]);
-						num_constant_rotations += count_set_bits(constant_value & rotation_track_bit_mask);
-						num_constant_translations += count_set_bits(constant_value & translation_track_bit_mask);
-						num_constant_scales += count_set_bits(constant_value & scale_track_bit_mask);
-					}
-				}
-				else
-				{
-					const uint32_t rotation_track_bit_mask = 0xAAAAAAAA;		// b10101010..
-					const uint32_t translation_track_bit_mask = 0x55555555;		// b01010101..
-
-					const uint32_t last_offset = track_index / 32;
-					uint32_t offset = 0;
-					for (; offset < last_offset; ++offset)
-					{
-						const uint32_t default_value = m_context.default_tracks_bitset[offset];
-						num_default_rotations += count_set_bits(default_value & rotation_track_bit_mask);
-						num_default_translations += count_set_bits(default_value & translation_track_bit_mask);
-
-						const uint32_t constant_value = m_context.constant_tracks_bitset[offset];
-						num_constant_rotations += count_set_bits(constant_value & rotation_track_bit_mask);
-						num_constant_translations += count_set_bits(constant_value & translation_track_bit_mask);
-					}
-
-					const uint32_t remaining_tracks = track_index % 32;
-					if (remaining_tracks != 0)
-					{
-						const uint32_t not_up_to_track_mask = ((1 << (32 - remaining_tracks)) - 1);
-						const uint32_t default_value = and_not(not_up_to_track_mask, m_context.default_tracks_bitset[offset]);
-						num_default_rotations += count_set_bits(default_value & rotation_track_bit_mask);
-						num_default_translations += count_set_bits(default_value & translation_track_bit_mask);
-
-						const uint32_t constant_value = and_not(not_up_to_track_mask, m_context.constant_tracks_bitset[offset]);
-						num_constant_rotations += count_set_bits(constant_value & rotation_track_bit_mask);
-						num_constant_translations += count_set_bits(constant_value & translation_track_bit_mask);
-					}
-				}
-
-				// Tracks that are default are also constant
-				const uint32_t num_animated_rotations = sample_bone_index - num_constant_rotations;
-				const uint32_t num_animated_translations = sample_bone_index - num_constant_translations;
-
-				const rotation_format8 packed_rotation_format = is_rotation_format_variable(rotation_format) ? get_highest_variant_precision(get_rotation_variant(rotation_format)) : rotation_format;
-				const uint32_t packed_rotation_size = get_packed_rotation_size(packed_rotation_format);
-
-				uint32_t constant_track_data_offset = (num_constant_rotations - num_default_rotations) * packed_rotation_size;
-				constant_track_data_offset += (num_constant_translations - num_default_translations) * get_packed_vector_size(vector_format8::vector3f_full);
-
-				uint32_t clip_range_data_offset = 0;
-				uint32_t segment_range_data_offset = 0;
-
-				const range_reduction_flags8 range_reduction = m_context.range_reduction;
-				if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::rotations) && m_settings.are_range_reduction_flags_supported(range_reduction_flags8::rotations))
-				{
-					clip_range_data_offset += m_context.num_rotation_components * sizeof(float) * 2 * num_animated_rotations;
-
-					if (header.num_segments > 1)
-						segment_range_data_offset += m_context.num_rotation_components * k_segment_range_reduction_num_bytes_per_component * 2 * num_animated_rotations;
-				}
-
-				if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::translations) && m_settings.are_range_reduction_flags_supported(range_reduction_flags8::translations))
-				{
-					clip_range_data_offset += k_clip_range_reduction_vector3_range_size * num_animated_translations;
-
-					if (header.num_segments > 1)
-						segment_range_data_offset += 3 * k_segment_range_reduction_num_bytes_per_component * 2 * num_animated_translations;
-				}
-
-				uint32_t num_animated_tracks = num_animated_rotations + num_animated_translations;
-				if (header.has_scale)
-				{
-					const uint32_t num_animated_scales = sample_bone_index - num_constant_scales;
-					num_animated_tracks += num_animated_scales;
-
-					constant_track_data_offset += (num_constant_scales - num_default_scales) * get_packed_vector_size(vector_format8::vector3f_full);
-
-					if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::scales) && m_settings.are_range_reduction_flags_supported(range_reduction_flags8::scales))
-					{
-						clip_range_data_offset += k_clip_range_reduction_vector3_range_size * num_animated_scales;
-
-						if (header.num_segments > 1)
-							segment_range_data_offset += 3 * k_segment_range_reduction_num_bytes_per_component * 2 * num_animated_scales;
-					}
-				}
-
-				sampling_context.track_index = track_index;
-				sampling_context.constant_track_data_offset = constant_track_data_offset;
-				sampling_context.clip_range_data_offset = clip_range_data_offset;
-				sampling_context.segment_range_data_offset = segment_range_data_offset;
-				sampling_context.format_per_track_data_offset = num_animated_tracks;
-
-				for (uint32_t animated_track_index = 0; animated_track_index < num_animated_tracks; ++animated_track_index)
-				{
-					const uint8_t bit_rate0 = m_context.format_per_track_data[0][animated_track_index];
-					const uint32_t num_bits_at_bit_rate0 = get_num_bits_at_bit_rate(bit_rate0) * 3;	// 3 components
-
-					sampling_context.key_frame_bit_offsets[0] += num_bits_at_bit_rate0;
-
-					const uint8_t bit_rate1 = m_context.format_per_track_data[1][animated_track_index];
-					const uint32_t num_bits_at_bit_rate1 = get_num_bits_at_bit_rate(bit_rate1) * 3;	// 3 components
-
-					sampling_context.key_frame_bit_offsets[1] += num_bits_at_bit_rate1;
-				}
-			}
-
-			const rtm::vector4f default_scale = scale_adapter.get_default_value();
-
-			sampling_context.vectors[0] = default_scale;	// Init with something to avoid GCC warning
-			sampling_context.vectors[1] = default_scale;	// Init with something to avoid GCC warning
-
-			if (out_rotation != nullptr)
-				*out_rotation = decompress_and_interpolate_rotation(m_settings, header, m_context, sampling_context);
-			else
-				skip_over_rotation(m_settings, header, m_context, sampling_context);
-
-			if (out_translation != nullptr)
-				*out_translation = decompress_and_interpolate_vector(translation_adapter, header, m_context, sampling_context);
-			else if (out_scale != nullptr && header.has_scale)
-			{
-				// We'll need to read the scale value that follows, skip the translation we don't need
-				skip_over_vector(translation_adapter, header, m_context, sampling_context);
-			}
-
-			if (out_scale != nullptr)
-				*out_scale = header.has_scale ? decompress_and_interpolate_vector(scale_adapter, header, m_context, sampling_context) : default_scale;
-			// No need to skip our last scale, we don't care anymore
-
-			if (m_settings.disable_fp_exeptions())
-				restore_fp_exceptions(fp_env);
-		}
-	}
-}
-
-ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/algorithm/uniformly_sampled/encoder.h b/includes/acl/algorithm/uniformly_sampled/encoder.h
deleted file mode 100644
--- a/includes/acl/algorithm/uniformly_sampled/encoder.h
+++ /dev/null
@@ -1,299 +0,0 @@
-#pragma once
-
-////////////////////////////////////////////////////////////////////////////////
-// The MIT License (MIT)
-//
-// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-////////////////////////////////////////////////////////////////////////////////
-
-#include "acl/core/algorithm_types.h"
-#include "acl/core/bitset.h"
-#include "acl/core/impl/compiler_utils.h"
-#include "acl/core/compressed_clip.h"
-#include "acl/core/error.h"
-#include "acl/core/error_result.h"
-#include "acl/core/floating_point_exceptions.h"
-#include "acl/core/iallocator.h"
-#include "acl/core/range_reduction_types.h"
-#include "acl/core/scope_profiler.h"
-#include "acl/core/track_types.h"
-#include "acl/compression/impl/compressed_clip_impl.h"
-#include "acl/compression/skeleton.h"
-#include "acl/compression/animation_clip.h"
-#include "acl/compression/output_stats.h"
-#include "acl/compression/impl/clip_context.h"
-#include "acl/compression/impl/track_stream.h"
-#include "acl/compression/impl/convert_rotation_streams.h"
-#include "acl/compression/impl/compact_constant_streams.h"
-#include "acl/compression/impl/normalize_streams.h"
-#include "acl/compression/impl/quantize_streams.h"
-#include "acl/compression/impl/segment_streams.h"
-#include "acl/compression/impl/write_segment_data.h"
-#include "acl/compression/impl/write_stats.h"
-#include "acl/compression/impl/write_stream_bitsets.h"
-#include "acl/compression/impl/write_stream_data.h"
-#include "acl/decompression/default_output_writer.h"
-
-#include <cstdint>
-
-ACL_IMPL_FILE_PRAGMA_PUSH
-
-namespace acl
-{
-	namespace uniformly_sampled
-	{
-		//////////////////////////////////////////////////////////////////////////
-		// Compresses a raw clip with uniform sampling
-		//
-		// This compression algorithm is the simplest by far and as such it offers
-		// the fastest compression and decompression. Every sample is retained and
-		// every track has the same number of samples playing back at the same
-		// sample rate. This means that when we sample at a particular time within
-		// the clip, we can trivially calculate the offsets required to read the
-		// desired data. All the data is sorted in order to ensure all reads are
-		// as contiguous as possible for optimal cache locality during decompression.
-		//
-		//    allocator: The allocator instance to use to allocate and free memory
-		//    clip: The raw clip to compress
-		//    settings: The compression settings to use
-		//    out_compressed_clip: The resulting compressed clip. The caller owns the returned memory and must free it
-		//    out_stats: Stat output structure
-		//////////////////////////////////////////////////////////////////////////
-		inline ErrorResult compress_clip(IAllocator& allocator, const AnimationClip& clip, CompressionSettings settings, CompressedClip*& out_compressed_clip, OutputStats& out_stats)
-		{
-			using namespace acl::acl_impl;
-			(void)out_stats;
-
-			ErrorResult error_result = clip.is_valid();
-			if (error_result.any())
-				return error_result;
-
-			error_result = settings.is_valid();
-			if (error_result.any())
-				return error_result;
-
-			// Disable floating point exceptions during compression because we leverage all SIMD lanes
-			// and we might intentionally divide by zero, etc.
-			scope_disable_fp_exceptions fp_off;
-
-#if defined(SJSON_CPP_WRITER)
-			ScopeProfiler compression_time;
-#endif
-
-			// If every track is retains full precision, we disable segmenting since it provides no benefit
-			if (!is_rotation_format_variable(settings.rotation_format) && !is_vector_format_variable(settings.translation_format) && !is_vector_format_variable(settings.scale_format))
-			{
-				settings.segmenting.ideal_num_samples = 0xFFFF;
-				settings.segmenting.max_num_samples = 0xFFFF;
-			}
-
-			// Variable bit rate tracks need range reduction
-			// Full precision tracks do not need range reduction since samples are stored raw
-			range_reduction_flags8 range_reduction = range_reduction_flags8::none;
-			if (is_rotation_format_variable(settings.rotation_format))
-				range_reduction |= range_reduction_flags8::rotations;
-
-			if (is_vector_format_variable(settings.translation_format))
-				range_reduction |= range_reduction_flags8::translations;
-
-			if (is_vector_format_variable(settings.scale_format))
-				range_reduction |= range_reduction_flags8::scales;
-
-			const uint32_t num_samples = clip.get_num_samples();
-			const RigidSkeleton& skeleton = clip.get_skeleton();
-
-			ClipContext additive_base_clip_context;
-			const AnimationClip* additive_base_clip = clip.get_additive_base();
-			if (additive_base_clip != nullptr)
-				initialize_clip_context(allocator, *additive_base_clip, skeleton, settings, additive_base_clip_context);
-
-			ClipContext raw_clip_context;
-			initialize_clip_context(allocator, clip, skeleton, settings, raw_clip_context);
-
-			ClipContext clip_context;
-			initialize_clip_context(allocator, clip, skeleton, settings, clip_context);
-
-			convert_rotation_streams(allocator, clip_context, settings.rotation_format);
-
-			// Extract our clip ranges now, we need it for compacting the constant streams
-			extract_clip_bone_ranges(allocator, clip_context);
-
-			// Compact and collapse the constant streams
-			compact_constant_streams(allocator, clip_context, settings.constant_rotation_threshold_angle, settings.constant_translation_threshold, settings.constant_scale_threshold);
-
-			uint32_t clip_range_data_size = 0;
-			if (range_reduction != range_reduction_flags8::none)
-			{
-				normalize_clip_streams(clip_context, range_reduction);
-				clip_range_data_size = get_stream_range_data_size(clip_context, range_reduction, settings.rotation_format);
-			}
-
-			segment_streams(allocator, clip_context, settings.segmenting);
-
-			// If we have a single segment, skip segment range reduction since it won't help
-			if (range_reduction != range_reduction_flags8::none && clip_context.num_segments > 1)
-			{
-				extract_segment_bone_ranges(allocator, clip_context);
-				normalize_segment_streams(clip_context, range_reduction);
-			}
-
-			quantize_streams(allocator, clip_context, settings, skeleton, raw_clip_context, additive_base_clip_context, out_stats);
-
-			uint16_t num_output_bones = 0;
-			uint16_t* output_bone_mapping = create_output_bone_mapping(allocator, clip, num_output_bones);
-
-			const uint32_t constant_data_size = get_constant_data_size(clip_context, output_bone_mapping, num_output_bones);
-
-			calculate_animated_data_size(clip_context, output_bone_mapping, num_output_bones);
-
-			const uint32_t format_per_track_data_size = get_format_per_track_data_size(clip_context, settings.rotation_format, settings.translation_format, settings.scale_format);
-
-			const uint32_t num_tracks_per_bone = clip_context.has_scale ? 3 : 2;
-			const uint32_t num_tracks = uint32_t(num_output_bones) * num_tracks_per_bone;
-			const BitSetDescription bitset_desc = BitSetDescription::make_from_num_bits(num_tracks);
-
-			// Adding an extra index at the end to delimit things, the index is always invalid: 0xFFFFFFFF
-			const uint32_t segment_start_indices_size = clip_context.num_segments > 1 ? (sizeof(uint32_t) * (clip_context.num_segments + 1)) : 0;
-
-			uint32_t buffer_size = 0;
-			// Per clip data
-			buffer_size += sizeof(CompressedClip);
-			buffer_size += sizeof(ClipHeader);
-
-			const uint32_t clip_header_size = buffer_size;
-
-			buffer_size += segment_start_indices_size;							// Segment start indices
-			buffer_size = align_to(buffer_size, 4);								// Align segment headers
-			buffer_size += sizeof(SegmentHeader) * clip_context.num_segments;	// Segment headers
-			buffer_size = align_to(buffer_size, 4);								// Align bitsets
-
-			const uint32_t clip_segment_header_size = buffer_size - clip_header_size;
-
-			buffer_size += bitset_desc.get_num_bytes();							// Default tracks bitset
-			buffer_size += bitset_desc.get_num_bytes();							// Constant tracks bitset
-			buffer_size = align_to(buffer_size, 4);								// Align constant track data
-			buffer_size += constant_data_size;									// Constant track data
-			buffer_size = align_to(buffer_size, 4);								// Align range data
-			buffer_size += clip_range_data_size;								// Range data
-
-			const uint32_t clip_data_size = buffer_size - clip_segment_header_size - clip_header_size;
-
-			if (are_all_enum_flags_set(out_stats.logging, StatLogging::Detailed))
-			{
-				constexpr uint32_t k_cache_line_byte_size = 64;
-				clip_context.decomp_touched_bytes = clip_header_size + clip_data_size;
-				clip_context.decomp_touched_bytes += sizeof(uint32_t) * 4;			// We touch at most 4 segment start indices
-				clip_context.decomp_touched_bytes += sizeof(SegmentHeader) * 2;		// We touch at most 2 segment headers
-				clip_context.decomp_touched_cache_lines = align_to(clip_header_size, k_cache_line_byte_size) / k_cache_line_byte_size;
-				clip_context.decomp_touched_cache_lines += align_to(clip_data_size, k_cache_line_byte_size) / k_cache_line_byte_size;
-				clip_context.decomp_touched_cache_lines += 1;						// All 4 segment start indices should fit in a cache line
-				clip_context.decomp_touched_cache_lines += 1;						// Both segment headers should fit in a cache line
-			}
-
-			// Per segment data
-			for (SegmentContext& segment : clip_context.segment_iterator())
-			{
-				const uint32_t header_start = buffer_size;
-
-				buffer_size += format_per_track_data_size;						// Format per track data
-				// TODO: Alignment only necessary with 16bit per component
-				buffer_size = align_to(buffer_size, 2);							// Align range data
-				buffer_size += segment.range_data_size;							// Range data
-
-				const uint32_t header_end = buffer_size;
-
-				// TODO: Variable bit rate doesn't need alignment
-				buffer_size = align_to(buffer_size, 4);							// Align animated data
-				buffer_size += segment.animated_data_size;						// Animated track data
-
-				segment.total_header_size = header_end - header_start;
-			}
-
-			// Ensure we have sufficient padding for unaligned 16 byte loads
-			buffer_size += 15;
-
-			uint8_t* buffer = allocate_type_array_aligned<uint8_t>(allocator, buffer_size, 16);
-			std::memset(buffer, 0, buffer_size);
-
-			CompressedClip* compressed_clip = make_compressed_clip(buffer, buffer_size, algorithm_type8::uniformly_sampled);
-
-			ClipHeader& header = get_clip_header(*compressed_clip);
-			header.num_bones = num_output_bones;
-			header.num_segments = clip_context.num_segments;
-			header.rotation_format = settings.rotation_format;
-			header.translation_format = settings.translation_format;
-			header.scale_format = settings.scale_format;
-			header.has_scale = clip_context.has_scale ? 1 : 0;
-			header.default_scale = additive_base_clip == nullptr || clip.get_additive_format() != additive_clip_format8::additive1;
-			header.num_samples = num_samples;
-			header.sample_rate = clip.get_sample_rate();
-			header.segment_start_indices_offset = sizeof(ClipHeader);
-			header.segment_headers_offset = align_to(header.segment_start_indices_offset + segment_start_indices_size, 4);
-			header.default_tracks_bitset_offset = align_to(header.segment_headers_offset + (sizeof(SegmentHeader) * clip_context.num_segments), 4);
-			header.constant_tracks_bitset_offset = header.default_tracks_bitset_offset + bitset_desc.get_num_bytes();
-			header.constant_track_data_offset = align_to(header.constant_tracks_bitset_offset + bitset_desc.get_num_bytes(), 4);
-			header.clip_range_data_offset = align_to(header.constant_track_data_offset + constant_data_size, 4);
-
-			if (clip_context.num_segments > 1)
-				write_segment_start_indices(clip_context, header.get_segment_start_indices());
-			else
-				header.segment_start_indices_offset = InvalidPtrOffset();
-
-			const uint32_t segment_data_start_offset = header.clip_range_data_offset + clip_range_data_size;
-			write_segment_headers(clip_context, settings, header.get_segment_headers(), segment_data_start_offset);
-			write_default_track_bitset(clip_context, header.get_default_tracks_bitset(), bitset_desc, output_bone_mapping, num_output_bones);
-			write_constant_track_bitset(clip_context, header.get_constant_tracks_bitset(), bitset_desc, output_bone_mapping, num_output_bones);
-
-			if (constant_data_size > 0)
-				write_constant_track_data(clip_context, header.get_constant_track_data(), constant_data_size, output_bone_mapping, num_output_bones);
-			else
-				header.constant_track_data_offset = InvalidPtrOffset();
-
-			if (range_reduction != range_reduction_flags8::none)
-				write_clip_range_data(clip_context, range_reduction, header.get_clip_range_data(), clip_range_data_size, output_bone_mapping, num_output_bones);
-			else
-				header.clip_range_data_offset = InvalidPtrOffset();
-
-			write_segment_data(clip_context, settings, range_reduction, header, output_bone_mapping, num_output_bones);
-
-			finalize_compressed_clip(*compressed_clip);
-
-#if defined(SJSON_CPP_WRITER)
-			compression_time.stop();
-
-			if (out_stats.logging != StatLogging::None)
-				write_stats(allocator, clip, clip_context, skeleton, *compressed_clip, settings, header, raw_clip_context, additive_base_clip_context, compression_time, out_stats);
-#endif
-
-			deallocate_type_array(allocator, output_bone_mapping, num_output_bones);
-			destroy_clip_context(allocator, clip_context);
-			destroy_clip_context(allocator, raw_clip_context);
-
-			if (additive_base_clip != nullptr)
-				destroy_clip_context(allocator, additive_base_clip_context);
-
-			out_compressed_clip = compressed_clip;
-			return ErrorResult();
-		}
-	}
-}
-
-ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/compression/animation_clip.h b/includes/acl/compression/animation_clip.h
deleted file mode 100644
--- a/includes/acl/compression/animation_clip.h
+++ /dev/null
@@ -1,420 +0,0 @@
-#pragma once
-
-////////////////////////////////////////////////////////////////////////////////
-// The MIT License (MIT)
-//
-// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-////////////////////////////////////////////////////////////////////////////////
-
-#include "acl/compression/animation_track.h"
-#include "acl/compression/skeleton.h"
-#include "acl/core/additive_utils.h"
-#include "acl/core/impl/compiler_utils.h"
-#include "acl/core/error_result.h"
-#include "acl/core/interpolation_utils.h"
-#include "acl/core/string.h"
-#include "acl/core/utils.h"
-
-#include <rtm/quatf.h>
-#include <rtm/qvvf.h>
-#include <rtm/vector4f.h>
-
-#include <cstdint>
-
-ACL_IMPL_FILE_PRAGMA_PUSH
-
-namespace acl
-{
-	//////////////////////////////////////////////////////////////////////////
-	// Small structure to wrap the three tracks a bone can own: rotation, translation and scale.
-	struct AnimatedBone
-	{
-		AnimationRotationTrack		rotation_track;
-		AnimationTranslationTrack	translation_track;
-		AnimationScaleTrack			scale_track;
-
-		// The bone output index. When writing out the compressed data stream, this index
-		// will be used instead of the bone index. This allows custom reordering for things
-		// like LOD sorting or skeleton remapping. A value of 'k_invalid_bone_index' will strip the bone
-		// from the compressed data stream. Defaults to the bone index. The output index
-		// must be unique and they must be contiguous.
-		uint16_t						output_index;
-
-		bool is_stripped_from_output() const { return output_index == k_invalid_bone_index; }
-	};
-
-	//////////////////////////////////////////////////////////////////////////
-	// A raw animation clip.
-	//
-	// A clip is a collection of animated bones that map directly to a rigid skeleton.
-	// Each bone has a rotation track, a translation track, and a scale track.
-	// All tracks should have the same number of samples at a particular
-	// sample rate.
-	//
-	// A clip can also have an additive base. Such clips are deemed additive in nature
-	// and also have a corresponding additive format that dictates the mathematical
-	// operation to add it onto its base clip.
-	//
-	// Instances of this class manage and own the raw animation data within.
-	//////////////////////////////////////////////////////////////////////////
-	class AnimationClip
-	{
-	public:
-		//////////////////////////////////////////////////////////////////////////
-		// Creates an instance and initializes it.
-		//    - allocator: The allocator instance to use to allocate and free memory
-		//    - skeleton: The rigid skeleton this clip is based on
-		//    - num_samples: The number of samples per track
-		//    - sample_rate: The rate at which samples are recorded (e.g. 30 means 30 FPS)
-		//    - name: Name of the clip (used for debugging purposes only)
-		AnimationClip(IAllocator& allocator, const RigidSkeleton& skeleton, uint32_t num_samples, float sample_rate, const String &name)
-			: m_allocator(allocator)
-			, m_skeleton(skeleton)
-			, m_bones()
-			, m_num_samples(num_samples)
-			, m_sample_rate(sample_rate)
-			, m_num_bones(skeleton.get_num_bones())
-			, m_additive_base_clip(nullptr)
-			, m_additive_format(additive_clip_format8::none)
-			, m_name(allocator, name)
-		{
-			m_bones = allocate_type_array<AnimatedBone>(allocator, m_num_bones);
-
-			for (uint16_t bone_index = 0; bone_index < m_num_bones; ++bone_index)
-			{
-				m_bones[bone_index].rotation_track = AnimationRotationTrack(allocator, num_samples, sample_rate);
-				m_bones[bone_index].translation_track = AnimationTranslationTrack(allocator, num_samples, sample_rate);
-				m_bones[bone_index].scale_track = AnimationScaleTrack(allocator, num_samples, sample_rate);
-				m_bones[bone_index].output_index = bone_index;
-			}
-		}
-
-		AnimationClip(AnimationClip&& other) noexcept
-			: m_allocator(other.m_allocator)
-			, m_skeleton(other.m_skeleton)
-			, m_bones(other.m_bones)
-			, m_num_samples(other.m_num_samples)
-			, m_sample_rate(other.m_sample_rate)
-			, m_num_bones(other.m_num_bones)
-			, m_additive_base_clip(other.m_additive_base_clip)
-			, m_additive_format(other.m_additive_format)
-			, m_name(std::move(other.m_name))
-		{
-			other.m_bones = nullptr;
-		}
-
-		~AnimationClip()
-		{
-			deallocate_type_array(m_allocator, m_bones, m_num_bones);
-		}
-
-		AnimationClip(const AnimationClip&) = delete;
-		AnimationClip& operator=(const AnimationClip&) = delete;
-		AnimationClip& operator=(AnimationClip&&) = delete;
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the rigid skeleton this clip was created with
-		const RigidSkeleton& get_skeleton() const { return m_skeleton; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the array of animated bone data
-		AnimatedBone* get_bones() { return m_bones; }
-		const AnimatedBone* get_bones() const { return m_bones; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the animated bone data for the provided bone index
-		AnimatedBone& get_animated_bone(uint16_t bone_index)
-		{
-			ACL_ASSERT(bone_index < m_num_bones, "Invalid bone index: %u >= %u", bone_index, m_num_bones);
-			return m_bones[bone_index];
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the animated bone data for the provided bone index
-		const AnimatedBone& get_animated_bone(uint16_t bone_index) const
-		{
-			ACL_ASSERT(bone_index < m_num_bones, "Invalid bone index: %u >= %u", bone_index, m_num_bones);
-			return m_bones[bone_index];
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the number of bones in this clip
-		uint16_t get_num_bones() const { return m_num_bones; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the number of samples per track in this clip
-		uint32_t get_num_samples() const { return m_num_samples; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the sample rate of this clip
-		float get_sample_rate() const { return m_sample_rate; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the clip playback duration in seconds
-		float get_duration() const { return calculate_duration(m_num_samples, m_sample_rate); }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the clip name
-		const String& get_name() const { return m_name; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Samples a whole pose at a particular sample time
-		//    - sample_time: The time at which to sample the clip
-		//    - rounding_policy: The rounding policy to use when sampling
-		//    - out_local_pose: An array of at least 'num_transforms' to output the data in
-		//    - num_transforms: The number of transforms in the output array
-		void sample_pose(float sample_time, sample_rounding_policy rounding_policy, rtm::qvvf* out_local_pose, uint16_t num_transforms) const
-		{
-			ACL_ASSERT(m_num_bones > 0, "Invalid number of bones: %u", m_num_bones);
-			ACL_ASSERT(m_num_bones == num_transforms, "Number of transforms does not match the number of bones: %u != %u", num_transforms, m_num_bones);
-			(void)num_transforms;
-
-			const float clip_duration = get_duration();
-
-			// Clamp for safety, the caller should normally handle this but in practice, it often isn't the case
-			sample_time = rtm::scalar_clamp(sample_time, 0.0F, clip_duration);
-
-			uint32_t sample_index0;
-			uint32_t sample_index1;
-			float interpolation_alpha;
-			find_linear_interpolation_samples_with_sample_rate(m_num_samples, m_sample_rate, sample_time, rounding_policy, sample_index0, sample_index1, interpolation_alpha);
-
-			for (uint16_t bone_index = 0; bone_index < m_num_bones; ++bone_index)
-			{
-				const AnimatedBone& bone = m_bones[bone_index];
-
-				const rtm::quatf rotation0 = rtm::quat_normalize(quat_cast(bone.rotation_track.get_sample(sample_index0)));
-				const rtm::quatf rotation1 = rtm::quat_normalize(quat_cast(bone.rotation_track.get_sample(sample_index1)));
-				const rtm::quatf rotation = rtm::quat_lerp(rotation0, rotation1, interpolation_alpha);
-
-				const rtm::vector4f translation0 = rtm::vector_cast(bone.translation_track.get_sample(sample_index0));
-				const rtm::vector4f translation1 = rtm::vector_cast(bone.translation_track.get_sample(sample_index1));
-				const rtm::vector4f translation = rtm::vector_lerp(translation0, translation1, interpolation_alpha);
-
-				const rtm::vector4f scale0 = rtm::vector_cast(bone.scale_track.get_sample(sample_index0));
-				const rtm::vector4f scale1 = rtm::vector_cast(bone.scale_track.get_sample(sample_index1));
-				const rtm::vector4f scale = rtm::vector_lerp(scale0, scale1, interpolation_alpha);
-
-				out_local_pose[bone_index] = rtm::qvv_set(rotation, translation, scale);
-			}
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Samples a whole pose at a particular sample time
-		//    - sample_time: The time at which to sample the clip
-		//    - out_local_pose: An array of at least 'num_transforms' to output the data in
-		//    - num_transforms: The number of transforms in the output array
-		void sample_pose(float sample_time, rtm::qvvf* out_local_pose, uint16_t num_transforms) const
-		{
-			sample_pose(sample_time, sample_rounding_policy::none, out_local_pose, num_transforms);
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the raw size for this clip. Note that this differs from the actual
-		// memory used by an instance of this class. It is meant for comparison against
-		// the compressed size.
-		uint32_t get_raw_size() const
-		{
-			const uint32_t rotation_size = sizeof(float) * 4;		// Quat == Vector4
-			const uint32_t translation_size = sizeof(float) * 3;	// Vector3
-			const uint32_t scale_size = sizeof(float) * 3;			// Vector3
-			const uint32_t bone_sample_size = rotation_size + translation_size + scale_size;
-			return uint32_t(m_num_bones) * bone_sample_size * m_num_samples;
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Sets the base animation clip and marks this instance as an additive clip of the provided format
-		void set_additive_base(const AnimationClip* base_clip, additive_clip_format8 additive_format) { m_additive_base_clip = base_clip; m_additive_format = additive_format; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the additive base clip, if any
-		const AnimationClip* get_additive_base() const { return m_additive_base_clip; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the additive format of this clip, if any
-		additive_clip_format8 get_additive_format() const { return m_additive_format; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Checks if the instance of this clip is valid and returns an error if it isn't
-		ErrorResult is_valid() const
-		{
-			if (m_num_bones == 0)
-				return ErrorResult("Clip has no bones");
-
-			if (m_num_samples == 0)
-				return ErrorResult("Clip has no samples");
-
-			if (m_num_samples == 0xFFFFFFFFU)
-				return ErrorResult("Clip has too many samples");
-
-			if (m_sample_rate <= 0.0F)
-				return ErrorResult("Clip has an invalid sample rate");
-
-			uint16_t num_output_bones = 0;
-			for (uint16_t bone_index = 0; bone_index < m_num_bones; ++bone_index)
-			{
-				const uint16_t output_index = m_bones[bone_index].output_index;
-				if (output_index != k_invalid_bone_index && output_index >= m_num_bones)
-					return ErrorResult("The output_index must be 'k_invalid_bone_index' or less than the number of bones");
-
-				if (output_index != k_invalid_bone_index)
-				{
-					for (uint16_t bone_index2 = bone_index + 1; bone_index2 < m_num_bones; ++bone_index2)
-					{
-						if (output_index == m_bones[bone_index2].output_index)
-							return ErrorResult("Duplicate output_index found");
-					}
-
-					num_output_bones++;
-				}
-			}
-
-			for (uint16_t output_index = 0; output_index < num_output_bones; ++output_index)
-			{
-				bool found = false;
-				for (uint16_t bone_index = 0; bone_index < m_num_bones; ++bone_index)
-				{
-					if (output_index == m_bones[bone_index].output_index)
-					{
-						found = true;
-						break;
-					}
-				}
-
-				if (!found)
-					return ErrorResult("Output indices are not contiguous");
-			}
-
-			if (m_additive_base_clip != nullptr)
-			{
-				if (m_num_bones != m_additive_base_clip->get_num_bones())
-					return ErrorResult("The number of bones does not match between the clip and its additive base");
-
-				if (&m_skeleton != &m_additive_base_clip->get_skeleton())
-					return ErrorResult("The RigidSkeleton differs between the clip and its additive base");
-
-				return m_additive_base_clip->is_valid();
-			}
-
-			return ErrorResult();
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns whether this clip has scale or not. A clip has scale if at least one
-		// bone has a scale sample that isn't equivalent to the default scale.
-		bool has_scale(float threshold) const
-		{
-			const rtm::vector4f default_scale = get_default_scale(m_additive_format);
-
-			for (uint16_t bone_index = 0; bone_index < m_num_bones; ++bone_index)
-			{
-				const AnimatedBone& bone = m_bones[bone_index];
-				const uint32_t num_samples = bone.scale_track.get_num_samples();
-				if (num_samples != 0)
-				{
-					const rtm::vector4f scale = rtm::vector_cast(bone.scale_track.get_sample(0));
-
-					rtm::vector4f min = scale;
-					rtm::vector4f max = scale;
-
-					for (uint32_t sample_index = 1; sample_index < num_samples; ++sample_index)
-					{
-						const rtm::vector4f sample = rtm::vector_cast(bone.scale_track.get_sample(sample_index));
-
-						min = rtm::vector_min(min, sample);
-						max = rtm::vector_max(max, sample);
-					}
-
-					const rtm::vector4f extent = rtm::vector_sub(max, min);
-					const bool is_constant = rtm::vector_all_less_than3(rtm::vector_abs(extent), rtm::vector_set(threshold));
-					if (!is_constant)
-						return true;	// Not constant means we have scale
-
-					const bool is_default = rtm::vector_all_near_equal3(scale, default_scale, threshold);
-					if (!is_default)
-						return true;	// Constant but not default means we have scale
-				}
-			}
-
-			// We have no tracks with non-default scale
-			return false;
-		}
-
-	private:
-		// The allocator instance used to allocate and free memory by this clip instance
-		IAllocator&				m_allocator;
-
-		// The rigid skeleton this clip is based on
-		const RigidSkeleton&	m_skeleton;
-
-		// The array of animated bone data. There are 'm_num_bones' entries
-		AnimatedBone*			m_bones;
-
-		// The number of samples per animated track
-		uint32_t				m_num_samples;
-
-		// The rate at which the samples were recorded
-		float					m_sample_rate;
-
-		// The number of bones in this clip
-		uint16_t				m_num_bones;
-
-		// The optional clip the current additive clip is based on
-		const AnimationClip*	m_additive_base_clip;
-
-		// If we have an additive base, this is the format we are in
-		additive_clip_format8	m_additive_format;
-
-		// The name of the clip
-		String					m_name;
-	};
-
-	//////////////////////////////////////////////////////////////////////////
-	// Allocates an array of integers that correspond to the output bone mapping: result[output_index] = bone_index
-	//    - allocator: The allocator instance to use to allocate and free memory
-	//    - clip: The animation clip that dictates the bone output
-	//    - out_num_output_bones: The number of output bones
-	//////////////////////////////////////////////////////////////////////////
-	inline uint16_t* create_output_bone_mapping(IAllocator& allocator, const AnimationClip& clip, uint16_t& out_num_output_bones)
-	{
-		const uint16_t num_bones = clip.get_num_bones();
-		const AnimatedBone* bones = clip.get_bones();
-		uint16_t num_output_bones = num_bones;
-		for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
-		{
-			if (bones[bone_index].is_stripped_from_output())
-				num_output_bones--;
-		}
-
-		uint16_t* output_bone_mapping = allocate_type_array<uint16_t>(allocator, num_output_bones);
-		for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
-		{
-			const uint16_t output_index = bones[bone_index].output_index;
-			if (output_index != k_invalid_bone_index)
-				output_bone_mapping[output_index] = bone_index;
-		}
-
-		out_num_output_bones = num_output_bones;
-		return output_bone_mapping;
-	}
-}
-
-ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/compression/animation_track.h b/includes/acl/compression/animation_track.h
deleted file mode 100644
--- a/includes/acl/compression/animation_track.h
+++ /dev/null
@@ -1,346 +0,0 @@
-#pragma once
-
-////////////////////////////////////////////////////////////////////////////////
-// The MIT License (MIT)
-//
-// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-////////////////////////////////////////////////////////////////////////////////
-
-#include "acl/core/impl/compiler_utils.h"
-#include "acl/core/iallocator.h"
-#include "acl/core/error.h"
-#include "acl/core/track_types.h"
-
-#include <rtm/quatd.h>
-#include <rtm/vector4d.h>
-
-#include <cstdint>
-#include <utility>
-
-// VS2015 sometimes dies when it attempts to compile too many inlined functions
-#if defined(_MSC_VER)
-	#define VS2015_HACK_NO_INLINE __declspec(noinline)
-#else
-	#define VS2015_HACK_NO_INLINE
-#endif
-
-ACL_IMPL_FILE_PRAGMA_PUSH
-
-namespace acl
-{
-	//////////////////////////////////////////////////////////////////////////
-	// A raw animation track.
-	//
-	// This is the base class for the three track types: rotation, translation, and scale.
-	// It holds and owns the raw data.
-	//////////////////////////////////////////////////////////////////////////
-	class AnimationTrack
-	{
-	public:
-		//////////////////////////////////////////////////////////////////////////
-		// Returns if the animation track has been initialized or not
-		bool is_initialized() const { return m_allocator != nullptr; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the number of samples in this track
-		uint32_t get_num_samples() const { return m_num_samples; }
-
-	protected:
-		AnimationTrack() noexcept
-			: m_allocator(nullptr)
-			, m_sample_data(nullptr)
-			, m_num_samples(0)
-			, m_sample_rate(0.0F)
-			, m_type(animation_track_type8::rotation)
-		{}
-
-		AnimationTrack(AnimationTrack&& other) noexcept
-			: m_allocator(other.m_allocator)
-			, m_sample_data(other.m_sample_data)
-			, m_num_samples(other.m_num_samples)
-			, m_sample_rate(other.m_sample_rate)
-			, m_type(other.m_type)
-		{
-			// Safe because our derived classes do not add any data and aren't virtual
-			new(&other) AnimationTrack();
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Constructs a new track instance
-		//    - allocator: The allocator instance to use to allocate and free memory
-		//    - num_samples: The number of samples in this track
-		//    - sample_rate: The rate at which samples are recorded (e.g. 30 means 30 FPS)
-		//    - type: The track type
-		AnimationTrack(IAllocator& allocator, uint32_t num_samples, float sample_rate, animation_track_type8 type)
-			: m_allocator(&allocator)
-			, m_sample_data(allocate_type_array_aligned<double>(allocator, size_t(num_samples) * get_animation_track_sample_size(type), alignof(rtm::vector4d)))
-			, m_num_samples(num_samples)
-			, m_sample_rate(sample_rate)
-			, m_type(type)
-		{}
-
-		~AnimationTrack()
-		{
-			if (is_initialized())
-				deallocate_type_array(*m_allocator, m_sample_data, size_t(m_num_samples) * get_animation_track_sample_size(m_type));
-		}
-
-		AnimationTrack& operator=(AnimationTrack&& track) noexcept
-		{
-			std::swap(m_allocator, track.m_allocator);
-			std::swap(m_sample_data, track.m_sample_data);
-			std::swap(m_num_samples, track.m_num_samples);
-			std::swap(m_sample_rate, track.m_sample_rate);
-			std::swap(m_type, track.m_type);
-			return *this;
-		}
-
-		AnimationTrack(const AnimationTrack&) = delete;
-		AnimationTrack& operator=(const AnimationTrack&) = delete;
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the number of values per sample
-		// TODO: constexpr
-		static uint32_t get_animation_track_sample_size(animation_track_type8 type)
-		{
-			switch (type)
-			{
-			default:
-			case animation_track_type8::rotation:		return 4;
-			case animation_track_type8::translation:	return 3;
-			case animation_track_type8::scale:		return 3;
-			}
-		}
-
-		// The allocator instance used to allocate and free memory by this clip instance
-		IAllocator*						m_allocator;
-
-		// The raw track data. There are 'get_animation_track_sample_size(m_type)' entries.
-		double*							m_sample_data;
-
-		// The number of samples in this track
-		uint32_t						m_num_samples;
-
-		// The rate at which the samples were recorded
-		float							m_sample_rate;
-
-		// The track type
-		animation_track_type8				m_type;
-	};
-
-	//////////////////////////////////////////////////////////////////////////
-	// A raw rotation track.
-	//
-	// Holds a track made of 'rtm::quatd' entries.
-	//////////////////////////////////////////////////////////////////////////
-	class AnimationRotationTrack final : public AnimationTrack
-	{
-	public:
-		AnimationRotationTrack() noexcept : AnimationTrack() {}
-		~AnimationRotationTrack() = default;
-
-		//////////////////////////////////////////////////////////////////////////
-		// Constructs a new rotation track instance
-		//    - allocator: The allocator instance to use to allocate and free memory
-		//    - num_samples: The number of samples in this track
-		//    - sample_rate: The rate at which samples are recorded (e.g. 30 means 30 FPS)
-		AnimationRotationTrack(IAllocator& allocator, uint32_t num_samples, float sample_rate)
-			: AnimationTrack(allocator, num_samples, sample_rate, animation_track_type8::rotation)
-		{
-			rtm::quatd* samples = safe_ptr_cast<rtm::quatd>(&m_sample_data[0]);
-			std::fill(samples, samples + num_samples, rtm::quat_identity());
-		}
-
-		AnimationRotationTrack(AnimationRotationTrack&& other) noexcept
-			: AnimationTrack(static_cast<AnimationTrack&&>(other))
-		{}
-
-		AnimationRotationTrack& operator=(AnimationRotationTrack&& track) noexcept
-		{
-			AnimationTrack::operator=(static_cast<AnimationTrack&&>(track));
-			return *this;
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Sets a sample value at a particular index
-		VS2015_HACK_NO_INLINE void set_sample(uint32_t sample_index, const rtm::quatd& rotation)
-		{
-			ACL_ASSERT(is_initialized(), "Track is not initialized");
-			ACL_ASSERT(sample_index < m_num_samples, "Invalid sample index. %u >= %u", sample_index, m_num_samples);
-			ACL_ASSERT(rtm::quat_is_finite(rotation), "Invalid rotation: [%f, %f, %f, %f]", (float)rtm::quat_get_x(rotation), (float)rtm::quat_get_y(rotation), (float)rtm::quat_get_z(rotation), (float)rtm::quat_get_w(rotation));
-			ACL_ASSERT(rtm::quat_is_normalized(rotation), "Rotation not normalized: [%f, %f, %f, %f]", (float)rtm::quat_get_x(rotation), (float)rtm::quat_get_y(rotation), (float)rtm::quat_get_z(rotation), (float)rtm::quat_get_w(rotation));
-
-			const uint32_t sample_size = get_animation_track_sample_size(m_type);
-			ACL_ASSERT(sample_size == 4, "Invalid sample size. %u != 4", sample_size);
-
-			double* sample = &m_sample_data[sample_index * sample_size];
-			rtm::quat_store(rotation, sample);
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Retrieves a sample value at a particular index
-		rtm::quatd get_sample(uint32_t sample_index) const
-		{
-			ACL_ASSERT(is_initialized(), "Track is not initialized");
-			ACL_ASSERT(sample_index < m_num_samples, "Invalid sample index. %u >= %u", sample_index, m_num_samples);
-
-			const uint32_t sample_size = get_animation_track_sample_size(m_type);
-
-			const double* sample = &m_sample_data[sample_index * sample_size];
-			return rtm::quat_load(sample);
-		}
-
-		AnimationRotationTrack(const AnimationRotationTrack&) = delete;
-		AnimationRotationTrack& operator=(const AnimationRotationTrack&) = delete;
-	};
-
-	//////////////////////////////////////////////////////////////////////////
-	// A raw translation track.
-	//
-	// Holds a track made of 3x 'double' entries.
-	//////////////////////////////////////////////////////////////////////////
-	class AnimationTranslationTrack final : public AnimationTrack
-	{
-	public:
-		AnimationTranslationTrack() noexcept : AnimationTrack() {}
-		~AnimationTranslationTrack() = default;
-
-		//////////////////////////////////////////////////////////////////////////
-		// Constructs a new translation track instance
-		//    - allocator: The allocator instance to use to allocate and free memory
-		//    - num_samples: The number of samples in this track
-		//    - sample_rate: The rate at which samples are recorded (e.g. 30 means 30 FPS)
-		AnimationTranslationTrack(IAllocator& allocator, uint32_t num_samples, float sample_rate)
-			: AnimationTrack(allocator, num_samples, sample_rate, animation_track_type8::translation)
-		{
-			std::fill(m_sample_data, m_sample_data + (num_samples * 3), 0.0);
-		}
-
-		AnimationTranslationTrack(AnimationTranslationTrack&& other) noexcept
-			: AnimationTrack(static_cast<AnimationTrack&&>(other))
-		{}
-
-		AnimationTranslationTrack& operator=(AnimationTranslationTrack&& track) noexcept
-		{
-			AnimationTrack::operator=(static_cast<AnimationTrack&&>(track));
-			return *this;
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Sets a sample value at a particular index
-		void set_sample(uint32_t sample_index, const rtm::vector4d& translation)
-		{
-			ACL_ASSERT(is_initialized(), "Track is not initialized");
-			ACL_ASSERT(sample_index < m_num_samples, "Invalid sample index. %u >= %u", sample_index, m_num_samples);
-			ACL_ASSERT(rtm::vector_is_finite3(translation), "Invalid translation: [%f, %f, %f]", (float)rtm::vector_get_x(translation), (float)rtm::vector_get_y(translation), (float)rtm::vector_get_z(translation));
-
-			const uint32_t sample_size = get_animation_track_sample_size(m_type);
-			ACL_ASSERT(sample_size == 3, "Invalid sample size. %u != 3", sample_size);
-
-			double* sample = &m_sample_data[sample_index * sample_size];
-			rtm::vector_store3(translation, sample);
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Retrieves a sample value at a particular index
-		rtm::vector4d get_sample(uint32_t sample_index) const
-		{
-			ACL_ASSERT(is_initialized(), "Track is not initialized");
-			ACL_ASSERT(sample_index < m_num_samples, "Invalid sample index. %u >= %u", sample_index, m_num_samples);
-
-			const uint32_t sample_size = get_animation_track_sample_size(m_type);
-
-			const double* sample = &m_sample_data[sample_index * sample_size];
-			return rtm::vector_load3(sample);
-		}
-
-		AnimationTranslationTrack(const AnimationTranslationTrack&) = delete;
-		AnimationTranslationTrack& operator=(const AnimationTranslationTrack&) = delete;
-	};
-
-	//////////////////////////////////////////////////////////////////////////
-	// A raw scale track.
-	//
-	// Holds a track made of 3x 'double' entries.
-	//////////////////////////////////////////////////////////////////////////
-	class AnimationScaleTrack final : public AnimationTrack
-	{
-	public:
-		AnimationScaleTrack() noexcept : AnimationTrack() {}
-		~AnimationScaleTrack() = default;
-
-		//////////////////////////////////////////////////////////////////////////
-		// Constructs a new scale track instance
-		//    - allocator: The allocator instance to use to allocate and free memory
-		//    - num_samples: The number of samples in this track
-		//    - sample_rate: The rate at which samples are recorded (e.g. 30 means 30 FPS)
-		AnimationScaleTrack(IAllocator& allocator, uint32_t num_samples, float sample_rate)
-			: AnimationTrack(allocator, num_samples, sample_rate, animation_track_type8::scale)
-		{
-			rtm::vector4d defaultScale = rtm::vector_set(1.0);
-			for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
-				rtm::vector_store3(defaultScale, m_sample_data + (sample_index * 3));
-		}
-
-		AnimationScaleTrack(AnimationScaleTrack&& other) noexcept
-			: AnimationTrack(static_cast<AnimationTrack&&>(other))
-		{}
-
-		AnimationScaleTrack& operator=(AnimationScaleTrack&& track) noexcept
-		{
-			AnimationTrack::operator=(static_cast<AnimationTrack&&>(track));
-			return *this;
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Sets a sample value at a particular index
-		VS2015_HACK_NO_INLINE void set_sample(uint32_t sample_index, const rtm::vector4d& scale)
-		{
-			ACL_ASSERT(is_initialized(), "Track is not initialized");
-			ACL_ASSERT(sample_index < m_num_samples, "Invalid sample index. %u >= %u", sample_index, m_num_samples);
-			ACL_ASSERT(rtm::vector_is_finite3(scale), "Invalid scale: [%f, %f, %f]", (float)rtm::vector_get_x(scale), (float)rtm::vector_get_y(scale), (float)rtm::vector_get_z(scale));
-
-			const uint32_t sample_size = get_animation_track_sample_size(m_type);
-			ACL_ASSERT(sample_size == 3, "Invalid sample size. %u != 3", sample_size);
-
-			double* sample = &m_sample_data[sample_index * sample_size];
-			rtm::vector_store3(scale, sample);
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Retrieves a sample value at a particular index
-		rtm::vector4d get_sample(uint32_t sample_index) const
-		{
-			ACL_ASSERT(is_initialized(), "Track is not initialized");
-			ACL_ASSERT(sample_index < m_num_samples, "Invalid sample index. %u >= %u", sample_index, m_num_samples);
-
-			const uint32_t sample_size = get_animation_track_sample_size(m_type);
-
-			const double* sample = &m_sample_data[sample_index * sample_size];
-			return rtm::vector_load3(sample);
-		}
-
-		AnimationScaleTrack(const AnimationScaleTrack&) = delete;
-		AnimationScaleTrack& operator=(const AnimationScaleTrack&) = delete;
-	};
-}
-
-ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/compression/compress.h b/includes/acl/compression/compress.h
--- a/includes/acl/compression/compress.h
+++ b/includes/acl/compression/compress.h
@@ -35,6 +35,7 @@
 #include "acl/compression/compression_settings.h"
 #include "acl/compression/output_stats.h"
 #include "acl/compression/track_array.h"
+#include "acl/compression/impl/clip_context.h"
 #include "acl/compression/impl/constant_track_impl.h"
 #include "acl/compression/impl/normalize_track_impl.h"
 #include "acl/compression/impl/quantize_track_impl.h"
@@ -43,12 +44,401 @@
 #include "acl/compression/impl/write_compression_stats_impl.h"
 #include "acl/compression/impl/write_track_data_impl.h"
 
+#include "acl/compression/impl/track_stream.h"
+#include "acl/compression/impl/convert_rotation_streams.h"
+#include "acl/compression/impl/compact_constant_streams.h"
+#include "acl/compression/impl/normalize_streams.h"
+#include "acl/compression/impl/quantize_streams.h"
+#include "acl/compression/impl/segment_streams.h"
+#include "acl/compression/impl/write_segment_data.h"
+#include "acl/compression/impl/write_stats.h"
+#include "acl/compression/impl/write_stream_bitsets.h"
+#include "acl/compression/impl/write_stream_data.h"
+
 #include <cstdint>
 
 ACL_IMPL_FILE_PRAGMA_PUSH
 
 namespace acl
 {
+	namespace acl_impl
+	{
+		inline error_result compress_scalar_track_list(iallocator& allocator, const track_array& track_list, compressed_tracks*& out_compressed_tracks, output_stats& out_stats)
+		{
+#if defined(SJSON_CPP_WRITER)
+			scope_profiler compression_time;
+#endif
+
+			track_list_context context;
+			initialize_context(allocator, track_list, context);
+
+			extract_track_ranges(context);
+			extract_constant_tracks(context);
+			normalize_tracks(context);
+			quantize_tracks(context);
+
+			// Done transforming our input tracks, time to pack them into their final form
+			const uint32_t per_track_metadata_size = write_track_metadata(context, nullptr);
+			const uint32_t constant_values_size = write_track_constant_values(context, nullptr);
+			const uint32_t range_values_size = write_track_range_values(context, nullptr);
+			const uint32_t animated_num_bits = write_track_animated_values(context, nullptr);
+			const uint32_t animated_values_size = (animated_num_bits + 7) / 8;		// Round up to nearest byte
+			const uint32_t num_bits_per_frame = context.num_samples != 0 ? (animated_num_bits / context.num_samples) : 0;
+
+			uint32_t buffer_size = 0;
+			buffer_size += sizeof(compressed_tracks);								// Headers
+			buffer_size += sizeof(scalar_tracks_header);							// Header
+			ACL_ASSERT(is_aligned_to(buffer_size, alignof(track_metadata)), "Invalid alignment");
+			buffer_size += per_track_metadata_size;									// Per track metadata
+			buffer_size = align_to(buffer_size, 4);									// Align constant values
+			buffer_size += constant_values_size;									// Constant values
+			ACL_ASSERT(is_aligned_to(buffer_size, 4), "Invalid alignment");
+			buffer_size += range_values_size;										// Range values
+			ACL_ASSERT(is_aligned_to(buffer_size, 4), "Invalid alignment");
+			buffer_size += animated_values_size;									// Animated values
+			buffer_size += 15;														// Ensure we have sufficient padding for unaligned 16 byte loads
+
+			uint8_t* buffer = allocate_type_array_aligned<uint8_t>(allocator, buffer_size, alignof(compressed_tracks));
+			std::memset(buffer, 0, buffer_size);
+
+			const uint8_t* buffer_start = buffer;
+			out_compressed_tracks = reinterpret_cast<compressed_tracks*>(buffer);
+
+			raw_buffer_header* buffer_header = safe_ptr_cast<raw_buffer_header>(buffer);
+			buffer += sizeof(raw_buffer_header);
+
+			tracks_header* header = safe_ptr_cast<tracks_header>(buffer);
+			buffer += sizeof(tracks_header);
+
+			// Write our primary header
+			header->tag = static_cast<uint32_t>(buffer_tag32::compressed_tracks);
+			header->version = get_algorithm_version(algorithm_type8::uniformly_sampled);
+			header->algorithm_type = algorithm_type8::uniformly_sampled;
+			header->track_type = track_list.get_track_type();
+			header->num_tracks = context.num_tracks;
+			header->num_samples = context.num_samples;
+			header->sample_rate = context.sample_rate;
+
+			// Write our scalar tracks header
+			scalar_tracks_header* scalars_header = safe_ptr_cast<scalar_tracks_header>(buffer);
+			buffer += sizeof(scalar_tracks_header);
+
+			scalars_header->num_bits_per_frame = num_bits_per_frame;
+
+			const uint8_t* packed_data_start_offset = buffer - sizeof(scalar_tracks_header);	// Relative to our header
+			scalars_header->metadata_per_track = buffer - packed_data_start_offset;
+			buffer += per_track_metadata_size;
+			buffer = align_to(buffer, 4);
+			scalars_header->track_constant_values = buffer - packed_data_start_offset;
+			buffer += constant_values_size;
+			scalars_header->track_range_values = buffer - packed_data_start_offset;
+			buffer += range_values_size;
+			scalars_header->track_animated_values = buffer - packed_data_start_offset;
+			buffer += animated_values_size;
+			buffer += 15;
+
+			(void)buffer_start;	// Avoid VS2017 bug, it falsely reports this variable as unused even when asserts are enabled
+			ACL_ASSERT((buffer_start + buffer_size) == buffer, "Buffer size and pointer mismatch");
+
+			// Write our compressed data
+			track_metadata* per_track_metadata = scalars_header->get_track_metadata();
+			write_track_metadata(context, per_track_metadata);
+
+			float* constant_values = scalars_header->get_track_constant_values();
+			write_track_constant_values(context, constant_values);
+
+			float* range_values = scalars_header->get_track_range_values();
+			write_track_range_values(context, range_values);
+
+			uint8_t* animated_values = scalars_header->get_track_animated_values();
+			write_track_animated_values(context, animated_values);
+
+			// Finish the raw buffer header
+			buffer_header->size = buffer_size;
+			buffer_header->hash = hash32(safe_ptr_cast<const uint8_t>(header), buffer_size - sizeof(raw_buffer_header));	// Hash everything but the raw buffer header
+
+#if defined(ACL_HAS_ASSERT_CHECKS)
+			for (const uint8_t* padding = buffer - 15; padding < buffer; ++padding)
+				ACL_ASSERT(*padding == 0, "Padding was overwritten");
+#endif
+
+#if defined(SJSON_CPP_WRITER)
+			compression_time.stop();
+
+			if (out_stats.logging != stat_logging::None)
+				write_compression_stats(context, *out_compressed_tracks, compression_time, out_stats);
+#endif
+
+			return error_result();
+		}
+
+		inline error_result compress_transform_track_list(iallocator& allocator, const track_array_qvvf& track_list, compression_settings settings, const track_array_qvvf* additive_base_track_list, additive_clip_format8 additive_format,
+			compressed_tracks*& out_compressed_tracks, output_stats& out_stats)
+		{
+			error_result result = settings.is_valid();
+			if (result.any())
+				return result;
+
+#if defined(SJSON_CPP_WRITER)
+			scope_profiler compression_time;
+#endif
+
+			// If every track is retains full precision, we disable segmenting since it provides no benefit
+			if (!is_rotation_format_variable(settings.rotation_format) && !is_vector_format_variable(settings.translation_format) && !is_vector_format_variable(settings.scale_format))
+			{
+				settings.segmenting.ideal_num_samples = 0xFFFF;
+				settings.segmenting.max_num_samples = 0xFFFF;
+			}
+
+			// Variable bit rate tracks need range reduction
+			// Full precision tracks do not need range reduction since samples are stored raw
+			range_reduction_flags8 range_reduction = range_reduction_flags8::none;
+			if (is_rotation_format_variable(settings.rotation_format))
+				range_reduction |= range_reduction_flags8::rotations;
+
+			if (is_vector_format_variable(settings.translation_format))
+				range_reduction |= range_reduction_flags8::translations;
+
+			if (is_vector_format_variable(settings.scale_format))
+				range_reduction |= range_reduction_flags8::scales;
+
+			clip_context raw_clip_context;
+			initialize_clip_context(allocator, track_list, additive_format, raw_clip_context);
+
+			clip_context lossy_clip_context;
+			initialize_clip_context(allocator, track_list, additive_format, lossy_clip_context);
+
+			const bool is_additive = additive_base_track_list != nullptr && additive_format != additive_clip_format8::none;
+			clip_context additive_base_clip_context;
+			if (is_additive)
+				initialize_clip_context(allocator, *additive_base_track_list, additive_format, additive_base_clip_context);
+
+			convert_rotation_streams(allocator, lossy_clip_context, settings.rotation_format);
+
+			// Extract our clip ranges now, we need it for compacting the constant streams
+			extract_clip_bone_ranges(allocator, lossy_clip_context);
+
+			// Compact and collapse the constant streams
+			compact_constant_streams(allocator, lossy_clip_context, track_list);
+
+			uint32_t clip_range_data_size = 0;
+			if (range_reduction != range_reduction_flags8::none)
+			{
+				normalize_clip_streams(lossy_clip_context, range_reduction);
+				clip_range_data_size = get_stream_range_data_size(lossy_clip_context, range_reduction, settings.rotation_format);
+			}
+
+			segment_streams(allocator, lossy_clip_context, settings.segmenting);
+
+			// If we have a single segment, skip segment range reduction since it won't help
+			if (range_reduction != range_reduction_flags8::none && lossy_clip_context.num_segments > 1)
+			{
+				extract_segment_bone_ranges(allocator, lossy_clip_context);
+				normalize_segment_streams(lossy_clip_context, range_reduction);
+			}
+
+			quantize_streams(allocator, lossy_clip_context, settings, raw_clip_context, additive_base_clip_context, out_stats);
+
+			uint32_t num_output_bones = 0;
+			uint32_t* output_bone_mapping = create_output_track_mapping(allocator, track_list, num_output_bones);
+
+			const uint32_t constant_data_size = get_constant_data_size(lossy_clip_context, output_bone_mapping, num_output_bones);
+
+			calculate_animated_data_size(lossy_clip_context, output_bone_mapping, num_output_bones);
+
+			const uint32_t format_per_track_data_size = get_format_per_track_data_size(lossy_clip_context, settings.rotation_format, settings.translation_format, settings.scale_format);
+
+			const uint32_t num_tracks_per_bone = lossy_clip_context.has_scale ? 3 : 2;
+			const uint32_t num_tracks = uint32_t(num_output_bones) * num_tracks_per_bone;
+			const bitset_description bitset_desc = bitset_description::make_from_num_bits(num_tracks);
+
+			// Adding an extra index at the end to delimit things, the index is always invalid: 0xFFFFFFFF
+			const uint32_t segment_start_indices_size = lossy_clip_context.num_segments > 1 ? (sizeof(uint32_t) * (lossy_clip_context.num_segments + 1)) : 0;
+			const uint32_t segment_headers_size = sizeof(segment_header) * lossy_clip_context.num_segments;
+
+			uint32_t buffer_size = 0;
+			// Per clip data
+			buffer_size += sizeof(compressed_tracks);							// Headers
+			buffer_size += sizeof(transform_tracks_header);						// Header
+
+			const uint32_t clip_header_size = buffer_size;
+
+			buffer_size += segment_start_indices_size;							// Segment start indices
+			buffer_size = align_to(buffer_size, 4);								// Align segment headers
+			buffer_size += segment_headers_size;								// Segment headers
+			buffer_size = align_to(buffer_size, 4);								// Align bitsets
+
+			const uint32_t clip_segment_header_size = buffer_size - clip_header_size;
+
+			buffer_size += bitset_desc.get_num_bytes();							// Default tracks bitset
+			buffer_size += bitset_desc.get_num_bytes();							// Constant tracks bitset
+			buffer_size = align_to(buffer_size, 4);								// Align constant track data
+			buffer_size += constant_data_size;									// Constant track data
+			buffer_size = align_to(buffer_size, 4);								// Align range data
+			buffer_size += clip_range_data_size;								// Range data
+
+			const uint32_t clip_data_size = buffer_size - clip_segment_header_size - clip_header_size;
+
+			if (are_all_enum_flags_set(out_stats.logging, stat_logging::Detailed))
+			{
+				constexpr uint32_t k_cache_line_byte_size = 64;
+				lossy_clip_context.decomp_touched_bytes = clip_header_size + clip_data_size;
+				lossy_clip_context.decomp_touched_bytes += sizeof(uint32_t) * 4;			// We touch at most 4 segment start indices
+				lossy_clip_context.decomp_touched_bytes += sizeof(segment_header) * 2;	// We touch at most 2 segment headers
+				lossy_clip_context.decomp_touched_cache_lines = align_to(clip_header_size, k_cache_line_byte_size) / k_cache_line_byte_size;
+				lossy_clip_context.decomp_touched_cache_lines += align_to(clip_data_size, k_cache_line_byte_size) / k_cache_line_byte_size;
+				lossy_clip_context.decomp_touched_cache_lines += 1;						// All 4 segment start indices should fit in a cache line
+				lossy_clip_context.decomp_touched_cache_lines += 1;						// Both segment headers should fit in a cache line
+			}
+
+			// Per segment data
+			for (SegmentContext& segment : lossy_clip_context.segment_iterator())
+			{
+				const uint32_t header_start = buffer_size;
+
+				buffer_size += format_per_track_data_size;						// Format per track data
+				// TODO: Alignment only necessary with 16bit per component (segment constant tracks), need to fix scalar decoding path
+				buffer_size = align_to(buffer_size, 2);							// Align range data
+				buffer_size += segment.range_data_size;							// Range data
+
+				const uint32_t header_end = buffer_size;
+
+				// TODO: Variable bit rate doesn't need alignment
+				buffer_size = align_to(buffer_size, 4);							// Align animated data
+				buffer_size += segment.animated_data_size;						// Animated track data
+
+				segment.total_header_size = header_end - header_start;
+			}
+
+			// Ensure we have sufficient padding for unaligned 16 byte loads
+			buffer_size += 15;
+
+			uint8_t* buffer = allocate_type_array_aligned<uint8_t>(allocator, buffer_size, alignof(compressed_tracks));
+			std::memset(buffer, 0, buffer_size);
+
+			const uint8_t* buffer_start = buffer;
+			out_compressed_tracks = reinterpret_cast<compressed_tracks*>(buffer);
+
+			raw_buffer_header* buffer_header = safe_ptr_cast<raw_buffer_header>(buffer);
+			buffer += sizeof(raw_buffer_header);
+
+			tracks_header* header = safe_ptr_cast<tracks_header>(buffer);
+			buffer += sizeof(tracks_header);
+
+			// Write our primary header
+			header->tag = static_cast<uint32_t>(buffer_tag32::compressed_tracks);
+			header->version = get_algorithm_version(algorithm_type8::uniformly_sampled);
+			header->algorithm_type = algorithm_type8::uniformly_sampled;
+			header->track_type = track_list.get_track_type();
+			header->num_tracks = num_output_bones;
+			header->num_samples = track_list.get_num_samples_per_track();
+			header->sample_rate = track_list.get_sample_rate();
+
+			// Write our transform tracks header
+			transform_tracks_header* transforms_header = safe_ptr_cast<transform_tracks_header>(buffer);
+			buffer += sizeof(transform_tracks_header);
+
+			transforms_header->num_segments = lossy_clip_context.num_segments;
+			transforms_header->rotation_format = settings.rotation_format;
+			transforms_header->translation_format = settings.translation_format;
+			transforms_header->scale_format = settings.scale_format;
+			transforms_header->has_scale = lossy_clip_context.has_scale ? 1 : 0;
+			// Our default scale is 1.0 if we have no additive base or if we don't use 'additive1', otherwise it is 0.0
+			transforms_header->default_scale = !is_additive || additive_format != additive_clip_format8::additive1 ? 1 : 0;
+			transforms_header->segment_start_indices_offset = sizeof(transform_tracks_header);	// Relative to the start of our header
+			transforms_header->segment_headers_offset = align_to(transforms_header->segment_start_indices_offset + segment_start_indices_size, 4);
+			transforms_header->default_tracks_bitset_offset = align_to(transforms_header->segment_headers_offset + segment_headers_size, 4);
+			transforms_header->constant_tracks_bitset_offset = transforms_header->default_tracks_bitset_offset + bitset_desc.get_num_bytes();
+			transforms_header->constant_track_data_offset = align_to(transforms_header->constant_tracks_bitset_offset + bitset_desc.get_num_bytes(), 4);
+			transforms_header->clip_range_data_offset = align_to(transforms_header->constant_track_data_offset + constant_data_size, 4);
+
+			uint32_t written_segment_start_indices_size = 0;
+			if (lossy_clip_context.num_segments > 1)
+				written_segment_start_indices_size = write_segment_start_indices(lossy_clip_context, transforms_header->get_segment_start_indices());
+			else
+				transforms_header->segment_start_indices_offset = invalid_ptr_offset();
+
+			const uint32_t segment_data_start_offset = transforms_header->clip_range_data_offset + clip_range_data_size;
+			const uint32_t written_segment_headers_size = write_segment_headers(lossy_clip_context, settings, transforms_header->get_segment_headers(), segment_data_start_offset);
+
+			uint32_t written_bitset_size = 0;
+			written_bitset_size += write_default_track_bitset(lossy_clip_context, transforms_header->get_default_tracks_bitset(), bitset_desc, output_bone_mapping, num_output_bones);
+			written_bitset_size += write_constant_track_bitset(lossy_clip_context, transforms_header->get_constant_tracks_bitset(), bitset_desc, output_bone_mapping, num_output_bones);
+
+			uint32_t written_constant_data_size = 0;
+			if (constant_data_size > 0)
+				written_constant_data_size = write_constant_track_data(lossy_clip_context, transforms_header->get_constant_track_data(), constant_data_size, output_bone_mapping, num_output_bones);
+			else
+				transforms_header->constant_track_data_offset = invalid_ptr_offset();
+
+			uint32_t written_clip_range_data_size = 0;
+			if (range_reduction != range_reduction_flags8::none)
+				written_clip_range_data_size = write_clip_range_data(lossy_clip_context, range_reduction, transforms_header->get_clip_range_data(), clip_range_data_size, output_bone_mapping, num_output_bones);
+			else
+				transforms_header->clip_range_data_offset = invalid_ptr_offset();
+
+			const uint32_t written_segment_data_size = write_segment_data(lossy_clip_context, settings, range_reduction, *transforms_header, output_bone_mapping, num_output_bones);
+
+#if defined(ACL_HAS_ASSERT_CHECKS)
+			{
+				// Make sure we wrote the right amount of data
+				buffer += written_segment_start_indices_size;
+				buffer = align_to(buffer, 4);								// Align segment headers
+				buffer += written_segment_headers_size;
+				buffer = align_to(buffer, 4);								// Align bitsets
+				buffer += written_bitset_size;
+				buffer = align_to(buffer, 4);								// Align constant track data
+				buffer += written_constant_data_size;
+				buffer = align_to(buffer, 4);								// Align range data
+				buffer += written_clip_range_data_size;
+				buffer += written_segment_data_size;
+
+				// Ensure we have sufficient padding for unaligned 16 byte loads
+				buffer += 15;
+
+				(void)buffer_start;	// Avoid VS2017 bug, it falsely reports this variable as unused even when asserts are enabled
+				ACL_ASSERT(written_segment_start_indices_size == segment_start_indices_size, "Wrote too little or too much data");
+				ACL_ASSERT(written_segment_headers_size == segment_headers_size, "Wrote too little or too much data");
+				ACL_ASSERT(written_bitset_size == (bitset_desc.get_num_bytes() * 2), "Wrote too little or too much data");
+				ACL_ASSERT(written_constant_data_size == constant_data_size, "Wrote too little or too much data");
+				ACL_ASSERT(written_clip_range_data_size == clip_range_data_size, "Wrote too little or too much data");
+				ACL_ASSERT(uint32_t(buffer - buffer_start) == buffer_size, "Wrote too little or too much data");
+				for (const uint8_t* padding = buffer - 15; padding < buffer; ++padding)
+					ACL_ASSERT(*padding == 0, "Padding was overwritten");
+			}
+#else
+			(void)written_segment_start_indices_size;
+			(void)written_segment_headers_size;
+			(void)written_bitset_size;
+			(void)written_constant_data_size;
+			(void)written_clip_range_data_size;
+			(void)written_segment_data_size;
+			(void)buffer_start;
+#endif
+			
+
+			// Finish the raw buffer header
+			buffer_header->size = buffer_size;
+			buffer_header->hash = hash32(safe_ptr_cast<const uint8_t>(header), buffer_size - sizeof(raw_buffer_header));	// Hash everything but the raw buffer header
+
+#if defined(SJSON_CPP_WRITER)
+			compression_time.stop();
+
+			if (out_stats.logging != stat_logging::None)
+				write_stats(allocator, track_list, lossy_clip_context, *out_compressed_tracks, settings, raw_clip_context, additive_base_clip_context, compression_time, out_stats);
+#endif
+
+			deallocate_type_array(allocator, output_bone_mapping, num_output_bones);
+			destroy_clip_context(allocator, lossy_clip_context);
+			destroy_clip_context(allocator, raw_clip_context);
+
+			if (is_additive)
+				destroy_clip_context(allocator, additive_base_clip_context);
+
+			return error_result();
+		}
+	}
+
 	//////////////////////////////////////////////////////////////////////////
 	// Compresses a track array with uniform sampling.
 	//
@@ -66,113 +456,66 @@ namespace acl
 	//    out_compressed_tracks:	The resulting compressed tracks. The caller owns the returned memory and must free it.
 	//    out_stats:				Stat output structure.
 	//////////////////////////////////////////////////////////////////////////
-	inline ErrorResult compress_track_list(IAllocator& allocator, const track_array& track_list, const compression_settings& settings, compressed_tracks*& out_compressed_tracks, OutputStats& out_stats)
+	inline error_result compress_track_list(iallocator& allocator, const track_array& track_list, const compression_settings& settings, compressed_tracks*& out_compressed_tracks, output_stats& out_stats)
 	{
 		using namespace acl_impl;
 
-		(void)settings;	// todo?
-		(void)out_stats;
+		error_result result = track_list.is_valid();
+		if (result.any())
+			return result;
 
-		ErrorResult error_result = track_list.is_valid();
-		if (error_result.any())
-			return error_result;
+		if (track_list.get_num_samples_per_track() > 0xFFFF)
+			return error_result("ACL only supports up to 65535 samples");
 
 		// Disable floating point exceptions during compression because we leverage all SIMD lanes
 		// and we might intentionally divide by zero, etc.
 		scope_disable_fp_exceptions fp_off;
 
-#if defined(SJSON_CPP_WRITER)
-		ScopeProfiler compression_time;
-#endif
+		if (track_list.get_track_category() == track_category8::transformf)
+			result = compress_transform_track_list(allocator, track_array_cast<track_array_qvvf>(track_list), settings, nullptr, additive_clip_format8::none, out_compressed_tracks, out_stats);
+		else
+			result = compress_scalar_track_list(allocator, track_list, out_compressed_tracks, out_stats);
 
-		track_list_context context;
-		initialize_context(allocator, track_list, context);
-
-		extract_track_ranges(context);
-		extract_constant_tracks(context);
-		normalize_tracks(context);
-		quantize_tracks(context);
-
-		// Done transforming our input tracks, time to pack them into their final form
-		const uint32_t per_track_metadata_size = write_track_metadata(context, nullptr);
-		const uint32_t constant_values_size = write_track_constant_values(context, nullptr);
-		const uint32_t range_values_size = write_track_range_values(context, nullptr);
-		const uint32_t animated_num_bits = write_track_animated_values(context, nullptr);
-		const uint32_t animated_values_size = (animated_num_bits + 7) / 8;		// Round up to nearest byte
-		const uint32_t num_bits_per_frame = context.num_samples != 0 ? (animated_num_bits / context.num_samples) : 0;
-
-		uint32_t buffer_size = 0;
-		buffer_size += sizeof(compressed_tracks);								// Headers
-		ACL_ASSERT(is_aligned_to(buffer_size, alignof(track_metadata)), "Invalid alignment");
-		buffer_size += per_track_metadata_size;									// Per track metadata
-		buffer_size = align_to(buffer_size, 4);									// Align constant values
-		buffer_size += constant_values_size;									// Constant values
-		ACL_ASSERT(is_aligned_to(buffer_size, 4), "Invalid alignment");
-		buffer_size += range_values_size;										// Range values
-		ACL_ASSERT(is_aligned_to(buffer_size, 4), "Invalid alignment");
-		buffer_size += animated_values_size;									// Animated values
-		buffer_size += 15;														// Ensure we have sufficient padding for unaligned 16 byte loads
-
-		uint8_t* buffer = allocate_type_array_aligned<uint8_t>(allocator, buffer_size, alignof(compressed_tracks));
-		std::memset(buffer, 0, buffer_size);
-
-		const uint8_t* buffer_start = buffer;
-		out_compressed_tracks = reinterpret_cast<compressed_tracks*>(buffer);
-
-		raw_buffer_header* buffer_header = safe_ptr_cast<raw_buffer_header>(buffer);
-		buffer += sizeof(raw_buffer_header);
-
-		tracks_header* header = safe_ptr_cast<tracks_header>(buffer);
-		buffer += sizeof(tracks_header);
-
-		// Write our primary header
-		header->tag = static_cast<uint32_t>(buffer_tag32::compressed_tracks);
-		header->version = get_algorithm_version(algorithm_type8::uniformly_sampled);
-		header->algorithm_type = algorithm_type8::uniformly_sampled;
-		header->track_type = track_list.get_track_type();
-		header->num_tracks = context.num_tracks;
-		header->num_samples = context.num_samples;
-		header->sample_rate = context.sample_rate;
-		header->num_bits_per_frame = num_bits_per_frame;
-
-		header->metadata_per_track = buffer - buffer_start;
-		buffer += per_track_metadata_size;
-		buffer = align_to(buffer, 4);
-		header->track_constant_values = buffer - buffer_start;
-		buffer += constant_values_size;
-		header->track_range_values = buffer - buffer_start;
-		buffer += range_values_size;
-		header->track_animated_values = buffer - buffer_start;
-		buffer += animated_values_size;
-		buffer += 15;
-
-		ACL_ASSERT(buffer_start + buffer_size == buffer, "Buffer size and pointer mismatch");
-
-		// Write our compressed data
-		track_metadata* per_track_metadata = header->get_track_metadata();
-		write_track_metadata(context, per_track_metadata);
-
-		float* constant_values = header->get_track_constant_values();
-		write_track_constant_values(context, constant_values);
-
-		float* range_values = header->get_track_range_values();
-		write_track_range_values(context, range_values);
-
-		uint8_t* animated_values = header->get_track_animated_values();
-		write_track_animated_values(context, animated_values);
-
-		// Finish the raw buffer header
-		buffer_header->size = buffer_size;
-		buffer_header->hash = hash32(safe_ptr_cast<const uint8_t>(header), buffer_size - sizeof(raw_buffer_header));	// Hash everything but the raw buffer header
+		return result;
+	}
 
-#if defined(SJSON_CPP_WRITER)
-		compression_time.stop();
+	//////////////////////////////////////////////////////////////////////////
+	// Compresses a transform track array and using its additive base and uniform sampling.
+	//
+	// This compression algorithm is the simplest by far and as such it offers
+	// the fastest compression and decompression. Every sample is retained and
+	// every track has the same number of samples playing back at the same
+	// sample rate. This means that when we sample at a particular time within
+	// the clip, we can trivially calculate the offsets required to read the
+	// desired data. All the data is sorted in order to ensure all reads are
+	// as contiguous as possible for optimal cache locality during decompression.
+	//
+	//    allocator:				The allocator instance to use to allocate and free memory.
+	//    track_list:				The track list to compress.
+	//    settings:					The compression settings to use.
+	//    out_compressed_tracks:	The resulting compressed tracks. The caller owns the returned memory and must free it.
+	//    out_stats:				Stat output structure.
+	//////////////////////////////////////////////////////////////////////////
+	inline error_result compress_track_list(iallocator& allocator, const track_array_qvvf& track_list, const compression_settings& settings, const track_array_qvvf& additive_base_track_list, additive_clip_format8 additive_format, compressed_tracks*& out_compressed_tracks, output_stats& out_stats)
+	{
+		using namespace acl_impl;
 
-		if (out_stats.logging != StatLogging::None)
-			write_compression_stats(context, *out_compressed_tracks, compression_time, out_stats);
-#endif
+		error_result result = track_list.is_valid();
+		if (result.any())
+			return result;
+
+		result = additive_base_track_list.is_valid();
+		if (result.any())
+			return result;
+
+		if (track_list.get_num_samples_per_track() > 0xFFFF)
+			return error_result("ACL only supports up to 65535 samples");
+
+		// Disable floating point exceptions during compression because we leverage all SIMD lanes
+		// and we might intentionally divide by zero, etc.
+		scope_disable_fp_exceptions fp_off;
 
-		return ErrorResult();
+		return compress_transform_track_list(allocator, track_list, settings, &additive_base_track_list, additive_format, out_compressed_tracks, out_stats);
 	}
 }
 
diff --git a/includes/acl/compression/compression_settings.h b/includes/acl/compression/compression_settings.h
--- a/includes/acl/compression/compression_settings.h
+++ b/includes/acl/compression/compression_settings.h
@@ -30,7 +30,7 @@
 #include "acl/core/track_types.h"
 #include "acl/core/range_reduction_types.h"
 #include "acl/compression/compression_level.h"
-#include "acl/compression/skeleton_error_metric.h"
+#include "acl/compression/transform_error_metrics.h"
 
 #include <rtm/scalarf.h>
 
@@ -46,7 +46,7 @@ namespace acl
 	// compressed independently to allow a smaller memory footprint as well as
 	// faster compression and decompression.
 	// See also: https://nfrechette.github.io/2016/11/10/anim_compression_uniform_segmenting/
-	struct SegmentingSettings
+	struct segmenting_settings
 	{
 		//////////////////////////////////////////////////////////////////////////
 		// How many samples to try and fit in our segments
@@ -58,7 +58,7 @@ namespace acl
 		// Defaults to '31'
 		uint16_t max_num_samples;
 
-		SegmentingSettings()
+		segmenting_settings()
 			: ideal_num_samples(16)
 			, max_num_samples(31)
 		{}
@@ -76,84 +76,55 @@ namespace acl
 		//////////////////////////////////////////////////////////////////////////
 		// Checks if everything is valid and if it isn't, returns an error string.
 		// Returns nullptr if the settings are valid.
-		ErrorResult is_valid() const
+		error_result is_valid() const
 		{
 			if (ideal_num_samples < 8)
-				return ErrorResult("ideal_num_samples must be greater or equal to 8");
+				return error_result("ideal_num_samples must be greater or equal to 8");
 
 			if (ideal_num_samples > max_num_samples)
-				return ErrorResult("ideal_num_samples must be smaller or equal to max_num_samples");
+				return error_result("ideal_num_samples must be smaller or equal to max_num_samples");
 
-			return ErrorResult();
+			return error_result();
 		}
 	};
 
 	//////////////////////////////////////////////////////////////////////////
 	// Encapsulates all the compression settings.
-	struct CompressionSettings
+	struct compression_settings
 	{
 		//////////////////////////////////////////////////////////////////////////
 		// The compression level determines how aggressively we attempt to reduce the memory
 		// footprint. Higher levels will try more permutations and bit rates. The higher
 		// the level, the slower the compression but the smaller the memory footprint.
+		// Transform tracks only.
 		compression_level8 level;
 
 		//////////////////////////////////////////////////////////////////////////
 		// The rotation, translation, and scale formats to use. See functions get_rotation_format(..) and get_vector_format(..)
 		// Defaults to raw: 'quatf_full' and 'vector3f_full'
+		// Transform tracks only.
 		rotation_format8 rotation_format;
 		vector_format8 translation_format;
 		vector_format8 scale_format;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Segmenting settings, if used
-		SegmentingSettings segmenting;
+		// Transform tracks only.
+		segmenting_settings segmenting;
 
 		//////////////////////////////////////////////////////////////////////////
 		// The error metric to use.
 		// Defaults to 'null', this value must be set manually!
+		// Transform tracks only.
 		itransform_error_metric* error_metric;
 
-		//////////////////////////////////////////////////////////////////////////
-		// Threshold angle when detecting if rotation tracks are constant or default.
-		// See the rtm::quatf quat_near_identity for details about how the default threshold
-		// was chosen. You will typically NEVER need to change this, the value has been
-		// selected to be as safe as possible and is independent of game engine units.
-		// Defaults to '0.00284714461' radians
-		float constant_rotation_threshold_angle;
-
-		//////////////////////////////////////////////////////////////////////////
-		// Threshold value to use when detecting if translation tracks are constant or default.
-		// Note that you will need to change this value if your units are not in centimeters.
-		// Defaults to '0.001' centimeters.
-		float constant_translation_threshold;
-
-		//////////////////////////////////////////////////////////////////////////
-		// Threshold value to use when detecting if scale tracks are constant or default.
-		// There are no units for scale as such a value that was deemed safe was selected
-		// as a default.
-		// Defaults to '0.00001'
-		float constant_scale_threshold;
-
-		//////////////////////////////////////////////////////////////////////////
-		// The error threshold used when optimizing the bit rate.
-		// Note that you will need to change this value if your units are not in centimeters.
-		// Defaults to '0.01' centimeters
-		float error_threshold;
-
-		//////////////////////////////////////////////////////////////////////////
-		// Default constructor sets things up to perform no compression and to leave things raw.
-		CompressionSettings()
+		compression_settings()
 			: level(compression_level8::low)
 			, rotation_format(rotation_format8::quatf_full)
 			, translation_format(vector_format8::vector3f_full)
 			, scale_format(vector_format8::vector3f_full)
 			, segmenting()
 			, error_metric(nullptr)
-			, constant_rotation_threshold_angle(0.00284714461F)
-			, constant_translation_threshold(0.001F)
-			, constant_scale_threshold(0.00001F)
-			, error_threshold(0.01F)
 		{}
 
 		//////////////////////////////////////////////////////////////////////////
@@ -171,35 +142,15 @@ namespace acl
 			if (error_metric != nullptr)
 				hash_value = hash_combine(hash_value, error_metric->get_hash());
 
-			hash_value = hash_combine(hash_value, hash32(constant_rotation_threshold_angle));
-			hash_value = hash_combine(hash_value, hash32(constant_translation_threshold));
-			hash_value = hash_combine(hash_value, hash32(constant_scale_threshold));
-
-			hash_value = hash_combine(hash_value, hash32(error_threshold));
-
 			return hash_value;
 		}
 
 		//////////////////////////////////////////////////////////////////////////
 		// Checks if everything is valid and if it isn't, returns an error string.
-		// Returns nullptr if the settings are valid.
-		ErrorResult is_valid() const
+		error_result is_valid() const
 		{
 			if (error_metric == nullptr)
-				return ErrorResult("error_metric cannot be NULL");
-
-			const float rotation_threshold_angle = constant_rotation_threshold_angle;
-			if (rotation_threshold_angle < 0.0F || !rtm::scalar_is_finite(rotation_threshold_angle))
-				return ErrorResult("Invalid constant_rotation_threshold_angle");
-
-			if (constant_translation_threshold < 0.0F || !rtm::scalar_is_finite(constant_translation_threshold))
-				return ErrorResult("Invalid constant_translation_threshold");
-
-			if (constant_scale_threshold < 0.0F || !rtm::scalar_is_finite(constant_scale_threshold))
-				return ErrorResult("Invalid constant_scale_threshold");
-
-			if (error_threshold < 0.0F || !rtm::scalar_is_finite(error_threshold))
-				return ErrorResult("Invalid error_threshold");
+				return error_result("error_metric cannot be NULL");
 
 			return segmenting.is_valid();
 		}
@@ -208,32 +159,23 @@ namespace acl
 	//////////////////////////////////////////////////////////////////////////
 	// Returns raw compression settings. No compression is performed and
 	// samples are all retained with full precision.
-	inline CompressionSettings get_raw_compression_settings()
+	inline compression_settings get_raw_compression_settings()
 	{
-		return CompressionSettings();
+		return compression_settings();
 	}
 
 	//////////////////////////////////////////////////////////////////////////
 	// Returns the recommended and default compression settings. These have
 	// been tested in a wide range of scenarios and perform best overall.
-	inline CompressionSettings get_default_compression_settings()
+	inline compression_settings get_default_compression_settings()
 	{
-		CompressionSettings settings;
+		compression_settings settings;
 		settings.level = compression_level8::medium;
 		settings.rotation_format = rotation_format8::quatf_drop_w_variable;
 		settings.translation_format = vector_format8::vector3f_variable;
 		settings.scale_format = vector_format8::vector3f_variable;
 		return settings;
 	}
-
-	//////////////////////////////////////////////////////////////////////////
-	// Encapsulates all the compression settings.
-	// Note: Currently only used by scalar track compression which contain no global settings.
-	struct compression_settings
-	{
-		compression_settings()
-		{}
-	};
 }
 
 ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/compression/impl/clip_context.h b/includes/acl/compression/impl/clip_context.h
--- a/includes/acl/compression/impl/clip_context.h
+++ b/includes/acl/compression/impl/clip_context.h
@@ -25,11 +25,11 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 #include "acl/core/additive_utils.h"
-#include "acl/core/impl/compiler_utils.h"
+#include "acl/core/bitset.h"
 #include "acl/core/iallocator.h"
 #include "acl/core/iterator.h"
 #include "acl/core/error.h"
-#include "acl/compression/animation_clip.h"
+#include "acl/core/impl/compiler_utils.h"
 #include "acl/compression/compression_settings.h"
 #include "acl/compression/impl/segment_context.h"
 
@@ -44,10 +44,97 @@ namespace acl
 {
 	namespace acl_impl
 	{
-		struct ClipContext
+		//////////////////////////////////////////////////////////////////////////
+		// Simple iterator utility class to allow easy looping
+		class BoneChainIterator
+		{
+		public:
+			BoneChainIterator(const uint32_t* bone_chain, bitset_description bone_chain_desc, uint16_t bone_index, uint16_t offset)
+				: m_bone_chain(bone_chain)
+				, m_bone_chain_desc(bone_chain_desc)
+				, m_bone_index(bone_index)
+				, m_offset(offset)
+			{}
+
+			BoneChainIterator& operator++()
+			{
+				ACL_ASSERT(m_offset <= m_bone_index, "Cannot increment the iterator, it is no longer valid");
+
+				// Skip the current bone
+				m_offset++;
+
+				// Iterate until we find the next bone part of the chain or until we reach the end of the chain
+				// TODO: Use clz or similar to find the next set bit starting at the current index
+				while (m_offset < m_bone_index && !bitset_test(m_bone_chain, m_bone_chain_desc, m_offset))
+					m_offset++;
+
+				return *this;
+			}
+
+			uint16_t operator*() const
+			{
+				ACL_ASSERT(m_offset <= m_bone_index, "Returned bone index doesn't belong to the bone chain");
+				ACL_ASSERT(bitset_test(m_bone_chain, m_bone_chain_desc, m_offset), "Returned bone index doesn't belong to the bone chain");
+				return m_offset;
+			}
+
+			// We only compare the offset in the bone chain. Two iterators on the same bone index
+			// from two different or equal chains will be equal.
+			bool operator==(const BoneChainIterator& other) const { return m_offset == other.m_offset; }
+			bool operator!=(const BoneChainIterator& other) const { return m_offset != other.m_offset; }
+
+		private:
+			const uint32_t*		m_bone_chain;
+			bitset_description	m_bone_chain_desc;
+			uint16_t			m_bone_index;
+			uint16_t			m_offset;
+		};
+
+		//////////////////////////////////////////////////////////////////////////
+		// Simple bone chain container to allow easy looping
+		//
+		// A bone chain allows looping over all bones up to a specific bone starting
+		// at the root bone.
+		//////////////////////////////////////////////////////////////////////////
+		struct BoneChain
+		{
+			BoneChain(const uint32_t* bone_chain, bitset_description bone_chain_desc, uint16_t bone_index)
+				: m_bone_chain(bone_chain)
+				, m_bone_chain_desc(bone_chain_desc)
+				, m_bone_index(bone_index)
+			{
+				// We don't know where this bone chain starts, find the root bone
+				// TODO: Use clz or similar to find the next set bit starting at the current index
+				uint16_t root_index = 0;
+				while (!bitset_test(bone_chain, bone_chain_desc, root_index))
+					root_index++;
+
+				m_root_index = root_index;
+			}
+
+			acl_impl::BoneChainIterator begin() const { return acl_impl::BoneChainIterator(m_bone_chain, m_bone_chain_desc, m_bone_index, m_root_index); }
+			acl_impl::BoneChainIterator end() const { return acl_impl::BoneChainIterator(m_bone_chain, m_bone_chain_desc, m_bone_index, m_bone_index + 1); }
+
+			const uint32_t*		m_bone_chain;
+			bitset_description	m_bone_chain_desc;
+			uint16_t			m_root_index;
+			uint16_t			m_bone_index;
+		};
+
+		struct transform_metadata
+		{
+			const uint32_t* transform_chain;
+			uint16_t parent_index;
+			float precision;
+			float shell_distance;
+		};
+
+		struct clip_context
 		{
 			SegmentContext* segments;
 			BoneRanges* ranges;
+			transform_metadata* metadata;
+			uint32_t* leaf_transform_chains;
 
 			uint16_t num_segments;
 			uint16_t num_bones;
@@ -65,59 +152,70 @@ namespace acl
 
 			additive_clip_format8 additive_format;
 
+			uint32_t num_leaf_transforms;
+
 			// Stat tracking
 			uint32_t decomp_touched_bytes;
 			uint32_t decomp_touched_cache_lines;
 
 			//////////////////////////////////////////////////////////////////////////
 
-			Iterator<SegmentContext> segment_iterator() { return Iterator<SegmentContext>(segments, num_segments); }
-			ConstIterator<SegmentContext> const_segment_iterator() const { return ConstIterator<SegmentContext>(segments, num_segments); }
+			iterator<SegmentContext> segment_iterator() { return iterator<SegmentContext>(segments, num_segments); }
+			const_iterator<SegmentContext> const_segment_iterator() const { return const_iterator<SegmentContext>(segments, num_segments); }
+
+			BoneChain get_bone_chain(uint32_t bone_index) const
+			{
+				ACL_ASSERT(bone_index < num_bones, "Invalid bone index: %u >= %u", bone_index, num_bones);
+				const transform_metadata& meta = metadata[bone_index];
+				return BoneChain(meta.transform_chain, bitset_description::make_from_num_bits(num_bones), (uint16_t)bone_index);
+			}
 		};
 
-		inline void initialize_clip_context(IAllocator& allocator, const AnimationClip& clip, const RigidSkeleton& skeleton, const CompressionSettings& settings, ClipContext& out_clip_context)
+		inline void initialize_clip_context(iallocator& allocator, const track_array_qvvf& track_list, additive_clip_format8 additive_format, clip_context& out_clip_context)
 		{
-			const uint16_t num_bones = clip.get_num_bones();
-			const uint32_t num_samples = clip.get_num_samples();
-			const float sample_rate = clip.get_sample_rate();
-			const AnimatedBone* bones = clip.get_bones();
-			const bool has_additive_base = clip.get_additive_base() != nullptr;
+			const uint32_t num_transforms = track_list.get_num_tracks();
+			const uint32_t num_samples = track_list.get_num_samples_per_track();
+			const float sample_rate = track_list.get_sample_rate();
 
-			ACL_ASSERT(num_bones > 0, "Clip has no bones!");
-			ACL_ASSERT(num_samples > 0, "Clip has no samples!");
+			ACL_ASSERT(num_transforms > 0, "Track array has no tracks!");
+			ACL_ASSERT(num_samples > 0, "Track array has no samples!");
 
 			// Create a single segment with the whole clip
 			out_clip_context.segments = allocate_type_array<SegmentContext>(allocator, 1);
 			out_clip_context.ranges = nullptr;
+			out_clip_context.metadata = allocate_type_array<transform_metadata>(allocator, num_transforms);
+			out_clip_context.leaf_transform_chains = nullptr;
 			out_clip_context.num_segments = 1;
-			out_clip_context.num_bones = num_bones;
-			out_clip_context.num_output_bones = num_bones;
+			out_clip_context.num_bones = safe_static_cast<uint16_t>(num_transforms);
+			out_clip_context.num_output_bones = safe_static_cast<uint16_t>(num_transforms);
 			out_clip_context.num_samples = num_samples;
 			out_clip_context.sample_rate = sample_rate;
-			out_clip_context.duration = clip.get_duration();
+			out_clip_context.duration = track_list.get_duration();
 			out_clip_context.are_rotations_normalized = false;
 			out_clip_context.are_translations_normalized = false;
 			out_clip_context.are_scales_normalized = false;
-			out_clip_context.has_additive_base = has_additive_base;
-			out_clip_context.additive_format = clip.get_additive_format();
+			out_clip_context.has_additive_base = additive_format != additive_clip_format8::none;
+			out_clip_context.additive_format = additive_format;
+			out_clip_context.num_leaf_transforms = 0;
 
 			bool has_scale = false;
-			const rtm::vector4f default_scale = get_default_scale(clip.get_additive_format());
+			const rtm::vector4f default_scale = get_default_scale(additive_format);
 
 			SegmentContext& segment = out_clip_context.segments[0];
 
-			BoneStreams* bone_streams = allocate_type_array<BoneStreams>(allocator, num_bones);
+			BoneStreams* bone_streams = allocate_type_array<BoneStreams>(allocator, num_transforms);
 
-			for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
+			for (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index)
 			{
-				const AnimatedBone& bone = bones[bone_index];
-				const RigidBone& skel_bone = skeleton.get_bone(bone_index);
-				BoneStreams& bone_stream = bone_streams[bone_index];
+				const track_qvvf& track = track_list[transform_index];
+				const track_desc_transformf& desc = track.get_description();
+
+				BoneStreams& bone_stream = bone_streams[transform_index];
 
 				bone_stream.segment = &segment;
-				bone_stream.bone_index = bone_index;
-				bone_stream.parent_bone_index = skel_bone.parent_index;
-				bone_stream.output_index = bone.output_index;
+				bone_stream.bone_index = safe_static_cast<uint16_t>(transform_index);
+				bone_stream.parent_bone_index = desc.parent_index == k_invalid_track_index ? k_invalid_bone_index : safe_static_cast<uint16_t>(desc.parent_index);
+				bone_stream.output_index = desc.output_index == k_invalid_track_index ? k_invalid_bone_index : safe_static_cast<uint16_t>(desc.output_index);
 
 				bone_stream.rotations = RotationTrackStream(allocator, num_samples, sizeof(rtm::quatf), sample_rate, rotation_format8::quatf_full);
 				bone_stream.translations = TranslationTrackStream(allocator, num_samples, sizeof(rtm::vector4f), sample_rate, vector_format8::vector3f_full);
@@ -125,27 +223,34 @@ namespace acl
 
 				for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
 				{
-					const rtm::quatf rotation = rtm::quat_normalize(rtm::quat_cast(bone.rotation_track.get_sample(sample_index)));
-					bone_stream.rotations.set_raw_sample(sample_index, rotation);
-
-					const rtm::vector4f translation = rtm::vector_cast(bone.translation_track.get_sample(sample_index));
-					bone_stream.translations.set_raw_sample(sample_index, translation);
+					const rtm::qvvf& transform = track[sample_index];
 
-					const rtm::vector4f scale = rtm::vector_cast(bone.scale_track.get_sample(sample_index));
-					bone_stream.scales.set_raw_sample(sample_index, scale);
+					bone_stream.rotations.set_raw_sample(sample_index, transform.rotation);
+					bone_stream.translations.set_raw_sample(sample_index, transform.translation);
+					bone_stream.scales.set_raw_sample(sample_index, transform.scale);
 				}
 
-				bone_stream.is_rotation_constant = num_samples == 1;
-				bone_stream.is_rotation_default = bone_stream.is_rotation_constant && rtm::quat_near_identity(rtm::quat_cast(bone.rotation_track.get_sample(0)), settings.constant_rotation_threshold_angle);
-				bone_stream.is_translation_constant = num_samples == 1;
-				bone_stream.is_translation_default = bone_stream.is_translation_constant && rtm::vector_all_near_equal3(rtm::vector_cast(bone.translation_track.get_sample(0)), rtm::vector_zero(), settings.constant_translation_threshold);
-				bone_stream.is_scale_constant = num_samples == 1;
-				bone_stream.is_scale_default = bone_stream.is_scale_constant && rtm::vector_all_near_equal3(rtm::vector_cast(bone.scale_track.get_sample(0)), default_scale, settings.constant_scale_threshold);
+				{
+					const rtm::qvvf& first_transform = track[0];
+
+					bone_stream.is_rotation_constant = num_samples == 1;
+					bone_stream.is_rotation_default = bone_stream.is_rotation_constant && rtm::quat_near_identity(first_transform.rotation, desc.constant_rotation_threshold_angle);
+					bone_stream.is_translation_constant = num_samples == 1;
+					bone_stream.is_translation_default = bone_stream.is_translation_constant && rtm::vector_all_near_equal3(first_transform.translation, rtm::vector_zero(), desc.constant_translation_threshold);
+					bone_stream.is_scale_constant = num_samples == 1;
+					bone_stream.is_scale_default = bone_stream.is_scale_constant && rtm::vector_all_near_equal3(first_transform.scale, default_scale, desc.constant_scale_threshold);
+				}
 
 				has_scale |= !bone_stream.is_scale_default;
 
 				if (bone_stream.is_stripped_from_output())
 					out_clip_context.num_output_bones--;
+
+				transform_metadata& metadata = out_clip_context.metadata[transform_index];
+				metadata.transform_chain = nullptr;
+				metadata.parent_index = desc.parent_index == k_invalid_track_index ? k_invalid_bone_index : safe_static_cast<uint16_t>(desc.parent_index);
+				metadata.precision = desc.precision;
+				metadata.shell_distance = desc.shell_distance;
 			}
 
 			out_clip_context.has_scale = has_scale;
@@ -156,7 +261,7 @@ namespace acl
 			segment.clip = &out_clip_context;
 			segment.ranges = nullptr;
 			segment.num_samples = safe_static_cast<uint16_t>(num_samples);
-			segment.num_bones = num_bones;
+			segment.num_bones = safe_static_cast<uint16_t>(num_transforms);
 			segment.clip_sample_offset = 0;
 			segment.segment_index = 0;
 			segment.distribution = SampleDistribution8::Uniform;
@@ -168,15 +273,87 @@ namespace acl
 			segment.animated_data_size = 0;
 			segment.range_data_size = 0;
 			segment.total_header_size = 0;
+
+			// Initialize our hierarchy information
+			{
+				// Calculate which bones are leaf bones that have no children
+				bitset_description bone_bitset_desc = bitset_description::make_from_num_bits(num_transforms);
+				uint32_t* is_leaf_bitset = allocate_type_array<uint32_t>(allocator, bone_bitset_desc.get_size());
+				bitset_reset(is_leaf_bitset, bone_bitset_desc, false);
+
+				// By default  and if we find a child, we'll mark it as non-leaf
+				bitset_set_range(is_leaf_bitset, bone_bitset_desc, 0, num_transforms, true);
+
+#if defined(ACL_HAS_ASSERT_CHECKS)
+				uint32_t num_root_bones = 0;
+#endif
+
+				// Move and validate the input data
+				for (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index)
+				{
+					const transform_metadata& metadata = out_clip_context.metadata[transform_index];
+
+					const bool is_root = metadata.parent_index == k_invalid_bone_index;
+
+					// If we have a parent, mark it as not being a leaf bone (it has at least one child)
+					if (!is_root)
+						bitset_set(is_leaf_bitset, bone_bitset_desc, metadata.parent_index, false);
+
+#if defined(ACL_HAS_ASSERT_CHECKS)
+					if (is_root)
+						num_root_bones++;
+#endif
+				}
+
+				const uint32_t num_leaf_transforms = bitset_count_set_bits(is_leaf_bitset, bone_bitset_desc);
+				out_clip_context.num_leaf_transforms = num_leaf_transforms;
+
+				uint32_t* leaf_transform_chains = allocate_type_array<uint32_t>(allocator, size_t(num_leaf_transforms) * bone_bitset_desc.get_size());
+				out_clip_context.leaf_transform_chains = leaf_transform_chains;
+
+				uint32_t leaf_index = 0;
+				for (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index)
+				{
+					if (!bitset_test(is_leaf_bitset, bone_bitset_desc, transform_index))
+						continue;	// Skip non-leaf bones
+
+					uint32_t* bone_chain = leaf_transform_chains + (leaf_index * bone_bitset_desc.get_size());
+					bitset_reset(bone_chain, bone_bitset_desc, false);
+
+					uint16_t chain_bone_index = safe_static_cast<uint16_t>(transform_index);
+					while (chain_bone_index != k_invalid_bone_index)
+					{
+						bitset_set(bone_chain, bone_bitset_desc, chain_bone_index, true);
+
+						transform_metadata& metadata = out_clip_context.metadata[chain_bone_index];
+
+						// We assign a bone chain the first time we find a bone that isn't part of one already
+						if (metadata.transform_chain == nullptr)
+							metadata.transform_chain = bone_chain;
+
+						chain_bone_index = metadata.parent_index;
+					}
+
+					leaf_index++;
+				}
+
+				ACL_ASSERT(num_root_bones > 0, "No root bone found. The root bones must have a parent index = 0xFFFF");
+				ACL_ASSERT(leaf_index == num_leaf_transforms, "Invalid number of leaf bone found");
+				deallocate_type_array(allocator, is_leaf_bitset, bone_bitset_desc.get_size());
+			}
 		}
 
-		inline void destroy_clip_context(IAllocator& allocator, ClipContext& clip_context)
+		inline void destroy_clip_context(iallocator& allocator, clip_context& context)
 		{
-			for (SegmentContext& segment : clip_context.segment_iterator())
+			for (SegmentContext& segment : context.segment_iterator())
 				destroy_segment_context(allocator, segment);
 
-			deallocate_type_array(allocator, clip_context.segments, clip_context.num_segments);
-			deallocate_type_array(allocator, clip_context.ranges, clip_context.num_bones);
+			deallocate_type_array(allocator, context.segments, context.num_segments);
+			deallocate_type_array(allocator, context.ranges, context.num_bones);
+			deallocate_type_array(allocator, context.metadata, context.num_bones);
+
+			bitset_description bone_bitset_desc = bitset_description::make_from_num_bits(context.num_bones);
+			deallocate_type_array(allocator, context.leaf_transform_chains, size_t(context.num_leaf_transforms) * bone_bitset_desc.get_size());
 		}
 
 		constexpr bool segment_context_has_scale(const SegmentContext& segment) { return segment.clip->has_scale; }
diff --git a/includes/acl/compression/impl/compact_constant_streams.h b/includes/acl/compression/impl/compact_constant_streams.h
--- a/includes/acl/compression/impl/compact_constant_streams.h
+++ b/includes/acl/compression/impl/compact_constant_streams.h
@@ -78,27 +78,29 @@ namespace acl
 			return true;
 		}
 
-		inline void compact_constant_streams(IAllocator& allocator, ClipContext& clip_context, float rotation_threshold_angle, float translation_threshold, float scale_threshold)
+		inline void compact_constant_streams(iallocator& allocator, clip_context& context, const track_array_qvvf& track_list)
 		{
-			ACL_ASSERT(clip_context.num_segments == 1, "ClipContext must contain a single segment!");
-			SegmentContext& segment = clip_context.segments[0];
+			ACL_ASSERT(context.num_segments == 1, "clip_context must contain a single segment!");
+			SegmentContext& segment = context.segments[0];
 
-			const uint16_t num_bones = clip_context.num_bones;
-			const rtm::vector4f default_scale = get_default_scale(clip_context.additive_format);
+			const uint16_t num_bones = context.num_bones;
+			const rtm::vector4f default_scale = get_default_scale(context.additive_format);
 			uint16_t num_default_bone_scales = 0;
 
 			// When a stream is constant, we only keep the first sample
 			for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
 			{
+				const track_desc_transformf& desc = track_list[bone_index].get_description();
+
 				BoneStreams& bone_stream = segment.bone_streams[bone_index];
-				BoneRanges& bone_range = clip_context.ranges[bone_index];
+				BoneRanges& bone_range = context.ranges[bone_index];
 
 				// We expect all our samples to have the same width of sizeof(rtm::vector4f)
 				ACL_ASSERT(bone_stream.rotations.get_sample_size() == sizeof(rtm::vector4f), "Unexpected rotation sample size. %u != %zu", bone_stream.rotations.get_sample_size(), sizeof(rtm::vector4f));
 				ACL_ASSERT(bone_stream.translations.get_sample_size() == sizeof(rtm::vector4f), "Unexpected translation sample size. %u != %zu", bone_stream.translations.get_sample_size(), sizeof(rtm::vector4f));
 				ACL_ASSERT(bone_stream.scales.get_sample_size() == sizeof(rtm::vector4f), "Unexpected scale sample size. %u != %zu", bone_stream.scales.get_sample_size(), sizeof(rtm::vector4f));
 
-				if (is_rotation_track_constant(bone_stream.rotations, rotation_threshold_angle))
+				if (is_rotation_track_constant(bone_stream.rotations, desc.constant_rotation_threshold_angle))
 				{
 					RotationTrackStream constant_stream(allocator, 1, bone_stream.rotations.get_sample_size(), bone_stream.rotations.get_sample_rate(), bone_stream.rotations.get_rotation_format());
 					rtm::vector4f rotation = bone_stream.rotations.get_raw_sample<rtm::vector4f>(0);
@@ -106,12 +108,12 @@ namespace acl
 
 					bone_stream.rotations = std::move(constant_stream);
 					bone_stream.is_rotation_constant = true;
-					bone_stream.is_rotation_default = rtm::quat_near_identity(rtm::vector_to_quat(rotation), rotation_threshold_angle);
+					bone_stream.is_rotation_default = rtm::quat_near_identity(rtm::vector_to_quat(rotation), desc.constant_rotation_threshold_angle);
 
 					bone_range.rotation = TrackStreamRange::from_min_extent(rotation, rtm::vector_zero());
 				}
 
-				if (bone_range.translation.is_constant(translation_threshold))
+				if (bone_range.translation.is_constant(desc.constant_translation_threshold))
 				{
 					TranslationTrackStream constant_stream(allocator, 1, bone_stream.translations.get_sample_size(), bone_stream.translations.get_sample_rate(), bone_stream.translations.get_vector_format());
 					rtm::vector4f translation = bone_stream.translations.get_raw_sample<rtm::vector4f>(0);
@@ -119,12 +121,12 @@ namespace acl
 
 					bone_stream.translations = std::move(constant_stream);
 					bone_stream.is_translation_constant = true;
-					bone_stream.is_translation_default = rtm::vector_all_near_equal3(translation, rtm::vector_zero(), translation_threshold);
+					bone_stream.is_translation_default = rtm::vector_all_near_equal3(translation, rtm::vector_zero(), desc.constant_translation_threshold);
 
 					bone_range.translation = TrackStreamRange::from_min_extent(translation, rtm::vector_zero());
 				}
 
-				if (bone_range.scale.is_constant(scale_threshold))
+				if (bone_range.scale.is_constant(desc.constant_scale_threshold))
 				{
 					ScaleTrackStream constant_stream(allocator, 1, bone_stream.scales.get_sample_size(), bone_stream.scales.get_sample_rate(), bone_stream.scales.get_vector_format());
 					rtm::vector4f scale = bone_stream.scales.get_raw_sample<rtm::vector4f>(0);
@@ -132,7 +134,7 @@ namespace acl
 
 					bone_stream.scales = std::move(constant_stream);
 					bone_stream.is_scale_constant = true;
-					bone_stream.is_scale_default = rtm::vector_all_near_equal3(scale, default_scale, scale_threshold);
+					bone_stream.is_scale_default = rtm::vector_all_near_equal3(scale, default_scale, desc.constant_scale_threshold);
 
 					bone_range.scale = TrackStreamRange::from_min_extent(scale, rtm::vector_zero());
 
@@ -140,7 +142,7 @@ namespace acl
 				}
 			}
 
-			clip_context.has_scale = num_default_bone_scales != num_bones;
+			context.has_scale = num_default_bone_scales != num_bones;
 		}
 	}
 }
diff --git a/includes/acl/compression/impl/compressed_clip_impl.h b/includes/acl/compression/impl/compressed_clip_impl.h
deleted file mode 100644
--- a/includes/acl/compression/impl/compressed_clip_impl.h
+++ /dev/null
@@ -1,50 +0,0 @@
-#pragma once
-
-////////////////////////////////////////////////////////////////////////////////
-// The MIT License (MIT)
-//
-// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-////////////////////////////////////////////////////////////////////////////////
-
-#include "acl/core/impl/compiler_utils.h"
-#include "acl/core/compressed_clip.h"
-
-ACL_IMPL_FILE_PRAGMA_PUSH
-
-namespace acl
-{
-	namespace acl_impl
-	{
-		inline CompressedClip* make_compressed_clip(void* buffer, uint32_t size, algorithm_type8 type)
-		{
-			return new(buffer) CompressedClip(size, type);
-		}
-
-		inline void finalize_compressed_clip(CompressedClip& compressed_clip)
-		{
-			// For now we just run the constructor in place again, it'll update the hash, etc.
-			// TODO: Fix this to be more efficient in make_compressed_clip above
-			new(&compressed_clip) CompressedClip(compressed_clip.get_size(), compressed_clip.get_algorithm_type());
-		}
-	}
-}
-
-ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/compression/impl/constant_track_impl.h b/includes/acl/compression/impl/constant_track_impl.h
--- a/includes/acl/compression/impl/constant_track_impl.h
+++ b/includes/acl/compression/impl/constant_track_impl.h
@@ -47,7 +47,7 @@ namespace acl
 		{
 			ACL_ASSERT(context.is_valid(), "Invalid context");
 
-			const BitSetDescription bitset_desc = BitSetDescription::make_from_num_bits(context.num_tracks);
+			const bitset_description bitset_desc = bitset_description::make_from_num_bits(context.num_tracks);
 
 			context.constant_tracks_bitset = allocate_type_array<uint32_t>(*context.allocator, bitset_desc.get_size());
 			bitset_reset(context.constant_tracks_bitset, bitset_desc, false);
diff --git a/includes/acl/compression/impl/convert_rotation_streams.h b/includes/acl/compression/impl/convert_rotation_streams.h
--- a/includes/acl/compression/impl/convert_rotation_streams.h
+++ b/includes/acl/compression/impl/convert_rotation_streams.h
@@ -60,7 +60,7 @@ namespace acl
 			}
 		}
 
-		inline void convert_rotation_streams(IAllocator& allocator, SegmentContext& segment, rotation_format8 rotation_format)
+		inline void convert_rotation_streams(iallocator& allocator, SegmentContext& segment, rotation_format8 rotation_format)
 		{
 			const rotation_format8 high_precision_format = get_rotation_variant(rotation_format) == rotation_variant8::quat ? rotation_format8::quatf_full : rotation_format8::quatf_drop_w_full;
 
@@ -99,9 +99,9 @@ namespace acl
 			}
 		}
 
-		inline void convert_rotation_streams(IAllocator& allocator, ClipContext& clip_context, rotation_format8 rotation_format)
+		inline void convert_rotation_streams(iallocator& allocator, clip_context& context, rotation_format8 rotation_format)
 		{
-			for (SegmentContext& segment : clip_context.segment_iterator())
+			for (SegmentContext& segment : context.segment_iterator())
 				convert_rotation_streams(allocator, segment, rotation_format);
 		}
 	}
diff --git a/includes/acl/compression/impl/normalize_streams.h b/includes/acl/compression/impl/normalize_streams.h
--- a/includes/acl/compression/impl/normalize_streams.h
+++ b/includes/acl/compression/impl/normalize_streams.h
@@ -78,17 +78,17 @@ namespace acl
 			}
 		}
 
-		inline void extract_clip_bone_ranges(IAllocator& allocator, ClipContext& clip_context)
+		inline void extract_clip_bone_ranges(iallocator& allocator, clip_context& context)
 		{
-			clip_context.ranges = allocate_type_array<BoneRanges>(allocator, clip_context.num_bones);
+			context.ranges = allocate_type_array<BoneRanges>(allocator, context.num_bones);
 
-			ACL_ASSERT(clip_context.num_segments == 1, "ClipContext must contain a single segment!");
-			SegmentContext& segment = clip_context.segments[0];
+			ACL_ASSERT(context.num_segments == 1, "clip_context must contain a single segment!");
+			SegmentContext& segment = context.segments[0];
 
-			acl_impl::extract_bone_ranges_impl(segment, clip_context.ranges);
+			acl_impl::extract_bone_ranges_impl(segment, context.ranges);
 		}
 
-		inline void extract_segment_bone_ranges(IAllocator& allocator, ClipContext& clip_context)
+		inline void extract_segment_bone_ranges(iallocator& allocator, clip_context& context)
 		{
 			const rtm::vector4f one = rtm::vector_set(1.0F);
 			const rtm::vector4f zero = rtm::vector_zero();
@@ -138,7 +138,7 @@ namespace acl
 				return TrackStreamRange::from_min_extent(padded_range_min, padded_range_extent);
 			};
 
-			for (SegmentContext& segment : clip_context.segment_iterator())
+			for (SegmentContext& segment : context.segment_iterator())
 			{
 				segment.ranges = allocate_type_array<BoneRanges>(allocator, segment.num_bones);
 
@@ -149,13 +149,13 @@ namespace acl
 					const BoneStreams& bone_stream = segment.bone_streams[bone_index];
 					BoneRanges& bone_range = segment.ranges[bone_index];
 
-					if (!bone_stream.is_rotation_constant && clip_context.are_rotations_normalized)
+					if (!bone_stream.is_rotation_constant && context.are_rotations_normalized)
 						bone_range.rotation = fixup_range(bone_range.rotation);
 
-					if (!bone_stream.is_translation_constant && clip_context.are_translations_normalized)
+					if (!bone_stream.is_translation_constant && context.are_translations_normalized)
 						bone_range.translation = fixup_range(bone_range.translation);
 
-					if (!bone_stream.is_scale_constant && clip_context.are_scales_normalized)
+					if (!bone_stream.is_scale_constant && context.are_scales_normalized)
 						bone_range.scale = fixup_range(bone_range.scale);
 				}
 			}
@@ -307,35 +307,35 @@ namespace acl
 			}
 		}
 
-		inline void normalize_clip_streams(ClipContext& clip_context, range_reduction_flags8 range_reduction)
+		inline void normalize_clip_streams(clip_context& context, range_reduction_flags8 range_reduction)
 		{
-			ACL_ASSERT(clip_context.num_segments == 1, "ClipContext must contain a single segment!");
-			SegmentContext& segment = clip_context.segments[0];
+			ACL_ASSERT(context.num_segments == 1, "clip_context must contain a single segment!");
+			SegmentContext& segment = context.segments[0];
 
 			const bool has_scale = segment_context_has_scale(segment);
 
 			if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::rotations))
 			{
-				normalize_rotation_streams(segment.bone_streams, clip_context.ranges, segment.num_bones);
-				clip_context.are_rotations_normalized = true;
+				normalize_rotation_streams(segment.bone_streams, context.ranges, segment.num_bones);
+				context.are_rotations_normalized = true;
 			}
 
 			if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::translations))
 			{
-				normalize_translation_streams(segment.bone_streams, clip_context.ranges, segment.num_bones);
-				clip_context.are_translations_normalized = true;
+				normalize_translation_streams(segment.bone_streams, context.ranges, segment.num_bones);
+				context.are_translations_normalized = true;
 			}
 
 			if (has_scale && are_any_enum_flags_set(range_reduction, range_reduction_flags8::scales))
 			{
-				normalize_scale_streams(segment.bone_streams, clip_context.ranges, segment.num_bones);
-				clip_context.are_scales_normalized = true;
+				normalize_scale_streams(segment.bone_streams, context.ranges, segment.num_bones);
+				context.are_scales_normalized = true;
 			}
 		}
 
-		inline void normalize_segment_streams(ClipContext& clip_context, range_reduction_flags8 range_reduction)
+		inline void normalize_segment_streams(clip_context& context, range_reduction_flags8 range_reduction)
 		{
-			for (SegmentContext& segment : clip_context.segment_iterator())
+			for (SegmentContext& segment : context.segment_iterator())
 			{
 				if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::rotations))
 				{
diff --git a/includes/acl/compression/impl/quantize_streams.h b/includes/acl/compression/impl/quantize_streams.h
--- a/includes/acl/compression/impl/quantize_streams.h
+++ b/includes/acl/compression/impl/quantize_streams.h
@@ -36,7 +36,7 @@
 #include "acl/compression/impl/sample_streams.h"
 #include "acl/compression/impl/normalize_streams.h"
 #include "acl/compression/impl/convert_rotation_streams.h"
-#include "acl/compression/skeleton_error_metric.h"
+#include "acl/compression/transform_error_metrics.h"
 #include "acl/compression/compression_settings.h"
 
 #include <rtm/quatf.h>
@@ -62,17 +62,17 @@ namespace acl
 {
 	namespace acl_impl
 	{
-		struct QuantizationContext
+		struct quantization_context
 		{
-			IAllocator& allocator;
-			ClipContext& clip;
-			const ClipContext& raw_clip;
-			const ClipContext& additive_base_clip;
+			iallocator& allocator;
+			clip_context& clip;
+			const clip_context& raw_clip;
+			const clip_context& additive_base_clip;
 			SegmentContext* segment;
 			BoneStreams* bone_streams;
+			const transform_metadata* metadata;
 			uint16_t num_bones;
-			const RigidSkeleton& skeleton;
-			const CompressionSettings& settings;
+			const itransform_error_metric* error_metric;
 
 			track_bit_rate_database bit_rate_database;
 			single_track_query local_query;
@@ -82,10 +82,16 @@ namespace acl
 			uint32_t segment_sample_start_index;
 			float sample_rate;
 			float clip_duration;
+			float error_threshold;					// Error threshold of the current bone being optimized
 			bool has_scale;
 			bool has_additive_base;
 			bool needs_conversion;
 
+			rotation_format8 rotation_format;
+			vector_format8 translation_format;
+			vector_format8 scale_format;
+			compression_level8 compression_level;
+
 			const BoneStreams* raw_bone_streams;
 
 			rtm::qvvf* additive_local_pose;			// 1 per transform
@@ -110,25 +116,30 @@ namespace acl
 			uint16_t padding0;	// unused
 			uint32_t padding1;	// unused
 
-			QuantizationContext(IAllocator& allocator_, ClipContext& clip_, const ClipContext& raw_clip_, const ClipContext& additive_base_clip_, const CompressionSettings& settings_, const RigidSkeleton& skeleton_)
+			quantization_context(iallocator& allocator_, clip_context& clip_, const clip_context& raw_clip_, const clip_context& additive_base_clip_, const compression_settings& settings_)
 				: allocator(allocator_)
 				, clip(clip_)
 				, raw_clip(raw_clip_)
 				, additive_base_clip(additive_base_clip_)
 				, segment(nullptr)
 				, bone_streams(nullptr)
+				, metadata(clip_.metadata)
 				, num_bones(clip_.num_bones)
-				, skeleton(skeleton_)
-				, settings(settings_)
-				, bit_rate_database(allocator_, settings_, clip_.segments->bone_streams, raw_clip_.segments->bone_streams, clip_.num_bones, clip_.segments->num_samples)
+				, error_metric(settings_.error_metric)
+				, bit_rate_database(allocator_, settings_.rotation_format, settings_.translation_format, settings_.scale_format, clip_.segments->bone_streams, raw_clip_.segments->bone_streams, clip_.num_bones, clip_.segments->num_samples)
 				, local_query()
 				, object_query(allocator_)
 				, num_samples(~0U)
 				, segment_sample_start_index(~0U)
 				, sample_rate(clip_.sample_rate)
 				, clip_duration(clip_.duration)
+				, error_threshold(0.0F)
 				, has_scale(clip_.has_scale)
 				, has_additive_base(clip_.has_additive_base)
+				, rotation_format(settings_.rotation_format)
+				, translation_format(settings_.translation_format)
+				, scale_format(settings_.scale_format)
+				, compression_level(settings_.level)
 				, raw_bone_streams(raw_clip_.segments[0].bone_streams)
 				, num_bones_in_chain(0)
 			{
@@ -155,13 +166,13 @@ namespace acl
 
 				for (uint16_t transform_index = 0; transform_index < num_bones; ++transform_index)
 				{
-					const RigidBone& bone = skeleton_.get_bone(transform_index);
-					parent_transform_indices[transform_index] = bone.parent_index;
+					const transform_metadata& metadata_ = clip_.metadata[transform_index];
+					parent_transform_indices[transform_index] = metadata_.parent_index;
 					self_transform_indices[transform_index] = transform_index;
 				}
 			}
 
-			~QuantizationContext()
+			~quantization_context()
 			{
 				deallocate_type_array(allocator, additive_local_pose, num_bones);
 				deallocate_type_array(allocator, raw_local_pose, num_bones);
@@ -187,7 +198,7 @@ namespace acl
 				bit_rate_database.set_segment(segment_.bone_streams, segment_.num_bones, segment_.num_samples);
 
 				// Cache every raw local/object transforms and the base local transforms since they never change
-				const itransform_error_metric* error_metric = settings.error_metric;
+				const itransform_error_metric* error_metric_ = error_metric;
 				const size_t sample_transform_size = metric_transform_size * num_bones;
 
 				const auto convert_transforms_impl = std::mem_fn(has_scale ? &itransform_error_metric::convert_transforms : &itransform_error_metric::convert_transforms_no_scale);
@@ -228,7 +239,7 @@ namespace acl
 					uint8_t* sample_raw_local_transforms = raw_local_transforms + (sample_index * sample_transform_size);
 
 					if (needs_conversion)
-						convert_transforms_impl(error_metric, convert_transforms_args_raw, sample_raw_local_transforms);
+						convert_transforms_impl(error_metric_, convert_transforms_args_raw, sample_raw_local_transforms);
 					else
 						std::memcpy(sample_raw_local_transforms, raw_local_pose, sample_transform_size);
 
@@ -241,31 +252,31 @@ namespace acl
 						uint8_t* sample_base_local_transforms = base_local_transforms + (sample_index * sample_transform_size);
 
 						if (needs_conversion)
-							convert_transforms_impl(error_metric, convert_transforms_args_base, sample_base_local_transforms);
+							convert_transforms_impl(error_metric_, convert_transforms_args_base, sample_base_local_transforms);
 						else
 							std::memcpy(sample_base_local_transforms, additive_local_pose, sample_transform_size);
 
 						apply_additive_to_base_args_raw.local_transforms = sample_raw_local_transforms;
 						apply_additive_to_base_args_raw.base_transforms = sample_base_local_transforms;
-						apply_additive_to_base_impl(error_metric, apply_additive_to_base_args_raw, sample_raw_local_transforms);
+						apply_additive_to_base_impl(error_metric_, apply_additive_to_base_args_raw, sample_raw_local_transforms);
 					}
 
 					local_to_object_space_args_raw.local_transforms = sample_raw_local_transforms;
 
 					uint8_t* sample_raw_object_transforms = raw_object_transforms + (sample_index * sample_transform_size);
-					local_to_object_space_impl(error_metric, local_to_object_space_args_raw, sample_raw_object_transforms);
+					local_to_object_space_impl(error_metric_, local_to_object_space_args_raw, sample_raw_object_transforms);
 				}
 			}
 
 			bool is_valid() const { return segment != nullptr; }
 
-			QuantizationContext(const QuantizationContext&) = delete;
-			QuantizationContext(QuantizationContext&&) = delete;
-			QuantizationContext& operator=(const QuantizationContext&) = delete;
-			QuantizationContext& operator=(QuantizationContext&&) = delete;
+			quantization_context(const quantization_context&) = delete;
+			quantization_context(quantization_context&&) = delete;
+			quantization_context& operator=(const quantization_context&) = delete;
+			quantization_context& operator=(quantization_context&&) = delete;
 		};
 
-		inline void quantize_fixed_rotation_stream(IAllocator& allocator, const RotationTrackStream& raw_stream, rotation_format8 rotation_format, RotationTrackStream& out_quantized_stream)
+		inline void quantize_fixed_rotation_stream(iallocator& allocator, const RotationTrackStream& raw_stream, rotation_format8 rotation_format, RotationTrackStream& out_quantized_stream)
 		{
 			// We expect all our samples to have the same width of sizeof(rtm::vector4f)
 			ACL_ASSERT(raw_stream.get_sample_size() == sizeof(rtm::vector4f), "Unexpected rotation sample size. %u != %zu", raw_stream.get_sample_size(), sizeof(rtm::vector4f));
@@ -298,7 +309,7 @@ namespace acl
 			out_quantized_stream = std::move(quantized_stream);
 		}
 
-		inline void quantize_fixed_rotation_stream(QuantizationContext& context, uint16_t bone_index, rotation_format8 rotation_format)
+		inline void quantize_fixed_rotation_stream(quantization_context& context, uint16_t bone_index, rotation_format8 rotation_format)
 		{
 			ACL_ASSERT(bone_index < context.num_bones, "Invalid bone index: %u", bone_index);
 
@@ -311,7 +322,7 @@ namespace acl
 			quantize_fixed_rotation_stream(context.allocator, bone_stream.rotations, rotation_format, bone_stream.rotations);
 		}
 
-		inline void quantize_variable_rotation_stream(QuantizationContext& context, const RotationTrackStream& raw_clip_stream, const RotationTrackStream& raw_segment_stream, const TrackStreamRange& clip_range, uint8_t bit_rate, RotationTrackStream& out_quantized_stream)
+		inline void quantize_variable_rotation_stream(quantization_context& context, const RotationTrackStream& raw_clip_stream, const RotationTrackStream& raw_segment_stream, const TrackStreamRange& clip_range, uint8_t bit_rate, RotationTrackStream& out_quantized_stream)
 		{
 			// We expect all our samples to have the same width of sizeof(rtm::vector4f)
 			ACL_ASSERT(raw_segment_stream.get_sample_size() == sizeof(rtm::vector4f), "Unexpected rotation sample size. %u != %zu", raw_segment_stream.get_sample_size(), sizeof(rtm::vector4f));
@@ -356,7 +367,7 @@ namespace acl
 			out_quantized_stream = std::move(quantized_stream);
 		}
 
-		inline void quantize_variable_rotation_stream(QuantizationContext& context, uint16_t bone_index, uint8_t bit_rate)
+		inline void quantize_variable_rotation_stream(quantization_context& context, uint16_t bone_index, uint8_t bit_rate)
 		{
 			ACL_ASSERT(bone_index < context.num_bones, "Invalid bone index: %u", bone_index);
 
@@ -377,7 +388,7 @@ namespace acl
 				quantize_variable_rotation_stream(context, raw_bone_stream.rotations, bone_stream.rotations, bone_range, bit_rate, bone_stream.rotations);
 		}
 
-		inline void quantize_fixed_translation_stream(IAllocator& allocator, const TranslationTrackStream& raw_stream, vector_format8 translation_format, TranslationTrackStream& out_quantized_stream)
+		inline void quantize_fixed_translation_stream(iallocator& allocator, const TranslationTrackStream& raw_stream, vector_format8 translation_format, TranslationTrackStream& out_quantized_stream)
 		{
 			// We expect all our samples to have the same width of sizeof(rtm::vector4f)
 			ACL_ASSERT(raw_stream.get_sample_size() == sizeof(rtm::vector4f), "Unexpected translation sample size. %u != %zu", raw_stream.get_sample_size(), sizeof(rtm::vector4f));
@@ -408,7 +419,7 @@ namespace acl
 			out_quantized_stream = std::move(quantized_stream);
 		}
 
-		inline void quantize_fixed_translation_stream(QuantizationContext& context, uint16_t bone_index, vector_format8 translation_format)
+		inline void quantize_fixed_translation_stream(quantization_context& context, uint16_t bone_index, vector_format8 translation_format)
 		{
 			ACL_ASSERT(bone_index < context.num_bones, "Invalid bone index: %u", bone_index);
 
@@ -424,7 +435,7 @@ namespace acl
 			quantize_fixed_translation_stream(context.allocator, bone_stream.translations, format, bone_stream.translations);
 		}
 
-		inline void quantize_variable_translation_stream(QuantizationContext& context, const TranslationTrackStream& raw_clip_stream, const TranslationTrackStream& raw_segment_stream, const TrackStreamRange& clip_range, uint8_t bit_rate, TranslationTrackStream& out_quantized_stream)
+		inline void quantize_variable_translation_stream(quantization_context& context, const TranslationTrackStream& raw_clip_stream, const TranslationTrackStream& raw_segment_stream, const TrackStreamRange& clip_range, uint8_t bit_rate, TranslationTrackStream& out_quantized_stream)
 		{
 			// We expect all our samples to have the same width of sizeof(rtm::vector4f)
 			ACL_ASSERT(raw_segment_stream.get_sample_size() == sizeof(rtm::vector4f), "Unexpected translation sample size. %u != %zu", raw_segment_stream.get_sample_size(), sizeof(rtm::vector4f));
@@ -467,7 +478,7 @@ namespace acl
 			out_quantized_stream = std::move(quantized_stream);
 		}
 
-		inline void quantize_variable_translation_stream(QuantizationContext& context, uint16_t bone_index, uint8_t bit_rate)
+		inline void quantize_variable_translation_stream(quantization_context& context, uint16_t bone_index, uint8_t bit_rate)
 		{
 			ACL_ASSERT(bone_index < context.num_bones, "Invalid bone index: %u", bone_index);
 
@@ -487,7 +498,7 @@ namespace acl
 				quantize_variable_translation_stream(context, raw_bone_stream.translations, bone_stream.translations, bone_range, bit_rate, bone_stream.translations);
 		}
 
-		inline void quantize_fixed_scale_stream(IAllocator& allocator, const ScaleTrackStream& raw_stream, vector_format8 scale_format, ScaleTrackStream& out_quantized_stream)
+		inline void quantize_fixed_scale_stream(iallocator& allocator, const ScaleTrackStream& raw_stream, vector_format8 scale_format, ScaleTrackStream& out_quantized_stream)
 		{
 			// We expect all our samples to have the same width of sizeof(rtm::vector4f)
 			ACL_ASSERT(raw_stream.get_sample_size() == sizeof(rtm::vector4f), "Unexpected scale sample size. %u != %zu", raw_stream.get_sample_size(), sizeof(rtm::vector4f));
@@ -518,7 +529,7 @@ namespace acl
 			out_quantized_stream = std::move(quantized_stream);
 		}
 
-		inline void quantize_fixed_scale_stream(QuantizationContext& context, uint16_t bone_index, vector_format8 scale_format)
+		inline void quantize_fixed_scale_stream(quantization_context& context, uint16_t bone_index, vector_format8 scale_format)
 		{
 			ACL_ASSERT(bone_index < context.num_bones, "Invalid bone index: %u", bone_index);
 
@@ -534,7 +545,7 @@ namespace acl
 			quantize_fixed_scale_stream(context.allocator, bone_stream.scales, format, bone_stream.scales);
 		}
 
-		inline void quantize_variable_scale_stream(QuantizationContext& context, const ScaleTrackStream& raw_clip_stream, const ScaleTrackStream& raw_segment_stream, const TrackStreamRange& clip_range, uint8_t bit_rate, ScaleTrackStream& out_quantized_stream)
+		inline void quantize_variable_scale_stream(quantization_context& context, const ScaleTrackStream& raw_clip_stream, const ScaleTrackStream& raw_segment_stream, const TrackStreamRange& clip_range, uint8_t bit_rate, ScaleTrackStream& out_quantized_stream)
 		{
 			// We expect all our samples to have the same width of sizeof(rtm::vector4f)
 			ACL_ASSERT(raw_segment_stream.get_sample_size() == sizeof(rtm::vector4f), "Unexpected scale sample size. %u != %zu", raw_segment_stream.get_sample_size(), sizeof(rtm::vector4f));
@@ -577,7 +588,7 @@ namespace acl
 			out_quantized_stream = std::move(quantized_stream);
 		}
 
-		inline void quantize_variable_scale_stream(QuantizationContext& context, uint16_t bone_index, uint8_t bit_rate)
+		inline void quantize_variable_scale_stream(quantization_context& context, uint16_t bone_index, uint8_t bit_rate)
 		{
 			ACL_ASSERT(bone_index < context.num_bones, "Invalid bone index: %u", bone_index);
 
@@ -599,31 +610,31 @@ namespace acl
 
 		enum class error_scan_stop_condition { until_error_too_high, until_end_of_segment };
 
-		inline float calculate_max_error_at_bit_rate_local(QuantizationContext& context, uint16_t target_bone_index, error_scan_stop_condition stop_condition)
+		inline float calculate_max_error_at_bit_rate_local(quantization_context& context, uint32_t target_bone_index, error_scan_stop_condition stop_condition)
 		{
-			const CompressionSettings& settings = context.settings;
-			const itransform_error_metric* error_metric = settings.error_metric;
+			const itransform_error_metric* error_metric = context.error_metric;
 			const bool needs_conversion = context.needs_conversion;
 			const bool has_additive_base = context.has_additive_base;
-			const RigidBone& target_bone = context.skeleton.get_bone(target_bone_index);
+			const transform_metadata& target_bone = context.metadata[target_bone_index];
 			const uint32_t num_transforms = context.num_bones;
 			const size_t sample_transform_size = context.metric_transform_size * context.num_bones;
 			const float sample_rate = context.sample_rate;
 			const float clip_duration = context.clip_duration;
-			const rtm::scalarf error_threshold = rtm::scalar_set(settings.error_threshold);
+			const rtm::scalarf error_threshold = rtm::scalar_set(context.error_threshold);
+			const uint16_t target_bone_index_ = (uint16_t)target_bone_index;
 
 			const auto convert_transforms_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::convert_transforms : &itransform_error_metric::convert_transforms_no_scale);
 			const auto apply_additive_to_base_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::apply_additive_to_base : &itransform_error_metric::apply_additive_to_base_no_scale);
 			const auto calculate_error_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::calculate_error : &itransform_error_metric::calculate_error_no_scale);
 
 			itransform_error_metric::convert_transforms_args convert_transforms_args_lossy;
-			convert_transforms_args_lossy.dirty_transform_indices = &target_bone_index;
+			convert_transforms_args_lossy.dirty_transform_indices = &target_bone_index_;
 			convert_transforms_args_lossy.num_dirty_transforms = 1;
 			convert_transforms_args_lossy.transforms = context.lossy_local_pose;
 			convert_transforms_args_lossy.num_transforms = num_transforms;
 
 			itransform_error_metric::apply_additive_to_base_args apply_additive_to_base_args_lossy;
-			apply_additive_to_base_args_lossy.dirty_transform_indices = &target_bone_index;
+			apply_additive_to_base_args_lossy.dirty_transform_indices = &target_bone_index_;
 			apply_additive_to_base_args_lossy.num_dirty_transforms = 1;
 			apply_additive_to_base_args_lossy.local_transforms = needs_conversion ? (const void*)context.local_transforms_converted : (const void*)context.lossy_local_pose;
 			apply_additive_to_base_args_lossy.base_transforms = nullptr;
@@ -632,7 +643,7 @@ namespace acl
 			itransform_error_metric::calculate_error_args calculate_error_args;
 			calculate_error_args.transform0 = nullptr;
 			calculate_error_args.transform1 = needs_conversion ? (const void*)(context.local_transforms_converted + (context.metric_transform_size * target_bone_index)) : (const void*)(context.lossy_local_pose + target_bone_index);
-			calculate_error_args.construct_sphere_shell(target_bone.vertex_distance);
+			calculate_error_args.construct_sphere_shell(target_bone.shell_distance);
 
 			const uint8_t* raw_transform = context.raw_local_transforms + (target_bone_index * context.metric_transform_size);
 			const uint8_t* base_transforms = context.base_local_transforms;
@@ -674,17 +685,16 @@ namespace acl
 			return rtm::scalar_cast(max_error);
 		}
 
-		inline float calculate_max_error_at_bit_rate_object(QuantizationContext& context, uint16_t target_bone_index, error_scan_stop_condition stop_condition)
+		inline float calculate_max_error_at_bit_rate_object(quantization_context& context, uint32_t target_bone_index, error_scan_stop_condition stop_condition)
 		{
-			const CompressionSettings& settings = context.settings;
-			const itransform_error_metric* error_metric = settings.error_metric;
+			const itransform_error_metric* error_metric = context.error_metric;
 			const bool needs_conversion = context.needs_conversion;
 			const bool has_additive_base = context.has_additive_base;
-			const RigidBone& target_bone = context.skeleton.get_bone(target_bone_index);
+			const transform_metadata& target_bone = context.metadata[target_bone_index];
 			const size_t sample_transform_size = context.metric_transform_size * context.num_bones;
 			const float sample_rate = context.sample_rate;
 			const float clip_duration = context.clip_duration;
-			const rtm::scalarf error_threshold = rtm::scalar_set(settings.error_threshold);
+			const rtm::scalarf error_threshold = rtm::scalar_set(context.error_threshold);
 
 			const auto convert_transforms_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::convert_transforms : &itransform_error_metric::convert_transforms_no_scale);
 			const auto apply_additive_to_base_impl = std::mem_fn(context.has_scale ? &itransform_error_metric::apply_additive_to_base : &itransform_error_metric::apply_additive_to_base_no_scale);
@@ -714,7 +724,7 @@ namespace acl
 			itransform_error_metric::calculate_error_args calculate_error_args;
 			calculate_error_args.transform0 = nullptr;
 			calculate_error_args.transform1 = context.lossy_object_pose + (target_bone_index * context.metric_transform_size);
-			calculate_error_args.construct_sphere_shell(target_bone.vertex_distance);
+			calculate_error_args.construct_sphere_shell(target_bone.shell_distance);
 
 			const uint8_t* raw_transform = context.raw_object_transforms + (target_bone_index * context.metric_transform_size);
 			const uint8_t* base_transforms = context.base_local_transforms;
@@ -758,17 +768,19 @@ namespace acl
 			return rtm::scalar_cast(max_error);
 		}
 
-		inline void calculate_local_space_bit_rates(QuantizationContext& context)
+		inline void calculate_local_space_bit_rates(quantization_context& context)
 		{
 			// To minimize the bit rate, we first start by trying every permutation in local space
 			// until our error is acceptable.
 			// We try permutations from the lowest memory footprint to the highest.
 
-			const CompressionSettings& settings = context.settings;
-			const float error_threshold = settings.error_threshold;
-
-			for (uint16_t bone_index = 0; bone_index < context.num_bones; ++bone_index)
+			const uint32_t num_bones = context.num_bones;
+			for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index)
 			{
+				// Update our error threshold
+				const float error_threshold = context.metadata[bone_index].precision;
+				context.error_threshold = error_threshold;
+
 				// Bit rates at this point are one of three value:
 				// 0: if the segment track is normalized, it can be constant within the segment
 				// 1: if the segment track isn't normalized, it starts at the lowest bit rate
@@ -927,30 +939,31 @@ namespace acl
 			}
 		}
 
-		constexpr uint8_t increment_and_clamp_bit_rate(uint8_t bit_rate, uint8_t increment)
+		constexpr uint32_t increment_and_clamp_bit_rate(uint32_t bit_rate, uint32_t increment)
 		{
-			return bit_rate >= k_highest_bit_rate ? bit_rate : std::min<uint8_t>(bit_rate + increment, k_highest_bit_rate);
+			return bit_rate >= k_highest_bit_rate ? bit_rate : std::min<uint32_t>(bit_rate + increment, k_highest_bit_rate);
 		}
 
-		inline float increase_bone_bit_rate(QuantizationContext& context, uint16_t bone_index, uint8_t num_increments, float old_error, BoneBitRate& out_best_bit_rates)
+		inline float increase_bone_bit_rate(quantization_context& context, uint32_t bone_index, uint32_t num_increments, float old_error, BoneBitRate& out_best_bit_rates)
 		{
 			const BoneBitRate bone_bit_rates = context.bit_rate_per_bone[bone_index];
-			const uint8_t num_scale_increments = context.has_scale ? num_increments : 0;
+			const uint32_t num_scale_increments = context.has_scale ? num_increments : 0;
+			const uint16_t bone_index_ = static_cast<uint16_t>(bone_index);
 
 			BoneBitRate best_bit_rates = bone_bit_rates;
 			float best_error = old_error;
 
-			for (uint8_t rotation_increment = 0; rotation_increment <= num_increments; ++rotation_increment)
+			for (uint32_t rotation_increment = 0; rotation_increment <= num_increments; ++rotation_increment)
 			{
-				const uint8_t rotation_bit_rate = increment_and_clamp_bit_rate(bone_bit_rates.rotation, rotation_increment);
+				const uint32_t rotation_bit_rate = increment_and_clamp_bit_rate(bone_bit_rates.rotation, rotation_increment);
 
-				for (uint8_t translation_increment = 0; translation_increment <= num_increments; ++translation_increment)
+				for (uint32_t translation_increment = 0; translation_increment <= num_increments; ++translation_increment)
 				{
-					const uint8_t translation_bit_rate = increment_and_clamp_bit_rate(bone_bit_rates.translation, translation_increment);
+					const uint32_t translation_bit_rate = increment_and_clamp_bit_rate(bone_bit_rates.translation, translation_increment);
 
-					for (uint8_t scale_increment = 0; scale_increment <= num_scale_increments; ++scale_increment)
+					for (uint32_t scale_increment = 0; scale_increment <= num_scale_increments; ++scale_increment)
 					{
-						const uint8_t scale_bit_rate = increment_and_clamp_bit_rate(bone_bit_rates.scale, scale_increment);
+						const uint32_t scale_bit_rate = increment_and_clamp_bit_rate(bone_bit_rates.scale, scale_increment);
 
 						if (rotation_increment + translation_increment + scale_increment != num_increments)
 						{
@@ -960,8 +973,8 @@ namespace acl
 								continue;
 						}
 
-						context.bit_rate_per_bone[bone_index] = BoneBitRate{ rotation_bit_rate, translation_bit_rate, scale_bit_rate };
-						const float error = calculate_max_error_at_bit_rate_object(context, bone_index, error_scan_stop_condition::until_error_too_high);
+						context.bit_rate_per_bone[bone_index] = BoneBitRate{ (uint8_t)rotation_bit_rate, (uint8_t)translation_bit_rate, (uint8_t)scale_bit_rate };
+						const float error = calculate_max_error_at_bit_rate_object(context, bone_index_, error_scan_stop_condition::until_error_too_high);
 
 						if (error < best_error)
 						{
@@ -987,10 +1000,9 @@ namespace acl
 			return best_error;
 		}
 
-		inline float calculate_bone_permutation_error(QuantizationContext& context, BoneBitRate* permutation_bit_rates, uint8_t* bone_chain_permutation, uint16_t bone_index, BoneBitRate* best_bit_rates, float old_error)
+		inline float calculate_bone_permutation_error(quantization_context& context, BoneBitRate* permutation_bit_rates, uint8_t* bone_chain_permutation, uint32_t bone_index, BoneBitRate* best_bit_rates, float old_error)
 		{
-			const CompressionSettings& settings = context.settings;
-
+			const float error_threshold = context.error_threshold;
 			float best_error = old_error;
 
 			do
@@ -999,12 +1011,13 @@ namespace acl
 				std::memcpy(permutation_bit_rates, context.bit_rate_per_bone, sizeof(BoneBitRate) * context.num_bones);
 
 				bool is_permutation_valid = false;
-				for (uint16_t chain_link_index = 0; chain_link_index < context.num_bones_in_chain; ++chain_link_index)
+				const uint32_t num_bones_in_chain = context.num_bones_in_chain;
+				for (uint32_t chain_link_index = 0; chain_link_index < num_bones_in_chain; ++chain_link_index)
 				{
 					if (bone_chain_permutation[chain_link_index] != 0)
 					{
 						// Increase bit rate
-						const uint16_t chain_bone_index = context.chain_bone_indices[chain_link_index];
+						const uint32_t chain_bone_index = context.chain_bone_indices[chain_link_index];
 						BoneBitRate chain_bone_best_bit_rates;
 						increase_bone_bit_rate(context, chain_bone_index, bone_chain_permutation[chain_link_index], old_error, chain_bone_best_bit_rates);
 						is_permutation_valid |= chain_bone_best_bit_rates.rotation != permutation_bit_rates[chain_bone_index].rotation;
@@ -1015,7 +1028,7 @@ namespace acl
 				}
 
 				if (!is_permutation_valid)
-					continue;
+					continue;	// Couldn't increase any bit rate, skip this permutation
 
 				// Measure error
 				std::swap(context.bit_rate_per_bone, permutation_bit_rates);
@@ -1027,7 +1040,7 @@ namespace acl
 					best_error = permutation_error;
 					std::memcpy(best_bit_rates, permutation_bit_rates, sizeof(BoneBitRate) * context.num_bones);
 
-					if (permutation_error < settings.error_threshold)
+					if (permutation_error < error_threshold)
 						break;
 				}
 			} while (std::next_permutation(bone_chain_permutation, bone_chain_permutation + context.num_bones_in_chain));
@@ -1035,11 +1048,11 @@ namespace acl
 			return best_error;
 		}
 
-		inline uint16_t calculate_bone_chain_indices(const RigidSkeleton& skeleton, uint16_t bone_index, uint16_t* out_chain_bone_indices)
+		inline uint32_t calculate_bone_chain_indices(const clip_context& clip, uint32_t bone_index, uint16_t* out_chain_bone_indices)
 		{
-			const BoneChain bone_chain = skeleton.get_bone_chain(bone_index);
+			const BoneChain bone_chain = clip.get_bone_chain(bone_index);
 
-			uint16_t num_bones_in_chain = 0;
+			uint32_t num_bones_in_chain = 0;
 			for (uint16_t chain_bone_index : bone_chain)
 				out_chain_bone_indices[num_bones_in_chain++] = chain_bone_index;
 
@@ -1052,7 +1065,8 @@ namespace acl
 			const bool is_translation_variable = is_vector_format_variable(translation_format);
 			const bool is_scale_variable = segment_context_has_scale(segment) && is_vector_format_variable(scale_format);
 
-			for (uint16_t bone_index = 0; bone_index < segment.num_bones; ++bone_index)
+			const uint32_t num_bones = segment.num_bones;
+			for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index)
 			{
 				BoneBitRate& bone_bit_rate = out_bit_rate_per_bone[bone_index];
 
@@ -1076,15 +1090,13 @@ namespace acl
 			}
 		}
 
-		inline void quantize_all_streams(QuantizationContext& context)
+		inline void quantize_all_streams(quantization_context& context)
 		{
-			ACL_ASSERT(context.is_valid(), "QuantizationContext isn't valid");
+			ACL_ASSERT(context.is_valid(), "quantization_context isn't valid");
 
-			const CompressionSettings& settings = context.settings;
-
-			const bool is_rotation_variable = is_rotation_format_variable(settings.rotation_format);
-			const bool is_translation_variable = is_vector_format_variable(settings.translation_format);
-			const bool is_scale_variable = is_vector_format_variable(settings.scale_format);
+			const bool is_rotation_variable = is_rotation_format_variable(context.rotation_format);
+			const bool is_translation_variable = is_vector_format_variable(context.translation_format);
+			const bool is_scale_variable = is_vector_format_variable(context.scale_format);
 
 			for (uint16_t bone_index = 0; bone_index < context.num_bones; ++bone_index)
 			{
@@ -1093,30 +1105,28 @@ namespace acl
 				if (is_rotation_variable)
 					quantize_variable_rotation_stream(context, bone_index, bone_bit_rate.rotation);
 				else
-					quantize_fixed_rotation_stream(context, bone_index, settings.rotation_format);
+					quantize_fixed_rotation_stream(context, bone_index, context.rotation_format);
 
 				if (is_translation_variable)
 					quantize_variable_translation_stream(context, bone_index, bone_bit_rate.translation);
 				else
-					quantize_fixed_translation_stream(context, bone_index, settings.translation_format);
+					quantize_fixed_translation_stream(context, bone_index, context.translation_format);
 
 				if (context.has_scale)
 				{
 					if (is_scale_variable)
 						quantize_variable_scale_stream(context, bone_index, bone_bit_rate.scale);
 					else
-						quantize_fixed_scale_stream(context, bone_index, settings.scale_format);
+						quantize_fixed_scale_stream(context, bone_index, context.scale_format);
 				}
 			}
 		}
 
-		inline void find_optimal_bit_rates(QuantizationContext& context)
+		inline void find_optimal_bit_rates(quantization_context& context)
 		{
-			ACL_ASSERT(context.is_valid(), "QuantizationContext isn't valid");
+			ACL_ASSERT(context.is_valid(), "quantization_context isn't valid");
 
-			const CompressionSettings& settings = context.settings;
-
-			initialize_bone_bit_rates(*context.segment, settings.rotation_format, settings.translation_format, settings.scale_format, context.bit_rate_per_bone);
+			initialize_bone_bit_rates(*context.segment, context.rotation_format, context.translation_format, context.scale_format, context.bit_rate_per_bone);
 
 			// First iterate over all bones and find the optimal bit rate for each track using the local space error.
 			// We use the local space error to prime the algorithm. If each parent bone has infinite precision,
@@ -1171,18 +1181,23 @@ namespace acl
 			BoneBitRate* best_bit_rates = allocate_type_array<BoneBitRate>(context.allocator, context.num_bones);
 			std::memcpy(best_bit_rates, context.bit_rate_per_bone, sizeof(BoneBitRate) * context.num_bones);
 
-			for (uint16_t bone_index = 0; bone_index < context.num_bones; ++bone_index)
+			const uint32_t num_bones = context.num_bones;
+			for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index)
 			{
-				const uint16_t num_bones_in_chain = calculate_bone_chain_indices(context.skeleton, bone_index, context.chain_bone_indices);
-				context.num_bones_in_chain = num_bones_in_chain;
+				// Update our error threshold
+				const float error_threshold = context.metadata[bone_index].precision;
+				context.error_threshold = error_threshold;
+
+				const uint32_t num_bones_in_chain = calculate_bone_chain_indices(context.clip, bone_index, context.chain_bone_indices);
+				context.num_bones_in_chain = (uint16_t)num_bones_in_chain;
 
 				float error = calculate_max_error_at_bit_rate_object(context, bone_index, error_scan_stop_condition::until_error_too_high);
-				if (error < settings.error_threshold)
+				if (error < error_threshold)
 					continue;
 
 				const float initial_error = error;
 
-				while (error >= settings.error_threshold)
+				while (error >= error_threshold)
 				{
 					// Generate permutations for up to 3 bit rate increments
 					// Perform an exhaustive search of the permutations and pick the best result
@@ -1191,83 +1206,83 @@ namespace acl
 					float best_error = error;
 
 					// The first permutation increases the bit rate of a single track/bone
-					std::fill(bone_chain_permutation, bone_chain_permutation + context.num_bones, uint8_t(0));
+					std::fill(bone_chain_permutation, bone_chain_permutation + num_bones, uint8_t(0));
 					bone_chain_permutation[num_bones_in_chain - 1] = 1;
 					error = calculate_bone_permutation_error(context, permutation_bit_rates, bone_chain_permutation, bone_index, best_permutation_bit_rates, original_error);
 					if (error < best_error)
 					{
 						best_error = error;
-						std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * context.num_bones);
+						std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * num_bones);
 
-						if (error < settings.error_threshold)
+						if (error < error_threshold)
 							break;
 					}
 
-					if (settings.level >= compression_level8::high)
+					if (context.compression_level >= compression_level8::high)
 					{
 						// The second permutation increases the bit rate of 2 track/bones
-						std::fill(bone_chain_permutation, bone_chain_permutation + context.num_bones, uint8_t(0));
+						std::fill(bone_chain_permutation, bone_chain_permutation + num_bones, uint8_t(0));
 						bone_chain_permutation[num_bones_in_chain - 1] = 2;
 						error = calculate_bone_permutation_error(context, permutation_bit_rates, bone_chain_permutation, bone_index, best_permutation_bit_rates, original_error);
 						if (error < best_error)
 						{
 							best_error = error;
-							std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * context.num_bones);
+							std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * num_bones);
 
-							if (error < settings.error_threshold)
+							if (error < error_threshold)
 								break;
 						}
 
 						if (num_bones_in_chain > 1)
 						{
-							std::fill(bone_chain_permutation, bone_chain_permutation + context.num_bones, uint8_t(0));
+							std::fill(bone_chain_permutation, bone_chain_permutation + num_bones, uint8_t(0));
 							bone_chain_permutation[num_bones_in_chain - 2] = 1;
 							bone_chain_permutation[num_bones_in_chain - 1] = 1;
 							error = calculate_bone_permutation_error(context, permutation_bit_rates, bone_chain_permutation, bone_index, best_permutation_bit_rates, original_error);
 							if (error < best_error)
 							{
 								best_error = error;
-								std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * context.num_bones);
+								std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * num_bones);
 
-								if (error < settings.error_threshold)
+								if (error < error_threshold)
 									break;
 							}
 						}
 					}
 
-					if (settings.level >= compression_level8::highest)
+					if (context.compression_level >= compression_level8::highest)
 					{
 						// The third permutation increases the bit rate of 3 track/bones
-						std::fill(bone_chain_permutation, bone_chain_permutation + context.num_bones, uint8_t(0));
+						std::fill(bone_chain_permutation, bone_chain_permutation + num_bones, uint8_t(0));
 						bone_chain_permutation[num_bones_in_chain - 1] = 3;
 						error = calculate_bone_permutation_error(context, permutation_bit_rates, bone_chain_permutation, bone_index, best_permutation_bit_rates, original_error);
 						if (error < best_error)
 						{
 							best_error = error;
-							std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * context.num_bones);
+							std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * num_bones);
 
-							if (error < settings.error_threshold)
+							if (error < error_threshold)
 								break;
 						}
 
 						if (num_bones_in_chain > 1)
 						{
-							std::fill(bone_chain_permutation, bone_chain_permutation + context.num_bones, uint8_t(0));
+							std::fill(bone_chain_permutation, bone_chain_permutation + num_bones, uint8_t(0));
 							bone_chain_permutation[num_bones_in_chain - 2] = 2;
 							bone_chain_permutation[num_bones_in_chain - 1] = 1;
 							error = calculate_bone_permutation_error(context, permutation_bit_rates, bone_chain_permutation, bone_index, best_permutation_bit_rates, original_error);
 							if (error < best_error)
 							{
 								best_error = error;
-								std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * context.num_bones);
+								std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * num_bones);
 
-								if (error < settings.error_threshold)
+								if (error < error_threshold)
 									break;
 							}
 
 							if (num_bones_in_chain > 2)
 							{
-								std::fill(bone_chain_permutation, bone_chain_permutation + context.num_bones, uint8_t(0));
+								std::fill(bone_chain_permutation, bone_chain_permutation + num_bones, uint8_t(0));
 								bone_chain_permutation[num_bones_in_chain - 3] = 1;
 								bone_chain_permutation[num_bones_in_chain - 2] = 1;
 								bone_chain_permutation[num_bones_in_chain - 1] = 1;
@@ -1275,9 +1290,9 @@ namespace acl
 								if (error < best_error)
 								{
 									best_error = error;
-									std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * context.num_bones);
+									std::memcpy(best_bit_rates, best_permutation_bit_rates, sizeof(BoneBitRate) * num_bones);
 
-									if (error < settings.error_threshold)
+									if (error < error_threshold)
 										break;
 								}
 							}
@@ -1307,7 +1322,7 @@ namespace acl
 						}
 #endif
 
-						std::memcpy(context.bit_rate_per_bone, best_bit_rates, sizeof(BoneBitRate) * context.num_bones);
+						std::memcpy(context.bit_rate_per_bone, best_bit_rates, sizeof(BoneBitRate) * num_bones);
 					}
 				}
 
@@ -1330,19 +1345,19 @@ namespace acl
 					}
 #endif
 
-					std::memcpy(context.bit_rate_per_bone, best_bit_rates, sizeof(BoneBitRate) * context.num_bones);
+					std::memcpy(context.bit_rate_per_bone, best_bit_rates, sizeof(BoneBitRate) * num_bones);
 				}
 
 				// Our error remains too high, this should be rare.
 				// Attempt to increase the bit rate as much as we can while still back tracking if it doesn't help.
 				error = calculate_max_error_at_bit_rate_object(context, bone_index, error_scan_stop_condition::until_end_of_segment);
-				while (error >= settings.error_threshold)
+				while (error >= error_threshold)
 				{
 					// From child to parent, increase the bit rate indiscriminately
-					uint16_t num_maxed_out = 0;
-					for (int16_t chain_link_index = num_bones_in_chain - 1; chain_link_index >= 0; --chain_link_index)
+					uint32_t num_maxed_out = 0;
+					for (int32_t chain_link_index = num_bones_in_chain - 1; chain_link_index >= 0; --chain_link_index)
 					{
-						const uint16_t chain_bone_index = context.chain_bone_indices[chain_link_index];
+						const uint32_t chain_bone_index = context.chain_bone_indices[chain_link_index];
 
 						// Work with a copy. We'll increase the bit rate as much as we can and retain the values
 						// that yield the smallest error BUT increasing the bit rate does NOT always means
@@ -1354,7 +1369,7 @@ namespace acl
 						BoneBitRate best_bone_bit_rate = bone_bit_rate;
 						float best_bit_rate_error = error;
 
-						while (error >= settings.error_threshold)
+						while (error >= error_threshold)
 						{
 							static_assert(offsetof(BoneBitRate, rotation) == 0 && offsetof(BoneBitRate, scale) == sizeof(BoneBitRate) - 1, "Invalid BoneBitRate offsets");
 							uint8_t& smallest_bit_rate = *std::min_element<uint8_t*>(&bone_bit_rate.rotation, &bone_bit_rate.scale + 1);
@@ -1384,7 +1399,7 @@ namespace acl
 
 #if ACL_IMPL_DEBUG_VARIABLE_QUANTIZATION
 								printf("%u: => %u %u %u (%f)\n", chain_bone_index, bone_bit_rate.rotation, bone_bit_rate.translation, bone_bit_rate.scale, error);
-								for (uint16_t i = chain_link_index + 1; i < num_bones_in_chain; ++i)
+								for (uint32_t i = chain_link_index + 1; i < num_bones_in_chain; ++i)
 								{
 									const uint16_t chain_bone_index2 = context.chain_bone_indices[chain_link_index];
 									float error2 = calculate_max_error_at_bit_rate_object(context, chain_bone_index2, error_scan_stop_condition::until_end_of_segment);
@@ -1398,7 +1413,7 @@ namespace acl
 						bone_bit_rate = best_bone_bit_rate;
 						error = best_bit_rate_error;
 
-						if (error < settings.error_threshold)
+						if (error < error_threshold)
 							break;
 					}
 
@@ -1417,19 +1432,19 @@ namespace acl
 				// not, sibling bones will remain fairly close in their error. Some packed rotation formats, namely
 				// drop W component can have a high error even with raw values, it is assumed that if such a format
 				// is used then a best effort approach to reach the error threshold is entirely fine.
-				if (error >= settings.error_threshold && context.settings.rotation_format == rotation_format8::quatf_full)
+				if (error >= error_threshold && context.rotation_format == rotation_format8::quatf_full)
 				{
 					// From child to parent, max out the bit rate
-					for (int16_t chain_link_index = num_bones_in_chain - 1; chain_link_index >= 0; --chain_link_index)
+					for (int32_t chain_link_index = num_bones_in_chain - 1; chain_link_index >= 0; --chain_link_index)
 					{
-						const uint16_t chain_bone_index = context.chain_bone_indices[chain_link_index];
+						const uint32_t chain_bone_index = context.chain_bone_indices[chain_link_index];
 						BoneBitRate& bone_bit_rate = context.bit_rate_per_bone[chain_bone_index];
 						bone_bit_rate.rotation = std::max<uint8_t>(bone_bit_rate.rotation, k_highest_bit_rate);
 						bone_bit_rate.translation = std::max<uint8_t>(bone_bit_rate.translation, k_highest_bit_rate);
 						bone_bit_rate.scale = std::max<uint8_t>(bone_bit_rate.scale, k_highest_bit_rate);
 
 						error = calculate_max_error_at_bit_rate_object(context, bone_index, error_scan_stop_condition::until_end_of_segment);
-						if (error < settings.error_threshold)
+						if (error < error_threshold)
 							break;
 					}
 				}
@@ -1441,17 +1456,17 @@ namespace acl
 			{
 				float error = calculate_max_error_at_bit_rate_object(context, i, error_scan_stop_condition::until_end_of_segment);
 				const BoneBitRate& bone_bit_rate = context.bit_rate_per_bone[i];
-				printf("%u: %u | %u | %u => %f %s\n", i, bone_bit_rate.rotation, bone_bit_rate.translation, bone_bit_rate.scale, error, error >= settings.error_threshold ? "!" : "");
+				printf("%u: %u | %u | %u => %f %s\n", i, bone_bit_rate.rotation, bone_bit_rate.translation, bone_bit_rate.scale, error, error >= error_threshold ? "!" : "");
 			}
 #endif
 
-			deallocate_type_array(context.allocator, bone_chain_permutation, context.num_bones);
-			deallocate_type_array(context.allocator, permutation_bit_rates, context.num_bones);
-			deallocate_type_array(context.allocator, best_permutation_bit_rates, context.num_bones);
-			deallocate_type_array(context.allocator, best_bit_rates, context.num_bones);
+			deallocate_type_array(context.allocator, bone_chain_permutation, num_bones);
+			deallocate_type_array(context.allocator, permutation_bit_rates, num_bones);
+			deallocate_type_array(context.allocator, best_permutation_bit_rates, num_bones);
+			deallocate_type_array(context.allocator, best_bit_rates, num_bones);
 		}
 
-		inline void quantize_streams(IAllocator& allocator, ClipContext& clip_context, const CompressionSettings& settings, const RigidSkeleton& skeleton, const ClipContext& raw_clip_context, const ClipContext& additive_base_clip_context, OutputStats& out_stats)
+		inline void quantize_streams(iallocator& allocator, clip_context& clip, const compression_settings& settings, const clip_context& raw_clip_context, const clip_context& additive_base_clip_context, output_stats& out_stats)
 		{
 			(void)out_stats;
 
@@ -1460,9 +1475,9 @@ namespace acl
 			const bool is_scale_variable = is_vector_format_variable(settings.scale_format);
 			const bool is_any_variable = is_rotation_variable || is_translation_variable || is_scale_variable;
 
-			QuantizationContext context(allocator, clip_context, raw_clip_context, additive_base_clip_context, settings, skeleton);
+			quantization_context context(allocator, clip, raw_clip_context, additive_base_clip_context, settings);
 
-			for (SegmentContext& segment : clip_context.segment_iterator())
+			for (SegmentContext& segment : clip.segment_iterator())
 			{
 #if ACL_IMPL_DEBUG_VARIABLE_QUANTIZATION
 				printf("Quantizing segment %u...\n", segment.segment_index);
@@ -1470,7 +1485,7 @@ namespace acl
 
 #if ACL_IMPL_PROFILE_MATH
 				{
-					ScopeProfiler timer;
+					scope_profiler timer;
 
 					for (int32_t i = 0; i < 10; ++i)
 					{
@@ -1500,7 +1515,7 @@ namespace acl
 			}
 
 #if defined(SJSON_CPP_WRITER)
-			if (are_all_enum_flags_set(out_stats.logging, StatLogging::Detailed))
+			if (are_all_enum_flags_set(out_stats.logging, stat_logging::Detailed))
 			{
 				sjson::ObjectWriter& writer = *out_stats.writer;
 				writer["track_bit_rate_database_size"] = static_cast<uint32_t>(context.bit_rate_database.get_allocated_size());
diff --git a/includes/acl/compression/impl/sample_streams.h b/includes/acl/compression/impl/sample_streams.h
--- a/includes/acl/compression/impl/sample_streams.h
+++ b/includes/acl/compression/impl/sample_streams.h
@@ -114,7 +114,7 @@ namespace acl
 		inline rtm::quatf RTM_SIMD_CALL get_rotation_sample(const BoneStreams& bone_steams, uint32_t sample_index)
 		{
 			const SegmentContext* segment = bone_steams.segment;
-			const ClipContext* clip_context = segment->clip;
+			const clip_context* clip = segment->clip;
 
 			const rotation_format8 format = bone_steams.rotations.get_rotation_format();
 			const uint8_t bit_rate = bone_steams.rotations.get_bit_rate();
@@ -126,7 +126,7 @@ namespace acl
 
 			rtm::vector4f packed_rotation = acl_impl::load_rotation_sample(quantized_ptr, format, bit_rate);
 
-			if (clip_context->are_rotations_normalized && !is_raw_bit_rate(bit_rate))
+			if (clip->are_rotations_normalized && !is_raw_bit_rate(bit_rate))
 			{
 				if (segment->are_rotations_normalized && !is_constant_bit_rate(bit_rate))
 				{
@@ -138,7 +138,7 @@ namespace acl
 					packed_rotation = rtm::vector_mul_add(packed_rotation, segment_range_extent, segment_range_min);
 				}
 
-				const BoneRanges& clip_bone_range = clip_context->ranges[bone_steams.bone_index];
+				const BoneRanges& clip_bone_range = clip->ranges[bone_steams.bone_index];
 
 				const rtm::vector4f clip_range_min = clip_bone_range.rotation.get_min();
 				const rtm::vector4f clip_range_extent = clip_bone_range.rotation.get_extent();
@@ -152,7 +152,7 @@ namespace acl
 		inline rtm::quatf RTM_SIMD_CALL get_rotation_sample(const BoneStreams& bone_steams, const BoneStreams& raw_bone_steams, uint32_t sample_index, uint8_t bit_rate)
 		{
 			const SegmentContext* segment = bone_steams.segment;
-			const ClipContext* clip_context = segment->clip;
+			const clip_context* clip = segment->clip;
 			const rotation_format8 format = bone_steams.rotations.get_rotation_format();
 
 			rtm::vector4f rotation;
@@ -202,7 +202,7 @@ namespace acl
 					packed_rotation = rtm::vector_mul_add(packed_rotation, segment_range_extent, segment_range_min);
 				}
 
-				const BoneRanges& clip_bone_range = clip_context->ranges[bone_steams.bone_index];
+				const BoneRanges& clip_bone_range = clip->ranges[bone_steams.bone_index];
 
 				const rtm::vector4f clip_range_min = clip_bone_range.rotation.get_min();
 				const rtm::vector4f clip_range_extent = clip_bone_range.rotation.get_extent();
@@ -216,7 +216,7 @@ namespace acl
 		inline rtm::quatf RTM_SIMD_CALL get_rotation_sample(const BoneStreams& bone_steams, uint32_t sample_index, rotation_format8 desired_format)
 		{
 			const SegmentContext* segment = bone_steams.segment;
-			const ClipContext* clip_context = segment->clip;
+			const clip_context* clip = segment->clip;
 
 			const uint8_t* quantized_ptr = bone_steams.rotations.get_raw_sample_ptr(sample_index);
 			const rotation_format8 format = bone_steams.rotations.get_rotation_format();
@@ -238,7 +238,7 @@ namespace acl
 				break;
 			}
 
-			const bool are_rotations_normalized = clip_context->are_rotations_normalized && !bone_steams.is_rotation_constant;
+			const bool are_rotations_normalized = clip->are_rotations_normalized && !bone_steams.is_rotation_constant;
 			if (are_rotations_normalized)
 			{
 				if (segment->are_rotations_normalized)
@@ -251,7 +251,7 @@ namespace acl
 					packed_rotation = rtm::vector_mul_add(packed_rotation, segment_range_extent, segment_range_min);
 				}
 
-				const BoneRanges& clip_bone_range = clip_context->ranges[bone_steams.bone_index];
+				const BoneRanges& clip_bone_range = clip->ranges[bone_steams.bone_index];
 
 				const rtm::vector4f clip_range_min = clip_bone_range.rotation.get_min();
 				const rtm::vector4f clip_range_extent = clip_bone_range.rotation.get_extent();
@@ -265,8 +265,8 @@ namespace acl
 		inline rtm::vector4f RTM_SIMD_CALL get_translation_sample(const BoneStreams& bone_steams, uint32_t sample_index)
 		{
 			const SegmentContext* segment = bone_steams.segment;
-			const ClipContext* clip_context = segment->clip;
-			const bool are_translations_normalized = clip_context->are_translations_normalized;
+			const clip_context* clip = segment->clip;
+			const bool are_translations_normalized = clip->are_translations_normalized;
 
 			const vector_format8 format = bone_steams.translations.get_vector_format();
 			const uint8_t bit_rate = bone_steams.translations.get_bit_rate();
@@ -290,7 +290,7 @@ namespace acl
 					packed_translation = rtm::vector_mul_add(packed_translation, segment_range_extent, segment_range_min);
 				}
 
-				const BoneRanges& clip_bone_range = clip_context->ranges[bone_steams.bone_index];
+				const BoneRanges& clip_bone_range = clip->ranges[bone_steams.bone_index];
 
 				const rtm::vector4f clip_range_min = clip_bone_range.translation.get_min();
 				const rtm::vector4f clip_range_extent = clip_bone_range.translation.get_extent();
@@ -304,7 +304,7 @@ namespace acl
 		inline rtm::vector4f RTM_SIMD_CALL get_translation_sample(const BoneStreams& bone_steams, const BoneStreams& raw_bone_steams, uint32_t sample_index, uint8_t bit_rate)
 		{
 			const SegmentContext* segment = bone_steams.segment;
-			const ClipContext* clip_context = segment->clip;
+			const clip_context* clip = segment->clip;
 			const vector_format8 format = bone_steams.translations.get_vector_format();
 
 			const uint8_t* quantized_ptr;
@@ -317,7 +317,7 @@ namespace acl
 
 			const rtm::vector4f translation = acl_impl::load_vector_sample(quantized_ptr, format, 0);
 
-			ACL_ASSERT(clip_context->are_translations_normalized, "Translations must be normalized to support variable bit rates.");
+			ACL_ASSERT(clip->are_translations_normalized, "Translations must be normalized to support variable bit rates.");
 
 			// Pack and unpack at our desired bit rate
 			rtm::vector4f packed_translation;
@@ -351,7 +351,7 @@ namespace acl
 					packed_translation = rtm::vector_mul_add(packed_translation, segment_range_extent, segment_range_min);
 				}
 
-				const BoneRanges& clip_bone_range = clip_context->ranges[bone_steams.bone_index];
+				const BoneRanges& clip_bone_range = clip->ranges[bone_steams.bone_index];
 
 				const rtm::vector4f clip_range_min = clip_bone_range.translation.get_min();
 				const rtm::vector4f clip_range_extent = clip_bone_range.translation.get_extent();
@@ -365,8 +365,8 @@ namespace acl
 		inline rtm::vector4f RTM_SIMD_CALL get_translation_sample(const BoneStreams& bone_steams, uint32_t sample_index, vector_format8 desired_format)
 		{
 			const SegmentContext* segment = bone_steams.segment;
-			const ClipContext* clip_context = segment->clip;
-			const bool are_translations_normalized = clip_context->are_translations_normalized && !bone_steams.is_translation_constant;
+			const clip_context* clip = segment->clip;
+			const bool are_translations_normalized = clip->are_translations_normalized && !bone_steams.is_translation_constant;
 			const uint8_t* quantized_ptr = bone_steams.translations.get_raw_sample_ptr(sample_index);
 			const vector_format8 format = bone_steams.translations.get_vector_format();
 
@@ -398,7 +398,7 @@ namespace acl
 					packed_translation = rtm::vector_mul_add(packed_translation, segment_range_extent, segment_range_min);
 				}
 
-				const BoneRanges& clip_bone_range = clip_context->ranges[bone_steams.bone_index];
+				const BoneRanges& clip_bone_range = clip->ranges[bone_steams.bone_index];
 
 				rtm::vector4f clip_range_min = clip_bone_range.translation.get_min();
 				rtm::vector4f clip_range_extent = clip_bone_range.translation.get_extent();
@@ -412,8 +412,8 @@ namespace acl
 		inline rtm::vector4f RTM_SIMD_CALL get_scale_sample(const BoneStreams& bone_steams, uint32_t sample_index)
 		{
 			const SegmentContext* segment = bone_steams.segment;
-			const ClipContext* clip_context = segment->clip;
-			const bool are_scales_normalized = clip_context->are_scales_normalized;
+			const clip_context* clip = segment->clip;
+			const bool are_scales_normalized = clip->are_scales_normalized;
 
 			const vector_format8 format = bone_steams.scales.get_vector_format();
 			const uint8_t bit_rate = bone_steams.scales.get_bit_rate();
@@ -437,7 +437,7 @@ namespace acl
 					packed_scale = rtm::vector_mul_add(packed_scale, segment_range_extent, segment_range_min);
 				}
 
-				const BoneRanges& clip_bone_range = clip_context->ranges[bone_steams.bone_index];
+				const BoneRanges& clip_bone_range = clip->ranges[bone_steams.bone_index];
 
 				const rtm::vector4f clip_range_min = clip_bone_range.scale.get_min();
 				const rtm::vector4f clip_range_extent = clip_bone_range.scale.get_extent();
@@ -451,7 +451,7 @@ namespace acl
 		inline rtm::vector4f RTM_SIMD_CALL get_scale_sample(const BoneStreams& bone_steams, const BoneStreams& raw_bone_steams, uint32_t sample_index, uint8_t bit_rate)
 		{
 			const SegmentContext* segment = bone_steams.segment;
-			const ClipContext* clip_context = segment->clip;
+			const clip_context* clip = segment->clip;
 			const vector_format8 format = bone_steams.scales.get_vector_format();
 
 			const uint8_t* quantized_ptr;
@@ -464,7 +464,7 @@ namespace acl
 
 			const rtm::vector4f scale = acl_impl::load_vector_sample(quantized_ptr, format, 0);
 
-			ACL_ASSERT(clip_context->are_scales_normalized, "Scales must be normalized to support variable bit rates.");
+			ACL_ASSERT(clip->are_scales_normalized, "Scales must be normalized to support variable bit rates.");
 
 			// Pack and unpack at our desired bit rate
 			rtm::vector4f packed_scale;
@@ -498,7 +498,7 @@ namespace acl
 					packed_scale = rtm::vector_mul_add(packed_scale, segment_range_extent, segment_range_min);
 				}
 
-				const BoneRanges& clip_bone_range = clip_context->ranges[bone_steams.bone_index];
+				const BoneRanges& clip_bone_range = clip->ranges[bone_steams.bone_index];
 
 				const rtm::vector4f clip_range_min = clip_bone_range.scale.get_min();
 				const rtm::vector4f clip_range_extent = clip_bone_range.scale.get_extent();
@@ -512,8 +512,8 @@ namespace acl
 		inline rtm::vector4f RTM_SIMD_CALL get_scale_sample(const BoneStreams& bone_steams, uint32_t sample_index, vector_format8 desired_format)
 		{
 			const SegmentContext* segment = bone_steams.segment;
-			const ClipContext* clip_context = segment->clip;
-			const bool are_scales_normalized = clip_context->are_scales_normalized && !bone_steams.is_scale_constant;
+			const clip_context* clip = segment->clip;
+			const bool are_scales_normalized = clip->are_scales_normalized && !bone_steams.is_scale_constant;
 			const uint8_t* quantized_ptr = bone_steams.scales.get_raw_sample_ptr(sample_index);
 			const vector_format8 format = bone_steams.scales.get_vector_format();
 
@@ -545,7 +545,7 @@ namespace acl
 					packed_scale = rtm::vector_mul_add(packed_scale, segment_range_extent, segment_range_min);
 				}
 
-				const BoneRanges& clip_bone_range = clip_context->ranges[bone_steams.bone_index];
+				const BoneRanges& clip_bone_range = clip->ranges[bone_steams.bone_index];
 
 				rtm::vector4f clip_range_min = clip_bone_range.scale.get_min();
 				rtm::vector4f clip_range_extent = clip_bone_range.scale.get_extent();
@@ -573,8 +573,8 @@ namespace acl
 			float interpolation_alpha = 0.0F;
 
 			// Our samples are uniform, grab the nearest samples
-			const ClipContext* clip_context = segment.clip;
-			find_linear_interpolation_samples_with_sample_rate(clip_context->num_samples, clip_context->sample_rate, sample_time, sample_rounding_policy::nearest, key0, key1, interpolation_alpha);
+			const clip_context* clip = segment.clip;
+			find_linear_interpolation_samples_with_sample_rate(clip->num_samples, clip->sample_rate, sample_time, sample_rounding_policy::nearest, key0, key1, interpolation_alpha);
 
 			// Offset for the current segment and clamp
 			key0 = key0 - segment.clip_sample_offset;
diff --git a/includes/acl/compression/impl/segment_context.h b/includes/acl/compression/impl/segment_context.h
--- a/includes/acl/compression/impl/segment_context.h
+++ b/includes/acl/compression/impl/segment_context.h
@@ -29,7 +29,6 @@
 #include "acl/core/error.h"
 #include "acl/core/hash.h"
 #include "acl/core/iterator.h"
-#include "acl/compression/animation_clip.h"
 #include "acl/compression/impl/track_stream.h"
 
 #include <cstdint>
@@ -40,7 +39,7 @@ namespace acl
 {
 	namespace acl_impl
 	{
-		struct ClipContext;
+		struct clip_context;
 
 		//////////////////////////////////////////////////////////////////////////
 		// The sample distribution.
@@ -56,7 +55,7 @@ namespace acl
 
 		struct SegmentContext
 		{
-			ClipContext* clip;
+			clip_context* clip;
 			BoneStreams* bone_streams;
 			BoneRanges* ranges;
 
@@ -79,11 +78,11 @@ namespace acl
 			uint32_t total_header_size;
 
 			//////////////////////////////////////////////////////////////////////////
-			Iterator<BoneStreams> bone_iterator() { return Iterator<BoneStreams>(bone_streams, num_bones); }
-			ConstIterator<BoneStreams> const_bone_iterator() const { return ConstIterator<BoneStreams>(bone_streams, num_bones); }
+			iterator<BoneStreams> bone_iterator() { return iterator<BoneStreams>(bone_streams, num_bones); }
+			const_iterator<BoneStreams> const_bone_iterator() const { return const_iterator<BoneStreams>(bone_streams, num_bones); }
 		};
 
-		inline void destroy_segment_context(IAllocator& allocator, SegmentContext& segment)
+		inline void destroy_segment_context(iallocator& allocator, SegmentContext& segment)
 		{
 			deallocate_type_array(allocator, segment.bone_streams, segment.num_bones);
 			deallocate_type_array(allocator, segment.ranges, segment.num_bones);
diff --git a/includes/acl/compression/impl/segment_streams.h b/includes/acl/compression/impl/segment_streams.h
--- a/includes/acl/compression/impl/segment_streams.h
+++ b/includes/acl/compression/impl/segment_streams.h
@@ -38,12 +38,12 @@ namespace acl
 {
 	namespace acl_impl
 	{
-		inline void segment_streams(IAllocator& allocator, ClipContext& clip_context, const SegmentingSettings& settings)
+		inline void segment_streams(iallocator& allocator, clip_context& clip, const segmenting_settings& settings)
 		{
-			ACL_ASSERT(clip_context.num_segments == 1, "ClipContext must have a single segment.");
+			ACL_ASSERT(clip.num_segments == 1, "clip_context must have a single segment.");
 			ACL_ASSERT(settings.ideal_num_samples <= settings.max_num_samples, "Invalid num samples for segmenting settings. %u > %u", settings.ideal_num_samples, settings.max_num_samples);
 
-			if (clip_context.num_samples <= settings.max_num_samples)
+			if (clip.num_samples <= settings.max_num_samples)
 				return;
 
 			//////////////////////////////////////////////////////////////////////////
@@ -59,14 +59,14 @@ namespace acl
 			// TODO: Can we provide a tighter guarantee?
 			//////////////////////////////////////////////////////////////////////////
 
-			uint32_t num_segments = (clip_context.num_samples + settings.ideal_num_samples - 1) / settings.ideal_num_samples;
+			uint32_t num_segments = (clip.num_samples + settings.ideal_num_samples - 1) / settings.ideal_num_samples;
 			const uint32_t max_num_samples = num_segments * settings.ideal_num_samples;
 
 			const uint32_t original_num_segments = num_segments;
 			uint32_t* num_samples_per_segment = allocate_type_array<uint32_t>(allocator, num_segments);
 			std::fill(num_samples_per_segment, num_samples_per_segment + num_segments, settings.ideal_num_samples);
 
-			const uint32_t num_leftover_samples = settings.ideal_num_samples - (max_num_samples - clip_context.num_samples);
+			const uint32_t num_leftover_samples = settings.ideal_num_samples - (max_num_samples - clip.num_samples);
 			if (num_leftover_samples != 0)
 				num_samples_per_segment[num_segments - 1] = num_leftover_samples;
 
@@ -88,20 +88,20 @@ namespace acl
 
 			ACL_ASSERT(num_segments != 1, "Expected a number of segments greater than 1.");
 
-			SegmentContext* clip_segment = clip_context.segments;
-			clip_context.segments = allocate_type_array<SegmentContext>(allocator, num_segments);
-			clip_context.num_segments = safe_static_cast<uint16_t>(num_segments);
+			SegmentContext* clip_segment = clip.segments;
+			clip.segments = allocate_type_array<SegmentContext>(allocator, num_segments);
+			clip.num_segments = safe_static_cast<uint16_t>(num_segments);
 
 			uint32_t clip_sample_index = 0;
 			for (uint32_t segment_index = 0; segment_index < num_segments; ++segment_index)
 			{
 				const uint32_t num_samples_in_segment = num_samples_per_segment[segment_index];
 
-				SegmentContext& segment = clip_context.segments[segment_index];
-				segment.clip = &clip_context;
-				segment.bone_streams = allocate_type_array<BoneStreams>(allocator, clip_context.num_bones);
+				SegmentContext& segment = clip.segments[segment_index];
+				segment.clip = &clip;
+				segment.bone_streams = allocate_type_array<BoneStreams>(allocator, clip.num_bones);
 				segment.ranges = nullptr;
-				segment.num_bones = clip_context.num_bones;
+				segment.num_bones = clip.num_bones;
 				segment.num_samples = safe_static_cast<uint16_t>(num_samples_in_segment);
 				segment.clip_sample_offset = clip_sample_index;
 				segment.segment_index = segment_index;
@@ -114,7 +114,7 @@ namespace acl
 				segment.range_data_size = 0;
 				segment.total_header_size = 0;
 
-				for (uint16_t bone_index = 0; bone_index < clip_context.num_bones; ++bone_index)
+				for (uint16_t bone_index = 0; bone_index < clip.num_bones; ++bone_index)
 				{
 					const BoneStreams& clip_bone_stream = clip_segment->bone_streams[bone_index];
 					BoneStreams& segment_bone_stream = segment.bone_streams[bone_index];
diff --git a/includes/acl/compression/impl/track_bit_rate_database.h b/includes/acl/compression/impl/track_bit_rate_database.h
--- a/includes/acl/compression/impl/track_bit_rate_database.h
+++ b/includes/acl/compression/impl/track_bit_rate_database.h
@@ -49,7 +49,7 @@ namespace acl
 		class hierarchical_track_query
 		{
 		public:
-			explicit hierarchical_track_query(IAllocator& allocator)
+			explicit hierarchical_track_query(iallocator& allocator)
 				: m_allocator(allocator)
 				, m_database(nullptr)
 				, m_track_index(0xFFFFFFFFU)
@@ -79,7 +79,7 @@ namespace acl
 				uint32_t	scale_cache_index;
 			};
 
-			IAllocator&						m_allocator;
+			iallocator&						m_allocator;
 			track_bit_rate_database*		m_database;
 			uint32_t						m_track_index;
 			const BoneBitRate*				m_bit_rates;
@@ -144,7 +144,7 @@ namespace acl
 		class track_bit_rate_database
 		{
 		public:
-			track_bit_rate_database(IAllocator& allocator, const CompressionSettings& settings, const BoneStreams* bone_streams, const BoneStreams* raw_bone_steams, uint32_t num_transforms, uint32_t num_samples_per_track);
+			track_bit_rate_database(iallocator& allocator, rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format, const BoneStreams* bone_streams, const BoneStreams* raw_bone_steams, uint32_t num_transforms, uint32_t num_samples_per_track);
 			~track_bit_rate_database();
 
 			void set_segment(const BoneStreams* bone_streams, uint32_t num_transforms, uint32_t num_samples_per_track);
@@ -200,7 +200,7 @@ namespace acl
 
 			rtm::vector4f		m_default_scale;
 
-			IAllocator&			m_allocator;
+			iallocator&			m_allocator;
 			const BoneStreams*	m_mutable_bone_streams;
 			const BoneStreams*	m_raw_bone_streams;
 
@@ -209,8 +209,8 @@ namespace acl
 			uint32_t			m_num_entries_per_transform;
 			uint32_t			m_track_size;
 
-			BitSetDescription	m_bitset_desc;
-			BitSetIndexRef		m_bitref_constant;
+			bitset_description	m_bitset_desc;
+			bitset_index_ref		m_bitref_constant;
 			rotation_format8		m_rotation_format;
 			vector_format8		m_translation_format;
 			vector_format8		m_scale_format;
@@ -296,7 +296,7 @@ namespace acl
 			return -1;
 		}
 
-		inline track_bit_rate_database::track_bit_rate_database(IAllocator& allocator, const CompressionSettings& settings, const BoneStreams* bone_streams, const BoneStreams* raw_bone_steams, uint32_t num_transforms, uint32_t num_samples_per_track)
+		inline track_bit_rate_database::track_bit_rate_database(iallocator& allocator, rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format, const BoneStreams* bone_streams, const BoneStreams* raw_bone_steams, uint32_t num_transforms, uint32_t num_samples_per_track)
 			: m_allocator(allocator)
 			, m_mutable_bone_streams(bone_streams)
 			, m_raw_bone_streams(raw_bone_steams)
@@ -315,15 +315,15 @@ namespace acl
 
 			const uint32_t num_cached_tracks = num_transforms * m_num_entries_per_transform;
 
-			m_bitset_desc = BitSetDescription::make_from_num_bits(num_samples_per_track);
-			m_bitref_constant = BitSetIndexRef(m_bitset_desc, 0);
+			m_bitset_desc = bitset_description::make_from_num_bits(num_samples_per_track);
+			m_bitref_constant = bitset_index_ref(m_bitset_desc, 0);
 
-			m_rotation_format = settings.rotation_format;
-			m_translation_format = settings.translation_format;
-			m_scale_format = settings.scale_format;
-			m_is_rotation_variable = is_rotation_format_variable(settings.rotation_format);
-			m_is_translation_variable = is_vector_format_variable(settings.translation_format);
-			m_is_scale_variable = is_vector_format_variable(settings.scale_format);
+			m_rotation_format = rotation_format;
+			m_translation_format = translation_format;
+			m_scale_format = scale_format;
+			m_is_rotation_variable = is_rotation_format_variable(rotation_format);
+			m_is_translation_variable = is_vector_format_variable(translation_format);
+			m_is_scale_variable = is_vector_format_variable(scale_format);
 
 			m_generation_id = 1;
 
@@ -646,7 +646,7 @@ namespace acl
 				rtm::quatf sample0;
 				rtm::quatf sample1;
 
-				const BitSetIndexRef bitref0(m_bitset_desc, key0);
+				const bitset_index_ref bitref0(m_bitset_desc, key0);
 				if (bitset_test(validity_bitset, bitref0))
 				{
 					// Cached
@@ -678,7 +678,7 @@ namespace acl
 
 				if (static_condition<distribution == SampleDistribution8::Variable>::test())
 				{
-					const BitSetIndexRef bitref1(m_bitset_desc, key1);
+					const bitset_index_ref bitref1(m_bitset_desc, key1);
 					if (bitset_test(validity_bitset, bitref1))
 					{
 						// Cached
@@ -778,7 +778,7 @@ namespace acl
 				rtm::vector4f sample0;
 				rtm::vector4f sample1;
 
-				const BitSetIndexRef bitref0(m_bitset_desc, key0);
+				const bitset_index_ref bitref0(m_bitset_desc, key0);
 				if (bitset_test(validity_bitset, bitref0))
 				{
 					// Cached
@@ -806,7 +806,7 @@ namespace acl
 
 				if (static_condition<distribution == SampleDistribution8::Variable>::test())
 				{
-					const BitSetIndexRef bitref1(m_bitset_desc, key1);
+					const bitset_index_ref bitref1(m_bitset_desc, key1);
 					if (bitset_test(validity_bitset, bitref1))
 					{
 						// Cached
@@ -906,7 +906,7 @@ namespace acl
 				rtm::vector4f sample0;
 				rtm::vector4f sample1;
 
-				const BitSetIndexRef bitref0(m_bitset_desc, key0);
+				const bitset_index_ref bitref0(m_bitset_desc, key0);
 				if (bitset_test(validity_bitset, bitref0))
 				{
 					// Cached
@@ -934,7 +934,7 @@ namespace acl
 
 				if (static_condition<distribution == SampleDistribution8::Variable>::test())
 				{
-					const BitSetIndexRef bitref1(m_bitset_desc, key1);
+					const bitset_index_ref bitref1(m_bitset_desc, key1);
 					if (bitset_test(validity_bitset, bitref1))
 					{
 						// Cached
diff --git a/includes/acl/compression/impl/track_list_context.h b/includes/acl/compression/impl/track_list_context.h
--- a/includes/acl/compression/impl/track_list_context.h
+++ b/includes/acl/compression/impl/track_list_context.h
@@ -61,7 +61,7 @@ namespace acl
 
 		struct track_list_context
 		{
-			IAllocator* allocator;
+			iallocator* allocator;
 			const track_array* reference_list;
 
 			track_array track_list;
@@ -98,7 +98,7 @@ namespace acl
 				{
 					deallocate_type_array(*allocator, range_list, num_tracks);
 
-					const BitSetDescription bitset_desc = BitSetDescription::make_from_num_bits(num_tracks);
+					const bitset_description bitset_desc = bitset_description::make_from_num_bits(num_tracks);
 					deallocate_type_array(*allocator, constant_tracks_bitset, bitset_desc.get_size());
 
 					deallocate_type_array(*allocator, bit_rate_list, num_tracks);
@@ -108,7 +108,7 @@ namespace acl
 			}
 
 			bool is_valid() const { return allocator != nullptr; }
-			bool is_constant(uint32_t track_index) const { return bitset_test(constant_tracks_bitset, BitSetDescription::make_from_num_bits(num_tracks), track_index); }
+			bool is_constant(uint32_t track_index) const { return bitset_test(constant_tracks_bitset, bitset_description::make_from_num_bits(num_tracks), track_index); }
 
 			track_list_context(const track_list_context&) = delete;
 			track_list_context(track_list_context&&) = delete;
@@ -117,7 +117,7 @@ namespace acl
 		};
 
 		// Promote scalar tracks to vector tracks for SIMD alignment and padding
-		inline track_array copy_and_promote_track_list(IAllocator& allocator, const track_array& ref_track_list)
+		inline track_array copy_and_promote_track_list(iallocator& allocator, const track_array& ref_track_list)
 		{
 			using namespace rtm;
 
@@ -180,7 +180,7 @@ namespace acl
 			return out_track_list;
 		}
 
-		inline uint32_t* create_output_track_mapping(IAllocator& allocator, const track_array& track_list, uint32_t& out_num_output_tracks)
+		inline uint32_t* create_output_track_mapping(iallocator& allocator, const track_array& track_list, uint32_t& out_num_output_tracks)
 		{
 			const uint32_t num_tracks = track_list.get_num_tracks();
 			uint32_t num_output_tracks = num_tracks;
@@ -203,7 +203,7 @@ namespace acl
 			return output_indices;
 		}
 
-		inline void initialize_context(IAllocator& allocator, const track_array& track_list, track_list_context& context)
+		inline void initialize_context(iallocator& allocator, const track_array& track_list, track_list_context& context)
 		{
 			ACL_ASSERT(track_list.is_valid().empty(), "Invalid track list");
 			ACL_ASSERT(!context.is_valid(), "Context already initialized");
diff --git a/includes/acl/compression/impl/track_stream.h b/includes/acl/compression/impl/track_stream.h
--- a/includes/acl/compression/impl/track_stream.h
+++ b/includes/acl/compression/impl/track_stream.h
@@ -99,7 +99,7 @@ namespace acl
 		protected:
 			TrackStream(animation_track_type8 type, track_format8 format) noexcept : m_allocator(nullptr), m_samples(nullptr), m_num_samples(0), m_sample_size(0), m_sample_rate(0.0F), m_type(type), m_format(format), m_bit_rate(0) {}
 
-			TrackStream(IAllocator& allocator, uint32_t num_samples, uint32_t sample_size, float sample_rate, animation_track_type8 type, track_format8 format, uint8_t bit_rate)
+			TrackStream(iallocator& allocator, uint32_t num_samples, uint32_t sample_size, float sample_rate, animation_track_type8 type, track_format8 format, uint8_t bit_rate)
 				: m_allocator(&allocator)
 				, m_samples(reinterpret_cast<uint8_t*>(allocator.allocate(sample_size * num_samples + k_padding, 16)))
 				, m_num_samples(num_samples)
@@ -164,7 +164,7 @@ namespace acl
 			// In order to guarantee the safety of unaligned SIMD loads of every byte, we add some padding
 			static constexpr uint32_t k_padding = 15;
 
-			IAllocator*				m_allocator;
+			iallocator*				m_allocator;
 			uint8_t*				m_samples;
 			uint32_t				m_num_samples;
 			uint32_t				m_sample_size;
@@ -179,7 +179,7 @@ namespace acl
 		{
 		public:
 			RotationTrackStream() noexcept : TrackStream(animation_track_type8::rotation, track_format8(rotation_format8::quatf_full)) {}
-			RotationTrackStream(IAllocator& allocator, uint32_t num_samples, uint32_t sample_size, float sample_rate, rotation_format8 format, uint8_t bit_rate = k_invalid_bit_rate)
+			RotationTrackStream(iallocator& allocator, uint32_t num_samples, uint32_t sample_size, float sample_rate, rotation_format8 format, uint8_t bit_rate = k_invalid_bit_rate)
 				: TrackStream(allocator, num_samples, sample_size, sample_rate, animation_track_type8::rotation, track_format8(format), bit_rate)
 			{}
 			RotationTrackStream(const RotationTrackStream&) = delete;
@@ -209,7 +209,7 @@ namespace acl
 		{
 		public:
 			TranslationTrackStream() noexcept : TrackStream(animation_track_type8::translation, track_format8(vector_format8::vector3f_full)) {}
-			TranslationTrackStream(IAllocator& allocator, uint32_t num_samples, uint32_t sample_size, float sample_rate, vector_format8 format, uint8_t bit_rate = k_invalid_bit_rate)
+			TranslationTrackStream(iallocator& allocator, uint32_t num_samples, uint32_t sample_size, float sample_rate, vector_format8 format, uint8_t bit_rate = k_invalid_bit_rate)
 				: TrackStream(allocator, num_samples, sample_size, sample_rate, animation_track_type8::translation, track_format8(format), bit_rate)
 			{}
 			TranslationTrackStream(const TranslationTrackStream&) = delete;
@@ -239,7 +239,7 @@ namespace acl
 		{
 		public:
 			ScaleTrackStream() noexcept : TrackStream(animation_track_type8::scale, track_format8(vector_format8::vector3f_full)) {}
-			ScaleTrackStream(IAllocator& allocator, uint32_t num_samples, uint32_t sample_size, float sample_rate, vector_format8 format, uint8_t bit_rate = k_invalid_bit_rate)
+			ScaleTrackStream(iallocator& allocator, uint32_t num_samples, uint32_t sample_size, float sample_rate, vector_format8 format, uint8_t bit_rate = k_invalid_bit_rate)
 				: TrackStream(allocator, num_samples, sample_size, sample_rate, animation_track_type8::scale, track_format8(format), bit_rate)
 			{}
 			ScaleTrackStream(const ScaleTrackStream&) = delete;
diff --git a/includes/acl/compression/impl/write_compression_stats_impl.h b/includes/acl/compression/impl/write_compression_stats_impl.h
--- a/includes/acl/compression/impl/write_compression_stats_impl.h
+++ b/includes/acl/compression/impl/write_compression_stats_impl.h
@@ -40,7 +40,7 @@ namespace acl
 {
 	namespace acl_impl
 	{
-		inline void write_compression_stats(const track_list_context& context, const compressed_tracks& tracks, const ScopeProfiler& compression_time, OutputStats& stats)
+		inline void write_compression_stats(const track_list_context& context, const compressed_tracks& tracks, const scope_profiler& compression_time, output_stats& stats)
 		{
 			ACL_ASSERT(stats.writer != nullptr, "Attempted to log stats without a writer");
 
diff --git a/includes/acl/compression/impl/write_decompression_stats.h b/includes/acl/compression/impl/write_decompression_stats.h
--- a/includes/acl/compression/impl/write_decompression_stats.h
+++ b/includes/acl/compression/impl/write_decompression_stats.h
@@ -26,15 +26,15 @@
 
 #if defined(SJSON_CPP_WRITER)
 
-#include "acl/core/impl/compiler_utils.h"
-#include "acl/core/impl/memory_cache.h"
 #include "acl/core/scope_profiler.h"
 #include "acl/core/utils.h"
-#include "acl/algorithm/uniformly_sampled/decoder.h"
+#include "acl/core/impl/compiler_utils.h"
+#include "acl/core/impl/memory_cache.h"
 #include "acl/compression/output_stats.h"
-#include "acl/decompression/default_output_writer.h"
+#include "acl/decompression/decompress.h"
 
 #include <rtm/scalard.h>
+#include <rtm/scalarf.h>
 
 #include <algorithm>
 #include <thread>
@@ -66,15 +66,14 @@ namespace acl
 
 		template<class DecompressionContextType>
 		inline void write_decompression_performance_stats(
-			StatLogging logging, sjson::ObjectWriter& writer, const char* action_type,
+			stat_logging logging, sjson::ObjectWriter& writer, const char* action_type,
 			PlaybackDirection playback_direction, DecompressionFunction decompression_function,
-			CompressedClip* compressed_clips[k_num_decompression_evaluations],
+			compressed_tracks* compressed_clips[k_num_decompression_evaluations],
 			DecompressionContextType* contexts[k_num_decompression_evaluations],
-			CPUCacheFlusher* cache_flusher, rtm::qvvf* lossy_pose_transforms)
+			CPUCacheFlusher* cache_flusher, debug_track_writer& pose_writer)
 		{
-			const ClipHeader& clip_header = get_clip_header(*compressed_clips[0]);
-			const float duration = calculate_duration(clip_header.num_samples, clip_header.sample_rate);
-			const uint16_t num_bones = clip_header.num_bones;
+			const uint32_t num_tracks = compressed_clips[0]->get_num_tracks();
+			const float duration = compressed_clips[0]->get_duration();
 			const bool is_cold_cache_profiling = cache_flusher != nullptr;
 
 			float sample_times[k_num_decompression_samples];
@@ -97,8 +96,6 @@ namespace acl
 				break;
 			}
 
-			DefaultOutputWriter pose_writer(lossy_pose_transforms, num_bones);
-
 			// Initialize and clear our contexts
 			for (uint32_t clip_index = 0; clip_index < k_num_decompression_evaluations; ++clip_index)
 				contexts[clip_index]->initialize(*compressed_clips[clip_index]);
@@ -140,14 +137,14 @@ namespace acl
 							// If we want the cache warm, decompress everything once to prime it
 							DecompressionContextType* context = contexts[0];
 							context->seek(sample_time, sample_rounding_policy::none);
-							context->decompress_pose(pose_writer);
+							context->decompress_tracks(pose_writer);
 						}
 
 						// We yield our time slice and wait for a new one before priming the cache
 						// to help keep it warm and minimize the risk that we'll be interrupted during decompression
 						std::this_thread::sleep_for(std::chrono::nanoseconds(1));
 
-						ScopeProfiler timer;
+						scope_profiler timer;
 
 						for (uint32_t clip_index = 0; clip_index < k_num_decompression_evaluations; ++clip_index)
 						{
@@ -159,11 +156,11 @@ namespace acl
 							switch (decompression_function)
 							{
 							case DecompressionFunction::DecompressPose:
-								context->decompress_pose(pose_writer);
+								context->decompress_tracks(pose_writer);
 								break;
 							case DecompressionFunction::DecompressBone:
-								for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
-									context->decompress_bone(bone_index, &lossy_pose_transforms[bone_index].rotation, &lossy_pose_transforms[bone_index].translation, &lossy_pose_transforms[bone_index].scale);
+								for (uint32_t bone_index = 0; bone_index < num_tracks; ++bone_index)
+									context->decompress_track(bone_index, pose_writer);
 								break;
 							}
 						}
@@ -172,7 +169,7 @@ namespace acl
 
 						const double elapsed_ms = timer.get_elapsed_milliseconds() / k_num_decompression_evaluations;
 
-						if (are_any_enum_flags_set(logging, StatLogging::ExhaustiveDecompression))
+						if (are_any_enum_flags_set(logging, stat_logging::ExhaustiveDecompression))
 							data_writer.push(elapsed_ms);
 
 						clip_min_ms = rtm::scalar_min(clip_min_ms, elapsed_ms);
@@ -191,7 +188,7 @@ namespace acl
 			};
 		}
 
-		inline void write_memcpy_performance_stats(IAllocator& allocator, sjson::ObjectWriter& writer, CPUCacheFlusher* cache_flusher, rtm::qvvf* lossy_pose_transforms, uint16_t num_bones)
+		inline void write_memcpy_performance_stats(iallocator& allocator, sjson::ObjectWriter& writer, CPUCacheFlusher* cache_flusher, rtm::qvvf* lossy_pose_transforms, uint32_t num_bones)
 		{
 			rtm::qvvf* memcpy_src_transforms = allocate_type_array<rtm::qvvf>(allocator, num_bones);
 
@@ -218,7 +215,7 @@ namespace acl
 				}
 
 				double execution_count;
-				ScopeProfiler timer;
+				scope_profiler timer;
 				if (cache_flusher != nullptr)
 				{
 					std::memcpy(lossy_pose_transforms, memcpy_src_transforms, sizeof(rtm::qvvf) * num_bones);
@@ -257,88 +254,83 @@ namespace acl
 		}
 
 		template<class DecompressionContextType>
-		inline void write_decompression_performance_stats(IAllocator& allocator, CompressedClip* compressed_clips[k_num_decompression_evaluations], DecompressionContextType* contexts[k_num_decompression_evaluations], StatLogging logging, sjson::ObjectWriter& writer)
+		inline void write_decompression_performance_stats(iallocator& allocator, compressed_tracks* compressed_clips[k_num_decompression_evaluations], DecompressionContextType* contexts[k_num_decompression_evaluations], stat_logging logging, sjson::ObjectWriter& writer)
 		{
 			CPUCacheFlusher* cache_flusher = allocate_type<CPUCacheFlusher>(allocator);
 
-			const ClipHeader& clip_header = get_clip_header(*compressed_clips[0]);
-			rtm::qvvf* lossy_pose_transforms = allocate_type_array<rtm::qvvf>(allocator, clip_header.num_bones);
+			const uint32_t num_tracks = compressed_clips[0]->get_num_tracks();
+			debug_track_writer pose_writer(allocator, track_type8::qvvf, num_tracks);
 
 			const uint32_t num_bytes_per_bone = (4 + 3 + 3) * sizeof(float);	// Rotation, Translation, Scale
-			writer["pose_size"] = uint32_t(clip_header.num_bones) * num_bytes_per_bone;
+			writer["pose_size"] = num_tracks * num_bytes_per_bone;
 
 			writer["decompression_time_per_sample"] = [&](sjson::ObjectWriter& per_sample_writer)
 			{
 				// Cold/Warm CPU cache, memcpy
-				write_memcpy_performance_stats(allocator, per_sample_writer, cache_flusher, lossy_pose_transforms, clip_header.num_bones);
-				write_memcpy_performance_stats(allocator, per_sample_writer, nullptr, lossy_pose_transforms, clip_header.num_bones);
+				write_memcpy_performance_stats(allocator, per_sample_writer, cache_flusher, pose_writer.tracks_typed.qvvf, num_tracks);
+				write_memcpy_performance_stats(allocator, per_sample_writer, nullptr, pose_writer.tracks_typed.qvvf, num_tracks);
 
 				// Cold CPU cache, decompress_pose
-				write_decompression_performance_stats(logging, per_sample_writer, "forward_pose_cold", PlaybackDirection::Forward, DecompressionFunction::DecompressPose, compressed_clips, contexts, cache_flusher, lossy_pose_transforms);
-				write_decompression_performance_stats(logging, per_sample_writer, "backward_pose_cold", PlaybackDirection::Backward, DecompressionFunction::DecompressPose, compressed_clips, contexts, cache_flusher, lossy_pose_transforms);
-				write_decompression_performance_stats(logging, per_sample_writer, "random_pose_cold", PlaybackDirection::Random, DecompressionFunction::DecompressPose, compressed_clips, contexts, cache_flusher, lossy_pose_transforms);
+				write_decompression_performance_stats(logging, per_sample_writer, "forward_pose_cold", PlaybackDirection::Forward, DecompressionFunction::DecompressPose, compressed_clips, contexts, cache_flusher, pose_writer);
+				write_decompression_performance_stats(logging, per_sample_writer, "backward_pose_cold", PlaybackDirection::Backward, DecompressionFunction::DecompressPose, compressed_clips, contexts, cache_flusher, pose_writer);
+				write_decompression_performance_stats(logging, per_sample_writer, "random_pose_cold", PlaybackDirection::Random, DecompressionFunction::DecompressPose, compressed_clips, contexts, cache_flusher, pose_writer);
 
 				// Warm CPU cache, decompress_pose
-				write_decompression_performance_stats(logging, per_sample_writer, "forward_pose_warm", PlaybackDirection::Forward, DecompressionFunction::DecompressPose, compressed_clips, contexts, nullptr, lossy_pose_transforms);
-				write_decompression_performance_stats(logging, per_sample_writer, "backward_pose_warm", PlaybackDirection::Backward, DecompressionFunction::DecompressPose, compressed_clips, contexts, nullptr, lossy_pose_transforms);
-				write_decompression_performance_stats(logging, per_sample_writer, "random_pose_warm", PlaybackDirection::Random, DecompressionFunction::DecompressPose, compressed_clips, contexts, nullptr, lossy_pose_transforms);
+				write_decompression_performance_stats(logging, per_sample_writer, "forward_pose_warm", PlaybackDirection::Forward, DecompressionFunction::DecompressPose, compressed_clips, contexts, nullptr, pose_writer);
+				write_decompression_performance_stats(logging, per_sample_writer, "backward_pose_warm", PlaybackDirection::Backward, DecompressionFunction::DecompressPose, compressed_clips, contexts, nullptr, pose_writer);
+				write_decompression_performance_stats(logging, per_sample_writer, "random_pose_warm", PlaybackDirection::Random, DecompressionFunction::DecompressPose, compressed_clips, contexts, nullptr, pose_writer);
 
 				// Cold CPU cache, decompress_bone
-				write_decompression_performance_stats(logging, per_sample_writer, "forward_bone_cold", PlaybackDirection::Forward, DecompressionFunction::DecompressBone, compressed_clips, contexts, cache_flusher, lossy_pose_transforms);
-				write_decompression_performance_stats(logging, per_sample_writer, "backward_bone_cold", PlaybackDirection::Backward, DecompressionFunction::DecompressBone, compressed_clips, contexts, cache_flusher, lossy_pose_transforms);
-				write_decompression_performance_stats(logging, per_sample_writer, "random_bone_cold", PlaybackDirection::Random, DecompressionFunction::DecompressBone, compressed_clips, contexts, cache_flusher, lossy_pose_transforms);
+				write_decompression_performance_stats(logging, per_sample_writer, "forward_bone_cold", PlaybackDirection::Forward, DecompressionFunction::DecompressBone, compressed_clips, contexts, cache_flusher, pose_writer);
+				write_decompression_performance_stats(logging, per_sample_writer, "backward_bone_cold", PlaybackDirection::Backward, DecompressionFunction::DecompressBone, compressed_clips, contexts, cache_flusher, pose_writer);
+				write_decompression_performance_stats(logging, per_sample_writer, "random_bone_cold", PlaybackDirection::Random, DecompressionFunction::DecompressBone, compressed_clips, contexts, cache_flusher, pose_writer);
 
 				// Warm CPU cache, decompress_bone
-				write_decompression_performance_stats(logging, per_sample_writer, "forward_bone_warm", PlaybackDirection::Forward, DecompressionFunction::DecompressBone, compressed_clips, contexts, nullptr, lossy_pose_transforms);
-				write_decompression_performance_stats(logging, per_sample_writer, "backward_bone_warm", PlaybackDirection::Backward, DecompressionFunction::DecompressBone, compressed_clips, contexts, nullptr, lossy_pose_transforms);
-				write_decompression_performance_stats(logging, per_sample_writer, "random_bone_warm", PlaybackDirection::Random, DecompressionFunction::DecompressBone, compressed_clips, contexts, nullptr, lossy_pose_transforms);
+				write_decompression_performance_stats(logging, per_sample_writer, "forward_bone_warm", PlaybackDirection::Forward, DecompressionFunction::DecompressBone, compressed_clips, contexts, nullptr, pose_writer);
+				write_decompression_performance_stats(logging, per_sample_writer, "backward_bone_warm", PlaybackDirection::Backward, DecompressionFunction::DecompressBone, compressed_clips, contexts, nullptr, pose_writer);
+				write_decompression_performance_stats(logging, per_sample_writer, "random_bone_warm", PlaybackDirection::Random, DecompressionFunction::DecompressBone, compressed_clips, contexts, nullptr, pose_writer);
 			};
 
-			deallocate_type_array(allocator, lossy_pose_transforms, clip_header.num_bones);
 			deallocate_type(allocator, cache_flusher);
 		}
 
-		inline void write_decompression_performance_stats(IAllocator& allocator, const CompressionSettings& settings, const CompressedClip& compressed_clip, StatLogging logging, sjson::ObjectWriter& writer)
+		inline void write_decompression_performance_stats(iallocator& allocator, const compression_settings& settings, const compressed_tracks& compressed_clip, stat_logging logging, sjson::ObjectWriter& writer)
 		{
 			(void)settings;
 
-			switch (compressed_clip.get_algorithm_type())
-			{
-			case algorithm_type8::uniformly_sampled:
-			{
+			if (compressed_clip.get_algorithm_type() != algorithm_type8::uniformly_sampled)
+				return;
+
 #if defined(ACL_HAS_ASSERT_CHECKS)
-				// If we can, we use a fast-path that simulates what a real game engine would use
-				// by disabling the things they normally wouldn't care about like deprecated formats
-				// and debugging features
-				const bool use_uniform_fast_path = settings.rotation_format == rotation_format8::quatf_drop_w_variable
-					&& settings.translation_format == vector_format8::vector3f_variable
-					&& settings.scale_format == vector_format8::vector3f_variable;
-
-				ACL_ASSERT(use_uniform_fast_path, "We do not support profiling the debug code path");
+			// If we can, we use a fast-path that simulates what a real game engine would use
+			// by disabling the things they normally wouldn't care about like deprecated formats
+			// and debugging features
+			const bool use_uniform_fast_path = settings.rotation_format == rotation_format8::quatf_drop_w_variable
+				&& settings.translation_format == vector_format8::vector3f_variable
+				&& settings.scale_format == vector_format8::vector3f_variable;
+
+			ACL_ASSERT(use_uniform_fast_path, "We do not support profiling the debug code path");
 #endif
 
-				CompressedClip* compressed_clips[k_num_decompression_evaluations];
-				for (uint32_t clip_index = 0; clip_index < k_num_decompression_evaluations; ++clip_index)
-				{
-					void* clip = allocator.allocate(compressed_clip.get_size(), alignof(CompressedClip));
-					std::memcpy(clip, &compressed_clip, compressed_clip.get_size());
-					compressed_clips[clip_index] = reinterpret_cast<CompressedClip*>(clip);
-				}
+			compressed_tracks* compressed_clips[k_num_decompression_evaluations];
+			for (uint32_t clip_index = 0; clip_index < k_num_decompression_evaluations; ++clip_index)
+			{
+				void* clip = allocator.allocate(compressed_clip.get_size(), alignof(compressed_tracks));
+				std::memcpy(clip, &compressed_clip, compressed_clip.get_size());
+				compressed_clips[clip_index] = reinterpret_cast<compressed_tracks*>(clip);
+			}
 
-				uniformly_sampled::DecompressionContext<uniformly_sampled::DefaultDecompressionSettings>* contexts[k_num_decompression_evaluations];
-				for (uint32_t clip_index = 0; clip_index < k_num_decompression_evaluations; ++clip_index)
-					contexts[clip_index] = uniformly_sampled::make_decompression_context<uniformly_sampled::DefaultDecompressionSettings>(allocator);
+			decompression_context<default_transform_decompression_settings>* contexts[k_num_decompression_evaluations];
+			for (uint32_t clip_index = 0; clip_index < k_num_decompression_evaluations; ++clip_index)
+				contexts[clip_index] = make_decompression_context<default_transform_decompression_settings>(allocator);
 
-				write_decompression_performance_stats(allocator, compressed_clips, contexts, logging, writer);
+			write_decompression_performance_stats(allocator, compressed_clips, contexts, logging, writer);
 
-				for (uint32_t pass_index = 0; pass_index < k_num_decompression_evaluations; ++pass_index)
-					deallocate_type(allocator, contexts[pass_index]);
+			for (uint32_t pass_index = 0; pass_index < k_num_decompression_evaluations; ++pass_index)
+				deallocate_type(allocator, contexts[pass_index]);
 
-				for (uint32_t clip_index = 0; clip_index < k_num_decompression_evaluations; ++clip_index)
-					allocator.deallocate(compressed_clips[clip_index], compressed_clip.get_size());
-				break;
-			}
-			}
+			for (uint32_t clip_index = 0; clip_index < k_num_decompression_evaluations; ++clip_index)
+				allocator.deallocate(compressed_clips[clip_index], compressed_clip.get_size());
 		}
 	}
 }
diff --git a/includes/acl/compression/impl/write_range_data.h b/includes/acl/compression/impl/write_range_data.h
--- a/includes/acl/compression/impl/write_range_data.h
+++ b/includes/acl/compression/impl/write_range_data.h
@@ -44,7 +44,7 @@ namespace acl
 {
 	namespace acl_impl
 	{
-		inline uint32_t get_stream_range_data_size(const ClipContext& clip_context, range_reduction_flags8 range_reduction, rotation_format8 rotation_format)
+		inline uint32_t get_stream_range_data_size(const clip_context& clip, range_reduction_flags8 range_reduction, rotation_format8 rotation_format)
 		{
 			const uint32_t rotation_size = are_any_enum_flags_set(range_reduction, range_reduction_flags8::rotations) ? get_range_reduction_rotation_size(rotation_format) : 0;
 			const uint32_t translation_size = are_any_enum_flags_set(range_reduction, range_reduction_flags8::translations) ? k_clip_range_reduction_vector3_range_size : 0;
@@ -52,7 +52,7 @@ namespace acl
 			uint32_t range_data_size = 0;
 
 			// Only use the first segment, it contains the necessary information
-			const SegmentContext& segment = clip_context.segments[0];
+			const SegmentContext& segment = clip.segments[0];
 			for (const BoneStreams& bone_stream : segment.const_bone_iterator())
 			{
 				if (!bone_stream.is_rotation_constant)
@@ -100,10 +100,10 @@ namespace acl
 			}
 		}
 
-		inline void write_range_track_data(const BoneStreams* bone_streams, const BoneRanges* bone_ranges,
+		inline uint32_t write_range_track_data(const BoneStreams* bone_streams, const BoneRanges* bone_ranges,
 			range_reduction_flags8 range_reduction, bool is_clip_range_data,
 			uint8_t* range_data, uint32_t range_data_size,
-			const uint16_t* output_bone_mapping, uint16_t num_output_bones)
+			const uint32_t* output_bone_mapping, uint32_t num_output_bones)
 		{
 			ACL_ASSERT(range_data != nullptr, "'range_data' cannot be null!");
 			(void)range_data_size;
@@ -112,9 +112,11 @@ namespace acl
 			const uint8_t* range_data_end = add_offset_to_ptr<uint8_t>(range_data, range_data_size);
 #endif
 
-			for (uint16_t output_index = 0; output_index < num_output_bones; ++output_index)
+			const uint8_t* range_data_start = range_data;
+
+			for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index)
 			{
-				const uint16_t bone_index = output_bone_mapping[output_index];
+				const uint32_t bone_index = output_bone_mapping[output_index];
 				const BoneStreams& bone_stream = bone_streams[bone_index];
 				const BoneRanges& bone_range = bone_ranges[bone_index];
 
@@ -174,19 +176,20 @@ namespace acl
 			}
 
 			ACL_ASSERT(range_data == range_data_end, "Invalid range data offset. Wrote too little data.");
+			return safe_static_cast<uint32_t>(range_data - range_data_start);
 		}
 
-		inline void write_clip_range_data(const ClipContext& clip_context, range_reduction_flags8 range_reduction, uint8_t* range_data, uint32_t range_data_size, const uint16_t* output_bone_mapping, uint16_t num_output_bones)
+		inline uint32_t write_clip_range_data(const clip_context& clip, range_reduction_flags8 range_reduction, uint8_t* range_data, uint32_t range_data_size, const uint32_t* output_bone_mapping, uint32_t num_output_bones)
 		{
 			// Only use the first segment, it contains the necessary information
-			const SegmentContext& segment = clip_context.segments[0];
+			const SegmentContext& segment = clip.segments[0];
 
-			write_range_track_data(segment.bone_streams, clip_context.ranges, range_reduction, true, range_data, range_data_size, output_bone_mapping, num_output_bones);
+			return write_range_track_data(segment.bone_streams, clip.ranges, range_reduction, true, range_data, range_data_size, output_bone_mapping, num_output_bones);
 		}
 
-		inline void write_segment_range_data(const SegmentContext& segment, range_reduction_flags8 range_reduction, uint8_t* range_data, uint32_t range_data_size, const uint16_t* output_bone_mapping, uint16_t num_output_bones)
+		inline uint32_t write_segment_range_data(const SegmentContext& segment, range_reduction_flags8 range_reduction, uint8_t* range_data, uint32_t range_data_size, const uint32_t* output_bone_mapping, uint32_t num_output_bones)
 		{
-			write_range_track_data(segment.bone_streams, segment.ranges, range_reduction, false, range_data, range_data_size, output_bone_mapping, num_output_bones);
+			return write_range_track_data(segment.bone_streams, segment.ranges, range_reduction, false, range_data, range_data_size, output_bone_mapping, num_output_bones);
 		}
 	}
 }
diff --git a/includes/acl/compression/impl/write_segment_data.h b/includes/acl/compression/impl/write_segment_data.h
--- a/includes/acl/compression/impl/write_segment_data.h
+++ b/includes/acl/compression/impl/write_segment_data.h
@@ -26,7 +26,7 @@
 
 #include "acl/core/iallocator.h"
 #include "acl/core/impl/compiler_utils.h"
-#include "acl/core/compressed_clip.h"
+#include "acl/core/impl/compressed_headers.h"
 #include "acl/compression/compression_settings.h"
 #include "acl/compression/impl/clip_context.h"
 #include "acl/compression/impl/segment_context.h"
@@ -41,27 +41,36 @@ namespace acl
 {
 	namespace acl_impl
 	{
-		inline void write_segment_start_indices(const ClipContext& clip_context, uint32_t* segment_start_indices)
+		inline uint32_t write_segment_start_indices(const clip_context& clip, uint32_t* segment_start_indices)
 		{
-			for (uint16_t segment_index = 0; segment_index < clip_context.num_segments; ++segment_index)
+			uint32_t size_written = 0;
+
+			const uint32_t num_segments = clip.num_segments;
+			for (uint32_t segment_index = 0; segment_index < num_segments; ++segment_index)
 			{
-				const SegmentContext& segment = clip_context.segments[segment_index];
+				const SegmentContext& segment = clip.segments[segment_index];
 				segment_start_indices[segment_index] = segment.clip_sample_offset;
+				size_written += sizeof(uint32_t);
 			}
 
 			// Write our sentinel value
-			segment_start_indices[clip_context.num_segments] = 0xFFFFFFFFU;
+			segment_start_indices[clip.num_segments] = 0xFFFFFFFFU;
+			size_written += sizeof(uint32_t);
+
+			return size_written;
 		}
 
-		inline void write_segment_headers(const ClipContext& clip_context, const CompressionSettings& settings, SegmentHeader* segment_headers, uint32_t segment_data_start_offset)
+		inline uint32_t write_segment_headers(const clip_context& clip, const compression_settings& settings, segment_header* segment_headers, uint32_t segment_data_start_offset)
 		{
-			const uint32_t format_per_track_data_size = get_format_per_track_data_size(clip_context, settings.rotation_format, settings.translation_format, settings.scale_format);
+			uint32_t size_written = 0;
+
+			const uint32_t format_per_track_data_size = get_format_per_track_data_size(clip, settings.rotation_format, settings.translation_format, settings.scale_format);
 
 			uint32_t segment_data_offset = segment_data_start_offset;
-			for (uint16_t segment_index = 0; segment_index < clip_context.num_segments; ++segment_index)
+			for (uint16_t segment_index = 0; segment_index < clip.num_segments; ++segment_index)
 			{
-				const SegmentContext& segment = clip_context.segments[segment_index];
-				SegmentHeader& header = segment_headers[segment_index];
+				const SegmentContext& segment = clip.segments[segment_index];
+				segment_header& header = segment_headers[segment_index];
 
 				header.animated_pose_bit_size = segment.animated_pose_bit_size;
 				header.format_per_track_data_offset = segment_data_offset;
@@ -69,34 +78,46 @@ namespace acl
 				header.track_data_offset = align_to(header.range_data_offset + segment.range_data_size, 4);						// Aligned to 4 bytes
 
 				segment_data_offset = header.track_data_offset + segment.animated_data_size;
+				size_written += sizeof(segment_header);
 			}
+
+			return size_written;
 		}
 
-		inline void write_segment_data(const ClipContext& clip_context, const CompressionSettings& settings, range_reduction_flags8 range_reduction, ClipHeader& header, const uint16_t* output_bone_mapping, uint16_t num_output_bones)
+		inline uint32_t write_segment_data(const clip_context& clip, const compression_settings& settings, range_reduction_flags8 range_reduction, transform_tracks_header& header, const uint32_t* output_bone_mapping, uint32_t num_output_bones)
 		{
-			SegmentHeader* segment_headers = header.get_segment_headers();
-			const uint32_t format_per_track_data_size = get_format_per_track_data_size(clip_context, settings.rotation_format, settings.translation_format, settings.scale_format);
+			segment_header* segment_headers = header.get_segment_headers();
+			const uint32_t format_per_track_data_size = get_format_per_track_data_size(clip, settings.rotation_format, settings.translation_format, settings.scale_format);
 
-			for (uint16_t segment_index = 0; segment_index < clip_context.num_segments; ++segment_index)
+			uint32_t size_written = 0;
+
+			const uint32_t num_segments = clip.num_segments;
+			for (uint32_t segment_index = 0; segment_index < num_segments; ++segment_index)
 			{
-				const SegmentContext& segment = clip_context.segments[segment_index];
-				SegmentHeader& segment_header = segment_headers[segment_index];
+				const SegmentContext& segment = clip.segments[segment_index];
+				segment_header& segment_header_ = segment_headers[segment_index];
 
 				if (format_per_track_data_size > 0)
-					write_format_per_track_data(segment, header.get_format_per_track_data(segment_header), format_per_track_data_size, output_bone_mapping, num_output_bones);
+					size_written += write_format_per_track_data(segment, header.get_format_per_track_data(segment_header_), format_per_track_data_size, output_bone_mapping, num_output_bones);
 				else
-					segment_header.format_per_track_data_offset = InvalidPtrOffset();
+					segment_header_.format_per_track_data_offset = invalid_ptr_offset();
+
+				size_written = align_to(size_written, 2);	// Align range data
 
 				if (segment.range_data_size > 0)
-					write_segment_range_data(segment, range_reduction, header.get_segment_range_data(segment_header), segment.range_data_size, output_bone_mapping, num_output_bones);
+					size_written += write_segment_range_data(segment, range_reduction, header.get_segment_range_data(segment_header_), segment.range_data_size, output_bone_mapping, num_output_bones);
 				else
-					segment_header.range_data_offset = InvalidPtrOffset();
+					segment_header_.range_data_offset = invalid_ptr_offset();
+
+				size_written = align_to(size_written, 4);	// Align animated data
 
 				if (segment.animated_data_size > 0)
-					write_animated_track_data(segment, header.get_track_data(segment_header), segment.animated_data_size, output_bone_mapping, num_output_bones);
+					size_written += write_animated_track_data(segment, header.get_track_data(segment_header_), segment.animated_data_size, output_bone_mapping, num_output_bones);
 				else
-					segment_header.track_data_offset = InvalidPtrOffset();
+					segment_header_.track_data_offset = invalid_ptr_offset();
 			}
+
+			return size_written;
 		}
 	}
 }
diff --git a/includes/acl/compression/impl/write_stats.h b/includes/acl/compression/impl/write_stats.h
--- a/includes/acl/compression/impl/write_stats.h
+++ b/includes/acl/compression/impl/write_stats.h
@@ -26,14 +26,12 @@
 
 #if defined(SJSON_CPP_WRITER)
 
+#include "acl/core/utils.h"
 #include "acl/core/impl/compiler_utils.h"
 #include "acl/core/impl/memory_cache.h"
-#include "acl/core/utils.h"
-#include "acl/algorithm/uniformly_sampled/decoder.h"
-#include "acl/decompression/default_output_writer.h"
+#include "acl/compression/transform_error_metrics.h"
+#include "acl/compression/track_error.h"
 #include "acl/compression/impl/clip_context.h"
-#include "acl/compression/skeleton_error_metric.h"
-#include "acl/compression/utils.h"
 
 #include <chrono>
 #include <cstdint>
@@ -99,9 +97,9 @@ namespace acl
 			writer["decomp_touched_cache_lines"] = segment.clip->decomp_touched_cache_lines + num_segment_header_cache_lines + num_animated_pose_cache_lines;
 		}
 
-		inline void write_exhaustive_segment_stats(IAllocator& allocator, const SegmentContext& segment, const ClipContext& raw_clip_context, const ClipContext& additive_base_clip_context, const RigidSkeleton& skeleton, const CompressionSettings& settings, sjson::ObjectWriter& writer)
+		inline void write_exhaustive_segment_stats(iallocator& allocator, const SegmentContext& segment, const clip_context& raw_clip_context, const clip_context& additive_base_clip_context, const compression_settings& settings, const track_array_qvvf& track_list, sjson::ObjectWriter& writer)
 		{
-			const uint16_t num_bones = skeleton.get_num_bones();
+			const uint16_t num_bones = raw_clip_context.num_bones;
 			const bool has_scale = segment_context_has_scale(segment);
 
 			ACL_ASSERT(!settings.error_metric->needs_conversion(has_scale), "Error metric conversion not supported");
@@ -122,8 +120,10 @@ namespace acl
 
 			for (uint16_t transform_index = 0; transform_index < num_bones; ++transform_index)
 			{
-				const RigidBone& bone = skeleton.get_bone(transform_index);
-				parent_transform_indices[transform_index] = bone.parent_index;
+				const track_qvvf& track = track_list[transform_index];
+				const track_desc_transformf& desc = track.get_description();
+
+				parent_transform_indices[transform_index] = desc.parent_index == k_invalid_track_index ? k_invalid_bone_index : safe_static_cast<uint16_t>(desc.parent_index);
 				self_transform_indices[transform_index] = transform_index;
 			}
 
@@ -150,7 +150,7 @@ namespace acl
 			itransform_error_metric::local_to_object_space_args local_to_object_space_args_lossy = local_to_object_space_args_raw;
 			local_to_object_space_args_lossy.local_transforms = lossy_local_pose;
 
-			BoneError worst_bone_error;
+			track_error worst_bone_error;
 
 			writer["error_per_frame_and_bone"] = [&](sjson::ArrayWriter& frames_writer)
 			{
@@ -176,28 +176,29 @@ namespace acl
 
 					frames_writer.push_newline();
 					frames_writer.push([&](sjson::ArrayWriter& frame_writer)
-					{
-						for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
 						{
-							const RigidBone& bone = skeleton.get_bone(bone_index);
+							for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
+							{
+								const track_qvvf& track = track_list[bone_index];
+								const track_desc_transformf& desc = track.get_description();
 
-							itransform_error_metric::calculate_error_args calculate_error_args;
-							calculate_error_args.transform0 = raw_object_pose + bone_index;
-							calculate_error_args.transform1 = lossy_object_pose + bone_index;
-							calculate_error_args.construct_sphere_shell(bone.vertex_distance);
+								itransform_error_metric::calculate_error_args calculate_error_args;
+								calculate_error_args.transform0 = raw_object_pose + bone_index;
+								calculate_error_args.transform1 = lossy_object_pose + bone_index;
+								calculate_error_args.construct_sphere_shell(desc.shell_distance);
 
-							const float error = rtm::scalar_cast(calculate_error_impl(settings.error_metric, calculate_error_args));
+								const float error = rtm::scalar_cast(calculate_error_impl(settings.error_metric, calculate_error_args));
 
-							frame_writer.push(error);
+								frame_writer.push(error);
 
-							if (error > worst_bone_error.error)
-							{
-								worst_bone_error.error = error;
-								worst_bone_error.index = bone_index;
-								worst_bone_error.sample_time = sample_time;
+								if (error > worst_bone_error.error)
+								{
+									worst_bone_error.error = error;
+									worst_bone_error.index = bone_index;
+									worst_bone_error.sample_time = sample_time;
+								}
 							}
-						}
-					});
+						});
 				}
 			};
 
@@ -216,35 +217,35 @@ namespace acl
 			deallocate_type_array(allocator, self_transform_indices, num_bones);
 		}
 
-		inline void write_stats(IAllocator& allocator, const AnimationClip& clip, const ClipContext& clip_context, const RigidSkeleton& skeleton,
-			const CompressedClip& compressed_clip, const CompressionSettings& settings, const ClipHeader& header, const ClipContext& raw_clip_context,
-			const ClipContext& additive_base_clip_context, const ScopeProfiler& compression_time,
-			OutputStats& stats)
+		inline void write_stats(iallocator& allocator, const track_array_qvvf& track_list, const clip_context& clip,
+			const compressed_tracks& compressed_clip, const compression_settings& settings, const clip_context& raw_clip,
+			const clip_context& additive_base_clip_context, const scope_profiler& compression_time,
+			output_stats& stats)
 		{
 			ACL_ASSERT(stats.writer != nullptr, "Attempted to log stats without a writer");
 
-			const uint32_t raw_size = clip.get_raw_size();
+			const uint32_t raw_size = track_list.get_raw_size();
 			const uint32_t compressed_size = compressed_clip.get_size();
 			const double compression_ratio = double(raw_size) / double(compressed_size);
 
 			sjson::ObjectWriter& writer = *stats.writer;
 			writer["algorithm_name"] = get_algorithm_name(algorithm_type8::uniformly_sampled);
 			writer["algorithm_uid"] = settings.get_hash();
-			writer["clip_name"] = clip.get_name().c_str();
+			//writer["clip_name"] = clip.get_name().c_str();
 			writer["raw_size"] = raw_size;
 			writer["compressed_size"] = compressed_size;
 			writer["compression_ratio"] = compression_ratio;
 			writer["compression_time"] = compression_time.get_elapsed_seconds();
-			writer["duration"] = clip.get_duration();
-			writer["num_samples"] = clip.get_num_samples();
-			writer["num_bones"] = clip.get_num_bones();
+			writer["duration"] = track_list.get_duration();
+			writer["num_samples"] = track_list.get_num_samples_per_track();
+			writer["num_bones"] = track_list.get_num_tracks();
 			writer["rotation_format"] = get_rotation_format_name(settings.rotation_format);
 			writer["translation_format"] = get_vector_format_name(settings.translation_format);
 			writer["scale_format"] = get_vector_format_name(settings.scale_format);
-			writer["has_scale"] = clip_context.has_scale;
+			writer["has_scale"] = clip.has_scale;
 			writer["error_metric"] = settings.error_metric->get_name();
 
-			if (are_all_enum_flags_set(stats.logging, StatLogging::Detailed) || are_all_enum_flags_set(stats.logging, StatLogging::Exhaustive))
+			if (are_all_enum_flags_set(stats.logging, stat_logging::Detailed) || are_all_enum_flags_set(stats.logging, stat_logging::Exhaustive))
 			{
 				uint32_t num_default_rotation_tracks = 0;
 				uint32_t num_default_translation_tracks = 0;
@@ -256,7 +257,7 @@ namespace acl
 				uint32_t num_animated_translation_tracks = 0;
 				uint32_t num_animated_scale_tracks = 0;
 
-				for (const BoneStreams& bone_stream : clip_context.segments[0].bone_iterator())
+				for (const BoneStreams& bone_stream : clip.segments[0].bone_iterator())
 				{
 					if (bone_stream.is_rotation_default)
 						num_default_rotation_tracks++;
@@ -303,24 +304,24 @@ namespace acl
 
 			writer["segmenting"] = [&](sjson::ObjectWriter& segmenting_writer)
 			{
-				segmenting_writer["num_segments"] = header.num_segments;
+				segmenting_writer["num_segments"] = clip.num_segments;
 				segmenting_writer["ideal_num_samples"] = settings.segmenting.ideal_num_samples;
 				segmenting_writer["max_num_samples"] = settings.segmenting.max_num_samples;
 			};
 
 			writer["segments"] = [&](sjson::ArrayWriter& segments_writer)
 			{
-				for (const SegmentContext& segment : clip_context.const_segment_iterator())
+				for (const SegmentContext& segment : clip.const_segment_iterator())
 				{
 					segments_writer.push([&](sjson::ObjectWriter& segment_writer)
 						{
 							write_summary_segment_stats(segment, settings.rotation_format, settings.translation_format, settings.scale_format, segment_writer);
 
-							if (are_all_enum_flags_set(stats.logging, StatLogging::Detailed))
+							if (are_all_enum_flags_set(stats.logging, stat_logging::Detailed))
 								write_detailed_segment_stats(segment, segment_writer);
 
-							if (are_all_enum_flags_set(stats.logging, StatLogging::Exhaustive))
-								write_exhaustive_segment_stats(allocator, segment, raw_clip_context, additive_base_clip_context, skeleton, settings, segment_writer);
+							if (are_all_enum_flags_set(stats.logging, stat_logging::Exhaustive))
+								write_exhaustive_segment_stats(allocator, segment, raw_clip, additive_base_clip_context, settings, track_list, segment_writer);
 						});
 				}
 			};
diff --git a/includes/acl/compression/impl/write_stream_bitsets.h b/includes/acl/compression/impl/write_stream_bitsets.h
--- a/includes/acl/compression/impl/write_stream_bitsets.h
+++ b/includes/acl/compression/impl/write_stream_bitsets.h
@@ -37,56 +37,64 @@ namespace acl
 {
 	namespace acl_impl
 	{
-		inline void write_default_track_bitset(const ClipContext& clip_context, uint32_t* default_tracks_bitset, BitSetDescription bitset_desc, const uint16_t* output_bone_mapping, uint16_t num_output_bones)
+		inline uint32_t write_default_track_bitset(const clip_context& clip, uint32_t* default_tracks_bitset, bitset_description bitset_desc, const uint32_t* output_bone_mapping, uint32_t num_output_bones)
 		{
 			ACL_ASSERT(default_tracks_bitset != nullptr, "'default_tracks_bitset' cannot be null!");
 
 			// Only use the first segment, it contains the necessary information
-			const SegmentContext& segment = clip_context.segments[0];
+			const SegmentContext& segment = clip.segments[0];
 
 			uint32_t default_track_offset = 0;
+			uint32_t size_written = 0;
 
 			bitset_reset(default_tracks_bitset, bitset_desc, false);
 
-			for (uint16_t output_index = 0; output_index < num_output_bones; ++output_index)
+			for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index)
 			{
-				const uint16_t bone_index = output_bone_mapping[output_index];
+				const uint32_t bone_index = output_bone_mapping[output_index];
 				const BoneStreams& bone_stream = segment.bone_streams[bone_index];
 
 				bitset_set(default_tracks_bitset, bitset_desc, default_track_offset++, bone_stream.is_rotation_default);
 				bitset_set(default_tracks_bitset, bitset_desc, default_track_offset++, bone_stream.is_translation_default);
 
-				if (clip_context.has_scale)
+				if (clip.has_scale)
 					bitset_set(default_tracks_bitset, bitset_desc, default_track_offset++, bone_stream.is_scale_default);
+
+				size_written += clip.has_scale ? 3 : 2;
 			}
 
 			ACL_ASSERT(default_track_offset <= bitset_desc.get_num_bits(), "Too many tracks found for bitset");
+			return ((size_written + 31) / 32) * sizeof(uint32_t);	// Convert bits to bytes
 		}
 
-		inline void write_constant_track_bitset(const ClipContext& clip_context, uint32_t* constant_tracks_bitset, BitSetDescription bitset_desc, const uint16_t* output_bone_mapping, uint16_t num_output_bones)
+		inline uint32_t write_constant_track_bitset(const clip_context& clip, uint32_t* constant_tracks_bitset, bitset_description bitset_desc, const uint32_t* output_bone_mapping, uint32_t num_output_bones)
 		{
 			ACL_ASSERT(constant_tracks_bitset != nullptr, "'constant_tracks_bitset' cannot be null!");
 
 			// Only use the first segment, it contains the necessary information
-			const SegmentContext& segment = clip_context.segments[0];
+			const SegmentContext& segment = clip.segments[0];
 
 			uint32_t constant_track_offset = 0;
+			uint32_t size_written = 0;
 
 			bitset_reset(constant_tracks_bitset, bitset_desc, false);
 
-			for (uint16_t output_index = 0; output_index < num_output_bones; ++output_index)
+			for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index)
 			{
-				const uint16_t bone_index = output_bone_mapping[output_index];
+				const uint32_t bone_index = output_bone_mapping[output_index];
 				const BoneStreams& bone_stream = segment.bone_streams[bone_index];
 
 				bitset_set(constant_tracks_bitset, bitset_desc, constant_track_offset++, bone_stream.is_rotation_constant);
 				bitset_set(constant_tracks_bitset, bitset_desc, constant_track_offset++, bone_stream.is_translation_constant);
 
-				if (clip_context.has_scale)
+				if (clip.has_scale)
 					bitset_set(constant_tracks_bitset, bitset_desc, constant_track_offset++, bone_stream.is_scale_constant);
+
+				size_written += clip.has_scale ? 3 : 2;
 			}
 
 			ACL_ASSERT(constant_track_offset <= bitset_desc.get_num_bits(), "Too many tracks found for bitset");
+			return ((size_written + 31) / 32) * sizeof(uint32_t);	// Convert bits to bytes
 		}
 	}
 }
diff --git a/includes/acl/compression/impl/write_stream_data.h b/includes/acl/compression/impl/write_stream_data.h
--- a/includes/acl/compression/impl/write_stream_data.h
+++ b/includes/acl/compression/impl/write_stream_data.h
@@ -27,7 +27,6 @@
 #include "acl/core/impl/compiler_utils.h"
 #include "acl/core/iallocator.h"
 #include "acl/core/error.h"
-#include "acl/core/compressed_clip.h"
 #include "acl/compression/impl/clip_context.h"
 
 #include <cstdint>
@@ -38,16 +37,16 @@ namespace acl
 {
 	namespace acl_impl
 	{
-		inline uint32_t get_constant_data_size(const ClipContext& clip_context, const uint16_t* output_bone_mapping, uint16_t num_output_bones)
+		inline uint32_t get_constant_data_size(const clip_context& clip, const uint32_t* output_bone_mapping, uint32_t num_output_bones)
 		{
 			// Only use the first segment, it contains the necessary information
-			const SegmentContext& segment = clip_context.segments[0];
+			const SegmentContext& segment = clip.segments[0];
 
 			uint32_t constant_data_size = 0;
 
-			for (uint16_t output_index = 0; output_index < num_output_bones; ++output_index)
+			for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index)
 			{
-				const uint16_t bone_index = output_bone_mapping[output_index];
+				const uint32_t bone_index = output_bone_mapping[output_index];
 				const BoneStreams& bone_stream = segment.bone_streams[bone_index];
 
 				if (!bone_stream.is_rotation_default && bone_stream.is_rotation_constant)
@@ -56,7 +55,7 @@ namespace acl
 				if (!bone_stream.is_translation_default && bone_stream.is_translation_constant)
 					constant_data_size += bone_stream.translations.get_packed_sample_size();
 
-				if (clip_context.has_scale && !bone_stream.is_scale_default && bone_stream.is_scale_constant)
+				if (clip.has_scale && !bone_stream.is_scale_default && bone_stream.is_scale_constant)
 					constant_data_size += bone_stream.scales.get_packed_sample_size();
 			}
 
@@ -87,16 +86,16 @@ namespace acl
 			}
 		}
 
-		inline void calculate_animated_data_size(ClipContext& clip_context, const uint16_t* output_bone_mapping, uint16_t num_output_bones)
+		inline void calculate_animated_data_size(clip_context& clip, const uint32_t* output_bone_mapping, uint32_t num_output_bones)
 		{
-			for (SegmentContext& segment : clip_context.segment_iterator())
+			for (SegmentContext& segment : clip.segment_iterator())
 			{
 				uint32_t num_animated_data_bits = 0;
 				uint32_t num_animated_pose_bits = 0;
 
-				for (uint16_t output_index = 0; output_index < num_output_bones; ++output_index)
+				for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index)
 				{
-					const uint16_t bone_index = output_bone_mapping[output_index];
+					const uint32_t bone_index = output_bone_mapping[output_index];
 					const BoneStreams& bone_stream = segment.bone_streams[bone_index];
 
 					if (!bone_stream.is_rotation_constant)
@@ -114,14 +113,14 @@ namespace acl
 			}
 		}
 
-		inline uint32_t get_format_per_track_data_size(const ClipContext& clip_context, rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format)
+		inline uint32_t get_format_per_track_data_size(const clip_context& clip, rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format)
 		{
 			const bool is_rotation_variable = is_rotation_format_variable(rotation_format);
 			const bool is_translation_variable = is_vector_format_variable(translation_format);
 			const bool is_scale_variable = is_vector_format_variable(scale_format);
 
 			// Only use the first segment, it contains the necessary information
-			const SegmentContext& segment = clip_context.segments[0];
+			const SegmentContext& segment = clip.segments[0];
 
 			uint32_t format_per_track_data_size = 0;
 
@@ -143,21 +142,23 @@ namespace acl
 			return format_per_track_data_size;
 		}
 
-		inline void write_constant_track_data(const ClipContext& clip_context, uint8_t* constant_data, uint32_t constant_data_size, const uint16_t* output_bone_mapping, uint16_t num_output_bones)
+		inline uint32_t write_constant_track_data(const clip_context& clip, uint8_t* constant_data, uint32_t constant_data_size, const uint32_t* output_bone_mapping, uint32_t num_output_bones)
 		{
 			ACL_ASSERT(constant_data != nullptr, "'constant_data' cannot be null!");
 			(void)constant_data_size;
 
 			// Only use the first segment, it contains the necessary information
-			const SegmentContext& segment = clip_context.segments[0];
+			const SegmentContext& segment = clip.segments[0];
 
 #if defined(ACL_HAS_ASSERT_CHECKS)
 			const uint8_t* constant_data_end = add_offset_to_ptr<uint8_t>(constant_data, constant_data_size);
 #endif
 
-			for (uint16_t output_index = 0; output_index < num_output_bones; ++output_index)
+			const uint8_t* constant_data_start = constant_data;
+
+			for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index)
 			{
-				const uint16_t bone_index = output_bone_mapping[output_index];
+				const uint32_t bone_index = output_bone_mapping[output_index];
 				const BoneStreams& bone_stream = segment.bone_streams[bone_index];
 
 				if (!bone_stream.is_rotation_default && bone_stream.is_rotation_constant)
@@ -176,7 +177,7 @@ namespace acl
 					constant_data += sample_size;
 				}
 
-				if (clip_context.has_scale && !bone_stream.is_scale_default && bone_stream.is_scale_constant)
+				if (clip.has_scale && !bone_stream.is_scale_default && bone_stream.is_scale_constant)
 				{
 					const uint8_t* scale_ptr = bone_stream.scales.get_raw_sample_ptr(0);
 					uint32_t sample_size = bone_stream.scales.get_sample_size();
@@ -188,6 +189,7 @@ namespace acl
 			}
 
 			ACL_ASSERT(constant_data == constant_data_end, "Invalid constant data offset. Wrote too little data.");
+			return safe_static_cast<uint32_t>(constant_data - constant_data_start);
 		}
 
 		inline void write_animated_track_data(const TrackStream& track_stream, uint32_t sample_index, uint8_t* animated_track_data_begin, uint8_t*& out_animated_track_data, uint64_t& out_bit_offset)
@@ -246,7 +248,7 @@ namespace acl
 			}
 		}
 
-		inline void write_animated_track_data(const SegmentContext& segment, uint8_t* animated_track_data, uint32_t animated_data_size, const uint16_t* output_bone_mapping, uint16_t num_output_bones)
+		inline uint32_t write_animated_track_data(const SegmentContext& segment, uint8_t* animated_track_data, uint32_t animated_data_size, const uint32_t* output_bone_mapping, uint32_t num_output_bones)
 		{
 			ACL_ASSERT(animated_track_data != nullptr, "'animated_track_data' cannot be null!");
 			(void)animated_data_size;
@@ -257,15 +259,16 @@ namespace acl
 			const uint8_t* animated_track_data_end = add_offset_to_ptr<uint8_t>(animated_track_data, animated_data_size);
 #endif
 
+			const uint8_t* animated_track_data_start = animated_track_data;
 			uint64_t bit_offset = 0;
 
 			// Data is sorted first by time, second by bone.
 			// This ensures that all bones are contiguous in memory when we sample a particular time.
 			for (uint32_t sample_index = 0; sample_index < segment.num_samples; ++sample_index)
 			{
-				for (uint16_t output_index = 0; output_index < num_output_bones; ++output_index)
+				for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index)
 				{
-					const uint16_t bone_index = output_bone_mapping[output_index];
+					const uint32_t bone_index = output_bone_mapping[output_index];
 					const BoneStreams& bone_stream = segment.bone_streams[bone_index];
 
 					if (!bone_stream.is_rotation_constant && !is_constant_bit_rate(bone_stream.rotations.get_bit_rate()))
@@ -285,9 +288,10 @@ namespace acl
 				animated_track_data = animated_track_data_begin + (align_to(bit_offset, 8) / 8);
 
 			ACL_ASSERT(animated_track_data == animated_track_data_end, "Invalid animated track data offset. Wrote too little data.");
+			return safe_static_cast<uint32_t>(animated_track_data - animated_track_data_start);
 		}
 
-		inline void write_format_per_track_data(const SegmentContext& segment, uint8_t* format_per_track_data, uint32_t format_per_track_data_size, const uint16_t* output_bone_mapping, uint16_t num_output_bones)
+		inline uint32_t write_format_per_track_data(const SegmentContext& segment, uint8_t* format_per_track_data, uint32_t format_per_track_data_size, const uint32_t* output_bone_mapping, uint32_t num_output_bones)
 		{
 			ACL_ASSERT(format_per_track_data != nullptr, "'format_per_track_data' cannot be null!");
 			(void)format_per_track_data_size;
@@ -296,9 +300,11 @@ namespace acl
 			const uint8_t* format_per_track_data_end = add_offset_to_ptr<uint8_t>(format_per_track_data, format_per_track_data_size);
 #endif
 
-			for (uint16_t output_index = 0; output_index < num_output_bones; ++output_index)
+			const uint8_t* format_per_track_data_start = format_per_track_data;
+
+			for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index)
 			{
-				const uint16_t bone_index = output_bone_mapping[output_index];
+				const uint32_t bone_index = output_bone_mapping[output_index];
 				const BoneStreams& bone_stream = segment.bone_streams[bone_index];
 
 				if (!bone_stream.is_rotation_constant && bone_stream.rotations.is_bit_rate_variable())
@@ -314,6 +320,8 @@ namespace acl
 			}
 
 			ACL_ASSERT(format_per_track_data == format_per_track_data_end, "Invalid format per track data offset. Wrote too little data.");
+
+			return safe_static_cast<uint32_t>(format_per_track_data - format_per_track_data_start);
 		}
 	}
 }
diff --git a/includes/acl/compression/output_stats.h b/includes/acl/compression/output_stats.h
--- a/includes/acl/compression/output_stats.h
+++ b/includes/acl/compression/output_stats.h
@@ -33,7 +33,7 @@ ACL_IMPL_FILE_PRAGMA_PUSH
 
 namespace acl
 {
-	enum class StatLogging
+	enum class stat_logging
 	{
 		None						= 0x0000,
 		Summary						= 0x0001,
@@ -43,14 +43,14 @@ namespace acl
 		ExhaustiveDecompression		= 0x0020,
 	};
 
-	ACL_IMPL_ENUM_FLAGS_OPERATORS(StatLogging)
+	ACL_IMPL_ENUM_FLAGS_OPERATORS(stat_logging)
 
-	struct OutputStats
+	struct output_stats
 	{
-		OutputStats() : logging(StatLogging::None), writer(nullptr) {}
-		OutputStats(StatLogging logging_, sjson::ObjectWriter* writer_) : logging(logging_), writer(writer_) {}
+		output_stats() : logging(stat_logging::None), writer(nullptr) {}
+		output_stats(stat_logging logging_, sjson::ObjectWriter* writer_) : logging(logging_), writer(writer_) {}
 
-		StatLogging				logging;
+		stat_logging				logging;
 		sjson::ObjectWriter*	writer;
 	};
 }
diff --git a/includes/acl/compression/skeleton.h b/includes/acl/compression/skeleton.h
deleted file mode 100644
--- a/includes/acl/compression/skeleton.h
+++ /dev/null
@@ -1,359 +0,0 @@
-#pragma once
-
-////////////////////////////////////////////////////////////////////////////////
-// The MIT License (MIT)
-//
-// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-////////////////////////////////////////////////////////////////////////////////
-
-#include "acl/core/bitset.h"
-#include "acl/core/impl/compiler_utils.h"
-#include "acl/core/error.h"
-#include "acl/core/iallocator.h"
-#include "acl/core/string.h"
-
-#include <rtm/qvvd.h>
-
-#include <cstdint>
-
-ACL_IMPL_FILE_PRAGMA_PUSH
-
-namespace acl
-{
-	//////////////////////////////////////////////////////////////////////////
-	// We only support up to 65534 bones, we reserve 65535 for the invalid index
-	constexpr uint16_t k_invalid_bone_index = 0xFFFF;
-
-	namespace acl_impl
-	{
-		//////////////////////////////////////////////////////////////////////////
-		// Simple iterator utility class to allow easy looping
-		class BoneChainIterator
-		{
-		public:
-			BoneChainIterator(const uint32_t* bone_chain, BitSetDescription bone_chain_desc, uint16_t bone_index, uint16_t offset)
-				: m_bone_chain(bone_chain)
-				, m_bone_chain_desc(bone_chain_desc)
-				, m_bone_index(bone_index)
-				, m_offset(offset)
-			{}
-
-			BoneChainIterator& operator++()
-			{
-				ACL_ASSERT(m_offset <= m_bone_index, "Cannot increment the iterator, it is no longer valid");
-
-				// Skip the current bone
-				m_offset++;
-
-				// Iterate until we find the next bone part of the chain or until we reach the end of the chain
-				// TODO: Use clz or similar to find the next set bit starting at the current index
-				while (m_offset < m_bone_index && !bitset_test(m_bone_chain, m_bone_chain_desc, m_offset))
-					m_offset++;
-
-				return *this;
-			}
-
-			uint16_t operator*() const
-			{
-				ACL_ASSERT(m_offset <= m_bone_index, "Returned bone index doesn't belong to the bone chain");
-				ACL_ASSERT(bitset_test(m_bone_chain, m_bone_chain_desc, m_offset), "Returned bone index doesn't belong to the bone chain");
-				return m_offset;
-			}
-
-			// We only compare the offset in the bone chain. Two iterators on the same bone index
-			// from two different or equal chains will be equal.
-			bool operator==(const BoneChainIterator& other) const { return m_offset == other.m_offset; }
-			bool operator!=(const BoneChainIterator& other) const { return m_offset != other.m_offset; }
-
-		private:
-			const uint32_t*		m_bone_chain;
-			BitSetDescription	m_bone_chain_desc;
-			uint16_t			m_bone_index;
-			uint16_t			m_offset;
-		};
-	}
-
-	//////////////////////////////////////////////////////////////////////////
-	// Simple bone chain container to allow easy looping
-	//
-	// A bone chain allows looping over all bones up to a specific bone starting
-	// at the root bone.
-	//////////////////////////////////////////////////////////////////////////
-	struct BoneChain
-	{
-		BoneChain(const uint32_t* bone_chain, BitSetDescription bone_chain_desc, uint16_t bone_index)
-			: m_bone_chain(bone_chain)
-			, m_bone_chain_desc(bone_chain_desc)
-			, m_bone_index(bone_index)
-		{
-			// We don't know where this bone chain starts, find the root bone
-			// TODO: Use clz or similar to find the next set bit starting at the current index
-			uint16_t root_index = 0;
-			while (!bitset_test(bone_chain, bone_chain_desc, root_index))
-				root_index++;
-
-			m_root_index = root_index;
-		}
-
-		acl_impl::BoneChainIterator begin() const { return acl_impl::BoneChainIterator(m_bone_chain, m_bone_chain_desc, m_bone_index, m_root_index); }
-		acl_impl::BoneChainIterator end() const { return acl_impl::BoneChainIterator(m_bone_chain, m_bone_chain_desc, m_bone_index, m_bone_index + 1); }
-
-		const uint32_t*		m_bone_chain;
-		BitSetDescription	m_bone_chain_desc;
-		uint16_t			m_root_index;
-		uint16_t			m_bone_index;
-	};
-
-	//////////////////////////////////////////////////////////////////////////
-	// A rigid bone description
-	//
-	// Bones are organized in a tree with a single root bone. Each bone has
-	// one or more children and every bone except the root has a single parent.
-	//////////////////////////////////////////////////////////////////////////
-	struct alignas(16) RigidBone
-	{
-		//////////////////////////////////////////////////////////////////////////
-		// Default constructor, initializes a simple root bone with no name
-		RigidBone() noexcept
-			: name()
-			, bone_chain(nullptr)
-			, vertex_distance(1.0F)
-			, parent_index(k_invalid_bone_index)
-			, bind_transform(rtm::qvv_identity())
-		{
-			(void)padding;
-		}
-
-		~RigidBone() = default;
-
-		RigidBone(RigidBone&& other) noexcept
-			: name(std::move(other.name))
-			, bone_chain(other.bone_chain)
-			, vertex_distance(other.vertex_distance)
-			, parent_index(other.parent_index)
-			, bind_transform(other.bind_transform)
-		{
-			new(&other) RigidBone();
-		}
-
-		RigidBone& operator=(RigidBone&& other) noexcept
-		{
-			std::swap(name, other.name);
-			std::swap(bone_chain, other.bone_chain);
-			std::swap(vertex_distance, other.vertex_distance);
-			std::swap(parent_index, other.parent_index);
-			std::swap(bind_transform, other.bind_transform);
-
-			return *this;
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns whether or not this bone is a root bone
-		bool is_root() const { return parent_index == k_invalid_bone_index; }
-
-		// Name of the bone (used for debugging purposes only)
-		String			name;
-
-		// A bit set, a set bit at index X indicates the bone at index X is in the chain
-		// This can be used to iterate on the bone chain efficiently from root to the current bone
-		const uint32_t*	bone_chain;
-
-		// Virtual vertex distance used by hierarchical error function
-		// The error metric measures the error of a virtual vertex at this
-		// distance from the bone in object space
-		float			vertex_distance;
-
-		// The parent bone index or an invalid bone index for the root bone
-		// TODO: Introduce a type for bone indices
-		uint16_t		parent_index;
-
-		// Unused memory left as padding
-		uint8_t			padding[2];
-
-		// The bind transform is in its parent's local space
-		// Note that the scale is ignored and this value is only used by the additive error metrics
-		rtm::qvvd		bind_transform;
-	};
-
-	//////////////////////////////////////////////////////////////////////////
-	// A rigid skeleton made up of a tree of rigid bones
-	//
-	// This hierarchical structure is important and forms the back bone of the
-	// error metrics. When calculating the error introduced by lowering the
-	// precision of a single bone track, we will walk up the hierarchy and
-	// calculate the error relative to the root bones (object/mesh space).
-	//////////////////////////////////////////////////////////////////////////
-	class RigidSkeleton
-	{
-	public:
-		//////////////////////////////////////////////////////////////////////////
-		// Constructs a RigidSkeleton instance and moves the data from the input
-		// 'bones' into the skeleton instance (destructive operation on the input array).
-		//    - allocator: The allocator instance to use to allocate and free memory
-		//    - bones: An array of bones to initialize the skeleton with
-		//    - num_bones: The number of input bones
-		RigidSkeleton(IAllocator& allocator, RigidBone* bones, uint16_t num_bones)
-			: m_allocator(allocator)
-			, m_bones(allocate_type_array<RigidBone>(allocator, num_bones))
-			, m_num_bones(num_bones)
-		{
-			// Calculate which bones are leaf bones that have no children
-			BitSetDescription bone_bitset_desc = BitSetDescription::make_from_num_bits(num_bones);
-			uint32_t* is_leaf_bitset = allocate_type_array<uint32_t>(allocator, bone_bitset_desc.get_size());
-			bitset_reset(is_leaf_bitset, bone_bitset_desc, false);
-
-			// By default  and if we find a child, we'll mark it as non-leaf
-			bitset_set_range(is_leaf_bitset, bone_bitset_desc, 0, num_bones, true);
-
-#if defined(ACL_HAS_ASSERT_CHECKS)
-			uint32_t num_root_bones = 0;
-#endif
-
-			// Move and validate the input data
-			for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
-			{
-				RigidBone& bone = bones[bone_index];
-
-				const bool is_root = bone.parent_index == k_invalid_bone_index;
-
-				ACL_ASSERT(bone.bone_chain == nullptr, "Bone chain should be calculated internally");
-				ACL_ASSERT(is_root || bone.parent_index < bone_index, "Bones must be sorted parent first");
-				ACL_ASSERT(rtm::quat_is_finite(bone.bind_transform.rotation), "Bind rotation is invalid: [%f, %f, %f, %f]", (float)rtm::quat_get_x(bone.bind_transform.rotation), (float)rtm::quat_get_y(bone.bind_transform.rotation), (float)rtm::quat_get_z(bone.bind_transform.rotation), (float)rtm::quat_get_w(bone.bind_transform.rotation));
-				ACL_ASSERT(rtm::quat_is_normalized(bone.bind_transform.rotation), "Bind rotation isn't normalized: [%f, %f, %f, %f]", (float)rtm::quat_get_x(bone.bind_transform.rotation), (float)rtm::quat_get_y(bone.bind_transform.rotation), (float)rtm::quat_get_z(bone.bind_transform.rotation), (float)rtm::quat_get_w(bone.bind_transform.rotation));
-				ACL_ASSERT(rtm::vector_is_finite3(bone.bind_transform.translation), "Bind translation is invalid: [%f, %f, %f]", (float)rtm::vector_get_x(bone.bind_transform.translation), (float)rtm::vector_get_y(bone.bind_transform.translation), (float)rtm::vector_get_z(bone.bind_transform.translation));
-
-				// If we have a parent, mark it as not being a leaf bone (it has at least one child)
-				if (!is_root)
-					bitset_set(is_leaf_bitset, bone_bitset_desc, bone.parent_index, false);
-
-#if defined(ACL_HAS_ASSERT_CHECKS)
-				if (is_root)
-					num_root_bones++;
-#endif
-
-				m_bones[bone_index] = std::move(bone);
-
-				// Input scale is ignored and always set to [1.0, 1.0, 1.0]
-				m_bones[bone_index].bind_transform.scale = rtm::vector_set(1.0);
-			}
-
-			m_num_leaf_bones = safe_static_cast<uint16_t>(bitset_count_set_bits(is_leaf_bitset, bone_bitset_desc));
-
-			m_leaf_bone_chains = allocate_type_array<uint32_t>(allocator, size_t(m_num_leaf_bones) * bone_bitset_desc.get_size());
-
-			uint16_t leaf_index = 0;
-			for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
-			{
-				if (!bitset_test(is_leaf_bitset, bone_bitset_desc, bone_index))
-					continue;	// Skip non-leaf bones
-
-				uint32_t* bone_chain = m_leaf_bone_chains + (leaf_index * bone_bitset_desc.get_size());
-				bitset_reset(bone_chain, bone_bitset_desc, false);
-
-				uint16_t chain_bone_index = bone_index;
-				while (chain_bone_index != k_invalid_bone_index)
-				{
-					bitset_set(bone_chain, bone_bitset_desc, chain_bone_index, true);
-
-					RigidBone& bone = m_bones[chain_bone_index];
-
-					// We assign a bone chain the first time we find a bone that isn't part of one already
-					if (bone.bone_chain == nullptr)
-						bone.bone_chain = bone_chain;
-
-					chain_bone_index = bone.parent_index;
-				}
-
-				leaf_index++;
-			}
-
-			ACL_ASSERT(num_root_bones > 0, "No root bone found. The root bones must have a parent index = 0xFFFF");
-			ACL_ASSERT(leaf_index == m_num_leaf_bones, "Invalid number of leaf bone found");
-			deallocate_type_array(m_allocator, is_leaf_bitset, bone_bitset_desc.get_size());
-		}
-
-		RigidSkeleton(RigidSkeleton&& other) noexcept
-			: m_allocator(other.m_allocator)
-			, m_bones(other.m_bones)
-			, m_leaf_bone_chains(other.m_leaf_bone_chains)
-			, m_num_bones(other.m_num_bones)
-			, m_num_leaf_bones(other.m_num_leaf_bones)
-		{
-			other.m_bones = nullptr;
-			other.m_leaf_bone_chains = nullptr;
-		}
-
-		~RigidSkeleton()
-		{
-			deallocate_type_array(m_allocator, m_bones, m_num_bones);
-
-			BitSetDescription bone_bitset_desc = BitSetDescription::make_from_num_bits(m_num_bones);
-			deallocate_type_array(m_allocator, m_leaf_bone_chains, size_t(m_num_leaf_bones) * bone_bitset_desc.get_size());
-		}
-
-		RigidSkeleton(const RigidSkeleton&) = delete;
-		RigidSkeleton& operator=(const RigidSkeleton&) = delete;
-		RigidSkeleton& operator=(RigidSkeleton&&) = delete;
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the array of bones contained in the skeleton
-		const RigidBone* get_bones() const { return m_bones; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns a specific bone from its index
-		const RigidBone& get_bone(uint16_t bone_index) const
-		{
-			ACL_ASSERT(bone_index < m_num_bones, "Invalid bone index: %u >= %u", bone_index, m_num_bones);
-			return m_bones[bone_index];
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the number of bones in the skeleton
-		uint16_t get_num_bones() const { return m_num_bones; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns a bone chain for a specific bone from its index
-		BoneChain get_bone_chain(uint16_t bone_index) const
-		{
-			ACL_ASSERT(bone_index < m_num_bones, "Invalid bone index: %u >= %u", bone_index, m_num_bones);
-			const RigidBone& bone = m_bones[bone_index];
-			return BoneChain(bone.bone_chain, BitSetDescription::make_from_num_bits(m_num_bones), bone_index);
-		}
-
-	private:
-		// The allocator instance used to allocate and free memory by this skeleton instance
-		IAllocator&	m_allocator;
-
-		// The array of bone data for this skeleton, contains 'm_num_bones' entries
-		RigidBone*	m_bones;
-
-		// Contiguous block of memory for the bone chains, contains m_num_leaf_bones * get_bitset_size(m_num_bones) entries
-		uint32_t*	m_leaf_bone_chains;
-
-		// Number of bones contained in this skeleton
-		uint16_t	m_num_bones;
-
-		// Number of leaf bones contained in this skeleton
-		uint16_t	m_num_leaf_bones;
-	};
-}
-
-ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/compression/track.h b/includes/acl/compression/track.h
--- a/includes/acl/compression/track.h
+++ b/includes/acl/compression/track.h
@@ -48,80 +48,27 @@ namespace acl
 	public:
 		//////////////////////////////////////////////////////////////////////////
 		// Creates an empty, untyped track.
-		track() noexcept
-			: m_allocator(nullptr)
-			, m_data(nullptr)
-			, m_num_samples(0)
-			, m_stride(0)
-			, m_data_size(0)
-			, m_sample_rate(0.0F)
-			, m_type(track_type8::float1f)
-			, m_category(track_category8::scalarf)
-			, m_sample_size(0)
-			, m_desc()
-		{}
+		track() noexcept;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Move constructor for a track.
-		track(track&& other) noexcept
-			: m_allocator(other.m_allocator)
-			, m_data(other.m_data)
-			, m_num_samples(other.m_num_samples)
-			, m_stride(other.m_stride)
-			, m_data_size(other.m_data_size)
-			, m_sample_rate(other.m_sample_rate)
-			, m_type(other.m_type)
-			, m_category(other.m_category)
-			, m_sample_size(other.m_sample_size)
-			, m_desc(other.m_desc)
-		{
-			other.m_allocator = nullptr;
-			other.m_data = nullptr;
-		}
+		track(track&& other) noexcept;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Destroys the track. If it owns the memory referenced, it will be freed.
-		~track()
-		{
-			if (is_owner())
-			{
-				// We own the memory, free it
-				m_allocator->deallocate(m_data, m_data_size);
-			}
-		}
+		~track();
 
 		//////////////////////////////////////////////////////////////////////////
 		// Move assignment for a track.
-		track& operator=(track&& other) noexcept
-		{
-			std::swap(m_allocator, other.m_allocator);
-			std::swap(m_data, other.m_data);
-			std::swap(m_num_samples, other.m_num_samples);
-			std::swap(m_stride, other.m_stride);
-			std::swap(m_data_size, other.m_data_size);
-			std::swap(m_sample_rate, other.m_sample_rate);
-			std::swap(m_type, other.m_type);
-			std::swap(m_category, other.m_category);
-			std::swap(m_sample_size, other.m_sample_size);
-			std::swap(m_desc, other.m_desc);
-			return *this;
-		}
+		track& operator=(track&& other) noexcept;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Returns a pointer to an untyped sample at the specified index.
-		void* operator[](uint32_t index)
-		{
-			ACL_ASSERT(index < m_num_samples, "Invalid sample index. %u >= %u", index, m_num_samples);
-			return m_data + (index * m_stride);
-		}
+		void* operator[](uint32_t index);
 
 		//////////////////////////////////////////////////////////////////////////
 		// Returns a pointer to an untyped sample at the specified index.
-		const void* operator[](uint32_t index) const
-		{
-			ACL_ASSERT(index < m_num_samples, "Invalid sample index. %u >= %u", index, m_num_samples);
-			return m_data + (index * m_stride);
-		}
+		const void* operator[](uint32_t index) const;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Returns true if the track owns its memory, false otherwise.
@@ -131,6 +78,10 @@ namespace acl
 		// Returns true if the track owns its memory, false otherwise.
 		bool is_ref() const { return m_allocator == nullptr; }
 
+		//////////////////////////////////////////////////////////////////////////
+		// Returns a pointer to the allocator instance or nullptr if there is none present.
+		iallocator* get_allocator() const { return m_allocator; }
+
 		//////////////////////////////////////////////////////////////////////////
 		// Returns the number of samples contained within the track.
 		uint32_t get_num_samples() const { return m_num_samples; }
@@ -162,58 +113,33 @@ namespace acl
 		// When compressing, it is often desirable to strip or re-order the tracks we output.
 		// This can be used to sort by LOD or to strip stale tracks. Tracks with an invalid
 		// track index are stripped in the output.
-		uint32_t get_output_index() const
-		{
-			switch (m_category)
-			{
-			default:
-			case track_category8::scalarf:	return m_desc.scalar.output_index;
-			}
-		}
+		uint32_t get_output_index() const;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Returns the track description.
 		template<typename desc_type>
-		desc_type& get_description()
-		{
-			ACL_ASSERT(desc_type::category == m_category, "Unexpected track category");
-			switch (desc_type::category)
-			{
-			default:
-			case track_category8::scalarf:	return m_desc.scalar;
-			}
-		}
+		desc_type& get_description();
 
 		//////////////////////////////////////////////////////////////////////////
 		// Returns the track description.
 		template<typename desc_type>
-		const desc_type& get_description() const
-		{
-			ACL_ASSERT(desc_type::category == m_category, "Unexpected track category");
-			switch (desc_type::category)
-			{
-			default:
-			case track_category8::scalarf:	return m_desc.scalar;
-			}
-		}
+		const desc_type& get_description() const;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Returns a copy of the track where the memory will be owned by the copy.
-		track get_copy(IAllocator& allocator) const
-		{
-			track track_;
-			get_copy_impl(allocator, track_);
-			return track_;
-		}
+		track get_copy(iallocator& allocator) const;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Returns a reference to the track where the memory isn't owned.
-		track get_ref() const
-		{
-			track track_;
-			get_ref_impl(track_);
-			return track_;
-		}
+		track get_ref() const;
+
+		//////////////////////////////////////////////////////////////////////////
+		// Returns whether a track is valid or not.
+		// A track is valid if:
+		//    - It is empty
+		//    - It has a positive and finite sample rate
+		//    - A valid description
+		error_result is_valid() const;
 
 	protected:
 		//////////////////////////////////////////////////////////////////////////
@@ -224,69 +150,21 @@ namespace acl
 		//////////////////////////////////////////////////////////////////////////
 		// Internal constructor.
 		// Creates an empty, untyped track.
-		track(track_type8 type, track_category8 category) noexcept
-			: m_allocator(nullptr)
-			, m_data(nullptr)
-			, m_num_samples(0)
-			, m_stride(0)
-			, m_data_size(0)
-			, m_sample_rate(0.0F)
-			, m_type(type)
-			, m_category(category)
-			, m_sample_size(0)
-			, m_desc()
-		{}
+		track(track_type8 type, track_category8 category) noexcept;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Internal constructor.
-		track(IAllocator* allocator, uint8_t* data, uint32_t num_samples, uint32_t stride, size_t data_size, float sample_rate, track_type8 type, track_category8 category, uint8_t sample_size) noexcept
-			: m_allocator(allocator)
-			, m_data(data)
-			, m_num_samples(num_samples)
-			, m_stride(stride)
-			, m_data_size(data_size)
-			, m_sample_rate(sample_rate)
-			, m_type(type)
-			, m_category(category)
-			, m_sample_size(sample_size)
-			, m_desc()
-		{}
+		track(iallocator* allocator, uint8_t* data, uint32_t num_samples, uint32_t stride, size_t data_size, float sample_rate, track_type8 type, track_category8 category, uint8_t sample_size) noexcept;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Internal helper.
-		void get_copy_impl(IAllocator& allocator, track& out_track) const
-		{
-			out_track.m_allocator = &allocator;
-			out_track.m_data = reinterpret_cast<uint8_t*>(allocator.allocate(m_data_size));
-			out_track.m_num_samples = m_num_samples;
-			out_track.m_stride = m_stride;
-			out_track.m_data_size = m_data_size;
-			out_track.m_sample_rate = m_sample_rate;
-			out_track.m_type = m_type;
-			out_track.m_category = m_category;
-			out_track.m_sample_size = m_sample_size;
-			out_track.m_desc = m_desc;
-
-			std::memcpy(out_track.m_data, m_data, m_data_size);
-		}
+		void get_copy_impl(iallocator& allocator, track& out_track) const;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Internal helper.
-		void get_ref_impl(track& out_track) const
-		{
-			out_track.m_allocator = nullptr;
-			out_track.m_data = m_data;
-			out_track.m_num_samples = m_num_samples;
-			out_track.m_stride = m_stride;
-			out_track.m_data_size = m_data_size;
-			out_track.m_sample_rate = m_sample_rate;
-			out_track.m_type = m_type;
-			out_track.m_category = m_category;
-			out_track.m_sample_size = m_sample_size;
-			out_track.m_desc = m_desc;
-		}
+		void get_ref_impl(track& out_track) const;
 
-		IAllocator*				m_allocator;		// Optional allocator that owns the memory
+		iallocator*				m_allocator;		// Optional allocator that owns the memory
 		uint8_t*				m_data;				// Pointer to the samples
 
 		uint32_t				m_num_samples;		// The number of samples
@@ -304,11 +182,12 @@ namespace acl
 		// This ensures every track has the same size regardless of its type.
 		union desc_union
 		{
-			track_desc_scalarf	scalar;
-			// TODO: Add other description types here
+			track_desc_scalarf		scalar;
+			track_desc_transformf	transform;
 
 			desc_union() : scalar() {}
 			explicit desc_union(const track_desc_scalarf& desc) : scalar(desc) {}
+			explicit desc_union(const track_desc_transformf& desc) : transform(desc) {}
 		};
 
 		desc_union				m_desc;				// The track description
@@ -357,41 +236,19 @@ namespace acl
 		// Returns the sample at the specified index.
 		// If this track does not own the memory, mutable references aren't allowed and an
 		// invalid reference will be returned, leading to a crash.
-		sample_type& operator[](uint32_t index)
-		{
-			ACL_ASSERT(index < m_num_samples, "Invalid sample index. %u >= %u", index, m_num_samples);
-			return *reinterpret_cast<sample_type*>(m_data + (index * m_stride));
-		}
+		sample_type& operator[](uint32_t index);
 
 		//////////////////////////////////////////////////////////////////////////
 		// Returns the sample at the specified index.
-		const sample_type& operator[](uint32_t index) const
-		{
-			ACL_ASSERT(index < m_num_samples, "Invalid sample index. %u >= %u", index, m_num_samples);
-			return *reinterpret_cast<const sample_type*>(m_data + (index * m_stride));
-		}
+		const sample_type& operator[](uint32_t index) const;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Returns the track description.
-		desc_type& get_description()
-		{
-			switch (category)
-			{
-			default:
-			case track_category8::scalarf:	return m_desc.scalar;
-			}
-		}
+		desc_type& get_description();
 
 		//////////////////////////////////////////////////////////////////////////
 		// Returns the track description.
-		const desc_type& get_description() const
-		{
-			switch (category)
-			{
-			default:
-			case track_category8::scalarf:	return m_desc.scalar;
-			}
-		}
+		const desc_type& get_description() const;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Returns the track type.
@@ -403,60 +260,27 @@ namespace acl
 
 		//////////////////////////////////////////////////////////////////////////
 		// Returns a copy of the track where the memory will be owned by the copy.
-		track_typed get_copy(IAllocator& allocator) const
-		{
-			track_typed track_;
-			track::get_copy_impl(allocator, track_);
-			return track_;
-		}
+		track_typed get_copy(iallocator& allocator) const;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Returns a reference to the track where the memory isn't owned.
-		track_typed get_ref() const
-		{
-			track_typed track_;
-			track::get_ref_impl(track_);
-			return track_;
-		}
+		track_typed get_ref() const;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Creates a track that copies the data and owns the memory.
-		static track_typed<track_type_> make_copy(const desc_type& desc, IAllocator& allocator, const sample_type* data, uint32_t num_samples, float sample_rate, uint32_t stride = sizeof(sample_type))
-		{
-			const size_t data_size = size_t(num_samples) * sizeof(sample_type);
-			const uint8_t* data_raw = reinterpret_cast<const uint8_t*>(data);
-
-			// Copy the data manually to avoid preserving the stride
-			sample_type* data_copy = reinterpret_cast<sample_type*>(allocator.allocate(data_size));
-			for (uint32_t index = 0; index < num_samples; ++index)
-				data_copy[index] = *reinterpret_cast<const sample_type*>(data_raw + (index * stride));
-
-			return track_typed<track_type_>(&allocator, reinterpret_cast<uint8_t*>(data_copy), num_samples, sizeof(sample_type), data_size, sample_rate, desc);
-		}
+		static track_typed<track_type_> make_copy(const desc_type& desc, iallocator& allocator, const sample_type* data, uint32_t num_samples, float sample_rate, uint32_t stride = sizeof(sample_type));
 
 		//////////////////////////////////////////////////////////////////////////
 		// Creates a track and preallocates but does not initialize the memory that it owns.
-		static track_typed<track_type_> make_reserve(const desc_type& desc, IAllocator& allocator, uint32_t num_samples, float sample_rate)
-		{
-			const size_t data_size = size_t(num_samples) * sizeof(sample_type);
-			return track_typed<track_type_>(&allocator, reinterpret_cast<uint8_t*>(allocator.allocate(data_size)), num_samples, sizeof(sample_type), data_size, sample_rate, desc);
-		}
+		static track_typed<track_type_> make_reserve(const desc_type& desc, iallocator& allocator, uint32_t num_samples, float sample_rate);
 
 		//////////////////////////////////////////////////////////////////////////
 		// Creates a track and takes ownership of the already allocated memory.
-		static track_typed<track_type_> make_owner(const desc_type& desc, IAllocator& allocator, sample_type* data, uint32_t num_samples, float sample_rate, uint32_t stride = sizeof(sample_type))
-		{
-			const size_t data_size = size_t(num_samples) * stride;
-			return track_typed<track_type_>(&allocator, reinterpret_cast<uint8_t*>(data), num_samples, stride, data_size, sample_rate, desc);
-		}
+		static track_typed<track_type_> make_owner(const desc_type& desc, iallocator& allocator, sample_type* data, uint32_t num_samples, float sample_rate, uint32_t stride = sizeof(sample_type));
 
 		//////////////////////////////////////////////////////////////////////////
 		// Creates a track that just references the data without owning it.
-		static track_typed<track_type_> make_ref(const desc_type& desc, const sample_type* data, uint32_t num_samples, float sample_rate, uint32_t stride = sizeof(sample_type))
-		{
-			const size_t data_size = size_t(num_samples) * stride;
-			return track_typed<track_type_>(nullptr, const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(data)), num_samples, stride, data_size, sample_rate, desc);
-		}
+		static track_typed<track_type_> make_ref(const desc_type& desc, const sample_type* data, uint32_t num_samples, float sample_rate, uint32_t stride = sizeof(sample_type));
 
 	private:
 		//////////////////////////////////////////////////////////////////////////
@@ -466,11 +290,7 @@ namespace acl
 
 		//////////////////////////////////////////////////////////////////////////
 		// Internal constructor.
-		track_typed(IAllocator* allocator, uint8_t* data, uint32_t num_samples, uint32_t stride, size_t data_size, float sample_rate, const desc_type& desc) noexcept
-			: track(allocator, data, num_samples, stride, data_size, sample_rate, type, category, sizeof(sample_type))
-		{
-			m_desc = desc_union(desc);
-		}
+		track_typed(iallocator* allocator, uint8_t* data, uint32_t num_samples, uint32_t stride, size_t data_size, float sample_rate, const desc_type& desc) noexcept;
 	};
 
 	//////////////////////////////////////////////////////////////////////////
@@ -523,6 +343,286 @@ namespace acl
 	using track_float3f			= track_typed<track_type8::float3f>;
 	using track_float4f			= track_typed<track_type8::float4f>;
 	using track_vector4f		= track_typed<track_type8::vector4f>;
+	using track_qvvf			= track_typed<track_type8::qvvf>;
+
+	//////////////////////////////////////////////////////////////////////////
+	// Implementation
+
+	inline track::track() noexcept
+		: m_allocator(nullptr)
+		, m_data(nullptr)
+		, m_num_samples(0)
+		, m_stride(0)
+		, m_data_size(0)
+		, m_sample_rate(0.0F)
+		, m_type(track_type8::float1f)
+		, m_category(track_category8::scalarf)
+		, m_sample_size(0)
+		, m_desc()
+	{}
+
+	inline track::track(track&& other) noexcept
+		: m_allocator(other.m_allocator)
+		, m_data(other.m_data)
+		, m_num_samples(other.m_num_samples)
+		, m_stride(other.m_stride)
+		, m_data_size(other.m_data_size)
+		, m_sample_rate(other.m_sample_rate)
+		, m_type(other.m_type)
+		, m_category(other.m_category)
+		, m_sample_size(other.m_sample_size)
+		, m_desc(other.m_desc)
+	{
+		other.m_allocator = nullptr;
+		other.m_data = nullptr;
+	}
+
+	inline track::~track()
+	{
+		if (is_owner())
+		{
+			// We own the memory, free it
+			m_allocator->deallocate(m_data, m_data_size);
+		}
+	}
+
+	inline track& track::operator=(track&& other) noexcept
+	{
+		std::swap(m_allocator, other.m_allocator);
+		std::swap(m_data, other.m_data);
+		std::swap(m_num_samples, other.m_num_samples);
+		std::swap(m_stride, other.m_stride);
+		std::swap(m_data_size, other.m_data_size);
+		std::swap(m_sample_rate, other.m_sample_rate);
+		std::swap(m_type, other.m_type);
+		std::swap(m_category, other.m_category);
+		std::swap(m_sample_size, other.m_sample_size);
+		std::swap(m_desc, other.m_desc);
+		return *this;
+	}
+
+	inline void* track::operator[](uint32_t index)
+	{
+		ACL_ASSERT(index < m_num_samples, "Invalid sample index. %u >= %u", index, m_num_samples);
+		return m_data + (index * m_stride);
+	}
+
+	inline const void* track::operator[](uint32_t index) const
+	{
+		ACL_ASSERT(index < m_num_samples, "Invalid sample index. %u >= %u", index, m_num_samples);
+		return m_data + (index * m_stride);
+	}
+
+	inline uint32_t track::get_output_index() const
+	{
+		switch (m_category)
+		{
+		default:
+		case track_category8::scalarf:		return m_desc.scalar.output_index;
+		case track_category8::transformf:	return m_desc.transform.output_index;
+		}
+	}
+
+	template<>
+	inline track_desc_scalarf& track::get_description()
+	{
+		ACL_ASSERT(track_desc_scalarf::category == m_category, "Unexpected track category");
+		return m_desc.scalar;
+	}
+
+	template<>
+	inline track_desc_transformf& track::get_description()
+	{
+		ACL_ASSERT(track_desc_transformf::category == m_category, "Unexpected track category");
+		return m_desc.transform;
+	}
+
+	template<>
+	inline const track_desc_scalarf& track::get_description() const
+	{
+		ACL_ASSERT(track_desc_scalarf::category == m_category, "Unexpected track category");
+		return m_desc.scalar;
+	}
+
+	template<>
+	inline const track_desc_transformf& track::get_description() const
+	{
+		ACL_ASSERT(track_desc_transformf::category == m_category, "Unexpected track category");
+		return m_desc.transform;
+	}
+
+	inline track track::get_copy(iallocator& allocator) const
+	{
+		track track_;
+		get_copy_impl(allocator, track_);
+		return track_;
+	}
+
+	inline track track::get_ref() const
+	{
+		track track_;
+		get_ref_impl(track_);
+		return track_;
+	}
+
+	inline error_result track::is_valid() const
+	{
+		if (m_data == nullptr)
+			return error_result();
+
+		if (m_num_samples == 0xFFFFFFFFU)
+			return error_result("Too many samples");
+
+		if (m_sample_rate <= 0.0F || !rtm::scalar_is_finite(m_sample_rate))
+			return error_result("Invalid sample rate");
+
+		switch (m_category)
+		{
+		case track_category8::scalarf:		return m_desc.scalar.is_valid();
+		case track_category8::transformf:	return m_desc.transform.is_valid();
+		default:							return error_result("Invalid category");
+		}
+	}
+
+	inline track::track(track_type8 type, track_category8 category) noexcept
+		: m_allocator(nullptr)
+		, m_data(nullptr)
+		, m_num_samples(0)
+		, m_stride(0)
+		, m_data_size(0)
+		, m_sample_rate(0.0F)
+		, m_type(type)
+		, m_category(category)
+		, m_sample_size(0)
+		, m_desc()
+	{}
+
+	inline track::track(iallocator* allocator, uint8_t* data, uint32_t num_samples, uint32_t stride, size_t data_size, float sample_rate, track_type8 type, track_category8 category, uint8_t sample_size) noexcept
+		: m_allocator(allocator)
+		, m_data(data)
+		, m_num_samples(num_samples)
+		, m_stride(stride)
+		, m_data_size(data_size)
+		, m_sample_rate(sample_rate)
+		, m_type(type)
+		, m_category(category)
+		, m_sample_size(sample_size)
+		, m_desc()
+	{}
+
+	inline void track::get_copy_impl(iallocator& allocator, track& out_track) const
+	{
+		out_track.m_allocator = &allocator;
+		out_track.m_data = reinterpret_cast<uint8_t*>(allocator.allocate(m_data_size));
+		out_track.m_num_samples = m_num_samples;
+		out_track.m_stride = m_stride;
+		out_track.m_data_size = m_data_size;
+		out_track.m_sample_rate = m_sample_rate;
+		out_track.m_type = m_type;
+		out_track.m_category = m_category;
+		out_track.m_sample_size = m_sample_size;
+		out_track.m_desc = m_desc;
+
+		std::memcpy(out_track.m_data, m_data, m_data_size);
+	}
+
+	inline void track::get_ref_impl(track& out_track) const
+	{
+		out_track.m_allocator = nullptr;
+		out_track.m_data = m_data;
+		out_track.m_num_samples = m_num_samples;
+		out_track.m_stride = m_stride;
+		out_track.m_data_size = m_data_size;
+		out_track.m_sample_rate = m_sample_rate;
+		out_track.m_type = m_type;
+		out_track.m_category = m_category;
+		out_track.m_sample_size = m_sample_size;
+		out_track.m_desc = m_desc;
+	}
+
+	template<track_type8 track_type_>
+	inline typename track_typed<track_type_>::sample_type& track_typed<track_type_>::operator[](uint32_t index)
+	{
+		ACL_ASSERT(index < m_num_samples, "Invalid sample index. %u >= %u", index, m_num_samples);
+		return *reinterpret_cast<sample_type*>(m_data + (index * m_stride));
+	}
+
+	template<track_type8 track_type_>
+	inline const typename track_typed<track_type_>::sample_type& track_typed<track_type_>::operator[](uint32_t index) const
+	{
+		ACL_ASSERT(index < m_num_samples, "Invalid sample index. %u >= %u", index, m_num_samples);
+		return *reinterpret_cast<const sample_type*>(m_data + (index * m_stride));
+	}
+
+	template<track_type8 track_type_>
+	inline typename track_typed<track_type_>::desc_type& track_typed<track_type_>::get_description()
+	{
+		return track::get_description<desc_type>();
+	}
+
+	template<track_type8 track_type_>
+	inline const typename track_typed<track_type_>::desc_type& track_typed<track_type_>::get_description() const
+	{
+		return track::get_description<desc_type>();
+	}
+
+	template<track_type8 track_type_>
+	inline track_typed<track_type_> track_typed<track_type_>::get_copy(iallocator& allocator) const
+	{
+		track_typed track_;
+		track::get_copy_impl(allocator, track_);
+		return track_;
+	}
+
+	template<track_type8 track_type_>
+	inline track_typed<track_type_> track_typed<track_type_>::get_ref() const
+	{
+		track_typed track_;
+		track::get_ref_impl(track_);
+		return track_;
+	}
+
+	template<track_type8 track_type_>
+	inline track_typed<track_type_> track_typed<track_type_>::make_copy(const typename track_typed<track_type_>::desc_type& desc, iallocator& allocator, const sample_type* data, uint32_t num_samples, float sample_rate, uint32_t stride)
+	{
+		const size_t data_size = size_t(num_samples) * sizeof(sample_type);
+		const uint8_t* data_raw = reinterpret_cast<const uint8_t*>(data);
+
+		// Copy the data manually to avoid preserving the stride
+		sample_type* data_copy = reinterpret_cast<sample_type*>(allocator.allocate(data_size));
+		for (uint32_t index = 0; index < num_samples; ++index)
+			data_copy[index] = *reinterpret_cast<const sample_type*>(data_raw + (index * stride));
+
+		return track_typed<track_type_>(&allocator, reinterpret_cast<uint8_t*>(data_copy), num_samples, sizeof(sample_type), data_size, sample_rate, desc);
+	}
+
+	template<track_type8 track_type_>
+	inline track_typed<track_type_> track_typed<track_type_>::make_reserve(const typename track_typed<track_type_>::desc_type& desc, iallocator& allocator, uint32_t num_samples, float sample_rate)
+	{
+		const size_t data_size = size_t(num_samples) * sizeof(sample_type);
+		return track_typed<track_type_>(&allocator, reinterpret_cast<uint8_t*>(allocator.allocate(data_size)), num_samples, sizeof(sample_type), data_size, sample_rate, desc);
+	}
+
+	template<track_type8 track_type_>
+	inline track_typed<track_type_> track_typed<track_type_>::make_owner(const typename track_typed<track_type_>::desc_type& desc, iallocator& allocator, sample_type* data, uint32_t num_samples, float sample_rate, uint32_t stride)
+	{
+		const size_t data_size = size_t(num_samples) * stride;
+		return track_typed<track_type_>(&allocator, reinterpret_cast<uint8_t*>(data), num_samples, stride, data_size, sample_rate, desc);
+	}
+
+	template<track_type8 track_type_>
+	inline track_typed<track_type_> track_typed<track_type_>::make_ref(const typename track_typed<track_type_>::desc_type& desc, const sample_type* data, uint32_t num_samples, float sample_rate, uint32_t stride)
+	{
+		const size_t data_size = size_t(num_samples) * stride;
+		return track_typed<track_type_>(nullptr, const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(data)), num_samples, stride, data_size, sample_rate, desc);
+	}
+
+	template<track_type8 track_type_>
+	inline track_typed<track_type_>::track_typed(iallocator* allocator, uint8_t* data, uint32_t num_samples, uint32_t stride, size_t data_size, float sample_rate, const typename track_typed<track_type_>::desc_type& desc) noexcept
+		: track(allocator, data, num_samples, stride, data_size, sample_rate, type, category, sizeof(sample_type))
+	{
+		m_desc = desc_union(desc);
+	}
 }
 
 ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/compression/track_array.h b/includes/acl/compression/track_array.h
--- a/includes/acl/compression/track_array.h
+++ b/includes/acl/compression/track_array.h
@@ -29,6 +29,7 @@
 #include "acl/core/iallocator.h"
 #include "acl/core/interpolation_utils.h"
 #include "acl/core/track_writer.h"
+#include "acl/core/utils.h"
 #include "acl/compression/track.h"
 
 #include <rtm/scalarf.h>
@@ -36,6 +37,7 @@
 
 #include <cstdint>
 #include <limits>
+#include <type_traits>
 
 ACL_IMPL_FILE_PRAGMA_PUSH
 
@@ -61,7 +63,7 @@ namespace acl
 		//////////////////////////////////////////////////////////////////////////
 		// Constructs an array with the specified number of tracks.
 		// Tracks will be empty and untyped by default.
-		track_array(IAllocator& allocator, uint32_t num_tracks)
+		track_array(iallocator& allocator, uint32_t num_tracks)
 			: m_allocator(&allocator)
 			, m_tracks(allocate_type_array<track>(allocator, num_tracks))
 			, m_num_tracks(num_tracks)
@@ -95,6 +97,10 @@ namespace acl
 			return *this;
 		}
 
+		//////////////////////////////////////////////////////////////////////////
+		// Returns a pointer to the allocator instance or nullptr if there is none present.
+		iallocator* get_allocator() const { return m_allocator; }
+
 		//////////////////////////////////////////////////////////////////////////
 		// Returns the number of tracks contained in this array.
 		uint32_t get_num_tracks() const { return m_num_tracks; }
@@ -149,27 +155,8 @@ namespace acl
 		//    - All tracks have the same type
 		//    - All tracks have the same number of samples
 		//    - All tracks have the same sample rate
-		ErrorResult is_valid() const
-		{
-			const track_type8 type = get_track_type();
-			const uint32_t num_samples = get_num_samples_per_track();
-			const float sample_rate = get_sample_rate();
-
-			for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index)
-			{
-				const track& track_ = m_tracks[track_index];
-				if (track_.get_type() != type)
-					return ErrorResult("Tracks must all have the same type within an array");
-
-				if (track_.get_num_samples() != num_samples)
-					return ErrorResult("Track array requires the same number of samples in every track");
-
-				if (track_.get_sample_rate() != sample_rate)
-					return ErrorResult("Track array requires the same sample rate in every track");
-			}
-
-			return ErrorResult();
-		}
+		//    - All tracks are valid
+		error_result is_valid() const;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Sample all tracks within this array at the specified sample time and
@@ -195,7 +182,7 @@ namespace acl
 		track_array(const track_array&) = delete;
 		track_array& operator=(const track_array&) = delete;
 
-		IAllocator*		m_allocator;		// The allocator used to allocate our tracks
+		iallocator*		m_allocator;		// The allocator used to allocate our tracks
 		track*			m_tracks;			// The track list
 		uint32_t		m_num_tracks;		// The number of tracks
 	};
@@ -226,7 +213,7 @@ namespace acl
 		//////////////////////////////////////////////////////////////////////////
 		// Constructs an array with the specified number of tracks.
 		// Tracks will be empty and untyped by default.
-		track_array_typed(IAllocator& allocator, uint32_t num_tracks) : track_array(allocator, num_tracks) {}
+		track_array_typed(iallocator& allocator, uint32_t num_tracks) : track_array(allocator, num_tracks) {}
 
 		//////////////////////////////////////////////////////////////////////////
 		// Move constructor for a track array.
@@ -317,26 +304,190 @@ namespace acl
 	//////////////////////////////////////////////////////////////////////////
 	// Create aliases for the various typed track array types.
 
-	using track_array_float1f = track_array_typed<track_type8::float1f>;
-	using track_array_float2f = track_array_typed<track_type8::float2f>;
-	using track_array_float3f = track_array_typed<track_type8::float3f>;
-	using track_array_float4f = track_array_typed<track_type8::float4f>;
-	using track_array_vector4f = track_array_typed<track_type8::vector4f>;
+	using track_array_float1f	= track_array_typed<track_type8::float1f>;
+	using track_array_float2f	= track_array_typed<track_type8::float2f>;
+	using track_array_float3f	= track_array_typed<track_type8::float3f>;
+	using track_array_float4f	= track_array_typed<track_type8::float4f>;
+	using track_array_vector4f	= track_array_typed<track_type8::vector4f>;
+	using track_array_qvvf		= track_array_typed<track_type8::qvvf>;
 
 	//////////////////////////////////////////////////////////////////////////
 
+	inline error_result track_array::is_valid() const
+	{
+		const track_type8 type = get_track_type();
+		const uint32_t num_samples = get_num_samples_per_track();
+		const float sample_rate = get_sample_rate();
+
+		for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index)
+		{
+			const track& track_ = m_tracks[track_index];
+			if (track_.get_type() != type)
+				return error_result("Tracks must all have the same type within an array");
+
+			if (track_.get_num_samples() != num_samples)
+				return error_result("Track array requires the same number of samples in every track");
+
+			if (track_.get_sample_rate() != sample_rate)
+				return error_result("Track array requires the same sample rate in every track");
+
+			const error_result result = track_.is_valid();
+			if (result.any())
+				return result;
+
+			if (track_.get_category() == track_category8::transformf)
+			{
+				const track_desc_transformf& desc = track_.get_description<track_desc_transformf>();
+				if (desc.parent_index != k_invalid_track_index && desc.parent_index >= m_num_tracks)
+					return error_result("Invalid parent_index. It must be 'k_invalid_track_index' or a valid track index");
+			}
+		}
+
+		// Validate output indices
+		uint32_t num_outputs = 0;
+		for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index)
+		{
+			const track& track_ = m_tracks[track_index];
+			const uint32_t output_index = track_.get_output_index();
+			if (output_index != k_invalid_track_index && output_index >= m_num_tracks)
+				return error_result("The output_index must be 'k_invalid_track_index' or less than the number of bones");
+
+			if (output_index != k_invalid_track_index)
+			{
+				for (uint32_t track_index2 = track_index + 1; track_index2 < m_num_tracks; ++track_index2)
+				{
+					const track& track2_ = m_tracks[track_index2];
+					const uint32_t output_index2 = track2_.get_output_index();
+					if (output_index == output_index2)
+						return error_result("Duplicate output_index found");
+				}
+
+				num_outputs++;
+			}
+		}
+
+		for (uint32_t output_index = 0; output_index < num_outputs; ++output_index)
+		{
+			bool found = false;
+			for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index)
+			{
+				const track& track_ = m_tracks[track_index];
+				const uint32_t output_index_ = track_.get_output_index();
+				if (output_index == output_index_)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+				return error_result("Output indices are not contiguous");
+		}
+
+		return error_result();
+	}
+
 	template<class track_writer_type>
 	inline void track_array::sample_tracks(float sample_time, sample_rounding_policy rounding_policy, track_writer_type& writer) const
 	{
+		static_assert(std::is_base_of<track_writer, track_writer_type>::value, "track_writer_type must derive from track_writer");
 		ACL_ASSERT(is_valid().empty(), "Invalid track array");
 
-		for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index)
-			sample_track(track_index, sample_time, rounding_policy, writer);
+		const uint32_t num_samples = get_num_samples_per_track();
+		const float sample_rate = get_sample_rate();
+		const track_type8 track_type = get_track_type();
+
+		// Clamp for safety, the caller should normally handle this but in practice, it often isn't the case
+		const float duration = get_duration();
+		sample_time = rtm::scalar_clamp(sample_time, 0.0F, duration);
+
+		uint32_t key_frame0;
+		uint32_t key_frame1;
+		float interpolation_alpha;
+		find_linear_interpolation_samples_with_sample_rate(num_samples, sample_rate, sample_time, rounding_policy, key_frame0, key_frame1, interpolation_alpha);
+
+		switch (track_type)
+		{
+		case track_type8::float1f:
+			for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index)
+			{
+				const track_float1f& track__ = track_cast<track_float1f>(m_tracks[track_index]);
+
+				const rtm::scalarf value0 = rtm::scalar_load(&track__[key_frame0]);
+				const rtm::scalarf value1 = rtm::scalar_load(&track__[key_frame1]);
+				const rtm::scalarf value = rtm::scalar_lerp(value0, value1, rtm::scalar_set(interpolation_alpha));
+				writer.write_float1(track_index, value);
+			}
+			break;
+		case track_type8::float2f:
+			for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index)
+			{
+				const track_float2f& track__ = track_cast<track_float2f>(m_tracks[track_index]);
+
+				const rtm::vector4f value0 = rtm::vector_load2(&track__[key_frame0]);
+				const rtm::vector4f value1 = rtm::vector_load2(&track__[key_frame1]);
+				const rtm::vector4f value = rtm::vector_lerp(value0, value1, interpolation_alpha);
+				writer.write_float2(track_index, value);
+			}
+			break;
+		case track_type8::float3f:
+			for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index)
+			{
+				const track_float3f& track__ = track_cast<track_float3f>(m_tracks[track_index]);
+
+				const rtm::vector4f value0 = rtm::vector_load3(&track__[key_frame0]);
+				const rtm::vector4f value1 = rtm::vector_load3(&track__[key_frame1]);
+				const rtm::vector4f value = rtm::vector_lerp(value0, value1, interpolation_alpha);
+				writer.write_float3(track_index, value);
+			}
+			break;
+		case track_type8::float4f:
+			for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index)
+			{
+				const track_float4f& track__ = track_cast<track_float4f>(m_tracks[track_index]);
+
+				const rtm::vector4f value0 = rtm::vector_load(&track__[key_frame0]);
+				const rtm::vector4f value1 = rtm::vector_load(&track__[key_frame1]);
+				const rtm::vector4f value = rtm::vector_lerp(value0, value1, interpolation_alpha);
+				writer.write_float4(track_index, value);
+			}
+			break;
+		case track_type8::vector4f:
+			for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index)
+			{
+				const track_vector4f& track__ = track_cast<track_vector4f>(m_tracks[track_index]);
+
+				const rtm::vector4f value0 = track__[key_frame0];
+				const rtm::vector4f value1 = track__[key_frame1];
+				const rtm::vector4f value = rtm::vector_lerp(value0, value1, interpolation_alpha);
+				writer.write_vector4(track_index, value);
+			}
+			break;
+		case track_type8::qvvf:
+			for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index)
+			{
+				const track_qvvf& track__ = track_cast<track_qvvf>(m_tracks[track_index]);
+
+				const rtm::qvvf& value0 = track__[key_frame0];
+				const rtm::qvvf& value1 = track__[key_frame1];
+				const rtm::quatf rotation = rtm::quat_lerp(value0.rotation, value1.rotation, interpolation_alpha);
+				const rtm::vector4f translation = rtm::vector_lerp(value0.translation, value1.translation, interpolation_alpha);
+				const rtm::vector4f scale = rtm::vector_lerp(value0.scale, value1.scale, interpolation_alpha);
+				writer.write_rotation(track_index, rotation);
+				writer.write_translation(track_index, translation);
+				writer.write_scale(track_index, scale);
+			}
+			break;
+		default:
+			ACL_ASSERT(false, "Invalid track type");
+			break;
+		}
 	}
 
 	template<class track_writer_type>
 	inline void track_array::sample_track(uint32_t track_index, float sample_time, sample_rounding_policy rounding_policy, track_writer_type& writer) const
 	{
+		static_assert(std::is_base_of<track_writer, track_writer_type>::value, "track_writer_type must derive from track_writer");
 		ACL_ASSERT(is_valid().empty(), "Invalid track array");
 		ACL_ASSERT(track_index < m_num_tracks, "Invalid track index");
 
@@ -405,6 +556,20 @@ namespace acl
 			writer.write_vector4(track_index, value);
 			break;
 		}
+		case track_type8::qvvf:
+		{
+			const track_qvvf& track__ = track_cast<track_qvvf>(track_);
+
+			const rtm::qvvf& value0 = track__[key_frame0];
+			const rtm::qvvf& value1 = track__[key_frame1];
+			const rtm::quatf rotation = rtm::quat_lerp(value0.rotation, value1.rotation, interpolation_alpha);
+			const rtm::vector4f translation = rtm::vector_lerp(value0.translation, value1.translation, interpolation_alpha);
+			const rtm::vector4f scale = rtm::vector_lerp(value0.scale, value1.scale, interpolation_alpha);
+			writer.write_rotation(track_index, rotation);
+			writer.write_translation(track_index, translation);
+			writer.write_scale(track_index, scale);
+			break;
+		}
 		default:
 			ACL_ASSERT(false, "Invalid track type");
 			break;
@@ -414,12 +579,17 @@ namespace acl
 	inline uint32_t track_array::get_raw_size() const
 	{
 		const uint32_t num_samples = get_num_samples_per_track();
+		const track_type8 track_type = get_track_type();
 
 		uint32_t total_size = 0;
 		for (uint32_t track_index = 0; track_index < m_num_tracks; ++track_index)
 		{
 			const track& track_ = m_tracks[track_index];
-			total_size += num_samples * track_.get_sample_size();
+
+			if (track_type == track_type8::qvvf)
+				total_size += num_samples * 10 * sizeof(float);	// 4 rotation floats, 3 translation floats, 3 scale floats
+			else
+				total_size += num_samples * track_.get_sample_size();
 		}
 
 		return total_size;
diff --git a/includes/acl/compression/track_error.h b/includes/acl/compression/track_error.h
--- a/includes/acl/compression/track_error.h
+++ b/includes/acl/compression/track_error.h
@@ -42,15 +42,6 @@ ACL_IMPL_FILE_PRAGMA_PUSH
 
 namespace acl
 {
-	namespace acl_impl
-	{
-		//////////////////////////////////////////////////////////////////////////
-		// SFINAE boilerplate to detect if a template argument derives from acl::uniformly_sampled::DecompressionContext.
-		//////////////////////////////////////////////////////////////////////////
-		template<class T>
-		using is_decompression_context = typename std::enable_if<std::is_base_of<acl::decompression_context<typename T::settings_type>, T>::value, std::nullptr_t>::type;
-	}
-
 	//////////////////////////////////////////////////////////////////////////
 	// A struct that contains the track index that has the worst error,
 	// its error, and the sample time at which it happens.
@@ -58,6 +49,7 @@ namespace acl
 	struct track_error
 	{
 		track_error() : index(k_invalid_track_index), error(0.0F), sample_time(0.0F) {}
+		track_error(uint32_t index_, float error_, float sample_time_) : index(index_), error(error_), sample_time(sample_time_) {}
 
 		//////////////////////////////////////////////////////////////////////////
 		// The track index with the worst error.
@@ -72,329 +64,633 @@ namespace acl
 		float sample_time;
 	};
 
-	//////////////////////////////////////////////////////////////////////////
-	// Calculates the worst compression error between a raw track array and its
-	// compressed tracks.
-	//
-	// Note: This function uses SFINAE to prevent it from matching when it shouldn't.
-	template<class decompression_context_type, acl_impl::is_decompression_context<decompression_context_type> = nullptr>
-	inline track_error calculate_compression_error(IAllocator& allocator, const track_array& raw_tracks, decompression_context_type& context)
+	namespace acl_impl
 	{
-		using namespace acl_impl;
+		//////////////////////////////////////////////////////////////////////////
+		// SFINAE boilerplate to detect if a template argument derives from acl::uniformly_sampled::DecompressionContext.
+		//////////////////////////////////////////////////////////////////////////
+		template<class T>
+		using is_decompression_context = typename std::enable_if<std::is_base_of<acl::decompression_context<typename T::settings_type>, T>::value, std::nullptr_t>::type;
 
-		ACL_ASSERT(raw_tracks.is_valid().empty(), "Raw tracks are invalid");
-		ACL_ASSERT(context.is_initialized(), "Context isn't initialized");
+		inline rtm::vector4f RTM_SIMD_CALL get_scalar_track_error(track_type8 track_type, uint32_t raw_track_index, uint32_t lossy_track_index, debug_track_writer& raw_tracks_writer, debug_track_writer& lossy_tracks_writer)
+		{
+			rtm::vector4f error;
+			switch (track_type)
+			{
+			case track_type8::float1f:
+			{
+				const float raw_value = raw_tracks_writer.read_float1(raw_track_index);
+				const float lossy_value = lossy_tracks_writer.read_float1(lossy_track_index);
+				error = rtm::vector_set(rtm::scalar_abs(raw_value - lossy_value));
+				break;
+			}
+			case track_type8::float2f:
+			{
+				const rtm::vector4f raw_value = raw_tracks_writer.read_float2(raw_track_index);
+				const rtm::vector4f lossy_value = lossy_tracks_writer.read_float2(lossy_track_index);
+				error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
+				error = rtm::vector_mix<rtm::mix4::x, rtm::mix4::y, rtm::mix4::c, rtm::mix4::d>(error, rtm::vector_zero());
+				break;
+			}
+			case track_type8::float3f:
+			{
+				const rtm::vector4f raw_value = raw_tracks_writer.read_float3(raw_track_index);
+				const rtm::vector4f lossy_value = lossy_tracks_writer.read_float3(lossy_track_index);
+				error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
+				error = rtm::vector_mix<rtm::mix4::x, rtm::mix4::y, rtm::mix4::z, rtm::mix4::d>(error, rtm::vector_zero());
+				break;
+			}
+			case track_type8::float4f:
+			{
+				const rtm::vector4f raw_value = raw_tracks_writer.read_float4(raw_track_index);
+				const rtm::vector4f lossy_value = lossy_tracks_writer.read_float4(lossy_track_index);
+				error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
+				break;
+			}
+			case track_type8::vector4f:
+			{
+				const rtm::vector4f raw_value = raw_tracks_writer.read_vector4(raw_track_index);
+				const rtm::vector4f lossy_value = lossy_tracks_writer.read_vector4(lossy_track_index);
+				error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
+				break;
+			}
+			default:
+				ACL_ASSERT(false, "Unsupported track type");
+				error = rtm::vector_zero();
+				break;
+			}
 
-		const uint32_t num_samples = raw_tracks.get_num_samples_per_track();
-		if (num_samples == 0)
-			return track_error();	// Cannot measure any error
+			return error;
+		}
 
-		const uint32_t num_tracks = raw_tracks.get_num_tracks();
-		if (num_tracks == 0)
-			return track_error();	// Cannot measure any error
+		struct calculate_track_error_args
+		{
+			// Scalar and transforms
+			uint32_t num_samples;
+			uint32_t num_tracks;
+			float duration;
+			float sample_rate;
+			track_type8 track_type;
+
+			std::function<void(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)> sample_tracks0;
+			std::function<void(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)> sample_tracks1;
+
+			// Transforms only
+			const itransform_error_metric* error_metric;
+			std::function<uint32_t(uint32_t track_index)> get_parent_index;
+			std::function<float(uint32_t track_index)> get_shell_distance;
+
+			// Optional
+			uint32_t base_num_samples;
+			float base_duration;
+			
+
+			std::function<void(float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)> sample_tracks_base;
+			std::function<uint32_t(uint32_t track_index)> get_output_index;
+			
+			std::function<void(debug_track_writer& track_writer0, debug_track_writer& track_writer1, debug_track_writer& track_writer_remapped)> remap_output;
+		};
+
+		inline track_error calculate_scalar_track_error(iallocator& allocator, const calculate_track_error_args& args)
+		{
+			const uint32_t num_samples = args.num_samples;
+			if (args.num_samples == 0)
+				return track_error();	// Cannot measure any error
 
-		track_error result;
-		result.error = -1.0F;		// Can never have a negative error, use -1 so the first sample is used
+			const uint32_t num_tracks = args.num_tracks;
+			if (args.num_tracks == 0)
+				return track_error();	// Cannot measure any error
 
-		const float duration = raw_tracks.get_duration();
-		const float sample_rate = raw_tracks.get_sample_rate();
-		const track_type8 track_type = raw_tracks.get_track_type();
+			track_error result;
+			result.error = -1.0F;		// Can never have a negative error, use -1 so the first sample is used
 
-		debug_track_writer raw_tracks_writer(allocator, track_type, num_tracks);
-		debug_track_writer lossy_tracks_writer(allocator, track_type, num_tracks);
+			const float duration = args.duration;
+			const float sample_rate = args.sample_rate;
+			const track_type8 track_type = args.track_type;
+
+			// We use the nearest sample to accurately measure the loss that happened, if any
+			const sample_rounding_policy rounding_policy = sample_rounding_policy::nearest;
+
+			debug_track_writer tracks_writer0(allocator, track_type, num_tracks);
+			debug_track_writer tracks_writer1(allocator, track_type, num_tracks);
+
+			// Measure our error
+			for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
+			{
+				const float sample_time = rtm::scalar_min(float(sample_index) / sample_rate, duration);
 
-		const rtm::vector4f zero = rtm::vector_zero();
+				args.sample_tracks0(sample_time, rounding_policy, tracks_writer0);
+				args.sample_tracks1(sample_time, rounding_policy, tracks_writer1);
 
-		// Measure our error
-		for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
+				// Validate decompress_tracks
+				for (uint32_t track_index = 0; track_index < num_tracks; ++track_index)
+				{
+					const uint32_t output_index = args.get_output_index ? args.get_output_index(track_index) : track_index;
+					if (output_index == k_invalid_track_index)
+						continue;	// Track is being stripped, ignore it
+
+					const rtm::vector4f error = get_scalar_track_error(track_type, track_index, output_index, tracks_writer0, tracks_writer1);
+
+					const float max_error = rtm::vector_get_max_component(error);
+					if (max_error > result.error)
+					{
+						result.error = max_error;
+						result.index = track_index;
+						result.sample_time = sample_time;
+					}
+				}
+			}
+
+			return result;
+		}
+
+		inline track_error calculate_transform_track_error(iallocator& allocator, const calculate_track_error_args& args)
 		{
-			const float sample_time = rtm::scalar_min(float(sample_index) / sample_rate, duration);
+			ACL_ASSERT(args.error_metric != nullptr, "Must have an error metric");
+			ACL_ASSERT(args.get_parent_index, "Must be able to query the parent track index");
+			ACL_ASSERT(args.get_shell_distance, "Must be able to query the shell distance");
+
+			const uint32_t num_samples = args.num_samples;
+			if (num_samples == 0)
+				return track_error();	// Cannot measure any error
+
+			const uint32_t num_tracks = args.num_tracks;
+			if (num_tracks == 0)
+				return track_error();	// Cannot measure any error
+
+			const float clip_duration = args.duration;
+			const float sample_rate = args.sample_rate;
+			const itransform_error_metric& error_metric = *args.error_metric;
+			const uint32_t additive_num_samples = args.base_num_samples;
+			const float additive_duration = args.base_duration;
+
+			// Always calculate the error with scale, slower but we don't need to know if we have scale or not
+			const bool has_scale = true;
 
 			// We use the nearest sample to accurately measure the loss that happened, if any
-			raw_tracks.sample_tracks(sample_time, sample_rounding_policy::nearest, raw_tracks_writer);
+			const sample_rounding_policy rounding_policy = sample_rounding_policy::nearest;
+
+			debug_track_writer tracks_writer0(allocator, track_type8::qvvf, num_tracks);
+			debug_track_writer tracks_writer1(allocator, track_type8::qvvf, num_tracks);
+			debug_track_writer tracks_writer1_remapped(allocator, track_type8::qvvf, num_tracks);
+			debug_track_writer tracks_writer_base(allocator, track_type8::qvvf, num_tracks);
+
+			const size_t transform_size = error_metric.get_transform_size(has_scale);
+			const bool needs_conversion = error_metric.needs_conversion(has_scale);
+			uint8_t* raw_local_pose_converted = nullptr;
+			uint8_t* base_local_pose_converted = nullptr;
+			uint8_t* lossy_local_pose_converted = nullptr;
+			if (needs_conversion)
+			{
+				raw_local_pose_converted = allocate_type_array_aligned<uint8_t>(allocator, num_tracks * transform_size, 64);
+				base_local_pose_converted = allocate_type_array_aligned<uint8_t>(allocator, num_tracks * transform_size, 64);
+				lossy_local_pose_converted = allocate_type_array_aligned<uint8_t>(allocator, num_tracks * transform_size, 64);
+			}
+
+			uint8_t* raw_object_pose = allocate_type_array_aligned<uint8_t>(allocator, num_tracks * transform_size, 64);
+			uint8_t* lossy_object_pose = allocate_type_array_aligned<uint8_t>(allocator, num_tracks * transform_size, 64);
 
-			context.seek(sample_time, sample_rounding_policy::nearest);
-			context.decompress_tracks(lossy_tracks_writer);
+			uint16_t* parent_transform_indices = allocate_type_array<uint16_t>(allocator, num_tracks);
+			uint16_t* self_transform_indices = allocate_type_array<uint16_t>(allocator, num_tracks);
 
-			// Validate decompress_tracks
-			for (uint32_t track_index = 0; track_index < num_tracks; ++track_index)
+			for (uint32_t transform_index = 0; transform_index < num_tracks; ++transform_index)
 			{
-				const track& track_ = raw_tracks[track_index];
-				const uint32_t output_index = track_.get_output_index();
-				if (output_index == k_invalid_track_index)
-					continue;	// Track is being stripped, ignore it
+				const uint32_t parent_index = args.get_parent_index(transform_index);
+				parent_transform_indices[transform_index] = parent_index == k_invalid_track_index ? k_invalid_bone_index : safe_static_cast<uint16_t>(parent_index);
+				self_transform_indices[transform_index] = safe_static_cast<uint16_t>(transform_index);
+			}
 
-				rtm::vector4f error;
+			void* raw_local_pose_ = needs_conversion ? (void*)raw_local_pose_converted : (void*)tracks_writer0.tracks_typed.qvvf;
+			void* base_local_pose_ = needs_conversion ? (void*)base_local_pose_converted : (void*)tracks_writer_base.tracks_typed.qvvf;
+			void* lossy_local_pose_ = needs_conversion ? (void*)lossy_local_pose_converted : (void*)tracks_writer1_remapped.tracks_typed.qvvf;
 
-				switch (track_type)
-				{
-				case track_type8::float1f:
-				{
-					const float raw_value = raw_tracks_writer.read_float1(track_index);
-					const float lossy_value = lossy_tracks_writer.read_float1(output_index);
-					error = rtm::vector_set(rtm::scalar_abs(raw_value - lossy_value));
-					break;
-				}
-				case track_type8::float2f:
-				{
-					const rtm::vector4f raw_value = raw_tracks_writer.read_float2(track_index);
-					const rtm::vector4f lossy_value = lossy_tracks_writer.read_float2(output_index);
-					error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
-					error = rtm::vector_mix<rtm::mix4::x, rtm::mix4::y, rtm::mix4::c, rtm::mix4::d>(error, zero);
-					break;
-				}
-				case track_type8::float3f:
-				{
-					const rtm::vector4f raw_value = raw_tracks_writer.read_float3(track_index);
-					const rtm::vector4f lossy_value = lossy_tracks_writer.read_float3(output_index);
-					error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
-					error = rtm::vector_mix<rtm::mix4::x, rtm::mix4::y, rtm::mix4::z, rtm::mix4::d>(error, zero);
-					break;
-				}
-				case track_type8::float4f:
+			itransform_error_metric::convert_transforms_args convert_transforms_args_raw;
+			convert_transforms_args_raw.dirty_transform_indices = self_transform_indices;
+			convert_transforms_args_raw.num_dirty_transforms = num_tracks;
+			convert_transforms_args_raw.transforms = tracks_writer0.tracks_typed.qvvf;
+			convert_transforms_args_raw.num_transforms = num_tracks;
+
+			itransform_error_metric::convert_transforms_args convert_transforms_args_base = convert_transforms_args_raw;
+			convert_transforms_args_base.transforms = tracks_writer_base.tracks_typed.qvvf;
+
+			itransform_error_metric::convert_transforms_args convert_transforms_args_lossy = convert_transforms_args_raw;
+			convert_transforms_args_lossy.transforms = tracks_writer1_remapped.tracks_typed.qvvf;
+
+			itransform_error_metric::apply_additive_to_base_args apply_additive_to_base_args_raw;
+			apply_additive_to_base_args_raw.dirty_transform_indices = self_transform_indices;
+			apply_additive_to_base_args_raw.num_dirty_transforms = num_tracks;
+			apply_additive_to_base_args_raw.local_transforms = raw_local_pose_;
+			apply_additive_to_base_args_raw.base_transforms = base_local_pose_;
+			apply_additive_to_base_args_raw.num_transforms = num_tracks;
+
+			itransform_error_metric::apply_additive_to_base_args apply_additive_to_base_args_lossy = apply_additive_to_base_args_raw;
+			apply_additive_to_base_args_lossy.local_transforms = lossy_local_pose_;
+
+			itransform_error_metric::local_to_object_space_args local_to_object_space_args_raw;
+			local_to_object_space_args_raw.dirty_transform_indices = self_transform_indices;
+			local_to_object_space_args_raw.num_dirty_transforms = num_tracks;
+			local_to_object_space_args_raw.parent_transform_indices = parent_transform_indices;
+			local_to_object_space_args_raw.local_transforms = raw_local_pose_;
+			local_to_object_space_args_raw.num_transforms = num_tracks;
+
+			itransform_error_metric::local_to_object_space_args local_to_object_space_args_lossy = local_to_object_space_args_raw;
+			local_to_object_space_args_lossy.local_transforms = lossy_local_pose_;
+
+			track_error result;
+			result.error = -1.0F;		// Can never have a negative error, use -1 so the first sample is used
+
+			for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
+			{
+				const float sample_time = rtm::scalar_min(float(sample_index) / sample_rate, clip_duration);
+
+				// Sample our tracks
+				args.sample_tracks0(sample_time, rounding_policy, tracks_writer0);
+				args.sample_tracks1(sample_time, rounding_policy, tracks_writer1);
+
+				// Maybe remap them
+				if (args.remap_output)
+					args.remap_output(tracks_writer0, tracks_writer1, tracks_writer1_remapped);
+				else
+					std::memcpy(tracks_writer1_remapped.tracks_typed.qvvf, tracks_writer1.tracks_typed.qvvf, sizeof(rtm::qvvf) * num_tracks);
+
+				if (needs_conversion)
 				{
-					const rtm::vector4f raw_value = raw_tracks_writer.read_float4(track_index);
-					const rtm::vector4f lossy_value = lossy_tracks_writer.read_float4(output_index);
-					error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
-					break;
+					error_metric.convert_transforms(convert_transforms_args_raw, raw_local_pose_converted);
+					error_metric.convert_transforms(convert_transforms_args_lossy, lossy_local_pose_converted);
 				}
-				case track_type8::vector4f:
+
+				if (args.sample_tracks_base)
 				{
-					const rtm::vector4f raw_value = raw_tracks_writer.read_vector4(track_index);
-					const rtm::vector4f lossy_value = lossy_tracks_writer.read_vector4(output_index);
-					error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
-					break;
-				}
-				default:
-					ACL_ASSERT(false, "Unsupported track type");
-					error = zero;
-					break;
+					const float normalized_sample_time = additive_num_samples > 1 ? (sample_time / clip_duration) : 0.0F;
+					const float additive_sample_time = additive_num_samples > 1 ? (normalized_sample_time * additive_duration) : 0.0F;
+					args.sample_tracks_base(additive_sample_time, rounding_policy, tracks_writer_base);
+
+					if (needs_conversion)
+						error_metric.convert_transforms(convert_transforms_args_base, base_local_pose_converted);
+
+					error_metric.apply_additive_to_base(apply_additive_to_base_args_raw, raw_local_pose_);
+					error_metric.apply_additive_to_base(apply_additive_to_base_args_lossy, lossy_local_pose_);
 				}
 
-				const float max_error = rtm::vector_get_max_component(error);
-				if (max_error > result.error)
+				error_metric.local_to_object_space(local_to_object_space_args_raw, raw_object_pose);
+				error_metric.local_to_object_space(local_to_object_space_args_lossy, lossy_object_pose);
+
+				for (uint32_t bone_index = 0; bone_index < num_tracks; ++bone_index)
 				{
-					result.error = max_error;
-					result.index = track_index;
-					result.sample_time = sample_time;
+					const float shell_distance = args.get_shell_distance(bone_index);
+
+					itransform_error_metric::calculate_error_args calculate_error_args;
+					calculate_error_args.transform0 = raw_object_pose + (bone_index * transform_size);
+					calculate_error_args.transform1 = lossy_object_pose + (bone_index * transform_size);
+					calculate_error_args.construct_sphere_shell(shell_distance);
+
+					const float error = rtm::scalar_cast(error_metric.calculate_error(calculate_error_args));
+
+					if (error > result.error)
+					{
+						result.error = error;
+						result.index = bone_index;
+						result.sample_time = sample_time;
+					}
 				}
 			}
-		}
 
-		return result;
+			deallocate_type_array(allocator, raw_local_pose_converted, num_tracks * transform_size);
+			deallocate_type_array(allocator, base_local_pose_converted, num_tracks * transform_size);
+			deallocate_type_array(allocator, lossy_local_pose_converted, num_tracks * transform_size);
+			deallocate_type_array(allocator, raw_object_pose, num_tracks * transform_size);
+			deallocate_type_array(allocator, lossy_object_pose, num_tracks * transform_size);
+			deallocate_type_array(allocator, parent_transform_indices, num_tracks);
+			deallocate_type_array(allocator, self_transform_indices, num_tracks);
+
+			return result;
+		}
 	}
 
 	//////////////////////////////////////////////////////////////////////////
-	// Calculates the worst compression error between two compressed tracks instances.
+	// Calculates the worst compression error between a raw track array and its
+	// compressed tracks.
+	// Supports scalar tracks only.
 	//
 	// Note: This function uses SFINAE to prevent it from matching when it shouldn't.
-	template<class decompression_context_type0, class decompression_context_type1, acl_impl::is_decompression_context<decompression_context_type0> = nullptr, acl_impl::is_decompression_context<decompression_context_type1> = nullptr>
-	inline track_error calculate_compression_error(IAllocator& allocator, decompression_context_type0& context0, decompression_context_type1& context1)
+	template<class decompression_context_type, acl_impl::is_decompression_context<decompression_context_type> = nullptr>
+	inline track_error calculate_compression_error(iallocator& allocator, const track_array& raw_tracks, decompression_context_type& context)
 	{
 		using namespace acl_impl;
 
-		ACL_ASSERT(context0.is_initialized(), "Context isn't initialized");
-		ACL_ASSERT(context1.is_initialized(), "Context isn't initialized");
+		ACL_ASSERT(raw_tracks.is_valid().empty(), "Raw tracks are invalid");
+		ACL_ASSERT(context.is_initialized(), "Context isn't initialized");
 
-		const compressed_tracks* tracks0 = context0.get_compressed_tracks();
+		if (raw_tracks.get_track_type() == track_type8::qvvf)
+			return track_error(~0U, -1.0F, -1.0F);	// Only supports scalar tracks
 
-		const uint32_t num_samples = tracks0->get_num_samples_per_track();
-		if (num_samples == 0)
-			return track_error();	// Cannot measure any error
+		calculate_track_error_args args;
+		args.num_samples = raw_tracks.get_num_samples_per_track();
+		args.num_tracks = raw_tracks.get_num_tracks();
+		args.duration = raw_tracks.get_duration();
+		args.sample_rate = raw_tracks.get_sample_rate();
+		args.track_type = raw_tracks.get_track_type();
 
-		const uint32_t num_tracks = tracks0->get_num_tracks();
-		if (num_tracks == 0)
-			return track_error();	// Cannot measure any error
+		args.sample_tracks0 = [&raw_tracks](float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)
+		{
+			raw_tracks.sample_tracks(sample_time, rounding_policy, track_writer);
+		};
 
-		track_error result;
-		result.error = -1.0F;		// Can never have a negative error, use -1 so the first sample is used
+		args.sample_tracks1 = [&context](float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)
+		{
+			context.seek(sample_time, rounding_policy);
+			context.decompress_tracks(track_writer);
+		};
 
-		const float duration = tracks0->get_duration();
-		const float sample_rate = tracks0->get_sample_rate();
-		const track_type8 track_type = tracks0->get_track_type();
+		args.get_output_index = [&raw_tracks](uint32_t track_index)
+		{
+			const track& track_ = raw_tracks[track_index];
+			return track_.get_output_index();
+		};
 
-		debug_track_writer tracks_writer0(allocator, track_type, num_tracks);
-		debug_track_writer tracks_writer1(allocator, track_type, num_tracks);
+		return calculate_scalar_track_error(allocator, args);
+	}
+
+	//////////////////////////////////////////////////////////////////////////
+	// Calculates the worst compression error between a raw track array and its
+	// compressed tracks.
+	// Supports scalar and transform tracks.
+	//
+	// Note: This function uses SFINAE to prevent it from matching when it shouldn't.
+	template<class decompression_context_type, acl_impl::is_decompression_context<decompression_context_type> = nullptr>
+	inline track_error calculate_compression_error(iallocator& allocator, const track_array& raw_tracks, decompression_context_type& context, const itransform_error_metric& error_metric)
+	{
+		using namespace acl_impl;
 
-		const rtm::vector4f zero = rtm::vector_zero();
+		ACL_ASSERT(raw_tracks.is_valid().empty(), "Raw tracks are invalid");
+		ACL_ASSERT(context.is_initialized(), "Context isn't initialized");
+
+		calculate_track_error_args args;
+		args.num_samples = raw_tracks.get_num_samples_per_track();
+		args.num_tracks = raw_tracks.get_num_tracks();
+		args.duration = raw_tracks.get_duration();
+		args.sample_rate = raw_tracks.get_sample_rate();
+		args.track_type = raw_tracks.get_track_type();
 
-		// Measure our error
-		for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
+		args.sample_tracks0 = [&raw_tracks](float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)
 		{
-			const float sample_time = rtm::scalar_min(float(sample_index) / sample_rate, duration);
+			raw_tracks.sample_tracks(sample_time, rounding_policy, track_writer);
+		};
 
-			// We use the nearest sample to accurately measure the loss that happened, if any
-			context0.seek(sample_time, sample_rounding_policy::nearest);
-			context0.decompress_tracks(tracks_writer0);
+		args.sample_tracks1 = [&context](float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)
+		{
+			context.seek(sample_time, rounding_policy);
+			context.decompress_tracks(track_writer);
+		};
+
+		args.get_output_index = [&raw_tracks](uint32_t track_index)
+		{
+			const track& track_ = raw_tracks[track_index];
+			return track_.get_output_index();
+		};
+
+		if (raw_tracks.get_track_type() != track_type8::qvvf)
+			return calculate_scalar_track_error(allocator, args);
 
-			context1.seek(sample_time, sample_rounding_policy::nearest);
-			context1.decompress_tracks(tracks_writer1);
+		uint32_t num_output_bones = 0;
+		uint32_t* output_bone_mapping = create_output_track_mapping(allocator, raw_tracks, num_output_bones);
 
-			// Validate decompress_tracks
-			for (uint32_t track_index = 0; track_index < num_tracks; ++track_index)
+		args.error_metric = &error_metric;
+
+		args.get_parent_index = [&raw_tracks](uint32_t track_index)
+		{
+			const track_qvvf& track_ = track_cast<track_qvvf>(raw_tracks[track_index]);
+			return track_.get_description().parent_index;
+		};
+
+		args.get_shell_distance = [&raw_tracks](uint32_t track_index)
+		{
+			const track_qvvf& track_ = track_cast<track_qvvf>(raw_tracks[track_index]);
+			return track_.get_description().shell_distance;
+		};
+
+		args.remap_output = [output_bone_mapping, num_output_bones](debug_track_writer& track_writer0, debug_track_writer& track_writer1, debug_track_writer& track_writer_remapped)
+		{
+			// Perform remapping by copying the raw pose first and we overwrite with the decompressed pose if
+			// the data is available
+			std::memcpy(track_writer_remapped.tracks_typed.qvvf, track_writer0.tracks_typed.qvvf, sizeof(rtm::qvvf) * track_writer_remapped.num_tracks);
+			for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index)
 			{
-				rtm::vector4f error;
+				const uint32_t bone_index = output_bone_mapping[output_index];
+				track_writer_remapped.tracks_typed.qvvf[bone_index] = track_writer1.tracks_typed.qvvf[output_index];
+			}
+		};
 
-				switch (track_type)
-				{
-				case track_type8::float1f:
-				{
-					const float raw_value = tracks_writer0.read_float1(track_index);
-					const float lossy_value = tracks_writer1.read_float1(track_index);
-					error = rtm::vector_set(rtm::scalar_abs(raw_value - lossy_value));
-					break;
-				}
-				case track_type8::float2f:
-				{
-					const rtm::vector4f raw_value = tracks_writer0.read_float2(track_index);
-					const rtm::vector4f lossy_value = tracks_writer1.read_float2(track_index);
-					error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
-					error = rtm::vector_mix<rtm::mix4::x, rtm::mix4::y, rtm::mix4::c, rtm::mix4::d>(error, zero);
-					break;
-				}
-				case track_type8::float3f:
-				{
-					const rtm::vector4f raw_value = tracks_writer0.read_float3(track_index);
-					const rtm::vector4f lossy_value = tracks_writer1.read_float3(track_index);
-					error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
-					error = rtm::vector_mix<rtm::mix4::x, rtm::mix4::y, rtm::mix4::z, rtm::mix4::d>(error, zero);
-					break;
-				}
-				case track_type8::float4f:
-				{
-					const rtm::vector4f raw_value = tracks_writer0.read_float4(track_index);
-					const rtm::vector4f lossy_value = tracks_writer1.read_float4(track_index);
-					error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
-					break;
-				}
-				case track_type8::vector4f:
-				{
-					const rtm::vector4f raw_value = tracks_writer0.read_vector4(track_index);
-					const rtm::vector4f lossy_value = tracks_writer1.read_vector4(track_index);
-					error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
-					break;
-				}
-				default:
-					ACL_ASSERT(false, "Unsupported track type");
-					error = zero;
-					break;
-				}
+		const track_error result = calculate_transform_track_error(allocator, args);
 
-				const float max_error = rtm::vector_get_max_component(error);
-				if (max_error > result.error)
-				{
-					result.error = max_error;
-					result.index = track_index;
-					result.sample_time = sample_time;
-				}
+		deallocate_type_array(allocator, output_bone_mapping, num_output_bones);
+
+		return result;
+	}
+
+	//////////////////////////////////////////////////////////////////////////
+	// Calculates the worst compression error between a raw track array and its
+	// compressed tracks.
+	// Supports transform tracks with an additive base.
+	//
+	// Note: This function uses SFINAE to prevent it from matching when it shouldn't.
+	template<class decompression_context_type, acl_impl::is_decompression_context<decompression_context_type> = nullptr>
+	inline track_error calculate_compression_error(iallocator& allocator, const track_array_qvvf& raw_tracks, decompression_context_type& context, const itransform_error_metric& error_metric, const track_array_qvvf& additive_base_tracks)
+	{
+		using namespace acl_impl;
+
+		ACL_ASSERT(raw_tracks.is_valid().empty(), "Raw tracks are invalid");
+		ACL_ASSERT(context.is_initialized(), "Context isn't initialized");
+
+		calculate_track_error_args args;
+		args.num_samples = raw_tracks.get_num_samples_per_track();
+		args.num_tracks = raw_tracks.get_num_tracks();
+		args.duration = raw_tracks.get_duration();
+		args.sample_rate = raw_tracks.get_sample_rate();
+		args.track_type = raw_tracks.get_track_type();
+
+		args.sample_tracks0 = [&raw_tracks](float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)
+		{
+			raw_tracks.sample_tracks(sample_time, rounding_policy, track_writer);
+		};
+
+		args.sample_tracks1 = [&context](float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)
+		{
+			context.seek(sample_time, rounding_policy);
+			context.decompress_tracks(track_writer);
+		};
+
+		args.get_output_index = [&raw_tracks](uint32_t track_index)
+		{
+			const track& track_ = raw_tracks[track_index];
+			return track_.get_output_index();
+		};
+
+		uint32_t num_output_bones = 0;
+		uint32_t* output_bone_mapping = create_output_track_mapping(allocator, raw_tracks, num_output_bones);
+
+		args.error_metric = &error_metric;
+
+		args.get_parent_index = [&raw_tracks](uint32_t track_index)
+		{
+			const track_qvvf& track_ = track_cast<track_qvvf>(raw_tracks[track_index]);
+			return track_.get_description().parent_index;
+		};
+
+		args.get_shell_distance = [&raw_tracks](uint32_t track_index)
+		{
+			const track_qvvf& track_ = track_cast<track_qvvf>(raw_tracks[track_index]);
+			return track_.get_description().shell_distance;
+		};
+
+		args.remap_output = [output_bone_mapping, num_output_bones](debug_track_writer& track_writer0, debug_track_writer& track_writer1, debug_track_writer& track_writer_remapped)
+		{
+			// Perform remapping by copying the raw pose first and we overwrite with the decompressed pose if
+			// the data is available
+			std::memcpy(track_writer_remapped.tracks_typed.qvvf, track_writer0.tracks_typed.qvvf, sizeof(rtm::qvvf) * track_writer_remapped.num_tracks);
+			for (uint32_t output_index = 0; output_index < num_output_bones; ++output_index)
+			{
+				const uint32_t bone_index = output_bone_mapping[output_index];
+				track_writer_remapped.tracks_typed.qvvf[bone_index] = track_writer1.tracks_typed.qvvf[output_index];
 			}
+		};
+
+		if (additive_base_tracks.get_num_tracks() != 0)
+		{
+			args.base_num_samples = additive_base_tracks.get_num_samples_per_track();
+			args.base_duration = additive_base_tracks.get_duration();
+
+			args.sample_tracks_base = [&additive_base_tracks](float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)
+			{
+				additive_base_tracks.sample_tracks(sample_time, rounding_policy, track_writer);
+			};
 		}
 
+		const track_error result = calculate_transform_track_error(allocator, args);
+
+		deallocate_type_array(allocator, output_bone_mapping, num_output_bones);
+
 		return result;
 	}
 
+	//////////////////////////////////////////////////////////////////////////
+	// Calculates the worst compression error between two compressed tracks instances.
+	// Supports scalar tracks only.
+	//
+	// Note: This function uses SFINAE to prevent it from matching when it shouldn't.
+	template<class decompression_context_type0, class decompression_context_type1, acl_impl::is_decompression_context<decompression_context_type0> = nullptr, acl_impl::is_decompression_context<decompression_context_type1> = nullptr>
+	inline track_error calculate_compression_error(iallocator& allocator, decompression_context_type0& context0, decompression_context_type1& context1)
+	{
+		using namespace acl_impl;
+
+		ACL_ASSERT(context0.is_initialized(), "Context isn't initialized");
+		ACL_ASSERT(context1.is_initialized(), "Context isn't initialized");
+
+		const compressed_tracks* tracks0 = context0.get_compressed_tracks();
+
+		if (tracks0->get_track_type() == track_type8::qvvf)
+			return track_error(~0U, -1.0F, -1.0F);	// Only supports scalar tracks
+
+		calculate_track_error_args args;
+		args.num_samples = tracks0->get_num_samples_per_track();
+		args.num_tracks = tracks0->get_num_tracks();
+		args.duration = tracks0->get_duration();
+		args.sample_rate = tracks0->get_sample_rate();
+		args.track_type = tracks0->get_track_type();
+
+		args.sample_tracks0 = [&context0](float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)
+		{
+			context0.seek(sample_time, rounding_policy);
+			context0.decompress_tracks(track_writer);
+		};
+
+		args.sample_tracks1 = [&context1](float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)
+		{
+			context1.seek(sample_time, rounding_policy);
+			context1.decompress_tracks(track_writer);
+		};
+
+		return calculate_scalar_track_error(allocator, args);
+	}
+
 	//////////////////////////////////////////////////////////////////////////
 	// Calculates the worst compression error between two raw track arrays.
-	inline track_error calculate_compression_error(IAllocator& allocator, const track_array& raw_tracks0, const track_array& raw_tracks1)
+	// Supports scalar tracks only.
+	inline track_error calculate_compression_error(iallocator& allocator, const track_array& raw_tracks0, const track_array& raw_tracks1)
 	{
 		using namespace acl_impl;
 
 		ACL_ASSERT(raw_tracks0.is_valid().empty(), "Raw tracks are invalid");
 		ACL_ASSERT(raw_tracks1.is_valid().empty(), "Raw tracks are invalid");
 
-		const uint32_t num_samples = raw_tracks0.get_num_samples_per_track();
-		if (num_samples == 0)
-			return track_error();	// Cannot measure any error
+		if (raw_tracks0.get_track_type() == track_type8::qvvf)
+			return track_error(~0U, -1.0F, -1.0F);	// Only supports scalar tracks
+
+		calculate_track_error_args args;
+		args.num_samples = raw_tracks0.get_num_samples_per_track();
+		args.num_tracks = raw_tracks0.get_num_tracks();
+		args.duration = raw_tracks0.get_duration();
+		args.sample_rate = raw_tracks0.get_sample_rate();
+		args.track_type = raw_tracks0.get_track_type();
 
-		const uint32_t num_tracks = raw_tracks0.get_num_tracks();
-		if (num_tracks == 0 || num_tracks != raw_tracks1.get_num_tracks())
-			return track_error();	// Cannot measure any error
+		args.sample_tracks0 = [&raw_tracks0](float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)
+		{
+			raw_tracks0.sample_tracks(sample_time, rounding_policy, track_writer);
+		};
+
+		args.sample_tracks1 = [&raw_tracks1](float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)
+		{
+			raw_tracks1.sample_tracks(sample_time, rounding_policy, track_writer);
+		};
 
-		const track_type8 track_type = raw_tracks0.get_track_type();
-		if (track_type != raw_tracks1.get_track_type())
-			return track_error();	// Cannot measure any error
+		return calculate_scalar_track_error(allocator, args);
+	}
 
-		track_error result;
-		result.error = -1.0F;		// Can never have a negative error, use -1 so the first sample is used
+	//////////////////////////////////////////////////////////////////////////
+	// Calculates the worst compression error between two raw track arrays.
+	// Supports scalar and transform tracks.
+	inline track_error calculate_compression_error(iallocator& allocator, const track_array& raw_tracks0, const track_array& raw_tracks1, const itransform_error_metric& error_metric)
+	{
+		using namespace acl_impl;
 
-		const float duration = raw_tracks0.get_duration();
-		const float sample_rate = raw_tracks0.get_sample_rate();
+		ACL_ASSERT(raw_tracks0.is_valid().empty(), "Raw tracks are invalid");
+		ACL_ASSERT(raw_tracks1.is_valid().empty(), "Raw tracks are invalid");
 
-		debug_track_writer tracks_writer0(allocator, track_type, num_tracks);
-		debug_track_writer tracks_writer1(allocator, track_type, num_tracks);
+		calculate_track_error_args args;
+		args.num_samples = raw_tracks0.get_num_samples_per_track();
+		args.num_tracks = raw_tracks0.get_num_tracks();
+		args.duration = raw_tracks0.get_duration();
+		args.sample_rate = raw_tracks0.get_sample_rate();
+		args.track_type = raw_tracks0.get_track_type();
 
-		const rtm::vector4f zero = rtm::vector_zero();
+		args.sample_tracks0 = [&raw_tracks0](float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)
+		{
+			raw_tracks0.sample_tracks(sample_time, rounding_policy, track_writer);
+		};
 
-		// Measure our error
-		for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
+		args.sample_tracks1 = [&raw_tracks1](float sample_time, sample_rounding_policy rounding_policy, debug_track_writer& track_writer)
 		{
-			const float sample_time = rtm::scalar_min(float(sample_index) / sample_rate, duration);
+			raw_tracks1.sample_tracks(sample_time, rounding_policy, track_writer);
+		};
 
-			// We use the nearest sample to accurately measure the loss that happened, if any
-			raw_tracks0.sample_tracks(sample_time, sample_rounding_policy::nearest, tracks_writer0);
-			raw_tracks1.sample_tracks(sample_time, sample_rounding_policy::nearest, tracks_writer1);
+		if (raw_tracks0.get_track_type() != track_type8::qvvf)
+			return calculate_scalar_track_error(allocator, args);
 
-			// Validate decompress_tracks
-			for (uint32_t track_index = 0; track_index < num_tracks; ++track_index)
-			{
-				rtm::vector4f error;
+		args.error_metric = &error_metric;
 
-				switch (track_type)
-				{
-				case track_type8::float1f:
-				{
-					const float raw_value = tracks_writer0.read_float1(track_index);
-					const float lossy_value = tracks_writer1.read_float1(track_index);
-					error = rtm::vector_set(rtm::scalar_abs(raw_value - lossy_value));
-					break;
-				}
-				case track_type8::float2f:
-				{
-					const rtm::vector4f raw_value = tracks_writer0.read_float2(track_index);
-					const rtm::vector4f lossy_value = tracks_writer1.read_float2(track_index);
-					error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
-					error = rtm::vector_mix<rtm::mix4::x, rtm::mix4::y, rtm::mix4::c, rtm::mix4::d>(error, zero);
-					break;
-				}
-				case track_type8::float3f:
-				{
-					const rtm::vector4f raw_value = tracks_writer0.read_float3(track_index);
-					const rtm::vector4f lossy_value = tracks_writer1.read_float3(track_index);
-					error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
-					error = rtm::vector_mix<rtm::mix4::x, rtm::mix4::y, rtm::mix4::z, rtm::mix4::d>(error, zero);
-					break;
-				}
-				case track_type8::float4f:
-				{
-					const rtm::vector4f raw_value = tracks_writer0.read_float4(track_index);
-					const rtm::vector4f lossy_value = tracks_writer1.read_float4(track_index);
-					error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
-					break;
-				}
-				case track_type8::vector4f:
-				{
-					const rtm::vector4f raw_value = tracks_writer0.read_vector4(track_index);
-					const rtm::vector4f lossy_value = tracks_writer1.read_vector4(track_index);
-					error = rtm::vector_abs(rtm::vector_sub(raw_value, lossy_value));
-					break;
-				}
-				default:
-					ACL_ASSERT(false, "Unsupported track type");
-					error = zero;
-					break;
-				}
+		args.get_parent_index = [&raw_tracks0](uint32_t track_index)
+		{
+			const track_qvvf& track_ = track_cast<track_qvvf>(raw_tracks0[track_index]);
+			return track_.get_description().parent_index;
+		};
 
-				const float max_error = rtm::vector_get_max_component(error);
-				if (max_error > result.error)
-				{
-					result.error = max_error;
-					result.index = track_index;
-					result.sample_time = sample_time;
-				}
-			}
-		}
+		args.get_shell_distance = [&raw_tracks0](uint32_t track_index)
+		{
+			const track_qvvf& track_ = track_cast<track_qvvf>(raw_tracks0[track_index]);
+			return track_.get_description().shell_distance;
+		};
 
-		return result;
+		return calculate_transform_track_error(allocator, args);
 	}
 }
 
diff --git a/includes/acl/compression/skeleton_error_metric.h b/includes/acl/compression/transform_error_metrics.h
similarity index 99%
rename from includes/acl/compression/skeleton_error_metric.h
rename to includes/acl/compression/transform_error_metrics.h
--- a/includes/acl/compression/skeleton_error_metric.h
+++ b/includes/acl/compression/transform_error_metrics.h
@@ -27,7 +27,6 @@
 #include "acl/core/additive_utils.h"
 #include "acl/core/impl/compiler_utils.h"
 #include "acl/core/hash.h"
-#include "acl/compression/skeleton.h"
 
 #include <rtm/matrix3x4f.h>
 #include <rtm/qvvf.h>
diff --git a/includes/acl/compression/skeleton_pose_utils.h b/includes/acl/compression/transform_pose_utils.h
similarity index 64%
rename from includes/acl/compression/skeleton_pose_utils.h
rename to includes/acl/compression/transform_pose_utils.h
--- a/includes/acl/compression/skeleton_pose_utils.h
+++ b/includes/acl/compression/transform_pose_utils.h
@@ -26,7 +26,6 @@
 
 #include "acl/core/impl/compiler_utils.h"
 #include "acl/core/error.h"
-#include "acl/compression/skeleton.h"
 
 #include <rtm/qvvf.h>
 
@@ -36,37 +35,35 @@ ACL_IMPL_FILE_PRAGMA_PUSH
 
 namespace acl
 {
-	// Note: It is safe for both pose buffers to alias since the data is sorted parent first
-	inline void local_to_object_space(const RigidSkeleton& skeleton, const rtm::qvvf* local_pose, rtm::qvvf* out_object_pose)
+	// Note: It is safe for both pose buffers to alias since the data if sorted parent first
+	inline void local_to_object_space(const uint32_t* parent_indices, const rtm::qvvf* local_pose, uint32_t num_transforms, rtm::qvvf* out_object_pose)
 	{
-		const uint16_t num_bones = skeleton.get_num_bones();
-		const RigidBone* bones = skeleton.get_bones();
-		ACL_ASSERT(num_bones != 0, "Invalid number of bones: %u", num_bones);
+		if (num_transforms == 0)
+			return;	// Nothing to do
 
 		out_object_pose[0] = local_pose[0];
 
-		for (uint16_t bone_index = 1; bone_index < num_bones; ++bone_index)
+		for (uint32_t bone_index = 1; bone_index < num_transforms; ++bone_index)
 		{
-			const uint16_t parent_bone_index = bones[bone_index].parent_index;
-			ACL_ASSERT(parent_bone_index < num_bones, "Invalid parent bone index: %u >= %u", parent_bone_index, num_bones);
+			const uint32_t parent_bone_index = parent_indices[bone_index];
+			ACL_ASSERT(parent_bone_index < num_transforms, "Invalid parent bone index: %u >= %u", parent_bone_index, num_transforms);
 
 			out_object_pose[bone_index] = rtm::qvv_normalize(rtm::qvv_mul(local_pose[bone_index], out_object_pose[parent_bone_index]));
 		}
 	}
 
-	// Note: It is safe for both pose buffers to alias since the data is sorted parent first
-	inline void object_to_local_space(const RigidSkeleton& skeleton, const rtm::qvvf* object_pose, rtm::qvvf* out_local_pose)
+	// Note: It is safe for both pose buffers to alias since the data if sorted parent first
+	inline void object_to_local_space(const uint32_t* parent_indices, const rtm::qvvf* object_pose, uint32_t num_transforms, rtm::qvvf* out_local_pose)
 	{
-		uint16_t num_bones = skeleton.get_num_bones();
-		const RigidBone* bones = skeleton.get_bones();
-		ACL_ASSERT(num_bones != 0, "Invalid number of bones: %u", num_bones);
+		if (num_transforms == 0)
+			return;	// Nothing to do
 
 		out_local_pose[0] = object_pose[0];
 
-		for (uint16_t bone_index = 1; bone_index < num_bones; ++bone_index)
+		for (uint32_t bone_index = 1; bone_index < num_transforms; ++bone_index)
 		{
-			const uint16_t parent_bone_index = bones[bone_index].parent_index;
-			ACL_ASSERT(parent_bone_index < num_bones, "Invalid parent bone index: %u >= %u", parent_bone_index, num_bones);
+			const uint32_t parent_bone_index = parent_indices[bone_index];
+			ACL_ASSERT(parent_bone_index < num_transforms, "Invalid parent bone index: %u >= %u", parent_bone_index, num_transforms);
 
 			const rtm::qvvf inv_parent_transform = rtm::qvv_inverse(object_pose[parent_bone_index]);
 			out_local_pose[bone_index] = rtm::qvv_normalize(rtm::qvv_mul(inv_parent_transform, object_pose[bone_index]));
diff --git a/includes/acl/compression/utils.h b/includes/acl/compression/utils.h
deleted file mode 100644
--- a/includes/acl/compression/utils.h
+++ /dev/null
@@ -1,552 +0,0 @@
-#pragma once
-
-////////////////////////////////////////////////////////////////////////////////
-// The MIT License (MIT)
-//
-// Copyright (c) 2018 Nicholas Frechette & Animation Compression Library contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-////////////////////////////////////////////////////////////////////////////////
-
-#include "acl/algorithm/uniformly_sampled/decoder.h"
-#include "acl/core/impl/compiler_utils.h"
-#include "acl/core/compressed_clip.h"
-#include "acl/core/iallocator.h"
-#include "acl/compression/compression_settings.h"
-#include "acl/compression/skeleton.h"
-#include "acl/compression/animation_clip.h"
-#include "acl/decompression/default_output_writer.h"
-
-#include <cstdint>
-#include <cstring>
-#include <type_traits>
-
-ACL_IMPL_FILE_PRAGMA_PUSH
-
-namespace acl
-{
-	namespace acl_impl
-	{
-		//////////////////////////////////////////////////////////////////////////
-		// SFINAE boilerplate to detect if a template argument derives from acl::uniformly_sampled::DecompressionContext.
-		//////////////////////////////////////////////////////////////////////////
-		template<class T>
-		using IsDecompressionContext = typename std::enable_if<std::is_base_of<acl::uniformly_sampled::DecompressionContext<typename T::SettingsType>, T>::value, std::nullptr_t>::type;
-	}
-
-	//////////////////////////////////////////////////////////////////////////
-	// Encapsulates the compression error for a specific bone at a point in time.
-	//////////////////////////////////////////////////////////////////////////
-	struct BoneError
-	{
-		BoneError() : index(k_invalid_bone_index), error(0.0F), sample_time(0.0F) {}
-
-		//////////////////////////////////////////////////////////////////////////
-		// The bone index.
-		uint16_t index;
-
-		//////////////////////////////////////////////////////////////////////////
-		// The measured error value.
-		float error;
-
-		//////////////////////////////////////////////////////////////////////////
-		// The point in time where the error was measured.
-		float sample_time;
-	};
-
-	//////////////////////////////////////////////////////////////////////////
-	// Calculates the error between a raw clip and a compressed clip.
-	//    - allocator: An allocate we can use for temporary data
-	//    - error_metric: The error metric to use when measuring the error
-	//    - clip: A raw clip
-	//    - context: A decompression context bound to a compressed clip
-	//
-	// Note: This function uses SFINAE to prevent it from matching when it shouldn't.
-	//////////////////////////////////////////////////////////////////////////
-	template<class DecompressionContextType, acl_impl::IsDecompressionContext<DecompressionContextType> = nullptr>
-	inline BoneError calculate_error_between_clips(IAllocator& allocator, const itransform_error_metric& error_metric, const AnimationClip& clip, DecompressionContextType& context)
-	{
-		ACL_ASSERT(clip.is_valid().empty(), "Clip is invalid");
-		ACL_ASSERT(context.is_initialized(), "Context isn't initialized");
-
-		const uint32_t num_samples = clip.get_num_samples();
-		if (num_samples == 0)
-			return BoneError();	// Cannot measure any error
-
-		const uint16_t num_bones = clip.get_num_bones();
-		if (num_bones == 0)
-			return BoneError();	// Cannot measure any error
-
-		const float clip_duration = clip.get_duration();
-		const float sample_rate = clip.get_sample_rate();
-		const RigidSkeleton& skeleton = clip.get_skeleton();
-
-		// Always calculate the error with scale, slower but we don't need to know if we have scale or not
-		const bool has_scale = true;
-
-		uint16_t num_output_bones = 0;
-		uint16_t* output_bone_mapping = create_output_bone_mapping(allocator, clip, num_output_bones);
-
-		const AnimationClip* additive_base_clip = clip.get_additive_base();
-		const uint32_t additive_num_samples = additive_base_clip != nullptr ? additive_base_clip->get_num_samples() : 0;
-		const float additive_duration = additive_base_clip != nullptr ? additive_base_clip->get_duration() : 0.0F;
-
-		rtm::qvvf* raw_local_pose = allocate_type_array<rtm::qvvf>(allocator, num_bones);
-		rtm::qvvf* base_local_pose = additive_base_clip != nullptr ? allocate_type_array<rtm::qvvf>(allocator, num_bones) : nullptr;
-		rtm::qvvf* lossy_local_pose = allocate_type_array<rtm::qvvf>(allocator, num_output_bones);
-		rtm::qvvf* lossy_remapped_local_pose = allocate_type_array<rtm::qvvf>(allocator, num_bones);
-
-		const size_t transform_size = error_metric.get_transform_size(has_scale);
-		const bool needs_conversion = error_metric.needs_conversion(has_scale);
-		uint8_t* raw_local_pose_converted = nullptr;
-		uint8_t* base_local_pose_converted = nullptr;
-		uint8_t* lossy_local_pose_converted = nullptr;
-		if (needs_conversion)
-		{
-			raw_local_pose_converted = allocate_type_array_aligned<uint8_t>(allocator, num_bones * transform_size, 64);
-			base_local_pose_converted = additive_base_clip != nullptr ? allocate_type_array_aligned<uint8_t>(allocator, num_bones * transform_size, 64) : nullptr;
-			lossy_local_pose_converted = allocate_type_array_aligned<uint8_t>(allocator, num_bones * transform_size, 64);
-		}
-
-		uint8_t* raw_object_pose = allocate_type_array_aligned<uint8_t>(allocator, num_bones * transform_size, 64);
-		uint8_t* lossy_object_pose = allocate_type_array_aligned<uint8_t>(allocator, num_bones * transform_size, 64);
-
-		uint16_t* parent_transform_indices = allocate_type_array<uint16_t>(allocator, num_bones);
-		uint16_t* self_transform_indices = allocate_type_array<uint16_t>(allocator, num_bones);
-
-		for (uint16_t transform_index = 0; transform_index < num_bones; ++transform_index)
-		{
-			const RigidBone& bone = skeleton.get_bone(transform_index);
-			parent_transform_indices[transform_index] = bone.parent_index;
-			self_transform_indices[transform_index] = transform_index;
-		}
-
-		void* raw_local_pose_ = needs_conversion ? (void*)raw_local_pose_converted : (void*)raw_local_pose;
-		void* base_local_pose_ = needs_conversion ? (void*)base_local_pose_converted : (void*)base_local_pose;
-		void* lossy_local_pose_ = needs_conversion ? (void*)lossy_local_pose_converted : (void*)lossy_remapped_local_pose;
-
-		itransform_error_metric::convert_transforms_args convert_transforms_args_raw;
-		convert_transforms_args_raw.dirty_transform_indices = self_transform_indices;
-		convert_transforms_args_raw.num_dirty_transforms = num_bones;
-		convert_transforms_args_raw.transforms = raw_local_pose;
-		convert_transforms_args_raw.num_transforms = num_bones;
-
-		itransform_error_metric::convert_transforms_args convert_transforms_args_base = convert_transforms_args_raw;
-		convert_transforms_args_base.transforms = base_local_pose;
-
-		itransform_error_metric::convert_transforms_args convert_transforms_args_lossy = convert_transforms_args_raw;
-		convert_transforms_args_lossy.transforms = lossy_remapped_local_pose;
-
-		itransform_error_metric::apply_additive_to_base_args apply_additive_to_base_args_raw;
-		apply_additive_to_base_args_raw.dirty_transform_indices = self_transform_indices;
-		apply_additive_to_base_args_raw.num_dirty_transforms = num_bones;
-		apply_additive_to_base_args_raw.local_transforms = raw_local_pose_;
-		apply_additive_to_base_args_raw.base_transforms = base_local_pose_;
-		apply_additive_to_base_args_raw.num_transforms = num_bones;
-
-		itransform_error_metric::apply_additive_to_base_args apply_additive_to_base_args_lossy = apply_additive_to_base_args_raw;
-		apply_additive_to_base_args_lossy.local_transforms = lossy_local_pose_;
-
-		itransform_error_metric::local_to_object_space_args local_to_object_space_args_raw;
-		local_to_object_space_args_raw.dirty_transform_indices = self_transform_indices;
-		local_to_object_space_args_raw.num_dirty_transforms = num_bones;
-		local_to_object_space_args_raw.parent_transform_indices = parent_transform_indices;
-		local_to_object_space_args_raw.local_transforms = raw_local_pose_;
-		local_to_object_space_args_raw.num_transforms = num_bones;
-
-		itransform_error_metric::local_to_object_space_args local_to_object_space_args_lossy = local_to_object_space_args_raw;
-		local_to_object_space_args_lossy.local_transforms = lossy_local_pose_;
-
-		BoneError bone_error;
-		bone_error.error = -1.0F;	// Can never have a negative error, use -1 so the first sample is used
-		DefaultOutputWriter pose_writer(lossy_local_pose, num_output_bones);
-
-		for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
-		{
-			const float sample_time = rtm::scalar_min(float(sample_index) / sample_rate, clip_duration);
-
-			// We use the nearest sample to accurately measure the loss that happened, if any
-			clip.sample_pose(sample_time, sample_rounding_policy::nearest, raw_local_pose, num_bones);
-
-			context.seek(sample_time, sample_rounding_policy::nearest);
-			context.decompress_pose(pose_writer);
-
-			// Perform remapping by copying the raw pose first and we overwrite with the decompressed pose if
-			// the data is available
-			std::memcpy(lossy_remapped_local_pose, raw_local_pose, sizeof(rtm::qvvf) * num_bones);
-			for (uint16_t output_index = 0; output_index < num_output_bones; ++output_index)
-			{
-				const uint16_t bone_index = output_bone_mapping[output_index];
-				lossy_remapped_local_pose[bone_index] = lossy_local_pose[output_index];
-			}
-
-			if (needs_conversion)
-			{
-				error_metric.convert_transforms(convert_transforms_args_raw, raw_local_pose_converted);
-				error_metric.convert_transforms(convert_transforms_args_lossy, lossy_local_pose_converted);
-			}
-
-			if (additive_base_clip != nullptr)
-			{
-				const float normalized_sample_time = additive_num_samples > 1 ? (sample_time / clip_duration) : 0.0F;
-				const float additive_sample_time = additive_num_samples > 1 ? (normalized_sample_time * additive_duration) : 0.0F;
-				additive_base_clip->sample_pose(additive_sample_time, sample_rounding_policy::nearest, base_local_pose, num_bones);
-
-				if (needs_conversion)
-					error_metric.convert_transforms(convert_transforms_args_base, base_local_pose_converted);
-
-				error_metric.apply_additive_to_base(apply_additive_to_base_args_raw, raw_local_pose_);
-				error_metric.apply_additive_to_base(apply_additive_to_base_args_lossy, lossy_local_pose_);
-			}
-
-			error_metric.local_to_object_space(local_to_object_space_args_raw, raw_object_pose);
-			error_metric.local_to_object_space(local_to_object_space_args_lossy, lossy_object_pose);
-
-			for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
-			{
-				const RigidBone& bone = skeleton.get_bone(bone_index);
-
-				itransform_error_metric::calculate_error_args calculate_error_args;
-				calculate_error_args.transform0 = raw_object_pose + (bone_index * transform_size);
-				calculate_error_args.transform1 = lossy_object_pose + (bone_index * transform_size);
-				calculate_error_args.construct_sphere_shell(bone.vertex_distance);
-
-				const float error = rtm::scalar_cast(error_metric.calculate_error(calculate_error_args));
-
-				if (error > bone_error.error)
-				{
-					bone_error.error = error;
-					bone_error.index = bone_index;
-					bone_error.sample_time = sample_time;
-				}
-			}
-		}
-
-		deallocate_type_array(allocator, output_bone_mapping, num_output_bones);
-		deallocate_type_array(allocator, raw_local_pose, num_bones);
-		deallocate_type_array(allocator, base_local_pose, num_bones);
-		deallocate_type_array(allocator, lossy_local_pose, num_output_bones);
-		deallocate_type_array(allocator, lossy_remapped_local_pose, num_bones);
-		deallocate_type_array(allocator, raw_local_pose_converted, num_bones * transform_size);
-		deallocate_type_array(allocator, base_local_pose_converted, num_bones * transform_size);
-		deallocate_type_array(allocator, lossy_local_pose_converted, num_bones * transform_size);
-		deallocate_type_array(allocator, raw_object_pose, num_bones * transform_size);
-		deallocate_type_array(allocator, lossy_object_pose, num_bones * transform_size);
-		deallocate_type_array(allocator, parent_transform_indices, num_bones);
-		deallocate_type_array(allocator, self_transform_indices, num_bones);
-
-		return bone_error;
-	}
-
-	//////////////////////////////////////////////////////////////////////////
-	// Calculates the error between two compressed clips.
-	//    - allocator: An allocate we can use for temporary data
-	//    - error_metric: The error metric to use when measuring the error
-	//    - skeleton: The skeleton used to compress the clips
-	//    - context0: A decompression context bound to a compressed clip
-	//    - context1: A decompression context bound to a compressed clip
-	//
-	// Note: This function uses SFINAE to prevent it from matching when it shouldn't.
-	//////////////////////////////////////////////////////////////////////////
-	template<class DecompressionContextType0, class DecompressionContextType1, acl_impl::IsDecompressionContext<DecompressionContextType0> = nullptr, acl_impl::IsDecompressionContext<DecompressionContextType1> = nullptr>
-	inline BoneError calculate_error_between_clips(IAllocator& allocator, const itransform_error_metric& error_metric, const RigidSkeleton& skeleton, DecompressionContextType0& context0, DecompressionContextType1& context1)
-	{
-		ACL_ASSERT(context0.is_initialized(), "Context isn't initialized");
-		ACL_ASSERT(context1.is_initialized(), "Context isn't initialized");
-
-		const ClipHeader& clip_header = get_clip_header(*context0.get_compressed_clip());
-		const uint32_t num_samples = clip_header.num_samples;
-		if (num_samples == 0)
-			return BoneError();	// Cannot measure any error
-
-		const uint16_t num_bones = clip_header.num_bones;
-		if (num_bones == 0)
-			return BoneError();	// Cannot measure any error
-
-		const float sample_rate = clip_header.sample_rate;
-		const float clip_duration = calculate_duration(num_samples, sample_rate);
-
-		// Always calculate the error with scale, slower but we don't need to know if we have scale or not
-		const bool has_scale = true;
-
-		rtm::qvvf* local_pose0 = allocate_type_array<rtm::qvvf>(allocator, num_bones);
-		rtm::qvvf* local_pose1 = allocate_type_array<rtm::qvvf>(allocator, num_bones);
-
-		const size_t transform_size = error_metric.get_transform_size(has_scale);
-		const bool needs_conversion = error_metric.needs_conversion(has_scale);
-		uint8_t* local_pose_converted0 = nullptr;
-		uint8_t* local_pose_converted1 = nullptr;
-		if (needs_conversion)
-		{
-			local_pose_converted0 = allocate_type_array_aligned<uint8_t>(allocator, num_bones * transform_size, 64);
-			local_pose_converted1 = allocate_type_array_aligned<uint8_t>(allocator, num_bones * transform_size, 64);
-		}
-
-		uint8_t* object_pose0 = allocate_type_array_aligned<uint8_t>(allocator, num_bones * transform_size, 64);
-		uint8_t* object_pose1 = allocate_type_array_aligned<uint8_t>(allocator, num_bones * transform_size, 64);
-
-		uint16_t* parent_transform_indices = allocate_type_array<uint16_t>(allocator, num_bones);
-		uint16_t* self_transform_indices = allocate_type_array<uint16_t>(allocator, num_bones);
-
-		for (uint16_t transform_index = 0; transform_index < num_bones; ++transform_index)
-		{
-			const RigidBone& bone = skeleton.get_bone(transform_index);
-			parent_transform_indices[transform_index] = bone.parent_index;
-			self_transform_indices[transform_index] = transform_index;
-		}
-
-		void* local_pose0_ = needs_conversion ? (void*)local_pose_converted0 : (void*)local_pose0;
-		void* local_pose1_ = needs_conversion ? (void*)local_pose_converted1 : (void*)local_pose1;
-
-		itransform_error_metric::convert_transforms_args convert_transforms_args0;
-		convert_transforms_args0.dirty_transform_indices = self_transform_indices;
-		convert_transforms_args0.num_dirty_transforms = num_bones;
-		convert_transforms_args0.transforms = local_pose0;
-		convert_transforms_args0.num_transforms = num_bones;
-
-		itransform_error_metric::convert_transforms_args convert_transforms_args1 = convert_transforms_args0;
-		convert_transforms_args1.transforms = local_pose1;
-
-		itransform_error_metric::local_to_object_space_args local_to_object_space_args0;
-		local_to_object_space_args0.dirty_transform_indices = self_transform_indices;
-		local_to_object_space_args0.num_dirty_transforms = num_bones;
-		local_to_object_space_args0.parent_transform_indices = parent_transform_indices;
-		local_to_object_space_args0.local_transforms = local_pose0_;
-		local_to_object_space_args0.num_transforms = num_bones;
-
-		itransform_error_metric::local_to_object_space_args local_to_object_space_args1 = local_to_object_space_args0;
-		local_to_object_space_args1.local_transforms = local_pose1_;
-
-		BoneError bone_error;
-		bone_error.error = -1.0F;	// Can never have a negative error, use -1 so the first sample is used
-
-		DefaultOutputWriter pose_writer0(local_pose0, num_bones);
-		DefaultOutputWriter pose_writer1(local_pose1, num_bones);
-
-		for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
-		{
-			const float sample_time = rtm::scalar_min(float(sample_index) / sample_rate, clip_duration);
-
-			// We use the nearest sample to accurately measure the loss that happened, if any
-			context0.seek(sample_time, sample_rounding_policy::nearest);
-			context0.decompress_pose(pose_writer0);
-
-			context1.seek(sample_time, sample_rounding_policy::nearest);
-			context1.decompress_pose(pose_writer1);
-
-			if (needs_conversion)
-			{
-				error_metric.convert_transforms(convert_transforms_args0, local_pose_converted0);
-				error_metric.convert_transforms(convert_transforms_args1, local_pose_converted1);
-			}
-
-			error_metric.local_to_object_space(local_to_object_space_args0, object_pose0);
-			error_metric.local_to_object_space(local_to_object_space_args1, object_pose1);
-
-			for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
-			{
-				const RigidBone& bone = skeleton.get_bone(bone_index);
-
-				itransform_error_metric::calculate_error_args calculate_error_args;
-				calculate_error_args.transform0 = object_pose0 + (bone_index * transform_size);
-				calculate_error_args.transform1 = object_pose1 + (bone_index * transform_size);
-				calculate_error_args.construct_sphere_shell(bone.vertex_distance);
-
-				const float error = rtm::scalar_cast(error_metric.calculate_error(calculate_error_args));
-
-				if (error > bone_error.error)
-				{
-					bone_error.error = error;
-					bone_error.index = bone_index;
-					bone_error.sample_time = sample_time;
-				}
-			}
-		}
-
-		deallocate_type_array(allocator, local_pose0, num_bones);
-		deallocate_type_array(allocator, local_pose1, num_bones);
-		deallocate_type_array(allocator, local_pose_converted0, num_bones * transform_size);
-		deallocate_type_array(allocator, local_pose_converted1, num_bones * transform_size);
-		deallocate_type_array(allocator, object_pose0, num_bones * transform_size);
-		deallocate_type_array(allocator, object_pose1, num_bones * transform_size);
-		deallocate_type_array(allocator, parent_transform_indices, num_bones);
-		deallocate_type_array(allocator, self_transform_indices, num_bones);
-
-		return bone_error;
-	}
-
-	//////////////////////////////////////////////////////////////////////////
-	// Calculates the error between two raw clips.
-	//    - allocator: An allocate we can use for temporary data
-	//    - error_metric: The error metric to use when measuring the error
-	//    - clip0: A raw clip
-	//    - clip1: A raw clip
-	//////////////////////////////////////////////////////////////////////////
-	inline BoneError calculate_error_between_clips(IAllocator& allocator, const itransform_error_metric& error_metric, const AnimationClip& clip0, const AnimationClip& clip1)
-	{
-		ACL_ASSERT(clip0.is_valid().empty(), "Clip is invalid");
-		ACL_ASSERT(clip1.is_valid().empty(), "Clip is invalid");
-		ACL_ASSERT(clip0.get_additive_base() == nullptr, "Additive clip not supported");
-		ACL_ASSERT(clip1.get_additive_base() == nullptr, "Additive clip not supported");
-
-		const uint32_t num_samples = clip0.get_num_samples();
-		if (num_samples == 0)
-			return BoneError();	// Cannot measure any error
-
-		const uint16_t num_bones = clip0.get_num_bones();
-		if (num_bones == 0 || num_bones != clip1.get_num_bones())
-			return BoneError();	// Cannot measure any error
-
-		const float clip_duration = clip0.get_duration();
-		const float sample_rate = clip0.get_sample_rate();
-		const RigidSkeleton& skeleton = clip0.get_skeleton();
-
-		// Always calculate the error with scale, slower but we don't need to know if we have scale or not
-		const bool has_scale = true;
-
-		uint16_t num_output_bones0 = 0;
-		uint16_t* output_bone_mapping0 = create_output_bone_mapping(allocator, clip0, num_output_bones0);
-
-		uint16_t num_output_bones1 = 0;
-		uint16_t* output_bone_mapping1 = create_output_bone_mapping(allocator, clip1, num_output_bones1);
-
-		rtm::qvvf* local_pose0 = allocate_type_array<rtm::qvvf>(allocator, num_output_bones0);
-		rtm::qvvf* local_pose1 = allocate_type_array<rtm::qvvf>(allocator, num_output_bones1);
-		rtm::qvvf* remapped_local_pose0 = allocate_type_array<rtm::qvvf>(allocator, num_bones);
-		rtm::qvvf* remapped_local_pose1 = allocate_type_array<rtm::qvvf>(allocator, num_bones);
-
-		const size_t transform_size = error_metric.get_transform_size(has_scale);
-		const bool needs_conversion = error_metric.needs_conversion(has_scale);
-		uint8_t* local_pose_converted0 = nullptr;
-		uint8_t* local_pose_converted1 = nullptr;
-		if (needs_conversion)
-		{
-			local_pose_converted0 = allocate_type_array_aligned<uint8_t>(allocator, num_bones * transform_size, 64);
-			local_pose_converted1 = allocate_type_array_aligned<uint8_t>(allocator, num_bones * transform_size, 64);
-		}
-
-		uint8_t* object_pose0 = allocate_type_array_aligned<uint8_t>(allocator, num_bones * transform_size, 64);
-		uint8_t* object_pose1 = allocate_type_array_aligned<uint8_t>(allocator, num_bones * transform_size, 64);
-
-		uint16_t* parent_transform_indices = allocate_type_array<uint16_t>(allocator, num_bones);
-		uint16_t* self_transform_indices = allocate_type_array<uint16_t>(allocator, num_bones);
-
-		for (uint16_t transform_index = 0; transform_index < num_bones; ++transform_index)
-		{
-			const RigidBone& bone = skeleton.get_bone(transform_index);
-			parent_transform_indices[transform_index] = bone.parent_index;
-			self_transform_indices[transform_index] = transform_index;
-		}
-
-		void* local_pose0_ = needs_conversion ? (void*)local_pose_converted0 : (void*)remapped_local_pose0;
-		void* local_pose1_ = needs_conversion ? (void*)local_pose_converted1 : (void*)remapped_local_pose1;
-
-		itransform_error_metric::convert_transforms_args convert_transforms_args0;
-		convert_transforms_args0.dirty_transform_indices = self_transform_indices;
-		convert_transforms_args0.num_dirty_transforms = num_bones;
-		convert_transforms_args0.transforms = remapped_local_pose0;
-		convert_transforms_args0.num_transforms = num_bones;
-
-		itransform_error_metric::convert_transforms_args convert_transforms_args1 = convert_transforms_args0;
-		convert_transforms_args1.transforms = remapped_local_pose1;
-
-		itransform_error_metric::local_to_object_space_args local_to_object_space_args0;
-		local_to_object_space_args0.dirty_transform_indices = self_transform_indices;
-		local_to_object_space_args0.num_dirty_transforms = num_bones;
-		local_to_object_space_args0.parent_transform_indices = parent_transform_indices;
-		local_to_object_space_args0.local_transforms = local_pose0_;
-		local_to_object_space_args0.num_transforms = num_bones;
-
-		itransform_error_metric::local_to_object_space_args local_to_object_space_args1 = local_to_object_space_args0;
-		local_to_object_space_args1.local_transforms = local_pose1_;
-
-		BoneError bone_error;
-		bone_error.error = -1.0F;	// Can never have a negative error, use -1 so the first sample is used
-
-		for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
-		{
-			const float sample_time = rtm::scalar_min(float(sample_index) / sample_rate, clip_duration);
-
-			// We use the nearest sample to accurately measure the loss that happened, if any
-			clip0.sample_pose(sample_time, sample_rounding_policy::nearest, local_pose0, num_bones);
-			clip1.sample_pose(sample_time, sample_rounding_policy::nearest, local_pose1, num_bones);
-
-			// Perform remapping by copying the raw pose first and we overwrite with the decompressed pose if
-			// the data is available
-			std::memcpy(remapped_local_pose0, local_pose0, sizeof(rtm::qvvf) * num_bones);
-			for (uint16_t output_index = 0; output_index < num_output_bones0; ++output_index)
-			{
-				const uint16_t bone_index = output_bone_mapping0[output_index];
-				remapped_local_pose0[bone_index] = local_pose0[output_index];
-			}
-
-			std::memcpy(remapped_local_pose1, local_pose1, sizeof(rtm::qvvf) * num_bones);
-			for (uint16_t output_index = 0; output_index < num_output_bones1; ++output_index)
-			{
-				const uint16_t bone_index = output_bone_mapping1[output_index];
-				remapped_local_pose1[bone_index] = local_pose1[output_index];
-			}
-
-			if (needs_conversion)
-			{
-				error_metric.convert_transforms(convert_transforms_args0, local_pose_converted0);
-				error_metric.convert_transforms(convert_transforms_args1, local_pose_converted1);
-			}
-
-			error_metric.local_to_object_space(local_to_object_space_args0, object_pose0);
-			error_metric.local_to_object_space(local_to_object_space_args1, object_pose1);
-
-			for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
-			{
-				const RigidBone& bone = skeleton.get_bone(bone_index);
-
-				itransform_error_metric::calculate_error_args calculate_error_args;
-				calculate_error_args.transform0 = object_pose0 + (bone_index * transform_size);
-				calculate_error_args.transform1 = object_pose1 + (bone_index * transform_size);
-				calculate_error_args.construct_sphere_shell(bone.vertex_distance);
-
-				const float error = rtm::scalar_cast(error_metric.calculate_error(calculate_error_args));
-
-				if (error > bone_error.error)
-				{
-					bone_error.error = error;
-					bone_error.index = bone_index;
-					bone_error.sample_time = sample_time;
-				}
-			}
-		}
-
-		deallocate_type_array(allocator, output_bone_mapping0, num_output_bones0);
-		deallocate_type_array(allocator, output_bone_mapping1, num_output_bones1);
-		deallocate_type_array(allocator, local_pose0, num_output_bones0);
-		deallocate_type_array(allocator, local_pose1, num_output_bones1);
-		deallocate_type_array(allocator, remapped_local_pose0, num_bones);
-		deallocate_type_array(allocator, remapped_local_pose1, num_bones);
-		deallocate_type_array(allocator, local_pose_converted0, num_bones * transform_size);
-		deallocate_type_array(allocator, local_pose_converted1, num_bones * transform_size);
-		deallocate_type_array(allocator, object_pose0, num_bones * transform_size);
-		deallocate_type_array(allocator, object_pose1, num_bones * transform_size);
-		deallocate_type_array(allocator, parent_transform_indices, num_bones);
-		deallocate_type_array(allocator, self_transform_indices, num_bones);
-
-		return bone_error;
-	}
-}
-
-ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/core/additive_utils.h b/includes/acl/core/additive_utils.h
--- a/includes/acl/core/additive_utils.h
+++ b/includes/acl/core/additive_utils.h
@@ -26,7 +26,6 @@
 
 #include "acl/core/impl/compiler_utils.h"
 
-#include <rtm/qvvd.h>
 #include <rtm/qvvf.h>
 
 #include <cstdint>
@@ -167,24 +166,24 @@ namespace acl
 		}
 	}
 
-	inline rtm::qvvd convert_to_relative(const rtm::qvvd& base, const rtm::qvvd& transform)
+	inline rtm::qvvf RTM_SIMD_CALL convert_to_relative(rtm::qvvf_arg0 base, rtm::qvvf_arg1 transform)
 	{
 		return rtm::qvv_mul(transform, rtm::qvv_inverse(base));
 	}
 
-	inline rtm::qvvd convert_to_additive0(const rtm::qvvd& base, const rtm::qvvd& transform)
+	inline rtm::qvvf RTM_SIMD_CALL convert_to_additive0(rtm::qvvf_arg0 base, rtm::qvvf_arg1 transform)
 	{
-		const rtm::quatd rotation = rtm::quat_mul(transform.rotation, rtm::quat_conjugate(base.rotation));
-		const rtm::vector4d translation = rtm::vector_sub(transform.translation, base.translation);
-		const rtm::vector4d scale = rtm::vector_div(transform.scale, base.scale);
+		const rtm::quatf rotation = rtm::quat_mul(transform.rotation, rtm::quat_conjugate(base.rotation));
+		const rtm::vector4f translation = rtm::vector_sub(transform.translation, base.translation);
+		const rtm::vector4f scale = rtm::vector_div(transform.scale, base.scale);
 		return rtm::qvv_set(rotation, translation, scale);
 	}
 
-	inline rtm::qvvd convert_to_additive1(const rtm::qvvd& base, const rtm::qvvd& transform)
+	inline rtm::qvvf RTM_SIMD_CALL convert_to_additive1(rtm::qvvf_arg0 base, rtm::qvvf_arg1 transform)
 	{
-		const rtm::quatd rotation = rtm::quat_mul(transform.rotation, rtm::quat_conjugate(base.rotation));
-		const rtm::vector4d translation = rtm::vector_sub(transform.translation, base.translation);
-		const rtm::vector4d scale = rtm::vector_sub(rtm::vector_mul(transform.scale, rtm::vector_reciprocal(base.scale)), rtm::vector_set(1.0));
+		const rtm::quatf rotation = rtm::quat_mul(transform.rotation, rtm::quat_conjugate(base.rotation));
+		const rtm::vector4f translation = rtm::vector_sub(transform.translation, base.translation);
+		const rtm::vector4f scale = rtm::vector_sub(rtm::vector_mul(transform.scale, rtm::vector_reciprocal(base.scale)), rtm::vector_set(1.0F));
 		return rtm::qvv_set(rotation, translation, scale);
 	}
 }
diff --git a/includes/acl/core/algorithm_versions.h b/includes/acl/core/algorithm_versions.h
--- a/includes/acl/core/algorithm_versions.h
+++ b/includes/acl/core/algorithm_versions.h
@@ -43,7 +43,7 @@ namespace acl
 	{
 		switch (type)
 		{
-			case algorithm_type8::uniformly_sampled:	return 6;
+			case algorithm_type8::uniformly_sampled:	return 7;
 			//case algorithm_type8::LinearKeyReduction:	return 0;
 			//case algorithm_type8::SplineKeyReduction:	return 0;
 			default:									return 0xFFFF;
diff --git a/includes/acl/core/ansi_allocator.h b/includes/acl/core/ansi_allocator.h
--- a/includes/acl/core/ansi_allocator.h
+++ b/includes/acl/core/ansi_allocator.h
@@ -58,11 +58,11 @@ namespace acl
 	// An ANSI allocator implementation. It uses the system malloc/free to manage
 	// memory as well as provides some debugging functionality to track memory leaks.
 	////////////////////////////////////////////////////////////////////////////////
-	class ANSIAllocator : public IAllocator
+	class ansi_allocator : public iallocator
 	{
 	public:
-		ANSIAllocator()
-			: IAllocator()
+		ansi_allocator()
+			: iallocator()
 #if defined(ACL_ALLOCATOR_TRACK_NUM_ALLOCATIONS)
 			, m_allocation_count(0)
 #endif
@@ -71,7 +71,7 @@ namespace acl
 #endif
 		{}
 
-		virtual ~ANSIAllocator()
+		virtual ~ansi_allocator()
 		{
 #if defined(ACL_ALLOCATOR_TRACK_ALL_ALLOCATIONS)
 			if (!m_debug_allocations.empty())
@@ -88,8 +88,8 @@ namespace acl
 #endif
 		}
 
-		ANSIAllocator(const ANSIAllocator&) = delete;
-		ANSIAllocator& operator=(const ANSIAllocator&) = delete;
+		ansi_allocator(const ansi_allocator&) = delete;
+		ansi_allocator& operator=(const ansi_allocator&) = delete;
 
 		virtual void* allocate(size_t size, size_t alignment = k_default_alignment) override
 		{
diff --git a/includes/acl/core/bitset.h b/includes/acl/core/bitset.h
--- a/includes/acl/core/bitset.h
+++ b/includes/acl/core/bitset.h
@@ -39,28 +39,28 @@ namespace acl
 	// A bit set description holds the required information to ensure type and memory safety
 	// with the various bit set functions.
 	////////////////////////////////////////////////////////////////////////////////
-	class BitSetDescription
+	class bitset_description
 	{
 	public:
 		////////////////////////////////////////////////////////////////////////////////
 		// Creates an invalid bit set description.
-		constexpr BitSetDescription() : m_size(0) {}
+		constexpr bitset_description() : m_size(0) {}
 
 		////////////////////////////////////////////////////////////////////////////////
 		// Creates a bit set description from a compile time known number of bits.
 		template<uint64_t num_bits>
-		static constexpr BitSetDescription make_from_num_bits()
+		static constexpr bitset_description make_from_num_bits()
 		{
 			static_assert(num_bits <= std::numeric_limits<uint32_t>::max() - 31, "Number of bits exceeds the maximum number allowed");
-			return BitSetDescription((uint32_t(num_bits) + 32 - 1) / 32);
+			return bitset_description((uint32_t(num_bits) + 32 - 1) / 32);
 		}
 
 		////////////////////////////////////////////////////////////////////////////////
 		// Creates a bit set description from a runtime known number of bits.
-		inline static BitSetDescription make_from_num_bits(uint32_t num_bits)
+		inline static bitset_description make_from_num_bits(uint32_t num_bits)
 		{
 			ACL_ASSERT(num_bits <= std::numeric_limits<uint32_t>::max() - 31, "Number of bits exceeds the maximum number allowed");
-			return BitSetDescription((num_bits + 32 - 1) / 32);
+			return bitset_description((num_bits + 32 - 1) / 32);
 		}
 
 		////////////////////////////////////////////////////////////////////////////////
@@ -83,7 +83,7 @@ namespace acl
 	private:
 		////////////////////////////////////////////////////////////////////////////////
 		// Creates a bit set description from a specified size.
-		explicit constexpr BitSetDescription(uint32_t size) : m_size(size) {}
+		explicit constexpr bitset_description(uint32_t size) : m_size(size) {}
 
 		// Number of words required to hold the bit set
 		// 1 == 32 bits, 2 == 64 bits, etc.
@@ -95,15 +95,15 @@ namespace acl
 	// It holds the bit set word offset as well as the bit mask required.
 	// This is useful if you sample multiple bit sets at the same index.
 	//////////////////////////////////////////////////////////////////////////
-	struct BitSetIndexRef
+	struct bitset_index_ref
 	{
-		BitSetIndexRef()
+		bitset_index_ref()
 			: desc()
 			, offset(0)
 			, mask(0)
 		{}
 
-		BitSetIndexRef(BitSetDescription desc_, uint32_t bit_index)
+		bitset_index_ref(bitset_description desc_, uint32_t bit_index)
 			: desc(desc_)
 			, offset(bit_index / 32)
 			, mask(1 << (31 - (bit_index % 32)))
@@ -111,14 +111,14 @@ namespace acl
 			ACL_ASSERT(desc_.is_bit_index_valid(bit_index), "Invalid bit index: %d", bit_index);
 		}
 
-		BitSetDescription desc;
+		bitset_description desc;
 		uint32_t offset;
 		uint32_t mask;
 	};
 
 	////////////////////////////////////////////////////////////////////////////////
 	// Resets the entire bit set to the provided value.
-	inline void bitset_reset(uint32_t* bitset, BitSetDescription desc, bool value)
+	inline void bitset_reset(uint32_t* bitset, bitset_description desc, bool value)
 	{
 		const uint32_t mask = value ? 0xFFFFFFFF : 0x00000000;
 		const uint32_t size = desc.get_size();
@@ -129,7 +129,7 @@ namespace acl
 
 	////////////////////////////////////////////////////////////////////////////////
 	// Sets a specific bit to its desired value.
-	inline void bitset_set(uint32_t* bitset, BitSetDescription desc, uint32_t bit_index, bool value)
+	inline void bitset_set(uint32_t* bitset, bitset_description desc, uint32_t bit_index, bool value)
 	{
 		ACL_ASSERT(desc.is_bit_index_valid(bit_index), "Invalid bit index: %d", bit_index);
 		(void)desc;
@@ -145,7 +145,7 @@ namespace acl
 
 	////////////////////////////////////////////////////////////////////////////////
 	// Sets a specific bit to its desired value.
-	inline void bitset_set(uint32_t* bitset, const BitSetIndexRef& ref, bool value)
+	inline void bitset_set(uint32_t* bitset, const bitset_index_ref& ref, bool value)
 	{
 		if (value)
 			bitset[ref.offset] |= ref.mask;
@@ -155,7 +155,7 @@ namespace acl
 
 	////////////////////////////////////////////////////////////////////////////////
 	// Sets a specified range of bits to a specified value.
-	inline void bitset_set_range(uint32_t* bitset, BitSetDescription desc, uint32_t start_bit_index, uint32_t num_bits, bool value)
+	inline void bitset_set_range(uint32_t* bitset, bitset_description desc, uint32_t start_bit_index, uint32_t num_bits, bool value)
 	{
 		ACL_ASSERT(desc.is_bit_index_valid(start_bit_index), "Invalid start bit index: %d", start_bit_index);
 		ACL_ASSERT(start_bit_index + num_bits <= desc.get_num_bits(), "Invalid num bits: %d > %d", start_bit_index + num_bits, desc.get_num_bits());
@@ -167,7 +167,7 @@ namespace acl
 
 	////////////////////////////////////////////////////////////////////////////////
 	// Returns the bit value as a specific index.
-	inline bool bitset_test(const uint32_t* bitset, BitSetDescription desc, uint32_t bit_index)
+	inline bool bitset_test(const uint32_t* bitset, bitset_description desc, uint32_t bit_index)
 	{
 		ACL_ASSERT(desc.is_bit_index_valid(bit_index), "Invalid bit index: %d", bit_index);
 		(void)desc;
@@ -180,14 +180,14 @@ namespace acl
 
 	////////////////////////////////////////////////////////////////////////////////
 	// Returns the bit value as a specific index.
-	inline bool bitset_test(const uint32_t* bitset, const BitSetIndexRef& ref)
+	inline bool bitset_test(const uint32_t* bitset, const bitset_index_ref& ref)
 	{
 		return (bitset[ref.offset] & ref.mask) != 0;
 	}
 
 	////////////////////////////////////////////////////////////////////////////////
 	// Counts the total number of set (true) bits within the bit set.
-	inline uint32_t bitset_count_set_bits(const uint32_t* bitset, BitSetDescription desc)
+	inline uint32_t bitset_count_set_bits(const uint32_t* bitset, bitset_description desc)
 	{
 		const uint32_t size = desc.get_size();
 
@@ -204,7 +204,7 @@ namespace acl
 	// Performs the operation: result = ~not_value & and_value
 	// Bit sets must have the same description
 	// Bit sets can alias
-	inline void bitset_and_not(uint32_t* bitset_result, const uint32_t* bitset_not_value, const uint32_t* bitset_and_value, BitSetDescription desc)
+	inline void bitset_and_not(uint32_t* bitset_result, const uint32_t* bitset_not_value, const uint32_t* bitset_and_value, bitset_description desc)
 	{
 		const uint32_t size = desc.get_size();
 
diff --git a/includes/acl/core/buffer_tag.h b/includes/acl/core/buffer_tag.h
--- a/includes/acl/core/buffer_tag.h
+++ b/includes/acl/core/buffer_tag.h
@@ -38,6 +38,7 @@ namespace acl
 	{
 		//////////////////////////////////////////////////////////////////////////
 		// Identifies a 'CompressedClip' buffer.
+		// Deprecated, no longer used. Belonged to pre-2.0 file format.
 		compressed_clip = 0xac10ac10,
 
 		//////////////////////////////////////////////////////////////////////////
diff --git a/includes/acl/core/compressed_clip.h b/includes/acl/core/compressed_clip.h
deleted file mode 100644
--- a/includes/acl/core/compressed_clip.h
+++ /dev/null
@@ -1,298 +0,0 @@
-#pragma once
-
-////////////////////////////////////////////////////////////////////////////////
-// The MIT License (MIT)
-//
-// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-////////////////////////////////////////////////////////////////////////////////
-
-#include "acl/core/algorithm_versions.h"
-#include "acl/core/buffer_tag.h"
-#include "acl/core/error_result.h"
-#include "acl/core/hash.h"
-#include "acl/core/memory_utils.h"
-#include "acl/core/ptr_offset.h"
-#include "acl/core/range_reduction_types.h"
-#include "acl/core/track_types.h"
-#include "acl/core/impl/compiler_utils.h"
-
-#include <cstdint>
-
-ACL_IMPL_FILE_PRAGMA_PUSH
-
-namespace acl
-{
-	class CompressedClip;
-
-	namespace acl_impl
-	{
-		CompressedClip* make_compressed_clip(void* buffer, uint32_t size, algorithm_type8 type);
-		void finalize_compressed_clip(CompressedClip& compressed_clip);
-	}
-
-	////////////////////////////////////////////////////////////////////////////////
-	// An instance of a compressed clip.
-	// The compressed data immediately follows the clip instance.
-	////////////////////////////////////////////////////////////////////////////////
-	class alignas(16) CompressedClip
-	{
-	public:
-		////////////////////////////////////////////////////////////////////////////////
-		// Returns the algorithm type used to compress the clip.
-		algorithm_type8 get_algorithm_type() const { return m_type; }
-
-		////////////////////////////////////////////////////////////////////////////////
-		// Returns the size in bytes of the compressed clip.
-		// Includes the 'CompressedClip' instance size.
-		uint32_t get_size() const { return m_size; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the hash for this compressed clip.
-		uint32_t get_hash() const { return m_hash; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Returns the binary tag for the compressed clip.
-		// This uniquely identifies the buffer as a proper 'CompressedClip' object.
-		buffer_tag32 get_tag() const { return static_cast<buffer_tag32>(m_tag); }
-
-		////////////////////////////////////////////////////////////////////////////////
-		// Returns true if a compressed clip is valid and usable.
-		// This mainly validates some invariants as well as ensuring that the
-		// memory has not been corrupted.
-		//
-		// check_hash: If true, the compressed clip hash will also be compared.
-		ErrorResult is_valid(bool check_hash) const
-		{
-			if (!is_aligned_to(this, alignof(CompressedClip)))
-				return ErrorResult("Invalid alignment");
-
-			if (m_tag != static_cast<uint32_t>(buffer_tag32::compressed_clip))
-				return ErrorResult("Invalid tag");
-
-			if (!is_valid_algorithm_type(m_type))
-				return ErrorResult("Invalid algorithm type");
-
-			if (m_version != get_algorithm_version(m_type))
-				return ErrorResult("Invalid algorithm version");
-
-			if (check_hash) {
-				const uint32_t hash = hash32(safe_ptr_cast<const uint8_t>(this) + k_hash_skip_size, m_size - k_hash_skip_size);
-				if (hash != m_hash)
-					return ErrorResult("Invalid hash");
-			}
-
-			return ErrorResult();
-		}
-
-	private:
-		////////////////////////////////////////////////////////////////////////////////
-		// The number of bytes to skip in the header when calculating the hash.
-		static constexpr uint32_t k_hash_skip_size = sizeof(uint32_t) + sizeof(uint32_t);	// m_size + m_hash
-
-		////////////////////////////////////////////////////////////////////////////////
-		// Constructs a compressed clip instance
-		CompressedClip(uint32_t size, algorithm_type8 type)
-			: m_size(size)
-			, m_hash(hash32(safe_ptr_cast<const uint8_t>(this) + k_hash_skip_size, size - k_hash_skip_size))
-			, m_tag(static_cast<uint32_t>(buffer_tag32::compressed_clip))
-			, m_version(get_algorithm_version(type))
-			, m_type(type)
-			, m_padding(0)
-		{
-			(void)m_padding;	// Avoid unused warning
-		}
-
-		////////////////////////////////////////////////////////////////////////////////
-		// 16 byte header, the rest of the data follows in memory.
-		////////////////////////////////////////////////////////////////////////////////
-
-		// Total size in bytes of the compressed clip. Includes 'sizeof(CompressedClip)'.
-		uint32_t		m_size;
-
-		// Hash of the compressed clip. Hashed memory starts immediately after this.
-		uint32_t		m_hash;
-
-		////////////////////////////////////////////////////////////////////////////////
-		// Everything starting here is included in the hash.
-		////////////////////////////////////////////////////////////////////////////////
-
-		// Serialization tag used to distinguish raw buffer types.
-		uint32_t		m_tag;
-
-		// Serialization version used to compress the clip.
-		uint16_t		m_version;
-
-		// Algorithm type used to compress the clip.
-		algorithm_type8	m_type;
-
-		// Unused memory left as padding
-		uint8_t			m_padding;
-
-		////////////////////////////////////////////////////////////////////////////////
-		// Friend function used to construct compressed clip instances. Should only
-		// be called by encoders.
-		friend CompressedClip* acl_impl::make_compressed_clip(void* buffer, uint32_t size, algorithm_type8 type);
-
-		////////////////////////////////////////////////////////////////////////////////
-		// Friend function to finalize a compressed clip once all memory has been written within.
-		friend void acl_impl::finalize_compressed_clip(CompressedClip& compressed_clip);
-	};
-
-	static_assert(alignof(CompressedClip) == 16, "Invalid alignment for CompressedClip");
-	static_assert(sizeof(CompressedClip) == 16, "Invalid size for CompressedClip");
-
-	//////////////////////////////////////////////////////////////////////////
-	// Create a CompressedClip instance in place from a raw memory buffer.
-	// If the buffer does not contain a valid CompressedClip instance, nullptr is returned
-	// along with an optional error result.
-	//////////////////////////////////////////////////////////////////////////
-	inline const CompressedClip* make_compressed_clip(const void* buffer, ErrorResult* out_error_result = nullptr)
-	{
-		if (buffer == nullptr)
-		{
-			if (out_error_result != nullptr)
-				*out_error_result = ErrorResult("Buffer is not a valid pointer");
-
-			return nullptr;
-		}
-
-		const CompressedClip* clip = static_cast<const CompressedClip*>(buffer);
-		if (out_error_result != nullptr)
-		{
-			const ErrorResult result = clip->is_valid(false);
-			*out_error_result = result;
-
-			if (result.any())
-				return nullptr;
-		}
-
-		return clip;
-	}
-
-	////////////////////////////////////////////////////////////////////////////////
-	// A compressed clip segment header. Each segment is built from a uniform number
-	// of samples per track. A clip is split into one or more segments.
-	////////////////////////////////////////////////////////////////////////////////
-	struct SegmentHeader
-	{
-		// Number of bits used by a fully animated pose (excludes default/constant tracks).
-		uint32_t				animated_pose_bit_size;						// TODO: Calculate from bitsets and formats?
-
-		// TODO: Only need one offset, calculate the others from the information we have?
-		// Offset to the per animated track format data.
-		PtrOffset32<uint8_t>	format_per_track_data_offset;				// TODO: Make this offset optional? Only present if variable
-
-		// Offset to the segment range data.
-		PtrOffset32<uint8_t>	range_data_offset;							// TODO: Make this offset optional? Only present if normalized
-
-		// Offset to the segment animated tracks data.
-		PtrOffset32<uint8_t>	track_data_offset;
-	};
-
-	////////////////////////////////////////////////////////////////////////////////
-	// A compressed clip header.
-	////////////////////////////////////////////////////////////////////////////////
-	struct ClipHeader
-	{
-		// The number of bones compressed.
-		uint16_t				num_bones;
-
-		// The number of segments contained.
-		uint16_t				num_segments;
-
-		// The rotation/translation/scale format used.
-		rotation_format8		rotation_format;
-		vector_format8			translation_format;
-		vector_format8			scale_format;								// TODO: Make this optional?
-
-		// Whether or not we have scale (bool).
-		uint8_t					has_scale;
-
-		// Whether the default scale is 0,0,0 or 1,1,1 (bool/bit).
-		uint8_t					default_scale;
-
-		uint8_t padding[3];
-
-		// The total number of samples per track our clip contained.
-		uint32_t				num_samples;
-
-		// The clip sample rate.
-		float					sample_rate;								// TODO: Store duration as float instead
-
-		// Offset to the segment headers data.
-		PtrOffset16<uint32_t>		segment_start_indices_offset;
-		PtrOffset16<SegmentHeader>	segment_headers_offset;
-
-		// Offsets to the default/constant tracks bitsets.
-		PtrOffset16<uint32_t>		default_tracks_bitset_offset;
-		PtrOffset16<uint32_t>		constant_tracks_bitset_offset;
-
-		// Offset to the constant tracks data.
-		PtrOffset16<uint8_t>		constant_track_data_offset;
-
-		// Offset to the clip range data.
-		PtrOffset16<uint8_t>		clip_range_data_offset;					// TODO: Make this offset optional? Only present if normalized
-
-		//////////////////////////////////////////////////////////////////////////
-		// Utility functions that return pointers from their respective offsets.
-
-		uint32_t*		get_segment_start_indices()			{ return segment_start_indices_offset.safe_add_to(this); }
-		const uint32_t*	get_segment_start_indices() const	{ return segment_start_indices_offset.safe_add_to(this); }
-
-		SegmentHeader*			get_segment_headers()		{ return segment_headers_offset.add_to(this); }
-		const SegmentHeader*	get_segment_headers() const	{ return segment_headers_offset.add_to(this); }
-
-		uint32_t*		get_default_tracks_bitset()			{ return default_tracks_bitset_offset.add_to(this); }
-		const uint32_t*	get_default_tracks_bitset() const	{ return default_tracks_bitset_offset.add_to(this); }
-
-		uint32_t*		get_constant_tracks_bitset()		{ return constant_tracks_bitset_offset.add_to(this); }
-		const uint32_t*	get_constant_tracks_bitset() const	{ return constant_tracks_bitset_offset.add_to(this); }
-
-		uint8_t*		get_constant_track_data()			{ return constant_track_data_offset.safe_add_to(this); }
-		const uint8_t*	get_constant_track_data() const		{ return constant_track_data_offset.safe_add_to(this); }
-
-		uint8_t*		get_format_per_track_data(const SegmentHeader& header)			{ return header.format_per_track_data_offset.safe_add_to(this); }
-		const uint8_t*	get_format_per_track_data(const SegmentHeader& header) const	{ return header.format_per_track_data_offset.safe_add_to(this); }
-
-		uint8_t*		get_clip_range_data()				{ return clip_range_data_offset.safe_add_to(this); }
-		const uint8_t*	get_clip_range_data() const			{ return clip_range_data_offset.safe_add_to(this); }
-
-		uint8_t*		get_track_data(const SegmentHeader& header)			{ return header.track_data_offset.safe_add_to(this); }
-		const uint8_t*	get_track_data(const SegmentHeader& header) const	{ return header.track_data_offset.safe_add_to(this); }
-
-		uint8_t*		get_segment_range_data(const SegmentHeader& header)			{ return header.range_data_offset.safe_add_to(this); }
-		const uint8_t*	get_segment_range_data(const SegmentHeader& header) const	{ return header.range_data_offset.safe_add_to(this); }
-	};
-
-	// Returns the clip header for a compressed clip.
-	inline ClipHeader& get_clip_header(CompressedClip& clip)
-	{
-		return *add_offset_to_ptr<ClipHeader>(&clip, sizeof(CompressedClip));
-	}
-
-	// Returns the clip header for a compressed clip.
-	inline const ClipHeader& get_clip_header(const CompressedClip& clip)
-	{
-		return *add_offset_to_ptr<const ClipHeader>(&clip, sizeof(CompressedClip));
-	}
-}
-
-ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/core/compressed_tracks.h b/includes/acl/core/compressed_tracks.h
--- a/includes/acl/core/compressed_tracks.h
+++ b/includes/acl/core/compressed_tracks.h
@@ -99,28 +99,28 @@ namespace acl
 		// memory has not been corrupted.
 		//
 		// check_hash: If true, the compressed tracks hash will also be compared.
-		ErrorResult is_valid(bool check_hash) const
+		error_result is_valid(bool check_hash) const
 		{
 			if (!is_aligned_to(this, alignof(compressed_tracks)))
-				return ErrorResult("Invalid alignment");
+				return error_result("Invalid alignment");
 
 			if (m_tracks_header.tag != static_cast<uint32_t>(buffer_tag32::compressed_tracks))
-				return ErrorResult("Invalid tag");
+				return error_result("Invalid tag");
 
 			if (!is_valid_algorithm_type(m_tracks_header.algorithm_type))
-				return ErrorResult("Invalid algorithm type");
+				return error_result("Invalid algorithm type");
 
 			if (m_tracks_header.version != get_algorithm_version(m_tracks_header.algorithm_type))
-				return ErrorResult("Invalid algorithm version");
+				return error_result("Invalid algorithm version");
 
 			if (check_hash)
 			{
 				const uint32_t hash = hash32(safe_ptr_cast<const uint8_t>(&m_tracks_header), m_buffer_header.size - sizeof(acl_impl::raw_buffer_header));
 				if (hash != m_buffer_header.hash)
-					return ErrorResult("Invalid hash");
+					return error_result("Invalid hash");
 			}
 
-			return ErrorResult();
+			return error_result();
 		}
 
 	private:
@@ -156,12 +156,12 @@ namespace acl
 	// If the buffer does not contain a valid compressed_tracks instance, nullptr is returned
 	// along with an optional error result.
 	//////////////////////////////////////////////////////////////////////////
-	inline const compressed_tracks* make_compressed_tracks(const void* buffer, ErrorResult* out_error_result = nullptr)
+	inline const compressed_tracks* make_compressed_tracks(const void* buffer, error_result* out_error_result = nullptr)
 	{
 		if (buffer == nullptr)
 		{
 			if (out_error_result != nullptr)
-				*out_error_result = ErrorResult("Buffer is not a valid pointer");
+				*out_error_result = error_result("Buffer is not a valid pointer");
 
 			return nullptr;
 		}
@@ -169,7 +169,7 @@ namespace acl
 		const compressed_tracks* clip = static_cast<const compressed_tracks*>(buffer);
 		if (out_error_result != nullptr)
 		{
-			const ErrorResult result = clip->is_valid(false);
+			const error_result result = clip->is_valid(false);
 			*out_error_result = result;
 
 			if (result.any())
@@ -181,8 +181,10 @@ namespace acl
 
 	namespace acl_impl
 	{
-		// Hide this implementation, it shouldn't be needed in user-space
+		// Hide these implementations, they shouldn't be needed in user-space
 		inline const tracks_header& get_tracks_header(const compressed_tracks& tracks) { return tracks.m_tracks_header; }
+		inline const scalar_tracks_header& get_scalar_tracks_header(const compressed_tracks& tracks) { return *reinterpret_cast<const scalar_tracks_header*>(reinterpret_cast<const uint8_t*>(&tracks) + sizeof(compressed_tracks)); }
+		inline const transform_tracks_header& get_transform_tracks_header(const compressed_tracks& tracks) { return *reinterpret_cast<const transform_tracks_header*>(reinterpret_cast<const uint8_t*>(&tracks) + sizeof(compressed_tracks)); }
 	}
 }
 
diff --git a/includes/acl/core/error_result.h b/includes/acl/core/error_result.h
--- a/includes/acl/core/error_result.h
+++ b/includes/acl/core/error_result.h
@@ -35,11 +35,11 @@ namespace acl
 	// coerce with 'bool' in order to simplify and clean up error handling.
 	// In an ideal world, I would love for a C++11 equivalent of std::result in Rust.
 	//////////////////////////////////////////////////////////////////////////
-	class ErrorResult
+	class error_result
 	{
 	public:
-		ErrorResult() : m_error(nullptr) {}
-		explicit ErrorResult(const char* error) : m_error(error) {}
+		error_result() : m_error(nullptr) {}
+		explicit error_result(const char* error) : m_error(error) {}
 
 		bool empty() const { return m_error == nullptr; }
 		bool any() const { return m_error != nullptr; }
diff --git a/includes/acl/core/iallocator.h b/includes/acl/core/iallocator.h
--- a/includes/acl/core/iallocator.h
+++ b/includes/acl/core/iallocator.h
@@ -45,16 +45,16 @@ namespace acl
 	//
 	// See ansi_allocator.h for an implementation that uses the system malloc/free.
 	////////////////////////////////////////////////////////////////////////////////
-	class IAllocator
+	class iallocator
 	{
 	public:
 		static constexpr size_t k_default_alignment = 16;
 
-		IAllocator() {}
-		virtual ~IAllocator() {}
+		iallocator() {}
+		virtual ~iallocator() {}
 
-		IAllocator(const IAllocator&) = delete;
-		IAllocator& operator=(const IAllocator&) = delete;
+		iallocator(const iallocator&) = delete;
+		iallocator& operator=(const iallocator&) = delete;
 
 		////////////////////////////////////////////////////////////////////////////////
 		// Allocates memory with the specified size and alignment.
@@ -73,73 +73,73 @@ namespace acl
 
 	//////////////////////////////////////////////////////////////////////////
 
-	template<typename AllocatedType, typename... Args>
-	AllocatedType* allocate_type(IAllocator& allocator, Args&&... args)
+	template<typename allocated_type, typename... args>
+	allocated_type* allocate_type(iallocator& allocator, args&&... arguments)
 	{
-		AllocatedType* ptr = reinterpret_cast<AllocatedType*>(allocator.allocate(sizeof(AllocatedType), alignof(AllocatedType)));
-		if (acl_impl::is_trivially_default_constructible<AllocatedType>::value)
+		allocated_type* ptr = reinterpret_cast<allocated_type*>(allocator.allocate(sizeof(allocated_type), alignof(allocated_type)));
+		if (acl_impl::is_trivially_default_constructible<allocated_type>::value)
 			return ptr;
-		return new(ptr) AllocatedType(std::forward<Args>(args)...);
+		return new(ptr) allocated_type(std::forward<args>(arguments)...);
 	}
 
-	template<typename AllocatedType, typename... Args>
-	AllocatedType* allocate_type_aligned(IAllocator& allocator, size_t alignment, Args&&... args)
+	template<typename allocated_type, typename... args>
+	allocated_type* allocate_type_aligned(iallocator& allocator, size_t alignment, args&&... arguments)
 	{
-		ACL_ASSERT(is_alignment_valid<AllocatedType>(alignment), "Invalid alignment: %u. Expected a power of two at least equal to %u", alignment, alignof(AllocatedType));
-		AllocatedType* ptr = reinterpret_cast<AllocatedType*>(allocator.allocate(sizeof(AllocatedType), alignment));
-		if (acl_impl::is_trivially_default_constructible<AllocatedType>::value)
+		ACL_ASSERT(is_alignment_valid<allocated_type>(alignment), "Invalid alignment: %u. Expected a power of two at least equal to %u", alignment, alignof(allocated_type));
+		allocated_type* ptr = reinterpret_cast<allocated_type*>(allocator.allocate(sizeof(allocated_type), alignment));
+		if (acl_impl::is_trivially_default_constructible<allocated_type>::value)
 			return ptr;
-		return new(ptr) AllocatedType(std::forward<Args>(args)...);
+		return new(ptr) allocated_type(std::forward<args>(arguments)...);
 	}
 
-	template<typename AllocatedType>
-	void deallocate_type(IAllocator& allocator, AllocatedType* ptr)
+	template<typename allocated_type>
+	void deallocate_type(iallocator& allocator, allocated_type* ptr)
 	{
 		if (ptr == nullptr)
 			return;
 
-		if (!std::is_trivially_destructible<AllocatedType>::value)
-			ptr->~AllocatedType();
+		if (!std::is_trivially_destructible<allocated_type>::value)
+			ptr->~allocated_type();
 
-		allocator.deallocate(ptr, sizeof(AllocatedType));
+		allocator.deallocate(ptr, sizeof(allocated_type));
 	}
 
-	template<typename AllocatedType, typename... Args>
-	AllocatedType* allocate_type_array(IAllocator& allocator, size_t num_elements, Args&&... args)
+	template<typename allocated_type, typename... args>
+	allocated_type* allocate_type_array(iallocator& allocator, size_t num_elements, args&&... arguments)
 	{
-		AllocatedType* ptr = reinterpret_cast<AllocatedType*>(allocator.allocate(sizeof(AllocatedType) * num_elements, alignof(AllocatedType)));
-		if (acl_impl::is_trivially_default_constructible<AllocatedType>::value)
+		allocated_type* ptr = reinterpret_cast<allocated_type*>(allocator.allocate(sizeof(allocated_type) * num_elements, alignof(allocated_type)));
+		if (acl_impl::is_trivially_default_constructible<allocated_type>::value)
 			return ptr;
 		for (size_t element_index = 0; element_index < num_elements; ++element_index)
-			new(&ptr[element_index]) AllocatedType(std::forward<Args>(args)...);
+			new(&ptr[element_index]) allocated_type(std::forward<args>(arguments)...);
 		return ptr;
 	}
 
-	template<typename AllocatedType, typename... Args>
-	AllocatedType* allocate_type_array_aligned(IAllocator& allocator, size_t num_elements, size_t alignment, Args&&... args)
+	template<typename allocated_type, typename... args>
+	allocated_type* allocate_type_array_aligned(iallocator& allocator, size_t num_elements, size_t alignment, args&&... arguments)
 	{
-		ACL_ASSERT(is_alignment_valid<AllocatedType>(alignment), "Invalid alignment: %zu. Expected a power of two at least equal to %zu", alignment, alignof(AllocatedType));
-		AllocatedType* ptr = reinterpret_cast<AllocatedType*>(allocator.allocate(sizeof(AllocatedType) * num_elements, alignment));
-		if (acl_impl::is_trivially_default_constructible<AllocatedType>::value)
+		ACL_ASSERT(is_alignment_valid<allocated_type>(alignment), "Invalid alignment: %zu. Expected a power of two at least equal to %zu", alignment, alignof(allocated_type));
+		allocated_type* ptr = reinterpret_cast<allocated_type*>(allocator.allocate(sizeof(allocated_type) * num_elements, alignment));
+		if (acl_impl::is_trivially_default_constructible<allocated_type>::value)
 			return ptr;
 		for (size_t element_index = 0; element_index < num_elements; ++element_index)
-			new(&ptr[element_index]) AllocatedType(std::forward<Args>(args)...);
+			new(&ptr[element_index]) allocated_type(std::forward<args>(arguments)...);
 		return ptr;
 	}
 
-	template<typename AllocatedType>
-	void deallocate_type_array(IAllocator& allocator, AllocatedType* elements, size_t num_elements)
+	template<typename allocated_type>
+	void deallocate_type_array(iallocator& allocator, allocated_type* elements, size_t num_elements)
 	{
 		if (elements == nullptr)
 			return;
 
-		if (!std::is_trivially_destructible<AllocatedType>::value)
+		if (!std::is_trivially_destructible<allocated_type>::value)
 		{
 			for (size_t element_index = 0; element_index < num_elements; ++element_index)
-				elements[element_index].~AllocatedType();
+				elements[element_index].~allocated_type();
 		}
 
-		allocator.deallocate(elements, sizeof(AllocatedType) * num_elements);
+		allocator.deallocate(elements, sizeof(allocated_type) * num_elements);
 	}
 }
 
diff --git a/includes/acl/core/impl/compressed_headers.h b/includes/acl/core/impl/compressed_headers.h
--- a/includes/acl/core/impl/compressed_headers.h
+++ b/includes/acl/core/impl/compressed_headers.h
@@ -46,11 +46,6 @@ namespace acl
 			uint32_t		hash;
 		};
 
-		struct track_metadata
-		{
-			uint8_t			bit_rate;
-		};
-
 		struct tracks_header
 		{
 			// Serialization tag used to distinguish raw buffer types.
@@ -74,29 +69,122 @@ namespace acl
 			// The sample rate our tracks use.
 			float			sample_rate;								// TODO: Store duration as float instead?
 
+			uint32_t		padding;
+		};
+
+		struct track_metadata
+		{
+			uint8_t			bit_rate;
+		};
+
+		struct scalar_tracks_header
+		{
 			// The number of bits used for a whole frame of data.
 			// The sum of one sample per track with all bit rates taken into account.
-			uint32_t		num_bits_per_frame;
+			uint32_t						num_bits_per_frame;
 
 			// Various data offsets relative to the start of this header.
-			PtrOffset32<track_metadata>		metadata_per_track;
-			PtrOffset32<float>				track_constant_values;
-			PtrOffset32<float>				track_range_values;
-			PtrOffset32<uint8_t>			track_animated_values;
+			ptr_offset32<track_metadata>		metadata_per_track;
+			ptr_offset32<float>				track_constant_values;
+			ptr_offset32<float>				track_range_values;
+			ptr_offset32<uint8_t>			track_animated_values;
 
 			//////////////////////////////////////////////////////////////////////////
 
-			track_metadata*			get_track_metadata() { return metadata_per_track.add_to(this); }
-			const track_metadata*	get_track_metadata() const { return metadata_per_track.add_to(this); }
+			track_metadata*					get_track_metadata() { return metadata_per_track.add_to(this); }
+			const track_metadata*			get_track_metadata() const { return metadata_per_track.add_to(this); }
+
+			float*							get_track_constant_values() { return track_constant_values.add_to(this); }
+			const float*					get_track_constant_values() const { return track_constant_values.add_to(this); }
+
+			float*							get_track_range_values() { return track_range_values.add_to(this); }
+			const float*					get_track_range_values() const { return track_range_values.add_to(this); }
+
+			uint8_t*						get_track_animated_values() { return track_animated_values.add_to(this); }
+			const uint8_t*					get_track_animated_values() const { return track_animated_values.add_to(this); }
+		};
+
+		////////////////////////////////////////////////////////////////////////////////
+		// A compressed clip segment header. Each segment is built from a uniform number
+		// of samples per track. A clip is split into one or more segments.
+		////////////////////////////////////////////////////////////////////////////////
+		struct segment_header
+		{
+			// Number of bits used by a fully animated pose (excludes default/constant tracks).
+			uint32_t						animated_pose_bit_size;				// TODO: Calculate from bitsets and formats?
+
+			// TODO: Only need one offset, calculate the others from the information we have?
+			// Offset to the per animated track format data.
+			ptr_offset32<uint8_t>			format_per_track_data_offset;		// TODO: Make this offset optional? Only present if variable
+
+			// Offset to the segment range data.
+			ptr_offset32<uint8_t>			range_data_offset;					// TODO: Make this offset optional? Only present if normalized
+
+			// Offset to the segment animated tracks data.
+			ptr_offset32<uint8_t>			track_data_offset;
+		};
+
+		struct transform_tracks_header
+		{
+			// The number of segments contained.
+			uint16_t						num_segments;
+
+			// The rotation/translation/scale format used.
+			rotation_format8				rotation_format;
+			vector_format8					translation_format;
+			vector_format8					scale_format;						// TODO: Make this optional?
+
+																				// Whether or not we have scale (bool).
+			uint8_t							has_scale;
+
+			// Whether the default scale is 0,0,0 or 1,1,1 (bool/bit).
+			uint8_t							default_scale;
+
+			uint8_t							padding[1];
+
+			// Offset to the segment headers data.
+			ptr_offset16<uint32_t>			segment_start_indices_offset;
+			ptr_offset16<segment_header>		segment_headers_offset;
+
+			// Offsets to the default/constant tracks bitsets.
+			ptr_offset16<uint32_t>			default_tracks_bitset_offset;
+			ptr_offset16<uint32_t>			constant_tracks_bitset_offset;
+
+			// Offset to the constant tracks data.
+			ptr_offset16<uint8_t>			constant_track_data_offset;
+
+			// Offset to the clip range data.
+			ptr_offset16<uint8_t>			clip_range_data_offset;				// TODO: Make this offset optional? Only present if normalized
+
+			//////////////////////////////////////////////////////////////////////////
+			// Utility functions that return pointers from their respective offsets.
+
+			uint32_t*					get_segment_start_indices() { return segment_start_indices_offset.safe_add_to(this); }
+			const uint32_t*				get_segment_start_indices() const { return segment_start_indices_offset.safe_add_to(this); }
+
+			segment_header*				get_segment_headers() { return segment_headers_offset.add_to(this); }
+			const segment_header*		get_segment_headers() const { return segment_headers_offset.add_to(this); }
+
+			uint32_t*					get_default_tracks_bitset() { return default_tracks_bitset_offset.add_to(this); }
+			const uint32_t*				get_default_tracks_bitset() const { return default_tracks_bitset_offset.add_to(this); }
+
+			uint32_t*					get_constant_tracks_bitset() { return constant_tracks_bitset_offset.add_to(this); }
+			const uint32_t*				get_constant_tracks_bitset() const { return constant_tracks_bitset_offset.add_to(this); }
+
+			uint8_t*					get_constant_track_data() { return constant_track_data_offset.safe_add_to(this); }
+			const uint8_t*				get_constant_track_data() const { return constant_track_data_offset.safe_add_to(this); }
+
+			uint8_t*					get_format_per_track_data(const segment_header& header) { return header.format_per_track_data_offset.safe_add_to(this); }
+			const uint8_t*				get_format_per_track_data(const segment_header& header) const { return header.format_per_track_data_offset.safe_add_to(this); }
 
-			float*					get_track_constant_values() { return track_constant_values.add_to(this); }
-			const float*			get_track_constant_values() const { return track_constant_values.add_to(this); }
+			uint8_t*					get_clip_range_data() { return clip_range_data_offset.safe_add_to(this); }
+			const uint8_t*				get_clip_range_data() const { return clip_range_data_offset.safe_add_to(this); }
 
-			float*					get_track_range_values() { return track_range_values.add_to(this); }
-			const float*			get_track_range_values() const { return track_range_values.add_to(this); }
+			uint8_t*					get_track_data(const segment_header& header) { return header.track_data_offset.safe_add_to(this); }
+			const uint8_t*				get_track_data(const segment_header& header) const { return header.track_data_offset.safe_add_to(this); }
 
-			uint8_t*				get_track_animated_values() { return track_animated_values.add_to(this); }
-			const uint8_t*			get_track_animated_values() const { return track_animated_values.add_to(this); }
+			uint8_t*					get_segment_range_data(const segment_header& header) { return header.range_data_offset.safe_add_to(this); }
+			const uint8_t*				get_segment_range_data(const segment_header& header) const { return header.range_data_offset.safe_add_to(this); }
 		};
 	}
 }
diff --git a/includes/acl/core/impl/debug_track_writer.h b/includes/acl/core/impl/debug_track_writer.h
--- a/includes/acl/core/impl/debug_track_writer.h
+++ b/includes/acl/core/impl/debug_track_writer.h
@@ -42,7 +42,7 @@ namespace acl
 	{
 		struct debug_track_writer final : public track_writer
 		{
-			debug_track_writer(IAllocator& allocator_, track_type8 type_, uint32_t num_tracks_)
+			debug_track_writer(iallocator& allocator_, track_type8 type_, uint32_t num_tracks_)
 				: allocator(allocator_)
 				, tracks_typed{ nullptr }
 				, buffer_size(0)
@@ -50,8 +50,8 @@ namespace acl
 				, type(type_)
 			{
 				// Large enough to accommodate the largest type
-				buffer_size = sizeof(rtm::vector4f) * num_tracks_;
-				tracks_typed.any = allocator_.allocate(buffer_size, alignof(rtm::vector4f));
+				buffer_size = sizeof(rtm::qvvf) * num_tracks_;
+				tracks_typed.any = allocator_.allocate(buffer_size, alignof(rtm::qvvf));
 			}
 
 			~debug_track_writer()
@@ -129,6 +129,36 @@ namespace acl
 				return tracks_typed.vector4f[track_index];
 			}
 
+			//////////////////////////////////////////////////////////////////////////
+			// Called by the decoder to write out a quaternion rotation value for a specified bone index.
+			void RTM_SIMD_CALL write_rotation(uint32_t track_index, rtm::quatf_arg0 rotation)
+			{
+				ACL_ASSERT(type == track_type8::qvvf, "Unexpected track type access");
+				tracks_typed.qvvf[track_index].rotation = rotation;
+			}
+
+			//////////////////////////////////////////////////////////////////////////
+			// Called by the decoder to write out a translation value for a specified bone index.
+			void RTM_SIMD_CALL write_translation(uint32_t track_index, rtm::vector4f_arg0 translation)
+			{
+				ACL_ASSERT(type == track_type8::qvvf, "Unexpected track type access");
+				tracks_typed.qvvf[track_index].translation = translation;
+			}
+
+			//////////////////////////////////////////////////////////////////////////
+			// Called by the decoder to write out a scale value for a specified bone index.
+			void RTM_SIMD_CALL write_scale(uint32_t track_index, rtm::vector4f_arg0 scale)
+			{
+				ACL_ASSERT(type == track_type8::qvvf, "Unexpected track type access");
+				tracks_typed.qvvf[track_index].scale = scale;
+			}
+
+			const rtm::qvvf& RTM_SIMD_CALL read_qvv(uint32_t track_index) const
+			{
+				ACL_ASSERT(type == track_type8::qvvf, "Unexpected track type access");
+				return tracks_typed.qvvf[track_index];
+			}
+
 			union ptr_union
 			{
 				void*			any;
@@ -137,9 +167,10 @@ namespace acl
 				rtm::float3f*	float3f;
 				rtm::float4f*	float4f;
 				rtm::vector4f*	vector4f;
+				rtm::qvvf*		qvvf;
 			};
 
-			IAllocator& allocator;
+			iallocator& allocator;
 
 			ptr_union tracks_typed;
 			size_t buffer_size;
diff --git a/includes/acl/core/iterator.h b/includes/acl/core/iterator.h
--- a/includes/acl/core/iterator.h
+++ b/includes/acl/core/iterator.h
@@ -35,13 +35,13 @@ namespace acl
 {
 	namespace acl_impl
 	{
-		template <class ItemType, bool is_const>
-		class IteratorImpl
+		template <class item_type, bool is_const>
+		class iterator_impl
 		{
 		public:
-			typedef typename std::conditional<is_const, const ItemType*, ItemType*>::type ItemPtr;
+			typedef typename std::conditional<is_const, const item_type*, item_type*>::type ItemPtr;
 
-			constexpr IteratorImpl(ItemPtr items, size_t num_items) : m_items(items), m_num_items(num_items) {}
+			constexpr iterator_impl(ItemPtr items, size_t num_items) : m_items(items), m_num_items(num_items) {}
 
 			constexpr ItemPtr begin() const { return m_items; }
 			constexpr ItemPtr end() const { return m_items + m_num_items; }
@@ -52,11 +52,11 @@ namespace acl
 		};
 	}
 
-	template <class ItemType>
-	using Iterator = acl_impl::IteratorImpl<ItemType, false>;
+	template <class item_type>
+	using iterator = acl_impl::iterator_impl<item_type, false>;
 
-	template <class ItemType>
-	using ConstIterator = acl_impl::IteratorImpl<ItemType, true>;
+	template <class item_type>
+	using const_iterator = acl_impl::iterator_impl<item_type, true>;
 }
 
 ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/core/memory_utils.h b/includes/acl/core/memory_utils.h
--- a/includes/acl/core/memory_utils.h
+++ b/includes/acl/core/memory_utils.h
@@ -228,8 +228,8 @@ namespace acl
 	//////////////////////////////////////////////////////////////////////////
 	// Endian and raw memory support
 
-	template<typename OutputPtrType, typename InputPtrType, typename OffsetType>
-	inline OutputPtrType* add_offset_to_ptr(InputPtrType* ptr, OffsetType offset)
+	template<typename OutputPtrType, typename InputPtrType, typename offset_type>
+	inline OutputPtrType* add_offset_to_ptr(InputPtrType* ptr, offset_type offset)
 	{
 		return safe_ptr_cast<OutputPtrType>(reinterpret_cast<uintptr_t>(ptr) + offset);
 	}
@@ -318,24 +318,24 @@ namespace acl
 		}
 	}
 
-	template<typename DataType>
-	inline DataType unaligned_load(const void* input)
+	template<typename data_type>
+	inline data_type unaligned_load(const void* input)
 	{
-		DataType result;
-		std::memcpy(&result, input, sizeof(DataType));
+		data_type result;
+		std::memcpy(&result, input, sizeof(data_type));
 		return result;
 	}
 
-	template<typename DataType>
-	inline DataType aligned_load(const void* input)
+	template<typename data_type>
+	inline data_type aligned_load(const void* input)
 	{
-		return *safe_ptr_cast<const DataType, const void*>(input);
+		return *safe_ptr_cast<const data_type, const void*>(input);
 	}
 
-	template<typename DataType>
-	inline void unaligned_write(DataType input, void* output)
+	template<typename data_type>
+	inline void unaligned_write(data_type input, void* output)
 	{
-		std::memcpy(output, &input, sizeof(DataType));
+		std::memcpy(output, &input, sizeof(data_type));
 	}
 }
 
diff --git a/includes/acl/core/ptr_offset.h b/includes/acl/core/ptr_offset.h
--- a/includes/acl/core/ptr_offset.h
+++ b/includes/acl/core/ptr_offset.h
@@ -34,88 +34,88 @@ ACL_IMPL_FILE_PRAGMA_PUSH
 namespace acl
 {
 	////////////////////////////////////////////////////////////////////////////////
-	// Represents an invalid pointer offset, used by 'PtrOffset'.
+	// Represents an invalid pointer offset, used by 'ptr_offset'.
 	////////////////////////////////////////////////////////////////////////////////
-	struct InvalidPtrOffset {};
+	struct invalid_ptr_offset final {};
 
 	////////////////////////////////////////////////////////////////////////////////
 	// A type safe pointer offset.
 	//
-	// This class only wraps an integer of the 'OffsetType' type and adds type safety
-	// by only casting to 'DataType'.
+	// This class only wraps an integer of the 'offset_type' type and adds type safety
+	// by only casting to 'data_type'.
 	////////////////////////////////////////////////////////////////////////////////
-	template<typename DataType, typename OffsetType>
-	class PtrOffset
+	template<typename data_type, typename offset_type>
+	class ptr_offset
 	{
 	public:
 		////////////////////////////////////////////////////////////////////////////////
 		// Constructs a valid but empty offset.
-		constexpr PtrOffset() : m_value(0) {}
+		constexpr ptr_offset() : m_value(0) {}
 
 		////////////////////////////////////////////////////////////////////////////////
 		// Constructs a valid offset with the specified value.
-		constexpr PtrOffset(size_t value) : m_value(safe_static_cast<OffsetType>(value)) {}
+		constexpr ptr_offset(size_t value) : m_value(safe_static_cast<offset_type>(value)) {}
 
 		////////////////////////////////////////////////////////////////////////////////
 		// Constructs an invalid offset.
-		constexpr PtrOffset(InvalidPtrOffset) : m_value(std::numeric_limits<OffsetType>::max()) {}
+		constexpr ptr_offset(invalid_ptr_offset) : m_value(std::numeric_limits<offset_type>::max()) {}
 
 		////////////////////////////////////////////////////////////////////////////////
 		// Adds this offset to the provided pointer.
 		template<typename BaseType>
-		inline DataType* add_to(BaseType* ptr) const
+		inline data_type* add_to(BaseType* ptr) const
 		{
-			ACL_ASSERT(is_valid(), "Invalid PtrOffset!");
-			return add_offset_to_ptr<DataType>(ptr, m_value);
+			ACL_ASSERT(is_valid(), "Invalid ptr_offset!");
+			return add_offset_to_ptr<data_type>(ptr, m_value);
 		}
 
 		////////////////////////////////////////////////////////////////////////////////
 		// Adds this offset to the provided pointer.
 		template<typename BaseType>
-		inline const DataType* add_to(const BaseType* ptr) const
+		inline const data_type* add_to(const BaseType* ptr) const
 		{
-			ACL_ASSERT(is_valid(), "Invalid PtrOffset!");
-			return add_offset_to_ptr<const DataType>(ptr, m_value);
+			ACL_ASSERT(is_valid(), "Invalid ptr_offset!");
+			return add_offset_to_ptr<const data_type>(ptr, m_value);
 		}
 
 		////////////////////////////////////////////////////////////////////////////////
 		// Adds this offset to the provided pointer or returns nullptr if the offset is invalid.
 		template<typename BaseType>
-		inline DataType* safe_add_to(BaseType* ptr) const
+		inline data_type* safe_add_to(BaseType* ptr) const
 		{
-			return is_valid() ? add_offset_to_ptr<DataType>(ptr, m_value) : nullptr;
+			return is_valid() ? add_offset_to_ptr<data_type>(ptr, m_value) : nullptr;
 		}
 
 		////////////////////////////////////////////////////////////////////////////////
 		// Adds this offset to the provided pointer or returns nullptr if the offset is invalid.
 		template<typename BaseType>
-		inline const DataType* safe_add_to(const BaseType* ptr) const
+		inline const data_type* safe_add_to(const BaseType* ptr) const
 		{
-			return is_valid() ? add_offset_to_ptr<DataType>(ptr, m_value) : nullptr;
+			return is_valid() ? add_offset_to_ptr<data_type>(ptr, m_value) : nullptr;
 		}
 
 		////////////////////////////////////////////////////////////////////////////////
-		// Coercion operator to the underlying 'OffsetType'.
-		constexpr operator OffsetType() const { return m_value; }
+		// Coercion operator to the underlying 'offset_type'.
+		constexpr operator offset_type() const { return m_value; }
 
 		////////////////////////////////////////////////////////////////////////////////
 		// Returns true if the offset is valid.
-		constexpr bool is_valid() const { return m_value != std::numeric_limits<OffsetType>::max(); }
+		constexpr bool is_valid() const { return m_value != std::numeric_limits<offset_type>::max(); }
 
 	private:
 		// Actual offset value.
-		OffsetType m_value;
+		offset_type m_value;
 	};
 
 	////////////////////////////////////////////////////////////////////////////////
 	// A 16 bit offset.
-	template<typename DataType>
-	using PtrOffset16 = PtrOffset<DataType, uint16_t>;
+	template<typename data_type>
+	using ptr_offset16 = ptr_offset<data_type, uint16_t>;
 
 	////////////////////////////////////////////////////////////////////////////////
 	// A 32 bit offset.
-	template<typename DataType>
-	using PtrOffset32 = PtrOffset<DataType, uint32_t>;
+	template<typename data_type>
+	using ptr_offset32 = ptr_offset<data_type, uint32_t>;
 }
 
 ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/core/scope_profiler.h b/includes/acl/core/scope_profiler.h
--- a/includes/acl/core/scope_profiler.h
+++ b/includes/acl/core/scope_profiler.h
@@ -36,16 +36,16 @@ namespace acl
 	////////////////////////////////////////////////////////////////////////////////
 	// A scope activated profiler.
 	////////////////////////////////////////////////////////////////////////////////
-	class ScopeProfiler
+	class scope_profiler
 	{
 	public:
 		////////////////////////////////////////////////////////////////////////////////
 		// Creates and starts a scope profiler and automatically starts it.
-		ScopeProfiler();
+		scope_profiler();
 
 		////////////////////////////////////////////////////////////////////////////////
 		// Destroys a scope profiler and automatically stops it.
-		~ScopeProfiler() = default;
+		~scope_profiler() = default;
 
 		////////////////////////////////////////////////////////////////////////////////
 		// Manually stops the profiler.
@@ -68,8 +68,8 @@ namespace acl
 		double get_elapsed_seconds() const { return std::chrono::duration<double, std::chrono::seconds::period>(get_elapsed_time()).count(); }
 
 	private:
-		ScopeProfiler(const ScopeProfiler&) = delete;
-		ScopeProfiler& operator=(const ScopeProfiler&) = delete;
+		scope_profiler(const scope_profiler&) = delete;
+		scope_profiler& operator=(const scope_profiler&) = delete;
 
 		// The time at which the profiler started.
 		std::chrono::time_point<std::chrono::high_resolution_clock>		m_start_time;
@@ -80,12 +80,12 @@ namespace acl
 
 	//////////////////////////////////////////////////////////////////////////
 
-	inline ScopeProfiler::ScopeProfiler()
+	inline scope_profiler::scope_profiler()
 	{
 		m_start_time = m_end_time = std::chrono::high_resolution_clock::now();
 	}
 
-	inline void ScopeProfiler::stop()
+	inline void scope_profiler::stop()
 	{
 		if (m_start_time == m_end_time)
 			m_end_time = std::chrono::high_resolution_clock::now();
diff --git a/includes/acl/core/string.h b/includes/acl/core/string.h
--- a/includes/acl/core/string.h
+++ b/includes/acl/core/string.h
@@ -40,12 +40,12 @@ namespace acl
 	//
 	// Strings are immutable.
 	//////////////////////////////////////////////////////////////////////////
-	class String
+	class string
 	{
 	public:
-		String() noexcept : m_allocator(nullptr), m_c_str(nullptr) {}
+		string() noexcept : m_allocator(nullptr), m_c_str(nullptr) {}
 
-		String(IAllocator& allocator, const char* c_str, size_t length)
+		string(iallocator& allocator, const char* c_str, size_t length)
 			: m_allocator(&allocator)
 		{
 			if (length > 0)
@@ -65,28 +65,28 @@ namespace acl
 			}
 		}
 
-		String(IAllocator& allocator, const char* c_str)
-			: String(allocator, c_str, c_str != nullptr ? std::strlen(c_str) : 0)
+		string(iallocator& allocator, const char* c_str)
+			: string(allocator, c_str, c_str != nullptr ? std::strlen(c_str) : 0)
 		{}
 
-		String(IAllocator& allocator, const String& str)
-			: String(allocator, str.c_str(), str.size())
+		string(iallocator& allocator, const string& str)
+			: string(allocator, str.c_str(), str.size())
 		{}
 
-		~String()
+		~string()
 		{
 			if (m_allocator != nullptr && m_c_str != nullptr)
 				deallocate_type_array(*m_allocator, m_c_str, std::strlen(m_c_str) + 1);
 		}
 
-		String(String&& other) noexcept
+		string(string&& other) noexcept
 			: m_allocator(other.m_allocator)
 			, m_c_str(other.m_c_str)
 		{
-			new(&other) String();
+			new(&other) string();
 		}
 
-		String& operator=(String&& other) noexcept
+		string& operator=(string&& other) noexcept
 		{
 			std::swap(m_allocator, other.m_allocator);
 			std::swap(m_c_str, other.m_c_str);
@@ -108,15 +108,15 @@ namespace acl
 
 		bool operator!=(const char* c_str) const { return !(*this == c_str); }
 
-		bool operator==(const String& other) const { return (*this == other.c_str()); }
-		bool operator!=(const String& other) const { return !(*this == other.c_str()); }
+		bool operator==(const string& other) const { return (*this == other.c_str()); }
+		bool operator!=(const string& other) const { return !(*this == other.c_str()); }
 
 		const char* c_str() const { return m_c_str != nullptr ? m_c_str : ""; }
 		size_t size() const { return m_c_str != nullptr ? std::strlen(m_c_str) : 0; }
 		bool empty() const { return m_c_str != nullptr ? (std::strlen(m_c_str) == 0) : true; }
 
 	private:
-		IAllocator* m_allocator;
+		iallocator* m_allocator;
 		char* m_c_str;
 	};
 }
diff --git a/includes/acl/core/track_traits.h b/includes/acl/core/track_traits.h
--- a/includes/acl/core/track_traits.h
+++ b/includes/acl/core/track_traits.h
@@ -92,6 +92,15 @@ namespace acl
 		using sample_type = rtm::vector4f;
 		using desc_type = track_desc_scalarf;
 	};
+
+	template<>
+	struct track_traits<track_type8::qvvf>
+	{
+		static constexpr track_category8 category = track_category8::transformf;
+
+		using sample_type = rtm::qvvf;
+		using desc_type = track_desc_transformf;
+	};
 }
 
 ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/core/track_types.h b/includes/acl/core/track_types.h
--- a/includes/acl/core/track_types.h
+++ b/includes/acl/core/track_types.h
@@ -24,8 +24,11 @@
 // SOFTWARE.
 ////////////////////////////////////////////////////////////////////////////////
 
-#include "acl/core/impl/compiler_utils.h"
+#include "acl/core/error_result.h"
 #include "acl/core/memory_utils.h"
+#include "acl/core/impl/compiler_utils.h"
+
+#include <rtm/scalarf.h>
 
 #include <cstdint>
 #include <cstring>
@@ -99,6 +102,11 @@ namespace acl
 	// We only support up to 4294967295 tracks. We reserve 4294967295 for the invalid index
 	constexpr uint32_t k_invalid_track_index = 0xFFFFFFFFU;
 
+	//////////////////////////////////////////////////////////////////////////
+	// Legacy value, to be removed
+	// TODO: remove me
+	constexpr uint16_t k_invalid_bone_index = 0xFFFFU;
+
 	//////////////////////////////////////////////////////////////////////////
 	// The various supported track types.
 	// Note: be careful when changing values here as they might be serialized.
@@ -119,7 +127,7 @@ namespace acl
 		//quatf		= 10,
 		//quatd		= 11,
 
-		//qvvf		= 12,
+		qvvf		= 12,
 		//qvvd		= 13,
 
 		//int1i		= 14,
@@ -140,11 +148,11 @@ namespace acl
 	enum class track_category8 : uint8_t
 	{
 		scalarf		= 0,
-		//scalard	= 1,
+		scalard		= 1,
 		//scalari	= 2,
 		//scalarq	= 3,
-		//transformf = 4,
-		//transformd = 5,
+		transformf	= 4,
+		transformd	= 5,
 	};
 
 	//////////////////////////////////////////////////////////////////////////
@@ -161,17 +169,29 @@ namespace acl
 		// will be used instead of the track index. This allows custom reordering for things
 		// like LOD sorting or skeleton remapping. A value of 'k_invalid_track_index' will strip the track
 		// from the compressed data stream. Output indices must be unique and contiguous.
-		uint32_t output_index;
+		uint32_t output_index = k_invalid_track_index;
 
 		//////////////////////////////////////////////////////////////////////////
 		// The per component precision threshold to try and attain when optimizing the bit rate.
 		// If the error is below the precision threshold, we will remove bits until we reach it without
 		// exceeding it. If the error is above the precision threshold, we will add more bits until
 		// we lower it underneath.
-		float precision;
+		// Defaults to '0.00001'
+		float precision = 0.00001F;
+
+		//////////////////////////////////////////////////////////////////////////
+		// Returns whether a scalar track description is valid or not.
+		// It is valid if:
+		//    - The precision is positive or zero and finite
+		error_result is_valid() const
+		{
+			if (precision < 0.0F || !rtm::scalar_is_finite(precision))
+				return error_result("Invalid precision");
+
+			return error_result();
+		}
 	};
 
-#if 0	// TODO: Add support for this
 	//////////////////////////////////////////////////////////////////////////
 	// This structure describes the various settings for transform tracks.
 	// Used by: quatf, qvvf
@@ -186,32 +206,78 @@ namespace acl
 		// will be used instead of the track index. This allows custom reordering for things
 		// like LOD sorting or skeleton remapping. A value of 'k_invalid_track_index' will strip the track
 		// from the compressed data stream. Output indices must be unique and contiguous.
-		uint32_t output_index;
+		uint32_t output_index = k_invalid_track_index;
 
 		//////////////////////////////////////////////////////////////////////////
 		// The index of the parent transform track or `k_invalid_track_index` if it has no parent.
-		uint32_t parent_index;
+		uint32_t parent_index = k_invalid_track_index;
 
 		//////////////////////////////////////////////////////////////////////////
 		// The shell precision threshold to try and attain when optimizing the bit rate.
 		// If the error is below the precision threshold, we will remove bits until we reach it without
 		// exceeding it. If the error is above the precision threshold, we will add more bits until
 		// we lower it underneath.
-		float precision;
+		// Note that you will need to change this value if your units are not in centimeters.
+		// Defaults to '0.01' centimeters
+		float precision = 0.01F;
 
 		//////////////////////////////////////////////////////////////////////////
 		// The error is measured on a rigidly deformed shell around every transform at the specified distance.
-		float shell_distance;
+		// Defaults to '3.0' centimeters
+		float shell_distance = 3.0F;
 
 		//////////////////////////////////////////////////////////////////////////
 		// TODO: Use the precision and shell distance?
-		float constant_rotation_threshold;
-		float constant_translation_threshold;
-		float constant_scale_threshold;
-	};
-#endif
 
-	// TODO: Add transform description?
+		//////////////////////////////////////////////////////////////////////////
+		// Threshold angle when detecting if rotation tracks are constant or default.
+		// See the rtm::quatf quat_near_identity for details about how the default threshold
+		// was chosen. You will typically NEVER need to change this, the value has been
+		// selected to be as safe as possible and is independent of game engine units.
+		// Defaults to '0.00284714461' radians
+		float constant_rotation_threshold_angle = 0.00284714461F;
+
+		//////////////////////////////////////////////////////////////////////////
+		// Threshold value to use when detecting if translation tracks are constant or default.
+		// Note that you will need to change this value if your units are not in centimeters.
+		// Defaults to '0.001' centimeters.
+		float constant_translation_threshold = 0.001F;
+
+		//////////////////////////////////////////////////////////////////////////
+		// Threshold value to use when detecting if scale tracks are constant or default.
+		// There are no units for scale as such a value that was deemed safe was selected
+		// as a default.
+		// Defaults to '0.00001'
+		float constant_scale_threshold = 0.00001F;
+
+		//////////////////////////////////////////////////////////////////////////
+		// Returns whether a transform track description is valid or not.
+		// It is valid if:
+		//    - The precision is positive or zero and finite
+		//    - The shell distance is positive or zero and finite
+		//    - The constant rotation threshold angle is positive or zero and finite
+		//    - The constant translation threshold is positive or zero and finite
+		//    - The constant scale threshold is positive or zero and finite
+		error_result is_valid() const
+		{
+			if (precision < 0.0F || !rtm::scalar_is_finite(precision))
+				return error_result("Invalid precision");
+
+			if (shell_distance < 0.0F || !rtm::scalar_is_finite(shell_distance))
+				return error_result("Invalid shell_distance");
+
+			if (constant_rotation_threshold_angle < 0.0F || !rtm::scalar_is_finite(constant_rotation_threshold_angle))
+				return error_result("Invalid constant_rotation_threshold_angle");
+
+			if (constant_translation_threshold < 0.0F || !rtm::scalar_is_finite(constant_translation_threshold))
+				return error_result("Invalid constant_translation_threshold");
+
+			if (constant_scale_threshold < 0.0F || !rtm::scalar_is_finite(constant_scale_threshold))
+				return error_result("Invalid constant_scale_threshold");
+
+			return error_result();
+		}
+	};
 
 	//////////////////////////////////////////////////////////////////////////
 
@@ -322,33 +388,14 @@ namespace acl
 		return false;
 	}
 
-	// TODO: constexpr
-	inline rotation_variant8 get_rotation_variant(rotation_format8 rotation_format)
+	constexpr rotation_variant8 get_rotation_variant(rotation_format8 rotation_format)
 	{
-		switch (rotation_format)
-		{
-		case rotation_format8::quatf_full:
-			return rotation_variant8::quat;
-		case rotation_format8::quatf_drop_w_full:
-		case rotation_format8::quatf_drop_w_variable:
-			return rotation_variant8::quat_drop_w;
-		default:
-			ACL_ASSERT(false, "Invalid or unsupported rotation format: %s", get_rotation_format_name(rotation_format));
-			return rotation_variant8::quat;
-		}
+		return rotation_format == rotation_format8::quatf_full ? rotation_variant8::quat : rotation_variant8::quat_drop_w;
 	}
 
-	// TODO: constexpr
-	inline rotation_format8 get_highest_variant_precision(rotation_variant8 variant)
+	constexpr rotation_format8 get_highest_variant_precision(rotation_variant8 variant)
 	{
-		switch (variant)
-		{
-		case rotation_variant8::quat:				return rotation_format8::quatf_full;
-		case rotation_variant8::quat_drop_w:		return rotation_format8::quatf_drop_w_full;
-		default:
-			ACL_ASSERT(false, "Invalid or unsupported rotation format: %u", (uint32_t)variant);
-			return rotation_format8::quatf_full;
-		}
+		return variant == rotation_variant8::quat ? rotation_format8::quatf_full : rotation_format8::quatf_drop_w_full;
 	}
 
 	constexpr bool is_rotation_format_variable(rotation_format8 format)
@@ -383,6 +430,7 @@ namespace acl
 		case track_type8::float3f:			return "float3f";
 		case track_type8::float4f:			return "float4f";
 		case track_type8::vector4f:			return "vector4f";
+		case track_type8::qvvf:				return "qvvf";
 		default:							return "<Invalid>";
 		}
 	}
@@ -400,9 +448,20 @@ namespace acl
 			"float3f",
 			"float4f",
 			"vector4f",
+
+			"float1d",
+			"float2d",
+			"float3d",
+			"float4d",
+			"vector4d",
+
+			"quatf",
+			"quatd",
+
+			"qvvf",
 		};
 
-		static_assert(get_array_size(k_track_type_names) == (size_t)track_type8::vector4f + 1, "Unexpected array size");
+		static_assert(get_array_size(k_track_type_names) == (size_t)track_type8::qvvf + 1, "Unexpected array size");
 
 		for (size_t type_index = 0; type_index < get_array_size(k_track_type_names); ++type_index)
 		{
@@ -424,17 +483,28 @@ namespace acl
 		// Entries in the same order as the enum integral value
 		static constexpr track_category8 k_track_type_to_category[]
 		{
-			track_category8::scalarf,	// float1f
-			track_category8::scalarf,	// float2f
-			track_category8::scalarf,	// float3f
-			track_category8::scalarf,	// float4f
-			track_category8::scalarf,	// vector4f
+			track_category8::scalarf,		// float1f
+			track_category8::scalarf,		// float2f
+			track_category8::scalarf,		// float3f
+			track_category8::scalarf,		// float4f
+			track_category8::scalarf,		// vector4f
+
+			track_category8::scalard,		// float1d
+			track_category8::scalard,		// float2d
+			track_category8::scalard,		// float3d
+			track_category8::scalard,		// float4d
+			track_category8::scalard,		// vector4d
+
+			track_category8::transformf,	// quatf
+			track_category8::transformd,	// quatd
+
+			track_category8::transformf,	// qvvf
 		};
 
-		static_assert(get_array_size(k_track_type_to_category) == (size_t)track_type8::vector4f + 1, "Unexpected array size");
+		static_assert(get_array_size(k_track_type_to_category) == (size_t)track_type8::qvvf + 1, "Unexpected array size");
 
-		ACL_ASSERT(type <= track_type8::vector4f, "Unexpected track type");
-		return type <= track_type8::vector4f ? k_track_type_to_category[static_cast<uint32_t>(type)] : track_category8::scalarf;
+		ACL_ASSERT(type <= track_type8::qvvf, "Unexpected track type");
+		return type <= track_type8::qvvf ? k_track_type_to_category[static_cast<uint32_t>(type)] : track_category8::scalarf;
 	}
 
 	//////////////////////////////////////////////////////////////////////////
@@ -449,12 +519,23 @@ namespace acl
 			3,	// float3f
 			4,	// float4f
 			4,	// vector4f
+
+			1,	// float1d
+			2,	// float2d
+			3,	// float3d
+			4,	// float4d
+			4,	// vector4d
+
+			4,	// quatf
+			4,	// quatd
+
+			12,	// qvvf
 		};
 
-		static_assert(get_array_size(k_track_type_to_num_elements) == (size_t)track_type8::vector4f + 1, "Unexpected array size");
+		static_assert(get_array_size(k_track_type_to_num_elements) == (size_t)track_type8::qvvf + 1, "Unexpected array size");
 
-		ACL_ASSERT(type <= track_type8::vector4f, "Unexpected track type");
-		return type <= track_type8::vector4f ? k_track_type_to_num_elements[static_cast<uint32_t>(type)] : 0;
+		ACL_ASSERT(type <= track_type8::qvvf, "Unexpected track type");
+		return type <= track_type8::qvvf ? k_track_type_to_num_elements[static_cast<uint32_t>(type)] : 0;
 	}
 }
 
diff --git a/includes/acl/core/track_writer.h b/includes/acl/core/track_writer.h
--- a/includes/acl/core/track_writer.h
+++ b/includes/acl/core/track_writer.h
@@ -42,6 +42,9 @@ namespace acl
 	//////////////////////////////////////////////////////////////////////////
 	struct track_writer
 	{
+		//////////////////////////////////////////////////////////////////////////
+		// Scalar track writing
+
 		//////////////////////////////////////////////////////////////////////////
 		// Called by the decoder to write out a value for a specified track index.
 		void RTM_SIMD_CALL write_float1(uint32_t track_index, rtm::scalarf_arg0 value)
@@ -81,6 +84,47 @@ namespace acl
 			(void)track_index;
 			(void)value;
 		}
+
+		//////////////////////////////////////////////////////////////////////////
+		// Transform track writing
+
+		//////////////////////////////////////////////////////////////////////////
+		// These allow the caller of decompress_pose to control which track types they are interested in.
+		// This information allows the codecs to avoid unpacking values that are not needed.
+		constexpr bool skip_all_rotations() const { return false; }
+		constexpr bool skip_all_translations() const { return false; }
+		constexpr bool skip_all_scales() const { return false; }
+
+		//////////////////////////////////////////////////////////////////////////
+		// These allow the caller of decompress_pose to control which tracks they are interested in.
+		// This information allows the codecs to avoid unpacking values that are not needed.
+		constexpr bool skip_track_rotation(uint32_t track_index) const { return (void)track_index, false; }
+		constexpr bool skip_track_translation(uint32_t track_index) const { return (void)track_index, false; }
+		constexpr bool skip_track_scale(uint32_t track_index) const { return (void)track_index, false; }
+
+		//////////////////////////////////////////////////////////////////////////
+		// Called by the decoder to write out a quaternion rotation value for a specified bone index.
+		void RTM_SIMD_CALL write_rotation(uint32_t track_index, rtm::quatf_arg0 rotation)
+		{
+			(void)track_index;
+			(void)rotation;
+		}
+
+		//////////////////////////////////////////////////////////////////////////
+		// Called by the decoder to write out a translation value for a specified bone index.
+		void RTM_SIMD_CALL write_translation(uint32_t track_index, rtm::vector4f_arg0 translation)
+		{
+			(void)track_index;
+			(void)translation;
+		}
+
+		//////////////////////////////////////////////////////////////////////////
+		// Called by the decoder to write out a scale value for a specified bone index.
+		void RTM_SIMD_CALL write_scale(uint32_t track_index, rtm::vector4f_arg0 scale)
+		{
+			(void)track_index;
+			(void)scale;
+		}
 	};
 }
 
diff --git a/includes/acl/core/unique_ptr.h b/includes/acl/core/unique_ptr.h
--- a/includes/acl/core/unique_ptr.h
+++ b/includes/acl/core/unique_ptr.h
@@ -31,47 +31,47 @@ ACL_IMPL_FILE_PRAGMA_PUSH
 
 namespace acl
 {
-	template<typename AllocatedType>
-	class Deleter
+	template<typename allocated_type>
+	class deleter
 	{
 	public:
-		Deleter() : m_allocator(nullptr) {}
-		explicit Deleter(IAllocator& allocator) : m_allocator(&allocator) {}
-		Deleter(const Deleter&) = default;
-		Deleter(Deleter&&) = default;
-		~Deleter() = default;
-		Deleter& operator=(const Deleter&) = default;
-		Deleter& operator=(Deleter&&) = default;
+		deleter() : m_allocator(nullptr) {}
+		explicit deleter(iallocator& allocator) : m_allocator(&allocator) {}
+		deleter(const deleter&) = default;
+		deleter(deleter&&) = default;
+		~deleter() = default;
+		deleter& operator=(const deleter&) = default;
+		deleter& operator=(deleter&&) = default;
 
-		void operator()(AllocatedType* ptr)
+		void operator()(allocated_type* ptr)
 		{
 			if (ptr == nullptr)
 				return;
 
-			if (!std::is_trivially_destructible<AllocatedType>::value)
-				ptr->~AllocatedType();
+			if (!std::is_trivially_destructible<allocated_type>::value)
+				ptr->~allocated_type();
 
-			m_allocator->deallocate(ptr, sizeof(AllocatedType));
+			m_allocator->deallocate(ptr, sizeof(allocated_type));
 		}
 
 	private:
-		IAllocator* m_allocator;
+		iallocator* m_allocator;
 	};
 
-	template<typename AllocatedType, typename... Args>
-	std::unique_ptr<AllocatedType, Deleter<AllocatedType>> make_unique(IAllocator& allocator, Args&&... args)
+	template<typename allocated_type, typename... args>
+	std::unique_ptr<allocated_type, deleter<allocated_type>> make_unique(iallocator& allocator, args&&... arguments)
 	{
-		return std::unique_ptr<AllocatedType, Deleter<AllocatedType>>(
-			allocate_type<AllocatedType>(allocator, std::forward<Args>(args)...),
-			Deleter<AllocatedType>(allocator));
+		return std::unique_ptr<allocated_type, deleter<allocated_type>>(
+			allocate_type<allocated_type>(allocator, std::forward<args>(arguments)...),
+			deleter<allocated_type>(allocator));
 	}
 
-	template<typename AllocatedType, typename... Args>
-	std::unique_ptr<AllocatedType, Deleter<AllocatedType>> make_unique_aligned(IAllocator& allocator, size_t alignment, Args&&... args)
+	template<typename allocated_type, typename... args>
+	std::unique_ptr<allocated_type, deleter<allocated_type>> make_unique_aligned(iallocator& allocator, size_t alignment, args&&... arguments)
 	{
-		return std::unique_ptr<AllocatedType, Deleter<AllocatedType>>(
-			allocate_type_aligned<AllocatedType>(allocator, alignment, std::forward<Args>(args)...),
-			Deleter<AllocatedType>(allocator));
+		return std::unique_ptr<allocated_type, deleter<allocated_type>>(
+			allocate_type_aligned<allocated_type>(allocator, alignment, std::forward<args>(arguments)...),
+			deleter<allocated_type>(allocator));
 	}
 }
 
diff --git a/includes/acl/decompression/decompress.h b/includes/acl/decompression/decompress.h
--- a/includes/acl/decompression/decompress.h
+++ b/includes/acl/decompression/decompress.h
@@ -32,8 +32,10 @@
 #include "acl/core/interpolation_utils.h"
 #include "acl/core/track_traits.h"
 #include "acl/core/track_types.h"
-#include "acl/core/track_writer.h"
-#include "acl/decompression/impl/track_sampling_impl.h"
+#include "acl/decompression/impl/decompression_context_selector.h"
+#include "acl/decompression/impl/scalar_track_decompression.h"
+#include "acl/decompression/impl/transform_track_decompression.h"
+#include "acl/decompression/impl/universal_track_decompression.h"
 #include "acl/math/vector4_packing.h"
 
 #include <rtm/types.h>
@@ -59,21 +61,48 @@ namespace acl
 	//////////////////////////////////////////////////////////////////////////
 	struct decompression_settings
 	{
+		//////////////////////////////////////////////////////////////////////////
+		// Common decompression settings
+
 		//////////////////////////////////////////////////////////////////////////
 		// Whether or not to clamp the sample time when `seek(..)` is called. Defaults to true.
-		constexpr bool clamp_sample_time() const { return true; }
+		// Must be static constexpr!
+		static constexpr bool clamp_sample_time() { return true; }
 
 		//////////////////////////////////////////////////////////////////////////
 		// Whether or not the specified track type is supported. Defaults to true.
 		// If a track type is statically known not to be supported, the compiler can strip
 		// the associated code.
-		constexpr bool is_track_type_supported(track_type8 /*type*/) const { return true; }
+		// Must be static constexpr!
+		static constexpr bool is_track_type_supported(track_type8 /*type*/) { return true; }
 
+		//////////////////////////////////////////////////////////////////////////
 		// Whether to explicitly disable floating point exceptions during decompression.
 		// This has a cost, exceptions are usually disabled globally and do not need to be
 		// explicitly disabled during decompression.
 		// We assume that floating point exceptions are already disabled by the caller.
-		constexpr bool disable_fp_exeptions() const { return false; }
+		// Must be static constexpr!
+		static constexpr bool disable_fp_exeptions() { return false; }
+
+		//////////////////////////////////////////////////////////////////////////
+		// Transform decompression settings
+
+		//////////////////////////////////////////////////////////////////////////
+		// Whether the specified rotation/translation/scale format are supported or not.
+		// Use this to strip code related to formats you do not need.
+		// Must be static constexpr!
+		static constexpr bool is_rotation_format_supported(rotation_format8 /*format*/) { return true; }
+		static constexpr bool is_translation_format_supported(vector_format8 /*format*/) { return true; }
+		static constexpr bool is_scale_format_supported(vector_format8 /*format*/) { return true; }
+		static constexpr rotation_format8 get_rotation_format(rotation_format8 format) { return format; }
+		static constexpr vector_format8 get_translation_format(vector_format8 format) { return format; }
+		static constexpr vector_format8 get_scale_format(vector_format8 format) { return format; }
+
+		// Whether rotations should be normalized before being output or not. Some animation
+		// runtimes will normalize in a separate step and do not need the explicit normalization.
+		// Enabled by default for safety.
+		// Must be static constexpr!
+		static constexpr bool normalize_rotations() { return true; }
 	};
 
 	//////////////////////////////////////////////////////////////////////////
@@ -86,7 +115,32 @@ namespace acl
 	// These are the default settings. Only the generally optimal settings
 	// are enabled and will offer the overall best performance.
 	//////////////////////////////////////////////////////////////////////////
-	struct default_decompression_settings : public decompression_settings {};
+	struct default_scalar_decompression_settings : public decompression_settings
+	{
+		//////////////////////////////////////////////////////////////////////////
+		// Only support scalar tracks
+		static constexpr bool is_track_type_supported(track_type8 type) { return type != track_type8::qvvf; }
+	};
+
+	//////////////////////////////////////////////////////////////////////////
+	// These are the default settings. Only the generally optimal settings
+	// are enabled and will offer the overall best performance.
+	//////////////////////////////////////////////////////////////////////////
+	struct default_transform_decompression_settings : public decompression_settings
+	{
+		//////////////////////////////////////////////////////////////////////////
+		// Only support transform tracks
+		static constexpr bool is_track_type_supported(track_type8 type) { return type == track_type8::qvvf; }
+
+		//////////////////////////////////////////////////////////////////////////
+		// By default, we only support the variable bit rates as they are generally optimal
+		static constexpr bool is_rotation_format_supported(rotation_format8 format) { return format == rotation_format8::quatf_drop_w_variable; }
+		static constexpr bool is_translation_format_supported(vector_format8 format) { return format == vector_format8::vector3f_variable; }
+		static constexpr bool is_scale_format_supported(vector_format8 format) { return format == vector_format8::vector3f_variable; }
+		static constexpr rotation_format8 get_rotation_format(rotation_format8 /*format*/) { return rotation_format8::quatf_drop_w_variable; }
+		static constexpr vector_format8 get_translation_format(vector_format8 /*format*/) { return vector_format8::vector3f_variable; }
+		static constexpr vector_format8 get_scale_format(vector_format8 /*format*/) { return vector_format8::vector3f_variable; }
+	};
 
 	//////////////////////////////////////////////////////////////////////////
 	// Decompression context for the uniformly sampled algorithm. The context
@@ -116,13 +170,9 @@ namespace acl
 		// The default constructor for the `decompression_settings_type` will be used.
 		decompression_context();
 
-		//////////////////////////////////////////////////////////////////////////
-		// Constructs a context instance from a settings instance.
-		decompression_context(const decompression_settings_type& settings);
-
 		//////////////////////////////////////////////////////////////////////////
 		// Returns the compressed tracks bound to this context instance.
-		const compressed_tracks* get_compressed_tracks() const { return m_context.tracks; }
+		const compressed_tracks* get_compressed_tracks() const { return m_context.get_compressed_tracks(); }
 
 		//////////////////////////////////////////////////////////////////////////
 		// Initializes the context instance to a particular compressed tracks instance.
@@ -130,11 +180,11 @@ namespace acl
 
 		//////////////////////////////////////////////////////////////////////////
 		// Returns true if this context instance is bound to a compressed tracks instance, false otherwise.
-		bool is_initialized() const { return m_context.tracks != nullptr; }
+		bool is_initialized() const { return m_context.is_initialized(); }
 
 		//////////////////////////////////////////////////////////////////////////
 		// Returns true if this context instance is bound to the specified compressed tracks instance, false otherwise.
-		bool is_dirty(const compressed_tracks& tracks);
+		bool is_dirty(const compressed_tracks& tracks) const;
 
 		//////////////////////////////////////////////////////////////////////////
 		// Seeks within the compressed tracks to a particular point in time with the
@@ -157,15 +207,21 @@ namespace acl
 		decompression_context(const decompression_context& other) = delete;
 		decompression_context& operator=(const decompression_context& other) = delete;
 
-		// Internal context data
-		acl_impl::persistent_decompression_context m_context;
+		// Whether the decompression context should support scalar tracks
+		static constexpr bool k_supports_scalar_tracks = decompression_settings_type::is_track_type_supported(track_type8::float1f)
+			|| decompression_settings_type::is_track_type_supported(track_type8::float2f)
+			|| decompression_settings_type::is_track_type_supported(track_type8::float3f)
+			|| decompression_settings_type::is_track_type_supported(track_type8::float4f)
+			|| decompression_settings_type::is_track_type_supported(track_type8::vector4f);
+
+		// Whether the decompression context should support transform tracks
+		static constexpr bool k_supports_transform_tracks = decompression_settings_type::is_track_type_supported(track_type8::qvvf);
 
-		// The static settings used to strip out code at runtime
-		decompression_settings_type m_settings;
+		// The type of our persistent context based on what track types we support
+		using context_type = typename acl_impl::persistent_decompression_context_selector<k_supports_scalar_tracks, k_supports_transform_tracks>::type;
 
-		// Ensure we have non-zero padding to avoid compiler warnings
-		static constexpr size_t k_padding_size = alignof(acl_impl::persistent_decompression_context) - sizeof(decompression_settings_type);
-		uint8_t m_padding[k_padding_size != 0 ? k_padding_size : alignof(acl_impl::persistent_decompression_context)];
+		// Internal context data
+		context_type m_context;
 
 		static_assert(std::is_base_of<decompression_settings, decompression_settings_type>::value, "decompression_settings_type must derive from decompression_settings!");
 	};
@@ -173,510 +229,51 @@ namespace acl
 	//////////////////////////////////////////////////////////////////////////
 	// Allocates and constructs an instance of the decompression context
 	template<class decompression_settings_type>
-	inline decompression_context<decompression_settings_type>* make_decompression_context(IAllocator& allocator)
+	inline decompression_context<decompression_settings_type>* make_decompression_context(iallocator& allocator)
 	{
 		return allocate_type<decompression_context<decompression_settings_type>>(allocator);
 	}
 
-	//////////////////////////////////////////////////////////////////////////
-	// Allocates and constructs an instance of the decompression context
-	template<class decompression_settings_type>
-	inline decompression_context<decompression_settings_type>* make_decompression_context(IAllocator& allocator, const decompression_settings_type& settings)
-	{
-		return allocate_type<decompression_context<decompression_settings_type>>(allocator, settings);
-	}
-
 	//////////////////////////////////////////////////////////////////////////
 	// decompression_context implementation
 
 	template<class decompression_settings_type>
 	inline decompression_context<decompression_settings_type>::decompression_context()
 		: m_context()
-		, m_settings()
-	{
-		m_context.tracks = nullptr;		// Only member used to detect if we are initialized
-	}
-
-	template<class decompression_settings_type>
-	inline decompression_context<decompression_settings_type>::decompression_context(const decompression_settings_type& settings)
-		: m_context()
-		, m_settings(settings)
 	{
-		m_context.tracks = nullptr;		// Only member used to detect if we are initialized
+		m_context.reset();
 	}
 
 	template<class decompression_settings_type>
 	inline void decompression_context<decompression_settings_type>::initialize(const compressed_tracks& tracks)
 	{
-		ACL_ASSERT(tracks.is_valid(false).empty(), "Compressed tracks are not valid");
-		ACL_ASSERT(tracks.get_algorithm_type() == algorithm_type8::uniformly_sampled, "Invalid algorithm type [%s], expected [%s]", get_algorithm_name(tracks.get_algorithm_type()), get_algorithm_name(algorithm_type8::uniformly_sampled));
-
-		m_context.tracks = &tracks;
-		m_context.tracks_hash = tracks.get_hash();
-		m_context.duration = tracks.get_duration();
-		m_context.sample_time = -1.0F;
-		m_context.interpolation_alpha = 0.0;
+		acl_impl::initialize<decompression_settings_type>(m_context, tracks);
 	}
 
 	template<class decompression_settings_type>
-	inline bool decompression_context<decompression_settings_type>::is_dirty(const compressed_tracks& tracks)
+	inline bool decompression_context<decompression_settings_type>::is_dirty(const compressed_tracks& tracks) const
 	{
-		if (m_context.tracks != &tracks)
-			return true;
-
-		if (m_context.tracks_hash != tracks.get_hash())
-			return true;
-
-		return false;
+		return acl_impl::is_dirty(m_context, tracks);
 	}
 
 	template<class decompression_settings_type>
 	inline void decompression_context<decompression_settings_type>::seek(float sample_time, sample_rounding_policy rounding_policy)
 	{
-		ACL_ASSERT(m_context.is_initialized(), "Context is not initialized");
-		ACL_ASSERT(rtm::scalar_is_finite(sample_time), "Invalid sample time");
-
-		// Clamp for safety, the caller should normally handle this but in practice, it often isn't the case
-		if (m_settings.clamp_sample_time())
-			sample_time = rtm::scalar_clamp(sample_time, 0.0F, m_context.duration);
-
-		if (m_context.sample_time == sample_time)
-			return;
-
-		m_context.sample_time = sample_time;
-
-		const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*m_context.tracks);
-
-		uint32_t key_frame0;
-		uint32_t key_frame1;
-		find_linear_interpolation_samples_with_sample_rate(header.num_samples, header.sample_rate, sample_time, rounding_policy, key_frame0, key_frame1, m_context.interpolation_alpha);
-
-		m_context.key_frame_bit_offsets[0] = key_frame0 * header.num_bits_per_frame;
-		m_context.key_frame_bit_offsets[1] = key_frame1 * header.num_bits_per_frame;
+		acl_impl::seek<decompression_settings_type>(m_context, sample_time, rounding_policy);
 	}
 
 	template<class decompression_settings_type>
 	template<class track_writer_type>
 	inline void decompression_context<decompression_settings_type>::decompress_tracks(track_writer_type& writer)
 	{
-		static_assert(std::is_base_of<track_writer, track_writer_type>::value, "track_writer_type must derive from track_writer");
-		ACL_ASSERT(m_context.is_initialized(), "Context is not initialized");
-
-		// Due to the SIMD operations, we sometimes overflow in the SIMD lanes not used.
-		// Disable floating point exceptions to avoid issues.
-		fp_environment fp_env;
-		if (m_settings.disable_fp_exeptions())
-			disable_fp_exceptions(fp_env);
-
-		const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*m_context.tracks);
-		const rtm::scalarf interpolation_alpha = rtm::scalar_set(m_context.interpolation_alpha);
-
-		const acl_impl::track_metadata* per_track_metadata = header.get_track_metadata();
-		const float* constant_values = header.get_track_constant_values();
-		const float* range_values = header.get_track_range_values();
-		const uint8_t* animated_values = header.get_track_animated_values();
-
-		uint32_t track_bit_offset0 = m_context.key_frame_bit_offsets[0];
-		uint32_t track_bit_offset1 = m_context.key_frame_bit_offsets[1];
-
-		for (uint32_t track_index = 0; track_index < header.num_tracks; ++track_index)
-		{
-			const acl_impl::track_metadata& metadata = per_track_metadata[track_index];
-			const uint8_t bit_rate = metadata.bit_rate;
-			const uint32_t num_bits_per_component = get_num_bits_at_bit_rate(bit_rate);
-
-			if (header.track_type == track_type8::float1f && m_settings.is_track_type_supported(track_type8::float1f))
-			{
-				rtm::scalarf value;
-				if (is_constant_bit_rate(bit_rate))
-				{
-					value = rtm::scalar_load(constant_values);
-					constant_values += 1;
-				}
-				else
-				{
-					rtm::scalarf value0;
-					rtm::scalarf value1;
-					if (is_raw_bit_rate(bit_rate))
-					{
-						value0 = unpack_scalarf_32_unsafe(animated_values, track_bit_offset0);
-						value1 = unpack_scalarf_32_unsafe(animated_values, track_bit_offset1);
-					}
-					else
-					{
-						value0 = unpack_scalarf_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset0);
-						value1 = unpack_scalarf_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset1);
-
-						const rtm::scalarf range_min = rtm::scalar_load(range_values);
-						const rtm::scalarf range_extent = rtm::scalar_load(range_values + 1);
-						value0 = rtm::scalar_mul_add(value0, range_extent, range_min);
-						value1 = rtm::scalar_mul_add(value1, range_extent, range_min);
-						range_values += 2;
-					}
-
-					value = rtm::scalar_lerp(value0, value1, interpolation_alpha);
-
-					const uint32_t num_sample_bits = num_bits_per_component;
-					track_bit_offset0 += num_sample_bits;
-					track_bit_offset1 += num_sample_bits;
-				}
-
-				writer.write_float1(track_index, value);
-			}
-			else if (header.track_type == track_type8::float2f && m_settings.is_track_type_supported(track_type8::float2f))
-			{
-				rtm::vector4f value;
-				if (is_constant_bit_rate(bit_rate))
-				{
-					value = rtm::vector_load(constant_values);
-					constant_values += 2;
-				}
-				else
-				{
-					rtm::vector4f value0;
-					rtm::vector4f value1;
-					if (is_raw_bit_rate(bit_rate))
-					{
-						value0 = unpack_vector2_64_unsafe(animated_values, track_bit_offset0);
-						value1 = unpack_vector2_64_unsafe(animated_values, track_bit_offset1);
-					}
-					else
-					{
-						value0 = unpack_vector2_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset0);
-						value1 = unpack_vector2_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset1);
-
-						const rtm::vector4f range_min = rtm::vector_load(range_values);
-						const rtm::vector4f range_extent = rtm::vector_load(range_values + 2);
-						value0 = rtm::vector_mul_add(value0, range_extent, range_min);
-						value1 = rtm::vector_mul_add(value1, range_extent, range_min);
-						range_values += 4;
-					}
-
-					value = rtm::vector_lerp(value0, value1, m_context.interpolation_alpha);
-
-					const uint32_t num_sample_bits = num_bits_per_component * 2;
-					track_bit_offset0 += num_sample_bits;
-					track_bit_offset1 += num_sample_bits;
-				}
-
-				writer.write_float2(track_index, value);
-			}
-			else if (header.track_type == track_type8::float3f && m_settings.is_track_type_supported(track_type8::float3f))
-			{
-				rtm::vector4f value;
-				if (is_constant_bit_rate(bit_rate))
-				{
-					value = rtm::vector_load(constant_values);
-					constant_values += 3;
-				}
-				else
-				{
-					rtm::vector4f value0;
-					rtm::vector4f value1;
-					if (is_raw_bit_rate(bit_rate))
-					{
-						value0 = unpack_vector3_96_unsafe(animated_values, track_bit_offset0);
-						value1 = unpack_vector3_96_unsafe(animated_values, track_bit_offset1);
-					}
-					else
-					{
-						value0 = unpack_vector3_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset0);
-						value1 = unpack_vector3_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset1);
-
-						const rtm::vector4f range_min = rtm::vector_load(range_values);
-						const rtm::vector4f range_extent = rtm::vector_load(range_values + 3);
-						value0 = rtm::vector_mul_add(value0, range_extent, range_min);
-						value1 = rtm::vector_mul_add(value1, range_extent, range_min);
-						range_values += 6;
-					}
-
-					value = rtm::vector_lerp(value0, value1, m_context.interpolation_alpha);
-
-					const uint32_t num_sample_bits = num_bits_per_component * 3;
-					track_bit_offset0 += num_sample_bits;
-					track_bit_offset1 += num_sample_bits;
-				}
-
-				writer.write_float3(track_index, value);
-			}
-			else if (header.track_type == track_type8::float4f && m_settings.is_track_type_supported(track_type8::float4f))
-			{
-				rtm::vector4f value;
-				if (is_constant_bit_rate(bit_rate))
-				{
-					value = rtm::vector_load(constant_values);
-					constant_values += 4;
-				}
-				else
-				{
-					rtm::vector4f value0;
-					rtm::vector4f value1;
-					if (is_raw_bit_rate(bit_rate))
-					{
-						value0 = unpack_vector4_128_unsafe(animated_values, track_bit_offset0);
-						value1 = unpack_vector4_128_unsafe(animated_values, track_bit_offset1);
-					}
-					else
-					{
-						value0 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset0);
-						value1 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset1);
-
-						const rtm::vector4f range_min = rtm::vector_load(range_values);
-						const rtm::vector4f range_extent = rtm::vector_load(range_values + 4);
-						value0 = rtm::vector_mul_add(value0, range_extent, range_min);
-						value1 = rtm::vector_mul_add(value1, range_extent, range_min);
-						range_values += 8;
-					}
-
-					value = rtm::vector_lerp(value0, value1, m_context.interpolation_alpha);
-
-					const uint32_t num_sample_bits = num_bits_per_component * 4;
-					track_bit_offset0 += num_sample_bits;
-					track_bit_offset1 += num_sample_bits;
-				}
-
-				writer.write_float4(track_index, value);
-			}
-			else if (header.track_type == track_type8::vector4f && m_settings.is_track_type_supported(track_type8::vector4f))
-			{
-				rtm::vector4f value;
-				if (is_constant_bit_rate(bit_rate))
-				{
-					value = rtm::vector_load(constant_values);
-					constant_values += 4;
-				}
-				else
-				{
-					rtm::vector4f value0;
-					rtm::vector4f value1;
-					if (is_raw_bit_rate(bit_rate))
-					{
-						value0 = unpack_vector4_128_unsafe(animated_values, track_bit_offset0);
-						value1 = unpack_vector4_128_unsafe(animated_values, track_bit_offset1);
-					}
-					else
-					{
-						value0 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset0);
-						value1 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset1);
-
-						const rtm::vector4f range_min = rtm::vector_load(range_values);
-						const rtm::vector4f range_extent = rtm::vector_load(range_values + 4);
-						value0 = rtm::vector_mul_add(value0, range_extent, range_min);
-						value1 = rtm::vector_mul_add(value1, range_extent, range_min);
-						range_values += 8;
-					}
-
-					value = rtm::vector_lerp(value0, value1, m_context.interpolation_alpha);
-
-					const uint32_t num_sample_bits = num_bits_per_component * 4;
-					track_bit_offset0 += num_sample_bits;
-					track_bit_offset1 += num_sample_bits;
-				}
-
-				writer.write_vector4(track_index, value);
-			}
-		}
-
-		if (m_settings.disable_fp_exeptions())
-			restore_fp_exceptions(fp_env);
+		acl_impl::decompress_tracks<decompression_settings_type>(m_context, writer);
 	}
 
 	template<class decompression_settings_type>
 	template<class track_writer_type>
 	inline void decompression_context<decompression_settings_type>::decompress_track(uint32_t track_index, track_writer_type& writer)
 	{
-		static_assert(std::is_base_of<track_writer, track_writer_type>::value, "track_writer_type must derive from track_writer");
-		ACL_ASSERT(m_context.is_initialized(), "Context is not initialized");
-		ACL_ASSERT(track_index < m_context.tracks->get_num_tracks(), "Invalid track index");
-
-		// Due to the SIMD operations, we sometimes overflow in the SIMD lanes not used.
-		// Disable floating point exceptions to avoid issues.
-		fp_environment fp_env;
-		if (m_settings.disable_fp_exeptions())
-			disable_fp_exceptions(fp_env);
-
-		const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*m_context.tracks);
-		const rtm::scalarf interpolation_alpha = rtm::scalar_set(m_context.interpolation_alpha);
-
-		const float* constant_values = header.get_track_constant_values();
-		const float* range_values = header.get_track_range_values();
-
-		const uint32_t num_element_components = get_track_num_sample_elements(header.track_type);
-		uint32_t track_bit_offset = 0;
-
-		const acl_impl::track_metadata* per_track_metadata = header.get_track_metadata();
-		for (uint32_t scan_track_index = 0; scan_track_index < track_index; ++scan_track_index)
-		{
-			const acl_impl::track_metadata& metadata = per_track_metadata[scan_track_index];
-			const uint8_t bit_rate = metadata.bit_rate;
-			const uint32_t num_bits_per_component = get_num_bits_at_bit_rate(bit_rate);
-			track_bit_offset += num_bits_per_component * num_element_components;
-
-			if (is_constant_bit_rate(bit_rate))
-				constant_values += num_element_components;
-			else if (!is_raw_bit_rate(bit_rate))
-				range_values += num_element_components * 2;
-		}
-
-		const acl_impl::track_metadata& metadata = per_track_metadata[track_index];
-		const uint8_t bit_rate = metadata.bit_rate;
-		const uint32_t num_bits_per_component = get_num_bits_at_bit_rate(bit_rate);
-
-		const uint8_t* animated_values = header.get_track_animated_values();
-
-		if (header.track_type == track_type8::float1f && m_settings.is_track_type_supported(track_type8::float1f))
-		{
-			rtm::scalarf value;
-			if (is_constant_bit_rate(bit_rate))
-				value = rtm::scalar_load(constant_values);
-			else
-			{
-				rtm::scalarf value0;
-				rtm::scalarf value1;
-				if (is_raw_bit_rate(bit_rate))
-				{
-					value0 = unpack_scalarf_32_unsafe(animated_values, m_context.key_frame_bit_offsets[0] + track_bit_offset);
-					value1 = unpack_scalarf_32_unsafe(animated_values, m_context.key_frame_bit_offsets[1] + track_bit_offset);
-				}
-				else
-				{
-					value0 = unpack_scalarf_uXX_unsafe(num_bits_per_component, animated_values, m_context.key_frame_bit_offsets[0] + track_bit_offset);
-					value1 = unpack_scalarf_uXX_unsafe(num_bits_per_component, animated_values, m_context.key_frame_bit_offsets[1] + track_bit_offset);
-
-					const rtm::scalarf range_min = rtm::scalar_load(range_values);
-					const rtm::scalarf range_extent = rtm::scalar_load(range_values + num_element_components);
-					value0 = rtm::scalar_mul_add(value0, range_extent, range_min);
-					value1 = rtm::scalar_mul_add(value1, range_extent, range_min);
-				}
-
-				value = rtm::scalar_lerp(value0, value1, interpolation_alpha);
-			}
-
-			writer.write_float1(track_index, value);
-		}
-		else if (header.track_type == track_type8::float2f && m_settings.is_track_type_supported(track_type8::float2f))
-		{
-			rtm::vector4f value;
-			if (is_constant_bit_rate(bit_rate))
-				value = rtm::vector_load(constant_values);
-			else
-			{
-				rtm::vector4f value0;
-				rtm::vector4f value1;
-				if (is_raw_bit_rate(bit_rate))
-				{
-					value0 = unpack_vector2_64_unsafe(animated_values, m_context.key_frame_bit_offsets[0] + track_bit_offset);
-					value1 = unpack_vector2_64_unsafe(animated_values, m_context.key_frame_bit_offsets[1] + track_bit_offset);
-				}
-				else
-				{
-					value0 = unpack_vector2_uXX_unsafe(num_bits_per_component, animated_values, m_context.key_frame_bit_offsets[0] + track_bit_offset);
-					value1 = unpack_vector2_uXX_unsafe(num_bits_per_component, animated_values, m_context.key_frame_bit_offsets[1] + track_bit_offset);
-
-					const rtm::vector4f range_min = rtm::vector_load(range_values);
-					const rtm::vector4f range_extent = rtm::vector_load(range_values + num_element_components);
-					value0 = rtm::vector_mul_add(value0, range_extent, range_min);
-					value1 = rtm::vector_mul_add(value1, range_extent, range_min);
-				}
-
-				value = rtm::vector_lerp(value0, value1, m_context.interpolation_alpha);
-			}
-
-			writer.write_float2(track_index, value);
-		}
-		else if (header.track_type == track_type8::float3f && m_settings.is_track_type_supported(track_type8::float3f))
-		{
-			rtm::vector4f value;
-			if (is_constant_bit_rate(bit_rate))
-				value = rtm::vector_load(constant_values);
-			else
-			{
-				rtm::vector4f value0;
-				rtm::vector4f value1;
-				if (is_raw_bit_rate(bit_rate))
-				{
-					value0 = unpack_vector3_96_unsafe(animated_values, m_context.key_frame_bit_offsets[0] + track_bit_offset);
-					value1 = unpack_vector3_96_unsafe(animated_values, m_context.key_frame_bit_offsets[1] + track_bit_offset);
-				}
-				else
-				{
-					value0 = unpack_vector3_uXX_unsafe(num_bits_per_component, animated_values, m_context.key_frame_bit_offsets[0] + track_bit_offset);
-					value1 = unpack_vector3_uXX_unsafe(num_bits_per_component, animated_values, m_context.key_frame_bit_offsets[1] + track_bit_offset);
-
-					const rtm::vector4f range_min = rtm::vector_load(range_values);
-					const rtm::vector4f range_extent = rtm::vector_load(range_values + num_element_components);
-					value0 = rtm::vector_mul_add(value0, range_extent, range_min);
-					value1 = rtm::vector_mul_add(value1, range_extent, range_min);
-				}
-
-				value = rtm::vector_lerp(value0, value1, m_context.interpolation_alpha);
-			}
-
-			writer.write_float3(track_index, value);
-		}
-		else if (header.track_type == track_type8::float4f && m_settings.is_track_type_supported(track_type8::float4f))
-		{
-			rtm::vector4f value;
-			if (is_constant_bit_rate(bit_rate))
-				value = rtm::vector_load(constant_values);
-			else
-			{
-				rtm::vector4f value0;
-				rtm::vector4f value1;
-				if (is_raw_bit_rate(bit_rate))
-				{
-					value0 = unpack_vector4_128_unsafe(animated_values, m_context.key_frame_bit_offsets[0] + track_bit_offset);
-					value1 = unpack_vector4_128_unsafe(animated_values, m_context.key_frame_bit_offsets[1] + track_bit_offset);
-				}
-				else
-				{
-					value0 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, m_context.key_frame_bit_offsets[0] + track_bit_offset);
-					value1 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, m_context.key_frame_bit_offsets[1] + track_bit_offset);
-
-					const rtm::vector4f range_min = rtm::vector_load(range_values);
-					const rtm::vector4f range_extent = rtm::vector_load(range_values + num_element_components);
-					value0 = rtm::vector_mul_add(value0, range_extent, range_min);
-					value1 = rtm::vector_mul_add(value1, range_extent, range_min);
-				}
-
-				value = rtm::vector_lerp(value0, value1, m_context.interpolation_alpha);
-			}
-
-			writer.write_float4(track_index, value);
-		}
-		else if (header.track_type == track_type8::vector4f && m_settings.is_track_type_supported(track_type8::vector4f))
-		{
-			rtm::vector4f value;
-			if (is_constant_bit_rate(bit_rate))
-				value = rtm::vector_load(constant_values);
-			else
-			{
-				rtm::vector4f value0;
-				rtm::vector4f value1;
-				if (is_raw_bit_rate(bit_rate))
-				{
-					value0 = unpack_vector4_128_unsafe(animated_values, m_context.key_frame_bit_offsets[0] + track_bit_offset);
-					value1 = unpack_vector4_128_unsafe(animated_values, m_context.key_frame_bit_offsets[1] + track_bit_offset);
-				}
-				else
-				{
-					value0 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, m_context.key_frame_bit_offsets[0] + track_bit_offset);
-					value1 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, m_context.key_frame_bit_offsets[1] + track_bit_offset);
-
-					const rtm::vector4f range_min = rtm::vector_load(range_values);
-					const rtm::vector4f range_extent = rtm::vector_load(range_values + num_element_components);
-					value0 = rtm::vector_mul_add(value0, range_extent, range_min);
-					value1 = rtm::vector_mul_add(value1, range_extent, range_min);
-				}
-
-				value = rtm::vector_lerp(value0, value1, m_context.interpolation_alpha);
-			}
-
-			writer.write_vector4(track_index, value);
-		}
-
-		if (m_settings.disable_fp_exeptions())
-			restore_fp_exceptions(fp_env);
+		acl_impl::decompress_track<decompression_settings_type>(m_context, track_index, writer);
 	}
 }
 
diff --git a/includes/acl/decompression/default_output_writer.h b/includes/acl/decompression/default_output_writer.h
deleted file mode 100644
--- a/includes/acl/decompression/default_output_writer.h
+++ /dev/null
@@ -1,75 +0,0 @@
-#pragma once
-
-////////////////////////////////////////////////////////////////////////////////
-// The MIT License (MIT)
-//
-// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-////////////////////////////////////////////////////////////////////////////////
-
-#include "acl/core/impl/compiler_utils.h"
-#include "acl/decompression/output_writer.h"
-
-#include <rtm/types.h>
-
-#include <cstdint>
-
-ACL_IMPL_FILE_PRAGMA_PUSH
-
-namespace acl
-{
-	//////////////////////////////////////////////////////////////////////////
-	// A simple output writer implementation that simply writes out to a
-	// Transform_32 array.
-	//////////////////////////////////////////////////////////////////////////
-	struct DefaultOutputWriter final : public OutputWriter
-	{
-		DefaultOutputWriter(rtm::qvvf* transforms, uint16_t num_transforms)
-			: m_transforms(transforms)
-			, m_num_transforms(num_transforms)
-		{
-			ACL_ASSERT(transforms != nullptr, "Transforms array cannot be null");
-			ACL_ASSERT(num_transforms != 0, "Transforms array cannot be empty");
-		}
-
-		void RTM_SIMD_CALL write_bone_rotation(uint16_t bone_index, rtm::quatf_arg0 rotation)
-		{
-			ACL_ASSERT(bone_index < m_num_transforms, "Invalid bone index. %u >= %u", bone_index, m_num_transforms);
-			m_transforms[bone_index].rotation = rotation;
-		}
-
-		void RTM_SIMD_CALL write_bone_translation(uint16_t bone_index, rtm::vector4f_arg0 translation)
-		{
-			ACL_ASSERT(bone_index < m_num_transforms, "Invalid bone index. %u >= %u", bone_index, m_num_transforms);
-			m_transforms[bone_index].translation = translation;
-		}
-
-		void RTM_SIMD_CALL write_bone_scale(uint16_t bone_index, rtm::vector4f_arg0 scale)
-		{
-			ACL_ASSERT(bone_index < m_num_transforms, "Invalid bone index. %u >= %u", bone_index, m_num_transforms);
-			m_transforms[bone_index].scale = scale;
-		}
-
-		rtm::qvvf* m_transforms;
-		uint16_t m_num_transforms;
-	};
-}
-
-ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/decompression/impl/decompress_data.h b/includes/acl/decompression/impl/decompress_data.h
deleted file mode 100644
--- a/includes/acl/decompression/impl/decompress_data.h
+++ /dev/null
@@ -1,622 +0,0 @@
-#pragma once
-
-////////////////////////////////////////////////////////////////////////////////
-// The MIT License (MIT)
-//
-// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-////////////////////////////////////////////////////////////////////////////////
-
-#include "acl/core/impl/compiler_utils.h"
-#include "acl/core/memory_utils.h"
-
-#include <rtm/quatf.h>
-#include <rtm/vector4f.h>
-#include <rtm/packing/quatf.h>
-
-ACL_IMPL_FILE_PRAGMA_PUSH
-
-namespace acl
-{
-	namespace acl_impl
-	{
-		template<class SettingsType, class DecompressionContextType, class SamplingContextType>
-		inline void skip_over_rotation(const SettingsType& settings, const ClipHeader& header, const DecompressionContextType& decomp_context, SamplingContextType& sampling_context)
-		{
-			const BitSetIndexRef track_index_bit_ref(decomp_context.bitset_desc, sampling_context.track_index);
-			const bool is_sample_default = bitset_test(decomp_context.default_tracks_bitset, track_index_bit_ref);
-			if (!is_sample_default)
-			{
-				const rotation_format8 rotation_format = settings.get_rotation_format(header.rotation_format);
-
-				const bool is_sample_constant = bitset_test(decomp_context.constant_tracks_bitset, track_index_bit_ref);
-				if (is_sample_constant)
-				{
-					const rotation_format8 packed_format = is_rotation_format_variable(rotation_format) ? get_highest_variant_precision(get_rotation_variant(rotation_format)) : rotation_format;
-					sampling_context.constant_track_data_offset += get_packed_rotation_size(packed_format);
-				}
-				else
-				{
-					constexpr size_t num_key_frames = SamplingContextType::k_num_samples_to_interpolate;
-
-					if (is_rotation_format_variable(rotation_format))
-					{
-						for (size_t i = 0; i < num_key_frames; ++i)
-						{
-							const uint8_t bit_rate = decomp_context.format_per_track_data[i][sampling_context.format_per_track_data_offset];
-							const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate) * 3;	// 3 components
-
-							sampling_context.key_frame_bit_offsets[i] += num_bits_at_bit_rate;
-						}
-
-						sampling_context.format_per_track_data_offset++;
-					}
-					else
-					{
-						const uint32_t rotation_size = get_packed_rotation_size(rotation_format);
-						const uint32_t num_bits_at_bit_rate = rotation_size == (sizeof(float) * 4) ? 128 : 96;
-
-						for (size_t i = 0; i < num_key_frames; ++i)
-							sampling_context.key_frame_bit_offsets[i] += num_bits_at_bit_rate;
-					}
-
-					if (are_any_enum_flags_set(decomp_context.range_reduction, range_reduction_flags8::rotations) && settings.are_range_reduction_flags_supported(range_reduction_flags8::rotations))
-					{
-						sampling_context.clip_range_data_offset += decomp_context.num_rotation_components * sizeof(float) * 2;
-
-						if (header.num_segments > 1)
-							sampling_context.segment_range_data_offset += decomp_context.num_rotation_components * k_segment_range_reduction_num_bytes_per_component * 2;
-					}
-				}
-			}
-
-			sampling_context.track_index++;
-		}
-
-		template<class SettingsAdapterType, class DecompressionContextType, class SamplingContextType>
-		inline void skip_over_vector(const SettingsAdapterType& settings, const ClipHeader& header, const DecompressionContextType& decomp_context, SamplingContextType& sampling_context)
-		{
-			const BitSetIndexRef track_index_bit_ref(decomp_context.bitset_desc, sampling_context.track_index);
-			const bool is_sample_default = bitset_test(decomp_context.default_tracks_bitset, track_index_bit_ref);
-			if (!is_sample_default)
-			{
-				const bool is_sample_constant = bitset_test(decomp_context.constant_tracks_bitset, track_index_bit_ref);
-				if (is_sample_constant)
-				{
-					// Constant Vector3 tracks store the remaining sample with full precision
-					sampling_context.constant_track_data_offset += get_packed_vector_size(vector_format8::vector3f_full);
-				}
-				else
-				{
-					constexpr size_t num_key_frames = SamplingContextType::k_num_samples_to_interpolate;
-					const vector_format8 format = settings.get_vector_format(header);
-
-					if (is_vector_format_variable(format))
-					{
-						for (size_t i = 0; i < num_key_frames; ++i)
-						{
-							const uint8_t bit_rate = decomp_context.format_per_track_data[i][sampling_context.format_per_track_data_offset];
-							const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate) * 3;	// 3 components
-
-							sampling_context.key_frame_bit_offsets[i] += num_bits_at_bit_rate;
-						}
-
-						sampling_context.format_per_track_data_offset++;
-					}
-					else
-					{
-						for (size_t i = 0; i < num_key_frames; ++i)
-							sampling_context.key_frame_bit_offsets[i] += 96;
-					}
-
-					const range_reduction_flags8 range_reduction_flag = settings.get_range_reduction_flag();
-
-					if (are_any_enum_flags_set(decomp_context.range_reduction, range_reduction_flag) && settings.are_range_reduction_flags_supported(range_reduction_flag))
-					{
-						sampling_context.clip_range_data_offset += k_clip_range_reduction_vector3_range_size;
-
-						if (header.num_segments > 1)
-							sampling_context.segment_range_data_offset += 3 * k_segment_range_reduction_num_bytes_per_component * 2;
-					}
-				}
-			}
-
-			sampling_context.track_index++;
-		}
-
-		template <class SettingsType, class DecompressionContextType, class SamplingContextType>
-		inline rtm::quatf RTM_SIMD_CALL decompress_and_interpolate_rotation(const SettingsType& settings, const ClipHeader& header, const DecompressionContextType& decomp_context, SamplingContextType& sampling_context)
-		{
-			static_assert(SamplingContextType::k_num_samples_to_interpolate == 2 || SamplingContextType::k_num_samples_to_interpolate == 4, "Unsupported number of samples");
-
-			rtm::quatf interpolated_rotation;
-
-			const BitSetIndexRef track_index_bit_ref(decomp_context.bitset_desc, sampling_context.track_index);
-			const bool is_sample_default = bitset_test(decomp_context.default_tracks_bitset, track_index_bit_ref);
-			if (is_sample_default)
-			{
-				interpolated_rotation = rtm::quat_identity();
-			}
-			else
-			{
-				const rotation_format8 rotation_format = settings.get_rotation_format(header.rotation_format);
-				const bool is_sample_constant = bitset_test(decomp_context.constant_tracks_bitset, track_index_bit_ref);
-				if (is_sample_constant)
-				{
-					if (rotation_format == rotation_format8::quatf_full && settings.is_rotation_format_supported(rotation_format8::quatf_full))
-						interpolated_rotation = unpack_quat_128(decomp_context.constant_track_data + sampling_context.constant_track_data_offset);
-					else if (rotation_format == rotation_format8::quatf_drop_w_full && settings.is_rotation_format_supported(rotation_format8::quatf_drop_w_full))
-						interpolated_rotation = unpack_quat_96_unsafe(decomp_context.constant_track_data + sampling_context.constant_track_data_offset);
-					else if (rotation_format == rotation_format8::quatf_drop_w_variable && settings.is_rotation_format_supported(rotation_format8::quatf_drop_w_variable))
-						interpolated_rotation = unpack_quat_96_unsafe(decomp_context.constant_track_data + sampling_context.constant_track_data_offset);
-					else
-					{
-						ACL_ASSERT(false, "Unrecognized rotation format");
-						interpolated_rotation = rtm::quat_identity();
-					}
-
-					ACL_ASSERT(rtm::quat_is_finite(interpolated_rotation), "Rotation is not valid!");
-					ACL_ASSERT(rtm::quat_is_normalized(interpolated_rotation), "Rotation is not normalized!");
-
-					const rotation_format8 packed_format = is_rotation_format_variable(rotation_format) ? get_highest_variant_precision(get_rotation_variant(rotation_format)) : rotation_format;
-					sampling_context.constant_track_data_offset += get_packed_rotation_size(packed_format);
-				}
-				else
-				{
-					constexpr size_t num_key_frames = SamplingContextType::k_num_samples_to_interpolate;
-
-					// This part is fairly complex, we'll loop and write to the stack (sampling context)
-					rtm::vector4f* rotations_as_vec = &sampling_context.vectors[0];
-
-					// Range ignore flags are used to skip range normalization at the clip and/or segment levels
-					// Each sample has two bits like so: sample 0 clip, sample 0 segment, sample 1 clip, sample 1 segment, etc
-					// By default, we never ignore range reduction
-					uint32_t range_ignore_flags = 0;
-
-					if (rotation_format == rotation_format8::quatf_drop_w_variable && settings.is_rotation_format_supported(rotation_format8::quatf_drop_w_variable))
-					{
-						for (size_t i = 0; i < num_key_frames; ++i)
-						{
-							range_ignore_flags <<= 2;
-
-							const uint8_t bit_rate = decomp_context.format_per_track_data[i][sampling_context.format_per_track_data_offset];
-							const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate);
-
-							if (is_constant_bit_rate(bit_rate))
-							{
-								rotations_as_vec[i] = unpack_vector3_u48_unsafe(decomp_context.segment_range_data[i] + sampling_context.segment_range_data_offset);
-								range_ignore_flags |= 0x00000001U;	// Skip segment only
-							}
-							else if (is_raw_bit_rate(bit_rate))
-							{
-								rotations_as_vec[i] = unpack_vector3_96_unsafe(decomp_context.animated_track_data[i], sampling_context.key_frame_bit_offsets[i]);
-								range_ignore_flags |= 0x00000003U;	// Skip clip and segment
-							}
-							else
-								rotations_as_vec[i] = unpack_vector3_uXX_unsafe(uint8_t(num_bits_at_bit_rate), decomp_context.animated_track_data[i], sampling_context.key_frame_bit_offsets[i]);
-
-							sampling_context.key_frame_bit_offsets[i] += num_bits_at_bit_rate * 3;
-						}
-
-						sampling_context.format_per_track_data_offset++;
-					}
-					else
-					{
-						if (rotation_format == rotation_format8::quatf_full && settings.is_rotation_format_supported(rotation_format8::quatf_full))
-						{
-							for (size_t i = 0; i < num_key_frames; ++i)
-							{
-								rotations_as_vec[i] = unpack_vector4_128_unsafe(decomp_context.animated_track_data[i], sampling_context.key_frame_bit_offsets[i]);
-								sampling_context.key_frame_bit_offsets[i] += 128;
-							}
-						}
-						else if (rotation_format == rotation_format8::quatf_drop_w_full && settings.is_rotation_format_supported(rotation_format8::quatf_drop_w_full))
-						{
-							for (size_t i = 0; i < num_key_frames; ++i)
-							{
-								rotations_as_vec[i] = unpack_vector3_96_unsafe(decomp_context.animated_track_data[i], sampling_context.key_frame_bit_offsets[i]);
-								sampling_context.key_frame_bit_offsets[i] += 96;
-							}
-						}
-					}
-
-					// Load our samples to avoid working with the stack now that things can be unrolled.
-					// We unroll because even if we work from the stack, with 2 samples the compiler always
-					// unrolls but it fails to keep the values in registers, working from the stack which
-					// is inefficient.
-					rtm::vector4f rotation_as_vec0 = rotations_as_vec[0];
-					rtm::vector4f rotation_as_vec1 = rotations_as_vec[1];
-					rtm::vector4f rotation_as_vec2;
-					rtm::vector4f rotation_as_vec3;
-
-					if (static_condition<num_key_frames == 4>::test())
-					{
-						rotation_as_vec2 = rotations_as_vec[2];
-						rotation_as_vec3 = rotations_as_vec[3];
-					}
-					else
-					{
-						rotation_as_vec2 = rotation_as_vec0;
-						rotation_as_vec3 = rotation_as_vec0;
-					}
-
-					const uint32_t num_rotation_components = decomp_context.num_rotation_components;
-
-					if (are_any_enum_flags_set(decomp_context.range_reduction, range_reduction_flags8::rotations) && settings.are_range_reduction_flags_supported(range_reduction_flags8::rotations))
-					{
-						if (header.num_segments > 1)
-						{
-							const uint32_t segment_range_min_offset = sampling_context.segment_range_data_offset;
-							const uint32_t segment_range_extent_offset = sampling_context.segment_range_data_offset + (num_rotation_components * sizeof(uint8_t));
-
-							if (rotation_format == rotation_format8::quatf_drop_w_variable && settings.is_rotation_format_supported(rotation_format8::quatf_drop_w_variable))
-							{
-								constexpr uint32_t ignore_mask = 0x00000001U << ((num_key_frames - 1) * 2);
-								if ((range_ignore_flags & (ignore_mask >> 0)) == 0)
-								{
-									const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_min_offset);
-									const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_extent_offset);
-
-									rotation_as_vec0 = rtm::vector_mul_add(rotation_as_vec0, segment_range_extent, segment_range_min);
-								}
-
-								if ((range_ignore_flags & (ignore_mask >> 2)) == 0)
-								{
-									const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_min_offset);
-									const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_extent_offset);
-
-									rotation_as_vec1 = rtm::vector_mul_add(rotation_as_vec1, segment_range_extent, segment_range_min);
-								}
-
-								if (static_condition<num_key_frames == 4>::test())
-								{
-									if ((range_ignore_flags & (ignore_mask >> 4)) == 0)
-									{
-										const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[2] + segment_range_min_offset);
-										const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[2] + segment_range_extent_offset);
-
-										rotation_as_vec2 = rtm::vector_mul_add(rotation_as_vec2, segment_range_extent, segment_range_min);
-									}
-
-									if ((range_ignore_flags & (ignore_mask >> 8)) == 0)
-									{
-										const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[3] + segment_range_min_offset);
-										const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[3] + segment_range_extent_offset);
-
-										rotation_as_vec3 = rtm::vector_mul_add(rotation_as_vec3, segment_range_extent, segment_range_min);
-									}
-								}
-							}
-							else
-							{
-								if (rotation_format == rotation_format8::quatf_full && settings.is_rotation_format_supported(rotation_format8::quatf_full))
-								{
-									{
-										const rtm::vector4f segment_range_min = unpack_vector4_32(decomp_context.segment_range_data[0] + segment_range_min_offset, true);
-										const rtm::vector4f segment_range_extent = unpack_vector4_32(decomp_context.segment_range_data[0] + segment_range_extent_offset, true);
-
-										rotation_as_vec0 = rtm::vector_mul_add(rotation_as_vec0, segment_range_extent, segment_range_min);
-									}
-
-									{
-										const rtm::vector4f segment_range_min = unpack_vector4_32(decomp_context.segment_range_data[1] + segment_range_min_offset, true);
-										const rtm::vector4f segment_range_extent = unpack_vector4_32(decomp_context.segment_range_data[1] + segment_range_extent_offset, true);
-
-										rotation_as_vec1 = rtm::vector_mul_add(rotation_as_vec1, segment_range_extent, segment_range_min);
-									}
-
-									if (static_condition<num_key_frames == 4>::test())
-									{
-										{
-											const rtm::vector4f segment_range_min = unpack_vector4_32(decomp_context.segment_range_data[2] + segment_range_min_offset, true);
-											const rtm::vector4f segment_range_extent = unpack_vector4_32(decomp_context.segment_range_data[2] + segment_range_extent_offset, true);
-
-											rotation_as_vec2 = rtm::vector_mul_add(rotation_as_vec2, segment_range_extent, segment_range_min);
-										}
-
-										{
-											const rtm::vector4f segment_range_min = unpack_vector4_32(decomp_context.segment_range_data[3] + segment_range_min_offset, true);
-											const rtm::vector4f segment_range_extent = unpack_vector4_32(decomp_context.segment_range_data[3] + segment_range_extent_offset, true);
-
-											rotation_as_vec3 = rtm::vector_mul_add(rotation_as_vec3, segment_range_extent, segment_range_min);
-										}
-									}
-								}
-								else
-								{
-									{
-										const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_min_offset);
-										const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_extent_offset);
-
-										rotation_as_vec0 = rtm::vector_mul_add(rotation_as_vec0, segment_range_extent, segment_range_min);
-									}
-
-									{
-										const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_min_offset);
-										const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_extent_offset);
-
-										rotation_as_vec1 = rtm::vector_mul_add(rotation_as_vec1, segment_range_extent, segment_range_min);
-									}
-
-									if (static_condition<num_key_frames == 4>::test())
-									{
-										{
-											const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[2] + segment_range_min_offset);
-											const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[2] + segment_range_extent_offset);
-
-											rotation_as_vec2 = rtm::vector_mul_add(rotation_as_vec2, segment_range_extent, segment_range_min);
-										}
-
-										{
-											const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[3] + segment_range_min_offset);
-											const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[3] + segment_range_extent_offset);
-
-											rotation_as_vec3 = rtm::vector_mul_add(rotation_as_vec3, segment_range_extent, segment_range_min);
-										}
-									}
-								}
-							}
-
-							sampling_context.segment_range_data_offset += num_rotation_components * k_segment_range_reduction_num_bytes_per_component * 2;
-						}
-
-						const rtm::vector4f clip_range_min = rtm::vector_load(decomp_context.clip_range_data + sampling_context.clip_range_data_offset);
-						const rtm::vector4f clip_range_extent = rtm::vector_load(decomp_context.clip_range_data + sampling_context.clip_range_data_offset + (num_rotation_components * sizeof(float)));
-
-						constexpr uint32_t ignore_mask = 0x00000002U << ((num_key_frames - 1) * 2);
-						if ((range_ignore_flags & (ignore_mask >> 0)) == 0)
-							rotation_as_vec0 = rtm::vector_mul_add(rotation_as_vec0, clip_range_extent, clip_range_min);
-
-						if ((range_ignore_flags & (ignore_mask >> 2)) == 0)
-							rotation_as_vec1 = rtm::vector_mul_add(rotation_as_vec1, clip_range_extent, clip_range_min);
-
-						if (static_condition<num_key_frames == 4>::test())
-						{
-							if ((range_ignore_flags & (ignore_mask >> 4)) == 0)
-								rotation_as_vec2 = rtm::vector_mul_add(rotation_as_vec2, clip_range_extent, clip_range_min);
-
-							if ((range_ignore_flags & (ignore_mask >> 8)) == 0)
-								rotation_as_vec3 = rtm::vector_mul_add(rotation_as_vec3, clip_range_extent, clip_range_min);
-						}
-
-						sampling_context.clip_range_data_offset += num_rotation_components * sizeof(float) * 2;
-					}
-
-					// No-op conversion
-					rtm::quatf rotation0 = rtm::vector_to_quat(rotation_as_vec0);
-					rtm::quatf rotation1 = rtm::vector_to_quat(rotation_as_vec1);
-					rtm::quatf rotation2 = rtm::vector_to_quat(rotation_as_vec2);
-					rtm::quatf rotation3 = rtm::vector_to_quat(rotation_as_vec3);
-
-					if (rotation_format != rotation_format8::quatf_full || !settings.is_rotation_format_supported(rotation_format8::quatf_full))
-					{
-						// We dropped the W component
-						rotation0 = rtm::quat_from_positive_w(rotation_as_vec0);
-						rotation1 = rtm::quat_from_positive_w(rotation_as_vec1);
-
-						if (static_condition<num_key_frames == 4>::test())
-						{
-							rotation2 = rtm::quat_from_positive_w(rotation_as_vec2);
-							rotation3 = rtm::quat_from_positive_w(rotation_as_vec3);
-						}
-					}
-
-					if (static_condition<num_key_frames == 4>::test())
-						interpolated_rotation = SamplingContextType::interpolate_rotation(rotation0, rotation1, rotation2, rotation3, decomp_context.interpolation_alpha);
-					else
-					{
-						const bool normalize_rotations = settings.normalize_rotations();
-						if (normalize_rotations)
-							interpolated_rotation = SamplingContextType::interpolate_rotation(rotation0, rotation1, decomp_context.interpolation_alpha);
-						else
-							interpolated_rotation = SamplingContextType::interpolate_rotation_no_normalization(rotation0, rotation1, decomp_context.interpolation_alpha);
-					}
-
-					ACL_ASSERT(rtm::quat_is_finite(interpolated_rotation), "Rotation is not valid!");
-					ACL_ASSERT(rtm::quat_is_normalized(interpolated_rotation) || !settings.normalize_rotations(), "Rotation is not normalized!");
-				}
-			}
-
-			sampling_context.track_index++;
-			return interpolated_rotation;
-		}
-
-		template<class SettingsAdapterType, class DecompressionContextType, class SamplingContextType>
-		inline rtm::vector4f RTM_SIMD_CALL decompress_and_interpolate_vector(const SettingsAdapterType& settings, const ClipHeader& header, const DecompressionContextType& decomp_context, SamplingContextType& sampling_context)
-		{
-			static_assert(SamplingContextType::k_num_samples_to_interpolate == 2 || SamplingContextType::k_num_samples_to_interpolate == 4, "Unsupported number of samples");
-
-			rtm::vector4f interpolated_vector;
-
-			const BitSetIndexRef track_index_bit_ref(decomp_context.bitset_desc, sampling_context.track_index);
-			const bool is_sample_default = bitset_test(decomp_context.default_tracks_bitset, track_index_bit_ref);
-			if (is_sample_default)
-			{
-				interpolated_vector = settings.get_default_value();
-			}
-			else
-			{
-				const bool is_sample_constant = bitset_test(decomp_context.constant_tracks_bitset, track_index_bit_ref);
-				if (is_sample_constant)
-				{
-					// Constant translation tracks store the remaining sample with full precision
-					interpolated_vector = unpack_vector3_96_unsafe(decomp_context.constant_track_data + sampling_context.constant_track_data_offset);
-					ACL_ASSERT(rtm::vector_is_finite3(interpolated_vector), "Vector is not valid!");
-
-					sampling_context.constant_track_data_offset += get_packed_vector_size(vector_format8::vector3f_full);
-				}
-				else
-				{
-					const vector_format8 format = settings.get_vector_format(header);
-
-					constexpr size_t num_key_frames = SamplingContextType::k_num_samples_to_interpolate;
-
-					// This part is fairly complex, we'll loop and write to the stack (sampling context)
-					rtm::vector4f* vectors = &sampling_context.vectors[0];
-
-					// Range ignore flags are used to skip range normalization at the clip and/or segment levels
-					// Each sample has two bits like so: sample 0 clip, sample 0 segment, sample 1 clip, sample 1 segment, etc
-					// By default, we never ignore range reduction
-					uint32_t range_ignore_flags = 0;
-
-					if (format == vector_format8::vector3f_variable && settings.is_vector_format_supported(vector_format8::vector3f_variable))
-					{
-						for (size_t i = 0; i < num_key_frames; ++i)
-						{
-							range_ignore_flags <<= 2;
-
-							const uint8_t bit_rate = decomp_context.format_per_track_data[i][sampling_context.format_per_track_data_offset];
-							const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate);
-
-							if (is_constant_bit_rate(bit_rate))
-							{
-								vectors[i] = unpack_vector3_u48_unsafe(decomp_context.segment_range_data[i] + sampling_context.segment_range_data_offset);
-								range_ignore_flags |= 0x00000001U;	// Skip segment only
-							}
-							else if (is_raw_bit_rate(bit_rate))
-							{
-								vectors[i] = unpack_vector3_96_unsafe(decomp_context.animated_track_data[i], sampling_context.key_frame_bit_offsets[i]);
-								range_ignore_flags |= 0x00000003U;	// Skip clip and segment
-							}
-							else
-								vectors[i] = unpack_vector3_uXX_unsafe(uint8_t(num_bits_at_bit_rate), decomp_context.animated_track_data[i], sampling_context.key_frame_bit_offsets[i]);
-
-							sampling_context.key_frame_bit_offsets[i] += num_bits_at_bit_rate * 3;
-						}
-
-						sampling_context.format_per_track_data_offset++;
-					}
-					else
-					{
-						if (format == vector_format8::vector3f_full && settings.is_vector_format_supported(vector_format8::vector3f_full))
-						{
-							for (size_t i = 0; i < num_key_frames; ++i)
-							{
-								vectors[i] = unpack_vector3_96_unsafe(decomp_context.animated_track_data[i], sampling_context.key_frame_bit_offsets[i]);
-								sampling_context.key_frame_bit_offsets[i] += 96;
-							}
-						}
-					}
-
-					// Load our samples to avoid working with the stack now that things can be unrolled.
-					// We unroll because even if we work from the stack, with 2 samples the compiler always
-					// unrolls but it fails to keep the values in registers, working from the stack which
-					// is inefficient.
-					rtm::vector4f vector0 = vectors[0];
-					rtm::vector4f vector1 = vectors[1];
-					rtm::vector4f vector2;
-					rtm::vector4f vector3;
-
-					if (static_condition<num_key_frames == 4>::test())
-					{
-						vector2 = vectors[2];
-						vector3 = vectors[3];
-					}
-					else
-					{
-						vector2 = vector0;
-						vector3 = vector0;
-					}
-
-					const range_reduction_flags8 range_reduction_flag = settings.get_range_reduction_flag();
-					if (are_any_enum_flags_set(decomp_context.range_reduction, range_reduction_flag) && settings.are_range_reduction_flags_supported(range_reduction_flag))
-					{
-						if (header.num_segments > 1)
-						{
-							const uint32_t segment_range_min_offset = sampling_context.segment_range_data_offset;
-							const uint32_t segment_range_extent_offset = sampling_context.segment_range_data_offset + (3 * sizeof(uint8_t));
-
-							constexpr uint32_t ignore_mask = 0x00000001U << ((num_key_frames - 1) * 2);
-							if ((range_ignore_flags & (ignore_mask >> 0)) == 0)
-							{
-								const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_min_offset);
-								const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_extent_offset);
-
-								vector0 = rtm::vector_mul_add(vector0, segment_range_extent, segment_range_min);
-							}
-
-							if ((range_ignore_flags & (ignore_mask >> 2)) == 0)
-							{
-								const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_min_offset);
-								const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_extent_offset);
-
-								vector1 = rtm::vector_mul_add(vector1, segment_range_extent, segment_range_min);
-							}
-
-							if (static_condition<num_key_frames == 4>::test())
-							{
-								if ((range_ignore_flags & (ignore_mask >> 4)) == 0)
-								{
-									const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[2] + segment_range_min_offset);
-									const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[2] + segment_range_extent_offset);
-
-									vector2 = rtm::vector_mul_add(vector2, segment_range_extent, segment_range_min);
-								}
-
-								if ((range_ignore_flags & (ignore_mask >> 8)) == 0)
-								{
-									const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[3] + segment_range_min_offset);
-									const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[3] + segment_range_extent_offset);
-
-									vector3 = rtm::vector_mul_add(vector3, segment_range_extent, segment_range_min);
-								}
-							}
-
-							sampling_context.segment_range_data_offset += 3 * k_segment_range_reduction_num_bytes_per_component * 2;
-						}
-
-						const rtm::vector4f clip_range_min = unpack_vector3_96_unsafe(decomp_context.clip_range_data + sampling_context.clip_range_data_offset);
-						const rtm::vector4f clip_range_extent = unpack_vector3_96_unsafe(decomp_context.clip_range_data + sampling_context.clip_range_data_offset + (3 * sizeof(float)));
-
-						constexpr uint32_t ignore_mask = 0x00000002U << ((num_key_frames - 1) * 2);
-						if ((range_ignore_flags & (ignore_mask >> 0)) == 0)
-							vector0 = rtm::vector_mul_add(vector0, clip_range_extent, clip_range_min);
-
-						if ((range_ignore_flags & (ignore_mask >> 2)) == 0)
-							vector1 = rtm::vector_mul_add(vector1, clip_range_extent, clip_range_min);
-
-						if (static_condition<num_key_frames == 4>::test())
-						{
-							if ((range_ignore_flags & (ignore_mask >> 4)) == 0)
-								vector2 = rtm::vector_mul_add(vector2, clip_range_extent, clip_range_min);
-
-							if ((range_ignore_flags & (ignore_mask >> 8)) == 0)
-								vector3 = rtm::vector_mul_add(vector3, clip_range_extent, clip_range_min);
-						}
-
-						sampling_context.clip_range_data_offset += k_clip_range_reduction_vector3_range_size;
-					}
-
-					if (static_condition<num_key_frames == 4>::test())
-						interpolated_vector = SamplingContextType::interpolate_vector4(vector0, vector1, vector2, vector3, decomp_context.interpolation_alpha);
-					else
-						interpolated_vector = SamplingContextType::interpolate_vector4(vector0, vector1, decomp_context.interpolation_alpha);
-
-					ACL_ASSERT(rtm::vector_is_finite3(interpolated_vector), "Vector is not valid!");
-				}
-			}
-
-			sampling_context.track_index++;
-			return interpolated_vector;
-		}
-	}
-}
-
-ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/decompression/impl/track_sampling_impl.h b/includes/acl/decompression/impl/decompression_context_selector.h
similarity index 59%
rename from includes/acl/decompression/impl/track_sampling_impl.h
rename to includes/acl/decompression/impl/decompression_context_selector.h
--- a/includes/acl/decompression/impl/track_sampling_impl.h
+++ b/includes/acl/decompression/impl/decompression_context_selector.h
@@ -3,7 +3,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // The MIT License (MIT)
 //
-// Copyright (c) 2019 Nicholas Frechette & Animation Compression Library contributors
+// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to deal
@@ -25,12 +25,9 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 #include "acl/core/impl/compiler_utils.h"
-#include "acl/core/compressed_tracks.h"
-
-#include <rtm/scalarf.h>
-#include <rtm/vector4f.h>
-
-#include <cstdint>
+#include "acl/decompression/impl/scalar_track_decompression.h"
+#include "acl/decompression/impl/transform_track_decompression.h"
+#include "acl/decompression/impl/universal_track_decompression.h"
 
 ACL_IMPL_FILE_PRAGMA_PUSH
 
@@ -38,26 +35,27 @@ namespace acl
 {
 	namespace acl_impl
 	{
-		struct alignas(64) persistent_decompression_context
-		{
-			// Clip related data							//   offsets
-			const compressed_tracks* tracks;				//   0 |   0
-
-			uint32_t tracks_hash;							//   4 |   8
-
-			float duration;									//   8 |  12
-
-			// Seeking related data
-			float interpolation_alpha;						//  12 |  16
-			float sample_time;								//  16 |  20
+		//////////////////////////////////////////////////////////////////////////
+		// Helper struct to choose the decompression context type based on what tracks we support
+		template<bool supports_scalar_tracks, bool supports_transform_tracks>
+		struct persistent_decompression_context_selector {};
 
-			uint32_t key_frame_bit_offsets[2];				//  20 |  24	// Variable quantization
-
-			uint8_t padding_tail[sizeof(void*) == 4 ? 36 : 32];
+		template<>
+		struct persistent_decompression_context_selector<true, false>
+		{
+			using type = persistent_scalar_decompression_context;
+		};
 
-			//////////////////////////////////////////////////////////////////////////
+		template<>
+		struct persistent_decompression_context_selector<false, true>
+		{
+			using type = persistent_transform_decompression_context;
+		};
 
-			inline bool is_initialized() const { return tracks != nullptr; }
+		template<>
+		struct persistent_decompression_context_selector<true, true>
+		{
+			using type = persistent_universal_decompression_context;
 		};
 	}
 }
diff --git a/includes/acl/decompression/impl/scalar_track_decompression.h b/includes/acl/decompression/impl/scalar_track_decompression.h
new file mode 100644
--- /dev/null
+++ b/includes/acl/decompression/impl/scalar_track_decompression.h
@@ -0,0 +1,555 @@
+#pragma once
+
+////////////////////////////////////////////////////////////////////////////////
+// The MIT License (MIT)
+//
+// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "acl/core/compressed_tracks.h"
+#include "acl/core/interpolation_utils.h"
+#include "acl/core/track_writer.h"
+#include "acl/core/impl/compiler_utils.h"
+
+#include <rtm/scalarf.h>
+#include <rtm/vector4f.h>
+
+#include <cstdint>
+#include <type_traits>
+
+ACL_IMPL_FILE_PRAGMA_PUSH
+
+namespace acl
+{
+	namespace acl_impl
+	{
+		struct alignas(64) persistent_scalar_decompression_context
+		{
+			persistent_scalar_decompression_context() {}
+
+			// Clip related data							//   offsets
+			// Only member used to detect if we are initialized, must be first
+			const compressed_tracks* tracks;				//   0 |   0
+
+			uint32_t tracks_hash;							//   4 |   8
+
+			float duration;									//   8 |  12
+
+															// Seeking related data
+			float interpolation_alpha;						//  12 |  16
+			float sample_time;								//  16 |  20
+
+			uint32_t key_frame_bit_offsets[2];				//  20 |  24	// Variable quantization
+
+			uint8_t padding_tail[sizeof(void*) == 4 ? 36 : 32];
+
+			//////////////////////////////////////////////////////////////////////////
+
+			inline const compressed_tracks* get_compressed_tracks() const { return tracks; }
+			inline bool is_initialized() const { return tracks != nullptr; }
+			inline void reset() { tracks = nullptr; }
+		};
+
+		static_assert(sizeof(persistent_scalar_decompression_context) == 64, "Unexpected size");
+
+		template<class decompression_settings_type>
+		inline void initialize(persistent_scalar_decompression_context& context, const compressed_tracks& tracks)
+		{
+			ACL_ASSERT(tracks.is_valid(false).empty(), "Compressed tracks are not valid");
+			ACL_ASSERT(tracks.get_algorithm_type() == algorithm_type8::uniformly_sampled, "Invalid algorithm type [%s], expected [%s]", get_algorithm_name(tracks.get_algorithm_type()), get_algorithm_name(algorithm_type8::uniformly_sampled));
+
+			context.tracks = &tracks;
+			context.tracks_hash = tracks.get_hash();
+			context.duration = tracks.get_duration();
+			context.sample_time = -1.0F;
+			context.interpolation_alpha = 0.0;
+		}
+
+		inline bool is_dirty(const persistent_scalar_decompression_context& context, const compressed_tracks& tracks)
+		{
+			if (context.tracks != &tracks)
+				return true;
+
+			if (context.tracks_hash != tracks.get_hash())
+				return true;
+
+			return false;
+		}
+
+		template<class decompression_settings_type>
+		inline void seek(persistent_scalar_decompression_context& context, float sample_time, sample_rounding_policy rounding_policy)
+		{
+			ACL_ASSERT(context.is_initialized(), "Context is not initialized");
+			ACL_ASSERT(rtm::scalar_is_finite(sample_time), "Invalid sample time");
+
+			// Clamp for safety, the caller should normally handle this but in practice, it often isn't the case
+			if (decompression_settings_type::clamp_sample_time())
+				sample_time = rtm::scalar_clamp(sample_time, 0.0F, context.duration);
+
+			if (context.sample_time == sample_time)
+				return;
+
+			context.sample_time = sample_time;
+
+			const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*context.tracks);
+
+			uint32_t key_frame0;
+			uint32_t key_frame1;
+			find_linear_interpolation_samples_with_sample_rate(header.num_samples, header.sample_rate, sample_time, rounding_policy, key_frame0, key_frame1, context.interpolation_alpha);
+
+			const acl_impl::scalar_tracks_header& scalars_header = acl_impl::get_scalar_tracks_header(*context.tracks);
+
+			context.key_frame_bit_offsets[0] = key_frame0 * scalars_header.num_bits_per_frame;
+			context.key_frame_bit_offsets[1] = key_frame1 * scalars_header.num_bits_per_frame;
+		}
+
+		template<class decompression_settings_type, class track_writer_type>
+		inline void decompress_tracks(persistent_scalar_decompression_context& context, track_writer_type& writer)
+		{
+			static_assert(std::is_base_of<track_writer, track_writer_type>::value, "track_writer_type must derive from track_writer");
+			ACL_ASSERT(context.is_initialized(), "Context is not initialized");
+
+			// Due to the SIMD operations, we sometimes overflow in the SIMD lanes not used.
+			// Disable floating point exceptions to avoid issues.
+			fp_environment fp_env;
+			if (decompression_settings_type::disable_fp_exeptions())
+				disable_fp_exceptions(fp_env);
+
+			const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*context.tracks);
+			const acl_impl::scalar_tracks_header& scalars_header = acl_impl::get_scalar_tracks_header(*context.tracks);
+			const rtm::scalarf interpolation_alpha = rtm::scalar_set(context.interpolation_alpha);
+
+			const acl_impl::track_metadata* per_track_metadata = scalars_header.get_track_metadata();
+			const float* constant_values = scalars_header.get_track_constant_values();
+			const float* range_values = scalars_header.get_track_range_values();
+			const uint8_t* animated_values = scalars_header.get_track_animated_values();
+
+			uint32_t track_bit_offset0 = context.key_frame_bit_offsets[0];
+			uint32_t track_bit_offset1 = context.key_frame_bit_offsets[1];
+
+			const track_type8 track_type = header.track_type;
+			const uint32_t num_tracks = header.num_tracks;
+
+			for (uint32_t track_index = 0; track_index < num_tracks; ++track_index)
+			{
+				const acl_impl::track_metadata& metadata = per_track_metadata[track_index];
+				const uint8_t bit_rate = metadata.bit_rate;
+				const uint32_t num_bits_per_component = get_num_bits_at_bit_rate(bit_rate);
+
+				if (track_type == track_type8::float1f && decompression_settings_type::is_track_type_supported(track_type8::float1f))
+				{
+					rtm::scalarf value;
+					if (is_constant_bit_rate(bit_rate))
+					{
+						value = rtm::scalar_load(constant_values);
+						constant_values += 1;
+					}
+					else
+					{
+						rtm::scalarf value0;
+						rtm::scalarf value1;
+						if (is_raw_bit_rate(bit_rate))
+						{
+							value0 = unpack_scalarf_32_unsafe(animated_values, track_bit_offset0);
+							value1 = unpack_scalarf_32_unsafe(animated_values, track_bit_offset1);
+						}
+						else
+						{
+							value0 = unpack_scalarf_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset0);
+							value1 = unpack_scalarf_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset1);
+
+							const rtm::scalarf range_min = rtm::scalar_load(range_values);
+							const rtm::scalarf range_extent = rtm::scalar_load(range_values + 1);
+							value0 = rtm::scalar_mul_add(value0, range_extent, range_min);
+							value1 = rtm::scalar_mul_add(value1, range_extent, range_min);
+							range_values += 2;
+						}
+
+						value = rtm::scalar_lerp(value0, value1, interpolation_alpha);
+
+						const uint32_t num_sample_bits = num_bits_per_component;
+						track_bit_offset0 += num_sample_bits;
+						track_bit_offset1 += num_sample_bits;
+					}
+
+					writer.write_float1(track_index, value);
+				}
+				else if (track_type == track_type8::float2f && decompression_settings_type::is_track_type_supported(track_type8::float2f))
+				{
+					rtm::vector4f value;
+					if (is_constant_bit_rate(bit_rate))
+					{
+						value = rtm::vector_load(constant_values);
+						constant_values += 2;
+					}
+					else
+					{
+						rtm::vector4f value0;
+						rtm::vector4f value1;
+						if (is_raw_bit_rate(bit_rate))
+						{
+							value0 = unpack_vector2_64_unsafe(animated_values, track_bit_offset0);
+							value1 = unpack_vector2_64_unsafe(animated_values, track_bit_offset1);
+						}
+						else
+						{
+							value0 = unpack_vector2_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset0);
+							value1 = unpack_vector2_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset1);
+
+							const rtm::vector4f range_min = rtm::vector_load(range_values);
+							const rtm::vector4f range_extent = rtm::vector_load(range_values + 2);
+							value0 = rtm::vector_mul_add(value0, range_extent, range_min);
+							value1 = rtm::vector_mul_add(value1, range_extent, range_min);
+							range_values += 4;
+						}
+
+						value = rtm::vector_lerp(value0, value1, interpolation_alpha);
+
+						const uint32_t num_sample_bits = num_bits_per_component * 2;
+						track_bit_offset0 += num_sample_bits;
+						track_bit_offset1 += num_sample_bits;
+					}
+
+					writer.write_float2(track_index, value);
+				}
+				else if (track_type == track_type8::float3f && decompression_settings_type::is_track_type_supported(track_type8::float3f))
+				{
+					rtm::vector4f value;
+					if (is_constant_bit_rate(bit_rate))
+					{
+						value = rtm::vector_load(constant_values);
+						constant_values += 3;
+					}
+					else
+					{
+						rtm::vector4f value0;
+						rtm::vector4f value1;
+						if (is_raw_bit_rate(bit_rate))
+						{
+							value0 = unpack_vector3_96_unsafe(animated_values, track_bit_offset0);
+							value1 = unpack_vector3_96_unsafe(animated_values, track_bit_offset1);
+						}
+						else
+						{
+							value0 = unpack_vector3_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset0);
+							value1 = unpack_vector3_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset1);
+
+							const rtm::vector4f range_min = rtm::vector_load(range_values);
+							const rtm::vector4f range_extent = rtm::vector_load(range_values + 3);
+							value0 = rtm::vector_mul_add(value0, range_extent, range_min);
+							value1 = rtm::vector_mul_add(value1, range_extent, range_min);
+							range_values += 6;
+						}
+
+						value = rtm::vector_lerp(value0, value1, interpolation_alpha);
+
+						const uint32_t num_sample_bits = num_bits_per_component * 3;
+						track_bit_offset0 += num_sample_bits;
+						track_bit_offset1 += num_sample_bits;
+					}
+
+					writer.write_float3(track_index, value);
+				}
+				else if (track_type == track_type8::float4f && decompression_settings_type::is_track_type_supported(track_type8::float4f))
+				{
+					rtm::vector4f value;
+					if (is_constant_bit_rate(bit_rate))
+					{
+						value = rtm::vector_load(constant_values);
+						constant_values += 4;
+					}
+					else
+					{
+						rtm::vector4f value0;
+						rtm::vector4f value1;
+						if (is_raw_bit_rate(bit_rate))
+						{
+							value0 = unpack_vector4_128_unsafe(animated_values, track_bit_offset0);
+							value1 = unpack_vector4_128_unsafe(animated_values, track_bit_offset1);
+						}
+						else
+						{
+							value0 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset0);
+							value1 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset1);
+
+							const rtm::vector4f range_min = rtm::vector_load(range_values);
+							const rtm::vector4f range_extent = rtm::vector_load(range_values + 4);
+							value0 = rtm::vector_mul_add(value0, range_extent, range_min);
+							value1 = rtm::vector_mul_add(value1, range_extent, range_min);
+							range_values += 8;
+						}
+
+						value = rtm::vector_lerp(value0, value1, interpolation_alpha);
+
+						const uint32_t num_sample_bits = num_bits_per_component * 4;
+						track_bit_offset0 += num_sample_bits;
+						track_bit_offset1 += num_sample_bits;
+					}
+
+					writer.write_float4(track_index, value);
+				}
+				else if (track_type == track_type8::vector4f && decompression_settings_type::is_track_type_supported(track_type8::vector4f))
+				{
+					rtm::vector4f value;
+					if (is_constant_bit_rate(bit_rate))
+					{
+						value = rtm::vector_load(constant_values);
+						constant_values += 4;
+					}
+					else
+					{
+						rtm::vector4f value0;
+						rtm::vector4f value1;
+						if (is_raw_bit_rate(bit_rate))
+						{
+							value0 = unpack_vector4_128_unsafe(animated_values, track_bit_offset0);
+							value1 = unpack_vector4_128_unsafe(animated_values, track_bit_offset1);
+						}
+						else
+						{
+							value0 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset0);
+							value1 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, track_bit_offset1);
+
+							const rtm::vector4f range_min = rtm::vector_load(range_values);
+							const rtm::vector4f range_extent = rtm::vector_load(range_values + 4);
+							value0 = rtm::vector_mul_add(value0, range_extent, range_min);
+							value1 = rtm::vector_mul_add(value1, range_extent, range_min);
+							range_values += 8;
+						}
+
+						value = rtm::vector_lerp(value0, value1, interpolation_alpha);
+
+						const uint32_t num_sample_bits = num_bits_per_component * 4;
+						track_bit_offset0 += num_sample_bits;
+						track_bit_offset1 += num_sample_bits;
+					}
+
+					writer.write_vector4(track_index, value);
+				}
+			}
+
+			if (decompression_settings_type::disable_fp_exeptions())
+				restore_fp_exceptions(fp_env);
+		}
+
+		template<class decompression_settings_type, class track_writer_type>
+		inline void decompress_track(persistent_scalar_decompression_context& context, uint32_t track_index, track_writer_type& writer)
+		{
+			static_assert(std::is_base_of<track_writer, track_writer_type>::value, "track_writer_type must derive from track_writer");
+			ACL_ASSERT(context.is_initialized(), "Context is not initialized");
+
+			// Due to the SIMD operations, we sometimes overflow in the SIMD lanes not used.
+			// Disable floating point exceptions to avoid issues.
+			fp_environment fp_env;
+			if (decompression_settings_type::disable_fp_exeptions())
+				disable_fp_exceptions(fp_env);
+
+			const acl_impl::tracks_header& header = acl_impl::get_tracks_header(*context.tracks);
+			const acl_impl::scalar_tracks_header& scalars_header = acl_impl::get_scalar_tracks_header(*context.tracks);
+			const rtm::scalarf interpolation_alpha = rtm::scalar_set(context.interpolation_alpha);
+
+			ACL_ASSERT(track_index < header.num_tracks, "Invalid track index");
+
+			const float* constant_values = scalars_header.get_track_constant_values();
+			const float* range_values = scalars_header.get_track_range_values();
+
+			const track_type8 track_type = header.track_type;
+			const uint32_t num_element_components = get_track_num_sample_elements(track_type);
+			uint32_t track_bit_offset = 0;
+
+			const acl_impl::track_metadata* per_track_metadata = scalars_header.get_track_metadata();
+			for (uint32_t scan_track_index = 0; scan_track_index < track_index; ++scan_track_index)
+			{
+				const acl_impl::track_metadata& metadata = per_track_metadata[scan_track_index];
+				const uint8_t bit_rate = metadata.bit_rate;
+				const uint32_t num_bits_per_component = get_num_bits_at_bit_rate(bit_rate);
+				track_bit_offset += num_bits_per_component * num_element_components;
+
+				if (is_constant_bit_rate(bit_rate))
+					constant_values += num_element_components;
+				else if (!is_raw_bit_rate(bit_rate))
+					range_values += num_element_components * 2;
+			}
+
+			const acl_impl::track_metadata& metadata = per_track_metadata[track_index];
+			const uint8_t bit_rate = metadata.bit_rate;
+			const uint32_t num_bits_per_component = get_num_bits_at_bit_rate(bit_rate);
+
+			const uint8_t* animated_values = scalars_header.get_track_animated_values();
+
+			if (track_type == track_type8::float1f && decompression_settings_type::is_track_type_supported(track_type8::float1f))
+			{
+				rtm::scalarf value;
+				if (is_constant_bit_rate(bit_rate))
+					value = rtm::scalar_load(constant_values);
+				else
+				{
+					rtm::scalarf value0;
+					rtm::scalarf value1;
+					if (is_raw_bit_rate(bit_rate))
+					{
+						value0 = unpack_scalarf_32_unsafe(animated_values, context.key_frame_bit_offsets[0] + track_bit_offset);
+						value1 = unpack_scalarf_32_unsafe(animated_values, context.key_frame_bit_offsets[1] + track_bit_offset);
+					}
+					else
+					{
+						value0 = unpack_scalarf_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[0] + track_bit_offset);
+						value1 = unpack_scalarf_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[1] + track_bit_offset);
+
+						const rtm::scalarf range_min = rtm::scalar_load(range_values);
+						const rtm::scalarf range_extent = rtm::scalar_load(range_values + num_element_components);
+						value0 = rtm::scalar_mul_add(value0, range_extent, range_min);
+						value1 = rtm::scalar_mul_add(value1, range_extent, range_min);
+					}
+
+					value = rtm::scalar_lerp(value0, value1, interpolation_alpha);
+				}
+
+				writer.write_float1(track_index, value);
+			}
+			else if (track_type == track_type8::float2f && decompression_settings_type::is_track_type_supported(track_type8::float2f))
+			{
+				rtm::vector4f value;
+				if (is_constant_bit_rate(bit_rate))
+					value = rtm::vector_load(constant_values);
+				else
+				{
+					rtm::vector4f value0;
+					rtm::vector4f value1;
+					if (is_raw_bit_rate(bit_rate))
+					{
+						value0 = unpack_vector2_64_unsafe(animated_values, context.key_frame_bit_offsets[0] + track_bit_offset);
+						value1 = unpack_vector2_64_unsafe(animated_values, context.key_frame_bit_offsets[1] + track_bit_offset);
+					}
+					else
+					{
+						value0 = unpack_vector2_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[0] + track_bit_offset);
+						value1 = unpack_vector2_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[1] + track_bit_offset);
+
+						const rtm::vector4f range_min = rtm::vector_load(range_values);
+						const rtm::vector4f range_extent = rtm::vector_load(range_values + num_element_components);
+						value0 = rtm::vector_mul_add(value0, range_extent, range_min);
+						value1 = rtm::vector_mul_add(value1, range_extent, range_min);
+					}
+
+					value = rtm::vector_lerp(value0, value1, interpolation_alpha);
+				}
+
+				writer.write_float2(track_index, value);
+			}
+			else if (track_type == track_type8::float3f && decompression_settings_type::is_track_type_supported(track_type8::float3f))
+			{
+				rtm::vector4f value;
+				if (is_constant_bit_rate(bit_rate))
+					value = rtm::vector_load(constant_values);
+				else
+				{
+					rtm::vector4f value0;
+					rtm::vector4f value1;
+					if (is_raw_bit_rate(bit_rate))
+					{
+						value0 = unpack_vector3_96_unsafe(animated_values, context.key_frame_bit_offsets[0] + track_bit_offset);
+						value1 = unpack_vector3_96_unsafe(animated_values, context.key_frame_bit_offsets[1] + track_bit_offset);
+					}
+					else
+					{
+						value0 = unpack_vector3_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[0] + track_bit_offset);
+						value1 = unpack_vector3_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[1] + track_bit_offset);
+
+						const rtm::vector4f range_min = rtm::vector_load(range_values);
+						const rtm::vector4f range_extent = rtm::vector_load(range_values + num_element_components);
+						value0 = rtm::vector_mul_add(value0, range_extent, range_min);
+						value1 = rtm::vector_mul_add(value1, range_extent, range_min);
+					}
+
+					value = rtm::vector_lerp(value0, value1, interpolation_alpha);
+				}
+
+				writer.write_float3(track_index, value);
+			}
+			else if (track_type == track_type8::float4f && decompression_settings_type::is_track_type_supported(track_type8::float4f))
+			{
+				rtm::vector4f value;
+				if (is_constant_bit_rate(bit_rate))
+					value = rtm::vector_load(constant_values);
+				else
+				{
+					rtm::vector4f value0;
+					rtm::vector4f value1;
+					if (is_raw_bit_rate(bit_rate))
+					{
+						value0 = unpack_vector4_128_unsafe(animated_values, context.key_frame_bit_offsets[0] + track_bit_offset);
+						value1 = unpack_vector4_128_unsafe(animated_values, context.key_frame_bit_offsets[1] + track_bit_offset);
+					}
+					else
+					{
+						value0 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[0] + track_bit_offset);
+						value1 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[1] + track_bit_offset);
+
+						const rtm::vector4f range_min = rtm::vector_load(range_values);
+						const rtm::vector4f range_extent = rtm::vector_load(range_values + num_element_components);
+						value0 = rtm::vector_mul_add(value0, range_extent, range_min);
+						value1 = rtm::vector_mul_add(value1, range_extent, range_min);
+					}
+
+					value = rtm::vector_lerp(value0, value1, interpolation_alpha);
+				}
+
+				writer.write_float4(track_index, value);
+			}
+			else if (track_type == track_type8::vector4f && decompression_settings_type::is_track_type_supported(track_type8::vector4f))
+			{
+				rtm::vector4f value;
+				if (is_constant_bit_rate(bit_rate))
+					value = rtm::vector_load(constant_values);
+				else
+				{
+					rtm::vector4f value0;
+					rtm::vector4f value1;
+					if (is_raw_bit_rate(bit_rate))
+					{
+						value0 = unpack_vector4_128_unsafe(animated_values, context.key_frame_bit_offsets[0] + track_bit_offset);
+						value1 = unpack_vector4_128_unsafe(animated_values, context.key_frame_bit_offsets[1] + track_bit_offset);
+					}
+					else
+					{
+						value0 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[0] + track_bit_offset);
+						value1 = unpack_vector4_uXX_unsafe(num_bits_per_component, animated_values, context.key_frame_bit_offsets[1] + track_bit_offset);
+
+						const rtm::vector4f range_min = rtm::vector_load(range_values);
+						const rtm::vector4f range_extent = rtm::vector_load(range_values + num_element_components);
+						value0 = rtm::vector_mul_add(value0, range_extent, range_min);
+						value1 = rtm::vector_mul_add(value1, range_extent, range_min);
+					}
+
+					value = rtm::vector_lerp(value0, value1, interpolation_alpha);
+				}
+
+				writer.write_vector4(track_index, value);
+			}
+
+			if (decompression_settings_type::disable_fp_exeptions())
+				restore_fp_exceptions(fp_env);
+		}
+	}
+}
+
+ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/decompression/impl/transform_track_decompression.h b/includes/acl/decompression/impl/transform_track_decompression.h
new file mode 100644
--- /dev/null
+++ b/includes/acl/decompression/impl/transform_track_decompression.h
@@ -0,0 +1,1018 @@
+#pragma once
+
+////////////////////////////////////////////////////////////////////////////////
+// The MIT License (MIT)
+//
+// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "acl/core/compressed_tracks.h"
+#include "acl/core/interpolation_utils.h"
+#include "acl/core/track_writer.h"
+#include "acl/core/impl/compiler_utils.h"
+#include "acl/math/quatf.h"
+
+#include <rtm/scalarf.h>
+#include <rtm/vector4f.h>
+
+#include <cstdint>
+#include <type_traits>
+
+ACL_IMPL_FILE_PRAGMA_PUSH
+
+namespace acl
+{
+	namespace acl_impl
+	{
+		struct alignas(64) persistent_transform_decompression_context
+		{
+			persistent_transform_decompression_context() {}
+
+			// Clip related data							//   offsets
+			// Only member used to detect if we are initialized, must be first
+			const compressed_tracks* tracks;				//   0 |   0
+
+			const uint32_t* constant_tracks_bitset;			//   4 |   8
+			const uint8_t* constant_track_data;				//   8 |  16
+			const uint32_t* default_tracks_bitset;			//  12 |  24
+
+			const uint8_t* clip_range_data;					//  16 |  32
+
+			float clip_duration;							//  20 |  40
+
+			bitset_description bitset_desc;					//  24 |  44
+
+			uint32_t clip_hash;								//  28 |  48
+
+			range_reduction_flags8 range_reduction;			//  32 |  52
+			uint8_t num_rotation_components;				//  33 |  53
+
+			uint8_t padding0[2];							//  34 |  54
+
+															// Seeking related data
+			const uint8_t* format_per_track_data[2];		//  36 |  56
+			const uint8_t* segment_range_data[2];			//  44 |  72
+			const uint8_t* animated_track_data[2];			//  52 |  88
+
+			uint32_t key_frame_bit_offsets[2];				//  60 | 104
+
+			float interpolation_alpha;						//  68 | 112
+			float sample_time;								//  76 | 120
+
+			uint8_t padding1[sizeof(void*) == 4 ? 52 : 4];	//  80 | 124
+
+			//									Total size:	   128 | 128
+
+			//////////////////////////////////////////////////////////////////////////
+
+			inline const compressed_tracks* get_compressed_tracks() const { return tracks; }
+			inline bool is_initialized() const { return tracks != nullptr; }
+			inline void reset() { tracks = nullptr; }
+		};
+
+		static_assert(sizeof(persistent_transform_decompression_context) == 128, "Unexpected size");
+
+		struct alignas(64) sampling_context
+		{
+			//														//   offsets
+			uint32_t track_index;									//   0 |   0
+			uint32_t constant_track_data_offset;					//   4 |   4
+			uint32_t clip_range_data_offset;						//   8 |   8
+
+			uint32_t format_per_track_data_offset;					//  12 |  12
+			uint32_t segment_range_data_offset;						//  16 |  16
+
+			uint32_t key_frame_bit_offsets[2];						//  20 |  20
+
+			uint8_t padding[4];										//  28 |  28
+
+			rtm::vector4f vectors[2];								//  32 |  32
+
+			//											Total size:	    64 |  64
+		};
+
+		static_assert(sizeof(sampling_context) == 64, "Unexpected size");
+
+		// We use adapters to wrap the decompression_settings
+		// This allows us to re-use the code for skipping and decompressing Vector3 samples
+		// Code generation will generate specialized code for each specialization
+		template<class decompression_settings_type>
+		struct translation_decompression_settings_adapter
+		{
+			// Forward to our decompression settings
+			static constexpr range_reduction_flags8 get_range_reduction_flag() { return range_reduction_flags8::translations; }
+			static constexpr vector_format8 get_vector_format(const transform_tracks_header& header) { return decompression_settings_type::get_translation_format(header.translation_format); }
+			static constexpr bool is_vector_format_supported(vector_format8 format) { return decompression_settings_type::is_translation_format_supported(format); }
+		};
+
+		template<class decompression_settings_type>
+		struct scale_decompression_settings_adapter
+		{
+			// Forward to our decompression settings
+			static constexpr range_reduction_flags8 get_range_reduction_flag() { return range_reduction_flags8::scales; }
+			static constexpr vector_format8 get_vector_format(const transform_tracks_header& header) { return decompression_settings_type::get_scale_format(header.scale_format); }
+			static constexpr bool is_vector_format_supported(vector_format8 format) { return decompression_settings_type::is_scale_format_supported(format); }
+		};
+
+		template<class decompression_settings_type>
+		inline void skip_over_rotation(const persistent_transform_decompression_context& decomp_context, const transform_tracks_header& header, sampling_context& sampling_context_)
+		{
+			const bitset_index_ref track_index_bit_ref(decomp_context.bitset_desc, sampling_context_.track_index);
+			const bool is_sample_default = bitset_test(decomp_context.default_tracks_bitset, track_index_bit_ref);
+			if (!is_sample_default)
+			{
+				const rotation_format8 rotation_format = decompression_settings_type::get_rotation_format(header.rotation_format);
+
+				const bool is_sample_constant = bitset_test(decomp_context.constant_tracks_bitset, track_index_bit_ref);
+				if (is_sample_constant)
+				{
+					const rotation_format8 packed_format = is_rotation_format_variable(rotation_format) ? get_highest_variant_precision(get_rotation_variant(rotation_format)) : rotation_format;
+					sampling_context_.constant_track_data_offset += get_packed_rotation_size(packed_format);
+				}
+				else
+				{
+					if (is_rotation_format_variable(rotation_format))
+					{
+						for (uint32_t i = 0; i < 2; ++i)
+						{
+							const uint8_t bit_rate = decomp_context.format_per_track_data[i][sampling_context_.format_per_track_data_offset];
+							const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate) * 3;	// 3 components
+
+							sampling_context_.key_frame_bit_offsets[i] += num_bits_at_bit_rate;
+						}
+
+						sampling_context_.format_per_track_data_offset++;
+					}
+					else
+					{
+						const uint32_t rotation_size = get_packed_rotation_size(rotation_format);
+						const uint32_t num_bits_at_bit_rate = rotation_size == (sizeof(float) * 4) ? 128 : 96;
+
+						for (uint32_t i = 0; i < 2; ++i)
+							sampling_context_.key_frame_bit_offsets[i] += num_bits_at_bit_rate;
+					}
+
+					if (are_any_enum_flags_set(decomp_context.range_reduction, range_reduction_flags8::rotations))
+					{
+						sampling_context_.clip_range_data_offset += decomp_context.num_rotation_components * sizeof(float) * 2;
+
+						if (header.num_segments > 1)
+							sampling_context_.segment_range_data_offset += decomp_context.num_rotation_components * k_segment_range_reduction_num_bytes_per_component * 2;
+					}
+				}
+			}
+
+			sampling_context_.track_index++;
+		}
+
+		template <class decompression_settings_type>
+		inline rtm::quatf RTM_SIMD_CALL decompress_and_interpolate_rotation(const persistent_transform_decompression_context& decomp_context, const transform_tracks_header& header, sampling_context& sampling_context_)
+		{
+			rtm::quatf interpolated_rotation;
+
+			const bitset_index_ref track_index_bit_ref(decomp_context.bitset_desc, sampling_context_.track_index);
+			const bool is_sample_default = bitset_test(decomp_context.default_tracks_bitset, track_index_bit_ref);
+			if (is_sample_default)
+			{
+				interpolated_rotation = rtm::quat_identity();
+			}
+			else
+			{
+				const rotation_format8 rotation_format = decompression_settings_type::get_rotation_format(header.rotation_format);
+				const bool is_sample_constant = bitset_test(decomp_context.constant_tracks_bitset, track_index_bit_ref);
+				if (is_sample_constant)
+				{
+					if (rotation_format == rotation_format8::quatf_full && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_full))
+						interpolated_rotation = unpack_quat_128(decomp_context.constant_track_data + sampling_context_.constant_track_data_offset);
+					else if (rotation_format == rotation_format8::quatf_drop_w_full && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_full))
+						interpolated_rotation = unpack_quat_96_unsafe(decomp_context.constant_track_data + sampling_context_.constant_track_data_offset);
+					else if (rotation_format == rotation_format8::quatf_drop_w_variable && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_variable))
+						interpolated_rotation = unpack_quat_96_unsafe(decomp_context.constant_track_data + sampling_context_.constant_track_data_offset);
+					else
+					{
+						ACL_ASSERT(false, "Unrecognized rotation format");
+						interpolated_rotation = rtm::quat_identity();
+					}
+
+					ACL_ASSERT(rtm::quat_is_finite(interpolated_rotation), "Rotation is not valid!");
+					ACL_ASSERT(rtm::quat_is_normalized(interpolated_rotation), "Rotation is not normalized!");
+
+					const rotation_format8 packed_format = is_rotation_format_variable(rotation_format) ? get_highest_variant_precision(get_rotation_variant(rotation_format)) : rotation_format;
+					sampling_context_.constant_track_data_offset += get_packed_rotation_size(packed_format);
+				}
+				else
+				{
+					// This part is fairly complex, we'll loop and write to the stack (sampling context)
+					rtm::vector4f* rotations_as_vec = &sampling_context_.vectors[0];
+
+					// Range ignore flags are used to skip range normalization at the clip and/or segment levels
+					// Each sample has two bits like so:
+					//    - 0x01 = sample 1 segment
+					//    - 0x02 = sample 1 clip
+					//    - 0x04 = sample 0 segment
+					//    - 0x08 = sample 0 clip
+					// By default, we never ignore range reduction
+					uint32_t range_ignore_flags = 0;
+
+					if (rotation_format == rotation_format8::quatf_drop_w_variable && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_variable))
+					{
+						for (uint32_t i = 0; i < 2; ++i)
+						{
+							range_ignore_flags <<= 2;
+
+							const uint8_t bit_rate = decomp_context.format_per_track_data[i][sampling_context_.format_per_track_data_offset];
+							const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate);
+
+							if (is_constant_bit_rate(bit_rate))
+							{
+								rotations_as_vec[i] = unpack_vector3_u48_unsafe(decomp_context.segment_range_data[i] + sampling_context_.segment_range_data_offset);
+								range_ignore_flags |= 0x00000001U;	// Skip segment only
+							}
+							else if (is_raw_bit_rate(bit_rate))
+							{
+								rotations_as_vec[i] = unpack_vector3_96_unsafe(decomp_context.animated_track_data[i], sampling_context_.key_frame_bit_offsets[i]);
+								range_ignore_flags |= 0x00000003U;	// Skip clip and segment
+							}
+							else
+								rotations_as_vec[i] = unpack_vector3_uXX_unsafe(uint8_t(num_bits_at_bit_rate), decomp_context.animated_track_data[i], sampling_context_.key_frame_bit_offsets[i]);
+
+							sampling_context_.key_frame_bit_offsets[i] += num_bits_at_bit_rate * 3;
+						}
+
+						sampling_context_.format_per_track_data_offset++;
+					}
+					else
+					{
+						if (rotation_format == rotation_format8::quatf_full && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_full))
+						{
+							for (uint32_t i = 0; i < 2; ++i)
+							{
+								rotations_as_vec[i] = unpack_vector4_128_unsafe(decomp_context.animated_track_data[i], sampling_context_.key_frame_bit_offsets[i]);
+								sampling_context_.key_frame_bit_offsets[i] += 128;
+							}
+						}
+						else if (rotation_format == rotation_format8::quatf_drop_w_full && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_full))
+						{
+							for (uint32_t i = 0; i < 2; ++i)
+							{
+								rotations_as_vec[i] = unpack_vector3_96_unsafe(decomp_context.animated_track_data[i], sampling_context_.key_frame_bit_offsets[i]);
+								sampling_context_.key_frame_bit_offsets[i] += 96;
+							}
+						}
+					}
+
+					// Load our samples to avoid working with the stack now that things can be unrolled.
+					// We unroll because even if we work from the stack, with 2 samples the compiler always
+					// unrolls but it fails to keep the values in registers, working from the stack which
+					// is inefficient.
+					rtm::vector4f rotation_as_vec0 = rotations_as_vec[0];
+					rtm::vector4f rotation_as_vec1 = rotations_as_vec[1];
+
+					const uint32_t num_rotation_components = decomp_context.num_rotation_components;
+
+					if (are_any_enum_flags_set(decomp_context.range_reduction, range_reduction_flags8::rotations))
+					{
+						if (header.num_segments > 1)
+						{
+							const uint32_t segment_range_min_offset = sampling_context_.segment_range_data_offset;
+							const uint32_t segment_range_extent_offset = sampling_context_.segment_range_data_offset + (num_rotation_components * sizeof(uint8_t));
+
+							if (rotation_format == rotation_format8::quatf_drop_w_variable && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_drop_w_variable))
+							{
+								if ((range_ignore_flags & 0x04) == 0)
+								{
+									const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_min_offset);
+									const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_extent_offset);
+
+									rotation_as_vec0 = rtm::vector_mul_add(rotation_as_vec0, segment_range_extent, segment_range_min);
+								}
+
+								if ((range_ignore_flags & 0x01) == 0)
+								{
+									const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_min_offset);
+									const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_extent_offset);
+
+									rotation_as_vec1 = rtm::vector_mul_add(rotation_as_vec1, segment_range_extent, segment_range_min);
+								}
+							}
+							else
+							{
+								if (rotation_format == rotation_format8::quatf_full && decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_full))
+								{
+									{
+										const rtm::vector4f segment_range_min = unpack_vector4_32(decomp_context.segment_range_data[0] + segment_range_min_offset, true);
+										const rtm::vector4f segment_range_extent = unpack_vector4_32(decomp_context.segment_range_data[0] + segment_range_extent_offset, true);
+
+										rotation_as_vec0 = rtm::vector_mul_add(rotation_as_vec0, segment_range_extent, segment_range_min);
+									}
+
+									{
+										const rtm::vector4f segment_range_min = unpack_vector4_32(decomp_context.segment_range_data[1] + segment_range_min_offset, true);
+										const rtm::vector4f segment_range_extent = unpack_vector4_32(decomp_context.segment_range_data[1] + segment_range_extent_offset, true);
+
+										rotation_as_vec1 = rtm::vector_mul_add(rotation_as_vec1, segment_range_extent, segment_range_min);
+									}
+								}
+								else
+								{
+									{
+										const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_min_offset);
+										const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_extent_offset);
+
+										rotation_as_vec0 = rtm::vector_mul_add(rotation_as_vec0, segment_range_extent, segment_range_min);
+									}
+
+									{
+										const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_min_offset);
+										const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_extent_offset);
+
+										rotation_as_vec1 = rtm::vector_mul_add(rotation_as_vec1, segment_range_extent, segment_range_min);
+									}
+								}
+							}
+
+							sampling_context_.segment_range_data_offset += num_rotation_components * k_segment_range_reduction_num_bytes_per_component * 2;
+						}
+
+						const rtm::vector4f clip_range_min = rtm::vector_load(decomp_context.clip_range_data + sampling_context_.clip_range_data_offset);
+						const rtm::vector4f clip_range_extent = rtm::vector_load(decomp_context.clip_range_data + sampling_context_.clip_range_data_offset + (num_rotation_components * sizeof(float)));
+
+						if ((range_ignore_flags & 0x08) == 0)
+							rotation_as_vec0 = rtm::vector_mul_add(rotation_as_vec0, clip_range_extent, clip_range_min);
+
+						if ((range_ignore_flags & 0x02) == 0)
+							rotation_as_vec1 = rtm::vector_mul_add(rotation_as_vec1, clip_range_extent, clip_range_min);
+
+						sampling_context_.clip_range_data_offset += num_rotation_components * sizeof(float) * 2;
+					}
+
+					// No-op conversion
+					rtm::quatf rotation0 = rtm::vector_to_quat(rotation_as_vec0);
+					rtm::quatf rotation1 = rtm::vector_to_quat(rotation_as_vec1);
+
+					if (rotation_format != rotation_format8::quatf_full || !decompression_settings_type::is_rotation_format_supported(rotation_format8::quatf_full))
+					{
+						// We dropped the W component
+						rotation0 = rtm::quat_from_positive_w(rotation_as_vec0);
+						rotation1 = rtm::quat_from_positive_w(rotation_as_vec1);
+					}
+
+					const bool normalize_rotations = decompression_settings_type::normalize_rotations();
+					if (normalize_rotations)
+						interpolated_rotation = rtm::quat_lerp(rotation0, rotation1, decomp_context.interpolation_alpha);
+					else
+						interpolated_rotation = quat_lerp_no_normalization(rotation0, rotation1, decomp_context.interpolation_alpha);
+
+					ACL_ASSERT(rtm::quat_is_finite(interpolated_rotation), "Rotation is not valid!");
+					ACL_ASSERT(rtm::quat_is_normalized(interpolated_rotation) || !decompression_settings_type::normalize_rotations(), "Rotation is not normalized!");
+				}
+			}
+
+			sampling_context_.track_index++;
+			return interpolated_rotation;
+		}
+
+		template<class decompression_settings_type>
+		inline void skip_over_vector(const persistent_transform_decompression_context& decomp_context, const transform_tracks_header& header, sampling_context& sampling_context_)
+		{
+			const bitset_index_ref track_index_bit_ref(decomp_context.bitset_desc, sampling_context_.track_index);
+			const bool is_sample_default = bitset_test(decomp_context.default_tracks_bitset, track_index_bit_ref);
+			if (!is_sample_default)
+			{
+				const bool is_sample_constant = bitset_test(decomp_context.constant_tracks_bitset, track_index_bit_ref);
+				if (is_sample_constant)
+				{
+					// Constant Vector3 tracks store the remaining sample with full precision
+					sampling_context_.constant_track_data_offset += get_packed_vector_size(vector_format8::vector3f_full);
+				}
+				else
+				{
+					const vector_format8 format = decompression_settings_type::get_vector_format(header);
+
+					if (is_vector_format_variable(format))
+					{
+						for (uint32_t i = 0; i < 2; ++i)
+						{
+							const uint8_t bit_rate = decomp_context.format_per_track_data[i][sampling_context_.format_per_track_data_offset];
+							const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate) * 3;	// 3 components
+
+							sampling_context_.key_frame_bit_offsets[i] += num_bits_at_bit_rate;
+						}
+
+						sampling_context_.format_per_track_data_offset++;
+					}
+					else
+					{
+						for (uint32_t i = 0; i < 2; ++i)
+							sampling_context_.key_frame_bit_offsets[i] += 96;
+					}
+
+					const range_reduction_flags8 range_reduction_flag = decompression_settings_type::get_range_reduction_flag();
+
+					if (are_any_enum_flags_set(decomp_context.range_reduction, range_reduction_flag))
+					{
+						sampling_context_.clip_range_data_offset += k_clip_range_reduction_vector3_range_size;
+
+						if (header.num_segments > 1)
+							sampling_context_.segment_range_data_offset += 3 * k_segment_range_reduction_num_bytes_per_component * 2;
+					}
+				}
+			}
+
+			sampling_context_.track_index++;
+		}
+
+		template<class decompression_settings_adapter_type>
+		inline rtm::vector4f RTM_SIMD_CALL decompress_and_interpolate_vector(const persistent_transform_decompression_context& decomp_context, const transform_tracks_header& header, rtm::vector4f_arg0 default_value, sampling_context& sampling_context_)
+		{
+			rtm::vector4f interpolated_vector;
+
+			const bitset_index_ref track_index_bit_ref(decomp_context.bitset_desc, sampling_context_.track_index);
+			const bool is_sample_default = bitset_test(decomp_context.default_tracks_bitset, track_index_bit_ref);
+			if (is_sample_default)
+			{
+				interpolated_vector = default_value;
+			}
+			else
+			{
+				const bool is_sample_constant = bitset_test(decomp_context.constant_tracks_bitset, track_index_bit_ref);
+				if (is_sample_constant)
+				{
+					// Constant translation tracks store the remaining sample with full precision
+					interpolated_vector = unpack_vector3_96_unsafe(decomp_context.constant_track_data + sampling_context_.constant_track_data_offset);
+					ACL_ASSERT(rtm::vector_is_finite3(interpolated_vector), "Vector is not valid!");
+
+					sampling_context_.constant_track_data_offset += get_packed_vector_size(vector_format8::vector3f_full);
+				}
+				else
+				{
+					const vector_format8 format = decompression_settings_adapter_type::get_vector_format(header);
+
+					// This part is fairly complex, we'll loop and write to the stack (sampling context)
+					rtm::vector4f* vectors = &sampling_context_.vectors[0];
+
+					// Range ignore flags are used to skip range normalization at the clip and/or segment levels
+					// Each sample has two bits like so:
+					//    - 0x01 = sample 1 segment
+					//    - 0x02 = sample 1 clip
+					//    - 0x04 = sample 0 segment
+					//    - 0x08 = sample 0 clip
+					// By default, we never ignore range reduction
+					uint32_t range_ignore_flags = 0;
+
+					if (format == vector_format8::vector3f_variable && decompression_settings_adapter_type::is_vector_format_supported(vector_format8::vector3f_variable))
+					{
+						for (uint32_t i = 0; i < 2; ++i)
+						{
+							range_ignore_flags <<= 2;
+
+							const uint8_t bit_rate = decomp_context.format_per_track_data[i][sampling_context_.format_per_track_data_offset];
+							const uint32_t num_bits_at_bit_rate = get_num_bits_at_bit_rate(bit_rate);
+
+							if (is_constant_bit_rate(bit_rate))
+							{
+								vectors[i] = unpack_vector3_u48_unsafe(decomp_context.segment_range_data[i] + sampling_context_.segment_range_data_offset);
+								range_ignore_flags |= 0x00000001U;	// Skip segment only
+							}
+							else if (is_raw_bit_rate(bit_rate))
+							{
+								vectors[i] = unpack_vector3_96_unsafe(decomp_context.animated_track_data[i], sampling_context_.key_frame_bit_offsets[i]);
+								range_ignore_flags |= 0x00000003U;	// Skip clip and segment
+							}
+							else
+								vectors[i] = unpack_vector3_uXX_unsafe(uint8_t(num_bits_at_bit_rate), decomp_context.animated_track_data[i], sampling_context_.key_frame_bit_offsets[i]);
+
+							sampling_context_.key_frame_bit_offsets[i] += num_bits_at_bit_rate * 3;
+						}
+
+						sampling_context_.format_per_track_data_offset++;
+					}
+					else
+					{
+						if (format == vector_format8::vector3f_full && decompression_settings_adapter_type::is_vector_format_supported(vector_format8::vector3f_full))
+						{
+							for (uint32_t i = 0; i < 2; ++i)
+							{
+								vectors[i] = unpack_vector3_96_unsafe(decomp_context.animated_track_data[i], sampling_context_.key_frame_bit_offsets[i]);
+								sampling_context_.key_frame_bit_offsets[i] += 96;
+							}
+						}
+					}
+
+					// Load our samples to avoid working with the stack now that things can be unrolled.
+					// We unroll because even if we work from the stack, with 2 samples the compiler always
+					// unrolls but it fails to keep the values in registers, working from the stack which
+					// is inefficient.
+					rtm::vector4f vector0 = vectors[0];
+					rtm::vector4f vector1 = vectors[1];
+
+					const range_reduction_flags8 range_reduction_flag = decompression_settings_adapter_type::get_range_reduction_flag();
+					if (are_any_enum_flags_set(decomp_context.range_reduction, range_reduction_flag))
+					{
+						if (header.num_segments > 1)
+						{
+							const uint32_t segment_range_min_offset = sampling_context_.segment_range_data_offset;
+							const uint32_t segment_range_extent_offset = sampling_context_.segment_range_data_offset + (3 * sizeof(uint8_t));
+
+							if ((range_ignore_flags & 0x04) == 0)
+							{
+								const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_min_offset);
+								const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_extent_offset);
+
+								vector0 = rtm::vector_mul_add(vector0, segment_range_extent, segment_range_min);
+							}
+
+							if ((range_ignore_flags & 0x01) == 0)
+							{
+								const rtm::vector4f segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_min_offset);
+								const rtm::vector4f segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_extent_offset);
+
+								vector1 = rtm::vector_mul_add(vector1, segment_range_extent, segment_range_min);
+							}
+
+							sampling_context_.segment_range_data_offset += 3 * k_segment_range_reduction_num_bytes_per_component * 2;
+						}
+
+						const rtm::vector4f clip_range_min = unpack_vector3_96_unsafe(decomp_context.clip_range_data + sampling_context_.clip_range_data_offset);
+						const rtm::vector4f clip_range_extent = unpack_vector3_96_unsafe(decomp_context.clip_range_data + sampling_context_.clip_range_data_offset + (3 * sizeof(float)));
+
+						if ((range_ignore_flags & 0x08) == 0)
+							vector0 = rtm::vector_mul_add(vector0, clip_range_extent, clip_range_min);
+
+						if ((range_ignore_flags & 0x02) == 0)
+							vector1 = rtm::vector_mul_add(vector1, clip_range_extent, clip_range_min);
+
+						sampling_context_.clip_range_data_offset += k_clip_range_reduction_vector3_range_size;
+					}
+
+					interpolated_vector = rtm::vector_lerp(vector0, vector1, decomp_context.interpolation_alpha);
+
+					ACL_ASSERT(rtm::vector_is_finite3(interpolated_vector), "Vector is not valid!");
+				}
+			}
+
+			sampling_context_.track_index++;
+			return interpolated_vector;
+		}
+
+		template<class decompression_settings_type>
+		inline void initialize(persistent_transform_decompression_context& context, const compressed_tracks& tracks)
+		{
+			ACL_ASSERT(tracks.is_valid(false).empty(), "Compressed tracks are not valid");
+			ACL_ASSERT(tracks.get_algorithm_type() == algorithm_type8::uniformly_sampled, "Invalid algorithm type [%s], expected [%s]", get_algorithm_name(tracks.get_algorithm_type()), get_algorithm_name(algorithm_type8::uniformly_sampled));
+
+			const tracks_header& header = get_tracks_header(tracks);
+			const transform_tracks_header& transform_header = get_transform_tracks_header(tracks);
+
+			const rotation_format8 rotation_format = decompression_settings_type::get_rotation_format(transform_header.rotation_format);
+			const vector_format8 translation_format = decompression_settings_type::get_translation_format(transform_header.translation_format);
+			const vector_format8 scale_format = decompression_settings_type::get_scale_format(transform_header.scale_format);
+
+			ACL_ASSERT(rotation_format == transform_header.rotation_format, "Statically compiled rotation format (%s) differs from the compressed rotation format (%s)!", get_rotation_format_name(rotation_format), get_rotation_format_name(transform_header.rotation_format));
+			ACL_ASSERT(decompression_settings_type::is_rotation_format_supported(rotation_format), "Rotation format (%s) isn't statically supported!", get_rotation_format_name(rotation_format));
+			ACL_ASSERT(translation_format == transform_header.translation_format, "Statically compiled translation format (%s) differs from the compressed translation format (%s)!", get_vector_format_name(translation_format), get_vector_format_name(transform_header.translation_format));
+			ACL_ASSERT(decompression_settings_type::is_translation_format_supported(translation_format), "Translation format (%s) isn't statically supported!", get_vector_format_name(translation_format));
+			ACL_ASSERT(scale_format == transform_header.scale_format, "Statically compiled scale format (%s) differs from the compressed scale format (%s)!", get_vector_format_name(scale_format), get_vector_format_name(transform_header.scale_format));
+			ACL_ASSERT(decompression_settings_type::is_scale_format_supported(scale_format), "Scale format (%s) isn't statically supported!", get_vector_format_name(scale_format));
+
+			context.tracks = &tracks;
+			context.clip_hash = tracks.get_hash();
+			context.clip_duration = calculate_duration(header.num_samples, header.sample_rate);
+			context.sample_time = -1.0F;
+			context.default_tracks_bitset = transform_header.get_default_tracks_bitset();
+
+			context.constant_tracks_bitset = transform_header.get_constant_tracks_bitset();
+			context.constant_track_data = transform_header.get_constant_track_data();
+			context.clip_range_data = transform_header.get_clip_range_data();
+
+			for (uint32_t key_frame_index = 0; key_frame_index < 2; ++key_frame_index)
+			{
+				context.format_per_track_data[key_frame_index] = nullptr;
+				context.segment_range_data[key_frame_index] = nullptr;
+				context.animated_track_data[key_frame_index] = nullptr;
+			}
+
+			const uint32_t num_tracks_per_bone = transform_header.has_scale ? 3 : 2;
+			context.bitset_desc = bitset_description::make_from_num_bits(header.num_tracks * num_tracks_per_bone);
+
+			range_reduction_flags8 range_reduction = range_reduction_flags8::none;
+			if (is_rotation_format_variable(rotation_format))
+				range_reduction |= range_reduction_flags8::rotations;
+			if (is_vector_format_variable(translation_format))
+				range_reduction |= range_reduction_flags8::translations;
+			if (is_vector_format_variable(scale_format))
+				range_reduction |= range_reduction_flags8::scales;
+
+			context.range_reduction = range_reduction;
+			context.num_rotation_components = rotation_format == rotation_format8::quatf_full ? 4 : 3;
+		}
+
+		inline bool is_dirty(const persistent_transform_decompression_context& context, const compressed_tracks& tracks)
+		{
+			if (context.tracks != &tracks)
+				return true;
+
+			if (context.clip_hash != tracks.get_hash())
+				return true;
+
+			return false;
+		}
+
+		template<class decompression_settings_type>
+		inline void seek(persistent_transform_decompression_context& context, float sample_time, sample_rounding_policy rounding_policy)
+		{
+			ACL_ASSERT(context.is_initialized(), "Context is not initialized");
+			ACL_ASSERT(rtm::scalar_is_finite(sample_time), "Invalid sample time");
+
+			// Clamp for safety, the caller should normally handle this but in practice, it often isn't the case
+			if (decompression_settings_type::clamp_sample_time())
+				sample_time = rtm::scalar_clamp(sample_time, 0.0F, context.clip_duration);
+
+			if (context.sample_time == sample_time)
+				return;
+
+			context.sample_time = sample_time;
+
+			const tracks_header& header = get_tracks_header(*context.tracks);
+			const transform_tracks_header& transform_header = get_transform_tracks_header(*context.tracks);
+
+			uint32_t key_frame0;
+			uint32_t key_frame1;
+			find_linear_interpolation_samples_with_sample_rate(header.num_samples, header.sample_rate, sample_time, rounding_policy, key_frame0, key_frame1, context.interpolation_alpha);
+
+			uint32_t segment_key_frame0;
+			uint32_t segment_key_frame1;
+
+			const segment_header* segment_header0;
+			const segment_header* segment_header1;
+
+			const segment_header* segment_headers = transform_header.get_segment_headers();
+			const uint32_t num_segments = transform_header.num_segments;
+
+			if (num_segments == 1)
+			{
+				// Key frame 0 and 1 are in the only segment present
+				// This is a really common case and when it happens, we don't store the segment start index (zero)
+				segment_header0 = segment_headers;
+				segment_key_frame0 = key_frame0;
+
+				segment_header1 = segment_headers;
+				segment_key_frame1 = key_frame1;
+			}
+			else
+			{
+				const uint32_t* segment_start_indices = transform_header.get_segment_start_indices();
+
+				// See segment_streams(..) for implementation details. This implementation is directly tied to it.
+				const uint32_t approx_num_samples_per_segment = header.num_samples / num_segments;	// TODO: Store in header?
+				const uint32_t approx_segment_index = key_frame0 / approx_num_samples_per_segment;
+
+				uint32_t segment_index0 = 0;
+				uint32_t segment_index1 = 0;
+
+				// Our approximate segment guess is just that, a guess. The actual segments we need could be just before or after.
+				// We start looking one segment earlier and up to 2 after. If we have too few segments after, we will hit the
+				// sentinel value of 0xFFFFFFFF and exit the loop.
+				// TODO: Can we do this with SIMD? Load all 4 values, set key_frame0, compare, move mask, count leading zeroes
+				const uint32_t start_segment_index = approx_segment_index > 0 ? (approx_segment_index - 1) : 0;
+				const uint32_t end_segment_index = start_segment_index + 4;
+
+				for (uint32_t segment_index = start_segment_index; segment_index < end_segment_index; ++segment_index)
+				{
+					if (key_frame0 < segment_start_indices[segment_index])
+					{
+						// We went too far, use previous segment
+						ACL_ASSERT(segment_index > 0, "Invalid segment index: %u", segment_index);
+						segment_index0 = segment_index - 1;
+						segment_index1 = key_frame1 < segment_start_indices[segment_index] ? segment_index0 : segment_index;
+						break;
+					}
+				}
+
+				segment_header0 = segment_headers + segment_index0;
+				segment_header1 = segment_headers + segment_index1;
+
+				segment_key_frame0 = key_frame0 - segment_start_indices[segment_index0];
+				segment_key_frame1 = key_frame1 - segment_start_indices[segment_index1];
+			}
+
+			context.format_per_track_data[0] = transform_header.get_format_per_track_data(*segment_header0);
+			context.format_per_track_data[1] = transform_header.get_format_per_track_data(*segment_header1);
+			context.segment_range_data[0] = transform_header.get_segment_range_data(*segment_header0);
+			context.segment_range_data[1] = transform_header.get_segment_range_data(*segment_header1);
+			context.animated_track_data[0] = transform_header.get_track_data(*segment_header0);
+			context.animated_track_data[1] = transform_header.get_track_data(*segment_header1);
+
+			context.key_frame_bit_offsets[0] = segment_key_frame0 * segment_header0->animated_pose_bit_size;
+			context.key_frame_bit_offsets[1] = segment_key_frame1 * segment_header1->animated_pose_bit_size;
+		}
+
+		template<class decompression_settings_type, class track_writer_type>
+		inline void decompress_tracks(persistent_transform_decompression_context& context, track_writer_type& writer)
+		{
+			static_assert(std::is_base_of<track_writer, track_writer_type>::value, "track_writer_type must derive from track_writer");
+			ACL_ASSERT(context.is_initialized(), "Context is not initialized");
+			ACL_ASSERT(context.sample_time >= 0.0f, "Context not set to a valid sample time");
+
+			// Due to the SIMD operations, we sometimes overflow in the SIMD lanes not used.
+			// Disable floating point exceptions to avoid issues.
+			fp_environment fp_env;
+			if (decompression_settings_type::disable_fp_exeptions())
+				disable_fp_exceptions(fp_env);
+
+			const tracks_header& header = get_tracks_header(*context.tracks);
+			const transform_tracks_header& transform_header = get_transform_tracks_header(*context.tracks);
+
+			using translation_adapter = acl_impl::translation_decompression_settings_adapter<decompression_settings_type>;
+			using scale_adapter = acl_impl::scale_decompression_settings_adapter<decompression_settings_type>;
+
+			const rtm::vector4f default_translation = rtm::vector_zero();
+			const rtm::vector4f default_scale = rtm::vector_set(float(transform_header.default_scale));
+			const uint8_t has_scale = transform_header.has_scale;
+
+			sampling_context sampling_context_;
+			sampling_context_.track_index = 0;
+			sampling_context_.constant_track_data_offset = 0;
+			sampling_context_.clip_range_data_offset = 0;
+			sampling_context_.format_per_track_data_offset = 0;
+			sampling_context_.segment_range_data_offset = 0;
+			sampling_context_.key_frame_bit_offsets[0] = context.key_frame_bit_offsets[0];
+			sampling_context_.key_frame_bit_offsets[1] = context.key_frame_bit_offsets[1];
+
+			sampling_context_.vectors[0] = default_translation;	// Init with something to avoid GCC warning
+			sampling_context_.vectors[1] = default_translation;	// Init with something to avoid GCC warning
+
+			const uint32_t num_tracks = header.num_tracks;
+			for (uint32_t track_index = 0; track_index < num_tracks; ++track_index)
+			{
+				if (writer.skip_all_rotations() || writer.skip_track_rotation(track_index))
+					skip_over_rotation<decompression_settings_type>(context, transform_header, sampling_context_);
+				else
+				{
+					const rtm::quatf rotation = decompress_and_interpolate_rotation<decompression_settings_type>(context, transform_header, sampling_context_);
+					writer.write_rotation(track_index, rotation);
+				}
+
+				if (writer.skip_all_translations() || writer.skip_track_translation(track_index))
+					skip_over_vector<translation_adapter>(context, transform_header, sampling_context_);
+				else
+				{
+					const rtm::vector4f translation = decompress_and_interpolate_vector<translation_adapter>(context, transform_header, default_translation, sampling_context_);
+					writer.write_translation(track_index, translation);
+				}
+
+				if (writer.skip_all_scales() || writer.skip_track_scale(track_index))
+				{
+					if (has_scale)
+						skip_over_vector<scale_adapter>(context, transform_header, sampling_context_);
+				}
+				else
+				{
+					const rtm::vector4f scale = has_scale ? decompress_and_interpolate_vector<scale_adapter>(context, transform_header, default_scale, sampling_context_) : default_scale;
+					writer.write_scale(track_index, scale);
+				}
+			}
+
+			if (decompression_settings_type::disable_fp_exeptions())
+				restore_fp_exceptions(fp_env);
+		}
+
+		template<class decompression_settings_type, class track_writer_type>
+		inline void decompress_track(persistent_transform_decompression_context& context, uint32_t track_index, track_writer_type& writer)
+		{
+			static_assert(std::is_base_of<track_writer, track_writer_type>::value, "track_writer_type must derive from track_writer");
+			ACL_ASSERT(context.is_initialized(), "Context is not initialized");
+			ACL_ASSERT(context.sample_time >= 0.0f, "Context not set to a valid sample time");
+
+			// Due to the SIMD operations, we sometimes overflow in the SIMD lanes not used.
+			// Disable floating point exceptions to avoid issues.
+			fp_environment fp_env;
+			if (decompression_settings_type::disable_fp_exeptions())
+				disable_fp_exceptions(fp_env);
+
+			const transform_tracks_header& transform_header = get_transform_tracks_header(*context.tracks);
+			ACL_ASSERT(track_index < get_tracks_header(*context.tracks).num_tracks, "Invalid track index");
+
+			using translation_adapter = acl_impl::translation_decompression_settings_adapter<decompression_settings_type>;
+			using scale_adapter = acl_impl::scale_decompression_settings_adapter<decompression_settings_type>;
+
+			const rtm::vector4f default_translation = rtm::vector_zero();
+			const rtm::vector4f default_scale = rtm::vector_set(float(transform_header.default_scale));
+			const uint8_t has_scale = transform_header.has_scale;
+
+			sampling_context sampling_context_;
+			sampling_context_.key_frame_bit_offsets[0] = context.key_frame_bit_offsets[0];
+			sampling_context_.key_frame_bit_offsets[1] = context.key_frame_bit_offsets[1];
+
+			const rotation_format8 rotation_format = decompression_settings_type::get_rotation_format(transform_header.rotation_format);
+			const vector_format8 translation_format = decompression_settings_type::get_translation_format(transform_header.translation_format);
+			const vector_format8 scale_format = decompression_settings_type::get_scale_format(transform_header.scale_format);
+
+			const bool are_all_tracks_variable = is_rotation_format_variable(rotation_format) && is_vector_format_variable(translation_format) && is_vector_format_variable(scale_format);
+			if (!are_all_tracks_variable)
+			{
+				// Slow path, not optimized yet because it's more complex and shouldn't be used in production anyway
+				sampling_context_.track_index = 0;
+				sampling_context_.constant_track_data_offset = 0;
+				sampling_context_.clip_range_data_offset = 0;
+				sampling_context_.format_per_track_data_offset = 0;
+				sampling_context_.segment_range_data_offset = 0;
+
+				for (uint32_t bone_index = 0; bone_index < track_index; ++bone_index)
+				{
+					skip_over_rotation<decompression_settings_type>(context, transform_header, sampling_context_);
+					skip_over_vector<translation_adapter>(context, transform_header, sampling_context_);
+
+					if (has_scale)
+						skip_over_vector<scale_adapter>(context, transform_header, sampling_context_);
+				}
+			}
+			else
+			{
+				const uint32_t num_tracks_per_bone = has_scale ? 3 : 2;
+				const uint32_t sub_track_index = track_index * num_tracks_per_bone;
+				uint32_t num_default_rotations = 0;
+				uint32_t num_default_translations = 0;
+				uint32_t num_default_scales = 0;
+				uint32_t num_constant_rotations = 0;
+				uint32_t num_constant_translations = 0;
+				uint32_t num_constant_scales = 0;
+
+				if (has_scale)
+				{
+					uint32_t rotation_track_bit_mask = 0x92492492;		// b100100100..
+					uint32_t translation_track_bit_mask = 0x49249249;	// b010010010..
+					uint32_t scale_track_bit_mask = 0x24924924;			// b001001001..
+
+					const uint32_t last_offset = sub_track_index / 32;
+					uint32_t offset = 0;
+					for (; offset < last_offset; ++offset)
+					{
+						const uint32_t default_value = context.default_tracks_bitset[offset];
+						num_default_rotations += count_set_bits(default_value & rotation_track_bit_mask);
+						num_default_translations += count_set_bits(default_value & translation_track_bit_mask);
+						num_default_scales += count_set_bits(default_value & scale_track_bit_mask);
+
+						const uint32_t constant_value = context.constant_tracks_bitset[offset];
+						num_constant_rotations += count_set_bits(constant_value & rotation_track_bit_mask);
+						num_constant_translations += count_set_bits(constant_value & translation_track_bit_mask);
+						num_constant_scales += count_set_bits(constant_value & scale_track_bit_mask);
+
+						// Because the number of tracks in a 32bit word isn't a multiple of the number of tracks we have (3),
+						// we have to rotate the masks left
+						rotation_track_bit_mask = rotate_bits_left(rotation_track_bit_mask, 2);
+						translation_track_bit_mask = rotate_bits_left(translation_track_bit_mask, 2);
+						scale_track_bit_mask = rotate_bits_left(scale_track_bit_mask, 2);
+					}
+
+					const uint32_t remaining_tracks = sub_track_index % 32;
+					if (remaining_tracks != 0)
+					{
+						const uint32_t not_up_to_track_mask = ((1 << (32 - remaining_tracks)) - 1);
+						const uint32_t default_value = and_not(not_up_to_track_mask, context.default_tracks_bitset[offset]);
+						num_default_rotations += count_set_bits(default_value & rotation_track_bit_mask);
+						num_default_translations += count_set_bits(default_value & translation_track_bit_mask);
+						num_default_scales += count_set_bits(default_value & scale_track_bit_mask);
+
+						const uint32_t constant_value = and_not(not_up_to_track_mask, context.constant_tracks_bitset[offset]);
+						num_constant_rotations += count_set_bits(constant_value & rotation_track_bit_mask);
+						num_constant_translations += count_set_bits(constant_value & translation_track_bit_mask);
+						num_constant_scales += count_set_bits(constant_value & scale_track_bit_mask);
+					}
+				}
+				else
+				{
+					const uint32_t rotation_track_bit_mask = 0xAAAAAAAA;		// b10101010..
+					const uint32_t translation_track_bit_mask = 0x55555555;		// b01010101..
+
+					const uint32_t last_offset = sub_track_index / 32;
+					uint32_t offset = 0;
+					for (; offset < last_offset; ++offset)
+					{
+						const uint32_t default_value = context.default_tracks_bitset[offset];
+						num_default_rotations += count_set_bits(default_value & rotation_track_bit_mask);
+						num_default_translations += count_set_bits(default_value & translation_track_bit_mask);
+
+						const uint32_t constant_value = context.constant_tracks_bitset[offset];
+						num_constant_rotations += count_set_bits(constant_value & rotation_track_bit_mask);
+						num_constant_translations += count_set_bits(constant_value & translation_track_bit_mask);
+					}
+
+					const uint32_t remaining_tracks = sub_track_index % 32;
+					if (remaining_tracks != 0)
+					{
+						const uint32_t not_up_to_track_mask = ((1 << (32 - remaining_tracks)) - 1);
+						const uint32_t default_value = and_not(not_up_to_track_mask, context.default_tracks_bitset[offset]);
+						num_default_rotations += count_set_bits(default_value & rotation_track_bit_mask);
+						num_default_translations += count_set_bits(default_value & translation_track_bit_mask);
+
+						const uint32_t constant_value = and_not(not_up_to_track_mask, context.constant_tracks_bitset[offset]);
+						num_constant_rotations += count_set_bits(constant_value & rotation_track_bit_mask);
+						num_constant_translations += count_set_bits(constant_value & translation_track_bit_mask);
+					}
+				}
+
+				// Tracks that are default are also constant
+				const uint32_t num_animated_rotations = track_index - num_constant_rotations;
+				const uint32_t num_animated_translations = track_index - num_constant_translations;
+
+				const rotation_format8 packed_rotation_format = is_rotation_format_variable(rotation_format) ? get_highest_variant_precision(get_rotation_variant(rotation_format)) : rotation_format;
+				const uint32_t packed_rotation_size = get_packed_rotation_size(packed_rotation_format);
+
+				uint32_t constant_track_data_offset = (num_constant_rotations - num_default_rotations) * packed_rotation_size;
+				constant_track_data_offset += (num_constant_translations - num_default_translations) * get_packed_vector_size(vector_format8::vector3f_full);
+
+				uint32_t clip_range_data_offset = 0;
+				uint32_t segment_range_data_offset = 0;
+
+				const range_reduction_flags8 range_reduction = context.range_reduction;
+				if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::rotations))
+				{
+					clip_range_data_offset += context.num_rotation_components * sizeof(float) * 2 * num_animated_rotations;
+
+					if (transform_header.num_segments > 1)
+						segment_range_data_offset += context.num_rotation_components * k_segment_range_reduction_num_bytes_per_component * 2 * num_animated_rotations;
+				}
+
+				if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::translations))
+				{
+					clip_range_data_offset += k_clip_range_reduction_vector3_range_size * num_animated_translations;
+
+					if (transform_header.num_segments > 1)
+						segment_range_data_offset += 3 * k_segment_range_reduction_num_bytes_per_component * 2 * num_animated_translations;
+				}
+
+				uint32_t num_animated_tracks = num_animated_rotations + num_animated_translations;
+				if (has_scale)
+				{
+					const uint32_t num_animated_scales = track_index - num_constant_scales;
+					num_animated_tracks += num_animated_scales;
+
+					constant_track_data_offset += (num_constant_scales - num_default_scales) * get_packed_vector_size(vector_format8::vector3f_full);
+
+					if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::scales))
+					{
+						clip_range_data_offset += k_clip_range_reduction_vector3_range_size * num_animated_scales;
+
+						if (transform_header.num_segments > 1)
+							segment_range_data_offset += 3 * k_segment_range_reduction_num_bytes_per_component * 2 * num_animated_scales;
+					}
+				}
+
+				sampling_context_.track_index = sub_track_index;
+				sampling_context_.constant_track_data_offset = constant_track_data_offset;
+				sampling_context_.clip_range_data_offset = clip_range_data_offset;
+				sampling_context_.segment_range_data_offset = segment_range_data_offset;
+				sampling_context_.format_per_track_data_offset = num_animated_tracks;
+
+				for (uint32_t animated_track_index = 0; animated_track_index < num_animated_tracks; ++animated_track_index)
+				{
+					const uint8_t bit_rate0 = context.format_per_track_data[0][animated_track_index];
+					const uint32_t num_bits_at_bit_rate0 = get_num_bits_at_bit_rate(bit_rate0) * 3;	// 3 components
+
+					sampling_context_.key_frame_bit_offsets[0] += num_bits_at_bit_rate0;
+
+					const uint8_t bit_rate1 = context.format_per_track_data[1][animated_track_index];
+					const uint32_t num_bits_at_bit_rate1 = get_num_bits_at_bit_rate(bit_rate1) * 3;	// 3 components
+
+					sampling_context_.key_frame_bit_offsets[1] += num_bits_at_bit_rate1;
+				}
+			}
+
+			sampling_context_.vectors[0] = default_translation;	// Init with something to avoid GCC warning
+			sampling_context_.vectors[1] = default_translation;	// Init with something to avoid GCC warning
+
+			const rtm::quatf rotation = decompress_and_interpolate_rotation<decompression_settings_type>(context, transform_header, sampling_context_);
+			writer.write_rotation(track_index, rotation);
+
+			const rtm::vector4f translation = decompress_and_interpolate_vector<translation_adapter>(context, transform_header, default_translation, sampling_context_);
+			writer.write_translation(track_index, translation);
+
+			const rtm::vector4f scale = has_scale ? decompress_and_interpolate_vector<scale_adapter>(context, transform_header, default_scale, sampling_context_) : default_scale;
+			writer.write_scale(track_index, scale);
+
+			if (decompression_settings_type::disable_fp_exeptions())
+				restore_fp_exceptions(fp_env);
+		}
+	}
+}
+
+ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/decompression/impl/universal_track_decompression.h b/includes/acl/decompression/impl/universal_track_decompression.h
new file mode 100644
--- /dev/null
+++ b/includes/acl/decompression/impl/universal_track_decompression.h
@@ -0,0 +1,173 @@
+#pragma once
+
+////////////////////////////////////////////////////////////////////////////////
+// The MIT License (MIT)
+//
+// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "acl/core/compressed_tracks.h"
+#include "acl/core/interpolation_utils.h"
+#include "acl/core/impl/compiler_utils.h"
+#include "acl/decompression/impl/scalar_track_decompression.h"
+#include "acl/decompression/impl/transform_track_decompression.h"
+
+#include <cstdint>
+
+ACL_IMPL_FILE_PRAGMA_PUSH
+
+namespace acl
+{
+	namespace acl_impl
+	{
+		union persistent_universal_decompression_context
+		{
+			persistent_universal_decompression_context() {}
+
+			persistent_scalar_decompression_context scalar;
+			persistent_transform_decompression_context transform;
+
+			//////////////////////////////////////////////////////////////////////////
+
+			inline const compressed_tracks* get_compressed_tracks() const { return scalar.tracks; }
+			inline bool is_initialized() const { return scalar.is_initialized(); }
+			inline void reset() { scalar.tracks = nullptr; }
+		};
+
+		template<class decompression_settings_type>
+		inline void initialize(persistent_universal_decompression_context& context, const compressed_tracks& tracks)
+		{
+			const track_type8 track_type = tracks.get_track_type();
+			switch (track_type)
+			{
+			case track_type8::float1f:
+			case track_type8::float2f:
+			case track_type8::float3f:
+			case track_type8::float4f:
+			case track_type8::vector4f:
+				initialize<decompression_settings_type>(context.scalar, tracks);
+				break;
+			case track_type8::qvvf:
+				initialize<decompression_settings_type>(context.transform, tracks);
+				break;
+			default:
+				ACL_ASSERT(false, "Invalid track type");
+				break;
+			}
+		}
+
+		inline bool is_dirty(const persistent_universal_decompression_context& context, const compressed_tracks& tracks)
+		{
+			if (!context.is_initialized())
+				return true;	// Always dirty if we are not initialized
+
+			const track_type8 track_type = context.scalar.tracks->get_track_type();
+			switch (track_type)
+			{
+			case track_type8::float1f:
+			case track_type8::float2f:
+			case track_type8::float3f:
+			case track_type8::float4f:
+			case track_type8::vector4f:
+				return is_dirty(context.scalar, tracks);
+			case track_type8::qvvf:
+				return is_dirty(context.transform, tracks);
+			default:
+				ACL_ASSERT(false, "Invalid track type");
+				return true;
+			}
+		}
+
+		template<class decompression_settings_type>
+		inline void seek(persistent_universal_decompression_context& context, float sample_time, sample_rounding_policy rounding_policy)
+		{
+			ACL_ASSERT(context.is_initialized(), "Context is not initialized");
+
+			const track_type8 track_type = context.scalar.tracks->get_track_type();
+			switch (track_type)
+			{
+			case track_type8::float1f:
+			case track_type8::float2f:
+			case track_type8::float3f:
+			case track_type8::float4f:
+			case track_type8::vector4f:
+				seek<decompression_settings_type>(context.scalar, sample_time, rounding_policy);
+				break;
+			case track_type8::qvvf:
+				seek<decompression_settings_type>(context.transform, sample_time, rounding_policy);
+				break;
+			default:
+				ACL_ASSERT(false, "Invalid track type");
+				break;
+			}
+		}
+
+		template<class decompression_settings_type, class track_writer_type>
+		inline void decompress_tracks(persistent_universal_decompression_context& context, track_writer_type& writer)
+		{
+			ACL_ASSERT(context.is_initialized(), "Context is not initialized");
+
+			const track_type8 track_type = context.scalar.tracks->get_track_type();
+			switch (track_type)
+			{
+			case track_type8::float1f:
+			case track_type8::float2f:
+			case track_type8::float3f:
+			case track_type8::float4f:
+			case track_type8::vector4f:
+				decompress_tracks<decompression_settings_type>(context.scalar, writer);
+				break;
+			case track_type8::qvvf:
+				decompress_tracks<decompression_settings_type>(context.transform, writer);
+				break;
+			default:
+				ACL_ASSERT(false, "Invalid track type");
+				break;
+			}
+		}
+
+		template<class decompression_settings_type, class track_writer_type>
+		inline void decompress_track(persistent_universal_decompression_context& context, uint32_t track_index, track_writer_type& writer)
+		{
+			ACL_ASSERT(context.is_initialized(), "Context is not initialized");
+
+			const track_type8 track_type = context.scalar.tracks->get_track_type();
+			switch (track_type)
+			{
+			case track_type8::float1f:
+			case track_type8::float2f:
+			case track_type8::float3f:
+			case track_type8::float4f:
+			case track_type8::vector4f:
+				decompress_track<decompression_settings_type>(context.scalar, track_index, writer);
+				break;
+			case track_type8::qvvf:
+				decompress_track<decompression_settings_type>(context.transform, track_index, writer);
+				break;
+			default:
+				ACL_ASSERT(false, "Invalid track type");
+				break;
+			}
+		}
+	}
+}
+
+ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/decompression/output_writer.h b/includes/acl/decompression/output_writer.h
deleted file mode 100644
--- a/includes/acl/decompression/output_writer.h
+++ /dev/null
@@ -1,85 +0,0 @@
-#pragma once
-
-////////////////////////////////////////////////////////////////////////////////
-// The MIT License (MIT)
-//
-// Copyright (c) 2017 Nicholas Frechette & Animation Compression Library contributors
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-////////////////////////////////////////////////////////////////////////////////
-
-#include "acl/core/impl/compiler_utils.h"
-
-#include <rtm/quatf.h>
-#include <rtm/vector4f.h>
-
-#include <cstdint>
-
-ACL_IMPL_FILE_PRAGMA_PUSH
-
-namespace acl
-{
-	//////////////////////////////////////////////////////////////////////////
-	// We use a struct like this to allow an arbitrary format on the end user side.
-	// Since our decode function is templated on this type implemented by the user,
-	// the callbacks can trivially be inlined and customized.
-	//////////////////////////////////////////////////////////////////////////
-	struct OutputWriter
-	{
-		//////////////////////////////////////////////////////////////////////////
-		// These allow the caller of decompress_pose to control which track types they are interested in.
-		// This information allows the codecs to avoid unpacking values that are not needed.
-		constexpr bool skip_all_bone_rotations() const { return false; }
-		constexpr bool skip_all_bone_translations() const { return false; }
-		constexpr bool skip_all_bone_scales() const { return false; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// These allow the caller of decompress_pose to control which tracks they are interested in.
-		// This information allows the codecs to avoid unpacking values that are not needed.
-		constexpr bool skip_bone_rotation(uint16_t bone_index) const { return (void)bone_index, false; }
-		constexpr bool skip_bone_translation(uint16_t bone_index) const { return (void)bone_index, false; }
-		constexpr bool skip_bone_scale(uint16_t bone_index) const { return (void)bone_index, false; }
-
-		//////////////////////////////////////////////////////////////////////////
-		// Called by the decoder to write out a quaternion rotation value for a specified bone index.
-		void RTM_SIMD_CALL write_bone_rotation(uint16_t bone_index, rtm::quatf_arg0 rotation)
-		{
-			(void)bone_index;
-			(void)rotation;
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Called by the decoder to write out a translation value for a specified bone index.
-		void RTM_SIMD_CALL write_bone_translation(uint16_t bone_index, rtm::vector4f_arg0 translation)
-		{
-			(void)bone_index;
-			(void)translation;
-		}
-
-		//////////////////////////////////////////////////////////////////////////
-		// Called by the decoder to write out a scale value for a specified bone index.
-		void RTM_SIMD_CALL write_bone_scale(uint16_t bone_index, rtm::vector4f_arg0 scale)
-		{
-			(void)bone_index;
-			(void)scale;
-		}
-	};
-}
-
-ACL_IMPL_FILE_PRAGMA_POP
diff --git a/includes/acl/io/clip_reader.h b/includes/acl/io/clip_reader.h
--- a/includes/acl/io/clip_reader.h
+++ b/includes/acl/io/clip_reader.h
@@ -27,18 +27,21 @@
 #if defined(SJSON_CPP_PARSER)
 
 #include "acl/io/clip_reader_error.h"
-#include "acl/compression/animation_clip.h"
 #include "acl/compression/compression_settings.h"
 #include "acl/compression/track_array.h"
-#include "acl/compression/skeleton.h"
 #include "acl/core/algorithm_types.h"
 #include "acl/core/impl/compiler_utils.h"
 #include "acl/core/iallocator.h"
 #include "acl/core/string.h"
 #include "acl/core/unique_ptr.h"
+#include "acl/math/quatf.h"
 
 #include <rtm/quatd.h>
+#include <rtm/quatf.h>
 #include <rtm/vector4d.h>
+#include <rtm/vector4f.h>
+#include <rtm/qvvd.h>
+#include <rtm/qvvf.h>
 
 #include <cstdint>
 
@@ -56,30 +59,38 @@ namespace acl
 	};
 
 	//////////////////////////////////////////////////////////////////////////
-	// A raw clip with transform tracks
+	// Raw transform tracks
 	struct sjson_raw_clip
 	{
-		std::unique_ptr<AnimationClip, Deleter<AnimationClip>> clip;
-		std::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>> skeleton;
+		track_array_qvvf track_list;
+
+		track_array_qvvf additive_base_track_list;
+		additive_clip_format8 additive_format;
+
+		track_qvvf bind_pose;
 
 		bool has_settings;
-		algorithm_type8 algorithm_type;
-		CompressionSettings settings;
+		compression_settings settings;
 	};
 
 	//////////////////////////////////////////////////////////////////////////
-	// A raw track list
+	// Raw scalar tracks
 	struct sjson_raw_track_list
 	{
 		track_array track_list;
+
+		track_qvvf bind_pose;
+
+		bool has_settings;
+		compression_settings settings;
 	};
 
 	//////////////////////////////////////////////////////////////////////////
 	// An SJSON ACL file reader.
-	class ClipReader
+	class clip_reader
 	{
 	public:
-		ClipReader(IAllocator& allocator, const char* sjson_input, size_t input_length)
+		clip_reader(iallocator& allocator, const char* sjson_input, size_t input_length)
 			: m_allocator(allocator)
 			, m_parser(sjson_input, input_length)
 			, m_error()
@@ -87,7 +98,14 @@ namespace acl
 			, m_num_samples(0)
 			, m_sample_rate(0.0F)
 			, m_is_binary_exact(false)
+			, m_bone_names(nullptr)
+			, m_num_bones(0)
+		{
+		}
+
+		~clip_reader()
 		{
+			deallocate_type_array(m_allocator, m_bone_names, m_num_bones);
 		}
 
 		sjson_file_type get_file_type()
@@ -116,17 +134,16 @@ namespace acl
 			if (!read_raw_clip_header())
 				return false;
 
-			if (!read_settings(&out_data.has_settings, &out_data.algorithm_type, &out_data.settings))
+			if (!read_settings(&out_data.has_settings, &out_data.settings))
 				return false;
 
-			if (!create_skeleton(out_data.skeleton))
+			if (!create_skeleton(out_data.track_list, out_data.bind_pose))
 				return false;
 
-			if (!create_clip(out_data.clip, *out_data.skeleton))
+			if (!read_tracks(out_data.track_list, out_data.additive_base_track_list))
 				return false;
 
-			if (!read_tracks(*out_data.clip, *out_data.skeleton))
-				return false;
+			out_data.additive_format = out_data.additive_base_track_list.get_num_tracks() != 0 ? m_additive_format : additive_clip_format8::none;
 
 			return nothing_follows();
 		}
@@ -141,24 +158,21 @@ namespace acl
 			if (!read_raw_track_list_header())
 				return false;
 
-			bool has_settings;				// Not used
-			algorithm_type8 algorithm_type;	// Not used
-			CompressionSettings settings;	// Not used
-			if (!read_settings(&has_settings, &algorithm_type, &settings))
+			if (!read_settings(&out_data.has_settings, &out_data.settings))
 				return false;
 
-			if (!create_track_list(out_data.track_list))
+			if (!create_track_list(out_data.track_list, out_data.bind_pose))
 				return false;
 
 			return nothing_follows();
 		}
 
-		ClipReaderError get_error() const { return m_error; }
+		clip_reader_error get_error() const { return m_error; }
 
 	private:
-		IAllocator& m_allocator;
+		iallocator& m_allocator;
 		sjson::Parser m_parser;
-		ClipReaderError m_error;
+		clip_reader_error m_error;
 
 		uint32_t m_version;
 		uint32_t m_num_samples;
@@ -170,10 +184,19 @@ namespace acl
 		uint32_t m_additive_base_num_samples;
 		float m_additive_base_sample_rate;
 
+		bool m_has_settings;
+		float m_constant_rotation_threshold_angle;
+		float m_constant_translation_threshold;
+		float m_constant_scale_threshold;
+		float m_error_threshold;
+
+		sjson::StringView* m_bone_names;
+		uint32_t m_num_bones;
+
 		void reset_state()
 		{
 			m_parser.reset_state();
-			set_error(ClipReaderError::None);
+			set_error(clip_reader_error::None);
 		}
 
 		bool read_version()
@@ -186,7 +209,7 @@ namespace acl
 
 			if (m_version > 5)
 			{
-				set_error(ClipReaderError::UnsupportedVersion);
+				set_error(clip_reader_error::UnsupportedVersion);
 				return false;
 			}
 
@@ -210,7 +233,7 @@ namespace acl
 			m_num_samples = static_cast<uint32_t>(num_samples);
 			if (static_cast<double>(m_num_samples) != num_samples)
 			{
-				set_error(ClipReaderError::UnsignedIntegerExpected);
+				set_error(clip_reader_error::UnsignedIntegerExpected);
 				return false;
 			}
 
@@ -221,7 +244,7 @@ namespace acl
 			m_sample_rate = static_cast<float>(sample_rate);
 			if (m_sample_rate <= 0.0F)
 			{
-				set_error(ClipReaderError::PositiveValueExpected);
+				set_error(clip_reader_error::PositiveValueExpected);
 				return false;
 			}
 
@@ -237,7 +260,7 @@ namespace acl
 			m_parser.try_read("additive_format", additive_format, "none");
 			if (!get_additive_clip_format(additive_format.c_str(), m_additive_format))
 			{
-				set_error(ClipReaderError::InvalidAdditiveClipFormat);
+				set_error(clip_reader_error::InvalidAdditiveClipFormat);
 				return false;
 			}
 
@@ -246,14 +269,14 @@ namespace acl
 			m_additive_base_num_samples = static_cast<uint32_t>(num_samples);
 			if (static_cast<double>(m_additive_base_num_samples) != num_samples || m_additive_base_num_samples == 0)
 			{
-				set_error(ClipReaderError::UnsignedIntegerExpected);
+				set_error(clip_reader_error::UnsignedIntegerExpected);
 				return false;
 			}
 			m_parser.try_read("additive_base_sample_rate", sample_rate, 30.0);
 			m_additive_base_sample_rate = static_cast<float>(sample_rate);
 			if (m_additive_base_sample_rate <= 0.0F)
 			{
-				set_error(ClipReaderError::PositiveValueExpected);
+				set_error(clip_reader_error::PositiveValueExpected);
 				return false;
 			}
 
@@ -281,7 +304,7 @@ namespace acl
 			m_num_samples = static_cast<uint32_t>(num_samples);
 			if (static_cast<double>(m_num_samples) != num_samples)
 			{
-				set_error(ClipReaderError::UnsignedIntegerExpected);
+				set_error(clip_reader_error::UnsignedIntegerExpected);
 				return false;
 			}
 
@@ -292,7 +315,7 @@ namespace acl
 			m_sample_rate = static_cast<float>(sample_rate);
 			if (m_sample_rate <= 0.0F)
 			{
-				set_error(ClipReaderError::PositiveValueExpected);
+				set_error(clip_reader_error::PositiveValueExpected);
 				return false;
 			}
 
@@ -309,8 +332,10 @@ namespace acl
 			return false;
 		}
 
-		bool read_settings(bool* out_has_settings, algorithm_type8* out_algorithm_type, CompressionSettings* out_settings)
+		bool read_settings(bool* out_has_settings, compression_settings* out_settings)
 		{
+			m_has_settings = false;
+
 			if (!m_parser.try_object_begins("settings"))
 			{
 				if (out_has_settings != nullptr)
@@ -320,7 +345,7 @@ namespace acl
 				return true;
 			}
 
-			CompressionSettings default_settings;
+			compression_settings default_settings;
 
 			sjson::StringView algorithm_name;
 			sjson::StringView compression_level;
@@ -364,10 +389,11 @@ namespace acl
 					goto parsing_error;
 			}
 
-			m_parser.try_read("constant_rotation_threshold_angle", constant_rotation_threshold_angle, double(default_settings.constant_rotation_threshold_angle));
-			m_parser.try_read("constant_translation_threshold", constant_translation_threshold, double(default_settings.constant_translation_threshold));
-			m_parser.try_read("constant_scale_threshold", constant_scale_threshold, double(default_settings.constant_scale_threshold));
-			m_parser.try_read("error_threshold", error_threshold, double(default_settings.error_threshold));
+			// Skip deprecated values
+			m_parser.try_read("constant_rotation_threshold_angle", constant_rotation_threshold_angle, 0.00284714461);
+			m_parser.try_read("constant_translation_threshold", constant_translation_threshold, 0.001);
+			m_parser.try_read("constant_scale_threshold", constant_scale_threshold, 0.00001);
+			m_parser.try_read("error_threshold", error_threshold, 0.01);
 
 			if (!m_parser.is_valid() || !m_parser.object_ends())
 				goto parsing_error;
@@ -376,9 +402,6 @@ namespace acl
 			{
 				*out_has_settings = true;
 
-				if (!get_algorithm_type(algorithm_name.c_str(), *out_algorithm_type))
-					goto invalid_value_error;
-
 				if (!get_compression_level(compression_level.c_str(), out_settings->level))
 					goto invalid_value_error;
 
@@ -394,12 +417,13 @@ namespace acl
 				out_settings->segmenting.ideal_num_samples = uint16_t(segmenting_ideal_num_samples);
 				out_settings->segmenting.max_num_samples = uint16_t(segmenting_max_num_samples);
 
-				out_settings->constant_rotation_threshold_angle = float(constant_rotation_threshold_angle);
-				out_settings->constant_translation_threshold = float(constant_translation_threshold);
-				out_settings->constant_scale_threshold = float(constant_scale_threshold);
-				out_settings->error_threshold = float(error_threshold);
+				m_constant_rotation_threshold_angle = float(constant_rotation_threshold_angle);
+				m_constant_translation_threshold = float(constant_translation_threshold);
+				m_constant_scale_threshold = float(constant_scale_threshold);
+				m_error_threshold = float(error_threshold);
 			}
 
+			m_has_settings = true;
 			return true;
 
 		parsing_error:
@@ -408,42 +432,37 @@ namespace acl
 
 		invalid_value_error:
 			m_parser.get_position(m_error.line, m_error.column);
-			m_error.error = ClipReaderError::InvalidCompressionSetting;
+			m_error.error = clip_reader_error::InvalidCompressionSetting;
 			return false;
 		}
 
-		bool create_skeleton(std::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>>& skeleton)
+		bool create_skeleton(track_array_qvvf& track_list, track_qvvf& bind_pose)
 		{
 			sjson::ParserState before_bones = m_parser.save_state();
 
 			uint16_t num_bones;
-			if (!process_each_bone(nullptr, num_bones))
+			if (!process_each_bone(nullptr, nullptr, num_bones))
 				return false;
 
 			m_parser.restore_state(before_bones);
 
-			RigidBone* bones = allocate_type_array<RigidBone>(m_allocator, num_bones);
+			m_num_bones = num_bones;
+			m_bone_names = allocate_type_array<sjson::StringView>(m_allocator, num_bones);
+
+			track_list = track_array_qvvf(m_allocator, num_bones);
+			bind_pose = track_qvvf::make_reserve(track_desc_transformf{}, m_allocator, num_bones, 30.0F);	// 1 sample per track
+
 			const uint16_t num_allocated_bones = num_bones;
 
-			if (!process_each_bone(bones, num_bones))
-			{
-				deallocate_type_array(m_allocator, bones, num_allocated_bones);
+			if (!process_each_bone(&track_list, &bind_pose, num_bones))
 				return false;
-			}
 
+			(void)num_allocated_bones;
 			ACL_ASSERT(num_bones == num_allocated_bones, "Number of bones read mismatch");
-			skeleton = make_unique<RigidSkeleton>(m_allocator, m_allocator, bones, num_bones);
-			deallocate_type_array(m_allocator, bones, num_allocated_bones);
 
 			return true;
 		}
 
-		bool read_skeleton()
-		{
-			uint16_t num_bones;
-			return process_each_bone(nullptr, num_bones);
-		}
-
 		static double hex_to_double(const sjson::StringView& value)
 		{
 			union UInt64ToDouble
@@ -497,19 +516,16 @@ namespace acl
 			return result;
 		}
 
-		bool process_each_bone(RigidBone* bones, uint16_t& num_bones)
+		bool process_each_bone(track_array_qvvf* tracks, track_qvvf* bind_pose, uint16_t& num_bones)
 		{
-			bool counting = bones == nullptr;
+			bool counting = tracks == nullptr;
 			num_bones = 0;
 
 			if (!m_parser.array_begins("bones"))
 				goto error;
 
-			for (uint16_t i = 0; !m_parser.try_array_ends(); ++i)
+			for (uint32_t i = 0; !m_parser.try_array_ends(); ++i)
 			{
-				RigidBone dummy;
-				RigidBone& bone = counting ? dummy : bones[i];
-
 				if (!m_parser.object_begins())
 					goto error;
 
@@ -517,61 +533,79 @@ namespace acl
 				if (!m_parser.read("name", name))
 					goto error;
 
-				if (!counting)
-					bone.name = String(m_allocator, name.c_str(), name.size());
+				if (tracks != nullptr)
+					m_bone_names[i] = name;
 
 				sjson::StringView parent;
 				if (!m_parser.read("parent", parent))
 					goto error;
 
-				if (!counting)
+				uint32_t parent_index = k_invalid_track_index;
+				if (tracks != nullptr && parent.size() != 0)
 				{
-					if (parent.size() == 0)
-					{
-						// This is the root bone.
-						bone.parent_index = k_invalid_bone_index;
-					}
-					else
+					parent_index = find_bone(parent);
+					if (parent_index == k_invalid_track_index)
 					{
-						bone.parent_index = find_bone(bones, num_bones, parent);
-						if (bone.parent_index == k_invalid_bone_index)
-						{
-							set_error(ClipReaderError::NoParentBoneWithThatName);
-							return false;
-						}
+						set_error(clip_reader_error::NoParentBoneWithThatName);
+						return false;
 					}
 				}
 
-				if (!m_parser.read("vertex_distance", bone.vertex_distance))
+				float vertex_distance;
+				if (!m_parser.read("vertex_distance", vertex_distance))
 					goto error;
 
+				rtm::qvvd bind_transform = rtm::qvv_identity();
 				if (m_is_binary_exact)
 				{
 					sjson::StringView rotation[4];
 					if (m_parser.try_read("bind_rotation", rotation, 4, nullptr) && !counting)
-						bone.bind_transform.rotation = hex_to_quat(rotation);
+						bind_transform.rotation = hex_to_quat(rotation);
 
 					sjson::StringView translation[3];
 					if (m_parser.try_read("bind_translation", translation, 3, nullptr) && !counting)
-						bone.bind_transform.translation = hex_to_vector3(translation);
+						bind_transform.translation = hex_to_vector3(translation);
 
 					sjson::StringView scale[3];
 					if (m_parser.try_read("bind_scale", scale, 3, nullptr) && !counting)
-						bone.bind_transform.scale = hex_to_vector3(scale);
+						bind_transform.scale = hex_to_vector3(scale);
 				}
 				else
 				{
 					double rotation[4] = { 0.0, 0.0, 0.0, 0.0 };
 					if (m_parser.try_read("bind_rotation", rotation, 4, 0.0) && !counting)
-						bone.bind_transform.rotation = rtm::quat_load(&rotation[0]);
+						bind_transform.rotation = rtm::quat_load(&rotation[0]);
 
 					double translation[3] = { 0.0, 0.0, 0.0 };
 					if (m_parser.try_read("bind_translation", translation, 3, 0.0) && !counting)
-						bone.bind_transform.translation = rtm::vector_load3(&translation[0]);
+						bind_transform.translation = rtm::vector_load3(&translation[0]);
 
 					double scale[3] = { 0.0, 0.0, 0.0 };
 					if (m_parser.try_read("bind_scale", scale, 3, 0.0) && !counting)
-						bone.bind_transform.scale = rtm::vector_load3(&scale[0]);
+						bind_transform.scale = rtm::vector_load3(&scale[0]);
+				}
+
+				if (tracks != nullptr)
+				{
+					track_desc_transformf desc;
+					desc.parent_index = parent_index;
+					desc.shell_distance = vertex_distance;
+
+					if (m_has_settings)
+					{
+						desc.precision = m_error_threshold;
+						desc.constant_rotation_threshold_angle = m_constant_rotation_threshold_angle;
+						desc.constant_translation_threshold = m_constant_translation_threshold;
+						desc.constant_scale_threshold = m_constant_scale_threshold;
+					}
+
+					// Create a dummy track for now to hold our arguments
+					(*tracks)[i] = track_qvvf::make_ref(desc, nullptr, 0, 30.0F);
+
+					rtm::qvvf bind_transform_ = rtm::qvv_cast(bind_transform);
+					bind_transform_.rotation = rtm::quat_normalize(bind_transform_.rotation);
+
+					(*bind_pose)[i] = bind_transform_;
 				}
 
 				if (!m_parser.object_ends())
@@ -587,24 +621,133 @@ namespace acl
 			return false;
 		}
 
-		uint16_t find_bone(const RigidBone* bones, uint16_t num_bones, const sjson::StringView& name) const
+		uint32_t find_bone(const sjson::StringView& name) const
 		{
-			for (uint16_t i = 0; i < num_bones; ++i)
+			for (uint32_t i = 0; i < m_num_bones; ++i)
 			{
-				if (name == bones[i].name.c_str())
+				if (name == m_bone_names[i])
 					return i;
 			}
 
-			return k_invalid_bone_index;
+			return k_invalid_track_index;
+		}
+
+		float read_optional_float(const char* key, float default_value)
+		{
+			float value = default_value;
+			if (m_is_binary_exact)
+			{
+				sjson::StringView value_str;
+				if (m_parser.try_read(key, value_str, ""))
+					value = hex_to_float(value_str);
+			}
+			else
+				m_parser.try_read(key, value, default_value);
+
+			return value;
 		}
 
-		bool create_clip(std::unique_ptr<AnimationClip, Deleter<AnimationClip>>& clip, const RigidSkeleton& skeleton)
+		bool read_qvv_sample(rtm::qvvf& out_sample)
 		{
-			clip = make_unique<AnimationClip>(m_allocator, m_allocator, skeleton, m_num_samples, m_sample_rate, String(m_allocator, m_clip_name.c_str(), m_clip_name.size()));
+			rtm::qvvf sample = rtm::qvv_identity();
+			sjson::StringView values_str[4];
+			double values[4] = { 0.0, 0.0, 0.0, 0.0 };
+
+			{
+				if (!m_parser.array_begins())
+					return false;
+
+				rtm::float4f value;
+				if (m_is_binary_exact)
+				{
+					if (!m_parser.read(values_str, 4))
+						return false;
+
+					value = hex_to_float4f(values_str, 4);
+				}
+				else
+				{
+					if (!m_parser.read(values, 4))
+						return false;
+
+					value = { static_cast<float>(values[0]), static_cast<float>(values[1]), static_cast<float>(values[2]), static_cast<float>(values[3])};
+				}
+
+				sample.rotation = quat_load(&value);
+				if (!rtm::quat_is_finite(sample.rotation))
+					return false;
+
+				if (!m_parser.array_ends())
+					return false;
+			}
+
+			if (!m_parser.read_comma())
+				return false;
+
+			{
+				if (!m_parser.array_begins())
+					return false;
+
+				rtm::float4f value;
+				if (m_is_binary_exact)
+				{
+					if (!m_parser.read(values_str, 3))
+						return false;
+
+					value = hex_to_float4f(values_str, 4);
+				}
+				else
+				{
+					if (!m_parser.read(values, 3))
+						return false;
+
+					value = { static_cast<float>(values[0]), static_cast<float>(values[1]), static_cast<float>(values[2]), 0.0F };
+				}
+
+				sample.translation = vector_load(&value);
+				if (!rtm::vector_is_finite3(sample.translation))
+					return false;
+
+				if (!m_parser.array_ends())
+					return false;
+			}
+
+			if (!m_parser.read_comma())
+				return false;
+
+			{
+				if (!m_parser.array_begins())
+					return false;
+
+				rtm::float4f value;
+				if (m_is_binary_exact)
+				{
+					if (!m_parser.read(values_str, 3))
+						return false;
+
+					value = hex_to_float4f(values_str, 4);
+				}
+				else
+				{
+					if (!m_parser.read(values, 3))
+						return false;
+
+					value = { static_cast<float>(values[0]), static_cast<float>(values[1]), static_cast<float>(values[2]), 0.0F };
+				}
+
+				sample.scale = vector_load(&value);
+				if (!rtm::vector_is_finite3(sample.scale))
+					return false;
+
+				if (!m_parser.array_ends())
+					return false;
+			}
+
+			out_sample = sample;
 			return true;
 		}
 
-		bool process_track_list(track* tracks, uint32_t& num_tracks)
+		bool process_track_list(track* tracks, track_qvvf* bind_pose, uint32_t& num_tracks)
 		{
 			const bool counting = tracks == nullptr;
 			track dummy;
@@ -634,7 +777,7 @@ namespace acl
 				track_type8 track_type;
 				if (!get_track_type(type.c_str(), track_type))
 				{
-					m_error.error = ClipReaderError::InvalidTrackType;
+					m_error.error = clip_reader_error::InvalidTrackType;
 					return false;
 				}
 
@@ -642,26 +785,80 @@ namespace acl
 					track_list_type = track_type;
 				else if (track_type != track_list_type)
 				{
-					m_error.error = ClipReaderError::InvalidTrackType;
+					m_error.error = clip_reader_error::InvalidTrackType;
 					return false;
 				}
 
 				const uint32_t num_components = get_track_num_sample_elements(track_type);
-				ACL_ASSERT(num_components > 0 && num_components <= 4, "Cannot have 0 or more than 4 components");
+				ACL_ASSERT((num_components > 0 && num_components <= 4) || num_components == 12, "Must have between 1 and 4 components or 12");
 
-				float precision;
-				m_parser.try_read("precision", precision, 0.0001F);
+				track_desc_scalarf scalar_desc;
+				track_desc_transformf transform_desc;
+
+				float precision = read_optional_float("precision", -1.0F);
 
 				// Deprecated, no longer used
-				float constant_threshold;
-				m_parser.try_read("constant_threshold", constant_threshold, 0.00001F);
+				read_optional_float("constant_threshold", -1.0F);
 
 				uint32_t output_index;
 				m_parser.try_read("output_index", output_index, i);
 
-				track_desc_scalarf scalar_desc;
+				m_parser.try_read("parent_index", transform_desc.parent_index, k_invalid_track_index);
+
+				transform_desc.shell_distance = read_optional_float("shell_distance", transform_desc.shell_distance);
+				transform_desc.constant_rotation_threshold_angle = read_optional_float("constant_rotation_threshold_angle", transform_desc.constant_rotation_threshold_angle);
+				transform_desc.constant_translation_threshold = read_optional_float("constant_translation_threshold", transform_desc.constant_translation_threshold);
+				transform_desc.constant_scale_threshold = read_optional_float("constant_scale_threshold", transform_desc.constant_scale_threshold);
+
 				scalar_desc.output_index = output_index;
-				scalar_desc.precision = precision;
+				transform_desc.output_index = output_index;
+
+				if (track_type == track_type8::qvvf)
+					transform_desc.precision = precision < 0.0F ? transform_desc.precision : precision;
+				else
+					scalar_desc.precision = precision < 0.0F ? scalar_desc.precision : precision;
+
+				rtm::qvvf bind_transform = rtm::qvv_identity();
+				if (m_is_binary_exact)
+				{
+					sjson::StringView rotation[4];
+					if (m_parser.try_read("bind_rotation", rotation, 4, nullptr) && !counting)
+					{
+						const rtm::float4f value = hex_to_float4f(rotation, 4);
+						bind_transform.rotation = quat_load(&value);
+					}
+
+					sjson::StringView translation[3];
+					if (m_parser.try_read("bind_translation", translation, 3, nullptr) && !counting)
+					{
+						const rtm::float4f value = hex_to_float4f(translation, 3);
+						bind_transform.translation = rtm::vector_load(&value);
+					}
+
+					sjson::StringView scale[3];
+					if (m_parser.try_read("bind_scale", scale, 3, nullptr) && !counting)
+					{
+						const rtm::float4f value = hex_to_float4f(scale, 3);
+						bind_transform.scale = rtm::vector_load(&value);
+					}
+				}
+				else
+				{
+					double rotation[4] = { 0.0, 0.0, 0.0, 0.0 };
+					if (m_parser.try_read("bind_rotation", rotation, 4, 0.0) && !counting)
+						bind_transform.rotation = rtm::quat_normalize(rtm::quat_cast(rtm::quat_load(&rotation[0])));
+
+					double translation[3] = { 0.0, 0.0, 0.0 };
+					if (m_parser.try_read("bind_translation", translation, 3, 0.0) && !counting)
+						bind_transform.translation = rtm::vector_cast(rtm::vector_load3(&translation[0]));
+
+					double scale[3] = { 0.0, 0.0, 0.0 };
+					if (m_parser.try_read("bind_scale", scale, 3, 0.0) && !counting)
+						bind_transform.scale = rtm::vector_cast(rtm::vector_load3(&scale[0]));
+				}
+
+				if (bind_pose != nullptr)
+					(*bind_pose)[i] = bind_transform;
 
 				if (!m_parser.array_begins("data"))
 					goto error;
@@ -674,6 +871,7 @@ namespace acl
 					rtm::float3f*	float3f;
 					rtm::float4f*	float4f;
 					rtm::vector4f*	vector4f;
+					rtm::qvvf*		qvvf;
 				};
 				track_samples_ptr_union track_samples_typed = { nullptr };
 
@@ -694,6 +892,9 @@ namespace acl
 				case track_type8::vector4f:
 					track_samples_typed.vector4f = allocate_type_array<rtm::vector4f>(m_allocator, m_num_samples);
 					break;
+				case track_type8::qvvf:
+					track_samples_typed.qvvf = allocate_type_array<rtm::qvvf>(m_allocator, m_num_samples);
+					break;
 				default:
 					ACL_ASSERT(false, "Unsupported track type");
 					break;
@@ -710,59 +911,77 @@ namespace acl
 
 					if (m_is_binary_exact)
 					{
-						sjson::StringView values[4];
-						if (m_parser.read(values, num_components))
+						switch (track_type)
 						{
-							switch (track_type)
-							{
-							case track_type8::float1f:
-							case track_type8::float2f:
-							case track_type8::float3f:
-							case track_type8::float4f:
-							case track_type8::vector4f:
+						case track_type8::float1f:
+						case track_type8::float2f:
+						case track_type8::float3f:
+						case track_type8::float4f:
+						case track_type8::vector4f:
+						{
+							sjson::StringView values[4];
+							if (m_parser.read(values, num_components))
 							{
 								const rtm::float4f value = hex_to_float4f(values, num_components);
 								std::memcpy(track_samples_typed.float1f + (sample_index * num_components), &value, sizeof(float) * num_components);
-								break;
-							}
-							default:
-								ACL_ASSERT(false, "Unsupported track type");
-								break;
 							}
+							else
+								has_error = true;
+							break;
 						}
-						else
+						case track_type8::qvvf:
 						{
-							has_error = true;
+							rtm::qvvf sample;
+							if (!read_qvv_sample(sample))
+								has_error = true;
+							else
+								std::memcpy(track_samples_typed.qvvf + sample_index, &sample, sizeof(rtm::qvvf));
 							break;
 						}
+						default:
+							ACL_ASSERT(false, "Unsupported track type");
+							break;
+						}
+
+						if (has_error)
+							break;
 					}
 					else
 					{
-						double values[4] = { 0.0, 0.0, 0.0, 0.0 };
-						if (m_parser.read(values, num_components))
+						switch (track_type)
 						{
-							switch (track_type)
-							{
-							case track_type8::float1f:
-							case track_type8::float2f:
-							case track_type8::float3f:
-							case track_type8::float4f:
-							case track_type8::vector4f:
+						case track_type8::float1f:
+						case track_type8::float2f:
+						case track_type8::float3f:
+						case track_type8::float4f:
+						case track_type8::vector4f:
+						{
+							double values[4] = { 0.0, 0.0, 0.0, 0.0 };
+							if (m_parser.read(values, num_components))
 							{
-								const rtm::float4f value = { static_cast<float>(values[0]), static_cast<float>(values[1]), static_cast<float>(values[2]), static_cast<float>(values[3])};
+								const rtm::float4f value = { static_cast<float>(values[0]), static_cast<float>(values[1]), static_cast<float>(values[2]), static_cast<float>(values[3]) };
 								std::memcpy(track_samples_typed.float1f + (sample_index * num_components), &value, sizeof(float) * num_components);
-								break;
-							}
-							default:
-								ACL_ASSERT(false, "Unsupported track type");
-								break;
 							}
+							else
+								has_error = true;
+							break;
 						}
-						else
+						case track_type8::qvvf:
 						{
-							has_error = true;
+							rtm::qvvf sample;
+							if (!read_qvv_sample(sample))
+								has_error = true;
+							else
+								std::memcpy(track_samples_typed.qvvf + sample_index, &sample, sizeof(rtm::qvvf));
 							break;
 						}
+						default:
+							ACL_ASSERT(false, "Unsupported track type");
+							break;
+						}
+
+						if (has_error)
+							break;
 					}
 
 					if (!has_error && !m_parser.array_ends())
@@ -803,6 +1022,9 @@ namespace acl
 					case track_type8::vector4f:
 						deallocate_type_array<rtm::vector4f>(m_allocator, track_samples_typed.vector4f, m_num_samples);
 						break;
+					case track_type8::qvvf:
+						deallocate_type_array<rtm::qvvf>(m_allocator, track_samples_typed.qvvf, m_num_samples);
+						break;
 					default:
 						ACL_ASSERT(false, "Unsupported track type");
 						break;
@@ -827,6 +1049,9 @@ namespace acl
 					case track_type8::vector4f:
 						track_ = track_vector4f::make_owner(scalar_desc, m_allocator, track_samples_typed.vector4f, m_num_samples, m_sample_rate);
 						break;
+					case track_type8::qvvf:
+						track_ = track_qvvf::make_owner(transform_desc, m_allocator, track_samples_typed.qvvf, m_num_samples, m_sample_rate);
+						break;
 					default:
 						ACL_ASSERT(false, "Unsupported track type");
 						break;
@@ -843,19 +1068,20 @@ namespace acl
 			return false;
 		}
 
-		bool create_track_list(track_array& track_list)
+		bool create_track_list(track_array& track_list, track_qvvf& bind_pose)
 		{
 			const sjson::ParserState before_tracks = m_parser.save_state();
 
 			uint32_t num_tracks;
-			if (!process_track_list(nullptr, num_tracks))
+			if (!process_track_list(nullptr, nullptr, num_tracks))
 				return false;
 
 			m_parser.restore_state(before_tracks);
 
 			track_list = track_array(m_allocator, num_tracks);
+			bind_pose = track_qvvf::make_reserve(track_desc_transformf{}, m_allocator, num_tracks, 30.0F);	// 1 sample per track
 
-			if (!process_track_list(track_list.begin(), num_tracks))
+			if (!process_track_list(track_list.begin(), &bind_pose, num_tracks))
 				return false;
 
 			ACL_ASSERT(num_tracks == track_list.get_num_tracks(), "Number of tracks read mismatch");
@@ -863,13 +1089,16 @@ namespace acl
 			return true;
 		}
 
-		bool read_tracks(AnimationClip& clip, const RigidSkeleton& skeleton)
+		bool read_tracks(track_array_qvvf& track_list, track_array_qvvf& additive_base_track_list)
 		{
-			std::unique_ptr<AnimationClip, Deleter<AnimationClip>> base_clip;
+			const uint32_t num_transforms = track_list.get_num_tracks();
 
 			if (m_parser.try_array_begins("base_tracks"))
 			{
-				base_clip = make_unique<AnimationClip>(m_allocator, m_allocator, skeleton, m_additive_base_num_samples, m_additive_base_sample_rate, String(m_allocator, m_additive_base_name.c_str(), m_additive_base_name.size()));
+				// Copy our metadata from the actual clip
+				additive_base_track_list = track_array_qvvf(m_allocator, num_transforms);
+				for (uint32_t transform_index = 0; transform_index < num_transforms; ++transform_index)
+					additive_base_track_list[transform_index].get_description() = track_list[transform_index].get_description();
 
 				while (!m_parser.try_array_ends())
 				{
@@ -880,48 +1109,53 @@ namespace acl
 					if (!m_parser.read("name", name))
 						goto error;
 
-					const uint16_t bone_index = find_bone(skeleton.get_bones(), skeleton.get_num_bones(), name);
-					if (bone_index == k_invalid_bone_index)
+					const uint32_t bone_index = find_bone(name);
+					if (bone_index == k_invalid_track_index)
 					{
-						set_error(ClipReaderError::NoBoneWithThatName);
+						set_error(clip_reader_error::NoBoneWithThatName);
 						return false;
 					}
 
-					AnimatedBone& bone = base_clip->get_animated_bone(bone_index);
+					track_desc_transformf desc = additive_base_track_list[bone_index].get_description();	// Copy our description
+					desc.output_index = bone_index;
+
+					track_qvvf track = track_qvvf::make_reserve(desc, m_allocator, m_additive_base_num_samples, m_additive_base_sample_rate);
 
 					if (m_parser.try_array_begins("rotations"))
 					{
-						if (!read_track_rotations(bone, m_additive_base_num_samples) || !m_parser.array_ends())
+						if (!read_track_rotations(track, m_additive_base_num_samples) || !m_parser.array_ends())
 							goto error;
 					}
 					else
 					{
 						for (uint32_t sample_index = 0; sample_index < m_additive_base_num_samples; ++sample_index)
-							bone.rotation_track.set_sample(sample_index, rtm::quat_identity());
+							track[sample_index].rotation = rtm::quat_identity();
 					}
 
 					if (m_parser.try_array_begins("translations"))
 					{
-						if (!read_track_translations(bone, m_additive_base_num_samples) || !m_parser.array_ends())
+						if (!read_track_translations(track, m_additive_base_num_samples) || !m_parser.array_ends())
 							goto error;
 					}
 					else
 					{
 						for (uint32_t sample_index = 0; sample_index < m_additive_base_num_samples; ++sample_index)
-							bone.translation_track.set_sample(sample_index, rtm::vector_zero());
+							track[sample_index].translation = rtm::vector_zero();
 					}
 
 					if (m_parser.try_array_begins("scales"))
 					{
-						if (!read_track_scales(bone, m_additive_base_num_samples) || !m_parser.array_ends())
+						if (!read_track_scales(track, m_additive_base_num_samples) || !m_parser.array_ends())
 							goto error;
 					}
 					else
 					{
 						for (uint32_t sample_index = 0; sample_index < m_additive_base_num_samples; ++sample_index)
-							bone.scale_track.set_sample(sample_index, rtm::vector_set(1.0));
+							track[sample_index].scale = rtm::vector_set(1.0F);
 					}
 
+					additive_base_track_list[bone_index] = std::move(track);
+
 					if (!m_parser.object_ends())
 						goto error;
 				}
@@ -939,54 +1173,57 @@ namespace acl
 				if (!m_parser.read("name", name))
 					goto error;
 
-				const uint16_t bone_index = find_bone(skeleton.get_bones(), skeleton.get_num_bones(), name);
-				if (bone_index == k_invalid_bone_index)
+				const uint32_t bone_index = find_bone(name);
+				if (bone_index == k_invalid_track_index)
 				{
-					set_error(ClipReaderError::NoBoneWithThatName);
+					set_error(clip_reader_error::NoBoneWithThatName);
 					return false;
 				}
 
-				AnimatedBone& bone = clip.get_animated_bone(bone_index);
+				track_desc_transformf desc = track_list[bone_index].get_description();	// Copy our description
+				desc.output_index = bone_index;
+
+				track_qvvf track = track_qvvf::make_reserve(desc, m_allocator, m_num_samples, m_sample_rate);
 
 				if (m_parser.try_array_begins("rotations"))
 				{
-					if (!read_track_rotations(bone, m_num_samples) || !m_parser.array_ends())
+					if (!read_track_rotations(track, m_num_samples) || !m_parser.array_ends())
 						goto error;
 				}
 				else
 				{
 					for (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index)
-						bone.rotation_track.set_sample(sample_index, rtm::quat_identity());
+						track[sample_index].rotation = rtm::quat_identity();
 				}
 
 				if (m_parser.try_array_begins("translations"))
 				{
-					if (!read_track_translations(bone, m_num_samples) || !m_parser.array_ends())
+					if (!read_track_translations(track, m_num_samples) || !m_parser.array_ends())
 						goto error;
 				}
 				else
 				{
 					for (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index)
-						bone.translation_track.set_sample(sample_index, rtm::vector_zero());
+						track[sample_index].translation = rtm::vector_zero();
 				}
 
 				if (m_parser.try_array_begins("scales"))
 				{
-					if (!read_track_scales(bone, m_num_samples) || !m_parser.array_ends())
+					if (!read_track_scales(track, m_num_samples) || !m_parser.array_ends())
 						goto error;
 				}
 				else
 				{
 					for (uint32_t sample_index = 0; sample_index < m_num_samples; ++sample_index)
-						bone.scale_track.set_sample(sample_index, rtm::vector_set(1.0));
+						track[sample_index].scale = rtm::vector_set(1.0F);
 				}
 
+				track_list[bone_index] = std::move(track);
+
 				if (!m_parser.object_ends())
 					goto error;
 			}
 
-			clip.set_additive_base(base_clip.release(), m_additive_format);
-
 			return true;
 
 		error:
@@ -994,7 +1231,7 @@ namespace acl
 			return false;
 		}
 
-		bool read_track_rotations(AnimatedBone& bone, uint32_t num_samples_expected)
+		bool read_track_rotations(track_qvvf& track, uint32_t num_samples_expected)
 		{
 			for (uint32_t i = 0; i < num_samples_expected; ++i)
 			{
@@ -1023,13 +1260,13 @@ namespace acl
 				if (!m_parser.array_ends())
 					return false;
 
-				bone.rotation_track.set_sample(i, rotation);
+				track[i].rotation = rtm::quat_normalize(rtm::quat_cast(rotation));
 			}
 
 			return true;
 		}
 
-		bool read_track_translations(AnimatedBone& bone, uint32_t num_samples_expected)
+		bool read_track_translations(track_qvvf& track, uint32_t num_samples_expected)
 		{
 			for (uint32_t i = 0; i < num_samples_expected; ++i)
 			{
@@ -1058,13 +1295,13 @@ namespace acl
 				if (!m_parser.array_ends())
 					return false;
 
-				bone.translation_track.set_sample(i, translation);
+				track[i].translation = rtm::vector_cast(translation);
 			}
 
 			return true;
 		}
 
-		bool read_track_scales(AnimatedBone& bone, uint32_t num_samples_expected)
+		bool read_track_scales(track_qvvf& track, uint32_t num_samples_expected)
 		{
 			for (uint32_t i = 0; i < num_samples_expected; ++i)
 			{
@@ -1093,7 +1330,7 @@ namespace acl
 				if (!m_parser.array_ends())
 					return false;
 
-				bone.scale_track.set_sample(i, scale);
+				track[i].scale = rtm::vector_cast(scale);
 			}
 
 			return true;
diff --git a/includes/acl/io/clip_reader_error.h b/includes/acl/io/clip_reader_error.h
--- a/includes/acl/io/clip_reader_error.h
+++ b/includes/acl/io/clip_reader_error.h
@@ -34,7 +34,7 @@ ACL_IMPL_FILE_PRAGMA_PUSH
 
 namespace acl
 {
-	struct ClipReaderError : sjson::ParserError
+	struct clip_reader_error : sjson::ParserError
 	{
 		enum : uint32_t
 		{
@@ -48,11 +48,11 @@ namespace acl
 			InvalidTrackType,
 		};
 
-		ClipReaderError()
+		clip_reader_error()
 		{
 		}
 
-		ClipReaderError(const sjson::ParserError& e)
+		clip_reader_error(const sjson::ParserError& e)
 		{
 			error = e.error;
 			line = e.line;
diff --git a/includes/acl/io/clip_writer.h b/includes/acl/io/clip_writer.h
--- a/includes/acl/io/clip_writer.h
+++ b/includes/acl/io/clip_writer.h
@@ -26,9 +26,7 @@
 
 #if defined(SJSON_CPP_WRITER)
 
-#include "acl/compression/animation_clip.h"
 #include "acl/compression/compression_settings.h"
-#include "acl/compression/skeleton.h"
 #include "acl/compression/track_array.h"
 #include "acl/core/impl/compiler_utils.h"
 #include "acl/core/iallocator.h"
@@ -62,29 +60,37 @@ namespace acl
 			return buffer;
 		};
 
-		inline const char* format_hex_double(double value, char* buffer, size_t buffer_size)
+		inline void write_sjson_settings(const compression_settings& settings, sjson::Writer& writer)
 		{
-			union DoubleToUInt64
+			writer["settings"] = [&](sjson::ObjectWriter& settings_writer)
 			{
-				uint64_t u64;
-				double dbl;
+				settings_writer["level"] = get_compression_level_name(settings.level);
+				settings_writer["rotation_format"] = get_rotation_format_name(settings.rotation_format);
+				settings_writer["translation_format"] = get_vector_format_name(settings.translation_format);
+				settings_writer["scale_format"] = get_vector_format_name(settings.scale_format);
 
-				constexpr explicit DoubleToUInt64(double dbl_value) : dbl(dbl_value) {}
+				settings_writer["segmenting"] = [&](sjson::ObjectWriter& segmenting_writer)
+				{
+					segmenting_writer["ideal_num_samples"] = settings.segmenting.ideal_num_samples;
+					segmenting_writer["max_num_samples"] = settings.segmenting.max_num_samples;
+				};
 			};
+			writer.insert_newline();
+		}
 
-			snprintf(buffer, buffer_size, "%" PRIX64, DoubleToUInt64(value).u64);
-
-			return buffer;
-		};
-
-		inline void write_sjson_clip(const AnimationClip& clip, sjson::Writer& writer)
+		inline void write_sjson_header(const track_array& track_list, sjson::Writer& writer)
 		{
-			writer["clip"] = [&](sjson::ObjectWriter& clip_writer)
+			writer["version"] = 5;
+			writer.insert_newline();
+
+			writer["track_list"] = [&](sjson::ObjectWriter& header_writer)
 			{
-				clip_writer["name"] = clip.get_name().c_str();
-				clip_writer["num_samples"] = clip.get_num_samples();
-				clip_writer["sample_rate"] = clip.get_sample_rate();
-				clip_writer["is_binary_exact"] = true;
+				//header_writer["name"] = track_list.get_name().c_str();
+				header_writer["num_samples"] = track_list.get_num_samples_per_track();
+				header_writer["sample_rate"] = track_list.get_sample_rate();
+				header_writer["is_binary_exact"] = true;
+
+#if 0	// TODO
 				clip_writer["additive_format"] = get_additive_clip_format_name(clip.get_additive_format());
 
 				const AnimationClip* base_clip = clip.get_additive_base();
@@ -94,179 +100,234 @@ namespace acl
 					clip_writer["additive_base_num_samples"] = base_clip->get_num_samples();
 					clip_writer["additive_base_sample_rate"] = base_clip->get_sample_rate();
 				}
+#endif
 			};
 			writer.insert_newline();
 		}
 
-		inline void write_sjson_settings(algorithm_type8 algorithm, const CompressionSettings& settings, sjson::Writer& writer)
+		inline void write_sjson_scalar_desc(const track_desc_scalarf& desc, sjson::ObjectWriter& writer)
 		{
-			writer["settings"] = [&](sjson::ObjectWriter& settings_writer)
-			{
-				settings_writer["algorithm_name"] = get_algorithm_name(algorithm);
-				settings_writer["level"] = get_compression_level_name(settings.level);
-				settings_writer["rotation_format"] = get_rotation_format_name(settings.rotation_format);
-				settings_writer["translation_format"] = get_vector_format_name(settings.translation_format);
-				settings_writer["scale_format"] = get_vector_format_name(settings.scale_format);
-
-				settings_writer["segmenting"] = [&](sjson::ObjectWriter& segmenting_writer)
-				{
-					segmenting_writer["ideal_num_samples"] = settings.segmenting.ideal_num_samples;
-					segmenting_writer["max_num_samples"] = settings.segmenting.max_num_samples;
-				};
+			char buffer[32] = { 0 };
 
-				settings_writer["constant_rotation_threshold_angle"] = settings.constant_rotation_threshold_angle;
-				settings_writer["constant_translation_threshold"] = settings.constant_translation_threshold;
-				settings_writer["constant_scale_threshold"] = settings.constant_scale_threshold;
-				settings_writer["error_threshold"] = settings.error_threshold;
-			};
-			writer.insert_newline();
+			writer["precision"] = format_hex_float(desc.precision, buffer, sizeof(buffer));
+			writer["output_index"] = desc.output_index;
 		}
 
-		inline void write_sjson_bones(const RigidSkeleton& skeleton, sjson::Writer& writer)
+		inline void write_sjson_transform_desc(const track_desc_transformf& desc, sjson::ObjectWriter& writer)
 		{
 			char buffer[32] = { 0 };
 
-			writer["bones"] = [&](sjson::ArrayWriter& bones_writer)
-			{
-				const uint16_t num_bones = skeleton.get_num_bones();
-				if (num_bones > 0)
-					bones_writer.push_newline();
-
-				for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
-				{
-					const RigidBone& bone = skeleton.get_bone(bone_index);
-					const RigidBone& parent_bone = bone.is_root() ? bone : skeleton.get_bone(bone.parent_index);
-
-					bones_writer.push([&](sjson::ObjectWriter& bone_writer)
-					{
-						bone_writer["name"] = bone.name.c_str();
-						bone_writer["parent"] = bone.is_root() ? "" : parent_bone.name.c_str();
-						bone_writer["vertex_distance"] = bone.vertex_distance;
-
-						if (!rtm::quat_near_identity(bone.bind_transform.rotation))
-						{
-							bone_writer["bind_rotation"] = [&](sjson::ArrayWriter& rot_writer)
-							{
-								rot_writer.push(format_hex_double(rtm::quat_get_x(bone.bind_transform.rotation), buffer, sizeof(buffer)));
-								rot_writer.push(format_hex_double(rtm::quat_get_y(bone.bind_transform.rotation), buffer, sizeof(buffer)));
-								rot_writer.push(format_hex_double(rtm::quat_get_z(bone.bind_transform.rotation), buffer, sizeof(buffer)));
-								rot_writer.push(format_hex_double(rtm::quat_get_w(bone.bind_transform.rotation), buffer, sizeof(buffer)));
-							};
-						}
-
-						if (!rtm::vector_all_near_equal3(bone.bind_transform.translation, rtm::vector_zero()))
-						{
-							bone_writer["bind_translation"] = [&](sjson::ArrayWriter& trans_writer)
-							{
-								trans_writer.push(format_hex_double(rtm::vector_get_x(bone.bind_transform.translation), buffer, sizeof(buffer)));
-								trans_writer.push(format_hex_double(rtm::vector_get_y(bone.bind_transform.translation), buffer, sizeof(buffer)));
-								trans_writer.push(format_hex_double(rtm::vector_get_z(bone.bind_transform.translation), buffer, sizeof(buffer)));
-							};
-						}
-
-						if (!rtm::vector_all_near_equal3(bone.bind_transform.scale, rtm::vector_set(1.0)))
-						{
-							bone_writer["bind_scale"] = [&](sjson::ArrayWriter& scale_writer)
-							{
-								scale_writer.push(format_hex_double(rtm::vector_get_x(bone.bind_transform.scale), buffer, sizeof(buffer)));
-								scale_writer.push(format_hex_double(rtm::vector_get_y(bone.bind_transform.scale), buffer, sizeof(buffer)));
-								scale_writer.push(format_hex_double(rtm::vector_get_z(bone.bind_transform.scale), buffer, sizeof(buffer)));
-							};
-						}
-					});
-				}
-			};
-			writer.insert_newline();
+			writer["precision"] = format_hex_float(desc.precision, buffer, sizeof(buffer));
+			writer["output_index"] = desc.output_index;
+			writer["parent_index"] = desc.parent_index;
+			writer["shell_distance"] = format_hex_float(desc.shell_distance, buffer, sizeof(buffer));
+			writer["constant_rotation_threshold_angle"] = format_hex_float(desc.constant_rotation_threshold_angle, buffer, sizeof(buffer));
+			writer["constant_translation_threshold"] = format_hex_float(desc.constant_translation_threshold, buffer, sizeof(buffer));
+			writer["constant_scale_threshold"] = format_hex_float(desc.constant_scale_threshold, buffer, sizeof(buffer));
 		}
 
-		inline void write_sjson_tracks(const RigidSkeleton& skeleton, const AnimationClip& clip, bool is_base_clip, sjson::Writer& writer)
+		inline void write_sjson_tracks(const track_array& track_list, sjson::Writer& writer)
 		{
 			char buffer[32] = { 0 };
 
-			writer[is_base_clip ? "base_tracks" : "tracks"] = [&](sjson::ArrayWriter& tracks_writer)
+			writer["tracks"] = [&](sjson::ArrayWriter& tracks_writer)
 			{
-				const uint16_t num_bones = skeleton.get_num_bones();
-				if (num_bones > 0)
+				const uint32_t num_tracks = track_list.get_num_tracks();
+				if (num_tracks > 0)
 					tracks_writer.push_newline();
 
-				for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
+				for (const track& track_ : track_list)
 				{
-					const RigidBone& rigid_bone = skeleton.get_bone(bone_index);
-					const AnimatedBone& bone = clip.get_animated_bone(bone_index);
-
 					tracks_writer.push([&](sjson::ObjectWriter& track_writer)
-					{
-						track_writer["name"] = rigid_bone.name.c_str();
-						track_writer["rotations"] = [&](sjson::ArrayWriter& rotations_writer)
 						{
-							const uint32_t num_rotation_samples = bone.rotation_track.get_num_samples();
-							if (num_rotation_samples > 0)
-								rotations_writer.push_newline();
+							//track_writer["name"] = track_.get_name().c_str();
+							track_writer["type"] = get_track_type_name(track_.get_type());
 
-							for (uint32_t sample_index = 0; sample_index < num_rotation_samples; ++sample_index)
+							switch (track_.get_type())
+							{
+							case track_type8::float1f:
 							{
-								const rtm::quatd rotation = bone.rotation_track.get_sample(sample_index);
-								rotations_writer.push([&](sjson::ArrayWriter& rot_writer)
+								const track_float1f& track__ = track_cast<track_float1f>(track_);
+								write_sjson_scalar_desc(track__.get_description(), track_writer);
+
+								track_writer["data"] = [&](sjson::ArrayWriter& data_writer)
 								{
-									rot_writer.push(format_hex_double(rtm::quat_get_x(rotation), buffer, sizeof(buffer)));
-									rot_writer.push(format_hex_double(rtm::quat_get_y(rotation), buffer, sizeof(buffer)));
-									rot_writer.push(format_hex_double(rtm::quat_get_z(rotation), buffer, sizeof(buffer)));
-									rot_writer.push(format_hex_double(rtm::quat_get_w(rotation), buffer, sizeof(buffer)));
-								});
-								rotations_writer.push_newline();
+									const uint32_t num_samples = track__.get_num_samples();
+									if (num_samples > 0)
+										data_writer.push_newline();
+
+									for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
+									{
+										data_writer.push([&](sjson::ArrayWriter& sample_writer)
+											{
+												const float sample = track__[sample_index];
+												sample_writer.push(acl_impl::format_hex_float(sample, buffer, sizeof(buffer)));
+											});
+										data_writer.push_newline();
+									}
+								};
+								break;
 							}
-						};
-
-						track_writer["translations"] = [&](sjson::ArrayWriter& translations_writer)
-						{
-							const uint32_t num_translation_samples = bone.translation_track.get_num_samples();
-							if (num_translation_samples > 0)
-								translations_writer.push_newline();
+							case track_type8::float2f:
+							{
+								const track_float2f& track__ = track_cast<track_float2f>(track_);
+								write_sjson_scalar_desc(track__.get_description(), track_writer);
 
-							for (uint32_t sample_index = 0; sample_index < num_translation_samples; ++sample_index)
+								track_writer["data"] = [&](sjson::ArrayWriter& data_writer)
+								{
+									const uint32_t num_samples = track__.get_num_samples();
+									if (num_samples > 0)
+										data_writer.push_newline();
+
+									for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
+									{
+										data_writer.push([&](sjson::ArrayWriter& sample_writer)
+											{
+												const rtm::float2f& sample = track__[sample_index];
+												sample_writer.push(acl_impl::format_hex_float(sample.x, buffer, sizeof(buffer)));
+												sample_writer.push(acl_impl::format_hex_float(sample.y, buffer, sizeof(buffer)));
+											});
+										data_writer.push_newline();
+									}
+								};
+								break;
+							}
+							case track_type8::float3f:
 							{
-								const rtm::vector4d translation = bone.translation_track.get_sample(sample_index);
-								translations_writer.push([&](sjson::ArrayWriter& trans_writer)
+								const track_float3f& track__ = track_cast<track_float3f>(track_);
+								write_sjson_scalar_desc(track__.get_description(), track_writer);
+
+								track_writer["data"] = [&](sjson::ArrayWriter& data_writer)
 								{
-									trans_writer.push(format_hex_double(rtm::vector_get_x(translation), buffer, sizeof(buffer)));
-									trans_writer.push(format_hex_double(rtm::vector_get_y(translation), buffer, sizeof(buffer)));
-									trans_writer.push(format_hex_double(rtm::vector_get_z(translation), buffer, sizeof(buffer)));
-								});
-								translations_writer.push_newline();
+									const uint32_t num_samples = track__.get_num_samples();
+									if (num_samples > 0)
+										data_writer.push_newline();
+
+									for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
+									{
+										data_writer.push([&](sjson::ArrayWriter& sample_writer)
+											{
+												const rtm::float3f& sample = track__[sample_index];
+												sample_writer.push(acl_impl::format_hex_float(sample.x, buffer, sizeof(buffer)));
+												sample_writer.push(acl_impl::format_hex_float(sample.y, buffer, sizeof(buffer)));
+												sample_writer.push(acl_impl::format_hex_float(sample.z, buffer, sizeof(buffer)));
+											});
+										data_writer.push_newline();
+									}
+								};
+								break;
 							}
-						};
+							case track_type8::float4f:
+							{
+								const track_float4f& track__ = track_cast<track_float4f>(track_);
+								write_sjson_scalar_desc(track__.get_description(), track_writer);
 
-						track_writer["scales"] = [&](sjson::ArrayWriter& scales_writer)
-						{
-							const uint32_t num_scale_samples = bone.scale_track.get_num_samples();
-							if (num_scale_samples > 0)
-								scales_writer.push_newline();
+								track_writer["data"] = [&](sjson::ArrayWriter& data_writer)
+								{
+									const uint32_t num_samples = track__.get_num_samples();
+									if (num_samples > 0)
+										data_writer.push_newline();
+
+									for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
+									{
+										data_writer.push([&](sjson::ArrayWriter& sample_writer)
+											{
+												const rtm::float4f& sample = track__[sample_index];
+												sample_writer.push(acl_impl::format_hex_float(sample.x, buffer, sizeof(buffer)));
+												sample_writer.push(acl_impl::format_hex_float(sample.y, buffer, sizeof(buffer)));
+												sample_writer.push(acl_impl::format_hex_float(sample.z, buffer, sizeof(buffer)));
+												sample_writer.push(acl_impl::format_hex_float(sample.w, buffer, sizeof(buffer)));
+											});
+										data_writer.push_newline();
+									}
+								};
+								break;
+							}
+							case track_type8::vector4f:
+							{
+								const track_vector4f& track__ = track_cast<track_vector4f>(track_);
+								write_sjson_scalar_desc(track__.get_description(), track_writer);
 
-							for (uint32_t sample_index = 0; sample_index < num_scale_samples; ++sample_index)
+								track_writer["data"] = [&](sjson::ArrayWriter& data_writer)
+								{
+									const uint32_t num_samples = track__.get_num_samples();
+									if (num_samples > 0)
+										data_writer.push_newline();
+
+									for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
+									{
+										data_writer.push([&](sjson::ArrayWriter& sample_writer)
+											{
+												const rtm::vector4f& sample = track__[sample_index];
+												sample_writer.push(acl_impl::format_hex_float(rtm::vector_get_x(sample), buffer, sizeof(buffer)));
+												sample_writer.push(acl_impl::format_hex_float(rtm::vector_get_y(sample), buffer, sizeof(buffer)));
+												sample_writer.push(acl_impl::format_hex_float(rtm::vector_get_z(sample), buffer, sizeof(buffer)));
+												sample_writer.push(acl_impl::format_hex_float(rtm::vector_get_w(sample), buffer, sizeof(buffer)));
+											});
+										data_writer.push_newline();
+									}
+								};
+								break;
+							}
+							case track_type8::qvvf:
 							{
-								const rtm::vector4d scale = bone.scale_track.get_sample(sample_index);
-								scales_writer.push([&](sjson::ArrayWriter& scale_writer)
+								const track_qvvf& track__ = track_cast<track_qvvf>(track_);
+								write_sjson_transform_desc(track__.get_description(), track_writer);
+
+								track_writer["data"] = [&](sjson::ArrayWriter& data_writer)
 								{
-									scale_writer.push(format_hex_double(rtm::vector_get_x(scale), buffer, sizeof(buffer)));
-									scale_writer.push(format_hex_double(rtm::vector_get_y(scale), buffer, sizeof(buffer)));
-									scale_writer.push(format_hex_double(rtm::vector_get_z(scale), buffer, sizeof(buffer)));
-								});
-								scales_writer.push_newline();
+									const uint32_t num_samples = track__.get_num_samples();
+									if (num_samples > 0)
+										data_writer.push_newline();
+
+									for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
+									{
+										data_writer.push([&](sjson::ArrayWriter& sample_writer)
+											{
+												const rtm::qvvf& sample = track__[sample_index];
+												sample_writer.push([&](sjson::ArrayWriter& rotation_writer)
+													{
+														rotation_writer.push(acl_impl::format_hex_float(rtm::quat_get_x(sample.rotation), buffer, sizeof(buffer)));
+														rotation_writer.push(acl_impl::format_hex_float(rtm::quat_get_y(sample.rotation), buffer, sizeof(buffer)));
+														rotation_writer.push(acl_impl::format_hex_float(rtm::quat_get_z(sample.rotation), buffer, sizeof(buffer)));
+														rotation_writer.push(acl_impl::format_hex_float(rtm::quat_get_w(sample.rotation), buffer, sizeof(buffer)));
+													});
+												sample_writer.push([&](sjson::ArrayWriter& translation_writer)
+													{
+														translation_writer.push(acl_impl::format_hex_float(rtm::vector_get_x(sample.translation), buffer, sizeof(buffer)));
+														translation_writer.push(acl_impl::format_hex_float(rtm::vector_get_y(sample.translation), buffer, sizeof(buffer)));
+														translation_writer.push(acl_impl::format_hex_float(rtm::vector_get_z(sample.translation), buffer, sizeof(buffer)));
+													});
+												sample_writer.push([&](sjson::ArrayWriter& scale_writer)
+													{
+														scale_writer.push(acl_impl::format_hex_float(rtm::vector_get_x(sample.scale), buffer, sizeof(buffer)));
+														scale_writer.push(acl_impl::format_hex_float(rtm::vector_get_y(sample.scale), buffer, sizeof(buffer)));
+														scale_writer.push(acl_impl::format_hex_float(rtm::vector_get_z(sample.scale), buffer, sizeof(buffer)));
+													});
+											});
+										data_writer.push_newline();
+									}
+								};
+								break;
 							}
-						};
-					});
+							default:
+								ACL_ASSERT(false, "Unknown track type");
+								break;
+							}
+						});
 				}
 			};
+			writer.insert_newline();
 		}
 
-		inline const char* write_acl_clip(const RigidSkeleton& skeleton, const AnimationClip& clip, algorithm_type8 algorithm, const CompressionSettings* settings, const char* acl_filename)
+		inline error_result write_track_list(const track_array& track_list, const compression_settings* settings, const char* acl_filename)
 		{
 			if (acl_filename == nullptr)
-				return "'acl_filename' cannot be NULL!";
+				return error_result("'acl_filename' cannot be NULL!");
 
 			const size_t filename_len = std::strlen(acl_filename);
 			if (filename_len < 10 || strncmp(acl_filename + filename_len - 10, ".acl.sjson", 10) != 0)
-				return "'acl_filename' file must be an ACL SJSON file of the form: *.acl.sjson";
+				return error_result("'acl_filename' file must be an ACL SJSON file of the form: *.acl.sjson");
 
 			std::FILE* file = nullptr;
 
@@ -279,248 +340,37 @@ namespace acl
 #endif
 
 			if (file == nullptr)
-				return "Failed to open ACL file for writing";
+				return error_result("Failed to open ACL file for writing");
 
 			sjson::FileStreamWriter stream_writer(file);
 			sjson::Writer writer(stream_writer);
 
-			writer["version"] = 5;
-			writer.insert_newline();
+			write_sjson_header(track_list, writer);
 
-			write_sjson_clip(clip, writer);
 			if (settings != nullptr)
-				write_sjson_settings(algorithm, *settings, writer);
-			write_sjson_bones(skeleton, writer);
+				write_sjson_settings(*settings, writer);
 
-			const AnimationClip* base_clip = clip.get_additive_base();
-			if (base_clip != nullptr)
-				write_sjson_tracks(skeleton, *base_clip, true, writer);
-
-			write_sjson_tracks(skeleton, clip, false, writer);
+			write_sjson_tracks(track_list, writer);
 
 			std::fclose(file);
-			return nullptr;
+			return error_result();
 		}
 	}
 
 	//////////////////////////////////////////////////////////////////////////
-	// Write out an SJSON ACL clip file from a skeleton, a clip,
-	// and no specific compression settings.
-	// Returns an error string on failure, null on success.
-	//////////////////////////////////////////////////////////////////////////
-	inline const char* write_acl_clip(const RigidSkeleton& skeleton, const AnimationClip& clip, const char* acl_filename)
-	{
-		return acl_impl::write_acl_clip(skeleton, clip, algorithm_type8::uniformly_sampled, nullptr, acl_filename);
-	}
-
-	//////////////////////////////////////////////////////////////////////////
-	// Write out an SJSON ACL clip file from a skeleton, a clip,
-	// and compression settings.
-	// Returns an error string on failure, null on success.
+	// Write out an SJSON ACL track list file.
 	//////////////////////////////////////////////////////////////////////////
-	inline const char* write_acl_clip(const RigidSkeleton& skeleton, const AnimationClip& clip, algorithm_type8 algorithm, const CompressionSettings& settings, const char* acl_filename)
+	inline error_result write_track_list(const track_array& track_list, const char* acl_filename)
 	{
-		return acl_impl::write_acl_clip(skeleton, clip, algorithm, &settings, acl_filename);
+		return acl_impl::write_track_list(track_list, nullptr, acl_filename);
 	}
 
 	//////////////////////////////////////////////////////////////////////////
 	// Write out an SJSON ACL track list file.
-	// Returns an error string on failure, null on success.
 	//////////////////////////////////////////////////////////////////////////
-	inline const char* write_track_list(const track_array& track_list, const char* acl_filename)
+	inline error_result write_track_list(const track_array& track_list, const compression_settings& settings, const char* acl_filename)
 	{
-		if (acl_filename == nullptr)
-			return "'acl_filename' cannot be NULL!";
-
-		const size_t filename_len = std::strlen(acl_filename);
-		if (filename_len < 10 || strncmp(acl_filename + filename_len - 10, ".acl.sjson", 10) != 0)
-			return "'acl_filename' file must be an ACL SJSON file of the form: *.acl.sjson";
-
-		std::FILE* file = nullptr;
-
-#ifdef _WIN32
-		char path[64 * 1024] = { 0 };
-		snprintf(path, get_array_size(path), "\\\\?\\%s", acl_filename);
-		fopen_s(&file, path, "w");
-#else
-		file = fopen(acl_filename, "w");
-#endif
-
-		if (file == nullptr)
-			return "Failed to open ACL file for writing";
-
-		char buffer[32] = { 0 };
-
-		sjson::FileStreamWriter stream_writer(file);
-		sjson::Writer writer(stream_writer);
-
-		writer["version"] = 5;
-		writer.insert_newline();
-
-		writer["track_list"] = [&](sjson::ObjectWriter& header_writer)
-		{
-			//header_writer["name"] = track_list.get_name().c_str();
-			header_writer["num_samples"] = track_list.get_num_samples_per_track();
-			header_writer["sample_rate"] = track_list.get_sample_rate();
-			header_writer["is_binary_exact"] = true;
-		};
-		writer.insert_newline();
-
-		writer["tracks"] = [&](sjson::ArrayWriter& tracks_writer)
-		{
-			const uint32_t num_tracks = track_list.get_num_tracks();
-			if (num_tracks > 0)
-				tracks_writer.push_newline();
-
-			for (const track& track_ : track_list)
-			{
-				tracks_writer.push([&](sjson::ObjectWriter& track_writer)
-				{
-					//track_writer["name"] = track_.get_name().c_str();
-					track_writer["type"] = get_track_type_name(track_.get_type());
-
-					switch (track_.get_type())
-					{
-					case track_type8::float1f:
-					{
-						const track_float1f& track__ = track_cast<track_float1f>(track_);
-						track_writer["precision"] = track__.get_description().precision;
-						track_writer["output_index"] = track__.get_description().output_index;
-
-						track_writer["data"] = [&](sjson::ArrayWriter& data_writer)
-						{
-							const uint32_t num_samples = track__.get_num_samples();
-							if (num_samples > 0)
-								data_writer.push_newline();
-
-							for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
-							{
-								data_writer.push([&](sjson::ArrayWriter& sample_writer)
-								{
-									const float sample = track__[sample_index];
-									sample_writer.push(acl_impl::format_hex_float(sample, buffer, sizeof(buffer)));
-								});
-								data_writer.push_newline();
-							}
-						};
-						break;
-					}
-					case track_type8::float2f:
-					{
-						const track_float2f& track__ = track_cast<track_float2f>(track_);
-						track_writer["precision"] = track__.get_description().precision;
-						track_writer["output_index"] = track__.get_description().output_index;
-
-						track_writer["data"] = [&](sjson::ArrayWriter& data_writer)
-						{
-							const uint32_t num_samples = track__.get_num_samples();
-							if (num_samples > 0)
-								data_writer.push_newline();
-
-							for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
-							{
-								data_writer.push([&](sjson::ArrayWriter& sample_writer)
-								{
-									const rtm::float2f& sample = track__[sample_index];
-									sample_writer.push(acl_impl::format_hex_float(sample.x, buffer, sizeof(buffer)));
-									sample_writer.push(acl_impl::format_hex_float(sample.y, buffer, sizeof(buffer)));
-								});
-								data_writer.push_newline();
-							}
-						};
-						break;
-					}
-					case track_type8::float3f:
-					{
-						const track_float3f& track__ = track_cast<track_float3f>(track_);
-						track_writer["precision"] = track__.get_description().precision;
-						track_writer["output_index"] = track__.get_description().output_index;
-
-						track_writer["data"] = [&](sjson::ArrayWriter& data_writer)
-						{
-							const uint32_t num_samples = track__.get_num_samples();
-							if (num_samples > 0)
-								data_writer.push_newline();
-
-							for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
-							{
-								data_writer.push([&](sjson::ArrayWriter& sample_writer)
-								{
-									const rtm::float3f& sample = track__[sample_index];
-									sample_writer.push(acl_impl::format_hex_float(sample.x, buffer, sizeof(buffer)));
-									sample_writer.push(acl_impl::format_hex_float(sample.y, buffer, sizeof(buffer)));
-									sample_writer.push(acl_impl::format_hex_float(sample.z, buffer, sizeof(buffer)));
-								});
-								data_writer.push_newline();
-							}
-						};
-						break;
-					}
-					case track_type8::float4f:
-					{
-						const track_float4f& track__ = track_cast<track_float4f>(track_);
-						track_writer["precision"] = track__.get_description().precision;
-						track_writer["output_index"] = track__.get_description().output_index;
-
-						track_writer["data"] = [&](sjson::ArrayWriter& data_writer)
-						{
-							const uint32_t num_samples = track__.get_num_samples();
-							if (num_samples > 0)
-								data_writer.push_newline();
-
-							for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
-							{
-								data_writer.push([&](sjson::ArrayWriter& sample_writer)
-								{
-									const rtm::float4f& sample = track__[sample_index];
-									sample_writer.push(acl_impl::format_hex_float(sample.x, buffer, sizeof(buffer)));
-									sample_writer.push(acl_impl::format_hex_float(sample.y, buffer, sizeof(buffer)));
-									sample_writer.push(acl_impl::format_hex_float(sample.z, buffer, sizeof(buffer)));
-									sample_writer.push(acl_impl::format_hex_float(sample.w, buffer, sizeof(buffer)));
-								});
-								data_writer.push_newline();
-							}
-						};
-						break;
-					}
-					case track_type8::vector4f:
-					{
-						const track_vector4f& track__ = track_cast<track_vector4f>(track_);
-						track_writer["precision"] = track__.get_description().precision;
-						track_writer["output_index"] = track__.get_description().output_index;
-
-						track_writer["data"] = [&](sjson::ArrayWriter& data_writer)
-						{
-							const uint32_t num_samples = track__.get_num_samples();
-							if (num_samples > 0)
-								data_writer.push_newline();
-
-							for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
-							{
-								data_writer.push([&](sjson::ArrayWriter& sample_writer)
-								{
-									const rtm::vector4f& sample = track__[sample_index];
-									sample_writer.push(acl_impl::format_hex_float(rtm::vector_get_x(sample), buffer, sizeof(buffer)));
-									sample_writer.push(acl_impl::format_hex_float(rtm::vector_get_y(sample), buffer, sizeof(buffer)));
-									sample_writer.push(acl_impl::format_hex_float(rtm::vector_get_z(sample), buffer, sizeof(buffer)));
-									sample_writer.push(acl_impl::format_hex_float(rtm::vector_get_w(sample), buffer, sizeof(buffer)));
-								});
-								data_writer.push_newline();
-							}
-						};
-						break;
-					}
-					default:
-						ACL_ASSERT(false, "Unknown track type");
-						break;
-					}
-				});
-			}
-		};
-		writer.insert_newline();
-
-		std::fclose(file);
-		return nullptr;
+		return acl_impl::write_track_list(track_list, &settings, acl_filename);
 	}
 }
 
diff --git a/includes/acl/math/quat_packing.h b/includes/acl/math/quat_packing.h
--- a/includes/acl/math/quat_packing.h
+++ b/includes/acl/math/quat_packing.h
@@ -90,33 +90,14 @@ namespace acl
 
 	//////////////////////////////////////////////////////////////////////////
 
-	// TODO: constexpr
-	inline uint32_t get_packed_rotation_size(rotation_format8 format)
+	constexpr uint32_t get_packed_rotation_size(rotation_format8 format)
 	{
-		switch (format)
-		{
-		case rotation_format8::quatf_full:				return sizeof(float) * 4;
-		case rotation_format8::quatf_drop_w_full:		return sizeof(float) * 3;
-		case rotation_format8::quatf_drop_w_variable:
-		default:
-			ACL_ASSERT(false, "Invalid or unsupported rotation format: %s", get_rotation_format_name(format));
-			return 0;
-		}
+		return format == rotation_format8::quatf_full ? (sizeof(float) * 4) : (sizeof(float) * 3);
 	}
 
-	inline uint32_t get_range_reduction_rotation_size(rotation_format8 format)
+	constexpr uint32_t get_range_reduction_rotation_size(rotation_format8 format)
 	{
-		switch (format)
-		{
-		case rotation_format8::quatf_full:
-			return sizeof(float) * 8;
-		case rotation_format8::quatf_drop_w_full:
-		case rotation_format8::quatf_drop_w_variable:
-			return sizeof(float) * 6;
-		default:
-			ACL_ASSERT(false, "Invalid or unsupported rotation format: %s", get_rotation_format_name(format));
-			return 0;
-		}
+		return format == rotation_format8::quatf_full ? (sizeof(float) * 8) : (sizeof(float) * 6);
 	}
 }
 
diff --git a/includes/acl/math/quatf.h b/includes/acl/math/quatf.h
new file mode 100644
--- /dev/null
+++ b/includes/acl/math/quatf.h
@@ -0,0 +1,145 @@
+#pragma once
+
+////////////////////////////////////////////////////////////////////////////////
+// The MIT License (MIT)
+//
+// Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "acl/core/impl/compiler_utils.h"
+
+#include <rtm/quatf.h>
+
+#include <cstdint>
+
+ACL_IMPL_FILE_PRAGMA_PUSH
+
+namespace acl
+{
+	//////////////////////////////////////////////////////////////////////////
+	// Loads an unaligned quat from memory.
+	// Missing from RTM for now
+	// TODO: Remove me once we upgrade to RTM 2.1 or later
+	//////////////////////////////////////////////////////////////////////////
+	RTM_DISABLE_SECURITY_COOKIE_CHECK inline rtm::quatf RTM_SIMD_CALL quat_load(const rtm::float4f* input) RTM_NO_EXCEPT
+	{
+#if defined(RTM_SSE2_INTRINSICS)
+		return _mm_loadu_ps(&input->x);
+#elif defined(RTM_NEON_INTRINSICS)
+		return vld1q_f32(&input->x);
+#else
+		return rtm::quat_set(input->x, input->y, input->z, input->w);
+#endif
+	}
+
+	RTM_DISABLE_SECURITY_COOKIE_CHECK inline rtm::quatf RTM_SIMD_CALL quat_lerp_no_normalization(rtm::quatf_arg0 start, rtm::quatf_arg1 end, float alpha) RTM_NO_EXCEPT
+	{
+		using namespace rtm;
+
+#if defined(RTM_SSE2_INTRINSICS)
+		// Calculate the vector4 dot product: dot(start, end)
+		__m128 dot;
+#if defined(RTM_SSE4_INTRINSICS)
+		// The dpps instruction isn't as accurate but we don't care here, we only need the sign of the
+		// dot product. If both rotations are on opposite ends of the hypersphere, the result will be
+		// very negative. If we are on the edge, the rotations are nearly opposite but not quite which
+		// means that the linear interpolation here will have terrible accuracy to begin with. It is designed
+		// for interpolating rotations that are reasonably close together. The bias check is mainly necessary
+		// because the W component is often kept positive which flips the sign.
+		// Using the dpps instruction reduces the number of registers that we need and helps the function get
+		// inlined.
+		dot = _mm_dp_ps(start, end, 0xFF);
+#else
+		{
+			__m128 x2_y2_z2_w2 = _mm_mul_ps(start, end);
+			__m128 z2_w2_0_0 = _mm_shuffle_ps(x2_y2_z2_w2, x2_y2_z2_w2, _MM_SHUFFLE(0, 0, 3, 2));
+			__m128 x2z2_y2w2_0_0 = _mm_add_ps(x2_y2_z2_w2, z2_w2_0_0);
+			__m128 y2w2_0_0_0 = _mm_shuffle_ps(x2z2_y2w2_0_0, x2z2_y2w2_0_0, _MM_SHUFFLE(0, 0, 0, 1));
+			__m128 x2y2z2w2_0_0_0 = _mm_add_ps(x2z2_y2w2_0_0, y2w2_0_0_0);
+			// Shuffle the dot product to all SIMD lanes, there is no _mm_and_ss and loading
+			// the constant from memory with the 'and' instruction is faster, it uses fewer registers
+			// and fewer instructions
+			dot = _mm_shuffle_ps(x2y2z2w2_0_0_0, x2y2z2w2_0_0_0, _MM_SHUFFLE(0, 0, 0, 0));
+		}
+#endif
+
+		// Calculate the bias, if the dot product is positive or zero, there is no bias
+		// but if it is negative, we want to flip the 'end' rotation XYZW components
+		__m128 bias = _mm_and_ps(dot, _mm_set_ps1(-0.0F));
+
+		// Lerp the rotation after applying the bias
+		// ((1.0 - alpha) * start) + (alpha * (end ^ bias)) == (start - alpha * start) + (alpha * (end ^ bias))
+		__m128 alpha_ = _mm_set_ps1(alpha);
+		__m128 interpolated_rotation = _mm_add_ps(_mm_sub_ps(start, _mm_mul_ps(alpha_, start)), _mm_mul_ps(alpha_, _mm_xor_ps(end, bias)));
+
+		// Due to the interpolation, the result might not be anywhere near normalized!
+		// Make sure to normalize afterwards before using
+		return interpolated_rotation;
+#elif defined (RTM_NEON64_INTRINSICS)
+		// On ARM64 with NEON, we load 1.0 once and use it twice which is faster than
+		// using a AND/XOR with the bias (same number of instructions)
+		float dot = vector_dot(start, end);
+		float bias = dot >= 0.0F ? 1.0F : -1.0F;
+
+		// ((1.0 - alpha) * start) + (alpha * (end * bias)) == (start - alpha * start) + (alpha * (end * bias))
+		vector4f interpolated_rotation = vector_mul_add(vector_mul(end, bias), alpha, vector_neg_mul_sub(start, alpha, start));
+
+		// Due to the interpolation, the result might not be anywhere near normalized!
+		// Make sure to normalize afterwards before using
+		return interpolated_rotation;
+#elif defined(RTM_NEON_INTRINSICS)
+		// Calculate the vector4 dot product: dot(start, end)
+		float32x4_t x2_y2_z2_w2 = vmulq_f32(start, end);
+		float32x2_t x2_y2 = vget_low_f32(x2_y2_z2_w2);
+		float32x2_t z2_w2 = vget_high_f32(x2_y2_z2_w2);
+		float32x2_t x2z2_y2w2 = vadd_f32(x2_y2, z2_w2);
+		float32x2_t x2y2z2w2 = vpadd_f32(x2z2_y2w2, x2z2_y2w2);
+
+		// Calculate the bias, if the dot product is positive or zero, there is no bias
+		// but if it is negative, we want to flip the 'end' rotation XYZW components
+		// On ARM-v7-A, the AND/XOR trick is faster than the cmp/fsel
+		uint32x2_t bias = vand_u32(vreinterpret_u32_f32(x2y2z2w2), vdup_n_u32(0x80000000));
+
+		// Lerp the rotation after applying the bias
+		// ((1.0 - alpha) * start) + (alpha * (end ^ bias)) == (start - alpha * start) + (alpha * (end ^ bias))
+		float32x4_t end_biased = vreinterpretq_f32_u32(veorq_u32(vreinterpretq_u32_f32(end), vcombine_u32(bias, bias)));
+		float32x4_t interpolated_rotation = vmlaq_n_f32(vmlsq_n_f32(start, start, alpha), end_biased, alpha);
+
+		// Due to the interpolation, the result might not be anywhere near normalized!
+		// Make sure to normalize afterwards before using
+		return interpolated_rotation;
+#else
+		// To ensure we take the shortest path, we apply a bias if the dot product is negative
+		vector4f start_vector = quat_to_vector(start);
+		vector4f end_vector = quat_to_vector(end);
+		float dot = vector_dot(start_vector, end_vector);
+		float bias = dot >= 0.0F ? 1.0F : -1.0F;
+		// ((1.0 - alpha) * start) + (alpha * (end * bias)) == (start - alpha * start) + (alpha * (end * bias))
+		vector4f interpolated_rotation = vector_mul_add(vector_mul(end_vector, bias), alpha, vector_neg_mul_sub(start_vector, alpha, start_vector));
+
+		// Due to the interpolation, the result might not be anywhere near normalized!
+		// Make sure to normalize afterwards before using
+		return vector_to_quat(interpolated_rotation);
+#endif
+	}
+}
+
+ACL_IMPL_FILE_PRAGMA_POP
diff --git a/tools/acl_compressor/main_emscripten/CMakeLists.txt b/tools/acl_compressor/main_emscripten/CMakeLists.txt
--- a/tools/acl_compressor/main_emscripten/CMakeLists.txt
+++ b/tools/acl_compressor/main_emscripten/CMakeLists.txt
@@ -34,6 +34,10 @@ if(USE_SJSON)
 	add_definitions(-DACL_USE_SJSON)
 endif()
 
+# Remove '-g' from compilation flags since it sometimes crashes the compiler
+string(REPLACE "-g" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
+string(REPLACE "-g" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
+
 target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra)		# Enable all warnings
 target_compile_options(${PROJECT_NAME} PRIVATE -Wshadow)			# Enable shadowing warnings
 target_compile_options(${PROJECT_NAME} PRIVATE -Werror)				# Treat warnings as errors
diff --git a/tools/acl_compressor/sources/acl_compressor.cpp b/tools/acl_compressor/sources/acl_compressor.cpp
--- a/tools/acl_compressor/sources/acl_compressor.cpp
+++ b/tools/acl_compressor/sources/acl_compressor.cpp
@@ -24,6 +24,7 @@
 
 #include "acl_compressor.h"
 
+#define DEBUG_MEGA_LARGE_CLIP 0
 
 // Enable 64 bit file IO
 #ifndef _WIN32
@@ -45,20 +46,14 @@
 #include "acl/core/range_reduction_types.h"
 #include "acl/core/string.h"
 #include "acl/core/impl/debug_track_writer.h"
-#include "acl/compression/animation_clip.h"
 #include "acl/compression/compress.h"
-#include "acl/compression/skeleton.h"
-#include "acl/compression/skeleton_error_metric.h"
-#include "acl/compression/skeleton_pose_utils.h"	// Just to test compilation
+#include "acl/compression/transform_error_metrics.h"
+#include "acl/compression/transform_pose_utils.h"	// Just to test compilation
 #include "acl/compression/impl/write_decompression_stats.h"
 #include "acl/compression/track_error.h"
-#include "acl/compression/utils.h"
 #include "acl/decompression/decompress.h"
 #include "acl/io/clip_reader.h"
 
-#include "acl/algorithm/uniformly_sampled/encoder.h"
-#include "acl/algorithm/uniformly_sampled/decoder.h"
-
 #include <cstring>
 #include <cstdio>
 #include <fstream>
@@ -134,7 +129,7 @@ struct Options
 	const char*		config_filename;
 #endif
 
-	bool			output_stats;
+	bool			do_output_stats;
 	const char*		output_stats_filename;
 	std::FILE*		output_stats_file;
 
@@ -169,7 +164,7 @@ struct Options
 		: input_filename(nullptr)
 		, config_filename(nullptr)
 #endif
-		, output_stats(false)
+		, do_output_stats(false)
 		, output_stats_filename(nullptr)
 		, output_stats_file(nullptr)
 		, output_bin_filename(nullptr)
@@ -291,7 +286,7 @@ static bool parse_options(int argc, char** argv, Options& options)
 		option_length = std::strlen(k_stats_output_option);
 		if (std::strncmp(argument, k_stats_output_option, option_length) == 0)
 		{
-			options.output_stats = true;
+			options.do_output_stats = true;
 			if (argument[option_length] == '=')
 			{
 				options.output_stats_filename = argument + option_length + 1;
@@ -422,24 +417,35 @@ static bool parse_options(int argc, char** argv, Options& options)
 }
 
 #if defined(ACL_USE_SJSON)
-template<class DecompressionContextType>
-static void validate_accuracy(IAllocator& allocator, const AnimationClip& clip, const CompressionSettings& settings, DecompressionContextType& context, double regression_error_threshold)
+static void validate_accuracy(iallocator& allocator, const track_array_qvvf& raw_tracks, const track_array_qvvf& additive_base_tracks, const itransform_error_metric& error_metric, const compressed_tracks& compressed_tracks_, double regression_error_threshold)
 {
+	using namespace acl_impl;
+
+	(void)allocator;
+	(void)raw_tracks;
+	(void)additive_base_tracks;
+	(void)error_metric;
+	(void)compressed_tracks_;
 	(void)regression_error_threshold;
 
-	const BoneError bone_error = calculate_error_between_clips(allocator, *settings.error_metric, clip, context);
-	(void)bone_error;
-	ACL_ASSERT(rtm::scalar_is_finite(bone_error.error), "Returned error is not a finite value");
-	ACL_ASSERT(bone_error.error < regression_error_threshold, "Error too high for bone %u: %f at time %f", bone_error.index, bone_error.error, bone_error.sample_time);
+#if defined(ACL_HAS_ASSERT_CHECKS)
+	// Disable floating point exceptions since decompression assumes it
+	scope_disable_fp_exceptions fp_off;
+
+	acl::decompression_context<acl::debug_decompression_settings> context;
+	context.initialize(compressed_tracks_);
 
-	const uint16_t num_bones = clip.get_num_bones();
-	const float clip_duration = clip.get_duration();
-	const float sample_rate = clip.get_sample_rate();
-	const uint32_t num_samples = calculate_num_samples(clip_duration, clip.get_sample_rate());
+	const track_error error = calculate_compression_error(allocator, raw_tracks, context, error_metric, additive_base_tracks);
+	(void)error;
+	ACL_ASSERT(rtm::scalar_is_finite(error.error), "Returned error is not a finite value");
+	ACL_ASSERT(error.error < regression_error_threshold, "Error too high for bone %u: %f at time %f", error.index, error.error, error.sample_time);
 
-	rtm::qvvf* lossy_pose_transforms = allocate_type_array<rtm::qvvf>(allocator, num_bones);
+	const uint32_t num_bones = raw_tracks.get_num_tracks();
+	const float clip_duration = raw_tracks.get_duration();
+	const float sample_rate = raw_tracks.get_sample_rate();
+	const uint32_t num_samples = raw_tracks.get_num_samples_per_track();
 
-	DefaultOutputWriter pose_writer(lossy_pose_transforms, num_bones);
+	debug_track_writer track_writer(allocator, track_type8::qvvf, num_bones);
 
 	// Regression test
 	for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
@@ -448,49 +454,25 @@ static void validate_accuracy(IAllocator& allocator, const AnimationClip& clip,
 
 		// We use the nearest sample to accurately measure the loss that happened, if any
 		context.seek(sample_time, sample_rounding_policy::nearest);
-		context.decompress_pose(pose_writer);
+		context.decompress_tracks(track_writer);
 
-		// Validate decompress_bone for rotations only
-		for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
+		// Validate decompress_track against decompress_tracks
+		for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index)
 		{
-			rtm::quatf rotation;
-			context.decompress_bone(bone_index, &rotation, nullptr, nullptr);
-			ACL_ASSERT(rtm::quat_near_equal(rotation, lossy_pose_transforms[bone_index].rotation), "Failed to sample bone index: %u", bone_index);
-		}
+			const rtm::qvvf transform0 = track_writer.read_qvv(bone_index);
 
-		// Validate decompress_bone for translations only
-		for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
-		{
-			rtm::vector4f translation;
-			context.decompress_bone(bone_index, nullptr, &translation, nullptr);
-			ACL_ASSERT(rtm::vector_all_near_equal3(translation, lossy_pose_transforms[bone_index].translation), "Failed to sample bone index: %u", bone_index);
-		}
-
-		// Validate decompress_bone for scales only
-		for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
-		{
-			rtm::vector4f scale;
-			context.decompress_bone(bone_index, nullptr, nullptr, &scale);
-			ACL_ASSERT(rtm::vector_all_near_equal3(scale, lossy_pose_transforms[bone_index].scale), "Failed to sample bone index: %u", bone_index);
-		}
+			context.decompress_track(bone_index, track_writer);
+			const rtm::qvvf transform1 = track_writer.read_qvv(bone_index);
 
-		// Validate decompress_bone
-		for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
-		{
-			rtm::quatf rotation;
-			rtm::vector4f translation;
-			rtm::vector4f scale;
-			context.decompress_bone(bone_index, &rotation, &translation, &scale);
-			ACL_ASSERT(rtm::quat_near_equal(rotation, lossy_pose_transforms[bone_index].rotation), "Failed to sample bone index: %u", bone_index);
-			ACL_ASSERT(rtm::vector_all_near_equal3(translation, lossy_pose_transforms[bone_index].translation), "Failed to sample bone index: %u", bone_index);
-			ACL_ASSERT(rtm::vector_all_near_equal3(scale, lossy_pose_transforms[bone_index].scale), "Failed to sample bone index: %u", bone_index);
+			ACL_ASSERT(rtm::vector_all_near_equal(rtm::quat_to_vector(transform0.rotation), rtm::quat_to_vector(transform1.rotation), 0.0F), "Failed to sample bone index: %u", bone_index);
+			ACL_ASSERT(rtm::vector_all_near_equal3(transform0.translation, transform1.translation, 0.0F), "Failed to sample bone index: %u", bone_index);
+			ACL_ASSERT(rtm::vector_all_near_equal3(transform0.scale, transform1.scale, 0.0F), "Failed to sample bone index: %u", bone_index);
 		}
 	}
-
-	deallocate_type_array(allocator, lossy_pose_transforms, num_bones);
+#endif
 }
 
-static void validate_accuracy(IAllocator& allocator, const track_array& raw_tracks, const compressed_tracks& tracks, double regression_error_threshold)
+static void validate_accuracy(iallocator& allocator, const track_array& raw_tracks, const compressed_tracks& tracks, double regression_error_threshold)
 {
 	(void)allocator;
 	(void)raw_tracks;
@@ -674,81 +656,54 @@ static void validate_accuracy(IAllocator& allocator, const track_array& raw_trac
 #endif	// defined(ACL_HAS_ASSERT_CHECKS)
 }
 
-static void try_algorithm(const Options& options, IAllocator& allocator, const AnimationClip& clip, const CompressionSettings& settings, algorithm_type8 algorithm_type, StatLogging logging, sjson::ArrayWriter* runs_writer, double regression_error_threshold)
+static void try_algorithm(const Options& options, iallocator& allocator, track_array_qvvf& transform_tracks, const track_array_qvvf& additive_base, additive_clip_format8 additive_format, const compression_settings& settings, stat_logging logging, sjson::ArrayWriter* runs_writer, double regression_error_threshold)
 {
 	(void)runs_writer;
 
 	auto try_algorithm_impl = [&](sjson::ObjectWriter* stats_writer)
 	{
-		if (clip.get_num_samples() == 0)
+		if (transform_tracks.get_num_samples_per_track() == 0)
 			return;
 
-		OutputStats stats(logging, stats_writer);
-		CompressedClip* compressed_clip = nullptr;
-		ErrorResult error_result; (void)error_result;
-		switch (algorithm_type)
-		{
-		case algorithm_type8::uniformly_sampled:
-			error_result = uniformly_sampled::compress_clip(allocator, clip, settings, compressed_clip, stats);
-			break;
-		}
+		output_stats stats(logging, stats_writer);
+		compressed_tracks* compressed_tracks_ = nullptr;
+		const error_result result = compress_track_list(allocator, transform_tracks, settings, additive_base, additive_format, compressed_tracks_, stats);
 
-		ACL_ASSERT(error_result.empty(), error_result.c_str());
-		ACL_ASSERT(compressed_clip->is_valid(true).empty(), "Compressed clip is invalid");
+		(void)result;
+		ACL_ASSERT(result.empty(), result.c_str());
+		ACL_ASSERT(compressed_tracks_->is_valid(true).empty(), "Compressed tracks are invalid");
 
 #if defined(SJSON_CPP_WRITER)
-		if (logging != StatLogging::None)
+		if (logging != stat_logging::None)
 		{
 			// Disable floating point exceptions since decompression assumes it
 			scope_disable_fp_exceptions fp_off;
 
-			// Use the compressed clip to make sure the decoder works properly
-			BoneError bone_error;
-			switch (algorithm_type)
-			{
-			case algorithm_type8::uniformly_sampled:
-			{
-				uniformly_sampled::DecompressionContext<uniformly_sampled::DebugDecompressionSettings> context;
-				context.initialize(*compressed_clip);
-				bone_error = calculate_error_between_clips(allocator, *settings.error_metric, clip, context);
-				break;
-			}
-			}
+			acl::decompression_context<acl::debug_decompression_settings> context;
+			context.initialize(*compressed_tracks_);
 
-			stats_writer->insert("max_error", bone_error.error);
-			stats_writer->insert("worst_bone", bone_error.index);
-			stats_writer->insert("worst_time", bone_error.sample_time);
+			const track_error error = calculate_compression_error(allocator, transform_tracks, context, *settings.error_metric);
+
+			stats_writer->insert("max_error", error.error);
+			stats_writer->insert("worst_track", error.index);
+			stats_writer->insert("worst_time", error.sample_time);
 
-			if (are_any_enum_flags_set(logging, StatLogging::SummaryDecompression))
-				acl_impl::write_decompression_performance_stats(allocator, settings, *compressed_clip, logging, *stats_writer);
+			if (are_any_enum_flags_set(logging, stat_logging::SummaryDecompression))
+				acl_impl::write_decompression_performance_stats(allocator, settings, *compressed_tracks_, logging, *stats_writer);
 		}
 #endif
 
 		if (options.regression_testing)
-		{
-			// Disable floating point exceptions since decompression assumes it
-			scope_disable_fp_exceptions fp_off;
-
-			switch (algorithm_type)
-			{
-			case algorithm_type8::uniformly_sampled:
-			{
-				uniformly_sampled::DecompressionContext<uniformly_sampled::DebugDecompressionSettings> context;
-				context.initialize(*compressed_clip);
-				validate_accuracy(allocator, clip, settings, context, regression_error_threshold);
-				break;
-			}
-			}
-		}
+			validate_accuracy(allocator, transform_tracks, additive_base, *settings.error_metric, *compressed_tracks_, regression_error_threshold);
 
 		if (options.output_bin_filename != nullptr)
 		{
 			std::ofstream output_file_stream(options.output_bin_filename, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc);
 			if (output_file_stream.is_open())
-				output_file_stream.write(reinterpret_cast<const char*>(compressed_clip), compressed_clip->get_size());
+				output_file_stream.write(reinterpret_cast<const char*>(compressed_tracks_), compressed_tracks_->get_size());
 		}
 
-		allocator.deallocate(compressed_clip, compressed_clip->get_size());
+		allocator.deallocate(compressed_tracks_, compressed_tracks_->get_size());
 	};
 
 #if defined(SJSON_CPP_WRITER)
@@ -759,7 +714,7 @@ static void try_algorithm(const Options& options, IAllocator& allocator, const A
 		try_algorithm_impl(nullptr);
 }
 
-static void try_algorithm(const Options& options, IAllocator& allocator, const track_array& track_list, StatLogging logging, sjson::ArrayWriter* runs_writer, double regression_error_threshold)
+static void try_algorithm(const Options& options, iallocator& allocator, const track_array& track_list, stat_logging logging, sjson::ArrayWriter* runs_writer, double regression_error_threshold)
 {
 	(void)runs_writer;
 
@@ -770,15 +725,15 @@ static void try_algorithm(const Options& options, IAllocator& allocator, const t
 
 		compression_settings settings;
 
-		OutputStats stats(logging, stats_writer);
+		output_stats stats(logging, stats_writer);
 		compressed_tracks* compressed_tracks_ = nullptr;
-		const ErrorResult error_result = compress_track_list(allocator, track_list, settings, compressed_tracks_, stats);
+		const error_result result = compress_track_list(allocator, track_list, settings, compressed_tracks_, stats);
 
-		ACL_ASSERT(error_result.empty(), error_result.c_str()); (void)error_result;
+		ACL_ASSERT(result.empty(), result.c_str()); (void)result;
 		ACL_ASSERT(compressed_tracks_->is_valid(true).empty(), "Compressed tracks are invalid");
 
 #if defined(SJSON_CPP_WRITER)
-		if (logging != StatLogging::None)
+		if (logging != stat_logging::None)
 		{
 			// Disable floating point exceptions since decompression assumes it
 			scope_disable_fp_exceptions fp_off;
@@ -793,7 +748,7 @@ static void try_algorithm(const Options& options, IAllocator& allocator, const t
 			stats_writer->insert("worst_time", error.sample_time);
 
 			// TODO: measure decompression performance
-			//if (are_any_enum_flags_set(logging, StatLogging::SummaryDecompression))
+			//if (are_any_enum_flags_set(logging, stat_logging::SummaryDecompression))
 				//write_decompression_performance_stats(allocator, settings, *compressed_clip, logging, *stats_writer);
 		}
 #endif
@@ -824,7 +779,7 @@ static void try_algorithm(const Options& options, IAllocator& allocator, const t
 		try_algorithm_impl(nullptr);
 }
 
-static bool read_acl_sjson_file(IAllocator& allocator, const Options& options,
+static bool read_acl_sjson_file(iallocator& allocator, const Options& options,
 	sjson_file_type& out_file_type,
 	sjson_raw_clip& out_raw_clip,
 	sjson_raw_track_list& out_raw_track_list)
@@ -833,7 +788,7 @@ static bool read_acl_sjson_file(IAllocator& allocator, const Options& options,
 	size_t file_size = 0;
 
 #if defined(__ANDROID__)
-	ClipReader reader(allocator, options.input_buffer, options.input_buffer_size - 1);
+	clip_reader reader(allocator, options.input_buffer, options.input_buffer_size - 1);
 #else
 	// Use the raw C API with a large buffer to ensure this is as fast as possible
 	std::FILE* file = nullptr;
@@ -885,7 +840,7 @@ static bool read_acl_sjson_file(IAllocator& allocator, const Options& options,
 		return false;
 	}
 
-	ClipReader reader(allocator, sjson_file_buffer, file_size - 1);
+	clip_reader reader(allocator, sjson_file_buffer, file_size - 1);
 #endif
 
 	const sjson_file_type ftype = reader.get_file_type();
@@ -908,8 +863,8 @@ static bool read_acl_sjson_file(IAllocator& allocator, const Options& options,
 
 	if (!success)
 	{
-		const ClipReaderError err = reader.get_error();
-		if (err.error != ClipReaderError::None)
+		const clip_reader_error err = reader.get_error();
+		if (err.error != clip_reader_error::None)
 			printf("\nError on line %d column %d: %s\n", err.line, err.column, err.get_description());
 	}
 
@@ -917,7 +872,7 @@ static bool read_acl_sjson_file(IAllocator& allocator, const Options& options,
 	return success;
 }
 
-static bool read_config(IAllocator& allocator, Options& options, algorithm_type8& out_algorithm_type, CompressionSettings& out_settings, double& out_regression_error_threshold)
+static bool read_config(iallocator& allocator, Options& options, compression_settings& out_settings, double& out_regression_error_threshold)
 {
 #if defined(__ANDROID__)
 	sjson::Parser parser(options.config_buffer, options.config_buffer_size - 1);
@@ -958,19 +913,20 @@ static bool read_config(IAllocator& allocator, Options& options, algorithm_type8
 		return false;
 	}
 
-	if (!get_algorithm_type(algorithm_name.c_str(), out_algorithm_type))
+	algorithm_type8 algorithm_type;
+	if (!get_algorithm_type(algorithm_name.c_str(), algorithm_type))
 	{
-		printf("Invalid algorithm name: %s\n", String(allocator, algorithm_name.c_str(), algorithm_name.size()).c_str());
+		printf("Invalid algorithm name: %s\n", string(allocator, algorithm_name.c_str(), algorithm_name.size()).c_str());
 		return false;
 	}
 
-	CompressionSettings default_settings;
+	compression_settings default_settings;
 
 	sjson::StringView compression_level;
 	parser.try_read("level", compression_level, get_compression_level_name(default_settings.level));
 	if (!get_compression_level(compression_level.c_str(), out_settings.level))
 	{
-		printf("Invalid compression level: %s\n", String(allocator, compression_level.c_str(), compression_level.size()).c_str());
+		printf("Invalid compression level: %s\n", string(allocator, compression_level.c_str(), compression_level.size()).c_str());
 		return false;
 	}
 
@@ -978,7 +934,7 @@ static bool read_config(IAllocator& allocator, Options& options, algorithm_type8
 	parser.try_read("rotation_format", rotation_format, get_rotation_format_name(default_settings.rotation_format));
 	if (!get_rotation_format(rotation_format.c_str(), out_settings.rotation_format))
 	{
-		printf("Invalid rotation format: %s\n", String(allocator, rotation_format.c_str(), rotation_format.size()).c_str());
+		printf("Invalid rotation format: %s\n", string(allocator, rotation_format.c_str(), rotation_format.size()).c_str());
 		return false;
 	}
 
@@ -986,7 +942,7 @@ static bool read_config(IAllocator& allocator, Options& options, algorithm_type8
 	parser.try_read("translation_format", translation_format, get_vector_format_name(default_settings.translation_format));
 	if (!get_vector_format(translation_format.c_str(), out_settings.translation_format))
 	{
-		printf("Invalid translation format: %s\n", String(allocator, translation_format.c_str(), translation_format.size()).c_str());
+		printf("Invalid translation format: %s\n", string(allocator, translation_format.c_str(), translation_format.size()).c_str());
 		return false;
 	}
 
@@ -994,17 +950,16 @@ static bool read_config(IAllocator& allocator, Options& options, algorithm_type8
 	parser.try_read("scale_format", scale_format, get_vector_format_name(default_settings.scale_format));
 	if (!get_vector_format(scale_format.c_str(), out_settings.scale_format))
 	{
-		printf("Invalid scale format: %s\n", String(allocator, scale_format.c_str(), scale_format.size()).c_str());
+		printf("Invalid scale format: %s\n", string(allocator, scale_format.c_str(), scale_format.size()).c_str());
 		return false;
 	}
 
-	double constant_rotation_threshold_angle;
-	parser.try_read("constant_rotation_threshold_angle", constant_rotation_threshold_angle, default_settings.constant_rotation_threshold_angle);
-	out_settings.constant_rotation_threshold_angle = float(constant_rotation_threshold_angle);
+	double dummy;
+	parser.try_read("constant_rotation_threshold_angle", dummy, 0.0F);
 
-	parser.try_read("constant_translation_threshold", out_settings.constant_translation_threshold, default_settings.constant_translation_threshold);
-	parser.try_read("constant_scale_threshold", out_settings.constant_scale_threshold, default_settings.constant_scale_threshold);
-	parser.try_read("error_threshold", out_settings.error_threshold, default_settings.error_threshold);
+	parser.try_read("constant_translation_threshold", dummy, 0.0F);
+	parser.try_read("constant_scale_threshold", dummy, 0.0F);
+	parser.try_read("error_threshold", dummy, 0.0F);
 
 	parser.try_read("regression_error_threshold", out_regression_error_threshold, 0.0);
 
@@ -1029,7 +984,7 @@ static bool read_config(IAllocator& allocator, Options& options, algorithm_type8
 	return true;
 }
 
-static itransform_error_metric* create_additive_error_metric(IAllocator& allocator, additive_clip_format8 format)
+static itransform_error_metric* create_additive_error_metric(iallocator& allocator, additive_clip_format8 format)
 {
 	switch (format)
 	{
@@ -1044,12 +999,15 @@ static itransform_error_metric* create_additive_error_metric(IAllocator& allocat
 	}
 }
 
-static void create_additive_base_clip(const Options& options, AnimationClip& clip, const RigidSkeleton& skeleton, AnimationClip& out_base_clip)
+static void create_additive_base_clip(const Options& options, track_array_qvvf& clip, const track_qvvf& bind_pose, track_array_qvvf& out_base_clip, additive_clip_format8& out_additive_format)
 {
 	// Convert the animation clip to be relative to the bind pose
-	const uint16_t num_bones = clip.get_num_bones();
-	const uint32_t num_samples = clip.get_num_samples();
-	AnimatedBone* bones = clip.get_bones();
+	const uint32_t num_bones = clip.get_num_tracks();
+	const uint32_t num_samples = clip.get_num_samples_per_track();
+	iallocator& allocator = *clip.get_allocator();
+	const track_desc_transformf bind_desc;
+
+	out_base_clip = track_array_qvvf(*clip.get_allocator(), num_bones);
 
 	additive_clip_format8 additive_format = additive_clip_format8::none;
 	if (options.is_bind_pose_relative)
@@ -1058,24 +1016,21 @@ static void create_additive_base_clip(const Options& options, AnimationClip& cli
 		additive_format = additive_clip_format8::additive0;
 	else if (options.is_bind_pose_additive1)
 		additive_format = additive_clip_format8::additive1;
+	out_additive_format = additive_format;
 
-	for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
+	for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index)
 	{
-		AnimatedBone& anim_bone = bones[bone_index];
-
 		// Get the bind transform and make sure it has no scale
-		const RigidBone& skel_bone = skeleton.get_bone(bone_index);
-		const rtm::qvvd bind_transform = rtm::qvv_set(skel_bone.bind_transform.rotation, skel_bone.bind_transform.translation, rtm::vector_set(1.0));
+		rtm::qvvf bind_transform = bind_pose[bone_index];
+		bind_transform.scale = rtm::vector_set(1.0F);
+
+		track_qvvf& track = clip[bone_index];
 
 		for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
 		{
-			const rtm::quatd rotation = rtm::quat_normalize(anim_bone.rotation_track.get_sample(sample_index));
-			const rtm::vector4d translation = anim_bone.translation_track.get_sample(sample_index);
-			const rtm::vector4d scale = anim_bone.scale_track.get_sample(sample_index);
-
-			const rtm::qvvd bone_transform = rtm::qvv_set(rotation, translation, scale);
+			const rtm::qvvf bone_transform = track[sample_index];
 
-			rtm::qvvd bind_local_transform = bone_transform;
+			rtm::qvvf bind_local_transform = bone_transform;
 			if (options.is_bind_pose_relative)
 				bind_local_transform = convert_to_relative(bind_transform, bone_transform);
 			else if (options.is_bind_pose_additive0)
@@ -1083,23 +1038,16 @@ static void create_additive_base_clip(const Options& options, AnimationClip& cli
 			else if (options.is_bind_pose_additive1)
 				bind_local_transform = convert_to_additive1(bind_transform, bone_transform);
 
-			anim_bone.rotation_track.set_sample(sample_index, bind_local_transform.rotation);
-			anim_bone.translation_track.set_sample(sample_index, bind_local_transform.translation);
-			anim_bone.scale_track.set_sample(sample_index, bind_local_transform.scale);
+			track[sample_index] = bind_local_transform;
 		}
 
-		AnimatedBone& base_bone = out_base_clip.get_animated_bone(bone_index);
-		base_bone.rotation_track.set_sample(0, bind_transform.rotation);
-		base_bone.translation_track.set_sample(0, bind_transform.translation);
-		base_bone.scale_track.set_sample(0, bind_transform.scale);
+		out_base_clip[bone_index] = track_qvvf::make_copy(bind_desc, allocator, &bind_transform, 1, 30.0F);
 	}
-
-	clip.set_additive_base(&out_base_clip, additive_format);
 }
 
-static CompressionSettings make_settings(rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format)
+static compression_settings make_settings(rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format)
 {
-	CompressionSettings settings;
+	compression_settings settings;
 	settings.rotation_format = rotation_format;
 	settings.translation_format = translation_format;
 	settings.scale_format = scale_format;
@@ -1123,9 +1071,11 @@ static int safe_main_impl(int argc, char* argv[])
 	}
 
 #if defined(ACL_USE_SJSON)
-	ANSIAllocator allocator;
-	std::unique_ptr<AnimationClip, Deleter<AnimationClip>> clip;
-	std::unique_ptr<RigidSkeleton, Deleter<RigidSkeleton>> skeleton;
+	ansi_allocator allocator;
+	track_array_qvvf transform_tracks;
+	track_array_qvvf base_clip;
+	additive_clip_format8 additive_format = additive_clip_format8::none;
+	track_qvvf bind_pose;
 
 #if defined(__ANDROID__)
 	const bool is_input_acl_bin_file = options.input_buffer_binary;
@@ -1134,8 +1084,7 @@ static int safe_main_impl(int argc, char* argv[])
 #endif
 
 	bool use_external_config = false;
-	algorithm_type8 algorithm_type = algorithm_type8::uniformly_sampled;
-	CompressionSettings settings;
+	compression_settings settings;
 
 	sjson_file_type sjson_type = sjson_file_type::unknown;
 	sjson_raw_clip sjson_clip;
@@ -1146,13 +1095,43 @@ static int safe_main_impl(int argc, char* argv[])
 		if (!read_acl_sjson_file(allocator, options, sjson_type, sjson_clip, sjson_track_list))
 			return -1;
 
-		clip = std::move(sjson_clip.clip);
-		skeleton = std::move(sjson_clip.skeleton);
+		transform_tracks = std::move(sjson_clip.track_list);
+		base_clip = std::move(sjson_clip.additive_base_track_list);
+		additive_format = sjson_clip.additive_format;
+		bind_pose = std::move(sjson_clip.bind_pose);
 		use_external_config = sjson_clip.has_settings;
-		algorithm_type = sjson_clip.algorithm_type;
 		settings = sjson_clip.settings;
 	}
 
+#if DEBUG_MEGA_LARGE_CLIP
+	track_array_qvvf new_transforms(allocator, transform_tracks.get_num_tracks());
+	float new_sample_rate = 19200.0F;
+	uint32_t new_num_samples = calculate_num_samples(transform_tracks.get_duration(), new_sample_rate);
+	float new_duration = calculate_duration(new_num_samples, new_sample_rate);
+	acl_impl::debug_track_writer dummy_writer(allocator, track_type8::qvvf, transform_tracks.get_num_tracks());
+
+	for (uint32_t track_index = 0; track_index < transform_tracks.get_num_tracks(); ++track_index)
+	{
+		track_qvvf& track = transform_tracks[track_index];
+		new_transforms[track_index] = track_qvvf::make_reserve(track.get_description(), allocator, new_num_samples, new_sample_rate);
+	}
+
+	for (uint32_t sample_index = 0; sample_index < new_num_samples; ++sample_index)
+	{
+		const float sample_time = rtm::scalar_min(float(sample_index) / new_sample_rate, new_duration);
+
+		transform_tracks.sample_tracks(sample_time, sample_rounding_policy::none, dummy_writer);
+
+		for (uint32_t track_index = 0; track_index < new_transforms.get_num_tracks(); ++track_index)
+		{
+			track_qvvf& track = new_transforms[track_index];
+			track[sample_index] = dummy_writer.tracks_typed.qvvf[track_index];
+		}
+	}
+
+	transform_tracks = std::move(new_transforms);
+#endif
+
 	double regression_error_threshold = 0.1;
 
 #if defined(__ANDROID__)
@@ -1162,32 +1141,25 @@ static int safe_main_impl(int argc, char* argv[])
 #endif
 	{
 		// Override whatever the ACL SJSON file might have contained
-		algorithm_type = algorithm_type8::uniformly_sampled;
-		settings = CompressionSettings();
+		settings = compression_settings();
 
-		if (!read_config(allocator, options, algorithm_type, settings, regression_error_threshold))
+		if (!read_config(allocator, options, settings, regression_error_threshold))
 			return -1;
 
 		use_external_config = true;
 	}
 
-	// TODO: Make a unique_ptr
-	AnimationClip* base_clip = nullptr;
-
 	if (!is_input_acl_bin_file && sjson_type == sjson_file_type::raw_clip)
 	{
 		// Grab whatever clip we might have read from the sjson file and cast the const away so we can manage the memory
-		base_clip = const_cast<AnimationClip*>(clip->get_additive_base());
-		if (base_clip == nullptr)
+		if (base_clip.get_num_tracks() == 0 && bind_pose.get_num_samples() != 0)
 		{
-			base_clip = allocate_type<AnimationClip>(allocator, allocator, *skeleton, 1, 30.0F, String(allocator, "Base Clip"));
-
 			if (options.is_bind_pose_relative || options.is_bind_pose_additive0 || options.is_bind_pose_additive1)
-				create_additive_base_clip(options, *clip, *skeleton, *base_clip);
+				create_additive_base_clip(options, transform_tracks, bind_pose, base_clip, additive_format);
 		}
 
 		// First try to create an additive error metric
-		settings.error_metric = create_additive_error_metric(allocator, clip->get_additive_format());
+		settings.error_metric = create_additive_error_metric(allocator, additive_format);
 
 		if (settings.error_metric == nullptr)
 		{
@@ -1201,16 +1173,16 @@ static int safe_main_impl(int argc, char* argv[])
 	// Compress & Decompress
 	auto exec_algos = [&](sjson::ArrayWriter* runs_writer)
 	{
-		StatLogging logging = options.output_stats ? StatLogging::Summary : StatLogging::None;
+		stat_logging logging = options.do_output_stats ? stat_logging::Summary : stat_logging::None;
 
 		if (options.stat_detailed_output)
-			logging |= StatLogging::Detailed;
+			logging |= stat_logging::Detailed;
 
 		if (options.stat_exhaustive_output)
-			logging |= StatLogging::Exhaustive;
+			logging |= stat_logging::Exhaustive;
 
 		if (options.profile_decompression)
-			logging |= StatLogging::SummaryDecompression | StatLogging::ExhaustiveDecompression;
+			logging |= stat_logging::SummaryDecompression | stat_logging::ExhaustiveDecompression;
 
 		if (is_input_acl_bin_file)
 		{
@@ -1220,17 +1192,17 @@ static int safe_main_impl(int argc, char* argv[])
 				// Disable floating point exceptions since decompression assumes it
 				scope_disable_fp_exceptions fp_off;
 
-				const CompressionSettings default_settings = get_default_compression_settings();
+				settings = get_default_compression_settings();
 
 #if defined(__ANDROID__)
-				const CompressedClip* compressed_clip = make_compressed_clip(options.input_buffer);
+				const compressed_tracks* compressed_clip = make_compressed_tracks(options.input_buffer);
 				ACL_ASSERT(compressed_clip != nullptr, "Compressed clip is invalid");
 				if (compressed_clip == nullptr)
 					return;	// Compressed clip is invalid, early out to avoid crash
 
 				runs_writer->push([&](sjson::ObjectWriter& writer)
 				{
-					acl_impl::write_decompression_performance_stats(allocator, default_settings, *compressed_clip, logging, writer);
+					acl_impl::write_decompression_performance_stats(allocator, settings, *compressed_clip, logging, writer);
 				});
 #else
 				std::ifstream input_file_stream(options.input_filename, std::ios_base::in | std::ios_base::binary);
@@ -1240,17 +1212,17 @@ static int safe_main_impl(int argc, char* argv[])
 					const size_t buffer_size = size_t(input_file_stream.tellg());
 					input_file_stream.seekg(0, std::ios_base::beg);
 
-					char* buffer = (char*)allocator.allocate(buffer_size, alignof(CompressedClip));
+					char* buffer = (char*)allocator.allocate(buffer_size, alignof(compressed_tracks));
 					input_file_stream.read(buffer, buffer_size);
 
-					const CompressedClip* compressed_clip = make_compressed_clip(buffer);
+					const compressed_tracks* compressed_clip = make_compressed_tracks(buffer);
 					ACL_ASSERT(compressed_clip != nullptr, "Compressed clip is invalid");
 					if (compressed_clip == nullptr)
 						return;	// Compressed clip is invalid, early out to avoid crash
 
 					runs_writer->push([&](sjson::ObjectWriter& writer)
 					{
-						acl_impl::write_decompression_performance_stats(allocator, default_settings, *compressed_clip, logging, writer);
+						acl_impl::write_decompression_performance_stats(allocator, settings, *compressed_clip, logging, writer);
 					});
 
 					allocator.deallocate(buffer, buffer_size);
@@ -1263,17 +1235,15 @@ static int safe_main_impl(int argc, char* argv[])
 		{
 			if (use_external_config)
 			{
-				ACL_ASSERT(algorithm_type == algorithm_type8::uniformly_sampled, "Only uniformly_sampled is supported for now");
-
 				if (options.compression_level_specified)
 					settings.level = options.compression_level;
 
-				try_algorithm(options, allocator, *clip, settings, algorithm_type8::uniformly_sampled, logging, runs_writer, regression_error_threshold);
+				try_algorithm(options, allocator, transform_tracks, base_clip, additive_format, settings, logging, runs_writer, regression_error_threshold);
 			}
 			else if (options.exhaustive_compression)
 			{
 				{
-					CompressionSettings uniform_tests[] =
+					compression_settings uniform_tests[] =
 					{
 						make_settings(rotation_format8::quatf_full, vector_format8::vector3f_full, vector_format8::vector3f_full),
 						make_settings(rotation_format8::quatf_drop_w_full, vector_format8::vector3f_full, vector_format8::vector3f_full),
@@ -1282,16 +1252,16 @@ static int safe_main_impl(int argc, char* argv[])
 						make_settings(rotation_format8::quatf_drop_w_variable, vector_format8::vector3f_variable, vector_format8::vector3f_variable),
 					};
 
-					for (CompressionSettings test_settings : uniform_tests)
+					for (compression_settings test_settings : uniform_tests)
 					{
 						test_settings.error_metric = settings.error_metric;
 
-						try_algorithm(options, allocator, *clip, test_settings, algorithm_type8::uniformly_sampled, logging, runs_writer, regression_error_threshold);
+						try_algorithm(options, allocator, transform_tracks, base_clip, additive_format, test_settings, logging, runs_writer, regression_error_threshold);
 					}
 				}
 
 				{
-					CompressionSettings uniform_tests[] =
+					compression_settings uniform_tests[] =
 					{
 						make_settings(rotation_format8::quatf_full, vector_format8::vector3f_full, vector_format8::vector3f_full),
 						make_settings(rotation_format8::quatf_drop_w_full, vector_format8::vector3f_full, vector_format8::vector3f_full),
@@ -1300,26 +1270,26 @@ static int safe_main_impl(int argc, char* argv[])
 						make_settings(rotation_format8::quatf_drop_w_variable, vector_format8::vector3f_variable, vector_format8::vector3f_variable),
 					};
 
-					for (CompressionSettings test_settings : uniform_tests)
+					for (compression_settings test_settings : uniform_tests)
 					{
 						test_settings.error_metric = settings.error_metric;
 
 						if (options.compression_level_specified)
 							test_settings.level = options.compression_level;
 
-						try_algorithm(options, allocator, *clip, test_settings, algorithm_type8::uniformly_sampled, logging, runs_writer, regression_error_threshold);
+						try_algorithm(options, allocator, transform_tracks, base_clip, additive_format, test_settings, logging, runs_writer, regression_error_threshold);
 					}
 				}
 			}
 			else
 			{
-				CompressionSettings default_settings = get_default_compression_settings();
+				compression_settings default_settings = get_default_compression_settings();
 				default_settings.error_metric = settings.error_metric;
 
 				if (options.compression_level_specified)
 					default_settings.level = options.compression_level;
 
-				try_algorithm(options, allocator, *clip, default_settings, algorithm_type8::uniformly_sampled, logging, runs_writer, regression_error_threshold);
+				try_algorithm(options, allocator, transform_tracks, base_clip, additive_format, default_settings, logging, runs_writer, regression_error_threshold);
 			}
 		}
 		else if (sjson_type == sjson_file_type::raw_track_list)
@@ -1329,7 +1299,7 @@ static int safe_main_impl(int argc, char* argv[])
 	};
 
 #if defined(SJSON_CPP_WRITER)
-	if (options.output_stats)
+	if (options.do_output_stats)
 	{
 		sjson::FileStreamWriter stream_writer(options.output_stats_file);
 		sjson::Writer writer(stream_writer);
@@ -1341,7 +1311,6 @@ static int safe_main_impl(int argc, char* argv[])
 		exec_algos(nullptr);
 
 	deallocate_type(allocator, settings.error_metric);
-	deallocate_type(allocator, base_clip);
 #endif	// defined(ACL_USE_SJSON)
 
 	return 0;
diff --git a/tools/format_reference.acl.sjson b/tools/format_reference.acl.sjson
--- a/tools/format_reference.acl.sjson
+++ b/tools/format_reference.acl.sjson
@@ -227,12 +227,42 @@ tracks =
 		// Defaults to the track index
 		output_index = 12
 
+		// The track index of our parent track
+		// Transforms only
+		parent_index = 2
+
+		// The shell distance of the transform's error metric
+		// Transforms only
+		shell_distance = 3.0
+
+		// The constant rotation detection threshold
+		// Transforms only
+		constant_rotation_threshold_angle = 0.1
+
+		// The constant translation detection threshold
+		// Transforms only
+		constant_translation_threshold = 0.1
+
+		// The constant scale detection threshold
+		// Transforms only
+		constant_scale_threshold = 0.1
+
+		// Bind pose transform information. All three are optional
+		// The bind pose should be in parent bone local space
+		bind_rotation = [ 0.0, 0.0, 0.0, 1.0 ]
+		bind_translation = [ 0.0, 0.0, 0.0 ]
+		bind_scale = [ 1.0, 1.0, 1.0 ]
+
 		// Track data
+		// The number of samples here must match num_samples
 		data =
 		[
+			// Scalar tracks are of the form: [ x, y, z, .. ] with one entry per scalar component
 			[ 0.0, 0.0, 0.0, 1.0 ]
 			[ 1.0, 0.0, 0.0, 0.0 ]
-			// The number of samples here must match num_samples
+
+			// Transform tracks are of the form: [ [ rot.x, rot.y, rot.z, rot.z ], [ trans.x, trans.y, trans.z ], [ scale.x, scale.y, scale.z ] ]
+			// All three parts must be present and cannot be omitted
 		]
 	}
 ]
diff --git a/tools/vs_visualizers/acl.natvis b/tools/vs_visualizers/acl.natvis
--- a/tools/vs_visualizers/acl.natvis
+++ b/tools/vs_visualizers/acl.natvis
@@ -2,14 +2,6 @@
 
 <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
 
-  <Type Name="acl::Vector4_32" Priority="MediumLow">
-    <DisplayString>({x}, {y}, {z}, {w})</DisplayString>
-  </Type>
-
-  <Type Name="acl::Quat_32" Priority="MediumLow">
-    <DisplayString>({x}, {y}, {z}, {w})</DisplayString>
-  </Type>
-
   <Type Name="__m128">
     <DisplayString>({m128_f32[0]}, {m128_f32[1]}, {m128_f32[2]}, {m128_f32[3]})</DisplayString>
   </Type>
@@ -18,22 +10,6 @@
     <DisplayString>({m128i_u32[0]}, {m128i_u32[1]}, {m128i_u32[2]}, {m128i_u32[3]})</DisplayString>
   </Type>
 
-  <Type Name="acl::Vector4_64" Priority="MediumLow">
-    <DisplayString>({x}, {y}, {z}, {w})</DisplayString>
-  </Type>
-
-  <Type Name="acl::Vector4_64">
-    <DisplayString>({xy.m128d_f64[0]}, {xy.m128d_f64[1]}, {zw.m128d_f64[0]}, {zw.m128d_f64[1]})</DisplayString>
-  </Type>
-
-  <Type Name="acl::Quat_64" Priority="MediumLow">
-    <DisplayString>({x}, {y}, {z}, {w})</DisplayString>
-  </Type>
-
-  <Type Name="acl::Quat_64">
-    <DisplayString>({xy.m128d_f64[0]}, {xy.m128d_f64[1]}, {zw.m128d_f64[0]}, {zw.m128d_f64[1]})</DisplayString>
-  </Type>
-
   <Type Name="acl::String">
     <DisplayString>{m_c_str,s8}</DisplayString>
   </Type>
@@ -59,6 +35,18 @@
       <Item Name="precision" Condition="m_type == acl::track_type8::float3f">m_desc.scalar.precision</Item>
       <Item Name="precision" Condition="m_type == acl::track_type8::float4f">m_desc.scalar.precision</Item>
       <Item Name="precision" Condition="m_type == acl::track_type8::vector4f">m_desc.scalar.precision</Item>
+      <Item Name="precision" Condition="m_type == acl::track_type8::qvvf">m_desc.transform.precision</Item>
+      <Item Name="shell_distance" Condition="m_type == acl::track_type8::qvvf">m_desc.transform.shell_distance</Item>
+      <Item Name="constant_rotation_threshold_angle" Condition="m_type == acl::track_type8::qvvf">m_desc.transform.constant_rotation_threshold_angle</Item>
+      <Item Name="constant_translation_threshold" Condition="m_type == acl::track_type8::qvvf">m_desc.transform.constant_translation_threshold</Item>
+      <Item Name="constant_scale_threshold" Condition="m_type == acl::track_type8::qvvf">m_desc.transform.constant_scale_threshold</Item>
+      <Item Name="output_index" Condition="m_type == acl::track_type8::float1f">m_desc.scalar.output_index</Item>
+      <Item Name="output_index" Condition="m_type == acl::track_type8::float2f">m_desc.scalar.output_index</Item>
+      <Item Name="output_index" Condition="m_type == acl::track_type8::float3f">m_desc.scalar.output_index</Item>
+      <Item Name="output_index" Condition="m_type == acl::track_type8::float4f">m_desc.scalar.output_index</Item>
+      <Item Name="output_index" Condition="m_type == acl::track_type8::vector4f">m_desc.scalar.output_index</Item>
+      <Item Name="output_index" Condition="m_type == acl::track_type8::qvvf">m_desc.transform.output_index</Item>
+      <Item Name="parent_index" Condition="m_type == acl::track_type8::qvvf">m_desc.transform.parent_index</Item>
       <Item Name="is_ref">m_allocator == nullptr</Item>
       <ArrayItems>
         <Size>m_num_samples</Size>
@@ -67,6 +55,7 @@
         <ValuePointer Condition="m_type == acl::track_type8::float3f">(rtm::float3f*)m_data</ValuePointer>
         <ValuePointer Condition="m_type == acl::track_type8::float4f">(rtm::float4f*)m_data</ValuePointer>
         <ValuePointer Condition="m_type == acl::track_type8::vector4f">(rtm::vector4f*)m_data</ValuePointer>
+        <ValuePointer Condition="m_type == acl::track_type8::qvvf">(rtm::qvvf*)m_data</ValuePointer>
       </ArrayItems>
     </Expand>
   </Type>


Test patch:
diff --git a/test_data/reference.config.sjson b/test_data/reference.config.sjson
--- a/test_data/reference.config.sjson
+++ b/test_data/reference.config.sjson
@@ -12,22 +12,6 @@ rotation_format = "quatf_full"
 translation_format = "vector3f_full"
 scale_format = "vector3f_full"
 
-// Threshold angle value to use when detecting if a rotation track is constant
-// Defaults to '0.00284714461' radians
-constant_rotation_threshold_angle = 0.00284714461
-
-// Threshold value to use when detecting if a translation track is constant
-// Defaults to '0.001' centimeters
-constant_translation_threshold = 0.001
-
-// Threshold value to use when detecting if a scale track is constant
-// Defaults to '0.00001'
-constant_scale_threshold = 0.00001
-
-// The error threshold used when optimizing the bit rate
-// Defaults to '0.01' centimeters
-error_threshold = 0.01
-
 // The error threshold used when performing regression testing
 // We will sample the clip at various positions and compare the raw and decompressed poses
 // and fail if the error is above or equal to this threshold
diff --git a/tests/sources/core/test_ansi_allocator.cpp b/tests/sources/core/test_ansi_allocator.cpp
--- a/tests/sources/core/test_ansi_allocator.cpp
+++ b/tests/sources/core/test_ansi_allocator.cpp
@@ -35,7 +35,7 @@ using namespace acl;
 
 TEST_CASE("ANSI allocator", "[core][memory]")
 {
-	ANSIAllocator allocator;
+	ansi_allocator allocator;
 	CHECK(allocator.get_allocation_count() == 0);
 
 	void* ptr0 = allocator.allocate(32);
diff --git a/tests/sources/core/test_bitset.cpp b/tests/sources/core/test_bitset.cpp
--- a/tests/sources/core/test_bitset.cpp
+++ b/tests/sources/core/test_bitset.cpp
@@ -32,25 +32,25 @@ using namespace acl;
 
 TEST_CASE("bitset", "[core][utils]")
 {
-	CHECK(BitSetDescription::make_from_num_bits(0).get_size() == 0);
-	CHECK(BitSetDescription::make_from_num_bits(1).get_size() == 1);
-	CHECK(BitSetDescription::make_from_num_bits(31).get_size() == 1);
-	CHECK(BitSetDescription::make_from_num_bits(32).get_size() == 1);
-	CHECK(BitSetDescription::make_from_num_bits(33).get_size() == 2);
-	CHECK(BitSetDescription::make_from_num_bits(64).get_size() == 2);
-	CHECK(BitSetDescription::make_from_num_bits(65).get_size() == 3);
-
-	CHECK(BitSetDescription::make_from_num_bits(0).get_num_bits() == 0);
-	CHECK(BitSetDescription::make_from_num_bits(1).get_num_bits() == 32);
-	CHECK(BitSetDescription::make_from_num_bits(31).get_num_bits() == 32);
-	CHECK(BitSetDescription::make_from_num_bits(32).get_num_bits() == 32);
-	CHECK(BitSetDescription::make_from_num_bits(33).get_num_bits() == 64);
-	CHECK(BitSetDescription::make_from_num_bits(64).get_num_bits() == 64);
-	CHECK(BitSetDescription::make_from_num_bits(65).get_num_bits() == 96);
-
-	constexpr BitSetDescription desc = BitSetDescription::make_from_num_bits<64>();
+	CHECK(bitset_description::make_from_num_bits(0).get_size() == 0);
+	CHECK(bitset_description::make_from_num_bits(1).get_size() == 1);
+	CHECK(bitset_description::make_from_num_bits(31).get_size() == 1);
+	CHECK(bitset_description::make_from_num_bits(32).get_size() == 1);
+	CHECK(bitset_description::make_from_num_bits(33).get_size() == 2);
+	CHECK(bitset_description::make_from_num_bits(64).get_size() == 2);
+	CHECK(bitset_description::make_from_num_bits(65).get_size() == 3);
+
+	CHECK(bitset_description::make_from_num_bits(0).get_num_bits() == 0);
+	CHECK(bitset_description::make_from_num_bits(1).get_num_bits() == 32);
+	CHECK(bitset_description::make_from_num_bits(31).get_num_bits() == 32);
+	CHECK(bitset_description::make_from_num_bits(32).get_num_bits() == 32);
+	CHECK(bitset_description::make_from_num_bits(33).get_num_bits() == 64);
+	CHECK(bitset_description::make_from_num_bits(64).get_num_bits() == 64);
+	CHECK(bitset_description::make_from_num_bits(65).get_num_bits() == 96);
+
+	constexpr bitset_description desc = bitset_description::make_from_num_bits<64>();
 	CHECK(desc.get_size() == 2);
-	CHECK(desc.get_size() == BitSetDescription::make_from_num_bits(64).get_size());
+	CHECK(desc.get_size() == bitset_description::make_from_num_bits(64).get_size());
 
 	uint32_t bitset_data[desc.get_size() + 1];	// Add padding
 	std::memset(&bitset_data[0], 0, sizeof(bitset_data));
diff --git a/tests/sources/core/test_error_result.cpp b/tests/sources/core/test_error_result.cpp
--- a/tests/sources/core/test_error_result.cpp
+++ b/tests/sources/core/test_error_result.cpp
@@ -31,17 +31,17 @@
 
 using namespace acl;
 
-TEST_CASE("ErrorResult", "[core][error]")
+TEST_CASE("error_result", "[core][error]")
 {
-	CHECK(ErrorResult().any() == false);
-	CHECK(ErrorResult().empty() == true);
-	CHECK(std::strlen(ErrorResult().c_str()) == 0);
+	CHECK(error_result().any() == false);
+	CHECK(error_result().empty() == true);
+	CHECK(std::strlen(error_result().c_str()) == 0);
 
-	CHECK(ErrorResult("failed").any() == true);
-	CHECK(ErrorResult("failed").empty() == false);
-	CHECK(std::strcmp(ErrorResult("failed").c_str(), "failed") == 0);
+	CHECK(error_result("failed").any() == true);
+	CHECK(error_result("failed").empty() == false);
+	CHECK(std::strcmp(error_result("failed").c_str(), "failed") == 0);
 
-	ErrorResult tmp("failed");
+	error_result tmp("failed");
 	CHECK(tmp.any() == true);
 	tmp.reset();
 	CHECK(tmp.any() == false);
diff --git a/tests/sources/core/test_iterator.cpp b/tests/sources/core/test_iterator.cpp
--- a/tests/sources/core/test_iterator.cpp
+++ b/tests/sources/core/test_iterator.cpp
@@ -36,7 +36,7 @@ TEST_CASE("iterator", "[core][iterator]")
 	constexpr uint32_t num_items = 3;
 	uint32_t items[num_items];
 
-	auto i = Iterator<uint32_t>(items, num_items);
+	auto i = iterator<uint32_t>(items, num_items);
 
 	SECTION("mutable returns correct type")
 	{
@@ -46,7 +46,7 @@ TEST_CASE("iterator", "[core][iterator]")
 
 	SECTION("const returns correct type")
 	{
-		auto ci = ConstIterator<uint32_t>(items, num_items);
+		auto ci = const_iterator<uint32_t>(items, num_items);
 
 		CHECK(std::is_same<const uint32_t*, decltype(ci.begin())>::value);
 		CHECK(std::is_same<const uint32_t*, decltype(ci.end())>::value);
diff --git a/tests/sources/core/test_ptr_offset.cpp b/tests/sources/core/test_ptr_offset.cpp
--- a/tests/sources/core/test_ptr_offset.cpp
+++ b/tests/sources/core/test_ptr_offset.cpp
@@ -30,9 +30,9 @@ using namespace acl;
 
 TEST_CASE("ptr_offset", "[core][memory]")
 {
-	CHECK(PtrOffset32<uint8_t>(InvalidPtrOffset()).is_valid() == false);
+	CHECK(ptr_offset32<uint8_t>(invalid_ptr_offset()).is_valid() == false);
 
-	PtrOffset32<uint8_t> offset(32);
+	ptr_offset32<uint8_t> offset(32);
 	CHECK(offset.is_valid() == true);
 
 	uint8_t* ptr = nullptr;
diff --git a/tests/sources/core/test_string.cpp b/tests/sources/core/test_string.cpp
--- a/tests/sources/core/test_string.cpp
+++ b/tests/sources/core/test_string.cpp
@@ -35,31 +35,31 @@
 
 using namespace acl;
 
-TEST_CASE("String", "[core][string]")
+TEST_CASE("string", "[core][string]")
 {
-	ANSIAllocator allocator;
+	ansi_allocator allocator;
 
-	CHECK(String().size() == 0);
-	CHECK(String().c_str() != nullptr);
-	CHECK(String(allocator, "").size() == 0);
-	CHECK(String(allocator, "").c_str() != nullptr);
+	CHECK(string().size() == 0);
+	CHECK(string().c_str() != nullptr);
+	CHECK(string(allocator, "").size() == 0);
+	CHECK(string(allocator, "").c_str() != nullptr);
 
 	const char* str0 = "this is a test string";
 	const char* str1 = "this is not a test string";
 	const char* str2 = "this is a test asset!";
 
-	CHECK(String(allocator, str0) == str0);
-	CHECK(String(allocator, str0) != str1);
-	CHECK(String(allocator, str0) != str2);
-	CHECK(String(allocator, str0) == String(allocator, str0));
-	CHECK(String(allocator, str0) != String(allocator, str1));
-	CHECK(String(allocator, str0) != String(allocator, str2));
-	CHECK(String(allocator, str0).c_str() != str0);
-	CHECK(String(allocator, str0).size() == std::strlen(str0));
-	CHECK(String(allocator, str0, 4) == String(allocator, str1, 4));
-	CHECK(String(allocator, str0, 4) == "this");
+	CHECK(string(allocator, str0) == str0);
+	CHECK(string(allocator, str0) != str1);
+	CHECK(string(allocator, str0) != str2);
+	CHECK(string(allocator, str0) == string(allocator, str0));
+	CHECK(string(allocator, str0) != string(allocator, str1));
+	CHECK(string(allocator, str0) != string(allocator, str2));
+	CHECK(string(allocator, str0).c_str() != str0);
+	CHECK(string(allocator, str0).size() == std::strlen(str0));
+	CHECK(string(allocator, str0, 4) == string(allocator, str1, 4));
+	CHECK(string(allocator, str0, 4) == "this");
 
-	CHECK(String().empty() == true);
-	CHECK(String(allocator, "").empty() == true);
-	CHECK(String(allocator, str0).empty() == false);
+	CHECK(string().empty() == true);
+	CHECK(string(allocator, "").empty() == true);
+	CHECK(string(allocator, str0).empty() == false);
 }
diff --git a/tests/sources/io/test_reader_writer.cpp b/tests/sources/io/test_reader_writer.cpp
--- a/tests/sources/io/test_reader_writer.cpp
+++ b/tests/sources/io/test_reader_writer.cpp
@@ -135,78 +135,60 @@ TEST_CASE("sjson_clip_reader_writer", "[io]")
 {
 	// Only test the reader/writer on non-mobile platforms
 #if defined(RTM_SSE2_INTRINSICS) && defined(ACL_USE_SJSON)
-	ANSIAllocator allocator;
+	ansi_allocator allocator;
 
-	const uint16_t num_bones = 3;
-	RigidBone bones[num_bones];
-	bones[0].name = String(allocator, "root");
-	bones[0].vertex_distance = 4.0F;
-	bones[0].parent_index = k_invalid_bone_index;
-	bones[0].bind_transform = rtm::qvv_identity();
+	const uint32_t num_tracks = 3;
+	const uint32_t num_samples = 4;
+	track_array_qvvf track_list(allocator, num_tracks);
 
-	bones[1].name = String(allocator, "bone1");
-	bones[1].vertex_distance = 3.0F;
-	bones[1].parent_index = 0;
-	bones[1].bind_transform = rtm::qvv_set(rtm::quat_from_axis_angle(rtm::vector_set(0.0, 1.0, 0.0), rtm::constants::pi() * 0.5), rtm::vector_set(3.2, 8.2, 5.1), rtm::vector_set(1.0));
+	track_desc_transformf desc0;
+	desc0.output_index = 0;
+	desc0.precision = 0.001F;
+	track_qvvf track0 = track_qvvf::make_reserve(desc0, allocator, num_samples, 32.0F);
+	track0[0].rotation = rtm::quat_from_euler(0.1F, 0.5F, 1.2F);
+	track0[0].translation = rtm::vector_set(0.0F, 0.6F, 2.3F);
+	track0[0].scale = rtm::vector_set(1.4F, 2.1F, 0.2F);
+	for (uint32_t i = 1; i < num_samples; ++i)
+	{
+		track0[i].rotation = rtm::quat_lerp(rtm::quat_identity(), track0[0].rotation, 0.1F * float(i));
+		track0[i].translation = rtm::vector_lerp(rtm::vector_zero(), track0[0].translation, 0.1F * float(i));
+		track0[i].scale = rtm::vector_lerp(rtm::vector_zero(), track0[0].scale, 0.1F * float(i));
+	}
+	track_list[0] = track0.get_ref();
 
-	bones[2].name = String(allocator, "bone2");
-	bones[2].vertex_distance = 2.0F;
-	bones[2].parent_index = 1;
-	bones[2].bind_transform = rtm::qvv_set(rtm::quat_from_axis_angle(rtm::vector_set(0.0, 0.0, 1.0), rtm::constants::pi() * 0.25), rtm::vector_set(6.3, 9.4, 1.5), rtm::vector_set(1.0));
+	track_desc_transformf desc1;
+	desc1.output_index = 0;
+	desc1.precision = 0.001F;
+	desc1.parent_index = 0;
+	desc1.shell_distance = 0.1241F;
+	desc1.constant_rotation_threshold_angle = 21.0F;
+	desc1.constant_translation_threshold = 0.11F;
+	desc1.constant_scale_threshold = 12.0F;
+	track_qvvf track1 = track_qvvf::make_reserve(desc1, allocator, num_samples, 32.0F);
+	track1[0].rotation = rtm::quat_from_euler(1.1F, 1.5F, 1.7F);
+	track1[0].translation = rtm::vector_set(0.0221F, 10.6F, 22.3F);
+	track1[0].scale = rtm::vector_set(1.451F, 24.1F, 10.2F);
+	for (uint32_t i = 1; i < num_samples; ++i)
+	{
+		track1[i].rotation = rtm::quat_lerp(rtm::quat_identity(), track1[0].rotation, 0.1F * float(i));
+		track1[i].translation = rtm::vector_lerp(rtm::vector_zero(), track1[0].translation, 0.1F * float(i));
+		track1[i].scale = rtm::vector_lerp(rtm::vector_zero(), track1[0].scale, 0.1F * float(i));
+	}
+	track_list[1] = track1.get_ref();
 
-	RigidSkeleton skeleton(allocator, bones, num_bones);
+	track_qvvf track2 = track_qvvf::make_reserve(desc1, allocator, num_samples, 32.0F);
+	track2[0].rotation = rtm::quat_from_euler(1.11F, 1.5333F, 0.17F);
+	track2[0].translation = rtm::vector_set(30.0221F, 101.6F, 22.3214F);
+	track2[0].scale = rtm::vector_set(21.451F, 244.1F, 100.2F);
+	for (uint32_t i = 1; i < num_samples; ++i)
+	{
+		track2[i].rotation = rtm::quat_lerp(rtm::quat_identity(), track2[0].rotation, 0.1F * float(i));
+		track2[i].translation = rtm::vector_lerp(rtm::vector_zero(), track2[0].translation, 0.1F * float(i));
+		track2[i].scale = rtm::vector_lerp(rtm::vector_zero(), track2[0].scale, 0.1F * float(i));
+	}
+	track_list[2] = track2.get_ref();
 
-	const uint32_t num_samples = 4;
-	AnimationClip clip(allocator, skeleton, num_samples, 30.0F, String(allocator, "test_clip"));
-
-	AnimatedBone* animated_bones = clip.get_bones();
-	animated_bones[0].output_index = 0;
-	animated_bones[0].rotation_track.set_sample(0, rtm::quat_from_axis_angle(rtm::vector_set(0.0, 1.0, 0.0), rtm::constants::pi() * 0.1));
-	animated_bones[0].rotation_track.set_sample(1, rtm::quat_from_axis_angle(rtm::vector_set(0.0, 1.0, 0.0), rtm::constants::pi() * 0.2));
-	animated_bones[0].rotation_track.set_sample(2, rtm::quat_from_axis_angle(rtm::vector_set(0.0, 1.0, 0.0), rtm::constants::pi() * 0.3));
-	animated_bones[0].rotation_track.set_sample(3, rtm::quat_from_axis_angle(rtm::vector_set(0.0, 1.0, 0.0), rtm::constants::pi() * 0.4));
-	animated_bones[0].translation_track.set_sample(0, rtm::vector_set(3.2, 1.4, 9.4));
-	animated_bones[0].translation_track.set_sample(1, rtm::vector_set(3.3, 1.5, 9.5));
-	animated_bones[0].translation_track.set_sample(2, rtm::vector_set(3.4, 1.6, 9.6));
-	animated_bones[0].translation_track.set_sample(3, rtm::vector_set(3.5, 1.7, 9.7));
-	animated_bones[0].scale_track.set_sample(0, rtm::vector_set(1.0, 1.5, 1.1));
-	animated_bones[0].scale_track.set_sample(1, rtm::vector_set(1.1, 1.6, 1.2));
-	animated_bones[0].scale_track.set_sample(2, rtm::vector_set(1.2, 1.7, 1.3));
-	animated_bones[0].scale_track.set_sample(3, rtm::vector_set(1.3, 1.8, 1.4));
-
-	animated_bones[1].output_index = 2;
-	animated_bones[1].rotation_track.set_sample(0, rtm::quat_from_axis_angle(rtm::vector_set(0.0, 1.0, 0.0), rtm::constants::pi() * 1.1));
-	animated_bones[1].rotation_track.set_sample(1, rtm::quat_from_axis_angle(rtm::vector_set(0.0, 1.0, 0.0), rtm::constants::pi() * 1.2));
-	animated_bones[1].rotation_track.set_sample(2, rtm::quat_from_axis_angle(rtm::vector_set(0.0, 1.0, 0.0), rtm::constants::pi() * 1.3));
-	animated_bones[1].rotation_track.set_sample(3, rtm::quat_from_axis_angle(rtm::vector_set(0.0, 1.0, 0.0), rtm::constants::pi() * 1.4));
-	animated_bones[1].translation_track.set_sample(0, rtm::vector_set(5.2, 2.4, 13.4));
-	animated_bones[1].translation_track.set_sample(1, rtm::vector_set(5.3, 2.5, 13.5));
-	animated_bones[1].translation_track.set_sample(2, rtm::vector_set(5.4, 2.6, 13.6));
-	animated_bones[1].translation_track.set_sample(3, rtm::vector_set(5.5, 2.7, 13.7));
-	animated_bones[1].scale_track.set_sample(0, rtm::vector_set(2.0, 0.5, 4.1));
-	animated_bones[1].scale_track.set_sample(1, rtm::vector_set(2.1, 0.6, 4.2));
-	animated_bones[1].scale_track.set_sample(2, rtm::vector_set(2.2, 0.7, 4.3));
-	animated_bones[1].scale_track.set_sample(3, rtm::vector_set(2.3, 0.8, 4.4));
-
-	animated_bones[2].output_index = 1;
-	animated_bones[2].rotation_track.set_sample(0, rtm::quat_from_axis_angle(rtm::vector_set(0.0, 0.0, 1.0), rtm::constants::pi() * 0.7));
-	animated_bones[2].rotation_track.set_sample(1, rtm::quat_from_axis_angle(rtm::vector_set(0.0, 0.0, 1.0), rtm::constants::pi() * 0.8));
-	animated_bones[2].rotation_track.set_sample(2, rtm::quat_from_axis_angle(rtm::vector_set(0.0, 0.0, 1.0), rtm::constants::pi() * 0.9));
-	animated_bones[2].rotation_track.set_sample(3, rtm::quat_from_axis_angle(rtm::vector_set(0.0, 0.0, 1.0), rtm::constants::pi() * 0.4));
-	animated_bones[2].translation_track.set_sample(0, rtm::vector_set(1.2, 123.4, 11.4));
-	animated_bones[2].translation_track.set_sample(1, rtm::vector_set(1.3, 123.5, 11.5));
-	animated_bones[2].translation_track.set_sample(2, rtm::vector_set(1.4, 123.6, 11.6));
-	animated_bones[2].translation_track.set_sample(3, rtm::vector_set(1.5, 123.7, 11.7));
-	animated_bones[2].scale_track.set_sample(0, rtm::vector_set(4.0, 2.5, 3.1));
-	animated_bones[2].scale_track.set_sample(1, rtm::vector_set(4.1, 2.6, 3.2));
-	animated_bones[2].scale_track.set_sample(2, rtm::vector_set(4.2, 2.7, 3.3));
-	animated_bones[2].scale_track.set_sample(3, rtm::vector_set(4.3, 2.8, 3.4));
-
-	CompressionSettings settings;
-	settings.constant_rotation_threshold_angle = 32.23F;
-	settings.constant_scale_threshold = 1.123F;
-	settings.constant_translation_threshold = 0.124F;
-	settings.error_threshold = 0.23F;
+	compression_settings settings;
 	settings.level = compression_level8::high;
 	settings.rotation_format = rotation_format8::quatf_drop_w_variable;
 	settings.scale_format = vector_format8::vector3f_variable;
@@ -217,18 +199,18 @@ TEST_CASE("sjson_clip_reader_writer", "[io]")
 	const uint32_t filename_size = k_max_filename_size;
 	char filename[filename_size] = { 0 };
 
-	const char* error = nullptr;
+	error_result error;
 	for (uint32_t try_count = 0; try_count < 20; ++try_count)
 	{
 		get_temporary_filename(filename, filename_size, "clip_");
 
 		// Write the clip to a temporary file
-		error = write_acl_clip(skeleton, clip, algorithm_type8::uniformly_sampled, settings, filename);
+		error = write_track_list(track_list, settings, filename);
 
-		if (error == nullptr)
+		if (error.empty())
 			break;	// Everything worked, stop trying
 	}
-	REQUIRE(error == nullptr);
+	REQUIRE(error.empty());
 
 	std::FILE* file = nullptr;
 
@@ -255,44 +237,49 @@ TEST_CASE("sjson_clip_reader_writer", "[io]")
 	std::remove(filename);
 
 	// Read back the clip
-	ClipReader reader(allocator, sjson_file_buffer, clip_size - 1);
+	clip_reader reader(allocator, sjson_file_buffer, clip_size - 1);
 
-	REQUIRE(reader.get_file_type() == sjson_file_type::raw_clip);
+	REQUIRE(reader.get_file_type() == sjson_file_type::raw_track_list);
 
-	sjson_raw_clip file_clip;
-	const bool success = reader.read_raw_clip(file_clip);
+	sjson_raw_track_list file_clip;
+	const bool success = reader.read_raw_track_list(file_clip);
 	REQUIRE(success);
 
-	CHECK(file_clip.algorithm_type == algorithm_type8::uniformly_sampled);
 	CHECK(file_clip.has_settings);
 	CHECK(file_clip.settings.get_hash() == settings.get_hash());
-	CHECK(file_clip.skeleton->get_num_bones() == num_bones);
-	CHECK(file_clip.clip->get_num_bones() == num_bones);
-	CHECK(file_clip.clip->get_name() == clip.get_name());
-	CHECK(rtm::scalar_near_equal(file_clip.clip->get_duration(), clip.get_duration(), 1.0E-8F));
-	CHECK(file_clip.clip->get_num_samples() == clip.get_num_samples());
-	CHECK(file_clip.clip->get_sample_rate() == clip.get_sample_rate());
-
-	for (uint16_t bone_index = 0; bone_index < num_bones; ++bone_index)
+
+	CHECK(file_clip.track_list.get_num_samples_per_track() == track_list.get_num_samples_per_track());
+	CHECK(file_clip.track_list.get_sample_rate() == track_list.get_sample_rate());
+	CHECK(file_clip.track_list.get_num_tracks() == track_list.get_num_tracks());
+	CHECK(rtm::scalar_near_equal(file_clip.track_list.get_duration(), track_list.get_duration(), 1.0E-8F));
+	CHECK(file_clip.track_list.get_track_type() == track_list.get_track_type());
+	CHECK(file_clip.track_list.get_track_category() == track_list.get_track_category());
+
+	for (uint32_t track_index = 0; track_index < num_tracks; ++track_index)
 	{
-		const RigidBone& src_bone = skeleton.get_bone(bone_index);
-		const RigidBone& file_bone = file_clip.skeleton->get_bone(bone_index);
-		CHECK(src_bone.name == file_bone.name);
-		CHECK(src_bone.vertex_distance == file_bone.vertex_distance);
-		CHECK(src_bone.parent_index == file_bone.parent_index);
-		CHECK(rtm::quat_near_equal(src_bone.bind_transform.rotation, file_bone.bind_transform.rotation, 0.0));
-		CHECK(rtm::vector_all_near_equal3(src_bone.bind_transform.translation, file_bone.bind_transform.translation, 0.0));
-		CHECK(rtm::vector_all_near_equal3(src_bone.bind_transform.scale, file_bone.bind_transform.scale, 0.0));
-
-		const AnimatedBone& src_animated_bone = clip.get_animated_bone(bone_index);
-		const AnimatedBone& file_animated_bone = file_clip.clip->get_animated_bone(bone_index);
-		//REQUIRE(src_animated_bone.output_index == file_animated_bone.output_index);
+		const track_qvvf& ref_track = track_cast<track_qvvf>(track_list[track_index]);
+		const track_qvvf& file_track = track_cast<track_qvvf>(file_clip.track_list[track_index]);
+
+		CHECK(file_track.get_description().output_index == ref_track.get_description().output_index);
+		CHECK(file_track.get_description().parent_index == ref_track.get_description().parent_index);
+		CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 0.0F));
+		CHECK(rtm::scalar_near_equal(file_track.get_description().shell_distance, ref_track.get_description().shell_distance, 0.0F));
+		CHECK(rtm::scalar_near_equal(file_track.get_description().constant_rotation_threshold_angle, ref_track.get_description().constant_rotation_threshold_angle, 0.0F));
+		CHECK(rtm::scalar_near_equal(file_track.get_description().constant_translation_threshold, ref_track.get_description().constant_translation_threshold, 0.0F));
+		CHECK(rtm::scalar_near_equal(file_track.get_description().constant_scale_threshold, ref_track.get_description().constant_scale_threshold, 0.0F));
+		CHECK(file_track.get_num_samples() == ref_track.get_num_samples());
+		CHECK(file_track.get_output_index() == ref_track.get_output_index());
+		CHECK(file_track.get_sample_rate() == ref_track.get_sample_rate());
+		CHECK(file_track.get_type() == ref_track.get_type());
+		CHECK(file_track.get_category() == ref_track.get_category());
 
 		for (uint32_t sample_index = 0; sample_index < num_samples; ++sample_index)
 		{
-			CHECK(rtm::quat_near_equal(src_animated_bone.rotation_track.get_sample(sample_index), file_animated_bone.rotation_track.get_sample(sample_index), 0.0));
-			CHECK(rtm::vector_all_near_equal3(src_animated_bone.translation_track.get_sample(sample_index), file_animated_bone.translation_track.get_sample(sample_index), 0.0));
-			CHECK(rtm::vector_all_near_equal3(src_animated_bone.scale_track.get_sample(sample_index), file_animated_bone.scale_track.get_sample(sample_index), 0.0));
+			const rtm::qvvf& ref_sample = ref_track[sample_index];
+			const rtm::qvvf& file_sample = file_track[sample_index];
+			CHECK(rtm::quat_near_equal(ref_sample.rotation, file_sample.rotation, 0.0F));
+			CHECK(rtm::vector_all_near_equal3(ref_sample.translation, file_sample.translation, 0.0F));
+			CHECK(rtm::vector_all_near_equal3(ref_sample.scale, file_sample.scale, 0.0F));
 		}
 	}
 #endif
@@ -302,7 +289,7 @@ TEST_CASE("sjson_track_list_reader_writer float1f", "[io]")
 {
 	// Only test the reader/writer on non-mobile platforms
 #if defined(RTM_SSE2_INTRINSICS) && defined(ACL_USE_SJSON)
-	ANSIAllocator allocator;
+	ansi_allocator allocator;
 
 	const uint32_t num_tracks = 3;
 	const uint32_t num_samples = 4;
@@ -333,7 +320,7 @@ TEST_CASE("sjson_track_list_reader_writer float1f", "[io]")
 	const uint32_t filename_size = k_max_filename_size;
 	char filename[filename_size] = { 0 };
 
-	const char* error = nullptr;
+	error_result error;
 	for (uint32_t try_count = 0; try_count < 20; ++try_count)
 	{
 		get_temporary_filename(filename, filename_size, "list_float1f_");
@@ -341,10 +328,10 @@ TEST_CASE("sjson_track_list_reader_writer float1f", "[io]")
 		// Write the clip to a temporary file
 		error = write_track_list(track_list, filename);
 
-		if (error == nullptr)
+		if (error.empty())
 			break;	// Everything worked, stop trying
 	}
-	REQUIRE(error == nullptr);
+	REQUIRE(error.empty());
 
 	std::FILE* file = nullptr;
 	for (uint32_t try_count = 0; try_count < 20; ++try_count)
@@ -370,7 +357,7 @@ TEST_CASE("sjson_track_list_reader_writer float1f", "[io]")
 	std::remove(filename);
 
 	// Read back the clip
-	ClipReader reader(allocator, sjson_file_buffer, buffer_size - 1);
+	clip_reader reader(allocator, sjson_file_buffer, buffer_size - 1);
 
 	REQUIRE(reader.get_file_type() == sjson_file_type::raw_track_list);
 
@@ -391,7 +378,7 @@ TEST_CASE("sjson_track_list_reader_writer float1f", "[io]")
 		const track_float1f& file_track = track_cast<track_float1f>(file_track_list.track_list[track_index]);
 
 		CHECK(file_track.get_description().output_index == ref_track.get_description().output_index);
-		CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 1.0E-8F));
+		CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 0.0F));
 		CHECK(file_track.get_num_samples() == ref_track.get_num_samples());
 		CHECK(file_track.get_output_index() == ref_track.get_output_index());
 		CHECK(file_track.get_sample_rate() == ref_track.get_sample_rate());
@@ -402,7 +389,7 @@ TEST_CASE("sjson_track_list_reader_writer float1f", "[io]")
 		{
 			const float ref_sample = ref_track[sample_index];
 			const float file_sample = file_track[sample_index];
-			CHECK(rtm::scalar_near_equal(ref_sample, file_sample, 1.0E-8F));
+			CHECK(rtm::scalar_near_equal(ref_sample, file_sample, 0.0F));
 		}
 	}
 #endif
@@ -412,7 +399,7 @@ TEST_CASE("sjson_track_list_reader_writer float2f", "[io]")
 {
 	// Only test the reader/writer on non-mobile platforms
 #if defined(RTM_SSE2_INTRINSICS) && defined(ACL_USE_SJSON)
-	ANSIAllocator allocator;
+	ansi_allocator allocator;
 
 	const uint32_t num_tracks = 3;
 	const uint32_t num_samples = 4;
@@ -443,7 +430,7 @@ TEST_CASE("sjson_track_list_reader_writer float2f", "[io]")
 	const uint32_t filename_size = k_max_filename_size;
 	char filename[filename_size] = { 0 };
 
-	const char* error = nullptr;
+	error_result error;
 	for (uint32_t try_count = 0; try_count < 20; ++try_count)
 	{
 		get_temporary_filename(filename, filename_size, "list_float2f_");
@@ -451,10 +438,10 @@ TEST_CASE("sjson_track_list_reader_writer float2f", "[io]")
 		// Write the clip to a temporary file
 		error = write_track_list(track_list, filename);
 
-		if (error == nullptr)
+		if (error.empty())
 			break;	// Everything worked, stop trying
 	}
-	REQUIRE(error == nullptr);
+	REQUIRE(error.empty());
 
 	std::FILE* file = nullptr;
 	for (uint32_t try_count = 0; try_count < 20; ++try_count)
@@ -480,7 +467,7 @@ TEST_CASE("sjson_track_list_reader_writer float2f", "[io]")
 	std::remove(filename);
 
 	// Read back the clip
-	ClipReader reader(allocator, sjson_file_buffer, buffer_size - 1);
+	clip_reader reader(allocator, sjson_file_buffer, buffer_size - 1);
 
 	REQUIRE(reader.get_file_type() == sjson_file_type::raw_track_list);
 
@@ -501,7 +488,7 @@ TEST_CASE("sjson_track_list_reader_writer float2f", "[io]")
 		const track_float2f& file_track = track_cast<track_float2f>(file_track_list.track_list[track_index]);
 
 		CHECK(file_track.get_description().output_index == ref_track.get_description().output_index);
-		CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 1.0E-8F));
+		CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 0.0F));
 		CHECK(file_track.get_num_samples() == ref_track.get_num_samples());
 		CHECK(file_track.get_output_index() == ref_track.get_output_index());
 		CHECK(file_track.get_sample_rate() == ref_track.get_sample_rate());
@@ -512,7 +499,7 @@ TEST_CASE("sjson_track_list_reader_writer float2f", "[io]")
 		{
 			const rtm::float2f& ref_sample = ref_track[sample_index];
 			const rtm::float2f& file_sample = file_track[sample_index];
-			CHECK(rtm::vector_all_near_equal2(rtm::vector_load2(&ref_sample), rtm::vector_load2(&file_sample), 1.0E-8F));
+			CHECK(rtm::vector_all_near_equal2(rtm::vector_load2(&ref_sample), rtm::vector_load2(&file_sample), 0.0F));
 		}
 	}
 #endif
@@ -522,7 +509,7 @@ TEST_CASE("sjson_track_list_reader_writer float3f", "[io]")
 {
 	// Only test the reader/writer on non-mobile platforms
 #if defined(RTM_SSE2_INTRINSICS) && defined(ACL_USE_SJSON)
-	ANSIAllocator allocator;
+	ansi_allocator allocator;
 
 	const uint32_t num_tracks = 3;
 	const uint32_t num_samples = 4;
@@ -553,7 +540,7 @@ TEST_CASE("sjson_track_list_reader_writer float3f", "[io]")
 	const uint32_t filename_size = k_max_filename_size;
 	char filename[filename_size] = { 0 };
 
-	const char* error = nullptr;
+	error_result error;
 	for (uint32_t try_count = 0; try_count < 20; ++try_count)
 	{
 		get_temporary_filename(filename, filename_size, "list_float3f_");
@@ -561,10 +548,10 @@ TEST_CASE("sjson_track_list_reader_writer float3f", "[io]")
 		// Write the clip to a temporary file
 		error = write_track_list(track_list, filename);
 
-		if (error == nullptr)
+		if (error.empty())
 			break;	// Everything worked, stop trying
 	}
-	REQUIRE(error == nullptr);
+	REQUIRE(error.empty());
 
 	std::FILE* file = nullptr;
 	for (uint32_t try_count = 0; try_count < 20; ++try_count)
@@ -590,7 +577,7 @@ TEST_CASE("sjson_track_list_reader_writer float3f", "[io]")
 	std::remove(filename);
 
 	// Read back the clip
-	ClipReader reader(allocator, sjson_file_buffer, buffer_size - 1);
+	clip_reader reader(allocator, sjson_file_buffer, buffer_size - 1);
 
 	REQUIRE(reader.get_file_type() == sjson_file_type::raw_track_list);
 
@@ -611,7 +598,7 @@ TEST_CASE("sjson_track_list_reader_writer float3f", "[io]")
 		const track_float3f& file_track = track_cast<track_float3f>(file_track_list.track_list[track_index]);
 
 		CHECK(file_track.get_description().output_index == ref_track.get_description().output_index);
-		CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 1.0E-8F));
+		CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 0.0F));
 		CHECK(file_track.get_num_samples() == ref_track.get_num_samples());
 		CHECK(file_track.get_output_index() == ref_track.get_output_index());
 		CHECK(file_track.get_sample_rate() == ref_track.get_sample_rate());
@@ -622,7 +609,7 @@ TEST_CASE("sjson_track_list_reader_writer float3f", "[io]")
 		{
 			const rtm::float3f& ref_sample = ref_track[sample_index];
 			const rtm::float3f& file_sample = file_track[sample_index];
-			CHECK(rtm::vector_all_near_equal3(rtm::vector_load3(&ref_sample), rtm::vector_load3(&file_sample), 1.0E-8F));
+			CHECK(rtm::vector_all_near_equal3(rtm::vector_load3(&ref_sample), rtm::vector_load3(&file_sample), 0.0F));
 		}
 	}
 #endif
@@ -632,7 +619,7 @@ TEST_CASE("sjson_track_list_reader_writer float4f", "[io]")
 {
 	// Only test the reader/writer on non-mobile platforms
 #if defined(RTM_SSE2_INTRINSICS) && defined(ACL_USE_SJSON)
-	ANSIAllocator allocator;
+	ansi_allocator allocator;
 
 	const uint32_t num_tracks = 3;
 	const uint32_t num_samples = 4;
@@ -663,7 +650,7 @@ TEST_CASE("sjson_track_list_reader_writer float4f", "[io]")
 	const uint32_t filename_size = k_max_filename_size;
 	char filename[filename_size] = { 0 };
 
-	const char* error = nullptr;
+	error_result error;
 	for (uint32_t try_count = 0; try_count < 20; ++try_count)
 	{
 		get_temporary_filename(filename, filename_size, "list_float4f_");
@@ -671,10 +658,10 @@ TEST_CASE("sjson_track_list_reader_writer float4f", "[io]")
 		// Write the clip to a temporary file
 		error = write_track_list(track_list, filename);
 
-		if (error == nullptr)
+		if (error.empty())
 			break;	// Everything worked, stop trying
 	}
-	REQUIRE(error == nullptr);
+	REQUIRE(error.empty());
 
 	std::FILE* file = nullptr;
 	for (uint32_t try_count = 0; try_count < 20; ++try_count)
@@ -700,7 +687,7 @@ TEST_CASE("sjson_track_list_reader_writer float4f", "[io]")
 	std::remove(filename);
 
 	// Read back the clip
-	ClipReader reader(allocator, sjson_file_buffer, buffer_size - 1);
+	clip_reader reader(allocator, sjson_file_buffer, buffer_size - 1);
 
 	REQUIRE(reader.get_file_type() == sjson_file_type::raw_track_list);
 
@@ -721,7 +708,7 @@ TEST_CASE("sjson_track_list_reader_writer float4f", "[io]")
 		const track_float4f& file_track = track_cast<track_float4f>(file_track_list.track_list[track_index]);
 
 		CHECK(file_track.get_description().output_index == ref_track.get_description().output_index);
-		CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 1.0E-8F));
+		CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 0.0F));
 		CHECK(file_track.get_num_samples() == ref_track.get_num_samples());
 		CHECK(file_track.get_output_index() == ref_track.get_output_index());
 		CHECK(file_track.get_sample_rate() == ref_track.get_sample_rate());
@@ -732,7 +719,7 @@ TEST_CASE("sjson_track_list_reader_writer float4f", "[io]")
 		{
 			const rtm::float4f& ref_sample = ref_track[sample_index];
 			const rtm::float4f& file_sample = file_track[sample_index];
-			CHECK(rtm::vector_all_near_equal(rtm::vector_load(&ref_sample), rtm::vector_load(&file_sample), 1.0E-8F));
+			CHECK(rtm::vector_all_near_equal(rtm::vector_load(&ref_sample), rtm::vector_load(&file_sample), 0.0F));
 		}
 	}
 #endif
@@ -742,7 +729,7 @@ TEST_CASE("sjson_track_list_reader_writer vector4f", "[io]")
 {
 	// Only test the reader/writer on non-mobile platforms
 #if defined(RTM_SSE2_INTRINSICS) && defined(ACL_USE_SJSON)
-	ANSIAllocator allocator;
+	ansi_allocator allocator;
 
 	const uint32_t num_tracks = 3;
 	const uint32_t num_samples = 4;
@@ -773,7 +760,7 @@ TEST_CASE("sjson_track_list_reader_writer vector4f", "[io]")
 	const uint32_t filename_size = k_max_filename_size;
 	char filename[filename_size] = { 0 };
 
-	const char* error = nullptr;
+	error_result error;
 	for (uint32_t try_count = 0; try_count < 20; ++try_count)
 	{
 		get_temporary_filename(filename, filename_size, "list_vector4f_");
@@ -781,10 +768,10 @@ TEST_CASE("sjson_track_list_reader_writer vector4f", "[io]")
 		// Write the clip to a temporary file
 		error = write_track_list(track_list, filename);
 
-		if (error == nullptr)
+		if (error.empty())
 			break;	// Everything worked, stop trying
 	}
-	REQUIRE(error == nullptr);
+	REQUIRE(error.empty());
 
 	std::FILE* file = nullptr;
 	for (uint32_t try_count = 0; try_count < 20; ++try_count)
@@ -810,7 +797,7 @@ TEST_CASE("sjson_track_list_reader_writer vector4f", "[io]")
 	std::remove(filename);
 
 	// Read back the clip
-	ClipReader reader(allocator, sjson_file_buffer, buffer_size - 1);
+	clip_reader reader(allocator, sjson_file_buffer, buffer_size - 1);
 
 	REQUIRE(reader.get_file_type() == sjson_file_type::raw_track_list);
 
@@ -831,7 +818,7 @@ TEST_CASE("sjson_track_list_reader_writer vector4f", "[io]")
 		const track_vector4f& file_track = track_cast<track_vector4f>(file_track_list.track_list[track_index]);
 
 		CHECK(file_track.get_description().output_index == ref_track.get_description().output_index);
-		CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 1.0E-8F));
+		CHECK(rtm::scalar_near_equal(file_track.get_description().precision, ref_track.get_description().precision, 0.0F));
 		CHECK(file_track.get_num_samples() == ref_track.get_num_samples());
 		CHECK(file_track.get_output_index() == ref_track.get_output_index());
 		CHECK(file_track.get_sample_rate() == ref_track.get_sample_rate());
@@ -842,7 +829,7 @@ TEST_CASE("sjson_track_list_reader_writer vector4f", "[io]")
 		{
 			const rtm::vector4f& ref_sample = ref_track[sample_index];
 			const rtm::vector4f& file_sample = file_track[sample_index];
-			CHECK(rtm::vector_all_near_equal(ref_sample, file_sample, 1.0E-8F));
+			CHECK(rtm::vector_all_near_equal(ref_sample, file_sample, 0.0F));
 		}
 	}
 #endif