teardown 是做什麼的?
一台機器,一個頁面。訪客看著機器運轉,按下按鈕,正在運作的那個零件就會打開,而周圍的儀表則顯示真實的數值。若無人操作,頁面會自動導覽,並顯示游標。相同的程式碼會逐幀錄製導覽過程,並在同一時間軸上建立配樂。
SKILL_DIR 指的是此檔案所在的資料夾(若是 Cursor 安裝,通常為 ~/.cursor/skills/teardown、~/.agents/skills/teardown 或專案內的 .cursor/skills/teardown)。建構和渲染的輸出會放在您執行指令所在資料夾的 ./teardown-out 中。
首次執行
cd "$SKILL_DIR" && npm install && npx playwright install chromium
頁面不需要其他東西。影片需要 ffmpeg 在 PATH 環境變數中,配樂需要 Python 3(僅需標準函式庫)。設定 FFMPEG、PYTHON 或 CHROMIUM 環境變數可指向其他執行檔。
3 個範例
建構其中一個,開啟 HTML,按下按鈕。當您開始製作新機器時,複製最接近的那個。
| 機器 | 驅動方式 | 值得參考的部分 | |
|---|---|---|---|
v8-* | 帶有玻璃汽缸襯套的十字曲軸 V8:您所追蹤的汽缸內燃燒、氣門在凸輪上升起、8 個活塞透過精確的滑塊-曲柄機構運動。 | 曲軸角度。點火順序 1-8-4-3-6-5-7-2,用於指示圖的燃燒室壓力模型。 | 剖面模式(玻璃、剖面、實心)、作為汽缸選擇器的點火順序、與曲軸同步的即時圖表。 |
cessna-* | Cessna 337 主起落架,在起飛時折疊進機身,降落時放下。 | 行程 0..1:一個 201 mm 的齒條帶動一個 32 齒的小齒輪(模數 4 mm)精確旋轉 180°。 | 單一自由度驅動整個機構、作為時間函數的飛行階段、在模型下方滾動的跑道。 |
strandbeest-* | Theo Jansen 的 Strandbeest,20 條腿安裝在同一根曲軸上,在沙地上橫向行走。 | 風力。曲軸速度由風力積分而來;每個關節透過 Jansen 的 11 個連桿長度進行圓相交計算來決定位置。 | 僅 67 行程式碼的精確連桿解算器、實例化的管狀零件、腳印軌跡、所有 20 隻腳的接觸圖表。 |
每個範例包含 3 個資料夾:<名稱>-shared/ 存放機器本身,<名稱>-film/ 存放錄製的導覽,<名稱>-live/ 存放您操作的頁面。
node "$SKILL_DIR/sims/build.mjs" v8-live # → teardown-out/dist/v8-live.html,離線,約 800 KB
node "$SKILL_DIR/sims/build.mjs" # 全部 6 個
如何建構一台機器
每個機器一個資料夾,按職責拆分。範例完全遵循此結構:
| 檔案 | 職責 |
|---|---|
kin.js / gear.js / jansen.js | 數學運算。使用真實尺寸和單位,一個輸入(曲軸角度、行程、風力)以及作為其函數的每個姿勢和讀數。此處不使用 three.js。 |
engine.js / gear.js / beast.js | 幾何圖形,透過程式碼建構:車削零件、管狀結構、實例化連桿。update(input) 設定其姿勢。 |
stage.js | 場景、燈光、陰影、後製(GTAO、Bloom)。frame(state, cam) 繪製一幀。 |
console.js | 覆蓋層:固定 1600 x 900 CSS 版面的面板、圖表、切換開關、帶有引導線的標註。update(state) 根據該幀的狀態重新繪製它。 |
film.js | 一個操作者動作的 SCRIPT(視角、隔離、剖面、油門)以及作為時間純函數的 render(t)。 |
live.js | 即時狀態下的相同機器:按鈕、滑桿、軌道、縮放、按鍵。若無輸入,它會播放 film.js 的腳本作為帶有游標的自動導覽;任何點擊、拖曳、滾輪或按鍵都會將控制權交給使用者,25 秒無操作後交還。 |
score.py | 影片時間軸上的聲音。 |
對於 SKILL_DIR 之外的機器,建立一個包含 main.js 的資料夾,並透過名稱導入工具包:import {runSim} from 'teardown-kit/runtime.js'。使用 node "$SKILL_DIR/sims/build.mjs" ./my-machine 建構它。
決定成敗的規則
- 從參考資料開始,而非憑空想像。 一張照片、一本手冊中的剖面圖、一段真實機器的影片。以其為標準進行建構,並逐幀比較。憑一句話想像出來的機器總是平庸的;而同一個模型在有明確比對目標時,才能精確重建出真實的機器。
- 真實數值,單一來源。 首先取得真實尺寸(缸徑、衝程、齒數、連桿長度)。每個儀表、圖表和標籤都讀取自同一個用來設定零件姿勢的函數,因此螢幕上沒有任何東西會與運動不一致。用手工驗證一次:在 Cessna 中,32 齒 × 4 mm 模數的齒輪節圓直徑為 128 mm,其半圓周長即為 201 mm 的行程。
- 一個輸入驅動一切。 根據曲軸角度、行程或風力設定姿勢。沒有任何部分是手動設定關鍵影格的,因此影片、即時頁面和配樂永遠不會不同步。
- 正在運作的那個零件會打開。 將其隔離、剖開、X 光透視。一個能進入內部觀察的攝影機,比十個外部軌道鏡頭更有價值。
- 燈光場景,一個重點色。 淺色背景在資訊流中容易閱讀;深色背景則會流失第一秒的注意力。重點色標示正在變化的東西。注意淺色背景上的近白色零件:為它們加上墨線或較深的地面。
- 為機構選擇攝影機。 正面觀看平面連桿機構會使其看起來像一個網格;從 3/4 角度沿著其主軸觀看,則能讓每條腿都清晰可辨。
- 用數字修復碰撞。 當一個零件穿入另一個零件時,在參數區塊中縮放或移動它。切勿用攝影機將其隱藏。
即時頁面規則
- 在整合式 GPU 上測試。 在桌機顯示卡上能以 60 fps 運行的頁面,在筆電內建 GPU 上可能會降至 15 fps。範例中包含了品質等級(
live.js中的TIERS):無環境光遮蔽、較低的像素比例、較小的陰影貼圖、每隔一幀重新繪製面板。首先找出最昂貴的著色器:每個頂點計算 5 次的置換,成本比所有等級加起來還高。 - 預熱著色器。
kit/warm.js在第一幀之前並行編譯所有程式,因此頁面永遠不會在首次繪製時凍結。 - 保持頁面小於 1 MB。 連結爬蟲(X、Slack)會從 HTML 讀取分享卡片,並放棄超過 15 MB 的頁面。大型模型會作為頁面旁邊的檔案輸出:
build.json→{"external": {"heavy.glb": "light.glb"}}。
render(t) 合約
import {runSim} from '../kit/runtime.js';
const DEFAULTS = {duration: 20, fps: 60, hud: {enabled: false}, director: {enabled: false}};
runSim(DEFAULTS, async ({cfg, canvas, capture}) => {
// 建立場景,預計算任何有狀態的內容
return {render(t) { /* 從頭開始繪製第 t 幀 */ return {}; }, resize() {}};
});
render(t)是純函數:沒有Math.random(在設定時使用kit/util.js的rng(seed)),沒有Date,沒有在幀之間攜帶狀態。capture會在不定的時間呼叫它。- 任何依自身規則運動的東西(代理、粒子)都在載入時模擬一次,存入型別陣列;
render只讀取它們。隨時間積分的量(Strandbeest 在變化風力下的曲軸)會以封閉形式積分或在載入時製成表格。 - 每個節拍都存入
<名稱>-film/timeline.json,單位為秒。配樂也讀取相同的檔案。 - 第 0 幀是縮圖。它必須已經顯示機器。
指令
node "$SKILL_DIR/sims/build.mjs" v8-film
node "$SKILL_DIR/sims/capture.mjs" v8-film --size 1600x900 --dpr 1.2 --stills beats --cols 4 --out teardown-out/v8-beats.png
node "$SKILL_DIR/sims/capture.mjs" v8-film --size 1600x900 --dpr 1.2 --still 12.5 --out teardown-out/v8-12.5.png
node "$SKILL_DIR/sims/capture.mjs" v8-film --size 1600x900 --dpr 1.2 --out teardown-out/v8-silent.mp4
python "$SKILL_DIR/run_sfx.py" "$SKILL_DIR/sims/v8-shared/score.py"
ffmpeg -y -i teardown-out/v8-silent.mp4 -i teardown-out/v8-score.wav -map 0:v -map 1:a -c:v copy -c:a aac -b:a 256k -shortest -movflags +faststart teardown-out/v8.mp4
node "$SKILL_DIR/wave.mjs" teardown-out/v8.mp4 --timeline "$SKILL_DIR/sims/v8-film/timeline.json"
--size 是 CSS 版面尺寸,--dpr 是其倍數:1600 x 900 在 1.2 倍下會得到 1920 x 1080。Capture 會列印它使用的 GPU;若出現軟體渲染器,表示 paths.mjs 中的 GPU 旗標未生效。
審查流程
您無法觀看影片;但您可以查看幀畫面。在閱讀當前渲染結果的畫面之前,絕不應報告機器已完成。
--stills beats:時間軸上每個節拍後 0.3 秒處的一格畫面,每個標示t=。- 根據下方檢查清單閱讀每一格畫面。
- 放大:對一個轉場使用
--stills every:0.2 --from A --to B,對一個完整幀使用--still T。 - 修正、重建、返回步驟 1。當完整檢查一輪後找不到任何問題時停止。
- 最終:
node "$SKILL_DIR/sheet.mjs" --video <最終.mp4> --timeline <timeline.json>,然後也閱讀該輸出:它會顯示編碼器寫入了什麼。
檢查清單:
- 機構。 每個應該接觸的零件都接觸到了;沒有任何東西穿過另一個物體;面板上的數值與姿勢匹配。
- 構圖。 每幀一個焦點。沒有重要物體距離邊緣 60 px 以內。標籤從不遮蓋它們所命名的零件。
- 文字。 沒有被裁切。關鍵讀數在手機寬度下也能讀取。
- 3D。 Bloom 會吞噬細節:保持自發光圖層低於 1.0。近白色表面會過曝。低光源下的光滑金屬會產生條紋:大表面的粗糙度應 ≥ 0.45,envMapIntensity 應 ≈ 0.5。
- 時機。 每個狀態停留時間足夠閱讀:一個短語 1 秒,超過三個字後每多一個字加 0.25 秒。
範例已經踩過的 three.js 陷阱
- 在
a > b的情況下,smoothstep(a, b, x)在 Windows(ANGLE/D3D)上會回傳 0。請改寫為1.0 - smoothstep(b, a, x)。 - 一個
ShadowMaterial或一個位於地板上方不到一毫米的貼花,在後處理渲染目標內部會失去深度測試。請為其加上polygonOffset。 - 一個使用鏡像映射、平放在地面上的四邊形,會被作為背面剔除。請使用
DoubleSide。 - 一個 NaN 像素值會透過 Bloom 使整個幀變黑。請在著色器中箝制每個
pow()的底數。 - 對已裁剪(剖面)模型使用 GTAO 時,需要在
gtao.normalMaterial上設定相同的裁剪平面,否則裁剪掉的一半會以蒼白的鬼影形式出現。
聲音
sfx/ 使用純 Python(48 kHz,可重現)合成所有聲音,並讀取影片的時間軸,因此聲音永遠不會與畫面不同步。python "$SKILL_DIR/run_sfx.py" --list 會顯示產生器。sample() 會加入 sfx/samples/ 中錄製的 CC0 聲音。sfx/score_kit.py 包含了三個配樂所使用的背景和重點音效:和弦背景、每個動力行程的輕柔重擊聲、馬達聲、空氣阻力聲。
- 每個時間值都來自時間軸,絕不來自手動輸入的數字。
- 母帶處理是自動的:−14 LUFS,真實峰值 ≤ −2 dBFS。
- 重點音效,而非旁白:連續運動時保持無聲。每個音調聲音只對應一個關鍵事件。
品味,以數字量化
- 緩動:進入時用
cubic-bezier(0.16, 1, 0.3, 1),退出時用cubic-bezier(0.6, 0, 0.9, 0.35),持續時間為進入時的一半,狀態間轉換用cubic-bezier(0.65, 0, 0.35, 1)。僅在連續運動時使用線性緩動。 - 機器應佔據視埠的 60-70%。在 1080 畫布上,標籤絕不小於 34 px。
- 一個重點色,並且它必須有意義:代表「現在正在運作的零件」。
- 沒有任何東西是完全靜止的:總有一個元素是動態的。
致謝
Cursor 移植版本由 bugeng777/teardown-cursor 基於 elvarofficial/teardown(MIT 授權)製作。工具包、幀捕捉、音效引擎和審查規則來自 whaleyxbt/claude-motion(MIT 授權)。錄製的樣本為來自 Freesound 的 CC0 授權聲音,由 Barty-Bart/motion-graphics(MIT 授權)裁剪;來源記錄於 sfx/samples/SOURCES.md。