Repository navigation
Releases
v0.9.0
0.9.0 (2026-11-02)
Changed
- Breaking:
maniSkeleton_BlendPosesrefuses a threshold below
FLT_MIN. The threshold must be at leastFLT_MIN(2^-126) and at
most 1. A subnormal threshold, which 0.8.0 accepted and which could
blend translations and scales to infinity or NaN, now returns
mani_errorInvalidand leaves the output pose as it was. Graphs blend
atMANI_BLEND_THRESHOLD(1e-5) and are not affected
(#216).
Fixed
- Sampling a clip between two rotation samples in opposite hemispheres
could give the identity rotation. A clip file may hold such samples,
for example (1, 0, 0, 2^-80) and (-1, 0, 2^-80, 0); halfway between
them, the interpolated rotation is too short for its squared length to
be a normal binary32. It is now scaled by a power of two before it is
normalized, so it keeps its direction. The builder keeps consecutive
samples in one hemisphere, so built clips do not reach this case
(#211). - The skinning jobs gave a zero normal or tangent for a direction that
had not collapsed when the palette's scale was far from 1: a uniform
scale of about 2^-40 or 2^40 for normals, and 2^-70 or 2^70 for
tangents. They now keep its direction. Only a zero direction, or one
that is not finite, still comes out zero
(#214).
Results change only for the inputs above, which gave the identity or a
zero direction before. Every other pose, sample, skinned vertex, file
byte and format version is the same, and no pinned hash moves. As with
every release before 1.0.0, a graph instance, spring state or trainer
checkpoint saved by 0.8.0 is refused with mani_errorVersion: keep
assets, not saved states, across the upgrade.
v0.8.0
0.8.0 (2026-10-31)
Changed
- Breaking: kinds, modes and choices are
int32_t, and flag sets have
uint32_ttypedefs. All 33 kind, mode and choice typedefs, such as
maniLoopPolicy, areint32_t; a negative value is refused like one
past the last value. New flag-set typedefs aremaniGraphSyncFlags,
maniGraphClipFlags,maniRootAxesandmaniMatchEntryFlags, and the
fields that hold them take them, as doesmaniClipSource.loop, now a
maniLoopPolicy. RenameMANI_MATCH_LOOP,MANI_MATCH_NO_JUMPand
MANI_MATCH_MIRROREDtomaniMatchEntryLoop,maniMatchEntryNoJump
andmaniMatchEntryMirrored.maniMorphTargets_GetAttributereturns
-1 past the count, where it returnedUINT32_MAX. Code that keeps a
kind in auint32_tvariable or compares it with an unsigned value
updates its types
(#199). - Breaking: two names follow the family's naming rules.
maniLearnedTrainerReport.iterationisiterationCount, and
maniRootMoveIdentityismaniMakeRootMoveIdentity
(#198). - Breaking: the CPU checks return
bool, andmaniHash64takes an
int64_tcount.maniIsCpuBackendSupportedandmaniCheckCpu
returnboolinstead of anint32_t1 or 0, so a caller comparing
the result with 1 tests it as abool.maniHash64hashes any byte
count up toINT64_MAX; pass the count without narrowing it. Hash
values are unchanged
(#176). - Breaking: a refused call or a "no" clears its outputs. A call on a
builder, a glTF or FBX document or a trainer clears its sizes, counts,
reports, structs and handles before any check, so a failed call never
leaves an earlier call's value to be read as its answer.
maniBuilder_CopyBuilt,maniBuilder_CopySkeletonOrder,
maniGltf_GetMorphWeights,maniFbx_GetMorphWeightsand
maniLearnedTrainer_SaveCheckpointalso zero the caller's array up to
its capacity; a short skeleton order is no longer filled as far as it
goes. A too-small buffer still reports the size or count it needed.
maniBuildGltfClipandmaniBuildFbxClipwriteboundOutonly when
the clip builds.maniClip_GetRootMotionSourceand
maniInfluences_GetCenterclear their output when they return false,
and the name getters of learned models and motion databases clear
lengthOutwhen they return NULL. The headers say what each output
holds after a failure
(#165,
#174). - Breaking: a call refused as busy leaves its outputs. A builder or
trainer call that its own allocator or task hook made is refused with
mani_errorBusyand writes none of its outputs, since they may be the
ones the call in progress is filling.maniOpenGltfand
maniOpenFbxstill set their handle to NULL
(#173). - Breaking: more calls back in from host code are refused as busy.
maniGltf_SupplyBuffer,maniGltf_ReadSpringsand
maniGltf_ReadRagdollreturnmani_errorBusywhen called from the
builder's allocator.maniLearnedTrainer_SaveCheckpointand
maniBuildLearnedModelFromTrainerreturn it when called from the
trainer's allocator or task hooks. While a model is built from a
trainer,maniLearnedTrainer_Train,
maniLearnedTrainer_RestoreCheckpointand
maniDestroyLearnedTraineron that trainer return it too. Each
refusal counts one misuse
(#187,
#197). - The headers say which outputs may be their input:
maniBlendRootMotion'smoveOutmay be one ofmoves,
maniLearnedModel_Project'sstateOutmay bequery, and
maniMorphTargets_ComputeTargetWeights'targetWeightsOutmay be
channelWeights
(#195).
Fixed
maniMorphTargets_ComputeTargetWeightsgave wrong weights when its
output was its own channel weights. It now computes them in place with
the same bits as apart
(#193).maniRetargetMap_RetargetPosegave a wrong pose when the target pose
was the source pose, or shared memory with it. It now refuses any
overlap of the two poses withmani_errorInvalidand writes nothing
(#194).- Destroying a builder or a trainer, or closing a glTF or FBX document,
let the host's free hook call back into the object: a hook could
build on a builder being destroyed, free it twice, or read a closing
document's freed names. Every call from those frees is now refused
withmani_errorBusy, and a closing document reads as empty
(#187). - Host code running while a model was built from a trainer could
destroy or retrain the trainer under the build, which then read freed
memory. The trainer is now marked busy for the whole build
(#197).
Poses, samples, file bytes and format versions do not change. As with
every release before 1.0.0, a graph instance, spring state or trainer
checkpoint saved by 0.7.0 is refused with mani_errorVersion: keep
assets, not saved states, across the upgrade.
v0.7.0
0.7.0 (2026-10-29)
Changed
- Breaking: every call is named by the Maul family's naming rule. A
call on one object is namedmaniObject_Verb: a getter
maniObject_GetThing, a predicatemaniObject_IsThing. The object is
the library object the call takes first, such as a clip, a pose, a
skeleton, a graph instance, a builder or a glTF document, even where
the call only reads it:maniSampleClipismaniClip_Sample,
maniClipDurationismaniClip_GetDuration,maniPoseIsDifference
ismaniPose_IsDifferenceandmaniBlendPosesis
maniSkeleton_BlendPoses. Creation, destruction and calls with no
object keep a verb first: everymaniOpen...,maniInit...,
maniCreate...andmaniBuild...keeps its name, and the importers'
builds take that shape (maniGltfBuildClipismaniBuildGltfClip).
The misuse counts aremaniBuilder_GetMisuseand
maniLearnedTrainer_GetMisuse,maniResultNameis
maniGetResultName, the CPU check ismaniIsCpuBackendSupported, and
the task hook types end inFunction. Math on plain values keeps its
name (maniRootMoveIdentity,maniComposeRootMotion,
maniDecomposeAffine,maniHash64). Rename every call in the table
below; the parameters stay the same, except formaniPose_AddLayers
(#156). - Breaking:
maniPose_AddLayerstakes the pose first. A pose a call
changes in place is its object, somaniAddPoses(skeleton, pose, layers, count)ismaniPose_AddLayers(pose, skeleton, layers, count). Swap the first two arguments; a compiler refuses or warns
about the old order, since the two pointers have different types. The
IK solvers already took the pose first and keep their order as
maniPose_SolveTwoBoneand the others
(#156). - The user guides and the API reference use the new names
(#156).
Poses, samples and file bytes do not change; only the names do. As
with every release before 1.0.0, a graph instance, spring state or
trainer checkpoint saved by 0.6.0 is refused with mani_errorVersion:
keep assets, not saved states, across the upgrade.
| Before | After |
|---|---|
maniResultName |
maniGetResultName |
maniTaskFn |
maniTaskFunction |
maniEnqueueTaskFn |
maniEnqueueTaskFunction |
maniFinishTaskFn |
maniFinishTaskFunction |
maniPoseDifference |
maniSkeleton_ComputePoseDifference |
maniBlendPoses |
maniSkeleton_BlendPoses |
maniBuildMask |
maniSkeleton_BuildMask |
maniAddPoses |
maniPose_AddLayers |
maniBodyIkScratchSize |
maniSkeleton_GetBodyIkScratchSize |
maniSolveBodyXpbd |
maniPose_SolveBodyXpbd |
maniSolveBodySdls |
maniPose_SolveBodySdls |
maniBuilderMisuses |
maniBuilder_GetMisuse |
maniMeasureClip |
maniBuilder_MeasureClip |
maniCopyBuilt |
maniBuilder_CopyBuilt |
maniCopySkeletonOrder |
maniBuilder_CopySkeletonOrder |
maniClipCompressed |
maniClip_IsCompressed |
maniClipDuration |
maniClip_GetDuration |
maniClipSampleRate |
maniClip_GetSampleRate |
maniClipSampleCount |
maniClip_GetSampleCount |
maniClipLoopPolicy |
maniClip_GetLoopPolicy |
maniClipAdditiveBasis |
maniClip_GetAdditiveBasis |
maniClipIdentity |
maniClip_GetIdentity |
maniClipSkeletonIdentity |
maniClip_GetSkeletonIdentity |
maniSampleClip |
maniClip_Sample |
maniClipFloatTrackCount |
maniClip_GetFloatTrackCount |
maniClipFloatTrackName |
maniClip_GetFloatTrackName |
maniSampleClipBones |
maniClip_SampleBones |
maniClipCursorSize |
maniClip_GetCursorSize |
maniClipCursorCompactSize |
maniClip_GetCursorCompactSize |
maniSampleClipCursor |
maniClipCursor_Sample |
maniSampleClipFloatsCursor |
maniClipCursor_SampleFloats |
maniClipFindFloatTrack |
maniClip_FindFloatTrack |
maniSampleClipFloats |
maniClip_SampleFloats |
maniClipDiscreteTrackCount |
maniClip_GetDiscreteTrackCount |
maniClipDiscreteTrackName |
maniClip_GetDiscreteTrackName |
maniClipFindDiscreteTrack |
maniClip_FindDiscreteTrack |
maniClipDiscreteValue |
maniClip_GetDiscreteValue |
maniClipEventCount |
maniClip_GetEventCount |
maniClipEventAt |
maniClip_GetEvent |
maniClipEventRange |
maniClip_GetEventRange |
maniClipMarkerCount |
maniClip_GetMarkerCount |
maniClipMarkerAt |
maniClip_GetMarker |
maniGraphIdentity |
maniGraph_GetIdentity |
maniGraphSkeletonIdentity |
maniGraph_GetSkeletonIdentity |
maniGraphNodeCount |
maniGraph_GetNodeCount |
maniGraphParameterCount |
maniGraph_GetParameterCount |
maniGraphFindParameter |
maniGraph_FindParameter |
maniGraphParameterTypeOf |
maniGraph_GetParameterType |
maniGraphParameterName |
maniGraph_GetParameterName |
maniGraphClipCount |
maniGraph_GetClipCount |
maniGraphClipIdentity |
maniGraph_GetClipIdentity |
maniGraphClipName |
maniGraph_GetClipName |
maniGraphDatabaseCount |
maniGraph_GetDatabaseCount |
maniGraphDatabaseIdentity |
maniGraph_GetDatabaseIdentity |
maniGraphDatabaseName |
maniGraph_GetDatabaseName |
maniGraphModelCount |
maniGraph_GetModelCount |
maniGraphModelIdentity |
maniGraph_GetModelIdentity |
maniGraphModelName |
maniGraph_GetModelName |
maniGraphSpringSetCount |
maniGraph_GetSpringSetCount |
maniGraphSpringSetIdentity |
maniGraph_GetSpringSetIdentity |
maniGraphSpringSetName |
maniGraph_GetSpringSetName |
maniGraphRetargetMapCount |
maniGraph_GetRetargetMapCount |
maniGraphRetargetMapIdentity |
maniGraph_GetRetargetMapIdentity |
maniGraphMirrorMapIdentity |
maniGraph_GetMirrorMapIdentity |
maniGraphRetargetMapSourceIdentity |
maniGraph_GetRetargetMapSourceIdentity |
maniGraphClipRetargetMap |
maniGraph_GetClipRetargetMap |
maniGraphNodeKindOf |
maniGraph_GetNodeKind |
maniGraphNodeInputCount |
maniGraph_GetNodeInputCount |
maniGraphNodeInput |
maniGraph_GetNodeInput |
maniGraphOutputNode |
maniGraph_GetOutputNode |
maniGraphNodeSourcePlace |
maniGraph_GetNodeSourcePlace |
maniGraphBindingSize |
maniGraph_GetBindingSize |
maniGraphInstanceSize |
maniGraphBinding_GetInstanceSize |
maniSetGraphParameter |
maniGraphInstance_SetParameter |
maniSetGraphPose |
maniGraphInstance_SetPose |
maniGetGraphPose |
maniGraphInstance_GetPose |
maniGetGraphParameter |
maniGraphInstance_GetParameter |
maniInspectGraphNode |
maniGraphInstance_InspectNode |
maniSaveGraphInstance |
maniGraphInstance_Save |
maniRestoreGraphInstance |
maniGraphInstance_Restore |
maniUpdateGraph |
maniGraphInstance_Update |
maniGraphScratchSize |
maniGraphBinding_GetScratchSize |
maniEvaluateGraph |
maniGraphInstance_Evaluate |
maniGraphSubsetSize |
maniGraphBinding_GetSubsetSize |
maniGraphSubsetBones |
maniGraphSubset_GetBones |
maniEvaluateGraphSubset |
maniGraphInstance_EvaluateSubset |
maniSolveTwoBone |
maniPose_SolveTwoBone |
maniSolveAim |
maniPose_SolveAim |
maniSolveCcd |
maniPose_SolveCcd |
maniSolveFabrik |
maniPose_SolveFabrik |
maniGltfBufferCount |
maniGltf_GetBufferCount |
maniGltfBufferUri |
maniGltf_GetBufferUri |
maniGltfSupplyBuffer |
maniGltf_SupplyBuffer |
maniGltfSkeletonCount |
maniGltf_GetSkeletonCount |
maniGltfSkinCount |
maniGltf_GetSkinCount |
maniGltfSkinSkeleton |
maniGltf_GetSkinSkeleton |
maniGltfNodeCount |
maniGltf_GetNodeCount |
maniGltfBuildSkeleton |
maniBuildGltfSkeleton |
maniGltfBuildNodeSkeleton |
maniBuildGltfNodeSkeleton |
maniGltfSkeletonPlacement |
maniGltf_GetSkeletonPlacement |
maniGltfBuildSkin |
maniBuildGltfSkin |
maniGltfNodeBoneName |
maniGltf_GetNodeBoneName |
maniGltfRenamedCount |
maniGltf_GetRenamedCount |
maniGltfRenamedNode |
maniGltf_GetRenamedNode |
maniGltfAnimationCount |
maniGltf_GetAnimationCount |
maniGltfAnimationName |
maniGltf_GetAnimationName |
maniGltfBuildClip |
maniBuildGltfClip |
maniGltfReadSprings |
maniGltf_ReadSprings |
maniGltfReadRagdoll |
maniGltf_ReadRagdoll |
maniGltfMeshCount |
maniGltf_GetMeshCount |
maniGltfPrimitiveCount |
maniGltf_GetPrimitiveCount |
maniGltfPrimitiveTargetCount |
maniGltf_GetPrimitiveTargetCount |
maniGltfNodeMesh |
maniGltf_GetNodeMesh |
maniGltfNodeSkin |
maniGltf_GetNodeSkin |
maniGltfMorphWeights |
maniGltf_GetMorphWeights |
maniGltfBuildInfluences |
maniBuildGltfInfluences |
maniGltfBuildMorphTargets |
maniBuildGltfMorphTargets |
maniFbxVersion |
maniFbx_GetVersion |
maniFbxModelCount |
maniFbx_GetModelCount |
maniFbxModelBoneName |
maniFbx_GetModelBoneName |
maniFbxRenamedCount |
maniFbx_GetRenamedCount |
maniFbxRenamedModel |
maniFbx_GetRenamedModel |
maniFbxSkeletonCount |
maniFbx_GetSkeletonCount |
maniFbxSkinCount |
maniFbx_GetSkinCount |
maniFbxSkinSkeleton |
maniFbx_GetSkinSkeleton |
maniFbxSkinModel |
maniFbx_GetSkinModel |
maniFbxReadSettings |
maniFbx_ReadSettings |
maniFbxBuildSkeleton |
maniBuildFbxSkeleton |
maniFbxBuildModelSkeleton |
maniBuildFbxModelSkeleton |
maniFbxSkeletonPlacement |
maniFbx_GetSkeletonPlacement |
maniFbxBuildSkin |
maniBuildFbxSkin |
maniFbxSkinMethod |
maniFbx_GetSkinMethod |
maniFbxBuildInfluences |
maniBuildFbxInfluences |
maniFbxMorphCount |
maniFbx_GetMorphCount |
maniFbxMorphModel |
maniFbx_GetMorphModel |
maniFbxMorphTargetCount |
maniFbx_GetMorphTargetCount |
maniFbxBuildMorphTargets |
maniBuildFbxMorphTargets |
| `maniFbxMorphWeig... |
v0.6.0
0.6.0 (2026-10-29)
Changed
- Breaking: a saved state is read only by the release that wrote
it. A saved graph instance and a saved spring state start with the
Maul family's 16-byte state prefix (magic,MANI_ABI_VERSION, format
version, header size), so their headers are 32 bytes and every saved
state's bytes change.maniRestoreGraphInstance,
maniRestoreSpringStateandmaniRestoreLearnedCheckpointrefuse a
state written by a release of another ABI version with
mani_errorVersion: before 1.0.0 that is any other release, from
1.0.0 another minor release. A state saved by 0.5.0 or earlier is
refused. Keep assets, not saved states, across an upgrade. Poses,
samples and file bytes do not change
(#134). - Breaking: each save call reports the saved size.
maniSaveGraphInstance,maniSaveSpringStateand
maniSaveLearnedCheckpointtake asizeOutafter the capacity, and a
NULL buffer with capacity 0 measures.maniGraphStateSize,
maniSpringSavedSizeandmaniLearnedCheckpointSizeare removed:
call the save with a NULL buffer instead
(#129). - Breaking: every call that fills an array takes its capacity and
reports the count.maniMotionDatabaseFeatures,
maniMotionDatabaseNormalization,maniLearnedModelNormalization,
maniMorphTargetWeightsandmaniLearnedProjecttake a capacity and
acountOutafter the array;maniGltfMorphWeightsand
maniFbxMorphWeightstake a capacity in place of the exact count, and
acountOut;maniRagdollBodiesFromPose,maniRagdollJointTargets
andmaniBuildMasktake acountOutafter the capacity.
maniLearnedOutputgainsfloatTrackCapacityandeventCapacity
after its arrays. A NULL array with capacity 0 measures
(#133). - Breaking: name lengths are
size_t. Every name getter (the
skeleton's bones, a clip's tracks, the graph's slots and parameters, a
motion database's channels, a learned model's tracks and events, morph
channels and targets, a spring set's springs, and the glTF and FBX
importers' names) reports a length throughsize_t*, and every name
length a public struct carries issize_t, so a name read from one
call passes into a source unchanged. A builder refuses a name longer
than the newMANI_MAX_NAME_BYTES(2^31 - 1 bytes) with
mani_errorLimit, at the reason and item it gives that name
otherwise; before, a morph channel name past it was refused with
mani_errorInvalid, and a graph event name past it was hashed as if
empty
(#135,
#138). - Breaking: the graph's retargeting maps are named as such.
maniGraphMapCount,maniGraphMapIdentity,
maniGraphMapSourceIdentityandmaniGraphClipMapare
maniGraphRetargetMapCount,maniGraphRetargetMapIdentity,
maniGraphRetargetMapSourceIdentityandmaniGraphClipRetargetMap;
MANI_MAX_GRAPH_MAPSisMANI_MAX_GRAPH_RETARGET_MAPS;
maniGraphClipSource.mapisretargetMap, andmaniGraphAssets.maps
andmapCountareretargetMapsandretargetMapCount
(#136). - Every builder's reference names the
MANI_MAX_NAME_BYTESrefusal, and
the getting-started guide gives the bound as an example of a fixed one
(#155).
Fixed
- The motion database builder no longer writes past its names.
Channels may name one track, and their names' total was kept in 32
bits: two channels naming one track of 2^31 + 1 bytes wrapped it, and
copying the names wrote past the block it sized. Names past 4 GiB in
all are refused withmani_errorInvalidandmaniBuildReasonCount,
before any name is read, and wheresize_thas 32 bits, sections past
what one allocation holds are refused withmani_errorCapacity
(#143). - The mirror map builder sizes its working memory in 64 bits. Where
size_thas 32 bits (WebAssembly), listed names totalling 4 GiB wrapped
the room for their partners, which were then written past it; such a
source is refused withmani_errorCapacity
(#142). - The graph and mirror map builders refuse names past their files'
32-bit offsets. Names or partners past 4 GiB in all were laid out
with wrapped offsets, so the file built pointed at other bytes; they
are refused withmani_errorInvalidandmaniBuildReasonCount, and
the mirror map's sections pastsize_twithmani_errorCapacity
(#144,
#151). - An imported name past 4 GiB is refused, not cut short. The glTF
and FBX importers cut a name's decoded length to 32 bits, handing the
builder the first bytes of the real name; the whole name now reaches
the builder, which refuses it withmani_errorLimit
(#153).
v0.5.0
0.5.0 (2026-10-28)
Changed
- Breaking: every file kind's layout and format version change, and
an opener reads only its own version. The reserved words are gone
from the files: the container header is 48 bytes, not 64, and the
clip, retargeting map, influences, learned model, learned checkpoint,
spring set, ragdoll profile and mirror map lose their zero words.
Every kind's format version goes up by one: skeleton 2, clip 4, graph
7, retargeting map 3, skin 2, influences 2, morph targets 2, motion
database 4, learned model 2, learned checkpoint 2, spring set 3,
ragdoll profile 2, mirror map 3. An opener now refuses any format
version but its own withmani_errorVersion, an earlier one included;
it used to accept an earlier version and read it with the current
layout. Build every stored file again from its source. Poses, samples
and other outputs do not change; only hashes of file bytes move
(#113). - Breaking: results take the Maul family's values.
mani_errorVersionis -7 (was -4) andmani_errorFormatis -8 (was
-3).mani_errorStateis removed: a call from an object's own host
code (its allocator or task hooks) is refused with the new
mani_errorBusy(-10) in its place, and still counts one misuse. Code
that compares results by name only renamesmani_errorStateto
mani_errorBusy
(#115). - Breaking: a fixed bound passed returns
mani_errorLimit(-9). An
argument, or a builder's or the trainer's source, past a bound the
library publishes returnsmani_errorLimitin place of
mani_errorInvalid, with the same reason and item. This covers the
aim, CCD, FABRIK and full-body IK solvers, the motion database search,
and the skeleton, skin, influences, clip, morph targets, spring set,
retargeting map, motion database, learned model, trainer and graph
builders. A bound checked while opening a file is still
mani_errorFormat, and a range of a parameter's meaning (a weight in
[0, 1], a cone up to pi) is stillmani_errorInvalid. Full-body IK
scratch that is too small returnsmani_errorCapacity, not
mani_errorInvalid
(#117). - Breaking: the bound constants are named
MANI_MAX_*.
MANI_AIM_MAX_BONESisMANI_MAX_AIM_BONES,MANI_IK_CHAIN_MAX_BONES
isMANI_MAX_IK_CHAIN_BONES,MANI_IK_MAX_ITERATIONSis
MANI_MAX_IK_ITERATIONS,MANI_SPRING_MAX_RATEis
MANI_MAX_SPRING_RATE,MANI_MORPH_MAX_ATTRIBUTESis
MANI_MAX_MORPH_ATTRIBUTES, and theMANI_BODY_IK_MAX_*,
MANI_MATCH_MAX_*,MANI_LEARNED_MAX_*andMANI_GRAPH_MAX_*names
areMANI_MAX_BODY_IK_*,MANI_MAX_MATCH_*,MANI_MAX_LEARNED_*and
MANI_MAX_GRAPH_*. A name that keeps the old form (MANI_AIM_MAX_CONE,
MANI_SPRING_MAX_STEPS,MANI_GRAPH_LEARNED_MAX_STEPS) is a range or
an internal cap, not a bound refused withmani_errorLimit
(#117). - Breaking: public structs have no
reservedfield. The field is
removed from every public struct, and builders no longer refuse a
source for a nonzero one. Remove it from initializers.
maniFileResultis{result, tag, offset}:tagnames the section
the first wrong byte lies in, 0 in the header and table, so positional
initializers take the tag second
(#109). - Breaking: a root graph's slots are checked against the root's own
lists.maniBuildGraphrefuses, withmaniBuildReasonNodeat the
node's place, a root node that names a clip, sync group, database,
model or spring set slot past the root's own list. In a graph with a
subgraph such a node used to build with the subgraph's slot of that
number: with 1 clip in the root and 1 in the subgraph, a root clip
node naming slot 1 played the subgraph's clip. Only a host's own
maniGraphSourcestructs reach this; the JSON form resolves each name
within its own graph. Every other graph builds to the same bytes. To
migrate, list in the root every asset and sync group its nodes name
(#93). MANI_MAX_INFLUENCES(64 weighted influences on a vertex) and
MANI_MAX_GRAPH_DEPTH(16 levels of subgraphs) are public
(#117).- On WebAssembly, the public calls no longer go through the
floating-point environment's out-of-line entries, which have nothing
to switch there, so the static library is about 14.7 KB smaller.
Results do not change
(#101). - The user guide's getting started page explains
mani_errorLimit,
mani_errorBusyand the one format version a release reads, and its
examples drop thereservedfield and use the new constant names.
Fixed
- The FBX importer bounds a file's property count. A file with 2^32 - 1
properties or more wrapped the count, and the importer wrote past the
property array it had allocated from it. Such a file is now refused
at the property past the bound; every file under it reads as before
(#105). - A motion database file whose entry is too long for a frame count and
stores0xFFFFFFFFas its count is refused at that entry's count. It
was still refused, but at a later, wrong place. Valid databases open
and build as before
(#107).
v0.4.0
0.4.0 (2026-10-25)
Changed
- Breaking: a subgraph's lists and slots are checked like the
root's.maniBuildGraphrefuses withmaniBuildReasonNode, at the
node's place, a node inside a subgraph that names a clip, sync group,
database, model or spring set slot past the subgraph's own list. Such
a graph used to build when the root had a slot of that number, which
the node then named: with 2 clips in the subgraph and 9 in the root,
a clip node naming slot 3 played the root's clip 3. It also refuses
withmaniBuildReasonCounta subgraph with a list whose count is not
zero and whose pointer is NULL; its clip, sync group, database, model
and spring set lists used to be read through that pointer when a node
named one of their slots. Only a host's ownmaniGraphSourcestructs
reach either case; the JSON form resolves and checks its lists. Every
other graph builds to the same bytes. To migrate, list in each
subgraph every asset and sync group its nodes name
(#77). - On 32-bit x86 the library is built with SSE2 math
(-msse2 -mfpmath=sse), so it needs a processor with SSE2, and every
build now leaves trapping math at the compiler's default, from the
family's compiler flags. 64-bit builds' tests and pinned hashes pass
unchanged
(#73).
Fixed
maniBuildGraphrefuses a subgraph's sync group whose name is NULL
withmaniBuildReasonCountat item 0, before anything is laid out, as
it refuses a subgraph's list that is not given. A root's own nameless
group is still refused by the check with the sync group list's Name
reason. Only a host's ownmaniGraphSourcestructs reach this; the
JSON form always gives a name. Every graph that built before builds to
the same bytes
(#81).
v0.3.0
0.3.0 (2026-10-25)
Changed
- Breaking: a graph's time scales are bounded. On any path from the
output, the product of the time scales' sizes must be at most
MANI_GRAPH_MAX_TIME_FACTOR(1e6), which is far past any playback
rate a graph needs. A scale that reads a parameter counts at the
larger size of the parameter's bounds, and a time scale may read only
a parameter with bounds. Scales on parallel branches never multiply.
maniBuildGraphrefuses a source past either rule with
maniBuildReasonScalar, andmaniOpenGraphrefuses such a file with
mani_errorFormat. Each node record now holds its time factor, so
the graph format goes from 5 to 6, andmaniOpenGraphrefuses an
earlier format withmani_errorVersion. To migrate, give bounds to
each parameter a time scale reads, and build every graph file again.
Results do not change for any graph still taken.
(#66)
Fixed
- A clip node's step that is not finite moves nothing, and the marker
walk's count is bounded by two loops of markers. Results do not
change for steps shorter than about 2^53 clip lengths.
(#64) - A graph's binary32 timers saturate at
FLT_MAX: the fade time of a
machine and of a one-shot, and an inertialize node's step and elapsed
time. Every timer value belowFLT_MAXgives the same bits as
before.
(#67)
v0.2.1
0.2.1 (2026-10-24)
Changed
- The runtime's core (sampling, blending, the model pass, skeletons and
root motion) is about 1.0 KB smaller at-O2with GCC: 68,562 bytes,
against ozz-animation's 69,698. The skeleton's opener and the file
checks are compiled for size under GCC and Clang. With GCC, opening a
skeleton takes about 7% more instructions and opening a compressed
clip about 9% more. Opening a full clip or a motion database does not
change, and neither does any call made every frame. Results do not
change.
(#29)
Fixed
- The host's allocator and task hooks run in the host's own
floating-point setting, like the rest of the host's code. In 0.2.0
they ran in the library's setting while a public call was in
progress.
(#27)
v0.2.0
0.2.0 (2026-10-24)
Changed
- Breaking: motion matching searches a motion database through an
exact k-d tree, in place of bounds over runs of frames. Every search
returns the same frame and cost as before. On large databases a search
takes far fewer instructions: about 6 times fewer at 100,000 frames
and up to 16 times fewer at 300,000 frames with the default leaf. On
small databases with many features (around 5,000 frames and 45
features) it can be slightly slower. The index takes about 49 bytes a
frame at the default leaf, up from about 17.
The motion database format version is 3, and
maniOpenMotionDatabaserefuses any other format version with
mani_errorVersion. Build databases again from their sources, along
with the graphs that name them, since a database's identity changes.
A learned trainer's checkpoint does not resume against a rebuilt
database.
maniMotionDatabaseSourcegainsleafFrames(8, 16 or 32 frames a
leaf, 0 for 16) and a reserved word that must be 0, so positional
initializers must name them.
(#24)
Fixed
- A host that runs its threads with flush-to-zero, denormals-are-zero
or another rounding mode now gets the same bits as every other host.
Every public call that computes switches to the library's
floating-point setting and restores the host's on return, and so do
the learned trainer's tasks on the host's worker threads. Results in
the default setting do not change.
(#26)
v0.1.0
0.1.0 (2026-10-24)
Changed
- Compressed clips keep each kind of sub-track together and decode
several sub-tracks at once: sampling at a random time takes about 28%
fewer instructions and a forward cursor about 15% fewer, and poses and
float tracks are the same bits as before.
The clip format version is 3, andmaniOpenCliprefuses a clip of
any other format version, compressed or full precision, with
mani_errorVersion. Build stored clips again from their sources, or
compress them again from full-precision clips made with this release.
Fixed
- A learned motion matching node whose model gives a value past the
float range, or whose root velocity moves past it over the step, no
longer passes it on:maniUpdateGraph,maniEvaluateGraphand
maniEvaluateGraphSubsetcomplete the call with valid, finite
outputs and returnmani_errorFormat, andmaniLearnedStep,
maniLearnedProjectandmaniLearnedDecompressreturn
mani_errorFormatinstead of success.maniGraphNodeView's reserved
word is nowfailed.