Battle(战斗)
Battle 已提供运行载荷、生命周期、状态快照、通用规则管线,以及三个机制插槽的类型和输出校验。默认盘策略仍没有具体规则,因此默认实现下合法行动不会推进玩法状态或随机状态,也不会进入终局。
机制载荷最初按代码基线 9caf58c 登记;本页已补充后续提交中的输入校验与合成规则验证。契约声明以 src/core/battle/contract.ts 为准,运行载荷见 runtime.ts,机制类型与校验见 mechanisms.ts。
顶部信息表
| 项 | 内容 |
|---|---|
| 契约版本 | battle@1 |
| 当前状态 | 实现中 |
| 源码目录 | src/core/battle |
| 页面定位 | 说明框架已支持的运行边界、规则与机制交接,以及尚未交付的具体玩法 |
对外接口
| 名称 | 类别 | policy | 说明 |
|---|---|---|---|
battle:host | 服务 | trusted | start、dispatch、settle、stop |
battle:state | 服务 | trusted | snapshot() 返回当前状态的独立副本 |
battle:resolve-formula | 机制插槽 | trusted | 输入当前状态、行动、行动者与目标,输出事件列表 |
battle:resolve-turn-order | 机制插槽 | trusted | 输入当前状态与可行动单位,输出单位顺序 |
battle:resolve-targeting | 机制插槽 | trusted | 输入当前状态、行动、行动者与候选目标,输出目标子集 |
battle:on-turn | 通知 | — | 载荷为成功提交后的 BattleSnapshot |
battle:on-action | 通知 | — | 载荷为本次提交的 BattleAction 封装 |
battle:on-settle | 通知 | — | 载荷为首次结算的 BattleResult |
battle:disc-policy@1 | 实现位(single) | public | 默认 builtin,卸载后回落 |
battle:ai@1 | 实现位(single) | public | 内置搜索式条目,支持敌方单次与 AUTO 整组行动;卸载后回落 |
battle:damage-pipeline@1 | 实现位(single) | public | 当前空缺,无默认条目 |
运行载荷
type BattleData = Readonly<Record<string, unknown>>
type BattleResult = BattleData
interface BattleSpec {
readonly seed: number
readonly data: BattleData
}
type BattleAction =
| { readonly kind: 'immediate'; readonly action: BattleData }
| { readonly kind: 'turn'; readonly actions: readonly BattleData[] }
interface BattleFrame {
readonly data: BattleData
readonly random: { readonly algorithm: 'mulberry32'; readonly state: number }
readonly result?: BattleResult
}
type BattleSnapshot =
| { readonly status: 'idle' }
| { readonly status: 'active' | 'ended' | 'settled'; readonly frame: BattleFrame }外层封装已固定;data、单条行动与结果中的具体玩法字段仍由规则定义。框架不会根据字段名猜测生命值、技能、胜负或奖励。
| 字段 | 约束 |
|---|---|
seed、random.state | 0~4294967295 的整数;缺失、越界或小数均拒绝,不静默截断 |
random.algorithm | 只接受 mulberry32 |
data、action、每个 actions 元素、result | 可结构化克隆的非 null、非数组对象;允许 {} |
kind: 'turn' 的 actions | 非空有序数组;一次提交整组行动 |
frame.result | 由规则返回;start 或 dispatch 的顶层字段不能直接设置终局 |
即时操作使用 immediate。需要联合判定的一组选择使用 turn,整组合法后才提交。行动封装不预设盘数或规则数值。
开始战斗时必须显式给出种子;随机状态从该种子起步并进入快照。使用随机数的规则从当前状态计算,再把新状态随 BattleFrame 交回。框架不从时间或 Math.random 隐式取种子,也不为每次行动自动推进随机状态。
生命周期与通知
idle ── start(spec) ──> active
│ dispatch(action):规则可继续 active
└─ 规则返回 result ──> ended
│ settle()
v
settled
stop():清除本场状态,回到 idle| 调用 | 行为 |
|---|---|
start(spec): Promise<void> | 校验种子与数据,创建本场规则管线;已开始或没有可用盘策略时拒绝 |
dispatch(action): Promise<void> | 仅 active 可调用;将整个行动封装交给管线,校验返回的完整状态后提交 |
settle(): Promise<BattleResult> | 仅已进入终局后可结算;首次转为 settled,重复调用返回独立的同值结果副本 |
stop(): Promise<void> | 清除运行状态与本场管线;之后可重新开始 |
snapshot(): BattleSnapshot | 未开始或停止后返回 { status: 'idle' };其余状态带 frame |
规则返回 result 后状态变为 ended,立即拒绝新行动。settle() 固化规则已经给出的结果,不负责判断胜负、发奖励或写存档。
成功 dispatch 后依次广播 battle:on-action、battle:on-turn。前者保留本次提交的行动封装,后者反映提交后的状态。首次 settle() 广播一次 battle:on-settle;重复结算不重复通知。
读取快照、接收开始数据和行动、接收规则返回数据时都会进行结构化克隆,并冻结普通对象层。快照是独立副本;其中的二进制视图不能依靠 Object.freeze 变为不可写,应按只读数据使用。
规则管线与提交边界
盘策略提供以下接口:
interface DiscPolicy {
create(): RulePipeline<BattleFrame, BattleAction, BattleData>
}默认 builtin 创建 validate、resolve、settle 三个阶段。规则按阶段顺序、整数优先级从小到大、登记先后执行;同名规则拒绝登记,撤销后不再执行。规则可以返回新的 state、后续规则使用的 action,以及追加到管线的 events。
阶段名 settle 表示管线中的规则阶段,可以产出终局结果;服务方法 battle:host.settle() 在终局之后固化并返回结果。二者发生在不同时间。
一次 dispatch 只在管线完成、返回的 BattleFrame 整份校验通过后替换内部状态。规则抛错或返回非法随机状态等数据时,不提交新状态,也不发行动与回合通知。规则应返回新状态,不能依靠修改输入对象推进状态;这套提交机制不负责撤回同一 JavaScript 运行环境中扩展代码自行产生的外部副作用。
默认 builtin 已接入内置规则:完整回合、状态、技能、伤害、胜负与评价结果均由实际规则推进。调用方也可通过盘策略实现位替换整套规则;通用框架不从字段名猜测玩法。
三个机制插槽
机制插槽由盘策略的规则项在需要决策时调用。根插件提供具名句柄,但 start、dispatch 和 settle 不会自动调用三个机制,也没有替它们安装默认玩法实现。
三个机制插槽的改写受 trusted policy 约束:修改它们的 Mod 须由官方清单内发布者签名,或经用户在装载裁决中同意。规则项正常调用插槽不因此改变;替换整个盘策略仍通过 public 的 battle:disc-policy@1。
输入与输出
| 插槽 | 输入 | 输出 |
|---|---|---|
battle:resolve-turn-order | { frame, units } | { order, random? } |
battle:resolve-targeting | { frame, action, actor, candidates } | { targets, random? } |
battle:resolve-formula | { frame, action, actor, targets } | { events, random? } |
共同字段中,frame 为只读的 BattleFrame,action 为 BattleAction,actor 为字符串标识;单位、候选目标、目标和顺序均使用字符串数组。调用方负责准备有效输入,其中 units 应为非空、无重复的可行动单位集合。
三个输出都可交回 random: { algorithm: 'mulberry32', state: number }。如果机制消耗随机数,应从输入中的当前状态计算并返回新状态,再由规则项写入后续状态;省略表示这次不消耗随机数。输出校验只验证算法与数值范围,不替调用方推进或消费随机数。
输出校验
mechanisms.ts 提供三个显式校验函数,调用方应在接收机制结果后调用。它们不会因插槽名或泛型声明而自动执行,也不会直接改写 BattleFrame。
| 校验函数 | 拒绝条件与保留语义 |
|---|---|
readTurnOrder(input, output) | 先拒绝空、重复或非法单位输入,再要求 order 为输入单位的完整排列;缺项、重复、外来标识与空顺序均拒绝 |
readTargets(input, output) | targets 必须是候选集合的无重复子集;候选外目标拒绝,不静默过滤;允许空目标 |
readEvents(input, output) | 逐项校验事件字段与目标归属,保留事件先后顺序 |
空目标表示这次行动没有合法目标。规则项决定将其记为落空还是拒绝本次提交;单目标、多目标上限等限制同样属于规则。
公式事件的字段如下:
interface BattleEvent {
readonly type: string
readonly target?: string
readonly amount?: number
readonly tags?: readonly string[]
}type必须是非空字符串,没有内置枚举。- 提供
target时,必须属于本次输入的targets。 - 提供
amount时,必须是有限数;不在通用校验层预设正负或数值上限。 - 提供
tags时,各项必须为非空字符串且互不重复。 events可以为空;多条事件保持原有顺序,由规则项消费后形成新状态。
校验失败会抛错。规则项将错误传回 dispatch 时,本次提交被拒绝;框架不会替规则过滤错误事件后继续。内置默认规则已调用三个插槽并消费事件;Mod 对 trusted 插槽的改写与摘除均有审计记录。
兼容变化与验证范围
旧版接受任意对象的 start、dispatch 和快照消费代码需要改用本页封装;盘策略需要返回新的管线类型。三个机制插槽也已从不透明数据改为具体输入输出类型。当前契约标识仍为 battle@1,不能仅凭版本字符串判断旧调用是否兼容。
已提交用例分别覆盖:
test/unit/battle-runtime.test.ts:种子与行动校验、规则错误和越界返回拒绝、终局限制、重复结算、快照隔离,以及合成规则下的同序列重放。test/unit/battle-mechanisms.test.ts:非空且无重复的单位输入、顺序排列、目标子集、事件字段与随机状态校验;合成盘策略依次调用三个插槽,核对事件按序消费、随机状态交回与保持,以及外来标识或规则拒绝空目标时整次回滚。test/contract/battle.test.ts:根插件登记、空快照、生命周期拒绝、默认条目与非法操作回滚;完整规则、AUTO 与序列另有对应契约文件。
这些用例覆盖通用边界与当前内置玩法。公开示例使用合成数值;它们不证明私有数据校对、所有内容或所有平台均已验证。
当前未实现范围
battle:damage-pipeline@1仍为vacant;battle:ai@1已有内置搜索条目。- 战斗状态的 Save 归属、跨进程续玩、奖励与进度写入。
- 持久活动、匹配、排行、挑战次数与运营规则不属于当前战斗范围。
- Growth、Collection 与完整的 Phase 3 玩法出口。Explore 的会话内地图与道具范围另见独立接口页。
Battle 不直接依赖 Stage、Audio、Save 或 Shell;演出与界面联动需经各自公开接口实现。
相关链接
- 插槽与实现位:机制 hook、single 实现位、vacant 与 fallback。
- policy 与写边:
public实现位与trusted机制插槽的约束。 - 装载管线与 guard:盘策略登记与机制改写的准入流程。
- Phase 3 玩法与内容:战斗及后续玩法的阶段边界。
- Explore 大插件:地图、会话内道具与剧情导航。
- Growth 大插件:等级、强化、显式旗标与独立养成页。
- 规划中的大插件:Collection、Online 的登记状态。