adventure-table

P0 — 開發設計方針

Phase:P0 — Character Core + SRD / Rules Foundation
本文件是 P0 的實作契約。只處理 P0 真正需要的技術設計,不替 P1 以後預先設計系統;已知跨 Phase 承接只記 requirement,不提前定 schema / API。

最後更新:2026-08-29


1. P0 Subphase 順序

三份 P0 文件使用完全一致的 Subphase 名稱與順序:

P0-A — Project Foundation
P0-B — Character-Relevant SRD Foundation
P0-C — Character Core & Persistence
P0-D — Character Rules & Backend API
P0-E — Character Sheet & State UI
P0-F — Full P0 Integration & Closeout

每個 Subphase 必須先完成自己的 implementation + tests + verification,再 commit 並進下一個。


P0-A — Project Foundation

基礎技術

沿用目前技術棧:

Frontend:

Backend:

Database:PostgreSQL。

Tests:pytest、Vitest、Playwright。

Local / Deployment baseline:Docker Compose。

P0 不需要 Redis、Message Queue、Microservices、Kubernetes、Vector DB。

Repository 結構

P0 建議第一版:

adventure-table/
├─ apps/
│  ├─ web/
│  │  ├─ src/
│  │  │  ├─ api/
│  │  │  ├─ components/
│  │  │  ├─ features/
│  │  │  │  └─ character-sheet/
│  │  │  ├─ pages/
│  │  │  └─ main.tsx
│  │  └─ ...
│  └─ server/
│     ├─ app/
│     │  ├─ api/
│     │  ├─ domain/
│     │  │  ├─ character/
│     │  │  └─ rules/
│     │  ├─ content/
│     │  ├─ persistence/
│     │  ├─ services/
│     │  └─ main.py
│     ├─ migrations/
│     └─ tests/
├─ data/
│  └─ srd5.1/
├─ docs/
│  └─ P0/
│     ├─ 實作規格.md
│     ├─ 開發設計方針.md
│     └─ 測試指南.md
├─ docker-compose.yml
├─ AGENTS.md
├─ PROJECT_BRIEF.md
├─ 規格企劃.md
└─ 技術棧討論.md

P0-A 只建立真正需要的 scaffold。不要建立 Combat、Room、Adventure、AI Runtime 等空 module。

Startup Baseline

P0-A 至少建立:

PostgreSQL available
↓
Alembic baseline works
↓
FastAPI starts
↓
health/readiness responds

Vite dev server starts
↓
minimal React app shell renders

測試 runner 在這一階段就要可以執行,即使只有最小 smoke case。


P0-B — Character-Relevant SRD Foundation

Source of Truth

P0 採:

Version-controlled normalized data files = P0 SRD reference content 的 Source of Truth。

P0 不需要為 static SRD reference data 再做一份可手動編輯的 DB truth。

資料放在:

data/srd5.1/

P0-B 建議至少包含:

data/srd5.1/
├─ manifest.json
├─ races.json
├─ classes.json
├─ subclasses.json
├─ backgrounds.json
├─ feats.json
├─ skills.json
├─ equipment.json
├─ spells.json
├─ conditions.json
├─ damage-types.json
├─ languages.json
└─ ... character-relevant rule/reference data

如果某類資料太大可拆多檔,不為形式一致硬塞單檔。

P0-B 不建立 monsters.json 或 equivalent Monster / Beast stat-block data。

P0-B SRD Scope

只導入角色系統與 P1 Builder 會真正引用的 SRD 5.1 類別:

Monster / Beast Deferral Contract

以下全部不屬於 P0-B

以上明確延後到 P4-A。這裡只記承接要求,不預先定 P4 的 Monster Template schema、API 或 Combat representation。

Stable Content Key

每個 reference entry 必須有不依賴顯示名稱的 stable key,例如:

srd5.1:spell:fireball
srd5.1:class:fighter
srd5.1:equipment:chain-mail

Character 只保存 stable content key,不複製整份 SRD definition。

顯示名稱可改,stable key 不應因此改變。

Content Schema

各類 reference content 使用 Pydantic model 驗證。

共同欄位至少:

key
name
source
ruleset

其他欄位依資料類型定義。

不要設計一個萬能巨大 SRD schema。

Content Registry

Server 啟動時:

Read manifest
↓
Load P0-B data files
↓
Pydantic validation
↓
Cross-reference validation
↓
Build ContentRegistry
↓
Application ready

若 mandatory content schema / reference 錯誤:啟動失敗,不要靜默 skip。

P0 先讓 static data 由 ContentRegistry 查詢即可;未來若真的需要 DB indexing,再於需求出現時增加。


P0-C — Character Core & Persistence

P0-C 正式建立 Character identity、immutable Build Version、mutable Current State 與 persistence。

Persistence Tables

characters

概念欄位:

id
name
ruleset
current_version_id
created_at
updated_at

使用 UUID 作為 DB entity id;SRD reference 仍使用 stable content key。

character_versions

保存 Build snapshot

id
character_id
version_no
build_payload JSONB
created_at

原則:

build_payload 必須通過 CharacterBuild Pydantic schema。

P0 採 JSONB snapshot 的理由:

若 P1 後真的需要 relational query,再對真正需求 migration。

character_states

概念欄位:

character_id
state_payload JSONB
updated_at

state_payload 通過 CharacterState schema,至少表示:

Build 與 State 不可共用同一 payload。

DTO Separation

至少區分:

CharacterBuild
CharacterState
CharacterSheetDTO

CharacterSheetDTO 的完整 calculated read model 於 P0-D 建立;P0-C 先確保 Build / State DTO 有正式 schema。

CharacterBuild Schema

至少能表示:

identity/basic profile
race_ref
class_progression[]
ability_scores
proficiencies
saving_throw_proficiencies
skill_choices
feature_refs[]
feat_refs[]
spell_access_entries[]
hp_progression[]
starting_equipment[]
roleplay_profile
numeric_overrides

Class Progression

不可只存:

fighter = 5
wizard = 5

至少保留 acquisition order,例如:

[
  fighter,
  fighter,
  fighter,
  fighter,
  fighter,
  wizard,
  wizard,
  wizard,
  wizard,
  wizard
]

之後可 derive total level、per-class level、starting class。

Ability Score Semantics

CharacterBuild.ability_scores = 永久 Build 選擇全部解析後、Numeric Override 前的 resolved Build Score

標準 fixture:

STR = 16
DEX = 14
CON = 14
INT = 16
WIS = 10
CHA = 8

Human racial bonus 已包含,之後 Rules Engine 不得再因 race_ref 加一次。

Spell Access

Build spell access entry 至少:

entry_id
spell_key
source_type
source_key
access_type

entry_id 是 Build 內 logical stable id。

P0 Build access_type

known
spellbook
always_prepared
granted

不得使用 prepared 作為 Build access type。

一般 Prepared Spells 存 CharacterState.prepared_spell_entry_ids[],指向目前 Build 可準備的 entry。

Always-prepared / Granted 不需要塞進每日 prepared selection 才生效。

P0 fixture 正式支援 Wizard spellbook + prepared state;Cleric / Druid 的完整準備模型等 P1 真正做 Builder 時再擴充,不先做大型 abstraction。

HP Progression

hp_progression[] 保存每個 Character Level 實際採用的 base HP gain,與 class_progression[] 對齊。

標準 fixture:

class_progression:
Fighter, Fighter, Fighter, Fighter, Fighter,
Wizard, Wizard, Wizard, Wizard, Wizard

hp_progression:
10, 6, 6, 6, 6,
4, 4, 4, 4, 4

這只代表 fixture 使用 fixed value;P1 若允許 rolled HP,保存該級實際採用的 base HP gain 即可。

Numeric Override

Numeric override 屬 Build,允許的 key 由 P0-D rules layer 明確定義;不得允許任意 raw DB field override。

可包含:

ability:<ability-key>
ac
max_hp
skill_modifier:<skill-key>
spell_save_dc:<source-key>

CharacterState Schema

HP

至少:

current_hp
temporary_hp

Max HP 不存成另一份一般 authoritative state;由 P0-D 計算。

Conditions / Temporary Effects

至少:

condition_ref
display/source note  # optional

P0 保存與顯示;完整 condition gameplay automation 屬 P4。

Prepared Spells / Resource Usage

至少:

prepared_spell_entry_ids[]
spell_slots_used / remaining
class-specific resource usage  # fixture 真正需要時

Prepared selection 改變只更新 State,不建立 Character Version。

Hit Dice State

Build total 可由 class progression derive;State 只保存每種骰型 available 數量。

標準 fixture:

d10: available 5 / total 5
d6:  available 5 / total 5

State 不複製 total 作另一份 truth。

Inventory State

整份 live Inventory 都住 Current State。

每個 live entry 至少:

entry_id
item_ref
quantity
equipped

關鍵規則:

P0 Fixture

固定:Human Fighter 5 / Wizard 5,Character Level 10

標準 fixture 無 Numeric Override,至少包含:

Seed 不需要 production Builder API,例如可用 developer-only command:

python -m app.scripts.seed_p0_fighter_wizard

Migration

P0-C Alembic migration 至少建立:

characters
character_versions
character_states

P0-D — Character Rules & Backend API

Rules Layer

Rules Engine 必須是純 Python domain logic,不依賴 FastAPI request、SQLAlchemy session 或 React。

建議:

app/domain/rules/
├─ abilities.py
├─ proficiency.py
├─ skills.py
├─ saves.py
├─ armor_class.py
├─ hit_points.py
├─ spellcasting.py
└─ character_sheet.py

計算流程:

CharacterBuild
+ CharacterState
+ ContentRegistry
↓
Resolve effective Ability Scores
↓
Rules Calculation
↓
Apply remaining Numeric Overrides
↓
CharacterSheetDTO

至少遵守:

Max HP baseline:

sum(base_hp_gain)
+ effective CON modifier × total character level
↓
其他已支援 structured modifier(若有)
↓
Max HP Numeric Override(若有,最後)

Reference Content API

GET /api/rules/content/{category}
GET /api/rules/content/{category}/{key}

可做基本 filter/search,但 P0 不做大型 search service。

Character API

GET   /api/characters/{character_id}
GET   /api/characters/{character_id}/sheet
PATCH /api/characters/{character_id}/state

PATCH /state 至少承載:

P0 不做 production POST /characters Builder workflow。

Error Contract

Backend 回 machine-readable error code + readable message;Frontend 不依英文 error string 判斷流程。

P0 不需完整 i18n framework。

Numeric Override Variant

從標準 fixture 建 test-derived variant:

ability:strength = 18
ac = 19
max_hp = 80

固定 expectation:

Multiclass Spellcasting Boundary

Fighter(非 Eldritch Knight)5 對 multiclass caster level 貢獻 0;Fighter5/Wizard5 slots 等同 Wizard5。

P0 fixture 只證明 representation / class order / source / access / prepared separation;不驗證多個 caster class combined progression,完整規則留 P1。


P0-E — Character Sheet & State UI

Route

至少:

/characters/:characterId

Layout

Character Header
├─ Name
├─ Level
├─ HP
├─ AC
└─ Conditions

Tabs / Pages
├─ Attributes / Skills
│  └─ Hit Dice by die type
├─ Spells
└─ Inventory

Page 1:Ability、Skills、Saves、Passive Perception、Hit Dice、Features/Traits、Conditions、optional Roleplay。

Page 2:Build spell access(Known / Spellbook / Always-prepared / Granted)與 Current State Prepared selection 分開顯示;另顯示 Spell Save DC / Attack、slot/resource state。

Page 3:只讀 CharacterState.inventory_state 的 live Inventory,不從 Build Starting Equipment 重建。

Data Flow

Read:

React page
↓
TanStack Query
↓
GET /api/characters/{id}/sheet
↓
CharacterSheetDTO

Mutation:

UI action
↓
PATCH state
↓
Server validates + persists
↓
invalidate / refetch
↓
UI 顯示 authoritative result

不要用 optimistic mutation 假裝正式 HP / resource / inventory state 已成功;P0 採 server-confirmed update。

P0 State UI Boundary

需要能驗證的 Current State 操作包括:

Prepared Spells 必須正確 display / persist / API validate,但 P0 不要求完整 Prepare-Spells Wizard。

Hit Dice 必須正確 display / persist,但 P0 不要求完整 Short Rest spend workflow。

Optional Roleplay 空白不得阻擋頁面。


P0-F — Full P0 Integration & Closeout

P0-F 原則上只做 integration、regression、補驗收缺口與必要 bugfix,不再新增新的大型產品能力。

完整 Startup

P0-F 最終 startup:

DB available
↓
Alembic schema ready
↓
ContentRegistry validate/load Character-Relevant SRD
↓
FastAPI ready
↓
React app can load fixture Character Sheet

SRD mandatory validation 失敗時 readiness 不得假裝正常。

必跑整合情境

P4 Monster / Beast Handoff Guard

P0-F 關門時必須確認:

P4-A 的具體資料表示方式等 P4 開工時才設計。


2. P0 不做的技術設計

本文件刻意不定義:

這些等對應 Phase 開工時再寫。