Imported from tim099/-Bar (
.claude/skills/ucl-stream-watch/SKILL.md). Install upstream withnpx skills add tim099/-Bar --skill ucl-stream-watch. Copyright stays with the author.
UCL Stream Watch — 觀影模式
觸發詞就是命令。 本 skill 只教第一步 —— 之後每一步的回傳檔都會指路(
## next)。 細節不寫在這裡:寫進 skill 的數字會過期而不會叫,一律讀回傳檔的當下讀數。
兩條鐵律
- 收工不由你判斷。 沒有
step=end;下一次cycle自己宣布並結算 —— 到期看的是實錄前緣蓋過 ends_at(牆鐘過了尾段沒補完會自動加班取材),中斷(Tim 停錄影)立即結算。 看到「我大概看完了」這個念頭 —— 那不是收工訊號,去跑cycle。 ⚠ 「我對 Tim 交付完一份報告了」也不是收工訊號,而它比前者難認 —— 它看起來像盡責。 🩸 2026-08-16 summit:發完第一則 observe 就轉去 chat 寫報告,30 分鐘沒跑 cycle, 整場到期收工,cycles=1、正片零格。回傳檔的## next第 1 行就寫著「繼續:cycle」,我讀過。 ⇒ 場次進行中回 chat:要嘛不回,要嘛回完立刻cycle,不要留在報告裡。 - 媒材鍵是共享鍵,不能由記憶供給。 不確定就讓 Cmd blocked 給你既有清單, 片名不確定問 Tim,不要猜。取錯名 ⇒ 既有 reader 的心得對新場次永遠隱形且不報錯。
第 0 步 —— 主觀影者的準備階段(Tim 2026-08-17 拍板;陪同者在這之後才進場)
senate ucmd run StreamWatch --persona <P> --arg step=prepare --arg title=<片名> --arg episode=<第幾集> [--arg media_id=<既有媒材 id>] [--arg reference_reader=<persona>] [--arg catchup_map="0001=summit,0002=gura"] [--arg start_recording=false]
它一次把開場前該定的東西全部定死,然後發一則「準備完成」公告叫陪同者進場:
| 做什麼 | 為什麼 |
|---|---|
| 媒材 id 查既有、不發明 | 命中 1 筆才用;0 筆要 --arg media_id= 明示(新作品先走 Cmd_Library op=media_init);≥2 筆停下來列清單 —— 猜一個等於替 Tim 選了平行宇宙 |
| 列出心得庫現況(誰已寫過哪幾章) | 這就是防漂移的那一眼;本場章號已有心得 ⇒ 提醒「這是重看?要開 r2」 |
| 定 reference_reader(接續基準) | 給陪同者追進度用;未指定=取章數最多者,並列時停下來要人挑 |
| 產 補課地圖(第 1..N-1 話各由誰的心得補) | 預設取基準者自己的;他缺的那幾集由主觀影者指定用誰的(--arg catchup_map=),沒指定就列出候選並擋下 |
收工自動匯出成章(填 chapter_title 才啟用) |
章名必須親筆 ⇒ 開場前就定死;沒填=不自動匯出(回傳檔照舊印手動指令)。旋鈕:export_chapter / export_work_title / auto_export=false。章 ≠ 場由 --from-session 併區間處理(主場 ∪ 陪同場 ∪ 同章舊場次),只有 primary 觸發 |
| 先填節目名,再開錄影 | stream_title 是開播公告「📺 本場節目」的唯一來源;反序的話公告已送出、標題追不回(公告不可 amend)。已在錄就不動作 |
落 StreamWatch/prepared/<media_id>.json |
join / catchup 都讀這份 ⇒ 陪同者一進場,媒材與章號已經是定值 |
prepare 可重入 —— 補課地圖缺來源時就帶著 catchup_map 重跑一次。
bilibili:先用工具取資訊,再自己填(Tim 2026-08-26/27 拍板)
Tim 給的網址原樣貼進工具,不要自己抄 BV、不要自己打標題。
python <UCL_Core>/Tools~/AgentCommands/bili_meta.py "<原樣貼上 Tim 給的網址>"
- 吃
BV號、完整網址、b23.tv短連結、av號網址(後兩者會跟隨 redirect/改走aid=)。 - ⚠ 一定要加引號。 B 站網址常帶
?spm_id_from=…&vd_source=…, 不加引號時&會被 shell 切開 —— 🩸 實測那次仍然印出正確結果(因為 BV 剛好在&前面), ⇒ 它是「看起來成功」的那種錯:沒出事的原因不是寫法對,是這次的字串剛好切在安全的位置。 - ⛔ 工具只取資訊:不寫檔、不代填、不生 slug(Tim 2026-08-26 拍板:填資訊仍由主觀影者負責)。
它印的 prepare 指令是草稿,
<…>要人自己填。 - ⭐ 為什麼值得先跑它:
up 主+mid是 work 身分的來源, 而工具會列出既有bilibili-*work ⇒ 命中就用,不要生第二個(鍵漂移血證:BUG-39)。
bilibili 的集數怎麼給(Tim 2026-08-27 拍板)
同一個頻道底下的影片多半彼此無關 ⇒ --arg episode= 直接用目前集數 +1,
章號只是索引不是進度。
| 情況 | 怎麼給 episode | 對陪同者的影響 |
|---|---|---|
| 各集獨立(多數) | 目前最大章號 +1(=章數 +1,見下方不變式) | 不需要 catchup,直接 step=join |
| 有關連性 | 依影片標題自己標的集數(這類片名通常會寫「第 N 期/(N)」) | 照常補課 |
⇒ 判準:看片名有沒有自己標集數 —— 有就照它,沒有就是獨立集、往下加一號。
🩸 「目前集數」要看哪一份清單?(2026-08-27 血證,summit) 我開場前查了
Library/media/<media>/readers/*/chapters/—— 0 筆 ⇒ 判「這支接0001」。 而實錄書那邊早就有001(Books/watch-<work>/001.txt,08-24 另一支影片)。 ⇒ 收工自動匯出把「同章號的舊場次」一起併區間重匯(那設計本來是為「一話跨數場」), 於是兩支完全不同的影片被併成同一章,連別人那天看另一支片的觀察都被吞進來。 ⚠ 而它的失效樣子是「匯出成功、26 筆、633 行」——所有讀數都健康,內容卻多了一支片。⇒ 章號的權威清單是實錄章(
Books/watch-<work>/NNN.txt),不是 reader 的chapters/。 兩份不同步時取兩者最大再 +1;不確定就先列出來看,別只查一邊。
⛔ 不變式:章號必須連續,不准有洞(Tim 2026-08-27 拍板:「最大章號應該要等於目前章數」)。 ⇒
1,2,3,4⇒ 最大 = 4 = 章數 ⇒ 「最大 +1」與「章數 +1」永遠是同一個答案。 📌 這條不變式是那兩句話得以互換的前提,而它此前沒有寫在任何地方 —— 一旦出現跳號(例:只有001、003),兩句話會給出不同的號,而其中一句會再撞一次號。 ⇒ 給號前先確認實錄章沒有洞;有洞先補洞,不要繞過它往上加。 📌 形狀:我查了一個索引,而合併用的是另一個 —— 跟 BUG-39(寫入端與讀取端用不同鍵)同族。 ⚠ 而「獨立集」要在公告與心得裡講出來,否則下一個人看到章號會以為那是進度 (🩸 已發生過:同一個 media 的書籤指向另一支影片的進度,而它在 catchup 的「接續點」欄裡 長得跟本章的進度一模一樣)。
⭐ 收工自動匯出(Tim 2026-08-19 拍板):
prepare帶--arg chapter_title="<章名>"⇒ 主觀影者收工結算時自動把實錄匯出成章(Editor 端就地直呼SCP_WatchWriter, TASK-0143 起不再 spawn python),並回填台帳一筆record_type=export事件。 ⚠ 沒填章名照樣會跑 —— 章名用哨兵值出書(TASK-0064): 「書不存在」跟「這一話沒人看」在產物上同形,而哨兵是看得見的缺。章名仍不由工具代取。 手動重出/查哨兵章走senate cmd watch --arg op=export|untitled。 回傳檔的## 實錄匯出(自動)段落原樣印回讀行(行數/字元數/實錄段數),失敗會明說章沒進書。
陪同者:一份檔案讀完就接上(形狀抄早安 brief)
senate ucmd run StreamWatch --persona <me> --arg step=catchup --arg media_id=<prepare 公告裡那個 id>
- 只要給自己的 persona ⇒ 自動算出我缺哪幾集,並把那幾集別人親筆心得的全文收進一份檔
(
letters/<me>/cmd/streamwatch_catchup.md),末尾附基準者的接續點與當前看法。 - 缺的來源若沒指定/該 reader 其實沒那章 ⇒ 逐條寫明,不靜默跳過 (「這集沒人寫過」與「我沒撈到」必須長得不一樣)。
- 讀完再
step=join。⚠ 補課讀到的是他們看到的,不是我看到的 —— 自己的心得要寫自己的觀察。
⛔ 沒有準備檔時
step=join會被擋下,並指名要主觀影者去跑 prepare。 理由:進場時若 media_id/章號還沒定,每個人各自打字就會長出兩個平行宇宙, 而兩邊都能寫心得、都不報錯。
第一步(唯一要背的一步)
直播沒開 ⇒ 主觀影者自己用 Cmd 開,不要請 Tim 去按按鈕(Tim 2026-08-16 拍板)
senate ucmd run StreamWatch --persona <P> --arg step=capture --arg on=1
- 沒在錄就沒有畫面可看 —— 與 Editor 頁那顆按鈕同一條規則、同一段邏輯(
ApplyEnabledInto)。 - 這是 primary 的責任:
step=join的陪看者不要開關錄影 (那是全域狀態,替別人開關錄影跟替別人下線是同一種越界)。 - 冪等,可以無腦先跑:已經在錄時回傳檔會說「已經是『錄影中』—— 未動作」,不會重複啟動、不會多發公告。 ⇒ 不確定有沒有在錄 ⇒ 直接跑,不要先去猜、也不要去問。
- 回傳檔一律寫完再讀一次
_config.json才報enabled—— 報的是回讀值,不是寫入的回傳值。
🩸 2026-08-16:Tim 說「一起看」時錄影是關的。當時的 skill 只把
capture寫成「開/關錄影」的 中性選項,沒說誰該開,於是它變成一個「可能要請 Tim 按一下」的模糊格。 模糊的責任歸屬在多人流程裡會停在最不該停的地方 —— 開場前。
只看一眼(不開場/不記帳/不發文,也是管線測試探針):
senate ucmd run StreamWatch --persona <me> --arg step=peek --arg seconds=60
正式開場(要寫評論、要計酬、要留接續點):
senate ucmd run StreamWatch --persona <me> \
--arg step=start --arg until=<HH:mm> --arg media=<work-slug>
- 跑完 Read run_cmd 印出的
📄 回傳檔:<路徑>—— 裡面的## next就是後續每一步 (cycle→ Read 縮圖牆/字幕 →observe→ …)。照它走,不用背。 media不給 ⇒ Cmd 會擋下並列出既有 work 清單(命中就用)。 bilibili 一律bilibili-<up主 slug>+--arg up=<up主名>。- 陪別人的場:
--arg step=join(自動繼承 primary 的媒材身分)。 - 長內文一律
--arg-file body=<檔>,不要 inline。
陪看=互相補格,同場的人講的話要讀、要回(Tim 2026-08-16)
「設計的目的就是互相補足觀影的細節,所以一定要讀酒館訊息。」
- 每輪素材只有十幾格,同場的人取到的窗口跟你不一樣 —— 他看到的正是你沒看到的那半邊。
- ⇒ 寫評論前先看 sidecar 的酒館段(已排除自己);有人講了東西就在評論裡回他, 指名哪一格、他補了什麼、有沒有推翻你已經寫下的。
- 被推翻要當場認,不要偷偷改前一則 —— 前面的貼文留著對照,那是實錄的價值。
🩸 2026-08-16 summit,同一天連兩場沒讀到同場的 basecamp: ① sidecar 酒館段游標從沒設過(
已讀 seq≤-1)⇒ 從全庫最舊開始列,把即時發言擠出額度; ② 修完①之後,游標在「0 筆未讀」時仍前進 ⇒ 跳過了她後來發言的區間。 兩次的共同點:那一段看起來都很正常(①一直有內容、②整段不見而我替它編了個無害的理由)。 ⇒ 讀不到同場的人時,先懷疑通道,不要當成「他今天沒講話」 —— 兩者在回傳檔上同形,而後者是預設會被相信的那個。 通道壞著也不是不讀的理由:直接翻ChatTavern/rooms/<room>/messages/也要讀到。
回傳檔要會讀的四行(判準,不是裝飾)
- 窗口對帳
窗口尾端 X ≤ 水位 Y ✅—— 出現>或「未夾」時, 尾端那幾格的「沒有字幕」不可信(沒字幕與還沒辨識同形)。 - 進度檔位(2026-08-25 起)—— 本輪窗口長度由 Cmd 依「可讀落後量」自動選檔
(預設:追進度/維持進度/放慢細看;後台 List 可配、相鄰輪保證重疊、可播放量不足就地等水位)。
窗口與等待都是 Cmd 的職責 —— 不要自己算窗口、不要自己 sleep;
primary 與 companion 的 cycle 都套,熱點觀看(
step=claim)不套。 - 接力(同日起)—— 除熱點外觀看區段一律接力:同場全員(含 primary)共用一條前緣, 誰的 cycle 先回來誰拿下一段(交接自帶重疊)⇒ 個人有洞是設計,不是漏看; 主線覆蓋看回傳檔「接力」行的前緣落後即時 Ns 那個數。別人已看過的段不會再發給你。 全場同時只有一個主觀影者(step=start 有硬守衛,撞到就走 catchup→join)—— primary 的差異只在職責:場次設定與熱點標記;取材上全員平等。
- STT
0 段≠無 cache≠ 這一行不存在(第三種是管線沒跑起來)。 - 保存期 名目只是設定值換算,實有才是現在真的回得去多久(兩個方向都會差)。
觀察標頭是 Cmd 自動帶的(2026-08-26 起,TASK-0060)
step=observe 會自己把標頭組在你的正文前面:段序、段起訖、格數/每格秒數、檔位、窗口對帳、餘裕。
⇒ 不要再手抄 cycle 回傳檔的那幾個數字(手抄會漂、會漏帶,而漏帶的那則跟「這段沒素材」同形)。
觀察 #N的 N 是全場共用的段序(接力發號),不是你個人的第幾輪 —— 所以跨人引用「#7」指的是同一段。個人輪數仍在回傳檔的cycles=,兩者不共用「#N」寫法。- 標頭只帶事實,判讀還是你的事:夾子沒生效時標頭印 ⚠ 而不是 ✅, 但「那幾格的沒字幕要怎麼處理」得你自己寫進正文。看到 ⚠ 不要當它是裝飾。
- 段序、段起訖與「這段的觀察是哪幾則 seq」都落在
StreamWatch/segments.jsonl(append-only), 匯出成書時照那份表排序 —— 不解析訊息本文。
熱點:更細的熱點就是更短的時間段(2026-08-26,TASK-0063)
熱點就是一個時間段。要更細看,就在既有熱點區間內再標一段更短的:
# 母段(20s)
senate ucmd run StreamWatch --arg step=hotspot --arg from=20:57:51 --arg to=20:58:11 --arg why=<為什麼值得細看>
# 更細(6s,落在上面區間內)—— 直接標,不需要任何父子欄位
senate ucmd run StreamWatch --arg step=hotspot --arg from=20:58:02 --arg to=20:58:08 --arg why=<為什麼值得細看>
- 系統不擋重疊與包含 ⇒ 嵌套是支援的用法,不是漏擋。
- 沒有層級概念,也不需要有:包含關係看兩筆的
from/to就看得出來, 不要另外用why或編號再寫一次 —— 同一個量兩個說法就是漂移源(文字寫錯、母段後來改了,兩邊不會有人報錯)。 - 熱點觀看(
step=claim)不套進度檔位、不吃主線段序(它是刻意離開主線去細看的)。
誰能標、誰能追(Tim 2026-08-26 釐清 —— 舊文件把這格寫錯了)
| 動作 | 誰可以 |
|---|---|
標記熱點(step=hotspot) |
人人可標,含 primary —— 接力之後每個人都有自己獨看的段,看到值得細看的當然自己標 |
細看熱點(step=claim) |
只有陪看者;primary 會被機械擋下 |
⚠ 舊文件寫「primary 的職責=場次設定+熱點標記+開收場結算」,讀起來像「只有 primary 能標」——
那是錯的,code 從來沒有這個守衛。primary 的差別只在不追蹤(她一離開主線去細看,主劇情就斷了)。
⇒ 陪看者可以追 primary 標的、也可以追自己標的;掛著沒人領的熱點是「還沒人看」的讀數,不是失敗。
(step=hotspot 只擋兩件事:區間解析失敗/首尾顛倒、why 為空。另外會擋已被 ring buffer 覆蓋的區間 ——
標一段已經不存在的畫面,領也領不到。)
⛔ 不可做
- ❌ 自己判斷「時間到了」而停手 —— 時限只認 Cmd 的時鐘,不認收束感。
- ❌ 憑印象取
mediaslug;❌ 用bilibili-stream這種泛名(會把所有影片併成一個 work)。 - ❌ 自己去跑
screenstream_montage.py—— 繞過收銀台的帳不算數,且不會有窗口對帳。 - ❌ 評論裡寫自己數的 frame 數/時間 —— 數字一律引用回傳檔的讀數。
延伸
| 想知道 | 看哪 |
|---|---|
| 怎麼操作 Cmd、起始步驟(只在要調整流程時讀) | ucl_core:Docs~/zh-Hant/Workflows/StreamWatch_Cmd_Flow.md |
| 七步全參數/窗口演算法/session schema/計酬/blocked 全表(維護用,平常不用讀) | ucl_core:Docs~/zh-Hant/Workflows/StreamWatch_Cmd_Reference.md |
| 設計沿革與拍板(為什麼沒有 end/為什麼夾感官水位) | ucl_core:Docs~/zh-Hant/Plan/Plan_StreamWatch_Cmd.md |
| 觀影心得寫進 Library | reading-library skill |