{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:akari-video:schema:edit:v1",
  "title": "AKARI Video edit.json v0/v1/v2",
  "description": "単一ソースの v0、sources[] と cuts[].src を使うマルチソース v1、および tracks[].items[] を持つ v2 の公開契約。v2 の出力側の位置と尺は整数フレーム、素材側の in/out は秒で宣言する。",
  "oneOf": [
    { "$ref": "#/$defs/editV0" },
    { "$ref": "#/$defs/editV1" },
    { "$ref": "#/$defs/editV2" }
  ],
  "$defs": {
    "positiveNumber": {
      "type": "number",
      "exclusiveMinimum": 0
    },
    "seconds": {
      "type": "number",
      "minimum": 0
    },
    "frames": {
      "type": "integer",
      "minimum": 0,
      "$comment": "notes-2026-08-18-timeline-latency-and-track-model.md §10 / §10-1。出力側の位置と尺は映像・音声とも 0 以上の整数フレームで宣言する。"
    },
    "itemAtV2": {
      "$ref": "#/$defs/frames",
      "$comment": "notes-2026-08-18-timeline-latency-and-track-model.md §10。v2 Phase 0 では絶対フレーム整数だけを受け付ける。相対参照は anchor（契約 2026-09-02）で実現し、この $def の oneOf: [frames, string] 拡張は保留する。"
    },
    "itemAnchorV2": {
      "type": "object",
      "additionalProperties": false,
      "required": ["caption"],
      "properties": {
        "caption": { "type": "string", "pattern": "^c-\\d{4}$" },
        "range": {
          "type": "object",
          "additionalProperties": false,
          "required": ["start", "end"],
          "properties": {
            "start": { "$ref": "#/$defs/seconds" },
            "end": { "$ref": "#/$defs/seconds" }
          }
        },
        "offset": { "type": "integer" },
        "edge": { "enum": ["start", "end"], "default": "start" },
        "attached_by": { "$ref": "#/$defs/itemAttachedByV2" },
        "duration": { "enum": ["caption", "own"], "default": "caption" }
      },
      "$comment": "source 秒で宣言する。range は参照字幕の [start, end] 内に置き、at / duration は解決キャッシュとする。正本は docs/contract-2026-09-02-item-caption-anchor-v0.md。"
    },
    "itemAttachedByV2": {
      "type": "object",
      "additionalProperties": false,
      "required": ["style_uid", "caption"],
      "properties": {
        "style_uid": { "type": "string", "minLength": 1 },
        "caption": { "type": "string", "pattern": "^c-\\d{4}$" }
      }
    },
    "nonNegativeInteger": {
      "type": "integer",
      "minimum": 0
    },
    "look": {
      "type": "object",
      "additionalProperties": true,
      "required": ["lut"],
      "properties": {
        "lut": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "intensity": { "type": "number", "minimum": 0, "maximum": 1 }
      },
      "$comment": "docs/contract-2026-07-22-render-basics.md #4。lut はプリセット参照名（presets/luts/ 配下）または .cube への相対パス。intensity 省略時 1.0。"
    },
    "adjustBasicV0": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "exposure": { "type": "number", "minimum": -3, "maximum": 3 },
        "contrast": { "type": "number", "minimum": -1, "maximum": 1 },
        "highlights": { "type": "number", "minimum": -1, "maximum": 1 },
        "shadows": { "type": "number", "minimum": -1, "maximum": 1 },
        "blacks": { "type": "number", "minimum": -1, "maximum": 1 },
        "whites": { "type": "number", "minimum": -1, "maximum": 1 },
        "temperature": { "type": "number", "minimum": -1, "maximum": 1 },
        "tint": { "type": "number", "minimum": -1, "maximum": 1 },
        "vibrance": { "type": "number", "minimum": -1, "maximum": 1 },
        "saturation": { "type": "number", "minimum": -1, "maximum": 1 }
      }
    },
    "adjustLutV0": {
      "type": "object",
      "additionalProperties": false,
      "required": ["lut"],
      "properties": {
        "lut": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "intensity": { "type": "number", "minimum": 0, "maximum": 1 }
      }
    },
    "adjustCurvePointV1": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "in": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "out": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        }
      },
      "required": [
        "in",
        "out"
      ]
    },
    "adjustHuePointV1": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "hue": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "value": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        }
      },
      "required": [
        "hue",
        "value"
      ]
    },
    "adjustCurvesV1": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "master": {
          "type": "array",
          "minItems": 2,
          "maxItems": 16,
          "items": {
            "$ref": "#/$defs/adjustCurvePointV1"
          }
        },
        "r": {
          "type": "array",
          "minItems": 2,
          "maxItems": 16,
          "items": {
            "$ref": "#/$defs/adjustCurvePointV1"
          }
        },
        "g": {
          "type": "array",
          "minItems": 2,
          "maxItems": 16,
          "items": {
            "$ref": "#/$defs/adjustCurvePointV1"
          }
        },
        "b": {
          "type": "array",
          "minItems": 2,
          "maxItems": 16,
          "items": {
            "$ref": "#/$defs/adjustCurvePointV1"
          }
        }
      }
    },
    "adjustHueCurvesV1": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "hue": {
          "type": "array",
          "minItems": 1,
          "maxItems": 16,
          "items": {
            "$ref": "#/$defs/adjustHuePointV1"
          }
        },
        "sat": {
          "type": "array",
          "minItems": 1,
          "maxItems": 16,
          "items": {
            "$ref": "#/$defs/adjustHuePointV1"
          }
        },
        "luma": {
          "type": "array",
          "minItems": 1,
          "maxItems": 16,
          "items": {
            "$ref": "#/$defs/adjustHuePointV1"
          }
        }
      }
    },
    "adjustWheelV1": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "r": {
          "type": "number",
          "minimum": -0.5,
          "maximum": 0.5
        },
        "g": {
          "type": "number",
          "minimum": -0.5,
          "maximum": 0.5
        },
        "b": {
          "type": "number",
          "minimum": -0.5,
          "maximum": 0.5
        }
      }
    },
    "adjustWheelsV1": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "lift": {
          "allOf": [
            {
              "$ref": "#/$defs/adjustWheelV1"
            },
            {
              "properties": {
                "r": {
                  "type": "number",
                  "minimum": -0.25,
                  "maximum": 0.25
                },
                "g": {
                  "type": "number",
                  "minimum": -0.25,
                  "maximum": 0.25
                },
                "b": {
                  "type": "number",
                  "minimum": -0.25,
                  "maximum": 0.25
                }
              }
            }
          ]
        },
        "gamma": {
          "allOf": [
            {
              "$ref": "#/$defs/adjustWheelV1"
            },
            {
              "properties": {
                "r": {
                  "type": "number",
                  "minimum": -0.5,
                  "maximum": 0.5
                },
                "g": {
                  "type": "number",
                  "minimum": -0.5,
                  "maximum": 0.5
                },
                "b": {
                  "type": "number",
                  "minimum": -0.5,
                  "maximum": 0.5
                }
              }
            }
          ]
        },
        "gain": {
          "allOf": [
            {
              "$ref": "#/$defs/adjustWheelV1"
            },
            {
              "properties": {
                "r": {
                  "type": "number",
                  "minimum": -0.5,
                  "maximum": 0.5
                },
                "g": {
                  "type": "number",
                  "minimum": -0.5,
                  "maximum": 0.5
                },
                "b": {
                  "type": "number",
                  "minimum": -0.5,
                  "maximum": 0.5
                }
              }
            }
          ]
        },
        "offset": {
          "allOf": [
            {
              "$ref": "#/$defs/adjustWheelV1"
            },
            {
              "properties": {
                "r": {
                  "type": "number",
                  "minimum": -0.1,
                  "maximum": 0.1
                },
                "g": {
                  "type": "number",
                  "minimum": -0.1,
                  "maximum": 0.1
                },
                "b": {
                  "type": "number",
                  "minimum": -0.1,
                  "maximum": 0.1
                }
              }
            }
          ]
        }
      }
    },
    "adjustFxV1": {
      "oneOf": [
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "id"
          ],
          "properties": {
            "id": {
              "const": "vignette"
            },
            "amount": {
              "type": "number",
              "minimum": -1,
              "maximum": 1,
              "default": 0.5
            },
            "midpoint": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "default": 0.5
            },
            "roundness": {
              "type": "number",
              "minimum": -1,
              "maximum": 1,
              "default": 0
            },
            "feather": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "default": 0.5
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "id"
          ],
          "properties": {
            "id": {
              "const": "blur"
            },
            "px": {
              "type": "number",
              "minimum": 0,
              "maximum": 50,
              "default": 8
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "id"
          ],
          "properties": {
            "id": {
              "const": "grain"
            },
            "amount": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "default": 0.3
            },
            "size": {
              "type": "number",
              "minimum": 0.5,
              "maximum": 4,
              "default": 1
            }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "id"
          ],
          "properties": {
            "id": {
              "const": "sharpen"
            },
            "amount": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "default": 0.5
            }
          }
        },
        {
          "type": "object",
          "required": [
            "id"
          ],
          "additionalProperties": false,
          "properties": {
            "id": {
              "const": "glow"
            },
            "intensity": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "default": 0.5
            },
            "radius": {
              "type": "number",
              "minimum": 0,
              "maximum": 100,
              "default": 20
            },
            "threshold": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "default": 0.7
            },
            "warmth": {
              "type": "number",
              "minimum": -1,
              "maximum": 1,
              "default": 0
            }
          }
        },
        {
          "type": "object",
          "required": [
            "id"
          ],
          "additionalProperties": false,
          "properties": {
            "id": {
              "const": "clarity"
            },
            "amount": {
              "type": "number",
              "minimum": -1,
              "maximum": 1,
              "default": 0.3
            },
            "radius": {
              "type": "number",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            }
          }
        },
        {
          "type": "object",
          "required": [
            "id"
          ],
          "additionalProperties": false,
          "properties": {
            "id": {
              "const": "dehaze"
            },
            "amount": {
              "type": "number",
              "minimum": -1,
              "maximum": 1,
              "default": 0.3
            }
          }
        },
        {
          "type": "object",
          "required": [
            "id"
          ],
          "additionalProperties": false,
          "properties": {
            "id": {
              "const": "denoise"
            },
            "amount": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "default": 0.3
            }
          }
        },
        {
          "type": "object",
          "required": [
            "id"
          ],
          "additionalProperties": false,
          "properties": {
            "id": {
              "const": "motion_blur"
            },
            "px": {
              "type": "number",
              "minimum": 0,
              "maximum": 100,
              "default": 10
            },
            "angle": {
              "type": "number",
              "minimum": -180,
              "maximum": 180,
              "default": 0
            }
          }
        }
      ]
    },
    "adjustV1": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "basic": {
          "$ref": "#/$defs/adjustBasicV0"
        },
        "lut": {
          "oneOf": [
            {
              "type": "null"
            },
            {
              "$ref": "#/$defs/adjustLutV0"
            }
          ]
        },
        "sections": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "basic": {
              "type": "boolean"
            },
            "lut": {
              "type": "boolean"
            },
            "curves": {
              "type": "boolean"
            },
            "wheels": {
              "type": "boolean"
            },
            "hue": {
              "type": "boolean"
            },
            "fx": {
              "type": "boolean"
            }
          }
        },
        "curves": {
          "$ref": "#/$defs/adjustCurvesV1"
        },
        "wheels": {
          "$ref": "#/$defs/adjustWheelsV1"
        },
        "hue": {
          "$ref": "#/$defs/adjustHueCurvesV1"
        },
        "fx": {
          "type": "array",
          "maxItems": 8,
          "items": {
            "$ref": "#/$defs/adjustFxV1"
          },
          "allOf": [
            {
              "contains": {
                "type": "object",
                "required": [
                  "id"
                ],
                "properties": {
                  "id": {
                    "const": "vignette"
                  }
                }
              },
              "minContains": 0,
              "maxContains": 1
            },
            {
              "contains": {
                "type": "object",
                "required": [
                  "id"
                ],
                "properties": {
                  "id": {
                    "const": "blur"
                  }
                }
              },
              "minContains": 0,
              "maxContains": 1
            },
            {
              "contains": {
                "type": "object",
                "required": [
                  "id"
                ],
                "properties": {
                  "id": {
                    "const": "grain"
                  }
                }
              },
              "minContains": 0,
              "maxContains": 1
            },
            {
              "contains": {
                "type": "object",
                "required": [
                  "id"
                ],
                "properties": {
                  "id": {
                    "const": "sharpen"
                  }
                }
              },
              "minContains": 0,
              "maxContains": 1
            }
          ]
        }
      },
      "$comment": "正本は docs/contract-2026-09-05-clip-adjust-v1.md。v2 visual item の per-clip 補正で、未指定の値は中立、sections は false のときだけ該当段をバイパスする。"
    },
    "output": {
      "type": "object",
      "additionalProperties": true,
      "required": ["width", "height", "fps"],
      "properties": {
        "width": { "$ref": "#/$defs/positiveNumber" },
        "height": { "$ref": "#/$defs/positiveNumber" },
        "fps": { "$ref": "#/$defs/positiveNumber" },
        "geometry": { "$ref": "#/$defs/outputGeometry" },
        "look": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/look" }] },
        "encoding": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "quality": { "enum": ["master", "high", "standard", "light"] },
            "encoder": { "enum": ["auto", "videotoolbox", "nvenc", "qsv", "amf", "mf", "x264"] }
          }
        }
      }
    },
    "outputV2": {
      "type": "object",
      "additionalProperties": true,
      "required": ["width", "height", "fps"],
      "properties": {
        "width": { "$ref": "#/$defs/positiveNumber" },
        "height": { "$ref": "#/$defs/positiveNumber" },
        "fps": { "type": "integer", "minimum": 1 },
        "geometry": { "$ref": "#/$defs/outputGeometry" },
        "look": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/look" }] },
        "encoding": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "quality": { "enum": ["master", "high", "standard", "light"] },
            "encoder": { "enum": ["auto", "videotoolbox", "nvenc", "qsv", "amf", "mf", "x264"] }
          }
        }
      },
      "$comment": "notes-2026-08-18-timeline-latency-and-track-model.md §10-1。v2 の出力 fps は 1 以上の整数に固定する。29.97 / 23.976 が必要になった時点で有理数 {num, den} へ拡張する。既存 output は v0/v1 の互換契約として変更しない。"
    },
    "outputGeometry": {
      "enum": ["source"],
      "$comment": "docs/contract-2026-08-02-preview-parity.md §2.2。映像 item の幾何の基準。省略 = fit 互換（出力へ contain fit した後に transform）。\"source\" = 実寸基準（ソース実寸 × scale の box）。移行は scale × fit を一度だけ焼き込んでからこのマーカーを立てる（部分適用禁止）。他の値は無い。"
    },
    "chromaKey": {
      "type": "object",
      "additionalProperties": true,
      "required": ["color"],
      "properties": {
        "color": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "similarity": { "type": "number", "minimum": 0, "maximum": 1 },
        "blend": { "type": "number", "minimum": 0, "maximum": 1 },
        "background": { "type": "string", "minLength": 1, "pattern": "\\S" }
      },
      "$comment": "docs/contract-2026-07-22-render-basics.md #2。background は色文字列（#rrggbb 等）または画像/動画パス。similarity/blend 省略時はそれぞれ 0.2/0.1。"
    },
    "sourceV0": {
      "type": "object",
      "additionalProperties": true,
      "required": ["path"],
      "properties": {
        "path": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "proxy": {
          "oneOf": [
            { "type": "null" },
            { "type": "string", "minLength": 1, "pattern": "\\S" }
          ]
        },
        "chroma_key": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/chromaKey" }] }
      },
      "$comment": "docs/contract-2026-08-12-still-image-cut-source-v0.md 裁定1: path が静止画拡張子（png/jpe?g/webp/bmp/gif、大小無視）のとき、cuts[] はこのソースを静止画として扱う。判定は拡張子のみで、この $def に isStatic 等の新フィールドは足さない（packages/render-cut/src/layers.mjs の IMAGE_LAYER_SOURCE_PATTERN と同一集合）。静止画ソースは v0 の「cuts 空 = source 全体」を適用できない（画像に尺が無いため）ため cuts の明示が必須（edit-lint の cuts.still-image-cuts-required が検証）。"
    },
    "sourceV1": {
      "type": "object",
      "additionalProperties": true,
      "required": ["id", "path", "proxy"],
      "properties": {
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "path": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "proxy": {
          "oneOf": [
            { "type": "null" },
            { "type": "string", "minLength": 1, "pattern": "\\S" }
          ]
        },
        "chroma_key": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/chromaKey" }] }
      },
      "$comment": "docs/contract-2026-08-12-still-image-cut-source-v0.md 裁定1: path が静止画拡張子（sourceV0 と同一集合）のとき、cuts[].src でこの source を参照するカットは静止画として扱われる（video/image のソース混在が v1 の主眼）。cuts[].in は 0 推奨・out - in が表示尺（cuts[].freeze/speed は動作はするが無意味なため edit-lint が警告する）。"
    },
    "sourceV2": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "path"],
      "properties": {
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "path": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "proxy": {
          "oneOf": [
            { "type": "null" },
            { "type": "string", "minLength": 1, "pattern": "\\S" }
          ]
        },
        "chroma_key": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/chromaKey" }] }
      }
    },
    "narrationProvenance": {
      "type": "object",
      "additionalProperties": true,
      "required": ["provider"],
      "properties": {
        "provider": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "engine": { "type": "string" },
        "voice": { "type": "string" },
        "credit": { "type": "string" },
        "generated_at": { "type": "string" }
      },
      "$comment": "provider は voicevox/fal/elevenlabs/human を契約文書で例示するのみで enum 強制はしない。provider === voicevox のときの credit 必須は validate-edit.mjs で検証する（兄弟値比較のため）。"
    },
    "audioClipDenoise": {
      "type": "object",
      "additionalProperties": false,
      "required": ["method", "strength"],
      "properties": {
        "method": { "enum": ["fft", "nlm"] },
        "strength": { "type": "number", "minimum": 0, "maximum": 1 }
      }
    },
    "audioKeyframeLegacy": {
      "type": "object",
      "additionalProperties": false,
      "required": ["t", "gain_db"],
      "properties": {
        "t": { "$ref": "#/$defs/seconds" },
        "gain_db": { "type": "number", "minimum": -60, "maximum": 12 },
        "easing": { "$ref": "#/$defs/keyframeV2/properties/easing" }
      }
    },
    "audioKeyframesLegacy": {
      "type": "array",
      "minItems": 2,
      "items": { "$ref": "#/$defs/audioKeyframeLegacy" }
    },
    "narrationItem": {
      "type": "object",
      "additionalProperties": true,
      "required": ["id", "path", "t", "provenance"],
      "properties": {
        "id": { "type": "string", "pattern": "^n-\\d{4}$" },
        "path": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "t": { "$ref": "#/$defs/seconds" },
        "in": { "$ref": "#/$defs/seconds" },
        "out": { "$ref": "#/$defs/positiveNumber" },
        "gain_db": { "type": "number", "minimum": -60, "maximum": 12 },
        "speed": { "type": "number", "exclusiveMinimum": 0.25, "maximum": 4, "default": 1 },
        "pitch_semitones": { "type": "number", "minimum": -24, "maximum": 24, "default": 0 },
        "formant": { "enum": ["preserve", "shift"], "default": "preserve" },
        "denoise": { "$ref": "#/$defs/audioClipDenoise" },
        "lowcut_hz": { "type": "number", "minimum": 0, "maximum": 400, "default": 0 },
        "keyframes": { "$ref": "#/$defs/audioKeyframesLegacy" },
        "ducking": { "type": "boolean" },
        "duck_db": { "type": "number", "minimum": -40, "maximum": 0, "default": -12 },
        "duck_attack": { "type": "number", "minimum": 0, "maximum": 2, "default": 0.3 },
        "duck_release": { "type": "number", "minimum": 0, "maximum": 5, "default": 0.8 },
        "script": { "type": "string" },
        "reading": { "type": "string" },
        "caption_ref": { "type": "string", "pattern": "^c-\\d{4}$" },
        "provenance": { "$ref": "#/$defs/narrationProvenance" }
      },
      "$comment": "再生区間 = 素材の [in, out)。in は素材秒・省略時 0（素材頭から）。out は素材秒・省略時 素材末尾まで。タイムライン上の開始は t（timeline 秒）。out > in は edit-lint が検証し、実尺越えは render-cut が素材末尾へクランプする。"
    },
    "bgm": {
      "type": "object",
      "additionalProperties": true,
      "required": ["path"],
      "properties": {
        "path": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "gain_db": { "type": "number", "minimum": -60, "maximum": 12 },
        "speed": { "type": "number", "exclusiveMinimum": 0.25, "maximum": 4, "default": 1 },
        "pitch_semitones": { "type": "number", "minimum": -24, "maximum": 24, "default": 0 },
        "formant": { "enum": ["preserve", "shift"], "default": "preserve" },
        "denoise": { "$ref": "#/$defs/audioClipDenoise" },
        "lowcut_hz": { "type": "number", "minimum": 0, "maximum": 400, "default": 0 },
        "ducking": { "type": "boolean" },
        "keyframes": { "$ref": "#/$defs/audioKeyframesLegacy" },
        "duck_db": { "type": "number", "minimum": -40, "maximum": 0, "default": -12 },
        "duck_attack": { "type": "number", "minimum": 0, "maximum": 2, "default": 0.3 },
        "duck_release": { "type": "number", "minimum": 0, "maximum": 5, "default": 0.8 },
        "in": { "$ref": "#/$defs/seconds" },
        "fadeIn": { "$ref": "#/$defs/seconds" },
        "fadeOut": { "$ref": "#/$defs/seconds" },
        "fade_in_shape": { "enum": ["linear", "equal_power", "s_curve", "slow"] },
        "fade_out_shape": { "enum": ["linear", "equal_power", "s_curve", "slow"] }
      },
      "$comment": "fadeIn/fadeOut は秒・省略時 0（フェードなし）。BGM は timeline 全体尺にループ+トリムされるため、どちらかが timeline 尺/2 を超えて指定された場合は尺/2 にクランプされる（render-cut が実装、edit-lint がクランプ発生時に warning を出す）。in は BGM ファイル内の開始オフセット（素材秒・省略時 0）— ループ・fadeIn/fadeOut の既存意味論には影響しない（不変）。"
    },
    "sfxItem": {
      "type": "object",
      "additionalProperties": true,
      "required": ["path", "t"],
      "properties": {
        "track": { "$ref": "#/$defs/nonNegativeInteger", "default": 0 },
        "path": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "t": { "$ref": "#/$defs/seconds" },
        "in": { "$ref": "#/$defs/seconds" },
        "out": { "$ref": "#/$defs/positiveNumber" },
        "gain_db": { "type": "number", "minimum": -60, "maximum": 12 },
        "speed": { "type": "number", "exclusiveMinimum": 0.25, "maximum": 4, "default": 1 },
        "pitch_semitones": { "type": "number", "minimum": -24, "maximum": 24, "default": 0 },
        "formant": { "enum": ["preserve", "shift"], "default": "preserve" },
        "denoise": { "$ref": "#/$defs/audioClipDenoise" },
        "lowcut_hz": { "type": "number", "minimum": 0, "maximum": 400, "default": 0 },
        "fade_in": { "$ref": "#/$defs/seconds" },
        "fade_out": { "$ref": "#/$defs/seconds" },
        "fade_in_shape": { "enum": ["linear", "equal_power", "s_curve", "slow"] },
        "fade_out_shape": { "enum": ["linear", "equal_power", "s_curve", "slow"] },
        "keyframes": { "$ref": "#/$defs/audioKeyframesLegacy" },
        "ducking": { "type": "boolean" },
        "duck_db": { "type": "number", "minimum": -40, "maximum": 0, "default": -12 },
        "duck_attack": { "type": "number", "minimum": 0, "maximum": 2, "default": 0.3 },
        "duck_release": { "type": "number", "minimum": 0, "maximum": 5, "default": 0.8 }
      },
      "$comment": "再生区間 = 素材の [in, out)。in は素材秒・省略時 0（素材頭から）。out は素材秒・省略時 素材末尾まで。タイムライン上の開始は従来どおり t（timeline 秒）・表示尺 = out − in。out > in が必須（edit-lint が検証）。実尺越えは消費側（render-cut / preview）が素材末尾へクランプする。fade_in/fade_out は秒・省略時 0（フェードなし。docs/contract-2026-07-25-r6-audio-tracks-and-trim.md §2 追記、audio-clip-fades task）。フェード対象はこのクリップの実効再生窓 [t, t + 実効尺)（実効尺 = out − in、in/out 省略時は素材尺）。audio.bgm.fadeIn/fadeOutと同じ『実効尺の半分まで』クランプ規則を適用する（render-cut が実装、edit-lint がクランプ発生時に warning を出す）。"
    },
    "audioMaster": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "denoise": { "enum": ["off", "std", "strong"] },
        "loudnorm": { "type": "number", "minimum": -70, "maximum": 0 },
        "true_peak_dbtp": { "type": "number", "minimum": -9, "maximum": 0 }
      },
      "$comment": "docs/contract-2026-07-22-render-basics.md #5。denoise 省略時 off（無処理）。loudnorm 省略時 -14（既定ターゲット LUFS）— master オブジェクトが存在する時点でラウドネス正規化は既定で有効という実装判断（司令塔裁定。denoise には off という無効化用の値があるが loudnorm には無いため）。"
    },
    "audio": {
      "type": "object",
      "additionalProperties": true,
      "$comment": "bgm/sfx は docs/contract-2026-07-14-edit-json-v1-audio.md の確定契約どおり型定義済み（bgm はオブジェクト単数・sfx は配列。§6）。master は docs/contract-2026-07-22-render-basics.md #5。",
      "properties": {
        "narration": {
          "type": "array",
          "items": { "$ref": "#/$defs/narrationItem" }
        },
        "bgm": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/bgm" }] },
        "sfx": {
          "oneOf": [
            { "type": "null" },
            { "type": "array", "items": { "$ref": "#/$defs/sfxItem" } }
          ]
        },
        "master": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/audioMaster" }] },
        "duck_keys": {
          "type": "array",
          "uniqueItems": true,
          "items": { "enum": ["narration", "speech"] },
          "default": ["narration", "speech"]
        }
      }
    },
    "inlineCaptions": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": true
      },
      "$comment": "v0/v1 の edit.json に埋め込まれた字幕宣言を v2 移行時にそのまま保持するための器。字幕の正本は captions.json であり、この定義は既存宣言の語彙を拡張しない。"
    },
    "beatItem": {
      "type": "object",
      "additionalProperties": true,
      "required": ["id", "t", "kind", "strength"],
      "properties": {
        "id": { "type": "string", "pattern": "^b-\\d{4}$" },
        "src": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "t": { "$ref": "#/$defs/seconds" },
        "kind": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "strength": { "type": "number", "minimum": 0, "maximum": 1 },
        "basis": { "type": "string" }
      },
      "$comment": "docs/contract-2026-07-22-edit-json-v1-beats.md（見せ場マーカー）。t は timeline 秒ではなく (src, source 秒) アンカー（同 §3）。kind は hook/turn/punchline/reveal/emotion を契約文書で例示するのみで enum 強制はしない。id のファイル内一意性と src の参照整合（v1: sources[].id に存在すること / v0: src 自体を使用できないこと）は兄弟値の突き合わせが要るため JSON Schema では表現せず validate-edit.mjs と edit-lint が検証する（cutV1.src と同じ扱い）。"
    },
    "emphasisWordItem": {
      "type": "object",
      "additionalProperties": true,
      "required": ["id", "t_start", "t_end", "word", "emotion"],
      "properties": {
        "id": { "type": "string", "pattern": "^e-\\d{4}$" },
        "src": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "t_start": { "$ref": "#/$defs/seconds" },
        "t_end": { "$ref": "#/$defs/seconds" },
        "word": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "emotion": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "style_hint": { "type": "string" }
      },
      "$comment": "docs/contract-2026-07-23-edit-json-v1-emphasis-words.md（語レベル演出）。t_start/t_end は timeline 秒ではなく (src, source 秒) アンカー（同 §3）であり、analysis の transcriptSegment.words / captions.json の words[] の実測時刻を写す（同 §4）。emotion は joy/pain/surprise/anger/sadness/emphasis を、style_hint は one-char-bang/size-pulse/color-accent を契約文書で例示するのみで enum 強制はしない。t_end > t_start・id のファイル内一意性・src の参照整合（v1: sources[].id に存在すること / v0: src 自体を使用できないこと）は兄弟値の突き合わせが要るため JSON Schema では表現せず validate-edit.mjs と edit-lint が検証する（cutV0.out > in / cutV1.src と同じ扱い）。"
    },
    "direction": {
      "type": "object",
      "additionalProperties": true,
      "required": ["preset"],
      "properties": {
        "preset": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "intensity": { "type": "integer", "minimum": 0, "maximum": 100 },
        "overrides": { "type": "object" }
      },
      "$comment": "docs/contract-2026-07-23-edit-json-v1-direction.md（演出宣言）。配列ではなくオブジェクト = 宣言はファイルに 1 つ（同 §1）。preset は youtube-long-standard/shorts-high-energy/calm-explainer を契約文書で例示するのみで enum 強制はしない（narration provider と同じ流儀）。intensity は output.look.intensity（[0,1] の実数）とは別物で、整数 [0, 100]・既定 50（同 §4）。overrides は語彙未定義のため type object のみを見る（同 §6）。"
    },
    "transitionOut": {
      "type": "object",
      "additionalProperties": true,
      "required": ["type", "duration"],
      "properties": {
        "type": { "type": "string", "minLength": 1, "pattern": "\\S", "x-akari-known-values": ["dissolve", "fade", "fade-black", "fade-white", "fade-grays", "wipe-left", "wipe-right", "wipe-up", "wipe-down", "radial", "slide-left", "slide-right", "slide-up", "slide-down", "cover-left", "cover-right", "cover-up", "cover-down", "reveal-left", "reveal-right", "reveal-down", "reveal-up", "circle-open", "circle-close", "zoom-in", "squeeze-h", "squeeze-v", "blur", "pixelize"] },
        "duration": { "$ref": "#/$defs/positiveNumber" },
        "align": { "type": "string", "minLength": 1, "x-akari-known-values": ["center", "before-cut", "after-cut"] },
        "fill": { "type": "string", "minLength": 1, "x-akari-known-values": ["freeze"] }
      },
      "$comment": "docs/contract-2026-07-22-render-basics.md #3。v0 は指定されたカット境界のみ xfade 経路（残裁定3）。既知値は packages/edit-store の正準トランジション語彙と drift test で一致させる。align は窓の置き方（省略と知らない値は center）、fill は足りない素材の埋め方（省略と知らない値は埋めない）。どちらも開いた文字列で、知らない値で読み込みを止めない。"
    },
    "cutTransform": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "x": { "type": "number" },
        "y": { "type": "number" },
        "scale": { "$ref": "#/$defs/positiveNumber" },
        "scaleX": { "$ref": "#/$defs/positiveNumber" },
        "scaleY": { "$ref": "#/$defs/positiveNumber" },
        "rotate": { "type": "number" }
      },
      "$comment": "cuts[].transform 用。layerTransform と同語彙・同意味論（x/y=px 省略時0・scale=正の数 省略時1・rotate=度 省略時0）。layerTransform は additionalProperties:true のため既存 layerItem 検証結果を変えないよう独立した $def にしている（cuts 側は未知キーを拒否）。"
    },
    "cutCrop": {
      "type": "object",
      "additionalProperties": false,
      "required": ["x", "y", "w", "h"],
      "properties": {
        "x": { "type": "number", "minimum": 0, "maximum": 1 },
        "y": { "type": "number", "minimum": 0, "maximum": 1 },
        "w": { "type": "number", "exclusiveMinimum": 0, "maximum": 1 },
        "h": { "type": "number", "exclusiveMinimum": 0, "maximum": 1 }
      },
      "$comment": "docs/contract-2026-07-22-render-basics.md #6。出力キャンバス相対（0..1）の静的クロップ窓。x+w<=1 / y+h<=1（窓がキャンバス内に収まる）は兄弟値比較のため validate-edit.mjs で検証する。"
    },
    "cutFramingKeyframe": {
      "type": "object",
      "additionalProperties": false,
      "required": ["t", "scale"],
      "properties": {
        "t": { "$ref": "#/$defs/seconds" },
        "scale": { "$ref": "#/$defs/positiveNumber" },
        "cx": { "type": "number", "minimum": 0, "maximum": 1 },
        "cy": { "type": "number", "minimum": 0, "maximum": 1 }
      },
      "$comment": "docs/contract-2026-07-22-render-basics.md #6。t はカット内秒（速度適用後のローカル時間）。cx/cy 省略時 0.5（画角中心）。scale < 1 はレンダ側で 1 にクランプされる（クロップ実装はキャンバス外へは広げられないため）。"
    },
    "cutFraming": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "crop": { "$ref": "#/$defs/cutCrop" },
        "keyframes": {
          "type": "array",
          "minItems": 2,
          "items": { "$ref": "#/$defs/cutFramingKeyframe" }
        }
      },
      "$comment": "docs/contract-2026-07-22-render-basics.md #6（cuts[].framing）。keyframes は 2 点でズーム（開始/終了）・3 点以上で段階縮小（区間ごと線形補間）。crop と keyframes を両方宣言した場合は keyframes を優先する（crop は 1 点ズームの縮退形であり両立に意味がないため）。t の昇順・重複禁止は兄弟値比較のため validate-edit.mjs で検証する。"
    },
    "cutFreeze": {
      "type": "object",
      "additionalProperties": false,
      "required": ["at_sec", "duration_sec"],
      "properties": {
        "at_sec": { "$ref": "#/$defs/seconds" },
        "duration_sec": { "$ref": "#/$defs/positiveNumber" }
      },
      "$comment": "docs/contract-2026-07-22-render-basics.md #7（cuts[].freeze）。at_sec はカット内秒（速度適用後）でフレームを静止する時刻。duration_sec だけカット尺が伸びる（コンテンツを削らない）。at_sec がカットの再生尺を超えないことは兄弟値比較のため validate-edit.mjs で検証する。v0 は gap-aware タイムライン（明示 at/track）との併用不可（同ファイル #7 に明記）。"
    },
    "cutFx": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id"],
      "properties": {
        "id": { "type": "string", "minLength": 1 },
        "intensity": { "type": "number", "minimum": 0, "maximum": 1 },
        "params": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "color": { "type": "string", "minLength": 1 }
          }
        }
      },
      "$comment": "docs/contract-2026-08-05-fx-v0.md（画面 FX 小語彙 v0 の技術仕様。旧実装 FX 479 の移植ではなく新規 5 id として起票）。2026-08-11 撤去: v0 の 5 id（noise/particles/vignette/flare/color-overlay）はオーナー裁定により製品面から撤去し、id の enum を string へ緩和した（データ契約の三原則 — 受け口を広げる方向の互換。docs/contract-2026-07-17-data-contract-versioning.md）。presets/fx/ の FX_BUILDERS に未登録の id はスキーマ上は valid のまま、render 側が警告 + no-op で通す（packages/render-cut/src/fx.mjs）。intensity は全 id 共通ツマミ（省略時 1・0 は恒等）。params.color は色を使う fx が任意で使う置き場（形は維持）。"
    },
    "cutV0": {
      "type": "object",
      "additionalProperties": true,
      "required": ["in", "out"],
      "properties": {
        "at": { "$ref": "#/$defs/seconds" },
        "track": { "$ref": "#/$defs/nonNegativeInteger", "default": 0 },
        "captions": { "enum": ["on", "off"], "default": "on" },
        "in": { "$ref": "#/$defs/seconds" },
        "out": { "$ref": "#/$defs/seconds" },
        "reason": { "enum": ["silence", "word"] },
        "label": { "type": "string" },
        "speed": { "$ref": "#/$defs/positiveNumber" },
        "transform": { "$ref": "#/$defs/cutTransform" },
        "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
        "transition_out": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/transitionOut" }] },
        "framing": { "$ref": "#/$defs/cutFraming" },
        "freeze": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/cutFreeze" }] },
        "fx": { "type": "array", "items": { "$ref": "#/$defs/cutFx" } }
      },
      "not": { "required": ["src"] },
      "$comment": "out > in は JSON Schema 標準だけでは兄弟値を比較できないため validate-edit.mjs でも検証する。speed 省略時 1.0（等速）。fx は配列順 = 適用順（複数重ね掛け可）。"
    },
    "cutV1": {
      "type": "object",
      "additionalProperties": true,
      "required": ["src", "in", "out"],
      "properties": {
        "at": { "$ref": "#/$defs/seconds" },
        "track": { "$ref": "#/$defs/nonNegativeInteger", "default": 0 },
        "captions": { "enum": ["on", "off"], "default": "on" },
        "src": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "in": { "$ref": "#/$defs/seconds" },
        "out": { "$ref": "#/$defs/seconds" },
        "reason": { "enum": ["silence", "word"] },
        "label": { "type": "string" },
        "speed": { "$ref": "#/$defs/positiveNumber" },
        "transform": { "$ref": "#/$defs/cutTransform" },
        "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
        "transition_out": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/transitionOut" }] },
        "framing": { "$ref": "#/$defs/cutFraming" },
        "freeze": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/cutFreeze" }] },
        "fx": { "type": "array", "items": { "$ref": "#/$defs/cutFx" } }
      },
      "$comment": "src の参照整合と out > in は validate-edit.mjs でも検証する。speed 省略時 1.0（等速）。fx は配列順 = 適用順（複数重ね掛け可）。"
    },
    "overlayItem": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "track": { "$ref": "#/$defs/nonNegativeInteger", "default": 0 },
        "role": { "enum": ["background"] }
      },
      "allOf": [
        {
          "if": {
            "properties": { "role": { "const": "background" } },
            "required": ["role"]
          },
          "then": {
            "not": { "required": ["transform"] }
          }
        }
      ],
      "$comment": "（2026-08-07 オーナー裁定・確定）。role なしは従来どおり（互換）。role===\"background\" は「動かせない・必ずフレームを埋める・区間ごとに差し替えられる」種別で、transform を持てない（host が出力フレームへ恒等固定し、--x/--y/--scale/--rotate を無条件でロックする。overlay-runtime.js / preview-server app.js の mount・render-cut rasterize.mjs の renderOverlayNode が同じロックを実装）。vars 経由での --x/--y/--scale/--rotate 上書きと区間の重なりは JSON Schema では表現できない（vars は additionalProperties な自由形・重なりは兄弟要素比較）ため edit-lint（overlays.role.vars / overlays.role.overlap）で検証する。"
    },
    "layerTransform": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "x": { "type": "number" },
        "y": { "type": "number" },
        "scale": { "$ref": "#/$defs/positiveNumber" },
        "scaleX": { "$ref": "#/$defs/positiveNumber" },
        "scaleY": { "$ref": "#/$defs/positiveNumber" },
        "rotate": { "type": "number" }
      }
    },
    "layerBlendMode": {
      "type": "string",
      "enum": ["normal", "screen", "multiply", "add", "difference", "darken", "lighten", "overlay", "hardlight", "softlight"],
      "$comment": "ffmpeg blend= の c0_mode 名に対応（normal は render-cut 側の合成語彙で overlay= 直合成を意味し、ffmpeg blend= フィルタは経由しない）。"
    },
    "layerVideoChromaKey": {
      "type": "object",
      "additionalProperties": true,
      "required": ["color"],
      "properties": {
        "color": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "similarity": { "type": "number", "minimum": 0, "maximum": 1 },
        "blend": { "type": "number", "minimum": 0, "maximum": 1 }
      },
      "$comment": "kind===video レイヤーの背景置換用（ffmpeg chromakey= 準拠: color/similarity/blend）。R1（render-basics）の source.chroma_key と語彙は揃えるが $defs は独立させている（並行実装の衝突回避・rebase 時に統合を検討）。"
    },
    "layerCrop": {
      "type": "object",
      "additionalProperties": true,
      "required": ["x", "y", "w", "h"],
      "properties": {
        "x": { "type": "number", "minimum": 0, "maximum": 1 },
        "y": { "type": "number", "minimum": 0, "maximum": 1 },
        "w": { "type": "number", "exclusiveMinimum": 0, "maximum": 1 },
        "h": { "type": "number", "exclusiveMinimum": 0, "maximum": 1 },
        "rotate": { "type": "number", "minimum": -45, "maximum": 45 }
      },
      "$comment": "PiP レイヤーの空間クロップ（切り抜き）。0..1 正規化・ソースフレーム相対。この静的値は layerItem.keyframes が無いとき、またはキーフレーム点が crop を持たない区間の既定値として使う（#layerKeyframe 参照）。px 絶対は採らない（比率切替ラウンドの教訓）。x+w<=1・y+h<=1 は宣言的なレンジでは表現できないため validate-edit.mjs のセマンティック検証で担保する。適用順は render-cut/src/layers.mjs の合成チェーンで crop→scale→rotate→opacity→overlay（契約 contract-2026-08-02-preview-parity.md）。"
    },
    "layerPerspective": {
      "type": "object",
      "additionalProperties": true,
      "required": ["corners"],
      "properties": {
        "corners": {
          "type": "array",
          "minItems": 4,
          "maxItems": 4,
          "items": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "items": { "type": "number", "minimum": 0, "maximum": 1 }
          }
        }
      },
      "$comment": "PiP レイヤーの corner-pin パース変形（案 A・v0）。SSOT は 4 隅の正規化座標のみ:corners=[TL,TR,BL,BR]（各 [x,y] は 0..1・crop 適用後の層ボックス相対）。この静的値は layerItem.keyframes が無いとき、またはキーフレーム点が perspective を持たない区間の既定値として使う（#layerKeyframe 参照。時間変化させたい場合は layerItem.keyframes[].perspective を使う — 4 隅それぞれを線形補間する）。プリセット（右奥/左奥/上奥/下奥）+ 角度ツマミはオーサリング側（UI）で 4 隅へ展開してから保存する。退化四角形（面積ほぼ0）の拒否は validate-edit.mjs のセマンティック検証で担保する。適用順は render-cut/src/layers.mjs の合成チェーンで crop→scale→perspective→rotate→opacity→overlay（契約 contract-2026-08-02-preview-parity.md §2.4.4）。書き出しは ffmpeg perspective（透明パディング→内側へコーナーピン→元サイズへクロップ戻し）、プレビュー（shell/Web 両面）は同じ 4 隅から computeLayerPerspectiveVisual が CSS matrix3d を導出し、共に Heckbert のユニット正方形→四角形射影変換を使う（意図的なコード重複 — §2.2.1 と同じ方針。3 実装は同じ参照点でユニットテストして数値一致を担保）。"
    },
    "layerKeyframe": {
      "type": "object",
      "additionalProperties": true,
      "required": ["t"],
      "properties": {
        "t": { "$ref": "#/$defs/seconds" },
        "transform": { "$ref": "#/$defs/layerTransform" },
        "crop": { "$ref": "#/$defs/layerCrop" },
        "perspective": { "$ref": "#/$defs/layerPerspective" },
        "easing": { "enum": ["linear", "ease-in-out"] }
      },
      "$comment": "契約 contract-2026-08-09-transform-keyframes-v0.md（layerItem.keyframes の要素）。t はレイヤー内秒（layerItem.t を 0 とするローカル時間、既存 cuts[].framing.keyframes[].t と同じ規約）。transform/crop/perspective はそれぞれ独立の任意プロパティ（プロパティ毎の別トラックにはしない — 1 点で複数プロパティを同時に動かせる。詳細は $comment 続き）。ある区間の両端点が同じプロパティ（例 transform）を持てばその間は線形補間、片方の端点にしか無ければ直近の宣言値を保持（hold）する。どの点にも一度も宣言されないプロパティは layerItem 直下の静的値（例 layerItem.transform、省略時は各 $def の既定値）を全区間で保持する。easing は点ごとに設定し、その点へ入る区間（1つ前の点からこの点まで）の補間カーブを決める（先頭点の easing は無視される）。省略時は linear。"
    },
    "keyframeV2": {
      "type": "object",
      "additionalProperties": true,
      "required": ["t"],
      "properties": {
        "t": { "$ref": "#/$defs/frames" },
        "transform": { "$ref": "#/$defs/layerTransform" },
        "crop": { "$ref": "#/$defs/layerCrop" },
        "perspective": { "$ref": "#/$defs/layerPerspective" },
        "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
        "gain_db": { "type": "number", "minimum": -60, "maximum": 12 },
        "animator": {
          "type": "object",
          "additionalProperties": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "offset": { "type": "number", "minimum": -1, "maximum": 1 },
              "start": { "type": "number", "minimum": 0, "maximum": 1 },
              "end": { "type": "number", "minimum": 0, "maximum": 1 }
            }
          }
        },
        "vars": {
          "type": "object",
          "propertyNames": { "pattern": "^--[A-Za-z0-9_-]+$" },
          "additionalProperties": { "type": "number" },
          "$comment": "数値の CSS 変数のキー。v0 で書けるのは 3D の向き --akari-3d-rot-x/y/z（度）の 3 本で、1 つのグループとしてそろって入る。静的値は source.vars。easing のキーは \"vars.<名前>\"。"
        },
        "easing": {
          "oneOf": [
            {
              "type": "string",
              "anyOf": [
                { "enum": ["linear", "ease-in-out", "in-quad", "out-quad", "in-out-quad", "in-cubic", "out-cubic", "in-out-cubic", "in-quart", "out-quart", "in-out-quart", "in-expo", "out-expo", "in-out-expo", "in-back", "out-back", "in-out-back", "out-bounce", "out-elastic", "hold"] },
                { "pattern": "^cubic-bezier\\(\\s*-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)\\s*,\\s*-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)\\s*,\\s*-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)\\s*,\\s*-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)\\s*\\)$" }
              ]
            },
            {
              "type": "object",
              "additionalProperties": {
                "type": "string",
                "anyOf": [
                  { "enum": ["linear", "ease-in-out", "in-quad", "out-quad", "in-out-quad", "in-cubic", "out-cubic", "in-out-cubic", "in-quart", "out-quart", "in-out-quart", "in-expo", "out-expo", "in-out-expo", "in-back", "out-back", "in-out-back", "out-bounce", "out-elastic", "hold"] },
                  { "pattern": "^cubic-bezier\\(\\s*-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)\\s*,\\s*-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)\\s*,\\s*-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)\\s*,\\s*-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)\\s*\\)$" }
                ]
              }
            }
          ]
        }
      },
      "$comment": "v2 item.keyframes の要素。t は item.at を 0 とするアイテム内の整数フレーム。transform/crop/perspective/opacity/animator の補間・hold と easing は contract-2026-08-30-motion-and-keyframes-v0.md §2.1。"
    },
    "motionV0": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "in": {
          "type": "object",
          "additionalProperties": false,
          "required": ["preset", "duration"],
          "properties": {
            "preset": { "enum": ["fade", "slide-up", "slide-down", "slide-left", "slide-right", "scale", "wipe", "pop", "zoom", "twirl"] },
            "duration": { "$ref": "#/$defs/frames" },
            "ease": { "type": "string", "minLength": 1 },
            "amount": { "type": "number" }
          }
        },
        "out": {
          "type": "object",
          "additionalProperties": false,
          "required": ["preset", "duration"],
          "properties": {
            "preset": { "enum": ["fade", "slide-up", "slide-down", "slide-left", "slide-right", "scale", "wipe", "pop", "zoom", "twirl"] },
            "duration": { "$ref": "#/$defs/frames" },
            "ease": { "type": "string", "minLength": 1 },
            "amount": { "type": "number" }
          }
        },
        "loop": {
          "type": "object",
          "additionalProperties": false,
          "required": ["preset", "period"],
          "properties": {
            "preset": { "enum": ["pulse", "float", "spin", "blink", "jiggle"] },
            "period": { "type": "integer", "minimum": 1 },
            "ease": { "type": "string", "minLength": 1 },
            "amount": { "type": "number" }
          }
        }
      }
    },
    "animatorV0": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "basis", "shape", "start", "end", "offset", "amount"],
      "properties": {
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "basis": { "enum": ["chars", "words", "lines", "segments"] },
        "shape": { "enum": ["ramp", "triangle", "round", "smooth", "square", "ramp-down"] },
        "start": { "type": "number", "minimum": 0, "maximum": 1 },
        "end": { "type": "number", "minimum": 0, "maximum": 1 },
        "offset": { "type": "number", "minimum": -1, "maximum": 1 },
        "randomize": {
          "type": "object",
          "additionalProperties": false,
          "required": ["seed"],
          "properties": { "seed": { "type": "integer" } }
        },
        "amount": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "x": { "type": "number" },
            "y": { "type": "number" },
            "scale": { "type": "number" },
            "rotate": { "type": "number" },
            "opacity": { "type": "number", "minimum": -1, "maximum": 1 },
            "letterSpacing": { "type": "number" },
            "blur": { "type": "number" }
          }
        },
        "ease": { "type": "string", "minLength": 1 }
      }
    },
    "layerFilter": {
      "type": "object",
      "required": ["type"],
      "oneOf": [
        {
          "properties": {
            "type": { "const": "invert" }
          },
          "required": ["type"],
          "additionalProperties": false
        },
        {
          "properties": {
            "type": { "const": "lut" },
            "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
            "intensity": { "type": "number", "minimum": 0, "maximum": 1 }
          },
          "required": ["type", "id"],
          "additionalProperties": false
        },
        {
          "properties": {
            "type": { "const": "saturation" },
            "value": { "type": "number", "minimum": 0, "maximum": 3 }
          },
          "required": ["type", "value"],
          "additionalProperties": false
        }
      ]
    },
    "layerItem": {
      "type": "object",
      "additionalProperties": true,
      "required": ["id", "t", "duration", "kind"],
      "allOf": [
        {
          "if": {
            "properties": { "kind": { "const": "filter" } },
            "required": ["kind"]
          },
          "then": {
            "required": ["filter"],
            "properties": {
              "src": false,
              "chroma_key": false,
              "blend": false,
              "crop": false,
              "transform": false
            }
          }
        },
        {
          "if": {
            "properties": { "kind": { "enum": ["baked", "video"] } },
            "required": ["kind"]
          },
          "then": { "required": ["src"] }
        }
      ],
      "properties": {
        "track": { "$ref": "#/$defs/nonNegativeInteger", "default": 0 },
        "captions": { "enum": ["on", "off"], "default": "on" },
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "t": { "$ref": "#/$defs/seconds" },
        "duration": { "$ref": "#/$defs/positiveNumber" },
        "kind": { "enum": ["baked", "video", "filter"] },
        "src": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "audio": { "type": "boolean", "default": true, "description": "Play embedded audio for video layers; ignored for other kinds." },
        "gain_db": { "type": "number", "minimum": -60, "maximum": 12, "default": 0 },
        "filter": { "$ref": "#/$defs/layerFilter" },
        "preset": { "type": "string" },
        "params": { "type": "object" },
        "transform": { "$ref": "#/$defs/layerTransform" },
        "crop": { "$ref": "#/$defs/layerCrop" },
        "perspective": { "$ref": "#/$defs/layerPerspective" },
        "keyframes": {
          "type": "array",
          "minItems": 2,
          "items": { "$ref": "#/$defs/layerKeyframe" }
        },
        "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
        "blend": { "$ref": "#/$defs/layerBlendMode" },
        "chroma_key": { "$ref": "#/$defs/layerVideoChromaKey" }
      },
      "$comment": "契約 contract-2026-07-22-prerender-rail-and-assets.md §1.2 / contract-2026-08-09-transform-keyframes-v0.md（keyframes）。kind===baked は R2b の bake CLI が生成するアルファ付き mov（preset/params は再 bake 用の来歴）。kind===video は実映像（PinP 等）。chroma_key は kind===video のときのみ意味を持つ（validate-edit.mjs で検証）。crop は kind を問わず使える静的空間クロップ（#layerCrop）。perspective は kind を問わず使える静的 corner-pin パース変形（#layerPerspective）。keyframes はこのレイヤーの transform/crop/perspective を時間で動かす共通機構（#layerKeyframe。2 点以上・t 昇順・重複禁止は validate-edit.mjs で検証）。keyframes が無い、または使える点が 2 点未満のときは既存の静的 transform/crop/perspective のみが効く（回帰なし・バイト等価）。render-cut は cuts 合成後のベース映像へ t 順に合成する。kind===filter は src を持たず、filter を perspective region の内側だけへ適用する。"
    },
    "trackState": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "muted": { "type": "boolean" },
        "hidden": { "type": "boolean" }
      }
    },
    "tracks": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "cuts": { "type": "array", "items": { "$ref": "#/$defs/trackState" } },
        "overlays": { "type": "array", "items": { "$ref": "#/$defs/trackState" } },
        "layers": { "type": "array", "items": { "$ref": "#/$defs/trackState" } },
        "audio": { "type": "array", "items": { "$ref": "#/$defs/trackState" } }
      }
    },
    "timelineTrack": {
      "type": "object",
      "additionalProperties": true,
      "required": ["id", "kind"],
      "properties": {
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "kind": { "enum": ["cuts", "layers", "overlays", "captions", "audio"] },
        "ref": { "$ref": "#/$defs/nonNegativeInteger" },
        "label": { "type": "string" },
        "muted": { "type": "boolean" },
        "hidden": { "type": "boolean" },
        "locked": { "type": "boolean" }
      },
      "$comment": "timeline.tracks[] の器。配列順は画面の下から上。既存の tracks/trackState とは別契約。"
    },
    "timeline": {
      "type": "object",
      "additionalProperties": true,
      "required": ["tracks"],
      "properties": {
        "tracks": {
          "type": "array",
          "items": { "$ref": "#/$defs/timelineTrack" }
        }
      }
    },
    "itemSourceMediaV2": {
      "type": "object",
      "additionalProperties": false,
      "required": ["kind", "src", "in", "out"],
      "properties": {
        "kind": { "const": "media" },
        "src": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "in": { "$ref": "#/$defs/seconds" },
        "out": { "$ref": "#/$defs/seconds" },
        "framing": { "$ref": "#/$defs/cutFraming" },
        "transition_out": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/transitionOut" }] },
        "freeze": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/cutFreeze" }] },
        "fx": { "type": "array", "items": { "$ref": "#/$defs/cutFx" } },
        "speed": { "$ref": "#/$defs/positiveNumber" },
        "gain_db": { "type": "number", "minimum": -60, "maximum": 12 },
        "mute": { "type": "boolean" },
        "chroma_key": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/chromaKey" }] }
      },
      "$comment": "notes-2026-08-18-timeline-latency-and-track-model.md §9 / §10-1。src は editV2.sources[].id を参照し、in/out は素材内の秒。参照整合と out > in は兄弟値比較のため reader / edit-lint が検証する。gain_db / mute は埋め込み音声（speech）の減衰 / ミュートで、書き出しとプレビューで同値。省略時は gain 0・鳴る。"
    },
    "itemSourceAudioMediaV2": {
      "type": "object",
      "additionalProperties": false,
      "required": ["kind", "src"],
      "properties": {
        "kind": { "const": "media" },
        "src": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "in": { "$ref": "#/$defs/seconds", "default": 0 },
        "out": { "$ref": "#/$defs/seconds" },
        "speed": { "type": "number", "exclusiveMinimum": 0.25, "maximum": 4, "default": 1 },
        "pitch_semitones": { "type": "number", "minimum": -24, "maximum": 24, "default": 0 },
        "formant": { "enum": ["preserve", "shift"], "default": "preserve" }
      },
      "$comment": "audio lane 専用の media source。in/out は素材内の秒。in は省略時 0、out 省略時はファイル末尾まで再生し、対応する item.duration は実尺未解決を表す 0 センチネルにする。"
    },
    "itemSourceHtmlV2": {
      "type": "object",
      "additionalProperties": false,
      "required": ["kind", "path"],
      "properties": {
        "kind": { "const": "html" },
        "path": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "part": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "style": { "type": "object", "additionalProperties": { "type": "string" } },
        "text": { "type": "string" },
        "elements": {
          "type": "object",
          "patternProperties": {
            "^[#.][^\\s\\[\\]]+\\[(0|[1-9]\\d*)\\]$": {
              "type": "object", "additionalProperties": false, "required": ["style"],
              "properties": { "style": { "type": "object", "additionalProperties": { "type": "string" } } }
            }
          },
          "additionalProperties": false
        },
        "exclude": { "type": "array", "items": { "type": "string", "minLength": 1, "pattern": "\\S" }, "uniqueItems": true },
        "derivedFrom": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "vars": { "type": "object" },
        "params": {
          "type": "object",
          "properties": {},
          "additionalProperties": { "type": "string" }
        }
      },
      "$comment": "notes-2026-08-18-timeline-latency-and-track-model.md §9 / contract-2026-08-22-overlay-html-slots.md §1 / contract-2026-08-30-edit-json-v2-object-tree-v0.md §1.3-1.6。HTML はその場で描画する絵の出どころであり、素材トリムの in/out を持たない。params/style は値を string のみに閉じる。"
    },
    "itemSourceShapeV2": {
      "type": "object",
      "additionalProperties": false,
      "required": ["kind", "shape"],
      "properties": {
        "kind": { "const": "shape" },
        "shape": { "enum": ["rect", "rounded-rect", "ellipse", "line", "arrow", "speech-bubble", "path", "bubble"] },
        "params": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "width": { "type": "number", "exclusiveMinimum": 0, "default": 600 },
            "height": { "type": "number", "exclusiveMinimum": 0 },
            "fill": { "oneOf": [{ "type": "string" }, { "$ref": "#/$defs/shapeGradientV1" }], "default": "#f97316" },
            "stroke": { "oneOf": [{ "type": "string" }, { "$ref": "#/$defs/shapeGradientV1" }] },
            "strokeWidth": { "type": "number", "minimum": 0 },
            "cornerRadius": { "type": "number", "minimum": 0 },
            "path": { "type": "object", "additionalProperties": false, "required": ["d", "vb"], "properties": {
              "d": { "type": "string", "minLength": 1, "maxLength": 100000, "pattern": "^[MLCZ0-9eE+., \\t\\r\\n-]+$" },
              "vb": { "type": "array", "prefixItems": [{ "type": "number", "exclusiveMinimum": 0 }, { "type": "number", "exclusiveMinimum": 0 }], "items": false },
              "rule": { "enum": ["nonzero", "evenodd"] }
            } },
            "preset": { "type": "string", "minLength": 1 },
            "dash": { "enum": ["solid", "dash", "dot"] },
            "startCap": { "enum": ["none", "triangle", "chevron", "bar", "square", "circle", "diamond"] },
            "endCap": { "enum": ["none", "triangle", "chevron", "bar", "square", "circle", "diamond"] },
            "startCapScale": { "type": "number", "minimum": 0.5, "maximum": 3.0 },
            "endCapScale": { "type": "number", "minimum": 0.5, "maximum": 3.0 },
            "startCapFilled": { "type": "boolean" },
            "endCapFilled": { "type": "boolean" },
            "lineCap": { "enum": ["butt", "round"] },
            "style": { "enum": ["ellipse", "rounded", "rect", "jagged", "burst", "cloud", "wobble"] },
            "count": { "type": "integer", "minimum": 4, "maximum": 48 },
            "depth": { "type": "number", "minimum": 0, "maximum": 100 },
            "jitter": { "type": "number", "minimum": 0, "maximum": 100 },
            "seed": { "type": "integer", "minimum": -2147483648, "maximum": 2147483647 },
            "tail": { "enum": ["point", "dots", "none"] },
            "tailAngle": { "type": "number", "minimum": 0, "maximum": 360 },
            "tailLength": { "type": "number", "minimum": 0, "maximum": 100 },
            "tailWidth": { "type": "number", "minimum": 0, "maximum": 100 },
            "tailCurve": { "type": "number", "minimum": -100, "maximum": 100 }
          }
        }
      },
      "allOf": [
        { "if": { "properties": { "shape": { "const": "path" } }, "required": ["shape"] }, "then": { "required": ["params"], "properties": { "params": { "required": ["path"] } } } },
        { "if": { "anyOf": [
          { "properties": { "shape": { "enum": ["path", "bubble"] } }, "required": ["shape"] },
          { "properties": { "params": { "anyOf": [
            { "required": ["preset"] }, { "required": ["dash"] }, { "required": ["startCap"] }, { "required": ["endCap"] },
            { "required": ["startCapFilled"] }, { "required": ["endCapFilled"] },
            { "required": ["startCapScale"] }, { "required": ["endCapScale"] }, { "required": ["lineCap"] },
            { "required": ["style"] }, { "required": ["count"] }, { "required": ["depth"] }, { "required": ["jitter"] },
            { "required": ["seed"] }, { "required": ["tail"] }, { "required": ["tailAngle"] },
            { "required": ["tailLength"] }, { "required": ["tailWidth"] }, { "required": ["tailCurve"] },
            { "properties": { "fill": { "type": "object" } }, "required": ["fill"] },
            { "properties": { "stroke": { "type": "object" } }, "required": ["stroke"] }
          ] } }, "required": ["params"] }
        ] }, "then": { "properties": { "params": { "properties": {
          "strokeWidth": { "maximum": 100 }
        } } } } },
        { "if": { "properties": { "shape": { "const": "path" } }, "required": ["shape"] },
          "then": { "properties": { "params": { "properties": { "cornerRadius": { "maximum": 100 } } } } } },
        { "if": { "properties": { "shape": { "const": "rounded-rect" } }, "required": ["shape"] },
          "then": { "properties": { "params": { "properties": { "cornerRadius": { "default": 24 } } } } } },
        {
          "if": { "properties": { "shape": { "enum": ["line", "arrow"] } }, "required": ["shape"] },
          "then": { "properties": { "params": { "properties": { "height": { "default": 80 }, "strokeWidth": { "default": 8 } } } } },
          "else": { "properties": { "params": { "properties": { "height": { "default": 340 }, "strokeWidth": { "default": 0 } } } } }
        }
      ],
      "$comment": "図形ソースは v0 語彙を保ちつつ v1 の path / bubble / 線の端 / グラデーションへ拡張する。edit-store が決定論的なインライン SVG HTML オーバーレイへ降下する。"
    },
    "shapeGradientV1": {
      "type": "object", "additionalProperties": false, "required": ["type", "stops"],
      "properties": {
        "type": { "enum": ["linear", "radial"] },
        "angle": { "type": "number", "minimum": 0, "maximum": 360 },
        "stops": { "type": "array", "minItems": 2, "maxItems": 5, "items": {
          "type": "object", "additionalProperties": false, "required": ["color", "offset"],
          "properties": { "color": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$" }, "offset": { "type": "number", "minimum": 0, "maximum": 1 } }
        } }
      },
      "allOf": [{ "if": { "properties": { "type": { "const": "linear" } } }, "then": { "required": ["angle"] }, "else": { "not": { "required": ["angle"] } } }]
    },
    "itemSourceTelopV2": {
      "type": "object",
      "additionalProperties": false,
      "required": ["kind", "preset"],
      "properties": {
        "kind": { "const": "telop" },
        "preset": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "params": { "type": "object" },
        "baked": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "from": { "type": "string", "minLength": 1, "pattern": "\\S" }
      },
      "$comment": "notes-2026-08-18-timeline-latency-and-track-model.md §9。baked は別種別ではなく、同じテロップを速く描くための任意キャッシュ。"
    },
    "itemSourceFilterV2": {
      "type": "object",
      "additionalProperties": false,
      "required": ["kind", "filter"],
      "properties": {
        "kind": { "const": "filter" },
        "filter": { "$ref": "#/$defs/layerFilter" }
      },
      "$comment": "notes-2026-08-18-timeline-latency-and-track-model.md §9。既存 layerFilter の閉じた invert/lut/saturation 直和を v2 の filter source でも共有する。"
    },
    "itemSourceGroupV2": {
      "type": "object",
      "additionalProperties": false,
      "required": ["kind"],
      "properties": {
        "kind": { "const": "group" },
        "canvas": {
          "type": "object", "additionalProperties": false,
          "required": ["origin", "durationMode"],
          "properties": {
            "origin": { "enum": ["user", "plan"] },
            "durationMode": { "const": "fixed" },
            "intent": { "type": "string" },
            "background": {
              "type": "object", "additionalProperties": false, "required": ["type"],
              "properties": {
                "type": { "enum": ["none", "color"] },
                "color": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$" }
              },
              "allOf": [
                { "if": { "properties": { "type": { "const": "color" } } }, "then": { "required": ["color"] } },
                { "if": { "properties": { "type": { "const": "none" } } }, "then": { "not": { "required": ["color"] } } }
              ]
            }
          }
        }
      }
    },
    "itemSourceCaptionsV2": {
      "type": "object",
      "additionalProperties": false,
      "required": ["kind", "path"],
      "properties": {
        "kind": { "const": "captions" },
        "path": { "const": "captions.json" },
        "exclude": { "type": "array", "items": { "type": "string", "minLength": 1, "pattern": "\\S" }, "uniqueItems": true }
      }
    },
    "itemSourceCaptionV2": {
      "type": "object",
      "additionalProperties": false,
      "required": ["kind", "path", "id"],
      "properties": {
        "kind": { "const": "caption" },
        "path": { "const": "captions.json" },
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" }
      }
    },
    "photoRegionV1": {
      "type": "object", "additionalProperties": false,
      "required": ["id", "maskRef"],
      "properties": {
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "name": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "maskRef": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "invert": { "type": "boolean" },
        "enabled": { "type": "boolean" },
        "adjust": { "type": "object", "additionalProperties": false,
          "properties": { "basic": { "type": "object", "additionalProperties": false, "properties": {
            "exposure": { "type": "number", "minimum": -3, "maximum": 3 },
            "contrast": { "type": "number", "minimum": -1, "maximum": 1 },
            "saturation": { "type": "number", "minimum": -1, "maximum": 1 },
            "temperature": { "type": "number", "minimum": -1, "maximum": 1 }
          } } } },
        "filter": { "$ref": "#/$defs/adjustLutV0" },
        "blur": { "type": "number", "minimum": 0, "maximum": 50 }
      }
    },
    "itemV2Media": {
      "type": "object",
      "additionalProperties": false,
      "patternProperties": {
        "^captions$": { "enum": ["on", "off"], "default": "on" },
        "^reason$": { "enum": ["silence", "word"] },
        "^label$": { "type": "string" }
      },
      "required": ["id", "at", "duration", "source"],
      "properties": {
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "name": { "type": "string" },
        "hidden": { "type": "boolean" },
        "locked": { "type": "boolean" },
        "at": { "$ref": "#/$defs/itemAtV2" },
        "duration": { "$ref": "#/$defs/frames" },
        "anchor": { "$ref": "#/$defs/itemAnchorV2" },
        "transform": { "$ref": "#/$defs/layerTransform" },
        "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
        "blend": { "$ref": "#/$defs/layerBlendMode" },
        "crop": { "$ref": "#/$defs/layerCrop" },
        "adjust": { "$ref": "#/$defs/adjustV1" },
        "perspective": { "$ref": "#/$defs/layerPerspective" },
        "motion": { "$ref": "#/$defs/motionV0" },
        "animator": { "type": "array", "items": { "$ref": "#/$defs/animatorV0" } },
        "keyframes": { "oneOf": [{ "type": "array", "minItems": 2, "items": { "$ref": "#/$defs/keyframeV2" } }, { "type": "object", "additionalProperties": false, "required": ["path", "count"], "properties": { "path": { "type": "string", "pattern": "^motion/.+\\.json$" }, "count": { "type": "integer", "minimum": 2 } } }] },
        "items": { "type": "array", "items": { "$ref": "#/$defs/itemV2" } },
        "mask": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "maskFeather": { "type": "number", "minimum": 0, "maximum": 100 },
        "regions": { "type": "array", "maxItems": 32, "items": { "$ref": "#/$defs/photoRegionV1" } },
        "erase": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["mode", "points", "size", "hardness"], "properties": { "mode": { "enum": ["erase", "restore"] }, "points": { "type": "array", "minItems": 1, "items": { "type": "array", "items": { "type": "number", "minimum": 0, "maximum": 1 }, "minItems": 2, "maxItems": 2 } }, "size": { "type": "number", "exclusiveMinimum": 0, "maximum": 1 }, "hardness": { "type": "number", "minimum": 0, "maximum": 1 } } } },
        "flip": { "type": "object", "additionalProperties": false, "properties": { "h": { "type": "boolean" }, "v": { "type": "boolean" } } },
        "frame": { "type": "object", "additionalProperties": false, "properties": { "stroke": { "type": "object", "additionalProperties": false, "required": ["color", "width"], "properties": { "color": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" }, "width": { "type": "number", "minimum": 0, "maximum": 100 } } }, "cornerRadius": { "type": "number", "minimum": 0, "maximum": 100 } } },
        "source": { "$ref": "#/$defs/itemSourceMediaV2" },
        "audio": { "const": false },
        "cut_edge": { "$ref": "#/$defs/cutEdgeV2" }
      },
      "$comment": "mask は sources[].id。動画はグレースケール動画、静止画は白が見える 8bit グレー PNG。erase は EXIF 補正後・切り抜き前の素材座標。flip は切り抜き後の表示反転。\ncontract-2026-09-06-cut-audio-split-v0.md: audio は visual media 限定。false は埋め込み音声を停止、省略は従来どおり。"
    },
    "itemV2Html": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "at", "duration", "source"],
      "properties": {
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "name": { "type": "string" },
        "hidden": { "type": "boolean" },
        "locked": { "type": "boolean" },
        "at": { "$ref": "#/$defs/itemAtV2" },
        "duration": { "$ref": "#/$defs/frames" },
        "anchor": { "$ref": "#/$defs/itemAnchorV2" },
        "transform": { "$ref": "#/$defs/layerTransform" },
        "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
        "blend": { "$ref": "#/$defs/layerBlendMode" },
        "crop": { "$ref": "#/$defs/layerCrop" },
        "adjust": { "$ref": "#/$defs/adjustV1" },
        "perspective": { "$ref": "#/$defs/layerPerspective" },
        "motion": { "$ref": "#/$defs/motionV0" },
        "animator": { "type": "array", "items": { "$ref": "#/$defs/animatorV0" } },
        "keyframes": { "oneOf": [{ "type": "array", "minItems": 2, "items": { "$ref": "#/$defs/keyframeV2" } }, { "type": "object", "additionalProperties": false, "required": ["path", "count"], "properties": { "path": { "type": "string", "pattern": "^motion/.+\\.json$" }, "count": { "type": "integer", "minimum": 2 } } }] },
        "items": { "type": "array", "items": { "$ref": "#/$defs/itemV2" } },
        "source": { "$ref": "#/$defs/itemSourceHtmlV2" }
      }
    },
    "itemV2Shape": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "at", "duration", "source"],
      "properties": {
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "name": { "type": "string" },
        "hidden": { "type": "boolean" },
        "locked": { "type": "boolean" },
        "at": { "$ref": "#/$defs/itemAtV2" },
        "duration": { "$ref": "#/$defs/frames" },
        "anchor": { "$ref": "#/$defs/itemAnchorV2" },
        "transform": { "$ref": "#/$defs/layerTransform" },
        "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
        "blend": { "$ref": "#/$defs/layerBlendMode" },
        "crop": { "$ref": "#/$defs/layerCrop" },
        "perspective": { "$ref": "#/$defs/layerPerspective" },
        "motion": { "$ref": "#/$defs/motionV0" },
        "animator": { "type": "array", "items": { "$ref": "#/$defs/animatorV0" } },
        "keyframes": { "oneOf": [{ "type": "array", "minItems": 2, "items": { "$ref": "#/$defs/keyframeV2" } }, { "type": "object", "additionalProperties": false, "required": ["path", "count"], "properties": { "path": { "type": "string", "pattern": "^motion/.+\\.json$" }, "count": { "type": "integer", "minimum": 2 } } }] },
        "items": { "type": "array", "items": { "$ref": "#/$defs/itemV2" } },
        "source": { "$ref": "#/$defs/itemSourceShapeV2" }
      }
    },
    "itemV2Telop": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "at", "duration", "source"],
      "properties": {
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "name": { "type": "string" },
        "hidden": { "type": "boolean" },
        "locked": { "type": "boolean" },
        "at": { "$ref": "#/$defs/itemAtV2" },
        "duration": { "$ref": "#/$defs/frames" },
        "anchor": { "$ref": "#/$defs/itemAnchorV2" },
        "transform": { "$ref": "#/$defs/layerTransform" },
        "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
        "blend": { "$ref": "#/$defs/layerBlendMode" },
        "crop": { "$ref": "#/$defs/layerCrop" },
        "adjust": { "$ref": "#/$defs/adjustV1" },
        "perspective": { "$ref": "#/$defs/layerPerspective" },
        "motion": { "$ref": "#/$defs/motionV0" },
        "animator": { "type": "array", "items": { "$ref": "#/$defs/animatorV0" } },
        "keyframes": { "oneOf": [{ "type": "array", "minItems": 2, "items": { "$ref": "#/$defs/keyframeV2" } }, { "type": "object", "additionalProperties": false, "required": ["path", "count"], "properties": { "path": { "type": "string", "pattern": "^motion/.+\\.json$" }, "count": { "type": "integer", "minimum": 2 } } }] },
        "items": { "type": "array", "items": { "$ref": "#/$defs/itemV2" } },
        "source": { "$ref": "#/$defs/itemSourceTelopV2" }
      }
    },
    "itemV2Filter": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "at", "duration", "source"],
      "properties": {
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "name": { "type": "string" },
        "hidden": { "type": "boolean" },
        "locked": { "type": "boolean" },
        "at": { "$ref": "#/$defs/itemAtV2" },
        "duration": { "$ref": "#/$defs/frames" },
        "anchor": { "$ref": "#/$defs/itemAnchorV2" },
        "transform": { "$ref": "#/$defs/layerTransform" },
        "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
        "blend": { "$ref": "#/$defs/layerBlendMode" },
        "crop": { "$ref": "#/$defs/layerCrop" },
        "adjust": { "$ref": "#/$defs/adjustV1" },
        "perspective": { "$ref": "#/$defs/layerPerspective" },
        "motion": { "$ref": "#/$defs/motionV0" },
        "animator": { "type": "array", "items": { "$ref": "#/$defs/animatorV0" } },
        "keyframes": { "oneOf": [{ "type": "array", "minItems": 2, "items": { "$ref": "#/$defs/keyframeV2" } }, { "type": "object", "additionalProperties": false, "required": ["path", "count"], "properties": { "path": { "type": "string", "pattern": "^motion/.+\\.json$" }, "count": { "type": "integer", "minimum": 2 } } }] },
        "items": { "type": "array", "items": { "$ref": "#/$defs/itemV2" } },
        "source": { "$ref": "#/$defs/itemSourceFilterV2" }
      }
    },
    "itemV2Group": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "at", "duration", "source"],
      "properties": {
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "name": { "type": "string" },
        "hidden": { "type": "boolean" },
        "locked": { "type": "boolean" },
        "at": { "$ref": "#/$defs/itemAtV2" },
        "duration": { "$ref": "#/$defs/frames" },
        "anchor": { "$ref": "#/$defs/itemAnchorV2" },
        "transform": { "$ref": "#/$defs/layerTransform", "properties": { "scaleX": false, "scaleY": false } },
        "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
        "blend": { "$ref": "#/$defs/layerBlendMode" },
        "crop": { "$ref": "#/$defs/layerCrop" },
        "adjust": { "$ref": "#/$defs/adjustV1" },
        "perspective": { "$ref": "#/$defs/layerPerspective" },
        "motion": { "$ref": "#/$defs/motionV0" },
        "animator": { "type": "array", "items": { "$ref": "#/$defs/animatorV0" } },
        "keyframes": {"allOf": [{"oneOf": [{"type": "array", "minItems": 2, "items": {"$ref": "#/$defs/keyframeV2"}}, {"type": "object", "additionalProperties": false, "required": ["path", "count"], "properties": {"path": {"type": "string", "pattern": "^motion/.+\\.json$"}, "count": {"type": "integer", "minimum": 2}}}]}, {"if": {"type": "array"}, "then": {"items": {"properties": {"transform": {"properties": {"scaleX": false, "scaleY": false}}}}}}]},
        "items": { "type": "array", "items": { "$ref": "#/$defs/itemV2" } },
        "source": { "$ref": "#/$defs/itemSourceGroupV2" }
      }
    },
    "itemV2Captions": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "at", "duration", "source"],
      "properties": {
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "name": { "type": "string" },
        "hidden": { "type": "boolean" },
        "locked": { "type": "boolean" },
        "at": { "$ref": "#/$defs/itemAtV2" },
        "duration": { "$ref": "#/$defs/frames" },
        "transform": { "$ref": "#/$defs/layerTransform" },
        "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
        "blend": { "$ref": "#/$defs/layerBlendMode" },
        "crop": { "$ref": "#/$defs/layerCrop" },
        "adjust": { "$ref": "#/$defs/adjustV1" },
        "perspective": { "$ref": "#/$defs/layerPerspective" },
        "motion": { "$ref": "#/$defs/motionV0" },
        "animator": { "type": "array", "items": { "$ref": "#/$defs/animatorV0" } },
        "keyframes": { "oneOf": [{ "type": "array", "minItems": 2, "items": { "$ref": "#/$defs/keyframeV2" } }, { "type": "object", "additionalProperties": false, "required": ["path", "count"], "properties": { "path": { "type": "string", "pattern": "^motion/.+\\.json$" }, "count": { "type": "integer", "minimum": 2 } } }] },
        "items": { "type": "array", "items": { "$ref": "#/$defs/itemV2" } },
        "source": { "$ref": "#/$defs/itemSourceCaptionsV2" }
      }
    },
    "itemV2Caption": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "at", "duration", "source"],
      "properties": {
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "name": { "type": "string" },
        "hidden": { "type": "boolean" },
        "locked": { "type": "boolean" },
        "at": { "$ref": "#/$defs/itemAtV2" },
        "duration": { "$ref": "#/$defs/frames" },
        "transform": { "$ref": "#/$defs/layerTransform" },
        "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
        "blend": { "$ref": "#/$defs/layerBlendMode" },
        "crop": { "$ref": "#/$defs/layerCrop" },
        "adjust": { "$ref": "#/$defs/adjustV1" },
        "perspective": { "$ref": "#/$defs/layerPerspective" },
        "motion": { "$ref": "#/$defs/motionV0" },
        "animator": { "type": "array", "items": { "$ref": "#/$defs/animatorV0" } },
        "keyframes": { "oneOf": [{ "type": "array", "minItems": 2, "items": { "$ref": "#/$defs/keyframeV2" } }, { "type": "object", "additionalProperties": false, "required": ["path", "count"], "properties": { "path": { "type": "string", "pattern": "^motion/.+\\.json$" }, "count": { "type": "integer", "minimum": 2 } } }] },
        "items": { "type": "array", "items": { "$ref": "#/$defs/itemV2" } },
        "source": { "$ref": "#/$defs/itemSourceCaptionV2" }
      }
    },
    "itemV2AudioMedia": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "at", "duration", "source"],
      "properties": {
        "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "name": { "type": "string" },
        "hidden": { "type": "boolean" },
        "locked": { "type": "boolean" },
        "at": { "$ref": "#/$defs/itemAtV2" },
        "duration": { "$ref": "#/$defs/frames" },
        "anchor": { "$ref": "#/$defs/itemAnchorV2" },
        "role": { "enum": ["sfx", "narration", "bgm", "speech"] },
        "link": { "type": "string", "minLength": 1, "pattern": "\\S" },
        "cut_edge": { "$ref": "#/$defs/cutEdgeV2" },
        "reason": { "enum": ["silence", "word"] },
        "label": { "type": "string" },
        "mute": { "type": "boolean" },
        "source": { "$ref": "#/$defs/itemSourceAudioMediaV2" },
        "gain_db": { "type": "number", "minimum": -60, "maximum": 12 },
        "denoise": { "$ref": "#/$defs/audioClipDenoise" },
        "lowcut_hz": { "type": "number", "minimum": 0, "maximum": 400, "default": 0 },
        "keyframes": {
          "type": "array",
          "minItems": 2,
          "items": {
            "allOf": [
              { "$ref": "#/$defs/keyframeV2" },
              { "required": ["gain_db"] }
            ]
          }
        },
        "fade_in": { "$ref": "#/$defs/seconds" },
        "fade_out": { "$ref": "#/$defs/seconds" },
        "fade_in_shape": { "enum": ["linear", "equal_power", "s_curve", "slow"] },
        "fade_out_shape": { "enum": ["linear", "equal_power", "s_curve", "slow"] },
        "ducking": { "type": "boolean" },
        "duck_db": { "type": "number", "minimum": -40, "maximum": 0, "default": -12 },
        "duck_attack": { "type": "number", "minimum": 0, "maximum": 2, "default": 0.3 },
        "duck_release": { "type": "number", "minimum": 0, "maximum": 5, "default": 0.8 },
        "script": { "type": "string" },
        "reading": { "type": "string" },
        "caption_ref": { "type": "string", "pattern": "^c-\\d{4}$" },
        "provenance": { "$ref": "#/$defs/narrationProvenance" }
      },
      "$comment": "audio lane 専用 item。role 省略時は sfx。at/duration は整数フレームで、duration 0 は実尺未解決を表す明示的センチネル。素材側 in/out と fade は秒。script/reading/provenance は narration 用の任意メタデータ。\ncontract-2026-09-06-cut-audio-split-v0.md: link は編集上の関連（同じ edit 内の visual media id と一対一）で、source や時刻の再計算命令ではない。mute 省略は false。speech は分離した本編音声。"
    },
    "cutEdgeV2": {
      "type": "object", "additionalProperties": false, "required": ["in", "out", "at"],
      "properties": {
        "in": { "type": "number", "minimum": 0 },
        "out": { "type": "number", "minimum": 0 },
        "at": { "type": "integer" }
      }
    },
    "itemV2": {
      "oneOf": [
        { "$ref": "#/$defs/itemV2Media" },
        { "$ref": "#/$defs/itemV2Html" },
        { "$ref": "#/$defs/itemV2Shape" },
        { "$ref": "#/$defs/itemV2Telop" },
        { "$ref": "#/$defs/itemV2Filter" },
        { "$ref": "#/$defs/itemV2Group" },
        { "$ref": "#/$defs/itemV2Captions" },
        { "$ref": "#/$defs/itemV2Caption" }
      ],
      "$comment": "notes-2026-08-18-timeline-latency-and-track-model.md §9 / §10-1 / contract-2026-08-30-edit-json-v2-object-tree-v0.md §1。共通キーだけを item 直下に置き、出どころ固有キーは source.kind ごとの閉じた oneOf に隔離する。at/duration は整数フレーム、子の at は親相対。"
    },
    "captionTrackContentV2": {
      "type": "object",
      "additionalProperties": false,
      "required": ["from"],
      "properties": {
        "from": { "const": "captions.json" }
      },
      "$comment": "notes-2026-08-18-timeline-latency-and-track-model.md §8 / §10-1。字幕の正本は captions.json のソース秒のまま保ち、track には射影済み item を焼かず参照の器だけを置く。"
    },
    "trackV2": {
      "oneOf": [
        {
          "type": "object",
          "additionalProperties": false,
          "required": ["id", "lane", "items"],
          "allOf": [
            {
              "if": {
                "properties": { "lane": { "const": "audio" } },
                "required": ["lane"]
              },
              "then": {
                "properties": {
                  "items": { "type": "array", "items": { "$ref": "#/$defs/itemV2AudioMedia" } }
                }
              },
              "else": {
                "properties": {
                  "items": { "type": "array", "items": { "$ref": "#/$defs/itemV2" } }
                }
              }
            }
          ],
          "properties": {
            "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
            "lane": { "enum": ["visual", "audio"] },
            "name": { "type": "string" },
            "muted": { "type": "boolean" },
            "target": { "type": "boolean" },
            "sync": { "type": "boolean" },
            "items": { "type": "array" }
          }
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": ["id", "lane", "content"],
          "properties": {
            "id": { "type": "string", "minLength": 1, "pattern": "\\S" },
            "lane": { "enum": ["visual", "audio"] },
            "name": { "type": "string" },
            "muted": { "type": "boolean" },
            "target": { "type": "boolean" },
            "sync": { "type": "boolean" },
            "content": { "$ref": "#/$defs/captionTrackContentV2" }
          }
        }
      ],
      "$comment": "deprecated: tracks[].content は tolerant reader のためだけに残す。lane は visual/audio の 2 値。audio lane の items は itemV2AudioMedia、visual lane の items は itemV2 を使う。items と字幕 content は閉じた oneOf により必ず排他。muted はトラックの音声をミュート。visual トラックなら cut の埋め込み音声、audio トラックなら item を書き出し・プレビューから除外。"
    },
    "editV0": {
      "type": "object",
      "additionalProperties": true,
      "required": ["version", "output", "source"],
      "properties": {
        "tracks": { "$ref": "#/$defs/tracks" },
        "timeline": { "$ref": "#/$defs/timeline" },
        "version": { "const": 0 },
        "output": { "$ref": "#/$defs/output" },
        "source": { "$ref": "#/$defs/sourceV0" },
        "cuts": {
          "type": "array",
          "items": { "$ref": "#/$defs/cutV0" }
        },
        "overlays": {
          "type": "array",
          "items": { "$ref": "#/$defs/overlayItem" }
        },
        "audio": { "$ref": "#/$defs/audio" },
        "layers": {
          "type": "array",
          "items": { "$ref": "#/$defs/layerItem" }
        },
        "beats": {
          "type": "array",
          "items": { "$ref": "#/$defs/beatItem" }
        },
        "emphasis_words": {
          "type": "array",
          "items": { "$ref": "#/$defs/emphasisWordItem" }
        },
        "direction": { "$ref": "#/$defs/direction" }
      },
      "not": { "required": ["sources"] }
    },
    "editV1": {
      "type": "object",
      "additionalProperties": true,
      "required": ["version", "output", "sources"],
      "properties": {
        "tracks": { "$ref": "#/$defs/tracks" },
        "timeline": { "$ref": "#/$defs/timeline" },
        "version": { "const": 1 },
        "output": { "$ref": "#/$defs/output" },
        "sources": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/sourceV1" }
        },
        "cuts": {
          "type": "array",
          "items": { "$ref": "#/$defs/cutV1" }
        },
        "overlays": {
          "type": "array",
          "items": { "$ref": "#/$defs/overlayItem" }
        },
        "audio": { "$ref": "#/$defs/audio" },
        "layers": {
          "type": "array",
          "items": { "$ref": "#/$defs/layerItem" }
        },
        "beats": {
          "type": "array",
          "items": { "$ref": "#/$defs/beatItem" }
        },
        "emphasis_words": {
          "type": "array",
          "items": { "$ref": "#/$defs/emphasisWordItem" }
        },
        "direction": { "$ref": "#/$defs/direction" }
      },
      "not": { "required": ["source"] }
    },
    "editV2": {
      "type": "object",
      "additionalProperties": false,
      "required": ["version", "output", "sources", "tracks"],
      "properties": {
        "version": { "const": 2 },
        "output": { "$ref": "#/$defs/outputV2" },
        "sources": {
          "type": "array",
          "items": { "$ref": "#/$defs/sourceV2" }
        },
        "sync_groups": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["id", "members"],
            "properties": {
              "id": { "type": "string", "minLength": 1 },
              "members": {
                "type": "array", "minItems": 2,
                "items": {
                  "type": "object", "additionalProperties": false,
                  "required": ["source", "offset_sec"],
                  "properties": {
                    "source": { "type": "string", "minLength": 1 },
                    "offset_sec": { "type": "number" }
                  }
                }
              }
            }
          }
        },
        "tracks": {
          "type": "array",
          "items": { "$ref": "#/$defs/trackV2" }
        },
        "audio": { "$ref": "#/$defs/audio" },
        "captions": { "$ref": "#/$defs/inlineCaptions" },
        "thumbnail": {
          "type": "object",
          "additionalProperties": true,
          "required": ["path"],
          "properties": { "path": { "type": "string", "minLength": 1, "pattern": "\\S" } }
        }
      },
      "$comment": "notes-2026-08-18-timeline-latency-and-track-model.md §9 / §10 / §10-1。tracks の配列順が画面の下から上、すなわち合成 z 順の唯一の正本。出力軸は映像・音声とも整数フレーム、素材軸は秒、字幕は captions.json のソース秒を維持する。新規の SFX / narration / BGM は audio lane の items で宣言する。top-level audio は既存 v2 fixture の互換 fallback と master 設定のため残す。"
    }
  }
}
