Adventure Table 是一個輕量、桌上跑團優先的 D&D 5e 2014 Web VTT。真人 DM 像實體跑團一樣主要靠口頭敘事,網站只管需要共享、同步、計算、保存、權限與 AI 接入的東西。外部 AI 未來可透過 MCP / Site Tools 正式進桌擔任 DM 或 Player,與真人共用同一套 Game State、規則與權限。
朋友間私人使用,非商品化平台。介面為 zh-TW / en 雙語。
目前進度以 PROJECT_BRIEF.md 為單一事實來源——本檔不複述 Phase 狀態。概略地說:角色端(Character Workshop / Builder / Sheet / Level Up / Version History)可用,Web 已建立 Room foundation;Campaign / Seat / Session / Combat 尚未實作。
文件入口:
| 檔案 | 內容 |
|---|---|
PROJECT_BRIEF.md |
當前 Phase、Roadmap、Subphase 進度、文件索引 |
規格企劃.md |
產品與玩法的單一事實來源 |
AGENTS.md |
開發與 AI agent 的工作規則 |
已知問題.md |
已確認但決定暫不處理的問題 |
docs/P0/、docs/P1/、docs/P2/、docs/M01/、docs/M02/、docs/M03/ |
各 Phase 的實作規格、開發設計方針、測試指南與 closeout |
需求:Docker + Docker Compose。
cp .env.example .env
docker compose up --build
啟動後:
/health 只表示 FastAPI process 可回應;/ready 會實際檢查 PostgreSQL,資料庫不可用時回傳 HTTP 503。Server 啟動時會載入並驗證全部 enabled content packs;schema、stable key 或 cross-reference 錯誤一律 fail-fast,應用程式不會帶著壞資料啟動。
規則內容住在 data/<pack>/,全部是 version-controlled 的 normalized JSON。每筆 entry 使用不依賴顯示名稱的 stable key,例如 srd5.1:spell:fireball、tce:class:artificer。
啟用清單的單一事實來源是 Settings.enabled_content_packs,可用環境變數覆寫。data/localization/ 放 locale policy 與術語表,各 pack 的 zh-TW 呈現字串放在該 pack 的 locales/ 之下。
data/srd5.1/NOTICE.md 保存 SRD 5.1 的 attribution 與繁中翻譯聲明。scripts/vendor_srd.py 是 maintainer 用的可重現 vendor 工具,runtime 不會連外下載規則資料。網站本身不接 LLM API。
P2 起 Alembic 有 shared Character 與 Web multiplayer 兩條 migration track。Web server 必須升到全部 heads;Windows standalone launcher 只會升 character@head,不會建立 Room tables。
Server container 啟動前會自動執行 alembic upgrade heads。也可手動驗證:
docker compose up -d db
docker compose run --rm server alembic upgrade heads
Web Chat connector 使用公開 HTTPS MCP 入口 /mcp。OAuth discovery 會公開 /.well-known/oauth-protected-resource 與 /.well-known/oauth-authorization-server;public client 透過 /mcp/oauth/register 做 DCR,再走 /mcp/oauth/authorize 與 /mcp/oauth/token 的 authorization-code + PKCE S256 流程。部署 web track 時可明確執行:
cd apps/server
alembic upgrade web@head
OAuth 不會建立、選擇、切換或改變 Seat / Role。在 Adventure Table 端必須先建立既有的 AI Seat,並由 Lobby / Player handoff 產生一次性的 AI Join Token;OAuth authorize 只把外部 client 綁到該既有 P3-D grant。授權完成後只保存 hash 與 OAuth authorization/token 狀態,不應把 AI Join Token 或 OAuth credential 寫進 README、log、commit 或部署設定範例。
Access / refresh token 與 authorization 會保存在 Web database,因此 server restart 後仍可驗證;但 P3-D grant 仍是唯一 authority。Take Back / revoke、Seat controller epoch 改變、Session End / Abandon、pre-session TTL 到期都會使對應 OAuth family 失效;MCP request 與 refresh 也會重新進 P3-D authority 驗證。Windows standalone 不掛載 MCP/OAuth routes,也不建立 OAuth tables。
網頁版 chat 的 connector 需要一個公網 HTTPS 入口。只轉發 /mcp* 與兩個 OAuth discovery 路徑,不把 Room UI(/、/api/*)露出公網;TLS 由入口終結,server 本身仍聽 HTTP。
server 只看得到 loopback 的 origin,metadata 與 401 challenge 裡的 URL 要靠 ADVENTURE_TABLE_MCP_PUBLIC_ORIGIN 指到公網 origin(不含路徑、不含尾斜線)。docker-compose.yml 會把這個環境變數透傳給 server;未設定時退回 request origin,只適合本機直連。
以 Tailscale Funnel 為例(M04-A/M04-B 實測的入口形態)。--set-path 的 target 必須帶同一個路徑,否則 Tailscale 會把 mount 前綴剝掉再轉給後端:
ADVENTURE_TABLE_MCP_PUBLIC_ORIGIN=https://<node>.<tailnet>.ts.net docker compose up -d --build server
tailscale funnel --bg --set-path /mcp http://127.0.0.1:8000/mcp
tailscale funnel --bg --set-path /.well-known/oauth-protected-resource http://127.0.0.1:8000/.well-known/oauth-protected-resource
tailscale funnel --bg --set-path /.well-known/oauth-authorization-server http://127.0.0.1:8000/.well-known/oauth-authorization-server
connector URL 填 https://<node>.<tailnet>.ts.net/mcp。驗證:GET /.well-known/oauth-authorization-server 的 issuer 是公網 origin;GET /api/rooms 在入口就 404。收工用 tailscale funnel reset。
Python 3.12+。venv 建在專案根目錄,讓所有人與 agent 看到一致結果:
python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux: source .venv/bin/activate
cd apps/server
pip install -e ".[dev]"
在可連線的 PostgreSQL 已啟動後:
# PowerShell: $env:DATABASE_URL="postgresql+psycopg://adventure:adventure@localhost:5432/adventure_table"
export DATABASE_URL="postgresql+psycopg://adventure:adventure@localhost:5432/adventure_table"
cd apps/server
alembic upgrade heads
uvicorn app.main:app --reload
Backend tests:
cd apps/server && pytest
需求:Node.js 24。先固定 npm 11.6.0,避開 npm 10.9.8 的 dependency resolver bug。
cd apps/web
npm install --global npm@11.6.0
npm install
npm run dev
單元測試與 build:
npm test -- --run
npm run build # tsc --noEmit + vite build
整套 Playwright 一律走隔離的容器 Linux dev stack;日常網站不會被重建或切換 DB:
cd apps/web && npm run test:e2e:docker
U01-A 後,這個單一入口會確保共用的 PostgreSQL container 已健康、建立(若尚不存在)adventure_table_e2e,再啟動 profile e2e 下的 server-e2e / web-e2e。測試固定使用:
server 8000 / web 5173 / database adventure_tableserver-e2e 8001 / web-e2e 5174 / database adventure_table_e2eserver-e2e / web-e2e 與 daily services 共用相同 Dockerfile / build context,因此 rebuild 可沿用 Docker layer cache;xge-less 第二輪也只 recreate E2E services,不會重啟 daily server / web。
Playwright global setup 的 destructive reset 只允許作用在 adventure_table_e2e。本機仍需顯式 opt-in:
cd apps/web && ADVENTURE_TABLE_E2E_ALLOW_DESTRUCTIVE_RESET=1 npm run test:e2e:docker
reset 在同一個 psql --single-transaction 內先檢查 current_database() == 'adventure_table_e2e',再執行 truncate;即使誤把 --database adventure_table 指到日常 DB,也會 fail closed。CI 可免手動 opt-in,但不能繞過 database identity guard。不再需要因執行 E2E 而先備份日常 adventure_table。
character-sheet.spec.ts 的 P0-F 三頁 full-page 截圖 smoke 預設跳過;要重新產出截圖時加 ADVENTURE_TABLE_E2E_VISUAL_SMOKE=1。
Windows 上不要讓 Playwright 自己託管 vite:dev server 會在跑測試途中停止接受連線,造成數十個 net::ERR_CONNECTION_REFUSED。playwright.config.ts 會直接擋下這條路徑。根因與量測見 已知問題.md 的 KI-ENV-001。
第一次執行前先安裝 Chromium:
npx playwright install chromium
Windows standalone 發版的套件版本固定在 apps/server/constraints-standalone-win.txt,本機與 CI 共用同一份。pyproject.toml 只宣告需要哪些套件與相容範圍;實際版本號只住這份清單。
scripts/build-standalone.cmd 會在建立 venv 後檢查直譯器版本,安裝時帶 -c 套用清單,裝完再比對一次實際安裝結果。環境與清單不符時 build 直接失敗,錯誤訊息會列出差在哪個套件。
發版用的 Python 固定為 3.13(與 M03 CI 契約一致)。若本機預設 python 不是 3.13,用 STANDALONE_PYTHON 指向 3.13 直譯器:
set "STANDALONE_PYTHON=C:\path\to\python3.13\python.exe"
scripts\build-standalone.cmd --version v0.1.0
平常改遊戲邏輯、UI 或翻譯都不必動這份清單。新增 Python 套件、升級既有套件,或更換發版 Python 版本時才更新:
rmdir /s /q .pin-venv
"%STANDALONE_PYTHON%" -m venv .pin-venv
.pin-venv\Scripts\python.exe -m pip install --upgrade pip
.pin-venv\Scripts\python.exe -m pip install -e "apps\server[standalone]"
.pin-venv\Scripts\python.exe -m pip freeze --exclude-editable --all
把輸出接在清單既有的註解標頭後面覆蓋原本的版本列表——標頭裡的 # python-version: 與說明要保留。一定要帶 --exclude-editable,否則 adventure-table-server 自己會被寫進清單,下次安裝就會壞。
更新後必須跑過完整 standalone build 與 frozen smoke 才提交:
scripts\build-standalone.cmd --version pin-check
.standalone-venv\Scripts\python.exe scripts\smoke_standalone.py dist\adventure-table-standalone --timeout 30
這份清單只適用 Windows,內含 win32 專用 wheel,不要拿去餵 Linux 或 Docker。重建某個舊版發行時,用當時 commit 的清單。
本 repo 的規則內容取自 System Reference Document 5.1,依 CC BY 4.0 使用;attribution 見 data/srd5.1/NOTICE.md。非 SRD 內容依私人專案需求逐步加入,不對外散布。