Repository navigation
Joints in the IR, the pin/pull/join/unjoin verbs, bodies from any rig, and the solve compile pass (P2, an#512) - #533
Merged
Merged
Conversation
… and the solve compile pass (P2, an#512)
…ror, pull as a rigid goal, reach wording, override spans, malformed descriptor joints, gate reads only rigs, verb spans recorded for validate)
This was referenced Oct 24, 2026
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.
Closes #512 (P2 of #497; ADR 0008). Depends on P1 (#528, merged).
What
JointSpec(pin,hingewithlimitsin degrees about the rest pose,weld,distance/string/rod,slide,contact,give,name) onShot.joints, on an entity (AssetRef.joints,arelative to it) and in a descriptor (joints:);Bone.limitson the rig;Shot.solve(SolveSettings:mode,gravity,damping,iterations,substeps) over the style pack'sgravity/damping. A body reference ispath[@u,v | @bone](a bone with no slot is a landmark, e.g. a grip) orworld@x,y.an.ir.joints, withscene.mdforms and builders):pin(exactly there for a span; the moved side is freed, so a pinned cup goes to the hand; refused out of reach, an error),pull(toward a point or along a path: exact where reachable, given up for the frame where not, so the chain reaches toward it; a positivegivemakes it a soft string; never a violation),join/unjoin.an.stage.rig.rig_structure): every slot's node is a body unless it rides a slot on its own bone;Bone.parentis a hinge at the child bone's position; slotless bones are skipped over and kept as named points.solvepass (an.stage.solve, order 525, betweenfaceandcamera_follow): evaluates the partial document at every frame (thestep_hzgrid when set, stepped keys), holds a rig's roots and every authored channel (a nested part's authored rotation becomes a weld at that angle), solves withan.kinematics.solvewarm-started frame to frame (dynamicwhen gravity or a string acts, elsepose), and emits one quantised key per frame in a__solve__<entity>clip placed last in the entity's track (x,y,rotationfor roots and flat parts;rotationalone in anesting: boneschain). A channel the solve leaves where the document has it is not emitted.meta.extensions.solverecords mode, solver version, effort, an inputs digest (inputs_digest, per P1's note), the joints, refused joints (a refused pin with its reach and a place in reach), rejoin snaps and the authored spans a solved channel overrides.Constraintinterface (an.verify.joints, order 250, after the catalogue's lints):unknown_body(error; the compile raises a typedUnknownBodyErrortoo),unreachable_pin(error, with the chain's reach and where to put the target),joint_violation(warning: a joined joint apart or bent past its range in the compiled output, a rejoin over 2 px; overridden spans as info). Each hasterm= the solver residual (joint_residuals).render_knobs["solve"](version, mode, effort, a digest of the declarations) only for a shot that declares joints; every other shot's key is unchanged.misc/bench/corpus/joint_pin(compile only, not inCORE_FIXTURES, no goldens); docs: architecture row and module map,scene.mdcontract, theanandan-specskills, changelog.Tests (
tests/test_solve.py)The acceptance cases, in flat and
bonesnesting: the hand on the grip at every frame (< 1e-3 px) with the elbow inside its range while the torso sways; a string undergravity: downsettles in dynamic mode and never stretches;unreachable_pinwith the reach;joint_violationwhen authored motion breaks a join;unknown_bodyfor a body, a point and a joint; a pinned prop goes to the hand without pulling the arm; a rejoin across a gap is a snap; the step grid; determinism; thescene.mdround trip; every core-corpus fixture compiles byte-identically with and without the pass.Decisions (not blessed elsewhere)
overrides: {free: true | [parts]};overrides: {fixed: true}holds every part. Without this a hand pinned to a wheel drags the whole figure.pin/pullis itspoint: a body target is solved without the verb first, then the verb pins to where it went (two solves per frame, only for such verbs).aand its extent's centre forb.pulldoes not make a shot dynamic; a declaredstringjoint (or gravity) does.fit_channelcompaction is not built. Both are follow-ups.scene.mdgainsjoints:/solve:in the shot block.Shot.welds(The held_item lint: welds (catalogue A4, #519 rank 10) #527) is still JSON-only; not touched here.Adversarial review (one round)
A fresh reviewer found and reproduced: a
nesting: boneschild drawn before its parent read its parent's unsolved pose (bodies are now ordered parents first); scale and mirroring frozen at rest (now read per frame: extents, mirrored points, hinge rests and ranges);pullreported as a violation and tearing the rig when unreachable (now a rigid goal the solver gives up first, excluded fromjoint_violation, and not a reason for dynamic mode);unreachable_pinsaying "out of reach" for a pin held off by an authored channel (now says so), and overridden-span info for channels the solve held; a malformed descriptorjoints:raising a raw pydantic error (now anunknown_bodyfinding and a typed compile error); the pass's gate reading non-rig store entries (a voice), which moved such shots' cache keys (now rig descriptors only); validate placing verbs without the plays' natural lengths (the compile now records spans and join events inmeta.extensions.solve, and validate reads them). Each has a regression test.Not fixed here (follow-ups): the other compile-based lints (
an.verify.constraints.compiled_shot,_placementsin validate) compile without the style pack, so under a gravity style they see a solve without gravity, and validate solves twice; one frame where a refused pull becomes reachable can under-converge (P1's slow-convergence path, reported honestly byjoint_violation);fit_channelcompaction; a blend for rejoins under 2 px.Labelled
run-browser-tests: a compiled document changes when joints are declared. Nothing here is verified in a browser on a developer machine.https://claude.ai/code/session_01JZRAaW59TnBKD9A6YKPPaR