1仕組みと動作原理
全編を通じた唯一の鉄則:残り時間は必ず権威あるクロックから算出し、state に格納した残り時間をデクリメントすることは固く禁じます。
Never store remaining time in state and decrement it. Recompute it every frame.
setInterval がドリフトするメカニズム上の理由を解説します(コールバックのトリガーが少なくとも 1000ms 遅れ、タブの間引きやレンダリングフレームの遅延時にその遅れが累積するため)。
A `setInterval(fn, 1000)` countdown **drifts** — the callback fires *at least* 1000 ms later, never exactly, and stacks up when the tab is throttled or a render frame is slow.
Remotion 側で信頼できるクロックは useCurrentFrame() です。第 N フレームは厳密に N/fps 秒と等しいため、「フレーム精度」が構造的に成り立ちます。
`useCurrentFrame()` is deterministic: frame N is always rendered at exactly `N/fps` seconds.
端数処理には ceil を用いて 0 にクランプし、「1」が丸々 1 秒間表示され、最終フレームが正確に 00:00 で止まるようにします。
const remaining = Math.max(0, Math.ceil(durationInSeconds - elapsed));
再生時間は定数ではなく prop とし、durationInFrames と連動させることで、カウントダウンが 00:00 に到達した後に余分なフレームが残らないようにします。
Set the composition's `durationInFrames` to `durationInSeconds * fps` so the video is exactly as long as the countdown.
Web(非レンダリング)環境の実行パスでは rAF を用いて performance.now() を駆動させ、表示秒が変化した瞬間にのみ再描画します。
if (remaining !== lastShown) { render(remaining); lastShown = remaining; }
フォントレイヤーには tabular-nums を適用して等幅化し、数字の変動によるレイアウトのズレを防止します。
Always set `font-variant-numeric: tabular-nums` (or a monospace digit font) so digits keep equal width and the layout does not jitter as numbers change.
数字アニメーションの原則は「テキスト内容ではなく位置を動かす」ことであり、CSS transition ではなくフレーム駆動で行う必要があります。
Animate the *position*, not the text content, so the change is a glide, not a pop.
配信や常駐画面では背景のシームレスなループが求められます。動きの周期がタイムラインを割り切るようにし、frame % loopFrames で参照します。
read it as `frame % loopFrames` so there is no visible seam at the wrap
検収条件は数式として定義されます:第 N フレームの表示値は必ず ceil(duration − N/fps) と一致し、最終フレームは 00:00 とならなければなりません。
at frame N, displayed = ceil(durationInSeconds - N/fps) (e.g. 300s @ 30fps, frame 4500 → 150s → "02:30")
reference ではこれらの規約を実行可能なコンポーネントへと落とし込み、固定日時へ向けた Web 向けバリアント(フレームごとの target − Date.now() 再計算 + タイムゾーン処理)も提供しています。
Register it so `durationInFrames` matches the countdown exactly — the video ends when the timer does:
2コア機能
3外部依存関係
| Kind | Dependency / Endpoint |
|---|---|
| cli | npx remotion still / render / compositions |
| cli | scripts/contact-sheet.sh + scripts/probe-mp4.sh(仓库根共享验证工具箱,SKILL.md 明列) |
| package | ffmpeg / ffprobe(系统依赖) |
| cli | npx -y playwright screenshot(scripts/seek-shot.sh,仓库级 Light tier 通用件) |
4推奨統制措置と判定根拠
- scripts/: ローカルスクリプトまたはコマンドの実行: (SKILL.md に明記された検証ループ)`npx remotion still Countdown out/f-*.png --frame=N --pro
- 网络端点: 外部ネットワークアクセスの宣言: (任意、通常は不使用)`scripts/seek-shot.sh` が `npx -y playwright screenshot` を実行:初回のみ npm から取得
- 本地工作目录: ファイル入出力および成果物の生成: エージェントが本スキルに基づき構築する Remotion プロジェクト、ならびに out/countdown.mp4、out/demo.gif
5二重目の独立検証
- [ok] 「禁止 setInterval 递减」是否在 reference 的示例实现里也真的遵守(防第一遍只信 SKILL.md 的说法) — reference 全量 grep:countdown-cookbook.md 组件 `const frame = useCurrentFrame();`,注册处 `durationInFrames={DURATION_SECONDS * FPS}`;到日期的 web 组件 `Math.max(0, target - Date.now())` 每帧重算并自述 "never a decrement";digit-flip.md 两个组件同样 `const frame = useCurrentFrame();`。setInterval 全 skill 仅作反例出现 1 次。
- [ok] 「倒计时结束时视频正好结束」是否真被约束住 — SKILL.md 明写 `durationInFrames` = `durationInSeconds * fps`;cookbook 给出 "Register it so `durationInFrames` matches the countdown exactly — the video ends when the timer does:" 的注册片段;收尾清单第 2 条把它列为验收项。文档、示例、验收三者一致,不是单向声明。
- [unlocatable] 「每分钟约丢 1 秒」的漂移量级是否有据 — SKILL.md 写 "Reports of ~1 second lost per minute are common.",但仓库内无基准脚本、无引用、无数据文件;本次为只读侦查且不运行计时实验,故不验证该数值,仅记为文档声明,不在结论中当已核事实使用。
- [ok] seek-shot.sh(Light tier)对本 skill 是否可用(防「Packaged helper」被读者误解为三条脚本都属本 skill) — scripts/README.md 把 seek-shot 归为「standalone HTML + `?t=N` harness」的 Light tier;本 skill 交付是 Remotion 组件(Heavy tier),SKILL.md 的 Packaged helper 行只点名 contact-sheet 与 probe-mp4。不存在悬空引用,也不存在被夸大的能力。
- [ok] 三仓库共享脚本是否同内容(跨包一致性) — md5 逐字节比对:contact-sheet.sh b7dcc28034c9bf8fda5d48683e54d8b5、probe-mp4.sh 167b7977c50f492014e1a6fef8c9ed12、seek-shot.sh 24a4130cd1e64874154429b0b4af12da,iart-tiktok / iart-text-message / iart-youtube 三包完全相同。
- [ok] SKILL.md 是否声称了 reference 未兑现的能力(防文档夸大) — SKILL.md 承诺的四件事——可配置 duration、mm:ss 与 dd:hh:mm:ss、drift-free 计时、无缝循环背景——在 countdown-cookbook.md 与 digit-flip.md 里各有对应实现(§1 组件、§2 到日期、§4 循环、digit-flip 两组件),逐项可定位,无空头承诺。
6結論
2730e6053383cccb…