Phase:P2 — Room / Campaign / Session / Seat
本文件只定義 P2 各 Subphase 完成後什麼必須成立。具體 DB schema、API、module、migration、route wiring、scope enforcement 與 standalone boundary 放在同目錄的開發設計方針.md;自動/人工驗收流程與證據要求放在測試指南.md。
最後更新:2026-09-06
P0 / P1 已完成 Character Core、Character Sheet、Character Builder、Level Up、immutable Build Version 與 mutable Current State;M01 建立可長期擴充的 Multi-Source Character Content;M02 建立 zh-TW / en 雙語基礎;M03 把同一套 Character Core 包成 Windows standalone,並建立 Character JSON Import / Export 與 standalone import boundary。
P2 是第一個真正的多人桌面基礎 Phase。P2 不做 Exploration / Chat / Roll / AI Event Queue / Combat / Adventure Runtime;它先把「一群朋友在哪一張桌、有哪些角色、哪個 Campaign、今天哪些 Seat 出席、這場 Session 何時開始/結束」做成可靠的 Server State 與 Web UX。
P2 關門時必須做到:
規格企劃.md 的已拍板行為實作。 不重新發明 Character Ownership / Transfer workflow。unstable legacy envelope,normalize 成 locked v1;從 v1 起維持 backward-compatible import contract。P2 固定拆成六個可獨立實作、驗證與 commit 的 Subphases:
P2-A — Room Foundation & Web Entry
↓
P2-B — Room Character Workspace
↓
P2-C — Campaign & Party Roster
↓
P2-D — Seat, Controller & Lobby
↓
P2-E — Session Lifecycle & Late Join
↓
P2-F — Full P2 Integration & Closeout
三份 P2 文件必須使用完全一致的名稱與順序。
不得在 P2-A 先做 P2-E 的 Session feature;也不得因 P2 已有 Room 就順手加入 P3 Chat / Roll / AI Tool、P4 Combat 或 P7 full Snapshot / Restore。
每個 Subphase closeout 都必須維持已完成產品可用。特別是 P2-A 與 P2-B 的過渡:P2-A 先讓首頁與新多人入口變成 Room-first,但在 P2-B 正式完成 Room Character Workspace 前,既有 Web global Character route / API 可暫時保留為 migration compatibility path;P2-A 不再從首頁導流到它,也不得把它稱為最終 Web UX。P2-B closeout 才是 global Web Character Workshop / global Web Character API 正式收口的 gate。
Web 首頁第一版入口:
Adventure Table
[ Enter Room ]
[ Create Room ]
Recent Rooms # 可有;只作 client convenience
P2 最終 Web 首頁不提供 Character Workshop。
流程固定是:
Web Homepage
↓
Create Room / Enter Room
↓
Room Workspace
├─ Characters
├─ Campaigns
└─ 後續 Phase 的 Room assets
角色工作流:
Enter Room
↓
Room Character Workshop
↓
Create / Import Character
↓
Builder Draft(已屬於該 Room)
↓
Confirm
↓
Character(仍屬於該 Room workspace)
不能建立一個「之後再決定放哪個 Room」的 Web Draft。
Standalone 仍是:
Standalone Landing
↓
Character Workshop
├─ Create
├─ Import
├─ Manage / Sheet
├─ Level Up / Build Edit / Version History
└─ Export
Standalone:
P2 的產品保證是:
Web Character / Draft → 一定被一個 Room workspace 管理
不是:
Character Core → 必須知道 Room
因此 standalone 可以繼續使用同一個 Character domain / Builder domain / JSON schema,而不建立假的 Room。
Room 是朋友共用的一張持久桌面/workspace。
第一版 Room 至少承載:
P2 不因為未來會有 Monster Templates / Maps / Adventures,就提前建立這些 domain。
MVP 不做完整帳號系統。沿用已拍板的:
Room Password
DM Key
Owner Key
產品語意:
P2 可建立未來 AI 所需的 scope / role shape,但不得宣稱 AI 已能正式加入桌內。
Room code是 public locator,不是 secret;具體格式、entropy、Password KDF與失敗登入 throttle由 開發設計方針.md 鎖定。P2-A不能把這些安全邊界留成未定 implementation detail。
Room 內沒有 My Characters / Other Characters / Ownership Transfer 制度。
一般成員可以依朋友間協調使用 Room Character Workshop。Server 仍要防止:
不要用 owner_user_id 類欄位把 Character 變成商業平台資產。
Owner 可 Hard Delete Room,必須強確認。
Room Hard Delete 的產品語意是整個 workspace 永久刪除,包括當下由該 Room 管理的:
不留下 orphan Web Characters。
重要 Character 要保留時,先 Character Export;P2 不做 recycle bin。
合法:
Room A
└─ Mira #character-A
若 Room B 也要一隻 Mira:
Room A Mira
↓ Export / Copy-as-new
Room B Import
↓
Room B Mira #character-B
兩者之後完全獨立。
禁止同一 character_id 同時掛到 Room A 與 Room B。
Room Character Workshop 要能看:
Drafts
Characters
Archived Characters
新 Create Draft / imported-repair Draft 從落地時就必須有 Room scope。
Confirm Create Draft 時:
Level Up / Build Edit / Correction Draft 必須繼承其 Character 的 Room scope,不可搬到另一 Room。
Character JSON:
同一 Character 可出現在同 Room 的多個 Campaign Roster:
Room A
├─ Campaign 1 → Mira
└─ Campaign 2 → Mira
這仍是同一隻 Mira:
若使用者要「同一起始角色但兩條世界線各自發展」,必須 Duplicate / Export+Import 成另一 Character identity。
P2-A 開始把 M03 Character exchange contract 從 unstable 帶到 locked v1。
P2 完成後必須為真:
unstable envelope,並先 normalize 到 v1 internal representation。unstable import 成功後,再 export 必須得到 v1。unstable compatibility fixture必須在P2-A修改 exporter前先由真實舊 exporter產生並commit,不得等切成v1後手刻舊格式。P2 不承諾「舊版 standalone binary 可以讀未來新 JSON」;承諾方向是新的 Adventure Table 版本可讀既有 locked schema。
建立第一個多人 workspace boundary,讓 Web 首頁正式從 global Character-first 改成 Room-first,同時鎖定 Character JSON v1,並先把 standalone boundary 保護好。
room=true;Standalone 仍 room=false。unstable fixture已先由舊 exporter產生、驗證並commit;之後 Character JSON 新 Export才切到 locked v1。unstable fixture並 normalize。head target、get_current_head()、len(heads)==1、自行推導唯一 repository head,以及把 Web alembic_version 固定當單列 scalar。P2 Non-E2E專屬 workflow已存在並提供真 PostgreSQL migration gate;其他歷史 workflow綠燈不可替代。app.main Character / Builder HTTP regression 在 Room-first 首頁上線後仍可完整執行,且仍作為 Web channel regression;不得改綁 standalone、不得整批 skip / fixme 來閃避 route 遷移。P2-A closeout 時,舊 Web /characters / /api/characters 等 global Character path 可以暫時存在到 P2-B,目的是讓每個 Subphase完成後既有 Character產品仍可操作;但:
把既有 Web Character Workshop、Character、Builder Draft、Import / Export 正式收進 Room workspace;Standalone 保留原本 Room-less workflow。
Room → Character Workshop 建立。/characters UX 列出整台 Server 的 Character。P2-B 必須處理 P2 之前已存在、沒有 Room association 的 Web Character / Draft。
要求:
Characters / Room Character Workshop 看到 owner-only Legacy Character Data migration card;只先顯示 unscoped Character / Draft 數量,不向一般 member暴露 global identities。Claim Legacy Character Data;確認 modal 必須顯示 target Room 名稱、Character / Draft count,並說明這是一次性把仍 unscoped 的舊資料歸入此 Room,不會搬動已 scoped object。具體 migration / bootstrap 技術契約見 開發設計方針.md。
讓 Room 可以建立多個持久 Campaign,並從同一 Room 的 Characters建立 Party Roster;Roster 管 Campaign participation,不複製 Character Build / State。
draft
active
completed
archived
active_campaign_id 作為 Room 目前選中的 Campaign。規格企劃.md 的 Campaign Level / Rules 仍是產品方向,但 P2 不新增 generic Campaign Rules blob,也不持久化 Leveling / Diagonal 欄位:Leveling = Milestone 在目前只有單一升級模式時維持全產品基線;Diagonal = 5/10 alternating 到 P5 Tactical Combat 真正需要空間規則時交付。未來若出現第二種 leveling mode,再由對應 Phase 把它提升成 Campaign setting。Roster entry:
Campaign
→ Room Character
→ Campaign status
status:
active
inactive
retired
dead
必須成立:
inactive。retired / dead 資料與歷史保留。active;不做 resurrection workflow。P2-C 必須明確證明:
Campaign Roster ≠ Campaign copy of CharacterState
Roster reference 同一 Room Character;HP / Inventory / Prepared / resources 等仍讀寫該 Character 的唯一 Current State。
建立「本場桌上的操作位置」與最小 Lobby,讓下一步 Session 可以固定每個 Player Seat 本場使用哪隻 Character。
產品概念保持:
Seat
├─ Role
└─ Controller
P2 必須清楚區分:
Owner 是 Room 管理 authority,不要求「Owner」成為獨立的 gameplay seat type;Owner 若實際玩桌,仍使用 DM / Player Seat。
資料 shape 必須能表示:
human
ai
none
但 P2 只正式交付 Human / None 的可操作控制流程。
ai 是為 P3 正式 AI actor 保留的合法 domain shape;P2 不建立 AI Join Token、MCP、event queue、wait_for_event 或 Connection workflow,也不得在 UI 假裝 AI 已經上線可玩。
P2-D 完成後:
active / inactive 可選;retired / dead 不出現在 Active Character 選單。完成最小 Session lifecycle,讓一群人可以在一個 Campaign 開始今天的桌、固定本場角色、late join、結束/abandon,並在下次重新進入時看到可恢復的 structured context。
active
ended
abandoned
Start 前:
Room
↓
Active Campaign
↓
Lobby / Seats
↓
Owner已完成DM assignment
↓
被指定的DM Start Session
Start 必須:
dm 或 owner Room authority;僅持DM Key但未被assign不能Start,Owner但未被assign也不能Start。P2 不提前實作 P7 完整 Snapshot payload / Restore subsystem。規格企劃.md 所稱 Session Start / End Snapshot,在 P2 先保留 deterministic boundary / future hook;P7 Snapshot subsystem 到位後再接真正整包 snapshot。
Session 一旦開始:
同一 Character 如果已是任何 active Session 的 Active Character,另一 active Session 不得再取得它。
這個限制跨同 Room 多 Campaign 生效。
Human late join由本場 current DM Controller管理:
敘事如何進場由 DM 決定。
End Session:只有本場 current DM Controller可執行;Owner / 其他 DM authority不能因權限較高就取代本場 DM。
Abandon Session:本場 current DM Controller或 Owner可執行,用於誤開、DM確定不回來等情況;這不是 DM succession。
End / Abandon 都必須:
P2 的 Resume UX 只顯示目前 P2 已有的 structured state,例如:
Combat Round / Pending Roll / Reaction 等資料要等對應 P3/P4/P5 subsystem 真正存在才顯示,不在 P2 建 placeholder truth。
證明 Room-first Web、Room Character Workspace、Campaign / Roster、Seat / Lobby、Session lifecycle、Character JSON v1 與 Standalone boundary 能作為一個完整產品基線工作。
至少必須證明:
heads 成功;Standalone fresh / legacy SQLite只升 character@head 成功。0008_m03c_import_records legacy Web DB upgrade 到 P2 heads 成功,既有 Character / Draft 不 silent loss。P2 Non-E2E workflow在final SHA提供PostgreSQL migration / persistence / concurrency evidence;其他workflow不能替代。unstable fixture → Preview → Import → Export v1。zh-TW / en P2 user-facing copy 完整,不出 raw key / raw server code。P2 不做帳號 ownership,但需有可解釋的 Room scope。Room authority與本場 Session Controller是兩個不同維度。
| 操作 | Room Member | DM authority | Owner authority |
|---|---|---|---|
| Enter Room / view allowed Room data | ✅ | ✅ | ✅ |
| Create / Import Character / Draft | ✅ | ✅ | ✅ |
| Export Room Character | ✅ | ✅ | ✅ |
| 使用 Builder / Level Up(無 active Seat衝突時) | ✅ | ✅ | ✅ |
| Reversible Character archive / restore(無 active Session lease) | ✅ | ✅ | ✅ |
| Permanent individual Character delete(archived 且無 history reference) | ❌ | ❌ | ✅ |
| Create / archive / select Campaign | ❌ | ❌ | ✅ |
| Manage current Campaign Roster | ❌ | ✅ | ✅ |
| Configure Player / Spectator Seats / Lobby | ❌ | ✅ | ✅ |
| Assign / reassign DM Seat Human controller | ❌ | ❌ | ✅ |
| Room settings / rotate keys / Hard Delete Room | ❌ | ❌ | ✅ |
這不是 Character ownership。一般朋友仍自行協調誰建立/升級哪隻 Character;Server 只對 destructive / table-authority action收緊。
| 操作 | 一般 Room authority | DM authority但未被Owner assign | Owner但未被assign | Owner-assigned DM Seat Controller / current DM Controller |
|---|---|---|---|---|
| Start Session | ❌ | ❌ | ❌ | ✅(Start時成為固定DM Controller) |
| Configure Late Join | ❌ | ❌ | ❌ | ✅(active後須current DM) |
| End Session | ❌ | ❌ | ❌ | ✅(current DM only) |
| Abandon stuck / mistaken Session | ❌ | ❌ | ✅ | ✅ |
| 修改自己 Seat 的 Current State | 需同時是該 Human Player Seat Controller | 同左 | 同左,Owner身分不額外放寬 | ✅ 可 DM 修正 |
Session active 後,Player 對 live Character State 的實際操作權必須受 Seat / Controller 約束;完整 GameAction permission會在 P3+ 擴充,但 P2 不可留下「知道另一角色 UUID就能 patch state」的 Web bypass。
Human DM 對 Player Character 的既有高修改權限,在 active Session中由本場 current DM Controller行使;不能讓另一個只持有 DM Key的人旁路修改本場角色。
P2 不做:
leveling_mode / diagonal_rule 等欄位。Leveling=Milestone 先維持現有基線,Diagonal=5/10 alternating 由 P5 Tactical Combat 實作。P2 關門後,P3 可以假設下列正式基礎已存在:
Web Request
→ Room Access Context
→ Room
→ Active Campaign
→ Active Session
→ Seat / Controller
→ Active Character
以及:
P3 在此基礎上加入 Exploration、Chat / Action / Check / Roll、正式 Human / AI table actor integration;P2 不提前替 P3 實作那些行為。