哪一段抽成插件,哪一段留在作品裡寫死
9 月 9 日凌晨簡報插件推上 Larch 市集,我名下的卡湊成四張:語音輸入、格莉奇-AI對話手機、片尾字卷、簡報。這篇講那條反覆在畫的線:哪一段抽出來給任何人用、哪一段留在作品裡寫死,還有到 9 月 12 日為止的裝機數。

同一個互動,該抽成插件給任何人用,還是留在作品裡寫死?這篇講的是那條反覆在畫的線。
先交代分工:程式是 AI 寫的,全螢幕扣掉的 65px、同卡連放卡死這些平台的坑,也是 AI 實作時踩到的。
Larch 是做視覺小說的平台,插件就是作者能放進故事版子的卡片。插件清單是公開端點,不帶金鑰也打得到:https://larch.ink/api/plugins。9 月 12 日打回來五筆,四筆的 author 是我,第五筆是陳怡珊做的「粉派鴨鴨・炫彩神奇歌唱棒球」。
我那四張是語音輸入 3.2.0、格莉奇-AI對話手機 0.3.4、片尾字卷 1.0.0、簡報 1.2.0。簡報是 9 月 9 日凌晨送上去的,數字就是它湊成四的;當天上午又更新到 1.2.0,所以這篇的日期停在那天。
四張的來歷
這四張的來歷都不一樣。
- 片尾字卷:《格莉奇與黑洞先生》外傳的謝幕先用一張手寫卡做出來,再往外搬。
- 語音輸入:在《咒泉鄉的桃園三結義》那條產線上一路改出來的,本機留下 3.1.1 之後的六個版號。
- 手機:反過來,插件先做,作品端後來才搬回手寫卡。
- 簡報:沒有前身,是我每個禮拜三直播自己要用的。
所以這四張回答的是同一個一直冒出來的問題:同一段東西,抽出來給任何人用,還是留在這部作品裡寫死。下面是目前的四個答案,其中一個是往回走的。
片尾字卷:往外搬,隔了一小時十五分
9 月 7 日晚上,《格莉奇與黑洞先生》外傳的謝幕另開成一章,片尾那一卷當時是一張手寫 HTML 的小遊戲卡。一小時十五分後,那一卷被包成插件 credits-roll,版號 1.0.0。
理由只有一條,跟好不好看無關。播放器收到小遊戲卡送的 larch:complete,只會顯示「完成」,再出一顆「套用結果並繼續」鈕等玩家按。插件卡收到同一個訊號,240 毫秒後自己往下走。
字卷捲完要自動接下一張卡,非得是插件卡不可。留在小遊戲卡裡,謝幕跑完一定會停在那裡等人按一下。
所以「播完自動繼續」做成插件裡的一個開關,關掉就出自己的「繼續」鈕。
插件化之後第一個拿它用的是《咒泉鄉的白帝城》的片尾。那份名單裡有兩行長這樣:
語音輸入 3.1.5|自製・Larch 插件市集可取用
片尾字卷 1.0.0|自製・你正在看的這一卷
裝機 2 次。
AI 對話手機:往回搬
這張裝機 8 次,四張裡最多。故事走到這張卡,螢幕上冒出一支手機,介面就是平常用的通訊軟體,打字給劇中角色,對方的回覆由 AI 當場生出來。
同一支插件另外附一張「收到訊息」卡,那張不開手機:通知橫幅滑下來、通知音響一聲,把作者事先寫好的那句塞進對話串,停一下就繼續往前走。
它 9 月 4 日那一輪的透明度改動與上架取捨另外寫過:「真透明做不到」寫進十段提示,官方隔天就開了。這裡只補一件事:抽成插件可以往回走。
9 月 8 日凌晨,格莉奇外傳的手機改回一張自己寫的小遊戲卡,那次 commit 訊息最後一句是「格莉奇手機插件不再用」。前半小時,手機已經先重新設計成一份文件,四頁:訊息、她的頁面、直播、電話,通知橫幅也併進同一份 HTML。那部作品要那四頁全部在同一支手機裡,通用的那張做不到。
兩個方向都走過一次。從結果看,會被抽出來的是「第二部作品還要再用一次」的那種;留著寫死的是「只有這一部要、而且要得很細」的那種。
語音輸入:等一支麥克風等到 9 月 2 日
這張現在 3.2.0,裝機 7 次。它做的事一句話講得完:玩家開口說一句,辨識到的文字寫進變數,關鍵字比對成固定值給劇情分支用。
麻煩在麥克風。插件卡跑在 sandbox="allow-scripts" 的 srcdoc iframe 裡,是 opaque origin。8 月 31 日在正式播放器與本機都量過,Web Speech 跟 getUserMedia 兩支都一樣,not-allowed 在 14 到 31 毫秒之間就回來,權限提示連跳都不跳。
卡片上那個輸入框就是退而求其次的做法:辨識交給作業系統,手機鍵盤的麥克風鍵或 Windows 的 Win+H 都行。玩家一樣用講的,比對與分支那段一個字都不必改。
我把這件事反應給官方之後,9 月 2 日插件卡的 iframe 加上了 allow="microphone *",小遊戲卡沒有。同一組探針再跑一次,兩支 API 結果分開了:webkitSpeechRecognition 過了,插件用的剛好就是它;getUserMedia 依舊 21 毫秒丟 SecurityError,因為 sandbox 那邊只給了 allow-scripts。真的開口講話那條路,是這天起才通的。
AI 量這件事時出了兩次包。第一次拿無頭 Chrome 測 Web Speech,它根本沒有語音辨識後端,怎麼測都失敗,跟沙箱無關,當證據不算數,要驗只能開真的瀏覽器。第二次是測錯對象:探針打的是 getUserMedia,插件用的卻是 SpeechRecognition。
作品寫壞我自己收,插件寫壞是一群人一起收
3.1.3 之前的關鍵字比對是子字串,而且由上往下第一條命中就回傳。所以「不理」會被前面規則裡的「理」吃掉,玩家說了否定句,故事跑去講道理。這個錯是在我自己的作品上量出來的,那條產線另外寫過一篇:誰後送誰贏:一個開著的編輯器分頁,把 95 張卡推回 80 張。
這裡要補的是插件這一側。那段比對程式躺在卡片的 pluginHtml 欄位裡,每一個裝了這張卡的專案都有一份。
所以修法不能動到別人既有的行為。這一段的推理與那十行修法,是 AI 整理成一份文件交出來的,標題就寫著「給 Larch voice-input 插件作者的回報」,我是照那份收的。定案的版本是:規則全部掃完,命中的關鍵字誰最長誰贏。只有一條規則命中時,結果跟舊版一模一樣;兩條一樣長,照原本的先後排。被改掉的只有一種情況:長關鍵字被寫在前面的短關鍵字截走,也就是這個錯本身。105 個案例,舊版失敗 4 個、新版 0 個。那份對照表與驗證腳本也是 AI 寫的。
3.2.0 多一張欄位相同的卡
9 月 9 日下午這張又動了一次,3.2.0 加了「說一句話(全螢幕)」,欄位跟原本那張一模一樣。
原因出在平台。全螢幕不是作者能勾的選項:版子節點上的 pluginPresentation 只在「加入這張卡」的瞬間,從 manifest 的 card.presentation 抄一次,之後編輯器沒有地方改得到。讀 studio bundle index-B5fkzg_5.js 確認過,pluginPresentation 全檔只出現一行,就是建立節點那行,搜「全螢幕」零筆。
要讓使用者在編輯器裡選得到全螢幕,唯一的辦法是插件本身多一張宣告 fullscreen 的卡。
順帶記一條:卡 id 只吃小寫英數與連字號、3 到 60 字。speakFull 這種駝峰會被市集擋下來。
簡報插件
第四張沒有前身。每週直播的簡報要放進 Larch 專案裡講,講完接著在同一個播放器裡示範,中間不用切視窗。樣式那層原封不動搬 slide-deck skill 的 deck.css,兩邊的 deck.css 位元組完全相同。
這個 repo 只有 9 個 commit,全是 9 月 9 日那天的。卡片 HTML、push.py、split.py 與 manifest 產生器都是 AI 寫的,我出的是要什麼、東西回來自己看。下面四件是那晚寫進規則的。
1280×720 的視窗,卡片只拿到 655
fullscreen 的插件卡,外框預設先收走 65px,1280×720 的視窗到卡片手上只剩 1280×655。要拿回那 65px,得在卡片 data 補上 pluginFrame:{showTitle:false, showButton:false}。沒補的話,按 720 排的版放進 655 就溢出去了。
驗收器 dev/check.mjs 最後就照 655 量:寧可每一頁都留那 65px 的餘裕,也不要哪天忘了補 pluginFrame 就當場溢出。這支 75 行的腳本把打包好的卡片放進跟 Larch 同款的 sandbox iframe,假扮 host 送 larch:init,然後逐頁檢查:
- 頁數對不對、有沒有溢出、備註數對不對
N、P與方向鍵有沒有反應- 最後一頁的結束鈕有沒有送
larch:complete
卡片那端也補了自動縮版,放不下或頂到狀態列就逐級縮。
一份簡報切成幾段
直播是講一段、動手一段輪著來,動手的段落有長有短。那段時間簡報還占著整個畫面。
我要了切段,當時說的是想照要點一步一步帶。做法是用 --breaks 指定斷點,把一份簡報切成幾張卡推上版子。講完一段按「結束」,畫面交還給白板,等一下再從下一段接回來。
這件要說清楚:9 月 9 日凌晨三點多,切成七段的版本推上了共創板,AI 在線上走了前三段,commit 訊息寫著「線上實走三段」。我說拆開之後講起來有點不順,可能還是用一張卡,幾分鐘後版子就收回成一張十七頁的 deck-0909,共創板上只剩它跟 deck-0909-after 兩個節點。當晚直播用的就是這一張。切段這條路做好了、上過版子,但那場沒用上。
做這件事的 split.py 切下去的同時補四樣東西:
[pace=N]原本是從開場起算的累積分鐘,換算成這一段自己的分鐘數,不然計時器不會在該轉紅的時候轉紅。- 結束鈕的字改成下一段的標題。
- 左上角補上這是第幾段、共幾段。
- 每一段依序接線,接到指定的那張既有卡片。
兩張簡報卡接在一起,後面那張是死的
這件是 AI 串接卡片時先撞到,才補進規則的。同一種插件卡連放兩張,後面那張根本不會啟動:播放器把前一張的 iframe 留著繼續用,畫面停在前一張的最後狀態,按鈕看起來都在,按下去沒反應。
這個症狀先前在背包插件的 grant-item 上記過一次,9 月 9 日用簡報插件又確認一遍,所以它是插件卡的通則,跟哪一支插件無關。中間插一張對話卡就正常。split.py 現在每兩段之間自動插一張旁白卡銜接,順便報下一段要做什麼。
講稿開在第二台螢幕
講稿要另外開在一台螢幕或一支手機上。slide-deck 原本靠 localStorage 讓兩個視窗互通,搬到這裡就失效了,opaque origin 沒有共用的儲存空間。改成掛一個 WebSocket worker,看到的結果一樣。
代價寫在 README 的已知限制裡。預設房號每次隨機生,卡片一重新載入就換一間,第二個畫面得跟著重開。想在開播前先把第二台螢幕架好,就得自己把房號跟密碼填成固定值。
預設不固定的理由:作品發佈到市集以後,卡片的設定值在公開 JSON 裡看得到,房號等於進場券。要填死可以,自己取一個猜不到的。
頁內嵌別的網站
寫一行 @embed 網址,頁面上就多一個小視窗,標題列可以拖、右下角可以拉、右上角一按放到 96% 畫面。這篇封面左上角那頁裡的格莉奇音樂就是這樣來的。它出得了聲,不過那是 9 月 8 日在本機同款的 sandbox iframe 裡量的(頻譜畫布的亮像素,修前固定 611 個,修後 5000 多個而且逐幀在變),不是在直播現場點過。
能不能嵌,除了看對方有沒有設 X-Frame-Options,還要看被嵌的站禁不禁得起沒有儲存空間。被嵌的站會一起繼承 opaque origin,凡是開機第一件事就去讀 storage 的站,整支腳本當場死在那裡。我自己的站被嵌進去就壞過,那天修了三個 PR,跨了六個多小時,寫在同一份 index.html,放進別人的頁面就開不起來。
9 月 9 日那場直播的簡報就是用這張卡上去的。現場發生什麼另外寫過:十一句台詞交給 AI 導演,沒有預期中好玩,原因在我給的提示詞。
這張裝機 1 次。
18 次裝機
到 9 月 12 日為止,市集列出來的數字是這樣:
| 插件 | 版本 | 裝機 |
|---|---|---|
| 格莉奇-AI對話手機 | 0.3.4 | 8 |
| 語音輸入 | 3.2.0 | 7 |
| 片尾字卷 | 1.0.0 | 2 |
| 簡報 | 1.2.0 | 1 |
| 粉派鴨鴨・炫彩神奇歌唱棒球(陳怡珊) | 1.2.0 | 9 |
我的四張加起來 18。整個市集裝機最多的那張不在我名下,是挑戰賽最佳原創那位作者做的鴨鴨棒球,9 次。
這支 API 只給總數,18 次裡有幾次是我自己的專案裝的,從這裡看不出來,我也沒有另外去數。
下個月它是幾,同一支 API 打一次就知道。
想自己看一眼
簡報插件開源,標記規格、切段工具與驗收器都在 https://github.com/yazelin/larch-slide-deck,MIT。另外三張的原始碼沒有公開,但在 Larch 的插件市集搜得到,清單也可以自己打一次 https://larch.ink/api/plugins 對。
想看這些卡放進一部作品裡長什麼樣,咒泉鄉那一系列的正典站在 https://yazelin.github.io/larch-taoyuan-canon/。