CNA-BUG-064: Curve::Evaluate reads the wrong key for Step continuity away from position 1 and for a Linear post-loop

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.

A Step segment returns the next key's value whenever the evaluated position is at least 1.0 (an absolute constant) instead of at the segment's end, and a Linear post-loop extrapolates with the first key's TangentOut instead of the last key's; XNA does neither.

Identifier
CNA-BUG-064
Category
Bug
Subsystem
Math & geometry
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Severity
Medium (a triage suggestion, not a project priority)
Evidence basis
Source-verified: read at TARGET, not executed
Tests touching this area
Yes: see Current tests
Affected contract
float Curve::Evaluate(float position) const with CurveContinuity::Step keys or CurveLoopType::Linear post-loop; C ABI cna_curve_evaluate

Expected behaviour

XNA 4.0 (read from the IL of the genuine XNA 4.0 Microsoft.Xna.Framework assembly; not executed): FindSegment computes the normalised parameter t in [0, 1] of the segment (in double; a width of at most 1e-10 gives t = 0), and Hermite returns the next key's value for a Step key only when t >= 1, i.e. at the segment's end, and the previous key's value everywhere else in the segment. After the last key, CurveLoopType.Linear returns last.Value - last.TangentOut * (last.Position - position), the last key's own slope.

Actual behaviour at TARGET

  • Step: Curve::GetCurvePosition in Curve.cpp returns next.getValueProperty() when position >= 1.0f and the previous value otherwise, comparing the absolute position with the constant 1. For keys (0, 0, Step), (1, 5, Step), (2, 9), Evaluate(1.5f) returns 9 where XNA returns 5; for Step keys at 0 and 0.5, Evaluate(0.5f) returns the first key's value where XNA returns the second's.
  • Linear post-loop: Curve::Evaluate returns last.Value + first.TangentOut * (position - last.Position). For keys (0, 0, TangentOut 2) and (1, 1, tangents 0), Evaluate(2.0f) returns 3 where XNA returns 1.

Both lines are ported unchanged from FNA's C# implementation (read at FNA commit 1358793, 2026-08-01); its comment on the Linear line even says "with a tangent of last point". The pre-loop Linear case (first key's TangentIn) matches XNA.

Source locations

Evidence

Checked by reading at 009d40f5; nothing was built or executed for this entry. Both branches were read in Curve.cpp; XNA's segment search, Hermite and loop handling were read from its IL, which settles the Step question that earlier documentation left open. The worked values were derived by hand from the two formulas.

Focused reproduction

Illustrative; not compiled or run for this entry.

Curve step;
step.getKeysProperty().Add(CurveKey(0.0f, 0.0f, 0.0f, 0.0f, CurveContinuity::Step));
step.getKeysProperty().Add(CurveKey(1.0f, 5.0f, 0.0f, 0.0f, CurveContinuity::Step));
step.getKeysProperty().Add(CurveKey(2.0f, 9.0f));
float a = step.Evaluate(1.5f);          // XNA: 5     TARGET code: 9

Curve lin;
lin.getKeysProperty().Add(CurveKey(0.0f, 0.0f, 0.0f, 2.0f));
lin.getKeysProperty().Add(CurveKey(1.0f, 1.0f));
lin.setPostLoopProperty(CurveLoopType::Linear);
float b = lin.Evaluate(2.0f);           // XNA: 1     TARGET code: 3

Current tests

CurveTest.EvaluatePostLoopLinear in CurveTests.cpp builds exactly the Linear case above and asserts 3, with a comment deriving it from first.TangentOut: it pins the defect and must change with the fix. CurveTest.StepContinuityReturnsCurrentSegmentValue evaluates at 0.5 in a segment from 0 to 1, where the absolute and normalised tests agree. The C ABI CurveSmoke.c loop checks use keys whose tangents are all 2, so they pass either way. No XNA oracle covers Curve.

Regression test

The two cases above with XNA's answers (5 and 1), a Step segment ending before 1.0 evaluated exactly at its end, and a correction of EvaluatePostLoopLinear's expectation; ideally cross-checked by running the genuine runtime through CNA's framework-oracle tooling.

Blast radius

Any curve with Step keys whose segments do not happen to span 0 to 1, and any curve with a Linear post-loop whose first and last out tangents differ, evaluated in C++, through cna_curve_evaluate or after loading from XNB. Smooth continuity inside the range, the other loop types and the pre-loop are unaffected by these two lines.

Workaround

For Step curves, look up the key values directly; for a Linear post-loop, set the first key's TangentOut equal to the last key's or extrapolate outside the curve.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Known issues
Bug index