adventure-table

Adventure Table

Python FastAPI PostgreSQL React TypeScript Vite Ruleset Status

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,應用程式不會帶著壞資料啟動。

Content packs

規則內容住在 data/<pack>/,全部是 version-controlled 的 normalized JSON。每筆 entry 使用不依賴顯示名稱的 stable key,例如 srd5.1:spell:fireballtce: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。

Database migration

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 MCP / OAuth 部署

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 連進來

網頁版 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-serverissuer 是公網 origin;GET /api/rooms 在入口就 404。收工用 tailscale funnel reset

Backend 本機開發

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

Frontend 本機開發

需求: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

E2E 測試

整套 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-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_REFUSEDplaywright.config.ts 會直接擋下這條路徑。根因與量測見 已知問題.md 的 KI-ENV-001。

第一次執行前先安裝 Chromium:

npx playwright install chromium

Standalone 發版相依版本

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 內容依私人專案需求逐步加入,不對外散布。