aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-28 10:31:32 +0100
committerDanilo M. <danix@danix.xyz>2026-10-28 10:31:32 +0100
commit1402ca7d6c2bb0cea2cf52a4ecce7b809ebf31b1 (patch)
treeebb446e11002b1a5a5ac7a22a95cfeca425f1017
parentf5a4bab82e97de7d20d1e6b5cae250a150b71891 (diff)
downloadgenerat3D-1402ca7d6c2bb0cea2cf52a4ecce7b809ebf31b1.tar.gz
generat3D-1402ca7d6c2bb0cea2cf52a4ecce7b809ebf31b1.zip
Reject malformed spline counts and unsorted knots
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
-rw-r--r--generat3d/formula.py8
-rw-r--r--tests/test_formula.py26
2 files changed, 33 insertions, 1 deletions
diff --git a/generat3d/formula.py b/generat3d/formula.py
index 2165e16..2434d89 100644
--- a/generat3d/formula.py
+++ b/generat3d/formula.py
@@ -123,7 +123,10 @@ class Graph:
def _spline_op(name, stack):
- n = int(stack.pop())
+ n = stack.pop()
+ if isinstance(n, bool) or not isinstance(n, (int, float)) or n != int(n):
+ raise ValueError
+ n = int(n)
if n < 1 or n > len(stack) - 1:
raise IndexError
knots = stack[-n:]
@@ -135,6 +138,8 @@ def spline(kind, x, knots):
"""Evaluate a DSON spline at x. Knots are [x, y] or [x, y, t, c, b], sorted by x."""
xs = [k[0] for k in knots]
ys = [k[1] for k in knots]
+ if any(a > b for a, b in zip(xs, xs[1:])):
+ raise ValueError("spline knots not sorted by x")
if x <= xs[0]:
return ys[0]
if x >= xs[-1]:
@@ -147,6 +152,7 @@ def spline(kind, x, knots):
return ys[i] + s * (ys[i + 1] - ys[i])
# ponytail: Kochanek-Bartels with uniform segment parameterization;
# if the corpus shows Daz scales tangents by knot spacing, add that here.
+ # end tangents use a one-sided difference (halved), check endpoints against the corpus too.
m0 = _tcb_tangent(knots, ys, i, outgoing=True)
m1 = _tcb_tangent(knots, ys, i + 1, outgoing=False)
s2, s3 = s * s, s * s * s
diff --git a/tests/test_formula.py b/tests/test_formula.py
index 7440681..10065d2 100644
--- a/tests/test_formula.py
+++ b/tests/test_formula.py
@@ -168,3 +168,29 @@ class SplineTest(unittest.TestCase):
g.add_prop("b")
g.add_formula("b", spline_ops("spline_linear", [[0, 0], [1, 10]]))
self.assertAlmostEqual(g.evaluate()["b"], 5.0)
+
+ def test_fractional_count_is_fatal(self):
+ g = Graph()
+ g.add_prop("a", 0.5)
+ g.add_prop("b")
+ ops = [push("a"), val([0, 0]), val([1, 1]), val(2.5), {"op": "spline_linear"}]
+ g.add_formula("b", ops, source="f")
+ with self.assertRaisesRegex(Error, "malformed formula for b"):
+ g.evaluate()
+
+ def test_unsorted_knots_are_fatal(self):
+ g = Graph()
+ g.add_prop("a", 0.5)
+ g.add_prop("b")
+ g.add_formula("b", spline_ops("spline_linear", [[1, 1], [0, 0], [2, 5]]), source="f")
+ with self.assertRaisesRegex(Error, "malformed formula for b"):
+ g.evaluate()
+
+ def test_count_larger_than_stack_is_fatal(self):
+ g = Graph()
+ g.add_prop("a", 0.5)
+ g.add_prop("b")
+ ops = [push("a"), val(0), val(1), {"op": "spline_linear"}]
+ g.add_formula("b", ops, source="f")
+ with self.assertRaisesRegex(Error, "malformed formula for b"):
+ g.evaluate()