{
  "version": 1,
  "language": "NeedleScript",
  "categories": [
    {
      "id": "syntax-control",
      "title": "Syntax & control flow",
      "description": "Grammar, declarations, procedures, and control flow"
    },
    {
      "id": "movement",
      "title": "Movement & turtle state",
      "description": "Turtle movement, heading, pen state, and state stack"
    },
    { "id": "transforms", "title": "Transforms", "description": "Block-scoped affine transforms" },
    {
      "id": "effects",
      "title": "Effects",
      "description": "Block-scoped nonlinear and stitch effects"
    },
    { "id": "trace", "title": "Trace", "description": "Capturing turtle geometry as path data" },
    {
      "id": "stitching",
      "title": "Stitching & machine control",
      "description": "Thread, fill, satin, planning, material, and machine commands"
    },
    { "id": "math", "title": "Core math", "description": "Core scalar math and turtle reporters" },
    {
      "id": "lists",
      "title": "Lists & sequences",
      "description": "List creation, queries, mutation, and sequences"
    },
    {
      "id": "higher-order",
      "title": "Higher-order functions",
      "description": "Procedure references, mapping, filtering, composition, and binding"
    },
    {
      "id": "strings",
      "title": "Strings",
      "description": "String conversion, queries, and transformations"
    },
    {
      "id": "colors",
      "title": "Colors",
      "description": "Color construction, interpolation, palette matching, and active color metadata"
    },
    {
      "id": "generative-scalars",
      "title": "Generative scalar math",
      "description": "Interpolation, seeded distributions, and noise fields"
    },
    { "id": "vectors", "title": "Vectors", "description": "Point and vector arithmetic" },
    {
      "id": "segments",
      "title": "Segments",
      "description": "Segment intersection and distance queries"
    },
    {
      "id": "paths-curves",
      "title": "Paths & curves",
      "description": "Path measurement, editing, resampling, curves, and routing"
    },
    {
      "id": "geometry",
      "title": "Geometry generators & operations",
      "description": "Sampling, tessellation, regions, clipping, and fill paths"
    },
    { "id": "field", "title": "Hoop field", "description": "Sewable-field queries" },
    {
      "id": "path-transforms",
      "title": "Pure path transforms",
      "description": "Functional transforms and path effects"
    },
    {
      "id": "satin-helpers",
      "title": "Satin helpers",
      "description": "Programmable satin and rail-pair tuple helpers"
    },
    {
      "id": "fill-helpers",
      "title": "Fill helpers",
      "description": "Programmable fill tuple helpers"
    },
    {
      "id": "history",
      "title": "Stitch history",
      "description": "Live coverage and prior-penetration queries"
    }
  ],
  "features": [
    {
      "id": "repeat",
      "label": "repeat",
      "category": "syntax-control",
      "tags": ["block", "keyword", "library", "syntax-control"],
      "summary": "Loop n times. `repcount` is the 1-based counter of the innermost repeat.",
      "editor": {
        "kind": "keyword",
        "detail": "loop n times",
        "documentation": "Loop n times. `repcount` is the 1-based counter of the innermost repeat.\n\n```\nrepeat 36 [\n  fd 5  rt 10\n]\n```",
        "completion": { "kind": "text", "text": "repeat ${1:n} [\n\t$0\n]" },
        "isSnippet": true
      }
    },
    {
      "id": "while",
      "label": "while",
      "category": "syntax-control",
      "tags": ["block", "keyword", "library", "syntax-control"],
      "summary": "Loop while the condition is true (non-zero). `while true [ … break ]` is the idiomatic search loop.",
      "editor": {
        "kind": "keyword",
        "detail": "loop while condition is true",
        "documentation": "Loop while the condition is true (non-zero). `while true [ … break ]` is the idiomatic search loop.",
        "completion": { "kind": "text", "text": "while ${1:condition} [\n\t$0\n]" },
        "isSnippet": true
      }
    },
    {
      "id": "for",
      "label": "for",
      "category": "syntax-control",
      "tags": ["block", "keyword", "library", "syntax-control"],
      "summary": "Counted loop: `for i = 0 to n [ … ]` — inclusive of *to*, step defaults to 1.",
      "editor": {
        "kind": "keyword",
        "detail": "counted or for-in loop",
        "documentation": "**Counted loop:** `for i = 0 to n [ … ]` — inclusive of *to*, step defaults to 1.\n\n**For-in loop:** `for x in xs [ … ]` — iterate list elements.\n\n**With step:** `for i = 10 to 1 step -2 [ … ]`",
        "completion": { "kind": "text", "text": "for ${1:i} = ${2:0} to ${3:n} [\n\t$0\n]" },
        "isSnippet": true
      }
    },
    {
      "id": "if",
      "label": "if",
      "category": "syntax-control",
      "tags": ["block", "keyword", "library", "syntax-control"],
      "summary": "Conditional block. Chains with `else if` and `else`.",
      "editor": {
        "kind": "keyword",
        "detail": "conditional",
        "documentation": "Conditional block. Chains with `else if` and `else`.\n\n```\nif x > 0 [\n  fd x\n] else [\n  bk x\n]\n```",
        "completion": { "kind": "text", "text": "if ${1:condition} [\n\t$0\n]" },
        "isSnippet": true
      }
    },
    {
      "id": "else",
      "label": "else",
      "category": "syntax-control",
      "tags": ["block", "keyword", "library", "syntax-control"],
      "summary": "Follows an `if` block. Can chain: `if … else if … else …`.",
      "editor": {
        "kind": "keyword",
        "detail": "alternative branch",
        "documentation": "Follows an `if` block. Can chain: `if … else if … else …`.",
        "completion": { "kind": "text", "text": "else [\n\t$0\n]" },
        "isSnippet": true
      }
    },
    {
      "id": "break",
      "label": "break",
      "category": "syntax-control",
      "tags": ["keyword", "library", "syntax-control"],
      "summary": "Exits the innermost `repeat`, `while`, or `for` loop immediately.",
      "editor": {
        "kind": "keyword",
        "detail": "exit innermost loop",
        "documentation": "Exits the innermost `repeat`, `while`, or `for` loop immediately.",
        "completion": { "kind": "text", "text": "break" }
      }
    },
    {
      "id": "continue",
      "label": "continue",
      "category": "syntax-control",
      "tags": ["keyword", "library", "syntax-control"],
      "summary": "Skips to the next iteration of the innermost loop.",
      "editor": {
        "kind": "keyword",
        "detail": "skip to next iteration",
        "documentation": "Skips to the next iteration of the innermost loop.",
        "completion": { "kind": "text", "text": "continue" }
      }
    },
    {
      "id": "stitchscope",
      "label": "stitchscope",
      "category": "syntax-control",
      "tags": ["block", "core", "embroidery", "heading", "keyword", "mode", "syntax-control"],
      "summary": "Run a block with temporary stitch-construction settings, then restore the outer configuration even after `return`, `break`, `continue`, or an error. It scopes running/satin/E-stitch/bean modes, satin cap/join/wide policies, fill settings and an armed fill, plus lock, compensation, underlay, auto-trim, and density policies. Turtle position, heading, pen, color, RNG, transforms/effects, output/history, hoop, budgets…",
      "editor": {
        "kind": "keyword",
        "detail": "temporarily override stitch construction settings",
        "documentation": "Run a block with temporary stitch-construction settings, then restore the outer configuration even after `return`, `break`, `continue`, or an error. It scopes running/satin/E-stitch/bean modes, satin cap/join/wide policies, fill settings and an armed fill, plus lock, compensation, underlay, auto-trim, and density policies. Turtle position, heading, pen, color, RNG, transforms/effects, output/history, hoop, budgets, and planning are not restored. Pending satin or reporter-running construction flushes at both boundaries; an active `beginfill` cannot cross a boundary.\n\n```\nstitchscope [\n  density 0.5\n  underlay 'edge'\n  satin 4\n  fd 20\n]\n```",
        "example": "stitchscope [\n  density 0.5\n  underlay 'edge'\n  satin 4\n  fd 20\n]",
        "completion": { "kind": "text", "text": "stitchscope [\n\t$0\n]" },
        "isSnippet": true,
        "signatures": [[]]
      }
    },
    {
      "id": "import",
      "label": "import",
      "category": "syntax-control",
      "tags": ["embroidery", "keyword", "library", "syntax-control", "top-level"],
      "summary": "Imports one exported procedure from a bundled standard-library module under a local name. Imports are compile-time only and must be top-level.",
      "editor": {
        "kind": "keyword",
        "detail": "import a standard-library procedure",
        "documentation": "Imports one exported procedure from a bundled standard-library module under a local name. Imports are compile-time only and must be top-level.\n\n```\nimport std.textures.radialdir as radial\nfill dir @radial\n```",
        "completion": { "kind": "text", "text": "import std.${1:module}.${2:name} as ${3:alias}" },
        "isSnippet": true
      }
    },
    {
      "id": "export",
      "label": "export",
      "category": "syntax-control",
      "tags": [
        "block",
        "call-syntax",
        "heading",
        "keyword",
        "library",
        "syntax-control",
        "top-level"
      ],
      "summary": "Marks a top-level procedure as part of a source module's public surface. The keyword directly prefixes `def` or classic `to`.",
      "editor": {
        "kind": "keyword",
        "detail": "export a module procedure",
        "documentation": "Marks a top-level procedure as part of a source module's public surface. The keyword directly prefixes `def` or classic `to`.\n\n```\nexport def radialdir(p) [\n  return vheading(p)\n]\n```",
        "completion": { "kind": "text", "text": "export def ${1:name}(${2:params}) [\n\t$0\n]" },
        "isSnippet": true
      }
    },
    {
      "id": "def",
      "label": "def",
      "category": "syntax-control",
      "tags": ["block", "call-syntax", "keyword", "library", "syntax-control"],
      "summary": "Define a procedure. Parameters are local and can recurse (depth limit 200). Anonymous `def(params) [ … ]` expressions capture enclosing locals by snapshot and return a configured reference.",
      "editor": {
        "kind": "keyword",
        "detail": "define a procedure",
        "documentation": "Define a procedure. Parameters are local and can recurse (depth limit 200). Anonymous `def(params) [ … ]` expressions capture enclosing locals by snapshot and return a configured reference.\n\n```\ndef multiplier(k) [\n  return def(x) [ return x * k ]\n]\n```\nClassic form: `to name :a :b … end`",
        "completion": { "kind": "text", "text": "def ${1:name}(${2:params}) [\n\t$0\n]" },
        "isSnippet": true
      }
    },
    {
      "id": "to",
      "label": "to",
      "category": "syntax-control",
      "tags": ["keyword", "library", "mode", "syntax-control"],
      "summary": "Classic Logo procedure definition. Modern equivalent: `def name(a, b) [ … ]`.",
      "editor": {
        "kind": "keyword",
        "detail": "classic procedure definition",
        "documentation": "Classic Logo procedure definition. Modern equivalent: `def name(a, b) [ … ]`.\n\n```\nto leaf :size\n  fd :size  bk :size\nend\n```",
        "completion": { "kind": "text", "text": "to ${1:name} :${2:param}\n\t$0\nend" },
        "isSnippet": true
      }
    },
    {
      "id": "end",
      "label": "end",
      "category": "syntax-control",
      "tags": ["keyword", "library", "syntax-control"],
      "summary": "Closes a `to … end` procedure definition.",
      "editor": {
        "kind": "keyword",
        "detail": "close classic procedure",
        "documentation": "Closes a `to … end` procedure definition.",
        "completion": { "kind": "text", "text": "end" }
      }
    },
    {
      "id": "return",
      "label": "return",
      "category": "syntax-control",
      "tags": ["keyword", "library", "syntax-control"],
      "summary": "Return a value from a procedure. Without argument, exits early. Classic aliases: `output`, `op`.",
      "editor": {
        "kind": "keyword",
        "detail": "return value / exit early",
        "documentation": "Return a value from a procedure. Without argument, exits early. Classic aliases: `output`, `op`.",
        "completion": { "kind": "text", "text": "return ${1:value}" },
        "isSnippet": true
      }
    },
    {
      "id": "output",
      "label": "output",
      "category": "syntax-control",
      "tags": ["keyword", "library", "syntax-control"],
      "summary": "Classic Logo alias for `return`. Only valid inside a procedure.",
      "editor": {
        "kind": "keyword",
        "detail": "classic return (alias)",
        "documentation": "Classic Logo alias for `return`. Only valid inside a procedure.",
        "completion": { "kind": "text", "text": "output ${1:value}" },
        "isSnippet": true
      }
    },
    {
      "id": "exit",
      "label": "exit",
      "category": "syntax-control",
      "tags": ["keyword", "library", "syntax-control"],
      "summary": "Classic Logo alias for `return` with no value.",
      "editor": {
        "kind": "keyword",
        "detail": "exit procedure early",
        "documentation": "Classic Logo alias for `return` with no value.",
        "completion": { "kind": "text", "text": "exit" }
      }
    },
    {
      "id": "let",
      "label": "let",
      "category": "syntax-control",
      "tags": ["keyword", "library", "stateful", "syntax-control"],
      "summary": "Declare a variable — global at top level, local inside a procedure. Redeclaring the same name in the same scope is a parse error.",
      "editor": {
        "kind": "keyword",
        "detail": "declare a variable",
        "documentation": "Declare a variable — global at top level, local inside a procedure. Redeclaring the same name in the same scope is a parse error.",
        "completion": { "kind": "text", "text": "let ${1:name} = ${2:value}" },
        "isSnippet": true
      }
    },
    {
      "id": "make",
      "label": "make",
      "category": "syntax-control",
      "tags": ["keyword", "library", "stateful", "syntax-control"],
      "summary": "Classic Logo assignment: `make \"x expr`. Same rules as `x = expr`.",
      "editor": {
        "kind": "keyword",
        "detail": "classic variable assignment",
        "documentation": "Classic Logo assignment: `make \"x expr`. Same rules as `x = expr`.",
        "completion": { "kind": "text", "text": "make \"${1:name} ${2:value}" },
        "isSnippet": true
      }
    },
    {
      "id": "local",
      "label": "local",
      "category": "syntax-control",
      "tags": ["keyword", "library", "syntax-control"],
      "summary": "Classic Logo local variable declaration inside a procedure. Illegal at top level.",
      "editor": {
        "kind": "keyword",
        "detail": "classic local variable",
        "documentation": "Classic Logo local variable declaration inside a procedure. Illegal at top level.",
        "completion": { "kind": "text", "text": "local \"${1:name} ${2:value}" },
        "isSnippet": true
      }
    },
    {
      "id": "and",
      "label": "and",
      "category": "syntax-control",
      "tags": ["keyword", "library", "syntax-control"],
      "summary": "Logical AND, short-circuits. `i > 0 and 10/i > 2` is safe.",
      "editor": {
        "kind": "keyword",
        "detail": "logical AND (short-circuit)",
        "documentation": "Logical AND, short-circuits. `i > 0 and 10/i > 2` is safe.",
        "completion": { "kind": "text", "text": "and" }
      }
    },
    {
      "id": "or",
      "label": "or",
      "category": "syntax-control",
      "tags": ["keyword", "library", "syntax-control"],
      "summary": "Logical OR, short-circuits.",
      "editor": {
        "kind": "keyword",
        "detail": "logical OR (short-circuit)",
        "documentation": "Logical OR, short-circuits.",
        "completion": { "kind": "text", "text": "or" }
      }
    },
    {
      "id": "true",
      "label": "true",
      "category": "syntax-control",
      "tags": ["constant", "library", "syntax-control"],
      "summary": "Literal for 1. Truthiness: anything non-zero is true.",
      "editor": {
        "kind": "constant",
        "detail": "literal 1",
        "documentation": "Literal for 1. Truthiness: anything non-zero is true.",
        "completion": { "kind": "text", "text": "true" }
      }
    },
    {
      "id": "false",
      "label": "false",
      "category": "syntax-control",
      "tags": ["constant", "library", "syntax-control"],
      "summary": "Literal for 0. Truthiness: 0 is false.",
      "editor": {
        "kind": "constant",
        "detail": "literal 0",
        "documentation": "Literal for 0. Truthiness: 0 is false.",
        "completion": { "kind": "text", "text": "false" }
      }
    },
    {
      "id": "in",
      "label": "in",
      "category": "syntax-control",
      "tags": ["keyword", "library", "syntax-control"],
      "summary": "Used in `for x in xs [ … ]` to iterate list elements.",
      "editor": {
        "kind": "keyword",
        "detail": "for-in keyword",
        "documentation": "Used in `for x in xs [ … ]` to iterate list elements.",
        "completion": { "kind": "text", "text": "in" }
      }
    },
    {
      "id": "step",
      "label": "step",
      "category": "syntax-control",
      "tags": ["keyword", "library", "syntax-control"],
      "summary": "Optional step in a `for` loop: `for i = 10 to 1 step -2 [ … ]`.",
      "editor": {
        "kind": "keyword",
        "detail": "loop step size",
        "documentation": "Optional step in a `for` loop: `for i = 10 to 1 step -2 [ … ]`.",
        "completion": { "kind": "text", "text": "step" }
      }
    },
    {
      "id": "fd",
      "label": "fd",
      "category": "movement",
      "tags": ["core", "embroidery", "function", "millimetres", "movement"],
      "aliases": ["forward"],
      "summary": "Sew forward n mm. Long moves auto-split at `stitchlen`.",
      "editor": {
        "kind": "function",
        "detail": "sew forward (mm)",
        "documentation": "Sew forward n mm. Long moves auto-split at `stitchlen`.\n\nAlias: `forward`",
        "completion": { "kind": "text", "text": "fd ${1:mm}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "forward",
      "label": "forward",
      "category": "movement",
      "tags": ["embroidery", "function", "library", "millimetres", "movement"],
      "aliasFor": "fd",
      "summary": "Alias for `fd`. Sew forward n mm.",
      "editor": {
        "kind": "function",
        "detail": "sew forward — alias for fd",
        "documentation": "Alias for `fd`. Sew forward n mm.",
        "completion": { "kind": "text", "text": "forward ${1:mm}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "bk",
      "label": "bk",
      "category": "movement",
      "tags": ["core", "embroidery", "function", "millimetres", "movement"],
      "aliases": ["back", "backward"],
      "summary": "Sew backward n mm.",
      "editor": {
        "kind": "function",
        "detail": "sew backward (mm)",
        "documentation": "Sew backward n mm.\n\nAliases: `back`, `backward`",
        "completion": { "kind": "text", "text": "bk ${1:mm}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "back",
      "label": "back",
      "category": "movement",
      "tags": ["embroidery", "function", "library", "millimetres", "movement"],
      "aliasFor": "bk",
      "summary": "Alias for `bk`. Sew backward n mm.",
      "editor": {
        "kind": "function",
        "detail": "sew backward — alias for bk",
        "documentation": "Alias for `bk`. Sew backward n mm.",
        "completion": { "kind": "text", "text": "back ${1:mm}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "rt",
      "label": "rt",
      "category": "movement",
      "tags": ["core", "function", "heading", "movement"],
      "aliases": ["right"],
      "summary": "Turn right by deg degrees.",
      "editor": {
        "kind": "function",
        "detail": "turn right (degrees)",
        "documentation": "Turn right by deg degrees.\n\nAlias: `right`",
        "completion": { "kind": "text", "text": "rt ${1:degrees}" },
        "isSnippet": true,
        "signatures": [["degrees"]]
      }
    },
    {
      "id": "right",
      "label": "right",
      "category": "movement",
      "tags": ["function", "heading", "library", "movement"],
      "aliasFor": "rt",
      "summary": "Alias for `rt`. Turn right by deg degrees.",
      "editor": {
        "kind": "function",
        "detail": "turn right — alias for rt",
        "documentation": "Alias for `rt`. Turn right by deg degrees.",
        "completion": { "kind": "text", "text": "right ${1:degrees}" },
        "isSnippet": true,
        "signatures": [["degrees"]]
      }
    },
    {
      "id": "lt",
      "label": "lt",
      "category": "movement",
      "tags": ["core", "function", "heading", "movement"],
      "aliases": ["left"],
      "summary": "Turn left by deg degrees.",
      "editor": {
        "kind": "function",
        "detail": "turn left (degrees)",
        "documentation": "Turn left by deg degrees.\n\nAlias: `left`",
        "completion": { "kind": "text", "text": "lt ${1:degrees}" },
        "isSnippet": true,
        "signatures": [["degrees"]]
      }
    },
    {
      "id": "left",
      "label": "left",
      "category": "movement",
      "tags": ["function", "heading", "library", "movement"],
      "aliasFor": "lt",
      "summary": "Alias for `lt`. Turn left by deg degrees.",
      "editor": {
        "kind": "function",
        "detail": "turn left — alias for lt",
        "documentation": "Alias for `lt`. Turn left by deg degrees.",
        "completion": { "kind": "text", "text": "left ${1:degrees}" },
        "isSnippet": true,
        "signatures": [["degrees"]]
      }
    },
    {
      "id": "up",
      "label": "up",
      "category": "movement",
      "tags": ["core", "embroidery", "function", "mode", "movement"],
      "aliases": ["penup", "pu"],
      "summary": "Needle up — subsequent moves are jump travels, not stitches.",
      "editor": {
        "kind": "function",
        "detail": "pen up (travel mode)",
        "documentation": "Needle up — subsequent moves are jump travels, not stitches.\n\nAliases: `penup`, `pu`",
        "completion": { "kind": "text", "text": "up" },
        "signatures": [[]]
      }
    },
    {
      "id": "down",
      "label": "down",
      "category": "movement",
      "tags": ["core", "embroidery", "function", "mode", "movement"],
      "aliases": ["pendown", "pd"],
      "summary": "Needle down — subsequent moves sew stitches.",
      "editor": {
        "kind": "function",
        "detail": "pen down (sew mode)",
        "documentation": "Needle down — subsequent moves sew stitches.\n\nAliases: `pendown`, `pd`",
        "completion": { "kind": "text", "text": "down" },
        "signatures": [[]]
      }
    },
    {
      "id": "penup",
      "label": "penup",
      "category": "movement",
      "tags": ["embroidery", "function", "library", "mode", "movement"],
      "aliasFor": "up",
      "summary": "Alias for `up`. Needle up — jump travel mode.",
      "editor": {
        "kind": "function",
        "detail": "pen up — alias for up",
        "documentation": "Alias for `up`. Needle up — jump travel mode.",
        "completion": { "kind": "text", "text": "penup" }
      }
    },
    {
      "id": "pendown",
      "label": "pendown",
      "category": "movement",
      "tags": ["embroidery", "function", "library", "mode", "movement"],
      "aliasFor": "down",
      "summary": "Alias for `down`. Needle down — sewing mode.",
      "editor": {
        "kind": "function",
        "detail": "pen down — alias for down",
        "documentation": "Alias for `down`. Needle down — sewing mode.",
        "completion": { "kind": "text", "text": "pendown" }
      }
    },
    {
      "id": "arc",
      "label": "arc",
      "category": "movement",
      "tags": ["core", "embroidery", "function", "heading", "millimetres", "mode", "movement"],
      "summary": "Sew along a circle of radius mm, turning deg in total. Positive degrees curves right, negative left. Works in every stitch mode — including satin!",
      "editor": {
        "kind": "function",
        "detail": "sew an arc",
        "documentation": "Sew along a circle of radius mm, turning deg in total. Positive degrees curves right, negative left. Works in every stitch mode — including satin!",
        "completion": { "kind": "text", "text": "arc ${1:degrees} ${2:radius}" },
        "isSnippet": true,
        "signatures": [["degrees", "radius"]]
      }
    },
    {
      "id": "circle",
      "label": "circle",
      "category": "movement",
      "tags": ["core", "embroidery", "function", "mode", "movement"],
      "summary": "Sew a full closed circle of radius r — exactly `arc 360 r`. Works in every stitch mode (satin ring, bean loop, etc.).",
      "editor": {
        "kind": "function",
        "detail": "full circle of radius r (≡ arc 360 r)",
        "documentation": "Sew a full closed circle of radius r — exactly `arc 360 r`. Works in every stitch mode (satin ring, bean loop, etc.).\n\nDraw cost: 0. Byte-identical to `arc 360 r`.",
        "completion": { "kind": "text", "text": "circle ${1:radius}" },
        "isSnippet": true,
        "signatures": [["radius"]]
      }
    },
    {
      "id": "setxy",
      "label": "setxy",
      "category": "movement",
      "tags": ["core", "embroidery", "function", "movement"],
      "summary": "Move (sew or jump depending on pen state) to the absolute position (x, y).",
      "editor": {
        "kind": "function",
        "detail": "move to absolute position",
        "documentation": "Move (sew or jump depending on pen state) to the absolute position (x, y).",
        "completion": { "kind": "text", "text": "setxy ${1:x} ${2:y}" },
        "isSnippet": true,
        "signatures": [["x", "y"]]
      }
    },
    {
      "id": "setx",
      "label": "setx",
      "category": "movement",
      "tags": ["core", "function", "movement"],
      "summary": "Set the x coordinate absolutely; y stays the same.",
      "editor": {
        "kind": "function",
        "detail": "set x position",
        "documentation": "Set the x coordinate absolutely; y stays the same.",
        "completion": { "kind": "text", "text": "setx ${1:x}" },
        "isSnippet": true,
        "signatures": [["x"]]
      }
    },
    {
      "id": "sety",
      "label": "sety",
      "category": "movement",
      "tags": ["core", "function", "movement"],
      "summary": "Set the y coordinate absolutely; x stays the same.",
      "editor": {
        "kind": "function",
        "detail": "set y position",
        "documentation": "Set the y coordinate absolutely; x stays the same.",
        "completion": { "kind": "text", "text": "sety ${1:y}" },
        "isSnippet": true,
        "signatures": [["y"]]
      }
    },
    {
      "id": "seth",
      "label": "seth",
      "category": "movement",
      "tags": ["core", "function", "heading", "movement"],
      "aliases": ["setheading"],
      "summary": "Set the heading absolutely. 0 = up/north, clockwise positive.",
      "editor": {
        "kind": "function",
        "detail": "set heading (degrees)",
        "documentation": "Set the heading absolutely. 0 = up/north, clockwise positive.\n\nAlias: `setheading`",
        "completion": { "kind": "text", "text": "seth ${1:degrees}" },
        "isSnippet": true,
        "signatures": [["degrees"]]
      }
    },
    {
      "id": "setheading",
      "label": "setheading",
      "category": "movement",
      "tags": ["function", "heading", "library", "movement"],
      "aliasFor": "seth",
      "summary": "Alias for `seth`. Set heading in degrees (0 = north, clockwise).",
      "editor": {
        "kind": "function",
        "detail": "set heading — alias for seth",
        "documentation": "Alias for `seth`. Set heading in degrees (0 = north, clockwise).",
        "completion": { "kind": "text", "text": "setheading ${1:degrees}" },
        "isSnippet": true,
        "signatures": [["degrees"]]
      }
    },
    {
      "id": "home",
      "label": "home",
      "category": "movement",
      "tags": ["core", "embroidery", "function", "heading", "movement"],
      "summary": "Return to origin (0, 0) with heading 0 (north). Sews/jumps depending on pen state.",
      "editor": {
        "kind": "function",
        "detail": "return to (0,0), heading 0",
        "documentation": "Return to origin (0, 0) with heading 0 (north). Sews/jumps depending on pen state.\n\n**Warning:** if the pen is *down*, this **sews a line** back to the origin. For a non-sewing return use `moveto 0 0` or `gohome`.",
        "completion": { "kind": "text", "text": "home" },
        "signatures": [[]]
      }
    },
    {
      "id": "moveto",
      "label": "moveto",
      "category": "movement",
      "tags": ["core", "embroidery", "function", "movement"],
      "aliases": ["jump"],
      "summary": "Reposition the needle to `(x, y)` as a jump, without sewing. Pen state is preserved: if the pen was down it ends down and the next move sews normally; if up it stays up.",
      "editor": {
        "kind": "function",
        "detail": "jump to (x, y) without sewing",
        "documentation": "Reposition the needle to `(x, y)` as a jump, **without sewing**. Pen state is preserved: if the pen was down it ends down and the next move sews normally; if up it stays up.\n\nEquivalent to `up setxy x y down` when pen is down, or `up setxy x y` when pen is already up. Respects the current transform.\n\nAlias: `jump`\n\nDraw cost: 0.\n\n```\nrepeat 18 [\n  moveto random(70) - 35, random(30) - 38\n  stem(14)\n  trim\n]\n```",
        "completion": { "kind": "text", "text": "moveto ${1:x} ${2:y}" },
        "isSnippet": true,
        "signatures": [["x", "y"]]
      }
    },
    {
      "id": "jump",
      "label": "jump",
      "category": "movement",
      "tags": ["embroidery", "function", "library", "movement"],
      "aliasFor": "moveto",
      "summary": "Alias for `moveto`. The embroidery industry term for a non-sewing travel. Pen state preserved.",
      "editor": {
        "kind": "function",
        "detail": "jump to (x, y) — alias for moveto",
        "documentation": "Alias for `moveto`. The embroidery industry term for a non-sewing travel. Pen state preserved.",
        "completion": { "kind": "text", "text": "jump ${1:x} ${2:y}" },
        "isSnippet": true,
        "signatures": [["x", "y"]]
      }
    },
    {
      "id": "gohome",
      "label": "gohome",
      "category": "movement",
      "tags": ["core", "embroidery", "function", "heading", "movement"],
      "summary": "Jump to `(0, 0)` without sewing — pen state preserved. Does not reset heading; add `seth 0` for a full neutral reset.",
      "editor": {
        "kind": "function",
        "detail": "pen-safe return to origin (≡ moveto 0 0)",
        "documentation": "Jump to `(0, 0)` without sewing — pen state preserved. Does **not** reset heading; add `seth 0` for a full neutral reset.\n\nEquivalent to `moveto 0 0`. Contrast with `home`, which sews a line back when the pen is down.\n\nDraw cost: 0.",
        "completion": { "kind": "text", "text": "gohome" },
        "signatures": [[]]
      }
    },
    {
      "id": "push",
      "label": "push",
      "category": "movement",
      "tags": ["core", "embroidery", "function", "heading", "movement"],
      "summary": "Save needle state (position, heading, pen up/down) onto a stack. Max 500 saved states.",
      "editor": {
        "kind": "function",
        "detail": "save needle state onto stack",
        "documentation": "Save needle state (position, heading, pen up/down) onto a stack. Max 500 saved states.",
        "completion": { "kind": "text", "text": "push" },
        "signatures": [[]]
      }
    },
    {
      "id": "pop",
      "label": "pop",
      "category": "movement",
      "tags": ["core", "embroidery", "function", "movement"],
      "summary": "Restore the last saved needle state from the stack. Pop on an empty stack warns and is ignored.",
      "editor": {
        "kind": "function",
        "detail": "restore needle state from stack",
        "documentation": "Restore the last saved needle state from the stack. Pop on an empty stack warns and is ignored.",
        "completion": { "kind": "text", "text": "pop" },
        "signatures": [[]]
      }
    },
    {
      "id": "cs",
      "label": "cs",
      "category": "movement",
      "tags": ["core", "embroidery", "function", "movement"],
      "aliases": ["clearscreen", "clear"],
      "summary": "Accepted for Logo familiarity; does nothing in NeedleScript.",
      "editor": {
        "kind": "function",
        "detail": "clearscreen (no-op)",
        "documentation": "Accepted for Logo familiarity; does nothing in NeedleScript.\n\nAliases: `clearscreen`, `clear`",
        "completion": { "kind": "text", "text": "cs" },
        "signatures": [[]]
      }
    },
    {
      "id": "xcor",
      "label": "xcor",
      "category": "movement",
      "tags": ["embroidery", "library", "millimetres", "movement", "variable"],
      "summary": "Reports the current needle x position in mm.",
      "editor": {
        "kind": "variable",
        "detail": "current needle x (mm)",
        "documentation": "Reports the current needle x position in mm.",
        "completion": { "kind": "text", "text": "xcor" },
        "signatures": [[]]
      }
    },
    {
      "id": "ycor",
      "label": "ycor",
      "category": "movement",
      "tags": ["embroidery", "library", "millimetres", "movement", "variable"],
      "summary": "Reports the current needle y position in mm.",
      "editor": {
        "kind": "variable",
        "detail": "current needle y (mm)",
        "documentation": "Reports the current needle y position in mm.",
        "completion": { "kind": "text", "text": "ycor" },
        "signatures": [[]]
      }
    },
    {
      "id": "heading",
      "label": "heading",
      "category": "movement",
      "tags": ["heading", "library", "movement", "variable"],
      "summary": "Reports the current heading in degrees (0 = north, clockwise positive).",
      "editor": {
        "kind": "variable",
        "detail": "current heading (degrees)",
        "documentation": "Reports the current heading in degrees (0 = north, clockwise positive).",
        "completion": { "kind": "text", "text": "heading" },
        "signatures": [[]]
      }
    },
    {
      "id": "repcount",
      "label": "repcount",
      "category": "movement",
      "tags": ["library", "movement", "variable"],
      "summary": "Reports the 1-based counter of the innermost `repeat` loop.",
      "editor": {
        "kind": "variable",
        "detail": "1-based repeat counter",
        "documentation": "Reports the 1-based counter of the innermost `repeat` loop.",
        "completion": { "kind": "text", "text": "repcount" },
        "signatures": [[]]
      }
    },
    {
      "id": "translate",
      "label": "translate",
      "category": "transforms",
      "tags": ["block", "core", "geometry", "keyword", "millimetres", "transforms"],
      "summary": "Shift everything the block draws by `(dx, dy)` mm. The turtle stays in local space — only emitted geometry moves.",
      "editor": {
        "kind": "keyword",
        "detail": "shift a block by (dx, dy) mm",
        "documentation": "Shift everything the block draws by `(dx, dy)` mm. The turtle stays in local space — only emitted geometry moves.\n\n```\ntranslate 20 0 [ leaf() ]\ntranslate(20, 0) [ leaf() ]   // same thing\n```",
        "completion": { "kind": "text", "text": "translate ${1:dx} ${2:dy} [\n\t$0\n]" },
        "isSnippet": true,
        "signatures": [["dx", "dy"]]
      }
    },
    {
      "id": "rotate",
      "label": "rotate",
      "category": "transforms",
      "tags": ["block", "core", "heading", "keyword", "transforms"],
      "summary": "Rotate the block `deg` degrees clockwise about the current origin (0 = north, matching `seth`/`rt`).",
      "editor": {
        "kind": "keyword",
        "detail": "rotate a block (clockwise, about origin)",
        "documentation": "Rotate the block `deg` degrees clockwise about the current origin (0 = north, matching `seth`/`rt`).",
        "completion": { "kind": "text", "text": "rotate ${1:degrees} [\n\t$0\n]" },
        "isSnippet": true,
        "signatures": [["degrees"]]
      }
    },
    {
      "id": "rotateabout",
      "label": "rotateabout",
      "category": "transforms",
      "tags": ["block", "core", "heading", "keyword", "transforms"],
      "summary": "Rotate the block `deg` clockwise about the pivot `(cx, cy)`.",
      "editor": {
        "kind": "keyword",
        "detail": "rotate about an explicit pivot",
        "documentation": "Rotate the block `deg` clockwise about the pivot `(cx, cy)`.",
        "completion": {
          "kind": "text",
          "text": "rotateabout ${1:degrees} ${2:cx} ${3:cy} [\n\t$0\n]"
        },
        "isSnippet": true,
        "signatures": [["degrees", "cx", "cy"]]
      }
    },
    {
      "id": "scale",
      "label": "scale",
      "category": "transforms",
      "tags": ["block", "core", "embroidery", "keyword", "transforms"],
      "summary": "Uniformly scale the block by `s`. Stitch length, satin width and the physics layer are re-evaluated after scaling, so a scaled motif still sews like real embroidery — not stretched stitches.",
      "editor": {
        "kind": "keyword",
        "detail": "uniform scale",
        "documentation": "Uniformly scale the block by `s`. Stitch length, satin width and the physics layer are re-evaluated **after** scaling, so a scaled motif still sews like real embroidery — not stretched stitches.",
        "completion": { "kind": "text", "text": "scale ${1:s} [\n\t$0\n]" },
        "isSnippet": true,
        "signatures": [["s"]]
      }
    },
    {
      "id": "scalexy",
      "label": "scalexy",
      "category": "transforms",
      "tags": ["block", "core", "embroidery", "keyword", "transforms"],
      "summary": "Scale the block by `sx` on x and `sy` on y. Non-uniform scale makes satin width direction-dependent (a column running across the stretched axis widens).",
      "editor": {
        "kind": "keyword",
        "detail": "independent axis scale",
        "documentation": "Scale the block by `sx` on x and `sy` on y. Non-uniform scale makes satin width direction-dependent (a column running across the stretched axis widens).",
        "completion": { "kind": "text", "text": "scalexy ${1:sx} ${2:sy} [\n\t$0\n]" },
        "isSnippet": true,
        "signatures": [["sx", "sy"]]
      }
    },
    {
      "id": "mirror",
      "label": "mirror",
      "category": "transforms",
      "tags": ["block", "core", "heading", "keyword", "transforms"],
      "summary": "Reflect the block across a line through the origin at heading `deg`. `mirror 0` flips left/right; `mirror 90` flips top/bottom.",
      "editor": {
        "kind": "keyword",
        "detail": "reflect across a heading line",
        "documentation": "Reflect the block across a line through the origin at heading `deg`. `mirror 0` flips left/right; `mirror 90` flips top/bottom.",
        "completion": { "kind": "text", "text": "mirror ${1:degrees} [\n\t$0\n]" },
        "isSnippet": true,
        "signatures": [["degrees"]]
      }
    },
    {
      "id": "skew",
      "label": "skew",
      "category": "transforms",
      "tags": ["block", "core", "heading", "keyword", "transforms"],
      "summary": "Shear the block: `x += tan(ax)·y`, `y += tan(ay)·x`.",
      "editor": {
        "kind": "keyword",
        "detail": "shear by ax / ay degrees",
        "documentation": "Shear the block: `x += tan(ax)·y`, `y += tan(ay)·x`.",
        "completion": { "kind": "text", "text": "skew ${1:ax} ${2:ay} [\n\t$0\n]" },
        "isSnippet": true,
        "signatures": [["ax", "ay"]]
      }
    },
    {
      "id": "transform",
      "label": "transform",
      "category": "transforms",
      "tags": ["block", "core", "keyword", "transforms"],
      "summary": "Apply the raw affine `(x, y) → (a·x + c·y + e, b·x + d·y + f)` to the block — the power-user escape hatch behind the named transforms.",
      "editor": {
        "kind": "keyword",
        "detail": "raw 2×3 affine escape hatch",
        "documentation": "Apply the raw affine `(x, y) → (a·x + c·y + e, b·x + d·y + f)` to the block — the power-user escape hatch behind the named transforms.",
        "completion": {
          "kind": "text",
          "text": "transform ${1:a} ${2:b} ${3:c} ${4:d} ${5:e} ${6:f} [\n\t$0\n]"
        },
        "isSnippet": true,
        "signatures": [["a", "b", "c", "d", "e", "f"]]
      }
    },
    {
      "id": "warp",
      "label": "warp",
      "category": "effects",
      "tags": ["block", "core", "effects", "embroidery", "geometry", "keyword"],
      "summary": "Map every emitted point through a `@name` reporter (a procedure that takes a point `[x, y]` and returns a point), before stitch splitting — a geometric deformation, exactly like a transform but nonlinear. This is the shader: fisheye, ripple, twist, domain-warp are all just reporters.",
      "editor": {
        "kind": "keyword",
        "detail": "run a block through a point→point reporter",
        "documentation": "Map every emitted point through a `@name` reporter (a procedure that takes a point `[x, y]` and returns a point), **before** stitch splitting — a geometric deformation, exactly like a transform but nonlinear. This is the shader: fisheye, ripple, twist, domain-warp are all just reporters.\n\n```\ndef push_out(p) [\n  let d = vlen(p)\n  return vscale(vnorm(p), d + 2 * snoise2(p[0] / 14, p[1] / 14))\n]\nwarp @push_out [ repeat 6 [ fd 30 rt 60 ] ]\n```",
        "completion": { "kind": "text", "text": "warp @${1:reporter} [\n\t$0\n]" },
        "isSnippet": true,
        "signatures": [["reporter"]]
      }
    },
    {
      "id": "humanize",
      "label": "humanize",
      "category": "effects",
      "tags": ["block", "core", "effects", "embroidery", "keyword", "millimetres", "seeded"],
      "summary": "Perturb each stitch penetration by coherent, seeded simplex noise (the hand drifts, so consecutive stitches err together — not white-noise damage). Runs after stitch splitting, on the final penetrations. `amount` is the jitter in mm (clamped 0–2). Draws exactly one value from the seeded stream (forks), so dropping a `humanize` block shifts downstream randomness by one draw, not by however many stitches were inside.",
      "editor": {
        "kind": "keyword",
        "detail": "seeded hand-stitched jitter (mm)",
        "documentation": "Perturb each stitch penetration by coherent, seeded simplex noise (the hand drifts, so consecutive stitches err together — not white-noise damage). Runs **after** stitch splitting, on the final penetrations. `amount` is the jitter in mm (clamped 0–2). Draws exactly one value from the seeded stream (forks), so dropping a `humanize` block shifts downstream randomness by one draw, not by however many stitches were inside.\n\n```\nhumanize 0.3 [ repeat 4 [ fd 20 rt 90 ] ]\n```",
        "completion": { "kind": "text", "text": "humanize ${1:amount} [\n\t$0\n]" },
        "isSnippet": true,
        "signatures": [["amount"]]
      }
    },
    {
      "id": "snaptogrid",
      "label": "snaptogrid",
      "category": "effects",
      "tags": ["block", "core", "effects", "keyword", "millimetres", "pure"],
      "summary": "Snap each penetration to a fixed hoop-space lattice, evaluated outside any enclosing transform — so the same grid config always yields the same lattice regardless of `translate`/`rotate`/`scale`. Pure and drawless. Overloads by arity:",
      "editor": {
        "kind": "keyword",
        "detail": "quantize penetrations to a fixed lattice",
        "documentation": "Snap each penetration to a fixed hoop-space lattice, evaluated **outside** any enclosing transform — so the same grid config always yields the same lattice regardless of `translate`/`rotate`/`scale`. Pure and drawless. Overloads by arity:\n\n```\nsnaptogrid 2 [ … ]                       // square, pitch 2 mm, origin (0,0)\nsnaptogrid 2 3 [ … ]                     // rectangular\nsnaptogrid(1.5, 1.5, 0.75, 0.75) [ … ]   // …with an origin offset\nsnaptogrid(2, 2, 0, 0, 30) [ … ]         // …rotated 30°\n```",
        "completion": { "kind": "text", "text": "snaptogrid ${1:cell} [\n\t$0\n]" },
        "isSnippet": true,
        "signatures": [["cell"]]
      }
    },
    {
      "id": "declump",
      "label": "declump",
      "category": "effects",
      "tags": [
        "block",
        "core",
        "effects",
        "embroidery",
        "geometry",
        "keyword",
        "millimetres",
        "pure"
      ],
      "summary": "Ease crowded needle penetrations along the thread's own line of travel — never sideways, so stitch angles stay intact. Each penetration that exceeds `limit` layers of coverage is slid backward or forward along its axis until it finds clear fabric, within `maxshift` mm (default 1.5, clamped 0–5). Runs after stitch splitting, like `humanize`. Drawless (zero RNG draws) — adding or removing the block never reshuffles…",
      "editor": {
        "kind": "keyword",
        "detail": "along-axis perforation-crowd relief",
        "documentation": "Ease crowded needle penetrations along the **thread's own line of travel** — never sideways, so stitch angles stay intact. Each penetration that exceeds `limit` layers of coverage is slid backward or forward along its axis until it finds clear fabric, within `maxshift` mm (default 1.5, clamped 0–5). Runs **after** stitch splitting, like `humanize`. Drawless (zero RNG draws) — adding or removing the block never reshuffles downstream randomness.\n\nThe fold is greedy: earlier stitches in the block win the space; later ones absorb the displacement. Sew the geometry whose fidelity matters most first. Generated fill underlay, edge run, topping, and sewn topping connectors use the same active limit in sew order. Fill shifts retain 0.1 mm clearance from outer and hole boundaries, keep their relief segment contained, preserve local row order, and fall back unchanged when no safe relief exists.\n\n```\n// Relief for a radial motif whose centre takes dozens of hits\ndeclump 2 1.5 [\n  repeat 24 [\n    moveto 0 0\n    seth repcount * 15\n    fd 40\n    trim\n  ]\n]\n```\n\nExclusions: satin columns (warn + skip, as with `humanize`) and inside `trace` (inert + note).\n\nTypical values: `limit` 1.5–2.5, `maxshift` 0.5 (subtle) to 1.5 (default) to 3+ (visible variation).",
        "completion": { "kind": "text", "text": "declump ${1:limit} [\n\t$0\n]" },
        "isSnippet": true,
        "signatures": [["limit"], ["limit", "maxshift"]]
      }
    },
    {
      "id": "trace",
      "label": "trace",
      "category": "trace",
      "tags": ["block", "embroidery", "geometry", "keyword", "library", "trace"],
      "summary": "Run a block in a sandbox — full language semantics, but the stitch machine is disconnected. Nothing is sewn, and on exit the turtle and all stitch state are restored. Returns the single pen-down path (a list of `[x, y]` points) at move-command resolution, unaffected by `stitchlen`. Errors if the block draws more than one pen-down run (use `tracerings` for that).",
      "editor": {
        "kind": "keyword",
        "detail": "capture a single pen-down path as data",
        "documentation": "Run a block in a sandbox — full language semantics, but the stitch machine is disconnected. Nothing is sewn, and on exit the turtle and all stitch state are restored. Returns the single pen-down path (a list of `[x, y]` points) at move-command resolution, unaffected by `stitchlen`. Errors if the block draws more than one pen-down run (use `tracerings` for that).\n\n```\nlet ring = trace [ repeat 6 [ fd 30 rt 60 ] ]\nsewpath(resample(ring, 2))\n```\n\n```\nlet disc = trace [ arc 360 28 ]\nfor p in scatter(3, disc) [\n  up setpos(p) down arc 360 0.5 trim\n]\n```",
        "completion": { "kind": "text", "text": "trace [\n\t$0\n]" },
        "isSnippet": true,
        "signatures": []
      }
    },
    {
      "id": "tracerings",
      "label": "tracerings",
      "category": "trace",
      "tags": ["block", "embroidery", "geometry", "keyword", "library", "trace"],
      "summary": "Like `trace`, but captures every pen-down run as a separate path. Returns a list of paths (list of lists of `[x, y]` points), in drawing order. Each pen-up/pen-down boundary starts a new ring.",
      "editor": {
        "kind": "keyword",
        "detail": "capture multiple pen-down paths as data",
        "documentation": "Like `trace`, but captures every pen-down run as a separate path. Returns a list of paths (list of lists of `[x, y]` points), in drawing order. Each pen-up/pen-down boundary starts a new ring.\n\n```\nlet donut = tracerings [\n  arc 360 25\n  up setxy 8 0 down\n  arc 360 12\n]\nfor ring in donut [ sewpath(resample(ring, 2)) trim ]\n```",
        "completion": { "kind": "text", "text": "tracerings [\n\t$0\n]" },
        "isSnippet": true,
        "signatures": []
      }
    },
    {
      "id": "stitchlen",
      "label": "stitchlen",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "aliases": ["stitchlength"],
      "summary": "Running-stitch length, clamped 0.4–12 mm (default 2.5). Alias: `stitchlength`",
      "editor": {
        "kind": "function",
        "detail": "running stitch length — three forms",
        "documentation": "Running-stitch length, clamped 0.4–12 mm (default 2.5).  Alias: `stitchlength`\n\n**Three forms:**\n\n- `stitchlen 2.5` — uniform numeric (unchanged)\n- `stitchlen [4, 1.5]` — cycling list; optional phase offset: `stitchlen [4, 1.5] 1`\n- `stitchlen @fn` — reporter, queried once per stitch:  \n  `def fn(t, s, i, p) [ return mm ]`  \n  `t` = arc-length from stretch start (mm); `s` = normalised 0..1; `i` = stitch index; `p` = hoop-space `[x, y]`.  \n  Must return a positive number.  Clamped 0.4–12 mm.\n\nA numeric `stitchlen` disengages the list or reporter.",
        "completion": { "kind": "text", "text": "stitchlen ${1:mm}" },
        "isSnippet": true,
        "signatures": [["mm"], ["[a, b, …]"], ["[a, b, …] phase"], ["@fn"]]
      }
    },
    {
      "id": "stitchlength",
      "label": "stitchlength",
      "category": "stitching",
      "tags": ["embroidery", "function", "library", "millimetres", "stitching"],
      "aliasFor": "stitchlen",
      "summary": "Alias for `stitchlen`. Running-stitch length 0.4–12 mm.",
      "editor": {
        "kind": "function",
        "detail": "running stitch length — alias for stitchlen",
        "documentation": "Alias for `stitchlen`. Running-stitch length 0.4–12 mm.",
        "completion": { "kind": "text", "text": "stitchlength ${1:mm}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "satin",
      "label": "satin",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "heading", "millimetres", "stitching"],
      "summary": "Zigzag satin column of this width; penetration spacing set by `density`. `satin 0` returns to running stitch. Width > ~8 mm risks snagging.",
      "editor": {
        "kind": "function",
        "detail": "satin column width (mm) — or @reporter",
        "documentation": "Zigzag satin column of this width; penetration spacing set by `density`. `satin 0` returns to running stitch. Width > ~8 mm risks snagging.\n\n**Programmable satin:** `satin @fn` engages a user *shape reporter* that controls the column per stitch pair. The reporter takes `(t, s, i, u)` — cursor arc-length (mm), normalized position (0..1), 0-based pair index, local heading — and returns `[advance, leftw, rightw, leftlag, rightlag]` (all mm; `advance` > 0). A reporter that may not return on every path is caught at **parse time**.\n\n**Tuple helpers** (call-syntax, library tier):\n- `satinpair(adv, w)` → `[adv, w, w, 0, 0]` — symmetric perpendicular bite\n- `satinasym(adv, lw, rw)` → `[adv, lw, rw, 0, 0]` — asymmetric column\n- `satinrake(adv, w, lag)` → `[adv, w, w, -lag, lag]` — diagonal rake / crosshatch\n\n`satin 4 ≡ satin @c` where `def c(t,s,i,u) [ return satinpair(0.4, 2) ]`.",
        "completion": { "kind": "text", "text": "satin ${1:width}" },
        "isSnippet": true,
        "signatures": [["width"]]
      }
    },
    {
      "id": "satinbetween",
      "label": "satinbetween",
      "category": "stitching",
      "tags": [
        "call-syntax",
        "core",
        "embroidery",
        "function",
        "geometry",
        "millimetres",
        "pure",
        "stitching"
      ],
      "summary": "Sews an immediate satin column between two independently authored path rails. Rails are mapped through the active transform/warp before arc-length pairing, so `density`, underlay, pull compensation, short-stitch relief, coverage, and ceiling checks use physical millimetres. Both rails must both be open or both be explicitly closed.",
      "editor": {
        "kind": "function",
        "detail": "satin column between two path rails",
        "documentation": "Sews an immediate satin column between two independently authored path rails. Rails are mapped through the active transform/warp before arc-length pairing, so `density`, underlay, pull compensation, short-stitch relief, coverage, and ceiling checks use physical millimetres. Both rails must both be open or both be explicitly closed.\n\nForms:\n- `satinbetween(a, b)`\n- `satinbetween(a, b, checkpoints)` where checkpoints are ordered `[[pointA, pointB], …]`\n- `satinbetween(a, b, @shape)`\n- `satinbetween(a, b, checkpoints, @shape)`\n\nA shape reporter takes `(t, s, i, u)` and returns `[advance, insetA, insetB, lagA, lagB]`. Use `railinset` and `railrake` to build tuples. Drawless unless the reporter draws. Call syntax only; statement-only.",
        "completion": { "kind": "text", "text": "satinbetween(${1:railA}, ${2:railB})" },
        "isSnippet": true,
        "signatures": [
          ["railA", "railB"],
          ["railA", "railB", "checkpoints"],
          ["railA", "railB", "@shape"],
          ["railA", "railB", "checkpoints", "@shape"]
        ]
      }
    },
    {
      "id": "satincap",
      "label": "satincap",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "mode", "pure", "stitching"],
      "summary": "Choose the construction at both ends of an open spine or rail-pair satin column. `'legacy'` preserves existing output; `'butt'` finishes at full width; `'taper'` narrows over `satincaplen` while retaining a safe terminal bite; `'point'` converges both rails with coincident tip penetrations merged; `'round'` fans through a semicircular profile when the column is long enough. Closed columns have no caps and retain t…",
      "editor": {
        "kind": "function",
        "detail": "choose open satin-column cap construction",
        "documentation": "Choose the construction at both ends of an open spine or rail-pair satin column. `'legacy'` preserves existing output; `'butt'` finishes at full width; `'taper'` narrows over `satincaplen` while retaining a safe terminal bite; `'point'` converges both rails with coincident tip penetrations merged; `'round'` fans through a semicircular profile when the column is long enough. Closed columns have no caps and retain their seam. Underlay is shortened beneath narrowing caps. The policy is sticky, `stitchscope`-aware, and drawless.",
        "example": "satincap 'taper'",
        "completion": { "kind": "modes", "source": "satincap", "quote": "'" },
        "isSnippet": true,
        "signatures": [["mode"]]
      }
    },
    {
      "id": "satincaplen",
      "label": "satincaplen",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "millimetres", "stitching"],
      "summary": "Set the physical transition length used by taper, point, and round caps. Range 0.4–20 mm; default 2. On a short column each end is bounded to half the available spine length. Round caps fall back to point when a true semicircle cannot fit.",
      "editor": {
        "kind": "function",
        "detail": "satin cap transition length (mm)",
        "documentation": "Set the physical transition length used by taper, point, and round caps. Range 0.4–20 mm; default 2. On a short column each end is bounded to half the available spine length. Round caps fall back to point when a true semicircle cannot fit.",
        "example": "satincaplen 2",
        "completion": { "kind": "text", "text": "satincaplen ${1:2}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "satinjoin",
      "label": "satinjoin",
      "category": "stitching",
      "tags": [
        "core",
        "embroidery",
        "function",
        "geometry",
        "millimetres",
        "mode",
        "pure",
        "stitching"
      ],
      "summary": "Choose how sharp corners at or above `satincorner` are constructed. `'legacy'` preserves the previous event stream; `'continuous'` keeps one continuous zigzag with short-stitch relief; `'fan'` distributes at most eight outer-rail penetrations around the turn and keeps at most two shortened inner bites; `'miter'` overlaps straight legs at their bounded rail intersections; `'split'` ends and restarts the topping leg…",
      "editor": {
        "kind": "function",
        "detail": "choose sharp satin-corner construction",
        "documentation": "Choose how sharp corners at or above `satincorner` are constructed. `'legacy'` preserves the previous event stream; `'continuous'` keeps one continuous zigzag with short-stitch relief; `'fan'` distributes at most eight outer-rail penetrations around the turn and keeps at most two shortened inner bites; `'miter'` overlaps straight legs at their bounded rail intersections; `'split'` ends and restarts the topping legs with a 0.5 mm overlap. Underlay remains continuous through every join. Miter and split connectors are stitches: they never add a trim or color change. Unsupported or closed geometry warns and falls back to continuous. The policy is sticky, `stitchscope`-aware, and drawless.",
        "example": "satinjoin 'fan'",
        "completion": { "kind": "modes", "source": "satinjoin", "quote": "'" },
        "isSnippet": true,
        "signatures": [["mode"]]
      }
    },
    {
      "id": "satincorner",
      "label": "satincorner",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "heading", "stitching"],
      "summary": "Set the minimum absolute change in travel direction that selects a non-legacy satin join. Range 5–175 degrees; default 60. Lower values classify gentler bends as corners. Measured after the authored output transform in physical hoop space.",
      "editor": {
        "kind": "function",
        "detail": "sharp satin turn threshold (degrees)",
        "documentation": "Set the minimum absolute change in travel direction that selects a non-legacy satin join. Range 5–175 degrees; default 60. Lower values classify gentler bends as corners. Measured after the authored output transform in physical hoop space.",
        "example": "satincorner 35",
        "completion": { "kind": "text", "text": "satincorner ${1:60}" },
        "isSnippet": true,
        "signatures": [["degrees"]]
      }
    },
    {
      "id": "satinwide",
      "label": "satinwide",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "pure", "stitching"],
      "summary": "Choose how columns wider than `satinmaxwidth` are handled. `'warn'` is the byte-identical legacy path. `'split'` partitions a safe open, smooth column into adjacent hoop-space subcolumns. Shared seams alternate ownership of the `satinsplitoverlap` band so the topping interlocks without a fixed double-density strip. Each subcolumn sews its underlay before topping, and nearest-end routing limits jumps. Closed column…",
      "editor": {
        "kind": "function",
        "detail": "choose wide satin-column handling",
        "documentation": "Choose how columns wider than `satinmaxwidth` are handled. `'warn'` is the byte-identical legacy path. `'split'` partitions a safe open, smooth column into adjacent hoop-space subcolumns. Shared seams alternate ownership of the `satinsplitoverlap` band so the topping interlocks without a fixed double-density strip. Each subcolumn sews its underlay before topping, and nearest-end routing limits jumps. Closed columns, sharp/cusped curves, crossed rails, and reporter-defined rake warn and remain unsplit. Sticky, `stitchscope`-aware, and drawless.",
        "example": "satinwide 'split'",
        "completion": { "kind": "modes", "source": "satinwide", "quote": "'" },
        "isSnippet": true,
        "signatures": [["mode"]]
      }
    },
    {
      "id": "satinmaxwidth",
      "label": "satinmaxwidth",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "summary": "Set the physical hoop-space width ceiling that activates and sizes `satinwide 'split'`. Range 2–12 mm; default 7.5. It does not replace the legacy snag warning while `satinwide 'warn'` is active.",
      "editor": {
        "kind": "function",
        "detail": "maximum split satin subcolumn width (mm)",
        "documentation": "Set the physical hoop-space width ceiling that activates and sizes `satinwide 'split'`. Range 2–12 mm; default 7.5. It does not replace the legacy snag warning while `satinwide 'warn'` is active.",
        "example": "satinmaxwidth 7.5",
        "completion": { "kind": "text", "text": "satinmaxwidth ${1:7.5}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "satinsplitoverlap",
      "label": "satinsplitoverlap",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "summary": "Set the physical width alternately assigned across neighboring split-column seams. Range 0–1 mm; default 0.5. The shared seam moves by half this amount to avoid both gaps and a stationary double-density band.",
      "editor": {
        "kind": "function",
        "detail": "split satin seam interlock (mm)",
        "documentation": "Set the physical width alternately assigned across neighboring split-column seams. Range 0–1 mm; default 0.5. The shared seam moves by half this amount to avoid both gaps and a stationary double-density band.",
        "example": "satinsplitoverlap 0.5",
        "completion": { "kind": "text", "text": "satinsplitoverlap ${1:0.5}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "density",
      "label": "density",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "summary": "Satin penetration spacing, 0.25–5 mm (default 0.4).",
      "editor": {
        "kind": "function",
        "detail": "satin penetration spacing (mm)",
        "documentation": "Satin penetration spacing, 0.25–5 mm (default 0.4).",
        "completion": { "kind": "text", "text": "density ${1:spacing}" },
        "isSnippet": true,
        "signatures": [["spacing"]]
      }
    },
    {
      "id": "bean",
      "label": "bean",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "stitching"],
      "summary": "Bold line: each stitch sewn n times (forced odd, max 9). `bean 1` off.",
      "editor": {
        "kind": "function",
        "detail": "bold stitch repeat count (1–9)",
        "documentation": "Bold line: each stitch sewn n times (forced odd, max 9). `bean 1` off.",
        "completion": { "kind": "text", "text": "bean ${1:count}" },
        "isSnippet": true,
        "signatures": [["count"]]
      }
    },
    {
      "id": "estitch",
      "label": "estitch",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "summary": "Blanket stitch: prongs of this length on the left of travel direction, spaced by `stitchlen`. `estitch 0` off.",
      "editor": {
        "kind": "function",
        "detail": "blanket stitch prong length (mm)",
        "documentation": "Blanket stitch: prongs of this length on the left of travel direction, spaced by `stitchlen`. `estitch 0` off.",
        "completion": { "kind": "text", "text": "estitch ${1:mm}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "beginfill",
      "label": "beginfill",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "stitching"],
      "summary": "Start tracing a fill boundary. Moves between `beginfill` and `endfill` define the shape rather than sewing. A pen-up move starts a new ring — inner rings become holes (even-odd rule).",
      "editor": {
        "kind": "function",
        "detail": "begin fill boundary trace",
        "documentation": "Start tracing a fill boundary. Moves between `beginfill` and `endfill` define the shape rather than sewing. A pen-up move starts a new ring — inner rings become holes (even-odd rule).",
        "completion": { "kind": "text", "text": "beginfill" },
        "signatures": [[]]
      }
    },
    {
      "id": "endfill",
      "label": "endfill",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "stitching"],
      "summary": "Close the fill boundary and sew a tatami fill of the enclosed area.",
      "editor": {
        "kind": "function",
        "detail": "end fill — sew the enclosed area",
        "documentation": "Close the fill boundary and sew a tatami fill of the enclosed area.",
        "completion": { "kind": "text", "text": "endfill" },
        "signatures": [[]]
      }
    },
    {
      "id": "fill",
      "label": "fill",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "stitching"],
      "summary": "Arm a programmable fill for the next `beginfill…endfill`. `fill dir @field` drives row direction; `fill shape @texture` drives spacing/length/brick; `fill paths @generator` supplies ordered path geometry; `fill paths pathsExpr` freezes static paths. The engine retains clipping, pull compensation, underlay, subdivision, coverage, and budgets.",
      "editor": {
        "kind": "function",
        "detail": "programmable fill (field, texture, or paths)",
        "documentation": "Arm a programmable fill for the next `beginfill…endfill`. `fill dir @field` drives row direction; `fill shape @texture` drives spacing/length/brick; `fill paths @generator` supplies ordered path geometry; `fill paths pathsExpr` freezes static paths. The engine retains clipping, pull compensation, underlay, subdivision, coverage, and budgets.",
        "completion": { "kind": "text", "text": "fill dir @${1:field}" },
        "isSnippet": true,
        "signatures": [["field"]]
      }
    },
    {
      "id": "fillangle",
      "label": "fillangle",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "heading", "stitching"],
      "summary": "Direction of the fill stitch rows, in degrees (default 0 = vertical).",
      "editor": {
        "kind": "function",
        "detail": "fill row direction (degrees)",
        "documentation": "Direction of the fill stitch rows, in degrees (default 0 = vertical).",
        "completion": { "kind": "text", "text": "fillangle ${1:degrees}" },
        "isSnippet": true,
        "signatures": [["degrees"]]
      }
    },
    {
      "id": "fillspacing",
      "label": "fillspacing",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "summary": "Fill row spacing, 0.25–5 mm (default 0.4).",
      "editor": {
        "kind": "function",
        "detail": "fill row spacing (mm)",
        "documentation": "Fill row spacing, 0.25–5 mm (default 0.4).",
        "completion": { "kind": "text", "text": "fillspacing ${1:mm}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "fillinset",
      "label": "fillinset",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "millimetres", "stitching"],
      "summary": "Reserve space inside a fill boundary for a later border. Range 0–10 mm (default 0). The complete compound even-odd region is inset in physical hoop space: outer boundaries shrink, holes expand, and concave regions may split. Topping and fill underlay use the inset region; disconnected pieces are crossed only by jumps. Collapsed or split geometry warns with a source line and preview location.",
      "editor": {
        "kind": "function",
        "detail": "inset the fill construction region (mm)",
        "documentation": "Reserve space inside a fill boundary for a later border. Range 0–10 mm (default 0). The complete compound even-odd region is inset in physical hoop space: outer boundaries shrink, holes expand, and concave regions may split. Topping and fill underlay use the inset region; disconnected pieces are crossed only by jumps. Collapsed or split geometry warns with a source line and preview location.",
        "example": "fillinset 0.4",
        "completion": { "kind": "text", "text": "fillinset ${1:mm}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "filledgerun",
      "label": "filledgerun",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "millimetres", "stitching"],
      "summary": "Add a closed boundary pass after fill underlay and before topping, inset by the requested physical distance. Range 0–10 mm; 0 disables it (default). Compound even-odd geometry keeps outer and hole contours inside the construction region and jumps between disconnected contours. Acute-corner penetrations are bounded, and dense overlap near a later border warns.",
      "editor": {
        "kind": "function",
        "detail": "add an inset topping edge run (mm)",
        "documentation": "Add a closed boundary pass after fill underlay and before topping, inset by the requested physical distance. Range 0–10 mm; 0 disables it (default). Compound even-odd geometry keeps outer and hole contours inside the construction region and jumps between disconnected contours. Acute-corner penetrations are bounded, and dense overlap near a later border warns.",
        "example": "filledgerun 0.5",
        "completion": { "kind": "text", "text": "filledgerun ${1:mm}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "filledgeshort",
      "label": "filledgeshort",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "millimetres", "stitching"],
      "summary": "Omit open topping row fragments shorter than this physical hoop-space length before connector routing. Range 0–10 mm; 0 disables it (default). Applies to fixed tatami, programmable streamlines, and open custom fill paths; underlay and closed decorative contours are unchanged.",
      "editor": {
        "kind": "function",
        "detail": "minimum useful fill-row fragment (mm)",
        "documentation": "Omit open topping row fragments shorter than this physical hoop-space length before connector routing. Range 0–10 mm; 0 disables it (default). Applies to fixed tatami, programmable streamlines, and open custom fill paths; underlay and closed decorative contours are unchanged.",
        "example": "filledgeshort 0.7",
        "completion": { "kind": "text", "text": "filledgeshort ${1:mm}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "fillstagger",
      "label": "fillstagger",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "mode", "stitching"],
      "summary": "Choose the topping-row phase policy. `'legacy'` preserves existing output; `'brick'` alternates 0 and `fillstaggeramount`; `'progressive'` repeats the wrapped four-row cycle `0, amount, 3×amount, 2×amount`; `'random'` hashes row geometry into a stable phase without drawing from the seeded RNG. A `fill shape @fn` reporter retains its cumulative phase as the base, then the policy offset is added and wrapped. Fill un…",
      "editor": {
        "kind": "function",
        "detail": "choose fill-row penetration staggering",
        "documentation": "Choose the topping-row phase policy. `'legacy'` preserves existing output; `'brick'` alternates 0 and `fillstaggeramount`; `'progressive'` repeats the wrapped four-row cycle `0, amount, 3×amount, 2×amount`; `'random'` hashes row geometry into a stable phase without drawing from the seeded RNG. A `fill shape @fn` reporter retains its cumulative phase as the base, then the policy offset is added and wrapped. Fill underlay is unaffected.",
        "example": "fillstagger 'progressive'",
        "completion": { "kind": "modes", "source": "fillstagger", "quote": "'" },
        "isSnippet": true,
        "signatures": [["mode"]]
      }
    },
    {
      "id": "fillstaggeramount",
      "label": "fillstaggeramount",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "mode", "stitching"],
      "summary": "Set the wrapped phase fraction used by non-legacy fill staggering. Range 0–1; default 0.65. With fixed fill length, the fraction is multiplied by that length. List/reporter forms use the first effective stitch length of each row. Policy-created edge fragments below 0.4 mm are merged with a spatial, source-attributed warning.",
      "editor": {
        "kind": "function",
        "detail": "fill stagger phase amount (fraction)",
        "documentation": "Set the wrapped phase fraction used by non-legacy fill staggering. Range 0–1; default 0.65. With fixed fill length, the fraction is multiplied by that length. List/reporter forms use the first effective stitch length of each row. Policy-created edge fragments below 0.4 mm are merged with a spatial, source-attributed warning.",
        "example": "fillstaggeramount 0.65",
        "completion": { "kind": "text", "text": "fillstaggeramount ${1:0.65}" },
        "isSnippet": true,
        "signatures": [["fraction"]]
      }
    },
    {
      "id": "fillconnect",
      "label": "fillconnect",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "millimetres", "stitching"],
      "summary": "Choose how topping rows and custom fill-path fragments connect. `'legacy'` preserves existing short sewn connectors. `'inside'` sews only when the complete physical hoop-space segment stays inside the compound fill region with edge clearance. `'jump'` always uses jump travel. `'trim'` jumps and cuts first when the connector reaches the active `autotrim` threshold (or 7 mm while automatic trimming is off). Fill und…",
      "editor": {
        "kind": "function",
        "detail": "choose between-row fill travel",
        "documentation": "Choose how topping rows and custom fill-path fragments connect. `'legacy'` preserves existing short sewn connectors. `'inside'` sews only when the complete physical hoop-space segment stays inside the compound fill region with edge clearance. `'jump'` always uses jump travel. `'trim'` jumps and cuts first when the connector reaches the active `autotrim` threshold (or 7 mm while automatic trimming is off). Fill underlay is unchanged.",
        "example": "fillconnect 'inside'",
        "completion": { "kind": "modes", "source": "fillconnect", "quote": "'" },
        "isSnippet": true,
        "signatures": [["mode"]]
      }
    },
    {
      "id": "filllen",
      "label": "filllen",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "summary": "Fill stitch length. Defaults to `stitchlen`. `filllen 0` follows `stitchlen` again.",
      "editor": {
        "kind": "function",
        "detail": "fill stitch length — three forms",
        "documentation": "Fill stitch length.  Defaults to `stitchlen`.  `filllen 0` follows `stitchlen` again.\n\n**Three forms:**\n\n- `filllen 3` — uniform numeric (1–7 mm)\n- `filllen [3.5, 1.0]` — cycling list per row stitch; optional phase offset\n- `filllen @fn` — reporter `def fn(t, s, i, p) [ return mm ]` per fill-row stitch\n\n`filllen 0` propagates whichever form `stitchlen` currently uses.",
        "completion": { "kind": "text", "text": "filllen ${1:mm}" },
        "isSnippet": true,
        "signatures": [["mm"], ["[a, b, …]"], ["@fn"]]
      }
    },
    {
      "id": "color",
      "label": "color",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "stitching"],
      "summary": "Switch to numeric thread n, or resolve a color string such as `color '#e94560'` or `color 'crimson'`.",
      "editor": {
        "kind": "function",
        "detail": "switch thread color",
        "documentation": "Switch to numeric thread n, or resolve a color string such as `color '#e94560'` or `color 'crimson'`.",
        "completion": { "kind": "text", "text": "color ${1:'#e94560'}" },
        "isSnippet": true,
        "signatures": [["n"]]
      }
    },
    {
      "id": "palette",
      "label": "palette",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "stateful", "stitching", "top-level"],
      "summary": "Top-level, once-only palette metadata. Takes a list of 1–64 colors and must precede stitches, `color`, and `stop`.",
      "editor": {
        "kind": "function",
        "detail": "declare thread colors",
        "documentation": "Top-level, once-only palette metadata. Takes a list of 1–64 colors and must precede stitches, `color`, and `stop`.",
        "completion": {
          "kind": "text",
          "text": "palette ['${1:#0b132b}', '${2:#5bc0be}', '${3:#e94560}']"
        },
        "isSnippet": true,
        "signatures": [["colors"]]
      }
    },
    {
      "id": "background",
      "label": "background",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "stateful", "stitching", "top-level"],
      "summary": "Top-level fabric-color metadata. Must precede the first stitch and does not affect DST output.",
      "editor": {
        "kind": "function",
        "detail": "declare fabric color",
        "documentation": "Top-level fabric-color metadata. Must precede the first stitch and does not affect DST output.",
        "completion": { "kind": "text", "text": "background '${1:#f5efe4}'" },
        "isSnippet": true,
        "signatures": [["color"]]
      }
    },
    {
      "id": "stop",
      "label": "stop",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "stitching"],
      "summary": "Shorthand for \"next colour\" — equivalent to incrementing the thread number by 1.",
      "editor": {
        "kind": "function",
        "detail": "next color (shorthand)",
        "documentation": "Shorthand for \"next colour\" — equivalent to incrementing the thread number by 1.",
        "completion": { "kind": "text", "text": "stop" },
        "signatures": [[]]
      }
    },
    {
      "id": "trim",
      "label": "trim",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "stitching"],
      "summary": "Cut the thread here. Long travels also get one automatically (see `autotrim`).",
      "editor": {
        "kind": "function",
        "detail": "cut thread here",
        "documentation": "Cut the thread here. Long travels also get one automatically (see `autotrim`).",
        "completion": { "kind": "text", "text": "trim" },
        "signatures": [[]]
      }
    },
    {
      "id": "lock",
      "label": "lock",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "summary": "Tie-in/tie-off: 4 micro back-stitches where thread starts/ends. Size 0.3–1.5 mm (default 0.7). `lock 0` off.",
      "editor": {
        "kind": "function",
        "detail": "tie-in/tie-off size (mm)",
        "documentation": "Tie-in/tie-off: 4 micro back-stitches where thread starts/ends. Size 0.3–1.5 mm (default 0.7). `lock 0` off.",
        "completion": { "kind": "text", "text": "lock ${1:size}" },
        "isSnippet": true,
        "signatures": [["size"]]
      }
    },
    {
      "id": "compensation",
      "label": "compensation",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "mode", "pure", "stitching"],
      "summary": "Choose compensation semantics. `'legacy'` (default) preserves scalar `pullcomp` for satin and fill. `'directional'` applies the grain-aligned tensor across satin columns and along open fill-row endpoint tangents in final physical hoop space. Curved rows resolve each end independently; closed fill contours stay unchanged. Endpoint crossings of an authored outer boundary or hole warn spatially—use `fillinset` to res…",
      "editor": {
        "kind": "function",
        "detail": "satin and fill compensation mode",
        "documentation": "Choose compensation semantics. `'legacy'` (default) preserves scalar `pullcomp` for satin and fill. `'directional'` applies the grain-aligned tensor across satin columns and along open fill-row endpoint tangents in final physical hoop space. Curved rows resolve each end independently; closed fill contours stay unchanged. Endpoint crossings of an authored outer boundary or hole warn spatially—use `fillinset` to reserve border overlap. `fabric` supplies the mean pull magnitude; a later explicit `pullcomp` replaces it while retaining `fabricstretch` anisotropy, and a later `fabric` restores profile defaults. Push remains unapplied pending sew-out evidence. Sticky, `stitchscope`-aware, and drawless.",
        "example": "compensation 'directional'",
        "completion": { "kind": "modes", "source": "compensation", "quote": "'" },
        "isSnippet": true,
        "signatures": [["mode"]]
      }
    },
    {
      "id": "pullcomp",
      "label": "pullcomp",
      "category": "stitching",
      "tags": [
        "core",
        "embroidery",
        "function",
        "geometry",
        "millimetres",
        "stateful",
        "stitching"
      ],
      "summary": "Pull compensation 0–1.5 mm: widens satin columns and extends open fill rows so shapes sew out at their digitized size. Under `compensation 'directional'`, it replaces the material tensor's mean pull magnitude while retaining declared stretch anisotropy; satin projects it across columns and fills project it along physical endpoint tangents. Reserve border overlap with `fillinset`.",
      "editor": {
        "kind": "function",
        "detail": "pull compensation (mm)",
        "documentation": "Pull compensation 0–1.5 mm: widens satin columns and extends open fill rows so shapes sew out at their digitized size. Under `compensation 'directional'`, it replaces the material tensor's mean pull magnitude while retaining declared stretch anisotropy; satin projects it across columns and fills project it along physical endpoint tangents. Reserve border overlap with `fillinset`.",
        "completion": { "kind": "text", "text": "pullcomp ${1:mm}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "shortstitch",
      "label": "shortstitch",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "stitching"],
      "summary": "Curve physics (on by default): on tight satin curves, alternate inner stitches are shortened to 60% width to prevent thread breaks.",
      "editor": {
        "kind": "function",
        "detail": "short-stitch on/off (0 or 1)",
        "documentation": "Curve physics (on by default): on tight satin curves, alternate inner stitches are shortened to 60% width to prevent thread breaks.",
        "completion": { "kind": "text", "text": "shortstitch ${1:on}" },
        "isSnippet": true,
        "signatures": [["on"]]
      }
    },
    {
      "id": "autotrim",
      "label": "autotrim",
      "category": "stitching",
      "tags": ["core", "function", "millimetres", "stitching"],
      "summary": "Auto trim before travels ≥ n mm (default 7, range 3–30). `autotrim 0` off.",
      "editor": {
        "kind": "function",
        "detail": "auto-trim threshold (mm)",
        "documentation": "Auto trim before travels ≥ n mm (default 7, range 3–30). `autotrim 0` off.",
        "completion": { "kind": "text", "text": "autotrim ${1:mm}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "maxdensity",
      "label": "maxdensity",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "stitching"],
      "summary": "Thread-coverage warning threshold in layers (default 3.5). `maxdensity 0` silences warnings.",
      "editor": {
        "kind": "function",
        "detail": "density warning threshold (layers)",
        "documentation": "Thread-coverage warning threshold in layers (default 3.5). `maxdensity 0` silences warnings.",
        "completion": { "kind": "text", "text": "maxdensity ${1:layers}" },
        "isSnippet": true,
        "signatures": [["layers"]]
      }
    },
    {
      "id": "hoop",
      "label": "hoop",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "mode", "stitching"],
      "summary": "Configure the physical hoop for this design. The sewable field is the hoop inset by 3 mm on every side.",
      "editor": {
        "kind": "function",
        "detail": "set the physical hoop and sewable field",
        "documentation": "Configure the physical hoop for this design. The sewable field is the hoop inset by 3 mm on every side.\n\n**Named presets:**\n- `'round100'` — ⌀100 mm round (default)\n- `'4x4'` — 100 × 100 mm\n- `'5x7'` — 130 × 180 mm  \n- `'6x10'` — 160 × 260 mm\n- `'8x8'` — 200 × 200 mm\n- `'8x12'` — 200 × 300 mm\n\n**Numeric (round hoop):** `hoop 150` → ⌀150 mm\n**List (rectangular):** `hoop [130, 180]` → 130 × 180 mm\n\nMust be at the top of the program, before any stitches. At most one per program.\n\n```\nhoop '5x7'\nseed 42\nlet pts = scatter(8)  // fills the 124 × 174 mm field\n```",
        "completion": { "kind": "text", "text": "hoop '${1|round100,4x4,5x7,6x10,8x8,8x12|}'" },
        "isSnippet": true,
        "signatures": [["preset"], ["diameter"], ["dimensions"]]
      }
    },
    {
      "id": "override",
      "label": "override",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "stitching"],
      "summary": "Raise (with a warning) or lower (with an info note) a run-envelope budget.",
      "editor": {
        "kind": "function",
        "detail": "raise or lower a run-envelope budget",
        "documentation": "Raise (with a warning) or lower (with an info note) a run-envelope budget.\n\n**Keys and stock values:**\n| Key | Stock | Ceiling |\n|---|---|---|\n| `'stitches'` | 100,000 | 250,000 |\n| `'ops'` | 10,000,000 | 50,000,000 |\n| `'calldepth'` | 200 | 2,000 |\n| `'loopiters'` | 200,000 | 5,000,000 |\n| `'listlen'` | 100,000 | 1,000,000 |\n| `'listcells'` | 1,000,000 | 8,000,000 |\n| `'stringlen'` | 10,000 | 1,000,000 |\n| `'stringtotal'` | 1,000,000 | 20,000,000 |\n| `'scatterpoints'` | 20,000 | 100,000 |\n| `'geoinput'` | 10,000 | 50,000 |\n| `'clipverts'` | 50,000 | 250,000 |\n| `'chalks'` | 2,000 | 20,000 |\n| `'chalkverts'` | 200,000 | 2,000,000 |\n\nMust be at the top of the program, before any stitches.\n\n```\nhoop '6x10'\noverride 'stitches' 120000\n```",
        "completion": {
          "kind": "text",
          "text": "override '${1|stitches,ops,calldepth,loopiters,listlen,listcells,stringlen,stringtotal,scatterpoints,geoinput,clipverts,chalks,chalkverts|}' ${2:value}"
        },
        "isSnippet": true,
        "signatures": [["key", "value"]]
      }
    },
    {
      "id": "plan",
      "label": "plan",
      "category": "stitching",
      "tags": ["block", "core", "embroidery", "function", "geometry", "stitching", "top-level"],
      "summary": "Top-level travel-planning directive. With no `routegroup`, `plan 'nearest'` greedily reorders whole thread runs within each color block after execution and before autotrim/locks. Once any route group executes, only grouped runs are eligible and ungrouped output remains authored; grouped intersections also receive bounded 2-opt improvement. `plan 'reversing-nearest'` may enter eligible stitch-only runs from their n…",
      "editor": {
        "kind": "function",
        "detail": "reorder independent thread runs to shorten travel",
        "documentation": "Top-level travel-planning directive. With no `routegroup`, `plan 'nearest'` greedily reorders whole thread runs within each color block after execution and before autotrim/locks. Once any route group executes, only grouped runs are eligible and ungrouped output remains authored; grouped intersections also receive bounded 2-opt improvement. `plan 'reversing-nearest'` may enter eligible stitch-only runs from their nearer endpoint. Planning never crosses a color change, changes stitch geometry, or removes an explicit `trim`. Use `plan 'off'` for an explicit no-op. Must appear before the first stitch and at most once.",
        "example": "plan 'reversing-nearest'",
        "completion": { "kind": "modes", "source": "plan", "quote": "'" },
        "isSnippet": true,
        "signatures": [["mode"]]
      }
    },
    {
      "id": "preflight",
      "label": "preflight",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "mode", "stitching", "top-level"],
      "summary": "Select the post-run diagnostic policy. `preflight 'off'` (the default) keeps existing always-on warnings and their structured locations, but skips extended event-stream and construction recommendations. `preflight 'warn'` adds those extended checks without changing stitches or turning findings into legacy console warnings. `preflight 'strict'` runs the same checks and rejects the run only when a finding has severi…",
      "editor": {
        "kind": "function",
        "detail": "run extended sewability diagnostics",
        "documentation": "Select the post-run diagnostic policy. `preflight 'off'` (the default) keeps existing always-on warnings and their structured locations, but skips extended event-stream and construction recommendations. `preflight 'warn'` adds those extended checks without changing stitches or turning findings into legacy console warnings. `preflight 'strict'` runs the same checks and rejects the run only when a finding has severity `error`; `warning` and `info` recommendations never fail strict mode. Top-level only, before the first committed stitch, forbidden in `trace`, and allowed at most once.",
        "example": "preflight 'warn'",
        "completion": { "kind": "modes", "source": "preflight", "quote": "'" },
        "isSnippet": true,
        "signatures": [["mode"]]
      }
    },
    {
      "id": "planbarrier",
      "label": "planbarrier",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "stitching"],
      "summary": "Start a new independent travel-planner segment at this point in the authored stitch stream. Planning may reorder runs on either side, but never moves a run across the barrier. `planbarrier` emits no stitch, jump, trim, color, or mark. During normal sewing execution it is completely inert when planning is absent or `plan 'off'`, including leaving buffered construction untouched. Consecutive barriers and barriers be…",
      "editor": {
        "kind": "function",
        "detail": "prevent travel planning across this authored boundary",
        "documentation": "Start a new independent travel-planner segment at this point in the authored stitch stream. Planning may reorder runs on either side, but never moves a run across the barrier. `planbarrier` emits no stitch, jump, trim, color, or mark. During normal sewing execution it is completely inert when planning is absent or `plan 'off'`, including leaving buffered construction untouched. Consecutive barriers and barriers before/after all sewing are harmless. It may appear in normal control flow and procedures. It is always rejected inside `trace`; with planning active it is also rejected inside an open `beginfill…endfill` recording.",
        "example": "fd 5\nplanbarrier\nrt 90 fd 5",
        "completion": { "kind": "text", "text": "planbarrier" },
        "signatures": [[]]
      }
    },
    {
      "id": "atomic",
      "label": "atomic",
      "category": "stitching",
      "tags": ["block", "core", "embroidery", "function", "stitching"],
      "summary": "Treat every routable run emitted by the block as one indivisible, forward-only travel-planner item. Internal stitches, jumps, trims, marks, underlay, and topping retain their authored order while the complete item may move within its color and `planbarrier` segment. Nested `atomic` blocks belong to the outermost span. With planning absent or `plan 'off'`, the block is byte-identical to its body and does not flush…",
      "editor": {
        "kind": "function",
        "detail": "keep a construction contiguous during travel planning",
        "documentation": "Treat every routable run emitted by the block as one indivisible, forward-only travel-planner item. Internal stitches, jumps, trims, marks, underlay, and topping retain their authored order while the complete item may move within its color and `planbarrier` segment. Nested `atomic` blocks belong to the outermost span. With planning absent or `plan 'off'`, the block is byte-identical to its body and does not flush buffered construction. Active atomics cannot cross a color change or `planbarrier`, start/end inside an open `beginfill…endfill`, or run inside `trace`.\n\n```\natomic [\n  // foundation and decorative pass stay together\n  underlay 'edge'\n  satin 4\n  fd 20\n  trim\n]\n```",
        "example": "atomic [\n  underlay 'edge'\n  satin 4\n  fd 20\n]",
        "completion": { "kind": "text", "text": "atomic [\n\t$0\n]" },
        "isSnippet": true,
        "signatures": [[]]
      }
    },
    {
      "id": "routegroup",
      "label": "routegroup",
      "category": "stitching",
      "tags": ["block", "core", "embroidery", "function", "stitching"],
      "summary": "Make the block's independent thread runs eligible for deterministic nearest routing followed by a bounded 2-opt improvement pass. The group's position is fixed: only runs inside it reorder, and when any `routegroup` executes, output outside all groups stays in authored order. Color changes and `planbarrier` boundaries split planning into independent intersections. An `atomic` inside the group remains one forward-o…",
      "editor": {
        "kind": "function",
        "detail": "limit travel reordering to an explicit collection",
        "documentation": "Make the block's independent thread runs eligible for deterministic nearest routing followed by a bounded 2-opt improvement pass. The group's position is fixed: only runs inside it reorder, and when any `routegroup` executes, output outside all groups stays in authored order. Color changes and `planbarrier` boundaries split planning into independent intersections. An `atomic` inside the group remains one forward-only item. Nested groups belong to the outermost group. With planning absent or `plan 'off'`, the wrapper is byte-identical and does not flush construction. Active groups cannot start inside `atomic`, cross an open `beginfill…endfill` boundary, or run inside `trace`.\n\n```\nroutegroup [\n  motif(20) trim\n  motif(5) trim\n  motif(12)\n]\n```",
        "example": "routegroup [\n  moveto -20 0 down fd 5 up trim\n  moveto 5 0 down fd 5 up trim\n  moveto 12 0 down fd 5\n]",
        "completion": { "kind": "text", "text": "routegroup [\n\t$0\n]" },
        "isSnippet": true,
        "signatures": [[]]
      }
    },
    {
      "id": "fabric",
      "label": "fabric",
      "category": "stitching",
      "tags": ["core", "function", "millimetres", "mode", "stitching"],
      "summary": "Apply a fabric preset. Sets pull compensation, density limit, and underlay defaults.",
      "editor": {
        "kind": "function",
        "detail": "fabric preset",
        "documentation": "Apply a fabric preset. Sets pull compensation, density limit, and underlay defaults.\n\n- `\"woven` — pull 0.2 mm, max 3.5 layers\n- `\"knit` — pull 0.5 mm, max 3.0, density floor 0.45 mm\n- `\"stretch` — pull 0.6 mm, max 2.8, density floor 0.5 mm\n- `\"denim` / `\"canvas` — pull 0.15 mm, max 4.0\n- `\"fleece` — pull 0.3 mm, max 2.6, double underlay",
        "example": "fabric 'knit'",
        "completion": { "kind": "modes", "source": "fabric", "quote": "\"" },
        "isSnippet": true,
        "signatures": [["preset"]]
      }
    },
    {
      "id": "fabricgrain",
      "label": "fabricgrain",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "heading", "stitching"],
      "summary": "Record the fabric grain heading as turtle degrees: 0 points up and positive angles turn clockwise. Values wrap to 0–360. It feeds preview diagnostics and opt-in `compensation 'directional'` satin/fill geometry.",
      "editor": {
        "kind": "function",
        "detail": "fabric grain heading (degrees)",
        "documentation": "Record the fabric grain heading as turtle degrees: 0 points up and positive angles turn clockwise. Values wrap to 0–360. It feeds preview diagnostics and opt-in `compensation 'directional'` satin/fill geometry.",
        "example": "fabricgrain 90",
        "completion": { "kind": "text", "text": "fabricgrain ${1:0}" },
        "isSnippet": true,
        "signatures": [["degrees"]]
      }
    },
    {
      "id": "fabricstretch",
      "label": "fabricstretch",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "stateful", "stitching"],
      "summary": "Record fractional stretch along and across the grain, each from 0 to 1. The values redistribute directional preview and opt-in satin/fill pull while preserving its mean magnitude. A later `fabric` command restores that profile's neutral stretch defaults.",
      "editor": {
        "kind": "function",
        "detail": "declared along/across fabric stretch",
        "documentation": "Record fractional stretch along and across the grain, each from 0 to 1. The values redistribute directional preview and opt-in satin/fill pull while preserving its mean magnitude. A later `fabric` command restores that profile's neutral stretch defaults.",
        "example": "fabricstretch 0.15 0.5",
        "completion": { "kind": "text", "text": "fabricstretch ${1:along} ${2:across}" },
        "isSnippet": true,
        "signatures": [["along", "across"]]
      }
    },
    {
      "id": "threadprofile",
      "label": "threadprofile",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "millimetres", "stitching"],
      "summary": "Select generic `'rayon-40wt'`, `'rayon-60wt'`, `'polyester-40wt'`, or `'polyester-60wt'` metadata. 40 wt resolves to an approximate 0.4 mm width and 60 wt to 0.3 mm. A later `threadwidth` overrides that default. Width scales live coverage queries, the final heatmap, and density warnings without changing stitch geometry.",
      "editor": {
        "kind": "function",
        "detail": "generic thread profile",
        "documentation": "Select generic `'rayon-40wt'`, `'rayon-60wt'`, `'polyester-40wt'`, or `'polyester-60wt'` metadata. 40 wt resolves to an approximate 0.4 mm width and 60 wt to 0.3 mm. A later `threadwidth` overrides that default. Width scales live coverage queries, the final heatmap, and density warnings without changing stitch geometry.",
        "example": "threadprofile 'polyester-40wt'",
        "completion": { "kind": "modes", "source": "threadprofile", "quote": "'" },
        "isSnippet": true,
        "signatures": [["profile"]]
      }
    },
    {
      "id": "threadwidth",
      "label": "threadwidth",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "millimetres", "stitching"],
      "summary": "Override the active thread profile's approximate width with 0.1–1 mm. The width scales live coverage queries, final heatmap layers, and density warnings. It never changes stitch geometry or rescales the active `maxdensity` threshold.",
      "editor": {
        "kind": "function",
        "detail": "resolved thread width metadata (mm)",
        "documentation": "Override the active thread profile's approximate width with 0.1–1 mm. The width scales live coverage queries, final heatmap layers, and density warnings. It never changes stitch geometry or rescales the active `maxdensity` threshold.",
        "example": "threadwidth 0.4",
        "completion": { "kind": "text", "text": "threadwidth ${1:0.4}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "needle",
      "label": "needle",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "stitching"],
      "summary": "Record an advisory NM needle size: 60, 65, 70, 75, 80, 90. Use `needle 0` to leave the size unspecified. Needle metadata does not alter stitch generation.",
      "editor": {
        "kind": "function",
        "detail": "advisory metric needle size",
        "documentation": "Record an advisory NM needle size: 60, 65, 70, 75, 80, 90. Use `needle 0` to leave the size unspecified. Needle metadata does not alter stitch generation.",
        "example": "needle 75",
        "completion": { "kind": "text", "text": "needle ${1|60,65,70,75,80,90|}" },
        "isSnippet": true,
        "signatures": [["sizeNM"]]
      }
    },
    {
      "id": "stabilizer",
      "label": "stabilizer",
      "category": "stitching",
      "tags": ["core", "function", "stitching"],
      "summary": "Record the generic stabilizer category: `'none'`, `'tearaway'`, `'cutaway'`, or `'washaway'`. This is portable intent metadata, not a brand or automatic construction recommendation.",
      "editor": {
        "kind": "function",
        "detail": "stabilizer category metadata",
        "documentation": "Record the generic stabilizer category: `'none'`, `'tearaway'`, `'cutaway'`, or `'washaway'`. This is portable intent metadata, not a brand or automatic construction recommendation.",
        "example": "stabilizer 'cutaway'",
        "completion": { "kind": "modes", "source": "stabilizer", "quote": "'" },
        "isSnippet": true,
        "signatures": [["category"]]
      }
    },
    {
      "id": "topping",
      "label": "topping",
      "category": "stitching",
      "tags": ["core", "function", "stitching"],
      "summary": "Record whether a topping is part of the material setup. Use `topping 1`/`true` when present and `topping 0`/`false` when absent. This advisory metadata does not alter construction.",
      "editor": {
        "kind": "function",
        "detail": "topping used (0/1)",
        "documentation": "Record whether a topping is part of the material setup. Use `topping 1`/`true` when present and `topping 0`/`false` when absent. This advisory metadata does not alter construction.",
        "example": "topping true",
        "completion": { "kind": "text", "text": "topping ${1|true,false|}" },
        "isSnippet": true,
        "signatures": [["enabled"]]
      }
    },
    {
      "id": "underlay",
      "label": "underlay",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "summary": "Stabilising stitches under each satin column.",
      "editor": {
        "kind": "function",
        "detail": "satin underlay style",
        "documentation": "Stabilising stitches under each satin column.\n\n- `\"auto` — picks by width: <1.5 mm none, <4 mm center, wider zigzag\n- `\"center` — center walk\n- `\"edge` — edge walk\n- `\"zigzag` — cross-grain zigzag\n- `\"off` — no underlay",
        "example": "underlay 'auto'",
        "completion": { "kind": "modes", "source": "underlay", "quote": "\"" },
        "isSnippet": true,
        "signatures": [["mode"]]
      }
    },
    {
      "id": "underlaypasses",
      "label": "underlaypasses",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "stitching"],
      "summary": "Set the exact ordered passes sewn beneath every satin column. Accepted pass names are `'center'`, `'edge'`, and `'zigzag'`; duplicates are allowed and an empty list disables underlay. Explicit pass order supersedes `fabric` doubling and `underlay 'auto'`. All underlay events retain the preview `u: 1` flag.",
      "editor": {
        "kind": "function",
        "detail": "ordered satin underlay passes",
        "documentation": "Set the exact ordered passes sewn beneath every satin column. Accepted pass names are `'center'`, `'edge'`, and `'zigzag'`; duplicates are allowed and an empty list disables underlay. Explicit pass order supersedes `fabric` doubling and `underlay 'auto'`. All underlay events retain the preview `u: 1` flag.\n\n```\nunderlaypasses ['center', 'edge']\nunderlaylen 2.8\nunderlayinset 0.6\nunderlayspacing 1.8\n```",
        "example": "underlaypasses ['center', 'edge']",
        "completion": { "kind": "mode-list", "source": "underlaypasses", "quote": "'" },
        "isSnippet": true,
        "signatures": [["passes"]]
      }
    },
    {
      "id": "underlaylen",
      "label": "underlaylen",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "summary": "Set center/edge running-stitch length and zigzag return-run length, in physical hoop millimetres. Range 0.4–12 mm. It tunes the current legacy pass selection unless `underlaypasses` supplies an explicit order.",
      "editor": {
        "kind": "function",
        "detail": "satin underlay running length (mm)",
        "documentation": "Set center/edge running-stitch length and zigzag return-run length, in physical hoop millimetres. Range 0.4–12 mm. It tunes the current legacy pass selection unless `underlaypasses` supplies an explicit order.",
        "example": "underlaylen 2.8",
        "completion": { "kind": "text", "text": "underlaylen ${1:2.8}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "underlayinset",
      "label": "underlayinset",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "summary": "Set edge-pass inset inward from each topping rail, in physical hoop millimetres (0–10 mm). This command is deliberately absolute-only; ratio-based legacy settings are not overloaded into the same syntax. On a column narrower than twice the inset, the edge walks meet at the center and a warning is emitted.",
      "editor": {
        "kind": "function",
        "detail": "absolute satin edge-underlay inset (mm)",
        "documentation": "Set edge-pass inset inward from each topping rail, in physical hoop millimetres (0–10 mm). This command is deliberately absolute-only; ratio-based legacy settings are not overloaded into the same syntax. On a column narrower than twice the inset, the edge walks meet at the center and a warning is emitted.",
        "example": "underlayinset 0.6",
        "completion": { "kind": "text", "text": "underlayinset ${1:0.6}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "underlayspacing",
      "label": "underlayspacing",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "summary": "Set spacing along zigzag underlay passes in physical hoop millimetres. Range 0.25–5 mm. Zigzag width remains the unambiguous built-in 60% column-width ratio.",
      "editor": {
        "kind": "function",
        "detail": "satin underlay zigzag spacing (mm)",
        "documentation": "Set spacing along zigzag underlay passes in physical hoop millimetres. Range 0.25–5 mm. Zigzag width remains the unambiguous built-in 60% column-width ratio.",
        "example": "underlayspacing 1.8",
        "completion": { "kind": "text", "text": "underlayspacing ${1:2}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "fillunderlay",
      "label": "fillunderlay",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "summary": "Underlay beneath fills.",
      "editor": {
        "kind": "function",
        "detail": "fill underlay style",
        "documentation": "Underlay beneath fills.\n\n- `\"auto` — tatami, plus edge run on areas > 100 mm²\n- `\"tatami` — sparse cross-grain pass\n- `\"edge` — inset edge run only\n- `\"off` — no underlay",
        "example": "fillunderlay 'auto'",
        "completion": { "kind": "modes", "source": "fillunderlay", "quote": "\"" },
        "isSnippet": true,
        "signatures": [["mode"]]
      }
    },
    {
      "id": "fillunderlaypasses",
      "label": "fillunderlaypasses",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "stitching"],
      "summary": "Set the exact ordered passes generated from each recorded fill region. Accepted pass names are `'edge'` and `'tatami'`; duplicates repeat and an empty list disables underlay. Explicit order supersedes `fillunderlay 'auto'` and fabric doubling. Custom path fills still generate these passes from the recorded compound region, not from returned decorative paths.",
      "editor": {
        "kind": "function",
        "detail": "ordered fill underlay passes",
        "documentation": "Set the exact ordered passes generated from each recorded fill region. Accepted pass names are `'edge'` and `'tatami'`; duplicates repeat and an empty list disables underlay. Explicit order supersedes `fillunderlay 'auto'` and fabric doubling. Custom path fills still generate these passes from the recorded compound region, not from returned decorative paths.\n\n```\nfillunderlaypasses ['edge', 'tatami']\nfillunderlaylen 3\nfillunderlayinset 0.8\nfillunderlayspacing 2.2\nfillunderlayangle 90\n```",
        "example": "fillunderlaypasses ['edge', 'tatami']",
        "completion": { "kind": "mode-list", "source": "fillunderlaypasses", "quote": "'" },
        "isSnippet": true,
        "signatures": [["passes"]]
      }
    },
    {
      "id": "fillunderlaylen",
      "label": "fillunderlaylen",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "summary": "Set edge-walk and tatami-underlay stitch length in physical hoop millimetres. Range 1–7 mm. It tunes the selected legacy passes unless `fillunderlaypasses` supplies an explicit order.",
      "editor": {
        "kind": "function",
        "detail": "fill underlay stitch length (mm)",
        "documentation": "Set edge-walk and tatami-underlay stitch length in physical hoop millimetres. Range 1–7 mm. It tunes the selected legacy passes unless `fillunderlaypasses` supplies an explicit order.",
        "example": "fillunderlaylen 3",
        "completion": { "kind": "text", "text": "fillunderlaylen ${1:3}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "fillunderlayinset",
      "label": "fillunderlayinset",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "summary": "Set the inward physical inset for edge and tatami fill-underlay passes. Range 0–10 mm. Custom edge passes use a compound even-odd inset, preserving holes, concavities, and disconnected components.",
      "editor": {
        "kind": "function",
        "detail": "fill underlay inset (mm)",
        "documentation": "Set the inward physical inset for edge and tatami fill-underlay passes. Range 0–10 mm. Custom edge passes use a compound even-odd inset, preserving holes, concavities, and disconnected components.",
        "example": "fillunderlayinset 0.8",
        "completion": { "kind": "text", "text": "fillunderlayinset ${1:0.8}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "fillunderlayspacing",
      "label": "fillunderlayspacing",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "millimetres", "stitching"],
      "summary": "Set tatami-underlay row spacing in physical hoop millimetres. Range 0.25–5 mm. Edge passes are unaffected.",
      "editor": {
        "kind": "function",
        "detail": "fill underlay row spacing (mm)",
        "documentation": "Set tatami-underlay row spacing in physical hoop millimetres. Range 0.25–5 mm. Edge passes are unaffected.",
        "example": "fillunderlayspacing 2.2",
        "completion": { "kind": "text", "text": "fillunderlayspacing ${1:2.2}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "fillunderlayangle",
      "label": "fillunderlayangle",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "heading", "stitching"],
      "summary": "Set the tatami-underlay angle relative to the topping direction. Plain fills use `fillangle + offset`; directional fills rotate the local direction field by the same offset before mapping it to hoop space. Any finite degree value is accepted.",
      "editor": {
        "kind": "function",
        "detail": "fill underlay relative angle (degrees)",
        "documentation": "Set the tatami-underlay angle relative to the topping direction. Plain fills use `fillangle + offset`; directional fills rotate the local direction field by the same offset before mapping it to hoop space. Any finite degree value is accepted.",
        "example": "fillunderlayangle 90",
        "completion": { "kind": "text", "text": "fillunderlayangle ${1:90}" },
        "isSnippet": true,
        "signatures": [["degrees"]]
      }
    },
    {
      "id": "seed",
      "label": "seed",
      "category": "stitching",
      "tags": ["core", "function", "seeded", "stitching"],
      "summary": "Reseed the random number generator (default 42). Same seed → same design.",
      "editor": {
        "kind": "function",
        "detail": "reseed the RNG",
        "documentation": "Reseed the random number generator (default 42). Same seed → same design.",
        "completion": { "kind": "text", "text": "seed ${1:n}" },
        "isSnippet": true,
        "signatures": [["n"]]
      }
    },
    {
      "id": "print",
      "label": "print",
      "category": "stitching",
      "tags": ["core", "function", "stitching"],
      "summary": "Log a value to the console. `print \"label expr` adds a label: `print \"radius r` → `radius: 1.5`",
      "editor": {
        "kind": "function",
        "detail": "log value to console",
        "documentation": "Log a value to the console. `print \"label expr` adds a label:\n`print \"radius r` → `radius: 1.5`",
        "completion": { "kind": "text", "text": "print ${1:value}" },
        "isSnippet": true,
        "signatures": [["value"]]
      }
    },
    {
      "id": "printloc",
      "label": "printloc",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "stitching"],
      "summary": "Log the current needle position to the console as `loc: [x, y]`.",
      "editor": {
        "kind": "function",
        "detail": "log needle position to console",
        "documentation": "Log the current needle position to the console as `loc: [x, y]`.\n\nCoordinates are in the **local (turtle) frame** — the same as `pos()`. Under a transform they reflect what the turtle \"thinks\", which is what you usually want when debugging motif logic.\n\n`printloc \"label` uses a custom label instead of `loc`.\n\nDraw cost: 0. Never exported.\n\n```\nfd 20  rt 45  fd 10\nprintloc \"after-elbow\n// prints: after-elbow: [7.07, 27.07]\n```",
        "completion": { "kind": "text", "text": "printloc" },
        "signatures": [[]]
      }
    },
    {
      "id": "mark",
      "label": "mark",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "stitching"],
      "summary": "Drop a numbered pin on the preview at the needle position. Optional string label shown instead of the pin number.",
      "editor": {
        "kind": "function",
        "detail": "drop debug pin on stage",
        "documentation": "Drop a numbered pin on the preview at the needle position. Optional string label shown instead of the pin number.\n\n```\nmark         // numbered pin\nmark 'rose'  // labelled pin\n```\n\nNever exported to the machine or counted in stats.",
        "completion": { "kind": "text", "text": "mark" },
        "signatures": [[], ["label"]]
      }
    },
    {
      "id": "chalk",
      "label": "chalk",
      "category": "stitching",
      "tags": ["core", "embroidery", "function", "geometry", "stitching"],
      "summary": "Draw a point, path, or group of paths as a removable tailor's-chalk guide on the preview. It does not sew, move the needle, consume random draws, affect coverage, or enter machine exports.",
      "editor": {
        "kind": "function",
        "detail": "preview path data without sewing",
        "documentation": "Draw a point, path, or group of paths as a removable tailor's-chalk guide on the preview. It does not sew, move the needle, consume random draws, affect coverage, or enter machine exports.\n\n```\nchalk points\nchalk spine 'satin guide'\nchalk seeds 'layout' 'dots'\n```\n\nStyles: `'auto'`, `'dots'`, `'line'`.",
        "completion": { "kind": "text", "text": "chalk ${1:value} '${2:label}'" },
        "isSnippet": true,
        "signatures": [["value", "label", "style"]]
      }
    },
    {
      "id": "assert",
      "label": "assert",
      "category": "stitching",
      "tags": ["core", "function", "stitching"],
      "summary": "Stop with an error (and line number) if the condition is false.",
      "editor": {
        "kind": "function",
        "detail": "assertion check",
        "documentation": "Stop with an error (and line number) if the condition is false.\n\nExample: `assert (distance 0 0) < 47`",
        "completion": { "kind": "text", "text": "assert ${1:condition}" },
        "isSnippet": true,
        "signatures": [["condition"]]
      }
    },
    {
      "id": "random",
      "label": "random",
      "category": "math",
      "tags": ["call-syntax", "function", "library", "math", "seeded"],
      "summary": "Seeded random number in 0…n. Reproducible — driven by `seed`.",
      "editor": {
        "kind": "function",
        "detail": "seeded random in 0…max",
        "documentation": "Seeded random number in 0…n. Reproducible — driven by `seed`.",
        "completion": { "kind": "text", "text": "random(${1:max})" },
        "isSnippet": true,
        "signatures": [["max"]]
      }
    },
    {
      "id": "sin",
      "label": "sin",
      "category": "math",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "heading", "library", "math"],
      "summary": "Sine of an angle in degrees. Returns a value in −1…1 that rises to 1 at 90°, falls back to 0 at 180°, reaches −1 at 270°, and completes the cycle at 360°. Multiply by the amplitude you need.",
      "editor": {
        "kind": "function",
        "detail": "sine (degrees)",
        "documentation": "Sine of an angle in degrees. Returns a value in −1…1 that rises to 1 at 90°, falls back to 0 at 180°, reaches −1 at 270°, and completes the cycle at 360°. Multiply by the amplitude you need.\n\nIn embroidery: produces widths or offsets that wave along a path. Combine with `cos` to trace circular arcs or orbiting motifs.\n\n```\n// Oscillating satin width — pulses wide and narrow along the column\ndef wave(t, s, i, u) [\n  return satinpair(0.4, 1.5 + sin(s * 360) * 1.0)\n]\nsatin @wave  fd 60\n```",
        "completion": { "kind": "text", "text": "sin(${1:degrees})" },
        "isSnippet": true,
        "signatures": [["degrees"]]
      }
    },
    {
      "id": "cos",
      "label": "cos",
      "category": "math",
      "tags": ["call-syntax", "function", "geometry", "heading", "library", "math"],
      "summary": "Cosine of an angle in degrees. Identical to `sin` but shifted 90° — `cos(0)` is 1 (peak) while `sin(0)` is 0. Returns a value in −1…1.",
      "editor": {
        "kind": "function",
        "detail": "cosine (degrees)",
        "documentation": "Cosine of an angle in degrees. Identical to `sin` but shifted 90° — `cos(0)` is 1 (peak) while `sin(0)` is 0. Returns a value in −1…1.\n\nPair with `sin` to trace circular paths: `setxy r*sin(a), r*cos(a)` steps around a circle of radius `r` as `a` runs 0…360.\n\n```\n// Draw a circle step-by-step using sin and cos\nup\nrepeat 36 [\n  setxy 20 * sin(repcount * 10), 20 * cos(repcount * 10)\n  down\n]\n```",
        "completion": { "kind": "text", "text": "cos(${1:degrees})" },
        "isSnippet": true,
        "signatures": [["degrees"]]
      }
    },
    {
      "id": "sqrt",
      "label": "sqrt",
      "category": "math",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "library", "math"],
      "summary": "Square root — the inverse of squaring. The most common use in generative embroidery is computing Euclidean distance: `sqrt(dx*dx + dy*dy)` gives the length of a line segment. Negative input is a runtime error. For distances between stored points, `vdist` is usually simpler.",
      "editor": {
        "kind": "function",
        "detail": "square root",
        "documentation": "Square root — the inverse of squaring. The most common use in generative embroidery is computing Euclidean distance: `sqrt(dx*dx + dy*dy)` gives the length of a line segment. Negative input is a runtime error. For distances between stored points, `vdist` is usually simpler.\n\n```\nlet dx = xcor    let dy = ycor\nlet d = sqrt(dx*dx + dy*dy)  // distance from needle to origin\n// equivalent to: distance(0, 0)\n```",
        "completion": { "kind": "text", "text": "sqrt(${1:n})" },
        "isSnippet": true,
        "signatures": [["n"]]
      }
    },
    {
      "id": "abs",
      "label": "abs",
      "category": "math",
      "tags": ["call-syntax", "embroidery", "function", "library", "math"],
      "summary": "Strips the sign from a number — `abs(-3)` and `abs(3)` both return 3. Use it when you need a magnitude regardless of direction, such as mirroring a left/right offset or ensuring a width is never negative.",
      "editor": {
        "kind": "function",
        "detail": "absolute value",
        "documentation": "Strips the sign from a number — `abs(-3)` and `abs(3)` both return 3. Use it when you need a magnitude regardless of direction, such as mirroring a left/right offset or ensuring a width is never negative.\n\n```\n// Satin width grows with distance from centre, symmetrically left and right\ndef mirror_taper(t, s, i, u) [\n  return satinpair(0.4, abs(s - 0.5) * 4 + 0.5)\n]\n```",
        "completion": { "kind": "text", "text": "abs(${1:n})" },
        "isSnippet": true,
        "signatures": [["n"]]
      }
    },
    {
      "id": "round",
      "label": "round",
      "category": "math",
      "tags": ["call-syntax", "function", "library", "math"],
      "summary": "Round to the nearest integer. `round(2.7)` → 3, `round(2.3)` → 2. Halfway values round away from zero: `round(2.5)` → 3.",
      "editor": {
        "kind": "function",
        "detail": "round to nearest integer",
        "documentation": "Round to the nearest integer. `round(2.7)` → 3, `round(2.3)` → 2. Halfway values round away from zero: `round(2.5)` → 3.\n\nUseful for snapping a count or index to a whole number before using it in `repeat` or as a list subscript.",
        "completion": { "kind": "text", "text": "round(${1:n})" },
        "isSnippet": true,
        "signatures": [["n"]]
      }
    },
    {
      "id": "floor",
      "label": "floor",
      "category": "math",
      "tags": ["call-syntax", "function", "library", "math"],
      "summary": "Round down toward negative infinity — always the integer at or below the value. `floor(2.9)` → 2, `floor(-2.1)` → -3.",
      "editor": {
        "kind": "function",
        "detail": "round down (floor)",
        "documentation": "Round down toward negative infinity — always the integer at or below the value. `floor(2.9)` → 2, `floor(-2.1)` → -3.\n\nUse it for grid snapping (\"which column does this x fall in?\") or to produce a 0-based index from a continuous value: `floor(t / cellSize)`.",
        "completion": { "kind": "text", "text": "floor(${1:n})" },
        "isSnippet": true,
        "signatures": [["n"]]
      }
    },
    {
      "id": "ceil",
      "label": "ceil",
      "category": "math",
      "tags": ["call-syntax", "embroidery", "function", "library", "math"],
      "summary": "Round up toward positive infinity — always the integer at or above the value. `ceil(2.1)` → 3, `ceil(-2.9)` → -2.",
      "editor": {
        "kind": "function",
        "detail": "round up (ceiling)",
        "documentation": "Round up toward positive infinity — always the integer at or above the value. `ceil(2.1)` → 3, `ceil(-2.9)` → -2.\n\nUse it when you need a count that is guaranteed to cover a range: \"how many stitches of length `l` fit in distance `d`?\" → `ceil(d / l)`.",
        "completion": { "kind": "text", "text": "ceil(${1:n})" },
        "isSnippet": true,
        "signatures": [["n"]]
      }
    },
    {
      "id": "mod",
      "label": "mod",
      "category": "math",
      "tags": ["call-syntax", "function", "library", "math"],
      "summary": "Floor modulo — result always has the sign of b. `mod(-7, 3)` is 2, not −1. The `%` operator is the same operation.",
      "editor": {
        "kind": "function",
        "detail": "floor modulo",
        "documentation": "Floor modulo — result always has the sign of b. `mod(-7, 3)` is 2, not −1. The `%` operator is the same operation.",
        "completion": { "kind": "text", "text": "mod(${1:a}, ${2:b})" },
        "isSnippet": true,
        "signatures": [["a", "b"]]
      }
    },
    {
      "id": "min",
      "label": "min",
      "category": "math",
      "tags": ["call-syntax", "function", "library", "math"],
      "summary": "Minimum of a and b.",
      "editor": {
        "kind": "function",
        "detail": "minimum of two numbers",
        "documentation": "Minimum of a and b.",
        "completion": { "kind": "text", "text": "min(${1:a}, ${2:b})" },
        "isSnippet": true,
        "signatures": [["a", "b"]]
      }
    },
    {
      "id": "max",
      "label": "max",
      "category": "math",
      "tags": ["call-syntax", "function", "library", "math"],
      "summary": "Maximum of a and b.",
      "editor": {
        "kind": "function",
        "detail": "maximum of two numbers",
        "documentation": "Maximum of a and b.",
        "completion": { "kind": "text", "text": "max(${1:a}, ${2:b})" },
        "isSnippet": true,
        "signatures": [["a", "b"]]
      }
    },
    {
      "id": "pow",
      "label": "pow",
      "category": "math",
      "tags": ["call-syntax", "function", "library", "math"],
      "summary": "base raised to the exp. Non-finite result is a runtime error.",
      "editor": {
        "kind": "function",
        "detail": "raise to a power",
        "documentation": "base raised to the exp. Non-finite result is a runtime error.",
        "completion": { "kind": "text", "text": "pow(${1:base}, ${2:exp})" },
        "isSnippet": true,
        "signatures": [["base", "exp"]]
      }
    },
    {
      "id": "log",
      "label": "log",
      "category": "math",
      "tags": ["call-syntax", "function", "library", "math"],
      "summary": "Natural logarithm (base e) — the inverse of exponential growth. `log(1)` is 0 and `log(pow(e, x))` is x, where `e` is approximately 2.71828. Input must be positive; zero or a negative number is a runtime error. For another base, use `log(x) / log(base)`.",
      "editor": {
        "kind": "function",
        "detail": "natural logarithm",
        "documentation": "Natural logarithm (base e) — the inverse of exponential growth. `log(1)` is 0 and `log(pow(e, x))` is x, where `e` is approximately 2.71828. Input must be positive; zero or a negative number is a runtime error. For another base, use `log(x) / log(base)`.",
        "completion": { "kind": "text", "text": "log(${1:n})" },
        "isSnippet": true,
        "signatures": [["n"]]
      }
    },
    {
      "id": "atan",
      "label": "atan",
      "category": "math",
      "tags": ["call-syntax", "function", "heading", "library", "math"],
      "summary": "Heading of the vector (x, y) in turtle degrees: 0 = north, clockwise. `atan(1, 0)` is 90.",
      "editor": {
        "kind": "function",
        "detail": "heading of vector (x, y)",
        "documentation": "Heading of the vector (x, y) in turtle degrees: 0 = north, clockwise. `atan(1, 0)` is 90.",
        "completion": { "kind": "text", "text": "atan(${1:x}, ${2:y})" },
        "isSnippet": true,
        "signatures": [["x", "y"]]
      }
    },
    {
      "id": "noise",
      "label": "noise",
      "category": "math",
      "tags": ["call-syntax", "function", "library", "math", "seeded"],
      "summary": "Smooth seeded value noise in 0…1. Sample slowly (divide coordinates by 10–20) for organic drift.",
      "editor": {
        "kind": "function",
        "detail": "1D value noise (0…1)",
        "documentation": "Smooth seeded value noise in 0…1. Sample slowly (divide coordinates by 10–20) for organic drift.",
        "completion": { "kind": "text", "text": "noise(${1:x})" },
        "isSnippet": true,
        "signatures": [["x"]]
      }
    },
    {
      "id": "noise2",
      "label": "noise2",
      "category": "math",
      "tags": ["call-syntax", "function", "library", "math", "seeded"],
      "summary": "2D smooth seeded value noise in 0…1. Same seed → same field.",
      "editor": {
        "kind": "function",
        "detail": "2D value noise (0…1)",
        "documentation": "2D smooth seeded value noise in 0…1. Same seed → same field.",
        "completion": { "kind": "text", "text": "noise2(${1:x}, ${2:y})" },
        "isSnippet": true,
        "signatures": [["x", "y"]]
      }
    },
    {
      "id": "distance",
      "label": "distance",
      "category": "math",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "library", "math"],
      "summary": "Distance from the current needle position to the point (x, y).",
      "editor": {
        "kind": "function",
        "detail": "distance from needle to point",
        "documentation": "Distance from the current needle position to the point (x, y).",
        "completion": { "kind": "text", "text": "distance(${1:x}, ${2:y})" },
        "isSnippet": true,
        "signatures": [["x", "y"]]
      }
    },
    {
      "id": "towards",
      "label": "towards",
      "category": "math",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "heading", "library", "math"],
      "summary": "Heading from the needle to the point (x, y). `seth towards(0, 0)` aims home.",
      "editor": {
        "kind": "function",
        "detail": "heading from needle to point",
        "documentation": "Heading from the needle to the point (x, y). `seth towards(0, 0)` aims home.",
        "completion": { "kind": "text", "text": "towards(${1:x}, ${2:y})" },
        "isSnippet": true,
        "signatures": [["x", "y"]]
      }
    },
    {
      "id": "not",
      "label": "not",
      "category": "math",
      "tags": ["call-syntax", "function", "library", "math"],
      "summary": "Logical NOT. Also written `!`. Binds tightly — write `!(a = 1)` when negating a comparison.",
      "editor": {
        "kind": "function",
        "detail": "logical NOT",
        "documentation": "Logical NOT. Also written `!`. Binds tightly — write `!(a = 1)` when negating a comparison.",
        "completion": { "kind": "text", "text": "not(${1:value})" },
        "isSnippet": true,
        "signatures": [["value"]]
      }
    },
    {
      "id": "range",
      "label": "range",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists"],
      "summary": "`range(n)` → [0…n-1] `range(a, b)` → [a…b-1] `range(a, b, step)` → stepped",
      "editor": {
        "kind": "function",
        "detail": "create a range list",
        "documentation": "`range(n)` → [0…n-1]\n`range(a, b)` → [a…b-1]\n`range(a, b, step)` → stepped\n\n0-based, end-exclusive (like Python). Call-syntax only.",
        "completion": { "kind": "text", "text": "range(${1:n})" },
        "isSnippet": true,
        "signatures": [["n"], ["start", "end"], ["start", "end", "step"]]
      }
    },
    {
      "id": "filled",
      "label": "filled",
      "category": "lists",
      "tags": ["call-syntax", "embroidery", "function", "library", "lists", "millimetres"],
      "summary": "Create a new list containing `count` deep copies of `value`. Useful for initialising a collection of slots that you will fill in later with a loop.",
      "editor": {
        "kind": "function",
        "detail": "list of n copies of a value",
        "documentation": "Create a new list containing `count` deep copies of `value`. Useful for initialising a collection of slots that you will fill in later with a loop.\n\n```\n// Start with 10 widths all at 2.5 mm, then override some\nlet widths = filled(10, 2.5)\nwidths[2] = 1.0\nwidths[7] = 3.8\n```",
        "completion": { "kind": "text", "text": "filled(${1:count}, ${2:value})" },
        "isSnippet": true,
        "signatures": [["count", "value"]]
      }
    },
    {
      "id": "len",
      "label": "len",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists"],
      "summary": "Element count of a list, or character count of a string.",
      "editor": {
        "kind": "function",
        "detail": "list or string length",
        "documentation": "Element count of a list, or character count of a string.",
        "completion": { "kind": "text", "text": "len(${1:xs})" },
        "isSnippet": true,
        "signatures": [["xs"]]
      }
    },
    {
      "id": "islist",
      "label": "islist",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists"],
      "summary": "1 if the value is a list, 0 otherwise.",
      "editor": {
        "kind": "function",
        "detail": "1 if value is a list",
        "documentation": "1 if the value is a list, 0 otherwise.",
        "completion": { "kind": "text", "text": "islist(${1:value})" },
        "isSnippet": true,
        "signatures": [["value"]]
      }
    },
    {
      "id": "first",
      "label": "first",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists"],
      "summary": "Returns the first element of a list (same as `xs[0]`).",
      "editor": {
        "kind": "function",
        "detail": "first element (xs[0])",
        "documentation": "Returns the first element of a list (same as `xs[0]`).",
        "completion": { "kind": "text", "text": "first(${1:list})" },
        "isSnippet": true,
        "signatures": [["list"]]
      }
    },
    {
      "id": "last",
      "label": "last",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists"],
      "summary": "Returns the last element of a list (same as `xs[-1]`).",
      "editor": {
        "kind": "function",
        "detail": "last element (xs[-1])",
        "documentation": "Returns the last element of a list (same as `xs[-1]`).",
        "completion": { "kind": "text", "text": "last(${1:list})" },
        "isSnippet": true,
        "signatures": [["list"]]
      }
    },
    {
      "id": "concat",
      "label": "concat",
      "category": "lists",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "library", "lists", "stateful"],
      "summary": "Join two lists end-to-end, returning a new combined list. The elements are shared references (shallow copy) — mutating a nested list in the result also mutates the original. Use `copy` if you need full independence.",
      "editor": {
        "kind": "function",
        "detail": "concatenate two lists",
        "documentation": "Join two lists end-to-end, returning a new combined list. The elements are shared references (shallow copy) — mutating a nested list in the result also mutates the original. Use `copy` if you need full independence.\n\n```\n// Combine two traced paths into one continuous route\nlet full = concat(first_half, second_half)\nsewpath(resample(full, 2))\n```",
        "completion": { "kind": "text", "text": "concat(${1:a}, ${2:b})" },
        "isSnippet": true,
        "signatures": [["a", "b"]]
      }
    },
    {
      "id": "slice",
      "label": "slice",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists"],
      "summary": "`slice(xs, start)` or `slice(xs, start, end)` — new list, Python semantics including negative bounds, clamped.",
      "editor": {
        "kind": "function",
        "detail": "slice a list",
        "documentation": "`slice(xs, start)` or `slice(xs, start, end)` — new list, Python semantics including negative bounds, clamped.",
        "completion": { "kind": "text", "text": "slice(${1:list}, ${2:start})" },
        "isSnippet": true,
        "signatures": [
          ["list", "start"],
          ["list", "start", "end"]
        ]
      }
    },
    {
      "id": "reverse",
      "label": "reverse",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists", "pure", "stateful"],
      "summary": "Returns a new reversed list (pure — does not mutate the original).",
      "editor": {
        "kind": "function",
        "detail": "reversed list (pure)",
        "documentation": "Returns a new reversed list (pure — does not mutate the original).",
        "completion": { "kind": "text", "text": "reverse(${1:list})" },
        "isSnippet": true,
        "signatures": [["list"]]
      }
    },
    {
      "id": "sort",
      "label": "sort",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists", "pure", "stateful"],
      "summary": "Returns a new sorted list. Numbers only, ascending, stable. Pure — does not mutate.",
      "editor": {
        "kind": "function",
        "detail": "sorted list (pure, ascending)",
        "documentation": "Returns a new sorted list. Numbers only, ascending, stable. Pure — does not mutate.",
        "completion": { "kind": "text", "text": "sort(${1:list})" },
        "isSnippet": true,
        "signatures": [["list"]]
      }
    },
    {
      "id": "copy",
      "label": "copy",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists"],
      "summary": "Deep copy — fully independent of the original.",
      "editor": {
        "kind": "function",
        "detail": "deep copy of a list",
        "documentation": "Deep copy — fully independent of the original.",
        "completion": { "kind": "text", "text": "copy(${1:list})" },
        "isSnippet": true,
        "signatures": [["list"]]
      }
    },
    {
      "id": "indexof",
      "label": "indexof",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists"],
      "summary": "First index of v (deep tolerant compare) or −1 if not found.",
      "editor": {
        "kind": "function",
        "detail": "first index of value (or -1)",
        "documentation": "First index of v (deep tolerant compare) or −1 if not found.",
        "completion": { "kind": "text", "text": "indexof(${1:list}, ${2:value})" },
        "isSnippet": true,
        "signatures": [["list", "value"]]
      }
    },
    {
      "id": "contains",
      "label": "contains",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists"],
      "summary": "1 if the list contains v (deep tolerant compare), 0 otherwise.",
      "editor": {
        "kind": "function",
        "detail": "1 if list contains value",
        "documentation": "1 if the list contains v (deep tolerant compare), 0 otherwise.",
        "completion": { "kind": "text", "text": "contains(${1:list}, ${2:value})" },
        "isSnippet": true,
        "signatures": [["list", "value"]]
      }
    },
    {
      "id": "sum",
      "label": "sum",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists"],
      "summary": "Sum of all elements. `sum([])` is 0.",
      "editor": {
        "kind": "function",
        "detail": "sum of list elements",
        "documentation": "Sum of all elements. `sum([])` is 0.",
        "completion": { "kind": "text", "text": "sum(${1:list})" },
        "isSnippet": true,
        "signatures": [["list"]]
      }
    },
    {
      "id": "mean",
      "label": "mean",
      "category": "lists",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "library", "lists"],
      "summary": "Arithmetic mean (average) of all elements in the list. Equivalent to `sum(xs) / len(xs)`. Errors on an empty list.",
      "editor": {
        "kind": "function",
        "detail": "mean of list elements",
        "documentation": "Arithmetic mean (average) of all elements in the list. Equivalent to `sum(xs) / len(xs)`. Errors on an empty list.\n\n```\n// Centre the needle on the average position of a point set\nlet xs = map(pts, @first)\nlet ys = map(pts, @last)\nmoveto mean(xs), mean(ys)\n```",
        "completion": { "kind": "text", "text": "mean(${1:list})" },
        "isSnippet": true,
        "signatures": [["list"]]
      }
    },
    {
      "id": "minof",
      "label": "minof",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists"],
      "summary": "Smallest value in a list. Errors on an empty list. Often paired with `maxof` to find the full data range before remapping or normalising.",
      "editor": {
        "kind": "function",
        "detail": "minimum element",
        "documentation": "Smallest value in a list. Errors on an empty list. Often paired with `maxof` to find the full data range before remapping or normalising.\n\n```\nlet lo = minof(widths)\nlet hi = maxof(widths)\n// Normalise each width to 0..1\nfor w in widths [\n  print remap(w, lo, hi, 0, 1)\n]\n```",
        "completion": { "kind": "text", "text": "minof(${1:list})" },
        "isSnippet": true,
        "signatures": [["list"]]
      }
    },
    {
      "id": "maxof",
      "label": "maxof",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists", "millimetres"],
      "summary": "Largest value in a list. Errors on an empty list. Often paired with `minof` to find the full data range.",
      "editor": {
        "kind": "function",
        "detail": "maximum element",
        "documentation": "Largest value in a list. Errors on an empty list. Often paired with `minof` to find the full data range.\n\n```\nlet hi = maxof(distances)\n// Scale all distances to fit inside a 40 mm circle\nfor d in distances [\n  print d / hi * 20\n]\n```",
        "completion": { "kind": "text", "text": "maxof(${1:list})" },
        "isSnippet": true,
        "signatures": [["list"]]
      }
    },
    {
      "id": "pick",
      "label": "pick",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists", "seeded"],
      "summary": "Returns a random element — seeded, exactly one RNG draw.",
      "editor": {
        "kind": "function",
        "detail": "random element from list",
        "documentation": "Returns a random element — seeded, exactly one RNG draw.",
        "completion": { "kind": "text", "text": "pick(${1:list})" },
        "isSnippet": true,
        "signatures": [["list"]]
      }
    },
    {
      "id": "shuffle",
      "label": "shuffle",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists", "pure", "seeded", "stateful"],
      "summary": "Returns a new shuffled list — seeded, forks a child RNG. Pure — does not mutate.",
      "editor": {
        "kind": "function",
        "detail": "shuffled list (pure)",
        "documentation": "Returns a new shuffled list — seeded, forks a child RNG. Pure — does not mutate.",
        "completion": { "kind": "text", "text": "shuffle(${1:list})" },
        "isSnippet": true,
        "signatures": [["list"]]
      }
    },
    {
      "id": "pos",
      "label": "pos",
      "category": "lists",
      "tags": ["call-syntax", "embroidery", "function", "library", "lists"],
      "summary": "Needle position as `[xcor, ycor]`. Pair with `setpos(p)` to save and restore positions.",
      "editor": {
        "kind": "function",
        "detail": "needle position as [x, y]",
        "documentation": "Needle position as `[xcor, ycor]`. Pair with `setpos(p)` to save and restore positions.",
        "completion": { "kind": "text", "text": "pos()" },
        "signatures": [[]]
      }
    },
    {
      "id": "removeat",
      "label": "removeat",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists", "stateful"],
      "summary": "Mutates: removes element at index i and returns the removed value.",
      "editor": {
        "kind": "function",
        "detail": "remove and return element at index",
        "documentation": "Mutates: removes element at index i and returns the removed value.",
        "completion": { "kind": "text", "text": "removeat(${1:list}, ${2:index})" },
        "isSnippet": true,
        "signatures": [["list", "index"]]
      }
    },
    {
      "id": "append",
      "label": "append",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists", "stateful"],
      "summary": "Mutates: adds v at the end of the list.",
      "editor": {
        "kind": "function",
        "detail": "add value to end of list",
        "documentation": "Mutates: adds v at the end of the list.",
        "completion": { "kind": "text", "text": "append(${1:list}, ${2:value})" },
        "isSnippet": true,
        "signatures": [["list", "value"]]
      }
    },
    {
      "id": "prepend",
      "label": "prepend",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists", "stateful"],
      "summary": "Mutates: adds v at the front of the list.",
      "editor": {
        "kind": "function",
        "detail": "add value to start of list",
        "documentation": "Mutates: adds v at the front of the list.",
        "completion": { "kind": "text", "text": "prepend(${1:list}, ${2:value})" },
        "isSnippet": true,
        "signatures": [["list", "value"]]
      }
    },
    {
      "id": "insertat",
      "label": "insertat",
      "category": "lists",
      "tags": ["call-syntax", "function", "library", "lists", "stateful"],
      "summary": "Mutates: inserts v at index i (0 through len allowed).",
      "editor": {
        "kind": "function",
        "detail": "insert value at index",
        "documentation": "Mutates: inserts v at index i (0 through len allowed).",
        "completion": { "kind": "text", "text": "insertat(${1:list}, ${2:index}, ${3:value})" },
        "isSnippet": true,
        "signatures": [["list", "index", "value"]]
      }
    },
    {
      "id": "setpos",
      "label": "setpos",
      "category": "lists",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "library", "lists"],
      "summary": "Command: move needle to the point p (like `setxy p[0] p[1]`). Pair with `pos()`.",
      "editor": {
        "kind": "function",
        "detail": "move needle to [x, y] point",
        "documentation": "Command: move needle to the point p (like `setxy p[0] p[1]`). Pair with `pos()`.",
        "completion": { "kind": "text", "text": "setpos(${1:point})" },
        "isSnippet": true,
        "signatures": [["point"]]
      }
    },
    {
      "id": "steps",
      "label": "steps",
      "category": "higher-order",
      "tags": ["call-syntax", "function", "higher-order", "library"],
      "summary": "Generate a list of evenly spaced numbers from `start` to `end` (inclusive).",
      "editor": {
        "kind": "function",
        "detail": "inclusive numeric sequence",
        "documentation": "Generate a list of evenly spaced numbers from `start` to `end` (inclusive).\n\n`steps(0, 6)` → `[0, 1, 2, 3, 4, 5, 6]`\n\n`steps(0, 6, 0.2)` → `[0, 0.2, 0.4, …, 5.8, 6]`\n\nUnlike `range`, the end value is included when it falls exactly on a step boundary.",
        "completion": { "kind": "text", "text": "steps(${1:start}, ${2:end}, ${3:step})" },
        "isSnippet": true,
        "signatures": [
          ["start", "end"],
          ["start", "end", "step"]
        ]
      }
    },
    {
      "id": "map",
      "label": "map",
      "category": "higher-order",
      "tags": ["call-syntax", "function", "higher-order", "library"],
      "summary": "Return a new list by applying `@fn` to each element of `list`.",
      "editor": {
        "kind": "function",
        "detail": "apply function to every element",
        "documentation": "Return a new list by applying `@fn` to each element of `list`.\n\n```\ndef double(x) [ return x * 2 ]\nprint map([1, 2, 3], @double)  // [2, 4, 6]\n```\n\nThe callback can be a user-defined procedure (`@myProc`) or a built-in function (`@vlen`, `@abs`, …).",
        "completion": { "kind": "text", "text": "map(${1:list}, @${2:fn})" },
        "isSnippet": true,
        "signatures": [["list", "@fn"]]
      }
    },
    {
      "id": "filter",
      "label": "filter",
      "category": "higher-order",
      "tags": ["call-syntax", "function", "higher-order", "library"],
      "summary": "Return a new list keeping only elements for which `@fn` returns a truthy value.",
      "editor": {
        "kind": "function",
        "detail": "keep elements that pass a test",
        "documentation": "Return a new list keeping only elements for which `@fn` returns a truthy value.\n\n```\ndef big(x) [ return x > 2 ]\nprint filter([1, 2, 3, 4], @big)  // [3, 4]\n```",
        "completion": { "kind": "text", "text": "filter(${1:list}, @${2:fn})" },
        "isSnippet": true,
        "signatures": [["list", "@fn"]]
      }
    },
    {
      "id": "reduce",
      "label": "reduce",
      "category": "higher-order",
      "tags": ["call-syntax", "function", "geometry", "higher-order", "library"],
      "summary": "Fold `list` with `@fn(accumulator, element)` starting from `init`.",
      "editor": {
        "kind": "function",
        "detail": "fold list into a single value",
        "documentation": "Fold `list` with `@fn(accumulator, element)` starting from `init`.\n\n```\ndef add(a, b) [ return a + b ]\nprint reduce([1, 2, 3], @add, 0)  // 6\n```\n\nWorks with built-in functions too: `reduce(points, @vadd, [0, 0])`.",
        "completion": { "kind": "text", "text": "reduce(${1:list}, @${2:fn}, ${3:init})" },
        "isSnippet": true,
        "signatures": [["list", "@fn", "init"]]
      }
    },
    {
      "id": "compose",
      "label": "compose",
      "category": "higher-order",
      "tags": ["call-syntax", "function", "higher-order", "library"],
      "summary": "Create a left-to-right pipeline from two or more `@references`.",
      "editor": {
        "kind": "function",
        "detail": "compose functions into a pipeline",
        "documentation": "Create a left-to-right pipeline from two or more `@references`.\n\n`compose(@f, @g, @h)` returns a reference where `step(x) = h(g(f(x)))`.\n\n```\ndef double(x) [ return x * 2 ]\nlet step = compose(@double, @round)\nprint map([1.7, 2.3], step)  // [3, 5]\n```\n\nWorks with user procs, built-in refs, and nested composes.",
        "completion": { "kind": "text", "text": "compose(@${1:fn1}, @${2:fn2})" },
        "isSnippet": true,
        "signatures": [["@fn1", "@fn2", "..."]]
      }
    },
    {
      "id": "bind",
      "label": "bind",
      "category": "higher-order",
      "tags": ["call-syntax", "function", "higher-order", "library"],
      "summary": "Return a configured reference with one or more leading arguments fixed. Values are evaluated once; lists retain reference semantics.",
      "editor": {
        "kind": "function",
        "detail": "bind leading reference arguments",
        "documentation": "Return a configured reference with one or more leading arguments fixed. Values are evaluated once; lists retain reference semantics.\n\n```\ndef add(a, b) [ return a + b ]\nlet add10 = bind(@add, 10)\nprint add10(5)  // 15\n```",
        "completion": { "kind": "text", "text": "bind(@${1:fn}, ${2:value})" },
        "isSnippet": true,
        "signatures": [["@fn", "value", "..."]]
      }
    },
    {
      "id": "isref",
      "label": "isref",
      "category": "higher-order",
      "tags": ["call-syntax", "function", "higher-order", "library"],
      "summary": "Return 1 when the value is a plain, bound, composed, or capturing reference; otherwise 0.",
      "editor": {
        "kind": "function",
        "detail": "test for a reference value",
        "documentation": "Return 1 when the value is a plain, bound, composed, or capturing reference; otherwise 0.",
        "completion": { "kind": "text", "text": "isref(${1:value})" },
        "isSnippet": true,
        "signatures": [["value"]]
      }
    },
    {
      "id": "str",
      "label": "str",
      "category": "strings",
      "tags": ["call-syntax", "function", "library", "strings"],
      "summary": "Convert a number to its string representation (same as `print` shows). `str` of a string is identity.",
      "editor": {
        "kind": "function",
        "detail": "number → string",
        "documentation": "Convert a number to its string representation (same as `print` shows). `str` of a string is identity.",
        "completion": { "kind": "text", "text": "str(${1:n})" },
        "isSnippet": true,
        "signatures": [["n"]]
      }
    },
    {
      "id": "num",
      "label": "num",
      "category": "strings",
      "tags": ["call-syntax", "function", "library", "strings"],
      "summary": "Parse a numeric string. Errors on non-numeric input unless a fallback is given.",
      "editor": {
        "kind": "function",
        "detail": "string → number",
        "documentation": "Parse a numeric string. Errors on non-numeric input unless a fallback is given.\n\n```\nnum(\"3.14\")    // 3.14\nnum(\"bad\", 0)  // 0\n```",
        "completion": { "kind": "text", "text": "num(${1:s})" },
        "isSnippet": true,
        "signatures": [["s"], ["s", "fallback"]]
      }
    },
    {
      "id": "isstring",
      "label": "isstring",
      "category": "strings",
      "tags": ["call-syntax", "function", "library", "strings"],
      "summary": "1 if the value is a string, 0 otherwise. The sibling of `islist`.",
      "editor": {
        "kind": "function",
        "detail": "1 if value is a string",
        "documentation": "1 if the value is a string, 0 otherwise. The sibling of `islist`.",
        "completion": { "kind": "text", "text": "isstring(${1:value})" },
        "isSnippet": true,
        "signatures": [["value"]]
      }
    },
    {
      "id": "chars",
      "label": "chars",
      "category": "strings",
      "tags": ["call-syntax", "function", "library", "strings"],
      "summary": "Split a string into a list of 1-character strings. Bridge to the whole list toolkit.",
      "editor": {
        "kind": "function",
        "detail": "string → list of chars",
        "documentation": "Split a string into a list of 1-character strings. Bridge to the whole list toolkit.",
        "completion": { "kind": "text", "text": "chars(${1:s})" },
        "isSnippet": true,
        "signatures": [["s"]]
      }
    },
    {
      "id": "split",
      "label": "split",
      "category": "strings",
      "tags": ["call-syntax", "function", "library", "strings"],
      "summary": "Split `s` at every occurrence of `sep`. `sep` must be non-empty.",
      "editor": {
        "kind": "function",
        "detail": "split by separator",
        "documentation": "Split `s` at every occurrence of `sep`. `sep` must be non-empty.",
        "completion": { "kind": "text", "text": "split(${1:s}, '${2:,}')" },
        "isSnippet": true,
        "signatures": [["s", "sep"]]
      }
    },
    {
      "id": "joinstr",
      "label": "joinstr",
      "category": "strings",
      "tags": ["call-syntax", "function", "library", "strings"],
      "summary": "Concatenate a list of strings with `sep` between each. All elements must be strings.",
      "editor": {
        "kind": "function",
        "detail": "join string list",
        "documentation": "Concatenate a list of strings with `sep` between each. All elements must be strings.",
        "completion": { "kind": "text", "text": "joinstr(${1:xs}, '${2:,}')" },
        "isSnippet": true,
        "signatures": [["xs", "sep"]]
      }
    },
    {
      "id": "upper",
      "label": "upper",
      "category": "strings",
      "tags": ["call-syntax", "function", "library", "strings"],
      "summary": "Return a copy of `s` with ASCII letters uppercased (A–Z only).",
      "editor": {
        "kind": "function",
        "detail": "ASCII uppercase",
        "documentation": "Return a copy of `s` with ASCII letters uppercased (A–Z only).",
        "completion": { "kind": "text", "text": "upper(${1:s})" },
        "isSnippet": true,
        "signatures": [["s"]]
      }
    },
    {
      "id": "lower",
      "label": "lower",
      "category": "strings",
      "tags": ["call-syntax", "function", "library", "strings"],
      "summary": "Return a copy of `s` with ASCII letters lowercased (a–z only).",
      "editor": {
        "kind": "function",
        "detail": "ASCII lowercase",
        "documentation": "Return a copy of `s` with ASCII letters lowercased (a–z only).",
        "completion": { "kind": "text", "text": "lower(${1:s})" },
        "isSnippet": true,
        "signatures": [["s"]]
      }
    },
    {
      "id": "strip",
      "label": "strip",
      "category": "strings",
      "tags": ["call-syntax", "embroidery", "function", "library", "strings"],
      "summary": "Return `s` with leading and trailing whitespace (space, tab, newline) removed.",
      "editor": {
        "kind": "function",
        "detail": "trim whitespace",
        "documentation": "Return `s` with leading and trailing whitespace (space, tab, newline) removed.\n\n**Note:** `trim` cuts the thread — use `strip` for whitespace.",
        "completion": { "kind": "text", "text": "strip(${1:s})" },
        "isSnippet": true,
        "signatures": [["s"]]
      }
    },
    {
      "id": "repeatstr",
      "label": "repeatstr",
      "category": "strings",
      "tags": ["call-syntax", "function", "library", "strings"],
      "summary": "Return `s` repeated `n` times (n must be a non-negative integer).",
      "editor": {
        "kind": "function",
        "detail": "repeat a string n times",
        "documentation": "Return `s` repeated `n` times (n must be a non-negative integer).",
        "completion": { "kind": "text", "text": "repeatstr(${1:s}, ${2:n})" },
        "isSnippet": true,
        "signatures": [["s", "n"]]
      }
    },
    {
      "id": "lerp",
      "label": "lerp",
      "category": "generative-scalars",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "generative-scalars",
        "geometry",
        "library",
        "millimetres"
      ],
      "summary": "Blend smoothly between two values. Returns `a` when `t = 0`, `b` when `t = 1`, and the midpoint when `t = 0.5`. `t` is unclamped — values outside 0…1 extrapolate.",
      "editor": {
        "kind": "function",
        "detail": "linear interpolation",
        "documentation": "Blend smoothly between two values. Returns `a` when `t = 0`, `b` when `t = 1`, and the midpoint when `t = 0.5`. `t` is unclamped — values outside 0…1 extrapolate.\n\nThe classic tool for things that change gradually: tapering a satin column from wide at the base to thin at the tip, or easing a spacing as the needle moves along a path.\n\n```\n// Taper a satin column from 3 mm at the root to 0.5 mm at the tip\ndef taper(t, s, i, u) [\n  return satinpair(0.4, lerp(3, 0.5, s))\n]\nsatin @taper  fd 40\n```",
        "completion": { "kind": "text", "text": "lerp(${1:a}, ${2:b}, ${3:t})" },
        "isSnippet": true,
        "signatures": [["a", "b", "t"]]
      }
    },
    {
      "id": "remap",
      "label": "remap",
      "category": "generative-scalars",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "generative-scalars",
        "library",
        "millimetres"
      ],
      "summary": "Linearly rescale a value from one range to another — like converting between units. `remap(value, inMin, inMax, outMin, outMax)` maps `inMin → outMin` and `inMax → outMax`. Result is unclamped; use `clamp` around it if the input might exceed the source range.",
      "editor": {
        "kind": "function",
        "detail": "remap value between ranges",
        "documentation": "Linearly rescale a value from one range to another — like converting between units. `remap(value, inMin, inMax, outMin, outMax)` maps `inMin → outMin` and `inMax → outMax`. Result is unclamped; use `clamp` around it if the input might exceed the source range.\n\nCommon use: translate noise (which lives in 0…1 or −1…1) into a practical stitch width or spacing.\n\n```\n// noise2 returns 0…1; drive a satin width between 1.5 mm and 3.5 mm\ndef textured(t, s, i, u) [\n  let w = remap(noise2(t / 12, 0), 0, 1, 1.5, 3.5)\n  return satinpair(0.4, w)\n]\nsatin @textured  fd 40\n```",
        "completion": {
          "kind": "text",
          "text": "remap(${1:value}, ${2:inMin}, ${3:inMax}, ${4:outMin}, ${5:outMax})"
        },
        "isSnippet": true,
        "signatures": [["value", "inMin", "inMax", "outMin", "outMax"]]
      }
    },
    {
      "id": "clamp",
      "label": "clamp",
      "category": "generative-scalars",
      "tags": ["call-syntax", "embroidery", "function", "generative-scalars", "library"],
      "summary": "Constrain a value so it never falls below `min` or above `max`. Equivalent to `min(max(value, lo), hi)`. Use it when a calculation might produce negative lengths, out-of-range widths, or other implausible values.",
      "editor": {
        "kind": "function",
        "detail": "clamp value to [min, max]",
        "documentation": "Constrain a value so it never falls below `min` or above `max`. Equivalent to `min(max(value, lo), hi)`. Use it when a calculation might produce negative lengths, out-of-range widths, or other implausible values.\n\n```\n// Keep a noise-driven satin width inside a safe range\ndef safe(t, s, i, u) [\n  let w = noise2(t / 10, 0) * 5   // 0…5, but noise can spike\n  return satinpair(0.4, clamp(w, 0.5, 4))\n]\nsatin @safe  fd 40\n```",
        "completion": { "kind": "text", "text": "clamp(${1:value}, ${2:min}, ${3:max})" },
        "isSnippet": true,
        "signatures": [["value", "min", "max"]]
      }
    },
    {
      "id": "smoothstep",
      "label": "smoothstep",
      "category": "generative-scalars",
      "tags": ["call-syntax", "embroidery", "function", "generative-scalars", "library"],
      "summary": "S-curve transition: returns 0 when `x ≤ edge0`, 1 when `x ≥ edge1`, and a smooth ease-in/ease-out curve in between. The curve accelerates from 0 then decelerates into 1, so transitions look far more natural than a straight `lerp`.",
      "editor": {
        "kind": "function",
        "detail": "Hermite smooth ease (0…1)",
        "documentation": "S-curve transition: returns 0 when `x ≤ edge0`, 1 when `x ≥ edge1`, and a smooth ease-in/ease-out curve in between. The curve accelerates from 0 then decelerates into 1, so transitions look far more natural than a straight `lerp`.\n\nUse it for soft fade-ins at the start of a column, soft fade-outs at the end, or any width change that should feel gradual rather than mechanical.\n\n```\n// Fade a satin column up and back down — wide in the middle\ndef soft_taper(t, s, i, u) [\n  let fade = smoothstep(0, 0.2, s) * smoothstep(1, 0.8, s)\n  return satinpair(0.4, lerp(0.3, 3.5, fade))\n]\nsatin @soft_taper  fd 50\n```",
        "completion": { "kind": "text", "text": "smoothstep(${1:edge0}, ${2:edge1}, ${3:x})" },
        "isSnippet": true,
        "signatures": [["edge0", "edge1", "x"]]
      }
    },
    {
      "id": "gauss",
      "label": "gauss",
      "category": "generative-scalars",
      "tags": ["call-syntax", "function", "generative-scalars", "library", "seeded"],
      "summary": "Seeded normally-distributed random number centred on `mean` with spread `sigma`. Unlike `random` (uniform), most values land close to the mean — only occasionally straying far. The larger `sigma` is, the wider the spread.",
      "editor": {
        "kind": "function",
        "detail": "seeded Gaussian random",
        "documentation": "Seeded normally-distributed random number centred on `mean` with spread `sigma`. Unlike `random` (uniform), most values land close to the mean — only occasionally straying far. The larger `sigma` is, the wider the spread.\n\nExactly 2 RNG draws per call (Box-Muller method) — predictable cost for downstream reproducibility.\n\nGood for organic variation: scatter placement that clusters naturally, jitter that feels hand-made, or noise that emphasises the average rather than the extreme.\n\n```\n// Scatter stems that cluster naturally around the centre\nrepeat 24 [\n  moveto gauss(0, 10), gauss(0, 5)\n  down  fd 12  up  trim\n]\n```",
        "completion": { "kind": "text", "text": "gauss(${1:mean}, ${2:sigma})" },
        "isSnippet": true,
        "signatures": [["mean", "sigma"]]
      }
    },
    {
      "id": "snoise2",
      "label": "snoise2",
      "category": "generative-scalars",
      "tags": ["call-syntax", "function", "generative-scalars", "library", "seeded"],
      "summary": "Seeded simplex noise in −1…1 (industry convention). Slightly finer-grained than legacy `noise2` (0…1).",
      "editor": {
        "kind": "function",
        "detail": "2D simplex noise (−1…1)",
        "documentation": "Seeded simplex noise in −1…1 (industry convention). Slightly finer-grained than legacy `noise2` (0…1).",
        "completion": { "kind": "text", "text": "snoise2(${1:x}, ${2:y})" },
        "isSnippet": true,
        "signatures": [["x", "y"]]
      }
    },
    {
      "id": "snoise3",
      "label": "snoise3",
      "category": "generative-scalars",
      "tags": ["call-syntax", "function", "generative-scalars", "library", "seeded"],
      "summary": "Seeded 3D simplex noise in −1…1. Use z for variation: `snoise3(x/14, y/14, motif*50)` gives each motif its own noise field.",
      "editor": {
        "kind": "function",
        "detail": "3D simplex noise (−1…1)",
        "documentation": "Seeded 3D simplex noise in −1…1. Use z for variation: `snoise3(x/14, y/14, motif*50)` gives each motif its own noise field.",
        "completion": { "kind": "text", "text": "snoise3(${1:x}, ${2:y}, ${3:z})" },
        "isSnippet": true,
        "signatures": [["x", "y", "z"]]
      }
    },
    {
      "id": "fbm2",
      "label": "fbm2",
      "category": "generative-scalars",
      "tags": ["call-syntax", "embroidery", "function", "generative-scalars", "library", "seeded"],
      "summary": "Fractal Brownian motion — layers multiple octaves of `snoise2` at increasing frequencies and decreasing amplitudes. Each octave adds finer detail on top of the large-scale shape, producing a rich, cloud-like texture. Returns approximately −1…1.",
      "editor": {
        "kind": "function",
        "detail": "fractal Brownian motion",
        "documentation": "Fractal Brownian motion — layers multiple octaves of `snoise2` at increasing frequencies and decreasing amplitudes. Each octave adds finer detail on top of the large-scale shape, producing a rich, cloud-like texture. Returns approximately −1…1.\n\n- `octaves` controls how many detail layers are stacked. 1 = smooth (same as `snoise2`), 4–6 = rich and detailed, 8 = maximum.\n- `lacunarity` = 2.0 (each octave doubles in frequency)\n- `gain` = 0.5 (each octave halves in amplitude)\n\nSample at low spatial frequency: divide coordinates by 10–20 for broad organic drift.\n\n```\n// Flow-fill with richly textured noise direction\ndef noisy_dir(p) [\n  return fbm2(p[0] / 15, p[1] / 15, 5) * 45\n]\nfill dir @noisy_dir\nbeginfill\n  repeat 6 [ fd 30 rt 60 ]\nendfill\n```",
        "completion": { "kind": "text", "text": "fbm2(${1:x}, ${2:y}, ${3:octaves})" },
        "isSnippet": true,
        "signatures": [["x", "y", "octaves"]]
      }
    },
    {
      "id": "vadd",
      "label": "vadd",
      "category": "vectors",
      "tags": ["call-syntax", "function", "geometry", "library", "vectors"],
      "summary": "Add two 2D vectors (stored as `[x, y]` lists), returning a new point. Use it to offset a position by a direction or to accumulate steps.",
      "editor": {
        "kind": "function",
        "detail": "add two vectors",
        "documentation": "Add two 2D vectors (stored as `[x, y]` lists), returning a new point. Use it to offset a position by a direction or to accumulate steps.\n\n```\nlet p = [10, 5]\nlet nudge = [0, 3]\nsetpos vadd(p, nudge)   // moves to [10, 8]\n```",
        "completion": { "kind": "text", "text": "vadd(${1:a}, ${2:b})" },
        "isSnippet": true,
        "signatures": [["a", "b"]]
      }
    },
    {
      "id": "vsub",
      "label": "vsub",
      "category": "vectors",
      "tags": ["call-syntax", "function", "geometry", "heading", "library", "vectors"],
      "summary": "Subtract vector `b` from `a`, returning a new point `[a[0]-b[0], a[1]-b[1]]`. The result is also the displacement vector from `b` to `a` — useful for computing the direction between two stored positions before normalising with `vnorm`.",
      "editor": {
        "kind": "function",
        "detail": "subtract two vectors",
        "documentation": "Subtract vector `b` from `a`, returning a new point `[a[0]-b[0], a[1]-b[1]]`. The result is also the displacement vector from `b` to `a` — useful for computing the direction between two stored positions before normalising with `vnorm`.\n\n```\n// Direction from base to tip, then normalise to unit length\nlet dir = vnorm(vsub(tip, base))\nseth vheading(dir)   fd vdist(base, tip)\n```",
        "completion": { "kind": "text", "text": "vsub(${1:a}, ${2:b})" },
        "isSnippet": true,
        "signatures": [["a", "b"]]
      }
    },
    {
      "id": "vscale",
      "label": "vscale",
      "category": "vectors",
      "tags": ["call-syntax", "function", "geometry", "library", "millimetres", "vectors"],
      "summary": "Multiply both components of a vector by scalar `s`, returning a new point. Use it to extend or shorten a direction vector, or to resize an offset.",
      "editor": {
        "kind": "function",
        "detail": "scale a vector",
        "documentation": "Multiply both components of a vector by scalar `s`, returning a new point. Use it to extend or shorten a direction vector, or to resize an offset.\n\n```\n// Push a point 5 mm further away from the origin\nlet dir = vnorm(p)           // unit direction toward p\nlet pushed = vscale(dir, vlen(p) + 5)\n```",
        "completion": { "kind": "text", "text": "vscale(${1:vector}, ${2:scale})" },
        "isSnippet": true,
        "signatures": [["vector", "scale"]]
      }
    },
    {
      "id": "vlerp",
      "label": "vlerp",
      "category": "vectors",
      "tags": ["call-syntax", "function", "geometry", "library", "vectors"],
      "summary": "Interpolate between two 2D points — returns `a` at `t = 0`, `b` at `t = 1`. Works like `lerp` but for positions. Good for moving along a line segment, finding a midpoint, or distributing jump targets evenly between two anchor points.",
      "editor": {
        "kind": "function",
        "detail": "lerp between two vectors",
        "documentation": "Interpolate between two 2D points — returns `a` at `t = 0`, `b` at `t = 1`. Works like `lerp` but for positions. Good for moving along a line segment, finding a midpoint, or distributing jump targets evenly between two anchor points.\n\n```\n// Find the midpoint between two corners\nlet mid = vlerp(cornerA, cornerB, 0.5)\nmoveto mid[0], mid[1]\n```",
        "completion": { "kind": "text", "text": "vlerp(${1:a}, ${2:b}, ${3:t})" },
        "isSnippet": true,
        "signatures": [["a", "b", "t"]]
      }
    },
    {
      "id": "vdot",
      "label": "vdot",
      "category": "vectors",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "heading",
        "library",
        "vectors"
      ],
      "summary": "Dot product: `a[0]*b[0] + a[1]*b[1]`. Measures how much two vectors point in the same direction. Positive when they agree, 0 when perpendicular, negative when they oppose each other.",
      "editor": {
        "kind": "function",
        "detail": "dot product",
        "documentation": "Dot product: `a[0]*b[0] + a[1]*b[1]`. Measures how much two vectors point in the same direction. Positive when they agree, 0 when perpendicular, negative when they oppose each other.\n\nThe key projection tool: `vdot(v, dir)` (where `dir` is a unit vector) gives the signed distance of `v` along that direction.\n\n```\n// How far along the path has the needle advanced?\nlet fwd = vfromheading(heading, 1)         // forward unit vector\nlet advance = vdot(vsub(pos(), start), fwd)\n```",
        "completion": { "kind": "text", "text": "vdot(${1:a}, ${2:b})" },
        "isSnippet": true,
        "signatures": [["a", "b"]]
      }
    },
    {
      "id": "vlen",
      "label": "vlen",
      "category": "vectors",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "library", "vectors"],
      "summary": "Length (magnitude) of a vector: `sqrt(v[0]² + v[1]²)`. Returns the distance from the origin to the point, or the \"size\" of a direction vector. To measure between two stored points, use `vdist`.",
      "editor": {
        "kind": "function",
        "detail": "vector length (magnitude)",
        "documentation": "Length (magnitude) of a vector: `sqrt(v[0]² + v[1]²)`. Returns the distance from the origin to the point, or the \"size\" of a direction vector. To measure between two stored points, use `vdist`.\n\n```\n// Scale satin width proportional to distance from centre\nlet d = vlen(pos())   // distance from origin\nsatin clamp(d / 10, 0.5, 3.5)\n```",
        "completion": { "kind": "text", "text": "vlen(${1:vector})" },
        "isSnippet": true,
        "signatures": [["vector"]]
      }
    },
    {
      "id": "vdist",
      "label": "vdist",
      "category": "vectors",
      "tags": ["call-syntax", "function", "geometry", "library", "vectors"],
      "summary": "Euclidean distance between two `[x, y]` points. Equivalent to `vlen(vsub(b, a))` but more readable. Use whenever you need the gap between two stored positions (e.g. decide whether to trim, check spacing, scale a motif).",
      "editor": {
        "kind": "function",
        "detail": "distance between two points",
        "documentation": "Euclidean distance between two `[x, y]` points. Equivalent to `vlen(vsub(b, a))` but more readable. Use whenever you need the gap between two stored positions (e.g. decide whether to trim, check spacing, scale a motif).\n\n```\nif vdist(pos(), target) > 5 [\n  moveto target[0], target[1]\n]\n```",
        "completion": { "kind": "text", "text": "vdist(${1:a}, ${2:b})" },
        "isSnippet": true,
        "signatures": [["a", "b"]]
      }
    },
    {
      "id": "vnorm",
      "label": "vnorm",
      "category": "vectors",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "heading",
        "library",
        "millimetres",
        "pure",
        "vectors"
      ],
      "summary": "Returns a unit vector (length exactly 1.0) pointing in the same direction. Use it when you need a pure direction without caring about magnitude — then multiply by the length you want with `vscale`. The zero vector is a runtime error.",
      "editor": {
        "kind": "function",
        "detail": "normalize to unit vector",
        "documentation": "Returns a unit vector (length exactly 1.0) pointing in the same direction. Use it when you need a pure direction without caring about magnitude — then multiply by the length you want with `vscale`. The zero vector is a runtime error.\n\n```\n// Aim the needle toward a target, then sew 10 mm that way\nlet dir = vnorm(vsub(target, pos()))\nseth vheading(dir)\nfd 10\n```",
        "completion": { "kind": "text", "text": "vnorm(${1:vector})" },
        "isSnippet": true,
        "signatures": [["vector"]]
      }
    },
    {
      "id": "vrot",
      "label": "vrot",
      "category": "vectors",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "heading",
        "library",
        "millimetres",
        "vectors"
      ],
      "summary": "Rotate a vector clockwise by `deg` degrees. The rotation matches NeedleScript's turtle convention (clockwise positive, 0 = north). Use it to create perpendicular offsets, fan spread patterns, or to generate N evenly-rotated copies of a direction.",
      "editor": {
        "kind": "function",
        "detail": "rotate a vector (clockwise)",
        "documentation": "Rotate a vector clockwise by `deg` degrees. The rotation matches NeedleScript's turtle convention (clockwise positive, 0 = north). Use it to create perpendicular offsets, fan spread patterns, or to generate N evenly-rotated copies of a direction.\n\n```\n// Place 6 radial motifs evenly around the centre\nlet base = [0, 20]    // 20 mm north\nrepeat 6 [\n  let p = vrot(base, (repcount - 1) * 60)\n  moveto p[0], p[1]   fd 8   trim\n]\n```",
        "completion": { "kind": "text", "text": "vrot(${1:vector}, ${2:degrees})" },
        "isSnippet": true,
        "signatures": [["vector", "degrees"]]
      }
    },
    {
      "id": "vheading",
      "label": "vheading",
      "category": "vectors",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "heading",
        "library",
        "vectors"
      ],
      "summary": "Convert a 2D vector to a turtle heading in degrees (0 = north, clockwise positive). Equivalent to `atan(v[0], v[1])`. Use it with `seth` to aim the needle along a computed direction or path tangent.",
      "editor": {
        "kind": "function",
        "detail": "turtle heading of a vector",
        "documentation": "Convert a 2D vector to a turtle heading in degrees (0 = north, clockwise positive). Equivalent to `atan(v[0], v[1])`. Use it with `seth` to aim the needle along a computed direction or path tangent.\n\n```\n// Aim along the tangent of a stored path segment\nlet tangent = vsub(path[i + 1], path[i])\nseth vheading(tangent)\n```",
        "completion": { "kind": "text", "text": "vheading(${1:vector})" },
        "isSnippet": true,
        "signatures": [["vector"]]
      }
    },
    {
      "id": "vfromheading",
      "label": "vfromheading",
      "category": "vectors",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "heading",
        "library",
        "millimetres",
        "vectors"
      ],
      "summary": "Make a 2D vector of the given `length` pointing in turtle heading `deg`. The inverse of `vheading`. Use it to compute offsets in any direction relative to the needle's current path.",
      "editor": {
        "kind": "function",
        "detail": "vector from heading + length",
        "documentation": "Make a 2D vector of the given `length` pointing in turtle heading `deg`. The inverse of `vheading`. Use it to compute offsets in any direction relative to the needle's current path.\n\n```\n// Step 5 mm to the right of the current heading\nlet sideways = vfromheading(heading + 90, 5)\nsetpos vadd(pos(), sideways)\n```\n\n`vfromheading(heading, 1)` gives the unit forward direction vector.",
        "completion": { "kind": "text", "text": "vfromheading(${1:degrees}, ${2:length})" },
        "isSnippet": true,
        "signatures": [["degrees", "length"]]
      }
    },
    {
      "id": "segisect",
      "label": "segisect",
      "category": "segments",
      "tags": ["call-syntax", "function", "geometry", "library", "segments"],
      "summary": "Intersection point [x, y] of segment a0→a1 and segment b0→b1, or [] if they don't cross. Segment test, not infinite-line — endpoints must actually meet. Collinear overlapping segments return the midpoint of the overlap.",
      "editor": {
        "kind": "function",
        "detail": "segment-segment intersection point (or [])",
        "documentation": "Intersection point [x, y] of segment a0→a1 and segment b0→b1, or [] if they don't cross.\nSegment test, not infinite-line — endpoints must actually meet. Collinear overlapping segments return the midpoint of the overlap.",
        "completion": { "kind": "text", "text": "segisect(${1:a0}, ${2:a1}, ${3:b0}, ${4:b1})" },
        "isSnippet": true,
        "signatures": [["a0", "a1", "b0", "b1"]]
      }
    },
    {
      "id": "segdist",
      "label": "segdist",
      "category": "segments",
      "tags": ["call-syntax", "function", "geometry", "library", "segments"],
      "summary": "Shortest distance from point p to the segment a→b. If the perpendicular foot falls outside the segment, returns the distance to the nearer endpoint. A zero-length segment behaves like vdist(p, a).",
      "editor": {
        "kind": "function",
        "detail": "distance from point to segment",
        "documentation": "Shortest distance from point p to the segment a→b. If the perpendicular foot falls outside the segment, returns the distance to the nearer endpoint. A zero-length segment behaves like vdist(p, a).",
        "completion": { "kind": "text", "text": "segdist(${1:p}, ${2:a}, ${3:b})" },
        "isSnippet": true,
        "signatures": [["p", "a", "b"]]
      }
    },
    {
      "id": "nearestonpath",
      "label": "nearestonpath",
      "category": "segments",
      "tags": ["call-syntax", "function", "geometry", "library", "segments"],
      "summary": "The closest point to p lying anywhere on path (vertices or along segments). Returns [x, y]. The path is treated as open (no implicit closing segment). O(len(path)) per call.",
      "editor": {
        "kind": "function",
        "detail": "closest point on a path to a point",
        "documentation": "The closest point to p lying anywhere on path (vertices or along segments). Returns [x, y]. The path is treated as open (no implicit closing segment). O(len(path)) per call.",
        "completion": { "kind": "text", "text": "nearestonpath(${1:p}, ${2:path})" },
        "isSnippet": true,
        "signatures": [["p", "path"]]
      }
    },
    {
      "id": "pathlen",
      "label": "pathlen",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "millimetres",
        "paths-curves"
      ],
      "summary": "Total length of a polyline path in mm — the sum of all segment lengths. Use it to normalise travel along a curve (compute `t = distanceSoFar / pathlen(path)`), decide how many stitches to place, or verify a path is the expected size.",
      "editor": {
        "kind": "function",
        "detail": "total path length (mm)",
        "documentation": "Total length of a polyline path in mm — the sum of all segment lengths. Use it to normalise travel along a curve (compute `t = distanceSoFar / pathlen(path)`), decide how many stitches to place, or verify a path is the expected size.\n\n```\nlet spine = trace [ fd 50  rt 30  fd 30 ]\nlet total = pathlen(spine)\nprint \"spine mm:\" total\n// Walk it with evenly-spaced motifs\nlet spacing = 8\nrepeat floor(total / spacing) [\n  // ... place motif at steps along the path\n]\n```",
        "completion": { "kind": "text", "text": "pathlen(${1:path})" },
        "isSnippet": true,
        "signatures": [["path"]]
      }
    },
    {
      "id": "resample",
      "label": "resample",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "millimetres",
        "paths-curves"
      ],
      "summary": "New path whose consecutive vertices are each exactly `spacing` mm apart (last segment may be shorter). The bridge between math curves and physical stitch spacing — generate an arbitrary shape with `trace`/`bezier`/`catmull`, then `resample` it to stitch pitch before `sewpath`.",
      "editor": {
        "kind": "function",
        "detail": "resample path to spacing (mm)",
        "documentation": "New path whose consecutive vertices are each exactly `spacing` mm apart (last segment may be shorter). The bridge between math curves and physical stitch spacing — generate an arbitrary shape with `trace`/`bezier`/`catmull`, then `resample` it to stitch pitch before `sewpath`.\n\n```\nlet curve = bezier([-20,0], [-10,20], [10,-20], [20,0], 0.5)\nsewpath(resample(curve, 2))    // sew at 2 mm stitches\n```",
        "completion": { "kind": "text", "text": "resample(${1:path}, ${2:spacing})" },
        "isSnippet": true,
        "signatures": [["path", "spacing"]]
      }
    },
    {
      "id": "chaikin",
      "label": "chaikin",
      "category": "paths-curves",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "library", "paths-curves"],
      "summary": "Corner-cut smoothing: each pass replaces every sharp vertex with two new points placed 25% and 75% along the incoming and outgoing edges, rounding the bend into a smooth curve. Applying multiple iterations produces progressively rounder, more organic shapes.",
      "editor": {
        "kind": "function",
        "detail": "corner-cut smoothing",
        "documentation": "Corner-cut smoothing: each pass replaces every sharp vertex with two new points placed 25% and 75% along the incoming and outgoing edges, rounding the bend into a smooth curve. Applying multiple iterations produces progressively rounder, more organic shapes.\n\nUse it to soften a jagged polygon or set of clicked waypoints before sewing.\n\n`iterations` 1–6 (values beyond 4 are rarely distinguishable).\n\n```\n// A rough pentagon becomes a flowing oval after 3 cuts\nlet poly = [[0,0],[20,5],[35,-8],[40,20],[15,30]]\nlet smooth = chaikin(poly, 3)\nsewpath(resample(smooth, 2))\n```",
        "completion": { "kind": "text", "text": "chaikin(${1:path}, ${2:iterations})" },
        "isSnippet": true,
        "signatures": [["path", "iterations"]]
      }
    },
    {
      "id": "catmull",
      "label": "catmull",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "millimetres",
        "paths-curves"
      ],
      "summary": "Smooth curve that passes exactly through every control point. Unlike Bézier curves, you do not need to supply separate handles — the spline infers the curvature from neighbouring points automatically. Resampled to `spacing` mm for sewing.",
      "editor": {
        "kind": "function",
        "detail": "Catmull-Rom spline",
        "documentation": "Smooth curve that passes exactly through every control point. Unlike Bézier curves, you do not need to supply separate handles — the spline infers the curvature from neighbouring points automatically. Resampled to `spacing` mm for sewing.\n\nGreat for animating paths through a set of waypoints or tracing an organic outline defined by hand-placed anchors.\n\n```\n// Sew a smooth curve through 4 waypoints\nlet pts = [[-20,0],[-5,20],[5,-20],[20,0]]\nsewpath(catmull(pts, 2))\n```",
        "completion": { "kind": "text", "text": "catmull(${1:points}, ${2:spacing})" },
        "isSnippet": true,
        "signatures": [["points", "spacing"]]
      }
    },
    {
      "id": "bezier",
      "label": "bezier",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "millimetres",
        "paths-curves"
      ],
      "summary": "Cubic Bézier from start `p0` to end `p1`, shaped by control handles `c0` (near the start) and `c1` (near the end). The curve is pulled toward the handles without passing through them — the further out you place a handle, the more the curve bends in that direction. Resampled to `spacing` mm for sewing.",
      "editor": {
        "kind": "function",
        "detail": "cubic Bézier curve",
        "documentation": "Cubic Bézier from start `p0` to end `p1`, shaped by control handles `c0` (near the start) and `c1` (near the end). The curve is pulled toward the handles without passing through them — the further out you place a handle, the more the curve bends in that direction. Resampled to `spacing` mm for sewing.\n\n```\nlet p0 = [-20, 0]   let c0 = [-10, 20]\nlet c1 = [10, -20]  let p1 = [20, 0]\nsewpath(bezier(p0, c0, c1, p1, 2))\n```",
        "completion": {
          "kind": "text",
          "text": "bezier(${1:p0}, ${2:c0}, ${3:c1}, ${4:p1}, ${5:spacing})"
        },
        "isSnippet": true,
        "signatures": [["p0", "c0", "c1", "p1", "spacing"]]
      }
    },
    {
      "id": "centroid",
      "label": "centroid",
      "category": "paths-curves",
      "tags": ["call-syntax", "function", "geometry", "library", "paths-curves"],
      "summary": "The geometric centre of a path — the average position of all its vertices. Use it to anchor rotation, find the middle of a region, or place a motif at the heart of a `voronoi` cell or scatter cluster.",
      "editor": {
        "kind": "function",
        "detail": "centroid of a path",
        "documentation": "The geometric centre of a path — the average position of all its vertices. Use it to anchor rotation, find the middle of a region, or place a motif at the heart of a `voronoi` cell or scatter cluster.\n\n```\nlet cells = voronoi(scatter(8))\nfor cell in cells [\n  let c = centroid(cell)\n  moveto c[0], c[1]\n  down  arc 360 1  up  trim   // dot at cell centre\n]\n```",
        "completion": { "kind": "text", "text": "centroid(${1:path})" },
        "isSnippet": true,
        "signatures": [["path"]]
      }
    },
    {
      "id": "bbox",
      "label": "bbox",
      "category": "paths-curves",
      "tags": ["call-syntax", "function", "geometry", "library", "millimetres", "paths-curves"],
      "summary": "Returns the smallest axis-aligned rectangle enclosing the path, as `[minx, miny, maxx, maxy]`. Use it to check a design's extents, frame a motif, compute a safe scatter region, or normalise coordinates to fit a specific area.",
      "editor": {
        "kind": "function",
        "detail": "bounding box [minx, miny, maxx, maxy]",
        "documentation": "Returns the smallest axis-aligned rectangle enclosing the path, as `[minx, miny, maxx, maxy]`. Use it to check a design's extents, frame a motif, compute a safe scatter region, or normalise coordinates to fit a specific area.\n\n```\nlet b = bbox(region)\nlet w = b[2] - b[0]   // width\nlet h = b[3] - b[1]   // height\nprint \"size mm:\" w h\n// Centre the region at the origin\nxlate(region, -(b[0] + w/2), -(b[1] + h/2))\n```",
        "completion": { "kind": "text", "text": "bbox(${1:path})" },
        "isSnippet": true,
        "signatures": [["path"]]
      }
    },
    {
      "id": "routesort",
      "label": "routesort",
      "category": "paths-curves",
      "tags": ["call-syntax", "function", "geometry", "library", "mode", "paths-curves", "pure"],
      "summary": "Returns a new greedily routed list. `routesort(items)` anchors the first item; `routesort(items, start)` starts nearest `[x,y]`. Mode `'both'` may return reversed copies of path elements so their nearer endpoint is entered first; `'chain'` is the default. Pure, deterministic, and drawless.",
      "editor": {
        "kind": "function",
        "detail": "order points or paths by nearest travel",
        "documentation": "Returns a new greedily routed list. `routesort(items)` anchors the first item; `routesort(items, start)` starts nearest `[x,y]`. Mode `'both'` may return reversed copies of path elements so their nearer endpoint is entered first; `'chain'` is the default. Pure, deterministic, and drawless.",
        "completion": {
          "kind": "text",
          "text": "routesort(${1:items}, ${2:start}, '${3|chain,both|}')"
        },
        "isSnippet": true,
        "signatures": [["items"], ["items", "start"], ["items", "mode"], ["items", "start", "mode"]]
      }
    },
    {
      "id": "sewpath",
      "label": "sewpath",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "mode",
        "paths-curves"
      ],
      "summary": "Exactly `for p in path [ setpos(p) ]`. Pen state, stitch mode, satin, and auto-split all apply as if hand-walked.",
      "editor": {
        "kind": "function",
        "detail": "sew along a list of points",
        "documentation": "Exactly `for p in path [ setpos(p) ]`. Pen state, stitch mode, satin, and auto-split all apply as if hand-walked.",
        "completion": { "kind": "text", "text": "sewpath(${1:path})" },
        "isSnippet": true,
        "signatures": [["path"]]
      }
    },
    {
      "id": "scatter",
      "label": "scatter",
      "category": "geometry",
      "tags": ["call-syntax", "function", "geometry", "library", "millimetres", "seeded"],
      "summary": "Seeded Poisson-disc (Bridson) points.",
      "editor": {
        "kind": "function",
        "detail": "Poisson-disc scatter points",
        "documentation": "Seeded Poisson-disc (Bridson) points.\n\n`scatter(minDist)` — over the 47 mm field\n`scatter(minDist, region)` — inside a region polygon\n\nCapped at 20,000 points.",
        "completion": { "kind": "text", "text": "scatter(${1:minDist})" },
        "isSnippet": true,
        "signatures": [["minDist"], ["minDist", "region"]]
      }
    },
    {
      "id": "voronoi",
      "label": "voronoi",
      "category": "geometry",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "library"],
      "summary": "Divide the canvas into cells, one per input point. Each cell contains every location that is closer to its seed point than to any other seed. Returns a list of closed regions in input order, clipped to the sewable field (or a given region).",
      "editor": {
        "kind": "function",
        "detail": "Voronoi cells from points",
        "documentation": "Divide the canvas into cells, one per input point. Each cell contains every location that is closer to its seed point than to any other seed. Returns a list of closed regions in input order, clipped to the sewable field (or a given region).\n\nCommon uses: organic tiling, stipple shading, cell-based fill patterns, or growing a motif inside each natural territory.\n\nMax 10,000 input points.\n\n```\nlet seeds = scatter(10)\nlet cells = voronoi(seeds)\nfor cell in cells [\n  beginfill\n    sewpath(resample(cell, 2))\n  endfill  trim\n]\n```",
        "completion": { "kind": "text", "text": "voronoi(${1:points})" },
        "isSnippet": true,
        "signatures": [["points"], ["points", "region"]]
      }
    },
    {
      "id": "triangulate",
      "label": "triangulate",
      "category": "geometry",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "library"],
      "summary": "Delaunay triangulation: connects a set of points into triangles such that the circumcircle of each triangle contains no other point. Returns a list of 3-point regions. The \"dual\" of Voronoi — the same seeds that define Voronoi cells also define the triangle mesh connecting them.",
      "editor": {
        "kind": "function",
        "detail": "Delaunay triangulation",
        "documentation": "Delaunay triangulation: connects a set of points into triangles such that the circumcircle of each triangle contains no other point. Returns a list of 3-point regions. The \"dual\" of Voronoi — the same seeds that define Voronoi cells also define the triangle mesh connecting them.\n\nCommon uses: structural weaving patterns (sew along each triangle edge), mesh-based fill, or truss-like geometric motifs.\n\nMax 10,000 input points.\n\n```\nlet pts = scatter(14)\nlet tris = triangulate(pts)\nfor tri in tris [\n  up  setpos(tri[0])  down\n  sewpath(tri)\n  setpos(tri[0])  up  trim\n]\n```",
        "completion": { "kind": "text", "text": "triangulate(${1:points})" },
        "isSnippet": true,
        "signatures": [["points"]]
      }
    },
    {
      "id": "hull",
      "label": "hull",
      "category": "geometry",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "library"],
      "summary": "Convex hull: the smallest convex polygon that encloses all given points, returned as a counter-clockwise region. Think of it as wrapping a rubber band around all the points — only the outermost ones form the boundary.",
      "editor": {
        "kind": "function",
        "detail": "convex hull of points",
        "documentation": "Convex hull: the smallest convex polygon that encloses all given points, returned as a counter-clockwise region. Think of it as wrapping a rubber band around all the points — only the outermost ones form the boundary.\n\nUse it as a bounding region for scatter or fill, to outline a cluster of points, or as a clip region.\n\n```\nlet pts = scatter(5)\nlet outline = hull(pts)\nbeginfill\n  sewpath(resample(outline, 2))\nendfill  trim\n```",
        "completion": { "kind": "text", "text": "hull(${1:points})" },
        "isSnippet": true,
        "signatures": [["points"]]
      }
    },
    {
      "id": "relax",
      "label": "relax",
      "category": "geometry",
      "tags": ["call-syntax", "function", "geometry", "library"],
      "summary": "n rounds of Lloyd's relaxation — moves each point to its Voronoi cell's centroid for even stippling.",
      "editor": {
        "kind": "function",
        "detail": "Lloyd's relaxation",
        "documentation": "n rounds of Lloyd's relaxation — moves each point to its Voronoi cell's centroid for even stippling.",
        "completion": { "kind": "text", "text": "relax(${1:points}, ${2:iterations})" },
        "isSnippet": true,
        "signatures": [["points", "iterations"]]
      }
    },
    {
      "id": "offsetpath",
      "label": "offsetpath",
      "category": "geometry",
      "tags": ["call-syntax", "function", "geometry", "library"],
      "summary": "Inflate (+) or shrink (−) a region. Returns a list of regions. Shrinking may split or erase the shape entirely.",
      "editor": {
        "kind": "function",
        "detail": "inflate / shrink a region",
        "documentation": "Inflate (+) or shrink (−) a region. Returns a list of regions. Shrinking may split or erase the shape entirely.",
        "completion": { "kind": "text", "text": "offsetpath(${1:region}, ${2:offset})" },
        "isSnippet": true,
        "signatures": [["region", "offset"]]
      }
    },
    {
      "id": "contourpaths",
      "label": "contourpaths",
      "category": "geometry",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "library"],
      "summary": "Closed inset contours at half-gap then gap spacing, ordered outside-in.",
      "editor": {
        "kind": "function",
        "detail": "concentric inset fill paths",
        "documentation": "Closed inset contours at half-gap then gap spacing, ordered outside-in.",
        "completion": { "kind": "text", "text": "contourpaths(${1:region}, ${2:gap})" },
        "isSnippet": true,
        "signatures": [["region", "gap"]]
      }
    },
    {
      "id": "spiralpath",
      "label": "spiralpath",
      "category": "geometry",
      "tags": ["call-syntax", "function", "geometry", "library"],
      "summary": "Contour rings spliced into one open inward path per disconnected fragment.",
      "editor": {
        "kind": "function",
        "detail": "connected inward spiral paths",
        "documentation": "Contour rings spliced into one open inward path per disconnected fragment.",
        "completion": { "kind": "text", "text": "spiralpath(${1:region}, ${2:gap})" },
        "isSnippet": true,
        "signatures": [["region", "gap"]]
      }
    },
    {
      "id": "fillrows",
      "label": "fillrows",
      "category": "geometry",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "library"],
      "summary": "Routed, unsplit tatami rows without pull compensation, ready for `fill paths`.",
      "editor": {
        "kind": "function",
        "detail": "tatami row spines as data",
        "documentation": "Routed, unsplit tatami rows without pull compensation, ready for `fill paths`.",
        "completion": { "kind": "text", "text": "fillrows(${1:region}, ${2:spacing}, ${3:angle})" },
        "isSnippet": true,
        "signatures": [["region", "spacing", "angle"]]
      }
    },
    {
      "id": "closepath",
      "label": "closepath",
      "category": "geometry",
      "tags": ["call-syntax", "function", "geometry", "library"],
      "summary": "Return the ring with its first point repeated. Requires at least three points.",
      "editor": {
        "kind": "function",
        "detail": "explicitly close a ring",
        "documentation": "Return the ring with its first point repeated. Requires at least three points.",
        "completion": { "kind": "text", "text": "closepath(${1:ring})" },
        "isSnippet": true,
        "signatures": [["ring"]]
      }
    },
    {
      "id": "clippaths",
      "label": "clippaths",
      "category": "geometry",
      "tags": ["call-syntax", "function", "geometry", "library"],
      "summary": "Boolean operation on two regions. Backed by Clipper2 at μm precision. Returns a list of regions.",
      "editor": {
        "kind": "function",
        "detail": "boolean of two regions",
        "documentation": "Boolean operation on two regions. Backed by Clipper2 at μm precision. Returns a list of regions.\n\nOperations: `'union'` `'intersect'` `'difference'` `'xor'`",
        "completion": {
          "kind": "text",
          "text": "clippaths(${1:a}, ${2:b}, '${3|union,intersect,difference,xor|}')"
        },
        "isSnippet": true,
        "signatures": [["a", "b", "'op'"]]
      }
    },
    {
      "id": "inpath",
      "label": "inpath",
      "category": "geometry",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "library"],
      "summary": "1 if the point is inside the region (even-odd rule, consistent with fills).",
      "editor": {
        "kind": "function",
        "detail": "1 if point is inside region",
        "documentation": "1 if the point is inside the region (even-odd rule, consistent with fills).",
        "completion": { "kind": "text", "text": "inpath(${1:point}, ${2:region})" },
        "isSnippet": true,
        "signatures": [["point", "region"]]
      }
    },
    {
      "id": "infield",
      "label": "infield",
      "category": "field",
      "tags": ["call-syntax", "embroidery", "field", "function", "geometry", "library"],
      "summary": "`1` if the point is inside the current sewable field, `0` otherwise. The point is mapped through the current transform (local frame → hoop space), consistent with `coverat`. Zero RNG draws.",
      "editor": {
        "kind": "function",
        "detail": "1 if point is inside the sewable field",
        "documentation": "`1` if the point is inside the current sewable field, `0` otherwise. The point is mapped through the current transform (local frame → hoop space), consistent with `coverat`. Zero RNG draws.\n\n```\nif infield(pos()) [ fd 2 ]  // only sew if inside the field\n```",
        "completion": { "kind": "text", "text": "infield(${1:point})" },
        "isSnippet": true,
        "signatures": [["point"]]
      }
    },
    {
      "id": "fieldbounds",
      "label": "fieldbounds",
      "category": "field",
      "tags": ["call-syntax", "embroidery", "field", "function", "library", "millimetres"],
      "summary": "Returns `[minX, minY, maxX, maxY]` — the bounding box of the sewable field in hoop space (mm). Same format as `bbox()`. Zero RNG draws.",
      "editor": {
        "kind": "function",
        "detail": "bounding box of the sewable field",
        "documentation": "Returns `[minX, minY, maxX, maxY]` — the bounding box of the sewable field in hoop space (mm). Same format as `bbox()`. Zero RNG draws.\n\n```\nlet b = fieldbounds()  // e.g. [-47, -47, 47, 47] for round100\n```",
        "completion": { "kind": "text", "text": "fieldbounds()" },
        "signatures": []
      }
    },
    {
      "id": "fieldpath",
      "label": "fieldpath",
      "category": "field",
      "tags": [
        "call-syntax",
        "embroidery",
        "field",
        "function",
        "geometry",
        "library",
        "millimetres"
      ],
      "summary": "Returns the boundary of the sewable field as a counter-clockwise polygon, ready for use as a region in `scatter`, `clippaths`, `offsetpath`, etc. Round fields are polygonised at ≤ 2 mm chords. Zero RNG draws.",
      "editor": {
        "kind": "function",
        "detail": "sewable field boundary as a CCW region",
        "documentation": "Returns the boundary of the sewable field as a counter-clockwise polygon, ready for use as a region in `scatter`, `clippaths`, `offsetpath`, etc. Round fields are polygonised at ≤ 2 mm chords. Zero RNG draws.\n\n`offsetpath(fieldpath(), -5)` gives a 5 mm safety margin inside whatever hoop is configured.\n\n```\nhoop '5x7'\nlet margin = first(offsetpath(fieldpath(), -6))\nlet pts = scatter(5, margin)\n```",
        "completion": { "kind": "text", "text": "fieldpath()" },
        "signatures": []
      }
    },
    {
      "id": "xlate",
      "label": "xlate",
      "category": "path-transforms",
      "tags": [
        "block",
        "call-syntax",
        "function",
        "geometry",
        "library",
        "millimetres",
        "path-transforms",
        "pure"
      ],
      "summary": "New path shifted by `(dx, dy)` mm. The functional companion to the `translate` block command — composes with `scatter`/`voronoi`/`offsetpath` data.",
      "editor": {
        "kind": "function",
        "detail": "translate a path (pure)",
        "documentation": "New path shifted by `(dx, dy)` mm. The functional companion to the `translate` block command — composes with `scatter`/`voronoi`/`offsetpath` data.",
        "completion": { "kind": "text", "text": "xlate(${1:path}, ${2:dx}, ${3:dy})" },
        "isSnippet": true,
        "signatures": [["path", "dx", "dy"]]
      }
    },
    {
      "id": "xrotate",
      "label": "xrotate",
      "category": "path-transforms",
      "tags": [
        "call-syntax",
        "function",
        "geometry",
        "heading",
        "library",
        "path-transforms",
        "pure"
      ],
      "summary": "New path rotated `deg` clockwise. Optional pivot: `xrotate(path, deg, cx, cy)`.",
      "editor": {
        "kind": "function",
        "detail": "rotate a path (pure)",
        "documentation": "New path rotated `deg` clockwise. Optional pivot: `xrotate(path, deg, cx, cy)`.",
        "completion": { "kind": "text", "text": "xrotate(${1:path}, ${2:degrees})" },
        "isSnippet": true,
        "signatures": [
          ["path", "degrees"],
          ["path", "degrees", "cx", "cy"]
        ]
      }
    },
    {
      "id": "xscale",
      "label": "xscale",
      "category": "path-transforms",
      "tags": ["call-syntax", "function", "geometry", "library", "path-transforms", "pure"],
      "summary": "New path scaled by `sx` (and `sy`). `xscale(path, s)` is uniform; `xscale(path, sx, sy)` is per-axis.",
      "editor": {
        "kind": "function",
        "detail": "scale a path (pure)",
        "documentation": "New path scaled by `sx` (and `sy`). `xscale(path, s)` is uniform; `xscale(path, sx, sy)` is per-axis.",
        "completion": { "kind": "text", "text": "xscale(${1:path}, ${2:s})" },
        "isSnippet": true,
        "signatures": [
          ["path", "s"],
          ["path", "sx", "sy"]
        ]
      }
    },
    {
      "id": "xmirror",
      "label": "xmirror",
      "category": "path-transforms",
      "tags": [
        "call-syntax",
        "function",
        "geometry",
        "heading",
        "library",
        "path-transforms",
        "pure"
      ],
      "summary": "New path reflected across a line through the origin at heading `deg`.",
      "editor": {
        "kind": "function",
        "detail": "mirror a path (pure)",
        "documentation": "New path reflected across a line through the origin at heading `deg`.",
        "completion": { "kind": "text", "text": "xmirror(${1:path}, ${2:degrees})" },
        "isSnippet": true,
        "signatures": [["path", "degrees"]]
      }
    },
    {
      "id": "warppath",
      "label": "warppath",
      "category": "path-transforms",
      "tags": [
        "block",
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "path-transforms",
        "pure"
      ],
      "summary": "New path with every point mapped through a `@name` reporter — the functional companion to the `warp` block. `warp @f [ sewpath(P) ]` ≡ `sewpath(warppath(P, @f))`.",
      "editor": {
        "kind": "function",
        "detail": "map a path through a reporter (pure)",
        "documentation": "New path with every point mapped through a `@name` reporter — the functional companion to the `warp` block. `warp @f [ sewpath(P) ]` ≡ `sewpath(warppath(P, @f))`.",
        "completion": { "kind": "text", "text": "warppath(${1:path}, @${2:reporter})" },
        "isSnippet": true,
        "signatures": [["path", "reporter"]]
      }
    },
    {
      "id": "humanizepath",
      "label": "humanizepath",
      "category": "path-transforms",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "millimetres",
        "path-transforms",
        "pure",
        "seeded"
      ],
      "summary": "New path with seeded coherent jitter (`amount` mm) — the functional companion to `humanize`. Forks one draw from the seeded stream.",
      "editor": {
        "kind": "function",
        "detail": "seeded coherent jitter on a path (pure)",
        "documentation": "New path with seeded coherent jitter (`amount` mm) — the functional companion to `humanize`. Forks one draw from the seeded stream.\n\n```\nlet coast = humanizepath(resample(cell, 2.0), 0.3)\nsewpath(coast)\n```",
        "completion": { "kind": "text", "text": "humanizepath(${1:path}, ${2:amount})" },
        "isSnippet": true,
        "signatures": [["path", "amount"]]
      }
    },
    {
      "id": "snappath",
      "label": "snappath",
      "category": "path-transforms",
      "tags": [
        "call-syntax",
        "function",
        "geometry",
        "library",
        "millimetres",
        "path-transforms",
        "pure"
      ],
      "summary": "New path with every point snapped to the fixed lattice — the functional companion to `snaptogrid`, same arity overloads (cell | cellx celly | …ox oy | …ang).",
      "editor": {
        "kind": "function",
        "detail": "quantize a path to a fixed lattice (pure)",
        "documentation": "New path with every point snapped to the fixed lattice — the functional companion to `snaptogrid`, same arity overloads (cell | cellx celly | …ox oy | …ang).\n\n```\nlet pts = snappath(scatter(8), 2)   // Poisson points on a 2 mm grid\n```",
        "completion": { "kind": "text", "text": "snappath(${1:path}, ${2:cell})" },
        "isSnippet": true,
        "signatures": [["path", "cell"]]
      }
    },
    {
      "id": "declumppath",
      "label": "declumppath",
      "category": "path-transforms",
      "tags": [
        "block",
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "path-transforms",
        "pure"
      ],
      "summary": "Run the `declump` fold over an explicit point list, reading real committed coverage history but committing nothing — the pure data twin of `declump`. Drawless.",
      "editor": {
        "kind": "function",
        "detail": "along-axis crowd relief on a path (pure, read-only)",
        "documentation": "Run the `declump` fold over an explicit point list, reading real committed coverage history but committing nothing — the pure data twin of `declump`. Drawless.\n\nThe fold self-interacts exactly as the block form does (consecutive points see each other's moved positions), but the results are never fed to the density grid, so `coverat` is unchanged after the call.\n\n**Important:** resample to stitch pitch first, then sew:\n\n```\nsewpath(declumppath(resample(spine, 2.5), 2, 1.5))\n```\n\nArgs: `declumppath(path, limit)` or `declumppath(path, limit, maxshift)` — same units as the block form.",
        "completion": { "kind": "text", "text": "declumppath(${1:path}, ${2:limit})" },
        "isSnippet": true,
        "signatures": [
          ["path", "limit"],
          ["path", "limit", "maxshift"]
        ]
      }
    },
    {
      "id": "satinpair",
      "label": "satinpair",
      "category": "satin-helpers",
      "tags": ["call-syntax", "embroidery", "function", "library", "satin-helpers"],
      "summary": "Build the 5-slot satin reporter contract by intent.",
      "editor": {
        "kind": "function",
        "detail": "symmetric satin tuple: [adv, w, w, 0, 0]",
        "documentation": "Build the 5-slot satin reporter contract by intent.\n\n`satinpair(advance, width)` → `[advance, width, width, 0, 0]`\n\nThe common case: a symmetric perpendicular bite of the given width. Equivalent to the built-in `satin` generator.\n\nDraw cost: 0. Library tier — shadowable with a note.\n\n```\ndef leaf(t, s, i, u) [\n  return satinpair(0.45, sin(s * 180) * 2.2)\n]\n```",
        "completion": { "kind": "text", "text": "satinpair(${1:advance}, ${2:width})" },
        "isSnippet": true,
        "signatures": [["advance", "width"]]
      }
    },
    {
      "id": "satinrake",
      "label": "satinrake",
      "category": "satin-helpers",
      "tags": ["call-syntax", "embroidery", "function", "library", "millimetres", "satin-helpers"],
      "summary": "Build the 5-slot satin reporter contract by intent.",
      "editor": {
        "kind": "function",
        "detail": "raked satin tuple: [adv, w, w, -lag, lag]",
        "documentation": "Build the 5-slot satin reporter contract by intent.\n\n`satinrake(advance, width, lag)` → `[advance, width, width, -lag, lag]`\n\nRakes the stitch into a diagonal by `lag` mm. Alternating the sign each pair makes successive diagonals cross — woven / crosshatch satin.\n\nDraw cost: 0. Library tier — shadowable with a note.\n\n```\ndef crosshatch(t, s, i, u) [\n  if mod(i, 2) = 0 [ return satinrake(0.4, 2, 0.8) ]\n  return satinrake(0.4, 2, -0.8)\n]\n```",
        "completion": { "kind": "text", "text": "satinrake(${1:advance}, ${2:width}, ${3:lag})" },
        "isSnippet": true,
        "signatures": [["advance", "width", "lag"]]
      }
    },
    {
      "id": "satinasym",
      "label": "satinasym",
      "category": "satin-helpers",
      "tags": ["call-syntax", "embroidery", "function", "library", "satin-helpers"],
      "summary": "Build the 5-slot satin reporter contract by intent.",
      "editor": {
        "kind": "function",
        "detail": "asymmetric satin tuple: [adv, lw, rw, 0, 0]",
        "documentation": "Build the 5-slot satin reporter contract by intent.\n\n`satinasym(advance, leftw, rightw)` → `[advance, leftw, rightw, 0, 0]`\n\nAsymmetric column: left and right rail widths are different, no rake.\n\nDraw cost: 0. Library tier — shadowable with a note.",
        "completion": {
          "kind": "text",
          "text": "satinasym(${1:advance}, ${2:leftw}, ${3:rightw})"
        },
        "isSnippet": true,
        "signatures": [["advance", "leftw", "rightw"]]
      }
    },
    {
      "id": "railinset",
      "label": "railinset",
      "category": "satin-helpers",
      "tags": ["call-syntax", "embroidery", "function", "library", "pure", "satin-helpers"],
      "summary": "`railinset(advance, inset)` builds `[advance, inset, inset, 0, 0]` for a `satinbetween` shape reporter. Insets move inward from both authored rails. Pure, drawless, Library tier.",
      "editor": {
        "kind": "function",
        "detail": "rail-pair tuple: [adv, inset, inset, 0, 0]",
        "documentation": "`railinset(advance, inset)` builds `[advance, inset, inset, 0, 0]` for a `satinbetween` shape reporter. Insets move inward from both authored rails. Pure, drawless, Library tier.",
        "completion": { "kind": "text", "text": "railinset(${1:advance}, ${2:inset})" },
        "isSnippet": true,
        "signatures": [["advance", "inset"]]
      }
    },
    {
      "id": "railrake",
      "label": "railrake",
      "category": "satin-helpers",
      "tags": ["call-syntax", "embroidery", "function", "library", "pure", "satin-helpers"],
      "summary": "`railrake(advance, lag)` builds `[advance, 0, 0, -lag, lag]` for a full-width raked `satinbetween` stitch. Pure, drawless, Library tier.",
      "editor": {
        "kind": "function",
        "detail": "rail-pair tuple: [adv, 0, 0, -lag, lag]",
        "documentation": "`railrake(advance, lag)` builds `[advance, 0, 0, -lag, lag]` for a full-width raked `satinbetween` stitch. Pure, drawless, Library tier.",
        "completion": { "kind": "text", "text": "railrake(${1:advance}, ${2:lag})" },
        "isSnippet": true,
        "signatures": [["advance", "lag"]]
      }
    },
    {
      "id": "railspine",
      "label": "railspine",
      "category": "satin-helpers",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "pure",
        "satin-helpers"
      ],
      "summary": "Returns the same derived midpoint path used by `satinbetween`, including orientation and deterministic closed-rail seam handling. Useful for a centre vein or manual run. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "derived midpoint path between two rails",
        "documentation": "Returns the same derived midpoint path used by `satinbetween`, including orientation and deterministic closed-rail seam handling. Useful for a centre vein or manual run. Pure and drawless.",
        "completion": { "kind": "text", "text": "railspine(${1:railA}, ${2:railB})" },
        "isSnippet": true,
        "signatures": [["railA", "railB"]]
      }
    },
    {
      "id": "tatamirow",
      "label": "tatamirow",
      "category": "fill-helpers",
      "tags": ["call-syntax", "embroidery", "fill-helpers", "function", "library"],
      "summary": "Build the 3-slot fill shape reporter contract by intent.",
      "editor": {
        "kind": "function",
        "detail": "fill row tuple: [spacing, len, phase]",
        "documentation": "Build the 3-slot fill shape reporter contract by intent.\n\n`tatamirow(spacing, len)` → `[spacing, len, 0.5]` — standard brick offset\n`tatamirow(spacing, len, phase)` → `[spacing, len, phase]` — explicit phase\n\nUsed inside a `fill shape @fn` reporter to return the row descriptor without memorising slot order. `phase = 0.5` is the standard tatami brick offset.\n\nDraw cost: 0. Library tier — shadowable with a note.\n\n```\ndef thin(p, row, v) [\n  return tatamirow(remap(v, 0, 1, 0.4, 1.1), 2.5)\n]\nfill shape @thin\n```",
        "completion": { "kind": "text", "text": "tatamirow(${1:spacing}, ${2:len})" },
        "isSnippet": true,
        "signatures": [
          ["spacing", "len"],
          ["spacing", "len", "phase"]
        ]
      }
    },
    {
      "id": "coverat",
      "label": "coverat",
      "category": "history",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "history",
        "library",
        "millimetres",
        "pure"
      ],
      "summary": "Coverage at a point, in layers (the heatmap / `maxdensity` unit; 1 ≈ one clean satin/tatami pass), read live and in sewing order over everything committed so far.",
      "editor": {
        "kind": "function",
        "detail": "thread coverage in layers at a point (live, pure)",
        "documentation": "Coverage at a point, in **layers** (the heatmap / `maxdensity` unit; 1 ≈ one clean satin/tatami pass), read live and in sewing order over everything committed so far.\n\n`coverat(p)` — the containing 1 mm cell\n`coverat(p, r)` — averaged over radius `r` mm\n\nPure: zero RNG draws, draws nothing. Sees flushed penetrations (a buffered satin column isn’t visible until it ends).\n\n```\nif coverat(p) < 1.5 [ up setpos(p) down arc 360 0.5 trim ]\n```",
        "completion": { "kind": "text", "text": "coverat(${1:p})" },
        "isSnippet": true,
        "signatures": [["p"], ["p", "r"]]
      }
    },
    {
      "id": "countat",
      "label": "countat",
      "category": "history",
      "tags": ["call-syntax", "function", "geometry", "history", "library", "millimetres", "pure"],
      "summary": "The number of penetrations in the 1 mm cell containing `p`, read live. Pure: zero draws, draws nothing.",
      "editor": {
        "kind": "function",
        "detail": "penetration count at a point (live, pure)",
        "documentation": "The number of penetrations in the 1 mm cell containing `p`, read live. Pure: zero draws, draws nothing.",
        "completion": { "kind": "text", "text": "countat(${1:p})" },
        "isSnippet": true,
        "signatures": [["p"]]
      }
    },
    {
      "id": "nearestsewn",
      "label": "nearestsewn",
      "category": "history",
      "tags": ["call-syntax", "embroidery", "function", "geometry", "history", "library", "pure"],
      "summary": "The closest already-sewn penetration to `p`, as `[x, y]` in hoop space, or `[]` if nothing is sewn yet. Backed by a spatial index, so it stays O(local) — no history scan. Pure: zero draws.",
      "editor": {
        "kind": "function",
        "detail": "closest prior penetration to a point (or [])",
        "documentation": "The closest already-sewn penetration to `p`, as `[x, y]` in hoop space, or `[]` if nothing is sewn yet. Backed by a spatial index, so it stays O(local) — no history scan. Pure: zero draws.",
        "completion": { "kind": "text", "text": "nearestsewn(${1:p})" },
        "isSnippet": true,
        "signatures": [["p"]]
      }
    },
    {
      "id": "sewnwithin",
      "label": "sewnwithin",
      "category": "history",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "history",
        "library",
        "millimetres",
        "pure"
      ],
      "summary": "A list of already-sewn penetrations within `r` mm of `p` (hoop space). Grid-bucketed, so proximity logic stays O(local) instead of scanning the whole history.",
      "editor": {
        "kind": "function",
        "detail": "prior penetrations within r mm of a point",
        "documentation": "A list of already-sewn penetrations within `r` mm of `p` (hoop space). Grid-bucketed, so proximity logic stays O(local) instead of scanning the whole history.\n\n```\nif len(sewnwithin(p, 2)) = 0 [ … ]   // nothing crowding p yet\n```\n\nPure: zero draws.",
        "completion": { "kind": "text", "text": "sewnwithin(${1:p}, ${2:r})" },
        "isSnippet": true,
        "signatures": [["p", "r"]]
      }
    },
    {
      "id": "stitchedpoints",
      "label": "stitchedpoints",
      "category": "history",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "history",
        "library",
        "pure",
        "stateful"
      ],
      "summary": "A deep-copied list of every penetration committed so far, as a path of `[x, y]` points (hoop space), captured at call time. Explicit and opt-in: you pay the O(n) copy when you ask, and the result is just a list (safe to mutate). Pure: zero draws.",
      "editor": {
        "kind": "function",
        "detail": "snapshot: a deep copy of all penetrations so far",
        "documentation": "A deep-copied list of every penetration committed so far, as a path of `[x, y]` points (hoop space), captured at call time. Explicit and opt-in: you pay the O(n) copy when you ask, and the result is just a list (safe to mutate). Pure: zero draws.",
        "completion": { "kind": "text", "text": "stitchedpoints()" },
        "isSnippet": true,
        "signatures": [[]]
      }
    },
    {
      "id": "backward",
      "label": "backward",
      "category": "movement",
      "tags": ["embroidery", "function", "library", "millimetres", "movement"],
      "aliasFor": "bk",
      "summary": "Alias for `bk`. Sew backward n mm.",
      "editor": {
        "kind": "function",
        "detail": "sew backward (mm) — alias for bk",
        "documentation": "Alias for `bk`. Sew backward n mm.\n\nAliases: `back`, `backward`",
        "completion": { "kind": "text", "text": "backward ${1:mm}" },
        "isSnippet": true,
        "signatures": [["mm"]]
      }
    },
    {
      "id": "pu",
      "label": "pu",
      "category": "movement",
      "tags": ["embroidery", "function", "library", "mode", "movement"],
      "aliasFor": "up",
      "summary": "Alias for `up`. Needle up — subsequent moves are jump travels, not stitches.",
      "editor": {
        "kind": "function",
        "detail": "pen up (travel mode) — alias for up",
        "documentation": "Alias for `up`. Needle up — subsequent moves are jump travels, not stitches.\n\nAliases: `penup`, `pu`",
        "completion": { "kind": "text", "text": "pu" },
        "signatures": [[]]
      }
    },
    {
      "id": "pd",
      "label": "pd",
      "category": "movement",
      "tags": ["embroidery", "function", "library", "mode", "movement"],
      "aliasFor": "down",
      "summary": "Alias for `down`. Needle down — subsequent moves sew stitches.",
      "editor": {
        "kind": "function",
        "detail": "pen down (sew mode) — alias for down",
        "documentation": "Alias for `down`. Needle down — subsequent moves sew stitches.\n\nAliases: `pendown`, `pd`",
        "completion": { "kind": "text", "text": "pd" },
        "signatures": [[]]
      }
    },
    {
      "id": "clearscreen",
      "label": "clearscreen",
      "category": "movement",
      "tags": ["embroidery", "function", "library", "movement"],
      "aliasFor": "cs",
      "summary": "Alias for `cs`. Accepted for Logo familiarity; does nothing in NeedleScript.",
      "editor": {
        "kind": "function",
        "detail": "clearscreen (no-op) — alias for cs",
        "documentation": "Alias for `cs`. Accepted for Logo familiarity; does nothing in NeedleScript.\n\nAliases: `clearscreen`, `clear`",
        "completion": { "kind": "text", "text": "clearscreen" },
        "signatures": [[]]
      }
    },
    {
      "id": "clear",
      "label": "clear",
      "category": "movement",
      "tags": ["embroidery", "function", "library", "movement"],
      "aliasFor": "cs",
      "summary": "Alias for `cs`. Accepted for Logo familiarity; does nothing in NeedleScript.",
      "editor": {
        "kind": "function",
        "detail": "clearscreen (no-op) — alias for cs",
        "documentation": "Alias for `cs`. Accepted for Logo familiarity; does nothing in NeedleScript.\n\nAliases: `clearscreen`, `clear`",
        "completion": { "kind": "text", "text": "clear" },
        "signatures": [[]]
      }
    },
    {
      "id": "rgb",
      "label": "rgb",
      "category": "colors",
      "tags": ["call-syntax", "colors", "embroidery", "function", "library", "pure"],
      "summary": "Return a normalized hex color from red, green, and blue channels in 0…1. Values outside the range are clamped. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "RGB color string",
        "documentation": "Return a normalized hex color from red, green, and blue channels in 0…1. Values outside the range are clamped. Pure and drawless.",
        "completion": { "kind": "text", "text": "rgb(${1:r}, ${2:g}, ${3:b})" },
        "isSnippet": true,
        "signatures": [["r", "g", "b"]]
      }
    },
    {
      "id": "hsl",
      "label": "hsl",
      "category": "colors",
      "tags": ["call-syntax", "colors", "embroidery", "function", "library", "pure"],
      "summary": "Return a normalized hex color from hue in degrees plus saturation and lightness in 0…1. Hue wraps; saturation and lightness clamp. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "HSL color string",
        "documentation": "Return a normalized hex color from hue in degrees plus saturation and lightness in 0…1. Hue wraps; saturation and lightness clamp. Pure and drawless.",
        "completion": { "kind": "text", "text": "hsl(${1:h}, ${2:s}, ${3:l})" },
        "isSnippet": true,
        "signatures": [["h", "s", "l"]]
      }
    },
    {
      "id": "hexparts",
      "label": "hexparts",
      "category": "colors",
      "tags": ["call-syntax", "colors", "embroidery", "function", "library", "pure"],
      "summary": "Parse a supported color string and return normalized `[r, g, b]` channels in 0…1. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "RGB channels from a color",
        "documentation": "Parse a supported color string and return normalized `[r, g, b]` channels in 0…1. Pure and drawless.",
        "completion": { "kind": "text", "text": "hexparts(${1:color})" },
        "isSnippet": true,
        "signatures": [["color"]]
      }
    },
    {
      "id": "lerpcolor",
      "label": "lerpcolor",
      "category": "colors",
      "tags": ["call-syntax", "colors", "embroidery", "function", "library", "pure"],
      "summary": "Interpolate colors at unclamped `t`. The default mode is perceptual OKLab; pass `'rgb'` as a fourth argument for raw sRGB interpolation. Returns a normalized hex color. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "interpolate two colors",
        "documentation": "Interpolate colors at unclamped `t`. The default mode is perceptual OKLab; pass `'rgb'` as a fourth argument for raw sRGB interpolation. Returns a normalized hex color. Pure and drawless.",
        "completion": { "kind": "text", "text": "lerpcolor(${1:a}, ${2:b}, ${3:t})" },
        "isSnippet": true,
        "signatures": [
          ["a", "b", "t"],
          ["a", "b", "t", "mode"]
        ]
      }
    },
    {
      "id": "nearestcolor",
      "label": "nearestcolor",
      "category": "colors",
      "tags": ["call-syntax", "colors", "embroidery", "function", "library", "pure"],
      "summary": "Return the lowest-index color in a non-empty palette with the smallest perceptual OKLab distance from `color`. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "nearest palette color",
        "documentation": "Return the lowest-index color in a non-empty palette with the smallest perceptual OKLab distance from `color`. Pure and drawless.",
        "completion": { "kind": "text", "text": "nearestcolor(${1:color}, ${2:colors})" },
        "isSnippet": true,
        "signatures": [["color", "colors"]]
      }
    },
    {
      "id": "colordist",
      "label": "colordist",
      "category": "colors",
      "tags": ["call-syntax", "colors", "embroidery", "function", "library", "pure"],
      "summary": "Return the OKLab distance between two supported color strings. Smaller values are more visually similar. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "perceptual color distance",
        "documentation": "Return the OKLab distance between two supported color strings. Smaller values are more visually similar. Pure and drawless.",
        "completion": { "kind": "text", "text": "colordist(${1:a}, ${2:b})" },
        "isSnippet": true,
        "signatures": [["a", "b"]]
      }
    },
    {
      "id": "slotcolor",
      "label": "slotcolor",
      "category": "colors",
      "tags": ["call-syntax", "colors", "embroidery", "function", "library", "pure"],
      "summary": "Return the normalized hex color for a 1-based palette slot, including the deterministic default color for an undeclared slot. Reads metadata, emits nothing, and draws nothing.",
      "editor": {
        "kind": "function",
        "detail": "resolved palette slot color",
        "documentation": "Return the normalized hex color for a 1-based palette slot, including the deterministic default color for an undeclared slot. Reads metadata, emits nothing, and draws nothing.",
        "completion": { "kind": "text", "text": "slotcolor(${1:slot})" },
        "isSnippet": true,
        "signatures": [["slot"]]
      }
    },
    {
      "id": "colorindex",
      "label": "colorindex",
      "category": "colors",
      "tags": ["call-syntax", "colors", "embroidery", "function", "library", "pure"],
      "summary": "Return the active thread slot as a 1-based index. Reads machine state, emits nothing, and draws nothing.",
      "editor": {
        "kind": "function",
        "detail": "active 1-based color slot",
        "documentation": "Return the active thread slot as a 1-based index. Reads machine state, emits nothing, and draws nothing.",
        "completion": { "kind": "text", "text": "colorindex()" },
        "isSnippet": true,
        "signatures": [[]]
      }
    },
    {
      "id": "colorhex",
      "label": "colorhex",
      "category": "colors",
      "tags": ["call-syntax", "colors", "embroidery", "function", "library", "pure"],
      "summary": "Return the normalized hex color of the active thread slot. Reads palette metadata, emits nothing, and draws nothing.",
      "editor": {
        "kind": "function",
        "detail": "active thread color",
        "documentation": "Return the normalized hex color of the active thread slot. Reads palette metadata, emits nothing, and draws nothing.",
        "completion": { "kind": "text", "text": "colorhex()" },
        "isSnippet": true,
        "signatures": [[]]
      }
    },
    {
      "id": "backgroundcolor",
      "label": "backgroundcolor",
      "category": "colors",
      "tags": ["call-syntax", "colors", "embroidery", "function", "library", "pure"],
      "summary": "Return the normalized resolved background color. Reads design metadata, emits nothing, and draws nothing.",
      "editor": {
        "kind": "function",
        "detail": "resolved background color",
        "documentation": "Return the normalized resolved background color. Reads design metadata, emits nothing, and draws nothing.",
        "completion": { "kind": "text", "text": "backgroundcolor()" },
        "isSnippet": true,
        "signatures": [[]]
      }
    },
    {
      "id": "curveflat",
      "label": "curveflat",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Adaptively flatten editable cubic anchors into a path at `tolerance` millimetres. Relative handles and compact corner anchors are supported; optional mode `'closed'` closes the curve. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "flatten an editable cubic curve spec",
        "documentation": "Adaptively flatten editable cubic anchors into a path at `tolerance` millimetres. Relative handles and compact corner anchors are supported; optional mode `'closed'` closes the curve. Pure and drawless.",
        "completion": { "kind": "text", "text": "curveflat(${1:spec}, ${2:tolerance})" },
        "isSnippet": true,
        "signatures": [
          ["spec", "tolerance"],
          ["spec", "tolerance", "mode"]
        ]
      }
    },
    {
      "id": "curvepath",
      "label": "curvepath",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Flatten an editable cubic curve spec at 0.05 mm tolerance, then arc-length resample it with numeric, list, or reporter spacing. Optional phase and `'open'`/`'closed'` mode follow `resample` semantics.",
      "editor": {
        "kind": "function",
        "detail": "flatten and resample a curve spec",
        "documentation": "Flatten an editable cubic curve spec at 0.05 mm tolerance, then arc-length resample it with numeric, list, or reporter spacing. Optional phase and `'open'`/`'closed'` mode follow `resample` semantics.",
        "completion": { "kind": "text", "text": "curvepath(${1:spec}, ${2:spacing})" },
        "isSnippet": true,
        "signatures": [
          ["spec", "spacing"],
          ["spec", "spacing", "phase"],
          ["spec", "spacing", "phase", "mode"]
        ]
      }
    },
    {
      "id": "isclosed",
      "label": "isclosed",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return `1` when a path explicitly repeats its first point at the end, otherwise `0`. Non-empty path validation still applies. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "test canonical path closure",
        "documentation": "Return `1` when a path explicitly repeats its first point at the end, otherwise `0`. Non-empty path validation still applies. Pure and drawless.",
        "completion": { "kind": "text", "text": "isclosed(${1:path})" },
        "isSnippet": true,
        "signatures": [["path"]]
      }
    },
    {
      "id": "openpath",
      "label": "openpath",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return a new open path by removing a duplicate final point when the input is canonically closed. Other vertices are preserved. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "remove a canonical closing point",
        "documentation": "Return a new open path by removing a duplicate final point when the input is canonically closed. Other vertices are preserved. Pure and drawless.",
        "completion": { "kind": "text", "text": "openpath(${1:path})" },
        "isSnippet": true,
        "signatures": [["path"]]
      }
    },
    {
      "id": "pathorientation",
      "label": "pathorientation",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return `1` for counter-clockwise, `-1` for clockwise, or `0` for a degenerate path, using the implicit closing segment. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "Cartesian path orientation",
        "documentation": "Return `1` for counter-clockwise, `-1` for clockwise, or `0` for a degenerate path, using the implicit closing segment. Pure and drawless.",
        "completion": { "kind": "text", "text": "pathorientation(${1:path})" },
        "isSnippet": true,
        "signatures": [["path"]]
      }
    },
    {
      "id": "pointat",
      "label": "pointat",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return the point at normalized arc-length parameter `t` on an open path. Parameters are clamped to 0…1. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "point at normalized path distance",
        "documentation": "Return the point at normalized arc-length parameter `t` on an open path. Parameters are clamped to 0…1. Pure and drawless.",
        "completion": { "kind": "text", "text": "pointat(${1:path}, ${2:t})" },
        "isSnippet": true,
        "signatures": [["path", "t"]]
      }
    },
    {
      "id": "headingat",
      "label": "headingat",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return the turtle heading of an open path at normalized arc-length parameter `t`. Parameters are clamped to 0…1. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "heading at normalized path distance",
        "documentation": "Return the turtle heading of an open path at normalized arc-length parameter `t`. Parameters are clamped to 0…1. Pure and drawless.",
        "completion": { "kind": "text", "text": "headingat(${1:path}, ${2:t})" },
        "isSnippet": true,
        "signatures": [["path", "t"]]
      }
    },
    {
      "id": "normalat",
      "label": "normalat",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return the turtle heading of the normal pointing left of path travel at normalized arc-length parameter `t`. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "left normal heading on a path",
        "documentation": "Return the turtle heading of the normal pointing left of path travel at normalized arc-length parameter `t`. Pure and drawless.",
        "completion": { "kind": "text", "text": "normalat(${1:path}, ${2:t})" },
        "isSnippet": true,
        "signatures": [["path", "t"]]
      }
    },
    {
      "id": "paramof",
      "label": "paramof",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Project a point to the nearest location on an open path and return its normalized arc-length parameter in 0…1. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "nearest normalized path parameter",
        "documentation": "Project a point to the nearest location on an open path and return its normalized arc-length parameter in 0…1. Pure and drawless.",
        "completion": { "kind": "text", "text": "paramof(${1:point}, ${2:path})" },
        "isSnippet": true,
        "signatures": [["point", "path"]]
      }
    },
    {
      "id": "paramtomm",
      "label": "paramtomm",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Convert normalized arc-length parameter `t` to millimetres along a path. The parameter is clamped to 0…1. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "normalized parameter to millimetres",
        "documentation": "Convert normalized arc-length parameter `t` to millimetres along a path. The parameter is clamped to 0…1. Pure and drawless.",
        "completion": { "kind": "text", "text": "paramtomm(${1:path}, ${2:t})" },
        "isSnippet": true,
        "signatures": [["path", "t"]]
      }
    },
    {
      "id": "mmtoparam",
      "label": "mmtoparam",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Convert a distance in millimetres along a path to normalized arc-length parameter 0…1. Distance is clamped to the path length. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "millimetres to normalized parameter",
        "documentation": "Convert a distance in millimetres along a path to normalized arc-length parameter 0…1. Distance is clamped to the path length. Pure and drawless.",
        "completion": { "kind": "text", "text": "mmtoparam(${1:path}, ${2:mm})" },
        "isSnippet": true,
        "signatures": [["path", "mm"]]
      }
    },
    {
      "id": "subpath",
      "label": "subpath",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return the shape-preserving open subpath between normalized arc-length parameters `a` and `b`, including interpolated boundary points. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "extract a normalized path interval",
        "documentation": "Return the shape-preserving open subpath between normalized arc-length parameters `a` and `b`, including interpolated boundary points. Pure and drawless.",
        "completion": { "kind": "text", "text": "subpath(${1:path}, ${2:a}, ${3:b})" },
        "isSnippet": true,
        "signatures": [["path", "a", "b"]]
      }
    },
    {
      "id": "splitat",
      "label": "splitat",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return two shape-preserving subpaths split at normalized arc-length parameter `t`. The shared split point ends the first and starts the second. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "split a path at a parameter",
        "documentation": "Return two shape-preserving subpaths split at normalized arc-length parameter `t`. The shared split point ends the first and starts the second. Pure and drawless.",
        "completion": { "kind": "text", "text": "splitat(${1:path}, ${2:t})" },
        "isSnippet": true,
        "signatures": [["path", "t"]]
      }
    },
    {
      "id": "insertvertex",
      "label": "insertvertex",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return a path with a vertex inserted at normalized arc-length parameter `t` without changing the represented polyline shape. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "insert a vertex at a parameter",
        "documentation": "Return a path with a vertex inserted at normalized arc-length parameter `t` without changing the represented polyline shape. Pure and drawless.",
        "completion": { "kind": "text", "text": "insertvertex(${1:path}, ${2:t})" },
        "isSnippet": true,
        "signatures": [["path", "t"]]
      }
    },
    {
      "id": "dashes",
      "label": "dashes",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return open dash fragments using repeating on/off lengths in millimetres. An optional phase enters the cycle; lengths must be non-negative with a positive sum. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "split a path into dash fragments",
        "documentation": "Return open dash fragments using repeating on/off lengths in millimetres. An optional phase enters the cycle; lengths must be non-negative with a positive sum. Pure and drawless.",
        "completion": { "kind": "text", "text": "dashes(${1:path}, ${2:onmm}, ${3:offmm})" },
        "isSnippet": true,
        "signatures": [
          ["path", "onmm", "offmm"],
          ["path", "onmm", "offmm", "phasemm"]
        ]
      }
    },
    {
      "id": "pathisectparams",
      "label": "pathisectparams",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return path intersections as `[point, ta, tb]`, where `ta` and `tb` are normalized arc-length parameters on the two input paths. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "path intersection points and parameters",
        "documentation": "Return path intersections as `[point, ta, tb]`, where `ta` and `tb` are normalized arc-length parameters on the two input paths. Pure and drawless.",
        "completion": { "kind": "text", "text": "pathisectparams(${1:a}, ${2:b})" },
        "isSnippet": true,
        "signatures": [["a", "b"]]
      }
    },
    {
      "id": "pathselfisects",
      "label": "pathselfisects",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return non-adjacent self-intersections as `[point, ta, tb]` with normalized arc-length parameters on the input path. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "path self-intersections and parameters",
        "documentation": "Return non-adjacent self-intersections as `[point, ta, tb]` with normalized arc-length parameters on the input path. Pure and drawless.",
        "completion": { "kind": "text", "text": "pathselfisects(${1:path})" },
        "isSnippet": true,
        "signatures": [["path"]]
      }
    },
    {
      "id": "joinpaths",
      "label": "joinpaths",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Deterministically weld fragment endpoints within `tolerance` millimetres. Closed chains become canonical rings; the result is a list of paths. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "join nearby path endpoints",
        "documentation": "Deterministically weld fragment endpoints within `tolerance` millimetres. Closed chains become canonical rings; the result is a list of paths. Pure and drawless.",
        "completion": { "kind": "text", "text": "joinpaths(${1:fragments}, ${2:tolerance})" },
        "isSnippet": true,
        "signatures": [["fragments", "tolerance"]]
      }
    },
    {
      "id": "ispoint",
      "label": "ispoint",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return `1` only for a two-number finite point `[x, y]`; return `0` for every other value without throwing. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "test point structure",
        "documentation": "Return `1` only for a two-number finite point `[x, y]`; return `0` for every other value without throwing. Pure and drawless.",
        "completion": { "kind": "text", "text": "ispoint(${1:value})" },
        "isSnippet": true,
        "signatures": [["value"]]
      }
    },
    {
      "id": "ispath",
      "label": "ispath",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return `1` only for a list of at least two finite points; return `0` for every other value without throwing. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "test path structure",
        "documentation": "Return `1` only for a list of at least two finite points; return `0` for every other value without throwing. Pure and drawless.",
        "completion": { "kind": "text", "text": "ispath(${1:value})" },
        "isSnippet": true,
        "signatures": [["value"]]
      }
    },
    {
      "id": "iscurvespec",
      "label": "iscurvespec",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return `1` when a value is a valid editable cubic curve specification; return `0` instead of throwing on malformed input. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "test editable curve structure",
        "documentation": "Return `1` when a value is a valid editable cubic curve specification; return `0` instead of throwing on malformed input. Pure and drawless.",
        "completion": { "kind": "text", "text": "iscurvespec(${1:value})" },
        "isSnippet": true,
        "signatures": [["value"]]
      }
    },
    {
      "id": "strokepath",
      "label": "strokepath",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return canonical outline regions for a path stroke of `width` millimetres. Optional caps are `'round'`, `'butt'`, or `'square'`; joins are `'round'`, `'miter'`, or `'bevel'`. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "stroke a path into regions",
        "documentation": "Return canonical outline regions for a path stroke of `width` millimetres. Optional caps are `'round'`, `'butt'`, or `'square'`; joins are `'round'`, `'miter'`, or `'bevel'`. Pure and drawless.",
        "completion": { "kind": "text", "text": "strokepath(${1:path}, ${2:width})" },
        "isSnippet": true,
        "signatures": [
          ["path", "width"],
          ["path", "width", "cap"],
          ["path", "width", "cap", "join"]
        ]
      }
    },
    {
      "id": "clipopen",
      "label": "clipopen",
      "category": "paths-curves",
      "tags": [
        "call-syntax",
        "embroidery",
        "function",
        "geometry",
        "library",
        "paths-curves",
        "pure"
      ],
      "summary": "Return open fragments of a path inside a compound even-odd region, or outside it when optional mode is `'outside'`. Pure and drawless.",
      "editor": {
        "kind": "function",
        "detail": "clip an open path to a region",
        "documentation": "Return open fragments of a path inside a compound even-odd region, or outside it when optional mode is `'outside'`. Pure and drawless.",
        "completion": { "kind": "text", "text": "clipopen(${1:path}, ${2:region})" },
        "isSnippet": true,
        "signatures": [
          ["path", "region"],
          ["path", "region", "mode"]
        ]
      }
    }
  ],
  "standardLibrary": {
    "modules": [
      {
        "id": "std.mathx",
        "title": "std.mathx",
        "order": 1,
        "description": "extended mathematics",
        "purpose": "Easing, angles, vectors, remapping, and deterministic random helpers",
        "tags": ["mathx", "standard-library"],
        "emitsStitches": "never",
        "rngDraws": "Only randbetween, randint, chance, weightedpick, and jitterpt draw (1, 1, 1, 1, and 2).",
        "groups": [
          {
            "id": "easing-and-waveforms",
            "title": "Easing and waveforms",
            "order": 1,
            "tags": ["mathx", "easing-and-waveforms", "standard-library"],
            "procedureIds": [
              "std.mathx.easein",
              "std.mathx.easeout",
              "std.mathx.easeinout",
              "std.mathx.easeback",
              "std.mathx.easepow",
              "std.mathx.triwave",
              "std.mathx.pulse"
            ]
          },
          {
            "id": "angles-vectors-and-remapping",
            "title": "Angles, vectors, and remapping",
            "order": 2,
            "tags": ["mathx", "angles-vectors-and-remapping", "standard-library"],
            "procedureIds": [
              "std.mathx.wrapdeg",
              "std.mathx.angdiff",
              "std.mathx.lerpheading",
              "std.mathx.vperp",
              "std.mathx.vproj",
              "std.mathx.vreflect",
              "std.mathx.remapc"
            ]
          },
          {
            "id": "deterministic-randomness",
            "title": "Deterministic randomness",
            "order": 3,
            "tags": ["mathx", "deterministic-randomness", "standard-library"],
            "procedureIds": [
              "std.mathx.randbetween",
              "std.mathx.randint",
              "std.mathx.chance",
              "std.mathx.weightedpick",
              "std.mathx.jitterpt"
            ]
          }
        ]
      },
      {
        "id": "std.listx",
        "title": "std.listx",
        "order": 2,
        "description": "higher-level list operations",
        "purpose": "Sorting, selection, reshaping, and predicate-based list operations",
        "tags": ["listx", "standard-library"],
        "emitsStitches": "never",
        "rngDraws": "Only when a supplied callback draws.",
        "groups": [
          {
            "id": "procedures",
            "title": "Procedures",
            "order": 1,
            "tags": ["listx", "procedures", "standard-library"],
            "procedureIds": [
              "std.listx.sortby",
              "std.listx.argmin",
              "std.listx.argmax",
              "std.listx.pairwise",
              "std.listx.zip",
              "std.listx.flatten",
              "std.listx.unique",
              "std.listx.chunk",
              "std.listx.rotatedlist",
              "std.listx.countif"
            ]
          }
        ]
      },
      {
        "id": "std.shapes",
        "title": "std.shapes",
        "order": 3,
        "description": "centered path constructors",
        "purpose": "Centered closed and open path constructors",
        "tags": ["shapes", "standard-library"],
        "emitsStitches": "never",
        "rngDraws": "None.",
        "groups": [
          {
            "id": "procedures",
            "title": "Procedures",
            "order": 1,
            "tags": ["shapes", "procedures", "standard-library"],
            "procedureIds": [
              "std.shapes.polypath",
              "std.shapes.starpath",
              "std.shapes.rectpath",
              "std.shapes.roundrect",
              "std.shapes.ellipsepath",
              "std.shapes.arcpath",
              "std.shapes.coilpath",
              "std.shapes.heartpath",
              "std.shapes.gearpath",
              "std.shapes.superellipsepath",
              "std.shapes.wavepath",
              "std.shapes.rosepath",
              "std.shapes.lissajouspath"
            ]
          }
        ]
      },
      {
        "id": "std.pathops",
        "title": "std.pathops",
        "order": 4,
        "description": "polyline queries and operations",
        "purpose": "Arc-length queries and polyline transformations",
        "tags": ["pathops", "standard-library"],
        "emitsStitches": "never",
        "rngDraws": "None.",
        "groups": [
          {
            "id": "procedures",
            "title": "Procedures",
            "order": 1,
            "tags": ["pathops", "procedures", "standard-library"],
            "procedureIds": [
              "std.pathops.pointat",
              "std.pathops.headingat",
              "std.pathops.paramof",
              "std.pathops.subpath",
              "std.pathops.dashes",
              "std.pathops.simplifypath",
              "std.pathops.smoothclosed",
              "std.pathops.morphpaths",
              "std.pathops.pathisects",
              "std.pathops.offsetopen"
            ]
          }
        ]
      },
      {
        "id": "std.regions",
        "title": "std.regions",
        "order": 5,
        "description": "region analysis and subdivision",
        "purpose": "Region measurement, tiling, insets, and partitions",
        "tags": ["regions", "standard-library"],
        "emitsStitches": "never",
        "rngDraws": "partitions draws exactly 1; all other exports draw none.",
        "groups": [
          {
            "id": "procedures",
            "title": "Procedures",
            "order": 1,
            "tags": ["regions", "procedures", "standard-library"],
            "procedureIds": [
              "std.regions.regionarea",
              "std.regions.poleof",
              "std.regions.insetrings",
              "std.regions.tilecells",
              "std.regions.gridpoints",
              "std.regions.partitions"
            ]
          }
        ]
      },
      {
        "id": "std.layout",
        "title": "std.layout",
        "order": 6,
        "description": "motif placements and fitting",
        "purpose": "Point/heading layouts and uniform path fitting",
        "tags": ["layout", "standard-library"],
        "emitsStitches": "never",
        "rngDraws": "None.",
        "groups": [
          {
            "id": "procedures",
            "title": "Procedures",
            "order": 1,
            "tags": ["layout", "procedures", "standard-library"],
            "procedureIds": [
              "std.layout.circlelayout",
              "std.layout.gridlayout",
              "std.layout.alongpath",
              "std.layout.fitpath"
            ]
          }
        ]
      },
      {
        "id": "std.textures",
        "title": "std.textures",
        "order": 7,
        "description": "fill callbacks and clipped texture paths",
        "purpose": "Direction/shape callbacks and clipped fill paths",
        "tags": ["textures", "standard-library"],
        "emitsStitches": "never",
        "rngDraws": "None; seeded simplex fields do not advance the main stream.",
        "groups": [
          {
            "id": "direction-reporters",
            "title": "Direction reporters",
            "order": 1,
            "tags": ["textures", "direction-reporters", "standard-library"],
            "procedureIds": [
              "std.textures.radialdir",
              "std.textures.griddir",
              "std.textures.radialdirfrom",
              "std.textures.curldir",
              "std.textures.curldirwith"
            ]
          },
          {
            "id": "fill-shape-reporters",
            "title": "Fill-shape reporters",
            "order": 2,
            "tags": ["textures", "fill-shape-reporters", "standard-library"],
            "procedureIds": [
              "std.textures.wovenshape",
              "std.textures.gradientshape",
              "std.textures.gradientshapewith"
            ]
          },
          {
            "id": "geometric-texture-paths",
            "title": "Geometric texture paths",
            "order": 3,
            "tags": ["textures", "geometric-texture-paths", "standard-library"],
            "procedureIds": [
              "std.textures.hilbertpaths",
              "std.textures.truchetpaths",
              "std.textures.hitomezashi",
              "std.textures.seigaiha",
              "std.textures.asanoha",
              "std.textures.herringbonepaths"
            ]
          }
        ]
      },
      {
        "id": "std.stitchcraft",
        "title": "std.stitchcraft",
        "order": 8,
        "description": "sewing procedures",
        "purpose": "Reusable embroidery construction and geometry rituals",
        "tags": ["stitchcraft", "standard-library"],
        "emitsStitches": "usually",
        "rngDraws": "stipple draws exactly 1; all other exports draw none unless a callback draws.",
        "groups": [
          {
            "id": "procedures",
            "title": "Procedures",
            "order": 1,
            "tags": ["stitchcraft", "procedures", "standard-library"],
            "procedureIds": [
              "std.stitchcraft.sewrun",
              "std.stitchcraft.satinalong",
              "std.stitchcraft.beanoutline",
              "std.stitchcraft.appliquesteps",
              "std.stitchcraft.appliquewith",
              "std.stitchcraft.eyelet",
              "std.stitchcraft.fillbordergeometry",
              "std.stitchcraft.fillandborder",
              "std.stitchcraft.fillandborderwith",
              "std.stitchcraft.gradientbands",
              "std.stitchcraft.gradientrows",
              "std.stitchcraft.gradientrowsn",
              "std.stitchcraft.serpentinerows",
              "std.stitchcraft.knockdown",
              "std.stitchcraft.threadblend",
              "std.stitchcraft.stipple"
            ]
          },
          {
            "id": "density-neutral-gradient-rows",
            "title": "Density-neutral gradient rows",
            "order": 2,
            "tags": ["stitchcraft", "density-neutral-gradient-rows", "standard-library"],
            "procedureIds": []
          },
          {
            "id": "production-construction-recipes",
            "title": "Production construction recipes",
            "order": 3,
            "tags": ["stitchcraft", "production-construction-recipes", "standard-library"],
            "procedureIds": []
          }
        ]
      },
      {
        "id": "std.debugx",
        "title": "std.debugx",
        "order": 9,
        "description": "preview and stitch diagnostics",
        "purpose": "Chalk overlays and stitch-history diagnostics",
        "tags": ["debugx", "standard-library"],
        "emitsStitches": "never",
        "rngDraws": "None.",
        "groups": [
          {
            "id": "procedures",
            "title": "Procedures",
            "order": 1,
            "tags": ["debugx", "procedures", "standard-library"],
            "procedureIds": [
              "std.debugx.chalkgrid",
              "std.debugx.chalkbbox",
              "std.debugx.chalkfield",
              "std.debugx.threadestimate",
              "std.debugx.coverprofile"
            ]
          }
        ]
      }
    ],
    "procedures": [
      {
        "id": "std.mathx.easein",
        "moduleId": "std.mathx",
        "name": "easein",
        "params": ["t"],
        "group": "easing-and-waveforms",
        "tags": ["easing-and-waveforms", "mathx", "standard-library"],
        "summary": "Quadratic ease-in, `u²`, after clamping `t` to 0…1.",
        "documentation": "`easein(t) -> number`. Quadratic ease-in, `u²`, after clamping `t` to 0…1."
      },
      {
        "id": "std.mathx.easeout",
        "moduleId": "std.mathx",
        "name": "easeout",
        "params": ["t"],
        "group": "easing-and-waveforms",
        "tags": ["easing-and-waveforms", "mathx", "standard-library"],
        "summary": "Quadratic ease-out, `1 - (1-u)²`, with clamped input.",
        "documentation": "`easeout(t) -> number`. Quadratic ease-out, `1 - (1-u)²`, with clamped input."
      },
      {
        "id": "std.mathx.easeinout",
        "moduleId": "std.mathx",
        "name": "easeinout",
        "params": ["t"],
        "group": "easing-and-waveforms",
        "tags": ["easing-and-waveforms", "geometry", "mathx", "standard-library"],
        "summary": "Symmetric quadratic ease-in/out with clamped input; its midpoint is 0.5.",
        "documentation": "`easeinout(t) -> number`. Symmetric quadratic ease-in/out with clamped input; its midpoint is 0.5."
      },
      {
        "id": "std.mathx.easeback",
        "moduleId": "std.mathx",
        "name": "easeback",
        "params": ["t"],
        "group": "easing-and-waveforms",
        "tags": ["easing-and-waveforms", "mathx", "standard-library"],
        "summary": "Back-ease curve using overshoot constant 1.70158. Input is clamped, but the curve itself dips below 0 near the start.",
        "documentation": "`easeback(t) -> number`. Back-ease curve using overshoot constant 1.70158. Input is clamped, but the curve itself dips below 0 near the start."
      },
      {
        "id": "std.mathx.easepow",
        "moduleId": "std.mathx",
        "name": "easepow",
        "params": ["power"],
        "group": "easing-and-waveforms",
        "tags": ["easing-and-waveforms", "higher-order", "mathx", "standard-library"],
        "summary": "Returns a configured one-argument reporter equivalent to `pow(clamp(t, 0, 1), power)`. Use directly as `easepow(3)(0.5)` or pass the returned reference to another reporter.",
        "documentation": "`easepow(power) -> reference`. Returns a configured one-argument reporter equivalent to `pow(clamp(t, 0, 1), power)`. Use directly as `easepow(3)(0.5)` or pass the returned reference to another reporter."
      },
      {
        "id": "std.mathx.triwave",
        "moduleId": "std.mathx",
        "name": "triwave",
        "params": ["t"],
        "group": "easing-and-waveforms",
        "tags": ["easing-and-waveforms", "mathx", "standard-library"],
        "summary": "Period-1 triangle wave: −1 at integer boundaries, 0 at quarter periods, and 1 at half periods. Negative `t` wraps with floor modulo.",
        "documentation": "`triwave(t) -> number`. Period-1 triangle wave: −1 at integer boundaries, 0 at quarter periods, and 1 at half periods. Negative `t` wraps with floor modulo."
      },
      {
        "id": "std.mathx.pulse",
        "moduleId": "std.mathx",
        "name": "pulse",
        "params": ["t", "duty"],
        "group": "easing-and-waveforms",
        "tags": ["easing-and-waveforms", "mathx", "standard-library"],
        "summary": "Period-1 pulse. Returns 1 while the wrapped phase is below `clamp(duty, 0, 1)`.",
        "documentation": "`pulse(t, duty) -> 0 or 1`. Period-1 pulse. Returns 1 while the wrapped phase is below `clamp(duty, 0, 1)`."
      },
      {
        "id": "std.mathx.wrapdeg",
        "moduleId": "std.mathx",
        "name": "wrapdeg",
        "params": ["d"],
        "group": "angles-vectors-and-remapping",
        "tags": ["angles-vectors-and-remapping", "mathx", "standard-library"],
        "summary": "Wraps an angle into 0…360, excluding 360.",
        "documentation": "`wrapdeg(d) -> number`. Wraps an angle into 0…360, excluding 360."
      },
      {
        "id": "std.mathx.angdiff",
        "moduleId": "std.mathx",
        "name": "angdiff",
        "params": ["a", "b"],
        "group": "angles-vectors-and-remapping",
        "tags": ["angles-vectors-and-remapping", "mathx", "standard-library"],
        "summary": "Shortest signed rotation from `a` to `b`, in −180…180, excluding +180. Positive is clockwise.",
        "documentation": "`angdiff(a, b) -> number`. Shortest signed rotation from `a` to `b`, in −180…180, excluding +180. Positive is clockwise."
      },
      {
        "id": "std.mathx.lerpheading",
        "moduleId": "std.mathx",
        "name": "lerpheading",
        "params": ["a", "b", "t"],
        "group": "angles-vectors-and-remapping",
        "tags": ["angles-vectors-and-remapping", "mathx", "standard-library"],
        "summary": "Interpolates along the shortest angular route and wraps the result. `t` is not clamped.",
        "documentation": "`lerpheading(a, b, t) -> number`. Interpolates along the shortest angular route and wraps the result. `t` is not clamped."
      },
      {
        "id": "std.mathx.vperp",
        "moduleId": "std.mathx",
        "name": "vperp",
        "params": ["v"],
        "group": "angles-vectors-and-remapping",
        "tags": ["angles-vectors-and-remapping", "geometry", "mathx", "standard-library"],
        "summary": "Returns `[-v[1], v[0]]`, a 90° mathematical counter-clockwise perpendicular in Cartesian coordinates.",
        "documentation": "`vperp(v) -> point`. Returns `[-v[1], v[0]]`, a 90° mathematical counter-clockwise perpendicular in Cartesian coordinates."
      },
      {
        "id": "std.mathx.vproj",
        "moduleId": "std.mathx",
        "name": "vproj",
        "params": ["a", "b"],
        "group": "angles-vectors-and-remapping",
        "tags": ["angles-vectors-and-remapping", "geometry", "mathx", "standard-library"],
        "summary": "Projects vector `a` onto `b`. Returns `[0, 0]` when `b` has near-zero squared length.",
        "documentation": "`vproj(a, b) -> point`. Projects vector `a` onto `b`. Returns `[0, 0]` when `b` has near-zero squared length."
      },
      {
        "id": "std.mathx.vreflect",
        "moduleId": "std.mathx",
        "name": "vreflect",
        "params": ["v", "n"],
        "group": "angles-vectors-and-remapping",
        "tags": ["angles-vectors-and-remapping", "geometry", "mathx", "standard-library"],
        "summary": "Reflects `v` across the line whose normal is `n`. `n` need not be normalized; a near-zero normal returns a copy of `v`.",
        "documentation": "`vreflect(v, n) -> point`. Reflects `v` across the line whose normal is `n`. `n` need not be normalized; a near-zero normal returns a copy of `v`."
      },
      {
        "id": "std.mathx.remapc",
        "moduleId": "std.mathx",
        "name": "remapc",
        "params": ["v", "inlo", "inhi", "outlo", "outhi"],
        "group": "angles-vectors-and-remapping",
        "tags": ["angles-vectors-and-remapping", "mathx", "standard-library"],
        "summary": "Clamped linear remap. Reversed input/output ranges work. A near-zero input span returns `outlo`.",
        "documentation": "`remapc(v, inlo, inhi, outlo, outhi) -> number`. Clamped linear remap. Reversed input/output ranges work. A near-zero input span returns `outlo`."
      },
      {
        "id": "std.mathx.randbetween",
        "moduleId": "std.mathx",
        "name": "randbetween",
        "params": ["a", "b"],
        "group": "deterministic-randomness",
        "tags": ["deterministic-randomness", "mathx", "rng", "standard-library"],
        "summary": "Uniform value starting at `a` with span `b-a`; consumes **1 draw**. Reversed bounds therefore work.",
        "documentation": "`randbetween(a, b) -> number`. Uniform value starting at `a` with span `b-a`; consumes **1 draw**. Reversed bounds therefore work."
      },
      {
        "id": "std.mathx.randint",
        "moduleId": "std.mathx",
        "name": "randint",
        "params": ["a", "b"],
        "group": "deterministic-randomness",
        "tags": ["deterministic-randomness", "mathx", "rng", "standard-library"],
        "summary": "Uniform inclusive integer between `ceil(min(a,b))` and `floor(max(a,b))`; normally consumes **1 draw**. If the bounds contain no integer, returns the rounded lower bound without drawing.",
        "documentation": "`randint(a, b) -> integer`. Uniform inclusive integer between `ceil(min(a,b))` and `floor(max(a,b))`; normally consumes **1 draw**. If the bounds contain no integer, returns the rounded lower bound without drawing."
      },
      {
        "id": "std.mathx.chance",
        "moduleId": "std.mathx",
        "name": "chance",
        "params": ["p"],
        "group": "deterministic-randomness",
        "tags": ["deterministic-randomness", "mathx", "rng", "standard-library"],
        "summary": "Bernoulli trial with `p` clamped to 0…1; consumes **1 draw** even at probabilities 0 and 1.",
        "documentation": "`chance(p) -> 0 or 1`. Bernoulli trial with `p` clamped to 0…1; consumes **1 draw** even at probabilities 0 and 1."
      },
      {
        "id": "std.mathx.weightedpick",
        "moduleId": "std.mathx",
        "name": "weightedpick",
        "params": ["xs", "ws"],
        "group": "deterministic-randomness",
        "tags": ["deterministic-randomness", "mathx", "rng", "standard-library"],
        "summary": "Selects from `xs` in order using cumulative weights; consumes **1 draw**. Supply a non-empty `xs`, an equally long `ws`, non-negative weights, and a positive total.",
        "documentation": "`weightedpick(xs, ws) -> value`. Selects from `xs` in order using cumulative weights; consumes **1 draw**. Supply a non-empty `xs`, an equally long `ws`, non-negative weights, and a positive total."
      },
      {
        "id": "std.mathx.jitterpt",
        "moduleId": "std.mathx",
        "name": "jitterpt",
        "params": ["p", "mm"],
        "group": "deterministic-randomness",
        "tags": ["deterministic-randomness", "geometry", "mathx", "rng", "standard-library"],
        "summary": "Independently offsets both coordinates uniformly within `[-mm, mm)`; consumes **2 draws**. Use non-negative `mm`.",
        "documentation": "`jitterpt(p, mm) -> point`. Independently offsets both coordinates uniformly within `[-mm, mm)`; consumes **2 draws**. Use non-negative `mm`."
      },
      {
        "id": "std.listx.sortby",
        "moduleId": "std.listx",
        "name": "sortby",
        "params": ["xs", "keyfn"],
        "group": "procedures",
        "tags": ["listx", "procedures", "standard-library"],
        "summary": "Returns a new list in ascending key order. Computes every key once and leaves `xs` unchanged. Equal-key items keep their original order.",
        "documentation": "`sortby(xs, keyfn) -> list`. Returns a new list in ascending key order. Computes every key once and leaves `xs` unchanged. Equal-key items keep their original order."
      },
      {
        "id": "std.listx.argmin",
        "moduleId": "std.listx",
        "name": "argmin",
        "params": ["xs", "keyfn"],
        "group": "procedures",
        "tags": ["listx", "procedures", "standard-library"],
        "summary": "Returns the first item with the smallest computed key. Keys are computed once. `xs` must be non-empty.",
        "documentation": "`argmin(xs, keyfn) -> value`. Returns the first item with the smallest computed key. Keys are computed once. `xs` must be non-empty."
      },
      {
        "id": "std.listx.argmax",
        "moduleId": "std.listx",
        "name": "argmax",
        "params": ["xs", "keyfn"],
        "group": "procedures",
        "tags": ["listx", "procedures", "standard-library"],
        "summary": "Returns the first item with the largest computed key. Keys are computed once. `xs` must be non-empty.",
        "documentation": "`argmax(xs, keyfn) -> value`. Returns the first item with the largest computed key. Keys are computed once. `xs` must be non-empty."
      },
      {
        "id": "std.listx.pairwise",
        "moduleId": "std.listx",
        "name": "pairwise",
        "params": ["xs"],
        "group": "procedures",
        "tags": ["listx", "procedures", "standard-library"],
        "summary": "Returns adjacent pairs: `[a,b,c]` becomes `[[a,b],[b,c]]`. Lists shorter than two produce `[]`.",
        "documentation": "`pairwise(xs) -> list`. Returns adjacent pairs: `[a,b,c]` becomes `[[a,b],[b,c]]`. Lists shorter than two produce `[]`."
      },
      {
        "id": "std.listx.zip",
        "moduleId": "std.listx",
        "name": "zip",
        "params": ["a", "b"],
        "group": "procedures",
        "tags": ["listx", "procedures", "standard-library"],
        "summary": "Pairs items at matching indices and stops at the shorter input.",
        "documentation": "`zip(a, b) -> list`. Pairs items at matching indices and stops at the shorter input."
      },
      {
        "id": "std.listx.flatten",
        "moduleId": "std.listx",
        "name": "flatten",
        "params": ["xs"],
        "group": "procedures",
        "tags": ["listx", "procedures", "standard-library"],
        "summary": "Recursively removes all nested list structure while preserving left-to-right leaf order. Empty nested lists contribute nothing.",
        "documentation": "`flatten(xs) -> list`. Recursively removes all nested list structure while preserving left-to-right leaf order. Empty nested lists contribute nothing."
      },
      {
        "id": "std.listx.unique",
        "moduleId": "std.listx",
        "name": "unique",
        "params": ["xs"],
        "group": "procedures",
        "tags": ["listx", "procedures", "standard-library"],
        "summary": "Removes later duplicates and preserves first occurrence order. Equality follows NeedleScript's deep, tolerant equality rules.",
        "documentation": "`unique(xs) -> list`. Removes later duplicates and preserves first occurrence order. Equality follows NeedleScript's deep, tolerant equality rules."
      },
      {
        "id": "std.listx.chunk",
        "moduleId": "std.listx",
        "name": "chunk",
        "params": ["xs", "n"],
        "group": "procedures",
        "tags": ["listx", "procedures", "standard-library"],
        "summary": "Splits `xs` into consecutive chunks. The width is `max(1, floor(n))`; the last chunk may be shorter.",
        "documentation": "`chunk(xs, n) -> list`. Splits `xs` into consecutive chunks. The width is `max(1, floor(n))`; the last chunk may be shorter."
      },
      {
        "id": "std.listx.rotatedlist",
        "moduleId": "std.listx",
        "name": "rotatedlist",
        "params": ["xs", "n"],
        "group": "procedures",
        "tags": ["listx", "procedures", "standard-library"],
        "summary": "Returns a new list rotated left by `round(n)` places. Negative values rotate right. Empty input returns `[]`.",
        "documentation": "`rotatedlist(xs, n) -> list`. Returns a new list rotated left by `round(n)` places. Negative values rotate right. Empty input returns `[]`."
      },
      {
        "id": "std.listx.countif",
        "moduleId": "std.listx",
        "name": "countif",
        "params": ["xs", "predfn"],
        "group": "procedures",
        "tags": ["listx", "procedures", "standard-library"],
        "summary": "Counts items for which the predicate returns non-zero. It has the same predicate requirements as the core `filter`.",
        "documentation": "`countif(xs, predfn) -> number`. Counts items for which the predicate returns non-zero. It has the same predicate requirements as the core `filter`."
      },
      {
        "id": "std.shapes.polypath",
        "moduleId": "std.shapes",
        "name": "polypath",
        "params": ["n", "r"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "shapes", "standard-library"],
        "summary": "Regular polygon of radius `r`. Vertex count is `max(3, round(n))`; result length is vertices + 1.",
        "documentation": "`polypath(n, r) -> closed path`. Regular polygon of radius `r`. Vertex count is `max(3, round(n))`; result length is vertices + 1."
      },
      {
        "id": "std.shapes.starpath",
        "moduleId": "std.shapes",
        "name": "starpath",
        "params": ["n", "rout", "rin"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "shapes", "standard-library"],
        "summary": "Alternating outer/inner radii with `max(2, round(n))` points of each kind.",
        "documentation": "`starpath(n, rout, rin) -> closed path`. Alternating outer/inner radii with `max(2, round(n))` points of each kind."
      },
      {
        "id": "std.shapes.rectpath",
        "moduleId": "std.shapes",
        "name": "rectpath",
        "params": ["w", "h"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "shapes", "standard-library"],
        "summary": "Axis-aligned rectangle of width `w` and height `h`, beginning at the top-edge midpoint.",
        "documentation": "`rectpath(w, h) -> closed path`. Axis-aligned rectangle of width `w` and height `h`, beginning at the top-edge midpoint."
      },
      {
        "id": "std.shapes.roundrect",
        "moduleId": "std.shapes",
        "name": "roundrect",
        "params": ["w", "h", "r"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "shapes", "standard-library"],
        "summary": "Rounded rectangle with nine samples per corner. Radius is `abs(r)` clamped to half the smaller absolute dimension.",
        "documentation": "`roundrect(w, h, r) -> closed path`. Rounded rectangle with nine samples per corner. Radius is `abs(r)` clamped to half the smaller absolute dimension."
      },
      {
        "id": "std.shapes.ellipsepath",
        "moduleId": "std.shapes",
        "name": "ellipsepath",
        "params": ["rx", "ry"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "shapes", "standard-library"],
        "summary": "Ellipse with 64 perimeter samples; `rx` and `ry` are horizontal and vertical radii.",
        "documentation": "`ellipsepath(rx, ry) -> closed path`. Ellipse with 64 perimeter samples; `rx` and `ry` are horizontal and vertical radii."
      },
      {
        "id": "std.shapes.arcpath",
        "moduleId": "std.shapes",
        "name": "arcpath",
        "params": ["deg", "r"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "shapes", "standard-library"],
        "summary": "Circular arc starting north, sampled at no more than 6° per segment. Positive `deg` progresses counter-clockwise; negative progresses clockwise.",
        "documentation": "`arcpath(deg, r) -> open path`. Circular arc starting north, sampled at no more than 6° per segment. Positive `deg` progresses counter-clockwise; negative progresses clockwise."
      },
      {
        "id": "std.shapes.coilpath",
        "moduleId": "std.shapes",
        "name": "coilpath",
        "params": ["turns", "r0", "r1"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "shapes", "standard-library"],
        "summary": "Spiral whose radius linearly changes from `r0` to `r1`, with 72 segments per absolute turn. Positive turns progress counter-clockwise.",
        "documentation": "`coilpath(turns, r0, r1) -> open path`. Spiral whose radius linearly changes from `r0` to `r1`, with 72 segments per absolute turn. Positive turns progress counter-clockwise."
      },
      {
        "id": "std.shapes.heartpath",
        "moduleId": "std.shapes",
        "name": "heartpath",
        "params": ["size"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "shapes", "standard-library"],
        "summary": "Parametric heart with 96 samples. Overall scale is controlled by `size`; the first point is on the north-west lobe.",
        "documentation": "`heartpath(size) -> closed path`. Parametric heart with 96 samples. Overall scale is controlled by `size`; the first point is on the north-west lobe."
      },
      {
        "id": "std.shapes.gearpath",
        "moduleId": "std.shapes",
        "name": "gearpath",
        "params": ["teeth", "r", "depth"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "shapes", "standard-library"],
        "summary": "Four vertices per tooth, alternating two outer points at `r` and two root points at `max(0, r-depth)`. Uses at least three teeth.",
        "documentation": "`gearpath(teeth, r, depth) -> closed path`. Four vertices per tooth, alternating two outer points at `r` and two root points at `max(0, r-depth)`. Uses at least three teeth."
      },
      {
        "id": "std.shapes.superellipsepath",
        "moduleId": "std.shapes",
        "name": "superellipsepath",
        "params": ["w", "h", "e"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "shapes", "standard-library"],
        "summary": "96-sample superellipse within `w × h`. Exponent `e` is floored at 0.01 before deriving the signed-power curve.",
        "documentation": "`superellipsepath(w, h, e) -> closed path`. 96-sample superellipse within `w × h`. Exponent `e` is floored at 0.01 before deriving the signed-power curve."
      },
      {
        "id": "std.shapes.wavepath",
        "moduleId": "std.shapes",
        "name": "wavepath",
        "params": ["length", "amp", "cycles"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "shapes", "standard-library"],
        "summary": "Horizontal sine wave from `-length/2` to `length/2`, with 24 segments per absolute cycle. Negative cycles reverse phase progression.",
        "documentation": "`wavepath(length, amp, cycles) -> open path`. Horizontal sine wave from `-length/2` to `length/2`, with 24 segments per absolute cycle. Negative cycles reverse phase progression."
      },
      {
        "id": "std.shapes.rosepath",
        "moduleId": "std.shapes",
        "name": "rosepath",
        "params": ["k", "r"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "shapes", "standard-library"],
        "summary": "Polar rose `radius = cos(k × angle) × r`, sampled with at least 72 points and 72 per absolute `k`. Integer `k` produces the expected closed rose.",
        "documentation": "`rosepath(k, r) -> closed path`. Polar rose `radius = cos(k × angle) × r`, sampled with at least 72 points and 72 per absolute `k`. Integer `k` produces the expected closed rose."
      },
      {
        "id": "std.shapes.lissajouspath",
        "moduleId": "std.shapes",
        "name": "lissajouspath",
        "params": ["a", "b", "phase", "size"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "shapes", "standard-library"],
        "summary": "Lissajous curve with x phase in degrees, within a square of side `size`; sample count is at least 96 and scales with `max(abs(a), abs(b))`.",
        "documentation": "`lissajouspath(a, b, phase, size) -> closed path`. Lissajous curve with x phase in degrees, within a square of side `size`; sample count is at least 96 and scales with `max(abs(a), abs(b))`."
      },
      {
        "id": "std.pathops.pointat",
        "moduleId": "std.pathops",
        "name": "pointat",
        "params": ["path", "t"],
        "group": "procedures",
        "tags": ["geometry", "pathops", "procedures", "standard-library"],
        "summary": "Point at normalized arc length `t`. A one-point path returns that point; repeated zero-length segments are tolerated.",
        "documentation": "`pointat(path, t) -> point`. Point at normalized arc length `t`. A one-point path returns that point; repeated zero-length segments are tolerated."
      },
      {
        "id": "std.pathops.headingat",
        "moduleId": "std.pathops",
        "name": "headingat",
        "params": ["path", "t"],
        "group": "procedures",
        "tags": ["geometry", "pathops", "procedures", "standard-library"],
        "summary": "Heading of the non-zero segment containing `t`. At an exact vertex it selects the preceding segment. If no non-zero segment exists, returns 0.",
        "documentation": "`headingat(path, t) -> heading`. Heading of the non-zero segment containing `t`. At an exact vertex it selects the preceding segment. If no non-zero segment exists, returns 0."
      },
      {
        "id": "std.pathops.paramof",
        "moduleId": "std.pathops",
        "name": "paramof",
        "params": ["p", "path"],
        "group": "procedures",
        "tags": ["geometry", "pathops", "procedures", "standard-library"],
        "summary": "Normalized arc-length position of the closest point on the polyline. Ties keep the earlier segment; a zero-length path returns 0.",
        "documentation": "`paramof(p, path) -> number`. Normalized arc-length position of the closest point on the polyline. Ties keep the earlier segment; a zero-length path returns 0."
      },
      {
        "id": "std.pathops.subpath",
        "moduleId": "std.pathops",
        "name": "subpath",
        "params": ["path", "t0", "t1"],
        "group": "procedures",
        "tags": ["geometry", "pathops", "procedures", "standard-library"],
        "summary": "Extracts a section, including interpolated endpoints and interior original vertices. If `t1 < t0`, returns the forward extraction reversed. Equal parameters return two equal endpoints.",
        "documentation": "`subpath(path, t0, t1) -> path`. Extracts a section, including interpolated endpoints and interior original vertices. If `t1 < t0`, returns the forward extraction reversed. Equal parameters return two equal endpoints."
      },
      {
        "id": "std.pathops.dashes",
        "moduleId": "std.pathops",
        "name": "dashes",
        "params": ["path", "onmm", "offmm"],
        "group": "procedures",
        "tags": ["geometry", "pathops", "procedures", "standard-library"],
        "summary": "Splits an arc-length route into on-segments. `phasemm` enters that far into the repeating cycle and may begin in a dash or gap. Use non-negative lengths and a positive sum.",
        "documentation": "`dashes(path, onmm, offmm[, phasemm]) -> list of paths`. Splits an arc-length route into on-segments. `phasemm` enters that far into the repeating cycle and may begin in a dash or gap. Use non-negative lengths and a positive sum."
      },
      {
        "id": "std.pathops.simplifypath",
        "moduleId": "std.pathops",
        "name": "simplifypath",
        "params": ["path", "tol"],
        "group": "procedures",
        "tags": ["geometry", "pathops", "procedures", "standard-library"],
        "summary": "Ramer–Douglas–Peucker simplification using perpendicular segment distance. Negative tolerance becomes 0; endpoints are preserved.",
        "documentation": "`simplifypath(path, tol) -> path`. Ramer–Douglas–Peucker simplification using perpendicular segment distance. Negative tolerance becomes 0; endpoints are preserved."
      },
      {
        "id": "std.pathops.smoothclosed",
        "moduleId": "std.pathops",
        "name": "smoothclosed",
        "params": ["ring", "n"],
        "group": "procedures",
        "tags": ["geometry", "pathops", "procedures", "standard-library"],
        "summary": "Applies 0…6 rounded Chaikin corner-cutting passes. An existing duplicate closing point is removed first, then one closing point is appended. Each pass doubles the unique point count.",
        "documentation": "`smoothclosed(ring, n) -> closed path`. Applies 0…6 rounded Chaikin corner-cutting passes. An existing duplicate closing point is removed first, then one closing point is appended. Each pass doubles the unique point count."
      },
      {
        "id": "std.pathops.morphpaths",
        "moduleId": "std.pathops",
        "name": "morphpaths",
        "params": ["a", "b", "t"],
        "group": "procedures",
        "tags": ["geometry", "pathops", "procedures", "standard-library"],
        "summary": "Arc-length-resamples both paths to the larger unique-point count and linearly interpolates corresponding points. `t` is not clamped. The result is closed only if both inputs are closed.",
        "documentation": "`morphpaths(a, b, t) -> path`. Arc-length-resamples both paths to the larger unique-point count and linearly interpolates corresponding points. `t` is not clamped. The result is closed only if both inputs are closed."
      },
      {
        "id": "std.pathops.pathisects",
        "moduleId": "std.pathops",
        "name": "pathisects",
        "params": ["a", "b"],
        "group": "procedures",
        "tags": ["geometry", "pathops", "procedures", "standard-library"],
        "summary": "Returns unique segment intersections in nested segment order. Collinear overlap behavior follows core `segisect`.",
        "documentation": "`pathisects(a, b) -> list of points`. Returns unique segment intersections in nested segment order. Collinear overlap behavior follows core `segisect`."
      },
      {
        "id": "std.pathops.offsetopen",
        "moduleId": "std.pathops",
        "name": "offsetopen",
        "params": ["path", "mm"],
        "group": "procedures",
        "tags": ["geometry", "pathops", "procedures", "standard-library"],
        "summary": "Approximate mitered offset of an open polyline. Positive `mm` offsets to the path's Cartesian left; negative offsets right. Near-180° joins use a bounded denominator to avoid division by zero.",
        "documentation": "`offsetopen(path, mm) -> path`. Approximate mitered offset of an open polyline. Positive `mm` offsets to the path's Cartesian left; negative offsets right. Near-180° joins use a bounded denominator to avoid division by zero."
      },
      {
        "id": "std.regions.regionarea",
        "moduleId": "std.regions",
        "name": "regionarea",
        "params": ["region"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "regions", "standard-library"],
        "summary": "Absolute shoelace area in mm². Orientation does not affect the result.",
        "documentation": "`regionarea(region) -> number`. Absolute shoelace area in mm². Orientation does not affect the result."
      },
      {
        "id": "std.regions.poleof",
        "moduleId": "std.regions",
        "name": "poleof",
        "params": ["region"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "regions", "standard-library"],
        "summary": "Deterministic approximation of the interior point farthest from the boundary. It tests the centroid, a 9×9 bounding-box grid, then seven local refinements. Useful for labels and seed points; it is not an exact polylabel solution.",
        "documentation": "`poleof(region) -> point`. Deterministic approximation of the interior point farthest from the boundary. It tests the centroid, a 9×9 bounding-box grid, then seven local refinements. Useful for labels and seed points; it is not an exact polylabel solution."
      },
      {
        "id": "std.regions.insetrings",
        "moduleId": "std.regions",
        "name": "insetrings",
        "params": ["region", "gap", "n"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "regions", "standard-library"],
        "summary": "Repeatedly offsets inward by `abs(gap)`, returning every piece from levels 1 through `max(0, round(n))`. Splits and collapsed levels are handled by `offsetpath`; the original region is not included.",
        "documentation": "`insetrings(region, gap, n) -> list of regions`. Repeatedly offsets inward by `abs(gap)`, returning every piece from levels 1 through `max(0, round(n))`. Splits and collapsed levels are handled by `offsetpath`; the original region is not included."
      },
      {
        "id": "std.regions.tilecells",
        "moduleId": "std.regions",
        "name": "tilecells",
        "params": ["region", "kind", "cell"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "regions", "standard-library"],
        "summary": "Covers and clips a global grid of cells to the region. `kind` must be `'square'`, `'hex'`, or `'tri'`; `cell` must be positive. Hex `cell` is circumradius; triangular cells are halves of square cells. Boundary cells may be partial or split.",
        "documentation": "`tilecells(region, kind, cell) -> list of regions`. Covers and clips a global grid of cells to the region. `kind` must be `'square'`, `'hex'`, or `'tri'`; `cell` must be positive. Hex `cell` is circumradius; triangular cells are halves of square cells. Boundary cells may be partial or split."
      },
      {
        "id": "std.regions.gridpoints",
        "moduleId": "std.regions",
        "name": "gridpoints",
        "params": ["region", "cell"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "regions", "standard-library"],
        "summary": "Returns centers of globally aligned `cell × cell` boxes that lie inside the region. `cell` must be positive. Points on the upper/right incomplete fringe are not sampled.",
        "documentation": "`gridpoints(region, cell) -> list of points`. Returns centers of globally aligned `cell × cell` boxes that lie inside the region. `cell` must be positive. Points on the upper/right incomplete fringe are not sampled."
      },
      {
        "id": "std.regions.partitions",
        "moduleId": "std.regions",
        "name": "partitions",
        "params": ["region", "n"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "regions", "rng", "standard-library"],
        "summary": "Produces `max(1, round(n))` clipped Voronoi cells after two centroidal-relaxation passes. Initial seeds use `scatter`, with grid/pole fallbacks. Consumes exactly **1 main-stream RNG draw**, regardless of the number of generated seeds.",
        "documentation": "`partitions(region, n) -> list of regions`. Produces `max(1, round(n))` clipped Voronoi cells after two centroidal-relaxation passes. Initial seeds use `scatter`, with grid/pole fallbacks. Consumes exactly **1 main-stream RNG draw**, regardless of the number of generated seeds."
      },
      {
        "id": "std.layout.circlelayout",
        "moduleId": "std.layout",
        "name": "circlelayout",
        "params": ["n", "r"],
        "group": "procedures",
        "tags": ["geometry", "layout", "procedures", "standard-library"],
        "summary": "Returns `max(0, round(n))` evenly spaced positions on radius `r`. The first is north. Each heading is tangent to the circle in counter-clockwise traversal; zero count returns `[]`.",
        "documentation": "`circlelayout(n, r) -> placements`. Returns `max(0, round(n))` evenly spaced positions on radius `r`. The first is north. Each heading is tangent to the circle in counter-clockwise traversal; zero count returns `[]`."
      },
      {
        "id": "std.layout.gridlayout",
        "moduleId": "std.layout",
        "name": "gridlayout",
        "params": ["cols", "rows", "dx", "dy"],
        "group": "procedures",
        "tags": ["geometry", "layout", "procedures", "standard-library"],
        "summary": "Centered row-major grid with rounded non-negative dimensions. Starts at the top-left for positive spacing; every heading is 0. Negative spacing mirrors an axis.",
        "documentation": "`gridlayout(cols, rows, dx, dy) -> placements`. Centered row-major grid with rounded non-negative dimensions. Starts at the top-left for positive spacing; every heading is 0. Negative spacing mirrors an axis."
      },
      {
        "id": "std.layout.alongpath",
        "moduleId": "std.layout",
        "name": "alongpath",
        "params": ["path", "n"],
        "group": "procedures",
        "tags": ["geometry", "layout", "procedures", "standard-library"],
        "summary": "Returns rounded non-negative count at equal normalized arc-length parameters, including both ends. One placement uses the midpoint (`t = 0.5`). Headings follow `std.pathops.headingat`.",
        "documentation": "`alongpath(path, n) -> placements`. Returns rounded non-negative count at equal normalized arc-length parameters, including both ends. One placement uses the midpoint (`t = 0.5`). Headings follow `std.pathops.headingat`."
      },
      {
        "id": "std.layout.fitpath",
        "moduleId": "std.layout",
        "name": "fitpath",
        "params": ["path", "region", "margin"],
        "group": "procedures",
        "tags": ["geometry", "layout", "procedures", "standard-library"],
        "summary": "Uniformly scales `path` to fit the region's bounding box after a non-negative margin, then centers bounding boxes. Preserves aspect ratio and handles horizontal, vertical, and point-like source paths. It fits the bounding box, not the exact polygon interior.",
        "documentation": "`fitpath(path, region, margin) -> path`. Uniformly scales `path` to fit the region's bounding box after a non-negative margin, then centers bounding boxes. Preserves aspect ratio and handles horizontal, vertical, and point-like source paths. It fits the bounding box, not the exact polygon interior."
      },
      {
        "id": "std.textures.radialdir",
        "moduleId": "std.textures",
        "name": "radialdir",
        "params": ["p"],
        "group": "direction-reporters",
        "tags": ["direction-reporters", "standard-library", "textures"],
        "summary": "Heading of the ray from origin to `p`; returns 0 within `0.000001` mm of the origin.",
        "documentation": "`radialdir(p) -> heading`. Heading of the ray from origin to `p`; returns 0 within `0.000001` mm of the origin."
      },
      {
        "id": "std.textures.griddir",
        "moduleId": "std.textures",
        "name": "griddir",
        "params": ["deg"],
        "group": "direction-reporters",
        "tags": [
          "direction-reporters",
          "embroidery",
          "geometry",
          "higher-order",
          "standard-library",
          "textures"
        ],
        "summary": "Returns a direction reporter that ignores its point and always returns `deg`. Example: `fill dir griddir(30)`.",
        "documentation": "`griddir(deg) -> reference`. Returns a direction reporter that ignores its point and always returns `deg`. Example: `fill dir griddir(30)`."
      },
      {
        "id": "std.textures.radialdirfrom",
        "moduleId": "std.textures",
        "name": "radialdirfrom",
        "params": ["cx", "cy"],
        "group": "direction-reporters",
        "tags": ["direction-reporters", "higher-order", "standard-library", "textures"],
        "summary": "Returns a reporter whose rays originate at `[cx, cy]`; returns 0 at that center.",
        "documentation": "`radialdirfrom(cx, cy) -> reference`. Returns a reporter whose rays originate at `[cx, cy]`; returns 0 at that center."
      },
      {
        "id": "std.textures.curldir",
        "moduleId": "std.textures",
        "name": "curldir",
        "params": ["p"],
        "group": "direction-reporters",
        "tags": ["direction-reporters", "standard-library", "textures"],
        "summary": "Divergence-free direction derived from finite differences of simplex noise at a fixed 14 mm scale. Returns 0 for a near-zero gradient.",
        "documentation": "`curldir(p) -> heading`. Divergence-free direction derived from finite differences of simplex noise at a fixed 14 mm scale. Returns 0 for a near-zero gradient."
      },
      {
        "id": "std.textures.curldirwith",
        "moduleId": "std.textures",
        "name": "curldirwith",
        "params": ["scaledown"],
        "group": "direction-reporters",
        "tags": ["direction-reporters", "higher-order", "standard-library", "textures"],
        "summary": "Configurable form of `curldir`; `scaledown` is the spatial noise scale and must be positive. Larger values vary more slowly.",
        "documentation": "`curldirwith(scaledown) -> reference`. Configurable form of `curldir`; `scaledown` is the spatial noise scale and must be positive. Larger values vary more slowly."
      },
      {
        "id": "std.textures.wovenshape",
        "moduleId": "std.textures",
        "name": "wovenshape",
        "params": ["p", "row", "v"],
        "group": "fill-shape-reporters",
        "tags": ["embroidery", "fill-shape-reporters", "standard-library", "textures"],
        "summary": "Uses 0.8 mm row spacing, 3 mm stitches, and alternates phase 0/0.5 by row parity for a woven rhythm.",
        "documentation": "`wovenshape(p, row, v) -> descriptor`. Uses 0.8 mm row spacing, 3 mm stitches, and alternates phase 0/0.5 by row parity for a woven rhythm."
      },
      {
        "id": "std.textures.gradientshape",
        "moduleId": "std.textures",
        "name": "gradientshape",
        "params": ["p", "row", "v"],
        "group": "fill-shape-reporters",
        "tags": ["embroidery", "fill-shape-reporters", "standard-library", "textures"],
        "summary": "Ramps row spacing from 0.45 to 1.2 mm using clamped cross-field coordinate `v`; stitch length is 2.5 mm and phase 0.5.",
        "documentation": "`gradientshape(p, row, v) -> descriptor`. Ramps row spacing from 0.45 to 1.2 mm using clamped cross-field coordinate `v`; stitch length is 2.5 mm and phase 0.5."
      },
      {
        "id": "std.textures.gradientshapewith",
        "moduleId": "std.textures",
        "name": "gradientshapewith",
        "params": ["lo", "hi"],
        "group": "fill-shape-reporters",
        "tags": [
          "fill-shape-reporters",
          "geometry",
          "higher-order",
          "standard-library",
          "textures"
        ],
        "summary": "Configurable gradient reporter interpolating spacing from `lo` to `hi`; it does not clamp the supplied spacing endpoints.",
        "documentation": "`gradientshapewith(lo, hi) -> reference`. Configurable gradient reporter interpolating spacing from `lo` to `hi`; it does not clamp the supplied spacing endpoints."
      },
      {
        "id": "std.textures.hilbertpaths",
        "moduleId": "std.textures",
        "name": "hilbertpaths",
        "params": ["region", "cell"],
        "group": "geometric-texture-paths",
        "tags": ["geometric-texture-paths", "geometry", "standard-library", "textures"],
        "summary": "Builds the smallest power-of-two Hilbert grid whose scaled span covers the larger bounding-box dimension, then clips its continuous curve. `cell` controls target detail and must be positive.",
        "documentation": "`hilbertpaths(region, cell) -> paths`. Builds the smallest power-of-two Hilbert grid whose scaled span covers the larger bounding-box dimension, then clips its continuous curve. `cell` controls target detail and must be positive."
      },
      {
        "id": "std.textures.truchetpaths",
        "moduleId": "std.textures",
        "name": "truchetpaths",
        "params": ["region", "cell"],
        "group": "geometric-texture-paths",
        "tags": ["geometric-texture-paths", "geometry", "standard-library", "textures"],
        "summary": "Alternating checkerboard Truchet quarter-circles, sampled every 15°. `cell` is tile size and must be positive.",
        "documentation": "`truchetpaths(region, cell) -> paths`. Alternating checkerboard Truchet quarter-circles, sampled every 15°. `cell` is tile size and must be positive."
      },
      {
        "id": "std.textures.hitomezashi",
        "moduleId": "std.textures",
        "name": "hitomezashi",
        "params": ["region", "cell", "rowbits", "colbits"],
        "group": "geometric-texture-paths",
        "tags": ["geometric-texture-paths", "geometry", "standard-library", "textures"],
        "summary": "Alternating horizontal and vertical sashiko dashes. Rounded bit values modulo 2 set row and column phases cyclically, including at negative grid indices. `cell` must be positive and both bit lists non-empty.",
        "documentation": "`hitomezashi(region, cell, rowbits, colbits) -> paths`. Alternating horizontal and vertical sashiko dashes. Rounded bit values modulo 2 set row and column phases cyclically, including at negative grid indices. `cell` must be positive and both bit lists non-empty."
      },
      {
        "id": "std.textures.seigaiha",
        "moduleId": "std.textures",
        "name": "seigaiha",
        "params": ["region", "r"],
        "group": "geometric-texture-paths",
        "tags": ["geometric-texture-paths", "geometry", "standard-library", "textures"],
        "summary": "Staggered Japanese wave pattern with three concentric semicircles at each origin. `r` is the largest radius and must be positive.",
        "documentation": "`seigaiha(region, r) -> paths`. Staggered Japanese wave pattern with three concentric semicircles at each origin. `r` is the largest radius and must be positive."
      },
      {
        "id": "std.textures.asanoha",
        "moduleId": "std.textures",
        "name": "asanoha",
        "params": ["region", "cell"],
        "group": "geometric-texture-paths",
        "tags": ["geometric-texture-paths", "geometry", "standard-library", "textures"],
        "summary": "Hexagonally arranged hemp-leaf spokes and half-edges. `cell` must be positive.",
        "documentation": "`asanoha(region, cell) -> paths`. Hexagonally arranged hemp-leaf spokes and half-edges. `cell` must be positive."
      },
      {
        "id": "std.textures.herringbonepaths",
        "moduleId": "std.textures",
        "name": "herringbonepaths",
        "params": ["region", "w"],
        "group": "geometric-texture-paths",
        "tags": ["geometric-texture-paths", "geometry", "standard-library", "textures"],
        "summary": "Staggered zigzag herringbone units with horizontal/vertical scale `w`, which must be positive.",
        "documentation": "`herringbonepaths(region, w) -> paths`. Staggered zigzag herringbone units with horizontal/vertical scale `w`, which must be positive."
      },
      {
        "id": "std.stitchcraft.sewrun",
        "moduleId": "std.stitchcraft",
        "name": "sewrun",
        "params": ["path", "mm"],
        "group": "procedures",
        "tags": ["embroidery", "geometry", "procedures", "standard-library", "stitchcraft"],
        "summary": "Resamples `path` at spacing `mm`, then sews it with the current stitch mode and thread. Equivalent to `sewpath(resample(path, mm))`.",
        "documentation": "`sewrun(path, mm)`. Resamples `path` at spacing `mm`, then sews it with the current stitch mode and thread. Equivalent to `sewpath(resample(path, mm))`."
      },
      {
        "id": "std.stitchcraft.satinalong",
        "moduleId": "std.stitchcraft",
        "name": "satinalong",
        "params": ["path", "w"],
        "group": "procedures",
        "tags": ["embroidery", "geometry", "procedures", "standard-library", "stitchcraft"],
        "summary": "Enables satin width `w`, sews `path`, then sets satin width to 0. The final satin state is always off, not restored to a prior width. Other satin settings still apply.",
        "documentation": "`satinalong(path, w)`. Enables satin width `w`, sews `path`, then sets satin width to 0. The final satin state is always off, not restored to a prior width. Other satin settings still apply."
      },
      {
        "id": "std.stitchcraft.beanoutline",
        "moduleId": "std.stitchcraft",
        "name": "beanoutline",
        "params": ["region", "n"],
        "group": "procedures",
        "tags": ["embroidery", "geometry", "procedures", "standard-library", "stitchcraft"],
        "summary": "Enables bean repeat `n`, sews the logically closed region, then sets bean repeat to 1. The prior bean setting is not restored.",
        "documentation": "`beanoutline(region, n)`. Enables bean repeat `n`, sews the logically closed region, then sets bean repeat to 1. The prior bean setting is not restored."
      },
      {
        "id": "std.stitchcraft.appliquesteps",
        "moduleId": "std.stitchcraft",
        "name": "appliquesteps",
        "params": ["region", "w"],
        "group": "procedures",
        "tags": ["embroidery", "geometry", "procedures", "standard-library", "stitchcraft"],
        "summary": "Performs a 2.5 mm running placement line, a narrow satin tack-down at `max(0.8, 0.35w)`, and a final satin cover at `w`. Inserts `stop` events between the three stages so fabric can be placed/trimmed. Each stage travels with needle up to the ring start. Ends at the closed ring's end with satin turned off.",
        "documentation": "`appliquesteps(region, w)`. Performs a 2.5 mm running placement line, a narrow satin tack-down at `max(0.8, 0.35w)`, and a final satin cover at `w`. Inserts `stop` events between the three stages so fabric can be placed/trimmed. Each stage travels with needle up to the ring start. Ends at the closed ring's end with satin turned off."
      },
      {
        "id": "std.stitchcraft.appliquewith",
        "moduleId": "std.stitchcraft",
        "name": "appliquewith",
        "params": ["region", "placementinset", "tackdowninset", "coverwidth", "stops"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "standard-library", "stitchcraft"],
        "summary": "Configurable three-stage appliqué construction. See below.",
        "documentation": "`appliquewith(region, placementInset, tackdownInset, coverWidth, stops)`. Configurable three-stage appliqué construction. See below."
      },
      {
        "id": "std.stitchcraft.eyelet",
        "moduleId": "std.stitchcraft",
        "name": "eyelet",
        "params": ["r"],
        "group": "procedures",
        "tags": ["embroidery", "procedures", "standard-library", "stitchcraft"],
        "summary": "Sews a resampled satin circle centered at the current needle position. Radius must be positive; satin width is `clamp(0.55r, 0.6, 1.5)`. A `push`/`pop` pair restores needle position, heading, and pen state after sewing; satin ends off.",
        "documentation": "`eyelet(r)`. Sews a resampled satin circle centered at the current needle position. Radius must be positive; satin width is `clamp(0.55r, 0.6, 1.5)`. A `push`/`pop` pair restores needle position, heading, and pen state after sewing; satin ends off."
      },
      {
        "id": "std.stitchcraft.fillbordergeometry",
        "moduleId": "std.stitchcraft",
        "name": "fillbordergeometry",
        "params": ["region", "coverwidth", "overlap"],
        "group": "procedures",
        "tags": ["embroidery", "geometry", "procedures", "pure", "standard-library", "stitchcraft"],
        "summary": "Pure fill-and-border construction geometry. See below.",
        "documentation": "`fillbordergeometry(region, coverWidth, overlap) -> [fillRings, borderPaths, inset]`. Pure fill-and-border construction geometry. See below."
      },
      {
        "id": "std.stitchcraft.fillandborder",
        "moduleId": "std.stitchcraft",
        "name": "fillandborder",
        "params": ["region", "deg", "spacing", "coverwidth"],
        "group": "procedures",
        "tags": ["embroidery", "geometry", "procedures", "standard-library", "stitchcraft"],
        "summary": "Sews inset fill rows, inserts a `stop`, then sews the satin border. Uses the standard 0.4 mm overlap.",
        "documentation": "`fillandborder(region, deg, spacing, coverWidth)`. Sews inset fill rows, inserts a `stop`, then sews the satin border. Uses the standard 0.4 mm overlap."
      },
      {
        "id": "std.stitchcraft.fillandborderwith",
        "moduleId": "std.stitchcraft",
        "name": "fillandborderwith",
        "params": ["region", "deg", "spacing", "coverwidth", "overlap"],
        "group": "procedures",
        "tags": ["embroidery", "geometry", "procedures", "standard-library", "stitchcraft"],
        "summary": "Explicit-overlap form of `fillandborder`.",
        "documentation": "`fillandborderwith(region, deg, spacing, coverWidth, overlap)`. Explicit-overlap form of `fillandborder`."
      },
      {
        "id": "std.stitchcraft.gradientbands",
        "moduleId": "std.stitchcraft",
        "name": "gradientbands",
        "params": ["region", "deg", "n"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "pure", "standard-library", "stitchcraft"],
        "summary": "Geometry-only helper: slices a region into `max(1, round(n))` parallel bands oriented at heading/angle `deg` and returns all clipped pieces in band order. Concavity can yield more pieces than requested bands.",
        "documentation": "`gradientbands(region, deg, n) -> list of regions`. Geometry-only helper: slices a region into `max(1, round(n))` parallel bands oriented at heading/angle `deg` and returns all clipped pieces in band order. Concavity can yield more pieces than requested bands."
      },
      {
        "id": "std.stitchcraft.gradientrows",
        "moduleId": "std.stitchcraft",
        "name": "gradientrows",
        "params": ["region", "deg", "pitch", "amount"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "pure", "standard-library", "stitchcraft"],
        "summary": "Geometry-only, density-neutral two-color blend. See below.",
        "documentation": "`gradientrows(region, deg, pitch, amount) -> [rowsA, rowsB]`. Geometry-only, density-neutral two-color blend. See below."
      },
      {
        "id": "std.stitchcraft.gradientrowsn",
        "moduleId": "std.stitchcraft",
        "name": "gradientrowsn",
        "params": ["region", "deg", "pitch", "weights"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "pure", "standard-library", "stitchcraft"],
        "summary": "Geometry-only, density-neutral blend across 2–8 colors. See below.",
        "documentation": "`gradientrowsn(region, deg, pitch, weights) -> list of row groups`. Geometry-only, density-neutral blend across 2–8 colors. See below."
      },
      {
        "id": "std.stitchcraft.serpentinerows",
        "moduleId": "std.stitchcraft",
        "name": "serpentinerows",
        "params": ["rows", "reversed"],
        "group": "procedures",
        "tags": ["geometry", "procedures", "standard-library", "stitchcraft"],
        "summary": "Greedily routes parallel row paths with endpoint reversal enabled, beginning from the first row when `reversed` is false or the last row when true. Returns `[]` for empty input and does not mutate `rows`.",
        "documentation": "`serpentinerows(rows, reversed) -> routed rows`. Greedily routes parallel row paths with endpoint reversal enabled, beginning from the first row when `reversed` is false or the last row when true. Returns `[]` for empty input and does not mutate `rows`."
      },
      {
        "id": "std.stitchcraft.knockdown",
        "moduleId": "std.stitchcraft",
        "name": "knockdown",
        "params": ["region", "deg", "spacing"],
        "group": "procedures",
        "tags": ["embroidery", "geometry", "procedures", "standard-library", "stitchcraft"],
        "summary": "Sparse running-stitch foundation for fleece, terry, and other high-pile fabrics. See below.",
        "documentation": "`knockdown(region, deg, spacing)`. Sparse running-stitch foundation for fleece, terry, and other high-pile fabrics. See below."
      },
      {
        "id": "std.stitchcraft.threadblend",
        "moduleId": "std.stitchcraft",
        "name": "threadblend",
        "params": ["region", "deg"],
        "group": "procedures",
        "tags": ["embroidery", "geometry", "procedures", "standard-library", "stitchcraft"],
        "summary": "Creates 1.2 mm fill rows at `deg`, sews even rows in the current color, advances once to the next color, then sews odd rows. Rows are resampled at 2.5 mm. Ends in the second color and does not restore needle position.",
        "documentation": "`threadblend(region, deg)`. Creates 1.2 mm fill rows at `deg`, sews even rows in the current color, advances once to the next color, then sews odd rows. Rows are resampled at 2.5 mm. Ends in the second color and does not restore needle position."
      },
      {
        "id": "std.stitchcraft.stipple",
        "moduleId": "std.stitchcraft",
        "name": "stipple",
        "params": ["region", "mindist"],
        "group": "procedures",
        "tags": ["embroidery", "geometry", "procedures", "rng", "standard-library", "stitchcraft"],
        "summary": "Scatters candidate points and sews a small circular mark only where coverage within `mindist/3` is below one layer. `mindist` must be positive. Each mark restores turtle state with `push`/`pop`. Consumes exactly **1 main-stream RNG draw** through `scatter`.",
        "documentation": "`stipple(region, mindist)`. Scatters candidate points and sews a small circular mark only where coverage within `mindist/3` is below one layer. `mindist` must be positive. Each mark restores turtle state with `push`/`pop`. Consumes exactly **1 main-stream RNG draw** through `scatter`."
      },
      {
        "id": "std.debugx.chalkgrid",
        "moduleId": "std.debugx",
        "name": "chalkgrid",
        "params": ["cell"],
        "group": "procedures",
        "tags": ["debugx", "embroidery", "procedures", "standard-library"],
        "summary": "Adds a `'grid'` line group spanning the configured field bounds, aligned to global multiples of `cell`. `cell` must be positive.",
        "documentation": "`chalkgrid(cell)`. Adds a `'grid'` line group spanning the configured field bounds, aligned to global multiples of `cell`. `cell` must be positive."
      },
      {
        "id": "std.debugx.chalkbbox",
        "moduleId": "std.debugx",
        "name": "chalkbbox",
        "params": ["path"],
        "group": "procedures",
        "tags": ["debugx", "embroidery", "geometry", "procedures", "standard-library"],
        "summary": "Adds a closed, axis-aligned `'bbox'` line overlay around `path`. Expects non-empty geometry accepted by core `bbox`.",
        "documentation": "`chalkbbox(path)`. Adds a closed, axis-aligned `'bbox'` line overlay around `path`. Expects non-empty geometry accepted by core `bbox`."
      },
      {
        "id": "std.debugx.chalkfield",
        "moduleId": "std.debugx",
        "name": "chalkfield",
        "params": [],
        "group": "procedures",
        "tags": ["debugx", "embroidery", "geometry", "procedures", "standard-library"],
        "summary": "Adds a `'field'` line overlay of the current sewable field path. Works for circular and rectangular hoop fields.",
        "documentation": "`chalkfield()`. Adds a `'field'` line overlay of the current sewable field path. Works for circular and rectangular hoop fields."
      },
      {
        "id": "std.debugx.threadestimate",
        "moduleId": "std.debugx",
        "name": "threadestimate",
        "params": [],
        "group": "procedures",
        "tags": ["debugx", "embroidery", "geometry", "procedures", "standard-library"],
        "summary": "Returns the polyline length through committed penetration points, in millimetres, or 0 with fewer than two points. It is an estimate: stitch history does not retain trims or color boundaries, and it does not model bobbin/thread consumption.",
        "documentation": "`threadestimate() -> number`. Returns the polyline length through committed penetration points, in millimetres, or 0 with fewer than two points. It is an estimate: stitch history does not retain trims or color boundaries, and it does not model bobbin/thread consumption."
      },
      {
        "id": "std.debugx.coverprofile",
        "moduleId": "std.debugx",
        "name": "coverprofile",
        "params": ["path", "stride"],
        "group": "procedures",
        "tags": ["debugx", "embroidery", "geometry", "procedures", "standard-library"],
        "summary": "Samples `coverat` along a resampled path and returns `[distanceMm, coverageLayers]` pairs. `stride` must be positive. Empty path returns `[]`; a one-point path returns one sample at distance 0.",
        "documentation": "`coverprofile(path, stride) -> list`. Samples `coverat` along a resampled path and returns `[distanceMm, coverageLayers]` pairs. `stride` must be positive. Empty path returns `[]`; a one-point path returns one sample at distance 0."
      }
    ]
  }
}
