adventure-table

P1 — 開發設計方針

Phase:P1 — Character Builder Complete
本文件是 P1 的實作契約。P1 必須建立在 P0 已存在的 Character / Rules / Content / Persistence 上,不重新發明另一套 Character model,也不提前建立 P2 以後的 Room / Permission / AI infrastructure。

最後更新:2026-08-29


1. P1 Subphase 順序

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

P1-A — Builder Domain & Draft Foundation
P1-B — Character Creation Basics
P1-C — Class Progression & Multiclass
P1-D — ASI, Feat & Structural Choices
P1-E — Spellcasting Progression
P1-F — Equipment, Review & Character Creation
P1-G — Level Up & Character Versions
P1-H — Full P1 Integration & Closeout

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


2. P0 現況與 P1 延伸原則

P1 開工時已存在的正式地基:

apps/server/app/domain/character/schemas.py
├─ CharacterBuild
├─ CharacterState
├─ PersistedCharacter
├─ SpellAccessEntry
├─ StartingEquipmentEntry
└─ NumericOverride

apps/server/app/domain/character/validation.py
apps/server/app/domain/rules/
apps/server/app/content/
apps/server/app/persistence/characters.py
apps/server/app/api/characters.py
apps/server/app/api/reference.py

apps/web/src/features/character-sheet/
apps/web/src/api/

Persistence 已有:

characters
character_versions
character_states

P0 character_versions.build_payload 是 immutable Build snapshot;character_states.state_payload 是 mutable live state。

P1 的核心原則:

不要把 Builder 半成品塞進 CharacterBuild。建立新的 Builder Draft model;只有 Draft 通過完整 compile + validation 後,才產生正式 CharacterBuild

另外:


P1-A — Builder Domain & Draft Foundation

Domain Module

建議新增:

apps/server/app/domain/character_builder/
├─ __init__.py
├─ schemas.py
├─ choices.py
├─ compiler.py
├─ validation.py
├─ service.py
└─ view.py

後續 Subphase 再按實際複雜度加入:

abilities.py
progression.py
multiclass.py
features.py
spellcasting.py
equipment.py
reconciliation.py
versions.py

不要 P1-A 一次建立所有空檔案;只有對應 Subphase 開工才加入真正需要的 module。

BuilderDraft

Builder Draft 與正式 CharacterBuild 分離。

概念:

BuilderDraft
├─ id
├─ mode
├─ character_id?          # create 時 null
├─ base_version_id?       # create 時 null
├─ revision
├─ name / identity input
├─ target_level
├─ partial build choices
├─ initial state seed choices
├─ status
└─ created_at / updated_at

mode 至少預留:

create
level_up
build_edit
correction

P1-A 可以只正式啟用 create,但 schema / persistence 不要做成之後完全無法加入其他 mode 的形狀。

Draft Payload

Draft payload 是輸入與選擇紀錄,不是最終 Build snapshot。

概念可包含:

basic
race_selection
background_selection
ability_generation
level_choices[]
choice_selections{}
spell_choices
starting_equipment_choices
roleplay_profile
numeric_overrides
initial_state_seed

Draft 允許缺欄位,因此使用 Builder 專用 Pydantic model。

不要直接把 partial dict 丟給 CharacterBuild.model_validate() 當 Draft validation。

Draft Persistence

新增 table:

character_build_drafts

概念欄位:

id UUID PK
mode string / enum
character_id UUID nullable
base_version_id UUID nullable
revision int not null
draft_payload JSONB not null
created_at
updated_at

P1 不需要 draft ownership / room_id / user_id。

Draft Cancel 可以 hard delete,因為它從未成為正式 Build history;P1 不需要把取消草稿做 Timeline。

Revision Guard

Draft write 建議帶 revision

GET Draft -> revision 7
PATCH Draft expected_revision=7
↓
成功後 revision 8

目的只是避免同一 browser 多分頁把新 Draft 內容靜默蓋掉;不是提前做 P2 concurrency subsystem。

如果實作時判定現有單人 P1 workflow 不需要 optimistic revision,可省略 API-level compare-and-swap,但 base_version_id 的 P1-G stale guard 不可省略。

Builder Validation Result

建議 DTO:

BuilderIssue
├─ code
├─ severity: blocking_error | warning | non_standard
├─ path
├─ message
└─ related_refs[]

BuilderValidationResult
├─ issues[]
├─ can_confirm
└─ non_standard_count

UI 不解析錯誤字串來判斷類型。

Choice Model

建議 canonical builder choice:

BuilderChoice
├─ choice_id
├─ label
├─ source_ref
├─ required
├─ choose_count
├─ option_source
├─ selected_option_ids[]
└─ disabled_reason?

Option 至少可承載:

reference
counted_reference
nested_choice
category_filter
branch

SRD upstream option shapes 不直接 leak 到 React;由 server-side adapter 轉成 Builder Choice DTO。

Deterministic Choice ID

choice_id 不能每次 GET 用 random UUID 重建。

建議由來源與 progression path deterministic 組合,例如概念:

race:<race-key>:language-choice:0
background:<background-key>:skill-choice:0
level:4:class:<class-key>:asi-or-feat
level:6:class:<class-key>:feature:<feature-key>:choice:0

實際字串格式可調整,但同一 Draft reload 後必須穩定,否則保存的 selection 會失聯。

Builder Compiler

建立純 domain compiler:

BuilderDraft
+ ContentRegistry
+ Rules Data
↓
Resolve choices
↓
Compile BuildCandidate
↓
Validate structural rules
↓
CharacterBuild candidate? / issues

Compiler 不依賴 FastAPI request、SQLAlchemy connection 或 React。

P1-A 不需要完成所有 D&D rules;先建立 extension points與 incomplete draft / missing-choice validation。

Content / Rules Data

P0 data/srd5.1/ 繼續是 SRD content source of truth。

P1 若遇到 Character Builder 必要、但 upstream SRD entry 不足以可靠表達的 2014 規則常數,可新增小型 version-controlled rule data,例如:

data/rules/dnd5e-2014/character-builder.json

只放真正缺少的 reusable rules data,不把所有規則重新抄一份,也不要把可調規則數值散落 Python / React magic numbers。

P1-A API

建議新增 router:

/api/character-builder

最低:

POST   /api/character-builder/drafts
GET    /api/character-builder/drafts/{draft_id}
PATCH  /api/character-builder/drafts/{draft_id}
POST   /api/character-builder/drafts/{draft_id}/validate
DELETE /api/character-builder/drafts/{draft_id}

GET Draft 建議直接回傳:

draft input
resolved summary
available / required choices
validation result

或提供等價的 server-generated Builder View DTO。

關鍵是不讓 frontend 下載 raw SRD 後自己推導 eligibility。

P1-A Persistence / Migration

新增 Alembic migration 建立 character_build_drafts

不要修改 P0 character_versions immutable payload semantics。

P1-A Frontend

先建立最小 feature shell:

apps/web/src/features/character-builder/

P1-A 只需要開 Draft / Save / Reload / Cancel 的最小 developer UI 或 API-driven smoke,不需要把完整 Wizard 樣式一次做完。


P1-B — Character Creation Basics

Character Workshop Route

建議新增:

/characters

作為 Character Workshop / Manage list。

至少顯示:

P1 不做 Account / ownership filtering。

Builder Route

建議:

/character-builder/:draftId

不要把所有 Draft JSON 放 query string / localStorage 當 truth。

Wizard Layout

Desktop:

┌──────── Steps / Rail ───────┬──── Summary ────┐
│ Builder form                │ live summary    │
│                             │ warnings        │
└─────────────────────────────┴─────────────────┘

Mobile:Summary 改 collapsible drawer / section,不要求雙欄。

正式資料仍來自 Server Draft View。

Frontend State

沿用 TanStack Query:

不要把整份正式 Draft只存在 React state 等到最後一次送。

Searchable Selectors

沿用 P0-E 已驗證的 accessible combobox pattern:

要求:

Ability Generation Model

Draft 保存 generation method與 method-specific input,不只保存最後六個 resolved numbers。

概念:

AbilityGeneration
├─ method: standard_array | point_buy | manual
├─ assignments / base_scores
└─ provenance

Compiler:

Ability generation base
↓
Race / Subrace permanent grants
↓
later ASI / Feat permanent grants
↓
resolved CharacterBuild.ability_scores
↓
Numeric Override
↓
effective ability used by rules / prerequisite

P0 CharacterBuild.ability_scores 的語意維持不變:它仍是永久 Build effects resolved 後、Numeric Override 前的值。

Subrace

P0 CharacterBuild 目前只有 race_ref,P1 若 SRD Race progression需要保留 Subrace choice,應正式擴充 Build schema,例如:

subrace_ref: StableKey | None

不要只把 Subrace traits展平後丟掉原始選擇來源,否則 Version History / correction 無法重建。

P0 fixture migration / compatibility:舊 Build 沒有 subrace_ref 時視為 None

Background / Starting Choices

Background ref 續用 P0 欄位。

Background 的 mechanical grants透過 builder resolver產生:

不要把 background description text 當規則解析來源;使用 normalized structured data / adapter。

目前 production SRD 5.1 backgrounds.json 只有 Acolyte。這只是 Built-in content scope,不是 Builder capability 上限;Background resolver / canonical choice model 不得因 production 只有一筆資料而寫成 Acolyte special case。

Ability Numeric Override UI

Numeric Override UI 放 Advanced / Non-standard 區域,不與 Standard Array / Point Buy / Manual tabs 混在一起。

Summary 顯示:

Calculated: X
Current: Y ⚠
Origin: Numeric Override

Character List API

P0 只有單 Character read 時,P1-B 建議補:

GET /api/characters

只回 Workshop list需要的 summary,不一次回整份 spellbook / inventory payload。


P1-C — Class Progression & Multiclass

Progression Domain

新增 / 擴充 pure domain logic:

character_builder/progression.py
character_builder/multiclass.py

核心輸入是 ordered Character Level records,不是 unordered class-level map。

Draft 概念:

level_choices: [
  { character_level: 1, class_ref: fighter, hp_method: first_level, ... },
  { character_level: 2, class_ref: fighter, hp_method: fixed, ... },
  { character_level: 3, class_ref: fighter, subclass_ref: ..., ... },
  { character_level: 6, class_ref: wizard, hp_method: manual, ... }
]

Compiler 最終仍產生 P0-compatible:

CharacterBuild.class_progression[]
CharacterBuild.hp_progression[]
CharacterBuild.subclasses[]

不建立第二份 mutually-authoritative class totals。

Derive Per-Class Level

任何 class level 都由 ordered progression derive:

class_level_at(character_level_index)

Feature / Subclass / ASI timing 以當下該 class 已取得的 class level判定。

Starting Class vs Multiclass Grants

Content adapter 必須能取得:

如果 upstream classes.json 已有 structured multi_classing資料,直接 adapter 使用;缺失才補 project rule data。

不要用「if class == fighter」散落硬編碼。

Multiclass Prerequisite Validation

在加入新 class level時驗:

Server validation是 authority;UI disabled只是 usability。

Feature Resolution

使用 levels.json / features.json 等 normalized content建立:

progression node
→ automatic feature refs
→ required builder choices

Automatic feature不需要使用者逐一勾選;Compiler加入 Build。

Subclass Selection

Builder adapter從 class progression知道 subclass selection timing。

Draft保存 user-selected subclass_ref;Compiler產生 P0 SubclassSelection

如果 class未達 timing:blocking error。

HP Choice Model

每一個 Character Level保存:

hp_method
hp_base_gain

hp_method

first_level
fixed_average
manual_rolled

hp_base_gain 是實際結果;Compile 時輸出 P0 hp_progression[]

Fixed average值與 legality來自 rules/content,不在 React重算。

Manual Rolled Result只是輸入既有骰子結果;P1 不建立 server dice roll event。

Downstream Invalidation

Draft PATCH 修改 earlier level時,Server re-resolve整份 progression。

建議策略:

  1. deterministic choice id相同且舊 selection仍合法 → 保留。
  2. 舊 selection不再是可選 option → selection標 stale / unresolved。
  3. validation回 blocking issue與 path。
  4. UI導航到 needs-review levels。

不要默默將不合法 option轉成另一個 default。


P1-D — ASI, Feat & Structural Choices

Reusable Choice Resolver

P1-D 完成 choices.py / features.py,使以下 source 都能輸出 canonical BuilderChoice:

Race
Subrace
Background
Class starting choice
Class feature
Subclass feature
ASI / Feat
future custom content

Resolver關注選擇結構,不負責 UI layout。

ASI Timing Adapter — ability_score_bonuses 是累計值

SRD data/srd5.1/levels.jsonability_score_bonuses 代表截至該 Class Level 累計取得的 ASI opportunity 數。不得使用 ability_score_bonuses > 0 判定『本級有 ASI』;那會讓第一次 ASI 之後的每個 level 都重複產生 ASI node。

對某一 class 的 Class Level L,本級新取得的 occurrence 固定由相鄰 Class Level delta 推導:

current = levels[class, L].ability_score_bonuses
previous = 0 if L == 1 else levels[class, L - 1].ability_score_bonuses
asi_occurrences_at_level = current - previous

規則:

ASI Record

Draft progression node保存 ASI / Feat branch choice;CharacterBuild最終保存 resolved Ability Scores與 feat_refs

為了 Version History / correction 可重建,P1可增加非-authoritative provenance / builder trace,例如:

build_choices / selection_records

若增加此欄位:

最簡單做法是 Compile 時同時產生 resolved fields與 deterministic selection trace,validation保證一致。

ASI / Feat Validation

Server檢查:

Effective Ability for Prerequisite

Structural prerequisite計算時使用:

resolved Build score at that progression point
↓
applicable Numeric Override
↓
effective numeric value
↓
prerequisite evaluation

但 Numeric Override不產生假的 ASI node / subclass node。

Validation 時點

不是只在 Confirm驗:


P1-E — Spellcasting Progression

Spellcasting Module

新增:

character_builder/spellcasting.py

必要時擴充 P0 domain/rules/spellcasting.py,但要區分:

不要把兩者混成一個巨大函式。

SpellcastingProfile DTO

概念:

SpellcastingProfile
├─ profile_id
├─ source_type
├─ source_key
├─ class_ref
├─ ability_ref
├─ access_model
├─ max_spell_level
├─ known / prepared / spellbook limits
└─ resource_pool_type

Profile由 Build progression + content/rules derive,不靠 frontend猜。

Spell Access Entry IDs

P0 SpellAccessEntry.entry_id 是 Build內 logical stable id。

P1 compiler產生 deterministic entry id,應至少包含 source identity與 spell key,避免同 spell different source collision。

概念:

<class-or-feature-source>::<spell-key>::<access-kind>

實際格式可調整,但 Build版本內唯一且重新 Compile同樣 choice應穩定。

Wizard Spellbook

Draft保存 Wizard spellbook selection provenance;Compiler輸出:

SpellAccessEntry(access_type="spellbook")

Initial prepared choice保存於 Draft initial-state seed,Create Confirm後轉成:

CharacterState.prepared_spell_entry_ids[]

不得在 CharacterBuild.spell_access_entries建立 prepared access type。

Prepared Caster Eligibility

對 Cleric / Druid / Paladin等:

如果 P0 prepared_spell_entry_ids 的「必須指向 Build access entry」語意不足以承載 prepared-from-full-class-list caster,P1-E 必須正式修正模型,不能為了遷就 P0 fixture把 Cleric全 spell list複製成幾百個 Build access entries而失去語意。

建議做法之一:

PreparedSpellSelection
├─ spell_key
├─ source_profile_id
└─ source_access_entry_id?   # spellbook / explicit access時可有

並提供 migration / compatibility adapter讓 P0 Wizard fixture仍可讀。

具體最終 schema在 P1-E實作前先以現有測試與所有 SRD caster需求驗證,選擇最簡單而語意正確者。

Known Casters

Known spell selection直接編譯為 Build known access entries。

Level Up replacement:Draft記錄 remove old known / add new known的選擇意圖;Confirm產生完整新 Build snapshot,不修改舊 version。

Always Prepared / Granted

由 content/rules resolver自動或經 feature choice產生:

access_type = always_prepared / granted

如果需要 counts_against_prepared_limit 等 metadata,P1-E正式擴充 SpellAccessEntry或 profile metadata,不靠 UI寫死 domain spell名字。

Multiclass Spell Slot Capacity

新增 pure function:

calculate_multiclass_spell_slots(build/progression, rules_data)

輸出 capacity read model,不直接寫 current used/remaining。

一般 slot capacity與各 profile spell access分開。

Pact Magic

使用獨立 resource pool key / DTO,例如概念:

resource_pool_id = pact_magic:<source-key>

P0 CharacterState.resources 可承載 class-specific resource;若要讓 Character Sheet清楚表示 slot level / count,P1-E可擴充 State / Sheet DTO,但不把 Pact Magic塞入一般 spell_slots[level]當同一 pool。

Upstream normalization trap: SRD levels.json 的 Warlock 也使用 spell_slots_level_1spell_slots_level_9 欄位;例如 Warlock Lv3 以 spell_slots_level_2 = 2 表示兩個二環 Pact Magic slots。欄位名稱本身無法區分 normal spell slots 與 Pact Magic。

因此 adapter 順序固定:

class / spellcasting source identity
→ classify resource_pool_type (normal_multiclass_slots | pact_magic | other)
→ parse that source's level row slot-shaped fields
→ only normal_multiclass_slots may enter combined-slot aggregation

Built-in SRD Warlock 可由 stable class identity(例如 srd5.1:class:warlock)映射到 pact_magic,但這個 source-specific mapping 必須封裝在 content/rules adapter;不要在 combined-slot calculator、API、React 等處散落 if class == warlock。未來 custom class 也應以 profile / rules metadata 分類,而不是靠英文名稱。

Resource Capacity vs Usage

P0 ResourceCounter 同時保存 used + remaining。P1-E / P1-G reconciliation需要知道 Build-derived capacity。

P1應建立單一 capacity calculation truth:

Build -> ResourceCapacity
State -> current usage
Sheet -> capacity + usage -> remaining

若維持 P0 State的 used + remaining欄位,所有 mutation / reconciliation都必須由同一 Server helper重新驗證 used + remaining == capacity;不要讓兩者逐漸漂移。

若 migration成只保存 usage更簡單,可在 P1-E做,但必須保留 P0 persistence migration與 API compatibility測試。


P1-F — Equipment, Review & Character Creation

Equipment Resolver

新增:

character_builder/equipment.py

輸入 SRD class/background structured options,輸出 canonical BuilderChoice。

必須支援目前 SRD data實際出現的:

counted_reference
choice
nested choice
equipment_category
quantity
automatic equipment

不要只針對 P0 fixture 的 Chain Mail / Shield寫特殊 case。

Starting Equipment Compile

Draft selections → P0-compatible:

CharacterBuild.starting_equipment[]

entry_id deterministic / unique。

Initial live inventory由 Starting Equipment compile結果複製一次

starting_equipment
↓ create confirm only
InventoryEntry[]
↓
CharacterState.inventory_state

之後兩邊完全分離。

Initial CharacterState Builder

P1-F 建立:

build_initial_character_state(build_candidate, initial_state_seed)

至少初始化:

不因 Builder Confirm自動建立 Room / Campaign state。

Review DTO

Server提供 BuilderReviewDTO,包含:

identity
race/background
level progression
subclasses
abilities + overrides
proficiencies / skills
features / feats
spell profiles / access / initial prepared
starting equipment
calculated summary
issues
can_confirm

Frontend不要自己將 raw Draft拼成一份可能與 compiler不同的 Review。

Version Metadata Baseline — P1-F Migration Ownership

P1-F 已經會建立 Builder-produced Character Version 1,所以 version metadata schema 不能延到 P1-G 才出現。P1-F migration 應在現有 character_versions 上建立至少:

version_kind
parent_version_id nullable
superseded_by_version_id nullable
change_note nullable

Baseline semantics:

若最終採獨立 lineage table 而不是 superseded_by_version_id,也必須在 P1-F 先完成等價的 create / legacy baseline,讓 P1-F 與 P1-G 對 version identity 的語意一致。

Confirm API

新增:

POST /api/character-builder/drafts/{draft_id}/confirm

Create mode服務流程:

lock/load draft
↓
compile against current ContentRegistry
↓
full validate
↓
construct CharacterBuild
↓
construct CharacterState
↓
transaction:
  characters insert
  character_versions v1 insert
  character_states insert
  current_version_id update
↓
return Character / Sheet location

應重用 / 擴充 CharacterRepository,不要在 FastAPI router直接複製 SQL。

Repository Extension

P0 CharacterRepository.create_character() 已能 atomic建立 Character + v1 + State。

P1-F優先重用它;必要擴充 transaction / metadata時,將版本建立能力抽成 repository method,不要繞過現有 reference/state validation。

Failed Confirm

Compiler / validation / DB failure:

Post-confirm Draft

Create confirm成功後可:

P1偏向 delete已完成 Create Draft即可,正式 truth已進 Character Version;若為了避免 double-submit需要狀態,可保留最小 confirmed marker / idempotency contract。

重點是 double-click confirm不得建立兩隻角色。


P1-G — Level Up & Character Versions

Character Version Metadata

P0 character_versions 目前:

id
character_id
version_no
build_payload
created_at

P1-F 已先完成 version metadata baseline 與 P0 legacy backfill。P1-G 沿用同一 schema,開始填入 parent / level-up / edit / correction lineage;不得在此再新增第二套版本分類。

不要把 Current State塞進 version table。

如果 P1-F 最終採獨立 lineage table 而不是 superseded_by_version_id,P1-G 就沿用該 table;核心契約仍是 Build payload immutable、history可追 lineage。

Version Kind

至少:

create
level_up
build_edit
correction

P0既有 version migration / read為 legacy;P1-F Builder Create 才使用 create。P1-G 新版本使用 level_up / build_edit / correction

Draft Initialization from Build

Level Up:

load current PersistedCharacter
↓
create BuilderDraft(mode=level_up)
↓
base_version_id=current_version_id
↓
seed choices from current Build / builder trace
↓
append target level = current + 1

若舊 P0 Build沒有完整 builder trace,P1需提供 adapter從 resolved Build fields產生可編輯的最低可重建 Draft;不能要求刪掉舊角色重建。

Level Up API

可以沿用統一 Create Draft endpoint:

POST /api/character-builder/drafts
{
  "mode": "level_up",
  "character_id": "..."
}

或提供 convenience endpoint:

POST /api/characters/{id}/level-up-draft

不論 URL,最後必須走同一 BuilderService。

Stale Base Guard

Confirm transaction內重新查:

characters.current_version_id == draft.base_version_id

不相等:reject stale_build_version

不能只在 Draft開啟時檢查。

Create New Version

Repository新增 transaction method概念:

create_build_version_and_reconcile_state(
    character_id,
    expected_base_version_id,
    new_build,
    reconciled_state,
    version_kind,
    ...
)

transaction內:

  1. lock / check current version。
  2. validate new Build refs。
  3. validate reconciled State against new Build。
  4. insert new version no = current + 1。
  5. update current_version_id。
  6. update current state。
  7. write lineage metadata。

全部成功才 commit。

Reconciliation Service

新增 pure domain:

character_builder/reconciliation.py

輸入:

old_build
old_state
new_build
old_sheet / calculated capacities
new_sheet / calculated capacities

輸出:

StateReconciliationPreview
├─ proposed_state
├─ changes[]
├─ blocking_issues[]
└─ warnings[]

Confirm 使用同一結果重新計算,不接受 frontend直接 POST arbitrary reconciled state。

HP Reconciliation

Server:

old_damage = max(0, old_max_hp - old_current_hp)
new_current_hp = clamp(new_max_hp - old_damage, 0, new_max_hp)

這是 Level Up / Build edit的 reconciliation,不是 healing transaction。

Spell / Class Resource Reconciliation

對每個 resource pool:

old_capacity
old_used
new_capacity

原則:

new_used = min(old_used, new_capacity)
new_remaining = new_capacity - new_used

若 capacity下降導致 old_used > new_capacity

一般升級 capacity增加時,舊 used數不變,所以新增 capacity成為 available。

Hit Dice Reconciliation

普通 Level Up:

Build correction造成 die type / total下降時,依 total invariant clamp並在 preview說明。

Prepared Spell Reconciliation

Server用新 Build spell eligibility重新驗 existing prepared selections:

不 silent drop。

Inventory / Other State

Inventory entry ids、quantity、equipped/carried原樣帶過,再用新 Build下的 state validation檢查。

Armor proficiency改變不代表自動丟裝備;角色可以攜帶甚至穿著不熟練裝備。P1不要因 Build edit擅自移除物品。

Version History API

至少:

GET /api/characters/{id}/versions
GET /api/characters/{id}/versions/{version_no-or-id}

History read不回 Current State snapshot,因 Character Version不是 live save state。

Version History UI

建議:

/characters/:characterId/versions

顯示 summary與current marker;可查看 build snapshot / summary。

P1不做 Git branch UI、不做 Restore Current State。

Build Edit / Correction

P1-G建立同一版本化管線,但 UI先保持簡單:

未來 P2 permission layer包住同一 service。


P1-H — Full P1 Integration & Closeout

CI / Regression

P1-H將 CI 升級為 P1 Full Regression,至少包含:

backend pytest
ContentRegistry / builder rules data validation
Alembic from fresh DB
Alembic upgrade from P0 schema
frontend TypeScript build
Vitest
Playwright real backend
Docker Compose readiness
P0 regressions
P1 builder fixtures / E2E

Deterministic Builder Fixtures

建立 P1 fixture helper,不依靠人工點 UI先建資料。

fixture來源可分:

Draft fixture
Expected CharacterBuild
Expected CharacterState
Expected Sheet summary

測試要比較 compile結果,不只看 can_confirm = true

Full Create Flow

至少有 real-backend Playwright:

Character Workshop
→ Create
→ complete Builder
→ Review
→ Confirm
→ Character Sheet
→ reload

Full Level Up Flow

至少:

existing deterministic character
→ Level Up
→ make level choices
→ reconciliation preview
→ Confirm
→ Version History shows N+1
→ Character Sheet reflects new Build
→ Current State preserved per contract

No P2 Dependency

P1-H必須證明在沒有:

Room
Campaign
Session
Seat
AI token
MCP server
Combat
Adventure

的情況下仍能 Create / Manage / Level Up Character。


3. API / Error Contract 共通原則

P1 API 延續 P0 machine-readable error方向。

Builder domain error建議 code例如:

draft_not_found
stale_draft_revision
missing_required_choice
invalid_choice
prerequisite_not_met
invalid_multiclass
invalid_subclass_timing
invalid_asi_choice
feat_prerequisite_not_met
invalid_spell_choice
invalid_prepared_selection
invalid_equipment_choice
cannot_confirm_draft
stale_build_version

實際 code可在實作時微調,但 tests必須對 machine-readable code做 assertion;不要只 assert英文錯誤句子。


4. Transaction / Authority 原則

所有正式 write:

HTTP intent
↓
BuilderService / CharacterService
↓
Domain resolve + validation
↓
Repository transaction
↓
Authoritative persisted state
↓
DTO response

禁止:


5. Migration / Backward Compatibility

P1 migration必須支援從目前 P0 schema直接升級。

至少保證:


6. P1 不建立的技術系統

P1 不建立:

如果實作某個 P1功能只需要一個小 adapter,不要因此提前長出後續 Phase architecture。