Changelog

All notable changes to this project are recorded here, grouped as Added, Changed, Fixed, Removed and Security. The project uses Semantic Versioning. From 1.0.0 every release of 1 keeps the API across 1.y and the ABI within a minor version; a context def built against another minor version's headers is refused with mtwn_errorVersion. Before 1.0.0, any release could change both.

1.1.0 (2026-11-04)

This release makes mtwnCreateClipPlayer check its bindings in time linear in their count, where it compared each binding with every one before it (#292). A player of 2^20 bindings took 194 s to make at -O2 and now takes 12 ms. At the old rate, the 2^24 slots a clip may have would have taken about 11 hours. Two refusals of a def that binds a slot twice change their status, as below. MTWN_ABI_VERSION and a shared library's SOVERSION are now 1.1. A context def stamped by 1.0.0's headers is refused with mtwn_errorVersion, so a program built against 1.0.0 is rebuilt; its source builds unchanged. No value, velocity or event record changes.

Changed

1.0.0 (2026-11-03)

This is the first release of 1. Maul Tween's API is settled, and from here it ships only minor and patch releases of 1 (#273). Every function, type, enum value, macro and struct field in include/maul-tween/ keeps its name, signature and meaning across 1.y. The binary interface holds within a minor version. The API is the same as 0.17.0's, so a program built against 0.17.0 needs only a rebuild. MTWN_ABI_VERSION and a shared library's SOVERSION are now 1.0, and a context def stamped by 0.17.0's headers is refused with mtwn_errorVersion. No value, velocity or event record changes.

Changed

0.17.0 (2026-11-03)

This release changes two results to follow the references they name, ahead of 1.0.0. A finished player given a new rate runs at it from the time it holds, so a positive rate keeps it finished at its end where it jumped back (#264). A path's direction through a hold or a step is the way the path goes on, as SVG 2's path directionality takes it, where it was the way it came (#266). The clips guide's GSAP porting notes now cover grids with fewer items than columns and every way a clip's positions differ from GSAP's timeline, each checked against GSAP 3.15.0's outputs (#260, #262). Results change for a finished player given a positive rate (its time, the values it writes and its records), and for the direction and angle a host asks of a timed path through a hold or a step.

Changed

0.16.0 (2026-11-03)

This release changes two stagger results, found by comparing staggers with GSAP's distribute, which the clips guide names as their model. A negative spacing now runs from the farthest items, as GSAP's does, so no offset falls below the start and every offset is a delay a transition takes (#257). An offset whose host easing turns out not finite when it is written is refused, where it was returned (#254). The guide's porting note now gives every translation a port from GSAP needs, checked against GSAP 3.15.0's own outputs (#258). Offsets change for every stagger with a negative spacing; no other value, velocity or event record changes.

Changed

tools/oracle/rivals.mjs writes 448 of GSAP's cases, and test/test_rival_stagger.c checks that each case, translated as the note says, agrees within GSAP's rounding.

0.15.4 (2026-11-02)

This release keeps linear() easings monotonic where mtwnIsEasingMonotonic says they are, which a timeline's crossing count relies on (#243). A sample inside a segment could land an ulp past the segment's later point and step down at it. A sample moves only there, by at most about an ulp, so only hosts sampling linear() just below a point see it.

Fixed

0.15.3 (2026-11-02)

This release fixes two ways the event records broke their promise that every crossing between two updates is counted, however far time moved. An endless node whose iteration passes the largest double is held there, as the timing model holds it, where its crossing count was NaN (#237). A sweep's count is taken between its two ends, so it no longer depends on how much the clip crossed before the sweep (#241). No value or velocity changes; event records change only for clips that cross more than 2^53 points before a sweep, or whose endless nodes pass the doubles. Each fix below names who sees it.

Changed

Fixed

0.15.2 (2026-11-02)

This release fixes two results that broke what the headers promise. An Oklab or OkLCh color far past its ends keeps each channel's sign (#227), and mtwnIsEasingMonotonic no longer approves a linear() easing whose halves step down where in-out or out-in joins them, so a timeline refuses it (#230). Values change only for colors driven past about 5.6e102 in Oklab or OkLCh; nothing within range moves. Each fix below names who sees it.

Changed

Fixed

0.15.1 (2026-11-01)

This release fixes five results that broke what the headers promise. A timeline eased by a cubic bezier or circ never steps back, so a mark at its end is crossed once (#214). A spring's settling time leaves room for the sample's rounding (#216). A stagger whose offsets would pass the doubles is refused (#221), an easing table holds a sample past the floats at the largest float (#223), and a host easing's NaN is taken as its progress and counted as misuse, while its infinity keeps equal ends in place (#225). Values change: circ samples everywhere, the child times of a timeline eased by a cubic bezier, the ends of spring transitions, and what a host easing's NaN or infinity gives. Each fix below names who sees it.

Fixed

0.15.0 (2026-10-31)

This release follows the Maul family's conventions of .github v0.0.40. The enumerations are int32_t and the rough easing's flags uint32_t, so every struct holding one changes its layout, and the dropped events counter is mtwnContext_GetEventsDropped (#207). mtwnContext_GetMisuse no longer counts two refusals that depend on the library's state (#203). An unknown color space, hue method, angle method or rotation path is refused whatever the target's or track's kind (#210), and five corrections make the headers and guides say what the code does. No value, velocity or event record changes.

Changed

Stale target and clip ids still count. A host now reads no misuse for stopping or reading a player after it ended, naming a kept player after destroying it, or destroying a clip a player still holds: the count is smaller for the same calls, never larger. The transitions guide no longer asks for mtwnPlayer_IsValid before stopping a transition that may have ended.

Fixed

0.14.0 (2026-10-30)

This release finishes the API review for 1.0. The readers take the context const and read it when host code calls back in (#170). The calls on a clip's player are named for it (#178) and the two counters that were not are named Get...Count (#176). The reduced motion switch returns nothing (#174), the host easing's callback is a function type (#172), and mtwnMakeTrack takes the keyframes it rewrites first (#184). Three getters no longer crash on a null context, and a refused rough easing leaves its points. The getting started guide says what 1.0 will keep. No value, velocity or event record changes.

Changed

Before Now
mtwnPlayer_Play, _Pause, _Reverse, _Finish, _Cancel mtwnClipPlayer_Play, _Pause, _Reverse, _Finish, _Cancel
mtwnPlayer_Seek, _Sweep, _SeekProgress, _SeekLabel mtwnClipPlayer_Seek, _Sweep, _SeekProgress, _SeekLabel
mtwnPlayer_SetRate, _SetWeight, _GetState mtwnClipPlayer_SetRate, _SetWeight, _GetState
mtwnCreatePlayer mtwnCreateClipPlayer
mtwnPlayerDef mtwnClipPlayerDef
mtwnDefaultPlayerDef mtwnDefaultClipPlayerDef
MTWN_PLAYER_DEF_COOKIE MTWN_CLIP_PLAYER_DEF_COOKIE
mtwnPlayerState mtwnClipPlayerState

mtwnPlayerId, mtwnDestroyPlayer and mtwnPlayer_IsValid, which take a transition's player too, and mtwnPlayState keep their names. Parameters, values and the cookie's value are unchanged. - Breaking: mtwnContext_GetDroppedEvents is mtwnContext_GetDroppedEventCount and mtwnContext_GetSerialAdvances is mtwnContext_GetSerialAdvanceCount, as the family names its counters. Their parameters and behavior are unchanged (#176). - Breaking: mtwnContext_EnableReducedMotion returns void. A null context changes nothing, and a call back in changes nothing and counts one misuse, as their refusals did; read the state with mtwnContext_IsReducedMotionEnabled (#174). - Breaking: mtwnEaseFunction is a function type, double (void* context, double progress), as the task hooks' are: mtwnMakeHostEasing takes, and mtwnEasing.function holds, a mtwnEaseFunction*. Passing a function's name compiles as before; a variable of the old type is declared mtwnEaseFunction* (#172). - Breaking: mtwnMakeTrack takes the keyframes and their count first and the def third: mtwnMakeTrack(keyframes, count, def, lengthsOut, mixesOut, trackOut). It rewrites the keyframes in place for angles and for rotations on the shortest path, so they are the call's object, as the family's conventions order it. A call in the old order no longer compiles (#184). - docs/user/getting-started.md has a Compatibility section, linked from the README: what 1.0.0 will keep across 1.y (every function, type, enum value, macro and struct field in the headers, things only added), what it leaves out (the fields of the made value structs a host never reads), the binary interface within a minor version, and results the same bits on every platform, any change of a bit stated here. The transitions guide says to ask mtwnPlayer_IsValid before stopping a transition that may have ended (#180).

Fixed

0.13.0 (2026-10-30)

This release settles what a refused call on a context leaves in its outputs, as the Maul family's conventions say. A refused update, event read or target read now clears its array as far as a success could write it, so a host that misses a status reads nothing stale (#160). A call refused as busy, because host code the library runs called the context back in, now writes none of its outputs, since the call in progress may have written them (#161). Only refusals change: no value, velocity or event record does.

Changed

A null context leaves the update's report and the events, since how far a success could write them depends on the context. A host that reused an array across a refused call now finds the null id or zeros there, not the last call's entries, so it checks the status before it reads the array. - Breaking: a call back in from the context's own host code (a host easing, the allocator, the task system's functions or tasks), refused with mtwn_errorBusy, now writes none of its outputs (#160, #161): - mtwnContext_Update leaves its total, and mtwnContext_ReadEvents its count and dropped count, beside the arrays they already left, where it set them to 0; - mtwnContext_GetClipBytes, mtwnClip_GetTrack, mtwnClip_GetNode, mtwnClip_FindLabel, mtwnClip_GetDuration, mtwnTarget_GetValue, mtwnTarget_GetState and mtwnPlayer_GetState leave their output as it was, where they cleared it.

The creations still give the null id: mtwnCreateTarget, mtwnCreateClip, mtwnCreatePlayer, mtwnStartTransition and the mtwnClip_Add... calls' node. Host code that calls the context back in now finds these outputs as it left them, not 0.

0.12.0 (2026-10-29)

This release gives every public name the Maul family's shape (#148): a call on the context starts with mtwnContext_, every other function starts with its verb, a switch is an Enable and Is...Enabled pair, and a callback type ends in Function. Each rename keeps the function's parameters and behavior, so a caller only updates its calls. No value, velocity or event record changes.

Changed

Before Now
mtwnGetContextMisuse mtwnContext_GetMisuse
mtwnGrowContext mtwnContext_Grow
mtwnMeasureTransition mtwnContext_MeasureTransition
mtwnContext_SetReducedMotion mtwnContext_EnableReducedMotion
mtwnContext_IsReducedMotion mtwnContext_IsReducedMotionEnabled
mtwnResultName mtwnGetResultName
mtwnEase mtwnSampleEasing
mtwnEaseCubicBezier mtwnSampleCubicBezierEasing
mtwnBind mtwnMakeBinding
mtwnLinearEasing mtwnMakeLinearEasing
mtwnTaskFn mtwnTaskFunction
mtwnEnqueueTaskFn mtwnEnqueueTaskFunction
mtwnFinishTaskFn mtwnFinishTaskFunction

Before 0.11.0, mtwnMakeLinearEasing was the maker of CSS's linear() from points, which is mtwnMakeLinearPointsEasing since 0.11.0. A call written for that maker passes points, so it no longer compiles. - The user guides and samples use the new names (#148).

0.11.0 (2026-10-28)

This release settles names and signatures ahead of 1.0.0 (#135). Every rename keeps the function's parameters and behavior. A caller updates its calls, and no value, velocity or event record changes, except a player weight given as a double (below).

Changed

0.10.0 (2026-10-28)

Added

Changed

A caller that switched on the old statuses handles the new ones at those calls; one that logs or stores the raw numbers reads them anew. No value, velocity or event record changes (#124). - Breaking: a clip's player keeps every start time finite, so its time can no longer become infinite. mtwnCreatePlayer and mtwnPlayer_SetRate refuse a rate other than 0 below MTWN_MIN_RATE (2^-20) in size. mtwnPlayer_Seek, mtwnPlayer_Sweep, mtwnPlayer_SeekProgress and mtwnPlayer_SeekLabel refuse a time past the new MTWN_SEEK_LIMIT (2^1000) in size: the argument, the share times the duration, or a root label's start. mtwnClip_AddTrack and mtwnClip_AddTimeline refuse a node that would take its clip's finite end past it. Each is mtwn_errorLimit, counted as misuse. These calls used to be taken: a share of 1e308 finished the player at an infinite time, and a rate of 5e-324 jumped it to its end at the next update. Every call still taken gives the same results (#119). - Breaking: the public structs have no reserved fields. The reserved member is gone from mtwnContextDef, mtwnClipDef, mtwnClipTrackDef, mtwnClipTimelineDef, mtwnPlayerDef, mtwnPosition, mtwnBinding, mtwnTiming, mtwnStagger, mtwnEasing, mtwnSpring, mtwnKeyframe, mtwnPath, mtwnTrackDef, mtwnTrack, mtwnColorMix, mtwnClipNodeInfo, mtwnPlayerState, mtwnTimingResult, mtwnTargetState and mtwnEvent, and no call refuses a struct for a reserved byte any longer. A host that set one, or listed it in a positional initializer, drops it. Layouts change: on x86-64 mtwnClipDef shrinks from 24 to 20 bytes and mtwnContextDef from 104 to 96, and on 32-bit x86 many shrink by 4 to 8 bytes, mtwnEvent from 64 to 60. A cleared output is still 0 in every byte, its padding included. No value, velocity or event record changes (#122). - The getting-started guide lists every result with its value, and the players guide gives the bounds of a rate and of a seek (#119, #124).

Fixed

0.9.0 (2026-10-27)

Changed

Fixed

0.8.0 (2026-10-26)

Changed

Fixed

0.7.0 (2026-10-25)

Changed

0.6.0 (2026-10-25)

Changed

0.5.0 (2026-10-25)

Changed

Fixed

0.4.0 (2026-10-25)

Changed

0.3.0 (2026-10-25)

Changed

0.2.1 (2026-10-25)

Fixed

0.2.0 (2026-10-24)

Changed

0.1.1 (2026-10-24)

Changed

Fixed

0.1.0 (2026-10-24)

Added

Changed

Fixed

0.0.1 (2026-10-24)

The first release: easing, springs, tweens and timelines driven by the host's clock, with the same results on every platform.

Added

Changed