proof — 變更旅程的不剪輯片段
審查者應能觀看使用者實際看到的內容,以正常速度從頭到尾。影片就是證據;螢幕截圖、連絡表或加速錄影都不算。
各層級需求的真相來源:docs/process/PROOF-POLICY.yml(T0 無需;T1 專注的測試 + 型別檢查;T2 為變更旅程新增一個片段,桌面版,若佈局變更則加上手機版,使用虛擬心智世界;T3/T4 新增更多)。使用 node tools/proof/tier.mjs --base origin/main 取得當前 diff 的層級。
規則
- 不剪輯且即時播放。 一個瀏覽器上下文,一次拍攝。絕不修剪、拼接、加速或減慢。如果旅程長於政策中的
max_seconds,請錄製一個較短但仍能顯示變更的旅程,而非更快的版本。 - 拍攝從到達開始(預設):錄製在世界顯示在螢幕上且到達卡消失後開始,因此片段從世界開啟,而非載入。從那時起,它是一個不剪輯、即時播放的旅程,這是政策要求的。當載入或到達本身就是變更的對象(到達卡、載入畫面、首次繪製),或旅程不在世界頁面上(世界列表、登入)時,請傳入
--from-load:拍攝則從頁面建立開始。sidecar 中的start(arrival或load)說明使用了哪個,而upload.mjs --proof會將其複製到 proof.json 的media[]中。 - 在白天進行。 在本地世界 URL 上,錄製器在頁面開啟前將世界設定為中午並暫停時鐘(
--time-of-day 12,預設值;使用開發所有者權限的 god 環境路由)。開發世界在 06:00 啟動,並以每真實小時一個世界日的速度運行,因此若沒有此設定,一個運行 40 分鐘的世界將處於夜晚。設定它會在世界的神祇歷史中新增一個world條目,並且時鐘之後保持暫停。當時鐘、光線或神祇歷史本身就是變更的對象時,請傳入另一個小時數,或--time-of-day keep。sidecar 的worldTime說明設定了什麼。 - 真實輸入。 使用
page.mouse、page.keyboard、page.touchscreen或定位器點擊來驅動,就像使用者會做的那樣。不要在鏡頭前透過page.evaluate呼叫應用程式內部來讓事情發生。 - 虛擬心智,隔離資料。 僅限本地世界,
BRAIN_MODE=fake,使用全新的資料目錄。除非維護者要求,否則絕不對生產世界進行錄製,也絕不使用已登入的個人瀏覽器設定檔。 - 絕不提交影片(或影格)到儲存庫。在
.e2e/(已加入 gitignore)下工作。 - 片段必須顯示變更。在附加之前觀看它(或至少提取影格,如下所述)。
1. 啟動一個虛擬心智世界
需要已建置的 Web 用戶端(pnpm install --frozen-lockfile && pnpm build;如果 apps/web/dist/index.html 已存在且對應當前變更則可跳過)。在全新的 clone 中,建置的預建置檢查會失敗,直到 Web 媒體就緒:執行 node tools/assets/pull.mjs --only 'apps/web/**'(幾個檔案)就足夠了。
node tools/god-skill/dev-world.mjs proof 4710 --genesis worlds/templates/hello-hamlet.json --fresh --data .local/proof
# 輸出 http://127.0.0.1:4710/w/proof?dev=1 (pid N, log .lane/proof.log);伺服器繼續執行,已分離
首頁(世界列表、新世界)由世界啟動器提供,而非單一世界。對於從那裡開始的旅程,請執行啟動器(其世界總是獲得虛擬心智)並透過其 API 建立一個世界以供列表使用:
node tools/world-launcher/launcher.mjs --port 4720 --range 4721-4723 --data "$PWD/.local/proof-launcher" --eager 0 &
curl -s -X POST -H 'content-type: application/json' -H 'authorization: Bearer dev:proof' \
-d '{"name":"Proof Garden","template":"hello-hamlet"}' http://127.0.0.1:4720/__sim/worlds
# 針對 http://127.0.0.1:4720/ 錄製;透過其 pid 停止啟動器(它會停止其世界)
選擇包含變更介面的範本(worlds/templates/:hello-hamlet 小而快;eden-2026、mars-two 等較大)。使用空閒連接埠;e2e/config.mjs 列出了保留給其他測試工具的連接埠。完成後使用 kill <pid> 停止伺服器。
快速證明應用程式運作:本地冒煙測試
在錄製之前,或作為觸及伺服器或 Web 應用程式的 T1 變更的完整證明,請執行本地冒煙測試。它會啟動自己隔離的啟動器(虛擬心智、開發登入、資料位於運行目錄下)在一個空閒連接埠上,開啟世界列表和一個世界,檢查人物是否繪製且無控制台錯誤和失敗請求,然後停止它啟動的所有程序:
node e2e/local-smoke.mjs --port 9950 --clip # 在新的 clone 上先使用 --build;省略 --port 以使用任何空閒連接埠
# → .local/e2e/<timestamp>/summary.json, shots/1-world-list.png 2-world.png 3-person.png, clip.mp4
node tools/proof/upload.mjs --pr $N .local/e2e/<timestamp>/summary.json .local/e2e/<timestamp>/shots/*.png
summary.json 中的 status: "pass" 就是證據;其片段是一次不剪輯、即時播放的冒煙旅程。它不是您變更的片段:T2 變更仍會錄製自己的旅程,如下所述。詳細資訊:docs/testing/e2e.md#local-smoke。
2. 撰寫步驟
將 .claude/skills/proof/steps.example.mjs 複製到 .e2e/proof/<slug>/steps.mjs,並編寫變更旅程的腳本:執行動作,顯示結果。使用預設的到達起始,步驟從已到達的世界開始(arrived 標記為 0:00);使用 --from-load 時,它們在導航後立即開始。mark('label') 會將章節時間戳記到 sidecar 中,以便 PR 可以說「面板在 0:07 開啟」。保持專注於變更所影響的旅程。
3. 錄製
node .claude/skills/proof/record.mjs --url 'http://127.0.0.1:4710/w/proof?dev=1' \
--steps .e2e/proof/<slug>/steps.mjs --out .e2e/proof/<slug>/desktop.mp4 --viewport desktop
# 佈局或觸控變更了?也做:
node .claude/skills/proof/record.mjs --url '...' --steps .e2e/proof/<slug>/steps.mjs \
--out .e2e/proof/<slug>/phone.mp4 --viewport phone
- 瀏覽器:使用
PLAYWRIGHT_BROWSERS_PATH,若存在則使用/opt/pw-browsers。如果缺少 Chromium:PLAYWRIGHT_BROWSERS_PATH="$PWD/.e2e/browsers" pnpm --filter mars-campus-viewer exec playwright install chromium。 - Playwright 的解析方式與遊戲測試工具相同(
tools/playtest/lib/platform.mjs:全域的playwright,否則使用mars/viewer中的@playwright/test)。無需 GPU;WebGL 在 SwiftShader 上運行。 --max-seconds預設為政策的max_seconds。退出碼 3 表示旅程超過了限制:請縮短它。- 使用 ffmpeg:一個帶底部進度條和
hh:mm:ss / total時間碼的 mp4(時間碼需要以 drawtext 編譯的 ffmpeg;sidecar 的timecode表示是否存在)。沒有 ffmpeg:原始.webm;請在 PR 中說明(播放器仍會顯示自己的進度條)。 - 每個片段都會有一個
<clip>.jsonsidecar:url、viewport、duration、start、startedAt、marks(其中包括arrived)、worldTime、控制台錯誤、commit 和 dirty 標記。沒有page.screencast的 Playwright 即使沒有--from-load也會從頁面建立開始錄製;sidecar 在startFallback中說明。 - 頁面有
--arrival-seconds(預設 120)讓世界在拍攝開始前到達;從未顯示世界的頁面會失敗(退出碼 1),並提示使用--from-load、卡住處的靜止影像(<clip>.open-failed.png)和一個 sidecar。sidecar 中的控制台錯誤是發現結果:請解釋或修復它們。
運行時間遠低於 10 分鐘的前景限制。要一次執行多個片段,請在背景執行它們(nohup setsid node ... > .e2e/proof/<slug>/rec.log 2>&1 &)並輪詢日誌。
4. 自行檢查
for t in 1 4 8; do ffmpeg -loglevel error -y -ss $t -i .e2e/proof/<slug>/desktop.mp4 -frames:v 1 .e2e/proof/<slug>/t$t.png; done
查看影格(讀取 PNG)。變更的狀態必須可見,世界必須已載入(不是空白或載入中的畫布),且影格中不應有私人內容。如果未顯示變更,請修正步驟並重新錄製;不要附加無法證明斷言的片段。
5. 附加到 PR(絕不提交它)
在發布前使用 node tools/proof/publication.mjs <payload files> 檢查每個外送的文字/JSON 負載。分別檢查媒體像素、音訊、檔名和二進位元資料。設備路徑、主機名稱、設備/私人 IP、本地 URL 和操作識別符僅屬於私人記錄。使用相對路徑和中性的捕獲標籤。上傳器在上傳批次前會封鎖偵測到的文字洩漏;這無法保證媒體隱私。
node tools/proof/upload.mjs --pr $N --proof .e2e/journal/$ISSUE/proof.json .e2e/proof/$SLUG/desktop.mp4 .e2e/proof/$SLUG/phone.mp4
- 預設目標是 PR 自己的預發行版
proofs-<n>(在預設分支上的標籤,首次使用時建立);每個 PR 一個發行版可使每個發行版保持在 GitHub 的 1000 個資產限制內,舊的共用proofs發行版已達到該限制。--release <tag>(或SIM_PROOF_RELEASE)可指定另一個發行版。資產命名為pr-<n>.<file>,重新拍攝會得到-2、-3,因此先前的拍攝(包括失敗的)仍可連結。SIM_PROOF_UPLOAD_URL(+SIM_PROOF_UPLOAD_TOKEN)可切換到另一個端點,例如 CDN。 - 使用
--proof,片段和圖片連結會寫入 proof.json 的media[],其他檔案(日誌、JSON)寫入files[],並且 proof.json 最後上傳。將其連結和片段連結貼到 PR 的「證明」部分。journal技能涵蓋 proof.json。 - 行內預覽。 GitHub 將發行版資產作為下載提供,因此 mp4 連結永遠不會在 PR 中播放。對於每個影片,
upload.mjs會使用 ffmpeg 製作一個小型動畫 GIF(<clip>.preview.gif:從片段的arrived標記開始即時播放 6 秒,或對於--from-load拍攝從 0 開始;最多 5 MB),將其與片段一起上傳,記錄為media[].preview,並列印如[](…pr-412.desktop.mp4)的行。貼上該行:預覽會在 PR 中播放,點擊它會開啟完整片段。當變更在片段後期顯示時,請使用--preview-start <s>(和--preview-seconds <s>)選擇視窗。預覽是指標,而非證明:不剪輯的 mp4 才是。在每個預覽之前,務必放置一個明確的全解析度不剪輯影片連結,說明實際的編碼尺寸和持續時間。保留捕獲解析度並框取動作,以便可以審查接觸點。不要放大捕獲,或使用縮小的 GIF 來聲稱動作證明。 - 退出碼 3:上傳被封鎖(沒有
gh、未登入、沒有權限或沒有網路,如同許多雲端會話中的情況)。告知使用者哪些片段在等待以及位置。在 PR 中發布一條評論,包含每個片段的持續時間、視口、清理過的 sidecar 摘要(標記、控制台錯誤、commit),以及一個相對於儲存庫的record.mjs命令和步驟檔案,以便任何人都可以重新錄製。在 PR 主體中將證明標記為「等待上傳」。絕不將影片作為 base64 貼上,絕不提交它,也絕不將其推送到另一個分支。
gh pr 在雲端會話中可能無法運作;請使用 gh api repos/$REPO/issues/$N/comments -F [email protected]/proof/$SLUG/comment.md 發表評論。
在片段旁要寫什麼
每個片段一行:什麼旅程、哪個世界範本、視口、持續時間、commit,以及變更可見的時刻("0:07 座位高亮顯示且靈魂坐下")。加上任何控制台錯誤以及為什麼可以接受。
