feat(slice): 敌人技能池批收编——废确定性行为环改技能池+开轮摇号(冒烟 263 项全绿)

- 机制:每敌种技能池(池模板/实例池/技能行三层),开轮摇号定拍(behavior_roll 第五流,
  每敌子流 scope=spawn_index×1000000+摇号序,单敌轨迹独立复放);两道闸 = 全技能各自
  冷却(普攻恒 0 锁死)+ 保底 M=4;死亡即不动手改摇定作废不进账;拍结构保留
- 数据:新增 skills.tres+skill_table.gd(五行+保底阈值)/skill_lottery.gd 独立抽签器;
  enemy.tres 六行 pattern→pool(9/1·6/2/2·6/2/2 provisional);status_effects 七→九行
  (empower/fortify——enemy_defense 敌方防御池新词汇);settlement effect_pool_map 增两键
- core:rng_service 增流+open_scope(salt 0x85EBCA7758C24E15,黄金向量 m=42 scope0=
  426657203655691839);unit_container 重写(实例池/冷却/待发/保底);battle_orchestrator
  开轮摇号编排+三乘法链+施放行 cast+敌方防御池乘区;config_db 挂技能表+池跨表校验
- 呈现:快照 pending_* 三字段+敌方增益投影(beat/ring 拆除);敌卡摇定动画位(普攻零
  表示/攻击系⚔/增益系✦);明细具名技能+施放行;状态槽九行全映射
- smoke 211→263:六项验收(回放逐位/单敌独立复放/EV 对锚/死亡作废/空池不可能/加载拒绝)
- 文档:敌人系统 v0.5/战斗结算 v0.11/增益状态 v0.5/难度曲线与Boss v0.6/战斗表现 v0.3/
  HUD框架 v0.4/battle.md 改型注记/registry(skill 新条目+四条目修订+术语七词条)/
  06-待办清单收编;active.md entry 60
- EV 实测(×5000 拍):地精 1.1056(保底闸抬升,登记难度曲线重锚)/熊地精 0.9998/酋长 1.0236
This commit is contained in:
DKChan committed 2026-09-17 02:00:06 +08:00
1 parent d76a023fa9
commit 8ca3da669b
35 files changed
+1309 -356

No files matched your search

+1 -1
View File
@@ -31,7 +31,7 @@
| 文件 | 一句话概要 | 状态 |
|---|---|---|
| `战斗结算.md` | 轮末结算的编排与逐行兑现 | 已定稿·自旧体系迁移 |
| `敌人系统.md` | 敌人数据、行为环、意图呈现 | 已定稿·自旧体系迁移 |
| `敌人系统.md` | 敌人数据、技能池出招、意图呈现 | 已定稿·自旧体系迁移(2026-09-16 技能池批 v0.5) |
| `增益状态.md` | 增益/减益状态的容器、叠层与衰减 | 已定稿·自旧体系迁移 |
| `地图战斗资源.md` | 格面资源的产出、极性字典与清图奖励封装 | 已定稿·自旧体系迁移 |
| `战斗表现.md` | 战斗动画与演出(旧体系未单独成文) | 范围草案·待设计 |
+6 -4
View File
@@ -17,11 +17,13 @@ MVP 做到两关:第二层不新造结构,与第一层共用节点链模板
**三种**,含一个 Boss(enemy.tres 六行 = 第一关五场遭遇 + 第二层锚行):
| 敌人 | 定位 | 行为环 |
| 敌人 | 定位 | 技能池(权重) |
|---|---|---|
| 地精 | 教学杂鱼,可成群(1–3 个) | 只会攻击 |
| 熊地精 | 主力,可成群 | 攻击 → 蓄力 → 重击三拍循环 |
| 食人魔酋长 | 第一关 Boss,单个 | 攻击 → 攻击 → 蓄力 → 重击四拍循环 |
| 地精 | 教学杂鱼,可成群(1–3 个) | 普攻 9 · 突刺 1 |
| 熊地精 | 主力,可成群 | 普攻 6 · 蓄力 2 · 重击 2 |
| 食人魔酋长 | 第一关 Boss,单个 | 普攻 6 · 重击 2 · 硬化 2(硬化独占) |
出招 = 开轮摇号(2026-09-16 敌人技能池批——固定出招剧本废除,池语义见[敌人系统](systems/战斗循环/敌人系统.md))。
每种敌人配一档护甲(轻甲 / 魔法护甲 / 重甲),正好构成"轻甲 → 魔法护甲 → 重甲"的教学梯度。MVP 不加新敌种,也不做同深度多敌种混搭。
+59 -3
View File
@@ -154,13 +154,21 @@
### 敌人系统
#### 单拍行为环的体验验证
#### 单拍行为环的体验验证(改型:技能池节奏体验验证)
**现状**:地精恒定出手、无自然空窗的单拍环是否站得住,登记为预注册试玩验证项(playtest 证伪项),至今未执行
**现状**:原登记对象(地精单拍行为环)已随 2026-09-16 敌人技能池批整体退役——行为环废除后本条失去原载体;验证问题改型为"技能池出招的节奏体验":普攻基底 + 随机特殊技的纹理是否成立、保底 M=4 与冷却值是否制造可背诵节奏或过旱/过涝,随下次 playtest 批采集(关联:池 EV 含闸实测读数已登记敌人系统/难度曲线调参表)
**归属**:难度曲线与Boss 与敌人系统共裁
**状态**:待办
**状态**:待办(2026-09-16 改型)
#### 行为环废除与蓄力拍退役
**现状**:确定性出招剧本(行为环:拍枚举/环指针/同种锁步/heavy_multiplier)已整体废除,改技能池开轮摇号出招;旧"蓄力→猛击"固定配对(哥布林弹珠拷贝)随批退役,蓄力改型为纯攻击增益技、Boss 池改独占增益技"硬化"——本条即原"删行为环取消蓄力拍"登记的收编关闭(2026-09-16 用户拍板,设计权威 docs/proposal-skill-pool.md v1.0)
**归属**:敌人系统(主 owner,v0.5)
**状态**:已解决(2026-09-16)——落批见敌人系统 v0.5 / 战斗结算 v0.11 / registry 同批登记
#### 伏击量纲与上限的联动复核
@@ -218,6 +226,54 @@
**状态**:待办
#### 技能池情境条件触发器
**现状**:低血触发防御、玩家停手时的偷袭技等情境出招——载体 = 技能行触发条件字段(P1 字段位已预留);启用是敌人系统修订,不是调参
**归属**:[敌人系统](systems/战斗循环/敌人系统.md)(P1)
**状态**:待办
#### 个体化变体池
**现状**:A 型好斗 / B 型守势、精英专属池(技能池共识点 A 的 A2 案);现行同敌种同池模板,实例分歧只来自摇号涌现
**归属**:[敌人系统](systems/战斗循环/敌人系统.md)(P1)
**状态**:待办
#### 全局特殊间隔旋钮
**现状**:异名特殊技背靠背现行概率低(是变化不是失控);若实测仍嫌,备用闸 = 全局"特殊技后至少隔 N 拍"——登记备用,未实测不启用
**归属**:[敌人系统](systems/战斗循环/敌人系统.md) 与[难度曲线与Boss](systems/冒险循环/难度曲线与Boss.md)(P1)
**状态**:待办
#### 技能池新效应词汇批
**现状**:妨害技(对主角施减益)、固定值伤害分量、多段命中(每段一掷)——三类新效应词汇,技能行 P1 字段位已预留;启用随对应内容批
**归属**:[敌人系统](systems/战斗循环/敌人系统.md) 与[战斗结算](systems/战斗循环/战斗结算.md)共裁(P1)
**状态**:待办
#### 第二关技能池与投放
**现状**:第二关生效前提 = 深度轴行值填齐(关联条目:冒险循环「第二关深度轴行值未填充」);池内容(新技能/新敌种池)随投放批定,投放节奏归难度曲线
**归属**:[难度曲线与Boss](systems/冒险循环/难度曲线与Boss.md)(P1)
**状态**:待办
#### 敌方技能图鉴 / 学习呈现
**现状**:池与权重不公开(D4/D5)口径下的进阶信息面——玩家侧技能知识系统(见过 → 认得 → 记谱);首发三池学习成本低,后置
**归属**:[HUD 框架](systems/表现与支撑/HUD框架.md) 与[敌人系统](systems/战斗循环/敌人系统.md)(P2)
**状态**:待办
### 增益状态
#### 叠层不续命的读感
@@ -22,7 +22,7 @@
| 遭遇数量与攻击浮动 | 每个坐标行的敌人数量区间与攻击浮动宽度(掷定执行归[战斗结算](../战斗循环/战斗结算.md)) |
| 生存链区间模型 | 每战"打不打得过"的三档判定与关级通关恒等式——难度验收的现行模型 |
| 期望值带 | 通关率带、通关余量带、形态带——调参的"正确行为"基准 |
| Boss 设计 | 关底 Boss 的血量锚、数值台阶与行为环配型;精英修饰只留预留位 |
| Boss 设计 | 关底 Boss 的血量锚、数值台阶与专属池配型(独占技 + 权重轮廓);精英修饰只留预留位 |
| 调参联动纪律 | 禁独立调参网络与对账重跑义务的登记与执行 |
不负责:
@@ -54,8 +54,8 @@
| 轴 | 规则 | 说明 |
|---|---|---|
| 关间台阶(关卡序号) | 每关至少投放一项新内容(新敌种或既有敌种新行为环段),加基准数值跳升 | 台阶必须能归因到看得见的变化,不是纯数字膨胀;投放断档是内容生产缺口,调参修补不了 |
| 关内坡度(节点深度) | 同敌种数值平缓上移,深度也可表达为敌人成群 | 数量是比单体质变更优先的深度表达:保持行为环读数不变、总量受控 |
| 关间台阶(关卡序号) | 每关至少投放一项新内容(新敌种或既有池新技能),加基准数值跳升 | 台阶必须能归因到看得见的变化,不是纯数字膨胀;投放断档是内容生产缺口,调参修补不了(技能池批措辞修订 2026-09-16:行为环段 → 既有池新技能) |
| 关内坡度(节点深度) | 同敌种数值平缓上移,深度也可表达为敌人成群 | 数量是比单体质变更优先的深度表达:保持技能池 EV 读数不变(权重即旋钮、EV 重锚随批登记)、总量受控 |
| Boss 与精英修饰(预留第三轴) | Boss 走最深深度直填行;精英只有字段命名空间预留 | Boss 不走修饰表;修饰乘数表 MVP 为空表、零行为,首个使用场景 = 精英节点(随[关卡节点地图](关卡节点地图.md)定稿联动);数值所有权在本系统,[棋盘生成](../排雷循环/棋盘生成.md)只按表生成 |
**结论**:关内坡度 = 每深一档,同敌种生命按 5%–20% 线性上移;关间台阶 = 相邻关同敌种基准乘 1.3–1.6。
@@ -71,7 +71,7 @@ base(L+1) ≈ base(L) × k_L k_L ∈ [1.3, 1.6](仅同敌
| D | 节点深度,Boss = 本关最深深度 |
| s_hp / k_L | 每档增幅 / 关间倍率,域内取值、provisional |
MVP 行值并不逐式现算,而是按此式量级直填经切片验证过的行(见调参表);敌种切换不受此式约束——台阶值直接定。新敌种登场时基准按其行为环复杂度重定,关间倍率不适用于这种场景。
MVP 行值并不逐式现算,而是按此式量级直填经切片验证过的行(见调参表);敌种切换不受此式约束——台阶值直接定。新敌种登场时基准按其技能池复杂度重定,关间倍率不适用于这种场景。
### 生存链区间模型:一场战斗"打不打得过"的三档判定
@@ -91,7 +91,7 @@ MVP 行值并不逐式现算,而是按此式量级直填经切片验证过的
| 变量 | 含义 |
|---|---|
| 进场生命 | 主角进入本战时的生命;跨战斗持久,归[冒险结构](冒险结构.md)的 HP 账本 |
| 受伤峰值 / 谷值 | 敌方最坏 / 最好一轮的输出(攻击、拍别倍率、伏击附加、减伤、削弱的合计构成见[战斗结算](../战斗循环/战斗结算.md)战斗段) |
| 受伤峰值 / 谷值 | 敌方最坏 / 最好一轮的输出(攻击、技能倍率、池闸长期速率、伏击附加、减伤、削弱的合计构成见[战斗结算](../战斗循环/战斗结算.md)战斗段;技能池批口径——出招构成为摇号分布,峰值/谷值构成按含闸长期速率复核而非固定剧本拍) |
| 输出峰值 / 谷值 | 玩家最好 / 最坏一轮的总输出(分量构成同上) |
| 一次性伤害 | 清障炸药一类当场引爆的大额伤害 |
@@ -120,7 +120,7 @@ MVP 行值并不逐式现算,而是按此式量级直填经切片验证过的
### Boss 设计:食人魔酋长与关底血量锚
首发关底 Boss 是食人魔酋长,摆在每关最深深度行上。Boss 约定有三条:位置 = 最深深度直填行,不走修饰表;定位 = 本关最大的单场消耗;行为环 = 专属四拍环且含蓄力拍——蓄力空拍是玩家的规划窗口(行为环语义见[敌人系统](../战斗循环/敌人系统.md))。
首发关底 Boss 是食人魔酋长,摆在每关最深深度行上。Boss 约定有三条:位置 = 最深深度直填行,不走修饰表;定位 = 本关最大的单场消耗;技能池 = 专属池承载——独占技能 + 权重轮廓(现行:独占增益技"硬化"——敌方防御池词汇载体;2026-09-16 敌人技能池批改写,旧"专属四拍环且含蓄力拍"约定随行为环退役,增益 ≤1 纪律下旧蓄力拍不进 Boss 池;池语义见[敌人系统](../战斗循环/敌人系统.md))。
**结论**:Boss 生命 ≈ 全关普通战斗敌方生命期望合计 × 血量锚比率,比率域 [0.5, 1.1];Boss 攻击基线不低于本关普通战的最高行。
@@ -215,16 +215,25 @@ Boss 战的棋盘用本关最大板形(第一关为 12×12),板参数仍
敌人表行值(第一关五行 + 第二关锚行,全部 provisional):
| 坐标 | 敌种(数据键) | 生命 | 攻击(浮动区间) | 数量 | 行为环 | 护甲 |
| 坐标 | 敌种(数据键) | 生命 | 攻击(浮动区间) | 数量 | 技能池(权重) | 护甲 |
|---|---|---|---|---|---|---|
| 第一关 D0(教学位) | 地精 goblin | 24 | 7 [4,10] | [1,2] | 攻击 | 轻甲 |
| 第一关 D1 | 地精 goblin | 13 | 5 [2,8] | [2,3] | 攻击 | 轻甲 |
| 第一关 D2(首遇固定) | 熊地精 bugbear | 40 | 9 [6,12] | 1 | 攻击/蓄力/重击 | 魔法护甲 |
| 第一关 D3 | 熊地精 bugbear | 18 | 6 [3,9] | [2,3] | 攻击/蓄力/重击 | 魔法护甲 |
| 第一关 D4(Boss) | 食人魔酋长 ogre_chief | 150 | 9 [6,12] | 1 | 攻击/攻击/蓄力/重击 | 重甲 |
| 第二关 D0(锚行) | 熊地精 bugbear | 30 | 7 [4,10] | 1 | 攻击/蓄力/重击 | 魔法护甲 |
| 第一关 D0(教学位) | 地精 goblin | 24 | 7 [4,10] | [1,2] | 普攻 9 · 突刺 1 | 轻甲 |
| 第一关 D1 | 地精 goblin | 13 | 5 [2,8] | [2,3] | 普攻 9 · 突刺 1 | 轻甲 |
| 第一关 D2(首遇固定) | 熊地精 bugbear | 40 | 9 [6,12] | 1 | 普攻 6 · 蓄力 2 · 重击 2 | 魔法护甲 |
| 第一关 D3 | 熊地精 bugbear | 18 | 6 [3,9] | [2,3] | 普攻 6 · 蓄力 2 · 重击 2 | 魔法护甲 |
| 第一关 D4(Boss) | 食人魔酋长 ogre_chief | 150 | 9 [6,12] | 1 | 普攻 6 · 重击 2 · 硬化 2 | 重甲 |
| 第二关 D0(锚行) | 熊地精 bugbear | 30 | 7 [4,10] | 1 | 普攻 6 · 蓄力 2 · 重击 2 | 魔法护甲 |
重击倍率全员 2(provisional);单轮伏击授予上限 4(已定稿)。攻击浮动开战时掷定一次、整战持久;数量区间同流掷定,教学位(地精 ×[1,2])与首遇位都不豁免——没有区间上界的行自然取定值,那不是豁免,是区间宽度为零。
单轮伏击授予上限 4(已定稿)。攻击浮动开战时掷定一次、整战持久;数量区间同流掷定,教学位(地精 ×[1,2])与首遇位都不豁免——没有区间上界的行自然取定值,那不是豁免,是区间宽度为零。
技能池投值(池权重/冷却/保底的所有权本文,机制归[敌人系统](../战斗循环/敌人系统.md),全部 provisional;建议池行值随 2026-09-16 敌人技能池批落地):
| 参数 | 现行值 | 说明 |
|---|---|---|
| 池权重 | 三敌种见上表(地精 9/1 · 熊地精 6/2/2 · 酋长 6/2/2) | 权重即旋钮:抬特殊技权重 = 抬输出频率,EV 随之重锚 |
| 技能冷却(拍) | 突刺 2 · 蓄力 3 · 重击 3 · 硬化 4 · 普攻恒 0 | 普攻冷却恒 0 不可配(空池不可能的结构前提——域校验锁死) |
| 保底 M | 4(全局单旋钮,技能表 pity_threshold) | 连续 M 拍执行普攻 → 下拍必出特殊技;地精这类单特殊技低权重池受保底支配(突刺率 10% → ~22%) |
| 池 EV 锚 | 静态份额:地精 1.05 · 熊地精 1.00 · 酋长 1.00 | 含闸长期速率实测(2026-09-16 实施批 ×5000 拍):地精 1.106 / 熊地精 1.000 / 酋长 1.024——EV 重锚与权重投值随本表修订 |
主角表与棋盘参数表(provisional):
@@ -274,3 +283,4 @@ Boss 战的棋盘用本关最大板形(第一关为 12×12),板参数仍
| v0.3 | 2026-09-15 | 雾格残留清理:关闭「待拍板」的雾相关供给验算失义条目(原供给与对账验算含雾板分层、雾成本负担比等输入分量);第二关新内容投放缺口条目改为引用「已从设计中移除的机制」(见不做清单)。曲线规则、供给带与对账义务不变 |
| v0.4 | 2026-09-15 | **护甲等级批(与战斗结算 / 敌人系统 / registry 同批登记)**:生存链区间模型补记护甲等级掷点带宽口径——峰值/谷值构成各含最坏/最好掷点带宽;EV 中性引入成文(期望伤害对齐现行表值、现行行值不重调,折扣档位与掷点形式定稿前区间扫描维持全额口径、带宽项随定稿批补扫,"零红档"验收与三档判定式不变);调参联动表新增"护甲等级 ↔ 攻击基线 ↔ 区间带宽"一行 |
| v0.5 | 2026-09-16 | **护甲等级掷点批·定稿落地(与战斗结算 / 敌人系统 / registry / 切片代码同批)**:带宽口径改写为"换装点锚定"——掷点形式与档位定稿(d20 / 缺口 1-3→×0.75 · 4-7→×0.5 · 8+→×0.25 / nat20 暴击 ×2 恒全额),基准 AC10 自带约 −21% 敌方输出期望(AC11 25% / AC12 28.75%),对比退役的 defense_up 满层 −60% 减伤墙为结构性削弱 + 伤害下限使零承伤轮结构性消失,方向性偏移与加难杠杆扫描合并处理、不单独重调行值;带宽补扫义务含暴击档与折扣档(并入加难扫描批);护甲等级行值:主角 10(暂定)、敌方首关 0(暂定,正式行值归怪物设定批);调参联动表"护甲等级"行改定稿口径;调参表主角表行与状态强度行同步(减伤 −30 → 护甲强化 +1 AC ×2 层) |
| v0.6 | 2026-09-16 | **敌人技能池批(与敌人系统 / 战斗结算 / 增益状态 / registry / 切片代码同批)**:曲线纪律两处改写(关间台阶"既有敌种新行为环段" → "既有池新技能";关内坡度"保持行为环读数不变" → "保持技能池 EV 读数不变〔权重即旋钮、EV 重锚随批登记〕");Boss 约定改专属池承载(独占技"硬化" + 权重轮廓,旧"专属四拍环且含蓄力拍"退役);调参表敌人行值表"行为环"列改"技能池(权重)"列并落建议池行值,新增"技能池投值"四行(池权重 / 技能冷却 / 保底 M / 池 EV 锚——含闸长期速率实测登记:地精 1.106〔保底闸抬升〕/ 熊地精 1.000 / 酋长 1.024);重击倍率注记随倍率归技能行删除;区间模型受伤峰值/谷值构成改按含闸长期速率复核口径;行值零改动(EV 中性锚定维持,重锚与权重投值随加难扫描批合并处理) |
+21 -12
View File
@@ -133,9 +133,9 @@
容器还有一条快照纪律:实例的幅度、初始次数、量纲在授予那一刻从表行拷贝。战斗中配置表即使被重载,已授予实例不受任何影响,只影响后续授予。
### 现在有哪些状态:MVP 六行内容
### 现在有哪些状态:现行九行内容
MVP 阶段状态表恰有六行:两个主角增益(挖安全格拿)、三个敌方效果(踩雷的伏击增强、标中旗的削弱、标空旗反噬的暴走)、一个主角减益(标空旗反噬的失衡)。六行全部是一次性、全部用叠加合并;数值全部 provisional(临时值,过锚验收后才定稿)。
现行状态表恰有九行:两个主角增益(挖安全格拿)、一个主角减益(标空旗反噬的失衡)、一个主角削弱(乏力——雷格负向的 weaken 型)、四个敌方效果(踩雷的伏击增强、标中旗的削弱、标空旗反噬的暴走、敌方技能池的蓄力/硬化增益)、一个敌方防御增益(硬化)。全部用同构实例承载;数值全部 provisional(临时值,过锚验收后才定稿)。
| 字段 | 取值 | 说明 |
|---|---|---|
@@ -148,14 +148,19 @@ MVP 阶段状态表恰有六行:两个主角增益(挖安全格拿)、三
| stack_policy | stack / refresh / independent | 同名再授予的合并策略 |
| max_stack | 整数 [1,16] | 叠层上限,仅叠加策略消费 |
| 状态 | 效果一句话 | 发给谁 | 幅度(现行值) | 叠层上限 | 来源类别 |
| 状态 | 效果一句话 | 发给谁 | 幅度(现行值) | 时限 / 合并 | 来源类别 |
|---|---|---|---|---|---|
| attack_up 增伤 | 主角攻击提升 | 主角 | +50%(provisional) | 2 | 安全格 |
| ac_up 护甲强化 | 主角护甲等级提升(掷点折扣判定阈值——AC 轴,不入池) | 主角 | +1 护甲等级/层(2026-09-16 换装,原 defense_up 减伤 −30% 退役) | 2 | 安全格 |
| ambush 伏击 | 敌方攻击附带额外伤害 | 敌方 | +3 固定值/层(provisional) | 16 | 踩雷 / 标空旗反噬 |
| weaken 削弱 | 敌方攻击被削弱 | 敌方 | −65%(provisional,取值域 [60,85]) | 16 | 标中旗 |
| enrage 暴走 | 敌方攻击提升 | 敌方 | +25%(provisional) | 16 | 标空旗反噬 |
| off_guard 失衡 | 主角承伤加深(+25 个百分点进承伤修正池) | 主角 | +25%(provisional) | 16 | 标空旗反噬 |
| attack_up 增伤 | 主角攻击提升 | 主角 | +50%(provisional) | 1 次 / 叠加 ×2 | 安全格 |
| ac_up 护甲强化 | 主角护甲等级提升(掷点折扣判定阈值——AC 轴,不入池) | 主角 | +1 护甲等级/层(2026-09-16 换装,原 defense_up 减伤 −30% 退役) | 1 次 / 叠加 ×2 | 安全格 |
| ambush 伏击 | 敌方攻击附带额外伤害 | 敌方 | +3 固定值/层(provisional) | 1 次 / 叠加 ×16 | 踩雷 / 标空旗反噬 |
| weaken 削弱 | 敌方攻击被削弱 | 敌方 | −65%(provisional,取值域 [60,85]) | 1 次 / 叠加 ×16 | 标中旗 |
| enrage 暴走 | 敌方攻击提升 | 敌方 | +25%(provisional) | 1 次 / 叠加 ×16 | 标空旗反噬 |
| off_guard 失衡 | 主角承伤加深(+25 个百分点进承伤修正池) | 主角 | +25%(provisional) | 1 次 / 叠加 ×16 | 标空旗反噬 |
| sapped 乏力 | 主角输出降低(weaken 型负向,hero_offense 池) | 主角 | −25%(provisional) | 1 次 / 叠加 ×16 | 雷格负向(2026-09-15 格面资源批) |
| empower 蓄力 | 敌方输出 ×2(enemy_output 池——蓄力技载体) | 敌方 | +100%(provisional) | 2 次 = 1 个有效增益拍 / 刷新 ×1 | 敌方技能施放(2026-09-16 敌人技能池批) |
| fortify 硬化 | 敌方防御提升(**敌方防御池 enemy_defense**——硬化技载体,主角攻击折半) | 敌方 | +100%(provisional) | 2 次 = 1 个有效增益拍 / 刷新 ×1 | 敌方技能施放(2026-09-16 敌人技能池批) |
empower 与 fortify 是敌方技能池增益技在容器侧的数据落点(技能行 buff_ref 引用,施放时长由技能行覆盖——技能冷却与增益持续是两个计时器):两行 duration = 2 的口径是"先兑付后衰减含施放当轮"——施放发生在第 N 结算战斗段,存活到第 N+1 结算战斗段才兑付"下一拍",即"持续 1 个有效结算"(proposal 语义的容器落地)。敌方防御池(enemy_defense)是第四个乘区池:敌方作为防守方的百分比修正池,消费面 = [战斗结算](战斗结算.md)主角攻击的池乘区——四池对称(敌打主角 = hero_defense × enemy_output,主角打敌 = hero_offense × enemy_defense),容器语义零延伸:仍是同构实例、side-wide 加算、不封顶不保底。
削弱行是当量锚在容器侧的数据落点:标中旗的收益折算成统一价值后,必须严格高于最低档主角增益——"标中旗必须比随便挖一格更值",否则没人愿意插旗。这条锚的契约归[行动经济](../排雷循环/行动经济.md),对照验收归[战斗结算](战斗结算.md);伏击行的固定值量纲定义与惩罚上限锚归[敌人系统](敌人系统.md)。暴走与失衡两行是[战斗结算](战斗结算.md)标错反噬机制(标空旗的三选一确定性惩罚)在容器侧的数据落点:三池内容与载体裁决归它,本表只承载行值。
@@ -191,7 +196,7 @@ MVP 阶段状态表恰有六行:两个主角增益(挖安全格拿)、三
| 优先级 | 内容 | 后置原因 |
|---|---|---|
| P1 | 刷新、并存两类合并策略的首批内容行 | 语义已定义、容器已承载;MVP 六行全用叠加,这两类暂无实际内容,首批上线时随批补验收 |
| P1 | 刷新、并存两类合并策略的后续内容行 | 语义已定义、容器已承载;刷新类已有首批内容(empower/fortify,2026-09-16 敌人技能池批),并存仍空,首个并存内容行上线时随批补验收 |
| P1 | 清图奖励含增益的授予路径 | 来源类别已预留;内容与档位归[地图战斗资源](地图战斗资源.md)定稿 |
| P1 | 单体寻址扩展位 | 现行对敌效果一律全体共享;首个"只作用于其中一个敌人"的效果上线时给实例加目标字段 |
| P2 | 驱散 / 延时 / 转移接口 | 状态一旦生效,玩家没有任何手段摘掉、延后或挪给别人——省掉一整类界面复杂度是明确的设计决定;要加属于新增接口的修订,不是调参 |
@@ -211,8 +216,11 @@ MVP 阶段状态表恰有六行:两个主角增益(挖安全格拿)、三
| weaken 幅度 | 百分比取值域 [60,85],取负生效 | −65(provisional) | 削弱;须过"标中旗锚"对照 |
| enrage 幅度 | 百分比 [−100,100] 且非 0 | +25(provisional) | 暴走;标错反噬三池之一,进敌方输出修正池 |
| off_guard 幅度 | 百分比 [−100,100] 且非 0 | +25(provisional) | 失衡;标错反噬三池之一,进主角承伤修正池(承伤 ×1.25) |
| 六行初始剩余次数 | 整数 [1,99] | 1 × 6(provisional) | 一次性语义;拉长 = 恢复跨轮延续、规划变简单 |
| 六行叠层上限 | 整数 [1,16] | 2 / 2 / 16 / 16 / 16 / 16(provisional) | 仅叠加策略消费;调低 = 重复奖励更容易变溢出 |
| sapped 幅度 | 百分比 [−100,100] 且非 0 | −25(provisional) | 乏力;雷格负向 weaken 型,进主角输出修正池(2026-09-15 格面资源批补登记) |
| empower 幅度 | 百分比 [−100,100] 且非 0 | +100(provisional) | 蓄力;敌方技能池增益技载体,进敌方输出修正池(敌方输出 ×2) |
| fortify 幅度 | 百分比 [−100,100] 且非 0 | +100(provisional) | 硬化;敌方技能池增益技载体,进**敌方防御池 enemy_defense**(主角攻击折半) |
| 初始剩余次数 | 整数 [1,99] | 七行 1 次 · empower/fortify 2 次(provisional) | 七行一次性语义;empower/fortify 2 次 = 1 个有效增益拍(先兑付后衰减含施放当轮);拉长 = 恢复跨轮延续、规划变简单 |
| 叠层上限 | 整数 [1,16] | 2 / 2 / 16 / 16 / 16 / 16 / 16 / 1 / 1(provisional) | 仅叠加策略消费;empower/fortify 用刷新 ×1(冷却闸下不可能自叠加) |
非调参的冻结项:三类合并策略语义、两种量纲语义、来源类别枚举、"先兑付后衰减"时序、拒绝原子性、零随机——改动走本文修订流程,不走调参。
@@ -244,3 +252,4 @@ MVP 阶段状态表恰有六行:两个主角增益(挖安全格拿)、三
| v0.2 | 2026-09-15 | 状态表四行改六行:补暴走(enrage,敌方 +25%)与失衡(off_guard,主角承伤 +25%)两行,承载[战斗结算](战斗结算.md)标错反噬机制(2026-09-12 批已上线);来源类别四选一改五选一(新增标空旗);连带更新"MVP 四行/四类"表述与调参表 |
| v0.3 | 2026-09-15 | 并存分支补容量语义(实例数上界 = 受理授予数,有界确定、无满层语义);P2 后置登记并存行高频授予(持续伤害类)的容量复核 |
| v0.4 | 2026-09-16 | **护甲等级掷点批·换装(与战斗结算 / 难度曲线与Boss / registry / 切片同批)**:defense_up(减伤 −30% ×2 层)整行退役换装 ac_up(护甲强化 +1 护甲等级 ×2 层)——unit 新增 ac 量纲(护甲等级阈值轴,幅度域 [−20,20] 且非 0,不入任何池),消费面 = 战斗结算护甲等级掷点判定;例子表与调参表同步;"叠满减伤触池底"的免伤墙随换装结构性消失。注:sapped 行与极性路由措辞的文档同步归增益状态既有修订批(本批不扩 scope) |
| v0.5 | 2026-09-16 | **敌人技能池批(与敌人系统 / 战斗结算 / 难度曲线与Boss / registry / 切片同批)**:状态表六行 → 九行——新增 empower(蓄力:敌方 +100% 入 enemy_output 池)与 fortify(硬化:敌方 +100% 入**敌方防御池 enemy_defense** 新池——硬化技载体,主角攻击乘区消费面归战斗结算)两行敌方技能施放增益,来源类别 = 敌方技能施放(技能行 buff_ref 引用、施放时长由技能行 buff_duration 覆盖——技能冷却与增益持续两个计时器);两行 duration 2 = 1 个有效增益拍的容器口径成文;**顺手清偿 v0.4 挂起的 sapped 文档同步**(2026-09-15 格面资源批入表未登记的文档漂移——状态行表与调参表补行);P1"刷新类首批内容行"核销(empower/fortify 即首批) |
+32 -17
View File
@@ -65,7 +65,7 @@
| ④ 衰减段 | 移除到期的临时效果;每轮恰好一次,含末轮——清图奖励先兑付、效果后衰减 |
| ⑤ 判定段 | 先判主角倒下(负),再判敌人清空(胜),都不满足则继续下一轮 |
结算本体是纯逻辑步骤流:零界面依赖、禁止动画等待,随机消费只走封闭随机流。触发只读行动经济的相位读数与轮末标记,这条消息有棋盘路径和停手路径两个发射源,到达先后不承载语义。这些性质共同保证结算可回放:同样的种子、同样的事件序列,跑出的步骤流逐位复现——录像回放与断言复测都建立在它上面。随机消费恰两处:战斗开始掷骰定敌方实际数量与攻击基线浮动,属于初始化随机、发生在结算步骤流之外;护甲等级掷点属于回合内随机、发生在步骤流之内——两处都消费封闭随机流,掷点消费专用的 `armor_roll` 封闭流(逐次掷逐次取号,可逐位回放)。
结算本体是纯逻辑步骤流:零界面依赖、禁止动画等待,随机消费只走封闭随机流。触发只读行动经济的相位读数与轮末标记,这条消息有棋盘路径和停手路径两个发射源,到达先后不承载语义。这些性质共同保证结算可回放:同样的种子、同样的事件序列,跑出的步骤流逐位复现——录像回放与断言复测都建立在它上面。随机消费恰三处(2026-09-16 敌人技能池批由两处扩为三处):战斗开始掷骰定敌方实际数量与攻击基线浮动,属于初始化随机、发生在结算步骤流之外;技能摇号属于回合内随机,但时点前置到每轮开轮、同样发生在结算步骤流之外(消费专用 `behavior_roll` 封闭流,每敌按生成序派生独立子流,单敌出招轨迹可独立复放、互不穿插);护甲等级掷点属于回合内随机、发生在步骤流之内(消费专用 `armor_roll` 封闭流,逐次掷逐次取号,可逐位回放)。三处皆封闭随机流,同种子同输入逐位复现。
举证:五段为流程编排,无计算;段序常量存配置表,加载时校验与定稿顺序完全一致,不一致拒绝启动。
@@ -81,7 +81,7 @@
| 溢出未生效行 | 踩雷超出伏击上限,超出的每颗一行 |
| 奖励未生效行 | 满层拒收、满血治疗等合法例外一行 |
| 标旗未中行 | 旗插空的零值归因行,每颗雷只记一次;标错的负向兑付另记效果行 |
| 蓄力行 | 敌人本拍蓄力不动手(攒下一击)时记一行 |
| 蓄力行 | 敌人本拍蓄力不动手(攒下一击)时记一行 **(2026-09-16 敌人技能池批改型为施放行 `cast`:敌人本拍施放增益技——0 伤害动作、具名技能、不兑付伏击;蓄力语义改写为纯攻击增益技,见[敌人系统](敌人系统.md))** |
| 衰减行 | 到期临时效果移除一行 |
| 判定行 | 胜 / 负 / 继续各一行;终局判定行同时是胜负画面的数据锚 |
| 高光行 | 完美排雷成立时一行,只记实际有效兑付 |
@@ -151,7 +151,19 @@ percent 类(每结算、每层): E = |层数 × 行值| ÷ 100 ×
### 战斗段与胜负判定:谁先动手、怎么扣血、怎么定胜负
战斗段是兑现的最后一刀。主角的基础攻击恒存在——这是项目拍板:若基线为零,百分比增益乘上去永远是零,增益系统就空转了。攻击力域 [1, 999]、0 于加载期拒绝,现行值 6(provisional)。顺序固定:主角先手攻击顺位最前的存活敌人,随后按行序逐个存活敌人还手;每个敌人依次走"查攻击、结算伤害、推进行动环"三步且恰好推进一次,主角不在推进范围。主角中途倒下,剩余敌人全部停手,直接进判定段。
战斗段是兑现的最后一刀。主角的基础攻击恒存在——这是项目拍板:若基线为零,百分比增益乘上去永远是零,增益系统就空转了。攻击力域 [1, 999]、0 于加载期拒绝,现行值 6(provisional)。顺序固定:主角先手攻击顺位最前的存活敌人,随后按行序逐个存活敌人按本拍摇定技能还手;每个敌人依次走"按技能行兑现、结算伤害、执行上报(技能进冷却 + 保底计数)"三步且恰行动一次,主角不在其列。主角中途倒下,剩余敌人全部停手,直接进判定段。
**开轮摇号与敌方技能兑现**(2026-09-16 敌人技能池批):敌人每拍做什么由开轮摇号决定——每轮轮次开始时,本系统在换算层排除冷却中技能,按行序逐敌从 `behavior_roll` 封闭流的独立子流摇定本拍动作写入实例池(首轮同样摇号;每轮恰摇一次;轮内刷新换图不重摇),随后冷却递减。摇定后、兑现前死亡 → 本拍技能作废、不动手(死亡即不动手)。结算战斗段只按技能行兑现:伤害技走三乘法链(见下),增益技记一行施放行并把状态效果按技能行持续回合挂入[增益状态](增益状态.md)容器——0 伤害动作不掷不折、不兑付伏击。池模板、技能行、冷却与保底语义归[敌人系统](敌人系统.md)(引用不转述)。
**三乘法链**(技能伤害的合成顺序,技能倍率乘在最前,其后既有链一字不动):
```
① 攻击伤害值 A = 攻击基线 × 技能伤害倍率 (普通攻击倍率恒 1;主角侧恒 1)
② AC 判断值:d20 vs 防守方护甲等级 → 档位折扣;nat20 → 暴击 ×2(量程上下限)
③ 命中伤害 H = (A × a) × 甲效倍率 (克制 1.5 / 中性 1.0 / 被克 0.5;敌打主角无甲轴恒中性)
── 以下既有链不动 ──
增益池乘区(含敌方防御池)→ 伏击附加(后置加法、必中全额、不吃 AC)→ 向下取整 → 伤害下限 max(1,·)
```
主角先手攻击由三个分量逐项取整后相加:普通分量(攻击力经增益修正,可被池封顶压到零)、穿刺分量、魔法分量。**攻击类型是伤害的属性轴**——它只决定这个分量查甲效表的哪一行,定义里不含任何棋盘来源。分量各自查表后取整相加;甲效表把攻击类型与敌方护甲类型交叉成一张 3×3 克制倍率:
@@ -174,19 +186,20 @@ percent 类(每结算、每层): E = |层数 × 行值| ÷ 100 ×
两条都是奖励侧现行定义(暂存结算表 `charge_grants`,正式归属见「待办」):换桶、换量、或把开格奖励换成增益与道具,都不触动攻击类型与甲效矩阵。
**护甲等级掷点:必中、非归零,只吃基础攻击分量**(2026-09-16 定稿落地,判定式与全部数值已拍板)。防守方由此多了一条数值轴——护甲等级(数据键 `armor_class`):攻击永远命中,没有 miss、没有零收益白回合;只有吃掷点的分量在掷点低于防守方护甲等级时按减伤档位打折,档位再重也不归零。判定式:攻击结算时掷 1d20,掷点 ≥ 防守方护甲等级 + 攻击加成 → 全额;掷点 < 护甲等级按缺口(护甲等级 − 掷点)打折——缺口 1-3 → ×0.75,缺口 4-7 → ×0.5,缺口 8+ → ×0.25,三档皆非零档。暴击:天然 20(nat20)使该次基础攻击分量 ×2,双方同规,且 nat20 恒全额(暴击与折扣互斥)。伤害下限:任何攻击落地伤害至少 1(全局 max(1, ·))——"被减伤归零 v=0"零值伤害行语义随批退役,重档折扣的攻击行在行文本记"被压制"。掷点范围:只有基础攻击分量吃掷点——主角先手攻击的普通分量,敌方攻击拍与重击拍的基线伤害(基线 × 重击倍率);穿刺与魔法两个充能分量不吃;棋盘来源一律无视护甲等级——伏击附加、爆裂、清障炸药、奖励即时伤害全部必中全额不掷,这是本机制的边界切口,与"奖励/惩罚必中"同构。护甲等级(数值轴,掷点打折)与护甲类型(属性轴,克制乘区)严格分离:护甲类型不做护甲等级映射,克制矩阵 150/100/50 消费面不变,两概念在文档、表字段与代码键中不得混用。数值载体:主角基准护甲等级 10(暂定),ac_up(护甲强化)+1/层 ×2 层在其上加算;敌方首关行值暂定 0,正式行值归怪物设定批(登记[待办清单](../../06-待办清单.md))。攻击加成(`attack_bonus`)为结构位预留字段,切片恒 0,未来归元进度 / 武器附魔投值。掷点时机在结算战斗段内、每次基础攻击伤害计算时判定,消费 `armor_roll` 封闭流(逐次掷逐次取号,可逐位回放)。
**护甲等级掷点:必中、非归零,只吃基础攻击分量**(2026-09-16 定稿落地,判定式与全部数值已拍板;判定对象措辞随敌人技能池批改述)。防守方由此多了一条数值轴——护甲等级(数据键 `armor_class`):攻击永远命中,没有 miss、没有零收益白回合;只有吃掷点的分量在掷点低于防守方护甲等级时按减伤档位打折,档位再重也不归零。判定式:攻击结算时掷 1d20,掷点 ≥ 防守方护甲等级 + 攻击加成 → 全额;掷点 < 护甲等级按缺口(护甲等级 − 掷点)打折——缺口 1-3 → ×0.75,缺口 4-7 → ×0.5,缺口 8+ → ×0.25,三档皆非零档。暴击:天然 20(nat20)使该次基础攻击分量 ×2,双方同规,且 nat20 恒全额(暴击与折扣互斥;nat20 暴击是掷点层概念,与技能重击〔池技能〕严格区分——两层不得混述)。伤害下限:任何攻击落地伤害至少 1(全局 max(1, ·))——"被减伤归零 v=0"零值伤害行语义随批退役,重档折扣的攻击行在行文本记"被压制"。掷点范围:只有基础攻击分量吃掷点——主角先手攻击的普通分量,敌方技能的基础伤害分量(= 攻击基线 × 技能伤害倍率,普通攻击倍率 1 为特例);无伤害分量的技能(增益类)不掷不折;穿刺与魔法两个充能分量不吃;棋盘来源一律无视护甲等级——伏击附加、爆裂、清障炸药、奖励即时伤害全部必中全额不掷,这是本机制的边界切口,与"奖励/惩罚必中"同构。护甲等级(数值轴,掷点打折)与护甲类型(属性轴,克制乘区)严格分离:护甲类型不做护甲等级映射,克制矩阵 150/100/50 消费面不变,两概念在文档、表字段与代码键中不得混用。数值载体:主角基准护甲等级 10(暂定),ac_up(护甲强化)+1/层 ×2 层在其上加算;敌方首关行值暂定 0,正式行值归怪物设定批(登记[待办清单](../../06-待办清单.md))。攻击加成(`attack_bonus`)为结构位预留字段,切片恒 0,未来归元进度 / 武器附魔投值。掷点时机在结算战斗段内、每次基础攻击伤害计算时判定,消费 `armor_roll` 封闭流(逐次掷逐次取号,可逐位回放)。
EV 参考数(按判定式复算,作对照锚写进文档):基准 AC10 期望减免 21.25%,AC11 25%,AC12 28.75%;对比旧 defense_up 满层 −60% 的减伤墙已整体退役换装为 AC(无双轨)。微调旋钮 = 档位边界与档位值,不动护甲等级值。换装点锚定:基准 AC10 自带约 −21% 敌方输出期望,该方向性偏移与[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)已登记的加难杠杆扫描合并处理;其生存链区间模型按掷点带宽口径复核(带宽含暴击档与折扣档),"零红档"验收保留。
**结论**:先合成、再取整、全程非负;吃掷点分量必中非归零、低掷按档位折扣、nat20 暴击翻倍恒全额、落地伤害保底 ≥1;溢杀丢弃、治疗封顶、死亡即时判定。
```
敌方一次攻击 = max(1, floor( 掷点档位基线 + 伏击附加 ) × 主角防御乘数 × 敌方输出乘数 );伏击为后置加法,不进乘区、不掷不折
乘数 = 1 + 池合成值 ÷ 100;池内加算、每池独立封顶 ±100 百分点 → 乘数域 [0, 2]
敌方一次技能攻击 = max(1, floor( 掷点档位技能基础分量 + 伏击附加 ) × 主角防御乘数 × 敌方输出乘数 );伏击为后置加法,不进乘区、不掷不折
主角一次攻击 = max(1, floor( Σ三分量 × (100 + 敌方防御乘数池合成值) ÷ 100 ) )——敌方防御池(硬化载体)乘在三分量合计之后
乘数 = 1 + 池合成值 ÷ 100;池内加算、每池独立封顶 ±100 百分点 → 乘数域 [0, 2];四池对称:敌打主角 = hero_defense × enemy_output,主角打敌 = hero_offense × enemy_defense(切片现状注记:hero_offense 现行走普通分量增益直乘、未并入合计乘区——正式化随 ADR 批收口,见待办清单)
护甲等级掷点:掷 1d20,掷点 ≥ 护甲等级 + 攻击加成 → 全额;低掷按缺口查档——1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25(无零档)
暴击:nat20 → 该次基础攻击分量 ×2 且恒全额(双方同规)
暴击:nat20 → 该次基础攻击分量 ×2 且恒全额(双方同规;掷点层概念,与技能重击严格区分)
伤害下限:任何攻击落地伤害 ≥ 1(全局 max(1, ·)——折扣与池归零都不产生 v=0 伤害行)
吃掷点分量 = 主角普通分量 / 敌方攻击拍与重击拍基线;穿刺、魔法、伏击附加与棋盘来源伤害不掷不折
吃掷点分量 = 主角普通分量 / 敌方技能基础伤害分量;穿刺、魔法、增益施放与棋盘来源伤害不掷不折
治疗量 = min( 当前生命 + 表值, 生命上限 )
```
@@ -194,7 +207,7 @@ EV 参考数(按判定式复算,作对照锚写进文档):基准 AC10
|---|---|
| 修正池封顶 | 同侧百分比修正加算后每池独立收口 ±100 百分点;乘数可为 0——输出压制触池底时落地伤害由全局下限保底 ≥1(叠满免伤归零的结构面已随护甲等级换装消失) |
| 伤害下限 | 任何攻击落地伤害至少 1(全局 max(1, ·));"被减伤归零 v=0"零值伤害行语义退役,重档折扣行文本记"被压制"——免伤与零输出仍逐行可见 |
| 护甲等级掷点 | 必中非归零:掷 1d20 对防守方护甲等级 + 攻击加成(恒 0),低掷按缺口三档折扣、档位不设零档;nat20 暴击 ×2 恒全额;只吃基础攻击分量(主角普通分量、敌方攻击/重击拍基线),棋盘来源伤害不掷不折 |
| 护甲等级掷点 | 必中非归零:掷 1d20 对防守方护甲等级 + 攻击加成(恒 0),低掷按缺口三档折扣、档位不设零档;nat20 暴击 ×2 恒全额;只吃基础攻击分量(主角普通分量、敌方技能基础伤害分量),棋盘来源伤害与增益施放不掷不折 |
| 溢杀 | 一刀超出目标剩余生命的部分丢弃:不转移给下一个敌人、不存续、无负生命中间态 |
| 治疗 | 生命不溢出上限;满血正向治疗零兑付,记"奖励未生效"行 |
| 死亡判定 | 生命归 0 即倒下;敌人受击即死;主角倒下战斗段立即短路;判定段先判主角、再判敌人(防御性次序) |
@@ -218,7 +231,7 @@ EV 参考数(按判定式复算,作对照锚写进文档):基准 AC10
挖掘期刷新是玩家在挖掘中途主动换图,经[行动经济](../排雷循环/行动经济.md)扣费:扣费成功后按"旧板退役、生成新板、装板、资源预分配恰一次"编排,本轮不打断——没有结算段、没有轮次结束。储备不足被拒是玩家决策的合法结果,不是编排错误。刷新图不发清图奖励,完美排雷计数随装板重置。
战斗开始按固定序列开张:关上配置热重载的闸门、打开增益状态容器、掷骰定敌方实际数量、生成敌人、装板并预分配资源恰一次、发开轮通知。上一关的大奖励(如血上限提升)经战斗开始入参的覆盖值通道生效,内容归[冒险结构](../冒险循环/冒险结构.md)。
战斗开始按固定序列开张:关上配置热重载的闸门、打开增益状态容器、掷骰定敌方实际数量、生成敌人、装板并预分配资源恰一次、发开轮通知并执行开轮摇号(摇定首轮各敌本拍动作)。上一关的大奖励(如血上限提升)经战斗开始入参的覆盖值通道生效,内容归[冒险结构](../冒险循环/冒险结构.md)。
举证:本节为编排流程,无计算。
@@ -232,7 +245,7 @@ EV 参考数(按判定式复算,作对照锚写进文档):基准 AC10
| 上游 | [行动经济](../排雷循环/行动经济.md) | 轮末两字段消息的接收方;回发换图指令、结算完成与战斗结束通知;挖掘期刷新经它扣费;五条经济契约的对账责任方 |
| 上游 | [排雷玩法](../排雷循环/排雷玩法.md) | 订阅其三事件流(挖开格、旗判定、轮次结束);换图时调用装板与退役 |
| 上游 | [棋盘生成](../排雷循环/棋盘生成.md) | 换图时调用其生成接口 |
| 上游 | [敌人系统](敌人系统.md) | 单位接口全调用面:生成、查询、扣血、推进、默认目标、伏击授予、清场;护甲等级(防守方掷点判定读数)随单位数据供给 |
| 上游 | [敌人系统](敌人系统.md) | 单位接口全调用面:生成、查询、扣血、摇定写入、执行上报、默认目标、伏击授予、清场;护甲等级(防守方掷点判定读数)随单位数据供给;池模板与技能行数据归它 |
| 上游 | [增益状态](增益状态.md) | 状态容器接口全调用面:开域、授予、读值、衰减、闭域 |
| 上游 | [地图战斗资源](地图战斗资源.md) | 格面资源接口:预分配、身份查询、受理、清图对象、当量供给投影、映射退役 |
| 上游 | [冒险结构](../冒险循环/冒险结构.md) | 战斗触发、遭遇上下文与主角入参覆盖值(provisional,MVP 冒烟直供) |
@@ -246,7 +259,7 @@ EV 参考数(按判定式复算,作对照锚写进文档):基准 AC10
| 呈现面 | 形态 | 载荷与语义 |
|---|---|---|
| 呈现状态快照 `presentation_snapshot` | 只读函数,全量 | 轮次、战斗结果、相位、行动点与储备、刷新可用性、主角(生命/上限/攻击/存活)、敌人数组(顺位/显示名/生命/敌种/护甲类型/当前拍/行动环进度/伏击层数/存活)、伏击层数、主角活跃效果投影——呈现层唯一的状态读取出口 |
| 呈现状态快照 `presentation_snapshot` | 只读函数,全量 | 轮次、战斗结果、相位、行动点与储备、刷新可用性、主角(生命/上限/攻击/存活)、敌人数组(顺位/显示名/生命/敌种/护甲类型/护甲等级/摇定动作〔pending_skill/type/name〕/敌方增益 effects/伏击层数/存活)、伏击层数、主角活跃效果投影——呈现层唯一的状态读取出口(敌人技能池批批 2 定稿:行为环当前拍/环进度字段拆除,池与权重不公开) |
| 战斗进行中谓词 `is_battle_live` | 只读函数,布尔 | 战斗未出结果且主角在场——组合根高频相位判断的低成本面 |
| 奖励身份查询 `query_reward_identity` | 只读函数,单格 | 已开格奖励身份解析的代理,转[地图战斗资源](地图战斗资源.md)的身份查询(就绪相位守卫归对方)——组合根注入棋盘徽章回调 |
| 结算快照信号 `settlement_snapshot` | 信号,每次结算末 | 对象销毁前组装的全量状态快照(终局含胜负结果)——测试记录与数值分析消费 |
@@ -256,11 +269,11 @@ EV 参考数(按判定式复算,作对照锚写进文档):基准 AC10
## P0 最小闭环
1. 战斗开始序列跑完(关闸门、开容器、掷骰定敌数、生成敌人、装板并预分配资源、发开轮通知),进入第一轮挖掘期。
1. 战斗开始序列跑完(关闸门、开容器、掷骰定敌数、生成敌人、装板并预分配资源、发开轮通知 + 开轮摇号),进入第一轮挖掘期。
2. 挖掘期本系统只缓冲事实流(开格、标旗、撤旗入结算队列),不做任何兑付。
3. 轮末自动触发五段结算,触发与事件到达序无关。
4. 授予段先做抵消过滤(标旗后被撤旗的不结算),再按极性矩阵三批兑现:开安全格 / 标中旗兑正向、开雷格 / 标错旗兑负向、清图发清图奖励对象(完美翻倍)。
5. 增益合成、主角先手加逐敌还手、衰减恰好一次。
5. 增益合成、主角先手加逐敌按摇定技能兑付(伤害技三乘法链 / 增益技施放行)、衰减恰好一次。
6. 判定段先主角后敌人:继续通知同图、清图换图续轮、胜负走战斗结束清理(魔法道具背包不清)。
7. 明细流水全程落行,供呈现、回放与导出。
@@ -302,13 +315,14 @@ EV 参考数(按判定式复算,作对照锚写进文档):基准 AC10
- 极性路由正确:开安全格 / 标中旗只兑正向、开雷格 / 标错旗只兑负向;兑付效果与该格资源类型的字典行一一对应。
- 伏击守恒:blast 负向兑付超出上限时兑付数等于上限,超出部分逐颗记"溢出未生效"行;多敌共享同一伏击池值。
- 完美排雷:零误标且至少一面标中时第二份兑付恰一次并发高光行;出现误标或零标中则无高光;计数换图重置。
- 战斗段次序:主角先手后逐敌"查攻击、结算伤害、推进"各恰一次;主角倒下立即短路,剩余敌人不行动。
- 护甲等级掷点:吃掷点分量(主角普通分量、敌方攻击/重击拍基线)逐次掷 d20——掷点 ≥ 防守方护甲等级 + 攻击加成全额兑付;低掷按缺口查档(1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25),档位不设零档;nat20 暴击该分量 ×2 且恒全额;穿刺/魔法分量与棋盘来源伤害(伏击附加、爆裂、清障炸药、奖励即时)不掷不折。
- 开轮摇号:每轮恰摇一次(首轮同样;轮内刷新换图不重摇);摇定后兑现前死亡 → 本拍技能作废(零敌方行动行)、不进冷却不进保底计数;已兑现技能进冷却、保底计数按执行结果更新。
- 战斗段次序:主角先手后逐敌"按摇定技能行兑现、结算伤害、执行上报"各恰一次;主角倒下立即短路,剩余敌人不行动。
- 护甲等级掷点:吃掷点分量(主角普通分量、敌方技能基础伤害分量)逐次掷 d20——掷点 ≥ 防守方护甲等级 + 攻击加成全额兑付;低掷按缺口查档(1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25),档位不设零档;nat20 暴击该分量 ×2 且恒全额;穿刺/魔法分量与棋盘来源伤害(伏击附加、爆裂、清障炸药、奖励即时)及增益施放不掷不折。
- 伤害下限与留档:任何攻击落地伤害 ≥1,全流水零 v=0 伤害行、零"被减伤归零"行;掷点读数、护甲等级与档位随伤害行留档,nat20 行带暴击标记。
- 数值边界:伤害下限保底后全程无零值/负值伤害;溢杀丢弃不转移;治疗封顶不溢出。
- 出口分发:先判主角倒下(负)再判敌清空(胜);停手与耗尽轮恒同图,清图轮才允许换图。
- 关卡收束:关卡结束清空魔法道具背包恰一次并返回清空前计数;战斗结束不清背包。
- 可回放(封闭随机流口径):同种子同输入两遍步骤流逐位复现;随机消费仅限封闭随机流——开战掷骰(初始化随机)与护甲等级掷点(回合内随机),无未登记随机源、零界面等待。
- 可回放(封闭随机流口径):同种子同输入两遍步骤流逐位复现;随机消费仅限三处封闭随机流——开战掷骰(初始化随机)、技能摇号 `behavior_roll`(回合内、开轮时点、步骤流之外)、护甲等级掷点 `armor_roll`(回合内、步骤流之内),无未登记随机源、零界面等待;单敌出招轨迹可独立复放、互不穿插。
- 无来源即炸:每行明细都能指回一个来源事实(棋盘格、单位、清图或段级结论);找不到来源即断言失败,行型与来源类别两层豁免清单见明细流水一节,豁免行仍受其它契约约束。
- 行型封闭:流水只出现本文登记的 10 类行型;新增或下线行型必须走本文修订并同步呈现层行格式化,未登记行型不得落行。
@@ -330,3 +344,4 @@ EV 参考数(按判定式复算,作对照锚写进文档):基准 AC10
| v0.8 | 2026-09-15 | **护甲等级批(用户拍板登记,只改文档与 registry、不动代码)**:战斗段新增护甲等级掷点规则——必中、非归零,掷点低于防守方护甲等级按减伤档位折扣,没有 miss、没有零收益白回合;只吃基础攻击分量(主角普通分量 / 敌方攻击拍与重击拍基线),穿刺/魔法充能分量不吃;棋盘来源(伏击附加、道具、清障炸药、奖励即时)一律无视;护甲等级(数值轴,数据键 `armor_class`)与护甲类型(克制属性轴)命名区分成文。随机口径修订:结算换算层首次引入回合内随机,可回放契约从"结算路径零随机"改为"封闭随机流可回放"(同种子同输入逐位复现);开战掷骰属初始化随机维持不变。调参表新增护甲等级掷点 / 减伤档位表 / `armor_class` 三行(全部 provisional 未定值);伤害行补掷点读数与折扣档位随行留档(行型 10 类封闭不变);验收标准新增护甲等级掷点与折扣零值归路两条、可回放条改封闭随机流口径;EV 中性引入成文(期望伤害对齐现行表值,"零红档"验收保留);待拍板新增"掷点形式与数值域""掷点的随机流归属"两条 |
| v0.9 | 2026-09-16 | 新增「呈现只读面」节(06 清单「呈现只读面契约登记缺口」闭合):把切片已实现的呈现面函数与信号登记为对外契约——呈现状态快照 `presentation_snapshot`、战斗进行中谓词 `is_battle_live`、奖励身份查询 `query_reward_identity`、结算快照信号 `settlement_snapshot`、战斗结束信号 `battle_finished` 五件成文(载荷字段、消费方、销毁前捕获语义),并登记"呈现层唯一状态读取出口、禁链式穿透"的出口纪律与诊断观测面的非契约定位;依赖与输出表 HUD 框架下游行补指路 |
| v0.10 | 2026-09-16 | **护甲等级掷点批·定稿落地(用户拍板,数字即规范——与敌人系统 / 难度曲线与Boss / registry / 切片代码同批)**:判定式定稿——掷 1d20,掷点 ≥ 防守方护甲等级 + 攻击加成(结构位 `attack_bonus`,切片恒 0)→ 全额,低掷按缺口三档折扣(1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25,无零档);nat20 暴击该基础攻击分量 ×2 且恒全额(双方同规);伤害下限全局 max(1, ·)(`damage_floor` = 1)——"被减伤归零 v=0"零值伤害行语义退役,重档折扣行文本记"被压制";百分比减伤增益 defense_up 整体退役换装 ac_up(护甲强化 +1 AC/层 ×2 层,`unit=ac` 新量纲、不入任何池,无双轨),off_guard 与池结构零改动;护甲等级与护甲类型分离成文(数值轴 / 属性轴,不做映射)。数值载体:主角 armor_class 10(暂定)、敌方首关 0(暂定,正式行值归怪物设定批,登记待办清单);掷点流归属定稿 = `armor_roll` 封闭流(逐次掷逐次取号,可逐位回放),调参表掷点/档位/下限/加成五行由未定改拍板值;EV 参考数成文(AC10 21.25% / AC11 25% / AC12 28.75%,按判定式复算),换装点锚定(基准 AC10 自带约 −21% 敌方输出期望)与加难杠杆扫描合并处理;验收标准改写掷点条目 + 新增伤害下限与留档条目;切片同批:settlement.tres 七参数入表、战斗段掷点判定/暴击/下限/行留档(roll/ac/tier/crit 字段)、armor_roll 流接入 rng_service(黄金向量 m=42 scope0 = 4806095330042243845)、零值伤害行分支删除、冒烟护栏与掷点行为断言(nat20 种子 / AC12 档位分布 / 池触底保底 / 棋盘必中)、英雄卡与敌人卡 AC 展示、ac_up 状态槽图标、nat20 金框高亮 |
| v0.11 | 2026-09-16 | **敌人技能池批(用户拍板,设计权威 docs/proposal-skill-pool.md v1.0——与敌人系统 / 增益状态 / 难度曲线与Boss / 战斗表现 / HUD框架 / registry / 切片代码同批)**:① 随机口径两处 → 三处——技能摇号 `behavior_roll` 封闭流(回合内、开轮时点、步骤流之外,每敌按生成序派生独立子流、单敌轨迹可独立复放)入册,可回放契约扩写为"三处皆封闭流,同种子同输入逐位复现";② 战斗段成文开轮摇号与敌方技能兑现(每轮恰摇一次、轮内刷新不重摇、摇定后兑现前死亡作废)、增益技 = 施放行 + 状态授予(0 伤害不掷不折不兑伏击);③ 三乘法链成文(技能倍率乘在最前:基线 × 倍率 → AC 掷点 → 甲效 → 既有链不动);④ AC 判定对象措辞四处改述("敌方攻击拍与重击拍基线" → "敌方技能基础伤害分量",含正文/公式行/边界表/验收标准);⑤ 敌方防御池入乘区(`enemy_defense` 新池——硬化载体):主角攻击 Σ三分量后 ×(100+enemy_defense)/100,四池对称成文(hero_offense 现行走普通分量直乘的切片现状登记待办);⑥ 行型改型:蓄力行 `charge` → 施放行 `cast`(10 类封闭不变);⑦ 呈现快照敌人数组字段定稿(当前拍/行动环进度 → 摇定动作三字段 + 敌方增益 effects);验收标准新增开轮摇号条目、战斗段次序与可回放条目改写;切片同批:`_roll_round_actions` 开轮编排、技能摇号器独立服务类、单位容器实例池/冷却/执行上报、settlement.tres effect_pool_map 增 empower/fortify、施放行格式化、摇定动画位 |
@@ -62,7 +62,7 @@
| 输入 | 登记处 | 将来要做的 |
|---|---|---|
| 受击反馈 / 死亡表现 / 顺位标记 | [敌人系统](敌人系统.md)的资产需求下限三类(终稿归样张批,呈现语义已归 battle.md) | 终稿资产与动画兑现;死亡不得瞬消 |
| 敌种姿态资产定稿 | battle.md 意图线索映射目录(数据面已锁:首发两敌种,一态与三态) | 把已锁的姿态清单做成终稿资产 |
| 特殊技技能动画定稿 | [敌人系统](敌人系统.md)三档可见性规格(2026-09-16 敌人技能池批改型:行为环姿态预告〔一态与三态〕退役——现行数据面 = 普通攻击零表示、特殊技攻/防系两型动画〔攻击系/增益系〕,暴击为掷点层无前置表示;battle.md 意图线索映射目录已同步改型注记) | 把摇定动画映射(两型)做成终稿资产 |
| 溢出未生效行的呈现 | [增益状态](增益状态.md)定稿时登记(数据源是[战斗结算](战斗结算.md)的明细协议) | 让这类行在演出中可读、不被吞掉 |
| 效果来源格的可读化 | 同上(候选形态:缩略图或格子高亮,坐标不直接呈现) | 把"效果来自哪一格"翻译成玩家可读的演出 |
| 坍塌显形的连锁动画 | [游戏概念](../../00-游戏概念.md)的概念层拍板——坍塌显形是[连锁展开](../排雷循环/排雷玩法.md)的玩家可见名,与音频方向同批配对登记 | 连锁展开触发时的连锁演出 |
@@ -155,3 +155,4 @@ MVP 不要求本文的闭环:不做清单把战斗动画演出列为 MVP 打
|---|---|---|
| v0.1 | 2026-09-15 | 自归档线索整合为范围草案(旧体系未单独成文) |
| v0.2 | 2026-09-16 | 新增「一屏样张的验收标准」:内容清单、五项按序判定与判定回退成文,四件锁定物(角色剪影/动画方案/战斗空间/敌层插入位置)的过判口径落到已冻结口径上(06 清单「样张验收标准未成文」闭合);依赖与输出表"五区"改"四区"随 UI 收口批同步 |
| v0.3 | 2026-09-16 | **敌人技能池批(与敌人系统 / 战斗结算 / HUD框架 / registry / 切片代码同批)**:上游呈现输入清单「敌种姿态资产定稿」行改型——行为环姿态预告(一态与三态)随行为环退役,改「特殊技技能动画定稿」(普通攻击零表示、特殊技攻/防系两型动画映射,暴击掷点层无前置表示);摇定动画位呈现语义 = 开轮摇定即可见、纯类型级提醒(池与权重不公开),样张验收联动口径不变 |
+138 -81
View File
@@ -1,6 +1,6 @@
# 敌人系统
> 一句话:敌人数据、行为环、意图呈现
> 一句话:敌人数据、技能池出招、意图呈现
> 状态:已定稿(自旧体系迁移)
> 旧版参考:../../../archive/gdd-2026-09-14/b2-enemy-system.md
@@ -8,19 +8,21 @@
敌人系统回答战斗里四个问题:对面是谁、有多强、接下来要干什么、怎么被打死。它同时管主角和敌人的数据面——双方共用同一套受伤与死亡的接口,所以它也是踩雷代价的收件人:惊动巢穴喂给怪物的每一分优势,最终都落在它管的血量上。没有它,惩罚没有落点、胜负没有主体、难度曲线没有施加对象。
它向玩家兑现三条承诺。威胁可推测:读怪物的姿态就知道它下一招要干什么。强弱有来处:它强是我踩雷喂的,它弱是我插旗拆的。越下越险:深层敌人更凶,是下探氛围的可见刻度。反向护栏只有一条——威胁绝不能是无源暗账,玩家说不出"它为什么变强、这一拳为什么这么重",就是本系统失职。
它向玩家兑现三条承诺。威胁可推测:特殊技有一次开轮类型级预告、结算明细具名到技能,普攻之外的行动都读得懂来龙去脉。强弱有来处:它强是我踩雷喂的,它弱是我插旗拆的。越下越险:深层敌人更凶,是下探氛围的可见刻度。反向护栏只有一条——威胁绝不能是无源暗账,玩家说不出"它为什么变强、这一拳为什么这么重",就是本系统失职。
出招的质感目标是动作游戏怪物 AI:以普通攻击为基底,随机时机出特殊技——出招序列不可背诵,但每一次出手都离散、具名、可归因。
## 系统范围
边界这样划:本系统是战斗单位的"数据与语义层"——单位有哪些数值、行为怎么轮转、挨打之后状态怎么变,都归这里;什么时候结算、一笔伤害最终算多少,归战斗结算编排;增益层数存放在哪,归增益状态;画成什么样,归 HUD 框架与战斗表现。本系统编排被动:自己从不主动做事,零随机、不碰界面、不订阅事件,一切等战斗结算来调。
边界这样划:本系统是战斗单位的"数据与语义层"——单位有哪些数值、技能池怎么构成、摇定之后状态怎么变,都归这里;什么时候结算、一笔伤害最终算多少、摇号怎么摇,归战斗结算编排;增益层数存放在哪,归增益状态;画成什么样,归 HUD 框架与战斗表现。本系统编排被动:自己从不主动做事,零随机、不碰界面、不订阅事件,一切等战斗结算来调。
负责:
| 负责 | 说明 |
|---|---|
| 单位数据与接口 | 主角与敌人共用的血量、存活、行为环指针;对外的"查状态"与"受动作"两组能力 |
| 两张单位表的定义与回填 | 敌人表与主角表的字段、取值域、表值回填(表本体的承载与读取归数据配置) |
| 行为环语义 | 拍的枚举、推进与死亡冻结规则、跨轮次跨图延续 |
| 单位数据与接口 | 主角与敌人共用的血量、存活;对外的"查状态"与"受动作"两组能力 |
| 两张单位表与技能表的定义与回填 | 敌人表(含池模板)、主角表、技能表的字段、取值域、表值回填(表本体的承载与读取归数据配置) |
| 技能池语义 | 池构成纪律、技能行词汇表、实例池与冷却状态、死亡作废规则 |
| 目标顺位规则 | 敌方打谁、玩家与道具默认打谁的固定顺位 |
| 伏击词条量纲 | 每层伏击附加多少伤害、单轮授予上限的表值 |
@@ -28,19 +30,19 @@
| 不负责 | 归谁 |
|---|---|
| 结算编排与伤害消费(何时调用、伤害合成、死亡判定时机、护甲克制矩阵) | [战斗结算](战斗结算.md) |
| 状态层数的存放与衰减(伏击层池、削弱层池) | [增益状态](增益状态.md) |
| 摇号执行与结算编排(何时摇、伤害合成、死亡判定时机、护甲克制矩阵) | [战斗结算](战斗结算.md) |
| 状态层数的存放与衰减(伏击层池、削弱层池、蓄力/硬化增益) | [增益状态](增益状态.md) |
| 奖励内容与掉落 | [地图战斗资源](地图战斗资源.md) |
| 棋盘规则(挖格、插旗、惊动巢穴等事实的发生) | [排雷玩法](../排雷循环/排雷玩法.md) |
| 遭遇构成(何时开战、坐标与数量怎么定)与跨战斗血量保留 | [关卡节点地图](../冒险循环/关卡节点地图.md) |
| 单位数值曲线与难度修饰 | [难度曲线与Boss](../冒险循环/难度曲线与Boss.md) |
| 呈现(线索映射清单、顺位与血条、受击与死亡演出) | [HUD 框架](../表现与支撑/HUD框架.md) 与 [战斗表现](战斗表现.md) |
| 单位数值曲线、池权重/冷却/保底投值与难度修饰 | [难度曲线与Boss](../冒险循环/难度曲线与Boss.md) |
| 呈现(线索映射清单、顺位与血条、摇定动画、受击与死亡演出) | [HUD 框架](../表现与支撑/HUD框架.md) 与 [战斗表现](战斗表现.md) |
| 配置表的 schema 与读取机制 | [数据配置](../表现与支撑/数据配置.md) |
## 在核心循环中的位置
```
战斗开始:战斗结算按遭遇坐标调本系统生成单位 ──▶ 每轮轮末结算:读拍兑付 → 受击 → 推进 ──▶ 敌全灭 / 主角归零,回流战斗结算判胜负
战斗开始:战斗结算按遭遇坐标调本系统生成单位(池快照成实例池)──▶ 每轮开轮摇号定拍 → 轮末结算按技能行兑现 ──▶ 敌全灭 / 主角归零,回流战斗结算判胜负
```
敌人系统站在"战斗兑现"这一段的单位层。上游是战斗结算的编排调用,以及经它转化而来的棋盘事实(踩了雷、插了旗);下游回给战斗结算两样东西——胜负判定素材和带来源的伤害分量。
@@ -56,7 +58,7 @@
| 状态 | 怎么进入 | 此时会发生什么 |
|---|---|---|
| 未上场 | 单位刚创建、还没开战 | 无任何作用,唯一出口是开战时被实例化 |
| 存活 | 开战时按表值(敌人)或血量入参(主角)生成 | 可被指定目标、按行为环出招、计入全灭判定 |
| 存活 | 开战时按表值(敌人)或血量入参(主角)生成 | 可被指定目标、按摇定技能行动、计入全灭判定 |
| 已死亡 | 血量归 0(判定时机归战斗结算) | 移出顺位、不再出招;不可逆(MVP 无复活);死亡记录保留只读可查 |
| 已消亡 | 战斗结束统一清场 | 逻辑失效:查询返回空、作用接口拒绝;唯一出口是下一场重新生成 |
@@ -66,7 +68,7 @@
战斗开始时,编排方给出遭遇坐标(关卡序号 + 节点深度),本系统据此在敌人表里精确匹配一种敌种,把本战实际数量的敌人生成出来——每场战斗 1 到 3 个敌人,全部同种同属性,来自同一行表值;战斗中敌人只减不增,不做召唤、增援、分裂。主角同时以当前血量入参生成,血量越界(高于上限或不超过 0)整单拒绝、不留半实例。
生成拿到的是当时的表值快照:开战那一刻把数值抄一份,之后改表不影响已生成的单位。若数量或攻击基线配成了区间,由战斗结算用封闭随机流在开战时掷定一次后传入确定值——本系统自身始终零随机;掷定后整场持久,攻击基线不逐拍重掷(数字要可读)。
生成拿到的是当时的表值快照:开战那一刻把数值和池模板各抄一份,之后改表不影响已生成的单位。池模板快照成每实例自己的实例池(各技能冷却清零),此后各自独立摇号、互不干涉。若数量或攻击基线配成了区间,由战斗结算用封闭随机流在开战时掷定一次后传入确定值——本系统自身始终零随机;掷定后整场持久,攻击基线不逐拍重掷(数字要可读)。
| 规则 | 含义 |
|---|---|
@@ -74,80 +76,120 @@
| 每个坐标只出一种敌种 | 敌人表按(关卡序号 × 节点深度)存放,同一坐标的行集内敌种唯一,违规加载期报错止动;同关随深度换敌种合法 |
| 主角数据行 | 主角战斗数值单独放一张全局单行表:生命上限、基础攻击力、护甲等级三字段(见调参表) |
| 基础攻击恒存在 | 基础攻击不许配 0(配置为 0 加载期拒绝)——零基线乘任何百分比增益恒为零,"胜利路径恒有"的保障从数据层杜绝死锁 |
| 先手攻击不是行为环拍 | 每次结算战斗段主角先手攻击顺位最前的存活敌人,这是固定动作;奖励侧的道具与增益是基础攻击之外的规划面伤害,归[地图战斗资源](地图战斗资源.md) |
| 先手攻击不是敌方技能拍 | 每次结算战斗段主角先手攻击顺位最前的存活敌人,这是固定动作;奖励侧的道具与增益是基础攻击之外的规划面伤害,归[地图战斗资源](地图战斗资源.md) |
首发敌种共三种,名字是玩家可见专名:地精、熊地精、食人魔酋长(末者为首发关底 Boss)。
| 敌种 | 行为环 | 护甲类型 | 护甲等级 | 现行定位 |
| 敌种 | 技能池(权重,provisional) | 护甲类型 | 护甲等级 | 现行定位 |
|---|---|---|---|---|
| 地精 | 攻击(单拍环) | 轻甲 | 0(暂定) | 首发杂兵;教学位节点 ×[1,2],开战掷定 |
| 熊地精 | 攻击 / 蓄力 / 重击(三拍环) | 魔法护甲 | 0(暂定) | 首发主力;重击倍率 2 |
| 食人魔酋长 | 攻击 / 攻击 / 蓄力 / 重击(四拍 Boss 环) | 重甲 | 0(暂定) | 首发关底 Boss;重击倍率 2 |
| 地精 | 普通攻击 9 · 突刺 1 | 轻甲 | 0(暂定) | 首发杂兵;教学位节点 ×[1,2],开战掷定 |
| 熊地精 | 普通攻击 6 · 蓄力 2 · 重击 2 | 魔法护甲 | 0(暂定) | 首发主力;重击倍率 2 |
| 食人魔酋长 | 普通攻击 6 · 重击 2 · 硬化 2 | 重甲 | 0(暂定) | 首发关底 Boss;重击倍率 2、硬化独占 |
护甲类型(轻甲 / 魔法护甲 / 重甲)与攻击类型的克制结算矩阵归[战斗结算](战斗结算.md),本系统只提供每个敌种的护甲类型数据。护甲等级是另一条数值轴(防守方字段,基础攻击掷点的折扣判定读数——2026-09-15 护甲等级批新增,2026-09-16 定稿落地):敌人表与主角表各增 `armor_class` 一列(域 [0, 999]),掷点与折扣的结算归[战斗结算](战斗结算.md),行值投值归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)——三敌种首关暂定 0(第一关怪物 AC 相对较低,正式行值待怪物设定投值,登记[待办清单](../../06-待办清单.md)),主角暂定 10,两条轴的概念与字段不得混用。举证:生成与护甲为数据面规则,无计算。
护甲类型(轻甲 / 魔法护甲 / 重甲)与攻击类型的克制结算矩阵归[战斗结算](战斗结算.md),本系统只提供每个敌种的护甲类型数据。护甲等级是另一条数值轴(防守方字段,基础攻击掷点的折扣判定读数):敌人表与主角表各增 `armor_class` 一列(域 [0, 999]),掷点与折扣的结算归[战斗结算](战斗结算.md),行值投值归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)——三敌种首关暂定 0(第一关怪物 AC 相对较低,正式行值待怪物设定投值,登记[待办清单](../../06-待办清单.md)),主角暂定 10,两条轴的概念与字段不得混用。举证:生成与护甲为数据面规则,无计算。
### 行为环:敌人按排好的剧本轮流出招,走到哪一拍零随机
### 技能池:每敌种一张带权技能池,每拍开轮摇号出招
每种敌人有一条行为环——一份排好的出招剧本,"拍"就是环里的一格行动。战斗开始时指针指向环首,之后每轮轮末结算恰推进一拍:先把当前拍的招打出去,再走一格(先作用后推进);环尾回环首;跨轮次、跨雷图延续,除战斗结束清场外没有任何重置入口。全程零随机,走到哪一拍由结算次数唯一决定——这正是"读怪"可以学会的原因。
每种敌人有一张技能池——一组带出招权重的技能行。敌人每拍做什么由一次摇号决定:把它实例池里冷却中的技能排除,剩下的按权重份额归一摇一个。**中签率 = 该技能权重 ÷ 当前可摇技能权重合计**——权重是份额不是固定百分比,排除谁,谁的份额按比例摊给其余技能。出招序列不可背诵,但摇号只决定"做什么";伤害多少由三乘法链在结算段算(见[战斗结算](战斗结算.md))。
死亡即从环上摘除:轮到它之前被打死,本拍不出手、指针就地冻结(MVP 无复活,永久冻结);行动完成后才死,则这一拍已打出、也已推进。其余存活敌人照常轮转。同种敌人永远同拍——它们都从环首出发、每场结算各推进一次。主角没有行为环,对主角调用环接口一律拒绝。
三层对象,模板与实例分离:
每次结算内"读当前拍 → 兑付 → 受击 → 推进"的调用次序由战斗结算钉死:推进必须放在受击之后,次序颠倒就会读到下一拍。
| 层 | 对象 | 载体 | 职责 |
|---|---|---|---|
| 定义层 | 技能行 | 独立技能表(数据配置承载、本系统回填) | 技能是什么:类型、倍率、冷却、效果引用——多敌种共享 |
| 定义层 | 池模板 | 内嵌敌行 pool 数组:{skill_id, weight} | 敌种用什么、多常用——难度曲线的调参单点 |
| 实例层 | 实例池 | 单位实例(开战把模板快照一份) | 这一只在什么状态:各技能冷却剩余、摇定动作、保底计数 |
| 拍 | 状态 | 语义 |
技能行字段词汇表:
| 字段 | 适用类型 | 说明 |
|---|---|---|
| 攻击 | 启用 | 对主角造成基线攻击点伤害 |
| 蓄力 | 启用 | 空拍不兑付——威胁的"预备动作",也是玩家的规划窗口;不兑付伏击 |
| 重击 | 启用 | 基线攻击 × 重击倍率 |
| 自防 / 强化 / 妨害 / 召唤 | 预留 | 配进表会被校验拒绝(加载期报错);启用走本文修订,不是调参 |
| skill_id | 全部 | 主键,池条目引用键 |
| display_name | 全部 | 玩家可见名(明细行与动画共用) |
| type | 全部 | 封闭枚举 {普通攻击, 伤害, 增益};新类型走本文修订 |
| damage_mult | 仅伤害 | 伤害倍率;域 [1.1, 16] provisional。普通攻击恒 1、不可配 |
| cooldown_beats | 全部 | 施放后连续 N 拍不可摇中;普通攻击恒 0、不可配 |
| buff_ref / buff_duration | 仅增益 | 效果引用(指向状态效果行)与增益持续回合数 |
| P1 字段位 | — | 触发条件、命中段数、目标数、固定值分量——随对应后置批加列 |
举证:**当前拍 = 环序列按步数取模,行动完成后步数加一。**
普通攻击也是一行技能行,两条域校验锁死它的特殊性:倍率恒 1、冷却恒 0——想配倍率或冷却的就不是普攻,是伤害技。技能伤害存倍率、不存固定值:伤害 = 施放者攻击基线 × 技能倍率——同一行"重击",熊地精用是 9×2=18,深层强化行自动缩放,这是共享技能表成立的前提;固定值分量若将来需要,是加字段的事(P1)。增益技带效果引用与持续回合,百分比天然按施放者同池生效,无缩放问题。
池构成纪律(加载校验,违规拒绝入表):**每池恰一行普通攻击**(普攻冷却恒 0 → 可摇集合恒非空,"空池不可能"由结构保证,最坏情况怪物平砍等待——那是玩家的反打窗口而非死拍);**每池增益类技能 ≤1**(非 RPG,怪物连出不同增益显得呆);池条目 skill_id 悬空致命;权重 > 0、冷却 ≥ 0。
两道闸(建议值 provisional,投值归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)):
| 闸 | 语义 | 现行值 |
|---|---|---|
| 技能冷却 | 每技能行带冷却拍数;施放后连续 N 拍不可摇中 | 普攻 0 · 突刺 2 · 蓄力 3 · 重击 3 · 硬化 4 |
| 保底 | 连续 M 拍执行的都是普通攻击 → 下一拍必出特殊技(特殊技内按权重,不受冷却限制) | M = 4 |
结构红利:增益存续期内下一拍必是伤害技(池里其余全是伤害技、增益自身还在冷却)——**增益不可能白白过期**,"连出不同增益"被池构成纪律结构性消除。同一技能连发不可能(自身冷却杜绝);异名特殊技背靠背概率低(是变化不是失控,全局特殊间隔闸登记 P1 备用旋钮)。
开轮摇号时序(随机消费全部在[战斗结算](战斗结算.md)换算层):
```
当前拍 = 环[步数 mod 环长] 开战时步数 = 0(指向环首)
行动完成后步数 + 1 轮到前被杀 → 步数冻结(死亡即不动手)
开轮(轮次开始):排除冷却中技能 → 按行序逐敌摇定本拍行动(behavior_roll 封闭流,每敌独立子流)→ 冷却递减
挖掘期:按摇定结果播类型级动画(特殊技)/ 零表示(普通攻击)
结算段:按技能行兑现——掷点、乘区、增益挂接、明细落行
判定段之后进入下一轮开轮……
```
首轮同样摇号;每轮恰摇一次,轮内刷新换图不重摇。摇号随机走封闭随机流、每敌按生成序派生独立子流——单敌出招轨迹可独立复放、互不穿插;本系统自身语义零随机不变,单位容器收到的是确定结果(armor_roll 先例同构)。
死亡即不动手(保留):摇定后、兑现前死亡(主角先手 / 授予段直伤),本拍技能作废——不动手、不进冷却、不进保底计数;其余存活敌人照常。拍结构保留:**每结算每存活敌恰行动一次、按行序、先作用后推进**——变的只是"轮到什么",由固定剧本改为摇号。同种敌人不再锁步:各自实例池独立摇号、独立冷却,怪物 1 的重击在冷却里不影响怪物 2,轨迹分歧是涌现,不设开战分配机制。
蓄力语义(2026-09-16 纠偏定稿):蓄力不是重击前奏——它是一行增益技:0 伤害动作,施加"攻击×2"状态,持续 1 个有效结算(provisional)。旧环"蓄力→猛击"的固定配对是哥布林弹珠的拷贝(给玩家输出窗口用的),随行为环一并退役;蓄力与重击之间**没有任何组合语义**——增益与技能倍率恰好同拍只是两层乘法共存(×2 遇 ×2 = ×4),蓄力后摇中普通攻击则是基线×2 的加成平砍,两者同为平常事,不是设计的连招。技能冷却(几拍内不能再摇中,技能行属性)与增益持续(状态挂几回合,状态行属性)是两个计时器,互不相干。旧蓄力拍"零兑付、不读伏击"的语义由施放行继承:0 伤害技能不兑付伏击——"本轮喂的伏击若逢施放拍不兑现"的读怪奖励保留。
Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(现行:酋长池含独占增益技"硬化"——敌方防御池词汇载体;旧"专属四拍环且含蓄力拍"约定随批退役,增益 ≤1 纪律下旧蓄力拍不进 Boss 池)。
举证:**摇定结果 = 可摇集合按权重份额归一的一次抽签;当前可摇集合 = 实例池排除冷却中技能(保底命中时 = 全部特殊技)。**连带可判别:把权重当固定百分比、把冷却当只锁高伤技、把蓄力当重击前奏,都是与本规约相悖的旧口径。
### 伏击:每踩一颗雷,敌方全体多叠一层附加伤害
玩家挖开惩罚格(雷格在玩家侧的叫法,首发类型是怪物巢穴)就是惊动巢穴:敌方全体获得一层"伏击"——敌方侧的增益词条。一层伏击 = 敌人每次攻击类行为(攻击或重击)额外附加 3 点固定伤害,按主角血量点数计。伏击只活一轮:轮末结算清零,没有跨轮存量。单轮授予有上限(现行 4 层):上限内逐颗叠加,达到上限后多踩的雷不生效,但逐颗记一行"溢出未生效"结算明细——惩罚不静默蒸发。
玩家挖开惩罚格(雷格在玩家侧的叫法,首发类型是怪物巢穴)就是惊动巢穴:敌方全体获得一层"伏击"——敌方侧的增益词条。一层伏击 = 敌人每次伤害类技能(普通攻击或伤害技)额外附加 3 点固定伤害,按主角血量点数计。伏击只活一轮:轮末结算清零,没有跨轮存量。单轮授予有上限(现行 4 层):上限内逐颗叠加,达到上限后多踩的雷不生效,但逐颗记一行"溢出未生效"结算明细——惩罚不静默蒸发。
上限 4 是对"单轮连踩 5–8 颗雷"灾难轮的反灾难护栏;单轮踩雷数天然受每轮行动点预算约束(现行 8 点),这是上限默认值的结构背景。上限配 0 表示该敌种免疫伏击:每颗雷都记明细、都可见、都无效。
上限 4 是对"单轮连踩 5–8 颗雷"灾难轮的反灾难护栏;单轮踩雷数天然受每轮行动点预算约束(现行 6 点),这是上限默认值的结构背景。上限配 0 表示该敌种免疫伏击:每颗雷都记明细、都可见、都无效。
| 细则 | 含义 |
|---|---|
| 附加是后置加法 | 伏击不进重击倍乘,倍率只乘基线伤害 |
| 蓄力拍零兑付 | 蓄力不出手、不读伏击层——本轮喂的伏击若逢敌人全是蓄力拍,一点不兑现(读怪的奖励) |
| 附加是后置加法 | 伏击不进技能倍率乘区,倍率只乘基线伤害 |
| 施放行零兑付 | 增益技不出手、不读伏击层——本轮喂的伏击若逢敌人全是施放拍,一点不兑现(读怪的奖励) |
| 层池共享不消耗 | 同一次结算多个敌人读同一层数,不均分、不先到先得 |
| 当轮授予当轮兑付 | 授予发生在兑付之前:本轮踩的雷,若该轮有攻击拍,当轮就生效 |
| 当轮授予当轮兑付 | 授予发生在兑付之前:本轮踩的雷,若该轮有伤害拍,当轮就生效 |
| 溢出可见 | 生效层数 = min(当轮新踩雷数, 上限),其余逐颗记"溢出未生效"明细 |
举证:**攻击拍伤害 = 基线 + 层数 × 3;重击拍伤害 = 基线 × 倍率 + 层数 × 3;蓄力拍恒为 0。**
举证:**伤害拍伤害 = 攻击基线 × 技能倍率 + 层数 × 3;施放拍恒为 0。**
```
攻击拍:伤害 = atk + 3 × L L = 当轮伏击层数(表值 3 provisional,量纲已定稿)
重击拍:伤害 = atk × 倍率 + 3 × L
蓄力拍:伤害 = 0 不读伏击层
伤害拍:伤害 = atk × 倍率 + 3 × L L = 当轮伏击层数(表值 3 provisional,量纲已定稿)
普攻拍:伤害 = atk + 3 × L 倍率恒 1 的特例
施放拍:伤害 = 0 不读伏击层
生效层数 L = min(当轮新踩雷数, 单轮上限) 溢出 = 雷数 − L,逐颗记明细
```
验算(表值 provisional,读数见调参表):熊地精基线 9、倍率 2、4 层伏击的重击拍 = 9 × 2 + 12 = 30;若把伏击塞进倍乘会算出 (9 + 12) × 2 = 42——后置加法由此判别。
验算(表值 provisional,读数见调参表):熊地精基线 9、重击倍率 2、4 层伏击的重击拍 = 9 × 2 + 12 = 30;若把伏击塞进倍乘会算出 (9 + 12) × 2 = 42——后置加法由此判别。
### 目标顺位与意图线索:打谁不用选,下一招靠看图推测
### 目标顺位与意图线索:打谁不用选,特殊技开轮有一次类型级预告
敌方一侧没有选择可言:所有存活敌人各自按当前拍作用于主角,主角是敌方唯一目标。玩家与道具一侧用固定顺位:单体效果默认打生成序最前的存活敌人,它死了顺延到下一个;范围效果打全体存活敌人,同样无需选择。场上只有一个敌人时,顺位与范围自然重合,是合法退化不是错误。顺位必须对玩家可读——"下一个被打的是谁"要能推测出来。
敌方一侧没有选择可言:所有存活敌人各自按摇定技能作用于主角,主角是敌方唯一目标。玩家与道具一侧用固定顺位:单体效果默认打生成序最前的存活敌人,它死了顺延到下一个;范围效果打全体存活敌人,同样无需选择。场上只有一个敌人时,顺位与范围自然重合,是合法退化不是错误。顺位必须对玩家可读——"下一个被打的是谁"要能推测出来。
意图呈现走"视觉推测":界面不直接预告敌人下一拍的行为与数值——威胁靠看图读出来,它弓起背,下一拍就是重击。即将执行的一拍在挖掘期(玩家花行动点挖格的阶段)全程亮出;精确数字只在轮末结算明细里兑现。线索映射以敌种为键、每一局恒定、禁止重掷——一旦重掷,"读怪"就退化成"猜怪"。首发敌种最长三拍环,学习成本刻意压低。
意图呈现走"摇定动画 + 具名明细"的组合,池与权重不公开、不做技能池界面。摇定结果的信息面分三档:
本系统只锁下面这张数据面规格;线索映射清单与视觉规格归[HUD 框架](../表现与支撑/HUD框架.md),姿态与受击死亡演出归[战斗表现](战斗表现.md)。
| 摇中 | 挖掘期呈现 | 结算揭示 |
|---|---|---|
| 普通攻击 | 零表示——完全不可预测 | 明细行"普通攻击(暴击/被压制)" |
| 特殊技 | 技能动画占位(攻击系 / 增益系不同)——纯类型级提醒,不预告技能名与数值 | 明细行具名技能 + 数值 |
| nat20 暴击 | 无前置表示(掷点层,与技能重击严格区分) | 明细行"(暴击)"标注 |
即:普攻完全不可预测;特殊技有一次类型级预告,不可撤销,但给"继续挖还是停手"的决策依据。旧环"下一拍全程亮出(意图徽标 + 环点)"的预告粒度随行为环退役。技能动画的攻/防系映射归[战斗表现](战斗表现.md),敌人卡摇定动画位归[HUD 框架](../表现与支撑/HUD框架.md),本系统只锁上面这张三档规格。
| 规格 | 含义 |
|---|---|
| 图像与行为一一对应 | 同一敌种的同一行为恒同一视觉状态(如熊地精蓄力拍 = 弓背蓄力姿态) |
| 形态暗示强度 | 敌种长相承载强度档位与环长的直觉编码("大个子多半有重击"式) |
| 映射每局恒定 | 以敌种为键,跨局不重掷 |
| 数字只在明细 | 预告面是图像,数字面是结算明细 |
| 预告时序锁定 | 呈现的是即将执行的一拍,挖掘期全程可见;结算时不另播意图动画 |
| 池与权重不公开 | 无技能池界面、无权重数值露出;实例池的可见面 = 摇定结果本身 |
| 类型级预告 | 特殊技只预告攻/防系类型,不预告技能名与数值 |
| 映射每局恒定 | 动画映射以技能类型为键,跨局不重掷 |
| 数字只在明细 | 预告面是类型级动画,数字面是结算明细(具名技能 + 数值) |
| 预告时序锁定 | 开轮摇定即可见,挖掘期全程;轮内刷新换图不重摇 |
举证:本节为顺位与呈现数据面规则,无计算。
@@ -157,42 +199,45 @@
| 方向 | 系统 | 接口要点 |
|---|---|---|
| 上游 | [战斗结算](战斗结算.md) | 唯一编排调用方:开战生成、结算内查询/受击/推进的次序由它钉死;踩雷到伏击施加的转化(每颗雷恰一次)也由它编排 |
| 上游 | [数据配置](../表现与支撑/数据配置.md) | 敌人表与主角表的 schema 承载与类型化读取;本系统是两表的回填责任方 |
| 上游 | [战斗结算](战斗结算.md) | 唯一编排调用方:开战生成、开轮摇号(随机消费)、结算内兑现/受击/执行上报的次序由它钉死;踩雷到伏击施加的转化(每颗雷恰一次)也由它编排 |
| 上游 | [数据配置](../表现与支撑/数据配置.md) | 敌人表、主角表、技能表的 schema 承载与类型化读取;本系统是三表的回填责任方 |
| 相邻 | [增益状态](增益状态.md) | 伏击词条的量纲与作用域由本系统定稿并登记;层数容器与轮末衰减归它,层数由战斗结算读出后作参数传入——运行时零直接调用 |
| 下游 | [战斗结算](战斗结算.md) | 胜负判定素材(敌全灭 / 主角归零)与伤害分量(基线 + 伏击)的供给方;护甲克制矩阵归它 |
| 下游 | [战斗结算](战斗结算.md) | 胜负判定素材(敌全灭 / 主角归零)与伤害分量(基线 × 技能倍率 + 伏击附加)的供给方;护甲克制矩阵归它 |
| 下游 | [地图战斗资源](地图战斗资源.md) | 固定顺位是道具伤害的默认目标规则;基础攻击恒在,奖励侧是基础攻击之外的规划面伤害 |
| 下游 | [关卡节点地图](../冒险循环/关卡节点地图.md) | 遭遇构成(何时开战、坐标与数量怎么定)归它;跨战斗血量是否保留归冒险循环裁决 |
| 下游 | [难度曲线与Boss](../冒险循环/难度曲线与Boss.md) | 两表曲线数值与遭遇分布的调参所有权归它;难度修饰施加点在行值快照之前(MVP 零修饰) |
| 下游 | [HUD 框架](../表现与支撑/HUD框架.md) | 线索映射清单、单位顺位与血条、敌方增益层数的呈现归它(引用不转述) |
| 下游 | [战斗表现](战斗表现.md) | 行为环视觉状态(蓄力姿态等)、受击与死亡表现的呈现归它(引用不转述) |
| 下游 | [难度曲线与Boss](../冒险循环/难度曲线与Boss.md) | 曲线数值与池权重/冷却/保底投值的调参所有权归它;难度修饰施加点在行值快照之前(MVP 零修饰) |
| 下游 | [HUD 框架](../表现与支撑/HUD框架.md) | 线索映射清单、单位顺位与血条、摇定动画位、敌方增益显示的呈现归它(引用不转述) |
| 下游 | [战斗表现](战斗表现.md) | 技能动画(攻/防系映射)、受击与死亡表现的呈现归它(引用不转述) |
对外接口只有两组能力:查状态(血量 / 当前拍 / 顺位 / 存活)与受动作(受击 / 推进 / 消亡)。什么时候调、按什么顺序调,全归战斗结算。
对外接口只有两组能力:查状态(血量 / 摇定动作 / 顺位 / 存活)与受动作(受击 / 摇定写入 / 执行上报 / 消亡)。什么时候调、按什么顺序调,全归战斗结算。
## P0 最小闭环
1. 战斗开始:战斗结算按遭遇坐标调本系统生成单位——读表快照实例化本战数量的敌人(指针指向环首),主角以血量入参生成。
2. 挖掘期踩雷:事实经战斗结算转化为伏击施加——每颗雷恰一次、上限截流、溢出逐颗记账。
3. 每轮结算:主角先手攻击顺位最前的存活敌人;每个存活敌人读当前拍兑付(攻击 / 重击附加伏击,蓄力空拍)。
4. 受击上报、血量递减;归 0 即移出顺位、指针冻结(死亡即不动手)。
5. 存活敌人各推进一拍(先作用后推进,环尾回环首)。
6. 敌全灭或主角归零,回流战斗结算作胜负判定素材。
7. 战斗结束:全部单位消亡、不跨战斗,下一场重新生成。
1. 战斗开始:战斗结算按遭遇坐标调本系统生成单位——读表快照实例化本战数量的敌人(池模板快照成实例池、冷却全零),主角以血量入参生成。
2. 开轮摇号:战斗结算换算层排除冷却中技能、按行序逐敌摇定本拍动作写入实例池,随后冷却递减(首轮同样摇;轮内刷新换图不重摇)。
3. 挖掘期踩雷:事实经战斗结算转化为伏击施加——每颗雷恰一次、上限截流、溢出逐颗记账。
4. 每轮结算:主角先手攻击顺位最前的存活敌人;每个存活敌人按摇定技能行兑付(伤害技 = 基线 × 倍率 + 伏击附加,增益技 = 施放行 + 状态授予)。
5. 受击上报、血量递减;归 0 即移出顺位(摇定后兑现前死亡 → 本拍作废不进账)。
6. 执行上报:已兑现的技能进冷却、保底计数更新(普攻 +1 / 特殊技归零)。
7. 敌全灭或主角归零,回流战斗结算作胜负判定素材。
8. 战斗结束:全部单位消亡、不跨战斗,下一场重新生成。
## P1 / P2 后置
| 优先级 | 内容 | 后置原因 |
|---|---|---|
| P1 | 预留行为拍(自防 / 强化 / 妨害 / 召唤)启用 | 拍枚举封闭——启用是本文修订,不是调参;排垂直切片 / Alpha |
| P1 | 敌种名录扩容与投放节奏 | 投放节奏是关卡命脉,归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md);排垂直切片 / Alpha |
| P1 | 情境条件触发器(低血触发防御、玩家停手时偷袭技等) | 载体 = 技能行触发条件字段加列;登记[待办清单](../../06-待办清单.md) |
| P1 | 个体化变体池(A 型好斗 / B 型守势、精英专属池) | 共识点 A 的 A2 案;登记[待办清单](../../06-待办清单.md) |
| P1 | 全局特殊间隔旋钮 | 特殊技背靠背实测仍嫌时的备用闸;登记[待办清单](../../06-待办清单.md) |
| P1 | 新效应词汇批(妨害技:对主角施减益;固定值分量;多段命中每段一掷) | 技能行字段位已预留;登记[待办清单](../../06-待办清单.md) |
| P1 | 敌种名录扩容与投放节奏(含第二关池与投放) | 投放节奏是关卡命脉,归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md) |
| P1 | 单体寻址("只打某一个敌人"的目标指定) | 首个单体效果上线时随批裁决归集语义与呈现 |
| P1 | 蓄力拍的音频前摇 | 读招的第二通道,垂直切片可选项 |
| P2 | 敌人图鉴 / 列表界面 | 首发名录学习成本低,名录扩容时再议 |
| P2 | 敌方技能图鉴 / 学习呈现(玩家侧技能知识系统) | 池不公开口径下的进阶信息面;登记[待办清单](../../06-待办清单.md) |
| P2 | 精英 / Boss 遭遇编码位 | 现表键结构没有编码位,改键随[关卡节点地图](../冒险循环/关卡节点地图.md)定稿强制核对(见待办) |
## 调参表
配置表共两张,参数域如下;首发行值全部 provisional、曲线调参所有权归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md),已锁数值以 [registry 的 enemy 与 hero 条目](../../../registry/entities.yaml) 为准。
配置表共三张,参数域如下;首发行值全部 provisional、曲线调参所有权归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md),已锁数值以 [registry 的 enemy / hero / skill 条目](../../../registry/entities.yaml) 为准。
敌人表(按关卡坐标存放,每坐标行集恰一敌种):
@@ -205,10 +250,19 @@
| armor_class(护甲等级) | int [0, 999],缺省 0 | 0(首关暂定——正式行值归怪物设定批) | 防守方护甲等级——基础攻击掷点的折扣判定读数,掷点结算归[战斗结算](战斗结算.md);与 armor_type(克制属性轴)两概念不得混用 |
| hp | [1, 999] | provisional | 单位生命基线(难度修饰前) |
| atk | [0, 999] | provisional | 攻击基线;0 合法 = 纯伏击伤敌种 |
| heavy_multiplier | [1, 16] | 2(provisional) | 无重击拍的敌种不消费,非校验项 |
| pattern | 环长 [1, 8] | 三敌种见核心规则 | 校验域 = 攻击 / 蓄力 / 重击;预留值拒绝 |
| pool(池模板) | {skill_id, weight} 数组,weight ∈ (0, 9999] | 三敌种见核心规则(全部 provisional) | 技能行引用在技能表;池构成纪律与悬空引用加载期拒绝(校验在数据配置跨表通道) |
| advantage_per_round_cap | [0, 16] | 4(定稿) | 单轮伏击授予上限;0 = 免疫伏击;也是扫雷经济侧对账的惩罚上限供给值 |
技能表(全局单表,技能行主键 skill_id):
| 参数 | 取值域 | 现行值 | 说明 |
|---|---|---|---|
| type | {普通攻击, 伤害, 增益} | 五行:普攻 / 突刺 / 蓄力 / 重击 / 硬化 | 封闭枚举;新类型走本文修订 |
| damage_mult | [1.1, 16] | 突刺 1.5 · 重击 2(provisional) | 仅伤害技;普通攻击恒 1 不可配(域校验锁死) |
| cooldown_beats | [0, 99] | 普攻 0 · 突刺 2 · 蓄力 3 · 重击 3 · 硬化 4(provisional) | 普通攻击恒 0 不可配(域校验锁死——空池不可能的结构前提) |
| buff_ref / buff_duration | 效果引用 / [1, 99] | 蓄力 → empower · 硬化 → fortify(持续 2 结算 = 1 个有效增益拍) | 仅增益技;悬空引用加载期拒绝 |
| pity_threshold(保底 M) | [1, 99] | 4(provisional) | 全局单旋钮:连续 M 拍执行普攻 → 下拍必出特殊技 |
主角表(单行全局):
| 参数 | 取值域 | 现行值 | 说明 |
@@ -217,22 +271,24 @@
| atk | [1, 999] | 6(原型锚 provisional) | 基础攻击;配 0 加载期拒绝——基础攻击恒在 |
| armor_class(护甲等级) | int [0, 999] | 10(暂定,2026-09-16 拍板) | 主角作为防守方的护甲等级基准,ac_up(护甲强化 +1/层 ×2 层)在其上加算;掷点结算归[战斗结算](战斗结算.md) |
三敌种现行行值(全部 provisional,出处 registry):地精教学位 24 生命 / 7 攻击 ×[1,2](开战掷定)、群行 13/5 ×[2,3];熊地精 40/9 ×1、群行 18/6 ×[2,3]、深层锚行 30/7;食人魔酋长 150/9 ×1。护甲等级行值首关暂定 0(正式行值归怪物设定批,登记[待办清单](../../06-待办清单.md))。
三敌种现行行值(全部 provisional,出处 registry):地精教学位 24 生命 / 7 攻击 ×[1,2](开战掷定)、群行 13/5 ×[2,3];熊地精 40/9 ×1、群行 18/6 ×[2,3]、深层锚行 30/7;食人魔酋长 150/9 ×1。护甲等级行值首关暂定 0(正式行值归怪物设定批,登记[待办清单](../../06-待办清单.md))。池 EV 锚(静态份额口径,provisional):地精 1.05(突刺倍率 >1 必然抬 EV,+5% 偏移显式登记不硬凑)、熊地精 1.00、酋长 1.00;**含闸长期速率实测(2026-09-16 实施批,×5000 拍):地精 1.106(保底闸把突刺率从自然 10% 抬到 ~22%——唯一特殊技低权重的池受保底支配)、熊地精 1.000(普攻+重击与蓄力零乘加权守恒,结构性恒 1)、酋长 1.024**——EV 重锚与权重投值归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)。
结构冻结(非调参项,修订走本文流程):行为拍枚举封闭、死亡即不动手、先作用后推进、伏击层池共享读法、附加后置加法、伏击按主角血量点计量纲、固定顺位、每坐标恰一敌种、零随机(口径修订 2026-09-15:本系统自身语义零随机不变——行为环与顺位由结算次数唯一决定;随机消费一律在[战斗结算](战斗结算.md)结算换算层按封闭随机流掷定,可回放契约 = 同种子同输入逐位复现)。联动纪律:单轮上限 × 攻击基线(支配边界联动)、数量 × 上限(单结算上界双线性)、倍率 × 攻击(重击拍支配)禁止独立调参,联调须重跑方向性预检并同步 registry(见待办)。
结构冻结(非调参项,修订走本文流程):技能类型枚举封闭、每池恰一普攻、每池增益 ≤1、死亡即不动手(摇定后兑现前死亡作废不进账)、每结算每存活敌恰行动一次、行序、先作用后推进、伏击层池共享读法、附加后置加法、伏击按主角血量点计量纲、固定顺位、每坐标恰一敌种、零随机(本系统自身语义零随机不变——摇号与掷点随机一律在[战斗结算](战斗结算.md)结算换算层按封闭随机流掷定,可回放契约 = 同种子同输入逐位复现,单敌出招轨迹可独立复放)。联动纪律:单轮上限 × 攻击基线(支配边界联动)、数量 × 上限(单结算上界双线性)、技能倍率 × 攻击(伤害拍支配)、权重 × 冷却 × 保底(池节奏三联动)禁止独立调参,联调须重跑方向性预检并同步 registry(见待办)。
## 验收标准
- 开战生成:按遭遇坐标精确匹配一行,实例化数量与数值投影同行值快照;主角以血量入参,越界(高于上限或不超过 0)拒绝且不留半实例。
- 行为环确定性:初拍恒为环首;任意推进序列后当前拍 = 环[步数 mod 环长];环长 1 恒同一拍;同种多敌全程同拍;除战斗结束清场外无任何重置入口。
- 死亡即不动手:轮到前被杀——本拍不出手、指针冻结;行动完成后才死——已出招已推进;对已死亡单位上报受击被拒绝且状态不变。
- 伏击读值:攻击拍读值 = 基线 + 层数 × 3;重击拍 = 基线 × 倍率 + 层数 × 3(与"伏击进倍乘"的错算可判别);蓄力拍恒为 0;同层数多敌读值一致、读值前后单位状态零变化。
- 开战生成:按遭遇坐标精确匹配一行,实例化数量与数值投影同行值快照;池模板快照成实例池且冷却全零;主角以血量入参,越界(高于上限或不超过 0)拒绝且不留半实例。
- 摇号确定性:同种子回放逐位复现(步骤流与各实例摇定动作序列逐位相同);单敌轨迹独立复放——只喂该敌子流(scope = 生成序 × 派生向量 + 摇号序)即可逐位重建其出招序列、互不穿插。
- 池节奏:中签率 = 权重 ÷ 可摇权重合计(排除冷却中技能后归一);施放后连续 N 拍不可摇中;连续 M 拍执行普攻 → 下拍必出特殊技(特殊技内按权重、不受冷却限制);同技能连发不可能;首轮摇号、每轮恰摇一次、轮内刷新不重摇。
- 死亡即不动手:摇定后、兑现前被杀——本拍技能作废(零行动明细行),不进冷却、不进保底计数;行动完成后才死——已兑付已进账;对已死亡单位上报受击被拒绝且状态不变。
- 空池不可能:每池恰一行普通攻击(域校验),普攻冷却恒 0 → 任意实例任意拍可摇集合恒非空。
- 伏击读值:伤害拍读值 = 基线 × 倍率 + 层数 × 3(与"伏击进倍乘"的错算可判别);普攻 = 倍率 1 特例;施放拍恒为 0;同层数多敌读值一致、读值前后单位状态零变化。
- 伏击授予与溢出:生效层数 = min(雷数, 上限),且生效 + 溢出 = 雷数(零静默蒸发);上限 4 时踩 6 颗雷生效 4、溢出 2;上限配 0 时全部记溢出且全部无效。
- 目标顺位:多敌时单体目标恒为生成序最前存活者,杀最前后顺延;杀至空返回空;单敌时顺位与范围效果重合。
- 快照抗改表:实例化后修改敌人表,已生成单位的数值投影与行为环逐字不变;新实例才用新表值。
- 表校验:越域值、预留拍、同坐标第二敌种均在加载期报错止动;域内合法极端(攻击基线 0、上限 0)不拒;主角表恰一行、攻击配 0 拒绝。
- 零随机回放:同一动作与受击序列驱动两个独立实例,存活投影、逐单位拍轨迹、顺位返回序列逐项相同。
- 表实一致:两张配置表字段与 registry 的 enemy / hero 条目逐字段一致。
- 快照抗改表:实例化后修改敌人表,已生成单位的数值投影与实例池逐字不变;新实例才用新表值。
- 表校验:越域值、池权重 ≤ 0、空池、普攻配倍率/冷却、池增益 > 1、skill_id 悬空、同坐标第二敌种均在加载期报错止动;域内合法极端(攻击基线 0、上限 0)不拒;主角表恰一行、攻击配 0 拒绝。
- 零随机回放:同一动作与受击序列驱动两个独立实例,存活投影、逐实例摇定轨迹、顺位返回序列逐项相同。
- 表实一致:三张配置表字段与 registry 的 enemy / hero / skill 条目逐字段一致。
## 待办
@@ -246,3 +302,4 @@
| v0.2 | 2026-09-15 | 教学位掷骰口径自洽(随[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)同批修订):unit_count 字段说明、地精敌种定位与现行行值句中"教学位固定 1 只/固定不掷"改为 ×[1,2] 开战掷定 |
| v0.3 | 2026-09-15 | **护甲等级批(用户拍板登记,与战斗结算 / 难度曲线与Boss / registry 同批,不动代码)**:敌人表与主角表 schema 各增 armor_class(护甲等级)字段(取值域随掷点形式定稿,provisional);三敌种表补护甲等级列(行值未定);护甲等级(数值轴)与护甲类型(克制属性轴)命名区分成文;结构冻结清单"零随机"措辞按新口径修订——本系统自身语义零随机不变,掷点随机在战斗结算换算层按封闭随机流消费、可回放契约 = 同种子同输入逐位复现;待拍板新增"护甲等级行值未定"条目 |
| v0.4 | 2026-09-16 | **护甲等级掷点批·定稿落地(与战斗结算 / 难度曲线与Boss / registry / 切片代码同批)**:armor_class 域定稿 [0, 999](缺省 0 向后兼容);主角行值 10(暂定)、三敌种首关行值 0(暂定)——"行值未定"改"暂定值 + 待办",敌方正式行值归怪物设定批(第一关怪物 AC 相对较低,值待怪物设定投值,登记待办清单);敌种表护甲等级列同步落值;调参表两行改域与现行值 |
| v0.5 | 2026-09-16 | **敌人技能池批(用户拍板,设计权威 docs/proposal-skill-pool.md v1.0;与战斗结算 / 增益状态 / 难度曲线与Boss / 战斗表现 / HUD框架 / registry / 切片代码同批)**:行为环章节整体退役——固定出招剧本、拍枚举(攻击/蓄力/重击)、环指针、同种锁步、heavy_multiplier 字段全部废除;技能池语义成文(三层对象〔技能行/池模板/实例池〕、技能行词汇表、池构成纪律、两道闸〔全技能各自冷却 + 保底〕、开轮摇号时序、死亡作废、独立子流);蓄力语义改写(纯攻击增益技、与重击无组合语义——纠偏二;施放行继承零兑付伏击);Boss 专属身份改专属池承载;意图呈现改三档可见性(普攻零表示 / 特殊技类型级预告 / 暴击掷点层);敌人表 schema pattern → pool;调参表增技能表与池 EV 锚(含闸长期速率实测登记);验收标准按摇号口径重写 |
@@ -154,7 +154,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
| 上游 | [排雷玩法](../排雷循环/排雷玩法.md) | 棋盘只读投影(格态、数字、旗、小人)与移动路径查询;玩家意图批次经组合根提交 |
| 上游 | [行动经济](../排雷循环/行动经济.md) | 行动点 / 储备 / 相位只读值;刷新入口可用性预判加返回值兜底 |
| 上游 | [战斗结算](../战斗循环/战斗结算.md) | 明细流水是结算明细带的统一数据源(现行 10 类行封闭协议);段序编排信号经组合根接线 |
| 上游 | [敌人系统](../战斗循环/敌人系统.md) | 英雄与敌人的只读投影(血量、顺位、当前拍、存活) |
| 上游 | [敌人系统](../战斗循环/敌人系统.md) | 英雄与敌人的只读投影(血量、顺位、摇定动作〔pending_skill/type/name〕、敌方增益 effects、存活——经战斗结算呈现快照;敌人技能池批批 2 定稿:当前拍/行动环进度字段拆除,池与权重不公开) |
| 上游 | [增益状态](../战斗循环/增益状态.md) | 状态投影经战斗结算呈现面取得,零直读 |
| 上游 | [地图战斗资源](../战斗循环/地图战斗资源.md) | 已开格奖励身份投影(`query_identity` 只读回调,组合根注入)零直读 |
| 上游 | [关卡节点地图](../冒险循环/关卡节点地图.md) | 节点链、当前节点、跨战斗生命携带的读数;胜利后推进 |
@@ -227,7 +227,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
- 结算逐行实时滚出且每行带来源标注;未生效类行聚合计数并带形状冗余;零值行不被吞;效果行的单次贡献与池合成值分示、禁相加误读。
- 高光三档:标中旗行金框、清图奖励行金框、完美排雷全屏喝彩级——庆祝不打在空兑现上(首份即零兑付则不发高光)。
- 状态槽显示图标加层数加剩余结算次数,悬停见来源;极性按键级表呈现(金满框 / 红缺框),空态整排隐藏、数据框上移补位;战斗结束全清有微文案。
- 敌人卡:意图徽标是下一拍预告且挖掘期全程可见、映射跨局恒定、顺位可读、血量数字可读、敌方共享层数各卡同值;死亡灰化加碎裂退场(禁止瞬间消失)且就地更新不丢灰化态。
- 敌人卡:意图徽标 = 本拍摇定结果的动画占位(敌人技能池批三档可见性——普通攻击零表示、特殊技类型级提示〔攻/防系两型〕,不预告技能名与数值、池与权重不公开)且挖掘期全程可见、映射跨局恒定、顺位可读、血量数字可读、敌方共享层数各卡同值;死亡灰化加碎裂退场(禁止瞬间消失)且就地更新不丢灰化态。
- 终局:胜利金蜡封、失败暮蓝氛围层(明度不变);储备清零与状态全清两反馈不合并、时序不倒挂(关卡末清库反馈义务载体 = 魔法道具背包,随魔法道具 P0 落地补验收)。
- 任一模态层打开时遮挡物隐藏或输入锁定、关闭逐层恢复;tooltip 与明细行等动态区固定槽高、换行裁剪不推挤布局。
- 终局呈现只消费流水与快照:核心对象销毁后呈现层对生命周期状态零回读;终局后重放渲染与实时渲染一致。
@@ -245,4 +245,5 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
|---|---|---|
| v0.1 | 2026-09-15 | 自归档 GDD 压缩改写 |
| v0.2 | 2026-09-15 | 雾格残留清理:关闭「待拍板」五条雾失义登记(雾图例条目、雾首遇一次性提示、四格态中的两个雾态、雾纹带与已开雾格 tint 残留、雾区全开合并域完成信号)。现行口径 = 格态两态一眼可辨(已开 / 未开)、图例面板只载现行格态与变异语义;通用变异框架的纹带形状通道保留为未来变异类型的呈现位 |
| v0.4 | 2026-09-16 | **敌人技能池批(与敌人系统 / 战斗结算 / registry / 切片代码同批)**:敌人卡义务改写——行为环环点拆除(proposal §2.4),意图徽标语义改「本拍摇定结果的动画占位」(普通攻击零表示 / 特殊技类型级提示两型:攻击系 ⚔ / 增益系 ✦),护甲行(护甲类型 + AC)保留常驻;敌方增益显示(蓄力/硬化)入敌卡状态行 + 状态槽图标键增 empower/fortify 两键(状态表九行全映射);呈现快照敌人字段消费面同步(摇定动作三字段 + effects,池与权重不投影);上游敌人系统行与验收条目同步 |
| v0.3 | 2026-09-16 | 格面资源批 UI 欠账收口(可选类道具与其轮末窗口已随 2026-09-15 代码批删除):战斗屏布局五区 → 四区(删 E 道具栏行,D 区加宽至约 1036px 并同步装板校验变量);删道具窗口弹层条款(原位点亮、非模态不入栈)、P0 闭环窗口锚点条目、「道具窗口原位点亮 / 至多选一件 / 禁用件带原因 / 空库存不弹窗」验收行;依赖与输出表的窗口选择提交、道具使用窗口呈现、库存查询义务删除或改述为现行口径(奖励身份投影经 `query_identity` 只读回调,零直读);关卡末清库反馈义务改载魔法道具背包,随魔法道具 P0 落地。魔法道具预留义务行与「待拍板」登记保持 |
@@ -60,7 +60,8 @@
| 行动经济表 `ActionEconomyTable` | 每轮行动点、动作价目、停手与刷新门槛 | [行动经济](../排雷循环/行动经济.md) |
| 棋盘生成参数表 `BoardGenTable` | 板尺寸、雷密度、可推理率目标等(进度键表) | [棋盘生成](../排雷循环/棋盘生成.md) |
| 变异表 `VariationTable` | 变异类型清单(现行空清单,合法态) | [地形变异](../排雷循环/地形变异.md) |
| 敌人表 `EnemyTable` | 敌种基线数值、遭遇数量区间、护甲类型、行为环(进度键表) | [敌人系统](../战斗循环/敌人系统.md) |
| 敌人表 `EnemyTable` | 敌种基线数值、遭遇数量区间、护甲类型、技能池模板(进度键表) | [敌人系统](../战斗循环/敌人系统.md) |
| 技能表 `SkillTable` | 技能行定义(类型/倍率/冷却/增益引用)与保底阈值 | [敌人系统](../战斗循环/敌人系统.md) |
| 主角表 `HeroTable` | 主角生命上限与基础攻击 | [敌人系统](../战斗循环/敌人系统.md) |
| 状态效果表 `StatusEffectTable` | 增益/减益效果定义行 | [增益状态](../战斗循环/增益状态.md) |
| 奖励表 `RewardTable` | 奖励条目、类别、权重与档位 | [地图战斗资源](../战斗循环/地图战斗资源.md) |
+55 -17
View File
@@ -20,8 +20,8 @@ entities:
owner: design/gdd/systems/表现与支撑/数据配置.md
attributes:
kind: autoload
streams: [board_gen, reward_roll, encounter, armor_roll]
stream_names: 当前封闭清单(增流需改代码 + P1 支柱评审;combat_var 已于 2026-09-07 full 评审删除——玩法随机不预置基础设施;encounter 增流 2026-09-12 用户拍板——遭遇随机化批,scope_event_binding = 每次战斗实例化恰一 scope;**armor_roll 增流 2026-09-16 用户拍板**——护甲等级掷点批,回合内随机首次入流:scope_event_binding = 每次掷点恰一 scope〔逐次掷逐次取号、可逐位回放〕,消费面 = 战斗结算 吃掷点分量判定)
streams: [board_gen, reward_roll, encounter, armor_roll, behavior_roll]
stream_names: 当前封闭清单(增流需改代码 + P1 支柱评审;combat_var 已于 2026-09-07 full 评审删除——玩法随机不预置基础设施;encounter 增流 2026-09-12 用户拍板——遭遇随机化批,scope_event_binding = 每次战斗实例化恰一 scope;**armor_roll 增流 2026-09-16 用户拍板**——护甲等级掷点批,回合内随机首次入流:scope_event_binding = 每次掷点恰一 scope〔逐次掷逐次取号、可逐位回放〕,消费面 = 战斗结算 吃掷点分量判定;**behavior_roll 增流 2026-09-16 用户拍板**——敌人技能池批(开轮摇号):回合内随机、时点前置到每轮开轮〔结算步骤流之外〕,scope_event_binding = 每敌每摇号恰一 scope,**派生向量 = spawn_index × 1000000 + 单敌摇号序**〔单敌出招轨迹可独立复放、敌互不穿插;取号门 = open_scope(stream, scope) 显式 scope 取号——计数器仍单调 +1 供观测〕,消费面 = 战斗结算 开轮摇号换算层 + 技能摇号器 SkillLottery)
stream_facade: open_stream(stream) 取号(计数器+1→派生→返回已播种实例);derive_at(stream, scope) 纯函数供测试/预览(不推计数器)
scope_event_binding: board_gen=每次生成新扫雷图(棋盘生成);reward_roll=每次 board install(格奖励批 + 清图奖励批)(地图战斗资源——2026-09-09 接口漂移改写,原"每次奖励结算 roll 批次";地图战斗资源 Rule 2/AC-1 为契约源);encounter=每次战斗实例化恰一 scope(战斗结算 begin_battle 实例化期、扫雷开始前——unit_count 区间均匀骰,挖掘决策域之外 P1 合规);armor_roll=每次掷点恰一 scope(战斗结算 战斗段吃掷点分量逐次判定——1d20,护甲等级掷点批 2026-09-16);armor_roll=每次掷点恰一 scope(战斗结算 战斗段吃掷点分量逐次判定——1d20,护甲等级掷点批 2026-09-16)
unseeded_access: 致命中止
@@ -34,17 +34,17 @@ entities:
attributes:
variables: [SALT_s, master_seed, scope]
output_range: "[0, 2^63)"
golden_vectors: 见《数据配置》Formulas-R2 向量表(7 常量:board_gen m=42 scope0-3 · reward_roll m=42 scope0=7422916049646135537 · encounter m=42 scope0=1061841193803499209 · armor_roll m=42 scope0=4806095330042243845——2026-09-16 护甲等级掷点批 armor_roll 增流补向量)
golden_vectors: 见《数据配置》Formulas-R2 向量表(7 常量:board_gen m=42 scope0-3 · reward_roll m=42 scope0=7422916049646135537 · encounter m=42 scope0=1061841193803499209 · armor_roll m=42 scope0=4806095330042243845——2026-09-16 护甲等级掷点批 armor_roll 增流补向量 · behavior_roll m=42 scope0=426657203655691839——2026-09-16 敌人技能池批 behavior_roll 增流补向量)
salt_board_gen: 0xA0761D6478BD642F
salt_reward_roll: 0xE7037ED1A0B428DB
salt_encounter: 0x8EBC6AF09C88C6E3(2026-09-14 encounter 增流——互异奇常数 ≠ 乘数)
salt_armor_roll: 0xC2B2AE3D27D4EB2F(2026-09-16 armor_roll 增流——互异奇常数 ≠ 乘数)
salt_armor_roll: 0xC2B2AE3D27D4EB2F(2026-09-16 armor_roll 增流——互异奇常数 ≠ 乘数)
salt_behavior_roll: 0x85EBCA7758C24E15(2026-09-16 behavior_roll 增流——互异奇常数 ≠ 乘数)
multiplier: 0x9E3779B97F4A7C15
impl_spec: wrap64=二补数自然回绕;禁 float 中间量、禁对中间量用 %、禁"主动修复溢出";常量用分段表达式书写;掩码收尾
reproducibility_claim: 同冒险内同流各 scope 无碰撞 + 跨版本恒定(不做跨 master_seed 全局单射声明)
master_seed_int64_min: 致命(abs 恒等回绕→与 0 静默别名)
set_master_seed_precondition: 仅全流计数器全零时受理(4 流——2026-09-14 数据配置 漂移批口径 + 2026-09-16 护甲等级掷点批 armor_roll 增流)
set_master_seed_precondition: 仅全流计数器全零时受理(5 流——2026-09-14 数据配置 漂移批口径 + 2026-09-16 护甲等级掷点批 armor_roll 增流 + 同日敌人技能池批 behavior_roll 增流)
referenced_by: [棋盘生成, 战斗结算, 地图战斗资源, 冒险结构, 存档序列化]
- name: difficulty_row_resolution
@@ -131,6 +131,8 @@ entities:
enrage: { display_name: 暴走(敌方增伤), target_side: enemy, magnitude: "+25 percent", duration_settlements: 1, max_stack: 16, stack_policy: stack, source_class: flag_miss } # 2026-09-12 标错反噬批上线——三池之一(进 enemy_output 池)
off_guard: { display_name: 失衡(我方承伤加深), target_side: hero, magnitude: "+25 percent", duration_settlements: 1, max_stack: 16, stack_policy: stack, source_class: flag_miss } # 2026-09-12 标错反噬批上线——三池之一(进 hero_defense 池,承伤 ×1.25);2026-09-15 格面资源批起 = defense 型负向(标错 defense 型安全格 / 踩到 defense 型雷格兑付)
sapped: { display_name: 乏力(我方输出降低), target_side: hero, magnitude: "-25 percent", duration_settlements: 1, max_stack: 16, stack_policy: stack, source_class: flag_miss } # 2026-09-15 格面资源批新增——weaken 型负向(标错 weaken 型安全格 / 踩到 weaken 型雷格兑付),进 hero_offense 池(attack_up 镜像);值 provisional
empower: { display_name: 蓄力(敌方输出 ×2), target_side: enemy, magnitude: "+100 percent", duration_settlements: 2, max_stack: 1, stack_policy: refresh } # 2026-09-16 敌人技能池批新增——蓄力技载体(敌方增益技施放,source_class=enemy_action),进 enemy_output 池;duration 2 = 1 个有效增益拍(先兑付后衰减含施放当轮);值 provisional
fortify: { display_name: 硬化(敌方防御 +100), target_side: enemy, magnitude: "+100 percent", duration_settlements: 2, max_stack: 1, stack_policy: refresh } # 2026-09-16 敌人技能池批新增——硬化技载体(Boss 池独占),进 enemy_defense 新池(敌方防御池:主角攻击乘区消费面归 战斗结算);duration 2 = 1 个有效增益拍;值 provisional
value_status: 七行值全部 provisional(2026-09-15 四行→六行——标错反噬批 enrage/off_guard 两行收编;六行→七行——格面资源批 sapped 收编为 weaken 型负向;伏击复用为反噬第三池项〔现为 blast 型负向〕;**2026-09-16 护甲等级掷点批:defense_up 行退役换装 ac_up〔护甲强化 +1 ac ×2 层〕——行数不变、一行换装,ac 幅度暂定值随掷点批定稿),验收锚——weaken→行动经济 F1 条款 2(地图战斗资源/战斗结算)· ambush 量纲→敌人系统(条款 3,**量纲已定稿见 ambush_unit_definition**)· 对照表→战斗结算(条款 1)· **weaken 已按 战斗结算 F0 裁决修订 −25→−65(2026-09-10,值域 [60,85]、终值共裁 战斗结算 OQ2——见 anchor_warning 回执)**
ambush_unit_definition: **量纲已定稿(2026-09-09 敌人系统 GDD Rule 6/F2——registry OQ2 关闭条件兑现)**:+3 flat = 每层伏击为每次攻击类行为(attack/heavy)附加 3 点主角 HP 伤害(后置加法、不进倍乘);charge 拍零兑付(空拍蒸发);值仍 provisional(锚验收归 地图战斗资源/战斗结算——行动经济 F1 条款 3;敌人系统 供给 cap 表值与单结算自然边界口径见 clause3_supply 条目)
anchor_warning: weaken 行 vs 行动经济 F1 条款 2 锚两独立线性代理同向预检不过(百分点·结算暴露 25 vs 100;原型战斗量级 ≈1 vs ≈4 当量)——战斗结算 对照验收优先裁决,登记 战斗结算 强制检查点输入。**修复空间不对称(2026-09-09 增补)**:每结算瞬时当量归一 m_w≥51 可行(F2 越域界随移 AP 界 −408%/表界 −816%);每 AP 总暴露归一 m_w>100 被 percent ∈ [-100,100] 域禁止(该分支不可行)——OQ1 前置步骤:战斗结算 先钉死归一口径,口径落定前禁止数值修订;落总暴露则现实解 = 地图战斗资源 重定最低档参照。**参照对象迁移注记(2026-09-09 地图战斗资源 评审;2026-09-11 回迁)**:最低档安全格 buff 当量的参照对象曾迁 defense_up 当量(attack_up 时为死行——地图战斗资源 F4 迁移)——**2026-09-11 用户裁决"基础攻击恒存在"(HeroTable.atk 域 [1,999]、0 加载拒绝、MVP=6)后 attack_up 复活,参照对象回迁 attack_up 当量**(消费面 = 主角战斗段先手攻击——战斗结算 GDD 同批;defense_up 迁移退役为历史记录,attack_up 入池时 tier_min_buff 重算);本条与 clause3_supply.direction_precheck 的 25 vs 100 预检算术系 attack_up 口径历史登记——defense_up |−50|×dur2 与 attack_up 数值巧合全等(100pp),今日结论不变;**迁移后预检未按新参照重跑**的义务已于 2026-09-14 战斗结算 重算批兑现(F0/F1 按 attack_up 当量重跑 + F1 锚对照实物改 attack_up 行——本迁移注记关闭)。**F0 裁决回执(2026-09-10 战斗结算 GDD——红警关闭)**:归一口径 = **每结算瞬时当量**(总暴露分支被 percent schema 域禁止 + 与"单结算自然边界"已锁事实冲突,排除);m_w 修订 −25 → **值域 [60,85]/默认 −65**(mvp_content.weaken 已同批改写——锚两腿 @D_ref=12:7.8 > 6.0 严格大于 ✓ / 7.8 ≥ E_dig 4.5 ✓〔E_dig 读数 2026-09-14 战斗结算 重算批按 难度曲线与Boss v3 曲线重推——原 7.2 随口径退役〕);地板 = 乘数零点 −100pp、交叉层数 = ⌈100/m_w⌉ = 2(原"7 层触地板"预读按 −400% 合成带估读,更正);defense_up 参照保持(总暴露分支的"重定参照"现实解未采纳);终值 = 地图战斗资源/战斗结算 共裁(战斗结算 OQ2);增益状态 GDD 正文不改以本条为准(收编先例)
@@ -201,9 +203,16 @@ entities:
单意图批: settle_batch 只装一个意图 ≡ 直接调 try_dig 等便捷 API,行为逐项相同
作废(voided): 轮次结束后未获执行的批次意图——零棋盘效果、零扣费、零事件,区别于非法拒绝
连锁不越旗: 连锁展开不经过插旗格(旗格不入链、不作传播源——2026-09-09 评审 GD-1 定稿,经典 flood 惯例)
行为环: 敌种出招的固定顺序表(如攻击→蓄力→重击),按结算轮转、环尾回环首——零随机、可学习(敌人系统 首次定义)
拍: 行为环里的一格行动("这一拍是重击")——敌人系统 首次定义
同步轮转: 同种敌人永远处于同一拍,任何死亡时序都不会让幸存者乱拍(敌人系统 F1 定理)
行为环: 【2026-09-16 敌人技能池批退役——本词条留档】敌种出招的固定顺序表(如攻击→蓄力→重击),按结算轮转、环尾回环首——零随机、可学习(敌人系统 首次定义;替代术语 = 技能池/开轮摇号)
拍: 一格行动的时间单位("这一拍是重击")——敌人系统 首次定义;2026-09-16 技能池批起"拍"保留为时间量词,拍的内容由开轮摇号决定〔原"行为环里的一格"定义随行为环退役〕
同步轮转: 【2026-09-16 敌人技能池批退役——本词条留档】同种敌人永远处于同一拍,任何死亡时序都不会让幸存者乱拍(敌人系统 F1 定理;技能池下同种各实例独立摇号,锁步不再存在)
技能池(池模板): 敌种用什么技能、多常用——带出招权重的技能行集合,内嵌敌行 pool 数组 {skill_id, weight};权重是份额不是固定百分比(敌人系统 首次定义,2026-09-16 敌人技能池批)
实例池: 单只敌人在开战时刻从池模板快照出的私有副本——各技能冷却剩余、摇定动作、保底计数;同种多敌各自实例池独立摇号互不干涉(敌人系统 首次定义,2026-09-16 敌人技能池批)
技能行: "技能是什么"的定义行——类型〔普通攻击/伤害/增益〕、倍率、冷却、效果引用,独立技能表承载、多敌种共享(敌人系统 首次定义,2026-09-16 敌人技能池批)
开轮摇号: 每轮轮次开始时为每个存活敌摇定本拍动作——排除冷却中技能后按权重份额归一抽签,中签率 = 权重 ÷ 可摇权重合计;随机消费在 战斗结算 换算层 behavior_roll 封闭流(敌人系统 首次定义,2026-09-16 敌人技能池批)
保底: 连续 M 拍执行的都是普通攻击 → 下拍必出特殊技(特殊技内按权重、不受冷却限制)——反旱季闸(敌人系统 首次定义,2026-09-16 敌人技能池批)
技能冷却: 施放后连续 N 拍不可摇中的技能行属性——与增益持续(状态行属性)是两个计时器互不相干(敌人系统 首次定义,2026-09-16 敌人技能池批)
施放行: 增益技的结算明细行型(cast)——0 伤害动作、具名技能、不兑付伏击;由蓄力行(charge)改型(敌人系统/战斗结算 首次定义,2026-09-16 敌人技能池批)
伏击池: 敌方侧共享的伏击层数——本轮踩几颗雷就几层,轮末清零(side-wide 语义,敌人系统 首次定义)
溢出(伏击): 单轮踩雷超过上限(cap)的部分——转为"溢出未生效"明细行可见、不生效(敌人系统 首次定义)
死亡即不动手: 轮到某敌人行动时它已死 → 这一下不打、招式轮转停格(2026-09-09 用户拍板,敌人系统 首次定义)
@@ -246,16 +255,15 @@ entities:
unit_count_max: int [unit_count, 3] 可省略(2026-09-14 难度曲线与Boss 修订批新增——遭遇区间上界;缺省 = 定值,旧行零迁移)
atk_min/atk_max: 可选配对 int [0,999](2026-09-14 敌方攻击区间批——上阶/下阶区间随机;缺省 = 定值行向后兼容;跨字段:必成对 · **atk = (min+max)/2 中点锚**;roll 时机 = 遭遇骰同流〔encounter 恰一 scope,数量骰先、atk 骰后〕spawn 期**整战持久**〔每拍抖动否决——数字可读性锚〕,战斗结算.begin_battle roll → 敌人系统.spawn enemy_atk_override 收确定值〔敌人系统 零随机纪律保持〕;MVP 六行 ±3 provisional 归 难度曲线与Boss;验收 = 完美策略档 0 加成通关 ≤40%〔OQ7〕;接口漂移 = 敌人系统.spawn 签名 +1 参数〔正式侧 GDD/registry 修订批随 ADR-0009 同批〕)
armor_type: enum {light, ward, heavy} 缺省 light(2026-09-14 护甲克制批转正登记——攻击类型 × 护甲克制,克制矩阵 = settlement_table.armor_effectiveness〔克制 ×150 / 中性 ×100 / 被克 ×50〕;MVP 映射 goblin=light / bugbear=ward / ogre_chief=heavy;**2026-09-15 命名统一批:原 enum {none, a, b} 为位置键(无语义、且"无甲/甲A/甲B"在文档间已分裂成四种写法),改 {light, ward, heavy} = 轻甲 / 魔法护甲 / 重甲**;数值 provisional 归 难度曲线与Boss)
armor_class: int 取值域未定(**2026-09-15 护甲等级批新增,provisional**——防守方护甲等级,主角基础攻击普通分量 / 敌方攻击拍与重击拍基线的掷点折扣判定读数,掷点与折扣结算归 settlement_table.ac_roll / ac_discount_tiers〔战斗结算〕;**与 armor_type〔克制属性轴 light/ward/heavy〕是两个概念、字段与文档口径不得混用**;三敌种行值未定,投值归 难度曲线与Boss 随掷点形式定稿批)
armor_class: int [0,999] 缺省 0(**2026-09-15 护甲等级批新增,2026-09-16 定稿落地**——防守方护甲等级,主角普通分量 / 敌方技能基础伤害分量的掷点折扣判定读数,掷点与折扣结算归 settlement_table.ac_roll / ac_discount_tiers〔战斗结算〕;**与 armor_type〔克制属性轴 light/ward/heavy〕是两个概念、字段与文档口径不得混用**;三敌种首关行值 0 暂定,正式行值归怪物设定批〔06 清单登记〕)
hp: int [1,999](knob——难度修饰前基线;MVP 18 provisional 出处待查)
atk: int [0,999](knob——attack 行为伤害基值;MVP 4 provisional 出处待查;0 = 纯伏击伤敌种合法)
heavy_multiplier: int [1,16](knob;无 heavy 拍敌种不消费——非校验项,增益状态 max_stack 先例;MVP 2 provisional)
pattern: StringList(行为环;元素 ∈ {attack, charge, heavy} MVP 校验域——guard/boost/hinder/summon 预留值校验拒绝〔棋盘生成 solver_rules 冻结先例〕;环长 [1,8])
pool: Array of {skill_id, weight}(**2026-09-16 敌人技能池批——池模板**,定义层;weight ∈ (0,9999] knob、池内 skill_id 不重复;每池恰一行普通攻击 + 增益技 ≤1 + skill_id 悬空致命〔加载校验在 config_db.validate_skill_pool_links 跨表通道〕;实例池〔快照+冷却状态〕在 单位容器 spawn——三敌种建议池 goblin {attack 9, lunge 1} / bugbear {attack 6, charge 2, heavy 2} / ogre_chief {attack 6, heavy 2, harden 2},全部 provisional 归 难度曲线与Boss;EV 锚 静态 1.05/1.00/1.00,含闸长期速率实测 1.106/1.000/1.024〔×5000 拍,随批登记〕)。**历史(2026-09-16 退役)**:heavy_multiplier int [1,16](重击倍率归技能行 damage_mult)与 pattern StringList(行为环环数组)两字段随行为环整体退役——固定出招剧本废除
advantage_per_round_cap: int [0,16](knob;**MVP 默认 4 定稿**〔2026-09-09——反灾难护栏定位,旧默认 8 溢出协议死码已废弃〕;0 = 免疫伏击合法极端;行动经济 F1 条款 3 供给表值来源字段)
rowset_rule: 每行集恰一敌种(行集内 enemy_id 唯一 = 表结构校验,违规加载期致命)——多敌种同深度选择规则归 关卡节点地图/难度曲线与Boss(provisional);精英/Boss 编码位预登记 敌人系统 OQ7(键冻结下同深度第二敌种行触发行集致命——改键走 数据配置 Rule 4 接口漂移,关卡节点地图 定稿时强制核对)
mvp_content: **恰 3 敌种(2026-09-14 难度曲线与Boss 修订批转正——DND 更名批用户指令:grub→goblin / ambusher→bugbear / boss_guardian→ogre_chief;旧两敌种登记由本条替代;敌人系统 AC-16③ 物种集断言随批更新为 {goblin, bugbear, ogre_chief})**:goblin(地精,[attack] 单拍环——L1 D0 24hp/7atk ×[1,2] 教学位固定 ×1、L1 D1 群 13/5 ×[2,3];armor=light)· bugbear(熊地精,[attack,charge,heavy] 三拍环——L1 D2 40/9 ×1、L1 D3 群 18/6 ×[2,3]、L2 D0 锚行 30/7;armor=ward)· ogre_chief(食人魔酋长,[attack,attack,charge,heavy] 四拍 Boss 环——L1 D4 150/9 ×1;armor=heavy);heavy_multiplier=2 全员;出处 = 切片 enemy.tres v3(试玩批次 1 裁决 + 遭遇随机化批),曲线数值归 难度曲线与Boss
load_bearing_rules: 多敌模型(1–3 同敌种单位,增益状态 单敌假设回验不成立——见 status_effect.single_enemy_assumption)· 行为环确定性轮转(零随机;先作用后推进;死亡即不动手——轮到前被杀本拍不打指针冻结;同步轮转定理)· 效果 side-wide(伏击池 = side 共享,增益状态 零接口变更)· 行值快照抗热重载(增益状态 EC9 同款)· 零随机零 UI 零事件订阅(排雷玩法 事件面不含 敌人系统——事实经 战斗结算 转化)
settlement_obligations: 六项(敌人系统 GDD 门禁评审缺项即 blocking)——① 推进每结算恰一次(敌单位专属——主角无环)+ 结算内调用序成文 query_attack→apply_damage→advance ② 雷→伏击恰一次转化 ③ clamp/负值/溢杀与死亡判定时机成文 ④ F1 条款 3 闭合对账(cap 表值 + 量纲 + 棋盘生成 actuals p10 非 target 参数)⑤ 默认目标消费与单体/AOE 分流 ⑥ 伏击 cap 溢出"溢出未生效"明细行协议。**履行回执(2026-09-10)**:六项已全数落位 战斗结算 GDD(Core Rules 5/6/9/10/12 + Formulas F3/F4——落位映射 = 战斗结算 AC 覆盖矩阵)
mvp_content: **恰 3 敌种(2026-09-14 难度曲线与Boss 修订批转正——DND 更名批用户指令:grub→goblin / ambusher→bugbear / boss_guardian→ogre_chief;旧两敌种登记由本条替代;敌人系统 AC-16③ 物种集断言随批更新为 {goblin, bugbear, ogre_chief})**:goblin(地精,池 {attack 9, lunge 1}——L1 D0 24hp/7atk ×[1,2]、L1 D1 群 13/5 ×[2,3];armor=light)· bugbear(熊地精,池 {attack 6, charge 2, heavy 2}——L1 D2 40/9 ×1、L1 D3 群 18/6 ×[2,3]、L2 D0 锚行 30/7;armor=ward)· ogre_chief(食人魔酋长,池 {attack 6, heavy 2, harden 2}——L1 D4 150/9 ×1;armor=heavy);**2026-09-16 敌人技能池批**:行为环拍序列([attack] 单拍环 / [attack,charge,heavy] 三拍环 / [attack,attack,charge,heavy] 四拍环)与 heavy_multiplier=2 退役,改池模板 + 技能表共享技能行(重击倍率 2 = 技能行 damage_mult);出处 = 切片 enemy.tres(技能池批改型),曲线数值与池权重归 难度曲线与Boss
load_bearing_rules: 多敌模型(1–3 同敌种单位,增益状态 单敌假设回验不成立——见 status_effect.single_enemy_assumption)· 行为环确定性轮转(零随机;先作用后推进;死亡即不动手——轮到前被杀本拍不打指针冻结;同步轮转定理)· 效果 side-wide(伏击池 = side 共享,增益状态 零接口变更)· 行值快照抗热重载(增益状态 EC9 同款——池模板同在快照范围,实例池冷却全零起步)· **技能池摇号出招(2026-09-16 敌人技能池批改写——原"行为环确定性轮转/同步轮转定理"退役)**:每拍做什么 = 实例池权重摇号〔开轮时点,随机消费在 战斗结算 换算层 behavior_roll 封闭流;本系统语义零随机不变、单位容器收确定值〕;两道闸 = 全技能各自冷却 + 保底 M;死亡即不动手(摇定后兑现前死亡 → 本拍作废不进账);拍结构保留(每结算每存活敌恰行动一次/行序/先作用后推进)· 零随机零 UI 零事件订阅(排雷玩法 事件面不含 敌人系统——事实经 战斗结算 转化)
settlement_obligations: 六项(敌人系统 GDD 门禁评审缺项即 blocking)——① 行动每结算恰一次(敌单位专属——主角无环)+ 结算内调用序成文 按摇定技能行兑现→apply_damage→commit_action〔2026-09-16 技能池批改型——原 query_attack→apply_damage→advance 随行为环退役〕 ② 雷→伏击恰一次转化 ③ clamp/负值/溢杀与死亡判定时机成文 ④ F1 条款 3 闭合对账(cap 表值 + 量纲 + 棋盘生成 actuals p10 非 target 参数)⑤ 默认目标消费与单体/AOE 分流 ⑥ 伏击 cap 溢出"溢出未生效"明细行协议。**履行回执(2026-09-10)**:六项已全数落位 战斗结算 GDD(Core Rules 5/6/9/10/12 + Formulas F3/F4——落位映射 = 战斗结算 AC 覆盖矩阵)
hud_obligations: 四项(视觉推测型呈现,缺项即 blocking)——① enemy_id→图像线索映射清单成文(预告时序=下一拍预告、挖掘期全程可见;含强度档位/环长直觉编码说明;形状+色相双通道)② 映射全局恒定(enemy_id 键——跨 run 禁重掷)③ 顺位与存活可读 ④ 敌方 side 池呈现口径
rule4_status: 本表是进度成长表(棋盘生成 BoardGenTable 同型,无 Rule 4 豁免)
referenced_by: [战斗结算, 地图战斗资源, 关卡节点地图, 难度曲线与Boss, HUD框架, 战斗表现, 教学引导]
@@ -273,6 +281,22 @@ entities:
hp_persistence: 跨战斗 HP 持久性归 关卡节点地图/冒险结构(provisional)——敌人系统 单位接口以初始 hp 入参,不定义冒险内持久性
referenced_by: [战斗结算, 地图战斗资源, 难度曲线与Boss]
- name: skill
type: schema_data
owner: design/gdd/systems/战斗循环/敌人系统.md
attributes:
table: SkillTable(**2026-09-16 敌人技能池批新增**——数据配置 承载、敌人系统 回填 owner;行 = Dictionary 键 skill_id,Rule 4 豁免;全局单表多敌种共享)
fields:
skill_id: String 全表唯一(池条目引用键——悬空引用加载致命〔config_db.validate_skill_pool_links〕)
display_name: String 非空(玩家可见名——明细行与动画共用,本地化归 设置与本地化)
type: enum {basic, damage, buff}(封闭枚举 = 普通攻击/伤害/增益;新类型走 敌人系统 修订)
damage_mult: float [1.1,16] 仅伤害技(provisional——**存倍率不存固定值**:伤害 = 施放者攻击基线 × 倍率,三乘法链第一乘〔战斗结算〕;普通攻击恒 1 不可配——域校验锁死)
cooldown_beats: int [0,99](provisional 现行 突刺 2 · 蓄力 3 · 重击 3 · 硬化 4 · 普攻恒 0 不可配——域校验锁死,空池不可能的结构前提;施放后连续 N 拍不可摇中,技能冷却与增益持续两计时器分账)
buff_ref / buff_duration: 仅增益技(效果引用 → StatusEffectTable 活行 + 持续回合;悬空引用加载致命;现行 charge→empower / harden→fortify,buff_duration=2 = 1 个有效增益拍〔先兑付后衰减含施放当轮〕;施放时长由技能行覆盖状态行时长——战斗结算 apply_effect duration_override)
pity_threshold: int [1,99] 全局单旋钮(provisional 现行 4——保底 M:连续 M 拍执行普攻 → 下拍必出特殊技〔特殊技内按权重、不受冷却限制〕;地精类单特殊技低权重池受保底支配)
mvp_content: 五行 = attack 普通攻击(basic)· lunge 突刺(damage ×1.5 cd2)· charge 蓄力(buff→empower cd3)· heavy 重击(damage ×2.0 cd3)· harden 硬化(buff→fortify cd4,Boss 池独占——增益 ≤1 纪律下旧蓄力拍不进 Boss 池)
referenced_by: [敌人系统, 战斗结算, 难度曲线与Boss, 数据配置]
- name: reward_table
type: schema_data
owner: design/gdd/systems/战斗循环/地图战斗资源.md
@@ -317,16 +341,16 @@ entities:
p_perfect: int {100,200} percent(knob,默认 200 provisional——完美排雷清图奖励**第二份兑付触发**;2026-09-10 战斗结算 评审批修订:真实双份交付载体〔Core Rule 13#2〕——100 = 仅高光、200 = 双份交付,非整数倍数乘数无运行时载体、域外加载致命;上界受护栏 B 约束〔双读数:D_ref 口径 24 / DPS 严格读数 ≈8,裁决归 难度曲线与Boss〕)
segment_order: StringList(结构冻结——段序骨架 [grant, buff_debuff, combat, decay, verdict];加载期恒等拍板序校验违例致命,防实现乱序静默漂移;2026-09-15 格面资源批五段——item_window/item 两段随可选类删除下线,旧七段序见底部修订记录)
source_label_map: Dictionary source_class→文案键(结构冻结——P2 归因呈现映射;键集 = 增益状态 source_class ∪ {判定扩展, enemy_action, flag_miss}——2026-09-10 战斗结算 评审批修订;2026-09-15 格面资源批删"道具"扩展键;新增键 = 接口漂移)
effect_pool_map: Dictionary effect_id→pool {hero_offense|hero_defense|enemy_output|flat_attach}(结构冻结——F2 效果语义映射,增益状态 Rule 7 载体;键 ⊆ StatusEffectTable 活行集、悬空加载期致命;新 effect_id 上线须随批登记——2026-09-15 格面资源批随 sapped 行收编登记:sapped → hero_offense;**2026-09-16 护甲等级掷点批:删 defense_up → hero_defense 键**〔减伤百分比行退役,hero_defense 池现仅 off_guard 正向行〕;ac_up 为 AC 阈值轴效果、不入任何池)
effect_pool_map: Dictionary effect_id→pool {hero_offense|hero_defense|enemy_output|enemy_defense|flat_attach}(结构冻结——F2 效果语义映射,增益状态 Rule 7 载体;键 ⊆ StatusEffectTable 活行集、悬空加载期致命;新 effect_id 上线须随批登记——2026-09-15 格面资源批随 sapped 行收编登记:sapped → hero_offense;**2026-09-16 护甲等级掷点批:删 defense_up → hero_defense 键**〔减伤百分比行退役,hero_defense 池现仅 off_guard 正向行〕;ac_up 为 AC 阈值轴效果、不入任何池;**2026-09-16 敌人技能池批:增 empower → enemy_output · fortify → enemy_defense**〔敌方防御池新词汇——硬化载体,消费面 = 主角攻击 Σ三分量后 ×(100+clamp(enemy_defense))/100,四池对称:敌打主角 = hero_defense × enemy_output、主角打敌 = hero_offense × enemy_defense;切片现状注记:hero_offense 现行走普通分量 attack_up 直乘、未并入合计乘区,正式化归 ADR 批〕)
pool_clamp_lo: int pp(结构冻结 −100——percent 池地板域下界,乘数域随之 [0,2];2026-09-10 统一八字段口径)
pool_clamp_hi: int pp(结构冻结 +100——percent 池地板域上界;原"七字段 pool_clamp_lo_hi"措辞作废,数据配置 GDD 行按收编先例以本条为准)
round_direction: enum {floor}(结构冻结——单一取整规则)
charge_grants: Dictionary 奖励触发源→{attack_type, per_unit, cap}(**2026-09-15 攻击类型/奖励抽象分离批新增**——攻击类型 = 伤害的属性轴(定义不含来源),充能 = 按攻击类型分桶的附加伤害储备;"哪类棋盘事实充哪个桶、每单位多少点"归本字典 = 奖励定义,替换字典即改奖励内容,攻击类型与 armor_effectiveness 不动;现行 safe_cell(开格)→ pierce 1/格 cap 8 · flag_hit(标中旗)→ magic 2/雷 cap 0=不封顶;**原 pierce_per_cell / pierce_cap / magic_per_flag("类型×来源"焊死命名,更早为 damage_a_per_cell / damage_a_cap / damage_b_per_flag)同批作废**;数值 provisional 归 难度曲线与Boss;正式归属(地图战斗资源 的奖励类别/效果面)见 战斗结算「待办」)
armor_effectiveness: Dictionary 攻击类型×护甲→百分比(键集结构冻结 {normal, pierce, magic} × {light, ward, heavy};默认 normal 全 100 / pierce→ward=150、heavy=50 / magic→heavy=150、ward=50——克制 ×150 / 中性 ×100 / 被克 ×50;**2026-09-15 命名统一批:改为语义键,原 {base, dmg_a, dmg_b} × {none, a, b} 位置键作废**;数值 provisional 归 难度曲线与Boss)
ac_roll: 护甲等级掷点(**2026-09-16 护甲等级掷点批定稿,数字即规范**——骰面 roll_faces=20、nat 最大面 crit_roll=20、暴击倍率 crit_multiplier=200、攻击加成 attack_bonus=0〔结构位,元进度/武器附魔未来投值〕、伤害下限 damage_floor=1、流名 armor_roll_stream="armor_roll";判定式:掷 1d20,掷点 ≥ 防守方护甲等级 + 攻击加成 → 全额、低掷按缺口查档〔见 ac_discount_tiers〕,nat20 暴击该基础攻击分量 ×2 且恒全额〔双方同规〕;任何攻击落地伤害 ≥ damage_floor〔全局 max(1,·)——零值伤害行语义退役,重档折扣行文本记"被压制"〕;吃掷点分量 = 主角先手攻击普通分量 / 敌方攻击拍与重击拍基线伤害,穿刺/魔法充能分量与棋盘来源伤害〔伏击附加 / 爆裂 / 清障炸药 / 奖励即时〕必中全额不掷;回合内随机、消费 armor_roll 封闭流逐次掷逐次取号,可逐位回放)
ac_roll: 护甲等级掷点(**2026-09-16 护甲等级掷点批定稿,数字即规范**——骰面 roll_faces=20、nat 最大面 crit_roll=20、暴击倍率 crit_multiplier=200、攻击加成 attack_bonus=0〔结构位,元进度/武器附魔未来投值〕、伤害下限 damage_floor=1、流名 armor_roll_stream="armor_roll";判定式:掷 1d20,掷点 ≥ 防守方护甲等级 + 攻击加成 → 全额、低掷按缺口查档〔见 ac_discount_tiers〕,nat20 暴击该基础攻击分量 ×2 且恒全额〔双方同规〕;任何攻击落地伤害 ≥ damage_floor〔全局 max(1,·)——零值伤害行语义退役,重档折扣行文本记"被压制"〕;吃掷点分量 = 主角先手攻击普通分量 / 敌方技能基础伤害分量〔= 攻击基线 × 技能伤害倍率——2026-09-16 敌人技能池批改述,原"敌方攻击拍与重击拍基线伤害"退役;无伤害分量的增益技不掷不折〕,穿刺/魔法充能分量与棋盘来源伤害〔伏击附加 / 爆裂 / 清障炸药 / 奖励即时〕必中全额不掷;回合内随机、消费 armor_roll 封闭流逐次掷逐次取号,可逐位回放)
ac_discount_tiers: Dictionary 缺口档边界→档位值 percent(**2026-09-16 定稿**——roll_tier_bounds=[3,7] · roll_tier_discounts=[75,50,25],档数 = 边界数 + 1;缺口 1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25,档位不设零档〔非归零〕,取整沿全局 floor;EV 参考数〔按判定式复算〕= AC10 期望减免 21.25% / AC11 25% / AC12 28.75%;微调旋钮 = 边界与档位值,不动 AC 值;难度曲线与Boss 生存链区间带宽补扫含暴击档与折扣档、"零红档"验收保留)
validation: segment_order 违例致命 · effect_pool_map 悬空致命 · p_perfect 域 {100,200}(2026-09-10 载体域——100 = "完美必须 ≥ 不加倍"机制语义下界,200 = 双份交付)——战斗结算 冒烟三行致命 manifest(segorder_violation / poolmap_dangling / pperfect_oob)
mechanics: 结算段序骨架唯一权威载体(授予→增益/减益→战斗→衰减→判定——2026-09-15 格面资源批五段,item_window/item 两段随可选类删除下线)· **随机口径(2026-09-15 护甲等级批修订)**:结算换算层引入回合内随机(ac_roll 护甲等级掷点——必中非归零、低掷按 ac_discount_tiers 档位折扣),掷点流归属未定〔rng_service 流封闭清单 board_gen/reward_roll/encounter 不含结算内消费——新增流须改代码+P1 支柱评审、复用 encounter 则改"实例化期恰一 scope"绑定,登记 战斗结算「待办」,rng_service 条目随定稿批修订〕· 可回放契约 = **封闭随机流可回放**(同 master_seed 同意图序列 ⇒ 步骤流逐位复现——数据配置 AC-15 落点,原"结算路径零随机 / 步骤流逐行相同"口径随批作废;开战掷区间 / 装板摇号属初始化随机维持不变)· 调用序钉死 query_attack→apply_damage→advance · 衰减恒执行含末轮 · 高光双落载体(perfect_sweep P_perfect——2026-09-10 修订为真实双份兑付载体 / 标中旗 highlight 标记)· 流水行协议字段分层(公共 7 字段 + 行类型特有 target/effect_id/pool_value/聚合计数——2026-09-10 评审批修订;行类型封闭枚举 10 类——2026-09-15 格面资源批由 15 类收拢:删道具行 / 已消耗未生效行 / 使用被拒行 / 窗口打开锚行 window_open / 窗口跳过锚行 window_skip 五类随可选类删除下线〔2026-09-15 窗口锚行批曾由 13 扩为 15,同日回撤〕;行来源断言两层豁免集合同批修订 = 行型豁免 {decay, verdict} × 来源类别豁免 {hero_attack, enemy_action, clear_reward},豁免不等于免检——豁免行仍受公共字段与各自语义约束,清单改动须同步 战斗结算 明细流水一节与 battle_log_view 格式化)· **穿刺/魔法 充能与护甲克制结算规格 = 战斗结算 GDD F6(W3 补全,2026-09-14 重算批;2026-09-15 命名统一批)**——挖掘期充能/战斗段消费/单轮生命周期、不吃 percent、逐分量 armor_effectiveness 查表 floor 相加
mechanics: 结算段序骨架唯一权威载体(授予→增益/减益→战斗→衰减→判定——2026-09-15 格面资源批五段,item_window/item 两段随可选类删除下线)· **随机口径(2026-09-15 护甲等级批修订)**:结算换算层引入回合内随机(ac_roll 护甲等级掷点——必中非归零、低掷按 ac_discount_tiers 档位折扣),掷点流归属未定〔rng_service 流封闭清单 board_gen/reward_roll/encounter 不含结算内消费——新增流须改代码+P1 支柱评审、复用 encounter 则改"实例化期恰一 scope"绑定,登记 战斗结算「待办」,rng_service 条目随定稿批修订〕· 可回放契约 = **封闭随机流可回放**(同 master_seed 同意图序列 ⇒ 步骤流逐位复现——数据配置 AC-15 落点,原"结算路径零随机 / 步骤流逐行相同"口径随批作废;开战掷区间 / 装板摇号属初始化随机维持不变)· 调用序钉死 按摇定技能行兑现→apply_damage→commit_action(2026-09-16 技能池批改型——原 query_attack→apply_damage→advance 随行为环退役;行型 10 类同批改型:蓄力行 charge → 施放行 cast〔增益技 0 伤害施放、具名技能、不兑伏击〕)· 衰减恒执行含末轮 · 高光双落载体(perfect_sweep P_perfect——2026-09-10 修订为真实双份兑付载体 / 标中旗 highlight 标记)· 流水行协议字段分层(公共 7 字段 + 行类型特有 target/effect_id/pool_value/聚合计数——2026-09-10 评审批修订;行类型封闭枚举 10 类——2026-09-15 格面资源批由 15 类收拢:删道具行 / 已消耗未生效行 / 使用被拒行 / 窗口打开锚行 window_open / 窗口跳过锚行 window_skip 五类随可选类删除下线〔2026-09-15 窗口锚行批曾由 13 扩为 15,同日回撤〕;行来源断言两层豁免集合同批修订 = 行型豁免 {decay, verdict} × 来源类别豁免 {hero_attack, enemy_action, clear_reward},豁免不等于免检——豁免行仍受公共字段与各自语义约束,清单改动须同步 战斗结算 明细流水一节与 battle_log_view 格式化)· **穿刺/魔法 充能与护甲克制结算规格 = 战斗结算 GDD F6(W3 补全,2026-09-14 重算批;2026-09-15 命名统一批)**——挖掘期充能/战斗段消费/单轮生命周期、不吃 percent、逐分量 armor_effectiveness 查表 floor 相加
closure_receipt: 行动经济 F1 条款①②③⑤闭合 owner = 战斗结算(2026-09-10 F0–F5 首版验算完成——F0 瞬时当量口径 + m_w 值域裁决见 status_effect.anchor_warning;条款③ clause3_supply 闭合回执;条款⑤ clause5_dominance 条目;actuals p10 交付后强制重跑)
referenced_by: [HUD框架, 战斗表现, 音频, 教学引导, 难度曲线与Boss]
@@ -651,3 +675,17 @@ entities:
# ④ status_effect:defense_up 行退役换装 ac_up(护甲强化 +1 ac ×2 层,unit=ac 新量纲,不入任何池);
# ⑤ reward_table:defense 型正向行 defense_up → ac_up(显示名暂定"护甲强化",权重/档位/cell_type 零改动);
# ⑥ settlement_table.effect_pool_map 删 defense_up 键(hero_defense 池现仅 off_guard 正向行——×0 免伤墙结构面消失)。
# 2026-09-16 敌人技能池批(用户拍板 2026-09-16,设计权威 docs/proposal-skill-pool.md v1.0——与 敌人系统 v0.5 /
# 战斗结算 v0.11 / 增益状态 v0.5 / 难度曲线与Boss v0.6 / 战斗表现 v0.3 / HUD框架 v0.4 / 数据配置 / 03-内容规模 /
# 02-系统清单 / 关卡节点地图 / 06-待办清单 / ux battle.md / architecture.md / 切片代码同批):
# ① rng_service 增流 behavior_roll(开轮摇号——回合内随机、时点前置到每轮开轮;派生向量 = spawn_index×1000000 +
# 单敌摇号序,单敌轨迹独立复放)+ salt_behavior_roll 0x85EBCA7758C24E15 + 黄金向量(m=42 scope0 =
# 426657203655691839)+ open_scope 显式 scope 取号门 + 重播种前提改 5 流;
# ② 新增 skill 条目(SkillTable 五行:普攻/突刺/蓄力/重击/硬化 + pity_threshold=4——池构成纪律与悬空引用加载校验);
# ③ enemy 条目:pattern/heavy_multiplier 退役改 pool 池模板(建议池 goblin 9/1 · bugbear 6/2/2 · 酋长 6/2/2,
# EV 静态锚 1.05/1.00/1.00、含闸实测 1.106/1.000/1.024);armor_class 行值改暂定 0 口径;
# ④ status_effect:新增 empower(蓄力 → enemy_output)/ fortify(硬化 → enemy_defense 新池)两行;
# ⑤ settlement_table:effect_pool_map 增 empower/fortify 两键(enemy_defense 敌方防御池新词汇——硬化载体);
# ac_roll 吃掷点分量措辞改"敌方技能基础伤害分量"(三乘法链:倍率乘在最前);调用序改 摇定兑现→apply_damage→
# commit_action;行型改型 蓄力行 charge → 施放行 cast;
# ⑥ terminology:技能池/实例池/技能行/开轮摇号/保底/技能冷却/施放行 七新词条;行为环/同步轮转退役留档、拍 改时间量词。
+3 -3
View File
@@ -111,7 +111,7 @@
|---|---|---|---|---|---|
| A | LevelStripView | 步进条 | 节点链 + 当前位置 | 只读(点击节点不可跳) | ✅ level_strip_view.gd 迁移 |
| B | 英雄卡 | 卡片 | 小人像、HP 条、攻击值、状态效果槽 ×4 | 只读 | HUDView 拆分 |
| B | 敌人卡 ×1–3 | 卡片 | 敌像、血条、顺位徽标、意图徽标、行为环点、状态槽(含敌方 side 池口径:伏击层数为全体敌人共享单值、各卡同值呈现——数据源 = 战斗结算流水经增益状态投影,敌人系统×4④) | 只读 | 新建 |
| B | 敌人卡 ×1–3 | 卡片 | 敌像、血条、顺位徽标、意图徽标、状态槽(含敌方 side 池口径:伏击层数为全体敌人共享单值、各卡同值呈现——数据源 = 战斗结算流水经增益状态投影,敌人系统×4④;2026-09-16 敌人技能池批改型:行为环点拆除〔proposal §2.4〕,意图徽标语义 = 本拍摇定结果的动画占位——普通攻击零表示 / 特殊技类型级提示,敌方增益〔蓄力/硬化〕入状态行) | 只读 | 新建 |
| B | 轮次·阶段指示 | 标签 | "第 N 轮 · 挖掘中/结算中"(终局定格「战斗结束」;Boss 变体附 Boss 名) | 只读 | 新建 |
| B | 结算明细带 | 滚动列表 | 结算期逐行流水([战斗结算](../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋) 10 类行协议);挖掘期收起为上轮摘要 1 行 | 点击展开明细回看 overlay | battle_log_view.gd 改造 |
| B | 结算明细回看 | 模态 overlay | 完整流水逐行(现行 10 类行协议;未生效类行聚合计数) | 关闭 | battle_log_view.gd 改造 |
@@ -136,7 +136,7 @@
- **上游修订(2026-09-12 已落稿,OQ11)**:① 行动经济 `request_refresh` Active 相位受理 + 每轮上限 2 次(Waiting 代理退役)② 排雷玩法装板受理域增 Active·仅刷新路径 ③ 编排 owner = **战斗结算 `refresh_board`**(行动经济扣费 → 排雷玩法 retire → 棋盘生成 generate → 排雷玩法 install → 地图战斗资源 roll_board 恰一次;无结算段、无 round_end——战斗结算编排唯一性原则保持)④ 旧板旗随旧板**作废**(零兑付、旗 AP 不返还——排雷玩法 EC11 全量重置承载;确认微交互须显式提示)。**经济预警**:当场刷新无结算发生,原「敌方时间税」自惩失效——行动经济 OQ3 弃图率/刷新率预警阈值联动复核。
- 备选(已否决):结算期决策提示(Waiting 内联)——结算期不做刷新决策(用户拍板);挖掘期预承诺 stop+reroll 复合——决策即终轮,不符「换图继续挖」语义。
**敌人意图线索映射目录(敌人系统×4① 成文占位,2026-09-12)**:
**敌人意图线索映射目录(敌人系统×4① 成文占位,2026-09-12;2026-09-16 敌人技能池批改型注记:行为环三态〔攻击/蓄力/重击〕预告随行为环退役——现行数据面 = 敌人系统三档可见性规格〔普攻零表示 / 特殊技类型级动画〔攻击系/增益系两型〕/ 暴击掷点层无前置〕,池与权重不公开;下表 grub/ambusher 三态清单留档作废,终稿资产目录随战斗表现样张批重立)**:
| enemy_id | 视觉状态(意图线索) | 强度档位编码 | 环长编码 |
|---|---|---|---|
@@ -278,7 +278,7 @@
| AP / RAP / 本轮刷新计数 | 行动经济 | Read | 常显;刷新入口可用性 = RAP 读数客户端预判(RAP≥8)+ `request_refresh` 返回值(行动经济 无 `can_request_refresh` API——仅 bool 返回 + 只读观测量;行动经济 EC13 拒绝合法可见) |
| 结算流水行(10 类封闭协议) | [战斗结算](../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋) 流水协议 | Read | 逐行来源标注(P2);未生效类行(溢出未生效/奖励未生效)聚合计数呈现 |
| 判定 verdict | 战斗结算 判定段 | Read | 终局画面数据锚 |
| 敌单位 hp/顺位/意图环下一拍/enemy_id/存活 | 敌人系统 只读面 | Read | 意图线索映射全局恒定(敌人系统×4②);**despawn 前捕获**(旧版台账 entry 17③——缺陷②教训) |
| 敌单位 hp/顺位/摇定动作(pending_skill/type/name)/敌方增益/enemy_id/存活 | 敌人系统 只读面(经战斗结算呈现快照) | Read | 意图线索映射全局恒定(敌人系统×4②,技能池批三档口径);**despawn 前捕获**(旧版台账 entry 17③——缺陷②教训) |
| 状态效果投影(含空态/极性/溢出) | 增益状态 投影口径 | Read | 极性映射数据源 = 战斗结算 SettlementTable 效果语义,非 magnitude 符号(增益状态×③) |
| 已开格奖励身份(奖励徽章) | 地图战斗资源 `query_identity`(经 board_view 身份解析只读回调,组合根注入) | Read | 已开格身份常读——挖掘期 Active 相位内有效;reward_id 全局恒定、禁止重掷;徽章绘制优先级见 §Component Inventory BoardView 行 |
| 节点链/当前节点/HP 携带 | 关卡节点地图 lite · LevelRunner | Read | A 区进度条 |
+2 -2
View File
@@ -102,7 +102,7 @@
|---|---|---|---|---|
| **board_generator**(A1) | 生成算法(五阶段管线)、BoardGenTable 回填、生成期指标集、合法性谓词(clearable / R₀≠S / min_safe)、可推理率度量(metric_version) | `generate(L, D, rng) -> Board`;度量独立入口×3(推理器 / F3 度量 / 合法性谓词——独立纯函数、同一实现) | ConfigDB(BoardGenTable/VariationTable);board_gen 流(一次 open_stream = 一张图);dig_ap_cost 只读 | 无 |
| **minesweeper_rules**(A2) | 棋盘状态(已开集 O / 旗集 G / 小人位)、三事件面、批次仲裁、install 四不变式 | `install_board(board)`;`settle_batch(intents)`(唯一提交入口);try_dig / try_flag_place / try_flag_pull / try_move(便捷形);`compute_move_path(cell)` 只读;`retire_board()`;三源拒绝(board_face / resource_face / phase)+ sub_reason 封闭枚举 | A1 产物(Board class_name 数据契约);A3 try_spend / last_round_end / 相位 | 无(零表读取、零 UI——切片实证) |
| **unit_container**(B2) | hero/enemy 单位实例(hp/atk/行为环指针)、行值快照 | 9 API:spawn / query_alive / query_beat / query_attack / apply_damage / advance / default_target / ambush_grant_plan / despawn_all | ConfigDB(EnemyTable/HeroTable) | 无 |
| **unit_container**(B2) | hero/enemy 单位实例(hp/atk/实例池:池快照+冷却+摇定动作+保底计数)、行值快照 | spawn / query_alive / rollable_skills / special_skills / set_pending_action / pending_action / basic_streak / commit_action / tick_cooldowns / apply_damage / heal / default_target / ambush_grant_plan / despawn_all(2026-09-16 敌人技能池批:query_beat/query_attack/advance 随行为环退役——architecture.md 旧口径残留归 API 面 ADR 批整表复核) | ConfigDB(EnemyTable/SkillTable/HeroTable) | 无 |
| **reward_economy**(B4) | 格面资源身份映射(install 期预分配、Ready 只读、换图映射消亡)、极性路由字典面((类型, 极性)→ 字典行查询;路由执行归 B1 授予段)、F6 当量供给投影 | 封闭 API:roll_board / query_identity / query_full_snapshot / grant / grant_clear / retire_mapping / polarity_row / equivalence_supply / phase / weighted_pick;另有 is_mapped 静默布尔投影(批量工具用) | ConfigDB(RewardTable);reward_roll 流(scope = 每次 install 批) | 无 |
| **helper_items**(A5,垂直切片) | HelperItemTable、探测显影语义 | (未设计——桥接位:A2 查询接口 + A4 counter_item_ids) | A2 / A4 / B4 | 无 |
@@ -212,7 +212,7 @@ D1 ◀──呈现数据源:A2 事件流 / B1 步骤流 / A3 ap-rap / ConfigDB
| RNG 封闭流 + scope 派生(R2 黄金向量);可回放 = 硬承诺 | D2 R2 + B1 Core Rule 15 |
| A2 纯规则引擎(零随机/零表读取/零 UI)+ 三事件面封闭 | A2 Core Rule 1/2 |
| B1 编排唯一性 + 七段段序骨架 + 明细行协议 | B1 Core Rule 1/4/14 |
| 多敌 side-wide / 行为环确定性 / 固定顺位 / 伏击 cap=4 | B2(2026-09-09 用户拍板) |
| 多敌 side-wide / 出招确定性(技能池批改型:零随机纪律保持、开轮摇号走 behavior_roll 封闭流)/ 固定顺位 / 伏击 cap=4 | B2(2026-09-09 用户拍板;2026-09-16 技能池批改型) |
| 奖励五值两类交付轴 / install 预分配 / 关卡域库存 / 窗口单件制 | B4(2026-09-11 用户裁决批) |
| weake­n 锚 F0 瞬时当量 / 条款①③⑤闭合对账 owner = B1 | B1 F0–F5 |
| 领域命名禁 ID 前缀 / 组合根模式 / 呈现层契约成文 | CLAUDE.md + docs/code-conventions.md(切片教训批) |
+2
View File
@@ -149,3 +149,5 @@
57. **护甲等级掷点批 · 定稿落地(2026-09-16,用户拍板"数字即规范"——切片代码 + 文档 + registry 同批,终局修复无伤通关根因)**:**判定式**:攻击结算掷 1d20,掷点 ≥ 防守方 AC + attack_bonus(结构位恒 0)→ 全额,低掷按缺口三档折扣(1-3→×0.75 · 4-7→×0.5 · 8+→×0.25,无零档);nat20 暴击该基础攻击分量 ×2 恒全额(双方同规);伤害下限全局 max(1,·)("被减伤归零 v=0"零值伤害行退役,重档行文本记"被压制");掷点只吃基础攻击分量(主角普通分量 / 敌方攻击与重击拍基线),穿刺/魔法充能与棋盘来源必中全额。**换装**:defense_up(减伤 −30% ×2)整行退役 → ac_up(护甲强化 +1 AC/层 ×2 层,unit=ac 新量纲、不入任何池);hero AC 10 / 敌方首关 0(暂定——正式行值归怪物设定批,新待办已登记);off_guard 与池结构零改动。**实现**:rng_service 增 armor_roll 流(salt 0xC2B2AE3D27D4EB2F,每次掷点恰一 scope,黄金向量 m=42 scope0=4806095330042243845);settlement.tres 七掷点参数(roll_faces/bounds[3,7]/discounts[75,50,25]/crit_roll/crit_multiplier/damage_floor/armor_roll_stream + attack_bonus)+ effect_pool_map 删 defense_up;hero/enemy 表 + armor_class 字段(域 [0,999]);battle_orchestrator 战斗段掷点判定 + damage_math 纯函数(roll_discount_percent / rolled_component)+ 伤害行随行 roll/ac/tier/crit 留档;presentation:英雄卡/敌人卡 AC 展示位、ac_up 状态槽图标◇、nat20 金框高亮行、开始页帮助文案表值驱动(顺手修 AP 8→6 遗留硬编码)。**验证**:冒烟 **233 项 0 失败**(+22:换装护栏〔defense_up 三处零残留 + 免伤墙护栏泛化〕+ 掷点行为面〔查档全掷点域 AC10/AC12 分布 + EV 锚 1575/2000=21.25% + nat20 种子扫描行为断言 + 池触底保底 1 + AC12 档位实际流动 + 棋盘必中〕);fresh 池攻击链回归改掷点口径;三探针 PASS;damage_probe/defense_scan(改 AC 口径)/monte_carlo 工具全通;真实种子逐行核算(seed 4200:roll5/AC11→×0.5→floor(4×1.75×0.6)=4 逐行归因;seed 23 nat20 暴击 24=6×(1+100%)×2);level_monte_carlo -- 30 4200 both:完美档 0/0 通关 **100%**(维持)、承伤 22.4→30.9(减伤墙 −60% 退役 + AC10 −21.25% 兑换净效果,零承伤轮结构性消失)、Boss 场 29.8%→36.0%(带 ≤50% 内)、盲挖档 7% 持平。**文档**:战斗结算 v0.10(掷点节定稿 + 调参表五参数 + 验收改写)/ 敌人系统 v0.4 / 难度曲线与Boss v0.5(换装点锚定:基准 AC10 自带约 −21% 敌方输出期望,与加难杠杆扫描合并调;带宽补扫含暴击档与折扣档)/ 增益状态 v0.4 / 地图战斗资源 v0.6 / 03-内容规模 / registry(rng_service 四流 + settlement ac 两字段定稿 + enemy/hero/reward_table/status_effect + 修订记录)/ 06-待办清单(三项 AC 条目改已裁决留痕 + 新增「敌方护甲等级正式行值」待办)。**已知口径注记**:批提案 EV 参考数 AC12=32.5% 与判定式复算 28.75% 不符,按判定式(规范)登记 28.75% 并在文档注明复算口径。
58. **美术素材管线拍板 + 主角样例批(2026-09-16,任务已收口)**:评估结论 = art-bible §1–4 足以支撑 §1.4 格子样张批,全套生产素材缺口 = §5 角色比例 / §8 资产规格(像素尺寸/格式/帧规范未成文,样张候选建议 32px,需拍板)。主角 DND 冒险者站姿像素样例经 PIL 脚本程序化生成(`design/art/samples/`:hero-standing-pose.png 32×48 + 四联测试条〔掩尘/裸土/暗底/灰度〕+ generate_hero_sample.py 字符画生成器;小人蓝主色 / 亮色描边 / 落影底盘 / 3 头身候选 / 对称剪影,art-bible 合规自检全过)。**用户评效不佳,拍板美术素材改走 ComfyUI 自行生成**——角色/场景插画级不再用脚本直出;脚本管线仅留给图标/格面/样张类精度约束资产(如立项);样例三件留存未删。合规校验义务不随管线转移:ComfyUI 出图仍按 art-bible §1.4 验收项(对比度/灰度可辨)做程序化校验。
60. **敌人技能池批落地(2026-09-16,用户拍板实施批——批 1+2+3 合并,设计权威 docs/proposal-skill-pool.md v1.0)**:确定性出招剧本(行为环)整体退役,改技能池 + 开轮摇号(behavior_roll 封闭流第五流 + 每敌子流派生向量 spawn_index×1000000+摇号序;独立抽签器 SkillLottery;两道闸 = 全技能各自冷却 + 保底 M=4;死亡作废不进账;拍结构保留)。**代码**:新增 skill_table.gd/skills.tres(五行 + pity_threshold)/skill_lottery.gd 三件;enemy.tres 六行 pattern→pool(9/1 · 6/2/2 · 6/2/2 provisional);status_effects 七→九行(empower/fortify——enemy_defense 敌方防御池新词汇);settlement.tres effect_pool_map 增两键;unit_container 重写(实例池/冷却/待发/保底);battle_orchestrator 开轮摇号编排 + 三乘法链 + 敌方防御池乘区;呈现替代(pending_* 三字段 + 敌卡动画位/环点拆除/施放行/状态键九行全映射);smoke 211→**263 项 0 失败** + 四探针全 PASS。**六项验收**:同种子回放逐位复现 ✓ / 单敌轨迹独立复放 ✓ / EV 抽样对锚 ✓(地精 1.1056〔保底闸抬升,登记难度曲线重锚〕/ 熊地精 0.9998 / 酋长 1.0236)/ 死亡打断作废 ✓ / 空池不可能 ✓ / 加载校验拒绝 ✓。**文档**:敌人系统 v0.5 / 战斗结算 v0.11 / 增益状态 v0.5(顺手清偿 sapped 文档漂移)/ 难度曲线与Boss v0.6 / 战斗表现 v0.3 / HUD框架 v0.4 / battle.md 改型注记 / registry(skill 新条目 + rng/enemy/status_effect/settlement 修订 + 术语七词条)/ 06-待办清单(改型"单拍行为环体验验证"、关闭"行为环废除与蓄力拍退役"、新增 P1 五项 + P2 图鉴)。**实现决策**:保底 M = 技能表全局字段;冷却时序 = 排除→递减(与提案口径可摇集合恒等);apply_effect duration_override 通道;敌方防御池乘区 = 主角攻击 Σ三分量后。**遗留登记**:hero_offense 池未并入主角攻击合计乘区(切片现状,归 ADR 批); RewardConfigTable 数据配置表清单残留(格面资源批遗留,非本批引入);AP6+AC 验收文档读数待技能池落地后另批重测(本批未动该文档)。
@@ -479,3 +479,25 @@ Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/s
**表实一致注记(对 registry reward_table 条目)**:十一行逐字段一致;两处切片级补全登记——① 负向 heal/blast 行 registry 未定 reward_id,切片取语义名 `heal_backlash` / `ambush_double`;② blast 负向加层行 effect_id 恒空(registry flat 行口径)+ power 校验例外(power 不消费——伏击加层为独立兑付形态,见战斗结算 GDD 兑付形态表)。
**待拍板(本批登记)**:① 关卡奖励点节点内容(功能道具渠道 P1——归 关卡节点地图 定稿批);② 已开格类型徽章与极性视觉(批 2——纯呈现);③ "剩余未探索安全区域"计数呈现口径(切片现无该计数器,随批 2)。
---
## 2026-09-16 敌人技能池批(批 1+2+3 合并落地——设计权威 docs/proposal-skill-pool.md v1.0;文档面 v0.5/v0.11/v0.5/v0.6/v0.3/v0.4 同批)
**机制**:确定性出招剧本(行为环:拍枚举/环指针/同种锁步/heavy_multiplier)整体退役,改技能池 + 开轮摇号——每轮轮次开始排除冷却中技能、按行序逐敌摇定本拍动作(behavior_roll 封闭流,每敌子流 scope = spawn_index×1000000+摇号序),战斗段按技能行兑现(伤害技 = 三乘法链〔基线×倍率→d20 掷点→甲效→伏击后置→池乘区→max(1,·)〕,增益技 = 施放行 cast + 状态授予〔0 伤害不掷不折不兑伏击〕);两道闸 = 全技能各自冷却(普攻恒 0 锁死)+ 保底 M=4(连续 4 拍执行普攻 → 下拍必出特殊技、不受冷却限制);死亡即不动手改"摇定后兑现前死亡 → 作废不进账";拍结构保留(每结算每存活敌恰行动一次/行序/先作用后推进)。
**数据面**:新增 `skills.tres` + `skill_table.gd`(五行:普攻/突刺×1.5 cd2/蓄力 cd3/重击×2 cd3/硬化 cd4;pity_threshold=4;普攻倍率/冷却域校验锁死);`enemy.tres` 六行 pattern/heavy_multiplier → pool(地精 9/1 · 熊地精 6/2/2 · 酋长 6/2/2,全部 provisional);`status_effects.tres` 七→九行(empower 蓄力 +100 enemy_output 池 / fortify 硬化 +100 **enemy_defense 敌方防御池新词汇**——duration 2 = 1 个有效增益拍〔先兑付后衰减含施放当轮〕);`settlement.tres` effect_pool_map 增 empower/fortify 两键。
**core 面**:`rng_service.gd` 增 behavior_roll 流(salt 0x85EBCA7758C24E15,黄金向量 m=42 scope0=426657203655691839 见冒烟;open_scope 显式 scope 取号门 + BEHAVIOR_SUBSTREAM_STRIDE);新增 `core/skill_lottery.gd`(独立抽签器——输入可摇条目+子流、输出 skill_id,D9 封装纪律);`unit_container.gd` 重写(实例池快照/冷却递减/待发动作/保底计数;query_beat/query_attack/advance/steps/pattern 退役);`battle_orchestrator.gd` 开轮摇号编排 `_roll_round_actions()`(首轮同样摇、轮内刷新不重摇)+ 战斗段技能行兑现 + 主角攻击增敌方防御池乘区(Σ三分量后 ×(100+clamp(enemy_defense))/100);`status_container.apply_effect` 增 duration_override(技能行 buff_duration 覆盖);`config_db.gd` 挂技能表 + validate_skill_pool_links 跨表校验(悬空致命/每池恰一普攻/增益 ≤1)。
**呈现面(批 2)**:`presentation_snapshot` 敌人数组字段定稿(beat/ring_size/ring_index 拆除 → pending_skill/pending_type/pending_name + effects 敌方增益投影;池与权重不公开);`enemy_card_view` 环点拆除(护甲行常驻)、意图徽标 = 摇定动画占位(普攻零表示 —/攻击系 ⚔/增益系 ✦);`battle_log_view` 施放行 ✦ 格式化、伤害行具名技能;`status_slot_row` 图标键补 empower ⇧ / fortify ▣(九行全映射);`settlement_snapshot` 敌方 beat 键 → pending_skill;damage_probe 拍名重建改 pending_skill 口径。
**smoke(211→263 项)**:行为环断言全部改型(行相位 cast、fresh 池采样、削弱触底/AC12 采样改多种子扫描);新增 `_smoke_skill_pool` 六项验收(proposal §8):数据面(池模板/普攻锁死/空池不可能)、同种子回放逐位复现 + 单敌轨迹独立复放(D3 多实例逐轮记录 vs 只喂该敌子流的独立重放器逐位对照)、EV 抽样对锚(×5000 拍)、死亡打断作废(D2 特殊技种子 + hero_atk 999 先手击杀零行动行 + 不进账)、加载校验拒绝(悬空/池构成/普攻锁死/倍率域/权重域/空池)。
**验证(本批实跑)**:冒烟 **263 项检查 0 失败**;四探针 ui_probe / ui_tune_probe / ui_layout_probe / ui_click_probe(种子 7、23)全 PASS;damage_probe(boss/level 全 PASS 含施放行)、defense_scan、monte_carlo 工具零脚本错误。
**EV 抽样读数(×5000 拍,含闸长期速率 vs 静态锚)**:地精 1.1056(静态 1.05——保底闸把突刺率从自然 10% 抬到 ~22%,登记难度曲线重锚)/ 熊地精 0.9998(结构守恒恒 1)/ 酋长 1.0236。
**文档/registry 同批(批 3)**:敌人系统 v0.5(技能池章节重写)/ 战斗结算 v0.11(随机口径三处/三乘法链/施放行/敌方防御池/AC 措辞四处)/ 增益状态 v0.5(九行 + 敌方防御池;顺手清偿 v0.4 挂起的 sapped 文档同步)/ 难度曲线与Boss v0.6(曲线纪律/Boss 专属池/技能池投值表 + EV 实测登记)/ 战斗表现 v0.3 / HUD框架 v0.4 / 数据配置(敌人表措辞 + 技能表行)/ 03-内容规模 / 02-系统清单 / 关卡节点地图 / ux battle.md(改型注记)/ architecture.md(单位容器 API 面)/ registry(rng/skill 新条目/enemy/status_effect/settlement/术语七词条/修订记录)/ 06-待办清单(改型"单拍行为环体验验证"、收编关闭"行为环废除与蓄力拍退役"、新增 P1 五项 + P2 技能图鉴)。
**实现决策登记(提案未定处的自行拍板,按封装纪律报告)**:① 保底 M 载体 = 技能表全局单字段 pity_threshold(不分敌种,难度曲线单旋钮);② 冷却"施放后连续 N 拍不可摇中"的实现 = 摇号按当前冷却值排除、递减在其后(与提案"递减→排除"的可摇集合逐拍恒等);③ 增益持续与技能冷却两计时器 = apply_effect duration_override 通道;④ 敌方防御池乘区落点 = 主角攻击 Σ三分量后(hero_offense 现行走普通分量直乘的切片现状不动——登记遗留);⑤ 摇号器类名 SkillLottery(core/skill_lottery.gd)。
@@ -4,8 +4,9 @@
# 战斗结算编排中枢:轮次生命周期唯一 owner · 五段结算(授予→增益/减益→战斗→衰减→判定——2026-09-15
# 格面资源批,道具窗口/道具段随可选类删除下线)· 授予段极性路由(「动作 × 格型」矩阵:开安全格/标中旗
# = 正向,开雷格/标错旗 = 负向;结算队列 + 抵消过滤——标旗后被撤旗以轮末在场旗为基线不判)·
# 10 类行协议可回放步骤流(零 await;随机消费 = 开战遭遇骰初始化随机 + 护甲等级掷点回合内随机——
# 均走封闭命名流,2026-09-16 护甲等级掷点批)· 高光双落(完美排雷 ×2 / 标中旗 highlight)·
# 10 类行协议可回放步骤流(零 await;随机消费三处皆封闭流——开战遭遇骰〔初始化随机〕+ 技能摇号
# behavior_roll〔回合内、开轮时点、步骤流之外——敌人技能池批 2026-09-16〕+ 护甲等级掷点〔回合内、
# 步骤流之内〕;同种子同输入逐位复现)· 高光双落(完美排雷 ×2 / 标中旗 highlight)·
# Waiting 相位结算触发(不依赖事件到达序)。
# 切片登记:① 事件总线载体 = 直连信号(实现期 ADR 项,provisional)② Waiting 相位刷新通道 2026-09-15 用户拍板整体
# 拆除(仅 Active 挖掘期受理)③ 效果行协议 = 授予段逐 grant 行 + 增益/减益段续存实例组合行。
@@ -22,7 +23,7 @@ signal settlement_snapshot(snap: Dictionary) # 每结算末尾的全量状态
# 明细流水行型协议(战斗结算 Core Rule;10 类——2026-09-15 格面资源批由 15 类收拢,
# 清单改动须同步 battle_log_view 的 _format_row 与本文档头注):
# 效果行 effect · 伤害行 damage · 治疗行 heal · 溢出未生效行 overflow ·
# 奖励未生效行 reward_ineffective · 标旗未中行 flag_miss · 蓄力行 charge · 衰减行 decay ·
# 奖励未生效行 reward_ineffective · 标旗未中行 flag_miss · 施放行 cast(敌人技能池批由蓄力行 charge 改型)· 衰减行 decay ·
# 判定行 verdict · 高光行 highlight
# 行来源断言豁免集合(行型层)——判定段 / 衰减段本就无棋盘来源,勿一刀切误炸
const SOURCE_EXEMPT_TYPES := ["decay", "verdict"]
@@ -122,6 +123,7 @@ func begin_battle(enc: Dictionary) -> bool:
_fatal("行动经济 首轮 notify_round_start 被拒")
return false
round_index = 1
_roll_round_actions() # 开轮摇号(敌人技能池批——首轮同样摇;先于 round_started,挖掘期动画位可读)
round_started.emit(round_index)
return true
@@ -218,7 +220,7 @@ func _settle() -> void:
# ② 增益/减益段(续存实例组合行;本结算新授予的已在授予段逐 grant 出行——增益状态 义务⑦)
_emit_composition_effects("hero", ri)
_emit_composition_effects("enemy", ri)
# ③ 战斗段(调用序钉死 query_attack → apply_damage → advance;hero hp=0 短路)
# ③ 战斗段(调用序:掷点判定 → apply_damage → commit_action;hero hp=0 短路)
_combat_segment(ri)
# ④ 衰减段(恰一次恒执行含末轮——先兑付后衰减)
var k: int = _status_effects.decay_round()
@@ -396,6 +398,26 @@ func _grant_clear_identity(snap: Dictionary, ri: int, suffix: String) -> bool:
return _apply_flat_row(row, "clear_reward", Vector2i(-1, -1), ri, "clear_reward_h2", suffix) == "redeemed"
# ---- 开轮摇号(敌人技能池批——共识点 B:技能选择前置到轮次开始,数值计算留结算段)----
# 每实例:保底判定(连续执行普攻 ≥ M → 必出特殊技、特殊技内按权重、不受冷却限制)→ 排除冷却中技能
# (中签率 = 权重 ÷ 可摇权重合计——份额归一)→ 按行序逐敌从 behavior_roll 独立子流摇定本拍动作。
# 随机消费只在换算层(本方法 + SkillLottery);单位容器收确定值(零随机纪律——armor_roll 先例同构)。
# 子流 scope = spawn_index × BEHAVIOR_SUBSTREAM_STRIDE + 单敌摇号序(registry 派生向量)——
# 单敌出招轨迹可独立复放、敌间互不穿插。摇定后、兑现前死亡 → 本拍技能作废(死亡即不动手)。
func _roll_round_actions() -> void:
var pity_m: int = int(db.skill.pity_threshold)
for u in _enemy_system.query_alive("enemy"):
var entries: Array = _enemy_system.rollable_skills(u)
if _enemy_system.basic_streak(u) >= pity_m:
var specials: Array = _enemy_system.special_skills(u)
if not specials.is_empty():
entries = specials # 保底命中——普攻恒可摇 → 非保底路径池恒非空,此为唯一替代集合
var scope: int = int(u.get("handle_id", 0)) * RngService.BEHAVIOR_SUBSTREAM_STRIDE + int(u.get("roll_seq", 0))
var stream: RandomNumberGenerator = rng.open_scope("behavior_roll", scope)
_enemy_system.set_pending_action(u, SkillLottery.draw(entries, stream))
_enemy_system.tick_cooldowns() # 递减在摇号读值之后——"施放后连续 N 拍不可摇中"的等价实现(单位容器 注记)
# ---- 战斗段 ----
func _combat_segment(ri: int) -> void:
@@ -433,33 +455,50 @@ func _combat_segment(ri: int) -> void:
payload["tier"] = discount
if crit:
payload["crit"] = true
# 增益池乘区·敌方防御池(敌人技能池批——硬化技载体):Σ三分量后 ×(100+clamp(enemy_defense))/100。
# 四池对称:敌打主角 = hero_defense×enemy_output(_resolve_damage);主角打敌 = hero_offense×enemy_defense
# (hero_offense 现行走普通分量 attack_up 直乘的切片现状——登记遗留,本批不动)。
d_hero = DamageMath.pool_damage(d_hero, 0, _clamped_pool("enemy_defense"))
var d_landed: int = maxi(int(db.settlement.damage_floor), d_hero)
_enemy_system.apply_damage(hero_target, d_landed)
payload["killed"] = not hero_target.alive # 击杀标记在扣血后取(原语义不变)
_emit_row("damage", "hero_attack", Vector2i(-1, -1), ri, _attack_label("主角攻击", crit, discount), d_landed, payload)
# 敌方行动(敌人技能池批——本拍动作开轮已摇定,此处按技能行兑现;行序 = 生成序)。
# 死亡即不动手:摇定后、兑现前死亡的敌不在存活序中——本拍技能作废,不 commit、不进冷却。
for u in _enemy_system.query_alive("enemy"):
if int(_enemy_system.hero_unit().hp) <= 0:
break # hero hp=0 立即短路(死尸挨打行是归因噪音——用户拍板)
var beat: String = _enemy_system.query_beat(u)
var atk: Dictionary = _enemy_system.query_attack(u, L, _m_amb) # 调用序①兑付读值
if beat == "charge":
_emit_row("charge", "enemy_action", Vector2i(-1, -1), ri, "%s 蓄力(本轮踩雷白喂)" % u.display_name, 0, {"target": "hero"})
else:
# 基线部分吃掷点判定(含暴击)+ 伏击附加(必中全额)→ 合计 → 增益池乘区 → max(1, ·)(伤害下限)
var roll_read_e: Dictionary = _armor_roll()
var threshold_e: int = _defender_ac(_enemy_system.hero_unit()) + int(db.settlement.attack_bonus)
var crit_e: bool = bool(roll_read_e.crit)
var discount_e: int = _roll_discount(int(roll_read_e.roll), threshold_e)
var rolled_base: int = DamageMath.rolled_component(float(int(atk.baseline)), discount_e, crit_e, int(db.settlement.crit_multiplier), 100)
var d_tick := _resolve_damage(rolled_base + int(atk.attach))
d_tick = maxi(int(db.settlement.damage_floor), d_tick)
_enemy_system.apply_damage(_enemy_system.hero_unit(), d_tick) # 调用序②受击上报
var payload_e: Dictionary = {"target": "hero", "baseline": int(atk.baseline), "attach": int(atk.attach),
"roll": int(roll_read_e.roll), "ac": threshold_e, "tier": discount_e}
if crit_e:
payload_e["crit"] = true
_emit_row("damage", "enemy_action", Vector2i(-1, -1), ri, _attack_label("%s 攻击" % u.display_name, crit_e, discount_e), d_tick, payload_e)
_enemy_system.advance(u) # 调用序③推进——每结算每存活敌恰一次(hero 不在推进域)
var skill_id := _enemy_system.pending_action(u)
var srow: Dictionary = db.skill_row(skill_id)
if srow.is_empty():
_fatal("敌实例待发动作悬空: %s" % skill_id)
continue
if String(srow.type) == "buff":
# 增益技 = 0 伤害施放行:不兑付伏击(旧蓄力拍"零兑付"语义由施放行继承——本轮喂的伏击
# 逢施放拍不兑现的读怪奖励保留);状态授予即数值兑现,技能冷却与增益持续两计时器分账。
var dur: int = int(srow.get("buff_duration", 1))
_status_effects.apply_effect(String(srow.buff_ref), {"source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "round_index": ri}, dur)
_emit_row("cast", "enemy_action", Vector2i(-1, -1), ri, "%s 施放 %s" % [u.display_name, String(srow.display_name)], 0, {"skill_id": skill_id, "effect_id": String(srow.buff_ref), "duration": dur})
_enemy_system.commit_action(u)
continue
# 伤害技(含普攻——倍率恒 1):三乘法链 ① A = 攻击基线 × 技能倍率(判定对象 = 技能基础伤害分量)
# → ② d20 vs 主角 AC(nat20 暴击 ≠ 技能重击,两层严格区分)→ ③ ×甲效(敌打主角无甲轴恒中性)
# → 伏击附加(后置加法、必中全额、不吃 AC)→ 增益池乘区 → 伤害下限 max(1,·)。
var baseline: int = int(round(float(u.atk) * float(srow.get("damage_mult", 1.0))))
var roll_read_e: Dictionary = _armor_roll()
var threshold_e: int = _defender_ac(_enemy_system.hero_unit()) + int(db.settlement.attack_bonus)
var crit_e: bool = bool(roll_read_e.crit)
var discount_e: int = _roll_discount(int(roll_read_e.roll), threshold_e)
var rolled_base: int = DamageMath.rolled_component(float(baseline), discount_e, crit_e, int(db.settlement.crit_multiplier), 100)
var d_tick := _resolve_damage(rolled_base + L * _m_amb)
d_tick = maxi(int(db.settlement.damage_floor), d_tick)
_enemy_system.apply_damage(_enemy_system.hero_unit(), d_tick)
var payload_e: Dictionary = {"target": "hero", "skill_id": skill_id, "baseline": baseline, "attach": L * _m_amb,
"roll": int(roll_read_e.roll), "ac": threshold_e, "tier": discount_e}
if crit_e:
payload_e["crit"] = true
_emit_row("damage", "enemy_action", Vector2i(-1, -1), ri, _attack_label("%s %s" % [u.display_name, String(srow.display_name)], crit_e, discount_e), d_tick, payload_e)
_enemy_system.commit_action(u) # 调用序③执行上报——技能进冷却 + 保底计数(每结算每存活敌恰一次)
# 攻击行显示名(掷点档位随行文本可读——2026-09-16 护甲等级掷点批;零值行语义已退役,重档折扣 = "被压制")
@@ -569,7 +608,7 @@ func _emit_composition_effects(side: String, ri: int) -> void:
func _emit_settlement_snapshot(verdict: String) -> void:
var enemies: Array = []
for u in _enemy_system.query_alive("enemy"):
enemies.append({"id": String(u.enemy_id), "hp": int(u.hp), "max_hp": int(u.max_hp), "atk": int(u.atk), "beat": query_beat_of(u), "armor": String(u.get("armor_type", "light")), "armor_class": int(u.get("armor_class", 0))})
enemies.append({"id": String(u.enemy_id), "hp": int(u.hp), "max_hp": int(u.max_hp), "atk": int(u.atk), "pending_skill": _enemy_system.pending_action(u), "armor": String(u.get("armor_type", "light")), "armor_class": int(u.get("armor_class", 0))})
var buffs: Array = []
for eff in _status_effects.query_active("hero"):
buffs.append({"id": eff.effect_id, "side": "hero", "layers": eff.layers, "left": eff.remaining_settlements})
@@ -606,10 +645,6 @@ func _emit_settlement_snapshot(verdict: String) -> void:
})
func query_beat_of(u: Dictionary) -> String:
return _enemy_system.query_beat(u)
# ---- 出口分发与战斗结束序列 ----
func _exit_distribution() -> void:
@@ -627,6 +662,7 @@ func _exit_distribution() -> void:
_reset_round_facts()
_refresh_used_this_round = 0
round_index += 1
_roll_round_actions() # 开轮摇号——每轮恰摇一次(轮内刷新换图不重摇:refresh 不走本通道)
round_started.emit(round_index)
@@ -669,16 +705,24 @@ func _battle_over(result: String) -> void:
# 协作对象(_enemy_system/_status_effects/_reward_economy)私有化后,跨层读取一律经本快照或下列命名面(diagnostic_snapshot 同款模式,
# 禁再链式穿透 settlement.enemy_system.hero 一类路径)。载荷 schema 成文(code-conventions 2.1):
# round / battle_result / phase / ap / rap / can_request_refresh / hero{hp,max_hp,atk,alive,armor_class} /
# enemies[{order,display_name,hp,max_hp,enemy_id,armor_type,armor_class,beat,ring_size,ring_index,ambush_layers,alive}] /
# enemies[{order,display_name,hp,max_hp,enemy_id,armor_type,armor_class,pending_skill,pending_type,pending_name,effects[],ambush_layers,alive}] /
# (敌人技能池批批 2 定稿:beat/ring_size/ring_index 拆除 → pending_* 三字段 + 敌方增益 effects 投影;池与权重不公开)
# ambush_layers / hero_effects[](增益状态 query_active 投影)
func presentation_snapshot() -> Dictionary:
var enemies: Array = []
var all: Array = _enemy_system.all_enemies()
var ambush: int = _status_effects.pool_layers("ambush")
# 敌方增益投影(side-wide 状态——蓄力/硬化/削弱/暴走等 percent 行;伏击单独走 ambush_layers)
var enemy_effects: Array = []
for eff in _status_effects.query_active("enemy"):
var erow: Dictionary = db.status_effect_row(eff.effect_id)
if not erow.is_empty() and String(erow.unit) == "percent" and String(eff.effect_id) != "ambush":
enemy_effects.append({"effect_id": eff.effect_id, "display_name": String(eff.display_name), "layers": int(eff.layers)})
for i in all.size():
var u: Dictionary = all[i]
var alive: bool = bool(u.get("alive", false))
var ring: Array = u.get("pattern", [])
var pending: String = String(u.get("pending_skill", "")) if alive else ""
var pending_row: Dictionary = db.skill_row(pending)
enemies.append({
"order": i + 1,
"display_name": String(u.display_name),
@@ -686,9 +730,12 @@ func presentation_snapshot() -> Dictionary:
"enemy_id": String(u.enemy_id),
"armor_type": String(u.get("armor_type", "light")),
"armor_class": int(u.get("armor_class", 0)),
"beat": String(_enemy_system.query_beat(u)) if alive else "",
"ring_size": ring.size(),
"ring_index": int(u.get("steps", 0)) % maxi(ring.size(), 1),
# 敌人技能池批(批 2 定稿):摇定动作三字段——普通攻击零表示 / 特殊技类型级动画占位。
# 池与权重不公开(proposal D4/D5)——实例池的权重/冷却不投影,实例池可见面 = 摇定结果本身。
"pending_skill": pending,
"pending_type": String(pending_row.get("type", "")) if not pending_row.is_empty() else "",
"pending_name": String(pending_row.get("display_name", "")) if not pending_row.is_empty() else "",
"effects": enemy_effects,
"ambush_layers": ambush,
"alive": alive,
})
@@ -14,6 +14,7 @@ const TABLE_MANIFEST := {
"status_effects": "status_effects.tres",
"board_gen": "board_gen.tres",
"enemy": "enemy.tres",
"skill": "skills.tres",
"hero": "hero.tres",
"reward": "reward.tres",
"settlement": "settlement.tres",
@@ -26,6 +27,7 @@ var variation: VariationTable
var status_effects: StatusEffectTable
var board_gen: BoardGenTable
var enemy: EnemyTable
var skill: SkillTable
var hero: HeroTable
var reward: RewardTable
var settlement: SettlementTable
@@ -95,6 +97,7 @@ func load_tables(root: String, collect_only: bool) -> bool:
status_effects = loaded.get("status_effects")
board_gen = loaded.get("board_gen")
enemy = loaded.get("enemy")
skill = loaded.get("skill")
hero = loaded.get("hero")
reward = loaded.get("reward")
settlement = loaded.get("settlement")
@@ -150,6 +153,39 @@ func _cross_validate(loaded: Dictionary) -> void:
push_diag("error", "level_node_dangling", "level %d %s 节点 (%s) 在 board_gen 无精确行" % [li, t, key])
if not en_keys.has(key):
push_diag("error", "level_node_dangling", "level %d %s 节点 (%s) 在 enemy 无精确行" % [li, t, key])
# ⑤ 技能池引用完整性(敌人技能池批——敌人表池 × 技能表;独立入口 validate_skill_pool_links 供冒烟直验)
var skt = loaded.get("skill")
if skt != null and en != null:
for e in validate_skill_pool_links(skt, en):
push_diag("error", e.category, e.message)
# 池模板 × 技能行跨表校验(加载期致命;返回错误列表——ConfigDB 诊断通道与冒烟直验共用):
# skill_id 悬空致命 + 池构成纪律(每池恰一行普攻、增益技 ≤1——proposal §2.1 结构保证)。
func validate_skill_pool_links(skill_table, enemy_table) -> Array:
var errs: Array = []
var skill_types := {}
for row in skill_table.rows:
skill_types[String(row.get("skill_id", ""))] = String(row.get("type", ""))
for row in enemy_table.rows:
var eid: String = String(row.get("enemy_id", ""))
var basic_n := 0
var buff_n := 0
for entry in row.get("pool", []):
var sid: String = String(entry.get("skill_id", ""))
if not skill_types.has(sid):
errs.append({"category": "dangling_skill", "message": "EnemyTable %s 池引用不存在的 skill_id: %s(悬空致命)" % [eid, sid]})
continue
match skill_types[sid]:
"basic":
basic_n += 1
"buff":
buff_n += 1
if basic_n != 1:
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池普攻行数 ≠ 1 (%d)——普攻冷却恒 0 = 空池不可能的结构前提" % [eid, basic_n]})
if buff_n > 1:
errs.append({"category": "pool_composition", "message": "EnemyTable %s 池增益技 > 1 (%d)——每池增益 ≤1 纪律(纠偏三)" % [eid, buff_n]})
return errs
# ---- R1 难度行解析(floor + warning 四元组去重;请求越域致命)----
@@ -259,6 +295,11 @@ func status_effect_row(effect_id: String) -> Dictionary:
return status_effects.row_by_id(effect_id)
# 技能行单点读取(敌人技能池批——单位容器执行上报 / 战斗结算兑现的消费面)
func skill_row(skill_id: String) -> Dictionary:
return skill.row_by_id(skill_id)
# ---- 数据配置 EC8:debug 热重载门(战斗结算 唯一调用方;切片实现 = 门标志 + 拒绝语义)----
func set_reload_gate(gated: bool) -> void:
@@ -7,7 +7,7 @@
# 取号语义:全新实例首个 open_stream = scope 0(地图战斗资源 预推进术语的登记口径)。
extends Node
const STREAM_NAMES := ["board_gen", "reward_roll", "encounter", "armor_roll"] # 2026-09-12 增 encounter(遭遇随机化批);2026-09-16 增 armor_roll(护甲等级掷点批,用户拍板——回合内随机、逐次掷逐次取号,registry rng_service 条目同步)
const STREAM_NAMES := ["board_gen", "reward_roll", "encounter", "armor_roll", "behavior_roll"] # 2026-09-12 增 encounter(遭遇随机化批);2026-09-16 增 armor_roll(护甲等级掷点批)+ behavior_roll(敌人技能池批——开轮摇号,每敌派生独立子流,registry rng_service 条目同步)
# 分段表达式书写(数据配置 R2 实现级规约——禁 ≥2^63 完整字面量)
const GOLDEN_RATIO := (0x9E3779B9 << 32) | 0x7F4A7C15 # 0x9E3779B97F4A7C15
@@ -15,6 +15,10 @@ const SALT_BOARD_GEN := (0xA0761D64 << 32) | 0x78BD642F # 0xA0761D6478BD642F
const SALT_REWARD_ROLL := (0xE7037ED1 << 32) | 0xA0B428DB # 0xE7037ED1A0B428DB
const SALT_ENCOUNTER := (0x8EBC6AF0 << 32) | 0x9C88C6E3 # 0x8EBC6AF09C88C6E3(遭遇骰——registry stream_seed_derivation 权威值;旧值 0x5DEECE66D<<32 超 int64 被截断,黄金向量漂移)
const SALT_ARMOR_ROLL := (0xC2B2AE3D << 32) | 0x27D4EB2F # 0xC2B2AE3D27D4EB2F(护甲掷点流——互异奇常数 ≠ 乘数;黄金向量见冒烟/registry 同批登记)
const SALT_BEHAVIOR_ROLL := (0x85EBCA77 << 32) | 0x58C24E15 # 0x85EBCA7758C24E15(技能摇号流——互异奇常数 ≠ 乘数;黄金向量见冒烟/registry 同批登记)
# behavior_roll 派生向量(registry 同批登记):子流 scope = spawn_index × STRIDE + 单敌摇号序(0 起)。
# 每敌消费自己的 scope 段 → 单敌出招轨迹可独立复放、敌间互不穿插(proposal 约束二)。
const BEHAVIOR_SUBSTREAM_STRIDE := 1000000
const OUT_MASK := 0x7FFFFFFFFFFFFFFF
const INT64_MIN := -9223372036854775808
@@ -23,7 +27,7 @@ const INT64_MIN := -9223372036854775808
var _master_seed: int = -1 # -1 = 未播种(取数即致命,EC7)
var master_seed: int:
get: return _master_seed
var _counters := {"board_gen": 0, "reward_roll": 0, "encounter": 0, "armor_roll": 0}
var _counters := {"board_gen": 0, "reward_roll": 0, "encounter": 0, "armor_roll": 0, "behavior_roll": 0}
func _salt_for(stream: String) -> int:
@@ -36,6 +40,8 @@ func _salt_for(stream: String) -> int:
return SALT_ENCOUNTER
"armor_roll":
return SALT_ARMOR_ROLL
"behavior_roll":
return SALT_BEHAVIOR_ROLL
push_error("[RngService] 未知名流: %s" % stream)
assert(false, "未知名流(流集合封闭,EC12)")
return 0
@@ -57,7 +63,7 @@ func set_master_seed(seed_val: int) -> void:
if seed_val < 0:
seed_val = -seed_val
push_warning("[RngService] master_seed 负输入 abs 归一化: %d" % seed_val)
assert(_counters["board_gen"] == 0 and _counters["reward_roll"] == 0 and _counters["encounter"] == 0 and _counters["armor_roll"] == 0, "冒险中途重播种会击穿同冒险无碰撞前提(仅许全部流计数器全零时调用)")
assert(_counters["board_gen"] == 0 and _counters["reward_roll"] == 0 and _counters["encounter"] == 0 and _counters["armor_roll"] == 0 and _counters["behavior_roll"] == 0, "冒险中途重播种会击穿同冒险无碰撞前提(仅许全部流计数器全零时调用)")
_master_seed = seed_val
_counters["board_gen"] = 0
_counters["reward_roll"] = 0
@@ -69,6 +75,7 @@ func new_adventure(seed_val: int) -> void:
_counters["reward_roll"] = 0
_counters["encounter"] = 0
_counters["armor_roll"] = 0
_counters["behavior_roll"] = 0
set_master_seed(seed_val)
@@ -87,6 +94,23 @@ func open_stream(stream: String) -> RandomNumberGenerator:
return rg
# 显式 scope 取号门(behavior_roll 每敌子流专用——armor_roll 式顺序取号不适用:子流 scope =
# spawn_index × BEHAVIOR_SUBSTREAM_STRIDE + 单敌摇号序,见派生向量注记)。计数器仍单调 +1
# (总摇号次数可观测);"首个 open_stream = scope 0"语义不变(不同流不同门,互不干扰)。
func open_scope(stream: String, scope: int) -> RandomNumberGenerator:
if not STREAM_NAMES.has(stream):
push_error("[RngService] 未知名流: %s" % stream)
assert(false, "未知名流(EC12)")
assert(scope >= 0, "负 scope 与 scope+2^63 经输出掩码别名(禁止)")
if _master_seed < 0:
push_error("[RngService] 未设 master_seed 即取数(EC7)")
assert(false, "未播种流取数致命(EC7)")
_counters[stream] = _counters[stream] + 1
var rg := RandomNumberGenerator.new()
rg.seed = derive_at(stream, scope)
return rg
# 只读计数器(存档序列化 快照契约的观测面)
func counter(stream: String) -> int:
return _counters[stream]
@@ -0,0 +1,28 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# Validation Question: 玩家能否无指导在一场完整战斗中经历「开战→挖掘规划→轮末结算逐行兑现→胜/负」并逐行归因(P2)
# Date: 2026-09-16
# 敌人系统·技能摇号器(proposal-skill-pool D9 封装纪律):独立服务类——输入 实例池可摇条目 + 随机子流,
# 输出 skill_id。权重份额归一:中签率 = 该技能权重 ÷ 可摇权重合计(排除谁,谁的份额按比例摊给其余技能)。
# 不碰血量、不碰表、不碰容器——过滤与冷却状态由调用方(战斗结算换算层)注入确定值。
# 零随机纪律:随机消费只在子流参数(behavior_roll 派生流),本类无自建随机源。
# 实现 GDD:design/gdd/systems/战斗循环/敌人系统.md(registry 追溯锚,命名规范见 CLAUDE.md)
class_name SkillLottery
extends RefCounted
# 权重摇号(整数份额,行序累计走查——同份额前行优先,确定性);entries = [{skill_id, weight}](非空)
static func draw(entries: Array, stream: RandomNumberGenerator) -> String:
assert(not entries.is_empty(), "[技能摇号] 空可摇集合(普攻冷却恒 0 → 池恒非空——结构违例即致命)")
var total := 0
for e in entries:
var w: int = int(e.get("weight", 0))
assert(w > 0, "[技能摇号] 池条目权重须 > 0: %s" % str(e))
total += w
assert(total > 0, "[技能摇号] 可摇权重合计为 0")
var r: int = stream.randi_range(1, total)
var acc := 0
for e in entries:
acc += int(e.weight)
if r <= acc:
return String(e.skill_id)
return String(entries[entries.size() - 1].skill_id) # 数值防御分支(不可达——r ≤ total 恒有落点)
@@ -0,0 +1 @@
uid://dvj4nqsa6juu
@@ -26,8 +26,10 @@ func notify_battle_start() -> bool:
return true
# 唯一授予入口(挖掘期零直写——战斗结算 结算编排期统一调用)
func apply_effect(effect_id: String, source: Dictionary) -> bool:
# 唯一授予入口(挖掘期零直写——战斗结算 结算编排期统一调用)。
# duration_override(敌人技能池批——增益技施放):技能行 buff_duration 覆盖状态行时长(-1 = 状态表值)。
# 施放是持续量的 owner(proposal §2.2 字段词汇表"buff_duration 增益持续回合数");其他授予路径传缺省。
func apply_effect(effect_id: String, source: Dictionary, duration_override: int = -1) -> bool:
if not _open:
push_warning("[增益状态] 域闭授予(EC1)")
return false
@@ -36,7 +38,7 @@ func apply_effect(effect_id: String, source: Dictionary) -> bool:
push_warning("[增益状态] 未知 effect_id(EC2): %s" % effect_id)
return false
var policy: String = row.stack_policy
var dur: int = int(row.duration_settlements)
var dur: int = int(row.duration_settlements) if duration_override < 1 else duration_override
var max_stack: int = int(row.max_stack)
# 授予时快照(抗表重载——EC9)
var snap_m: int = int(row.magnitude)
@@ -1,17 +1,24 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# Validation Question: 玩家能否无指导在一场完整战斗中经历「开战→挖掘规划→轮末结算逐行兑现→胜/负」并逐行归因(P2)
# Date: 2026-09-10
# 敌人系统·战斗单位容器:hero/enemy 统一受伤死亡接口 · 行为环确定性轮转(死亡即不动手/先作用后推进)·
# 行值快照抗热重载 · side-wide 伏击池读值由调用方注入(敌人系统 无池写面)· 编排被动零事件。
# 切片登记接口漂移:heal() 为切片新增(战斗结算 治疗 clamp 需要写入口;敌人系统 API 封闭枚举原本无治疗面——
# 正式侧应走 敌人系统 GDD 修订,切片以头注登记)。
# 实现 GDD:design/archive/gdd-2026-09-14/b2-enemy-system.md(系统 ID B2·敌人系统——registry 追溯锚,命名规范见 CLAUDE.md)
# Date: 2026-09-16
# 敌人系统·战斗单位容器:hero/enemy 统一受伤死亡接口 · 实例池状态机(敌人技能池批——2026-09-16 用户拍板,
# 设计权威 docs/proposal-skill-pool.md v1.0)· 行值快照抗热重载 · 编排被动零事件。
# 实例池三层对象(proposal §2.2):定义层技能行(SkillTable)· 定义层池模板(EnemyTable.pool)·
# 本类持实例层——spawn 把模板快照为各实例池(冷却全零),此后冷却递减/待发动作/保底计数各自独立。
# 零随机零事件纪律(proposal 约束二):随机消费只在 战斗结算换算层抽签器(SkillLottery + behavior_roll 子流);
# 本类只收确定结果(set_pending_action)并管理确定性状态(冷却递减、执行上报)。旧行为环
# (pattern 环数组 + steps 指针 + heavy_multiplier)随技能池整体退役——拍结构保留(每结算每存活敌恰行动一次、
# 行序、先作用后推进),"轮到什么"改由开轮摇号决定(死亡即不动手:摇定后兑现前死亡 → 本拍作废、不 commit)。
# 切片登记接口漂移:heal() 为切片新增(战斗结算 治疗 clamp 需要写入口);spawn 签名 +3 参数(E4/区间批/
# 遭遇骰——正式侧 GDD/registry 修订随对应批)。
# 实现 GDD:design/gdd/systems/战斗循环/敌人系统.md(registry 追溯锚,命名规范见 CLAUDE.md)
class_name UnitContainer
extends RefCounted
var db
# 单位容器私有(E3 封装收口 2026-09-13):hero/enemies 原始数组不再外露——读取经 hero_unit()/
# all_enemies() 投影面(死亡残位查询包含在内),写入只经 apply_damage/heal/advance/spawn/despawn。
# all_enemies() 投影面(死亡残位查询包含在内),写入只经 apply_damage/heal/set_pending_action/commit_action/
# tick_cooldowns/spawn/despawn。
# 句柄语义不变:投影返回的 Dictionary 引用即单位句柄(同 query_alive 先例)。
var _hero: Dictionary = {} # {hp, max_hp, atk, is_hero: true}
var _enemies: Array = [] # 行主序生成序;元素 = 单位 Dictionary
@@ -25,9 +32,8 @@ func _init(db_ref) -> void:
# E4 收口(2026-09-13):加成值经参数显式传入(atk_override/max_hp_override,-1 = 表值兜底)——
# 此前调用方写 db.hero 表值再由本类读表 = 共享可变单例传参(双写叠加 6→18→54 教训根源)。
# 接口漂移登记:spawn 签名 +2 参数(敌人系统 GDD API 封闭枚举——正式侧 GDD/registry 修订批随 ADR-0009)。
# enemy_atk_override(2026-09-14 敌方攻击区间批):战斗结算 遭遇骰同流的 spawn 期整战持久 roll 结果
# (-1 = 表值定值路径;≥0 = 行值替换后再乘关卡 atk_mult——修饰语义不变)。敌人系统 零随机纪律保持。
# enemy_atk_override(2026-09-14 敌方攻击区间批):遭遇骰同流的 spawn 期整战持久 roll 结果
# (-1 = 表值定值路径;≥0 = 行值替换后再乘关卡 atk_mult)。敌人系统 零随机纪律保持。
func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_override: int = -1,
atk_override: int = -1, max_hp_override: int = -1, enemy_atk_override: int = -1) -> bool:
if _spawned:
@@ -45,7 +51,7 @@ func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_override:
return false
# max_hp = 生效上限(非入参 hero_hp)——2026-09-11 关卡扩展批修复:跨战斗携带 HP 时上限不得压扁
# (恢复点/治疗包的满血守卫与治疗上限随之失效——切片登记接口语义澄清)
_hero = {"hp": hero_hp, "max_hp": eff_max_hp, "atk": eff_atk, "is_hero": true, "alive": true, "display_name": "探险者", "steps": 0, "pattern": [], "enemy_id": "hero"}
_hero = {"hp": hero_hp, "max_hp": eff_max_hp, "atk": eff_atk, "is_hero": true, "alive": true, "display_name": "探险者", "enemy_id": "hero"}
_enemies.clear() # 难度曲线与Boss DifficultyTable 修饰(2026-09-12 结构位兑现):施加于行值、行值快照之前——MVP 空表 = 恒 1.0 无行为(AC-10)
var mod: Dictionary = db.resolve_difficulty_modifiers(level_index, node_depth)
var hp_mult: float = float(mod.get("hp_mult", 1.0))
@@ -57,6 +63,14 @@ func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_override:
return false
for i in range(count):
var eff_row_atk: int = enemy_atk_override if enemy_atk_override >= 0 else int(row.atk)
# 实例池快照(proposal §2.2:开战把模板快照一份,冷却全零——与"生成拿到当时的表值快照"同一条快照纪律)
var pool_snap: Array = []
for entry in row.get("pool", []):
pool_snap.append({
"skill_id": String(entry.get("skill_id", "")),
"weight": int(entry.get("weight", 0)),
"cooldown_left": 0,
})
_enemies.append({
"handle_id": i,
"enemy_id": String(row.enemy_id),
@@ -64,12 +78,13 @@ func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_override:
"hp": maxi(1, int(round(float(row.hp) * hp_mult))),
"max_hp": maxi(1, int(round(float(row.hp) * hp_mult))),
"atk": maxi(0, int(round(float(eff_row_atk) * atk_mult))),
"heavy_multiplier": int(row.heavy_multiplier),
"pattern": Array(row.pattern), # 行值快照(拷贝)
"pool": pool_snap, # 实例池(模板快照 + 冷却状态——独立摇号互不干涉)
"pending_skill": "", # 本拍摇定动作(开轮摇号写入;兑现前死亡 = 作废不 commit)
"roll_seq": 0, # 单敌摇号序(behavior_roll 子流 scope 派生向量——registry 登记)
"basic_streak": 0, # 保底计数:连续执行普攻的拍数(执行上报驱动)
"advantage_per_round_cap": int(row.advantage_per_round_cap),
"armor_type": String(row.get("armor_type", "light")), # 护甲类型快照(克制矩阵消费面——战斗结算·战斗段;2026-09-15 命名统一批:light/ward/heavy)
"armor_class": int(row.get("armor_class", 0)), # 护甲等级快照(掷点判定阈值基数——战斗结算·战斗段;2026-09-16 护甲等级掷点批)
"steps": 0,
"alive": true,
"is_hero": false,
})
@@ -91,30 +106,92 @@ func query_alive(side: String) -> Array:
return out
# 当前拍(Dead 句柄保留只读可查——冻结不变式观测量;hero 句柄拒绝)
func query_beat(u: Dictionary) -> String:
# ---- 实例池状态机(敌人技能池批——全部确定性状态,随机在 战斗结算换算层)----
# 可摇集合:冷却已尽的池条目投影 [{skill_id, weight}]。普通攻击冷却恒 0 → 恒非空(空池不可能的结构前提,
# 加载校验"每池恰一行普攻"兜底)。hero/失效句柄拒绝。
func rollable_skills(u: Dictionary) -> Array:
if u.is_empty() or _despawned or u.get("is_hero", false):
_diag("rollable_skills 失效句柄")
return []
var out: Array = []
for e in u.pool:
if int(e.cooldown_left) <= 0:
out.append({"skill_id": String(e.skill_id), "weight": int(e.weight)})
return out
# 特殊技集合(保底专用——proposal §2.1:必出特殊技、特殊技内按权重、不受冷却限制):type ≠ 普攻的全部池条目。
func special_skills(u: Dictionary) -> Array:
if u.is_empty() or _despawned or u.get("is_hero", false):
_diag("special_skills 失效句柄")
return []
var out: Array = []
for e in u.pool:
var srow: Dictionary = db.skill_row(String(e.skill_id))
if not srow.is_empty() and String(srow.type) != "basic":
out.append({"skill_id": String(e.skill_id), "weight": int(e.weight)})
return out
# 摇定结果写入(战斗结算换算层调用——确定值;roll_seq 递增 = 子流 scope 派生向量的取号序)
func set_pending_action(u: Dictionary, skill_id: String) -> void:
if u.is_empty() or _despawned or u.get("is_hero", false):
_diag("set_pending_action 失效句柄")
return
u.pending_skill = skill_id
u.roll_seq = int(u.roll_seq) + 1
# 本拍摇定动作(未摇 = "";死亡残位保留可查——作废观测量)
func pending_action(u: Dictionary) -> String:
if u.is_empty() or _despawned:
_diag("query_beat 失效句柄")
_diag("pending_action 失效句柄")
return ""
if u.get("is_hero", false):
_diag("hero 无行为环(环接口对主角句柄拒绝)")
return ""
var p: Array = u.pattern
return String(p[u.steps % p.size()])
return String(u.get("pending_skill", ""))
# F2 分量供给面:{baseline, attach, total};L/m_amb 由调用方注入(判别探针,不校验游戏态域)
func query_attack(u: Dictionary, L: int, m_amb: int) -> Dictionary:
if u.is_empty() or _despawned or not u.alive:
_diag("query_attack 失效/死亡句柄")
return {"baseline": 0, "attach": 0, "total": 0}
var beat := query_beat(u)
if beat == "charge":
return {"baseline": 0, "attach": 0, "total": 0} # charge 拍不读伏击池——空拍零兑付
var baseline: int = u.atk
if beat == "heavy":
baseline = u.atk * int(u.heavy_multiplier)
return {"baseline": baseline, "attach": L * m_amb, "total": baseline + L * m_amb}
# 保底计数读数(连续执行普攻拍数——战斗结算开轮摇号时与 SkillTable.pity_threshold 比较)
func basic_streak(u: Dictionary) -> int:
if u.is_empty() or _despawned:
_diag("basic_streak 失效句柄")
return 0
return int(u.get("basic_streak", 0))
# 执行上报(战斗段兑现时调用):该技能进冷却(施放后连续 N 拍不可摇中——含增溢技,技能冷却与增益持续
# 是两个计时器互不相干)+ 保底计数更新(普攻 +1 / 特殊技归零)。摇定后兑现前死亡不经过本方法
# = 作废不进账(无冷却无计数——死亡即不动手)。
func commit_action(u: Dictionary) -> void:
if u.is_empty() or _despawned or u.get("is_hero", false):
_diag("commit_action 失效句柄")
return
var sid := String(u.get("pending_skill", ""))
if sid == "":
_diag("commit_action 无待发动作(兑现序违例)")
return
var srow: Dictionary = db.skill_row(sid)
var cd: int = int(srow.get("cooldown_beats", 0)) if not srow.is_empty() else 0
for e in u.pool:
if String(e.skill_id) == sid:
e.cooldown_left = cd
if not srow.is_empty() and String(srow.type) == "basic":
u.basic_streak = int(u.basic_streak) + 1
else:
u.basic_streak = 0
u.pending_skill = ""
# 开轮冷却递减(战斗结算开轮摇号编排调用,摇号读值之后全体恰一次):floor 0。
# 时序口径 = 摇号按当前 cooldown_left 排除(>0 冷却中),递减在其后——等价实现"施放后连续 N 拍
# 不可摇中"(施放拍置 N → 后续 N 个开轮各排除一次);与 proposal §2.1"冷却递减 → 排除"的
# 可摇集合逐拍恒等(init N+1 的同构变换)。
func tick_cooldowns() -> void:
if _despawned:
return
for u in _enemies:
for e in u.pool:
e.cooldown_left = maxi(0, int(e.cooldown_left) - 1)
# 受击上报:hp 递减 floor 0 → Dead;n ≤ 0 拒绝
@@ -141,20 +218,6 @@ func heal(u: Dictionary, n: int) -> int:
return actual
# 推进:存活才推进;false 两级——预期冻结(无诊断)/ 失效句柄(诊断);hero 拒绝
func advance(u: Dictionary) -> bool:
if u.is_empty() or _despawned:
_diag("advance 失效句柄")
return false
if u.get("is_hero", false):
_diag("hero 不在推进域(主角无行为环)")
return false
if not u.alive:
return false # 轮到前被杀——预期冻结(死亡即不动手),无诊断
u.steps += 1
return true
# 固定顺位:行主序生成序最前存活敌;死亡后顺延;空 → null
func default_target():
for u in _enemies:
@@ -2,14 +2,15 @@
# Validation Question: 玩家能否无指导在一场完整战斗中经历「开战→挖掘规划→轮末结算逐行兑现→胜/负」并逐行归因(P2)
# Date: 2026-09-10
# 数据配置 EnemyTable / HeroTable schema(敌人系统 回填值)。EnemyTable 键 (L,D,enemy_id),每行集恰一敌种;
# HeroTable 单行全局(Rule 4 豁免)。依据 design/archive/gdd-2026-09-14/b2-enemy-system.md Schema 节。
# HeroTable 单行全局(Rule 4 豁免)。依据 design/gdd/systems/战斗循环/敌人系统.md Schema 节。
# 敌人技能池批(2026-09-16 用户拍板,设计权威 docs/proposal-skill-pool.md v1.0):pattern 行为环数组与
# heavy_multiplier 退役,改池模板 pool = [{skill_id, weight}](定义层——实例池快照与冷却状态在 单位容器)。
# 池构成纪律(每池恰一行普攻、增益 ≤1、skill_id 悬空致命)需跨表视野,校验在 ConfigDB 跨表通道。
class_name EnemyTable
extends Resource
@export var rows: Array = [] # Array[Dictionary]
const PATTERN_MVP := ["attack", "charge", "heavy"]
const PATTERN_RESERVED := ["guard", "boost", "hinder", "summon"]
# 护甲类型封闭枚举(攻击类型 × 护甲克制批——克制矩阵本体在 SettlementTable;
# 2026-09-15 命名统一批:位置键 {none, a, b} → 语义键 {light, ward, heavy} = 轻甲 / 魔法护甲 / 重甲)
const ARMOR_TYPES := ["light", "ward", "heavy"]
@@ -64,17 +65,27 @@ func validate() -> Array:
errs.append({"category": "domain", "message": "%s atk 区间越域 [0,999] 且 min≤max: %d..%d" % [eid, amin, amax]})
if atk != int((amin + amax) / 2.0):
errs.append({"category": "cross_field", "message": "%s atk(%d) ≠ 区间中点(%d)——中点恒等期望当量锚" % [eid, atk, (amin + amax) / 2]})
var hm: int = int(row.get("heavy_multiplier", 0))
if hm < 1 or hm > 16:
errs.append({"category": "domain", "message": "%s heavy_multiplier 越域 [1,16]" % eid})
var pattern: Array = row.get("pattern", [])
if pattern.size() < 1 or pattern.size() > 8:
errs.append({"category": "domain", "message": "%s pattern 环长越域 [1,8]" % eid})
for beat in pattern:
if PATTERN_RESERVED.has(beat):
errs.append({"category": "enum", "message": "%s pattern 预留值拒绝(MVP 校验域): %s" % [eid, beat]})
elif not PATTERN_MVP.has(beat):
errs.append({"category": "enum", "message": "%s pattern 未知枚举: %s" % [eid, beat]})
# 池模板(敌人技能池批——单表结构校验;跨表构成纪律在 ConfigDB):
# 非空池 + 条目 {skill_id 非空, weight > 0} + 池内 skill_id 不重复。
# 普攻冷却恒 0(SkillTable schema 锁死)→ 可摇集合恒非空,"空池不可能"由此结构保证。
var pool: Array = row.get("pool", [])
if pool.is_empty():
errs.append({"category": "domain", "message": "%s pool 空池(禁——技能池行必带池模板)" % eid})
var pool_ids := {}
for entry_v in pool:
if not (entry_v is Dictionary):
errs.append({"category": "domain", "message": "%s pool 条目非字典: %s" % [eid, str(entry_v)]})
continue
var entry: Dictionary = entry_v
var sid: String = String(entry.get("skill_id", ""))
if sid == "":
errs.append({"category": "domain", "message": "%s pool 条目 skill_id 空" % eid})
elif pool_ids.has(sid):
errs.append({"category": "dup_key", "message": "%s pool 内 skill_id 重复: %s" % [eid, sid]})
pool_ids[sid] = true
var w: int = int(entry.get("weight", 0))
if w <= 0 or w > 9999:
errs.append({"category": "domain", "message": "%s pool 权重越域 (0,9999]: %d" % [eid, w]})
var cap: int = int(row.get("advantage_per_round_cap", -1))
if cap < 0 or cap > 16:
errs.append({"category": "domain", "message": "%s advantage_per_round_cap 越域 [0,16]" % eid})
@@ -0,0 +1,85 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# Validation Question: 玩家能否无指导在一场完整战斗中经历「开战→挖掘规划→轮末结算逐行兑现→胜/负」并逐行归因(P2)
# Date: 2026-09-16
# 数据配置 SkillTable schema(敌人技能池批——定义层"技能行",敌人系统 回填值;行 = Dictionary,键 skill_id;Rule 4 豁免)。
# 设计权威:docs/proposal-skill-pool.md v1.0 §2.2 字段词汇表。
# 三层对象:技能行(本表,多敌种共享)· 池模板(EnemyTable.pool:{skill_id, weight})· 实例池(单位容器 spawn 快照)。
# 池构成纪律的加载校验在 ConfigDB 跨表通道(每池恰一行普攻、增益 ≤1、skill_id 悬空致命——本表无池视野)。
class_name SkillTable
extends Resource
@export var rows: Array = [] # Array[Dictionary]
# 保底 M(provisional 归 难度曲线与Boss):连续 M 拍执行的都是普通攻击 → 下拍必出特殊技
# (特殊技内按权重、不受冷却限制——proposal §2.1 两道闸之二)。全局单旋钮(不分敌种)。
@export var pity_threshold: int = 4
const SKILL_TYPES := ["basic", "damage", "buff"] # 普通攻击 / 伤害 / 增益(封闭枚举——新类型走文档修订)
const MULT_DOMAIN_MIN := 1.1 # damage_mult 域 [1.1, 16] provisional(proposal §2.2)
const MULT_DOMAIN_MAX := 16.0
func validate() -> Array:
var errs: Array = []
if rows.is_empty():
errs.append({"category": "empty_table", "message": "SkillTable 为空"})
return errs
var seen := {}
var has_basic := false
for row in rows:
var sid: String = row.get("skill_id", "")
if sid == "":
errs.append({"category": "domain", "message": "skill_id 缺失/空"})
continue
if seen.has(sid):
errs.append({"category": "dup_key", "message": "skill_id 重复: %s" % sid})
seen[sid] = true
if String(row.get("display_name", "")) == "":
errs.append({"category": "domain", "message": "%s display_name 空" % sid})
var type: String = row.get("type", "")
if not SKILL_TYPES.has(type):
errs.append({"category": "enum", "message": "%s type 未知枚举: %s(域 %s)" % [sid, type, str(SKILL_TYPES)]})
continue
# 冷却域(全类型):施放后连续 N 拍不可摇中
var cd: int = int(row.get("cooldown_beats", -1))
if cd < 0 or cd > 99:
errs.append({"category": "domain", "message": "%s cooldown_beats 越域 [0,99]: %d" % [sid, cd]})
# 普通攻击两条域校验锁死特殊性:倍率恒 1、冷却恒 0——想配的就是伤害技(proposal §2.2)
if type == "basic":
has_basic = true
if row.has("damage_mult") and not is_equal_approx(float(row.damage_mult), 1.0):
errs.append({"category": "cross_field", "message": "%s 普攻 damage_mult ≠ 1(禁配——要配就是伤害技): %s" % [sid, str(row.damage_mult)]})
if row.has("cooldown_beats") and cd != 0:
errs.append({"category": "cross_field", "message": "%s 普攻 cooldown_beats ≠ 0(禁配——池恒非空的结构前提): %d" % [sid, cd]})
if row.has("buff_ref") or row.has("buff_duration"):
errs.append({"category": "cross_field", "message": "%s 普攻不带增益字段" % sid})
# 伤害技:存倍率不存固定值——伤害 = 施放者攻击基线 × 技能倍率(三乘法链第一乘)
if type == "damage":
if not row.has("damage_mult"):
errs.append({"category": "cross_field", "message": "%s 伤害技缺 damage_mult(存倍率不存固定值)" % sid})
else:
var m: float = float(row.damage_mult)
if m < MULT_DOMAIN_MIN or m > MULT_DOMAIN_MAX:
errs.append({"category": "domain", "message": "%s damage_mult 越域 [%.1f, %.1f]: %s" % [sid, MULT_DOMAIN_MIN, MULT_DOMAIN_MAX, str(m)]})
if row.has("buff_ref") or row.has("buff_duration"):
errs.append({"category": "cross_field", "message": "%s 伤害技不带增益字段" % sid})
# 增益技:0 伤害动作(无倍率),效果引用 + 持续回合;悬空引用由 ConfigDB 跨表校验兜底
if type == "buff":
if row.has("damage_mult"):
errs.append({"category": "cross_field", "message": "%s 增益技为 0 伤害动作,不带 damage_mult" % sid})
if String(row.get("buff_ref", "")) == "":
errs.append({"category": "cross_field", "message": "%s 增益技缺 buff_ref(效果引用)" % sid})
var bd: int = int(row.get("buff_duration", 0))
if bd < 1 or bd > 99:
errs.append({"category": "domain", "message": "%s buff_duration 越域 [1,99]: %d" % [sid, bd]})
if not has_basic:
errs.append({"category": "structural", "message": "SkillTable 无普攻行(basic)——池构成纪律的结构前提"})
if pity_threshold < 1 or pity_threshold > 99:
errs.append({"category": "domain", "message": "pity_threshold 越域 [1,99]: %d" % pity_threshold})
return errs
func row_by_id(skill_id: String) -> Dictionary:
for row in rows:
if row.get("skill_id", "") == skill_id:
return row
return {}
@@ -0,0 +1 @@
uid://b05eabwqt7yer
@@ -15,8 +15,13 @@ rows = [{
"atk": 7,
"atk_min": 4,
"atk_max": 10,
"heavy_multiplier": 2,
"pattern": ["attack"],
"pool": [{
"skill_id": "attack",
"weight": 9
}, {
"skill_id": "lunge",
"weight": 1
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
@@ -31,8 +36,13 @@ rows = [{
"atk": 5,
"atk_min": 2,
"atk_max": 8,
"heavy_multiplier": 2,
"pattern": ["attack"],
"pool": [{
"skill_id": "attack",
"weight": 9
}, {
"skill_id": "lunge",
"weight": 1
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
@@ -46,8 +56,16 @@ rows = [{
"atk": 9,
"atk_min": 6,
"atk_max": 12,
"heavy_multiplier": 2,
"pattern": ["attack", "charge", "heavy"],
"pool": [{
"skill_id": "attack",
"weight": 6
}, {
"skill_id": "charge",
"weight": 2
}, {
"skill_id": "heavy",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "ward",
"armor_class": 0
@@ -62,8 +80,16 @@ rows = [{
"atk": 6,
"atk_min": 3,
"atk_max": 9,
"heavy_multiplier": 2,
"pattern": ["attack", "charge", "heavy"],
"pool": [{
"skill_id": "attack",
"weight": 6
}, {
"skill_id": "charge",
"weight": 2
}, {
"skill_id": "heavy",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "ward",
"armor_class": 0
@@ -77,8 +103,16 @@ rows = [{
"atk": 9,
"atk_min": 6,
"atk_max": 12,
"heavy_multiplier": 2,
"pattern": ["attack", "attack", "charge", "heavy"],
"pool": [{
"skill_id": "attack",
"weight": 6
}, {
"skill_id": "heavy",
"weight": 2
}, {
"skill_id": "harden",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "heavy",
"armor_class": 0
@@ -92,8 +126,16 @@ rows = [{
"atk": 7,
"atk_min": 4,
"atk_max": 10,
"heavy_multiplier": 2,
"pattern": ["attack", "charge", "heavy"],
"pool": [{
"skill_id": "attack",
"weight": 6
}, {
"skill_id": "charge",
"weight": 2
}, {
"skill_id": "heavy",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "ward",
"armor_class": 0
@@ -122,3 +164,11 @@ rows = [{
; 护甲等级掷点批(2026-09-16 用户拍板):armor_class 字段入列(缺省 0 向后兼容)——防守方掷点判定阈值基数,
; 与 armor_type(克制属性轴)两概念严格分离、不做映射。首关六行暂定 0(正式行值归怪物/敌人系统设定批——
; 登记待办:第一关怪物 AC 相对较低,值待怪物设定投值;投值所有权归 难度曲线与Boss)。
; 敌人技能池批(2026-09-16 用户拍板——设计权威 docs/proposal-skill-pool.md v1.0):
; pattern 行为环数组 + heavy_multiplier 整体退役,改池模板 pool = [{skill_id, weight}](技能行本体在 skills.tres;
; 实例池快照与冷却状态在 单位容器 spawn)。建议池(全部 provisional,投值归 难度曲线与Boss;EV 锚 = proposal §2.5):
; 地精 普攻 9/突刺 1(每拍 EV 1.05——突刺倍率 >1 必然抬 EV,+5% 偏移显式登记不硬凑);
; 熊地精 普攻 6/蓄力 2/重击 2(EV 1.00 中性);食人魔酋长 普攻 6/重击 2/硬化 2(EV 1.00 中性;
; 硬化 = Boss 池独占增益技——增益 ≤1 纪律下旧蓄力拍退役不进 Boss 池)。
; 含闸长期速率(冷却/保底)EV 锚按实施批 EV 抽样复核登记(proposal §2.5)。
@@ -22,7 +22,9 @@ effect_pool_map = {
"ambush": "flat_attach",
"enrage": "enemy_output",
"off_guard": "hero_defense",
"sapped": "hero_offense"
"sapped": "hero_offense",
"empower": "enemy_output",
"fortify": "enemy_defense"
}
pool_clamp_lo = -100
pool_clamp_hi = 100
@@ -88,3 +90,8 @@ armor_roll_stream = "armor_roll"
; EV 参考数(按判定式复算):AC10 期望减免 21.25% / AC11 25% / AC12 28.75%;微调旋钮 = bounds/discounts,不动 AC 值。
; effect_pool_map 同批删 defense_up 键(减伤百分比行整体退役、无双轨)——ac_up 是 AC 阈值轴效果,不入任何池;
; off_guard(hero_defense 池)保留不动,池结构与乘区公式零改动。
; 敌人技能池批(2026-09-16 用户拍板):effect_pool_map 增 empower→enemy_output(蓄力技载体——敌方输出 ×2
; 经既有 enemy_output 乘区兑付)与 fortify→enemy_defense(硬化技载体——敌方防御池新词汇)。
; enemy_defense 池乘区消费面 = 主角攻击落地段(Σ三分量后 ×(100+clamp(enemy_defense))/100)——
; 四池对称乘区:敌打主角 = hero_defense×enemy_output,主角打敌 = hero_offense×enemy_defense
; (hero_offense 现行走主角普通分量 attack_up 直乘、切片未改——登记遗留)。clamp 域 [-100,+100] 共用。
@@ -0,0 +1,50 @@
[gd_resource type="Resource" script_class="SkillTable" load_steps=2 format=3]
[ext_resource type="Script" path="res://data/schemas/skill_table.gd" id="1"]
[resource]
script = ExtResource("1")
pity_threshold = 4
rows = [{
"skill_id": "attack",
"display_name": "普通攻击",
"type": "basic",
"cooldown_beats": 0
}, {
"skill_id": "lunge",
"display_name": "突刺",
"type": "damage",
"damage_mult": 1.5,
"cooldown_beats": 2
}, {
"skill_id": "charge",
"display_name": "蓄力",
"type": "buff",
"buff_ref": "empower",
"buff_duration": 2,
"cooldown_beats": 3
}, {
"skill_id": "heavy",
"display_name": "重击",
"type": "damage",
"damage_mult": 2.0,
"cooldown_beats": 3
}, {
"skill_id": "harden",
"display_name": "硬化",
"type": "buff",
"buff_ref": "fortify",
"buff_duration": 2,
"cooldown_beats": 4
}]
; 敌人技能池批(2026-09-16 用户拍板——设计权威 docs/proposal-skill-pool.md v1.0,全值 provisional 投值归 难度曲线与Boss):
; 五行 = 普通攻击 / 突刺(新行:伤害 ×1.5 冷却 2)/ 蓄力(增益技)/ 重击 / 硬化(新行:独占增益技——Boss 专属池载体)。
; 普通攻击两条域校验锁死:倍率恒 1、冷却恒 0(schema 拒绝配置)——池恒非空的结构前提。
; 蓄力 = 纯"攻击力增加"增益技(0 伤害、施加 empower 状态),与重击无任何组合语义(纠偏二——旧"蓄力→猛击"
; 固定配对随行为环退役);增益与倍率同拍只是两层乘法共存(×2 遇 ×2 = ×4)。
; buff_duration = 2 的口径:容器时限纲 = remaining_settlements(先兑付后衰减、授予当轮衰减段即 −1)——
; 施放发生在第 N 结算战斗段,需存活到第 N+1 结算战斗段才兑付("下一拍"),故持续 1 个有效结算 ⇔ 表值 2
; (提案"攻击×2 持续 1 结算"的容器语义落地;有效增益拍恰 1 拍,归 增益状态/难度曲线 复核)。
; 硬化 = 自防增益(fortify 状态 → 敌方防御池),Boss 池独占——增益 ≤1 纪律下旧蓄力拍不进 Boss 池(proposal §2.5)。
; 保底 M = pity_threshold = 4:连续 4 拍执行普攻 → 下拍必出特殊技(特殊技内按权重、不受冷却限制)。
@@ -67,6 +67,24 @@ rows = [{
"duration_settlements": 1,
"stack_policy": "stack",
"max_stack": 16
}, {
"effect_id": "empower",
"display_name": "蓄力",
"target_side": "enemy",
"magnitude": 100,
"unit": "percent",
"duration_settlements": 2,
"stack_policy": "refresh",
"max_stack": 1
}, {
"effect_id": "fortify",
"display_name": "硬化",
"target_side": "enemy",
"magnitude": 100,
"unit": "percent",
"duration_settlements": 2,
"stack_policy": "refresh",
"max_stack": 1
}]
; 减伤墙调参批(2026-09-13,playtest-2026-09-12 报告 Q1 触发——defense_scan 实测墙轮 39%、
@@ -82,3 +100,10 @@ rows = [{
; unit=ac 新量纲(AC 阈值轴效果,不入 effect_pool_map 任何池),消费面 = 战斗结算掷点判定
; (防守方 AC = 主角表值 armor_class + Σac 型效果层数×幅度);off_guard(失衡 +25 percent,hero_defense 池)
; 保留不动,其余六行零改动——状态表仍七行(一行换装)。
; 敌人技能池批(2026-09-16 用户拍板——增益技新状态行,七行→九行;敌方增益走既有增益状态容器):
; empower(蓄力:敌方攻击 +100 percent,enemy_output 池——蓄力技 buff_ref 载体)与
; fortify(硬化:敌方防御 +100 percent,enemy_defense 池——敌方防御池新词汇载体,Boss 池独占硬化技)。
; 两行 duration = 2 的口径:先兑付后衰减含施放当轮 → 表值 2 ⇔ 有效增益拍 1 拍(施放后下一结算兑付,
; 提案"持续 1 结算"的容器语义落地——详见 skills.tres 批注);refresh ×1 层(冷却闸下不可能自叠加)。
; enemy 侧池语义 = side-wide(registry 既有口径):蓄力 +100 入 enemy_output 与 weaken/enrage 共池 clamp;
; 硬化 +100 入 enemy_defense 新池(乘区消费面 = 战斗结算 主角攻击落地段)。值全部 provisional 归 难度曲线与Boss。
@@ -5,7 +5,7 @@
# 静态无状态——呈现不持久化任何核心句柄。
# E1 封装收口(2026-09-13):核心状态读取统一经 战斗结算.presentation_snapshot() 成文快照面——
# 禁再链式穿透 settlement.enemy_system.hero / settlement.enemy_system.enemies / settlement.status_effects.query_active 一类路径(战斗结算 协作对象已私有化,
# 敌人含死亡残位的投影由快照 enemies[] 承载,含 alive/beat/ring 字段)。
# 敌人含死亡残位的投影由快照 enemies[] 承载,含 alive/pending_*/effects 字段——2026-09-16 敌人技能池批批 2 定稿)。
# 更新触发注记(battle.md Data Requirements):旗/拔/移动成功反馈零事件外发(排雷玩法 发射面封闭)——
# 经 ActionOutcome 返回值命令式驱动;AP/RAP 无推送信号——组合根每次调用返回后重查;
# 增益状态 纯被动容器零信号——状态栏刷新绑 战斗结算 结算编排完成时点。
@@ -5,7 +5,8 @@
# highlight 金框〔flag_hit/clear_reward_h2/perfect_broken〕、击杀标记)。
# Date: 2026-09-12(基础 = 2026-09-11 13 类行协议格式化版;2026-09-15 格面资源批 15→10 类:
# 删 item / consumed_ineffective / use_rejected / window_open / window_skip 五类——可选类道具与窗口下线,
# 战斗结算 行协议清单同步;2026-09-16 护甲等级掷点批:伤害行补掷点读数/档位随行标注 + nat20 金框高亮)
# 战斗结算 行协议清单同步;2026-09-16 护甲等级掷点批:伤害行补掷点读数/档位随行标注 + nat20 金框高亮;
# 同日 敌人技能池批:施放行 = 蓄力行 charge 改型〔增益技 0 伤害施放、具名技能〕)
# 高内聚边界:战斗结算 步骤流 → 玩家文案的全部格式化规则内聚于此;来源文案映射来自 SettlementTable。
class_name BattleLogView
extends PanelContainer
@@ -200,8 +201,9 @@ func _format_row(row: Dictionary, agg_count := -1) -> String:
"flag_miss":
var body := "✗ %s%s" % [name_, "(本板完美排雷已破——归因此旗)" if hl == "perfect_broken" else ""]
return prefix + (_gold_frame(body) if hl == "perfect_broken" else "[color=#9a8a72]%s[/color]" % body)
"charge":
return prefix + "[color=#9a8a72]%s%s[/color]" % [name_, suffix]
"cast":
# 施放行(敌人技能池批——蓄力行 charge 改型):增益技 0 伤害施放,具名技能
return prefix + "[color=#b09a72]✦ %s%s[/color]" % [name_, suffix]
"decay":
return prefix + "[color=#8a9ab0]状态衰减 ×%d[/color]" % v
"verdict":
@@ -1,8 +1,9 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# 实现 GDD:design/ux/battle.md(B 区敌人卡 ×1–3——敌像占位、血条、顺位徽标、意图徽标〔常驻:
# 挖掘期全程可见,映射 = enemy_id 恒定、环点 = 拍数直觉编码,敌人系统 数据源 = query_beat 当前拍〕、
# 实现 GDD:design/ux/battle.md(B 区敌人卡 ×1–3——敌像占位、血条、顺位徽标、意图徽标〔常驻:挖掘期全程可见〕、
# 状态槽〔敌方 side 池口径:伏击层数全体共享单值各卡同值——战斗结算 流水经 增益状态 投影〕)。
# Date: 2026-09-12
# Date: 2026-09-12(2026-09-16 敌人技能池批改型:行为环环点拆除〔proposal §2.4〕——
# 意图徽标语义 = 本拍摇定结果的动画占位:普通攻击零表示 / 特殊技类型级提示(攻击系 ⚔ / 增益系 ✦),
# 池与权重不公开(D4/D5);环点行退役,护甲类型 + AC 常驻;敌方增益(蓄力/硬化等)入状态行)
# 敌人死亡呈现(敌人系统 资产下限同等 blocking):血条归零 → 卡体灰化 + 碎裂退场,禁瞬消;
# 卡体保留灰化残位——定长卡槽,死亡动画不回流布局。
# 意图线索映射目录(敌人系统×4① 成文占位——资产定稿归 战斗表现,本处按 enemy_id 程序化占位)。
@@ -10,8 +11,14 @@ class_name EnemyCardView
extends PanelContainer
const COL_INK := Color8(51, 38, 27)
const COL_BEAT := {"attack": Color8(198, 61, 34), "charge": Color8(90, 108, 140), "heavy": Color8(143, 47, 106)}
const BEAT_NAMES := {"attack": "攻击", "charge": "蓄力", "heavy": "重击"}
# 摇定动作呈现映射(敌人技能池批 §2.4 三档可见性):键 = pending_type(快照投影)。
# 普通攻击零表示("—" 中性色——完全不可预测);伤害特殊技 = 攻击系 ⚔(朱红);
# 增益特殊技 = 防御/增益系 ✦(蓝)。nat20 暴击无前置表示(掷点层,与技能重击严格区分)。
const PENDING_STYLE := {
"basic": ["—", Color8(120, 106, 88)],
"damage": ["⚔", Color8(198, 61, 34)],
"buff": ["✦", Color8(46, 110, 142)],
}
# 护甲类型呈现(攻击类型×护甲克制批——可读性前提:玩家须能看出该用穿刺还是魔法打它)
const ARMOR_NAMES := {"light": "轻甲", "ward": "魔法护甲", "heavy": "重甲"}
@@ -19,10 +26,10 @@ var _portrait: Label
var _name_label: Label
var _hp_bar: ProgressBar
var _intent_badge: Label
var _ring: Label
var _armor_line: Label
var _status_label: Label
var _armor := "light" # 护甲类型快照(set_data 写入、set_intent 渲染——环点行常驻标签)
var _armor_class := 0 # 护甲等级快照(2026-09-16 护甲等级掷点批——环点行常驻;与护甲类型两轴分立)
var _armor := "light" # 护甲类型快照(set_data 写入、set_pending 渲染——护甲行常驻标签)
var _armor_class := 0 # 护甲等级快照(2026-09-16 护甲等级掷点批——护甲行常驻;与护甲类型两轴分立)
var _dead := false
@@ -60,13 +67,14 @@ func _init() -> void:
_intent_badge = UiKit.label("意图 —", 13, true)
_intent_badge.add_theme_color_override("font_color", COL_INK)
col.add_child(_intent_badge)
_ring = UiKit.label("", 12, false)
col.add_child(_ring)
_armor_line = UiKit.label("", 12, false)
col.add_child(_armor_line)
_status_label = _fixed_label(30)
col.add_child(_status_label)
# data = 增益状态/敌人系统 投影:{order, display_name, hp, max_hp, enemy_id, beat, ring_size, ring_index, ambush_layers}
# data = 战斗结算 presentation_snapshot 投影:{order, display_name, hp, max_hp, enemy_id,
# pending_skill/pending_type/pending_name(摇定动作三字段), effects[](敌方增益), ambush_layers}
func set_data(d: Dictionary) -> void:
_name_label.text = "%s" % String(d.get("display_name", "敌人"))
_hp_bar.max_value = maxi(1, int(d.get("max_hp", 1)))
@@ -74,9 +82,15 @@ func set_data(d: Dictionary) -> void:
_set_order(int(d.get("order", 1)))
_armor = String(d.get("armor_type", "light"))
_armor_class = int(d.get("armor_class", 0))
set_intent(String(d.get("beat", "")), int(d.get("ring_size", 0)), int(d.get("ring_index", 0)))
set_pending(String(d.get("pending_type", "")), String(d.get("pending_name", "")))
# 状态行:伏击池(flat 共享池,单独通道)+ 敌方增益 percent 状态(蓄力/硬化/削弱/暴走——side-wide)
var parts: Array = []
var ambush: int = int(d.get("ambush_layers", 0))
_status_label.text = "伏击池 ×%d(全体共享)" % ambush if ambush > 0 else ""
if ambush > 0:
parts.append("伏击池 ×%d(全体共享)" % ambush)
for eff in d.get("effects", []):
parts.append("%s ×%d" % [String(eff.get("display_name", "")), int(eff.get("layers", 1))])
_status_label.text = " · ".join(parts)
_portrait.text = _portrait_glyph(String(d.get("enemy_id", "")))
if _dead and int(d.get("hp", 0)) > 0:
_revive()
@@ -88,29 +102,20 @@ func _set_order(i: int) -> void:
_name_label.text = "%s %s" % [String("①②③"[clampi(i - 1, 0, 2)]), base]
# beat = 敌人系统 行为环词表原键(attack/charge/heavy,战斗结算 query_beat 直出——presenter 不翻译);
# 名(BEAT_NAMES)/色(COL_BEAT)/重击标记(▼)渲染映射唯一定义处 = 本类(code-review 2026-09-12:
# 双翻译曾使色相与重击通道永久失效)。信号/入参契约:set_data 每结算行刷新、set_intent 每拍刷新。
func set_intent(beat: String, ring_size: int, ring_index: int) -> void:
# 摇定动作渲染(敌人技能池批——presenter 不翻译,渲染映射唯一定义处归本类〔code-review 2026-09-12 先例〕):
# pending_type = 战斗结算 快照投影(basic/damage/buff;"" = 未摇定/死亡残位)。
# 信号/入参契约:set_data 每结算行刷新、set_pending 每轮开轮刷新(开轮摇号时点)。
func set_pending(ptype: String, pname: String) -> void:
if _dead:
return
var label := String(BEAT_NAMES.get(beat, beat))
# 强度档位编码:重击 = 垂直延伸体量(占位:▼)+ 色相双通道(色弱冗余 = 文字常驻)
var beat_col: Color = COL_BEAT.get(beat, Color8(90, 78, 66))
_intent_badge.add_theme_color_override("font_color", beat_col)
if label == "":
_intent_badge.text = "意图:—"
var style: Array = PENDING_STYLE.get(ptype, ["—", Color8(120, 106, 88)])
_intent_badge.add_theme_color_override("font_color", style[1])
if ptype == "" or ptype == "basic":
_intent_badge.text = "意图:—" # 普通攻击零表示(不可预测——§2.4)
else:
_intent_badge.text = "意图:" + label + ("▼" if beat == "heavy" else "")
# 环点 = 拍数直觉编码(● 已到拍 / ○ 未到)+ 护甲类型标签(克制对策的可读前提——恒常驻非悬停)
# + 护甲等级 AC(掷点折扣阈值——2026-09-16 护甲等级掷点批;与护甲类型两轴分立可读)
if ring_size > 0:
var dots := ""
for i in ring_size:
dots += "●" if i == ring_index else "○"
_ring.text = "%s · AC %d · 环 %s(%d 拍)" % [_armor_tag(_armor), _armor_class, dots, ring_size]
else:
_ring.text = "%s · AC %d" % [_armor_tag(_armor), _armor_class]
_intent_badge.text = "意图:%s %s" % [String(style[0]), pname] # 特殊技类型级占位(纯提醒,不可撤销)
# 护甲行(环点拆除——护甲类型 + AC 恒常驻,克制对策与掷点阈值的可读前提)
_armor_line.text = "%s · AC %d" % [_armor_tag(_armor), _armor_class]
func _armor_tag(armor: String) -> String:
@@ -130,7 +135,7 @@ func mark_dead() -> void:
_portrait.text = "✕"
_intent_badge.text = "—— 击破 ——"
_intent_badge.add_theme_color_override("font_color", Color8(120, 110, 98))
_ring.text = ""
_armor_line.text = ""
_status_label.text = ""
_hp_bar.value = 0
@@ -10,10 +10,11 @@ extends HBoxContainer
const MAX_SLOTS := 4
# 极性/语义映射(数据源 = SettlementTable.effect_pool_map 效果语义 + StatusEffectTable display_name):
# 益 = ▲/◆/▼(金/蓝族);害 = ⚡/⚔/✕/☾(旗朱族)——形状+色相双通道,文字名常驻第三通道。
# 状态表七行全映射(attack_up/ac_up/weaken/ambush/enrage/off_guard/sapped)——HUD框架 图标键契约
# "一键一枚专属纹样,查不到的键立即断言 + ? 占位 + 诊断"(2026-09-15 格面资源批补 sapped 键;
# 2026-09-16 护甲等级掷点批:defense_up → ac_up 换装)。
# 益 = ▲/◆/▼/◇(金/蓝族);害 = ⚡/⚔/✕/☾/⇧/▣(旗朱族;⇧/▣ = 敌方增益威胁纹样)——形状+色相双通道,
# 文字名常驻第三通道。状态表九行全映射(attack_up/ac_up/weaken/ambush/enrage/off_guard/sapped/empower/fortify)
# ——HUD框架 图标键契约"一键一枚专属纹样,查不到的键立即断言 + ? 占位 + 诊断"
# (2026-09-15 格面资源批补 sapped;2026-09-16 护甲等级掷点批 defense_up → ac_up 换装;
# 同日 敌人技能池批补 empower/fortify 敌方增益两键——敌方增益显示 = 敌卡状态行文本 + 本图标键词汇)。
const EFFECT_ICON := {
"attack_up": ["▲", Color8(176, 122, 20), "益"],
"ac_up": ["◇", Color8(46, 110, 142), "益"],
@@ -22,6 +23,8 @@ const EFFECT_ICON := {
"enrage": ["⚔", Color8(198, 61, 34), "害"],
"off_guard": ["✕", Color8(198, 61, 34), "害"],
"sapped": ["☾", Color8(198, 61, 34), "害"],
"empower": ["⇧", Color8(198, 61, 34), "害"],
"fortify": ["▣", Color8(120, 106, 88), "害"],
}
const EFFECT_SEMANTIC := {
"attack_up": "主角攻击加成(hero_offense 池加算)",
@@ -31,6 +34,8 @@ const EFFECT_SEMANTIC := {
"enrage": "敌方输出加成(enemy_output 池加算——attack 型负向)",
"off_guard": "主角承伤加深(hero_defense 池加算——defense 型负向)",
"sapped": "主角输出降低(hero_offense 池加算——weaken 型负向)",
"empower": "敌方输出 ×2(enemy_output 池加算——蓄力技施放;持续 1 个有效结算)",
"fortify": "敌方防御 +100(enemy_defense 池——硬化技施放,主角攻击折半;持续 1 个有效结算)",
}
const SOURCE_NAMES := {"safe_cell": "安全格", "flag_hit": "标中旗", "mine_hit": "踩雷", "clear_reward": "清图奖励"}
# 未知图标键的占位纹样(HUD框架:占位也要可见,禁静默空图标)
@@ -199,8 +199,8 @@ func _analyze_round(db, math, rows: Array, beats: Dictionary) -> Dictionary:
var ticks: Array = []
for row in rows:
var t := String(row.get("type", ""))
if t == "charge":
lines.append("拍=charge(蓄力拍零兑付——设计语义,不读池)")
if t == "cast":
lines.append("拍=cast(施放行零兑付——增益技不读池,技能池批由蓄力行改型)")
elif t == "damage" and String(row.get("source_class", "")) == "enemy_action":
var baseline: int = int(row.get("baseline", 0))
var attach: int = int(row.get("attach", 0))
@@ -224,19 +224,15 @@ func _analyze_round(db, math, rows: Array, beats: Dictionary) -> Dictionary:
return {"pd": pd, "pe": pe, "grants": grants, "lines": lines, "ticks": ticks}
# 拍名重建:round r 的已打拍 = 快照 r-1 的敌人 beat 字段(快照取在 advance 之后 = 下一拍预付);
# R1 = 行 pattern 首元素(steps 从 0 起)。快照 enemies 空时(敌死亡轮)回退 "?"——该轮敌已无行动行。
# 拍名重建(敌人技能池批):pending_skill 字段 = 该结算开轮摇定的动作 id(快照取在结算末尾)——
# round r 的拍 = 快照 r 的 enemies[0].pending_skill。快照 enemies 空时(敌死亡轮)回退 "?"——该轮敌已无行动行。
func _played_beats(run: Dictionary, db) -> Dictionary:
var beats := {}
var erow: Dictionary = db.resolve_enemy_rowset(1, 4)
var pat: Array = erow.get("pattern", [])
if pat.size() > 0:
beats[1] = String(pat[0])
for r in range(2, run.snaps.size() + 1):
var s: Dictionary = run.snaps[r - 2]
for r in range(1, run.snaps.size() + 1):
var s: Dictionary = run.snaps[r - 1]
var en: Array = s.get("enemies", [])
if en.size() > 0:
beats[r] = String(en[0].get("beat", "?"))
beats[r] = String(en[0].get("pending_skill", "?"))
return beats
@@ -39,6 +39,7 @@ func _initialize() -> void:
check(rng.derive_at("reward_roll", 0) == 7422916049646135537, "R2 向量 reward_roll m=42 scope=0")
check(rng.derive_at("encounter", 0) == 1061841193803499209, "R2 向量 encounter m=42 scope=0(registry salt_encounter=0x8EBC6AF09C88C6E3 权威值)")
check(rng.derive_at("armor_roll", 0) == 4806095330042243845, "R2 向量 armor_roll m=42 scope=0(护甲等级掷点批 2026-09-16——registry salt_armor_roll=0xC2B2AE3D27D4EB2F 权威值)")
check(rng.derive_at("behavior_roll", 0) == 426657203655691839, "R2 向量 behavior_roll m=42 scope=0(敌人技能池批 2026-09-16——registry salt_behavior_roll=0x85EBCA7758C24E15 权威值)")
check(rng.derive_at("board_gen", 0) == rng.derive_at("board_gen", 0), "derive_at 纯函数恒等")
# scope 取号语义:首个 open_stream = scope 0;计数器单调
var s0 = rng.open_stream("board_gen")
@@ -83,6 +84,7 @@ func _run_core_smoke(db, rng) -> void:
_smoke_negative_dictionary()
_smoke_resource_surface()
_smoke_settlement_segments()
_smoke_skill_pool()
# ---- 地图战斗资源·身份面单元冒烟(格面资源批:roll 恰一次 / 雷安同池 / 幂等 / 清图批尾)----
@@ -346,7 +348,8 @@ func _first_hero_attack_rows(seed_val: int) -> Dictionary:
return {}
# 削弱 ×2(池 enemy_output 触底 ×0)下的敌方攻击伤害行采样(D2 熊地精——首拍攻击)
# 削弱 ×2(池 enemy_output 触底 ×0)下的敌方攻击伤害行采样(D2 熊地精)。
# 敌人技能池批:开轮摇号下首拍可能是施放行(零伤害)——逐轮重授削弱并停手,直到采到伤害行为止。
func _weaken_zero_round_rows() -> Array:
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(20260916)
@@ -356,36 +359,47 @@ func _weaken_zero_round_rows() -> Array:
if not settlement.begin_battle({"level_index": 1, "node_depth": 2, "hero_hp": int(_db.hero.max_hp)}):
return rows
# 测试通道:直授削弱 ×2(来源格取真实坐标——行来源断言要求;时效 1 当轮结算内有效)
for i in range(2):
settlement._status_effects.apply_effect("weaken", {"source_class": "mine_hit", "origin_cell": Vector2i(0, 0), "round_index": 1})
settlement.minesweeper.settle_batch([{"action": "stop"}])
var out: Array = []
for row in rows:
if String(row.get("type", "")) == "damage" and String(row.get("source_class", "")) == "enemy_action":
out.append(row)
var guard := 8
while out.is_empty() and guard > 0 and settlement.battle_result == "":
guard -= 1
rows.clear()
for i in range(2):
settlement._status_effects.apply_effect("weaken", {"source_class": "mine_hit", "origin_cell": Vector2i(0, 0), "round_index": settlement.round_index})
settlement.minesweeper.settle_batch([{"action": "stop"}])
for row in rows:
if String(row.get("type", "")) == "damage" and String(row.get("source_class", "")) == "enemy_action":
out.append(row)
return out
# AC12 折扣档采样:每停手轮前直授 ac_up ×2(时效 1 逐轮衰减)——Boss 行攻击/重击拍逐拍判定留档
# AC12 折扣档采样:每停手轮前直授 ac_up ×2(时效 1 逐轮衰减)——敌拍逐拍判定留档。
# 敌人技能池批:拍序改为摇号制,原 AC 批定影种子不再保证档位多样性——扫描种子取首个 ≥2 档样本集。
func _ac12_tier_samples() -> Array:
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(20260917)
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng)
var rows: Array = []
settlement.step_row.connect(func(row): rows.append(row))
if not settlement.begin_battle({"level_index": 1, "node_depth": 2, "hero_hp": int(_db.hero.max_hp)}):
return []
var guard := 12
while settlement.battle_result == "" and guard > 0:
guard -= 1
for i in range(2):
settlement._status_effects.apply_effect("ac_up", {"source_class": "safe_cell", "origin_cell": Vector2i(0, 0), "round_index": settlement.round_index})
settlement.minesweeper.settle_batch([{"action": "stop"}])
var tiers: Array = []
for row in rows:
if String(row.get("type", "")) == "damage" and String(row.get("source_class", "")) == "enemy_action":
tiers.append(int(row.get("tier", -1)))
return tiers
for seed_offset in range(20):
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(20260917 + seed_offset)
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng)
var rows: Array = []
settlement.step_row.connect(func(row): rows.append(row))
if not settlement.begin_battle({"level_index": 1, "node_depth": 2, "hero_hp": int(_db.hero.max_hp)}):
continue
var guard := 12
while settlement.battle_result == "" and guard > 0:
guard -= 1
for i in range(2):
settlement._status_effects.apply_effect("ac_up", {"source_class": "safe_cell", "origin_cell": Vector2i(0, 0), "round_index": settlement.round_index})
settlement.minesweeper.settle_batch([{"action": "stop"}])
var tiers: Array = []
for row in rows:
if String(row.get("type", "")) == "damage" and String(row.get("source_class", "")) == "enemy_action":
tiers.append(int(row.get("tier", -1)))
var distinct := {}
for t in tiers:
distinct[int(t)] = true
if tiers.size() >= 3 and distinct.size() >= 2:
return tiers
return []
func _smoke_board_generator() -> void:
@@ -960,12 +974,12 @@ func _smoke_attack_chain_fresh_pool() -> void:
ticks.append({"baseline": int(row.baseline), "attach": int(row.attach), "roll": int(row.roll),
"tier": int(row.tier), "crit": bool(row.get("crit", false)), "tick": int(row.value)}))
check(settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp)}), "fresh 池回归战斗启动(Boss 行)")
# 停手轮:零挖掘零旗 → 零授予 → 池恒空;Boss pattern 前 4 拍 = attack/attack/charge/heavy
for i in range(4):
# 停手轮:零挖掘零旗 → 零授予 → 池恒空;Boss 拍 = 开轮摇号结果(伤害技/普攻兑付伤害行、硬化施放行不计)
for i in range(6):
if settlement.battle_result != "":
break
settlement.minesweeper.settle_batch([{"action": "stop"}])
check(ticks.size() >= 2, "非蓄力攻击拍采样充足(%d 拍——charge 拍零兑付为设计语义不计)" % ticks.size())
check(ticks.size() >= 2, "非施放攻击拍采样充足(%d 拍——增益施放行零兑付为设计语义不计)" % ticks.size())
var all_full := true
for t in ticks:
var base_factor: float = float(t.tier) * (float(_db.settlement.crit_multiplier) / 100.0 if t.crit else 1.0)
@@ -1383,7 +1397,7 @@ func _row_phase(row: Dictionary, ri: int) -> int:
return 3
"verdict":
return 4
"charge":
"cast":
return 2
"damage":
if String(row.get("source_class", "")) in ["hero_attack", "enemy_action"]:
@@ -1473,3 +1487,285 @@ func check_silent(cond: bool) -> void:
if not cond:
_failures += 1
print(" [FAIL] (silent check)")
# ==================== 敌人技能池批(2026-09-16 用户拍板——开轮摇号出招系统) ====================
# 六项验收断言(docs/proposal-skill-pool.md §8 批 1):同种子回放逐位复现 / 单敌轨迹独立复放 /
# EV 抽样对锚 / 死亡打断作废 / 空池不可能 / 加载校验拒绝。
func _smoke_skill_pool() -> void:
print("--- 敌人技能池(开轮摇号 · 冷却/保底两闸 · 三乘法链 · 独立子流)---")
_smoke_skill_pool_data()
_smoke_skill_pool_replay_independence()
_smoke_skill_pool_ev_anchor()
_smoke_skill_pool_death_void()
_smoke_skill_pool_validation_reject()
# ① 数据面:技能表行集 + 普攻锁死 + 建议池行值 + 空池不可能(结构断言)
func _smoke_skill_pool_data() -> void:
check(_db.skill.rows.size() == 5, "技能表五行(普攻/突刺/蓄力/重击/硬化)")
var basic: Dictionary = _db.skill.row_by_id("attack")
check(not basic.is_empty() and String(basic.type) == "basic" and int(basic.cooldown_beats) == 0
and (not basic.has("damage_mult") or is_equal_approx(float(basic.damage_mult), 1.0)),
"普攻行:倍率恒 1 · 冷却恒 0(域校验锁死——池恒非空的结构前提)")
var lunge: Dictionary = _db.skill.row_by_id("lunge")
check(not lunge.is_empty() and is_equal_approx(float(lunge.damage_mult), 1.5) and int(lunge.cooldown_beats) == 2,
"突刺行(伤害 ×1.5 · 冷却 2——提案 §2.5 建议池新行)")
var charge_row: Dictionary = _db.skill.row_by_id("charge")
check(not charge_row.is_empty() and String(charge_row.buff_ref) == "empower",
"蓄力行 = 纯攻击增益技(0 伤害、buff_ref → empower——与重击无组合语义)")
var expect_pools := {
"goblin": {"attack": 9, "lunge": 1},
"bugbear": {"attack": 6, "charge": 2, "heavy": 2},
"ogre_chief": {"attack": 6, "heavy": 2, "harden": 2},
}
var pools_ok := true
for row in _db.enemy.rows:
var eid := String(row.enemy_id)
if not expect_pools.has(eid):
pools_ok = false
continue
var got := {}
for entry in row.pool:
got[String(entry.skill_id)] = int(entry.weight)
if got != expect_pools[eid]:
pools_ok = false
check(pools_ok, "六行池模板 = 建议池行值(地精 9/1 · 熊地精 6/2/2 · 酋长 6/2/2——全部 provisional)")
var basic_count_ok := true
for row in _db.enemy.rows:
var n := 0
for entry in row.pool:
var srow: Dictionary = _db.skill_row(String(entry.skill_id))
if not srow.is_empty() and String(srow.type) == "basic":
n += 1
if n != 1:
basic_count_ok = false
check(basic_count_ok, "每池恰一行普攻(空池不可能的加载校验同款)")
var rollable_ok := true
for depth in [0, 1, 2, 3, 4]:
var es = load("res://core/unit_container.gd").new(_db)
es.spawn(1, int(depth), 50)
for u in es.all_enemies():
if es.rollable_skills(u).is_empty():
rollable_ok = false
check(rollable_ok, "全深度实例 spawn 后可摇集合恒非空(普攻恒冷却 0——容器级断言)")
# 状态表新行 + 敌方防御池收编
var empower: Dictionary = _db.status_effect_row("empower")
var fortify: Dictionary = _db.status_effect_row("fortify")
check(not empower.is_empty() and String(empower.target_side) == "enemy" and int(empower.magnitude) == 100,
"蓄力状态行 empower(enemy +100 percent——敌方输出 ×2 走既有 enemy_output 池)")
check(not fortify.is_empty() and String(fortify.target_side) == "enemy" and int(fortify.magnitude) == 100,
"硬化状态行 fortify(enemy +100 percent——敌方防御池新词汇载体)")
check(String(_db.settlement.effect_pool_map.get("empower", "")) == "enemy_output" \
and String(_db.settlement.effect_pool_map.get("fortify", "")) == "enemy_defense",
"effect_pool_map 收编 empower/fortify(敌方防御池 enemy_defense 入表)")
# ② 同种子回放逐位复现 + ③ 单敌轨迹独立复放(D3 熊地精双雄——多实例同种)
func _smoke_skill_pool_replay_independence() -> void:
var seed_val := -1
for s in range(1, 60):
var seqs := _record_pool_trajectories(s, 1)
if not seqs.is_empty() and int(seqs.count) >= 2:
seed_val = s
break
check(seed_val > 0, "多实例种子可达(seed=%d——D3 遭遇 ≥2 实例)" % seed_val)
var rounds := 10
var run1 := _record_pool_trajectories(seed_val, rounds)
var run2 := _record_pool_trajectories(seed_val, rounds)
check(not run1.is_empty() and JSON.stringify(run1.seqs) == JSON.stringify(run2.seqs),
"同种子回放:各实例摇定动作序列逐位相同(×%d 轮 × %d 实例)" % [rounds, int(run1.count)])
var indep_ok := true
var detail := ""
var units: Array = _db.resolve_enemy_rowset(1, 3).pool
for i in range(int(run1.count)):
var replayed := _replay_single_trajectory(seed_val, units, rounds, i)
if JSON.stringify(replayed) != JSON.stringify(run1.seqs[i]):
indep_ok = false
detail += " #%d" % i
check(indep_ok, "单敌轨迹独立复放(只喂该敌子流 scope=spawn_index×%d+序 即可逐位重建%s——互不穿插)" % [load("res://core/rng_service.gd").BEHAVIOR_SUBSTREAM_STRIDE, detail])
# 多敌战逐轮记录各实例摇定动作(hero_atk=1/hp=300 构造全员存活满 N 轮——轨迹长度 = 轮数)
func _record_pool_trajectories(seed_val: int, rounds: int) -> Dictionary:
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(seed_val)
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng)
if not settlement.begin_battle({"level_index": 1, "node_depth": 3, "hero_hp": 300, "hero_max_hp": 300, "hero_atk": 1}):
return {}
var seqs: Array = []
for u in settlement._enemy_system.all_enemies():
seqs.append([])
for r in rounds:
var handles: Array = settlement._enemy_system.all_enemies()
for i in handles.size():
seqs[i].append(settlement._enemy_system.pending_action(handles[i]))
settlement.minesweeper.settle_batch([{"action": "stop"}])
if settlement.battle_result != "":
return {}
return {"count": seqs.size(), "seqs": seqs}
# 独立重放器(镜像 开轮摇号 + 容器冷却/保底规则——只消费目标实例自己的子流 scope 段)
func _replay_single_trajectory(seed_val: int, pool_template: Array, rounds: int, spawn_index: int) -> Array:
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(seed_val)
var stride: int = load("res://core/rng_service.gd").BEHAVIOR_SUBSTREAM_STRIDE
var pool: Array = []
for entry in pool_template:
pool.append({"skill_id": String(entry.skill_id), "weight": int(entry.weight), "cooldown_left": 0})
var streak := 0
var pity_m: int = int(_db.skill.pity_threshold)
var out: Array = []
for k in rounds:
var entries: Array = []
for e in pool:
if int(e.cooldown_left) <= 0:
entries.append({"skill_id": e.skill_id, "weight": e.weight})
if streak >= pity_m:
var specials: Array = []
for e in pool:
var sr: Dictionary = _db.skill_row(String(e.skill_id))
if not sr.is_empty() and String(sr.type) != "basic":
specials.append({"skill_id": e.skill_id, "weight": e.weight})
if not specials.is_empty():
entries = specials
var sid := SkillLottery.draw(entries, rng.open_scope("behavior_roll", spawn_index * stride + k))
out.append(sid)
var srow: Dictionary = _db.skill_row(sid)
for e in pool:
e.cooldown_left = maxi(0, int(e.cooldown_left) - 1) # 开轮递减(摇号读值之后——与容器同序)
if String(e.skill_id) == sid:
e.cooldown_left = int(srow.cooldown_beats) # 执行上报置冷却(战斗段时点)
streak = streak + 1 if String(srow.type) == "basic" else 0
return out
# ④ EV 抽样对锚(含闸长期速率 vs 静态锚——proposal §2.5;精确重锚归 难度曲线与Boss)。
# 带宽口径:熊地精/酋长 EV 结构性恒 1(普攻 1 + 重击 2 与蓄力/硬化 0 加权守恒)→ 窄带;
# 地精突刺池受保底闸抬升(唯一特殊技 w=1/10,保底 M=4 把突刺率从自然 10% 抬到 ~20%+)→ 宽带登记。
func _smoke_skill_pool_ev_anchor() -> void:
var bands := {"goblin": [1.00, 1.20], "bugbear": [0.97, 1.03], "ogre_chief": [0.97, 1.03]}
var species := ["goblin", "bugbear", "ogre_chief"]
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(20260921)
var stride: int = load("res://core/rng_service.gd").BEHAVIOR_SUBSTREAM_STRIDE
var n_beats := 5000
var pity_m: int = int(_db.skill.pity_threshold)
for si in species.size():
var eid: String = species[si]
var template: Array = []
for row in _db.enemy.rows:
if String(row.enemy_id) == eid:
template = row.pool
break
var pool: Array = []
for entry in template:
pool.append({"skill_id": String(entry.skill_id), "weight": int(entry.weight), "cooldown_left": 0})
var streak := 0
var ev_sum := 0.0
for k in n_beats:
var entries: Array = []
for e in pool:
if int(e.cooldown_left) <= 0:
entries.append({"skill_id": e.skill_id, "weight": e.weight})
if streak >= pity_m:
var specials: Array = []
for e in pool:
var sr: Dictionary = _db.skill_row(String(e.skill_id))
if not sr.is_empty() and String(sr.type) != "basic":
specials.append({"skill_id": e.skill_id, "weight": e.weight})
if not specials.is_empty():
entries = specials
var sid := SkillLottery.draw(entries, rng.open_scope("behavior_roll", si * stride + k))
var srow: Dictionary = _db.skill_row(sid)
var mult := 0.0
if String(srow.type) == "basic":
mult = 1.0
elif String(srow.type) == "damage":
mult = float(srow.get("damage_mult", 1.0))
ev_sum += mult
for e in pool:
e.cooldown_left = maxi(0, int(e.cooldown_left) - 1)
if String(e.skill_id) == sid:
e.cooldown_left = int(srow.cooldown_beats)
streak = streak + 1 if String(srow.type) == "basic" else 0
var measured: float = ev_sum / float(n_beats)
var band: Array = bands[eid]
var in_band: bool = measured >= float(band[0]) and measured <= float(band[1])
check(in_band, "EV 抽样对锚 %s(实测 %.4f ∈ [%.2f, %.2f] 含闸长期速率,×%d 拍)" % [eid, measured, float(band[0]), float(band[1]), n_beats])
# ⑤ 死亡打断作废(摇定后、兑现前死亡 → 本拍技能作废、不动手、不进账)
func _smoke_skill_pool_death_void() -> void:
var void_seed := -1
for s in range(1, 400):
var rg = load("res://core/rng_service.gd").new()
rg.set_master_seed(s)
var erow: Dictionary = _db.resolve_enemy_rowset(1, 2)
var entries: Array = []
for entry in erow.pool:
entries.append({"skill_id": String(entry.skill_id), "weight": int(entry.weight)})
var sid := SkillLottery.draw(entries, rg.open_scope("behavior_roll", 0))
if String(_db.skill_row(sid).type) != "basic":
void_seed = s
break
check(void_seed > 0, "死亡打断种子可达(seed=%d——D2 首拍摇中特殊技)" % void_seed)
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(void_seed)
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng)
var rows: Array = []
settlement.step_row.connect(func(row): rows.append(row))
check(settlement.begin_battle({"level_index": 1, "node_depth": 2, "hero_hp": 50, "hero_atk": 999}),
"死亡打断战斗启动(hero_atk 999——先手必杀 40HP 熊地精)")
var u: Dictionary = settlement._enemy_system.all_enemies()[0] # 句柄预捕获(胜利 despawn 后容器已清,字典引用仍可读)
var pending: String = settlement._enemy_system.pending_action(u)
check(pending != "", "开轮已摇定本拍动作(待发 = %s)" % pending)
settlement.minesweeper.settle_batch([{"action": "stop"}])
var enemy_rows := 0
for row in rows:
if String(row.get("source_class", "")) == "enemy_action":
enemy_rows += 1
check(enemy_rows == 0, "先手击杀 → 摇定技能作废(第 1 轮零敌方行动行——死亡即不动手)")
var cd_sum := 0
for e in u.pool:
cd_sum += int(e.cooldown_left)
check(cd_sum == 0 and int(u.basic_streak) == 0,
"作废不进账(全池冷却 0 · 保底计数 0——执行上报未发生)")
# ⑥ 加载校验拒绝(单表域校验 + 跨表池构成纪律)
func _smoke_skill_pool_validation_reject() -> void:
var skill_table = load("res://data/schemas/skill_table.gd").new()
var enemy_table = load("res://data/schemas/enemy_table.gd").new()
enemy_table.rows = [{"enemy_id": "x", "display_name": "x", "pool": [{"skill_id": "ghost", "weight": 1}]}]
check(_has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "dangling_skill"),
"加载校验拒绝:池条目 skill_id 悬空致命")
skill_table.rows = [{"skill_id": "a", "display_name": "a", "type": "damage", "damage_mult": 1.5, "cooldown_beats": 2}]
check(_has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition"),
"加载校验拒绝:池普攻行数 ≠ 1(空池不可能前提)")
skill_table.rows = [
{"skill_id": "b1", "display_name": "b1", "type": "buff", "buff_ref": "empower", "buff_duration": 2, "cooldown_beats": 3},
{"skill_id": "b2", "display_name": "b2", "type": "buff", "buff_ref": "fortify", "buff_duration": 2, "cooldown_beats": 4}]
enemy_table.rows = [{"enemy_id": "x", "display_name": "x", "pool": [{"skill_id": "b1", "weight": 1}, {"skill_id": "b2", "weight": 1}]}]
check(_has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition"),
"加载校验拒绝:池增益技 > 1(每池增益 ≤1 纪律)")
skill_table.rows = [{"skill_id": "atk", "display_name": "a", "type": "basic", "damage_mult": 2.0, "cooldown_beats": 0}]
check(not skill_table.validate().is_empty(), "加载校验拒绝:普攻配倍率 ≠ 1(域校验锁死)")
skill_table.rows = [{"skill_id": "atk", "display_name": "a", "type": "basic", "cooldown_beats": 2}]
check(not skill_table.validate().is_empty(), "加载校验拒绝:普攻配冷却 ≠ 0(域校验锁死)")
skill_table.rows = [{"skill_id": "d", "display_name": "d", "type": "damage", "damage_mult": 30.0, "cooldown_beats": 1}]
check(not skill_table.validate().is_empty(), "加载校验拒绝:damage_mult 越域 [1.1,16]")
var et2 = load("res://data/schemas/enemy_table.gd").new()
et2.rows = [{"enemy_id": "x", "display_name": "x", "pool": [{"skill_id": "a", "weight": 0}]}]
check(not et2.validate().is_empty(), "加载校验拒绝:池权重 ≤ 0")
et2.rows = [{"enemy_id": "x", "display_name": "x", "pool": []}]
check(not et2.validate().is_empty(), "加载校验拒绝:空池(禁)")
func _has_err_category(errs: Array, category: String) -> bool:
for e in errs:
if String(e.get("category", "")) == category:
return true
return false