一份涵蓋 Minecraft、CS:S、OrbStack、Apple container 與 NAV 自動生成的實作紀錄。
實作日期:2026 年 10 月 2–4 日|紀錄截點:10 月 4 日 23:55,Asia/Taipei
最初的需求很簡單:清理 Mac mini 的磁碟空間,讓它更適合長時間運作。接著,工作逐漸延伸到部署 Minecraft、搬入幾套 CS:S 伺服器、保留玩家的真實 IP,再評估 Apple 自家的容器工具。
真正花時間的地方,是讓每一層都能提出證據:遊戲程序有沒有跑、玩家能不能連進來、伺服器看到的是誰的 IP,以及 Bot 換到另一張地圖後會不會卡住。
本文記錄一台 M4 Mac mini、16 GB 記憶體上的實際結果。硬體、版本與插件組合都有範圍限制;短時間 Bot 測試不能當成滿人服的效能保證。文中的入口位址以角色名稱代替,未公開實際公網 IP、玩家識別資料或連線憑證。
01 先建立一個可維護的起點
第一步使用 Mole 1.56.1,先預覽,再執行使用者層級清理。Mole 記錄清理了 123.7 MB,執行期間量到的可用空間增加約 104.9 MB。兩個數字衡量的事情不同,背景磁碟寫入也會影響可用空間,不能把它們直接當成同一個數值。
下載檔、垃圾桶、郵件附件與可恢復的應用程式狀態先保留,沒有為了讓清理數字好看就擴大刪除範圍。工具來源是 Mole 官方專案。
遊戲資料則改成「先分遊戲,再分實例」:
Servers/
├── minecraft/
│ └── prominence-ii-1.20.1/
├── counter-strike-source/
│ ├── deathmatch_mix64_orbstack/
│ ├── deathmatch_mix64_apple/
│ ├── nav-lab/
│ └── _network/
│ └── css-wireguard/
└── _shared/
└── runtimes/
每個實例各有啟動、停止、控制台與日誌入口。世界資料、玩家資料與測試副本有清楚的歸屬;要驗證另一個容器環境,就建立獨立副本,避免兩套伺服器同時寫入同一份資料。
02 Minecraft:先讓 ARM64 Java 正常工作
Minecraft 這次採用 macOS 原生 Java,組合是 Minecraft 1.20.1、Fabric Loader 0.16.5、Prominence II RPG v3.0.10,以及 ARM64 的 Java 17。
部署前先檢查來源壓縮檔與模組。可用的 ZIP 有 2,073 個項目,337 個頂層模組 JAR 通過完整性檢查;Fabric 計入內嵌依賴後,載入 508 個模組。
設定採初始 heap 2 GB、最大 6 GB,玩家槽位 16,視距 8、模擬距離 6。世界能載入、存檔、正常停止並重新啟動,也完成玩家登入與單人跑圖。
這階段無玩家時觀察到 20 TPS,但沒有進行 16 人同時跑圖測試。日誌仍有既有的配方、進度與模組設定警告,沒有為了消除警告而全面更新模組包。因此目前能確認的是部署與基本遊玩成功;多人容量、所有任務和模組互動仍需要另外驗證。
03 CS:S:64 位元啟動,仍然需要確認指令集
CS:S 原本的 Linux 32 位元伺服器在容器內啟動時,於 Steam API 初始化附近遇到 futex 與 segmentation fault。Docker 的簡單 ARM64、AMD64、i386 測試能執行,並不代表整套遊戲也能正常初始化。
後續改用 x86-64 啟動鏈,補齊啟動檔、Steam 函式庫與相容擴充。這次採用共用的 64 位元啟動檔搭配既有 CS:S 引擎,並非取得一套完整、標準打包的官方 CS:S 64 位元伺服器發行版。這個組合在遊戲更新後仍需要重新驗證,可參考 Valve 的相關追蹤紀錄。
插件也要跟著對齊:CSSDM 換成含 x64 擴充的版本,自訂裝備選單使用對應 include 重新編譯,處理 native 不匹配的錯誤。使用的套件見 CSSDM v2.1.6-git325。
最後確認 39 個 SourceMod 插件、9 個擴充載入,20 隻 Bot 能進場、戰鬥與換回合。
這裡的「64 位元」指 x86-64。M4 是 ARM64,遊戲仍經過轉譯執行。改成另一個容器工具,不會自動把遊戲二進位轉成 ARM64 原生版本。
128 tick 要量到,而不是只寫進設定
最初只加上 -tickrate 128,實際仍約 66.7 tick。加入相容的 Source Tickrate v1.4.5 後,啟動日誌才顯示 128,SourceMod 量到的間隔是 0.0078125 秒,也就是 7.8125 ms。
在 OrbStack 的一次真人加 Bot、合計 20 人短測中,六段約五秒的樣本為 128.21、128.00、128.03、127.96、128.02、127.96 tick/s。這可以證明當時的設定生效,還不足以證明複雜地圖、滿人或長期運行都能維持相同結果。
後續雖然討論過回到約 66 tick,正式服的既有設定仍是 128;導航生成測試服則使用預設約 66.7 tick。
04 網路排查:監聽、回覆、登入,是三個不同檢查
最容易造成誤判的是看到 docker ps 顯示運行,或 lsof 顯示 UDP 埠存在,就認為玩家應該能連線。
這次實際碰到的情況是:遊戲內 Bot 持續戰鬥,外部玩家卻連線逾時。排查因此拆成幾層:
- 查看遊戲程序與實際監聽位址。
- 用固定內容的 UDP echo,先檢查封包能否雙向返回。
- 使用支援 challenge 的 A2S 查詢,確認回覆來自遊戲。
- 由另一台玩家電腦登入,觀察進場與遊戲事件。
- 比對封包標頭與遊戲連線日誌,確認來源 IP。
Docker Desktop 的 host 測試裡,曾出現 echo 程式收到封包、sendto 回報成功,發送端仍然等不到回覆。改成 bridge 並明確發布連接埠後,雙向傳送有改善。
不能因此推論所有 Mac 都有相同問題,也不能把 macOS 上的 host 模式直接套用成原生 Linux 的網路模型。Docker 官方說明了 Desktop host networking 的第 4 層實作與網卡存取限制,見 host network 文件。
OrbStack 的一般 UDP echo 在 host、bridge 測試都能成功,但 CS:S host 連線仍未通。切到 bridge 與明確埠映射後,才完成玩家登入。也就是說,echo 成功還需要接著驗證遊戲協定。
另一個反覆遇到的現象是:本次 CS:S 組合能回應容器介面 IP 的 A2S 查詢,卻忽略 loopback 位址 的查詢。後來 NAV 工作站的監督程式也必須用容器 IP,否則會把正在正常運作的遊戲誤判為無回應。這是本機實測行為,並非所有 Source 伺服器的通則。
05 保留玩家來源 IP:把遊戲流量送進獨立通道
bridge 模式讓玩家連進來了,但遊戲日誌看到的來源是轉送層位址。SteamID 與暱稱仍有各自的用途,卻不能拿來證明來源 IP 已保留。
後續與 RouterOS 配合,建立一條 WireGuard 通道。簡化後的遊戲流量路徑如下:
玩家
│ 遊戲 UDP / TCP
▼
RouterOS:入口轉送與路由
│ WireGuard 隧道內的原始玩家封包
▼
WireGuard 容器
│ 與遊戲共用網路命名空間
▼
CS:S
因為當時容器直達區網路由器的外層 UDP 路徑仍有問題,Mac 上另放了一個 relay,協助搬運 WireGuard 的加密封包。隧道端點仍在 RouterOS 與容器,relay 不負責解密,也不把玩家遊戲流量改造成應用層代理。
要保留內層來源,路由器上的 SNAT 例外與回程路由都必須配合;只有加上 WireGuard 並不保證成功。這次先驗證固定 UDP echo,再接入 CS:S,最後比對兩份證據:隧道內 IP/UDP 標頭,以及遊戲的 connected 紀錄。兩者都能對上區網玩家的原始 IP 與來源埠。
常駐部分交給 launchd 管理 relay,使用一般服務帳號執行;OrbStack 與遊戲堆疊在登入後啟動。也測過 relay 程序被終止後,由 launchd 拉起新程序、通道恢復。
到這一步,區網玩家的來源 IP 保留有直接證據,固定出口也有查核。但獨立外網玩家登入尚未補測,不能把 LAN 或 hairpin 的結果寫成完整外網驗證。
06 Apple container 能跑遊戲,但正式遷移還沒完成
測試後期環境為 macOS 26.7.1,安裝 Apple container 1.5.0。Apple 官方將其描述為利用輕量 Linux 虛擬機運行容器的工具,專案見 apple/container。
遊戲採用獨立 APFS 副本,沿用 x86-64 映像與 Rosetta,分配 4 vCPU、2 GB 記憶體。測試確認遊戲啟動、39 個原有插件、9 個擴充、20 隻 Bot、Steam/VAC,以及短時間約 128 tick 的樣本;停止後重新啟動也能再次取得回覆。
尚未解決的是 Mac 入口轉送。直接查詢容器 IP 的 A2S 成功,但 Mac 的 localhost 與區網入口逾時,轉送日誌出現 No route to host。這可以幫忙縮小問題範圍,還不足以判定根因,更不能直接推論 Apple container 無法運行 CS:S。
因此 Apple 測試服保留並停止,沒有接替 OrbStack 正式服,也沒有搬入既有 WireGuard 通道。
| 方案 | 本次實際驗證 | 留下的限制 |
|---|---|---|
| Docker Desktop | 基本容器測試、CS:S 64 位元與 bridge 路徑測試 | host UDP 路徑與來源 IP 問題;後續移除 Desktop |
| OrbStack | bridge 玩家登入;配合 RouterOS/WireGuard 保留區網玩家來源 | 仍需外網玩家與長期運行驗證 |
| Apple container | 遊戲、插件、Bot、容器 IP 查詢與短測 tick | Mac 埠轉送未通,未切換正式服 |
| Podman | 曾納入討論 | 沒有部署實測,不作效能或穩定性排名 |
這張表反映的是這台機器的驗證進度,不是容器產品的通用排行榜。
移除 Docker Desktop,不必刪掉 OrbStack 正在用的 CLI
Docker Desktop 應用程式約 2.2 GiB、舊容器資料約 2.3 GiB 已移除。遊戲的外掛資料目錄保留,唯一需要備份的 SteamCMD volume 與相關舊設定也另存。
這些是刪除的資料量,不能直接當成安裝新工具後的磁碟淨增加。最後的小型 macOS 保護中繼資料已移到垃圾桶,當時沒有完成永久刪除。
OrbStack 仍會使用 Docker 相容 CLI、context 與設定。因此目錄裡看到 .docker,不能只憑名稱就認定 Docker Desktop 還沒移除乾淨。
07 真正的事故:容器還在,遊戲已經卡住
10 月 4 日檢查日誌時,正式服容器顯示已運行超過一天,卻有一個明顯矛盾:遊戲與 SourceMod 的日誌停在 10 月 3 日 17:22。
最後的地圖是 de_bit_gallery,最後訊息是:
Unknown NavArea sub-version number
同時觀察到約一個 CPU 核心持續被占用、記憶體約 282 MiB。容器網路內直接查詢 A2S 也逾時,控制台指令無回覆;另一邊,WireGuard 握手與路由器 ping 仍正常。
這時可以確認遊戲程序本身沒有回應,問題不只在玩家到伺服器的網路。沒有新的 SourceMod 錯誤檔,也不能用來保證插件健康,因為卡住的程序已經不再正常記錄日誌。
另外還有一筆較早的事件:10 月 3 日 15:35,正式服退出碼 139,之後自動重啟,沒有 OOM 記錄。當時剛完成另一張地圖的導航生成與換圖;目前只有時間上的關聯,沒有足夠證據指認某個插件或容器工具。
既有遊戲容器沒有遊戲層健康檢查,通道的 healthcheck 也只檢查 WireGuard 介面位址。程序卡在迴圈裡仍會顯示 running,退出後的自動重啟政策自然不會觸發。這次事故因此留下另一項維護需求:偵測遊戲是否真的回應,並設計有寬限期、有重試上限的恢復機制。
08 把 NAV 生成搬到獨立工作站
.nav 是 Bot 使用的導航資料,描述可通行區域、連接關係與相關資訊;它與射擊、選武器等行為邏輯分屬不同層。Valve 的 NAV 讀寫實作 可以看到資料如何由伺服器載入。
de_bit_gallery 外面沒有獨立 NAV,BSP 的內嵌壓縮包卻有一份。其標頭異常與最後錯誤相呼應。只檢查 maps/ 是否出現同名檔案,會漏掉這種情況。
因此另建 nav-lab:使用已驗證能運行遊戲的 OrbStack 環境,複製遊戲與地圖,移除玩法插件。運行時只保留 MetaMod、SourceMod、必要框架擴充,以及新寫的 NAV Laboratory 1.0.0。
本批次是地圖目錄與輪替清單的聯集,共 116 張。初步檔案掃描標出了 55 張沒有可接受獨立或 BSP 內嵌 NAV 的候選;這不代表 55 張一定都要重建,因為官方 VPK 也可能包含導航。真正執行時,插件會再查 Valve 的檔案搜尋路徑。
工作流程是:
讀取地圖清單
↓
啟動該地圖的獨立遊戲程序
↓
確認引擎可見的 NAV
├─ 已有 → 匯出候選檔
└─ 缺少 → nav_generate → 重載地圖
↓
兩隻 Bot 運行至少 20 秒
↓
檢查 tick、NAV 標頭與 A2S 回應
↓
通過才匯出成果;失敗留下原因,再處理下一張
生成採 nav_quicksave 0,保留完整分析。容器限制 2 個 CPU、2 GiB 記憶體,一次處理一張地圖,不發布 Mac 連接埠。測試使用預設約 66.7 tick,避免把高 tickrate 當成導航生成的前提。
插件以外,還需要一個監督程式
如果地圖讓遊戲主執行緒卡住,SourceMod 插件的計時器也可能無法繼續執行。把「逾時就換圖」完全放在同一個插件裡,無法處理這種停頓。
所以另外用 Python 監督遊戲子程序:載入限時 120 秒、生成限時 1,200 秒、驗證限時 90 秒;每張最多嘗試兩次。超時就回收該遊戲程序,保存紀錄,繼續下一張。進度會寫入檔案,中止後可續跑,整批完成則退出。
監督程式先做過故障注入:讓假引擎一直等待,確認它會在期限後被回收,而且下一張工作仍能執行。這項測試處理的是「整批不能被一張地圖拖住」。
de_bit_gallery 的實際修復結果
只在測試副本裡移除異常內嵌 NAV,保留其餘素材。處理後逐一比對所有非 pak 區塊,以及壓縮包裡所有非 NAV 檔案,確認內容與原始資料一致;正式服 BSP 的雜湊也再次比對未變。
接著,地圖成功越過原本卡住的載入階段,完整導航生成耗時 79.8 秒。連同啟動與驗證,該次工作約 107.3 秒。重載後兩隻 Bot 能運行,20 秒內推進 1,333 tick,A2S 回覆也確認地圖與 Bot 數正確。
新 NAV 約 814 KiB,清理版 BSP 與 NAV 分別另存。這支持異常導航是本次重要問題,但仍只是乾淨測試服的基本驗證;回到原來 39 個插件、20 隻 Bot 的正式組合後,仍需要驗證。
09 NAV 不需要發給一般連線玩家
CS:S 的 Bot 導航由伺服器使用。玩家連進這台伺服器時,不需要另外下載獨立 .nav,因此不必把生成結果放入 FastDL 或強制下載清單。玩家若自己在本機開 Bot 對戰,才是另一個本機伺服器使用情境。
單純換一份外部 NAV,不代表玩家需要換地圖。這次 de_bit_gallery 額外改動了 BSP 內嵌內容,所以正式套用前,還要另外驗證玩家原有 BSP 的相容性,不能把 NAV 與整張地圖的部署需求混在一起。
10 寫到這裡,哪些完成了,哪些還在跑?
以下是 2026-10-04 23:55 的固定快照,不是即時儀表板。
| 項目 | 截稿時的結果 |
|---|---|
| NAV 總地圖數 | 116 張 |
| 完成驗證 | 40 張 |
| 已完成項目中的失敗數 | 0 張 |
| 已確認重新生成並通過 | 4 張,其餘已完成項目沿用引擎可見 NAV |
| 當前工作 | de_company |
de_bit_gallery |
已重建並通過基本驗證 |
| 正式服 | 先前卡死事件尚未在本次 NAV 作業中重啟修復 |
| 成果回寫 | 尚未套用正式服 |
工作站保留每張地圖的引擎日誌、嘗試次數、失敗原因、NAV 大小與雜湊。後續應以整批結果清單處理未通過項目,再安排正式服套用與玩家端驗證。
這段部署最有用的收穫,是把「成功」拆成可以重做的檢查:程序啟動、協定回覆、真人登入、來源位址、換圖、Bot 與持續運行。把導航生成搬離正式服之後,也終於能在不覆蓋玩家資料的情況下,逐張處理地圖問題。
紀錄與引用方式
本文以本機部署報告、容器輸出、網路診斷、遊戲/SourceMod 日誌與 NAV 批次快照交叉整理。原始紀錄已另存私人檔案庫並建立 SHA-256 索引;公開文章只保留排查所需的資訊。
文中連結指向相關工具、開發者原始碼與官方文件。效能樣本、故障時間與批次數字來自這次本機實測,不代表工具作者對其他環境的承諾。後續若補上正式服修復或整批完成結果,應新增更新日期,保留這個截稿時間點。