import XCTest @testable import ForgeColor final class CurveTests: XCTestCase { // Default 2-point identity curve is a no-op. func testIdentityNoOp() { let c = Curve.identity for x: Float in [0, 0.1, 0.18, 0.5, 0.9, 1] { XCTAssertEqual(c.evaluate(x), x, accuracy: 1e-6) } } // Curve passes exactly through its control points. func testInterpolatesControlPoints() { let c = Curve(points: [ .init(x: 0, y: 0), .init(x: 0.25, y: 0.4), .init(x: 0.75, y: 0.7), .init(x: 1, y: 1), ]) XCTAssertEqual(c.evaluate(0.25), 0.4, accuracy: 1e-5) XCTAssertEqual(c.evaluate(0.75), 0.7, accuracy: 1e-5) XCTAssertEqual(c.evaluate(0), 0, accuracy: 1e-5) XCTAssertEqual(c.evaluate(1), 1, accuracy: 1e-5) } // Monotonic control points -> monotonic output (no spline overshoot ringing). func testMonotonicPreserved() { let c = Curve(points: [ .init(x: 0, y: 0), .init(x: 0.1, y: 0.5), // steep segment then flat — classic overshoot trap .init(x: 0.2, y: 0.55), .init(x: 1, y: 1), ]) var prev: Float = -1 for i in 0...1000 { let y = c.evaluate(Float(i) / 1000) XCTAssertGreaterThanOrEqual(y, prev - 1e-6, "non-monotonic at x=\(Float(i) / 1000)") prev = y } } // Input outside domain pins to endpoint values. func testEndpointPinning() { let c = Curve(points: [.init(x: 0.2, y: 0.3), .init(x: 0.8, y: 0.9)]) XCTAssertEqual(c.evaluate(0), 0.3, accuracy: 1e-6) XCTAssertEqual(c.evaluate(-5), 0.3, accuracy: 1e-6) XCTAssertEqual(c.evaluate(1), 0.9, accuracy: 1e-6) XCTAssertEqual(c.evaluate(99), 0.9, accuracy: 1e-6) } // Points auto-sorted by x. func testUnsortedPointsSorted() { let c = Curve(points: [.init(x: 1, y: 1), .init(x: 0, y: 0), .init(x: 0.5, y: 0.6)]) XCTAssertEqual(c.evaluate(0.5), 0.6, accuracy: 1e-5) } // CurveSet: master applies to all channels, then per-channel. func testCurveSetMasterThenChannel() { var set = CurveSet.identity set.master = Curve(points: [.init(x: 0, y: 0), .init(x: 1, y: 0.5)]) // halve set.red = Curve(points: [.init(x: 0, y: 0.1), .init(x: 1, y: 1.1)]) // +0.1 let out = set.apply(SIMD3(1, 1, 1)) XCTAssertEqual(out.x, 0.6, accuracy: 1e-4) // master 1->0.5, red 0.5->0.6 XCTAssertEqual(out.y, 0.5, accuracy: 1e-4) XCTAssertEqual(out.z, 0.5, accuracy: 1e-4) } func testCurveSetIdentityNoOp() { let out = CurveSet.identity.apply(SIMD3(0.3, 0.6, 0.9)) XCTAssertEqual(out.x, 0.3, accuracy: 1e-6) XCTAssertEqual(out.y, 0.6, accuracy: 1e-6) XCTAssertEqual(out.z, 0.9, accuracy: 1e-6) } }