哪一段抽成插件,哪一段留在作品裡寫死

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

四張插件卡的實際畫面。左上是簡報卡的第二頁,右側嵌著可以真的點的格莉奇音樂;右上是語音輸入卡的三張選項縮圖與輸入框;左下是 AI 對話手機;右下是片尾字卷的人物格

同一個互動,該抽成插件給任何人用,還是留在作品裡寫死?這篇講的是那條反覆在畫的線。

先交代分工:程式是 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,所以這篇的日期停在那天。

四張的來歷

這四張的來歷都不一樣。

所以這四張回答的是同一個一直冒出來的問題:同一段東西,抽出來給任何人用,還是留在這部作品裡寫死。下面是目前的四個答案,其中一個是往回走的。

片尾字卷:往外搬,隔了一小時十五分

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,然後逐頁檢查:

卡片那端也補了自動縮版,放不下或頂到狀態列就逐級縮。

一份簡報切成幾段

直播是講一段、動手一段輪著來,動手的段落有長有短。那段時間簡報還占著整個畫面。

我要了切段,當時說的是想照要點一步一步帶。做法是用 --breaks 指定斷點,把一份簡報切成幾張卡推上版子。講完一段按「結束」,畫面交還給白板,等一下再從下一段接回來。

這件要說清楚:9 月 9 日凌晨三點多,切成七段的版本推上了共創板,AI 在線上走了前三段,commit 訊息寫著「線上實走三段」。我說拆開之後講起來有點不順,可能還是用一張卡,幾分鐘後版子就收回成一張十七頁的 deck-0909,共創板上只剩它跟 deck-0909-after 兩個節點。當晚直播用的就是這一張。切段這條路做好了、上過版子,但那場沒用上。

做這件事的 split.py 切下去的同時補四樣東西:

兩張簡報卡接在一起,後面那張是死的

這件是 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/。