誰後送誰贏:一個開著的編輯器分頁,把 95 張卡推回 80 張
《咒泉鄉的桃園三結義》推上 Larch 的那條產線。9 月 5 日我進網頁填更新說明,那個分頁把它快取的舊版送了上去,沒有一行錯誤訊息,市集上發出去的就是那一版。同一天出了兩次。

9 月 5 日凌晨四點半,0.3.0 發上市集。更新說明用 agent API 填不進去,只能我自己開 larch.ink 的發佈對話框手打,所以我進了編輯器那個分頁。
那個分頁手上握的是開頁那一刻的版子,它把那份快取推了回去。線上那份就換成它那一份:95 張卡變成 80 張,圖鑑畫廊 27 張變成 24 張,4 句台詞的音檔被洗掉。沒有紅字,沒有衝突提示。在平台眼裡,那是一次合法的儲存。圖鑑少圖是我先問到的:「CG Gallerry 裡好像沒有你的圖?」
而市集上剛發出去的,就是被推回去的那一版。
四點四十五分補回來重發,市集回到 95 張卡,缺音檔 0 句。
這部作品叫《咒泉鄉的桃園三結義》,跑在 Larch 這個視覺小說平台上。劇情一句話:桃園結義那三個人,誤飲了咒泉鄉的娘溺泉;因為是喝下去的,泡熱水沒有用。目前做到《三國演義》第一回完結。
分工先講一次。這條產線上的每一支工具、每一支檢查器,程式碼都是 AI 寫的。我做的是要它做什麼、看出哪裡不對、要它改、拍板,以及親自在 Larch 網頁上按按鈕:發佈、編版號、貼更新說明、在編輯器裡改幾句台詞。上面那次覆蓋,就發生在我按按鈕的那一半。
整包覆蓋,兩邊都是
為什麼一個分頁就能把整份蓋掉,原因寫在推版腳本 build.py 的第一行註解:POST /nodes 會把 condition 吃掉,所以帶條件的邊只能整包 PUT。每次推版,都是把整張版子的卡與邊組好,整包送上去覆蓋。網頁編輯器按存檔,走的也是這一條。
兩邊都整包覆蓋,平台這時候不比對版本,所以誰後送誰贏。分頁放著不動,它手上那份的年紀就是開頁的時間。
番外《咒泉鄉的白帝城》那支建號腳本上,同一個形狀還有第二種長法:PUT 專案本體會把版子清空。所以 baidi/newproject.py 凡是要 PUT 專案本體的地方都走四步:先 GET 把版子存下來,再 PUT 專案,把版子推回去,最後回讀一次對卡數與邊數。
改標題畫面那一支,收尾的兩個斷言不寫死數字,比的是這次跑之前讀到的版子。9 月 6 日就抓到一次:卡數與邊數都改了,寫死的舊數字忘了跟著改,下次重跑會直接炸在斷言上。
同一天的第二次
同一天下午,同一個坑再來一次。版號表上 0.3.4 那一列的說明整行是一個括號:
0.3.4 (發的時候被編輯器的舊快取蓋掉,內容其實還是 0.3.3)
它佔一個版號,內容跟上一版一模一樣,那一列還在表上。版本.md 從這天起多一條規則:作者開著編輯器分頁的時候不要推版子,推完一定要讀回來對卡數。
走查十三分鐘,讀 JSON 幾秒
推完要驗收。原本驗一次要開瀏覽器把一條路線走完,十三分鐘,而一條路線只碰得到每張語音卡的其中一個選項。三條走下來四十分鐘,摸到十二個關鍵字。
verify.py 改成讀 API 回來的 JSON,不開瀏覽器。這個方向是我提的:「你如果用 api json 下載回來檢 能嗎?」取水地點、誓詞、募兵、帳前四個岔路各三選一,一共 81 條路線,每一條都算到底,看收不收得進結尾那張。
順便驗結構:
- 帶條件的邊要正好 15 條
- 沒有走不到的卡,也沒有非終點的死路
- 音檔對得上台詞
- 打亮的是說話的那個人
- 沒有人叫錯輩分
瀏覽器那條沒有廢掉,降級成一條冒煙。docs/驗收方法.md 的標題直接寫成結論:能不開瀏覽器就不要開。
關鍵字比對要用平台自己那段程式跑
語音卡收玩家打進來的字,比對關鍵字決定走哪一條分支。做比對的 norm 跟 matchValue 兩支是純函式,整份就躺在專案 JSON 的 pluginHtml 欄位裡。
matchcheck.py 把那一段抓出來,交給 node 跑平台自己那份程式碼。檔頭寫了一條紀律:這一段不准用 Python 重寫,重寫等於在測自己的實作,插件改版就測不出來。
它抓到三件走查抓不到的事:比對用子字串、有些關鍵字太短、先寫的先贏。三件湊在一起,「不理」被「理」吃掉,「不攔」被「攔」吃掉,玩家打的否定句被判成肯定句,故事往另外一條分支走。這一側也完全沒有錯誤訊息,玩家只會覺得「我明明說了不理,怎麼跑去講道理」。
這件事整理成一份回報,附 105 個案例的對照與一份十行的修法。那支語音輸入插件是我自己上架的,所以報告寫完四十多分鐘後,我在後台上架了修好的 3.1.3。修法是把「第一條命中就回傳」換成「命中最長的關鍵字贏」,同樣那 105 個案例,失敗數從 4 變 0。
9 月 6 日,案例表補上負控制,也就是必須比對不到的字。番外那四張語音卡,每張列出近義字與鄰居字,像碑文那張的「良」「涼」「泉」「溺」,帶著跑 69 個案例 0 失敗。只驗「關鍵字會中」是半套,規則吃得太寬的時候,正向案例一樣全綠。
第一章那四張卡到今天還沒有負控制。它的 101 個案例全綠,綠的只是「該中的有中」那一半。
正典的規則只寫在一支檔案裡
正典是這部作品的設定集。寫久了會忘記哪些取自《三國演義》、哪些是自己編的,然後拿自己編的當「原著就這樣」往下推。canon/canoncheck.py 守的是這條線。
規則只在那一支裡寫一份,沒有 canon.json。檔頭寫了理由:抄成兩份,改設定要同時改兩份,遲早有一份是舊的,而那正是正典要防的事。
它守的條目長這樣:
- 關羽的刀改打之前得說過一次八十二斤(原文的原重),改打之後是二十八斤,順序不能顛倒
- 董卓那一問一答照原話
- 盧植撥的是一千官軍
- 大興山那場是五萬,不是五千
還有一條寫成密度:關羽對三妹的稱呼,一段最多出現一次。註解順手記了為什麼不訂成「全書只有三次」,那個數字寫到下一批就會變成假的。
平台這一側的三件事,都不報錯
一、更新說明,agent API 塞不進去。note、changelog 這類欄位名試了十個,全部被靜默忽略。網頁那一側走另一支端點,只認 Google 登入 cookie,帶金鑰一律 401。所以更新說明只能我自己進網頁貼,發完可以用鉛筆改。開頭那次覆蓋,就是我為了貼這一段才開那個分頁的。
二、走 agent API 發佈,更新日誌會自己多出一列,版號寫成 release number,說明欄空白。那一列刪不掉,一樣卡在那個 cookie 上。每次 API 發完,得我進網頁按垃圾桶。
三、標籤歸市集發佈那一層管,沒送就沿用上一版的。meta.py 長期送空 body,所以這部作品發到 release 22,標籤還停在第一版我在網頁上填的「視覺小說」。9 月 5 日上午 meta.py 加了 --tags,發完會把市集回報的標籤印出來對,對不上就是沒生效。
作品放在別人家的平台上,這三種形狀大概會一直有。能做的是每次寫完都讀回來對一次。
產差分之前先數句子
表情差分是同一張立繪換不同的臉。docs/表情差分研究.md 是 9 月 7 日寫的,範圍只到研究,不產圖。每一句台詞先標情緒,標籤放在 cast/emotions.json。文件裡的統計全部從標籤算,改標籤就重算,數字不准手改。
算出來三件事。
戰甲是唯一值得認真做的階段。 三人 430 句台詞裡,戰甲佔 251 句。其他九個階段加起來每人只有 20 到 60 句,多數情緒只出現在一張卡裡,產了只會被用一次。
三個人的情緒分布各長各的,一套差分複製三份沒有用。
| 角色 | 情緒分布(句數) |
|---|---|
| 劉備 | 沉 39、凜 30、柔 17、硬 14 |
| 關羽 | 硬 44、沉 21、柔 8 |
| 張飛 | 怒 30、驚 22、沉 21、笑 20、慌 13 |
關羽從頭到尾沒有一句是笑,張飛有 20 句,替關羽產大笑差分是浪費。
基準圖沒有全部中性。 男甲、跌坐、溫泉那三個階段的基準圖,臉本來就是臉紅、慘叫、放空,在那上面再做一張害羞等於原地踏步。還有至少七個場面差的是姿勢,換臉救不回來,得當姿勢差分另外排。
這份研究停在把要拍板的事列出來:13 個情緒標籤要不要合併、男版做不做(線上的關羽張飛男身沒有鬍子,原文是「髯長二尺」「燕頷虎鬚」)、溫泉基準要不要重做成泡在水裡、正裝跪姿要不要做、走 Larch 的 /art 還是本機那條。
立繪與背景沒有一張是我畫的,全是 AI 產的。我做的是定規格、一張一張挑。
9 月 7 日的 93 筆
那天有 93 筆 commit,84 筆動到番外那個目錄,70 筆改的是番外的劇本檔。
其中 11 筆在改老婆婆那幾句話:她的來歷、她當年喊的那一句、為什麼沒有人聽懂。另外兩筆改的是她的配音聲線。
同一個角色的幾句話一天內來回改十幾次,靠的是改故事只碰劇本那幾支檔案,推版一支指令,驗收一支指令,而 verify.py 跑完是秒級的。
那一批裡有幾句是我自己改的字,腳本再照著跟上。碑文那張的對照字也一樣,我在編輯器裡改完,腳本才跟著對齊。
現在玩得到的
作品在這裡:https://larch.ink/play/market/2375b478-10c0-4b52-8ff6-239e57162c64。
正典那份公開了,三類設定分開記、附原典連結:https://yazelin.github.io/larch-taoyuan-canon/。
後記(2026-09-11)
9 月 8 日 Larch 上了樂觀鎖。回應帶一個 ETag,PUT /projects 跟 PUT /boards 都要帶 If-Match;沒帶回 428,帶舊的回 409。開頭那個靜默覆蓋,從平台那一層變成會報錯的東西。
9 月 9 日這套抽成 larchrev.py 一支共用,meta.py、sync.py、baidi/newproject.py 三支接上去。盤點的時候發現那三支的 PUT 下次發版就會卡住。卡住是安全的,寫不進去比寫壞好。build.py 更早就有自己一套。
表情差分 9 月 9 日上線:桃園 16 張、番外 2 張,版號 0.4.1。封面那四張就是那一批裡劉備戰甲的沉、凜、硬、柔。研究文件第六節「換表情要切卡」那段在 9 月 8 日就更正掉了:每一句可以有自己的 stage.actors,播放器照它換圖。男版最後沒做;溫泉那組重生三輪之後我看過成品,決定沿用岸上站姿那張。