Repository navigation
Motion: the animation system of #853 end to end, and every open motion issue - #891
Merged
Merged
Conversation
- worker.wait answers (ran, why): the image preload takes the count. - std.file's long names are gone: file.close for file_close in test_scene_io and generate_shaders. - wait_for_idle now panics inside an actor or on a scheduler core; ae3d does not call it. - aephysics 7985511, its main on 0.839's @keep rules, builds on it. All 290 programs and the editor compile on 0.860 without an error.
…, on real people (#853) One pose a frame, one writer: a figure's animator evaluates its pose and writes it to the figure's joints once, in the pose phase. Games set parameters and never write bones. The layers, beside the old modules, which stay frozen until #860 retires them: - ae3d.pose (L0): a rig read once from a skin, its joints mapped onto the UE5 mannequin's 53 humanoid slots from UE5, Rigify or Mixamo naming; poses as flat arrays; blends, masks, additive differences, model space. - ae3d.retarget (L0): a take carried from one humanoid to another, rest poses aligned bone by bone, the pelvis and root scaled by the two pelvises' heights. - ae3d.clips (L1): an animation resampled for a rig at the file's key rate, with root motion taken out and, read at import, foot contacts, planted frames, speed over the ground, heading and footfall sync markers. - ae3d.animator (L2): states and 1D blend spaces, inertialized transitions (Bollo, GDC 2018), gaits blended on their footfalls and played at the speed the figure moves, hand-overs in step, masked and additive layers, root motion onto the figure's object. - ae3d.ikrig (L3): feet on the ground under each foot with the pelvis dropped for the lower one, the foot locked on its heel then its toe's tip where the clip plants it, the heel peeled up when the leg cannot reach, two-bone legs with soft reach, the head turned to a point. The test figures are real people for the first time: the Quaternius mannequins (UE5 skeleton, male and female) and a Rigify-named one, with 89 authored clips, CC0, in tests/fixtures/people (#854). tests/test_anim_pipeline.ae holds the layers to: - a planted toe's slide with feet locked, 1 to 8 m/s: under 1e-12 mm; - the blend's speed against the speed asked: within 1%; - idle to sprint and back, inertialized: no joint faster than the takes' own; - retargeted segments against the source's: 0.00005 degrees; - root motion carried exactly; - a foot onto a 10 cm block within 5 mm, at most 4.4 degrees a step. tools/anim_showcase.ae shows three figures on the layers, and docs/animation-pipeline.md describes them.
The README opens on what the engine is and why it is built the way it is (measured, driven by programs, one language, two renderers at parity), then lists what is inside by area, each with its doc. The animation pipeline is in it with the numbers it is held to and a real frame from tools/anim_showcase.ae, now under a late afternoon sky on a dark ground. Links point at aether-lang-dev/ae3d, the roadmap names only open work, and the layout follows the modules as they are.
…eet (#855) - pose.mirror: each joint's turn from rest reflected onto its other side's joint (names with left and right swapped), places reflected. The mannequins' rest poses are their own mirrors to 0.02 degrees; a walk mirrored twice is itself to 2e-6 degrees; a mirrored walk leads with the other foot at the same speed. clips.mirror does a whole take. - Clip events: named moments of a take, footfalls found at import, fired by the animator as its clock passes them (a blend's from its heaviest take). - ae3d.bvh reads motion capture (Xsens naming mapped as a fifth scheme, End Sites as joints, units found from the height); tools/bvh_cut.ae cuts stretches of a take unedited. Nine cuts of 100STYLE's Neutral takes (CC BY 4.0, Ian Mason) are the fixtures: walks and runs forward, back and sideways, turns, a stop and a start. - Planted feet must also move with the ground: a capture's shuffle at a turn was low and flat and went 137 mm through its "stance". A take whose pelvis travels in its frames is on still ground, at its pelvis's speed. - The retargeter aligns the figures' facings (from the thighs), aligns hands and toes to a child where a slot is missing, and keeps the feet on their contacts: a planted ball held on the source's ground (clips.ground_carry), the pelvis lowered where a leg cannot reach, legs solved by two-bone IK, the source's contacts kept. 100STYLE's walk and run onto the UE5 mannequin: a planted toe holds to 4.5 and 4.0 mm a stance, against 46.8 and 19.4 mm turned bone by bone and the capture's own 6.9 and 5.6 mm. test_anim_pipeline: 44 checks, all passing.
…st (#855) - A figure with a character controller is moved by its capsule: the root's travel is the step's velocity, so a wall stops it. The roll goes its root's 4.99 m on open ground and stops at 1.705 m against a wall whose face is at 2.0 (the capsule 0.3 wide). - Loops are sealed at import, as Unity's Loop Pose seals them: the gap between a loop's last frame and its first spread over the take, root travel and pelvis placement left alone. UAL's idle, jog and sprint ended 0.5 to 0.8 degrees off their starts; every loop now closes to 2e-6. - Measured: sampling a 53-joint take costs 0.32 microseconds a character. test_anim_pipeline: 50 checks, all passing.
…ng (#855) - Retargeting a capture (no root bone) onto a rig with one makes the root under the hips: its place and heading averaged over 0.4 s either side, narrowed at the ends so it starts and ends under the hips. Its root motion is the hips' travel exactly (1.786 of 1.786 m on the walk). - The root lock holds the root over its rest place, facing as at rest: a capture's figure was drawn 8 m from its object (where the actor stood) and 100STYLE's backward walk carried it 45 degrees off its back. The root's turn is read as its twist about up (the UE5 root's forward axis points up, and read off it the turn was always 0). - clips.straighten: no turn, the travel on its line, or on the nearest of forward, back, left and right; the captured walker in the showcase goes out and back on its line to 1e-13 m. - The retargeter holds a planted foot by its toe's tip, as ikrig locks it (ikrig.toe_local, shared): the backward walk's toe went 5.1 mm held by the ball, and now 1e-12 mm; the walk and run likewise. - A planted foot must also be still only over a still ground (a capture read whole): filtered on an in-place sprint, the runner's lock let go mid-stance at 8.5 m/s and its toe went 327 mm. - 100STYLE cuts are whole strides, left footfall to left footfall, where travel and hips both keep their direction (README lists frames and sums). - animator.current_take; the showcase's fourth figure walks the captured forward and backward walks on a capsule by root motion. test_anim_pipeline: 64 checks, all passing.
…rgeting with IK goals (#855) The layer table gains ae3d.bvh and what each layer now does; a section on motion capture (read, retarget, root made under the hips, root lock, straighten) and one on events, sealed loops and mirrors; the techniques gain IK goals in retargeting and planted-means-still; the numbers table gains each new measurement from test_anim_pipeline.
- animator.add_blend2 / blend_add2 / set_velocity: a blend space over the ground's two directions, each take at its velocity relative to the figure's facing, weighted by polar gradient band interpolation (Johansen 2009, Unity's freeform directional), its takes in step by their footfalls and paced so their blended velocity (a vector sum) carries the ground at the speed asked. blend_weight reads a take's weight. - Blends carry root motion: their takes' root deltas, weighted. - clips.loop_cut: a capture's best loop, the two frames at least 0.6 s apart whose key joints' places and motion match best, cropped (crop) and sealed. Cut between footfalls, 100STYLE's strides ended 16 to 41 degrees off their starts at a foot, too far to seal, and wrapping jumped the foot. - seal_loop seals the pelvis's place too when the take's root carries its travel; only a joint that travels is left alone. - Over a still ground a dragged foot is neither planted nor down: the side step drags its trailing foot for most of a stride, and the lock held it and let go a third of a metre behind. - clips.mirror keeps the take's contacts, left for right. 100STYLE's walks and runs forward, back and to either side on the UE5 mannequin, moved at 8 directions by 0.6, 1.0 and 1.4 m/s with feet locked: a planted toe slides 1.8e-12 mm at worst, the speed matched exactly. test_anim_pipeline: 66 checks, all passing.
…, held poses, aim offsets (#856) - Named parameters (set_float, set_bool, trigger) a game drives the graph by; blend spaces bound to them (bind, bind2). - Transitions the graph takes by itself when all their conditions hold: a parameter ABOVE or BELOW a value, a switch IS or IS_NOT on, a trigger TRIGGERED (spent when taken), the state's take ENDED; from a state or ANY; inertialized, or cross-faded with both states playing. - snapshot: a state holding the pose drawn now, to blend out of (what a get-up starts from). - Aim offsets: aim poses at their yaw and pitch, blended by gradient bands and added over a mask as their difference from the level one. On the Rigify mannequin: a graph of idle, gaits and a roll driven by its parameters alone goes idle, gaits, idle, roll, idle, its fastest joint turn 32.6 rad/s under the takes' own 38.7; halfway through a cross-fade the pose is 42 and 43 degrees from states 83 apart; a held pose stays within 2e-6 degrees; the pistol's aim offset holds the hand at 0.90, 1.40 and 1.80 m aimed down, level and up, and aimed level leaves the idle as it is. test_anim_pipeline: 71 checks, all passing.
…#855) macOS's leak check found the two controllers capsule_root_motion made: a controller a program makes with physics.character_controller is its own to free (physics frees only those it made), as test_character frees its own.
- ae3d.mover: the controller a game drives with a velocity and, for strafing, a facing. Holden's springs move the figure's object and feed the graph its parameters (vx, vz, speed, moving, go, turning, travelled, rest); the gait is the graph's choice from the real velocity. It brakes at a constant deceleration when set (set_braking), from a stop take's own (clips.braking), so the stop enters where its feet are in the stride. - Distance matching (Paragon, GDC 2016): animator.clock_by plays a take by a parameter's distance, from its start or left to its end, from the frame it is entered; clips.travel_length, time_at_distance(_left), moving_at, still_at (the first stand after its fastest). - Transitions entered in step (transition_synced: a start hands over to the gaits at its stride's phase) and choosing among targets by the feet (transition_or: a stop or its mirror, whichever's feet match). - clips.straighten re-integrates the path in the root's own frame (a turn's step as the root took it) and turns poses and path together onto the nearest axis: zeroing a stop's 29 degrees of turn and dropping its path's side slid its planted ball 362 mm over its own ground. Loops cut for the blend are straightened onto their axes too. - Fixture: 100STYLE's straightest forward stop of every style's transitions (LookUp; the Neutral style has none). On the UE5 mannequin, a mover and a graph of idle, a start, the 2D blend and a stop: stand, walk 4 s at 0.8 m/s, let go: idle, start, gaits, stop, idle, no planted toe sliding 9e-13 mm with the feet locked. The 2D blend matches the speed asked exactly at 8 directions within its samples. test_anim_pipeline: 73 checks, all passing.
…856) - ikrig.feet_set_steps: while a figure stands, a planted foot the clip has turned or moved off where it is held steps there: lifted 6 cm at its middle over 0.28 s (quicker for a foot far behind), set down and locked again; one foot at a time, the next lifting as the first is 70% down; begun on the twist the foot will have half a step on (stepped on the twist alone, a fast turn left a foot 58 degrees behind the body). - A locked foot keeps its heading over the ground as the body turns above it, its toe no longer swept round its ankle. The UE5 mannequin on its captured idle, turned half round on the spot by a mover (its facing on a 0.6 s half-life): 10 steps of 18 degrees, a planted toe still to 4e-13 mm, the hips 5 mm off their spot, no planted foot more than 38 degrees further off the body than it stood. test_anim_pipeline: 75 checks, all passing.
…rough toe-off (#856) - animator.add_matcher / matcher_add / matcher_build: a state that plays whichever frame of its takes best fits the pose drawn and the way ahead asked (Clavet, GDC 2016; Holden; Unreal's pose search): features of each foot's place and velocity about the root, the hips' velocity, and the root's place and facing a third, two thirds and a whole second on, each scaled by its spread. Searches every 0.1 s; changes, inertialized, only for a clear gain over playing on, and only to frames that keep each planted foot planted within 15 cm (changing into a frame with a planted foot elsewhere dragged it a third of a metre off its lock). Looping takes play on round. - mover.set_follow: the figure moved by its clips' own root motion, the springs a simulation it is eased toward (at most 0.2 m/s over its own motion: each metre eased is a metre a planted foot's lock drifts) and kept within 15 cm of; set_matcher asks the matcher for the simulation's way ahead, from where the figure stands. - ikrig: a foot's heading is its twist about up from rest (read heel to toe over the ground it swung 50 degrees in two frames as the foot rose onto its toe); the heading held through a stance is eased out with the place when the lock lets go, and a lock that slips eases out too (both snapped before, a foot swinging 105 degrees in a frame). A turn on the spot now takes 7 steps of 26 degrees, no planted foot more than 0.7 degrees further off the body than it stood. On the UE5 mannequin over 100STYLE's walks, runs, side steps, turns, a start, a stop and a stand: the matcher plays a frame going ahead at 0.69 m/s while walking, left at 0.48 m/s while stepping left, still at rest, no planted toe sliding over 1.4e-12 mm. Its fastest joint turn, 25.6 rad/s against the takes' own 14.8, is printed and not yet held: a database of the style's whole takes is the next step. test_anim_pipeline: 76 checks, all passing.
…a stick (#855, #856) - The retargeter aligns a bone that one rig has nothing beyond (the UE5 head has no child) by the bone coming into it on both rigs: left unaligned, the mannequin's head, pitched 11 degrees at rest where the capture's stands upright, bowed that much further than the capture's. The capture test now holds the head's own orientation, not only the neck's line: within 1.2e-6 degrees on the walk, run and backward walk. - tools/anim_showcase.ae's fifth figure (AE3D_WATCH=5): the UE5 mannequin on a mover and a graph of the captured stand, start, 2D blend of walks, runs and side steps, and stop (and its mirror), its feet stepping as it turns on the spot, the runner's pistol aims retargeted as an aim offset, its eyes ahead: through 24 s it stands, walks, steps left and right facing ahead, runs, stops, turns round on the spot, walks back aiming, stops and turns round again. The showcase's own circling figures' struct is a Circler now (the mover's is a Mover). test_anim_pipeline: 79 checks, all passing.
…n the spot, motion matching (#856) The layer table gains the graph's states and transitions and ae3d.mover; a section shows a figure on a stick (a 2D blend, a start and a stop played by distance, transitions in step and by the feet) and what each technique is and where it comes from; the numbers table gains each new measurement.
…rs and slopes (#857) Limbs: two-bone limbs with hinge limits (no knee or elbow past 150 degrees, no knee bent back) and the hand's extra twist shared into the forearm; one triangle (ae3d.ik.triangle) under the pose rig's limbs and the physical figure's. FABRIK chains within cones. Whole-body reach with effector weights: the spine and the hips lean to what the arms cannot reach, the feet held. Aim over the spine with hands held on a joint, so a rifle aimed with the chest carries both hands in the same solve. Feet on any ground: soles traced heel, pad and toe where the foot goes; one surface laid on with the ankle stood off along its normal, a stair's edge rested on by its corner (found by halving); a swinging foot looks a foot's length ahead for an edge; after the leg solve the foot is rested on the ground by its lowest point and its toes bent at the ball out of it (the walk clip alone sinks a toe 29 mm); the pelvis down by what the lower foot and each leg's reach need; a capsule's step up leaves feet and pelvis where they were in the world; stride warping for stairs. tests/test_ik_constraints.ae (16 checks) holds it on the Rigify mannequin; the showcase gains a climber on stairs and a ramp and a rifleman.
…#857) Found watching the showcase, measured on it with its new AE3D_METRICS mode (per figure: legs through each other, a knee out off the hips, the chest off the hips, a forearm over-rolled, and when): - A retarget carried points through the world, so a source figure kept out of sight 50 m below put the showcase's female 50 m underground; the new sole clamp then reached for the ground 50 m up and her planted toes slid 0.9 m a cycle. Points are carried over each figure's own ground. - ikrig's figure forward ran heel to ball, 30 degrees down: every look pointed the head 30 degrees over its target. Levelled; the pipeline's look test now checks where the head points (1e-6 degrees off). - A knee bent in the clip's plane taken against the line to a foot held off the clip's place swung out up to 90 degrees and crossed the legs (the climber's legs came 2 cm apart). A knee keeps the clip's plane within 20 degrees of its hinge's, and is held there past it. - The pelvis drop for reach no longer squats to reach a foot left behind by a lock: only by how far the ground fell away under it. - geometry's growth doubled an int without a bound, so a memset could be sized by a negative number; GCC's fortified memset warned on Linux (test_falls). The growth is bounded and sized in 64 bits. The showcase's rifleman turns his whole body to his target (55 degrees of chest off the hips down to 19), its climber walks the ramp at the slope stride, and AE3D_WATCH_FRONT=1 watches a figure from in front.
This was referenced Oct 31, 2026
Closed
…, #898) Watching the showcase from in front (AE3D_WATCH_FRONT=1) and measuring knees caving in and feet crossing (AE3D_METRICS=1), with the feet IK on and off: the IK caved the runner's knee in 33 degrees (5 with it off). A knee was bent the way the clip bends it measured off the line to its target; with the foot held off the clip's place, that line leans and the knee turned in. Held to the hinge from the thigh's twist instead, it swung out 90 (authored clips do not twist their thighs as their knees bend). It now takes the clip's bend off the clip's own hip-to-foot line, turned onto the line to its target by the least turn between the two: knees in 5 to 7 degrees, out as the clip has them; the block step's fastest turn back to 4.4 degrees a step. test_falls failed to build on Linux (GCC's fortified memset warned) once motion imported ae3d.ik for the shared triangle; motion keeps its own copy until it moves onto the pipeline (#860), and the geometry change made for it is backed out (it recompiled every suite, and the linux leg ran out of time on a cold cache); the growth overflow it fixed is #899.
- README: the animation block names what the pipeline now does (BVH capture with its feet kept, the state graph, motion matching, the mover, IK to joint limits, FABRIK, whole-body reach, a rifle in both hands, soles on stairs and slopes) and its numbers as the tests measure them today: a planted toe under 1.5 mm from 1 to 8 m/s, no sole 1 mm into 0.2 m stairs or a 20 degree ramp. The showcase is seven people. - CI: GitHub's ubuntu-latest moved to 26.04, whose GCC misses every object cached on 24.04, and actions/cache saves only after a job that passed: the suites' first Linux shard was cut off cold at 40 minutes on two runs, saving nothing, and the third passed at 39.5. The compile cache is now restored and saved as two steps, the save whatever became of the run, and Run CI stops six minutes inside the job's limit so a cut-off run still saves. - ci.sh prints the six lines GCC puts over a build warning (the function, what it was inlined from), so the memset 26.04's GCC flags in test_falls is named on the next run.
…#898) Found on the showcase's figure on a stick (a knee 51 degrees out, a foot 10 cm over the hips' middle as it turned round), reproduced without a window in test_anim_pipeline's turn on the spot, which now measures what a player sees there too: - A planted foot's heading was read in the figure's own frame, which a mover turns: the clip's foot never turned in it (4.2 degrees through a 60 degree turn), so the lock held nothing and the foot swung round with the body on its toe, its heel 14 cm across the other leg. It is read over the ground now; the figure takes 10 steps of 18 degrees for half a turn where it took 7 of 26 dragged round. - A step kept its foot's lock until it landed, and the heel's peel about the locked toe and the toe's return over its lock both still pulled the foot back to where it stood: the stepping ankle jumped 16 cm between frames. Neither holds a foot that is stepping. - A stepping foot was held down on the ground as the clip has it (a stand has both feet down), so a step slid along the floor, its ankle never 13 mm up. It is lifted through its step now, 60 mm at its middle, and laid on the slope at its ends only. Turning half round: no foot past the hips' middle (4.9 cm short of it, was 16.7 cm past), each step lifts its foot 60 mm, the fastest ankle 0.89 m/s; a planted toe slides 0.0007 mm. test_anim_pipeline 81 checks, test_ik_constraints 16, test_matching 16, all passing.
GitHub's ubuntu-latest 26.04 GCC flagged a memset in aephysics's update_mesh_contact as sized by a negative int: aephysics declared memset's size as a 32-bit int, so `old_count * 4` was worked out in wrapping int arithmetic. aephysics 9ea02c3 (branch fix/mesh-contact-memset) takes the size as a long and computes every byte count in 64 bits from its count. test_falls builds without a warning and passes its 21 checks.
# Conflicts: # ci.sh
main pinned the Linux jobs to ubuntu-24.04 (266f2c9, #901), where the memset warning does not show, so this PR need not hang on an aephysics branch with no pull request yet. The fix (aephysics 9ea02c3, branch fix/mesh-contact-memset: memset sizes in 64 bits) returns with the move back to ubuntu-latest under #901.
The type-check ran aetherc on some 150 modules one after another, and on the first Linux suites shard it took 19 minutes on a warm runner and 24.6 on the 26.04 image (run 41249856048): with the suites, the Vulkan validation pass and the benchmarks after it, that shard was cut off at its limit with its tests all passed. Each module's check now runs in ci.sh's pool (in_pool, as the builds do), its log and status kept, and the results are said in the same order after.
This was referenced Nov 1, 2026
11 tasks
nicolas-maman
added a commit
that referenced
this pull request
Nov 1, 2026
After #891, a merge that touched most of the engine, main's first Linux run on the 26.04 image built cold and its linux and linux-2 legs were cut off at 34 minutes in Run CI (run 41279330619), each leaving a rerun to finish what it had started. A cut-off run and its rerun cost more than one run let finish, and a warm shard (24 minutes) never comes near the limit. Run CI stops at 54, six inside it, so a cut-off run still saves its compile cache.
Merged
18 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every open motion and animation issue in one pull request: the animation system of #853 built end to end (M0 to M8), and each motion defect closed by a fix and the test that measures it, or shown fixed with its evidence. Work is in dependency order. A box is ticked only when its issue's own acceptance list is met on this branch, and this PR merges only when every box is ticked.
The animation system (#853), in order
Where it stands
Done: #856 (the graph, the mover, starts, stops, turns on the spot, motion matching) and most of #855 (rig, clips, retargeting, root motion, 100STYLE captures; left: lifting the 96-bone cap to 256).
#857, IK and constraints, built in
ae3d.ikrigand held bytests/test_ik_constraints.ae(16 checks, all passing):Left in #857: one foot system (
ae3d.feet, motion's feet andae3d.lookatretired ontoikrig, with #860), and #700's reach across the trunk (an elbow swung clear of the trunk got the palm from 22 to 19 cm but turned the arm at 26 rad/s against its 14; not kept). On the ramp at the walk's own stride the trailing toe stands 16 mm off the slope at push-off; the test walks it at 0.7 of the stride, as people step short up a steep slope.tests/test_anim_pipeline.ae: 81 checks, all passing: a planted toe's slide now 0.03 to 1.4 mm with toes bent at the ball, against 5 mm; a look checks where the head points, within 2°; and turning half round on the spot (#898), no foot crosses the hips' middle (was 16.7 cm past it: a planted foot's heading was held in the turning figure's frame), each step lifts its foot 60 mm (steps slid along the floor), and no ankle passes 3 m/s (a stepping foot jumped 16 cm between frames).tools/anim_showcase.ae(off CI) has seven figures: a runner and a female through starts, stops and sprints; a mannequin on a block taking hits; 100STYLE's captured walker; the figure on a stick (graph, mover, matching); a climber on stairs and a ramp with a bowl (AE3D_WATCH=6); a rifleman aiming with both hands (AE3D_WATCH=7).Also in this PR: the dependency move to Aether 0.860.0 (worker.wait's tuple, file.close) and aephysics 7985511, on which all 290 programs and the editor compile; and the README, rewritten to say what ae3d is and does, with the animation pipeline in it.
CI: GitHub's ubuntu-latest moved to 26.04 during this PR, and its GCC flagged a memset in aephysics's
update_mesh_contact(a byte count in 32-bit wrapping int); aephysics ad0cae2 fixed it and main is back on ubuntu-latest (#901), both merged in here. From this PR: actions/cache saved only after a passing job, so a shard cut off cold never saved and stayed cold (the first Linux suites shard hit 40 minutes twice); the compile cache is now saved whatever became of the run, with Run CI stopped six minutes inside the job's limit, and ci.sh type-checks the modules in its pool (the first Linux suites shard's type-check went from 19 to 25 minutes one module at a time down to 10; the shard from cut off at its limit to 23.5 minutes).docs/animation-pipeline.mdhas the reference.