# GraphicSpec 规范文档 — 自定义"背景特效"

> 本文档译自日语原版。**日语版为正式版本**，如有出入以日语版为准。

本文档是 movlyric 背景特效（在歌词背后绘制图形、线条）定义“GraphicSpec”的完整规范。
在应用的“样式编辑器 →“背景”标签 → 背景特效 →“自定义”→ 用 AI 生成…”中使用（让歌词文字本身运动的“文字动作”是另一套体系，
其规范请见 `ANIMATION_SPEC.md`）。
本文档的目标是：只读过此文档的 AI（或人类）也能写出可以直接粘贴到应用中并被接受的 JSON。
文档末尾附有可交给 AI 使用的提示词模板。将本文档整体作为提示词的一部分贴入同样可以正常使用。

## 1. 概述

背景特效是在歌词**背后**运动的动态图形（歌词本身的动作由另一套规范 AnimationSpec 处理）。

GraphicSpec 以"排列 N 个相同形状的图形并使其运动"的形式书写。图形共有**圆形、矩形、线条、多边形**四种。

每个图形的位置、大小、颜色、不透明度，可以用两种写法来决定。

1. **定型合成**（`基准值 + 随机数 + 正弦波 + 漂移 + 音频`）— 写法简短
2. **数学表达式** — 可以像 `0.5 + sin(t * 2 + i) * 0.2` 这样自由计算，也能写条件分支，以及**引用前一个图形位置的链式排布**

数学表达式并不是 JavaScript。**不能写赋值、循环、函数定义，只能是一个表达式返回一个数值**（与电子表格公式的思路相同）。可使用的名称仅限 §2.6 列出的范围。

## 2. JSON 格式完整规范

### 2.1 根节点：GraphicSpec

```json
{ "layers": [ <Layer>, ... ] }
```

| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
| `layers` | Layer[] | **必需** | 1～4 个。先写的图层在**最里侧**，后写的画在更靠前的位置 |

根节点下能写的键只有这一个。

### 2.2 Layer

| 字段 | 类型 | 必需 | 取值范围 | 说明 |
|---|---|---|---|---|
| `shape` | 字符串 | **必需** | `"circle"` / `"rect"` / `"line"` / `"polygon"` | 图形种类 |
| `count` | 整数 | **必需** | 1～200 | 图形数量 |
| `sides` | 整数 | 可选 | 3～24 | polygon 的顶点数（省略时为 5）。其他 shape 会忽略此项 |
| `star` | 数值 | 可选 | 0～1 | polygon 的星形程度。0=正多边形，0.5 为标准星形。其他 shape 会忽略此项 |
| `when` | 字符串 | 可选 | 数学表达式 | 绘制条件。求值为 0 的实例不会被绘制（§2.6） |
| `spread` | 字符串 | 可选 | §2.4 | 各图形的初始排布方式。省略时全部重叠在同一位置 |
| `x` | Value | **必需** | — | 横向位置。**相对画面宽度的比例**（0=左端，0.5=居中，1=右端） |
| `y` | Value | **必需** | — | 纵向位置。**相对画面高度的比例**（0=上端，1=下端） |
| `size` | Value | **必需** | — | 大小。**相对画面高度的比例**。circle=半径 / rect=高度 / line=长度 |
| `aspect` | 数值 | 可选 | 0.01～100 | rect 的宽度 = size × aspect。对 line 影响的是粗细。circle 会忽略此项 |
| `rotation` | Value | 可选 | — | 旋转（**度**）。circle 会忽略此项 |
| `color` | 字符串或整数 | **必需** | §2.5 | 颜色 |
| `alpha` | Value | **必需** | — | 不透明度，会被截断到 0～1 |
| `wrap` | 布尔值 | 可选 | — | 为 true 时，移出画面的图形会从对侧重新出现（用于持续流动的效果） |

### 2.3 Value（值的写法）

有三种写法。

| 写法 | 示例 | 用途 |
|---|---|---|
| 数值 | `"x": 0.5` | 常量 |
| **数学表达式（字符串）** | `"x": "0.5 + sin(t) * 0.2"` | 自由计算、条件分支、依赖前一个图形（§2.6） |
| 对象 | 见下方 | "基准值＋随机数＋波动＋漂移＋音频"的定型合成 |

**拿不定主意时，用数学表达式更容易写。** 对象形式是为了把常见组合写得更简短。

**直接写数值即为常量**（`"x": 0.5`）。对象形式如下。

```json
{
  "base": 0.5,
  "random": [-0.2, 0.2],
  "wave": { "amplitude": 0.05, "periodMs": 3000, "phaseByIndex": 0.7 },
  "drift": -0.1,
  "audio": { "band": 2, "scale": 0.3 }
}
```

| 字段 | 类型 | 必需 | 取值范围 | 说明 |
|---|---|---|---|---|
| `base` | 数值 | **必需** | — | 基准值 |
| `random` | [数值, 数值] | 可选 | — | 在此范围内取随机数加到 base 上。**每个图形取值都不同**（每次都生成相同画面的确定性随机数） |
| `wave` | 对象 | 可选 | — | 用正弦波使其摆动（见下） |
| `drift` | 数值 | 可选 | — | 随时间线性变化的量（**每秒**）。负值表示反方向 |
| `audio` | 对象 | 可选 | — | 使其对音频作出反应（见下） |

**wave**

| 字段 | 类型 | 必需 | 取值范围 | 说明 |
|---|---|---|---|---|
| `amplitude` | 数值 | **必需** | — | 摆动幅度 |
| `periodMs` | 整数 | **必需** | 50～60000 | 一次往返所需时间（毫秒）。**不可为小数** |
| `phaseByIndex` | 数值 | 可选 | — | 按图形错开相位的量。**省略时所有图形会一齐做相同运动**，想让它们错落有致时可填入 0.3～1.0 左右的值 |

**audio**

| 字段 | 类型 | 必需 | 取值范围 | 说明 |
|---|---|---|---|---|
| `band` | 整数 | **必需** | 0～63 | 频段。**0 为最低音（底鼓）**，数字越大音调越高 |
| `scale` | 数值 | **必需** | — | 乘以频段电平（0～1）后加到 base 上的量 |

若想对低音作出反应，`band` 建议取 0～3；中音约 8～16；高音约 24 以上。

### 2.4 spread（初始排布）

会**叠加**到 x/y 的值上。

| 值 | 排布方式 |
|---|---|
| `"none"`（省略时） | 全部位于同一位置。用于打算配合 `random` 打散的场景 |
| `"random"` | 在整个画面内随机散布（每次都生成相同排布的确定性随机数） |
| `"row"` | 横向一列等间距排列 |
| `"column"` | 纵向一列等间距排列 |
| `"ring"` | 沿圆周等间距排列 |
| `"spiral"` | 从中心向外画出螺旋（按黄金角分布，不易重叠） |
| `"grid"` | 接近正方形的网格 |

### 2.5 color（颜色）

**推荐引用调色板。** 这样用户更改配色时，特效也会随之联动变化。

| 值 | 含义 |
|---|---|
| `"text"` | 文字颜色 |
| `"accent"` | 强调色（副歌颜色） |
| `"stroke"` | 描边颜色 |
| 整数 | `0xRRGGBB` 的直接数值（例如：红色 = 16711680）。**不可使用字符串 `"#ff0000"`** |

### 2.6 数学表达式

在值的位置写字符串即视为数学表达式。**最多 200 个字符**。

```json
{ "x": "0.5 + sin(t * 2 + i * 0.5) * 0.2" }
```

#### 可用变量

| 名称 | 含义 |
|---|---|
| `t` | 经过的秒数 |
| `at` | 音频的经过秒数（想让效果对音频响应时使用这个） |
| `i` | 当前图形的编号（从 0 开始） |
| `n` | 图形总数 |
| `p` | `i/(n-1)`（归一化到 0～1 的位置；n=1 时为 0） |
| `beat` | 节拍进度 0～1（0 为拍点起始） |
| `energy` | 该段落的能量值 0～1 |
| `chorus` | 处于副歌时为 1，否则为 0 |
| `aspect` | 画面的宽高比（宽/高） |
| `px` `py` `psize` `prot` | **前一个图形**的位置、大小、旋转（§2.7） |
| `PI` `TAU` | 圆周率及其 2 倍 |

#### 可用函数

`sin` `cos` `tan` `asin` `acos` `atan` `atan2` `abs` `sqrt` `exp` `log` `sign` `floor` `ceil` `round` `min` `max` `pow` `hypot` `mod` `clamp` `lerp` `step` `smoothstep` `rand` `band`

- `mod(a, b)` — 结果恒为正的取模（`mod(-1, 3)` = 2）
- `clamp(v, min, max)` / `lerp(a, b, t)` / `step(edge, v)` / `smoothstep(e0, e1, v)`
- `rand(k)` — **该图形专属**的随机数 0～1（改变 `k` 会得到不同的数列；每次都生成相同值的确定性随机数）
- `band(k)` — 频段 `k` 的电平 0～1（0 为最低音）

#### 运算符

`+` `-` `*` `/` `%` `^`（乘方），比较运算符 `<` `<=` `>` `>=` `==` `!=`，逻辑运算符 `&&` `||` `!`，以及**三元运算符 `条件 ? A : B`**。

**条件分支用三元运算符书写。**

```json
{ "size": "chorus ? 0.15 : 0.05" }
```

#### 不能写的内容

不能写变量赋值、循环、函数定义、字符串。**只能用一个表达式得出一个数值**。写上表之外的名称（如 `Math`、`Date`、`random` 等）会报错。

### 2.7 依赖前一个图形的计算

`px` / `py` / `psize` / `prot` 是**前一个图形（`i-1`）的确定值**，借此可以写出链式、堆叠的排布。这些值会保持**画面比例**原样传入。

```json
{ "x": "px + 0.15", "y": "py + sin(i) * 0.05" }
```

在开头（`i = 0`）时从 0 开始。想让"第一个特殊处理"时，请用 `i` 做分支。

```json
{ "size": "i == 0 ? 0.1 : psize * 0.8" }
```

## 3. 常见错误（禁止事项）

- **超出取值范围**：`count: 0`、`300`，`periodMs: 10` 或 `100000`，`band: 64`，`layers` 达 5 个以上，均不允许
- **不存在的属性**：Layer 中只能写 §2.2 列出的项目，Value 的对象形式只能写 §2.3 的这 5 个。不存在 `speed`、`opacity`、`radius`、`points` 等属性
- **在表达式中写不存在的名称**：`Math.sin(t)` 不可用（应写作 `sin(t)`）。`Date`、`random`、`window` 等均不可用
- **在表达式中写循环或赋值**：不能写 `for`、`=`、`let`。一个表达式只能得出一个数值
- **用字符串写颜色**：`"#ff0066"`、`"red"` 均不可，只能用调色板名称或整数
- **禁止使用小数的位置**：`count` / `periodMs` / `band` 只能是整数
- **单位搞混**：x/y/size 是**相对画面的比例，而不是像素**。`"size": 40` 会变成"画面高度的 40 倍"，从而铺满整个画面。**大小通常在 0.01～0.2 左右**
- **忘记设置 `phaseByIndex`**：不设置的话所有图形会一齐做相同运动，显得机械、不自然
- **混入非 JSON 内容**：不可写注释、末尾逗号（**数学表达式可以作为字符串书写** — §2.6）

## 4. 示例

以下均为可以直接粘贴到应用中并通过校验的完整 JSON。

### 4.1 飘浮的光粒

随机散布的圆形缓缓向上漂移，同时左右摆动并明灭。移出画面上端后会从下方重新出现。

```json
{
  "layers": [
    {
      "shape": "circle",
      "count": 24,
      "spread": "random",
      "x": { "base": 0, "wave": { "amplitude": 0.02, "periodMs": 7000, "phaseByIndex": 0.9 } },
      "y": { "base": 0, "drift": -0.05 },
      "size": { "base": 0.03, "random": [0, 0.03] },
      "color": "accent",
      "alpha": { "base": 0.18, "wave": { "amplitude": 0.1, "periodMs": 4000, "phaseByIndex": 0.6 } },
      "wrap": true
    }
  ]
}
```

### 4.2 音频响应的均衡器

排列在画面下部的竖条，会随着从低音到高音各频段的电平伸缩。

```json
{
  "layers": [
    {
      "shape": "rect",
      "count": 32,
      "spread": "row",
      "x": 0.5,
      "y": 0.95,
      "size": { "base": 0.02, "audio": { "band": 4, "scale": 0.5 } },
      "aspect": 0.4,
      "color": "accent",
      "alpha": 0.75
    }
  ]
}
```

### 4.3 斜向流动的线条

斜向的线条在背景中缓缓持续横穿。通过叠加一层较淡的远景和一层较浓的近景来营造纵深感。

```json
{
  "layers": [
    {
      "shape": "line",
      "count": 14,
      "spread": "random",
      "x": { "base": 0, "drift": 0.06 },
      "y": 0,
      "size": { "base": 0.5, "random": [0, 0.4] },
      "aspect": 0.03,
      "rotation": -24,
      "color": "text",
      "alpha": 0.06,
      "wrap": true
    },
    {
      "shape": "line",
      "count": 5,
      "spread": "random",
      "x": { "base": 0, "drift": 0.11 },
      "y": 0,
      "size": { "base": 0.7, "random": [0, 0.3] },
      "aspect": 0.05,
      "rotation": -24,
      "color": "accent",
      "alpha": 0.14,
      "wrap": true
    }
  ]
}
```

### 4.4 随底鼓脉动的圆环

排列在圆周上的点，会随低音（底鼓）向外扩张。

```json
{
  "layers": [
    {
      "shape": "circle",
      "count": 24,
      "spread": "ring",
      "x": { "base": 0.5, "audio": { "band": 0, "scale": 0.06 } },
      "y": { "base": 0.5, "audio": { "band": 0, "scale": 0.06 } },
      "size": { "base": 0.012, "audio": { "band": 0, "scale": 0.02 } },
      "color": "accent",
      "alpha": 0.5
    }
  ]
}
```

### 4.5 仅副歌时飞舞的星星（条件分支 + 多边形）

只有在副歌期间星形才会出现并旋转。用 `when` 按图层控制出现/消失，大小随节拍脉动。

```json
{
  "layers": [
    {
      "shape": "polygon",
      "sides": 5,
      "star": 0.55,
      "count": 18,
      "spread": "random",
      "when": "chorus",
      "x": 0,
      "y": { "base": 0, "drift": -0.03 },
      "size": "0.02 + (1 - beat) * 0.015 + rand(0) * 0.02",
      "rotation": "t * 40 + i * 20",
      "color": "accent",
      "alpha": 0.5,
      "wrap": true
    }
  ]
}
```

### 4.6 连绵延伸的线条（依赖前一个图形）

从第一条线开始，依次把下一条线放在前一条线终点附近，形成链式排布。这是使用 `px` / `py` 的示例。

```json
{
  "layers": [
    {
      "shape": "line",
      "count": 40,
      "x": "i == 0 ? 0.05 : px + 0.022",
      "y": "i == 0 ? 0.5 : py + sin(t + i * 0.4) * 0.012",
      "size": 0.04,
      "aspect": 0.06,
      "rotation": "sin(t + i * 0.4) * 40",
      "color": "accent",
      "alpha": "0.15 + p * 0.5"
    }
  ]
}
```

## 5. 供 AI 使用的提示词模板

请复制以下框内内容交给 AI。推荐的用法是：先贴上本规范文档全文，紧接着再贴这份模板（如果不附带规范文档、只把模板单独交给 AI，AI 容易在取值范围或单位上出错）。

**以下为复制起点**

```text
你是 movlyric 的背景特效设计师。请严格遵循上面贴出的“GraphicSpec 规范文档”，创建一个 GraphicSpec 的 JSON。

输出规则:
- 只输出 JSON（不要写说明、前言或结语；可以用代码围栏包裹 JSON）
- 根节点为 {"layers": [...]}，layers 为 1～4 个
- Layer 中只能写 shape, count, sides, star, when, spread, x, y, size, aspect, rotation, color, alpha, wrap
- shape 为 "circle" / "rect" / "line" / "polygon" 之一。polygon 可以带 sides(3..24) 和 star(0..1)
- count 为 1～200 的整数
- x, y, size, alpha 为必需项，可用以下三种方式书写:
  (a) 直接写数值（常量）
  (b) **数学表达式字符串**（最多 200 字符）
  (c) {"base":..., "random":[min,max], "wave":{"amplitude":...,"periodMs":整数50..60000,"phaseByIndex":...}, "drift":..., "audio":{"band":整数0..63,"scale":...}}
- 数学表达式中只能使用变量 t, at, i, n, p, beat, energy, chorus, aspect, px, py, psize, prot, PI, TAU
- 数学表达式中只能使用函数 sin cos tan asin acos atan atan2 abs sqrt exp log sign floor ceil round min max pow hypot mod clamp lerp step smoothstep rand(k) band(k)
- 数学表达式中不能写赋值、循环、函数定义、字符串。条件分支请用三元运算符 `条件 ? A : B` 书写
- **不可写成 Math.sin(t) 这样的形式**（应写作 sin(t)）。不能使用 Date / random / window 等名称
- 想依赖前一个图形时，使用 px / py / psize / prot（以画面比例原样传入；i=0 时为 0）
- 想按图层控制出现/消失时，在 when 中写数学表达式（求值为 0 则不绘制）
- **x/y/size 是相对画面的比例**（不是像素）。size 通常在 0.01～0.2 左右；x/y 在 0～1 范围内表示画面内部
- color 为 "text" / "accent" / "stroke" 之一，或 0xRRGGBB 形式的整数。不可使用字符串形式的颜色代码
- 想让多个图形错开运动时，在 wave 中加入 phaseByIndex 来错开相位（不设置的话全部会一齐运动，显得不自然）
- 想让效果对音频响应时使用 audio。band 为 0 表示最低音（底鼓），数字越大音调越高
- 想让图形持续流出画面外时，将 drift 与 wrap:true 组合使用
- 不要写不存在的属性、注释、末尾逗号、表达式

想要的特效:
{在此写下你想要的效果}

（仅在想修改已有特效时，请在下方粘贴当前的 JSON。若是新建，请删除这一整节）
当前 JSON:
{当前 JSON}
```

**复制到此为止**
