---
name: gitruck-gtrk-mg
description: "MG 动态图颗粒铺轨器——成片 SOP 第 ⑤ 步（**最后叠上**），在 B-roll 底轨三源（影视素材 / 本地素材 / AI 情景片段）全部铺齐、且 ④ 全局抽帧检查构图通过之后，把栏目的 html-particle 动态图颗粒（透明叠加 / 满屏底层）叠铺到已定稿的底轨之上。先由栏目 MG 生产 skill 按派单各槽位产颗粒，再驱动 `gtrk mg` 命令 lint + 铺进 `.gtrk` 工程的 beat_track。当用户想「铺 MG 颗粒 / 上动态图 / 铺动效 / 给这段配动画铺进工程 / 把 MG 派单铺轨 / 消费 dispatch.mg」时使用本 skill。凡涉及把 MG 动态图颗粒铺进已剪好的成片工程，优先用本 skill 驱动 gtrk CLI，别让用户自己去终端敲 `gtrk mg`。"
license: "MIT"
metadata:
  decimalai-skill-id: "a44ba3a8-d46d-4334-8668-59d121a1577a"
  decimalai-version-id: "85e5e000-f153-4567-8fd2-8fc36ef6a9c6"
  decimalai-version: "2"
  decimalai-body-sha256: "7a2c57f76ae01b398e732aa5859823ff1c879fa4a38f66e29f1a8cd829297b12"
  decimalai-name: "gitruck/gtrk-mg"
  decimalai-format: "1"
  decimalai-invocation: "model"
  decimalai-category: "design-media"
  decimalai-source: "https://app.decimal.ai/s/gitruck-gtrk-mg@2/SKILL.md"
---
# MG 动态图铺轨（gtrk-mg）

把 `gtrk split` 派好的 **MG 槽位**（`dispatch.mg`），先由**栏目的 MG 生产 skill** 产成 html-particle 动态图颗粒，再交给 `gtrk mg` 命令 lint + 铺进 `.gtrk` 工程的 `beat_track`——**叠在已经定稿的底轨之上（B-roll 三源全落齐、构图已核）**。**CLI 是手（lint / 铺轨 / 写回），你是脑（认 SOP 位置、驱动栏目生产 skill、管用户检查点、判 lint 该不该硬铺）。栏目生产 skill 只产纯净颗粒、不知 gtrk 命令，由你驱动。**

> **本 skill 已含你需要的全部信息**（SOP 位置、业务分离、逐槽位工作流、命令参数、lint 铁律、排错、交棒）。命令参数细节以 `gtrk mg --help` 为准；颗粒的视觉/内容规范**不在这里**——那是栏目资产，由栏目 MG 生产 skill 全权负责。

## 你在成片 SOP 的哪一步（先认位置，别抢跑）

成片是**有序 SOP + 用户检查点**，不是并行一次铺完。全序：

> ① `/gtrk-oralcut`（剪口播）→ ② `/gtrk-splitter`（拆分派单出 `dispatch.json`）→ **③ B-roll 底轨全铺齐**（`/gtrk-matrix` 铺影视/本地素材 + `/gtrk-ai-drama` 产稿→外部出片→用户手动回铺，**两条腿同一阶段**）→ **④ 全局抽帧检查画面构图（用户确认）** → **⑤ 本 skill：MG（含 ov）最后叠上** → `gtrk subtitle lay` 上字幕（快速成片模式必做）→ `gtrk render` 收口（**会对未命中缓存的颗粒云渲、有计费**，见末节）

**你是第 ⑤ 步、也是最后一层。** MG 颗粒是**唯一的叠加层**——底下那整层底轨必须先定死（**三源全落齐**），否则你的排版避让是对着一个不完整的画面做的。

> ⚠️ **AI 情景片段属于底轨 B-roll 画面家族，不是叠加层**。先完成 AI 片段回铺，再依据最终底轨构图产出 MG，避免遮挡主体。

所以动手前先过前置硬门。

### 前置硬门（不满足就别铺，先回上一步）

1. **影视/本地素材 B-roll 已铺且用户已确认**：若 `dispatch.film_broll` 非空，必须 ③ 已跑过 `gtrk matrix` **且用户已在 opencut 里挑选/调整确认**。没确认 → **别铺 MG**，先回 ③。（为空 = 本片没这条腿，看下一门。）
2. ★ **AI 情景片段已回铺，或用户明示先跳过**：若 `dispatch.ai_drama` 非空，必须已回铺完（用户已把外部平台出的片子拖进工程）**或**用户**明示**「先跳过 AI、先把 MG 铺上」。
   - 外部平台抽卡可能数天，严格串行会把你无限期卡住，故留这个口子。
   - **走跳过时你 MUST 付这个对价**：把**与 AI beat 相邻或重叠区间的 MG 颗粒**逐一标记为「**AI 回铺后待复查构图**」，并在交棒 `render` 时把该清单复述给用户。**MUST NOT 静默跳过** —— 用户得知道哪几颗将来可能要返工。
   - （为空 = 本片没 AI 车道，此门自动通过。）
3. ★ **④ 全局抽帧检查已过、用户已确认构图**：对**三源合并后的最终底轨**抽帧看主体位置 / 安全区 / 画面朝向 / 明暗，用户确认无误。**这与 ③ 那次「挑选 B-roll」的确认是两次不同的确认**，别拿前一次顶替。没做 → 先做 ④ 再回来。
4. **有 split 产物**：需要一个跑过 `gtrk split` 的产物目录（含 `split/dispatch.json` 与工程 `.gtrk`）。没有 → 先回 ② `/gtrk-splitter`。
5. **`dispatch.mg` 非空**：为空 = 本片没有 MG 车道 → **别硬造**，直接 `gtrk render` 收口（见末节；无颗粒 = 零计费）。
6. **CLI 在**：`gtrk` 找不到 → 让用户装 `npm i -g @gitruck/cli@latest`（需先有 Node.js）。

## 业务分离：谁产颗粒（栏目），谁铺轨（本 skill）

**本框架 skill 不硬编任何栏目的视觉风格/生产内容。** MG 颗粒长什么样、用什么视觉语法，全由**栏目的 MG 生产 skill** 决定；本 skill 只负责「在对的时候、按派单、驱动它产，然后把成品铺进工程」。

**车道 → 栏目生产 skill 的解析（照此，别猜）**：读**有效栏目配置**的 `style.skills[]`（配置文件 `~/.gitruck/columns/<id>.json`；栏目 id 由 `--column <id>` 或 config `defaultColumn` 选取；零配置 = L0 内置默认栏目，**不携带 style 清单** → 必然走下方「无匹配」分支）。在 `style.skills[]` 里取 **`produces` 归一后（旧 `RRV_MG` → `MG`）等于 `MG`** 的条目 → 触发它 `ref` 指向的 skill 产颗粒。
- 命中多条 → 按栏目约定取其一（一般栏目只登记一个 MG 生产 skill）。
- 条目带 `routing:"none"`（管线外产物，如封面）→ 跳过，不当 MG 生产 skill。
- **无匹配** = 本栏目没有 MG 生产 skill → 别硬铺，给用户**四选一**（都不替他选）：
  1. **建栏目**：`/gtrk-style-maker` 沉淀自己的 MG 生产 skill（一次性，长期资产）；
  2. **换栏目**：`--column <id>` 指向已配置的栏目；
  3. **用中性块**：Hyperframes registry 的中性颗粒骨架（`gtrk mg fetch`，见下节「中性块工作流」）——没建栏目也能当场出 MG，但**骨架不是成品**，内容与审美 MUST 改写。
  4. **用文字模板**：同合云自家的文字特效模板库（`gtrk mg fetch --source text`，见下节「文字模板工作流」）。改字走 `gtrk mg compile`、改效果走 `gtrk mg edit --say`。
     ⚠️ **它的适用面由派单的 `visual_job` 决定，不由「省不省事」决定**（见下「按 visual_job 路由」）。
  另可查推荐目录（公约 §三‴）：`gtrk skills recommend --scene mg-explainer` 列第三方 MG 生产 skill，装完 `gtrk skills add <owner/repo> --produces MG` 登记即被本节解析到——本 skill 正文不点名任何仓、不点名任何块。
  推荐目录另有一方**排版技法族**（`gtrk skills recommend --scene technique`）：技法不是生产者、不登记为 MG 车道（`routing:none`），某槽位的内容形态命中某技法（如引用文献页 / 截图局部强调）时由你按槽位取用——内容与节奏按口播定、栏目色未定时用技法自带的中性底；本 skill 正文同样不点名仓。
- **有匹配时中性块与文字模板缺省都不用**：只当栏目 skill 明显不擅长某类（如数据图表 / 界面拟物 / 纯文字入场）时可**提议**「这一槽用中性块」或「这一槽用文字模板」，用户点头才用。技法族同理——槽位内容形态命中某技法时可**提议**「这一槽用技法」（`gtrk skills recommend --scene technique`），栏目色板与字体照栏目注入，用户点头才用。

## ★ 按 `visual_job` 路由（不是按省事路由）

每个 MG 槽位的派单里都有一个必填的 `visual_job`（`gtrk split` 判的，四档）。
**它决定这一槽的候选集里有什么**：

| `visual_job` | 这段是什么 | 候选集 |
|---|---|---|
| `statement` | 单一陈述：一句话 / 一个词 / 一个标题 | 文字模板优先 |
| `relation` | 有关系：对比 / 并置 / 因果 / 包含 / 递进 / 聚合 / 循环 | **文字模板不在候选集里**——手做，或栏目 skill |
| `data` | 具体的量、步骤或拓扑 | 同上 |
| `decor` | 不承载信息（转场 / 角标） | 中性块 / 栏目 skill |

⚠️ **`relation` / `data` 两档，`gtrk mg lint` 会硬拒文字模板颗粒**（判据取产物内嵌的
`v:"text-v0"` IR，改名骗不过去）。这不是建议，是闸。

⚠️ **被禁的是「从库里捞一个套上」，不是「用文字」。** 用排版画关系的手写颗粒
——并置两列、中间破折号连起来、右列更重——**正是这一档要的东西**，它没有内嵌 IR，闸不拦。
区别不在媒介，在**有没有为这个意思专门做设计**。

### `relation` 档的手做样例（照着改，别去库里找）

一块半透明面板，小标题 + 两行并置 + 落款：

```
┌─────────────────────────────┐
│        边界的姿态            │  ← 小标题，字重轻，字距开
│                             │
│    温柔  —  坚定             │  ← 两列同基线，中间破折号把它们连起来
│    和善  —  有立场           │     右列字重更重（它是「立场」那一侧）
│                             │
│  语气可以柔软，立场必须清楚    │  ← 落款，最小号
└─────────────────────────────┘
```

**它是纯文字画的，但它表达的是一个关系**（语气 vs 立场的对照）。
关系靠三样东西成立：**同基线的两列**、**把两列连起来的破折号**、**右列更重的字重**。
去掉任意一样，它就退回成四行不相干的字。

⚠️ **模板库给不出这个**：103 件全是单 / 双槽位的标题与字幕，
**结构化排版不在它的表达力射程内**。这不是库没做好，是这一档本来就该手做。

⚠️ 这个样例留在图纸里是有目的的：**没有样例，「自己做一个」那块肌肉会随图纸一起萎缩**。
（正本：`gitruck-infra/docs/同合云顶层设计文档/模板化对自由创意的侵蚀.md` §三 第二种损失。）

### ⚠️ 排产顺序：先原创，后套用

`gtrk mg fetch --source text`（派单模式）在本片仍有 `relation` / `data` 槽位**没产出源 HTML**
时会**拒绝执行**，并列出是哪几个 beat。

为什么是顺序：连着看过十几颗现成品之后，「MG 该长什么样」的默认印象已经被那批东西定住了；
这时再自由发挥，发挥出来的多半还是它们的形状。**顺序本身就是一道隔离。**

⚠️ 它**牺牲了「先铺一批模板看整体效果」这种工作法**。要保留只能走
`--allow-template-first --why "<一句话>"`（一次性例外，不是开关）。
理由会写进 `<工程>/split/mg-exceptions.json`——**终端会消失，工程文件不会**，
日后翻这条片子的人查得出当初为什么按了例外。

## 文字模板工作流（同合云自家模板 → 改字/改效果 → lint → 铺）

> 与中性块是**两条路，别混**。中性块来自第三方、是骨架，要改字体换色板才合规（机械改写八条）；
> 文字模板是我方 clean-room 重写的**成品**，**MUST NOT 做机械改写、MUST NOT 直接改 HTML**。

**为什么不能直接改 HTML**：每颗文字模板都内嵌了产生它的 IR 与两个哈希，颗粒因此自带
「我是由这份 IR 编出来的」的证明（三态：`ir` 可云调 / `detached` 已脱离 / `html` 普通颗粒）。
手改一个字节，它就从 `ir` 掉到 `detached`——`gtrk mg edit` 和客户端属性面板都会拒绝它，
云端再也调不动。`gtrk mg lint` 会报非致命的 `x-ir-detached` 提醒你这件事。

逐槽位：

1. **候选**：`gtrk mg fetch --source text "<检索词>" --top 3 --json`（中文词可用：开场 / 字卡 / 标题 / 字幕 / 强调 / 打字机 / 字条 / 引用 / 气泡 / 故障 / 竖排 / 闪光 / 清单 / 计数 …）。候选态**离线可用**；目录走远端择新、随包兜底，命令会明示这次用的是哪一版、从哪来的。
2. **取块**：`gtrk mg fetch --source text --pick <模板 id> --slot <beatId> --project <目录> --json`，或独立模式 `--as <composition_id> [--duration <坑位秒>] [--out <目录>]`。落盘即可铺，**不需要改写**。
   - **落点与时长由命令钉定，你不用管**（fix-mg-fetch-text-slot-identity）：派单模式的 `composition_id` 取自 `dispatch.mg` 那条（`<工程slug>-<beatId>`，**不是** `--slot` 收的 beat id），产物落 `<产物目录>/mg/<composition_id>.html`；颗粒内嵌 IR 的 `id` 与 `canvas.duration` 被一并钉到「期望 id」与「坑位包络 + 0.3s 余量」（铁律⑦），贴模板末尾的层跟着钉，然后走 `mg compile` 同一条本地编译链重编。产物因此仍是 `ir` 态（云端还能改），且 `mg lint --dispatch` 的 `1-cid-expect` 天然对得上。
   - `--slot` 与 `--duration` **互斥**：派单模式的包络由 `track_st` / `track_ed` 定，显式给时长会与它打架；独立模式才用 `--duration`。
   - 出参里的 `pinned` 写明改了什么（`id` 的 from→to、`duration` 的 from→to、被钉的层）；两者都没改、且目标画幅就是模板的 1920×1080 时 `changed:false` 且**原字节落盘**（不重编）。
   - **画幅也由命令定**（fix-text-template-canvas-not-compiled）：派单模式按工程 `.gtrk` 的 `video_size` 编，出参 `canvas` / `canvas_from` 写明用了哪个画幅、从哪来；画幅不是 IR 改动，`pinned` 不记它。
   - **满屏槽位拿不出实心底 ⇒ 致命 `x-category-opaque`，拒落盘**（`gate-fullscreen-slot-needs-solid-bed`）。
     判据只看**产物**——「这颗有没有满屏实心底」，**不看它是不是模板颗粒**。
     ⚠️ 派单模式下**通常不用你操心**：`dispatch.mg[].bg` 写了底色的话，取块时会自动钉进
     `canvas.bg`，声明当场兑现。撞上这条红一般意味着**派单只声明了 `fullscreen` 却没给 `bg`** ——
     两条出路：① 这颗本来就该是叠加 ⇒ 改派单的 `category` 为 `overlay`；
     ② 确实要盖住画面 ⇒ 给派单补 `bg`，或手动在 IR 里写 `canvas.bg`（编译器会产成根下第一个全幅子层）。
     MUST NOT 靠「加一层 shape 当底」绕过：那种写法的覆盖面取决于祖先定位与 transform，
     **静态判不出来**，`opaque` 登记不上（画出来了但成片按透明叠加处理）。
   - 反方向（槽位派 `overlay`、颗粒却是实心）仍是**非致命**告警：那只是多盖了底轨，看得见、改得动，
     不是「以为有其实没有」的静默落空。两个方向后果不对称，判据也就不对称。
3. **改字（L0，0 积分）**：把内嵌的 IR 存成 `<id>.ir.json`，改 `slots` 里的文字（也可改 `colors` / `font` / `stroke` / `shadow` / `canvas.duration`），然后 `gtrk mg compile <id>.ir.json --out <目录>`。竖屏 / 方形工程加 `--canvas <工程画幅>`（如 `--canvas 1080x1920`）——`.ir.json` 里不记画幅，不给就按 1920×1080 编；给颗粒 HTML 重置时缺省沿用那颗自己的画幅。
4. **改效果（6 积分/候选，前 200 个之后 8）**：`gtrk mg edit <particle.html> --say "打字机快一倍，副标改成青色" [--n 1|3]`。`--n` 只收 1 或 3，**它就是计费单位数**。返回的候选带 `scope`：`L1` = 在模板可调范围内，`L2` = 越界了、等于新生成（**不是失败**）。服务端说做不到时会给一句 `refusal`，**如实转述给用户**——他要的是「为什么不能」，不是一个没有解释的错误。
5. **lint → 铺**：同中性块，`gtrk mg lint` 过致命项 → `gtrk mg --project <目录>`。
6. **竖屏 / 方形工程**：文字模板按工程画幅现编（IR 住在 1920 参考系、画幅是编译入参），**不用另做**。
   - 派单模式缺省就取工程 `.gtrk` 的 `video_size`，不用给 `--canvas`；给了却与工程不符命令会拒（跨比例挂载是非等比缩放，铺进去就是拉伸）。
   - 找不到 `.gtrk`（只给了 `--dispatch`、或工程文件不在 `gtrk/project.gtrk` / `project.gtrk`）时会按 1920×1080 编并提示——竖屏工程看到这句提示就补 `--canvas 1080x1920` 重取。
   - 独立模式没有工程可取：竖屏要显式 `--canvas 1080x1920`。
   - 取完核一眼出参 `canvas` 等于工程画幅再铺；1.3.7 及更早的 CLI 无论给什么都出 1920×1080，见到竖屏工程里取来横屏颗粒先 `gtrk upgrade`。

## 中性块工作流（registry 骨架 → 改内容审美 → lint → 铺）

> 快照随包（钉 commit，候选态离线可用）；取块走三源（我方镜像优先，大陆可达）并校 sha256；改写八条是机械活（template 包裹 / 改 id / 去 data-start 贴坑位 / 实心底下沉 / GSAP 源 / 字体 / 时长贴坑位 / 信箱缩放），**内容与审美不在机械活里**。

逐槽位：

1. **候选**：`gtrk mg fetch "<按 handoff.theme 与 beat 语义组的检索词>" --top 3 --json`（中文词可用：数据 / 图表 / 标题 / 字幕 / 转场 / 通知 / 代码 / 地图 / 手写 / 卡片 / 片头 …）。把 2–3 件候选连**海报链接**给用户选（**候选选择是检查点**，形状同 matrix 的 B-roll 候选）；快速成片模式取首选、铺完在抽帧检查里一并给用户看。`[review]` 件可选但 MUST 真渲验收（多为 canvas / 铁律 8 高危形态）；`[excluded]` 不在可取范围，命令会拒。
2. **取块改写**：`gtrk mg fetch --pick <块名> --slot <beatId> --project <目录> --json`——从派单取 composition_id / 坑位包络 / category，产物直接落 `<产物目录>/mg/<composition_id>.html`。返回里的 `editable`（文案 / 数值数组 / 色值）就是你下一步要改的地方。
3. **改内容与审美（你的活，MUST 做）**：按 beat 语义改文案与数据；色板与字体——有栏目 skill 按栏目的，没有按公约 §三「内容驱动的临场泛化」全片自洽一套；**MUST NOT 把块的示例文案 / 示例数据 / 默认色板原样铺进成片**。字体缺省已换成运行时镜像可证的 CJK 字体，要换用 `--font`。
4. **lint → 铺**：`gtrk mg lint` 过致命项 → `gtrk mg --project <目录>`（与栏目产颗粒同门槛）。
5. **竖屏 / 方形工程**：registry 中性块只有 1920×1080 一种几何（块是手写 HTML、尺寸写死在块里，`--canvas` 给别的画幅会被拒），不适用。派单模式下工程 `video_size` 比例不是 16:9（容差 7%：4K 横屏与 DCI 17:9 照取）命令会**在下载前拒**、不落盘；改走 `--source text`（文字模板按工程画幅编，见上节）或栏目 skill 按工程画幅产颗粒，MUST NOT 拉伸。

独立模式（脱离派单补一颗）：`gtrk mg fetch --pick <块名> --as <composition_id> --duration <坑位秒> [--category overlay|fullscreen] [--out <目录>]`，随后同 `gtrk mg render` 的独立颗粒路。

> **生产 skill 是栏目的纯净资产**：它只懂本栏目的视觉语法、只产 html-particle 颗粒，**不知道 gtrk 命令、不知道产物落哪**。由本 skill 驱动它、并**由本 skill 负责让它以 `gtrk mg` 要读的路径为写入路径**：颗粒 SHALL **直接写到** `<产物目录>/mg/<composition_id>.html`，MUST NOT 先写 agent 自有目录再拷入（中转本身即违规，见上「产物落点纪律」第 5 条）。举例（仅示意解析机制，非硬编）：`real-roam-guide` 栏目配置里 `style.skills` 有一条 `{produces:"MG", ref:"…/real-roam-viz"}` → 驱动它产颗粒；换个栏目、换个 ref，产颗粒的就是另一个生产 skill。**本 skill 正文里不绑死任何栏目的生产 skill。**

## 逐槽位工作流（产 → lint → 铺，循环到铺满）

> **产物落点纪律（MUST · 全文见随包 `AGENT.md` 同名一节）**：
> 交付物 MUST NOT 先写 agent 自有目录再拷进工程；中转本身即违规，体积不是豁免理由。
> 一切产物（颗粒 HTML / 成片 / 预览 / 素材 / 工程文件）只落**工程目录**（颗粒恒存 `<产物目录>/mg/`）或**用户显式指定的输出路径**；
> **MUST NOT** 把成片、预览或任何大媒体文件复制到 agent 自有工作目录（如用户文档目录下 agent 产品自建的目录、agent 家目录缓存、会话工作区）——需要引用媒体时**用原路径引用**，不做副本；
> 临时文件（抽帧图等）一律放系统 temp 且**用完即删**。交付物直接写入工程目录或用户显式指定的路径；引用媒体用原路径。

### 1. 看板起手：先看 `dispatch.mg` 有哪些槽位、铺到哪了

```bash
gtrk mg status --project "<split产物目录>" --json
```

- 汇总 MG 流水线：`dispatch.mg` beat 槽位总数 / 已产源 HTML 数 / 已铺进 `.gtrk` 数，并**逐 beat 标注**（缺 HTML / 已产未铺 / 已铺）。
- 每个槽位有 `composition_id`（主颗粒 = `<工程slug>-<beatId>`；`overlay` 叠层派生颗粒 = `<工程slug>-<beatId>-aux<n>`）与 `handoff`（`theme` / `duration_hint`（秒）/ `category` 等）。**这些是你交给栏目生产 skill 的产片订单。**

### 2. 逐槽位产颗粒（驱动栏目 MG 生产 skill）

对每个「缺 HTML」的槽位：把它的 `composition_id` + `handoff`（`theme` / `duration_hint` / `category`：`overlay` 透明叠加·不挡主体 / `fullscreen` 不透明满屏）+ beat 语义上下文，**交给第「业务分离」节解析出的栏目 MG 生产 skill 产一颗 html-particle**。产颗粒时 SHALL **直接以** `<产物目录>/mg/<composition_id>.html` **为写入路径**（MUST NOT 先在别处产再拷过来）（`gtrk mg` 就从这里读）。
- **产前先抽帧看底轨（`overlay` 槽位必做，别只凭派单盲产）**：颗粒最终要叠在真实画面上，构图必须因势象形。对该槽位挂点用本地 ffmpeg 抽 1-2 帧（`ffmpeg -ss <track_st秒> -i <底轨视频> -frames:v 1 "<系统temp>/<beat>.jpg"`，成本≈0；抽帧图是临时文件——落系统 temp、用完即删，**别落 cwd / agent 自有目录**）看清三件事——①真人主体/人脸的位置（颗粒 bbox 避开，呼应栏目「不盖说话人」铁律）；②该处是否已有烧入包装或 AI 画面（有 → 别铺这颗，报用户裁决——二次过毛片高发）；③画面明暗基调（深底/浅底影响衬底与配色取舍）。把这些**连同抽帧图一起附进产片订单**交给栏目生产 skill；`fullscreen` 槽位可免（满屏不透明无避让问题）。
- **产片订单必须附带坑位硬约束（gsap-emit v1 铁律⑦）**：颗粒 GSAP 时间线总长 ≥ **坑位时长**（该槽位 `track_ed − track_st` 包络，**不是** `duration_hint`）+ 0.3s 余量；`duration_hint` 只是动画主叙事节奏参考。主叙事播完后颗粒必须定格保持或有限循环驻留到坑位末尾；禁止全局渐隐、整体退场或清空画面。
- **`-aux<n>` 叠层颗粒同样处理**：`gtrk split` 若在某 beat 的 `aux_layers` 派了 `overlay`，会派生 `<beat>-aux<n>` 进 `dispatch.mg`（多为 B-roll 底轨之上叠透明概念图解）——照样产、照样存到对应 `composition_id.html`。
- **画幅 = 工程画布（竖屏 / 方形工程同样适用）**：颗粒根 `data-width` / `data-height` MUST 等于 `.gtrk` 的 `video_size`（竖屏 1080×1920、方形 1080×1080……），写进产片订单。`gtrk mg lint --dispatch` 与铺轨会按工程 `video_size` 比对颗粒声明的**比例**，跨比例判 `1-canvas-match` 致命、不铺（挂载是非等比缩放，铺进去就是拉伸）；同比例不同尺寸（4K 工程里的 1920×1080 颗粒）放行。裸 `gtrk mg lint <file>` 不知道目标工程、只核正整数，所以仍要在订单里钉死。`gtrk render` / `gtrk project export` 的颗粒预渲按颗粒声明比例捕获（竖屏 720×1280，横屏照旧 1280×720）；1.3.7 及更早的 CLI 一律按 1280×720 烤，竖屏颗粒会横胖竖扁，见到这种症状先 `gtrk upgrade`。客户端导出剪映 / PR 走客户端自己的颗粒预渲，客户端跟进前竖屏颗粒仍可能压扁，以 `gtrk project export` 的产物为准。
- **category 决定叠法**：`overlay` 颗粒背景透明、盖在 B-roll 上不挡主体；`fullscreen` 不透明满屏。最终透明度由颗粒 HTML 的 `background` 声明反推的 `opaque` 定，生产 skill 要让二者自洽（lint 会查）。**「实心底该写在哪个元素上」的正本条款是 `contracts/gsap-emit-v1.md` 铁律 4——本 skill 不复述元素位置，改了要以契约为准**；`fullscreen` 颗粒把实心底写错地方会吃非致命 `4-bg-on-root`（见下节）。

### 3. lint 门：铺之前先单测每颗（不过就退回生产 skill，别硬铺）

```bash
gtrk mg lint "<产物目录>/mg/<composition_id>.html" --dispatch "<产物目录>/split/dispatch.json"
```

- 纯本地静态校验颗粒 HTML 的**铁律机器可判定子集**：`<template>` 包裹、`data-composition-id` + 根 `data-width` / `data-height`（正整数）、`gsap.timeline({ paused: true })`、`window.__timelines` 注册、无 `Math.random` / `Date.now`（可逐帧 seek 的确定性）、自包含无相对外链、`background` 声明与 `opaque` 自洽…；给 `--dispatch` 会额外校验 `composition_id` 命中派单。
- **`1-cid-expect`（致命）——复制改名必查**：颗粒内部的 `data-composition-id` 必须等于期望 id（`--dispatch` 命中时 = 该条派单的 `composition_id`，否则 = 文件名）。**你从别的颗粒复制模板时最容易漏改内部 id**：那样铺出来的 clip 指向 A、文件注册的却是 `__timelines["B"]`，渲染必错，且两颗粒抢同一个 `[data-composition-id]` 样式作用域。改名的临时副本（`./tmp.html`）不比对，不会误伤。
- **`1-canvas-match`（致命）——画幅比例对不上**：给了 `--dispatch` / `--project` 时按工程 `.gtrk` 的 `video_size` 比对颗粒根声明的比例（容差 7%：DCI 4K 这类 17:9 工程里的 16:9 颗粒放行，接受 6.7% 横向拉伸），铺轨同一口径。横屏颗粒进竖屏工程就是这条。**回步骤 2 让生产 skill 按工程画幅重做**；文字模板在派单模式下重取即可（缺省就按工程画幅编）。工程缺 `.gtrk` 或 `video_size` 不合法时这条不跑，命令会明说「本次颗粒画幅未与工程比对」。
- **铁律④⑤「体积上限」两项**（正本条款 = `contracts/gsap-emit-v1.md` 铁律 4⑤；上限**同源自客户端颗粒运行时**，不是 CLI 的取舍）：
  - `4-html-size`（**致命，命中即短路、其余项不跑**）：颗粒 HTML 超过 **2,000,000 字符**，客户端无法加载。文案会点名最大的一段 `data:` URI。处置：按实际显示尺寸裁剪缩放，不透明图改 JPEG/WebP；仍超就把大图放图片素材轨，颗粒只留透明叠加层。云渲成功不代表客户端可加载。
  - `4-html-size-heavy`（非致命）：500,000～2,000,000 字符——客户端每次 seek 都把颗粒根节点的 outerHTML 整份快照回传，体积直接决定预览帧率。能缩就缩；**不拦铺轨**。
- **铁律④「实心底与透明度」两项恒非致命**（正本条款 = `contracts/gsap-emit-v1.md` 铁律 4；**本处只说看到该码怎么办，元素位置以契约为准**）：
  - `4-bg-on-root`：这颗把**实心底写在了根元素的 `style` 上**。**根元素的绘制属性会在子合成挂载时被丢弃**——那层底在成片里**一个像素都不落地**：前景照常渲出、底没了、底轨透出来，观感是「浮空面板」；而**本地播放器与客户端预览都看不出**（预览会按登记的 `opaque` 位自己给根盒打底，对这个问题结构性失明）。**报了要回步骤 2 让生产 skill 把实心底下沉为子层**——改法是机械的（把那条 `background` 声明整体搬到根下第一个全幅子层，根上改写 `background:transparent`）。⚠️ 它**不影响退出码、不拦铺轨**（避免存量颗粒一夜之间铺不进去），但**不是可以放着不管的噪音**：不改就是出片丢底。
  - `4-bg-explicit`：根与根下首个全幅子层**都**没有 `background` 声明 → lint 只能按「透明叠加」处理。契约要求**透明与否显式**：满屏颗粒在全幅子层写实心底，透明叠加颗粒在根写 `background:transparent`。**别靠「不写」表达透明**——作者与机器都分不清「想透明」和「忘了想」。
  - 两项都**只看 `background` 声明摆在哪**，不看色值（色值属栏目审美，契约不管）。`opaque` 的推导面 = 「根 `style` ∪ 根下首个全幅子层 `style`」，与契约铁律 4 同源；**合规的子层写法会正确识别为不透明**。
- **铁律⑦ 估长三项恒非致命、不是放行凭据**（`--dispatch` 命中派单条目才跑，因为要拿该条的坑位包络）：
  - `7-fill-slot`：静态估长 < 坑位包络 → 「疑未占满」。估长是**下界**（表达式 position、非字面量 duration 那些调用算不了、跳过不计），报出来基本就是真短，**回步骤 2 让生产 skill 把驻留补到坑位末尾**。
  - `7-no-estimate`：一条时长调用都解析不到 → **明说「铁律⑦这颗没校验」**，不是通过。这时 tl 总长只能靠真引擎 seek 或人读代码确认，别当它过了。
  - `7-infinite-repeat`：颗粒里写了 `repeat:-1` → 总长变 `Infinity`、铁律⑦不可验证。让生产 skill 改成按坑位算死的有限次数：`repeat = ceil((坑位 − 循环起点) / 单圈) − 1`。
  - 三项都**不影响退出码、不拦铺轨**——真判据是渲染引擎逐帧，静态估长只做提醒。**但「没报 7-fill-slot」≠「时长够了」**，要看是否同时出了 `7-no-estimate`。
- **铁律⑧「重复图元合并」为恒非致命提示，但看见必须处理**：
  - `8-primitive-merge`：这颗有一批由循环直接创建、或由循环调用具名工厂创建的可合并重复图元；它们落到同一父节点，且没有逐元素动画驱动。纯数字循环可算且同父累计 **≥ 8** 时会报数；边界算不出时会报「条数未知」。逐元素 `gsap.set` / tween、或作为 tween 首实参使用的元素数组会被排除。
  - 本项报的是「这批图元可**无损**合并，合并后画面逐像素不变」这一写法事实，**不是**「该颗粒有缺陷」或「超过某个数量会出问题」；真实触发轴未知。反过来，没报也**不代表安全**，真判据仍是真渲染出片抽帧。
  - **处置**：回步骤 2，把原始提示完整交给栏目 MG 生产 skill，让它把整组同步驱动的网格 / 排线 / 刻度 / 点阵合成单元素（SVG 用一条 `<path>` 多子路径；同色分档可分档合成），然后重产、重 lint。**别硬铺，也别当没看见**；CLI 为兼容存量把它做成**非致命、不影响退出码、不拦铺轨**，不等于 agent 可以跳过修正。确需 stagger / 逐条画入 / 逐个变色的逐元素动画批次属于契约豁免，不要为了消项删掉动画身份。
- **「回调与 seek 语义」三项恒非致命**（**报了不用改颗粒**）：
  - `x-callback-driven`：颗粒画面由时间线回调写 DOM 驱动时，回调可达性由引擎侧契约保证。本项保留为哨兵；若引擎实现变化导致回调不跑，画面可能静默停在初始态。不要因此改写生产 skill 或自行添加垫片。
  - `x-engine-api-override`：颗粒运行时覆写 `tl.seek` 或替换 `__timelines[…]` 时会绕过引擎接口。既有垫片可保留，但新颗粒不要再加。
  - `x-raf-interval`：颗粒里有 `requestAnimationFrame(` / `setInterval(`。这类自有时钟**不被 seek 驱动**，逐帧渲染时等于冻结（契约明令：所有视觉变化必须挂在 tl 上）。静态正则分不清用途，报了要**人眼确认它是不是在驱动画面**；若是 → 回步骤 2 让生产 skill 改挂 tl。
  - 另：引擎不抑制回调 = 逐帧 scrub 时 `onComplete`/`onStart`/`onRepeat` 会**反复触发**。颗粒里**别写「只跑一次」的回调**（累加计数 / `push` 数组 / 一次性 DOM 插入），要写成每次从补间状态**重算**的幂等形式。
- **成本项 `c-filter-*` 三项恒非致命、当期全部不拦铺轨**（正本条款 = `contracts/gsap-emit-v1.md`「渲染成本：真卷积滤镜」一节）：这组查的是**每帧成本**、不是正确性——命中的颗粒**画面是对的，只是渲得慢**。
  - `c-filter-animated`：**时间线补间直接驱动了真卷积滤镜**（`filter:blur()/drop-shadow()`，或补间驱动 SVG `feGaussianBlur` 的 `stdDeviation`）。滤镜值一变，上一帧的卷积结果当场失效、缓存彻底失灵，是这组里**最贵**的形态。**处置**：回步骤 2 让生产 skill 改用 `opacity` / `transform`（`scale`/位移）表达同一叙事动作；确需滤镜就做成**静态两态切换**。**不拦铺轨**。
  - `c-filter-static-fullbleed`：**整幅静态真卷积滤镜**（该声明自身就是 `position:absolute;inset:0` 一类铺满形态）。每帧对整幅做一次卷积。**首选改法是缩小被滤镜覆盖的几何面积**（面积一项同时压掉卷积与整幅逐帧合成两笔成本）；次选把静态滤镜结果预烘成图，且**必须是 RGBA PNG**（透明叠加颗粒的 alpha 是成片合成的必需通道，烘成 JPEG 或无 alpha 格式会在成片里塌成不透明色块），但预烘只消得掉卷积、消不掉整幅逐帧合成，**收益有上限**。去不去滤镜属审美取值，**判断权在作者**，本项只给信息与改法。**不拦铺轨**。
  - `c-filter-indeterminate`：补间的 `filter` 值不是字符串字面量（变量 / 模板串 / 函数返回），lint **对该处没作判定**——既不是「判过且通过」，也不是命中。**人眼确认它到底会不会驱动 blur / drop-shadow**；会就按 `c-filter-animated` 的改法处理。**不拦铺轨**。
  - **`box-shadow` 整族不在射程内**（含 `box-shadow: 0 0 0 Npx` 这类零卷积脉冲写法，也含带模糊半径的写法）——看到 `box-shadow` 没被报**不是漏报**，是既定射程边界。
  - ⚠️ **没报 ≠ 这颗便宜**：这三项是形态提示，真判据永远是实际渲染计时；不要把单批素材的收益外推到所有语料。
- **id 前缀读法**（三个命名空间，别按前缀反推错阻断行为）：**数字前缀 `1-`~`8-`** = 契约铁律项，致命性**按项声明**（`4-html-size` **致命**且命中即短路；其余 `4-*` / `5-*` / `7-*` / `8-*` 非致命）；**`x-`** = 哨兵项，**恒不拦**；**`c-`** = 成本项，致命性按项声明，**当前三项均不拦**。
- **不过（非 0 退出、逐条报因）→ 把报错原样丢回栏目 MG 生产 skill 修，重产重 lint，别硬铺**。铺一颗不合规颗粒会污染工程。
- 只想批量干校验不写回：`gtrk mg --project <dir> --lint-only`（有 beat 没过就 `ok:false` + **非 0 退出**，工程一个字节都不动）。

### 4. 铺轨：全槽位就绪后铺进工程（叠在 B-roll 之上）

```bash
gtrk mg --project "<split产物目录>" --json
```

- 读 `dispatch.mg` → 逐 beat 从 `<project>/mg/<composition_id>.html` 取源颗粒 → lint → 铺进 `.gtrk` 的 `beat_track`，把 `struct_meta.mg` 原子写回。**幂等**：重铺先剥旧自产轨再 append，用户在 opencut 手加的轨零连带。
  - ⚠️ **「哪几条轨是我们的」按内容判，不按轨号判**（`fix-mg-lay-track-identity`）。工程被客户端打开保存过之后，`track_index` 会**整体重编**（客户端按当刻车道顺序现场发号，契约明写它会漂），而且一条 beat 轨可能被**劈成两条**（含物化颗粒视频的那些会单独成轨）。所以账本里记的号常常已经指不中了。
  - 重铺前看结果里的**逐轨识别结论**（`tracks[]`：`self-produced` / `self-produced-edited` / `user-track` 各带一句依据）。`self-produced-edited` = 号漂了但内容对得上，**良性降级不是故障**；某条轨号在册却判成 `user-track`，说明重编号之后那个号撞上了别人的轨——**保留是对的**。
  - **剥离面口径**（= 「本次会剥掉什么」，和「本次铺什么」是两件事）：
    - **`--only <beat>` = 只剥命中的那几颗**（真增量合并）——轨上其余已铺颗粒连同用户手调**原样保留**。
    - **全量重铺 = 剥掉登记里的全部自产轨再整轨重建**，**唯一例外**：本次派单里有、却因缺 HTML / lint 未过 / 重投影后零存活而**没铺成**的那些（`skipped` 里那几颗），它们上一轮的 clip **留在轨上**——不会因为新的做坏了就把旧的也毁掉。反过来，**派单里已不存在**的已铺条目（你重跑过 `gtrk split`、这个 beat 不做 MG 了）仍照剥：那是计划变更，不是做坏了。
    - 所以「轨上共几颗」**不一定**等于「本次铺了几颗」——差额就是 `kept`（见下 `--json`），**必须一起读**。
    - 真要**重置整轨**（连其余已铺颗粒一起剥掉重来）：加 `--replace-all` 显式授权。
  - **素材表不再囤积**（剥离键按「自产身份 × 零引用」判，不认客户端可改写的 `html_material` 前缀）：
    在 opencut 里编辑过工程之后重铺，旧的自产素材条目照样剥得掉，`mg-` 素材数**恒等于轨上颗粒数**；
    重复 / 孤儿条目会在下一次重铺时一并清掉。**非自产素材零连带**——`broll-*`、`ex-solid-*`
    垫轨、你自己加的素材一条不碰（哪怕它当前没被任何 clip 引用）。盘上 `assets/mg/` 的 html 副本从不删。
- `--json` 输出：`{ ok, mode:"lay", laid, track_total, kept, kept_ids, removed, skipped, reason?, integrity?, … }`。
  - **`laid`** = **本次**铺成数；**`track_total`** = **轨上现存**已铺数；**`kept`** = 其中**上轮遗留**（本次没重铺）的颗数。三个数一起读——只看 `laid` 会让「铺 1 颗、剥 20 颗」跟「补铺 1 颗」长得一模一样。恒有 `track_total = laid + kept`。
  - **`kept_ids`** = 上轮遗留的 `composition_id` 清单。**要判断轨上某颗是不是这一轮的版本，看它在不在这里**——`kept > 0` 就意味着轨上内容与本次派单不完全对应（要全部刷新：去掉 `--only` 全量重铺）。
  - **`removed`** = 本次被剥掉的旧自产颗粒数（= 上一轮已铺里没被保留下来的，含「剥了再铺」的那些）。
  - **`skipped`** = 缺 HTML / lint 失败的 beat（不拦其余）——**回步骤 2 补产、重铺**，别当没看见。**注意它和 `kept_ids` 会重叠**：某颗这轮没铺成（进 `skipped`）、上一轮那条还留在轨上（进 `kept_ids`），画面在但不是新版本，跟用户说清楚。
  - **`reason`** = 非全绿时的机读判据：`skipped`（部分没铺上）/ `empty_queue`（**拒写回**，工程未被改动）/ `no_project`（工程缺失未铺轨）。
  - **`reprojection`** = 本次落轨时码的来源与漂移摘要（恒出，`--lint-only` 也有）：`{ mode:"reprojected"|"dispatch_snapshot", degraded, reason?, drifted, max_offset, shrunk:[…], dropped:[…] }`。
    **时码是现场重投影出来的**：命令每次都用 `transcript × 当刻 .gtrk` 重算每个 beat 的窗口（与 `gtrk split` 落地同一段代码），`dispatch.json` 里的时码只是投影那一刻的快照。**所以用户在 split 之后微调口播轨不用回去重跑 `gtrk split`**，只有**拆分稿本身**变了才要重跑。
    `drifted > 0` = 这次铺的位置和派单上写的不一样（正是它该做的），把条数与 `max_offset` 报给用户；`dropped` 里的 beat = 那段已被整个剪出成片，**故意不铺**（不会按快照塞回去）。
  - **`integrity`** = **素材落盘自检**（与 `gtrk matrix` 同名同形）：写回工程之后自动查一遍
    「`materials[].path` 是不是真的都落盘了」。**只在真写回过的运行里出现**（`no_project` / 拒写回 /
    `--lint-only` 时**字段缺席** = 本次没查，别当成「查过且干净」）。
    `dangling` = 悬空引用全量（相对路径素材登记在、文件不在，每条带 `referenced` 与引用位置 `refs`）；
    `danglingReferenced` / `danglingOrphan` = 被时间线引用数 / 孤儿数——**两者严重度差一个量级，回报时分开说**；
    `external` = 绝对路径素材找不到文件（另一档，不进 `dangling`）。
    **告知不拦阻**：查出悬空不改 `ok`、不改退出码、不删任何东西；非零就报给用户（哪几条、在哪一段），
    多半来自客户端「确认原片」下载中断，修法在客户端而不在这里。**别自己去删素材或文件。**
  - **退出码**：`ok:false` 一律连带**非 0 退出**（含「有 beat 被 skip」这种循环中途的正常态）。**别把非 0 读成「命令崩了」**——它说的是「这一轮没全铺成」，按 `reason` / `skipped` 处理后重跑即可。
- **只铺单个 beat**：`--only <beatId>`（收的是 **beat id** 如 `B12`，不是 `composition_id`；主颗粒 + 其 `-aux<n>` 叠层一并选）。
  - **语义 = 真增量合并**：只重铺命中的那几颗，轨上**其余已铺颗粒原样保留**——连它们的透明度与用户在 opencut 的手调一起活着（保留的是既有 clip 原件，不是照登记重建的）。适合迭代改单段。
  - 保留下来的那几颗**不重新 lint、不重新复制源 HTML**：工程是自包含的，`<project>/mg/` 下对应源文件即便已经删了也不影响。
  - 代价看 `kept` / `kept_ids`：轨上会同时存在「本次这版」和「上轮那版」。要把全部颗粒刷成最新（含按当刻时间线重投影的新位置），**去掉 `--only` 全量重铺**（幂等、多铺几颗不花钱）。
  - 确实要**重置整轨**（连其余已铺颗粒一起剥掉重来）才加 `--replace-all` 显式授权。
  - **仍会拒写的唯一情形**：本次**一条都没定位到**（`--only` 打错 beat id、`dispatch.mg` 为空、或本次条目全被 skip）**而轨上已有已铺颗粒**——那是派单或选择器出问题的信号，不是清空指令，CLI 拒绝写回并报因（`.gtrk` 逐字节不变）。
- **铺完必核「clip 占满坑位」**：读回 `.gtrk`，各 beat clip 的时长必须 = 槽位包络（`track_ed − track_st`），不能使用 `duration_hint` 作为落轨时长。若发现 clip 短于坑位，把 `dispatch.mg[]` 各条 `duration` 改为包络秒数后重铺。

### 5. 循环到铺满

多 MG 槽位就**循环「产 → lint → 铺」直到 `dispatch.mg` 全部铺满**（`gtrk mg status` 逐 beat 全标「已铺」、`gtrk mg` 的 `skipped` 为空、`ok:true` 且退出码 0）。**循环中途 `skipped` 非空时命令按 `ok:false` + 非 0 退出如实上报**，那是「还没铺满」不是「命令失败」——照常补产重铺即可；`--lint-only` 同口径。

> **循环里别用 `--only` 逐颗铺**：`--only` 是增量的（安全，不会铲掉别的），但它**只刷新命中的那几颗**——轨上其余颗粒会一直停在上一轮的版本与上一轮的落轨位置（`kept_ids` 会如实点名）。循环的正确姿势是**每轮都全量重铺**：铺轨幂等，多铺几颗不花钱，而且顺带把全部颗粒按当刻时间线重投影一遍。`--only` 留给「已经铺满、只想改某一段」。

> 旧名 `gtrk rrv` 是去品牌化前的弃用别名（仍能跑但会打提示）——**一律用 `gtrk mg`**。

## 读结果、给用户交代（别只说「铺好了」）

- **铺了哪些**：`laid[]` 的 `composition_id`，对应哪些 beat；其中哪些是 `overlay` 透明叠加（盖在 B-roll 上）、哪些是 `fullscreen` 满屏。
- **没铺上的**：`skipped[]` + 原因（缺 HTML 还是 lint 失败 / 重投影后零存活）——如实说，别谎报全铺；已补产重铺的说清楚补了哪几颗。
- **落轨位置变了要说**：`reprojection.drifted > 0` 时报出「本次按当刻时间线重投影，N 个 beat 的位置与派单不同、最大偏移 X 秒」。这是对的（用户改过口播轨），但**必须让他看见**，别悄悄改掉。
- **报「已降级至派单快照时码」**（`reprojection.degraded:true`）：不是故障、没拦你，是算不出当刻窗口、退回了可能已过期的快照。照 `reprojection.reason` 给出路——`transcript_missing` → 把 `transcript.json` 补回产物目录（`transcript/` 或 `json/` 下），或用新版 `gtrk oralcut` 重出产物；`no_project`/`gtrk_unreadable` → 把工程放回 `<产物目录>/gtrk/project.gtrk` 或 `--project` 指对目录；`no_material_clip` → 口播主轨被整条删了 / relink 换了素材 id / 拿了另一个工程的 dispatch。用户接受快照也行，但要如实说「这批位置按的是上一次投影的时间线」。
- **轨上有几颗不是这一轮的**：`kept > 0` 时点名 `kept_ids`——「这次只重铺了 N 颗，轨上另外 K 颗是上一轮的（位置和内容都还是旧版），要全刷新就全量重铺一次」。**别让「铺轨完成」读起来像轨上全是新的。**
- 想在 opencut 里精修颗粒（手调参数/时长）→ 提示用户可打开工程手调。**但要把话说全**：手调只在「这颗**没被**下一次重铺命中」时活得下来——`--only <别的 beat>` 或全量重铺时被 skip 掉的那几颗会原样保留（含手调）；**一旦某次重铺真的铺到了这颗，CLI 自产的新 clip 会整条覆盖它，手调就没了**。要长期保留的手调，别放在 CLI 自产的 MG clip 上。

## 交棒 `render` 收口（你是最后一层，别停在铺完）

`dispatch.mg` 全铺满、`gtrk mg status` 全绿后**别收工**——你已是最后一层，顺势收口。快速成片模式先 `gtrk subtitle lay --project <目录>` 把字幕上好（样式取用户拍板值，没选用 default）再 `gtrk render` 出片。一句话交代即推进：「MG 颗粒已叠铺完，我接着上字幕出片」。

### ⚠️ `render` 收口会花钱

`gtrk render` 现在**会把 MG 颗粒真的渲进成片**——做法是把每颗颗粒送去同合云烤成透明 MOV，再本地叠。于是这条命令**不再是零计费**：

- **计量 = 唯一颗粒数 × 未命中缓存数**（按分钟）。同一颗粒在轨上出现 N 次只烤一次。
- **第二次渲染全命中缓存 ⇒ 零计费**；用户先在客户端导出过剪映的工程，CLI 首渲即全命中（两端共用同一份工程目录缓存）。
- 未命中时 CLI **会先出预估并要确认**。`--yes` 跳过确认；`--json` 模式下**必须**显式 `--yes`，否则硬拒。
- 不想花钱：`--no-particles` 出无颗粒版（B-roll 等 overlay 轨照常合成，那部分零计费）。

**MUST 在推进前把这件事说给用户**（一句话即可：「渲这条会对 N 颗未命中缓存的颗粒云渲、约 X 分钟计费；不想花钱可以先出无颗粒版」），**MUST NOT** 一声不吭替他确认。

### 剪映交付的 alpha 口径（正本 = `contracts/gsap-emit-v1.md`「Alpha 交付口径」）

- 颗粒里一律写直通 alpha，半透明光晕 / 渐变 / `text-shadow` 软晕都合法；**别为迁就剪映把半透明色改暗或回避半透明**，预乘是云端 qtrle 转码的义务（infra `fix-qtrle-premultiplied-alpha-for-jianying`），CLI 与颗粒 MUST NOT 再乘一次。
- lint 报 `x-soft-alpha`（非致命）= 这颗含半透明面积：照常铺、照常渲；剪映验收必须使用含半透明的颗粒。若软边成了实心，先查渲染服务是否已含预乘配方。

另一条路仍在：**客户端出片**（opencut 打开工程 → 云渲）同样能出带颗粒的成片，且与 CLI 共用缓存。两条路选哪条由用户定。

★ **若你是走前置硬门 2 的「明示跳过 AI」进来的**：收口前 MUST 把那份「**AI 回铺后待复查构图**」清单复述给用户（哪几颗颗粒、对应哪几个 AI beat 区间），并说明「AI 片段回铺后这几颗可能要重产」。**MUST NOT 静默收口。**


### 出片后 QC 报了缺陷：怎么落回 plan 重铺（闭环，别原地调参）

`gtrk render` 出片后跑 `gtrk qc <成片> --gtrk <工程> --json`。它报的缺陷**带工程坐标**（`beat` / 槽位序号 / `clip_id` / 源窗），不是一串光秃秃的成片时码——所以「换哪一段」是可答的，闭环走法：

1. **读缺陷的坐标**，定位到是哪个 beat 的第几槽、用的哪条 `clip_id` 的哪个源窗。
2. **读它给的排序备选**：段内跳切与判 `error` 的过短镜头条目带 `alternatives`——同 beat 里还没上轨的候选段（取自工程的 `split/broll-plan.json`，按段判、不按 `clip_id`），每条给 `clip_id`、段区间与排序依据，按「稳」排序（运动量升序 → 段内切点密度升序 → score 降序），实际参与排序的维度在 `alternatives_ranked_by`。候选缺运动或切点信号时 `alternatives_note` 会标注**口径受限**——那时别把第一名当定论。`alternatives: []` 只说明 plan 里这个 beat 的段都已上轨、没有备用段，不等于这处换不了：要换就先重新检索补候选（`/gtrk-matrix` 的单独检索 `gtrk matrix search`），或在同一素材里另找避开切点的段，拟换方案照第 3 步列给用户；没有 `alternatives` 键、只有 note 是没读到 plan，同样别读成「没得换」。排序备选是**建议，不是授权**：它判的是晃不晃、切点多不多，不知道用户当初为什么选了现在这一段。
3. **先列给用户，等他同意**：把可换段的缺陷一次列成清单，每行一处——哪个 beat 第几槽、成片时码、现在用的段、拟换成的段及排序依据（口径受限的照标）。清单后说清连带影响：
   - 该 beat 的 B-roll 按改过的 plan 重铺。本地素材的工程重铺要走云端编排、按编排量计费，而且全片 beat 都会重新编排、落点可能微挪，不只换的那几处；花多少先带 `--arrange-estimate-only` 估一次（零调用、零改动）；
   - 受影响 beat 的 MG 颗粒按 `--only` 重铺，那几颗上的手调会被覆盖；
   - 重渲时内容没变的颗粒命中缓存不计费，重新产过的那几颗按 `gtrk render` 的预估再确认一次。

   然后停下等答复：整批换、挑着换、都不换都行，只换用户同意的那几处。一次列全，别逐条碎问。**用户同意之前 MUST NOT 改 plan、跑 `gtrk matrix lay`、`gtrk mg --only` 或重渲。**
4. **留副本，再改 plan 换段**：先用同一个时间戳留两份副本——`split/broll-plan.json` 复制为同目录 `broll-plan.before-qc-<yymmdd-HHMMSS>.json`，`gtrk/project.gtrk` 复制为同目录 `project.before-qc-<yymmdd-HHMMSS>.gtrk`。告诉用户回退方法：把这两份副本拷回原文件名，就回到换段前的 plan 与时间线（MG 颗粒的落点一并回来），不再计费；别用副本再跑一次 `gtrk matrix lay` 回退，那要再付一次编排费，铺出来也不是原样。再把用户同意的槽位换成备选（他没另指就用第一名）。⚠️ 改的时候把候选条目**原样搬**，别自己重写 `segments`——`cuts` / `motion` 就住在里面，重写即丢（详见 `/gtrk-matrix` 的 plan 编辑口径）。
5. **`gtrk matrix lay --project <目录> --plan <path>`** 消费改过的 plan 重铺 B-roll ⇒ 本 skill 的 MG 颗粒**按增量重铺受影响的那几颗**（`--only <beat>`）⇒ 重渲 ⇒ 再 QC 复核那条缺陷是否消失。

**事先授权的例外**：用户在本会话里事先明确说过「出片后 QC 报的画面问题你直接换」之类的话，第 3 步「换哪几处」可以不再征询；第 4 步照留副本，换完 SHALL 列出换了哪些槽位、换成了什么。⚠️ 这份授权**只免换不换段这一问，不免计费确认**：第 5 步里本地素材工程的云端编排、颗粒重渲，照 `/gtrk-matrix` 与上文 render 一节的口径先报预估、等同意，**MUST NOT 因为这句授权就自带 `--yes`**——除非用户明说了「花钱也不用问」。授权只在本会话有效。

🔴 **这是「换素材」而不是「调参数」**：QC 报的多数画面级缺陷（晃、糊、闪）根子在选中的那一段本身，调 `--score-floor` / 阈值只会连带影响全片其它槽位。先换段，换不动再谈调参。

⚠️ **QC 是只读诊断**：常规扫描零写库。只有显式带回流开关时才会把确认的切点补录进本地索引（治检测阈值漏网），无法唯一映射的会跳过并报数——**别把「跳过 N 条」读成失败**。

### 逃生门：两种信号都要认（范围 + 节奏）

上面的「别收工、顺势收口」是**默认**，但**用户表达了范围保留或节奏保留时 SHALL 停在铺完这一步、
SHALL NOT 自动上字幕出片**（`gtrk render` 还是计费/耗时动作，更不能替他拍板）。
两者语义正交，认漏一个都会误冲：

- **范围信号 = 走多远**（我只要这一个产物）：「只铺这一版 MG、暂不出片」「先不渲」「工程给我就行」。
- **节奏信号 = 怎么走**（每步停等我）：**「逐步推进」**（本仓公约立法定名的正式模式名，
  定义就是「每步停等确认」，用户会照着这么说）、「一步步来」「先 X 然后我们再说」
  「我看看再定」「等我确认」。

⚠️ **词表只是举例、不是白名单**——判据是「**用户表达了节奏/范围保留**」，换个说法照样得听懂。
特别注意「先 X **然后**我们逐步推进」：用户明说了还要往下，**范围**逃生门逐字不成立，
但**节奏**逃生门成立 ⇒ **停**。

停下时**必须交代下一步是什么、他说一声就继续**（例：「MG 颗粒已叠铺完。按你说的逐步来我先停在这，
下一步是上字幕 + `gtrk render` 出片，你说一声我就接着做」），别退化成事事请示。
★ 上面那份「AI 回铺后待复查构图」清单**该复述照复述**——停下不等于不交代。

**没有这两类信号时不改默认**：仍按上文顺势收口。

**渲完必看质检摘要**：`gtrk render` 收口后会自动扫一遍成片（闪帧 / 段内跳切 / 黑帧 / 冻结 / 爆音 / 静音 / 音画不同步），落 `<成片名>.qc.json` 并打摘要。**「严重」条目要如实转述给用户并给出路**，别把有缺陷的片子说成出好了：

- **过短镜头 / 段内跳切** → B-roll 颗粒窗口跨过了源素材的镜头切点。报「严重」时让用户在 opencut 里微调那几颗的入出点或重铺该 beat；报「备注」且属于素材自身的快剪节奏时无需处理。若索引缺少切点，先 `gtrk matrix index --dirs <素材库> --rebuild` 重建再重铺。
- **音画不同步（`av_drift`）** → 画面与配音总长不一致。本地渲染的逐段帧对齐已按累计时间线修好（不再渐进失步），所以这一项**报出来就是异常**：先确认成片是不是旧版 CLI 渲的，再看工程时间线本身有没有轨间长度冲突。云渲成片在后端跟进帧对齐前仍可能报此项（已知过渡态）。
- **黑帧** → 对照铺 B-roll 时的空洞（空档补黑）告警：已登记的会自动降为「备注」，报成「提示」的是意料外的纯黑，值得回工程看一眼。

要单独复查某条成片：`gtrk qc <成片.mp4> --gtrk <工程.gtrk>`（带 `--gtrk` 才认得出段内跳切）。

> 原则：**agent 替用户跑 CLI / 接力 skill、驱动栏目生产 skill，用户只对话**——别让用户自己去终端敲 `gtrk mg`，也别让用户手动去 call 栏目生产 skill。

---

## 独立颗粒（精剪补充）：`gtrk mg render`，脱离工程出一颗透明 MOV

**场景**：粗剪已完成、工程已导剪映，用户在剪映精剪时说「这里想补一颗动态图」——不回客户端、不重跑全流程，产一颗直接拖进剪映的透明 MOV。这不是新管线，是既有管线的**旁路补给口**（add-mg-standalone-render）。

> ⚠️ 别与上文「交棒 `render` 收口」混淆：那是 `gtrk render`（整片成片渲染），这是 `gtrk mg render`（单颗颗粒独立云渲）。同词不同物。

动线（agent 替用户跑全程）：

1. **现场组产片订单**：无 dispatch 可消费——按用户现场需求组订单（theme / 时长 / overlay 还是 fullscreen），驱动栏目 MG 生产 skill 产颗粒 HTML。时长问清楚：**它就是 `--duration` 的值**（独立模式没有坑位包络，这个数同时是 lint 包络、成片时长、计费时长）。
2. **lint**：`gtrk mg lint <颗粒.html>` 先过致命项，不过退回生产 skill 修（与铺轨同门槛，别硬渲）。
3. **渲**：`gtrk mg render <颗粒.html> --duration <sec> [--out <dir>]`——lint 前置 → **计费预估确认**（用户点头才提交；`--yes` 只在用户已明确授权时用）→ 云渲 qtrle → `<composition_id>.mov` 落盘（缺省 `./mg-render/<composition_id>/`，含 `task.json`/`result.json` 面包屑）。
4. **交付话术**：「透明 MOV 已出，直接拖进剪映即用（真透底）」。

把话说全的三条（MUST 如实告知，别只报成功）：

- **计费明示**：CLI 没有本地 HTML 渲染引擎，独立颗粒**唯一路 = 云渲计费任务**（按时长分钟计）。确认提示会报实时价，别替用户跳过。
- **体积预期**：qtrle 是无损 RLE，MOV 比 mp4 大不少——秒级颗粒没事，别拿它渲长片。
- **工程分叉明示**：用户在剪映里补的这颗料**不会回流 `.gtrk`**（导出本就单向）——`.gtrk` 与剪映草稿从此各改各的，回头再从工程重导剪映会**没有**这颗补的料。用户要长期用这颗，提醒他自存产物文件。

射程边界（命令会明确拒绝，不是故障）：首发只出 qtrle（webm 剪映不吃）、颗粒几何须为正整数、`--duration` 必填。MOV 按颗粒声明的 `data-width` / `data-height` 原尺寸出（竖屏 1080×1920 颗粒出 1080×1920 MOV）。产物落普通目录，**绝不写剪映草稿目录**——拖入剪映由用户做，素材归属才清晰。

---

## 改工程：走 `gtrk patch`，不要裸手改 JSON

跑完之后要**微调某个片段**（挪位置 / 改时长 / 切开 / 改音量）时，一律用 `gtrk patch`：

```bash
gtrk patch move --project <dir> --clip <clip_id> --to 5.0
gtrk patch trim --project <dir> --clip <clip_id> --out -1s
```

**MUST NOT 直接编辑 `.gtrk` 的 JSON。** 片段时码是两套并存的（`clip_st`+`clip_ed` 与
`clip_st`+`duration`）：改一份不改另一份，客户端优先读 `clip_ed`、后端又不强校验它 ⇒
`gtrk patch` 负责同步两套时码、帧对齐与写前校验，避免手改 JSON 后客户端与后端读取不同出点。

> 参数手册在 `AGENT.md` 的「元素级编辑：`gtrk patch`」一节与 README 命令参考。
> 本节**只指路不搬手册** —— 搬过来就会长出会漂移的副本。
