{"record":{"id":"2bff12e5a9527309","repo":"heygen-com/hyperframes","slug":"hue-curve-inputs-must-be-unique","errorCode":null,"errorMessage":"Hue curve inputs must be unique","messagePattern":"Hue curve inputs must be unique","errorType":"validation","errorClass":"RangeError","httpStatus":null,"severity":"error","filePath":"packages/core/src/colorGradingCurves.ts","lineNumber":188,"sourceCode":"/** Samples a periodic hue curve without duplicating the 0/360-degree texel. */\nexport function compileHfHueCurve(\n  points: readonly HfHueCurvePoint[],\n  outputMin: number,\n  outputMax: number,\n  size = HF_COLOR_CURVE_LUT_SIZE,\n): Float32Array {\n  if (points.length < 3) throw new RangeError(\"A hue curve requires at least three points\");\n  if (points.length > HF_COLOR_CURVE_MAX_POINTS) {\n    throw new RangeError(`A hue curve supports at most ${HF_COLOR_CURVE_MAX_POINTS} points`);\n  }\n  const sorted = [...points].sort((a, b) => a[0] - b[0]);\n  for (let index = 0; index < sorted.length; index += 1) {\n    const point = sorted[index];\n    if (!point || point[0] < 0 || point[0] >= 360) {\n      throw new RangeError(\"Hue curve inputs must be from 0 up to 360 degrees\");\n    }\n    if (index > 0 && point[0] === sorted[index - 1]?.[0]) {\n      throw new RangeError(\"Hue curve inputs must be unique\");\n    }\n  }\n  const before = sorted.slice(-2).map(([hue, delta]) => [hue - 360, delta] as const);\n  const after = sorted.slice(0, 2).map(([hue, delta]) => [hue + 360, delta] as const);\n  const periodic = [...before, ...sorted, ...after];\n  validateCurvePoints(periodic, size);\n  return compileCurveSamples(periodic, size, (index) => (index / size) * 360, outputMin, outputMax);\n}\n","sourceCodeStart":170,"sourceCodeEnd":197,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/core/src/colorGradingCurves.ts#L170-L197","documentation":"After sorting, the loop at line 182-189 rejects two consecutive points with identical hue values. Duplicate hues make the spline's per-segment span zero, which would divide by zero in pointSlopes (span = point[0] - previous[0]).","triggerScenarios":"Two points in the input array whose first element compares equal after sort, e.g. [[120, 0.1], [120, -0.1], [240, 0]]. Float-exact equality is required to trip the check.","commonSituations":"Copy-paste of a control point; rounding hue to integer degrees producing ties; merging two presets that both anchor the same hue.","solutions":["Detect duplicate hues (e.g. via a Set on the first element) and drop or nudge conflicting points before compile.","When rounding hues, snap to a fixed grid that cannot collide (or merge deltas at ties).","Run validateHueCurve() upstream — it flags duplicate hue inputs."],"exampleFix":"// before\ncompileHfHueCurve([[0, 0.1], [120, 0.05], [120, -0.05], [240, 0]], -1, 1);\n\n// after — merge or nudge duplicates\ncompileHfHueCurve([[0, 0.1], [120, 0.0], [240, 0]], -1, 1);","handlingStrategy":"validation","validationCode":"function dedupeHues(points: readonly [number, number][]): [number, number][] {\n  const seen = new Set<number>();\n  return points.filter(([hue]) => {\n    if (seen.has(hue)) return false;\n    seen.add(hue);\n    return true;\n  });\n}\n\ncompileHfHueCurve(dedupeHues(points), -1, 1);","typeGuard":"function hasUniqueHues(points: readonly [number, number][]): boolean {\n  const hues = points.map(([h]) => h);\n  return new Set(hues).size === hues.length;\n}","tryCatchPattern":"try {\n  compileHfHueCurve(points, -1, 1);\n} catch (err) {\n  if (err instanceof RangeError && err.message === 'Hue curve inputs must be unique') {\n    // merge or nudge duplicate hues, then retry\n  } else throw err;\n}","preventionTips":["Track hues in a Set during authoring to reject duplicates at entry.","Round hues to a fixed grid that cannot collide.","Run validateHueCurve() upstream — it reports duplicate hue inputs with the path."],"tags":["color-grading","hue-curve","duplicates","range-error"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}