The solver was refusing a quarter of the sweep
Assumes Exactly right, and unbuildable.
This site’s premise is that a figure of a mechanism should be a solved configuration rather than a drawing. Every position in every figure is the output of Newton–Raphson driven to a residual of , against an analytic Jacobian — written out by hand rather than differenced, because the rank of that matrix is a quantity the site publishes and a differenced matrix has no exact rank.
Four entries of it had the wrong sign. Since the first phase.
Nothing was ever drawn incorrectly. That is the part worth an essay.
What the rows say
A coupler point is a point rigidly attached to a link. It is specified in the link’s own frame by two numbers: along the link from A to B, and perpendicular to it, both as fractions of the link’s length. That is the only way to attach a tracing point that survives a solve — giving it absolute coordinates would make it a free joint and hand the mechanism a degree of freedom it does not have.
The constraint is two equations:
Differentiating is a minute’s work. , because appears once, in . And .
The code had them the other way round. All four of the entries involving — two in each row — carried the opposite sign.
The error is exactly , which is the shape that decides everything about how it behaved.
Why it drew nothing wrong
A Jacobian is not part of the answer. It is part of the route to the answer.
Newton’s method computes a step by solving , applies it, and repeats until the residual is small enough. What makes the final position correct is that the residual is zero, and the residual is computed from the constraint equations, which were right the whole time. Whatever route Newton took, it stopped when the mechanism satisfied its own closure conditions to fourteen places.
A wrong Jacobian gives a wrong step direction, so it changes the route. It does not change the destination, because the destination is defined by a different function.
And the specific wrongness here was benign in the way that hurts most. A step computed from a slightly wrong derivative is usually still a descent direction — it still points, roughly, downhill — so the line search, which tries the full step and halves until the residual improves, kept finding an improvement. Just a smaller and smaller one.
The trace of a single failing solve, at one crank angle of the site’s own default four-bar:
- Iteration 0: residual , improving step found at a quarter of the full step, new residual . Ratio 0.90.
- Iteration 1: improving step at a sixteenth. Ratio 0.9985.
- Iteration 2: improving step at a thirty-second. Ratio 0.9998.
- Stall rule fires. The position is reported as not reached.
Every one of those iterations improved the residual. The solver never diverged, never produced a NaN, never took an uphill step. It simply crawled, and the rule that exists to stop it wasting time on a mechanism that genuinely cannot assemble — two consecutive iterations without meaningful progress — fired on a mechanism that could.
And an unreachable position is not drawn
Here is where the defect becomes invisible rather than merely subtle.
When solve() returns ok: false, the calling code does the right thing: it does not draw that frame. A sweep records it as unreached and moves on. That is correct behaviour and it is exactly what should happen for a crank angle a rocker cannot reach — the site’s whole treatment of non-Grashof linkages depends on being able to ask for a position and be refused.
So a figure built from a sweep had fewer frames in it than it asked for, and every frame it did have was perfect.
There is no visual signature. A coupler curve traced at 360 points and drawn at 302 of them is a coupler curve. A drag slider built from fifteen frames whose sweep dropped some of them still has fifteen frames, because the frames are chosen after the sweep. Nothing anywhere printed a complaint, because nothing had been asked a question it could answer wrongly.
How much of a sweep
The size of the loss depends entirely on how far the coupler point sits off the coupler line, which is what measures — and the dependence has a threshold in it.
At — a coupler point on the line of the coupler — the error vanishes entirely, because it is and is zero. Nothing is lost.
Up to : still nothing. The Jacobian is wrong, the steps are worse than they should be, and Newton absorbs it.
At : 58 of 360 crank angles refused. That is the site’s own default four-bar with a typical coupler point.
At : 159. At : 267 of 360 — three quarters of the turn.
The threshold is why this survived. A defect that appears only past a certain offset, on a site where most coupler points sit at modest offsets, produces intermittent losses that are individually unremarkable.
The historical behaviour in that figure is reconstructed rather than remembered: withOldAttachSigns rebuilds the wrong rows from the corrected ones, which is a two-line transformation because the error was so simple. So the figure is a measurement rather than a recollection, and it is the same helper the gate’s refusal uses — a figure that rebuilt the defect its own way could disagree with the gate about what the defect was.
Why nothing caught it
This is the part with something transferable in it, because the site already had what looks like the right check.
assertVelocityAgreesWithFiniteDifference compares an analytic velocity against a differenced one. It has been in the gate since the foundation and it caught a genuine sign error in that phase — the velocity solve had double-negated its right-hand side, and the check reported exactly 2, which is the signature of a flipped sign.
It cannot catch this one, and the reason is structural rather than a matter of tolerance. The analytic velocity is obtained by solving a linear system with the same Jacobian. If that Jacobian is wrong, the linear solve produces a velocity that satisfies a wrong equation — but the position it is compared against comes from Newton, which is driven to a residual of whatever route it takes. The two are compared after both have converged, and both converge to the right answer.
A wrong Jacobian makes Newton slower, not wrong. So every quantity the site prints — every position, velocity, angle, ratio, rank — was correct, and the only observable was a count of frames that nobody was counting.
The other twenty-three shared gates have the same blind spot for the same reason. svg_check asks whether a label fits and contrasts and stays inside the viewBox. figbox asks whether a figure fills its canvas. drag_check drives every slider in a real browser and asks whether it works. None of them asks whether a figure has as many frames as it asked for, because until now nobody knew that was a thing a figure could lose.
The check that would have caught it
It is one page, and it is now in the gate.
assertJacobianMatchesFiniteDifference takes each mechanism the site builds, nudges it off its solution so that no residual is identically zero, and compares every entry of the analytic Jacobian against a central difference of the residual it differentiates. The nudge matters: a row that is only correct at the answer would hide from a check performed at the answer.
On the corrected code the worst disagreement across the four-bar, a coupler four-bar with a negative offset, and a slider-crank is , which is finite-difference noise. On the shipped code it was , at exactly the entries above.
And the check has a refusal, because this site’s habit is that an assertion which has never rejected anything proves nothing. assertTheJacobianCheckCatchesTheOldSigns rebuilds the pre-correction rows and hands them to the same comparison, and fails if they are accepted. The thing it exists to catch is a specific, nameable thing, and it is required to still catch it.
What changed when it was fixed
Thirty of the site’s 240 built pages changed, and the changes fall into two kinds.
Figures gained frames. The cognate figure — three four-bars tracing one curve — gained an entire linkage. One of the three cognates has a coupler point far off its own coupler line, its sweep had been failing, and it was simply not being drawn. The figure looked complete because two linkages tracing one curve is already the point.
A published number moved by two thirds. The defect survey synthesises 1,176 four-bars through three prescribed poses and asks how many of them a machine could use, and it answers by sweeping each one and recording which poses it reaches. A pose the sweep cannot reach is recorded as a branch defect — the linkage is exactly correct and cannot get there in one piece — which is precisely what a refused solve looks like.
So the survey had been counting the solver’s refusals as the mechanism’s defects.
Corrected: 176 usable rather than 111, 810 branch defects rather than 934, and 190 order defects rather than 131. Sixty-five linkages that reach all three poses perfectly well had been reported as unable to, and another fifty-nine had been filed under the wrong defect.
Fifteen per cent, not nine. The essay that publishes that number has carried the corrected version since, with the change recorded in it rather than quietly applied.
The line search was not the problem, and looked like it
Finding this took a wrong turn worth recording, because the evidence pointed somewhere else first.
The symptom that started it was a six-bar that would not sweep. Chasing it back gave a four-bar that would not sweep, and chasing that back gave the trace above: a solve taking one improving step at a quarter, one at a sixteenth, one at a thirty-second, and stalling.
That is a textbook picture of a line search misbehaving. Steps shrinking geometrically while the residual barely moves is what happens when a backtracking search is too eager to accept, or when the acceptance test is wrong, or when the stall rule is too aggressive. Three plausible fixes present themselves immediately, and all three would have “worked” in the sense of making the sweep complete: loosen the stall rule to three consecutive failures, or accept only steps that reduce the residual by a fixed fraction, or raise the iteration cap.
Each of those would have papered over a wrong derivative with a slower solver, and the survey would still have been counting refusals as branch defects — fewer of them, at a cost in build time, with the cause untouched.
What stopped it was noticing that the full Newton step increased the residual. Near a solution, with a correct Jacobian and a well-conditioned system, that should not happen: the whole point of Newton is that the full step is the good one. A full step that makes things worse at a residual of is not a line-search problem. It is a statement about the direction, and the direction comes from one place.
The general form: when a solver crawls, ask whether the direction is right before improving the search along it. A line search exists to compensate for a nonlinearity, not for a wrong derivative, and the two look identical from the outside.
Where the fix belongs
lib/mechanism.js is this site’s own, so the fix belongs here and nowhere else — and that was checked rather than assumed. A grep across all twenty fleet sites and the four shared kits finds the attach constraint in exactly one place. No other site has a solver with this construct, so there is nothing to promote and nothing else to repair.
That is the pleasant version of the fleet’s rule about where a fix goes. The unpleasant version, which this site has recorded before, is ticks() in figure-kit — a shared function that returned an empty array for a descending domain, so that two figures in the fleet carried no tick labels and no gridlines at all, at 24 out of 24 gates, because every gate asks whether a label fits and none asks whether it exists.
The two defects have the same shape and it is worth naming it: the symptom was absence. A figure with no gridlines and a sweep with missing frames are both things that are not there, and a gate written to check the properties of what is there cannot see either.
What to check for the next one
The general form of the lesson is a question worth asking of any numerical machinery, and it is not the obvious one.
The obvious question is “is the answer right”, and this site asks it constantly — two routes to every number, a closed form where one exists, a finite difference against every analytic derivative that feeds an answer.
The question that was missing is “is the machinery being asked something it can answer wrongly?” A derivative that only affects convergence speed is not being asked anything. It has no output that anybody reads. It can be arbitrarily wrong and every printed number stays correct, and the only way to test it is to compare it against something directly — which is a test with no motivation until the moment it finds something.
So the practical residue is a small rule: any quantity that is computed and not read is untested by construction. The Jacobian was such a quantity for four phases. The list of others on this site is short and now worth walking: the rank gap, which is reported and rarely inspected; the condition estimates inside the damped solve; the residual of a fit that is required only to be small. Each of those is a number the code computes, uses to make a decision, and never publishes — which is exactly the position the Jacobian was in.
The cheapest defence is the one now in the gate, and it is cheap because it needs no theory: compare the analytic thing against a numerical thing that was derived independently. A central difference of a residual knows nothing about how the derivative was written out, so agreement between the two is evidence about both. It costs one evaluation per column, it runs in under a second across every mechanism the site builds, and it would have found this on the day the code was written.
The reason it was not there is the ordinary one. A check is written when somebody suspects something, and nobody suspects a derivative that produces correct answers.
The defect’s signature generalises past this one bug and is worth carrying as a diagnostic. A wrong Jacobian does not move a converged answer, because convergence is decided by the residual and the residual is computed from the constraints rather than from their derivatives. What it moves is the number of iterations, since Newton’s step is only as good as the derivative it was built from — a wrong sign turns quadratic convergence into a crawl, and a crawl reaches the stall rule before it reaches the answer. So iteration count is the quantity that carries information about a derivative, and it is the one nobody logs. A solver that recorded its mean and worst iteration counts across a sweep would have shown a quarter of the positions taking an order of magnitude more steps than the rest — a pattern no residual could produce and no picture could show. A wrong derivative is invisible in the output and loud in the effort, which is a good argument for measuring the effort.
About the same objects
Not linked from either essay — found by the objects both name.
- Four bars and four pins loop closure · newton–raphson
- The chain that does not close jacobian · loop closure
- The machine, compiled loop closure · newton–raphson
- Two circles, four answers kinematic solve · loop closure
What links here
The 8 of 21 essays linking to this one that name the most of the same objects.
- Two routes to a sensitivity As built
- A length is a range As built
- Two routes to a Jacobian One path to the tool
- The mechanism drops out The motion, not the mechanism
- The search that was right How many answers
- A gap is a number Links with a width
- A roller is not a slider Machines you have met
- Deciding that two chains are one The chain before the lengths
The objects this essay names
Each one links to every other essay that touches it.
Coupler pointFinite differenceJacobianKinematic solveLine searchLoop closureNewton–RaphsonVerification