$schema: "http://json-schema.org/draft-07/schema#"
title: Hyper-Animator Quality Gates
description: |
  Pre-render quality contract for the hyper-animator skill.
  Defines 12 gates that generated HyperFrames HTML must pass before rendering.
  Gates are checked in order; any error-severity failure blocks rendering.
type: object
required:
  - schemaVersion
  - gates
properties:
  schemaVersion:
    type: string
    description: Schema version
    enum: ["1.0.0"]

  gates:
    type: array
    description: Ordered list of quality gate rules
    items:
      $ref: "#/definitions/QualityGate"
    minItems: 13
    maxItems: 13

definitions:
  QualityGate:
    type: object
    description: A single quality gate rule
    required:
      - id
      - name
      - severity
      - check
      - failureMessage
      - fix
    properties:
      id:
        type: string
        description: Unique gate identifier (kebab-case)
        pattern: "^[a-z][a-z0-9-]*$"

      name:
        type: string
        description: Human-readable gate name

      severity:
        type: string
        description: "error -- blocks rendering; warning -- flags a concern"
        enum:
          - error
          - warning

      check:
        type: object
        description: The inspection rule to apply
        required:
          - type
        properties:
          type:
            type: string
            description: Type of check to perform
            enum:
              - regex
              - inspection
          pattern:
            type: string
            description: Regular expression to match (for regex checks)
          description:
            type: string
            description: Inspection description (for inspection checks)
          appliesTo:
            type: array
            description: Generation modes this gate applies to
            items:
              type: string
              enum:
                - assemble_existing_catalog_items
                - generate_new_hyperframes_html
            default:
              - assemble_existing_catalog_items
              - generate_new_hyperframes_html

      failureMessage:
        type: string
        description: Human-readable message shown when the gate fails

      fix:
        type: object
        description: Remediation guidance
        required:
          - guidance
        properties:
          guidance:
            type: string
            description: Instructions for fixing the violation
          example:
            type: string
            description: Optional code example showing the fix

gates:
  - id: has-fixed-dimensions
    name: Fixed Dimensions Present
    severity: error
    check:
      type: regex
      pattern: "data-width=.*data-height=|data-height=.*data-width="
      description: "HTML must contain data-width and data-height attributes"
    failureMessage: "Missing fixed width and height dimensions in HTML."
    fix:
      guidance: "Add data-width and data-height attributes to the root composition element. Values must be positive integers matching FinalPlan dimensions."
      example: "<div data-composition-id=\"my-comp\" data-width=\"1920\" data-height=\"1080\">"

  - id: has-composition-id
    name: Composition ID Attribute
    severity: error
    check:
      type: regex
      pattern: "data-composition-id="
      description: "The HTML must have a data-composition-id attribute"
    failureMessage: "Missing data-composition-id attribute."
    fix:
      guidance: "Add data-composition-id to the root wrapper with a unique kebab-case identifier for CSS scoping."
      example: "<div data-composition-id=\"my-composition\">"

  - id: has-paused-timeline
    name: Paused GSAP Timeline
    severity: error
    check:
      type: regex
      pattern: "gsap\\.timeline\\(\\{.*paused:\\s*true"
      description: "Primary animation uses gsap.timeline({ paused: true })"
    failureMessage: "Missing gsap.timeline({ paused: true }). The primary timeline must be paused for external control."
    fix:
      guidance: "Create the main timeline using gsap.timeline({ paused: true })."
      example: "const tl = gsap.timeline({ paused: true });"

  - id: has-timeline-registration
    name: Timeline Registration
    severity: error
    check:
      type: regex
      pattern: "window\\.__timelines"
      description: "Paused timeline registered on window.__timelines"
    failureMessage: "Missing window.__timelines registration."
    fix:
      guidance: "Register the timeline on window.__timelines using the data-composition-id as the key."
      example: "window.__timelines = window.__timelines || {};\nwindow.__timelines[\"my-comp\"] = tl;"

  - id: has-duration-coverage
    name: Duration Coverage
    severity: error
    check:
      type: regex
      pattern: "data-duration="
      description: "Composition declares data-duration matching the plan"
    failureMessage: "Missing duration coverage declaration."
    fix:
      guidance: "Add data-duration matching the FinalPlan duration. Pad the GSAP timeline to cover the full duration."
      example: "data-duration=\"30\"\ntl.to({}, { duration: padTime }, totalTime);"

  - id: no-date-now
    name: No Date.now() for Timing
    severity: error
    check:
      type: regex
      pattern: "Date\\.now\\(\\)"
      description: "No Date.now() used for driving animation progress"
    failureMessage: "Uses Date.now() which can desync timelines during render."
    fix:
      guidance: "Replace Date.now() with GSAP timeline methods like tl.time() or tl.progress()."
      example: "const elapsed = tl.time() * 1000;"

  - id: no-setinterval
    name: No setInterval for Primary Timing
    severity: error
    check:
      type: regex
      pattern: "setInterval\\("
      description: "No setInterval for primary animation timing"
    failureMessage: "Uses setInterval which is unreliable for frame-accurate render."
    fix:
      guidance: "Replace setInterval with GSAP ticker or requestAnimationFrame driven by the timeline."
      example: "gsap.ticker.add(function() { if (tl.isActive()) { ... } });"

  - id: no-paste-comments
  - id: no-math-random
    name: No Math.random() for Visual Generation
    severity: error
    check:
      type: regex
      pattern: "Math\\.random\\(\\)"
      description: "No Math.random() calls — use seeded PRNG (mulberry32)"
    failureMessage: "Uses Math.random() which is non-deterministic — produces different output every render frame."
    fix:
      guidance: "Replace all Math.random() with a seeded PRNG (mulberry32). Use var rng = mulberry32(FIXED_SEED)."
      example: "function mulberry32(a) { return function() { a |= 0; a = a + 0x6D2B79F5 | 0; var t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }; var rng = mulberry32(20260630);"

  - id: no-paste-comments
    name: No Unresolved Paste Comments
    severity: error
    check:
      type: regex
      pattern: "<!-- paste from "
      description: "No unresolved paste placeholder comments in HTML"
    failureMessage: "HTML contains unresolved paste placeholder comments."
    fix:
      guidance: "Replace each paste comment with the actual rendered snippet or use generate mode."
      example: "<div class=\"caption-pill\">text</div>"

  - id: async-readiness
    name: Async Resource Readiness
    severity: error
    check:
      type: inspection
      description: "Fonts, images, GLTF, WebGL assets loaded before timeline registration; WebGL has fallback"
    failureMessage: "Async resource readiness not handled. Timeline may register before assets are ready."
    fix:
      guidance: "Wrap registration in Promise.all for fonts and images. Provide CSS fallback timeline for WebGL."
      example: "Promise.all([document.fonts.ready, ...]).then(() => { register(); }).catch(() => { fallback(); });"

  - id: has-beat-helper
    name: Beat Helper Function
    severity: warning
    check:
      type: regex
      pattern: "function beat\\(n\\)"
      description: "beat(n) helper function for beat-indexed animation timing"
    failureMessage: "Missing beat(n) helper function for beat-indexed animation timing."
    fix:
      guidance: "Add inline beat helper function referencing the __beats array from beat detection JSON."
      example: "function beat(n) { return (__beats[n] || (n * 60000 / __bpm)) / 1000; }"

  - id: beat-array-present
    name: Beat Timestamps Array
    severity: warning
    check:
      type: regex
      pattern: "var __beats\\s*="
      description: "Beat timestamps array for beat-synced animation"
    failureMessage: "Missing __beats array for beat-synced animation."
    fix:
      guidance: "Inline the beat timestamps (in milliseconds) from the beat detection JSON output."
      example: "var __beats = [0, 468, 937, 1406, ...];"

  - id: no-stale-beat-path
    name: No Stale Beat References
    severity: warning
    check:
      type: inspection
      description: "Verify all beat(n) calls use indices within __beats array bounds"
    failureMessage: "beat(n) call references index beyond __beats array length, producing NaN timing."
    fix:
      guidance: "Ensure all beat() indices exist in the __beats array. If the composition extends beyond the BGM, the fallback BPM-based calculation in beat() handles this automatically."

  - id: catalog-map-current
    name: Catalog Map Current
    severity: warning
    check:
      type: inspection
      description: "Check catalog-map.json source.generatedAt is within 30 days"
    failureMessage: "Catalog map is stale — run sync-catalog.py --sync-catalog-map to update."
    fix:
      guidance: "Run python3 scripts/sync-catalog.py --sync-catalog-map to merge with live HyperFrames catalog."

  - id: no-curl-subprocess
    name: No curl Subprocess for API Calls
    severity: error
    check:
      type: regex
      pattern: "subprocess.*curl"
      description: "API calls must use urllib, not curl subprocess — avoids Chinese text encoding corruption"
    failureMessage: "Using curl subprocess for API calls may corrupt Chinese text encoding. Use urllib.request instead."
    fix:
      guidance: "Replace curl subprocess with urllib.request. See tts-gen.py and minimax-gen.py for reference."
