adventure-table

P4 — 開發設計方針

Phase:P4 — Quick Combat
本文件是 P4 的具體實作契約。產品行為以 實作規格.md 與根目錄 規格企劃.md 為準;測試與 closeout evidence以 測試指南.md 為準。

最後更新:2026-09-13


1. P4 Subphase 順序

三份 P4 文件固定使用:

P4-A — Monster & Combatant Foundation
P4-B — Combat Lifecycle, Initiative & Action Economy
P4-C — Attack, Damage & Core Action Resolution
P4-D — Spells, Conditions, Concentration & Reactions
P4-E — Quick Combat UI, DM Adjudication & AI Tool Surface
P4-F — Full P4 Integration & Closeout

每個 Subphase完成 code + tests + static review後才進下一段;需要 Actions時依 AGENTS.md gate執行。


2. 依賴方向與既有 substrate

P4必須建立在 P3已完成的 actor / event / roll / Current State substrate上:

Human Web UI ─┐
              ├─> Combat application services ─> Rules / Character Current State
MCP Adapter ──┘                 │
                                ├─> P3 RollGroup / RollRequest
                                ├─> P3 TableActorContext / DM proxy
                                └─> P3 Session event stream

Persistence <─ Combat application services

Standalone Character Core ─X→ Combat multiplayer modules
MCP Adapter              ─X→ raw repository mutation

固定原則:


3. Campaign-scoped Combat canonical model

因 Session End不結束 Combat,canonical Combat不得以 session_id 作 ownership root。建議最小模型:

combats
  id UUID PK
  room_id FK
  campaign_id FK
  mode = quick | tactical   # P4只建立 quick;保留P5可演進shape
  status = active | ended
  round nullable int
  current_turn_entry_id nullable FK
  revision bigint
  created_in_session_id nullable FK   # audit only
  ended_in_session_id nullable FK     # audit only
  started_at
  ended_at nullable

combat_entries
  id UUID PK
  combat_id FK
  kind = character | monster
  character_id nullable FK
  monster_instance_id nullable FK
  initiative nullable int
  initiative_group_key nullable
  initiative_tiebreaker / order_key
  turn_order_index
  surprise_state
  action_used bool
  bonus_action_used bool
  reaction_used bool
  attacks_used int
  attacks_allowed int
  ready_state nullable JSON/typed columns
  status
  position_note nullable text
  visibility
  joined_round nullable int
  removed_at nullable

實作可以調整命名/正規化,但必須滿足:

建議 unique invariant:

UNIQUE one active combat per campaign

PostgreSQL可用 partial unique index;SQLite standalone不建這些表。

3.4 Shared Character Current State 演進

P4 不能把所有 Combat state都塞進 combat_entries。ownership固定為:

Character Current State(shared character track / Standalone 可攜)
  current_hp / temporary_hp
  persistent conditions
  exhaustion_level
  concentration
  death_save_state
  persistent temporary_effects
  slots / class resources / inventory

combat_entries(web multiplayer track)
  initiative / order
  action / bonus / reaction availability
  attacks_used / attacks_allowed
  surprise
  ready action / reaction window
  position_note
  combat-only temporary bookkeeping

Concentration / DeathSaveState / TemporaryEffect 等 shared DTO不得保存 Room / Campaign / Session / Combat FK;只使用 Character自身資料、stable content/rule refs與可攜 scalar metadata,避免 Character Core反向依賴多人層。

Character JSON v1的 envelope目前把 current_state.state_payload視為 payload object;P4的新增 Current State欄位採 additive/default-safe演進,不改既有 v1欄位語意,因此不因 P4 自動升成 v2。Importer仍必須接受既有 locked v1與最後 M03 unstable fixture;Web / Standalone都要能保存、export、import新欄位。若日後需要破壞 v1既有欄位語意,再另開 schema version,不在 P4暗改。

Character persistence migration若需要新欄位/JSON shape,落在 shared character Alembic track;Combat / Monster Instance tables才落 web track。P4 implementation同時擴充 M03 standalone schema-parity / import-boundary regression。


4. P4-A — Monster & Combatant Foundation

4.1 Content data

SRD Monster / Beast走正式 data/ content pipeline。不要在 runtime parse docs/暫用規則資訊、Markdown或 hardcoded Python dict。

P4-A的 authoritative upstream固定沿用 repo既有 SRD provenance:

repository: 5e-bits/5e-database
commit: ce47a18dfeb3e41a1b2a2dfe00a25761c3c3a4f1
path: src/2014/en/5e-SRD-Monsters.json
expected_records: 334
expected_beasts: 87

Beast不是另一份 dataset;是上述 334 records中 type == "beast" 的 87 筆 subset。Authoring/import工具把 pinned source materialize成 repo內正式 data/srd5.1/ runtime資料,並 checked-in manifest記錄 source repo / commit / path / expected records / expected beasts(可再記 source blob/hash);runtime與tests都以 materialized data + manifest為真,不在啟動時抓 GitHub。

Locale scope同 P4-A一起鎖死:

Monster Template建議沿用現有 content registry的 stable key / pack pattern:

srd5.1:monster:goblin
srd5.1:monster:wolf
...

實際 key convention以現有 registry規則為準,不為 P4另外造命名系統。

Template結構建議拆:

MonsterTemplate
  identity / taxonomy
  armor_class
  hit_points { average, formula }
  speed
  abilities
  saves / skills
  damage_resistances / immunities / vulnerabilities
  condition_immunities
  senses / languages / cr
  traits[]
  actions[]
  bonus_actions[]
  reactions[]
  legendary_actions[]
  spellcasting[]

Action不要只存純 description。常見 attack / save / damage欄位應 machine-readable:

MonsterAction
  name
  kind = attack | save | heal | multiattack | other
  attack_kind = melee_weapon | ranged_weapon | melee_spell | ranged_spell | null
  attack_bonus nullable
  reach_or_range_text nullable   # P4展示;不做geometry calculation
  target_text nullable
  save_ability nullable
  save_dc nullable
  damage_parts[] { dice, flat, damage_type }
  condition_effects[]
  recharge nullable
  resource_cost nullable
  description
  automation_level = structured | partial | dm_adjudication

automation_level不是 CRPG script engine;它只是告訴 application/UI「可直接 resolve到哪裡」。

4.2 Monster Instance

建議 durable world/combat instance:

monster_instances
  id
  room_id
  campaign_id
  template_ref nullable
  display_name
  max_hp
  current_hp
  temp_hp
  armor_class
  speed_text / speed_structured
  ability_scores
  quick_attacks JSON/child rows
  status
  visibility
  position_note nullable
  conditions/effects via shared state model
  resources/recharge via typed state
  created_at / updated_at

Quick Enemy可 template_ref = null。Template-derived instance在建立時要保存足夠的 resolved combat stats,避免 content pack後續更新讓進行中的敵人瞬間改 HP / AC;但 source template ref保留 provenance。

不要把 initiative / turn order永久放 Monster Template;那是 Combat Entry state。

4.3 Secrecy projection

建立單一 CombatantProjector /等價 projection service:

DM -> full combatant view
Player -> safe combatant view

Player view不得含精確敵人 HP / AC / hidden resources。模糊傷勢從 current/max HP即時計算,不另存 writable wound_label truth。

所有 REST / Resume / event / MCP context共用 projector,避免「UI隱藏了但 MCP拿得到」。


5. P4-B — Combat Lifecycle, Initiative & Action Economy

5.1 Combat service

建議集中在 actor-neutral application service:

CombatService
  start_quick_combat(actor, input)
  get_active_combat(actor)
  add_character(actor, ...)
  add_monster(actor, ...)
  request_initiative(actor, ...)
  submit/complete initiative via RollService
  resolve_initiative_order(actor, ...)
  start_turn(actor, ...)
  advance_turn(actor, ...)
  end_combat(actor, ...)

真正 API / MCP adapter只做 DTO與auth resolution。

5.2 Initiative整合

Initiative不要另存 opaque random integer而失去 roll audit。每個 initiative result應能 reference既有 RollRequest / RollResult;monster group initiative可讓多個 combat entries reference同一 initiative group result。

Tie:

5.3 Turn state machine

最小狀態:

combat inactive/active/ended
active -> initiative_pending -> running
running:
  round >= 1
  current_turn_entry_id

若實作不需要獨立 initiative_pending status可用 derived state,但不得在 initiative未完成時假裝已有 Current Turn。

每次新 Turn重置該 combatant:

不要在 UI自己 reset。

5.4 Action economy validator

所有正式 combat actions先通過:

CombatActionContext
  acting_actor
  subject_combatant
  action_kind
  economy_cost
  current_turn / reaction_window

Player self、DM proxy、DM控制敵人只在 permission resolution不同;後面的 economy validator相同。

Freeform Action可以記錄 economy_cost 或由 DM指定 none/action/bonus/reaction,但不能用 freeform偷偷繞過資源/turn invariant去執行已結構化的 Attack / Cast。

5.5 跨 Session Combat entry 與 controller rebinding

Combat是 Campaign-scoped;entry的 subject identity以 Character / Monster Instance為準,不以某一場 Session Seat snapshot為準。

新 Session resume active Combat時:


6. P4-C — Attack, Damage & Core Action Resolution

6.1 Combat transaction

建議每個正式 resolve入口使用單一 transaction boundary:

validate actor/current turn/economy
resolve source action definition
create/resolve required RollRequest(s)
compute outcome
apply damage/healing/state
consume action economy/resources
append durable event(s)
commit once

若 formal roll需要跨 request等待 Player擲骰,則 PendingAction / CombatAction先進 waiting_for_roll;最終 resolve transaction需有 compare-and-set / status guard,避免兩個 callback都 apply damage。

6.2 Attack definition normalization

Character weapon / feature attack與Monster action需 normalize成相同 runtime shape:

ResolvedAttack
  source_ref
  attack_bonus
  attack_kind
  damage_parts
  crit_behavior
  ability/source metadata
  notes

Rules resolver從 Character Build / Current equipment或 Monster Instance action產生,不把 Character logic複製進 Combat controller。

6.3 Damage pipeline

建議順序:

raw damage parts
→ crit dice expansion
→ per-type resistance / immunity / vulnerability
→ flat / special adjustments
→ Temp HP absorption
→ Current HP
→ concentration trigger if applicable
→ 0 HP consequences

每一層保存足夠 audit detail供 Log展開,但不要把整個 Character snapshot塞 event。

Damage type與 resistance資料用 stable enum/key;顯示名稱走 locale data。

6.4 Manual HP UX 與 semantic mutation

P3既有 TableCharacterStatePatch.current_hp 是 absolute Current State patch;它不能在 active Combat中被當成「受到 N 點傷害」的替代品,因為 absolute target HP無法可靠推導:

因此 active Combat的正常 gameplay入口統一成 semantic command:

apply_damage(target, amount, source...)
apply_healing(target, amount, source...)

實際 function / DTO名稱可依 codebase調整,但 Attack / Spell、Player自行扣血 UI、AI DM、DM proxy都必須進同一 damage/healing application service。UI仍可維持桌上習慣的「扣 7」快速操作,不要求 Player走大型 Attack workflow。

Human DM Direct Edit是例外的 correction path:可以 absolute set HP / Temp HP / condition / economy,用於修正桌上錯誤;它不反推成 damage、也不自動觸發 Concentration或0 HP transition,並照既有規格記錄正式 edit event。

active Combat中一般 Player / AI Player不得以 raw current_hp patch繞過 semantic pipeline;outside Combat的既有 Current State手動編輯可維持原行為。

6.5 Idempotency

每個 write tool / REST action接受 idempotency_key(沿 P3慣例)或有等價 command id。至少保證:

DB-level unique / state transition guard要有測試,不只 application if-check。


7. P4-D — Spells, Conditions, Concentration & Reactions

7.1 Spell resolver

不要建立第二套 Spell資料。P4只讀既有 Character spell access / prepared / resource pools與 content spell definitions,新增 combat-facing resolver:

ResolvedCombatSpell
  casting_source
  spellcasting_ability
  attack_bonus / save_dc
  slot/resource options
  upcast choice
  targeting_mode
  automation_level
  concentration
  effects[]

casting_source 至少允許兩種正式來源:

若現有 SRD spell data不足以 machine-resolve某 spell,P4-D補的是正式 content fields / adapters,不是在 UI hardcode spell name。

7.2 AoE adjudication

Quick Combat沒有 geometry。對 AoE spell:

  1. acting user選/提議 affected targets。
  2. 若當前 authority可直接裁定(DM自己施法或 DM proxy),可直接 confirm。
  3. Player提出時,若 affected set屬空間裁定,建立 DM adjudication request。
  4. confirm後建立 group saves / damage resolve。

Server只驗 target identities合法、沒有跨 Room/Campaign;不計算「半徑20呎內」誰真的在範圍。

7.3 Conditions / Effects

建議 shared typed modifier contribution,而不是 condition name到處寫 if:

ConditionDefinition
  stable key
  mechanical contributions
  action restrictions
  save/attack/check modifiers
  notes / partial automation flags

但 P4不能因此打造任意 user scripting DSL。SRD 2014 Conditions逐個用 typed code / data adapter即可。

Temporary Effect採可序列化 typed contributions,未能結構化部分保留 note;未知 note不得偷偷被 Rules Engine猜成 modifier。

7.4 ReactionRequest

沿用 P3 PendingAction / durable event思想,建議:

combat_reaction_requests
  id
  combat_id
  trigger_kind
  source_entry_id
  eligible_entry_ids
  target_entry_id nullable
  payload safe/secret split
  status = pending | accepted | declined | resolved | cancelled
  created_in_session_id
  expires_at nullable   # 不依 wall-clock自動 fiction timeout,僅技術選項

OA Quick trigger由 DM action建立;Tactical P5未來可由 movement engine建立同一種 request。

Ready也落到相同 request / reaction resolve service,不另造 Ready engine。


8. P4-E — Quick Combat UI, DM Adjudication & AI Tool Surface

8.1 REST / realtime API

可依現有 Session routes形狀新增:

/api/rooms/{room}/campaigns/{campaign}/combats/active
/api/.../combats/{combat}/...

具體 route可調整,但所有 mutation需同時知道/驗證 current Session actor;不能因 Combat campaign-scoped就允許「人在 Room但不在本場 Session」直接操作 active combat。

read projection可由 current Session resume附 active Combat摘要,再按需 fetch detail;避免每個 P3 event都重抓整場 monster data。

8.2 Combat events

建議 stable kinds:

combat.started
combat.combatant_added
combat.initiative_requested
combat.initiative_resolved
combat.turn_started
combat.action_declared
combat.attack_resolved
combat.save_resolved
combat.damage_applied
combat.healing_applied
combat.condition_changed
combat.concentration_changed
combat.reaction_requested
combat.reaction_resolved
combat.adjudication_requested
combat.adjudication_resolved
combat.ended

實際拆分可收斂,但不要用一個 combat.updated + 巨型 mutable payload取代可 audit event。

Player-safe event payload也要 projector;例如 combat.damage_applied可公開「Goblin受傷」與已公開 damage number,但不必附 goblin remaining HP。

8.3 DM Adjudication

建立通用但有限的 Combat adjudication,不是 P6世界 adjudication framework:

CombatAdjudicationRequest
  kind = range | cover | affected_targets | opportunity_attack | special
  action_id
  subject / proposed targets
  DM-only hints
  status
  decision typed fields + optional note

這個 model只服務 P4 Quick Combat缺 geometry的決策;P5可在有 geometry後減少產生,但不必刪 contract。

8.4 MCP tools

Tool數量保持小,偏高階 command,不把 repository CRUD全部暴露。建議候選:

DM:

get_combat_context
start_quick_combat
add_combatant
request_initiative / resolve initiative orchestration
perform_combat_action
resolve_combat_adjudication
advance_turn
end_combat

Player:

get_combat_context
perform_combat_action
respond_to_reaction

perform_combat_action schema過大,可按 Attack / Cast / Freeform拆工具,但各 tool仍只轉入同一 Combat action service。

不要提供:

set_enemy_hp_raw
set_current_turn_raw
set_combat_json

DM Direct Edit若需要 MCP,必須是明確 DM-only typed repair tool,且 AI DM是否允許依既有「AI DM不能 Undo」產品限制審慎處理;P4預設不要為 AI DM開 raw repair escape hatch。

8.5 Guide / briefing

M04 guide / tool descriptions / get_session_context.briefing同源規則繼續適用。Active Combat的 mandatory loop至少包含:


9. P4-F — Integration / migration / regression contracts

P4若新增 web migrations,建立 P4_POSTGRES_URL測試名稱可以,但 CI同時必須提供所有直接被跑到的 legacy env,避免 silent skip。至少 review P2 / P3 migration suites實際讀哪些 env後明確設定。

P4 full-stack workflow可沿用或演進既有 P3 Full-Stack E2E,但 phase closeout evidence必須能具名指出 P4 Combat journey的 workflow run;不可只說「某次 regression綠」。若建立新 workflow,名稱建議:

P4 Full-Stack E2E

9.1 Standalone boundary

同 P3:

9.2 P5 演進保留

P4不得先做 P5 spatial schema,但 Combat Engine的非空間 abstraction要讓 P5加 geometry而不是重寫:


10. 禁止的設計捷徑

P4不得: