From 365ddc964e5db7f0ac0ffe8b037c7d546dba2c0f Mon Sep 17 00:00:00 2001 From: zhanglinghao Date: Fri, 2 Oct 2026 15:03:40 +0800 Subject: [PATCH 1/3] Style presets are references, not templates new --style a,b and style apply a,b attach presets as references: copies in style-refs//, a Style refs line in the BRIEF, a note atop STYLE.md and a DECISIONS line per preset for what it lends. Nothing is pasted into the BRIEF; re-attaching adds nothing; departing from a preset needs no override line. Docs (CLAUDE.md, playbooks 01 and 12, styles/README, the template, READMEs) follow. --- AGENTS.md | 6 ++-- CHANGELOG.md | 5 ++++ CLAUDE.md | 6 ++-- README.md | 2 +- README.zh-CN.md | 2 +- bin/vh | 61 +++++++++++++++++++++++++---------------- playbook/01-pipeline.md | 2 +- playbook/12-ideation.md | 10 ++++--- styles/README.md | 10 +++---- styles/_TEMPLATE.md | 2 +- templates/BRIEF.md | 1 + tools/ci.sh | 11 +++++--- 12 files changed, 71 insertions(+), 47 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0fa4cac..fb9fa9e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,7 @@ | 适合 | 试方向、草稿、随手发 | 大多数正式视频 | 发布片、旗舰内容、会被反复看的片子 | | 人工关卡 | 0 道,直接出片。选 quick 就等于授权跳过关卡,把这一点记进 REVIEW.md;需求有歧义时最多问 1 个问题;人点名要拍板的事照样停(导演模式) | 3 道 | 3 道;关卡 ① 每张立意卡附一段 10–20 s 草图,关卡 ② 附全长 animatic | | 立意 | 自己想 3 个一句话点子,挑 1 个,写一行理由 | 关卡 ① 给 2–3 张立意卡,每张配一帧画面 | 同左,每张配一段 10–20 s 的草图(look-dev) | -| 风格 | 从立意推出来,可以借 `styles/` 的 1 个预设,写明理由 | 写在每张立意卡的"画面"一行(专属,或借一个预设);选卡就一起定了,人可以在回复里换 | 同左;每张卡的草图就是那种风格的小样,借来的预设人想看时在 E0 补小样 | +| 风格 | 从立意推出来,可以参考 `styles/` 的预设,写明借了什么 | 写在每张立意卡的"画面"一行(专属,或借一个预设);选卡就一起定了,人可以在回复里换 | 同左;每张卡的草图就是那种风格的小样,借来的预设人想看时在 E0 补小样 | | 分镜 | 简表:镜头、时长、reads | 完整 STORYBOARD + 分镜预览图 | 同左 + animatic | | 自查 | 整片一张联系表、一张按目标屏缩的联系表、前 2 s 的 strip,20 条清单速查一遍 | 每个场景:联系表、关键动作 strip、局部 crop;20 条清单 | 同左,再加目标屏尺寸测试、循环接缝、无损帧确定性、静默故障扫描 | | 独立评审(打分层) | 不做 | 1 轮:全新上下文的 reviewer 打 8 项分(含立意),修最差的 3 处 | ≥ 3 轮,8 项都 ≥ 8 才出片;达不到就带着分数进关卡 ③ | @@ -64,7 +64,7 @@ `quick` 省掉的是轮数和审阅,不是这些。 -**底线之外都是默认口味**:`TASTE_CHECKLIST.md` 里没标【底线】的条目、BRIEF 的 Motion defaults、类型文档 Prompt 增量块里的语域和节拍、风格预设和配方,都是没有立意时的稳妥做法。立意需要时可以反着来:在写用到它的场景之前,把改了什么、立意为什么需要写在立意卡或 STYLE.md 上,记进 `DECISIONS.md`;人批过的卡上写了的,算人的决定,reviewer 只查有没有做到;之后才加的由 reviewer 判断站不站得住,判了 FAIL 再补的不算(`playbook/12-ideation.md` 第 6 节)。类型文档"禁止"一节也分两种:管事实、许可、可读和隐私的是底线,管长相的(配色、质感、缓动、图表样式)是默认口味。规则是用来防止犯错的,不是用来规定口味的。 +**底线之外都是默认口味**:`TASTE_CHECKLIST.md` 里没标【底线】的条目、BRIEF 的 Motion defaults、类型文档 Prompt 增量块里的语域和节拍、镜头配方,都是没有立意时的稳妥做法。风格预设只是参考:借了什么在 `DECISIONS.md` 记一行,不借、借一半、几个混着借都行,和它不一样不用解释。立意需要时可以反着来:在写用到它的场景之前,把改了什么、立意为什么需要写在立意卡或 STYLE.md 上,记进 `DECISIONS.md`;人批过的卡上写了的,算人的决定,reviewer 只查有没有做到;之后才加的由 reviewer 判断站不站得住,判了 FAIL 再补的不算(`playbook/12-ideation.md` 第 6 节)。类型文档"禁止"一节也分两种:管事实、许可、可读和隐私的是底线,管长相的(配色、质感、缓动、图表样式)是默认口味。规则是用来防止犯错的,不是用来规定口味的。 ## 导演模式:谁来拍板 @@ -85,7 +85,7 @@ effort 管 agent 自己查得多细,导演模式管人拍板哪些事,两个 用户要 `quick` 时,不用把上面各步列的文档都读完,照这张卡做: 1. **读**:路由表对应的类型文档,只读"工作流"和"禁止"两节("Prompt 增量块"会贴在 BRIEF 末尾,挑定立意以后再读);再读 `engines/README.md` 里对应引擎的一节,HyperFrames 先看其中的"最小写法"。 -2. **建项目和立意**:`bin/vh new --effort quick`。先写 3 个一句话立意,挑一个(`playbook/12-ideation.md` 第 7 节最后一条),再读 BRIEF 末尾的 TYPE 块,决定留哪些默认。风格从立意推出来,合适的话借一个预设:`bin/vh style apply `(`bin/vh style list` 挑),它的 `STYLE_PRESET.md` 会带进项目,写代码前读一遍。立意和风格各写一行理由进 `DECISIONS.md`;改了哪些口味默认,交付时一并列出。 +2. **建项目和立意**:`bin/vh new --effort quick`。先写 3 个一句话立意,挑一个(`playbook/12-ideation.md` 第 7 节最后一条),再读 BRIEF 末尾的 TYPE 块,决定留哪些默认。风格从立意推出来;想参考预设就 `bin/vh style apply `(`bin/vh style list` 挑),副本放进项目的 `style-refs/`,借了什么在 `DECISIONS.md` 记一行,和它不一样不用解释。立意和风格各写一行理由进 `DECISIONS.md`;改了哪些口味默认,交付时一并列出。 3. **写**:补齐 BRIEF;分镜只写简表(镜头、时长、reads);然后写场景代码。HyperFrames 写完一段,用 `npx hyperframes snapshot --at <秒> --describe false` 看几个关键时刻。 4. **出片**:HyperFrames 先 `export HYPERFRAMES_SKIP_SKILLS=1 DO_NOT_TRACK=1`,再 `npx hyperframes render --quality draft --output out/draft.mp4`;手绘类用 `node render.mjs --clip --out=out/draft.mp4`。 5. **自查一遍**:`bin/vh sheet out/draft.mp4` 看整片联系表,对着 `TASTE_CHECKLIST.md` 的 20 条速查;有字就跑 `bin/vh readcheck`。再看两样(命令见 `playbook/02-verification.md` 的"手机测试"):一张按目标屏缩的联系表(BRIEF 的 `Watch on`:phone、feed 缩到 360 px 宽,desktop 缩到 640),必读字读不读得出,读数、标签别贴着下限;前 2 s 的逐帧 strip,开头是不是第 0 帧就在动、有没有让人停下来的东西。`quick` 没有 reviewer,这两样最容易漏(`docs/research/06-concept-first-ab.md`)。 diff --git a/CHANGELOG.md b/CHANGELOG.md index a1de055..d95162b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +**Style presets are references, not templates** +- Why: the maintainer would rather the presets be examples the agent learns from and combines freely ("预设样式只是一些模版参考,让 Agents 自由学习组合并参考") than templates it executes. Before, `--style` copied the preset as `STYLE_PRESET.md`, pasted its prompt block into the BRIEF as instructions and opened the project's STYLE.md with "以风格预设为底…只写改动"; in research note 06 a workflow agent that borrowed a preset then logged three taste overrides just to depart from it. +- `bin/vh new --style a,b` and `bin/vh style apply a,b ` attach one or several presets as references: copies in `style-refs//`, a `Style refs (repo presets)` line in the BRIEF, a note at the top of STYLE.md ("只是参考…和它们不一样不用解释"), and one `风格参考` line per preset in DECISIONS.md to fill in with what it lends. Nothing is pasted into the BRIEF; attaching a preset again adds nothing; an old pasted `## Style preset` block is removed when a project is touched again. CI checks two side by side, no repeats, no pasted block. +- Docs: presets leave the list of taste defaults that need an override line (CLAUDE.md, playbook 12 §1 and §6); the quick card, playbook 01's stage 2, the concept card's 画面 line, `styles/README.md`'s how-to (references, mixing allowed, the textures caution kept as advice), the template's prompt-block note and both READMEs say "reference". + **Spec up front: where it is watched sets the text-size floor; 4K is a render setting** - Why: research note 06 left open whether the 44 px floor, written for 1080 wide, should scale for landscape. The maintainer's answer: it depends on where the film is watched, a phone or a computer, and a landscape 4K film on a computer does not need text scaled up in proportion; and the spec (resolution, 4K or 1080p, and so on) should be set at the start, guided or defaulted. - **Where it is watched**: the BRIEF has `Watch on: phone | feed | desktop` (a vertical phone; a landscape film in a phone feed, not rotated; a computer, or a phone turned sideways) and `Resolution: 1080p | 4k`. `bin/vh new` fills both from the type and frame (portrait and square → phone, landscape → desktop, landscape memes → feed, editing → phone; 1080p) and takes `--watch` and `--res` (`4K` and `2160p` are aliases); it refuses `--res 4k` for the hand-drawn engine, which has no 4K output, and targets that contradict the frame (feed for a vertical or square frame, phone for a landscape one). CI covers defaults, flags, the alias and each refusal's message. diff --git a/CLAUDE.md b/CLAUDE.md index df44679..4957885 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,7 +36,7 @@ | 适合 | 试方向、草稿、随手发 | 大多数正式视频 | 发布片、旗舰内容、会被反复看的片子 | | 人工关卡 | 0 道,直接出片。选 quick 就等于授权跳过关卡,把这一点记进 REVIEW.md;需求有歧义时最多问 1 个问题;人点名要拍板的事照样停(导演模式) | 3 道 | 3 道;关卡 ① 每张立意卡附一段 10–20 s 草图,关卡 ② 附全长 animatic | | 立意 | 自己想 3 个一句话点子,挑 1 个,写一行理由 | 关卡 ① 给 2–3 张立意卡,每张配一帧画面 | 同左,每张配一段 10–20 s 的草图(look-dev) | -| 风格 | 从立意推出来,可以借 `styles/` 的 1 个预设,写明理由 | 写在每张立意卡的"画面"一行(专属,或借一个预设);选卡就一起定了,人可以在回复里换 | 同左;每张卡的草图就是那种风格的小样,借来的预设人想看时在 E0 补小样 | +| 风格 | 从立意推出来,可以参考 `styles/` 的预设,写明借了什么 | 写在每张立意卡的"画面"一行(专属,或借一个预设);选卡就一起定了,人可以在回复里换 | 同左;每张卡的草图就是那种风格的小样,借来的预设人想看时在 E0 补小样 | | 分镜 | 简表:镜头、时长、reads | 完整 STORYBOARD + 分镜预览图 | 同左 + animatic | | 自查 | 整片一张联系表、一张按目标屏缩的联系表、前 2 s 的 strip,20 条清单速查一遍 | 每个场景:联系表、关键动作 strip、局部 crop;20 条清单 | 同左,再加目标屏尺寸测试、循环接缝、无损帧确定性、静默故障扫描 | | 独立评审(打分层) | 不做 | 1 轮:全新上下文的 reviewer 打 8 项分(含立意),修最差的 3 处 | ≥ 3 轮,8 项都 ≥ 8 才出片;达不到就带着分数进关卡 ③ | @@ -61,7 +61,7 @@ `quick` 省掉的是轮数和审阅,不是这些。 -**底线之外都是默认口味**:`TASTE_CHECKLIST.md` 里没标【底线】的条目、BRIEF 的 Motion defaults、类型文档 Prompt 增量块里的语域和节拍、风格预设和配方,都是没有立意时的稳妥做法。立意需要时可以反着来:在写用到它的场景之前,把改了什么、立意为什么需要写在立意卡或 STYLE.md 上,记进 `DECISIONS.md`;人批过的卡上写了的,算人的决定,reviewer 只查有没有做到;之后才加的由 reviewer 判断站不站得住,判了 FAIL 再补的不算(`playbook/12-ideation.md` 第 6 节)。类型文档"禁止"一节也分两种:管事实、许可、可读和隐私的是底线,管长相的(配色、质感、缓动、图表样式)是默认口味。规则是用来防止犯错的,不是用来规定口味的。 +**底线之外都是默认口味**:`TASTE_CHECKLIST.md` 里没标【底线】的条目、BRIEF 的 Motion defaults、类型文档 Prompt 增量块里的语域和节拍、镜头配方,都是没有立意时的稳妥做法。风格预设只是参考:借了什么在 `DECISIONS.md` 记一行,不借、借一半、几个混着借都行,和它不一样不用解释。立意需要时可以反着来:在写用到它的场景之前,把改了什么、立意为什么需要写在立意卡或 STYLE.md 上,记进 `DECISIONS.md`;人批过的卡上写了的,算人的决定,reviewer 只查有没有做到;之后才加的由 reviewer 判断站不站得住,判了 FAIL 再补的不算(`playbook/12-ideation.md` 第 6 节)。类型文档"禁止"一节也分两种:管事实、许可、可读和隐私的是底线,管长相的(配色、质感、缓动、图表样式)是默认口味。规则是用来防止犯错的,不是用来规定口味的。 ## 导演模式:谁来拍板 @@ -82,7 +82,7 @@ effort 管 agent 自己查得多细,导演模式管人拍板哪些事,两个 用户要 `quick` 时,不用把上面各步列的文档都读完,照这张卡做: 1. **读**:路由表对应的类型文档,只读"工作流"和"禁止"两节("Prompt 增量块"会贴在 BRIEF 末尾,挑定立意以后再读);再读 `engines/README.md` 里对应引擎的一节,HyperFrames 先看其中的"最小写法"。 -2. **建项目和立意**:`bin/vh new --effort quick`。先写 3 个一句话立意,挑一个(`playbook/12-ideation.md` 第 7 节最后一条),再读 BRIEF 末尾的 TYPE 块,决定留哪些默认。风格从立意推出来,合适的话借一个预设:`bin/vh style apply `(`bin/vh style list` 挑),它的 `STYLE_PRESET.md` 会带进项目,写代码前读一遍。立意和风格各写一行理由进 `DECISIONS.md`;改了哪些口味默认,交付时一并列出。 +2. **建项目和立意**:`bin/vh new --effort quick`。先写 3 个一句话立意,挑一个(`playbook/12-ideation.md` 第 7 节最后一条),再读 BRIEF 末尾的 TYPE 块,决定留哪些默认。风格从立意推出来;想参考预设就 `bin/vh style apply `(`bin/vh style list` 挑),副本放进项目的 `style-refs/`,借了什么在 `DECISIONS.md` 记一行,和它不一样不用解释。立意和风格各写一行理由进 `DECISIONS.md`;改了哪些口味默认,交付时一并列出。 3. **写**:补齐 BRIEF;分镜只写简表(镜头、时长、reads);然后写场景代码。HyperFrames 写完一段,用 `npx hyperframes snapshot --at <秒> --describe false` 看几个关键时刻。 4. **出片**:HyperFrames 先 `export HYPERFRAMES_SKIP_SKILLS=1 DO_NOT_TRACK=1`,再 `npx hyperframes render --quality draft --output out/draft.mp4`;手绘类用 `node render.mjs --clip --out=out/draft.mp4`。 5. **自查一遍**:`bin/vh sheet out/draft.mp4` 看整片联系表,对着 `TASTE_CHECKLIST.md` 的 20 条速查;有字就跑 `bin/vh readcheck`。再看两样(命令见 `playbook/02-verification.md` 的"手机测试"):一张按目标屏缩的联系表(BRIEF 的 `Watch on`:phone、feed 缩到 360 px 宽,desktop 缩到 640),必读字读不读得出,读数、标签别贴着下限;前 2 s 的逐帧 strip,开头是不是第 0 帧就在动、有没有让人停下来的东西。`quick` 没有 reviewer,这两样最容易漏(`docs/research/06-concept-first-ab.md`)。 diff --git a/README.md b/README.md index 43f7bd2..88b6ae1 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ Watch them back to back, each with its own sound, in [`styles/gallery.mp4`](styl ```bash bin/vh style list # see all 29 -bin/vh new promo launch-film --style cutout-jazz # start a project from a style +bin/vh new promo launch-film --style cutout-jazz # attach a style as a reference (several: --style a,b) ``` We learn the *grammar* of these works; we don't copy them. No original characters, logos or shots, and the prompts never say "in the style of" a person. More in [styles/README.md](styles/README.md). diff --git a/README.zh-CN.md b/README.zh-CN.md index 6e10d3d..77184bd 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -141,7 +141,7 @@ AI 做的视频很容易长成一个样子:暗底、发光、玻璃卡片、 ```bash bin/vh style list # 看 29 种风格 -bin/vh new promo launch-film --style cutout-jazz # 建项目时直接带上一种风格 +bin/vh new promo launch-film --style cutout-jazz # 挂上一种风格当参考(几种:--style a,b) ``` 学的是这些作品的"语法",不是照抄作品:不用原作的角色、logo 和镜头,写给 AI 的提示词里也不写"模仿某某导演"。详见 [styles/README.md](styles/README.md)。 diff --git a/bin/vh b/bin/vh index a5f1ae3..4e46c08 100755 --- a/bin/vh +++ b/bin/vh @@ -8,7 +8,7 @@ # make # types list video types and their docs # effort [quick|standard|studio] how much iteration, review and polish each level means (CLAUDE.md "努力程度") -# new [--style ] [--effort quick|standard|studio] [--aspect 16:9|9:16|1:1] [--watch phone|feed|desktop] [--res 1080p|4k] [--dir ] +# new [--style ] [--effort quick|standard|studio] [--aspect 16:9|9:16|1:1] [--watch phone|feed|desktop] [--res 1080p|4k] [--dir ] # create projects/- from templates (+ engine: p5 for handdrawn/mv, HyperFrames for short/promo/data/meme); # --aspect: short/promo/data/meme only; --dir (or $OVH_PROJECTS): make it in /- outside the harness # style list | [--draft] | gallery [--mp4] | check style library: list presets, render a 5 s swatch, build styles/gallery.jpg @@ -17,7 +17,7 @@ # decide: pictures and pages for the human # storyboard [--video v.mp4 | --snapshot] [--unsure S02,S04] labelled storyboard pages from shots.json: one per segment + an overview (+ review.json) # rhythm [--segment NAME | --from s --to s] [--max s] rhythm map: shots, narration and captions, music; too-long shots and failed reads in red -# style compare a,b,c [--frame t] | style apply presets side by side (posters, or swatch frames at t) / attach a preset to a project +# style compare a,b,c [--frame t] | style apply presets side by side (posters, or swatch frames at t) / attach presets to a project as references # cover-preview [more…] [--text-px N] a cover at real feed sizes (Bilibili, Douyin, YouTube, Xiaohongshu) + a small-size legibility check # review [gate] decision-first review page: out/review/gate-.json → gate-.html (+ index.html); prints the chat message (playbook/01-pipeline.md, 审阅页) # sound @@ -237,33 +237,46 @@ project_dir() { # a project folder, or a name under the projects folder (the dat if [ "$n" = 0 ]; then echo "no such project: $a (a folder, or a name under $(rel "$base"))" >&2; else echo "$a: several projects match: $(printf '%s\n' "$m" | xargs -n1 basename | tr '\n' ' ')" >&2; fi return 1 } -attach_style() { # attach_style : STYLE_PRESET.md + style.tokens.json, the preset's prompt block at the end of - # BRIEF.md and a pointer at the top of STYLE.md (what `new --style` does). A preset attached before is replaced, not stacked. - local dir=$1 style=$2 old="" - [ -f "$dir/STYLE_PRESET.md" ] && old=$(sed -n '1s/.* · \([a-z0-9-]*\)$/\1/p' "$dir/STYLE_PRESET.md") - cp "$ROOT/styles/$style/STYLE.md" "$dir/STYLE_PRESET.md"; cp "$ROOT/styles/$style/tokens.json" "$dir/style.tokens.json" - python3 - "$ROOT/styles/$style/STYLE.md" "$dir/BRIEF.md" "$dir/STYLE.md" "$style" <<'PY' -import re, sys -src, brief, style, slug = sys.argv[1:5] -m = re.search(r"## Prompt 块\s*```text\n(.*?)```", open(src).read(), re.S) +attach_style() { # attach_style : attach a preset as a reference, not a template. Its STYLE.md and tokens.json + # are copied to style-refs//, the BRIEF's "Style refs" line and the note at the top of STYLE.md list it, and DECISIONS.md + # gets a line to fill in with what it lends. Nothing is pasted into the BRIEF as instructions. Several presets can be attached; + # attaching one again changes nothing. (Projects made before this keep their STYLE_PRESET.md; the old pasted block goes.) + local dir=$1 style=$2 + mkdir -p "$dir/style-refs/$style" + cp "$ROOT/styles/$style/STYLE.md" "$dir/style-refs/$style/STYLE.md"; cp "$ROOT/styles/$style/tokens.json" "$dir/style-refs/$style/tokens.json" + python3 - "$dir/BRIEF.md" "$dir/STYLE.md" "$dir/DECISIONS.md" "$style" <<'PY' +import datetime, os, re, sys +brief, style, dec, slug = sys.argv[1:5] b = open(brief).read() -b = re.sub(r"\n\n## Style preset: [^\n]*\n\n```text\n.*?```\n", "", b, flags=re.S) # the preset attached before, if any -if m: b += f"\n\n## Style preset: {slug}\n\n```text\n{m.group(1).strip()}\n```\n" +b = re.sub(r"\n\n## Style preset: [^\n]*\n\n```text\n.*?```\n", "", b, flags=re.S) # pasted by bin/vh before 2026-10: an instruction block +m = re.search(r"^- Style refs \(repo presets\): (.*)$", b, re.M) +refs = re.findall(r"styles/([a-z0-9-]+)", m.group(1).split("" +if m: b = b[:m.start()] + line + b[m.end():] +elif "\n## Style\n" in b: b = b.replace("\n## Style\n", "\n## Style\n" + line + "\n", 1) +else: b += "\n" + line + "\n" open(brief, "w").write(b) s = open(style).read() s = re.sub(r"\A> 本项目以风格预设 `styles/[^`]*` 为底:[^\n]*\n\n", "", s) -open(style, "w").write(f"> 本项目以风格预设 `styles/{slug}` 为底:先读 `STYLE_PRESET.md`,数值取 `style.tokens.json`;下面只写本片对预设的改动。\n\n" + s) +s = re.sub(r"\A> 参考的风格预设:[^\n]*\n\n", "", s) +note = "> 参考的风格预设:" + "、".join(f"`styles/{r}`" for r in refs) + "(副本在 `style-refs/`)。只是参考:借了什么写进 `DECISIONS.md`,和它们不一样不用解释;本片的风格从立意来,写在下面。\n\n" +open(style, "w").write(note + s) +if os.path.exists(dec): + d = open(dec).read() + if f"风格参考 · `styles/{slug}`" not in d: + d = d.rstrip("\n") + f"\n- {datetime.date.today():%Y-%m-%d} 风格参考 · `styles/{slug}` — 借了:{{色板 / 字体 / 转场 / 声音 / 质感,写具体;没借就删掉这一行}}\n" + open(dec, "w").write(d) PY - [ -n "$old" ] && [ "$old" != "$style" ] && echo " replaced : styles/$old (its prompt block in BRIEF.md and the pointer in STYLE.md; your own notes below it stay)" return 0 } cmd_new() { - [ $# -ge 2 ] || { echo "usage: bin/vh new [--style ] [--effort quick|standard|studio] [--aspect 16:9|9:16|1:1] [--watch phone|feed|desktop] [--res 1080p|4k] [--dir ]"; cmd_types; exit 1; } + [ $# -ge 2 ] || { echo "usage: bin/vh new [--style ] [--effort quick|standard|studio] [--aspect 16:9|9:16|1:1] [--watch phone|feed|desktop] [--res 1080p|4k] [--dir ]"; cmd_types; exit 1; } local type=$1 slug=$2 doc style="" effort="" aspect="" res="" parent="" wh="" watch="" outres=""; doc=$(type_doc "$type") || { echo "unknown type: $type"; cmd_types; exit 1; } shift 2 while [ $# -gt 0 ]; do case "$1" in - --style) style=${2:?--style needs a preset (bin/vh style list)}; shift 2 ;; + --style) style=${2:?--style needs a preset or a comma list (bin/vh style list)}; shift 2 ;; --effort) effort=${2:?--effort needs quick, standard or studio}; shift 2 ;; --aspect) aspect=${2:?--aspect needs 16:9, 9:16 or 1:1}; shift 2 ;; --dir) parent=${2:?--dir needs a folder: the project goes in /-}; shift 2 ;; @@ -271,7 +284,7 @@ cmd_new() { --res) outres=${2:?--res needs 1080p or 4k}; shift 2 ;; *) echo "unknown option: $1"; exit 1 ;; esac; done - [ -z "$style" ] || [ -f "$ROOT/styles/$style/STYLE.md" ] || { echo "unknown style: $style"; cmd_style list; exit 1; } + local s; for s in $(printf '%s\n' "$style" | tr ',' ' '); do [ -f "$ROOT/styles/$s/STYLE.md" ] && [ "${s#_}" = "$s" ] || { echo "unknown style: $s"; cmd_style list; exit 1; }; done case "${effort:-standard}" in quick|standard|studio) ;; *) echo "unknown effort: $effort (quick | standard | studio)"; exit 1 ;; esac case "$watch" in ""|phone|feed|desktop) ;; *) echo "unknown --watch: $watch (phone: a vertical phone · feed: landscape in a phone feed, not rotated · desktop: a computer, or a phone turned sideways)"; exit 1 ;; esac case "$outres" in ""|1080p) ;; 4k|4K|2160p) outres=4k ;; *) echo "unknown --res: $outres (1080p | 4k)"; exit 1 ;; esac @@ -336,13 +349,13 @@ PY sedi "s/^- Effort: standard /- Effort: $effort /" "$dir/BRIEF.md" [ "$effort" = quick ] && printf '\n> **Effort: quick**(%s,建项目时选定):按 CLAUDE.md"努力程度",人工关卡豁免,直接出片;人点名要拍板的事照样停(导演模式);需求有歧义时最多问 1 个问题。\n' "$(date +%F)" >> "$dir/REVIEW.md" fi - [ -n "$style" ] && attach_style "$dir" "$style" # the preset rides along: its STYLE.md + tokens, and its prompt block appended to BRIEF + for s in $(printf '%s\n' "$style" | tr ',' ' '); do attach_style "$dir" "$s"; done # presets as references: copies in style-refs/, listed in BRIEF, a DECISIONS line trap - EXIT echo "$c_ok created $(rel "$dir")" echo " type doc : video-types/$doc.md" [ -n "$wh" ] && echo " frame : $wh ($res)" echo " watch on : $watch · ${outres:-1080p} (text-size floor and readability check: playbook/03 §4)" - [ -n "$style" ] && echo " style : styles/$style (STYLE_PRESET.md, style.tokens.json)" + [ -n "$style" ] && echo " style ref: styles/$(printf '%s' "$style" | sed 's/,/, styles\//g') (copies in style-refs/; references, not rules: note in DECISIONS.md what each lends)" echo " effort : ${effort:-standard} (bin/vh effort ${effort:-standard} shows what that means)" echo " next : open Claude Code in the repo root and say what the video is about;" if [ "${effort:-standard}" = quick ]; then echo " the agent fills $(rel "$dir")/BRIEF.md and renders straight through; it stops only for what you say you'll decide." @@ -402,12 +415,12 @@ PY check) shift; swatch_deps "${1:-}"; "$sw/determinism.sh" "$@" ;; compare) shift; [ $# -gt 0 ] || { echo "usage: bin/vh style compare a,b,c [--frame t] [--out file.png]"; exit 1; } uv run -q --no-project --with pillow python "$ROOT/tools/style_compare.py" "$@" ;; - apply) shift; [ $# -eq 2 ] || { echo "usage: bin/vh style apply (bin/vh style list shows the presets)"; exit 1; } - [ -f "$ROOT/styles/$1/STYLE.md" ] && [ "${1#_}" = "$1" ] || { echo "unknown style: $1"; cmd_style list; exit 1; } + apply) shift; [ $# -eq 2 ] || { echo "usage: bin/vh style apply (bin/vh style list shows the presets)"; exit 1; } + local s; for s in $(printf '%s\n' "$1" | tr ',' ' '); do [ -f "$ROOT/styles/$s/STYLE.md" ] && [ "${s#_}" = "$s" ] || { echo "unknown style: $s"; cmd_style list; exit 1; }; done local dir; dir=$(project_dir "$2") || exit 1 [ -f "$dir/BRIEF.md" ] && [ -f "$dir/STYLE.md" ] || { echo "$(rel "$dir") has no BRIEF.md and STYLE.md: not a bin/vh project"; exit 1; } - attach_style "$dir" "$1" - echo "$c_ok styles/$1 attached to $(rel "$dir") (STYLE_PRESET.md, style.tokens.json, its prompt block in BRIEF.md)" ;; + for s in $(printf '%s\n' "$1" | tr ',' ' '); do attach_style "$dir" "$s" + echo "$c_ok styles/$s attached to $(rel "$dir") as a reference (style-refs/$s/, BRIEF Style refs, a DECISIONS line to fill in)"; done ;; *) swatch_deps "$1"; exec "$sw/render.sh" "$@" ;; esac } diff --git a/playbook/01-pipeline.md b/playbook/01-pipeline.md index 05c0ff0..721b8b4 100644 --- a/playbook/01-pipeline.md +++ b/playbook/01-pipeline.md @@ -8,7 +8,7 @@ |---|---|---|---| | 0 选路径 | — | `00-paradigm.md` 的选型表和 `video-types/` | 能说清验收标准 | | 1 素材、立意、Brief、大纲 | `NOTES.md` 的素材清单;2–3 张立意卡;`BRIEF.md`(立意、受众、平台、时长、画幅、fps、分辨率、在哪看、有无声音、验收项)+ 3–7 段大纲 | `12-ideation.md`、`templates/BRIEF.md` | 立意一句话说得出、换个题材就不成立;**人工关卡 ①** | -| 2 风格 | `STYLE.md`:从立意推出的调色板、字体、缓动、安全区或锚点网格、禁止项;借了预设就只写改动 | `templates/STYLE.md` + 类型文档,可借 `styles/` | 一页纸能讲清,看得出是从立意来的 | +| 2 风格 | `STYLE.md`:从立意推出的调色板、字体、缓动、安全区或锚点网格、禁止项;参考了预设,就在 `DECISIONS.md` 记借了什么 | `templates/STYLE.md` + 类型文档,可参考 `styles/` | 一页纸能讲清,看得出是从立意来的 | | 3 脚本与分镜 | `SCRIPT.md`(有旁白时写,关键词标 `{cue}`)、`STORYBOARD.md`(每镜的时间、reads、转场)、每镜一张关键帧(`shots.json` → `bin/vh storyboard`,出在 `out/check/storyboard/`);节奏要紧的片子再加一版 animatic `out/animatic.mp4` | `templates/SCRIPT.md`、`templates/STORYBOARD.md` | 每镜都有事件,reads 不重叠;**人工关卡 ②** | | 4 音频先行 | `audio/*`、`timeline.json`(词级时间)、`beats.json` | 见 `04-audio.md` | 用实测时长回写分镜 | | 5 搭引擎 | 渲染脚本、公共库(hash、ease、keyframe、camera、pulse),先做一个样板场景 | `engines/README.md` | 乱序跳到同一帧,结果一致 | diff --git a/playbook/12-ideation.md b/playbook/12-ideation.md index 5bdc7a1..7053326 100644 --- a/playbook/12-ideation.md +++ b/playbook/12-ideation.md @@ -9,7 +9,7 @@ 1. **好片都有一个立意,风格在服务它**【综合】。`cases/oneshot-five.md` 拆的五支片子,从成片看,风格都在服务一个点子;创作顺序从成片看不出来,但先想点子、再定风格,至少不会让风格替代点子。 2. **立意从材料里长出来**:先挖素材(第 2 节),再想点子(第 3 节);先自己想,再看别人怎么做(第 4 节)。 -3. **底线不动,口味可以改**:底线照旧;风格预设、镜头配方、类型文档的节拍和语域、清单里没标【底线】的条目,都是默认值,立意需要时可以反着来,写一行理由(第 6 节)。 +3. **底线不动,口味可以改**:底线照旧;镜头配方、类型文档的节拍和语域、清单里没标【底线】的条目,都是默认值,立意需要时可以反着来,写一行理由(第 6 节)。风格预设只是参考,借了什么记一行就行,和它不一样不用解释。 4. **关卡 ① 先定立意,再定风格**(第 7 节)。 ## 1. 什么是立意 @@ -88,7 +88,7 @@ 为什么是这份材料:{用到素材清单的哪几条;换个题目为什么就不成立} 主体:{从头跟到尾的东西,以及它怎么变} 首尾:{开头的画面或第一句话;结尾怎么回应} -画面:{由立意推出的语言:底色、字体、运动的语域、镜头;最接近的风格预设(可选)和要改的地方} +画面:{由立意推出的语言:底色、字体、运动的语域、镜头;可以参考的风格预设(可选,借哪一样)} 钩子:{开头几秒怎么抓人,常常就是立意本身;要发平台的片子按 10-hooks-and-packaging.md 写成钩子卡} 口味覆盖:{这张卡要反着用的默认(TASTE_CHECKLIST 的条号、Motion defaults、类型的语域或节拍),各一句为什么;没有就写"无"} 声音:{谁说话、配乐的语域、有没有一个声音母题} @@ -115,7 +115,9 @@ - `TASTE_CHECKLIST.md` 里没标【底线】的条目; - `BRIEF.md` 的 Motion defaults; - 类型文档 Prompt 增量块里的语域、配色和节拍顺序; -- 风格预设、镜头配方和全片骨架。 +- 镜头配方和全片骨架。 + +风格预设不在这一层:它们是参考,不是默认值。借了什么在 `DECISIONS.md` 记一行;不借、借一半、几个混着借都行,和预设不一样不用解释。 反着来的规矩: @@ -139,7 +141,7 @@ FunTech 在终场前用了约 1 s 真静音。这碰的是底线,不在可改 - **人已经给了立意**("把它做成一个游戏关卡"),就记进 BRIEF,不再出卡,最多附一个备选。 - **每张卡配一张图**。人看图比读文字快:`standard` 每张立意卡配一帧关键画面(草图、灰盒,或者 HyperFrames 的 `snapshot`);`studio` 每张配一段 10–20 s 的草图,做法同 `01-pipeline.md` 的 look-dev;这段草图同时就是那种风格的小样。借来的预设,人想看真渲小样时放到 E0。 - **自由草图**(可选,一句话需求时推荐):写立意卡之前,agent 可以先按自己最想做的那个点子,只守底线、不套任何默认值,渲一段 10–20 s 的草图,当作那张卡的图。它也能暴露模型自己的直觉,常常比套模板的版本更有意思。 -- **`quick`**:写 3 个一句话点子,挑一个,在 `DECISIONS.md` 写一行为什么;风格从它推出来,可以借一个预设(`bin/vh style apply `),也可以不借。 +- **`quick`**:写 3 个一句话点子,挑一个,在 `DECISIONS.md` 写一行为什么;风格从它推出来,可以参考一个或几个预设(`bin/vh style apply `),也可以不参考。 ## 8. 例子:同一句需求,三张立意卡 diff --git a/styles/README.md b/styles/README.md index 5716f18..eeeddda 100644 --- a/styles/README.md +++ b/styles/README.md @@ -11,15 +11,15 @@ ## 怎么用 1. **在关卡 ① 借用**。风格从立意里推出来(`playbook/12-ideation.md`);立意卡的"画面"一行可以借这里的一个预设,附上样片(`/media/swatch.mp4`)。人想多看几种时,从这里挑 2–3 个彼此拉得开的,让人选或混搭。不要默认只给一种口味。 -2. **建项目时带上预设**: +2. **建项目时挂上参考**: ```bash bin/vh style list # 29 个风格、家族、有没有样片 - bin/vh new promo launch-film --style cutout-jazz # 项目里多出 STYLE_PRESET.md 和 style.tokens.json,BRIEF 末尾追加预设的 prompt 块 + bin/vh new promo launch-film --style cutout-jazz # 副本放进项目的 style-refs/,BRIEF 的 Style refs 记上它;可以 --style a,b 挂几个 ``` -3. **在项目的 `STYLE.md` 里只写改动**。预设是起点,不是牢笼:换一个强调色、改转场节奏都可以,写清楚改了什么、为什么改。 -4. **混搭的规矩**:一个主风格,最多再借一样东西,例如借另一个风格的转场或字体;不要同时叠两种质感(半调加水墨、扫描线加纸纹)。 -5. **自查**:先按预设 STYLE.md 的"自查重点",再按 `templates/TASTE_CHECKLIST.md`。 +3. **预设是参考,不是模板**。项目的 `STYLE.md` 从立意写起;从预设里借了什么(色板、字体、转场、声音、质感),在 `DECISIONS.md` 记一行,和它不一样不用解释。预设里的数字是样片的值,换场景就按理由重新定。 +4. **可以混搭**:从几个预设里各借一样,例如这个的色板、那个的转场。叠两种质感(半调加水墨、扫描线加纸纹)很容易糊,真要叠,先渲一小段看。 +5. **自查**:按 `templates/TASTE_CHECKLIST.md`;借来的那部分,可以顺手看一眼原预设的"自查重点"。 ## 29 个风格 diff --git a/styles/_TEMPLATE.md b/styles/_TEMPLATE.md index 3acb0d2..d36e2a5 100644 --- a/styles/_TEMPLATE.md +++ b/styles/_TEMPLATE.md @@ -48,7 +48,7 @@ ## Prompt 块 -可以直接粘进 BRIEF.md 的一段英文描述,用语法写,不写人名。 +借这个风格时可以参考的一段英文描述,用语法写,不写人名。它是参考,不会被贴进项目的 BRIEF;用的人挑着借。 ```text … diff --git a/templates/BRIEF.md b/templates/BRIEF.md index ac62469..ec5dcb1 100644 --- a/templates/BRIEF.md +++ b/templates/BRIEF.md @@ -22,6 +22,7 @@ - Source material: {paper path / product URL / data.csv / song path / script} ## Style +- Style refs (repo presets): {none} - Refs (2–3 named works): {…} - Explicitly NOT: {anti-refs, e.g. purple-cyan gradients, Pixar-like 3D, bullet-point slides} - Palette: bg {hex}, fg {hex}, ONE accent {hex}; no pure #000/#fff unless chosen or required by the type doc (e.g. 3b1b-style black) diff --git a/tools/ci.sh b/tools/ci.sh index c717db0..5f56f94 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -139,7 +139,8 @@ smoke_checks() { dir=$(ls -d "$ROOT"/projects/*-"$slug" 2>/dev/null); [ -z "$dir" ] || rm -rf "$dir" if vh new math "$slug" --effort quick --style dark-math >/dev/null; then dir=$(ls -d "$ROOT"/projects/*-"$slug") - grep -q '^- Effort: quick' "$dir/BRIEF.md" && grep -q 'Effort: quick' "$dir/REVIEW.md" && [ -f "$dir/STYLE_PRESET.md" ] \ + grep -q '^- Effort: quick' "$dir/BRIEF.md" && grep -q 'Effort: quick' "$dir/REVIEW.md" && [ -f "$dir/style-refs/dark-math/STYLE.md" ] \ + && grep -q '^- Style refs (repo presets): `styles/dark-math`' "$dir/BRIEF.md" && ! grep -q '^## Style preset' "$dir/BRIEF.md" \ && ok "bin/vh new --effort quick --style" || bad "bin/vh new: effort or style not written into the project" rm -rf "$dir" else bad "bin/vh new math"; fi @@ -225,10 +226,12 @@ decision_checks() { && OVH_PROJECTS="$t/w2" vh new edit ci-e --res 4K >/dev/null 2>&1 && grep -q '^- Watch on: phone' "$(ls -d "$t"/w2/*-ci-e)/BRIEF.md" \ && grep -q '^- Resolution: 4k' "$(ls -d "$t"/w2/*-ci-e)/BRIEF.md"; then ok "new writes Watch on and Resolution into the BRIEF (defaults, flags, the 4K alias)" else bad "new: Watch on / Resolution missing or wrong in the BRIEF"; fi - # style apply: attach, replace, re-apply without stacking; by name under OVH_PROJECTS + # style apply: presets are references; two attach side by side, re-attaching one adds nothing; by name under OVH_PROJECTS vh style apply blueprint "$p" >/dev/null && vh style apply ink-wash "$p" >/dev/null && OVH_PROJECTS="$t/elsewhere" vh style apply ink-wash ci-dir >/dev/null - if [ "$(grep -c '^## Style preset' "$p/BRIEF.md")" = 1 ] && grep -q '^## Style preset: ink-wash' "$p/BRIEF.md" && [ "$(grep -c '本项目以风格预设' "$p/STYLE.md")" = 1 ] \ - && head -1 "$p/STYLE_PRESET.md" | grep -q 'ink-wash'; then ok "style apply replaces the preset instead of stacking it"; else bad "style apply: presets stacked or missing"; fi + if grep -q '^- Style refs (repo presets): `styles/blueprint`, `styles/ink-wash` " if m: b = b[:m.start()] + line + b[m.end():] elif "\n## Style\n" in b: b = b.replace("\n## Style\n", "\n## Style\n" + line + "\n", 1) @@ -262,11 +283,11 @@ s = re.sub(r"\A> 本项目以风格预设 `styles/[^`]*` 为底:[^\n]*\n\n", " s = re.sub(r"\A> 参考的风格预设:[^\n]*\n\n", "", s) note = "> 参考的风格预设:" + "、".join(f"`styles/{r}`" for r in refs) + "(副本在 `style-refs/`)。只是参考:借了什么写进 `DECISIONS.md`,和它们不一样不用解释;本片的风格从立意来,写在下面。\n\n" open(style, "w").write(note + s) -if os.path.exists(dec): - d = open(dec).read() - if f"风格参考 · `styles/{slug}`" not in d: - d = d.rstrip("\n") + f"\n- {datetime.date.today():%Y-%m-%d} 风格参考 · `styles/{slug}` — 借了:{{色板 / 字体 / 转场 / 声音 / 质感,写具体;没借就删掉这一行}}\n" - open(dec, "w").write(d) +d = open(dec).read() +for r in ([old] if old else []) + [slug]: + if f"风格参考 · `styles/{r}`" not in d: + d = d.rstrip("\n") + f"\n- {datetime.date.today():%Y-%m-%d} 风格参考 · `styles/{r}` — 借了:{{色板 / 字体 / 转场 / 声音 / 质感,写具体;没借就删掉这一行}}\n" +open(dec, "w").write(d) PY return 0 } @@ -284,7 +305,7 @@ cmd_new() { --res) outres=${2:?--res needs 1080p or 4k}; shift 2 ;; *) echo "unknown option: $1"; exit 1 ;; esac; done - local s; for s in $(printf '%s\n' "$style" | tr ',' ' '); do [ -f "$ROOT/styles/$s/STYLE.md" ] && [ "${s#_}" = "$s" ] || { echo "unknown style: $s"; cmd_style list; exit 1; }; done + local s styles=""; if [ -n "$style" ]; then styles=$(parse_styles "$style") || { cmd_style list; exit 1; }; fi case "${effort:-standard}" in quick|standard|studio) ;; *) echo "unknown effort: $effort (quick | standard | studio)"; exit 1 ;; esac case "$watch" in ""|phone|feed|desktop) ;; *) echo "unknown --watch: $watch (phone: a vertical phone · feed: landscape in a phone feed, not rotated · desktop: a computer, or a phone turned sideways)"; exit 1 ;; esac case "$outres" in ""|1080p) ;; 4k|4K|2160p) outres=4k ;; *) echo "unknown --res: $outres (1080p | 4k)"; exit 1 ;; esac @@ -349,13 +370,13 @@ PY sedi "s/^- Effort: standard /- Effort: $effort /" "$dir/BRIEF.md" [ "$effort" = quick ] && printf '\n> **Effort: quick**(%s,建项目时选定):按 CLAUDE.md"努力程度",人工关卡豁免,直接出片;人点名要拍板的事照样停(导演模式);需求有歧义时最多问 1 个问题。\n' "$(date +%F)" >> "$dir/REVIEW.md" fi - for s in $(printf '%s\n' "$style" | tr ',' ' '); do attach_style "$dir" "$s"; done # presets as references: copies in style-refs/, listed in BRIEF, a DECISIONS line + for s in $styles; do attach_style "$dir" "$s"; done # presets as references: copies in style-refs/, listed in BRIEF, a DECISIONS line trap - EXIT echo "$c_ok created $(rel "$dir")" echo " type doc : video-types/$doc.md" [ -n "$wh" ] && echo " frame : $wh ($res)" echo " watch on : $watch · ${outres:-1080p} (text-size floor and readability check: playbook/03 §4)" - [ -n "$style" ] && echo " style ref: styles/$(printf '%s' "$style" | sed 's/,/, styles\//g') (copies in style-refs/; references, not rules: note in DECISIONS.md what each lends)" + [ -n "$styles" ] && echo " style ref: $(printf '%s\n' $styles | sed 's|^|styles/|' | paste -sd, - | sed 's/,/, /g') (copies in style-refs/; references, not rules: note in DECISIONS.md what each lends)" echo " effort : ${effort:-standard} (bin/vh effort ${effort:-standard} shows what that means)" echo " next : open Claude Code in the repo root and say what the video is about;" if [ "${effort:-standard}" = quick ]; then echo " the agent fills $(rel "$dir")/BRIEF.md and renders straight through; it stops only for what you say you'll decide." @@ -416,10 +437,10 @@ PY compare) shift; [ $# -gt 0 ] || { echo "usage: bin/vh style compare a,b,c [--frame t] [--out file.png]"; exit 1; } uv run -q --no-project --with pillow python "$ROOT/tools/style_compare.py" "$@" ;; apply) shift; [ $# -eq 2 ] || { echo "usage: bin/vh style apply (bin/vh style list shows the presets)"; exit 1; } - local s; for s in $(printf '%s\n' "$1" | tr ',' ' '); do [ -f "$ROOT/styles/$s/STYLE.md" ] && [ "${s#_}" = "$s" ] || { echo "unknown style: $s"; cmd_style list; exit 1; }; done + local s styles; styles=$(parse_styles "$1") || { cmd_style list; exit 1; } local dir; dir=$(project_dir "$2") || exit 1 [ -f "$dir/BRIEF.md" ] && [ -f "$dir/STYLE.md" ] || { echo "$(rel "$dir") has no BRIEF.md and STYLE.md: not a bin/vh project"; exit 1; } - for s in $(printf '%s\n' "$1" | tr ',' ' '); do attach_style "$dir" "$s" + for s in $styles; do attach_style "$dir" "$s" echo "$c_ok styles/$s attached to $(rel "$dir") as a reference (style-refs/$s/, BRIEF Style refs, a DECISIONS line to fill in)"; done ;; *) swatch_deps "$1"; exec "$sw/render.sh" "$@" ;; esac diff --git a/playbook/12-ideation.md b/playbook/12-ideation.md index 7053326..9d39e24 100644 --- a/playbook/12-ideation.md +++ b/playbook/12-ideation.md @@ -117,7 +117,7 @@ - 类型文档 Prompt 增量块里的语域、配色和节拍顺序; - 镜头配方和全片骨架。 -风格预设不在这一层:它们是参考,不是默认值。借了什么在 `DECISIONS.md` 记一行;不借、借一半、几个混着借都行,和预设不一样不用解释。 +风格预设不在这一层:它们是参考,不是默认值。借了什么在 `DECISIONS.md` 记一行;不借、借一半、几个混着借都行,和预设不一样不用解释。借来的东西碰到 `TASTE_CHECKLIST.md` 里没标【底线】的条目时,照上面的规矩记口味覆盖。 反着来的规矩: diff --git a/recipes/README.md b/recipes/README.md index a54c7ce..55a78dc 100644 --- a/recipes/README.md +++ b/recipes/README.md @@ -20,7 +20,7 @@ **冲突时听谁的**: -1. 顺序是:用户 > 类型文档 > 项目 `STYLE.md` > 风格预设 > 配方的默认值 > `playbook/` 的通用数值。配方比 playbook 具体,所以同一件事两边都写了数,以配方为准。 +1. 顺序是:用户 > 类型文档 > 项目 `STYLE.md`(从风格预设借来的东西写进这里才算数)> 配方的默认值 > `playbook/` 的通用数值。配方比 playbook 具体,所以同一件事两边都写了数,以配方为准。 2. CLAUDE.md 的硬规则和"任何档位都不降的底线"高于任何配方:每一帧是 t 的纯函数、全屏闪白每秒不超过 3 次、字号不低于下限、片中没有数字静音。 3. 参数表里标 ★ 的是命门:风格可以给它换皮,不能降它的档。例如风格要求一拍二(12 fps),hold 仍按秒数给足,切点取整到 2 帧,而不是把 hold 砍半。风格明确不用某种手法时(例如 `product-keynote` 不用 glitch),换一张配方,不要把这张做残。 4. 有意偏离配方(降档、改结构、换顺序),在 `DECISIONS.md` 写一句为什么。 diff --git a/styles/README.md b/styles/README.md index eeeddda..91d13d3 100644 --- a/styles/README.md +++ b/styles/README.md @@ -10,7 +10,7 @@ ## 怎么用 -1. **在关卡 ① 借用**。风格从立意里推出来(`playbook/12-ideation.md`);立意卡的"画面"一行可以借这里的一个预设,附上样片(`/media/swatch.mp4`)。人想多看几种时,从这里挑 2–3 个彼此拉得开的,让人选或混搭。不要默认只给一种口味。 +1. **在关卡 ① 借用**。风格从立意里推出来(`playbook/12-ideation.md`);立意卡的"画面"一行可以参考这里的预设(一个或几个),附上样片(`/media/swatch.mp4`)。人想多看几种时,从这里挑 2–3 个彼此拉得开的,让人选或混搭。不要默认只给一种口味。 2. **建项目时挂上参考**: ```bash diff --git a/tools/ci.sh b/tools/ci.sh index 5f56f94..1aefd35 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -218,6 +218,9 @@ decision_checks() { a=$(vh new short ci-x --watch feed 2>&1); rc=$?; [ $rc = 1 ] && case "$a" in *"already --watch phone"*) true ;; *) false ;; esac && ok "new refuses a feed target for a vertical frame" || bad "new short --watch feed exited $rc: $a" a=$(vh new promo ci-x --watch phone 2>&1); rc=$?; [ $rc = 1 ] && case "$a" in *"--watch feed"*) true ;; *) false ;; esac && ok "new refuses a phone target for a landscape frame" || bad "new promo --watch phone exited $rc: $a" vh new math ci-x --watch phone >/dev/null 2>&1; rc=$?; [ $rc = 1 ] && ok "new refuses a phone target for a Manim (landscape) type" || bad "new math --watch phone exited $rc" + for bad_style in "dark-math,_swatch" "Dark-Math" "../styles/ink-wash" ","; do + vh new math ci-x --style "$bad_style" >/dev/null 2>&1; rc=$?; [ $rc = 1 ] || bad "new --style '$bad_style' exited $rc"; done + ok "new --style rejects a bad name anywhere in the list, paths and an empty list" [ -z "$(ls -d "$ROOT"/projects/*-ci-x 2>/dev/null)" ] || bad "a refused new left a project behind" # new: where it is watched and the output resolution land in the BRIEF (defaults from the frame, flags override) if grep -q '^- Watch on: desktop' "$p/BRIEF.md" && grep -q '^- Resolution: 1080p' "$p/BRIEF.md" \ @@ -232,6 +235,19 @@ decision_checks() { && [ "$(grep -c '^> 参考的风格预设:`styles/blueprint`、`styles/ink-wash`' "$p/STYLE.md")" = 1 ] && [ "$(grep -c '风格参考 · ' "$p/DECISIONS.md")" = 2 ] \ && [ -f "$p/style-refs/blueprint/tokens.json" ] && [ -f "$p/style-refs/ink-wash/STYLE.md" ]; then ok "style apply attaches presets as references, side by side, without repeats" else bad "style apply: references missing, repeated, or an instruction block pasted into the BRIEF"; fi + # a comma list in one call, and a project made the old way (STYLE_PRESET.md + a pasted block + the 为底 pointer) keeps its base as a reference + if OVH_PROJECTS="$t/m" vh new math ci-m >/dev/null 2>&1; then + q=$(ls -d "$t"/m/*-ci-m) + cp "$ROOT/styles/cutout-jazz/STYLE.md" "$q/STYLE_PRESET.md"; cp "$ROOT/styles/cutout-jazz/tokens.json" "$q/style.tokens.json" + printf '\n\n## Style preset: cutout-jazz\n\n```text\nold block\n```\n' >> "$q/BRIEF.md" + { printf '> 本项目以风格预设 `styles/cutout-jazz` 为底:先读 `STYLE_PRESET.md`。\n\n'; cat "$q/STYLE.md"; } > "$q/STYLE.md.new" && mv "$q/STYLE.md.new" "$q/STYLE.md" + vh style apply blueprint,ink-wash "$q" >/dev/null 2>&1 + if [ -f "$q/style-refs/cutout-jazz/STYLE.md" ] && [ -f "$q/style-refs/cutout-jazz/tokens.json" ] && [ ! -e "$q/STYLE_PRESET.md" ] \ + && grep -q '^- Style refs (repo presets): `styles/cutout-jazz`, `styles/blueprint`, `styles/ink-wash`' "$q/BRIEF.md" \ + && ! grep -q '^## Style preset' "$q/BRIEF.md" && ! grep -q '为底' "$q/STYLE.md" && [ "$(grep -c '风格参考 · ' "$q/DECISIONS.md")" = 3 ]; then + ok "style apply a,b: one call attaches both; an old base preset becomes a reference" + else bad "style apply: comma list or migration of an old project's preset failed"; fi + else bad "new math ci-m failed"; fi vh style apply no-such-style "$p" >/dev/null 2>&1; rc=$?; [ $rc = 1 ] && ok "style apply rejects an unknown preset" || bad "style apply unknown preset exited $rc" # readcheck: a budget, and the timed text of a composition without a browser a=$(vh readcheck --budget 3.2) From d5c265606deb29fd70ffe363629a46bab0029939 Mon Sep 17 00:00:00 2001 From: zhanglinghao Date: Fri, 2 Oct 2026 15:26:17 +0800 Subject: [PATCH 3/3] parse_styles: spell out the lower-case letters (a-z ranges follow the locale in bash 3.2 and matched Dark-Math on the macOS runner) --- bin/vh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/vh b/bin/vh index ff5cf7f..6e77392 100755 --- a/bin/vh +++ b/bin/vh @@ -242,7 +242,7 @@ parse_styles() { # parse_styles "": the preset names, one per line (spaces local s n=0 IFS=, set -f; set -- $1; set +f for s in "$@"; do s=$(printf '%s' "$s" | tr -d ' \t'); [ -z "$s" ] && continue - case $s in *[!a-z0-9-]*|-*) echo "unknown style: $s" >&2; return 1 ;; esac + case $s in *[!abcdefghijklmnopqrstuvwxyz0123456789-]*|-*) echo "unknown style: $s" >&2; return 1 ;; esac # spelled out: a-z ranges follow the locale (bash 3.2) and can match capitals [ -f "$ROOT/styles/$s/STYLE.md" ] || { echo "unknown style: $s" >&2; return 1; } printf '%s\n' "$s"; n=$((n+1)) done