Bug found: Axes renders its axis line in the wrong place whenever the range is asymmetric about zero


Root cause (src/mobject/coordinate_systems.ts): Axes builds each NumberLine centered on world-origin, then calls xAxis.shift(-xAxis.numberToPoint(xRef)) to slide the rendered axis line so its zero-reference lands at the world origin. That shift mutates the axis's drawn points — but NumberLine.numberToPoint() (the function Axes.c2p() calls for every plotted point, dot, or line) is never updated to account for the shift; it still uses the pre-shift leftX. The result: the visible axis line moves, but every coordinate computed through c2p() doesn't.
For a symmetric range like [-4, 4] (the value in ecmanim's own bundled graph.ts example), the shift happens to be zero, so the bug is invisible — which is exactly why it slipped through their existing example and test suite. Our birthday-paradox scene used xRange: [0, 70], and I verified algebraically that any range where the reference point isn't the midpoint (e.g. [0,70], [10,20]) produces a mismatch — in our case a 4.5-unit horizontal offset, which is exactly the gap visible between the amber curve and the axis tick marks in the earlier frames you flagged.
I confirmed this is a real ecmanim defect, not a sandbox or scene-script issue, by:
- Deriving the offset algebraically from the library's own source and testing it across four different
xRange/xLength combinations.
- Writing a minimal repro scene using only
Axes + Dot + c2p() — no custom math, no scene logic of mine — that reproduces the same visible offset.
Bug found:
Axesrenders its axis line in the wrong place whenever the range is asymmetric about zeroRoot cause (
src/mobject/coordinate_systems.ts):Axesbuilds eachNumberLinecentered on world-origin, then callsxAxis.shift(-xAxis.numberToPoint(xRef))to slide the rendered axis line so its zero-reference lands at the world origin. That shift mutates the axis's drawn points — butNumberLine.numberToPoint()(the functionAxes.c2p()calls for every plotted point, dot, or line) is never updated to account for the shift; it still uses the pre-shiftleftX. The result: the visible axis line moves, but every coordinate computed throughc2p()doesn't.For a symmetric range like
[-4, 4](the value in ecmanim's own bundledgraph.tsexample), the shift happens to be zero, so the bug is invisible — which is exactly why it slipped through their existing example and test suite. Our birthday-paradox scene usedxRange: [0, 70], and I verified algebraically that any range where the reference point isn't the midpoint (e.g.[0,70],[10,20]) produces a mismatch — in our case a 4.5-unit horizontal offset, which is exactly the gap visible between the amber curve and the axis tick marks in the earlier frames you flagged.I confirmed this is a real ecmanim defect, not a sandbox or scene-script issue, by:
xRange/xLengthcombinations.Axes+Dot+c2p()— no custom math, no scene logic of mine — that reproduces the same visible offset.