feat(combat): 伏击单轮上限删除批——反灾难护栏退役(2026-09-20 用户拍板)——①动机=用户裁决:摆烂连踩雷受罚是应得反馈,行动点预算天然封顶(AP 6/轮)无需再设截断;原上限 4 < AP 6 砍掉最后两层的保护恰是用户不要的保护;②代码删除面=enemy.tres 十二行 advantage_per_round_cap 字段+敌表域校验+战斗编排器 _enemy_cap 截流与溢出行发射+单位容器 F3 伏击授予计划函数与快照字段+日志视图 overflow 行格式化与聚合类型(残留消费面零);③行协议 10 类→9 类=溢出未生效行唯一发射源即伏击上限截流,上限删除后零发射面整类下线;满层拒收走奖励未生效行(增益状态文档三处行名随代码实发对齐);④GDD 同步=敌人系统 v0.11(伏击节重述/字段表删行/结构冻结联动纪律去两条/验收标准同步)+战斗结算 v0.19(行协议 9 类/授予段直授无上限/守恒条改池共享)+地图战斗资源 v0.13+难度曲线与Boss v0.14+UX 三文档/HUD/音频/战斗表现行协议计数与未生效类行枚举同步;⑥registry=伏击溢出词条与 cap 字段行删除+敌表 clause3_supply 上界式改 unit_count × AP 上界 × m_amb+两处义务履行回执追加 2026-09-20 修订注记+行型封闭枚举 9 类;⑦06 待办=伏击量纲与上限联动复核改写为量级当量复核(上限面收口,量级重核归曲线调参批)+同深度第二敌种条目混编上限取值面消解注记;⑧验证=冒烟 330 项 0 失败三闸全过(blast 负向断言改 stacks 读极性字典行的直授累加)+gdd_lint 与 master 基线一致(8 文件存量不通过非本批引入);⑨遗留=docs/lean-gdd-sample-b1.md 样例文档两处旧口径未动(非规范件);随批新增 rhythm_batch.gd.uid(编辑器缓存刷新产物,Godot 4.7 uid 侧车入籍)

This commit is contained in:
DKChan committed 2026-09-20 02:15:46 +08:00
1 parent 4708940b7e
commit 656f00055f
21 files changed
+68 -101

No files matched your search

@@ -20,9 +20,9 @@ signal round_started(round_index: int)
signal board_changed()
signal settlement_snapshot(snap: Dictionary) # 每结算末尾的全量状态快照(playtest_logger 消费——JSONL 数值分析)
# 明细流水行型协议(战斗结算 Core Rule;10 类——2026-09-15 格面资源批由 15 类收拢,
# 清单改动须同步 battle_log_view 的 _format_row 与本文档头注):
# 效果行 effect · 伤害行 damage · 治疗行 heal · 溢出未生效行 overflow ·
# 明细流水行型协议(战斗结算 Core Rule;9 类——2026-09-20 伏击单轮上限退役,溢出未生效行 overflow 随之下线;
# 2026-09-15 格面资源批由 15 类收拢,清单改动须同步 battle_log_view 的 _format_row 与本文档头注):
# 效果行 effect · 伤害行 damage · 治疗行 heal ·
# 奖励未生效行 reward_ineffective · 标旗未中行 flag_miss · 施放行 cast(敌人技能池批由蓄力行 charge 改型)· 衰减行 decay ·
# 判定行 verdict · 高光行 highlight
# 行来源断言豁免集合(行型层)——判定段 / 衰减段本就无棋盘来源,勿一刀切误炸
@@ -58,7 +58,6 @@ var _flag_consumed := {} # 兑付资格以雷格实例为键
var _flag_miss_seen := {} # 标旗未中行去重(每安全格实例一次)
var _perfect_hit_true := 0 # 板级完美排雷计数(install 重置)
var _perfect_hit_false := 0
var _enemy_cap := 0 # EnemyTable 行值快照(伏击 cap)
var _m_amb := 0 # StatusEffectTable.ambush.magnitude 快照
var _hero_atk := 0 # 主角攻击基值快照(encounter.hero_atk;E4 收口——表值运行期不可变)
@@ -113,7 +112,6 @@ func begin_battle(enc: Dictionary) -> bool:
_fatal("敌人系统 spawn 被拒")
return false
var erow: Dictionary = db.resolve_enemy_rowset(int(enc.level_index), int(enc.node_depth))
_enemy_cap = int(erow.get("advantage_per_round_cap", 0))
_m_amb = int(db.status_effect_row("ambush").get("magnitude", 0))
if not _install_new_board():
return false
@@ -288,7 +286,7 @@ func _apply_percent_row(row: Dictionary, source_class: String, cell: Vector2i, r
# flat 行兑付(负向恒即时,无库存语义):
# blast 正向 = 全体存活敌各 power 点(无敌 → 奖励未生效行);blast 负向 = 敌方伏击 +stacks 层(cap/溢出行协议不变);
# blast 正向 = 全体存活敌各 power 点(无敌 → 奖励未生效行);blast 负向 = 敌方伏击 +stacks 层(直授无上限);
# heal 正向 = 主角回复 clamp(满血 → 奖励未生效行);heal 负向 = 主角直伤 power 点(不经增益状态;归零 → 战斗段短路/判定段判负)。
# pierce/magic(伤害道具批 2026-09-18)正向 = 单目标克制直伤(必中全额);负向 = 主角直伤。
# 返回 "redeemed" = 实际有效兑付 / "zero" = 零兑付(行已自记)——清图高光只记实际有效兑付。
@@ -343,19 +341,14 @@ func _apply_flat_row(row: Dictionary, source_class: String, cell: Vector2i, ri:
return "zero"
# blast 负向:敌方伏击 +stacks 层(伏击池 cap 与"溢出未生效"行协议不变——敌人系统 F3 逐层守恒,
# 多源共享同一池值;单结算内先到先占 cap,超出部分逐层记溢出行)
# blast 负向:敌方伏击 +stacks 层(直授无上限——单轮授予上限已随 2026-09-20 用户拍板退役,
# 一轮踩雷数由行动点预算天然封顶;多源共享同一池值)
func _apply_ambush_grant(row: Dictionary, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "") -> String:
var requested: int = maxi(1, int(row.get("stacks", 1)))
var remaining_cap: int = maxi(0, _enemy_cap - _status_effects.pool_layers("ambush"))
var plan: Dictionary = _enemy_system.ambush_grant_plan(requested, remaining_cap)
for i in range(int(plan.granted)):
for i in range(requested):
_status_effects.apply_effect("ambush", {"source_class": source_class, "origin_cell": cell, "round_index": ri})
if int(plan.granted) > 0:
_emit_row("effect", source_class, cell, ri, "敌方伏击 +%d 层%s" % [int(plan.granted), label_suffix], int(plan.granted), {"effect_id": "ambush", "pool_value": _status_effects.pool_layers("ambush") * _m_amb}, highlight)
for i in range(int(plan.overflow)):
_emit_row("overflow", source_class, cell, ri, "伏击溢出未生效", 0, {"batch_key": "ambush_overflow"})
return "redeemed" if int(plan.granted) > 0 else "zero"
_emit_row("effect", source_class, cell, ri, "敌方伏击 +%d 层%s" % [requested, label_suffix], requested, {"effect_id": "ambush", "pool_value": _status_effects.pool_layers("ambush") * _m_amb}, highlight)
return "redeemed"
# 旗判定批:标中 = 该雷格资源的正向兑付(每雷实例一次;高光 = 强制检查点);
@@ -59,7 +59,7 @@ func apply_effect(effect_id: String, source: Dictionary, duration_override: int
for inst in _instances:
if inst.effect_id == effect_id:
if inst.layers >= max_stack:
return false # 满层拒绝原子——容器零写入(溢出行归 战斗结算)
return false # 满层拒绝原子——容器零写入(拒收由战斗结算记"奖励未生效"行)
inst.layers += 1 # stack 授予不触碰 R(spec 决议)
return true
_instances.append(_new_instance(effect_id, 1, dur, source, snap_m, snap_unit, snap_display))
@@ -82,7 +82,6 @@ func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_override:
"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 护甲等级掷点批)
"alive": true,
@@ -238,12 +237,6 @@ func all_enemies() -> Array:
return _enemies.duplicate()
# F3 机制算术纯函数(战斗结算 预截流消费面):cap 判定在授予前;守恒 granted + overflow = mines_r
func ambush_grant_plan(mines_r: int, cap: int) -> Dictionary:
var granted: int = mini(mines_r, cap)
return {"granted": granted, "overflow": mines_r - granted}
func despawn_all() -> bool:
if _despawned or not _spawned:
_diag("重复/无效 despawn_all")
@@ -86,9 +86,6 @@ func validate() -> Array:
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})
# armor_type(可省略——缺省 light 向后兼容)
var at: String = String(row.get("armor_type", "light"))
if not ARMOR_TYPES.has(at):
@@ -22,7 +22,6 @@ rows = [{
"skill_id": "lunge",
"weight": 1
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -43,7 +42,6 @@ rows = [{
"skill_id": "lunge",
"weight": 1
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -66,7 +64,6 @@ rows = [{
"skill_id": "heavy",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -90,7 +87,6 @@ rows = [{
"skill_id": "heavy",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -113,7 +109,6 @@ rows = [{
"skill_id": "harden",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -136,7 +131,6 @@ rows = [{
"skill_id": "heavy",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -160,7 +154,6 @@ rows = [{
"skill_id": "heavy",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -183,7 +176,6 @@ rows = [{
"skill_id": "charge",
"weight": 1
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -206,7 +198,6 @@ rows = [{
"skill_id": "heavy",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -230,7 +221,6 @@ rows = [{
"skill_id": "charge",
"weight": 1
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -254,7 +244,6 @@ rows = [{
"skill_id": "charge",
"weight": 1
}],
"advantage_per_round_cap": 4,
"armor_type": "light",
"armor_class": 0
}, {
@@ -280,7 +269,6 @@ rows = [{
"skill_id": "web_snare",
"weight": 2
}],
"advantage_per_round_cap": 4,
"armor_type": "ward",
"armor_class": 0
}]
@@ -168,7 +168,7 @@ rows = [{
; 装板期一次摇定;摇号只消费正向行权重(类型频次 = attack 20 / defense 16 / blast 16 / heal 3 / weaken 12,合计 67;
; 头段"heal 15 / weaken 12,合计 79"为历史注记——数值压缩批后 heal 3、合计 67,见本文件尾部 2026-09-17 批注);
; 负向行 weight=0 不参与摇号,纯(类型, 极性)兑付字典——heal 负向 = 主角直伤 4(不经增益状态),
; blast 负向 = ambush +2 层(stacks=2,伏击池 cap 与"溢出未生效"行协议不变)。
; blast 负向 = ambush +2 层(stacks=2,直授无上限——伏击单轮上限 2026-09-20 退役)。
; 可选类三行(knife_t1/knife_adv/heal_pack)与注入池 active_inject 随可选类道具整体删除(供给控制器/保底/窗口同步下线)。
; 区间行(伤害量值区间批 2026-09-14):heal_minor [2,6] · clear_blast [25,55]——中点锚 power 恒等期望当量,
; roll 时机 = 身份预分配批内追加取号(既有取号序零扰动);spark_aoe 定值行无区间字段。
@@ -15,7 +15,7 @@ signal review_requested
const BAND_H_DIG := 24
const BAND_H_SETTLE := 84
const AGG_TYPES := ["reward_ineffective", "overflow"]
const AGG_TYPES := ["reward_ineffective"]
const COL_GOLD := "#f0a828"
const COL_INK := "#33261b"
@@ -194,8 +194,8 @@ func _format_row(row: Dictionary, agg_count := -1) -> String:
return prefix + "[color=%s]%s[/color]" % [body_col, body]
"heal":
return prefix + "[color=#7cc47c]%s +%d%s%s[/color]" % [name_, v, tail, suffix]
"reward_ineffective", "overflow":
# 未生效两类行:⚠ icon + 划线形状冗余(非色相独载)+ 聚合计数
"reward_ineffective":
# 未生效类行:⚠ icon + 划线形状冗余(非色相独载)+ 聚合计数
var n := (" ×%d" % agg_count) if agg_count > 1 else ""
return prefix + "[s][color=#b08060]⚠ %s%s[/color][/s]%s" % [name_, n, suffix]
"flag_miss":
@@ -0,0 +1 @@
uid://cc2yhv5d20gfk
@@ -1391,9 +1391,9 @@ func _rows_at(rows: Array, source_class: String, cell) -> Array:
# 负向字典逐型直验(类型化兑付行协议):找真实板上各型非封锁格,直调 _grant_routed 负向路由,
# 断言每型的兑付形态(enrage/off_guard/sapped 效果行、heal 直伤行、blast ambush+cap 溢出协议)。
# 断言每型的兑付形态(enrage/off_guard/sapped 效果行、heal 直伤行、blast ambush 直授)。
func _smoke_negative_dictionary() -> void:
print("--- 负向字典逐型直验(sapped 在位 / heal 直伤 / blast 伏击+2 与 cap 溢出)---")
print("--- 负向字典逐型直验(sapped 在位 / heal 直伤 / blast 伏击直授)---")
# 数据面:sapped 行在位(状态表七行 + effect_pool_map 收编)
var sapped: Dictionary = _db.status_effect_row("sapped")
check(not sapped.is_empty() and String(sapped.get("target_side", "")) == "hero" \
@@ -1456,20 +1456,16 @@ func _smoke_negative_dictionary() -> void:
if String(row.get("reward_id", "")) == "heal_backlash":
backlash_power = int(row.power)
check(hp_after == hp_before - backlash_power, "heal 型负向 → 主角直伤 %d(%d → %d,不经增益状态)" % [backlash_power, hp_before, hp_after])
# blast:负向 = 伏击 +2 层;cap 内生效、超 cap 逐层溢出(cap 读敌行值——零字面量)
# blast:负向 = 伏击 +stacks 层直授(无上限——单轮授予上限 2026-09-20 退役;stacks 读表零字面量)
if by_type.has("blast"):
var cap: int = int(_db.resolve_enemy_rowset(1, 0).get("advantage_per_round_cap", 0))
rows.clear()
var snap_b: Dictionary = settlement._reward_economy.query_full_snapshot(by_type["blast"])
var stacks: int = maxi(1, int(settlement._reward_economy.polarity_row("blast", "negative").get("stacks", 1)))
settlement._grant_routed(snap_b, "negative", "mine_hit", by_type["blast"], 1)
var layers_after_first: int = settlement._status_effects.pool_layers("ambush")
check(layers_after_first == mini(2, cap), "blast 型负向 → 伏击 +%d 层(cap=%d)" % [layers_after_first, cap])
while settlement._status_effects.pool_layers("ambush") < cap:
settlement._grant_routed(snap_b, "negative", "mine_hit", by_type["blast"], 1)
rows.clear()
check(layers_after_first == stacks, "blast 型负向 → 伏击 +%d 层(stacks=%d)" % [layers_after_first, stacks])
settlement._grant_routed(snap_b, "negative", "mine_hit", by_type["blast"], 1)
check(_rows_of_type(rows, "overflow").size() >= 1 and settlement._status_effects.pool_layers("ambush") == cap,
"blast 型负向超 cap → 溢出未生效行(伏击池守恒在 cap=%d)" % cap)
check(settlement._status_effects.pool_layers("ambush") == 2 * stacks, "blast 型负向连发两次 → 伏击池逐层累加至 %d(无上限截断)" % (2 * stacks))
# 资源面断言:chain/dig 事件恒携带 resource=true(雷格同权);initial 恒 false(封锁格);