Screenplay: Dramatize the Interaction, Don't Caption the Narration / 剧本:把交互戏剧化,而不是给旁白配字幕
This is the generation step, and it is your authoring job. Turn the transcript into the two-party interaction the creator is describing: what they asked Muse, what Muse said back, what skills ran, what results appeared. A video of only user bubbles is a failed screenplay; that is captions, not a story.
这是生成步骤,也是你的创作任务。把转录文本变成创作者所描述的双方交互:他们问了 Muse 什么,Muse 回了什么,跑了哪些技能,出现了什么结果。只有用户气泡的视频是失败的剧本;那是字幕,不是故事。
The screenplay contract and the validator live in /opt/hatch/skills/magic-moment/cmm/script.py; read its docstring for the exact dict shape.
剧本契约与校验器位于 /opt/hatch/skills/magic-moment/cmm/script.py;确切的字典形状见其 docstring。
The renderer closes every video with the full-screen Muse finisher, which takes the whole frame after the source footage ends; /opt/hatch/skills/magic-moment/guide/timeline.md owns the cutoff rule that keeps your beats clear of it.
渲染器用全屏 Muse 收尾画面结束每个视频,它在源素材结束后占据整个画面;/opt/hatch/skills/magic-moment/guide/timeline.md 负责让节拍避开它的截止规则。
Beat types:
节拍类型:
bubblespeakeruser: the creator's ask, first person, as if typing to Muse: "Book me a flight on Tuesday?" The send test: every bubble must read as a message someone would actually send in this thread. The voiceover narrates ABOUT the conversation ("all I did was tell it I needed a flight on Tuesday"); the bubble shows the message itself ("Need a flight on Tuesday"), not the narration ("I just told it I needed a flight" is a caption, not a message, andvalidate_scriptrejects the obvious forms). Convert, don't drop: rewording into send form keeps the turn; deleting the beat to dodge the rule breaks the conversation shape.bubblespeakeruser:创作者的请求,第一人称,就像正在给 Muse 打字:"Book me a flight on Tuesday?"。发送测试:每个气泡读起来都必须像有人真的会在这个会话里发出的消息。旁白是在"讲述"这段对话("all I did was tell it I needed a flight on Tuesday");气泡展示消息本身("Need a flight on Tuesday"),而不是讲述("I just told it I needed a flight"是字幕不是消息,validate_script会拒绝这类明显形式)。改写而不是删掉:改写成可发送的形式保留这一轮;为绕开规则而删除节拍会破坏对话形状。bubblespeakermuse: Muse's reply in the agent's own voice: short, warm, action-forward, entity-bearing. "On it. Searching Delta flights." Authorship is carried by bubble color alone (white agent, #CBE5FF user), so its bubbles must read like the agent texting in this user's actual thread, not like a support desk. Take the tone from the persona in your standing context (SOUL.md, IDENTITY.md): contractions, first person, a little play where the moment invites it. "Sent! You two are on for 7." beats "Message delivered." Personality changes the wording, never the claims: a playful line still draws every specific from the fact sheet or the voiceover and adds no new deeds. The send test applies to Muse bubbles too: write one only if Muse could have sent it in the real thread, doing work, reporting a result, or asking one scoped question. Do not put the creator's narration or feelings in a Muse bubble. When the voiceover says "I don't even have to open my apps", that is the creator's sentiment; a Muse bubble saying "No need to open your apps" is an ad line, not a reply. Show the event instead ("Sam just texted: You free tonight?"). No em-dashes in any bubble or chip: this copy is burned into the frame, Muse copy rules bar them, andvalidate_scriptrejects them.bubblespeakermuse:Muse 的回复,用代理自己的声音:简短、温暖、面向行动、带实体。"On it. Searching Delta flights."。作者归属只靠气泡颜色区分(代理为白色,用户为 #CBE5FF),因此它的气泡必须读起来像代理在这个用户的真实会话里发消息,而不像客服台。语气取自常驻上下文中的人设(SOUL.md、IDENTITY.md):用缩写、第一人称,在时机允许时带一点俏皮。"Sent! You two are on for 7." 优于 "Message delivered."。个性只改变措辞,绝不改变论断:俏皮的句子仍然从事实清单或旁白提取每个具体信息,不添加新的行为。发送测试同样适用于 Muse 气泡:只有当 Muse 在真实会话中可能发出它——正在做事、报告结果或问一个范围明确的问题——才写它。不要把创作者的叙述或感受放进 Muse 气泡。当旁白说"I don't even have to open my apps"时,那是创作者的感受;Muse 气泡说"No need to open your apps"是广告词,不是回复。改为展示事件("Sam just texted: You free tonight?")。任何气泡或胶囊中都不用破折号:这些文案会烧进画面,Muse 文案规则禁止它们,validate_script也会拒绝。
【评论】"个性只改措辞、不改论断"与"广告词不是回复"共同构成对生成文案的约束:避免营销腔替代理言,也避免借角色之口添加未发生的行为。visual: a receipt/status card or a real image. Every visual stands alone: rounded corners and a drop shadow sitting directly on the video, with no white shell around it (double-framing reads as "weird white cards").visual:回执/状态卡片或真实图像。每个视觉都独立呈现:圆角与投影直接落在视频上,周围没有白色外壳(双重边框会被读成"奇怪的白色卡片")。html: your own card, rendered through the capture engine, and the only synthetic path; every detail on it needs source evidence. The body stays bare (the validator rejects any background, border, or shadow on it); the card surface lives on the component's root element, like./mm example's.padwrapper. Do not set body height or min-height, because the card's height comes from its content. Keep it under ~600px tall at 1240 wide (render_htmlrejects taller).html:你自己的卡片,经捕获引擎渲染,是唯一的合成路径;其上每个细节都需要来源证据。body 保持裸样式(校验器拒绝其上的任何背景、边框或阴影);卡片表面放在组件的根元素上,如./mm example的.pad包装。不要设置 body 的 height 或 min-height,因为卡片高度来自内容。在 1240 宽下保持高度低于约 600px(render_html拒绝更高的卡片)。image: a real image (user photo, image-gen output, screenshot).image:真实图像(用户照片、图像生成输出、截图)。- For image-backed visuals only,
"shell": trueopts a visual back into the white receipt shell, which is rarely right;"hero": trueimplies it and marks the story's final reveal (no special chrome — the tap carries the emphasis).
仅对以图像为背景的视觉,"shell": true可让视觉退回白色回执外壳,这很少是对的;"hero": true隐含它,并标记故事的最终揭晓(没有特殊装饰——强调由点按承载)。 "tap": trueplays the tap effect on this visual: the card lands in the thread, presses in like a fingertap, then the thread clears — older messages slide up and out — while the card grows and centers in the cleared stage, holds, and settles back into place. Use it for emphasis on hero items — the payoff proof, the screenshot that IS the moment, and the live browser-driving card (default the drive card to a tap) — not on routine status cards. Any shape works; tall grows the most: tap beats get an 1100px authoring budget (thread cards cap at 600px), and an 850-1100px card previews compact and roughly doubles on the tap, while a flat card enlarges modestly and sits spotlit. The beat needs at least 3 seconds and no other beat may start inside the tap window;validaterejects both."tap": true在该视觉上播放点按效果:卡片落入会话,像指尖点按一样压入,然后会话清空——较旧的消息向上滑出——同时卡片在清空的舞台上放大并居中,停留,再落回原位。用它强调主角项——作为回报的证明、本身就是那一刻的截图、以及实时浏览器驱动的卡片(驱动卡片默认带点按)——不要用在常规状态卡上。任何形状都可以;高的放大最多:点按节拍有 1100px 的创作预算(会话卡上限 600px),850-1100px 的卡片预览时紧凑、点按时约放大一倍,而扁平卡片放大有限并处于聚光灯下。该节拍至少需要 3 秒,且点按窗口内不得开始其他节拍;validate对两者都会拒绝。
browser: one journey of Muse driving a website, in a single beat, rendered as a browser window whose pages you rebuild.{"type": "browser", "address": "rei.com", "reference": ["<run>/webshot_00_www.rei.com.png"], "pages": [{"html": "<page 1>", "click": "#add-to-cart"}, {"html": "<page 2>"}], "start": 12.0, "end": 22.0}runs 5-20s and taps by default. Each page'shtmlis a simplified rebuild of a page the browser really drove, authored 1240px wide against a 640px viewport that cuts like a real browser fold. Name the real screenshots those pages rebuild from inreference, as one path or a list; the validator rejects a browser beat that names none or names a missing file. Give every page but the last aclickCSS selector naming the element the cursor presses to reach the next page; the renderer measures that element and lands the cursor on it. Give the beat at least two pages. Setaddressto the real site's domain, as one string or as a list matchingpagesone to one. Do not animate a page; the renderer draws the shell, the address bar, the cursor, the click ring, and the page swaps./opt/hatch/skills/magic-moment/guide/visuals.mdowns how to ground the pages in the browser's real captures and how to author them.browser:Muse 驱动一个网站的一段历程,在单个节拍内,渲染为一个由你重建页面的浏览器窗口。{"type": "browser", "address": "rei.com", "reference": ["<run>/webshot_00_www.rei.com.png"], "pages": [{"html": "<page 1>", "click": "#add-to-cart"}, {"html": "<page 2>"}], "start": 12.0, "end": 22.0}运行 5-20 秒并默认带点按。每页的html是浏览器真实驱动过的页面的简化重建,以 1240px 宽创作,对应一个像真实浏览器折叠线的 640px 视口。在reference中指明这些页面据以重建的真实截图,单个路径或列表;校验器会拒绝未指明或指明了缺失文件的 browser 节拍。除最后一页外,每页都要给一个clickCSS 选择器,指明光标为到达下一页而按压的元素;渲染器测量该元素并把光标落在其上。该节拍至少给两页。address设为真实站点的域名,单个字符串或与pages一一对应的列表。不要让页面自行动效;外壳、地址栏、光标、点击圆环与页面切换都由渲染器绘制。如何把页面锚定在浏览器的真实截图上以及如何创作它们,由/opt/hatch/skills/magic-moment/guide/visuals.md负责。video: a real video artifact plays inside the thread as a message (rounded and shadowed like photos; muted, because the creator's voiceover keeps the audio).{"type": "video", "video": "~/workspace/imagine_media/x.mp4", "start": 19.0, "end": 24.0}plays from itsstartfor up toend - startseconds, then holds its last frame while it rides up.video:真实视频工件作为消息在会话内播放(像照片一样圆角带阴影;静音,因为音频由创作者的旁白承担)。{"type": "video", "video": "~/workspace/imagine_media/x.mp4", "start": 19.0, "end": 24.0}从其start起播放至多end - start秒,随后在向上滑走时保持最后一帧。typing: the three bouncing dots in an agent bubble, meaning Muse is about to reply. End it exactly when the reply bubble starts and the renderer swaps them in place, iOS-style: the dots stop atend, then their slot eases closed over the 0.85-second depth glide while the reply enters over 0.30 seconds.{"type": "typing", "start": 1.9, "end": 3.1}typing:代理气泡中的三个跳动圆点,表示 Muse 即将回复。恰好在回复气泡开始时结束它,渲染器会原位替换,iOS 风格:圆点停在end,其槽位在 0.85 秒的纵深滑动中收拢,同时回复在 0.30 秒内进入。{"type": "typing", "start": 1.9, "end": 3.1}reaction: an emoji tapback that springs onto a bubble's corner with a plink sound.{"type": "reaction", "emoji": "❤️", "target": 0.6, "start": 1.6}, wheretargetis the start time of the bubble being reacted to. Land it at least 0.4s after that bubble pops, and only react to bubbles still on screen. The reaction always lands BEFORE the response: react to a message first, then let the reply pop — a tapback arriving after the next message reads out of order, because a real person reacts the moment they read, before they type. Real color emoji render (Noto Color Emoji ships in the toolkit's fonts). Use sparingly: one or two per video, on the user's message.reaction:一个带"啵"声弹进气泡角落的表情回应。{"type": "reaction", "emoji": "❤️", "target": 0.6, "start": 1.6},其中target是被回应气泡的开始时间。落点至少在该气泡弹出 0.4 秒之后,且只回应仍在屏幕上的气泡。表情回应总是落在回复之前:先回应消息,再让回复弹出——在下一条消息之后才到达的回应读起来顺序错乱,因为真人是一读到就回应,先于打字。真实彩色表情可以渲染(Noto Color Emoji 随工具包字体提供)。节制使用:每个视频一两个,用在用户的消息上。
Sounds are automatic and directional, iMessage-style: the user's bubbles get a bright rising "swip" (sent), Muse bubbles and receipt cards get a lower "ding" (received), reactions get a plink. Typing is silent. Emoji in bubble text render as real color emoji inline. Chip beats are retired: work-in-flight is depicted by cards (a driving card, a status row on the moment's component), never by a spinner pill in the thread.
音效是自动且定向的,iMessage 风格:用户气泡配明亮的上升"swip"(已发送),Muse 气泡与回执卡配较低的"ding"(已接收),表情回应配"啵"声。打字无音效。气泡文本中的 emoji 以真实彩色 emoji 行内渲染。胶囊节拍已弃用:进行中的工作由卡片表现(驱动卡片、当下组件上的状态行),绝不用会话里的转圈胶囊。
Grounding is an upgrade, not a gate. Before writing the screenplay, ask what real material exists: a screen recording of the thread, thread files, images. Anything real satisfies its beat first (crop it per /opt/hatch/skills/magic-moment/reference/card-spec.md) and is reported as provenance real; everything else renders synthetic. Missing material does not block the video.
真实素材是增强,不是门槛。写剧本之前,先问存在哪些真实素材:会话的屏幕录制、会话文件、图像。任何真实素材优先满足其节拍(按 /opt/hatch/skills/magic-moment/reference/card-spec.md 裁剪)并以来源 real 报告;其余一律合成渲染。素材缺失不阻塞视频。
【评论】"素材缺失不阻塞"配合逐节拍的 provenance 标注,是在合成视频流程中保留真实/合成的边界:允许补齐,但要求如实区分来源。