Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff5fcd644d | ||
|
|
c10bbc1114 | ||
|
|
fde1795dd0 | ||
|
|
45b53d6897 | ||
|
|
f498d2ec85 | ||
|
|
bf7aa514b6 | ||
|
|
208e5652db | ||
|
|
be7ef54e30 | ||
|
|
c062b62d12 | ||
|
|
b44d3234b8 | ||
|
|
94a05afd49 |
No files matched your search
+29
-12
@@ -93,11 +93,29 @@ func request_dig(chain_size: int, clears_map: bool) -> bool:
|
|||||||
_reject("phase")
|
_reject("phase")
|
||||||
return false
|
return false
|
||||||
var cost := _cost("dig")
|
var cost := _cost("dig")
|
||||||
# dig_paid 发射+施加(付费开掘发起、动作经济闸收对价前:
|
# dig_paid 发射+施加(付费开掘发起、动作经济闸收对价前——施加单点 _apply_dig_paid_hits):
|
||||||
# counter_spend 命中=尝试加收对价并积计数;资源不足走 fallback/耗尽即止=只付常规基础对价。
|
# counter_spend 命中=尝试加收对价并积计数;资源不足走 fallback/耗尽即止=只付常规基础对价。
|
||||||
# counter_add〔双管镐 付费挖格计数〕/ grant_ap〔每第 2 次返 1 AP——对价减免,
|
var apply_res := _apply_dig_paid_hits(cost)
|
||||||
# mod_eq 闸在行 when〕同面施加)
|
cost = int(apply_res["cost"])
|
||||||
|
if _ap < cost:
|
||||||
|
_reject("resource_face")
|
||||||
|
return false # 拒绝轮零入池(矿脉储能只在扣费受理后兑付——C2 成功才登记同款纪律)
|
||||||
|
if int(apply_res["refund"]) > 0:
|
||||||
|
cost = maxi(0, cost - int(apply_res["refund"])) # 返还=对价减免(净费不低于 0——免费开格不产生负对价)
|
||||||
|
var accepted := _spend_cost_and_arbitrate(cost, clears_map)
|
||||||
|
if accepted and int(apply_res["rap_gain"]) > 0:
|
||||||
|
grant_reserve_ap(int(apply_res["rap_gain"])) # 每次受理的付费挖格产 RAP(一次性入池——每动作恰一挖,连锁不二次)
|
||||||
|
return accepted
|
||||||
|
|
||||||
|
|
||||||
|
# dig_paid 命中施加单点(迭代 46 卡A T3 自 request_dig 迁出——分支本体逐字保留:
|
||||||
|
# counter_spend 加收对价并积计数/counter_add 施加〔双管镐 付费挖格计数〕/
|
||||||
|
# grant_ap 对价减免〔每第 2 次返 1 AP——mod_eq 闸在行 when〕/grant_reserve_ap 矿脉储能累计;
|
||||||
|
# 命中序=声明序不变。返回 {cost, refund, rap_gain} 交受理面消费)
|
||||||
|
func _apply_dig_paid_hits(base_cost: int) -> Dictionary:
|
||||||
|
var cost := base_cost
|
||||||
var dig_refund := 0
|
var dig_refund := 0
|
||||||
|
var rap_gain := 0
|
||||||
if _fx != null:
|
if _fx != null:
|
||||||
for h in _fx.dispatch(_ctx, "dig_paid", {"ap_cost": cost, "ap_surplus": _ap - cost}):
|
for h in _fx.dispatch(_ctx, "dig_paid", {"ap_cost": cost, "ap_surplus": _ap - cost}):
|
||||||
if String(h.get("kind", "")) == "counter_spend":
|
if String(h.get("kind", "")) == "counter_spend":
|
||||||
@@ -110,12 +128,9 @@ func request_dig(chain_size: int, clears_map: bool) -> bool:
|
|||||||
EffectEngine.counter_add(_ctx, String(h.get("counter_id", "")), int(h.get("value", 0)) if set_v == null else int(set_v), set_v != null)
|
EffectEngine.counter_add(_ctx, String(h.get("counter_id", "")), int(h.get("value", 0)) if set_v == null else int(set_v), set_v != null)
|
||||||
elif String(h.get("kind", "")) == "grant_ap":
|
elif String(h.get("kind", "")) == "grant_ap":
|
||||||
dig_refund += int(h.get("value", 0))
|
dig_refund += int(h.get("value", 0))
|
||||||
if _ap < cost:
|
elif String(h.get("kind", "")) == "grant_reserve_ap":
|
||||||
_reject("resource_face")
|
rap_gain += int(h.get("value", 0))
|
||||||
return false
|
return {"cost": cost, "refund": dig_refund, "rap_gain": rap_gain}
|
||||||
if dig_refund > 0:
|
|
||||||
cost = maxi(0, cost - dig_refund) # 返还=对价减免(净费不低于 0——免费开格不产生负对价)
|
|
||||||
return _spend_cost_and_arbitrate(cost, clears_map)
|
|
||||||
|
|
||||||
|
|
||||||
func _spend_cost_and_arbitrate(cost: int, clears_map: bool) -> bool:
|
func _spend_cost_and_arbitrate(cost: int, clears_map: bool) -> bool:
|
||||||
@@ -196,13 +211,15 @@ func finish_round_stop(transfer: bool, transfer_pct: int = 100) -> void:
|
|||||||
# 轮内刷新:唯一入口 =
|
# 轮内刷新:唯一入口 =
|
||||||
# Active 相位——仅扣 RAP,相位与 AP 均不动。刷新不打断轮次、不触发结算;换图编排归 战斗结算
|
# Active 相位——仅扣 RAP,相位与 AP 均不动。刷新不打断轮次、不触发结算;换图编排归 战斗结算
|
||||||
# (retire + install 立即执行,玩家同轮继续挖新图)。决策主体 = 玩家(经 HUD框架发起)纪律不变。
|
# (retire + install 立即执行,玩家同轮继续挖新图)。决策主体 = 玩家(经 HUD框架发起)纪律不变。
|
||||||
func request_refresh() -> bool:
|
# cost_override=折后价入参(迭代 46 卡A T3——BattleRefreshFlow 折价读数传入;缺省 -1=表基线逐位不变)
|
||||||
|
func request_refresh(cost_override: int = -1) -> bool:
|
||||||
if _phase != Phase.ACTIVE:
|
if _phase != Phase.ACTIVE:
|
||||||
_reject("phase")
|
_reject("phase")
|
||||||
return false
|
return false
|
||||||
if _rap < db.run.action_economy.rap_refresh_cost:
|
var cost := int(db.run.action_economy.rap_refresh_cost) if cost_override < 0 else cost_override
|
||||||
|
if _rap < cost:
|
||||||
return false # EC13 资源不足拒绝——合法相位内资源拒绝,非编排错误
|
return false # EC13 资源不足拒绝——合法相位内资源拒绝,非编排错误
|
||||||
_rap -= db.run.action_economy.rap_refresh_cost
|
_rap -= cost
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,9 @@ signal settlement_snapshot(snap: Dictionary) ## 每结算末尾的全量状态
|
|||||||
const SOURCE_EXEMPT_TYPES := ["decay", "verdict"]
|
const SOURCE_EXEMPT_TYPES := ["decay", "verdict"]
|
||||||
# 行来源断言豁免集合(来源类别层)——事实来源是单位 / 清图而非棋盘格;
|
# 行来源断言豁免集合(来源类别层)——事实来源是单位 / 清图而非棋盘格;
|
||||||
# fx_reveal=专长揭示掷格(落空行无真实格);
|
# fx_reveal=专长揭示掷格(落空行无真实格);
|
||||||
# fx_grant_gp=专长·GP 入账(钱包面而非棋盘面,行无格子可指)
|
# fx_grant_gp=专长·GP 入账(钱包面而非棋盘面,行无格子可指);
|
||||||
const SOURCE_EXEMPT_CLASSES := ["hero_attack", "enemy_action", "clear_reward", "fx_reveal", "fx_grant_gp"]
|
# fx_board_refresh=成功刷新事实族(迭代 46 卡A——崩壁冲击群伤行源=换图动作而非棋盘格)
|
||||||
|
const SOURCE_EXEMPT_CLASSES := ["hero_attack", "enemy_action", "clear_reward", "fx_reveal", "fx_grant_gp", "fx_board_refresh"]
|
||||||
|
|
||||||
var _facts: BattleFacts # 战斗事实件(battle_facts.gd——事实族 15 成员单点)
|
var _facts: BattleFacts # 战斗事实件(battle_facts.gd——事实族 15 成员单点)
|
||||||
var _rounds: RoundCycle # 轮循环驱动件(settlement/round_cycle.gd——五段持有/段序/重入闸/摇号)
|
var _rounds: RoundCycle # 轮循环驱动件(settlement/round_cycle.gd——五段持有/段序/重入闸/摇号)
|
||||||
@@ -596,26 +597,17 @@ func _exit_distribution() -> void:
|
|||||||
round_started.emit(round_index)
|
round_started.emit(round_index)
|
||||||
|
|
||||||
|
|
||||||
# 呈现层查询面(HUD 刷新按钮可用性)——挖掘期唯一通道
|
# 呈现层查询面(HUD 刷新按钮可用性)——挖掘期唯一通道(折价成本同源:判据归 BattleRefreshFlow)
|
||||||
func can_refresh() -> bool:
|
func can_refresh() -> bool:
|
||||||
return battle_result == "" \
|
return BattleRefreshFlow.can_refresh(self)
|
||||||
and _facts.refresh_used < int(db.run.action_economy.refresh_cap_per_round) \
|
|
||||||
and action_economy.phase == action_economy.PHASE_ACTIVE \
|
|
||||||
and action_economy.rap >= int(db.run.action_economy.rap_refresh_cost)
|
|
||||||
|
|
||||||
|
|
||||||
# 玩家经 HUD框架发起、战斗结算 代理调用(禁止 RAP 满自动刷)。
|
# 玩家经 HUD框架发起、战斗结算 代理调用(禁止 RAP 满自动刷)。
|
||||||
# 挖掘期唯一通道:Active 相位立即换图
|
# 挖掘期唯一通道:Active 相位立即换图继续挖(RAP 按折后价扣;AP 保留、相位不动、不触发结算);
|
||||||
# 继续挖(RAP -8;AP 保留、相位不动、不触发结算);非挖掘期请求一律拒绝——非清图轮恒同图。
|
# 非挖掘期请求一律拒绝——非清图轮恒同图。刷新门/折价扣费/换图/成功事实归 BattleRefreshFlow
|
||||||
|
# (迭代 46 卡A T3 拆出——编排器入口与唯一通道不变,C10 结构预算只降不升)。
|
||||||
func player_request_refresh() -> bool:
|
func player_request_refresh() -> bool:
|
||||||
if not can_refresh():
|
return BattleRefreshFlow.execute(self)
|
||||||
return false
|
|
||||||
if not action_economy.request_refresh():
|
|
||||||
return false
|
|
||||||
_facts.refresh_used += 1
|
|
||||||
_reward_economy.retire_mapping() # 旧图批次退役(地图战斗资源 Ready→retired;E6 新批重置前提)
|
|
||||||
minesweeper.retire_board(true) # 旧板退役(轮内通道)
|
|
||||||
return _install_and_reset(true) # 当场换图——失败即编排违例 fail-fast
|
|
||||||
|
|
||||||
|
|
||||||
func _battle_over(result: String) -> void:
|
func _battle_over(result: String) -> void:
|
||||||
|
|||||||
@@ -460,8 +460,8 @@ func _hero_attack_deliver(t: Dictionary, ctx: Dictionary, redemption: Dictionary
|
|||||||
# 敌方伤害技:掷点 → 折扣/暴击 → 克制点数(attack_type vs 主角护甲类型——
|
# 敌方伤害技:掷点 → 折扣/暴击 → 克制点数(attack_type vs 主角护甲类型——
|
||||||
# 无类型恒中性 delta 0,写法同 hero_attack 的 _counter_delta 消费位)→ hero_defense×enemy_output
|
# 无类型恒中性 delta 0,写法同 hero_attack 的 _counter_delta 消费位)→ hero_defense×enemy_output
|
||||||
# 池乘区 → 下限 → +rider(后置必中附加)→ 交付 → commit_action(作废单不 commit——死亡即不动手)。
|
# 池乘区 → 下限 → +rider(后置必中附加)→ 交付 → commit_action(作废单不 commit——死亡即不动手)。
|
||||||
# 掷点算术归 _enemy_roll;damage_taken 施加面归 _enemy_shield_apply(发射在本函数——
|
# 掷点算术归 _enemy_roll;damage_taken 统一发射+消费归 _emit_hero_damage_taken(发射在本函数——
|
||||||
# 与减伤落快照的先后序固定)。
|
# 与减伤落快照的先后序固定;「掷点后、交付前」消费时点不变)。
|
||||||
func _resolve_enemy_skill(t: Dictionary) -> void:
|
func _resolve_enemy_skill(t: Dictionary) -> void:
|
||||||
var hero: Dictionary[Variant, Variant] = units.hero_unit()
|
var hero: Dictionary[Variant, Variant] = units.hero_unit()
|
||||||
var roll := _enemy_roll(t)
|
var roll := _enemy_roll(t)
|
||||||
@@ -469,9 +469,8 @@ func _resolve_enemy_skill(t: Dictionary) -> void:
|
|||||||
# damage_taken 发射(kind=enemy_skill;铁壁整备减伤订阅面:shield 加法族合计 value×per(charge_layers),
|
# damage_taken 发射(kind=enemy_skill;铁壁整备减伤订阅面:shield 加法族合计 value×per(charge_layers),
|
||||||
# 稳健拓荒树规则同面(rebuild 合成订阅);减后过下限保底。damage_reduce〔背水 hp_ratio when〕/
|
# 稳健拓荒树规则同面(rebuild 合成订阅);减后过下限保底。damage_reduce〔背水 hp_ratio when〕/
|
||||||
# counter_add〔保险丝/石肤〕/ac_bonus〔石肤〕同面施加;hp_ratio fact 由 _fx_fact 补充事实面回落提供)
|
# counter_add〔保险丝/石肤〕/ac_bonus〔石肤〕同面施加;hp_ratio fact 由 _fx_fact 补充事实面回落提供)
|
||||||
var dt_hits := _fx_hits("damage_taken", {"kind": "enemy_skill", "amount": total,
|
var dt_res := _emit_hero_damage_taken(total, "enemy_skill", String(t.context.get("skill_id", "")), t.origin_cell, int(t.round_index))
|
||||||
"source": String(t.context.get("skill_id", "")), "round": int(t.round_index)})
|
total = int(dt_res["total"])
|
||||||
total = int(_enemy_shield_apply(dt_hits, total)["total"])
|
|
||||||
units.apply_damage(hero, total)
|
units.apply_damage(hero, total)
|
||||||
var payload: Dictionary[Variant, Variant] = {"target": "hero", "skill_id": String(t.context.get("skill_id", "")), "baseline": int(t.context.get("baseline", 0)),
|
var payload: Dictionary[Variant, Variant] = {"target": "hero", "skill_id": String(t.context.get("skill_id", "")), "baseline": int(t.context.get("baseline", 0)),
|
||||||
"roll": int(roll["roll"]), "ac": int(roll["threshold"]), "tier": int(roll["discount"]), "attack_type": String(t.attack_type)}
|
"roll": int(roll["roll"]), "ac": int(roll["threshold"]), "tier": int(roll["discount"]), "attack_type": String(t.attack_type)}
|
||||||
@@ -502,9 +501,22 @@ func _enemy_roll(t: Dictionary) -> Dictionary:
|
|||||||
return {"roll": int(roll_read.roll), "threshold": threshold, "crit": crit, "discount": discount, "total": total}
|
return {"roll": int(roll_read.roll), "threshold": threshold, "crit": crit, "discount": discount, "total": total}
|
||||||
|
|
||||||
|
|
||||||
# damage_taken 施加面(shield 加法族合计;damage_reduce 逐条减量〔背水〕;ac_bonus 转编排器单点
|
# 全局受伤发射单点(迭代 46 卡A T2——所有主角承伤路径唯一发射口:敌方攻击/地形/危险格/错误封印
|
||||||
# 〔石肤 AC 累积归 set_hero_ac_override 消费面〕;counter_add 施加)。返回 {total, shield}。
|
# 资源直伤(后三者经 board_flat_hero 单据进入)都经此 dispatch 一次 damage_taken 并消费一次;
|
||||||
func _enemy_shield_apply(hits: Array, total: int) -> Dictionary:
|
# 同一伤害单据重复发射由 context.hero_damage_consumed 标记闸拦(地形路径在开单点标记)。
|
||||||
|
# payload 必含 kind/amount/source/cell/round/hp_ratio;返回 {total, hits} 交调用侧按减后值开单/交付)。
|
||||||
|
func _emit_hero_damage_taken(amount: int, damage_kind: String, source: String, cell: Vector2i, ri: int) -> Dictionary:
|
||||||
|
var hu: Dictionary[Variant, Variant] = units.hero_unit()
|
||||||
|
var payload: Dictionary[Variant, Variant] = {"kind": damage_kind, "amount": amount, "source": source,
|
||||||
|
"cell": cell, "round": ri, "hp_ratio": int(round(100.0 * float(int(hu.get("hp", 0))) / float(maxi(1, int(hu.get("max_hp", 1))))))}
|
||||||
|
var hits := _fx_hits("damage_taken", payload)
|
||||||
|
return {"total": int(_apply_hero_damage_effects(hits, amount)["total"]), "hits": hits}
|
||||||
|
|
||||||
|
|
||||||
|
# damage_taken 唯一消费族(组合次序冻结:shield 加法族合计→damage_reduce 逐条减量〔背水/石层缓冲〕→
|
||||||
|
# ac_bonus 转编排器单点〔石肤 AC 累积归 set_hero_ac_override 消费面〕→counter_add 施加〔保险丝/石肤/
|
||||||
|
# 换图系临时计数夹具〕。返回 {total, shield})。
|
||||||
|
func _apply_hero_damage_effects(hits: Array, total: int) -> Dictionary:
|
||||||
var shield := 0
|
var shield := 0
|
||||||
for h in hits:
|
for h in hits:
|
||||||
match String(h.get("kind", "")):
|
match String(h.get("kind", "")):
|
||||||
@@ -565,9 +577,15 @@ func _resolve_board_blast(t: Dictionary) -> void:
|
|||||||
_outcomes[int(t.ticket_id)] = "redeemed"
|
_outcomes[int(t.ticket_id)] = "redeemed"
|
||||||
|
|
||||||
|
|
||||||
# 棋盘主角直伤(反噬/治疗包负向/地形变异):无类型必中,下限保底;context 键并入行载荷
|
# 棋盘主角直伤(反噬/治疗包负向/危险格/错误封印资源直伤):无类型必中,下限保底;context 键并入行载荷。
|
||||||
|
# damage_taken 全局发射点(迭代 46 卡A T2——单据未标 hero_damage_consumed 时在下限前发射并消费一次;
|
||||||
|
# 地形路径已在授予段开单点发射并标记,此处跳过防重复)
|
||||||
func _resolve_board_flat_hero(t: Dictionary) -> void:
|
func _resolve_board_flat_hero(t: Dictionary) -> void:
|
||||||
var dealt: int = maxi(int(db.combat.settlement.damage_roll.damage_floor), int(t.base_value)) # 伤害下限全来源
|
var dealt: int = int(t.base_value)
|
||||||
|
if not bool((t.get("context", {}) as Dictionary).get("hero_damage_consumed", false)):
|
||||||
|
var res_flat := _emit_hero_damage_taken(dealt, "board_flat_hero", String(t.get("source_class", "")), t.origin_cell, int(t.round_index))
|
||||||
|
dealt = int(res_flat["total"])
|
||||||
|
dealt = maxi(int(db.combat.settlement.damage_roll.damage_floor), dealt) # 伤害下限全来源(减后过下限)
|
||||||
units.apply_damage(units.hero_unit(), dealt)
|
units.apply_damage(units.hero_unit(), dealt)
|
||||||
var extra: Dictionary[Variant, Variant] = {"target": "hero"}
|
var extra: Dictionary[Variant, Variant] = {"target": "hero"}
|
||||||
for k in t.context.keys():
|
for k in t.context.keys():
|
||||||
|
|||||||
@@ -12,11 +12,15 @@ extends RefCounted
|
|||||||
const EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "attack_commit", "clamp_check",
|
const EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "attack_commit", "clamp_check",
|
||||||
"armor_roll", "kill", "damage_taken", "enemy_hp_threshold", "round_start", "seal_settle",
|
"armor_roll", "kill", "damage_taken", "enemy_hp_threshold", "round_start", "seal_settle",
|
||||||
"round_end", "reward_roll", "treasure_used", "charge_full", "dig_stop", "battle_won", "seal_missed",
|
"round_end", "reward_roll", "treasure_used", "charge_full", "dig_stop", "battle_won", "seal_missed",
|
||||||
"board_installed"]
|
"board_installed",
|
||||||
|
# 换图系双事件(refresh_request=玩家请求刷新〔省力周转 apply_discount 订阅面——折扣只改门槛
|
||||||
|
# 与扣费不改拒绝判据〕;board_refreshed=成功刷新事实〔换图系七件订阅主挂点——装新板后才发射〕)
|
||||||
|
"refresh_request", "board_refreshed"]
|
||||||
|
|
||||||
## 扫雷侧事件集(🔴 流派扫雷触发铁律新形态:每流派 ≥1 行订阅本集事件——schema 加载校验。
|
## 扫雷侧事件集(🔴 流派扫雷触发铁律新形态:每流派 ≥1 行订阅本集事件——schema 加载校验。
|
||||||
## 原 MINE_HOOKS 的同义迁移:挖掘/封印/轮末结算面事件)
|
## 原 MINE_HOOKS 的同义迁移:挖掘/封印/轮末结算面事件+轮内刷新面〔换图系〕)
|
||||||
const MINE_EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "seal_settle", "round_end", "reward_roll"]
|
const MINE_EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "seal_settle", "round_end", "reward_roll",
|
||||||
|
"refresh_request", "board_refreshed"]
|
||||||
|
|
||||||
## 效果类型词汇(解释器 switch 闭集——词表实数以 EFFECT_KINDS 常量为准,含共享行拆型:
|
## 效果类型词汇(解释器 switch 闭集——词表实数以 EFFECT_KINDS 常量为准,含共享行拆型:
|
||||||
## grant_gp/bias_wallet、counter_add/counter_spend、reveal_tiles/remove_mines/refresh_pick、
|
## grant_gp/bias_wallet、counter_add/counter_spend、reveal_tiles/remove_mines/refresh_pick、
|
||||||
@@ -40,7 +44,11 @@ const EFFECT_KINDS := ["add_ticket_rider", "modify_ticket", "damage_mult", "open
|
|||||||
"mine_count_preview",
|
"mine_count_preview",
|
||||||
# forgive_seal_miss=完美保险(本场首次封印落空不计入完美
|
# forgive_seal_miss=完美保险(本场首次封印落空不计入完美
|
||||||
# 封印判定——订阅 seal_missed 的豁免效果,消费点在授予段;feats.tres 首例 domain=battle 行)
|
# 封印判定——订阅 seal_missed 的豁免效果,消费点在授予段;feats.tres 首例 domain=battle 行)
|
||||||
"forgive_seal_miss"]
|
"forgive_seal_miss",
|
||||||
|
# 换图系两效果(grant_reserve_ap=矿脉储能·付费挖格产 RAP〔消费点在行动经济 dig_paid
|
||||||
|
# 受理后一次性入池〕;apply_discount=省力周转·刷新成本折扣〔mode=refresh cost_mult 百分比,
|
||||||
|
# floor 取整——只改门槛与扣费、不改拒绝判据〕)
|
||||||
|
"grant_reserve_ap", "apply_discount"]
|
||||||
|
|
||||||
## fact 条件词汇(封闭枚举——payload 同名键直读面〔fact 取值序 payload
|
## fact 条件词汇(封闭枚举——payload 同名键直读面〔fact 取值序 payload
|
||||||
## 优先——effect_engine._fact_value 既有机制〕。new_resource_type=首开资源类型〔地质学家〕;
|
## 优先——effect_engine._fact_value 既有机制〕。new_resource_type=首开资源类型〔地质学家〕;
|
||||||
|
|||||||
@@ -365,6 +365,12 @@ func outcome_of(ticket_id: int) -> String:
|
|||||||
return "redeemed" if ticket_id < 0 else damage.outcome(ticket_id)
|
return "redeemed" if ticket_id < 0 else damage.outcome(ticket_id)
|
||||||
|
|
||||||
|
|
||||||
|
# 延迟单据入队(迭代 46 卡A T4——成功刷新的崩壁冲击群伤单走案C 同队列:授予段闸外唯一入口,
|
||||||
|
# 战斗段末 _board_deferred_settle 按开单序补提交统一结账;类变量零增员——复用 defer.queue)
|
||||||
|
func defer_ticket(t: Dictionary) -> void:
|
||||||
|
(defer.queue as Array).append(t)
|
||||||
|
|
||||||
|
|
||||||
# 主角直伤单据(反噬/治疗包负向——无类型必中,下限保底在控制器管线内)
|
# 主角直伤单据(反噬/治疗包负向——无类型必中,下限保底在控制器管线内)
|
||||||
func _submit_flat_hero(display: String, power: int, att: Dictionary, sync: bool) -> String:
|
func _submit_flat_hero(display: String, power: int, att: Dictionary, sync: bool) -> String:
|
||||||
var t := damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, String(att.get("source_class", "")), display,
|
var t := damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, String(att.get("source_class", "")), display,
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
class_name BattleRefreshFlow
|
||||||
|
extends RefCounted
|
||||||
|
## 换图刷新流程件(迭代 46 卡A T3/T4 自编排器拆出):刷新可用性门、折价扣费、换图执行与成功事实登记。
|
||||||
|
## 拆分边界(需求卡片 C13):本件只负责可用性、扣费、换图与成功事实登记——七件效果的算法留在
|
||||||
|
## 效果引擎、授予段与伤害控制器。调用路径=呈现层→编排器单行转发→本件→行动经济/装板壳/授予段。
|
||||||
|
## 反自动刷新(C11):只经玩家入口发起,禁 RAP 满自动刷——编排器公开面单行转发,无自动调用点。
|
||||||
|
## 成功序钉死(T4):折价扣 RAP→refresh_used+1→旧资源退役→旧板退役→装新板→授予段成功事实发射;
|
||||||
|
## 装板失败返回 false 且零效果——成功事实只在新板就位后发射。
|
||||||
|
|
||||||
|
|
||||||
|
## 折价成本读数(refresh_request 命中省力周转 apply_discount——只认 kind==apply_discount 且
|
||||||
|
## mode==refresh,取声明序第一命中,floor(base×cost_mult/100);无命中=表基线逐位不变。
|
||||||
|
## can_refresh 与 execute 同源取数〔V3——can 与执行同源判断〕;折扣只改变门槛与扣费,
|
||||||
|
## 不改变拒绝判据〔C2——每轮上限与相位判据照旧〕)
|
||||||
|
static func _effective_refresh_cost(oc: BattleOrchestrator) -> int:
|
||||||
|
var base := int(oc.db.run.action_economy.rap_refresh_cost)
|
||||||
|
for h in oc.fx_host().fx_emit("refresh_request", {"mode": "refresh", "base": base}):
|
||||||
|
if String(h.get("kind", "")) == "apply_discount" and String(h.get("mode", "")) == "refresh":
|
||||||
|
return int(floor(float(base) * float(int(h.get("cost_mult", 100))) / 100.0))
|
||||||
|
return base
|
||||||
|
|
||||||
|
|
||||||
|
## 可用性门(HUD 刷新按钮同源判据):战斗未终局、每轮上限未满、Active 相位、RAP 抵折后价。
|
||||||
|
static func can_refresh(oc: BattleOrchestrator) -> bool:
|
||||||
|
return oc.battle_result == "" \
|
||||||
|
and oc._facts.refresh_used < int(oc.db.run.action_economy.refresh_cap_per_round) \
|
||||||
|
and oc.action_economy.phase == oc.action_economy.PHASE_ACTIVE \
|
||||||
|
and oc.action_economy.rap >= _effective_refresh_cost(oc)
|
||||||
|
|
||||||
|
|
||||||
|
## 执行(玩家请求刷新唯一通道体):扣费受理后继续;扣费拒绝/门拒零效果零事实。
|
||||||
|
static func execute(oc: BattleOrchestrator) -> bool:
|
||||||
|
if not can_refresh(oc):
|
||||||
|
return false
|
||||||
|
if not oc.action_economy.request_refresh(_effective_refresh_cost(oc)):
|
||||||
|
return false
|
||||||
|
oc._facts.refresh_used += 1
|
||||||
|
oc._reward_economy.retire_mapping() # 旧图批次退役(地图战斗资源 Ready→retired;E6 新批重置前提)
|
||||||
|
oc.minesweeper.retire_board(true) # 旧板退役(轮内通道)
|
||||||
|
if not oc._install_and_reset(true): # 当场换图——失败即编排违例 fail-fast(零成功事实)
|
||||||
|
return false
|
||||||
|
oc._rounds._grant_seg.apply_board_refreshed(oc.round_index) # 成功事实:新板就位后一次发射
|
||||||
|
return true
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://d2kjxlt5b2eqv
|
||||||
@@ -6,7 +6,8 @@ extends RefCounted
|
|||||||
# 效果施加归 BattleFxHost(段持三引用直调,零转发壳)——
|
# 效果施加归 BattleFxHost(段持三引用直调,零转发壳)——
|
||||||
# 本段只搬运时序,零算法改动(🔴 零行为变更契约:同种子完整局输出逐位一致)。
|
# 本段只搬运时序,零算法改动(🔴 零行为变更契约:同种子完整局输出逐位一致)。
|
||||||
# run 编排骨架+批私有族(_reveal_batch/_terrain_batch/_seal_batch/_round_end_apply,段内施加面
|
# run 编排骨架+批私有族(_reveal_batch/_terrain_batch/_seal_batch/_round_end_apply,段内施加面
|
||||||
# 再拆 _reveal_safe_apply/_reveal_mine_apply/_terrain_damage_reduce/_seal_hit_effects 等单面函数);
|
# 再拆 _reveal_safe_apply/_reveal_mine_apply/_seal_hit_effects 等单面函数);地形 damage_taken
|
||||||
|
# 消费随迭代 46 卡A T2 并入伤害控制器全局消费族(本段零本地减伤函数);
|
||||||
# kind 分派单点化(P1-1 同批):counter_add 五位三行组 / grant_material 三位兑付块
|
# kind 分派单点化(P1-1 同批):counter_add 五位三行组 / grant_material 三位兑付块
|
||||||
# 收 _apply_counter_hit / _apply_material_hit 两助手(分支本体逐字保留在助手内)。
|
# 收 _apply_counter_hit / _apply_material_hit 两助手(分支本体逐字保留在助手内)。
|
||||||
|
|
||||||
@@ -32,6 +33,27 @@ func run(ri: int) -> void:
|
|||||||
_round_end_apply(ri)
|
_round_end_apply(ri)
|
||||||
|
|
||||||
|
|
||||||
|
# 成功刷新事实发射+施加(迭代 46 卡A T4——BattleRefreshFlow 装新板后单点调用,每成功刷新恰一次;
|
||||||
|
# 拒绝刷新零事实〔C2〕。命中消费三面:counter_add→既有单点落账〔蓄力周转/石层缓冲/坑道小憩 计数〕;
|
||||||
|
# grant_gp→战斗内缓冲 escrow 入账+fx_grant_gp 行〔矿道红利——不绑清图奖励,C7〕;
|
||||||
|
# open_ticket channel=blast→崩壁冲击群伤单延迟入队〔案C 同队列,战斗段末按存活目标结账〕)
|
||||||
|
func apply_board_refreshed(ri: int) -> void:
|
||||||
|
var br_hits := fx_host.fx_emit("board_refreshed", {"round": ri})
|
||||||
|
for h in br_hits:
|
||||||
|
if String(h.get("kind", "")) == "counter_add":
|
||||||
|
_apply_counter_hit(h)
|
||||||
|
for h in br_hits:
|
||||||
|
if String(h.get("kind", "")) == "grant_gp":
|
||||||
|
reward_grant.escrow += int(h.get("value", 0))
|
||||||
|
oc._emit_row({"type": "effect", "source_class": "fx_grant_gp", "origin_cell": Vector2i(-1, -1)}, ri, "专长·GP 入账 +%d" % int(h.get("value", 0)), int(h.get("value", 0)), {"effect_id": "fx_grant_gp", "escrow": reward_grant.escrow})
|
||||||
|
for h in br_hits:
|
||||||
|
if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "blast":
|
||||||
|
var bb_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "fx_board_refresh", "崩壁冲击",
|
||||||
|
{"channel": "board", "base_value": int(h.get("value", 0)), "target_rule": DamageController.TARGET_ALL_ALIVE,
|
||||||
|
"ri": ri, "origin_cell": Vector2i(-1, -1)})
|
||||||
|
reward_grant.defer_ticket(bb_t)
|
||||||
|
|
||||||
|
|
||||||
# 开格/踩雷兑付批(轮 facts_reveals 资源事件序:safe 正向乘区兑付 / mine_hit 减半负向兑付)
|
# 开格/踩雷兑付批(轮 facts_reveals 资源事件序:safe 正向乘区兑付 / mine_hit 减半负向兑付)
|
||||||
func _reveal_batch(ri: int) -> void:
|
func _reveal_batch(ri: int) -> void:
|
||||||
for evt in oc._facts.reveals:
|
for evt in oc._facts.reveals:
|
||||||
@@ -167,37 +189,22 @@ func _terrain_batch(ri: int) -> void:
|
|||||||
if String(vrow.get("effect", "")) != "flat_damage":
|
if String(vrow.get("effect", "")) != "flat_damage":
|
||||||
continue # 触发词表按 effect 路由:flat_damage=on_open 兑现;hide_number/on_open 外组合 = 数据行新词表未接线(显式忽略面)
|
continue # 触发词表按 effect 路由:flat_damage=on_open 兑现;hide_number/on_open 外组合 = 数据行新词表未接线(显式忽略面)
|
||||||
# 地形变异伤害单据——无类型必中,下限保底在控制器管线内
|
# 地形变异伤害单据——无类型必中,下限保底在控制器管线内
|
||||||
# damage_taken 发射在单据开立前:damage_reduce〔背水——hp_ratio when
|
# damage_taken 统一发射(迭代 46 卡A T2——地形路径并入全局消费族 _emit_hero_damage_taken:
|
||||||
# 面〕先收集再按减后值开单;counter_add〔保险丝/石肤〕+ ac_bonus〔石肤〕照常施加;
|
# 发射在单据开立前、减后值开单,组合消费含 shield〔全局受伤语义〕;
|
||||||
# grant_material〔荆棘/地脉〕移到单据提交后(兑付值序一致——收益不因减伤变化)
|
# hero_damage_consumed 标记入单据 context——战斗段末 _resolve_board_flat_hero 跳过重复发射;
|
||||||
|
# grant_material〔荆棘/地脉〕移到单据提交后(兑付值序一致——收益不因减伤变化))
|
||||||
var dt_amount := int(vrow.get("effect_value", 0))
|
var dt_amount := int(vrow.get("effect_value", 0))
|
||||||
var dt_hits := fx_host.fx_emit("damage_taken", {"kind": "terrain", "amount": dt_amount,
|
var dt_res := oc._damage._emit_hero_damage_taken(dt_amount, "terrain", String(vid), evt.cell, ri)
|
||||||
"source": String(vid), "cell": evt.cell, "round": ri, "terrain_kind": String(vid),
|
dt_amount = int(dt_res["total"])
|
||||||
"hp_ratio": int(round(100.0 * float(int(oc._enemy_system.hero_unit().get("hp", 0))) / float(maxi(1, int(oc._enemy_system.hero_unit().get("max_hp", 1))))))})
|
|
||||||
dt_amount = _terrain_damage_reduce(dt_hits, dt_amount)
|
|
||||||
var vt := oc._damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, "地形变异", String(vrow.get("display_name", vid)),
|
var vt := oc._damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, "地形变异", String(vrow.get("display_name", vid)),
|
||||||
{"channel": "board", "base_value": dt_amount, "target_rule": DamageController.TARGET_HERO,
|
{"channel": "board", "base_value": dt_amount, "target_rule": DamageController.TARGET_HERO,
|
||||||
"ri": ri, "origin_cell": evt.cell, "context": {"variant_id": String(vid)}})
|
"ri": ri, "origin_cell": evt.cell, "context": {"variant_id": String(vid), "hero_damage_consumed": true}})
|
||||||
reward_grant.submit(vt, false)
|
reward_grant.submit(vt, false)
|
||||||
for h in dt_hits:
|
for h in (dt_res["hits"] as Array):
|
||||||
if String(h.get("kind", "")) == "grant_material":
|
if String(h.get("kind", "")) == "grant_material":
|
||||||
_apply_material_hit(h, evt.cell, ri)
|
_apply_material_hit(h, evt.cell, ri)
|
||||||
|
|
||||||
|
|
||||||
# damage_taken 施加面(damage_reduce 减量收集〔背水——定值〕/ac_bonus〔石肤〕/counter_add〔保险丝/石肤〕;
|
|
||||||
# 返回减后 amount 交开单消费)
|
|
||||||
func _terrain_damage_reduce(dt_hits: Array, dt_amount: int) -> int:
|
|
||||||
for h in dt_hits:
|
|
||||||
match String(h.get("kind", "")):
|
|
||||||
"damage_reduce":
|
|
||||||
dt_amount = maxi(0, dt_amount - int(h.get("value", 0)))
|
|
||||||
"ac_bonus":
|
|
||||||
fx_host.apply_ac_bonus(h)
|
|
||||||
"counter_add":
|
|
||||||
_apply_counter_hit(h)
|
|
||||||
return dt_amount
|
|
||||||
|
|
||||||
|
|
||||||
# 封印判定批+战封印军规开单段(封印流:seal_judged 批后统一开单:轮末每面封印命中对最前存活敌
|
# 封印判定批+战封印军规开单段(封印流:seal_judged 批后统一开单:轮末每面封印命中对最前存活敌
|
||||||
# 结算 board_typed 必中伤害〔复用零新配方;「封印所在巢穴敌」简化为 default_target 最前存活敌——
|
# 结算 board_typed 必中伤害〔复用零新配方;「封印所在巢穴敌」简化为 default_target 最前存活敌——
|
||||||
# 台账披露待追认〕;完美布防=本轮封印批全中(hit>0 且 miss==0)时封印伤 ×mult——全对推理奖励)。
|
# 台账披露待追认〕;完美布防=本轮封印批全中(hit>0 且 miss==0)时封印伤 ×mult——全对推理奖励)。
|
||||||
@@ -259,6 +266,7 @@ func _seal_strike_value(fs_fx: Array, fs_hits: int, fs_misses: int) -> Dictionar
|
|||||||
func _round_end_apply(ri: int) -> void:
|
func _round_end_apply(ri: int) -> void:
|
||||||
var re_fx := fx_host.fx_emit("round_end", {"round": ri})
|
var re_fx := fx_host.fx_emit("round_end", {"round": ri})
|
||||||
_round_end_blast_tickets(re_fx, ri)
|
_round_end_blast_tickets(re_fx, ri)
|
||||||
|
_round_end_heal_value_counter(re_fx, ri) # 迭代 46 卡A T5——坑道小憩恢复兑付(先于清零行消费层值,声明序=heal 行在 counter set 行前)
|
||||||
for h in re_fx:
|
for h in re_fx:
|
||||||
if String(h.get("kind", "")) == "counter_add":
|
if String(h.get("kind", "")) == "counter_add":
|
||||||
_apply_counter_hit(h)
|
_apply_counter_hit(h)
|
||||||
@@ -278,6 +286,24 @@ func _round_end_apply(ri: int) -> void:
|
|||||||
oc._damage.flush() # 授予段界结账(案C 后本段伤害单据全数暂存,此处对空队 no-op——保留作防御性段界)
|
oc._damage.flush() # 授予段界结账(案C 后本段伤害单据全数暂存,此处对空队 no-op——保留作防御性段界)
|
||||||
|
|
||||||
|
|
||||||
|
# round_end heal value_counter 兑付(迭代 46 卡A T5——坑道小憩:层值=本轮成功刷新累计恢复点,
|
||||||
|
# units.heal 封顶、满血发未生效行;层清零由同事件 counter_add set 行承载〔声明序在后〕——本面
|
||||||
|
# 先于 counter_add 遍历消费层值即保序。行源=成功刷新事实族(无棋盘格——fx_board_refresh 豁免类))
|
||||||
|
func _round_end_heal_value_counter(re_fx: Array, ri: int) -> void:
|
||||||
|
for h in re_fx:
|
||||||
|
if String(h.get("kind", "")) != "heal" or String(h.get("value_counter", "")) == "":
|
||||||
|
continue
|
||||||
|
var layers := EffectEngine.counter(fx_host.counters_ctx, String(h.get("value_counter", "")))
|
||||||
|
if layers <= 0:
|
||||||
|
continue
|
||||||
|
var hu: Dictionary[Variant, Variant] = oc._enemy_system.hero_unit()
|
||||||
|
if int(hu.get("hp", 0)) >= int(hu.get("max_hp", 1)):
|
||||||
|
oc._emit_row({"type": "reward_ineffective", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 未生效(满血)", 0, {"batch_key": String(h.get("value_counter", ""))})
|
||||||
|
continue
|
||||||
|
var healed: int = oc._enemy_system.heal(hu, layers)
|
||||||
|
oc._emit_row({"type": "heal", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 回复", healed, {"target": "hero"})
|
||||||
|
|
||||||
|
|
||||||
# round_end blast 通道反打单(per-counter 积层读数×value=反打总量;source_class 用中性名,
|
# round_end blast 通道反打单(per-counter 积层读数×value=反打总量;source_class 用中性名,
|
||||||
# per-counter 反打通道保留泛用面;案C 暂存战斗段末兑付)
|
# per-counter 反打通道保留泛用面;案C 暂存战斗段末兑付)
|
||||||
func _round_end_blast_tickets(re_fx: Array, ri: int) -> void:
|
func _round_end_blast_tickets(re_fx: Array, ri: int) -> void:
|
||||||
|
|||||||
@@ -869,5 +869,215 @@ rows = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"feat_id": "vein_reserve",
|
||||||
|
"display_name": "矿脉储能",
|
||||||
|
"rarity": "excellent",
|
||||||
|
"cap": 1,
|
||||||
|
"desc": "挖格储能:每次受理的付费挖格 +1 储备点(RAP)",
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"on": "dig_paid",
|
||||||
|
"do": {
|
||||||
|
"kind": "grant_reserve_ap",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"feat_id": "easy_swap",
|
||||||
|
"display_name": "省力周转",
|
||||||
|
"rarity": "excellent",
|
||||||
|
"cap": 1,
|
||||||
|
"desc": "刷新半价:弃图刷新成本为基线 50%",
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"on": "refresh_request",
|
||||||
|
"do": {
|
||||||
|
"kind": "apply_discount",
|
||||||
|
"mode": "refresh",
|
||||||
|
"cost_mult": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"feat_id": "wall_blast",
|
||||||
|
"display_name": "崩壁冲击",
|
||||||
|
"rarity": "excellent",
|
||||||
|
"cap": 1,
|
||||||
|
"desc": "刷后群伤:成功刷新后全体存活敌各受 2 点",
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"on": "board_refreshed",
|
||||||
|
"do": {
|
||||||
|
"kind": "open_ticket",
|
||||||
|
"channel": "blast",
|
||||||
|
"value": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"feat_id": "mine_profit",
|
||||||
|
"display_name": "矿道红利",
|
||||||
|
"rarity": "rare",
|
||||||
|
"cap": 1,
|
||||||
|
"desc": "刷后GP:每次成功刷新固定 +1 GP",
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"on": "board_refreshed",
|
||||||
|
"do": {
|
||||||
|
"kind": "grant_gp",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"feat_id": "stone_mitigation",
|
||||||
|
"display_name": "石层缓冲",
|
||||||
|
"rarity": "common",
|
||||||
|
"cap": 1,
|
||||||
|
"desc": "当轮减伤:本轮成功刷新后,每次受伤固定 −2(多刷不叠加)",
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"on": "board_refreshed",
|
||||||
|
"do": {
|
||||||
|
"kind": "counter_add",
|
||||||
|
"counter_id": "round_swap_guard",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"on": "damage_taken",
|
||||||
|
"when": [
|
||||||
|
{
|
||||||
|
"fact": "counter",
|
||||||
|
"id": "round_swap_guard",
|
||||||
|
"op": "gte",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"do": {
|
||||||
|
"kind": "damage_reduce",
|
||||||
|
"value": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"feat_id": "charge_rotation",
|
||||||
|
"display_name": "蓄力周转",
|
||||||
|
"rarity": "excellent",
|
||||||
|
"cap": 1,
|
||||||
|
"desc": "刷后蓄力:每次刷新 +1 层(上限 3),下一次攻击消费全部层数",
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"on": "board_refreshed",
|
||||||
|
"when": [
|
||||||
|
{
|
||||||
|
"fact": "counter",
|
||||||
|
"id": "swap_charge_layers",
|
||||||
|
"op": "lt",
|
||||||
|
"value": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"do": {
|
||||||
|
"kind": "counter_add",
|
||||||
|
"counter_id": "swap_charge_layers",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"on": "attack_commit",
|
||||||
|
"when": [
|
||||||
|
{
|
||||||
|
"fact": "counter",
|
||||||
|
"id": "swap_charge_layers",
|
||||||
|
"op": "gte",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"do": {
|
||||||
|
"kind": "attack_bonus",
|
||||||
|
"value_counter": "swap_charge_layers"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"on": "attack_commit",
|
||||||
|
"when": [
|
||||||
|
{
|
||||||
|
"fact": "counter",
|
||||||
|
"id": "swap_charge_layers",
|
||||||
|
"op": "gte",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"do": {
|
||||||
|
"kind": "counter_add",
|
||||||
|
"counter_id": "swap_charge_layers",
|
||||||
|
"set": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"feat_id": "tunnel_rest",
|
||||||
|
"display_name": "坑道小憩",
|
||||||
|
"rarity": "common",
|
||||||
|
"cap": 1,
|
||||||
|
"desc": "当轮恢复:每次刷新累计 1 点(上限 3),本轮轮末合计回复",
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"on": "board_refreshed",
|
||||||
|
"when": [
|
||||||
|
{
|
||||||
|
"fact": "counter",
|
||||||
|
"id": "round_swap_rest",
|
||||||
|
"op": "lt",
|
||||||
|
"value": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"do": {
|
||||||
|
"kind": "counter_add",
|
||||||
|
"counter_id": "round_swap_rest",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"on": "round_end",
|
||||||
|
"when": [
|
||||||
|
{
|
||||||
|
"fact": "counter",
|
||||||
|
"id": "round_swap_rest",
|
||||||
|
"op": "gte",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"do": {
|
||||||
|
"kind": "heal",
|
||||||
|
"value_counter": "round_swap_rest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"on": "round_end",
|
||||||
|
"when": [
|
||||||
|
{
|
||||||
|
"fact": "counter",
|
||||||
|
"id": "round_swap_rest",
|
||||||
|
"op": "gte",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"do": {
|
||||||
|
"kind": "counter_add",
|
||||||
|
"counter_id": "round_swap_rest",
|
||||||
|
"set": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -173,7 +173,7 @@ func _draw_south_facade(canvas: CanvasItem, rect: Rect2) -> void:
|
|||||||
var y_far := rect.size.x - far_h
|
var y_far := rect.size.x - far_h
|
||||||
var y_near := y_far - near_h
|
var y_near := y_far - near_h
|
||||||
var y_hl := y_near - FACADE_HIGHLIGHT_PX
|
var y_hl := y_near - FACADE_HIGHLIGHT_PX
|
||||||
canvas.draw_rect(Rect2(0.0, y_hl, rect.size.x, FACADE_HIGHLIGHT_PX), UiKit.STAND_STONE_RIM, true) # 口沿高光(UiKit.STAND_STONE_RIM 并档——同值单点)
|
canvas.draw_rect(Rect2(0.0, y_hl, rect.size.x, FACADE_HIGHLIGHT_PX), UiKit.BRICK, true) # 口沿高光(合并归掩尘枚 BRICK——art-bible §4.1 换色合并①同值单点)
|
||||||
canvas.draw_rect(Rect2(0.0, y_near, rect.size.x, near_h), FACADE_NEAR_COL, true)
|
canvas.draw_rect(Rect2(0.0, y_near, rect.size.x, near_h), FACADE_NEAR_COL, true)
|
||||||
canvas.draw_rect(Rect2(0.0, y_far, rect.size.x, far_h), FACADE_FAR_COL, true)
|
canvas.draw_rect(Rect2(0.0, y_far, rect.size.x, far_h), FACADE_FAR_COL, true)
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ func intent_probe() -> Dictionary:
|
|||||||
|
|
||||||
|
|
||||||
# 名字行探针出口(ui_probe 呈现口径 + 本批 V6/V8 断言面:armor_icon = 绑定纹理路径〔缺位回退字形〕、
|
# 名字行探针出口(ui_probe 呈现口径 + 本批 V6/V8 断言面:armor_icon = 绑定纹理路径〔缺位回退字形〕、
|
||||||
# 几何 = 素底牌矩形与文字基线、素底色——V6 判据 = baseline_y < plinth_top_y)
|
# 几何 = 素底牌矩形与文字基线、素底色——V6 判据 = baseline_y < plinth_top_y;
|
||||||
|
# name_font_size = 名字行字号档实读(迭代 46 卡B——提档 FS_HINT 14 的几何单点出口))
|
||||||
func name_line_probe() -> Dictionary:
|
func name_line_probe() -> Dictionary:
|
||||||
var f := _efont()
|
var f := _efont()
|
||||||
var tex := UiKit.load_tex(_armor_tex_path(st._armor))
|
var tex := UiKit.load_tex(_armor_tex_path(st._armor))
|
||||||
@@ -69,7 +70,8 @@ func name_line_probe() -> Dictionary:
|
|||||||
"armor_tex_bound": tex != null,
|
"armor_tex_bound": tex != null,
|
||||||
"plate": plate, "baseline_y": _name_baseline(plate).y,
|
"plate": plate, "baseline_y": _name_baseline(plate).y,
|
||||||
"plinth_top_y": _pedestal_rect().position.y,
|
"plinth_top_y": _pedestal_rect().position.y,
|
||||||
"plate_color": UiKit.HP_BAR_BACK}
|
"plate_color": UiKit.HP_BAR_BACK,
|
||||||
|
"name_font_size": UiKit.FS_HINT}
|
||||||
|
|
||||||
|
|
||||||
# ---- setter 面(presenter/组合根零改排) ----
|
# ---- setter 面(presenter/组合根零改排) ----
|
||||||
@@ -332,15 +334,16 @@ func _name_plate_rect(f: Font, tex: Texture2D) -> Rect2:
|
|||||||
var icon_w := 24.0
|
var icon_w := 24.0
|
||||||
if tex == null:
|
if tex == null:
|
||||||
icon_w = f.get_string_size(String(ARMOR_ICONS.get(st._armor, "◇")),
|
icon_w = f.get_string_size(String(ARMOR_ICONS.get(st._armor, "◇")),
|
||||||
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_CAPTION).x
|
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
|
||||||
var name_w := f.get_string_size(st._display_name, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_CAPTION).x
|
var name_w := f.get_string_size(st._display_name, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
|
||||||
var total_w := icon_w + 3.0 + name_w
|
var total_w := icon_w + 3.0 + name_w
|
||||||
return Rect2((box.x - total_w - 8.0) * 0.5, box.y - 18.0 - 26.0, total_w + 8.0, 26.0)
|
return Rect2((box.x - total_w - 8.0) * 0.5, box.y - 18.0 - 26.0, total_w + 8.0, 26.0)
|
||||||
|
|
||||||
|
|
||||||
# 名字行文字基线(牌内几何单点——图标 24px 垂直居中牌内〔中心=牌顶+13〕,12px 字形基线=中心+4)
|
# 名字行文字基线(牌内几何单点——图标 24px 垂直居中牌内〔中心=牌顶+13〕,14px 字形基线=中心+5
|
||||||
|
# (0.35h 规则按字号档推导 14×0.35≈4.9→5,T0 实测校验——迭代 46 卡B §7.4 提档))
|
||||||
func _name_baseline(plate: Rect2) -> Vector2:
|
func _name_baseline(plate: Rect2) -> Vector2:
|
||||||
return plate.position + Vector2(4.0, 17.0)
|
return plate.position + Vector2(4.0, 18.0)
|
||||||
|
|
||||||
|
|
||||||
# 绘制内容矩形(世界局部;ui_struct_probe ⑥standee「不出框+下余量≥8px」断言读数面——
|
# 绘制内容矩形(世界局部;ui_struct_probe ⑥standee「不出框+下余量≥8px」断言读数面——
|
||||||
@@ -362,8 +365,8 @@ func _draw() -> void:
|
|||||||
## 地面域:椭圆落影(圣经 §6.1 地台落影压脚:方向稿椭圆 (0,0,0,70),压在地台
|
## 地面域:椭圆落影(圣经 §6.1 地台落影压脚:方向稿椭圆 (0,0,0,70),压在地台
|
||||||
## 上缘周围;24 段多边形近似椭圆,中心=台面上方 6px、横半径=台宽一半)
|
## 上缘周围;24 段多边形近似椭圆,中心=台面上方 6px、横半径=台宽一半)
|
||||||
## + 石板地台(§6.1 近景地台独立件:TEX_STAGE_PLAZA 源 48×12 九宫格横向拉伸、顶缘受光线 2px
|
## + 石板地台(§6.1 近景地台独立件:TEX_STAGE_PLAZA 源 48×12 九宫格横向拉伸、顶缘受光线 2px
|
||||||
## 不随拉伸变形、跨三关恒定;素材缺位回退程序化三段绘制〔STAND_STONE 族方向稿取色,几何同源
|
## 不随拉伸变形、跨三关恒定;素材缺位回退程序化三段绘制〔板身 STAND_STONE/描边 PLAZA_EDGE/
|
||||||
## _pedestal_rect——与 panel_skinned 缺位回退同构〕)
|
## 顶缘线 BRICK——art-bible §4.1 换色合并①②改指目标色,几何同源 _pedestal_rect〕)
|
||||||
func _draw_ground(f: Font) -> void:
|
func _draw_ground(f: Font) -> void:
|
||||||
var sh_center := Vector2(box.x * 0.5, box.y - 24.0)
|
var sh_center := Vector2(box.x * 0.5, box.y - 24.0)
|
||||||
var sh_rx := (box.x - 36.0) * 0.5
|
var sh_rx := (box.x - 36.0) * 0.5
|
||||||
@@ -379,8 +382,8 @@ func _draw_ground(f: Font) -> void:
|
|||||||
_draw_plinth_stretched(plaza, ped)
|
_draw_plinth_stretched(plaza, ped)
|
||||||
else:
|
else:
|
||||||
draw_rect(ped, UiKit.STAND_STONE, true)
|
draw_rect(ped, UiKit.STAND_STONE, true)
|
||||||
draw_rect(ped, UiKit.STAND_STONE_EDGE, false, 1.0)
|
draw_rect(ped, UiKit.PLAZA_EDGE, false, 1.0)
|
||||||
draw_rect(Rect2(ped.position.x, ped.position.y, ped.size.x, 2.0), UiKit.STAND_STONE_RIM, true)
|
draw_rect(Rect2(ped.position.x, ped.position.y, ped.size.x, 2.0), UiKit.BRICK, true)
|
||||||
|
|
||||||
|
|
||||||
## 地台九宫格横向拉伸域(循环体提函数——左右 4px 端头切片 + 中段拉伸;纵向恒 12px 源档,
|
## 地台九宫格横向拉伸域(循环体提函数——左右 4px 端头切片 + 中段拉伸;纵向恒 12px 源档,
|
||||||
@@ -464,13 +467,13 @@ func _draw_armor_name_line(f: Font) -> void:
|
|||||||
draw_texture_rect(tex, Rect2(baseline.x, plate.position.y + 1.0, 24.0, 24.0), false)
|
draw_texture_rect(tex, Rect2(baseline.x, plate.position.y + 1.0, 24.0, 24.0), false)
|
||||||
else:
|
else:
|
||||||
var armor_txt := String(ARMOR_ICONS.get(st._armor, "◇"))
|
var armor_txt := String(ARMOR_ICONS.get(st._armor, "◇"))
|
||||||
icon_w = f.get_string_size(armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_CAPTION).x
|
icon_w = f.get_string_size(armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x
|
||||||
var col: Color = UiKit.WARM_FAINT # 轻甲 = 暖白弱化(v3.5 暗底双色墨制)
|
var col: Color = UiKit.WARM_FAINT # 轻甲 = 暖白弱化(v3.5 暗底双色墨制)
|
||||||
match st._armor:
|
match st._armor:
|
||||||
"ward":
|
"ward":
|
||||||
col = UiKit.HERO_BLUE_BRIGHT # 魔法护甲 = 小人蓝亮档
|
col = UiKit.HERO_BLUE_BRIGHT # 魔法护甲 = 小人蓝亮档
|
||||||
"heavy":
|
"heavy":
|
||||||
col = UiKit.GOLD_BRIGHT # 重甲 = 金族亮档
|
col = UiKit.GOLD_BRIGHT # 重甲 = 金族亮档
|
||||||
f.draw_string(get_canvas_item(), baseline, armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_CAPTION, col)
|
f.draw_string(get_canvas_item(), baseline, armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, col)
|
||||||
f.draw_string(get_canvas_item(), baseline + Vector2(icon_w + 3.0, 0.0), st._display_name,
|
f.draw_string(get_canvas_item(), baseline + Vector2(icon_w + 3.0, 0.0), st._display_name,
|
||||||
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_CAPTION, UiKit.WARM) # v3.5 暗底双色墨制
|
HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, UiKit.WARM) # v3.5 暗底双色墨制
|
||||||
@@ -185,8 +185,7 @@ const TIP_BG := Color(0.16, 0.12, 0.09, 0.96) # tooltip 深底
|
|||||||
const STAGE_BG := Color8(62, 52, 42) # B 区舞台暗底(方向稿 render_battle_screen——battle_stage_view 面板底)
|
const STAGE_BG := Color8(62, 52, 42) # B 区舞台暗底(方向稿 render_battle_screen——battle_stage_view 面板底)
|
||||||
const BEAM_LIGHT := Color8(255, 236, 200) # B 区顶光柱暖光(用时乘 alpha 渐隐——battle_stage_view 光柱层)
|
const BEAM_LIGHT := Color8(255, 236, 200) # B 区顶光柱暖光(用时乘 alpha 渐隐——battle_stage_view 光柱层)
|
||||||
const STAND_STONE := Color8(150, 130, 100) # 站桩石板地台·板身(standee_body 方向稿取色)
|
const STAND_STONE := Color8(150, 130, 100) # 站桩石板地台·板身(standee_body 方向稿取色)
|
||||||
const STAND_STONE_EDGE := Color8(90, 76, 58) # 站桩石板地台·描边
|
const PLAZA_EDGE := Color8(76, 64, 56) # 小石台描边(art-bible §4.1 换色合并②合并目标——贴图已落此值,缺位回退同值)
|
||||||
const STAND_STONE_RIM := Color8(196, 172, 132) # 站桩石板地台·顶缘受光线
|
|
||||||
const CAMP_HEADER_BAND := Color8(110, 69, 38) # 营地柜台头带深棕 #6E4526(4-camp.png 方向稿取色;对齐 demo --wood-deep)
|
const CAMP_HEADER_BAND := Color8(110, 69, 38) # 营地柜台头带深棕 #6E4526(4-camp.png 方向稿取色;对齐 demo --wood-deep)
|
||||||
const CAMP_ROW_PLATE := Color8(240, 230, 206) # 营地行板暖米(同上)
|
const CAMP_ROW_PLATE := Color8(240, 230, 206) # 营地行板暖米(同上)
|
||||||
const CAMP_BADGE_PANEL := Color8(40, 32, 24) # 营地钥石徽暗底(同上)
|
const CAMP_BADGE_PANEL := Color8(40, 32, 24) # 营地钥石徽暗底(同上)
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ func _suites() -> Array:
|
|||||||
{"path": "res://tests/unit/presentation/pacing_queue_test.gd", "expect": 0}, # AC⑤/T9——排空零分叉/零丢行/延迟双态
|
{"path": "res://tests/unit/presentation/pacing_queue_test.gd", "expect": 0}, # AC⑤/T9——排空零分叉/零丢行/延迟双态
|
||||||
{"path": "res://tests/unit/presentation/standee_fx_sweep_test.gd", "expect": 0}, # 呈现域缺陷扫掠(d2 蓄力档/d4 庆祝句柄/d5+d8 互斥/d12 站桩按 id 解析)
|
{"path": "res://tests/unit/presentation/standee_fx_sweep_test.gd", "expect": 0}, # 呈现域缺陷扫掠(d2 蓄力档/d4 庆祝句柄/d5+d8 互斥/d12 站桩按 id 解析)
|
||||||
{"path": "res://tests/unit/presentation/tune_sim_batch_parity_test.gd", "expect": 0}, # 测速循环批双实现对拍(帧索引/运行态/进度串/stats/报告五面——信号步进 vs await 基线)
|
{"path": "res://tests/unit/presentation/tune_sim_batch_parity_test.gd", "expect": 0}, # 测速循环批双实现对拍(帧索引/运行态/进度串/stats/报告五面——信号步进 vs await 基线)
|
||||||
{"path": "res://tests/unit/presentation/standee_present_values_test.gd", "expect": 0}, # 战斗一屏样张批 V5–V9(血条危险红/名字行素底不压地台/徽标 FS_HINT/护甲 PNG 绑定/素材入包)
|
{"path": "res://tests/unit/presentation/standee_present_values_test.gd", "expect": 0}, # 战斗一屏样张批 V5–V12(血条危险红/名字行素底不压地台与提档 FS_HINT/徽标/护甲 PNG 绑定/素材入包/地台色合并退役)
|
||||||
{"path": "res://tests/unit/presentation/reward_overlay_test.gd", "expect": 0}, # 奖励节点 UI 直验(盲区补漏)
|
{"path": "res://tests/unit/presentation/reward_overlay_test.gd", "expect": 0}, # 奖励节点 UI 直验(盲区补漏)
|
||||||
{"path": "res://tests/unit/presentation/level_map_info_fog_test.gd", "expect": 0}, # 信息迷雾——呈现显隐/Boss 豁免/详情零显示/大图拟合
|
{"path": "res://tests/unit/presentation/level_map_info_fog_test.gd", "expect": 0}, # 信息迷雾——呈现显隐/Boss 豁免/详情零显示/大图拟合
|
||||||
{"path": "res://tests/unit/presentation/forge_workbench_test.gd", "expect": 0}, # 工作间两区六路线 UI 直验(V13 自动化前置面)
|
{"path": "res://tests/unit/presentation/forge_workbench_test.gd", "expect": 0}, # 工作间两区六路线 UI 直验(V13 自动化前置面)
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ fi
|
|||||||
# get_tree());排除注释行(行首 #/##)与字符串字面量(双引号包裹段内的 await 非代码),
|
# get_tree());排除注释行(行首 #/##)与字符串字面量(双引号包裹段内的 await 非代码),
|
||||||
# 口径仿 S1/S2 的 grep 排除手法。
|
# 口径仿 S1/S2 的 grep 排除手法。
|
||||||
# 自检:清单内文件缺位(路径写错/被改名)即判红——防扫面为空静默绿。
|
# 自检:清单内文件缺位(路径写错/被改名)即判红——防扫面为空静默绿。
|
||||||
S8_FILES="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/settlement/round_cycle.gd game/core/damage_controller.gd"
|
S8_FILES="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/settlement/round_cycle.gd game/core/damage_controller.gd game/core/settlement/battle_refresh_flow.gd"
|
||||||
S8_MISS=0
|
S8_MISS=0
|
||||||
for f in $S8_FILES; do
|
for f in $S8_FILES; do
|
||||||
if [ ! -f "$f" ]; then
|
if [ ! -f "$f" ]; then
|
||||||
@@ -356,7 +356,9 @@ fi
|
|||||||
# 自检:扫描面为空即判红(require_scan_face);B1 文件集与三族自家文件缺位即判红
|
# 自检:扫描面为空即判红(require_scan_face);B1 文件集与三族自家文件缺位即判红
|
||||||
# (文件被改名=豁免名单失效,宁可红不可静默放行)。
|
# (文件被改名=豁免名单失效,宁可红不可静默放行)。
|
||||||
if require_scan_face "S7" "$DIR_CORE"; then
|
if require_scan_face "S7" "$DIR_CORE"; then
|
||||||
S7_B1_SET="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/settlement/round_cycle.gd game/core/damage_controller.gd"
|
# B1 域文件集(迭代 46 卡A 增补 battle_refresh_flow——换图刷新流程件=C13 拆分边界的 B1 域拆件,
|
||||||
|
# 承载旧资源退役/装板编排调用;architecture.md「模块归属」BattleRefreshFlow 行随反哺点落文)
|
||||||
|
S7_B1_SET="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/settlement/round_cycle.gd game/core/damage_controller.gd game/core/settlement/battle_refresh_flow.gd"
|
||||||
S7_MISS=0
|
S7_MISS=0
|
||||||
for f in $S7_B1_SET game/core/unit_container.gd game/core/status_container.gd game/core/reward_economy.gd; do
|
for f in $S7_B1_SET game/core/unit_container.gd game/core/status_container.gd game/core/reward_economy.gd; do
|
||||||
if [ ! -f "$f" ]; then
|
if [ ! -f "$f" ]; then
|
||||||
|
|||||||
@@ -0,0 +1,650 @@
|
|||||||
|
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||||
|
# 换图系双基座七件专项宿主(迭代 46 卡A——对照表 V1–V12 按轮落地:V5=T2 全局受伤链路、
|
||||||
|
# V2/V3=T3 两基座、V4/V10=T4 成功事实与 GP、V6–V9=T5 五件后续、V11=T6 数据与池;
|
||||||
|
# V12 无专长基线回归归 round_flow/seal_polarity 既有断言承载)。
|
||||||
|
# 临时承伤计数专长经现行 ConfigDB 注入(表行追加+持有集传 id——正常数据配置路径,卡片 §12);
|
||||||
|
# 真实入口驱动:AutoPlayer 盲挖循环+停手/封印动作;判据绑可观测提交面(damage_taken 命中计数器
|
||||||
|
# 与 target=hero 明细行)。
|
||||||
|
# 运行:godot --headless --path game --script res://tests/unit/battle_settlement/refresh_feats_test.gd(expect 0)
|
||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
const Kit := preload("res://tests/unit/test_kit.gd")
|
||||||
|
const ConfigDBScript = preload("res://core/config_db.gd")
|
||||||
|
const EffectEngineScript = preload("res://core/effect_engine.gd")
|
||||||
|
const AutoPlayer := preload("res://sim/auto_player.gd")
|
||||||
|
|
||||||
|
# 直接伤型资源类型(负向极性兑付=主角直伤单据的三型——reward 表 pierce/magic/heal 负向行)
|
||||||
|
const DIRECT_TYPES := ["pierce", "magic", "heal"]
|
||||||
|
|
||||||
|
var _db
|
||||||
|
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
_db = ConfigDBScript.new()
|
||||||
|
if not _db.load_tables("res://data/tables", true):
|
||||||
|
print("[refresh_feats_test] FAIL — 数值表加载失败")
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
_install_dt_probe()
|
||||||
|
test_all_hero_damage_paths_emit_once()
|
||||||
|
test_no_duplicate_damage_taken_event()
|
||||||
|
test_reserve_on_accepted_dig()
|
||||||
|
test_refresh_discount_gate_and_charge()
|
||||||
|
test_double_base_stack()
|
||||||
|
test_no_feats_baseline()
|
||||||
|
test_board_refresh_fact_only_on_success()
|
||||||
|
test_mine_profit_gp()
|
||||||
|
test_stone_mitigation_all_damage()
|
||||||
|
test_rest_rotation_cap_and_clear()
|
||||||
|
test_charge_layers_consumed_same_round()
|
||||||
|
test_blast_defers_to_combat_segment()
|
||||||
|
test_seven_feats_validate_and_pool()
|
||||||
|
quit(Kit.finish("refresh_feats_test"))
|
||||||
|
|
||||||
|
|
||||||
|
# ---- 夹具族(真实入口驱动——settle_batch/stop/封印动作;直构只用于编排器受理)----
|
||||||
|
|
||||||
|
# 临时承伤计数专长(damage_taken → counter_add battle 桶——每张主角承伤单据恰 +1)
|
||||||
|
func _install_dt_probe() -> void:
|
||||||
|
if _db.crossrun.feats.row_by_id("probe_dt_counter").is_empty():
|
||||||
|
(_db.crossrun.feats.rows as Array).append({
|
||||||
|
"feat_id": "probe_dt_counter", "display_name": "承伤计数探针", "rarity": "common",
|
||||||
|
"desc": "测试夹具:每次主角承伤计数 +1",
|
||||||
|
"triggers": [{"on": "damage_taken", "do": {"kind": "counter_add", "counter_id": "dt_probe_hits", "value": 1}}],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
func _orch(seed_v: int):
|
||||||
|
var rng = load("res://core/rng_service.gd").new()
|
||||||
|
rng.set_master_seed(seed_v)
|
||||||
|
return load("res://core/battle_orchestrator.gd").new(_db, rng)
|
||||||
|
|
||||||
|
|
||||||
|
func _enc(feats: Array, voucher_rap: int = 0, armor: int = 10, hero_hp: int = 200) -> Dictionary:
|
||||||
|
return {"level_index": 1, "node_depth": 4, "hero_hp": hero_hp, "hero_atk": 8, "hero_max_hp": 200,
|
||||||
|
"hero_armor_class": armor, "hero_ap_bonus": 0, "attunement": [], "feats": feats, "tree_rules": [],
|
||||||
|
"battle_index": 1, "voucher_rap": voucher_rap, "voucher_time_sand": 0, "node_type": "boss", "adv_bucket": {}}
|
||||||
|
|
||||||
|
|
||||||
|
func _probe_count(orch) -> int:
|
||||||
|
return EffectEngineScript.counter(orch.fx_host().counters_ctx, "dt_probe_hits")
|
||||||
|
|
||||||
|
|
||||||
|
# 主角承伤明细行收集(type=damage 且 target=hero——敌方技与棋盘主角直伤两类单据的行面)
|
||||||
|
func _collect_hero_rows(orch) -> Array:
|
||||||
|
var rows: Array = []
|
||||||
|
orch.step_row.connect(func(row):
|
||||||
|
if String(row.get("type", "")) == "damage" and String(row.get("target", "")) == "hero":
|
||||||
|
rows.append(row))
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
# 按来源类分桶计数(rows 收集器的只读后处理)
|
||||||
|
func _count_by_source(rows: Array, source: String) -> int:
|
||||||
|
var n := 0
|
||||||
|
for r_v in rows:
|
||||||
|
if String((r_v as Dictionary).get("source_class", "")) == source:
|
||||||
|
n += 1
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
# AutoPlayer 盲挖循环直到 cell_revealed 谓词命中(真实挖掘入口;返回 {"cell","evt"} 或空)
|
||||||
|
func _dig_until(orch, pred: Callable) -> Dictionary:
|
||||||
|
var ms = orch.minesweeper
|
||||||
|
var hit := {}
|
||||||
|
ms.cell_revealed.connect(func(evt):
|
||||||
|
if hit.is_empty() and bool(pred.call(evt)):
|
||||||
|
hit["cell"] = evt.cell
|
||||||
|
hit["evt"] = evt)
|
||||||
|
var guard := 40
|
||||||
|
while hit.is_empty() and guard > 0:
|
||||||
|
guard -= 1
|
||||||
|
var plan = AutoPlayer.choose_dig(ms)
|
||||||
|
if plan.is_empty():
|
||||||
|
break
|
||||||
|
if plan.hero_cell != ms.hero_pos():
|
||||||
|
ms.settle_batch([{"action": "move", "cell": plan.hero_cell}])
|
||||||
|
ms.settle_batch([{"action": "dig", "cell": plan.dig_cell}])
|
||||||
|
return hit
|
||||||
|
|
||||||
|
|
||||||
|
# 直接伤型快照判定(未揭示格可查——reward_economy 装板期摇定身份,示警烟火同源读面)
|
||||||
|
func _is_direct_type(orch, c: Vector2i) -> bool:
|
||||||
|
var snap_v = orch.reward_grant().reward_economy.query_full_snapshot(c)
|
||||||
|
return snap_v is Dictionary and DIRECT_TYPES.has(String((snap_v as Dictionary).get("cell_type", "")))
|
||||||
|
|
||||||
|
|
||||||
|
# 可封印的直接伤型安全格扫描(未揭示+非雷+直接伤型+邻接可达 revealed 格——封印动作真实落点位)
|
||||||
|
func _find_sealable_direct(orch) -> Vector2i:
|
||||||
|
var ms = orch.minesweeper
|
||||||
|
var b = ms.current_board()
|
||||||
|
var revealed := {}
|
||||||
|
for c_v in ms.revealed_cells():
|
||||||
|
revealed[c_v] = true
|
||||||
|
for y in b.height:
|
||||||
|
for x in b.width:
|
||||||
|
var c := Vector2i(x, y)
|
||||||
|
if ms.is_revealed(c) or b.is_mine(c) or ms.has_seal(c) or not _is_direct_type(orch, c):
|
||||||
|
continue
|
||||||
|
for n_v in b.neighbors8(c):
|
||||||
|
var n := Vector2i(n_v)
|
||||||
|
if revealed.has(n) and (ms.compute_move_path(n).size() > 0 or n == ms.hero_pos()):
|
||||||
|
return c
|
||||||
|
return Vector2i(-1, -1)
|
||||||
|
|
||||||
|
|
||||||
|
# 盲挖扩板直至出现可封印直接伤型安全格(有界;返回格位或 (-1,-1))
|
||||||
|
func _seal_direct_with_expansion(orch) -> Vector2i:
|
||||||
|
var c := _find_sealable_direct(orch)
|
||||||
|
var guard := 12
|
||||||
|
while c.x < 0 and guard > 0:
|
||||||
|
guard -= 1
|
||||||
|
var plan = AutoPlayer.choose_dig(orch.minesweeper)
|
||||||
|
if plan.is_empty():
|
||||||
|
break
|
||||||
|
if plan.hero_cell != orch.minesweeper.hero_pos():
|
||||||
|
orch.minesweeper.settle_batch([{"action": "move", "cell": plan.hero_cell}])
|
||||||
|
orch.minesweeper.settle_batch([{"action": "dig", "cell": plan.dig_cell}])
|
||||||
|
c = _find_sealable_direct(orch)
|
||||||
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
# ---- V5 全局受伤链路(迭代 46 卡A T2)----
|
||||||
|
|
||||||
|
# 四条真实路径(敌方攻击/危险格/错误封印/地形)逐路驱动:每条至少一张主角承伤单据在案,
|
||||||
|
# 且承伤计数器 == 主角承伤单据行数(每单据恰发射一次的全量判据——重复发射即计数器>行数)。
|
||||||
|
func test_all_hero_damage_paths_emit_once() -> void:
|
||||||
|
print("--- V5 全局 damage_taken:四路径逐路恰一次 ---")
|
||||||
|
# ① 敌方攻击路径(Boss 战零挖掘连停三轮——干净样本:零地形零资源单据;多轮保证伤害技在案)
|
||||||
|
var orch1 = _orch(46001)
|
||||||
|
var rows1 := _collect_hero_rows(orch1)
|
||||||
|
Kit.check("V5① 前置:战斗受理", orch1.begin_battle(_enc(["probe_dt_counter"])))
|
||||||
|
for _i in 3:
|
||||||
|
orch1.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
var enemy_rows := _count_by_source(rows1, "enemy_action")
|
||||||
|
Kit.check("V5① 敌方攻击路径:承伤单据在案且逐单恰一次(行 %d=计数 %d)〔换图系批 V5〕" % [enemy_rows, _probe_count(orch1)],
|
||||||
|
enemy_rows >= 1 and _probe_count(orch1) == enemy_rows)
|
||||||
|
# ② 地形路径(AutoPlayer 真挖至伤害型变异格开出——发射在授予段开单点、单据标 hero_damage_consumed)
|
||||||
|
var orch2 = _orch(46002)
|
||||||
|
var rows2 := _collect_hero_rows(orch2)
|
||||||
|
Kit.check("V5② 前置:战斗受理", orch2.begin_battle(_enc(["probe_dt_counter"])))
|
||||||
|
var terr := _dig_until(orch2, func(evt):
|
||||||
|
for vid_v in (evt.get("variant_ids", []) as Array):
|
||||||
|
if String(_db.combat.variation.row_by_id(String(vid_v)).get("effect", "")) == "flat_damage":
|
||||||
|
return true
|
||||||
|
return false)
|
||||||
|
Kit.check("V5② 前置:伤害型变异格已挖开(%s)" % str(terr.get("cell", Vector2i(-1, -1))), not terr.is_empty())
|
||||||
|
orch2.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
var terrain_rows := _count_by_source(rows2, "地形变异")
|
||||||
|
Kit.check("V5② 地形路径:地形单据在案且全单据逐张恰一次(行 %d=计数 %d——盲挖途经单据一并计入)〔换图系批 V5〕" % [rows2.size(), _probe_count(orch2)],
|
||||||
|
terrain_rows >= 1 and _probe_count(orch2) == rows2.size())
|
||||||
|
# ③ 危险格路径(AutoPlayer 真挖直接伤型危险格——负向直伤单据)
|
||||||
|
var orch3 = _orch(46003)
|
||||||
|
var rows3 := _collect_hero_rows(orch3)
|
||||||
|
Kit.check("V5③ 前置:战斗受理", orch3.begin_battle(_enc(["probe_dt_counter"])))
|
||||||
|
var mine_hit := _dig_until(orch3, func(evt):
|
||||||
|
return String(evt.get("kind", "")) == "mine" and _is_direct_type(orch3, evt.cell))
|
||||||
|
Kit.check("V5③ 前置:直接伤型危险格已挖开(%s)" % str(mine_hit.get("cell", Vector2i(-1, -1))), not mine_hit.is_empty())
|
||||||
|
orch3.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
var danger_rows := _count_by_source(rows3, "mine_hit")
|
||||||
|
Kit.check("V5③ 危险格路径:直伤单据在案且全单据逐张恰一次(行 %d=计数 %d)〔换图系批 V5〕" % [rows3.size(), _probe_count(orch3)],
|
||||||
|
danger_rows >= 1 and _probe_count(orch3) == rows3.size())
|
||||||
|
# ④ 错误封印路径(扩板→封直接伤型安全格不挖→停手轮末判标错——负向直伤单据)
|
||||||
|
var orch4 = _orch(46004)
|
||||||
|
var rows4 := _collect_hero_rows(orch4)
|
||||||
|
Kit.check("V5④ 前置:战斗受理", orch4.begin_battle(_enc(["probe_dt_counter"])))
|
||||||
|
var scell := _seal_direct_with_expansion(orch4)
|
||||||
|
Kit.check("V5④ 前置:可封直接伤型安全格在位(%s)" % str(scell), scell.x >= 0)
|
||||||
|
if scell.x >= 0:
|
||||||
|
var plan4 = AutoPlayer.choose_dig(orch4.minesweeper)
|
||||||
|
# 封印动作真实落点:先就位到目标邻接可达格(封印距离同挖掘距离)
|
||||||
|
var seal_plan := _plan_seal(orch4, scell)
|
||||||
|
if not seal_plan.is_empty():
|
||||||
|
if seal_plan["hero_cell"] != orch4.minesweeper.hero_pos():
|
||||||
|
orch4.minesweeper.settle_batch([{"action": "move", "cell": seal_plan["hero_cell"]}])
|
||||||
|
var seal_out: Array = orch4.minesweeper.settle_batch([{"action": "seal_place", "cell": scell}])
|
||||||
|
Kit.check("V5④ 前置:封印受理", not seal_out.is_empty() and bool((seal_out[0] as Dictionary).get("accepted", false)))
|
||||||
|
orch4.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
var sealwrong_rows := _count_by_source(rows4, "seal_miss")
|
||||||
|
Kit.check("V5④ 错误封印路径:直伤单据在案且全单据逐张恰一次(行 %d=计数 %d)〔换图系批 V5〕" % [rows4.size(), _probe_count(orch4)],
|
||||||
|
sealwrong_rows >= 1 and _probe_count(orch4) == rows4.size())
|
||||||
|
|
||||||
|
|
||||||
|
# 封印落点计划(目标格邻接可达 revealed 位——AutoPlayer._plan_on 同构最小私有形)
|
||||||
|
func _plan_seal(orch, target: Vector2i) -> Dictionary:
|
||||||
|
var ms = orch.minesweeper
|
||||||
|
for n_v in ms.current_board().neighbors8(target):
|
||||||
|
var n := Vector2i(n_v)
|
||||||
|
if ms.is_revealed(n) and (ms.compute_move_path(n).size() > 0 or n == ms.hero_pos()):
|
||||||
|
return {"hero_cell": n}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
# 同一伤害不得出现两次消费(重复发射=计数器>行数):敌方路径与地形路径两样本逐位核对——
|
||||||
|
# 地形单据带 hero_damage_consumed 标记,战斗段末 _resolve_board_flat_hero 跳过二次发射由此钉住。
|
||||||
|
func test_no_duplicate_damage_taken_event() -> void:
|
||||||
|
print("--- V5 全局 damage_taken:无重复消费 ---")
|
||||||
|
var orch1 = _orch(46011)
|
||||||
|
var rows1 := _collect_hero_rows(orch1)
|
||||||
|
Kit.check("ND 前置:战斗受理", orch1.begin_battle(_enc(["probe_dt_counter"])))
|
||||||
|
for _i in 2:
|
||||||
|
orch1.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
Kit.check("ND 敌方路径零重复(行 %d=计数 %d——重复消费即计数>行)〔换图系批 V5〕" % [rows1.size(), _probe_count(orch1)],
|
||||||
|
rows1.size() >= 1 and _probe_count(orch1) == rows1.size())
|
||||||
|
var orch2 = _orch(46012)
|
||||||
|
var rows2 := _collect_hero_rows(orch2)
|
||||||
|
Kit.check("ND 前置:战斗受理", orch2.begin_battle(_enc(["probe_dt_counter"])))
|
||||||
|
var terr2 := _dig_until(orch2, func(evt):
|
||||||
|
for vid_v in (evt.get("variant_ids", []) as Array):
|
||||||
|
if String(_db.combat.variation.row_by_id(String(vid_v)).get("effect", "")) == "flat_damage":
|
||||||
|
return true
|
||||||
|
return false)
|
||||||
|
Kit.check("ND 前置:伤害型变异格已挖开(%s)" % str(terr2.get("cell", Vector2i(-1, -1))), not terr2.is_empty())
|
||||||
|
orch2.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
Kit.check("ND 地形路径零重复(行 %d=计数 %d——context 标记闸失效即计数翻倍)〔换图系批 V5〕" % [rows2.size(), _probe_count(orch2)],
|
||||||
|
rows2.size() >= 1 and _probe_count(orch2) == rows2.size())
|
||||||
|
|
||||||
|
|
||||||
|
# ---- V2/V3 两件基座(迭代 46 卡A T3)----
|
||||||
|
|
||||||
|
# 表驱动停手攒 RAP(每轮 1:1 转存——无转存率订阅缺省)
|
||||||
|
func _bank_rap(orch, rounds_n: int) -> void:
|
||||||
|
for _i in rounds_n:
|
||||||
|
orch.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
|
||||||
|
|
||||||
|
# AutoPlayer 单次付费挖格(真实挖掘入口——移位+dig 一动作)
|
||||||
|
func _auto_dig(orch) -> void:
|
||||||
|
var plan = AutoPlayer.choose_dig(orch.minesweeper)
|
||||||
|
if not plan.is_empty():
|
||||||
|
if plan.hero_cell != orch.minesweeper.hero_pos():
|
||||||
|
orch.minesweeper.settle_batch([{"action": "move", "cell": plan.hero_cell}])
|
||||||
|
orch.minesweeper.settle_batch([{"action": "dig", "cell": plan.dig_cell}])
|
||||||
|
|
||||||
|
|
||||||
|
# V2 矿脉储能:受理的付费挖格 RAP 恰 +1、AP 按表扣、板资源不变
|
||||||
|
func test_reserve_on_accepted_dig() -> void:
|
||||||
|
print("--- V2 矿脉储能:付费挖格产 RAP ---")
|
||||||
|
var orch = _orch(46021)
|
||||||
|
Kit.check("V2 前置:战斗受理", orch.begin_battle(_enc(["vein_reserve"])))
|
||||||
|
_bank_rap(orch, 1)
|
||||||
|
var rap_before: int = orch.action_economy.rap
|
||||||
|
var ap_before: int = orch.action_economy.ap
|
||||||
|
_auto_dig(orch)
|
||||||
|
var dig_cost: int = int(_db.run.action_economy.dig_ap_cost)
|
||||||
|
Kit.check("V2 受理付费挖格 RAP 恰 +1(%d→%d)〔换图系批 V2〕" % [rap_before, orch.action_economy.rap],
|
||||||
|
orch.action_economy.rap == rap_before + 1)
|
||||||
|
Kit.check("V2 AP 按表扣(%d→%d,dig 价 %d)〔换图系批 V2〕" % [ap_before, orch.action_economy.ap, dig_cost],
|
||||||
|
orch.action_economy.ap == ap_before - dig_cost)
|
||||||
|
Kit.check("V2 受理记账闭合:AP 耗尽轮 RAP 增量恰等于受理挖数(AP 消耗数——每受理恰 +1,未受理零入池)〔换图系批 V2〕",
|
||||||
|
_drain_round_rap_accounting(orch))
|
||||||
|
|
||||||
|
|
||||||
|
# V2 辅证:挖到 AP 耗尽——RAP 增量 == 受理挖数(每受理恰 +1;移位耗 AP 不入账故按挖计数)
|
||||||
|
func _drain_round_rap_accounting(orch) -> bool:
|
||||||
|
var rap0: int = orch.action_economy.rap
|
||||||
|
var digs := 0
|
||||||
|
var guard := 24
|
||||||
|
while orch.action_economy.phase == "Active" and guard > 0:
|
||||||
|
guard -= 1
|
||||||
|
var plan = AutoPlayer.choose_dig(orch.minesweeper)
|
||||||
|
if plan.is_empty():
|
||||||
|
break
|
||||||
|
if plan.hero_cell != orch.minesweeper.hero_pos():
|
||||||
|
orch.minesweeper.settle_batch([{"action": "move", "cell": plan.hero_cell}])
|
||||||
|
if orch.action_economy.phase != "Active":
|
||||||
|
break # 移位耗尽 AP 收轮——本轮不再有受理挖
|
||||||
|
orch.minesweeper.settle_batch([{"action": "dig", "cell": plan.dig_cell}])
|
||||||
|
digs += 1
|
||||||
|
return digs > 0 and orch.action_economy.rap - rap0 == digs
|
||||||
|
|
||||||
|
|
||||||
|
# V3 省力周转:折价门槛与扣费(can 与执行同源;base 8→扣 4)
|
||||||
|
func test_refresh_discount_gate_and_charge() -> void:
|
||||||
|
print("--- V3 省力周转:折价门槛与扣费 ---")
|
||||||
|
var orch = _orch(46022)
|
||||||
|
Kit.check("V3 前置:战斗受理", orch.begin_battle(_enc(["easy_swap"])))
|
||||||
|
_bank_rap(orch, 1)
|
||||||
|
var base_cost: int = int(_db.run.action_economy.rap_refresh_cost)
|
||||||
|
var discounted: int = int(floor(float(base_cost) * 50.0 / 100.0))
|
||||||
|
var rap0: int = orch.action_economy.rap
|
||||||
|
Kit.check("V3 门同源:can_refresh 与折后价同源(RAP %d ≥ 折后 %d)〔换图系批 V3〕" % [rap0, discounted],
|
||||||
|
orch.can_refresh())
|
||||||
|
Kit.check("V3 第 1 次刷新受理", orch.player_request_refresh())
|
||||||
|
Kit.check("V3 折后单次扣费(%d→%d,−%d)〔换图系批 V3〕" % [rap0, orch.action_economy.rap, discounted],
|
||||||
|
orch.action_economy.rap == rap0 - discounted)
|
||||||
|
Kit.check("V3 第 2 次刷新受理(RAP=%d)" % orch.action_economy.rap, orch.player_request_refresh())
|
||||||
|
Kit.check("V3 两次折后扣费累计(RAP=%d=%d−2×%d)〔换图系批 V3〕" % [orch.action_economy.rap, rap0, discounted],
|
||||||
|
orch.action_economy.rap == rap0 - 2 * discounted)
|
||||||
|
Kit.check("V3 门槛拒:RAP 不足折后价零受理(can=false 且执行拒)〔换图系批 V3〕",
|
||||||
|
orch.action_economy.rap < discounted and not orch.can_refresh() and not orch.player_request_refresh())
|
||||||
|
|
||||||
|
|
||||||
|
# V3 双基座叠加:挖格产 RAP 与刷新折价同场生效
|
||||||
|
func test_double_base_stack() -> void:
|
||||||
|
print("--- V3 双基座叠加 ---")
|
||||||
|
var orch = _orch(46023)
|
||||||
|
Kit.check("V3 前置:战斗受理", orch.begin_battle(_enc(["vein_reserve", "easy_swap"])))
|
||||||
|
_bank_rap(orch, 1)
|
||||||
|
var rap_before: int = orch.action_economy.rap
|
||||||
|
var base_cost: int = int(_db.run.action_economy.rap_refresh_cost)
|
||||||
|
_auto_dig(orch)
|
||||||
|
Kit.check("V3 双基座:挖格产 RAP +1(%d→%d)〔换图系批 V3〕" % [rap_before, orch.action_economy.rap],
|
||||||
|
orch.action_economy.rap == rap_before + 1)
|
||||||
|
var rap_after_dig: int = orch.action_economy.rap
|
||||||
|
Kit.check("V3 双基座:刷新按折后价扣(−%d)〔换图系批 V3〕" % int(base_cost / 2),
|
||||||
|
orch.player_request_refresh() and orch.action_economy.rap == rap_after_dig - int(base_cost / 2))
|
||||||
|
|
||||||
|
|
||||||
|
# V12(refresh_feats 侧):无专长基线——挖格零产 RAP、刷新按表基线扣、AP 保留、上限照旧
|
||||||
|
func test_no_feats_baseline() -> void:
|
||||||
|
print("--- V12 无专长基线(refresh_feats 侧) ---")
|
||||||
|
var orch = _orch(46024)
|
||||||
|
Kit.check("V12 前置:战斗受理", orch.begin_battle(_enc([])))
|
||||||
|
_bank_rap(orch, 3)
|
||||||
|
var rap_before: int = orch.action_economy.rap
|
||||||
|
var ap_before: int = orch.action_economy.ap
|
||||||
|
var base_cost: int = int(_db.run.action_economy.rap_refresh_cost)
|
||||||
|
_auto_dig(orch)
|
||||||
|
Kit.check("V12 无基座:挖格零产 RAP(%d 不变)〔换图系批 V12〕" % rap_before, orch.action_economy.rap == rap_before)
|
||||||
|
Kit.check("V12 刷新受理并按表基线扣(−%d)〔换图系批 V12〕" % base_cost,
|
||||||
|
orch.player_request_refresh() and orch.action_economy.rap == rap_before - base_cost)
|
||||||
|
Kit.check("V12 AP 保留、相位 Active(基线刷新不重置)〔换图系批 V12〕",
|
||||||
|
orch.action_economy.ap == ap_before - int(_db.run.action_economy.dig_ap_cost) and orch.action_economy.phase == "Active")
|
||||||
|
Kit.check("V12 每轮上限照旧:第 3 次拒(RAP 足额仍拒)〔换图系批 V12〕",
|
||||||
|
orch.player_request_refresh() and not orch.player_request_refresh())
|
||||||
|
|
||||||
|
|
||||||
|
# ---- V4/V10 成功刷新事实与命中施加(迭代 46 卡A T4)----
|
||||||
|
|
||||||
|
# 成功刷新事实探针专长安装(board_refreshed → counter_add——成功事实的可观测计数面)
|
||||||
|
func _install_br_probe() -> void:
|
||||||
|
if _db.crossrun.feats.row_by_id("probe_br_counter").is_empty():
|
||||||
|
(_db.crossrun.feats.rows as Array).append({
|
||||||
|
"feat_id": "probe_br_counter", "display_name": "刷新事实探针", "rarity": "common",
|
||||||
|
"desc": "测试夹具:每次成功刷新计数 +1",
|
||||||
|
"triggers": [{"on": "board_refreshed", "do": {"kind": "counter_add", "counter_id": "br_probe_hits", "value": 1}}],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
func _br_count(orch) -> int:
|
||||||
|
return EffectEngineScript.counter(orch.fx_host().counters_ctx, "br_probe_hits")
|
||||||
|
|
||||||
|
|
||||||
|
# V4 成功事实只在新板就位后发射:成功恰一次;非 Active/每轮上限/RAP 不足三拒面全零事实零换图
|
||||||
|
func test_board_refresh_fact_only_on_success() -> void:
|
||||||
|
print("--- V4 成功刷新事实:拒绝零事实 ---")
|
||||||
|
_install_br_probe()
|
||||||
|
var orch = _orch(46031)
|
||||||
|
var swaps := {"n": 0}
|
||||||
|
var waiting_rejected := {"v": false}
|
||||||
|
orch.board_changed.connect(func(): swaps["n"] += 1)
|
||||||
|
# 非 Active 探针(round_flow 同款:借衰减行在结算段内同步探测一次——Waiting 相位刷新必拒)
|
||||||
|
orch.step_row.connect(func(row):
|
||||||
|
if String(row.get("type", "")) == "decay" and not bool(waiting_rejected["v"]):
|
||||||
|
waiting_rejected["v"] = not orch.player_request_refresh())
|
||||||
|
Kit.check("V4 前置:战斗受理", orch.begin_battle(_enc(["probe_br_counter"])))
|
||||||
|
_bank_rap(orch, 3) # RAP=3×AP;首轮结算衰减行触发 Waiting 探针
|
||||||
|
var swaps0: int = swaps["n"]
|
||||||
|
Kit.check("V4 非 Active 拒零事实(结算段内探测拒绝)〔换图系批 V4〕",
|
||||||
|
bool(waiting_rejected["v"]) and _br_count(orch) == 0 and int(swaps["n"]) == swaps0)
|
||||||
|
# 成功 ×2:事实计数与换图信号逐次 +1
|
||||||
|
Kit.check("V4 第 1 次成功(事实 +1 换图 +1)〔换图系批 V4〕",
|
||||||
|
orch.player_request_refresh() and _br_count(orch) == 1 and int(swaps["n"]) == swaps0 + 1)
|
||||||
|
Kit.check("V4 第 2 次成功(事实 +1 换图 +1)〔换图系批 V4〕",
|
||||||
|
orch.player_request_refresh() and _br_count(orch) == 2 and int(swaps["n"]) == swaps0 + 2)
|
||||||
|
# 每轮上限拒:used=2 满门、RAP 足额——零事实零换图
|
||||||
|
Kit.check("V4 上限拒零事实(RAP %d 足额仍拒)〔换图系批 V4〕" % orch.action_economy.rap,
|
||||||
|
not orch.can_refresh() and not orch.player_request_refresh() and _br_count(orch) == 2 and int(swaps["n"]) == swaps0 + 2)
|
||||||
|
# RAP 不足拒:停手进新轮(used 重置、停手再入 RAP)→ 消费至 RAP 0 → 拒(零事实零换图;
|
||||||
|
# 纯 RAP 不足拒绝面已由 V3 门槛拒单测钉住,此处并拦形态)
|
||||||
|
orch.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
Kit.check("V4 新轮受理两次(used 重置、RAP 复入)〔换图系批 V4〕",
|
||||||
|
orch.action_economy.phase == "Active" and orch.player_request_refresh() and orch.player_request_refresh() and _br_count(orch) == 4)
|
||||||
|
Kit.check("V4 RAP 不足拒零事实(RAP %d < 价 %d)〔换图系批 V4〕" % [orch.action_economy.rap, int(_db.run.action_economy.rap_refresh_cost)],
|
||||||
|
orch.action_economy.rap < int(_db.run.action_economy.rap_refresh_cost) and not orch.can_refresh() and not orch.player_request_refresh() and _br_count(orch) == 4 and int(swaps["n"]) == swaps0 + 4)
|
||||||
|
|
||||||
|
|
||||||
|
# V10 矿道红利:每次成功刷新 escrow 恰 +1 且 fx_grant_gp 行在案;拒绝零变化
|
||||||
|
func test_mine_profit_gp() -> void:
|
||||||
|
print("--- V10 矿道红利:刷新固定 GP ---")
|
||||||
|
var orch = _orch(46032)
|
||||||
|
var gp_rows := {"n": 0}
|
||||||
|
orch.step_row.connect(func(row):
|
||||||
|
if String(row.get("type", "")) == "effect" and String(row.get("effect_id", "")) == "fx_grant_gp":
|
||||||
|
gp_rows["n"] += 1)
|
||||||
|
Kit.check("V10 前置:战斗受理", orch.begin_battle(_enc(["mine_profit"])))
|
||||||
|
_bank_rap(orch, 2)
|
||||||
|
var escrow0: int = orch.reward_grant().escrow
|
||||||
|
Kit.check("V10 第 1 次成功 escrow +1(%d→%d)〔换图系批 V10〕" % [escrow0, orch.reward_grant().escrow],
|
||||||
|
orch.player_request_refresh() and orch.reward_grant().escrow == escrow0 + 1)
|
||||||
|
Kit.check("V10 fx_grant_gp 行在案(%d 行)〔换图系批 V10〕" % int(gp_rows["n"]), int(gp_rows["n"]) == 1)
|
||||||
|
Kit.check("V10 第 2 次成功 escrow +1(%d→%d)〔换图系批 V10〕" % [orch.reward_grant().escrow, orch.reward_grant().escrow + 1],
|
||||||
|
orch.player_request_refresh() and orch.reward_grant().escrow == escrow0 + 2)
|
||||||
|
# 拒绝零变化:RAP 不足
|
||||||
|
Kit.check("V10 拒绝零变化(escrow=%d)〔换图系批 V10〕" % orch.reward_grant().escrow,
|
||||||
|
not orch.player_request_refresh() and orch.reward_grant().escrow == escrow0 + 2 and int(gp_rows["n"]) == 2)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- V6/V8/V9/V7 五件后续效果(迭代 46 卡A T5)----
|
||||||
|
|
||||||
|
func _counter(orch, counter_id: String) -> int:
|
||||||
|
return EffectEngineScript.counter(orch.fx_host().counters_ctx, counter_id)
|
||||||
|
|
||||||
|
|
||||||
|
# 伤害明细行泛收集(type=damage 全量——按来源类与值断言)
|
||||||
|
func _collect_damage_rows(orch) -> Array:
|
||||||
|
var rows: Array = []
|
||||||
|
orch.step_row.connect(func(row):
|
||||||
|
if String(row.get("type", "")) == "damage":
|
||||||
|
rows.append(row))
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
func _damage_rows_by_source(rows: Array, source: String) -> Array:
|
||||||
|
var out: Array = []
|
||||||
|
for r_v in rows:
|
||||||
|
var r := r_v as Dictionary
|
||||||
|
if String(r.get("source_class", "")) == source:
|
||||||
|
out.append(r)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# V6 石层缓冲:全局固定减伤(危险格 pierce 8→6 两档钉死:单刷减 2·同轮多刷不叠加·未刷新轮不减;
|
||||||
|
# 地形路径减后值=max(下限, effect_value−2)——含下限保持;敌方路径照常单次发射)
|
||||||
|
func test_stone_mitigation_all_damage() -> void:
|
||||||
|
print("--- V6 石层缓冲:全局固定减伤 ---")
|
||||||
|
# S1 同轮双刷 → 危险格 pierce 8 减 2=6(叠加即 4——多刷不增钉死)
|
||||||
|
var orch1 = _orch(46041)
|
||||||
|
var rows1 := _collect_damage_rows(orch1)
|
||||||
|
Kit.check("V6 S1 前置:战斗受理", orch1.begin_battle(_enc(["stone_mitigation"])))
|
||||||
|
_bank_rap(orch1, 3)
|
||||||
|
Kit.check("V6 S1 同轮双刷受理", orch1.player_request_refresh() and orch1.player_request_refresh())
|
||||||
|
Kit.check("V6 S1 guard 累计 2(同轮双刷)", _counter(orch1, "round_swap_guard") == 2)
|
||||||
|
var mine1 := _dig_until(orch1, func(evt):
|
||||||
|
return String(evt.get("kind", "")) == "mine" and _is_direct_type(orch1, evt.cell))
|
||||||
|
Kit.check("V6 S1 前置:直接伤型危险格已挖开(%s)" % str(mine1.get("cell", Vector2i(-1, -1))), not mine1.is_empty())
|
||||||
|
orch1.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
var pierce_rows1 := _damage_rows_by_source(rows1, "mine_hit")
|
||||||
|
Kit.check("V6 S1 危险格减伤恰 2 且多刷不叠加(行值 %s∈[6]——叠加态应为 4)〔换图系批 V6〕" % str(pierce_rows1.map(func(r): return int(r.value))),
|
||||||
|
pierce_rows1.size() >= 1 and int((pierce_rows1[0] as Dictionary).get("value", -1)) == 6)
|
||||||
|
# S2 未刷新轮:guard 随 round_start 清空 → 同型危险格不减(pierce/magic 表幂 8——heal 型幂 6 不同锚勿混)
|
||||||
|
var mine2 := _dig_until(orch1, func(evt):
|
||||||
|
var snap_v2 = orch1.reward_grant().reward_economy.query_full_snapshot(evt.cell)
|
||||||
|
return String(evt.get("kind", "")) == "mine" and snap_v2 is Dictionary \
|
||||||
|
and ["pierce", "magic"].has(String((snap_v2 as Dictionary).get("cell_type", ""))))
|
||||||
|
Kit.check("V6 S2 前置:第二直接伤型危险格已挖开(%s)" % str(mine2.get("cell", Vector2i(-1, -1))), not mine2.is_empty())
|
||||||
|
orch1.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
var pierce_rows2 := _damage_rows_by_source(rows1, "mine_hit")
|
||||||
|
Kit.check("V6 S2 未刷新轮不减伤(新轮行值 %s 含 8——round 桶轮始清空)〔换图系批 V6〕" % str(pierce_rows2.map(func(r): return int(r.value))),
|
||||||
|
pierce_rows2.size() >= 2 and int((pierce_rows2[pierce_rows2.size() - 1] as Dictionary).get("value", -1)) == 8)
|
||||||
|
# S3 地形路径:减后值 = max(下限, effect_value−2)——下限保持与定值减一并钉死
|
||||||
|
# (种子 46002=V5② 已证含 flat_damage 变异板;刷新换板后变异重布—— digging guard 内可达)
|
||||||
|
var orch3 = _orch(46002)
|
||||||
|
var rows3 := _collect_damage_rows(orch3)
|
||||||
|
Kit.check("V6 S3 前置:战斗受理", orch3.begin_battle(_enc(["stone_mitigation"])))
|
||||||
|
_bank_rap(orch3, 2)
|
||||||
|
Kit.check("V6 S3 单刷受理", orch3.player_request_refresh())
|
||||||
|
var terr3 := _dig_until(orch3, func(evt):
|
||||||
|
for vid_v in (evt.get("variant_ids", []) as Array):
|
||||||
|
if String(_db.combat.variation.row_by_id(String(vid_v)).get("effect", "")) == "flat_damage":
|
||||||
|
return true
|
||||||
|
return false)
|
||||||
|
Kit.check("V6 S3 前置:伤害型变异格已挖开(%s)" % str(terr3.get("cell", Vector2i(-1, -1))), not terr3.is_empty())
|
||||||
|
orch3.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
var terrain_rows := _damage_rows_by_source(rows3, "地形变异")
|
||||||
|
var floor_v: int = int(_db.combat.settlement.damage_roll.damage_floor)
|
||||||
|
var ev_ok := true
|
||||||
|
for tr_v in terrain_rows:
|
||||||
|
var tr := tr_v as Dictionary
|
||||||
|
var vid := String(tr.get("variant_id", ""))
|
||||||
|
var ev: int = int(_db.combat.variation.row_by_id(vid).get("effect_value", 0))
|
||||||
|
if int(tr.get("value", -1)) != maxi(floor_v, ev - 2):
|
||||||
|
ev_ok = false
|
||||||
|
Kit.check("V6 S3 地形减伤=max(下限,effect_value−2) 逐单闭合(%d 单)〔换图系批 V6〕" % terrain_rows.size(),
|
||||||
|
terrain_rows.size() >= 1 and ev_ok)
|
||||||
|
|
||||||
|
|
||||||
|
# V9 坑道小憩:轮末合计回复(白盒预置承载上限 3——计数器直改属数据面注入;
|
||||||
|
# 自然累计 2 与满血未生效两形态分战场钉死)
|
||||||
|
func test_rest_rotation_cap_and_clear() -> void:
|
||||||
|
print("--- V9 坑道小憩:轮末恢复 ---")
|
||||||
|
# R1 白盒预置 3 层 + 刷新闸(when<3 拒第 4 层)→ 停手回复 min(3, 缺血)——护甲降档保缺血 ≥3
|
||||||
|
var orch1 = _orch(46051)
|
||||||
|
var rows1: Array = []
|
||||||
|
orch1.step_row.connect(func(row): rows1.append(row))
|
||||||
|
Kit.check("V9 R1 前置:战斗受理", orch1.begin_battle(_enc(["tunnel_rest"], 0, 10, 100)))
|
||||||
|
_bank_rap(orch1, 1) # 开局血差 100(hero_hp 100/max 200)——缺血 ≥3 恒成立,回复面确定
|
||||||
|
var missing: int = 200 - int(orch1._enemy_system.hero_unit().get("hp", 0))
|
||||||
|
EffectEngineScript.counter_add(orch1.fx_host().counters_ctx, "round_swap_rest", 3)
|
||||||
|
Kit.check("V9 R1 预置 3 层后刷新被闸(when<3——上限 3)", orch1.player_request_refresh() and _counter(orch1, "round_swap_rest") == 3)
|
||||||
|
orch1.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
var heal_rows1: Array = []
|
||||||
|
var idle_rows1 := 0
|
||||||
|
for r_v in rows1:
|
||||||
|
var r := r_v as Dictionary
|
||||||
|
if String(r.get("type", "")) == "heal" and String(r.get("source_class", "")) == "fx_board_refresh":
|
||||||
|
heal_rows1.append(r)
|
||||||
|
if String(r.get("type", "")) == "reward_ineffective" and String(r.get("batch_key", "")) == "round_swap_rest":
|
||||||
|
idle_rows1 += 1
|
||||||
|
Kit.check("V9 R1 轮末回复=min(3,缺血%d) 且层清 0(heal 行值 %s)〔换图系批 V9〕" % [missing, str(heal_rows1.map(func(r): return int(r.value)))],
|
||||||
|
missing >= 3 and heal_rows1.size() == 1 and int((heal_rows1[0] as Dictionary).get("value", -1)) == 3 and _counter(orch1, "round_swap_rest") == 0)
|
||||||
|
# R2 满血未生效:首轮刷新后停手——授予段 heal 时点主角仍满血(combat 段还手在其后)
|
||||||
|
var orch2 = _orch(46052)
|
||||||
|
var rows2: Array = []
|
||||||
|
orch2.step_row.connect(func(row): rows2.append(row))
|
||||||
|
Kit.check("V9 R2 前置:战斗受理", orch2.begin_battle(_enc(["tunnel_rest"], 8)))
|
||||||
|
Kit.check("V9 R2 首轮刷新受理(voucher_rap 入池)", orch2.player_request_refresh())
|
||||||
|
orch2.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
var heal_rows2 := 0
|
||||||
|
var idle2 := 0
|
||||||
|
for r2_v in rows2:
|
||||||
|
var r2 := r2_v as Dictionary
|
||||||
|
if String(r2.get("type", "")) == "heal" and String(r2.get("source_class", "")) == "fx_board_refresh":
|
||||||
|
heal_rows2 += 1
|
||||||
|
if String(r2.get("type", "")) == "reward_ineffective" and String(r2.get("batch_key", "")) == "round_swap_rest":
|
||||||
|
idle2 += 1
|
||||||
|
Kit.check("V9 R2 满血未生效行在案且零 heal 行(未生效 %d·heal %d)〔换图系批 V9〕" % [idle2, heal_rows2],
|
||||||
|
idle2 == 1 and heal_rows2 == 0 and _counter(orch2, "round_swap_rest") == 0)
|
||||||
|
# R3 自然累计:同轮双刷 → 回复 2(上限 3 的闸面由 R1 白盒钉住,此处证自然路径)
|
||||||
|
var orch3 = _orch(46053)
|
||||||
|
var rows3: Array = []
|
||||||
|
orch3.step_row.connect(func(row): rows3.append(row))
|
||||||
|
Kit.check("V9 R3 前置:战斗受理", orch3.begin_battle(_enc(["tunnel_rest"])))
|
||||||
|
_bank_rap(orch3, 2)
|
||||||
|
Kit.check("V9 R3 同轮双刷受理", orch3.player_request_refresh() and orch3.player_request_refresh())
|
||||||
|
Kit.check("V9 R3 自然累计 2 层", _counter(orch3, "round_swap_rest") == 2)
|
||||||
|
orch3.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
var heal3 := 0
|
||||||
|
for r3_v in rows3:
|
||||||
|
if String((r3_v as Dictionary).get("type", "")) == "heal" and String((r3_v as Dictionary).get("source_class", "")) == "fx_board_refresh":
|
||||||
|
heal3 += 1
|
||||||
|
Kit.check("V9 R3 轮末 heal 行值 2(自然累计路径)〔换图系批 V9〕",
|
||||||
|
heal3 == 1 and _counter(orch3, "round_swap_rest") == 0)
|
||||||
|
|
||||||
|
|
||||||
|
# V8 蓄力周转:当轮攻击消费全部层数(上限 3 闸=白盒预置;战斗新建自然清空)
|
||||||
|
func test_charge_layers_consumed_same_round() -> void:
|
||||||
|
print("--- V8 蓄力周转:攻击消费层 ---")
|
||||||
|
var orch1 = _orch(46061)
|
||||||
|
var rows1 := _collect_damage_rows(orch1)
|
||||||
|
Kit.check("V8 前置:战斗受理", orch1.begin_battle(_enc(["charge_rotation"], 16)))
|
||||||
|
# 上限 3 闸:白盒预置 3 层后刷新被拒层(when<3)
|
||||||
|
EffectEngineScript.counter_add(orch1.fx_host().counters_ctx, "swap_charge_layers", 3)
|
||||||
|
Kit.check("V8 上限 3 闸(预置 3 刷新不加层)", orch1.player_request_refresh() and _counter(orch1, "swap_charge_layers") == 3)
|
||||||
|
# 当轮攻击消费:停手 → 主角先手攻击 → 层数全消费清零
|
||||||
|
orch1.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
var attack_rows := 0
|
||||||
|
for r_v in rows1:
|
||||||
|
if String((r_v as Dictionary).get("source_class", "")) == "hero_attack":
|
||||||
|
attack_rows += 1
|
||||||
|
Kit.check("V8 当轮攻击消费全部层(攻击行在案 %d·层清 0)〔换图系批 V8〕" % attack_rows,
|
||||||
|
attack_rows >= 1 and _counter(orch1, "swap_charge_layers") == 0)
|
||||||
|
# 自然累计:双刷 2 层(voucher_rap 16 入池)→ 停手消费归零
|
||||||
|
var orch2 = _orch(46062)
|
||||||
|
Kit.check("V8 前置:战斗受理", orch2.begin_battle(_enc(["charge_rotation"], 16)))
|
||||||
|
Kit.check("V8 战斗新建层 0(引擎重建自然清空)", _counter(orch2, "swap_charge_layers") == 0)
|
||||||
|
Kit.check("V8 双刷累计 2 层", orch2.player_request_refresh() and orch2.player_request_refresh() and _counter(orch2, "swap_charge_layers") == 2)
|
||||||
|
orch2.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
Kit.check("V8 攻击后层清 0(消费面闭合)〔换图系批 V8〕", _counter(orch2, "swap_charge_layers") == 0)
|
||||||
|
|
||||||
|
|
||||||
|
# V7 崩壁冲击:延迟群伤单(刷新时点零伤害行——战斗段末按存活目标结账)
|
||||||
|
func test_blast_defers_to_combat_segment() -> void:
|
||||||
|
print("--- V7 崩壁冲击:延迟群伤 ---")
|
||||||
|
var orch = _orch(46071)
|
||||||
|
var rows := _collect_damage_rows(orch)
|
||||||
|
Kit.check("V7 前置:战斗受理", orch.begin_battle(_enc(["wall_blast"], 8)))
|
||||||
|
Kit.check("V7 刷新受理(voucher_rap 入池)", orch.player_request_refresh())
|
||||||
|
Kit.check("V7 刷新时点零伤害行(授予段不结账——案C 延迟)〔换图系批 V7〕",
|
||||||
|
_damage_rows_by_source(rows, "fx_board_refresh").is_empty())
|
||||||
|
orch.minesweeper.settle_batch([{"action": "stop"}])
|
||||||
|
var blast_rows := _damage_rows_by_source(rows, "fx_board_refresh")
|
||||||
|
Kit.check("V7 战斗段末群伤单恰一单(全体存活敌 ×2——行值 2 count≥1)〔换图系批 V7〕",
|
||||||
|
blast_rows.size() == 1 and int((blast_rows[0] as Dictionary).get("value", -1)) == 2
|
||||||
|
and int((blast_rows[0] as Dictionary).get("count", 0)) >= 1)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- V11 数据与池(迭代 46 卡A T6)----
|
||||||
|
|
||||||
|
# 七件 validate/在册/稀有度 cap 对表/跨表计数器引用闭合/desc 禁词
|
||||||
|
func test_seven_feats_validate_and_pool() -> void:
|
||||||
|
print("--- V11 七件数据与池 ---")
|
||||||
|
var seven := {"vein_reserve": "excellent", "easy_swap": "excellent", "wall_blast": "excellent",
|
||||||
|
"charge_rotation": "excellent", "stone_mitigation": "common", "tunnel_rest": "common", "mine_profit": "rare"}
|
||||||
|
Kit.check("V11 feats.validate 零违例(七行全过词表/行键白名单/前缀校验)〔换图系批 V11〕",
|
||||||
|
_db.crossrun.feats.validate().is_empty())
|
||||||
|
var all_in := true
|
||||||
|
var desc_bad := 0
|
||||||
|
for fid in seven.keys():
|
||||||
|
var row: Dictionary = _db.crossrun.feats.row_by_id(String(fid))
|
||||||
|
if row.is_empty() or String(row.get("rarity", "")) != String(seven[fid]) or int(row.get("cap", 0)) != 1:
|
||||||
|
all_in = false
|
||||||
|
if String(row.get("desc", "")).contains("雷") or String(row.get("desc", "")).contains("旗"):
|
||||||
|
desc_bad += 1
|
||||||
|
Kit.check("V11 七行在册且稀有度/cap 逐件对表〔换图系批 V11〕", all_in)
|
||||||
|
Kit.check("V11 七件 desc 禁词零命中(雷/旗字面)〔换图系批 V11〕", desc_bad == 0)
|
||||||
|
# 跨表计数器引用闭合(判据域=换图系七件自身:value_counter/when-id 读取全部有 counter_add 写入方;
|
||||||
|
# 预存行的消费点记账形态〔如完美保险 insurance_used 由授予段落账〕不在此判据域)
|
||||||
|
var written := {}
|
||||||
|
for row_v in (_db.crossrun.feats.rows as Array):
|
||||||
|
for tv in ((row_v as Dictionary).get("triggers", []) as Array):
|
||||||
|
var do_d: Dictionary = (tv as Dictionary).get("do", {})
|
||||||
|
if String(do_d.get("kind", "")) == "counter_add":
|
||||||
|
written[String(do_d.get("counter_id", ""))] = true
|
||||||
|
var refs_ok := true
|
||||||
|
for fid2 in seven.keys():
|
||||||
|
var row2: Dictionary = _db.crossrun.feats.row_by_id(String(fid2))
|
||||||
|
for tv2 in ((row2.get("triggers", []) as Array)):
|
||||||
|
var tr2 := tv2 as Dictionary
|
||||||
|
var do2: Dictionary = tr2.get("do", {})
|
||||||
|
for reader in [String(do2.get("value_counter", "")), String(do2.get("value_from", ""))]:
|
||||||
|
if reader != "" and not written.has(reader):
|
||||||
|
refs_ok = false
|
||||||
|
for c_v in (tr2.get("when", []) as Array):
|
||||||
|
var cid := String((c_v as Dictionary).get("id", ""))
|
||||||
|
if (c_v as Dictionary).get("fact", "") == "counter" and cid != "" and not written.has(cid):
|
||||||
|
refs_ok = false
|
||||||
|
Kit.check("V11 七件跨表计数器引用闭合(value_counter/when-id 全部有 counter_add 写入方)〔换图系批 V11〕", refs_ok)
|
||||||
|
Kit.check("V11 换图系三计数器在写入集(swap_charge_layers/round_swap_guard/round_swap_rest)〔换图系批 V11〕",
|
||||||
|
written.has("swap_charge_layers") and written.has("round_swap_guard") and written.has("round_swap_rest"))
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bmsh0idfmpw3j
|
||||||
@@ -461,8 +461,8 @@ func _smoke_adventure() -> void:
|
|||||||
|
|
||||||
func _smoke_feats() -> void:
|
func _smoke_feats() -> void:
|
||||||
print("--- 专长重设计实装(feat_table 四级 · 专长带池接线 · 订阅面逐件)---")
|
print("--- 专长重设计实装(feat_table 四级 · 专长带池接线 · 订阅面逐件)---")
|
||||||
# ① 现行表牌库构成:30 件全量入池——102 数值令牌+30 relic 令牌(四级 rarity 直映射)
|
# ① 现行表牌库构成:37 件全量入池——102 数值令牌+37 relic 令牌(四级 rarity 直映射)
|
||||||
Kit.check("feat_table 已加载(30 件=基础 27+迁入 2+卡② 完美保险 1——迭代 31 卡② 换代)", _db.crossrun.feats != null and (_db.crossrun.feats.rows as Array).size() == 30)
|
Kit.check("feat_table 已加载(37 件=基础 27+迁入 2+完美保险 1+迭代 46 换图系七件——换代)", _db.crossrun.feats != null and (_db.crossrun.feats.rows as Array).size() == 37)
|
||||||
Kit.check("feat_table.validate 零违例(四级 rarity/cap 域/trigger 词表/行键白名单全过)", _db.crossrun.feats.validate().is_empty())
|
Kit.check("feat_table.validate 零违例(四级 rarity/cap 域/trigger 词表/行键白名单全过)", _db.crossrun.feats.validate().is_empty())
|
||||||
var a_r0 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(71))
|
var a_r0 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(71))
|
||||||
Kit.check("专长带场景:冒险启动(seed 71)", a_r0.start_adventure(71))
|
Kit.check("专长带场景:冒险启动(seed 71)", a_r0.start_adventure(71))
|
||||||
@@ -473,7 +473,7 @@ func _smoke_feats() -> void:
|
|||||||
feat_tokens += 1
|
feat_tokens += 1
|
||||||
var tk := String((t as Dictionary).get("tier", ""))
|
var tk := String((t as Dictionary).get("tier", ""))
|
||||||
tier_counts[tk] = int(tier_counts.get(tk, 0)) + 1
|
tier_counts[tk] = int(tier_counts.get(tk, 0)) + 1
|
||||||
Kit.check("专长带入池:30 件四级直映射(普通 12/优秀 9/稀有 9/传说 0)——牌库 102+30=132〔专长批 V2+卡② 完美保险迁入〕", feat_tokens == 30 and int(tier_counts.get("common", 0)) == 12 and int(tier_counts.get("excellent", 0)) == 9 and int(tier_counts.get("rare", 0)) == 9 and a_r0.deck().deck.size() == 132)
|
Kit.check("专长带入池:37 件四级直映射(普通 14/优秀 13/稀有 10/传说 0)——牌库 102+37=139〔专长批 V2+迭代 46 换图系七件换代〕", feat_tokens == 37 and int(tier_counts.get("common", 0)) == 14 and int(tier_counts.get("excellent", 0)) == 13 and int(tier_counts.get("rare", 0)) == 10 and a_r0.deck().deck.size() == 139)
|
||||||
# ② 旧 27 件零残留钉面(V2——arcane_ember 系旧件表首行;全仓 grep 零残留归静态核查)
|
# ② 旧 27 件零残留钉面(V2——arcane_ember 系旧件表首行;全仓 grep 零残留归静态核查)
|
||||||
Kit.check("旧 27 件已退役:arcane_ember/magma_body 表内零残留〔专长批 V2〕", _db.crossrun.feats.row_by_id("arcane_ember").is_empty() and _db.crossrun.feats.row_by_id("magma_body").is_empty())
|
Kit.check("旧 27 件已退役:arcane_ember/magma_body 表内零残留〔专长批 V2〕", _db.crossrun.feats.row_by_id("arcane_ember").is_empty() and _db.crossrun.feats.row_by_id("magma_body").is_empty())
|
||||||
Kit.check("迁出收尾:treasure_items 无 venom_edge/seal_burst 行(同调目录自然收窄)〔专长批 V13〕", _db.crossrun.treasure_items.row_by_id("venom_edge").is_empty() and _db.crossrun.treasure_items.row_by_id("seal_burst").is_empty())
|
Kit.check("迁出收尾:treasure_items 无 venom_edge/seal_burst 行(同调目录自然收窄)〔专长批 V13〕", _db.crossrun.treasure_items.row_by_id("venom_edge").is_empty() and _db.crossrun.treasure_items.row_by_id("seal_burst").is_empty())
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ func _init() -> void:
|
|||||||
# ① 词表在册断言(扩签面:2 事件+8 事实+7 效果类型+1 算子)
|
# ① 词表在册断言(扩签面:2 事件+8 事实+7 效果类型+1 算子)
|
||||||
Kit.check_quiet(EffectEngineScript.EVENTS.has("dig_stop") and EffectEngineScript.EVENTS.has("battle_won"),
|
Kit.check_quiet(EffectEngineScript.EVENTS.has("dig_stop") and EffectEngineScript.EVENTS.has("battle_won"),
|
||||||
"新事件在册:dig_stop/battle_won〔专长批 V1〕")
|
"新事件在册:dig_stop/battle_won〔专长批 V1〕")
|
||||||
|
# ①-a 换图系两事件两效果在册(迭代 46 扩签——旧出域负例全部保留)
|
||||||
|
Kit.check_quiet(EffectEngineScript.EVENTS.has("refresh_request") and EffectEngineScript.EVENTS.has("board_refreshed"),
|
||||||
|
"换图系新事件在册:refresh_request/board_refreshed〔换图系批 V1〕")
|
||||||
|
Kit.check_quiet(EffectEngineScript.EFFECT_KINDS.has("grant_reserve_ap") and EffectEngineScript.EFFECT_KINDS.has("apply_discount"),
|
||||||
|
"换图系新效果在册:grant_reserve_ap/apply_discount〔换图系批 V1〕")
|
||||||
Kit.check_quiet(EffectEngineScript.FACTS.has("hp_ratio") and EffectEngineScript.FACTS.has("is_edge_cell")
|
Kit.check_quiet(EffectEngineScript.FACTS.has("hp_ratio") and EffectEngineScript.FACTS.has("is_edge_cell")
|
||||||
and EffectEngineScript.FACTS.has("new_resource_type") and EffectEngineScript.FACTS.has("dug_under_seal")
|
and EffectEngineScript.FACTS.has("new_resource_type") and EffectEngineScript.FACTS.has("dug_under_seal")
|
||||||
and EffectEngineScript.FACTS.has("ap_exhausted_last") and EffectEngineScript.FACTS.has("ap_left")
|
and EffectEngineScript.FACTS.has("ap_exhausted_last") and EffectEngineScript.FACTS.has("ap_left")
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ func _test_negative_cases() -> void:
|
|||||||
# 现行 31;本套件零扩签,钉现行数(将来任何加词即转红提示复核)。
|
# 现行 31;本套件零扩签,钉现行数(将来任何加词即转红提示复核)。
|
||||||
|
|
||||||
func _test_effect_kinds_closed() -> void:
|
func _test_effect_kinds_closed() -> void:
|
||||||
Kit.check("效果引擎效果类型闭集 32 种(锻造工坊批零新增口径随迭代 31 卡② 扩签 forgive_seal_miss 换代)〔锻造工坊批 V11〕",
|
Kit.check("效果引擎效果类型闭集 34 种(迭代 46 换图系扩签 grant_reserve_ap/apply_discount 换代)〔锻造工坊批 V11〕",
|
||||||
EffectEngineScript.EFFECT_KINDS.size() == 32)
|
EffectEngineScript.EFFECT_KINDS.size() == 34)
|
||||||
Kit.check("效果引擎事件词表在册 20 种(本批零扩签参照面)〔锻造工坊批 V11〕",
|
Kit.check("效果引擎事件词表在册 22 种(迭代 46 换图系扩签 refresh_request/board_refreshed 换代)〔锻造工坊批 V11〕",
|
||||||
EffectEngineScript.EVENTS.size() == 20)
|
EffectEngineScript.EVENTS.size() == 22)
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||||
# 战斗一屏样张批 V5–V9 断言面(2026-10-29 迭代 43)——五项实装定值的真实入口断言:
|
# 战斗一屏样张批 V5–V12 断言面(2026-10-29 迭代 43 立五项;2026-10-31 迭代 46 卡B 增 V10–V12)——
|
||||||
|
# 实装定值的真实入口断言:
|
||||||
# V5 血条填充 = 危险红(UiKit.HP_BAR_FILL 值单点 + 绘制点源码锚:全仓零 #689CE8 与零裸
|
# V5 血条填充 = 危险红(UiKit.HP_BAR_FILL 值单点 + 绘制点源码锚:全仓零 #689CE8 与零裸
|
||||||
# Color(0.41,0.61,0.91);条底收 HP_BAR_TRACK 常量);
|
# Color(0.41,0.61,0.91);条底收 HP_BAR_TRACK 常量);
|
||||||
# V6 站桩名字行垫 HP_BAR_BACK 素底且不压地台(名字行基线 y < 地台顶 y——name_line_probe 几何出口);
|
# V6 站桩名字行垫 HP_BAR_BACK 素底且不压地台(名字行基线 y < 地台顶 y——name_line_probe 几何出口);
|
||||||
# V7 顺位徽标提档 UiKit.FS_HINT(值断言 + 绘制点源码锚,§7.4 清单外 11–13px 提档);
|
# V7 顺位徽标提档 UiKit.FS_HINT(值断言 + 绘制点源码锚,§7.4 清单外 11–13px 提档);
|
||||||
# V8 护甲图标三键(light/ward/heavy)绑对应 24×24 PNG(ResourceLoader 装载结果 + name_line_probe
|
# V8 护甲图标三键(light/ward/heavy)绑对应 24×24 PNG(ResourceLoader 装载结果 + name_line_probe
|
||||||
# 绑定面);
|
# 绑定面);
|
||||||
# V9 地台件 scene_stage_plaza.png + 护甲三枚入包可加载且尺寸 48×12 / 24×24(§5.2/§5.3 工单)。
|
# V9 地台件 scene_stage_plaza.png + 护甲三枚入包可加载且尺寸 48×12 / 24×24(§5.2/§5.3 工单);
|
||||||
|
# V10 名字行提档 FS_HINT 14(探针 name_font_size 实读 + 两绘制点源码锚 + FS_CAPTION 计数锚==1);
|
||||||
|
# V11 退役常量不在册 + 掩尘 BRICK 逐位 + 顶缘线/口沿高光两绘制点改指源码锚;
|
||||||
|
# V12 PLAZA_EDGE 逐位 #4C4038 + 回退描边改指源码锚。
|
||||||
# headless 无渲染面:像素采样以常量单点 + 源码锚 + 探针几何承载(观测面注记,验收对照表 V5 行)。
|
# headless 无渲染面:像素采样以常量单点 + 源码锚 + 探针几何承载(观测面注记,验收对照表 V5 行)。
|
||||||
# 运行:godot --headless --path game --script res://tests/unit/presentation/standee_present_values_test.gd(expect 0)
|
# 运行:godot --headless --path game --script res://tests/unit/presentation/standee_present_values_test.gd(expect 0)
|
||||||
extends SceneTree
|
extends SceneTree
|
||||||
@@ -48,14 +52,39 @@ func _init() -> void:
|
|||||||
Rect2(nl.plate).end.y <= float(nl.plinth_top_y) + 0.01)
|
Rect2(nl.plate).end.y <= float(nl.plinth_top_y) + 0.01)
|
||||||
Kit.check("名字行素底 = HP_BAR_BACK 深档 #282220(挂载底色口径)〔样张批 V6〕",
|
Kit.check("名字行素底 = HP_BAR_BACK 深档 #282220(挂载底色口径)〔样张批 V6〕",
|
||||||
Color(nl.plate_color).to_html(false) == "282220")
|
Color(nl.plate_color).to_html(false) == "282220")
|
||||||
Kit.check("基线读数与实装几何同源(基线 = 地台顶上方 9px 档:149 < 158)〔样张批 V6〕",
|
Kit.check("基线读数与实装几何同源(基线 = 地台顶上方 8px 档:150 < 158——14px 档牌顶+18)〔样张批 V6〕",
|
||||||
absf(float(nl.baseline_y) - 149.0) < 0.51 and absf(float(nl.plinth_top_y) - 158.0) < 0.51)
|
absf(float(nl.baseline_y) - 150.0) < 0.51 and absf(float(nl.plinth_top_y) - 158.0) < 0.51)
|
||||||
|
|
||||||
# ---- V7 顺位徽标提档 FS_HINT ----
|
# ---- V7 顺位徽标提档 FS_HINT ----
|
||||||
Kit.check("FS_HINT = 14(§7.4 六档)〔样张批 V7〕", UiKit.FS_HINT == 14)
|
Kit.check("FS_HINT = 14(§7.4 六档)〔样张批 V7〕", UiKit.FS_HINT == 14)
|
||||||
Kit.check("顺位徽标绘制点取 UiKit.FS_HINT(源码锚)〔样张批 V7〕",
|
Kit.check("顺位徽标绘制点取 UiKit.FS_HINT(源码锚)〔样张批 V7〕",
|
||||||
body_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT"))
|
body_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT"))
|
||||||
|
|
||||||
|
# ---- V10 名字行提档 FS_HINT 14(迭代 46 卡B——五取值点+基线随档联动) ----
|
||||||
|
Kit.check("名字行探针字号实读 = FS_HINT(几何单点出口 name_font_size)〔名字行 V10〕",
|
||||||
|
int(nl.name_font_size) == UiKit.FS_HINT)
|
||||||
|
Kit.check("名字与护甲两绘制点取 FS_HINT(源码锚——沿 V7 先例)〔名字行 V10〕",
|
||||||
|
body_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, UiKit.WARM")
|
||||||
|
and body_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, col"))
|
||||||
|
Kit.check("全文件 FS_CAPTION 仅剩消散提示一处(计数锚==1——三处宽度测量点强制换代)〔名字行 V10〕",
|
||||||
|
body_src.count("UiKit.FS_CAPTION") == 1)
|
||||||
|
|
||||||
|
# ---- V11/V12 地台色合并(迭代 46 卡B——常量面载体=Script 常量表,源码锚正证改指目标色) ----
|
||||||
|
var uikit_script: Script = load("res://presentation/views/ui_kit.gd")
|
||||||
|
var cmap: Dictionary = uikit_script.get_script_constant_map()
|
||||||
|
var cell_src := _read_source("res://presentation/views/cell_base_layer.gd")
|
||||||
|
Kit.check("退役常量不在册(STAND_STONE_RIM/STAND_STONE_EDGE 零键)〔名字行 V11〕",
|
||||||
|
not cmap.has("STAND_STONE_RIM") and not cmap.has("STAND_STONE_EDGE"))
|
||||||
|
Kit.check("掩尘 BRICK 值逐位 #C9AF83(合并目标①)〔名字行 V11〕",
|
||||||
|
UiKit.BRICK.to_html(false) == "c9af83")
|
||||||
|
Kit.check("顶缘受光线与口沿高光两绘制点改指 BRICK(源码锚)〔名字行 V11〕",
|
||||||
|
body_src.contains("ped.size.x, 2.0), UiKit.BRICK, true")
|
||||||
|
and cell_src.contains("FACADE_HIGHLIGHT_PX), UiKit.BRICK, true"))
|
||||||
|
Kit.check("PLAZA_EDGE 值逐位 #4C4038(合并目标②)〔名字行 V12〕",
|
||||||
|
UiKit.PLAZA_EDGE.to_html(false) == "4c4038")
|
||||||
|
Kit.check("回退描边绘制点改指 PLAZA_EDGE(源码锚)〔名字行 V12〕",
|
||||||
|
body_src.contains("UiKit.PLAZA_EDGE"))
|
||||||
|
|
||||||
# ---- V8 护甲图标三键绑 24×24 PNG ----
|
# ---- V8 护甲图标三键绑 24×24 PNG ----
|
||||||
var armor_paths := {"light": UiKit.TEX_ARMOR_LIGHT, "ward": UiKit.TEX_ARMOR_WARD,
|
var armor_paths := {"light": UiKit.TEX_ARMOR_LIGHT, "ward": UiKit.TEX_ARMOR_WARD,
|
||||||
"heavy": UiKit.TEX_ARMOR_HEAVY}
|
"heavy": UiKit.TEX_ARMOR_HEAVY}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# 迭代 46 GDD 反哺点清单(2026-10-31)
|
||||||
|
|
||||||
|
> 接收方:待办清单「反哺点专条」通道——用户会话逐条裁决后由文档批写入 `design/`,写入后本清单条目清掉(吸收即死)。
|
||||||
|
> 依据:迭代 46 实现报告(同目录)与实测评读数。实现批禁写 `design/`,以下为待裁决落文面。
|
||||||
|
|
||||||
|
## 卡A 换图系双基座七件批
|
||||||
|
|
||||||
|
1. **[奇物.md]** 基础件表(或专长实装状态句):七件(矿脉储能/省力周转/崩壁冲击/蓄力周转/石层缓冲/坑道小憩/矿道红利)由设计声明改为**已实装**(feats.tres 37 行、稀有度 14/13/10/0、cap 均 1);「双基座叠加、后续效果本轮五段结算内兑付」已在案零改述。依据=实现报告 §二 V2–V11。
|
||||||
|
2. **[行动经济.md]** 两件基座由「待实现」改为在册:付费挖格 +1 RAP(受理后一次性入池、拒绝轮零入池)、刷新成本折价 50%(floor 取整、can 与执行同源、只改门槛与扣费不改拒绝判据)。依据=V2/V3。
|
||||||
|
3. **[战斗结算.md]** 全局 `damage_taken` 落码状态补记:敌方攻击/地形/危险格/错误封印资源直伤四路径统一经唯一发射口与唯一消费族;**地形路径消费面自三件扩为五件(+shield)——铁壁自此对地形伤害生效;危险格/错误封印直伤路径自此纳入 damage_taken 订阅面**(用户已裁全局语义,实现报告 §六);成功刷新事实 `board_refreshed` 为七件订阅主挂点(装新板后恰一次发射)。依据=V5/V4。
|
||||||
|
4. **[地图战斗资源.md]** 清图奖励语义确认句:矿道红利不绑清图奖励(每次成功刷新固定 +1 GP 走战斗内缓冲,不读旧图探索进度、不触发清图事件;刷新图零清图奖励守卫不变)——若现行正文已含此意则零改(本行核对性反哺)。依据=V10/V12。
|
||||||
|
5. **[architecture.md]** 「模块归属」增补/现状化三行:①**BattleRefreshFlow**(新 B1 域拆件——刷新可用性门/折价扣费/换图执行/成功事实登记,调用路径呈现层→编排器单行转发→流程件→行动经济/装板壳/授予段;static_scan S7_B1_SET 已随批增补该文件,判据口径不变);②**damage_controller** 行现状化(damage_taken 唯一发射口 `_emit_hero_damage_taken`+唯一消费族 `_apply_hero_damage_effects`(原名 _enemy_shield_apply 改名)+board_flat_hero 结账前统一发射);③**action_economy** 行现状化(dig_paid 命中施加抽私有 `_apply_dig_paid_hits` 单点;request_refresh 收折后价入参)+**grant_segment** 行现状化(apply_board_refreshed 成功事实消费+round_end heal value_counter 兑付面)。依据=实现报告 §五/§八.6。
|
||||||
|
|
||||||
|
## 卡B 站桩名字行提档与地台色合并批
|
||||||
|
|
||||||
|
6. **[art-bible.md §4.1]** 换色合并登记①②句的代码指称换代:①「代码 `STAND_STONE_RIM`」②「代码 `STAND_STONE_EDGE`」两源常量已删除(全仓零引用——唯一残留=退役断言测试串,预期例外);②合并目标 `#4C4038` 补代码名 **`PLAZA_EDGE`**(ui_kit 唯一落点)。依据=卡B 对照表 3/4 行+grep 取证(实现报告 §三)。
|
||||||
|
7. **[art-bible.md §8.2]** 高墙瓦片体系条口沿高光行:「Color8(196,172,132)」值随换值换代为 **`#C9AF83`(201,175,131)**(并档掩尘枚 BRICK 单点);程序化回退顶缘线与口沿高光两绘制点已改指。依据=对照表 3 行。
|
||||||
|
|
||||||
|
## 卡C 棋盘格边长口径收口批(停摆——随裁决落文)
|
||||||
|
|
||||||
|
8. **[battle.md §棋盘格尺寸公式]** 算例表(:148-152)与实机偏差实测落点:1280 档实板可用面积 **(1022, 454)**(算例假设 486 可用高)→ 实测 L1 格边 40px、13×13 推定 30px(恰达标)、**14×14 推定 28px(低于可读下限 30)**;1080 档 (1662, 734) → L1=46、13/14 推定=46(钳制顶格)。落文方向随「30px 下限适用域」裁决(读法甲/乙见停机呈报 §三):读法乙需补「下限按视口档分级」条+算例表填实测值或标注「按旧布局口径」。依据=停机呈报 §一/§四。
|
||||||
|
|
||||||
|
## 术语表
|
||||||
|
|
||||||
|
9. 零新增术语——本批新名(BattleRefreshFlow/_cell_for/PLAZA_EDGE/counter_id 三枚)均属代码标识符与数据行键,不入术语表(登记口径=概念规范名)。
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# 迭代 46 实现报告(2026-10-31)
|
||||||
|
|
||||||
|
> 批次:迭代 46 三卡——卡A 换图系双基座七件批 · 卡B 站桩名字行提档与地台色合并批 · 卡C 棋盘格边长口径收口批。
|
||||||
|
> 工作区:`D:\code\g-plan2-iter46`(分支 iter46,自 b5b2c555 检出)。
|
||||||
|
> **批内状态总览:卡A 收口 · 卡B 收口 · 卡C 停摆(L3 #2 预设停机点,呈报见主区 `reports/停机呈报-棋盘格边长口径收口批-2026-10-31.md`)**。合并阻塞等裁决。
|
||||||
|
|
||||||
|
## 一、各卡状态
|
||||||
|
|
||||||
|
| 卡 | 状态 | 出口证据 |
|
||||||
|
|---|---|---|
|
||||||
|
| 卡A 换图系双基座七件批 | **收口**(T0–T8 全落) | `tools/run_all_gates.sh --code` 7 闸全绿(回执 commit 45b53d68 时点);此后 S9/S7 修正与卡B 提交待迭代级复跑 |
|
||||||
|
| 卡B 站桩名字行提档与地台色合并批 | **收口**(T0–T3 全落) | standee_present_values_test 26 checks 绿;golden 板面三张 0.00% 留痕;gdstyle 零新增键 |
|
||||||
|
| 卡C 棋盘格边长口径收口批 | **停摆**(T0 完成;T1–T3 改动已验未提交;T4 未走) | 卡C §9 L3 #2:实板可用面积代入 14×14 得 28px < 30px(1280 档)——停卡呈报等裁决 |
|
||||||
|
|
||||||
|
## 二、卡A 实际对照表(改动—验收)
|
||||||
|
|
||||||
|
| 需求点 | 改动(文件:行为) | 验收测试(名称+落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 结果 |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| V1 词表扩签 | effect_engine.gd EVENTS 20→22/MINE_EVENTS 同步换图刷新面/EFFECT_KINDS 32→34;forge_table/effect_words 计数换代 | `effect_words_t1_test.gd:_init`(新增①-a 两断言);`forge_table_test.gd:_test_effect_kinds_closed` | 单元 | validate 与整表加载 | EVENTS=22、EFFECT_KINDS=34、出域仍拒(旧负例全保留) | 绿(20/41 checks) |
|
||||||
|
| V2 矿脉储能 | action_economy.gd dig_paid 命中环累计 grant_reserve_ap(扣费受理后一次性入池·拒绝轮零入池);feats.tres vein_reserve 行 | `refresh_feats_test.gd:test_reserve_on_accepted_dig` | 接口 | `settle_batch(dig)`(AutoPlayer 真实挖掘) | RAP 恰+1(8→9)、AP 按表扣(8→7)、耗尽轮增量==受理挖数 | 绿 |
|
||||||
|
| V3 省力周转 | BattleRefreshFlow `_effective_refresh_cost`(声明序第一命中 floor(base×mult/100),can 与执行同源);request_refresh 收折后价入参;easy_swap 行 | `test_refresh_discount_gate_and_charge`;`test_double_base_stack` | 接口 | `player_request_refresh()` | base 8→扣 4;两次折后扣尽 8→0;门槛拒零受理;双基座同场生效 | 绿 |
|
||||||
|
| V4 成功事实 | battle_refresh_flow execute 成功序(折价扣RAP→used+1→旧资源退役→旧板退役→装新板→apply_board_refreshed);编排器两公开方法单行转发 | `test_board_refresh_fact_only_on_success` | 集成 | `player_request_refresh()` | 非 Active/每轮上限/RAP 不足三拒面零事实零换图(probe_br_counter 计数+board_changed 双观测);成功恰一次 | 绿 |
|
||||||
|
| V5 全局受伤 | damage_controller `_emit_hero_damage_taken` 唯一发射口+`_apply_hero_damage_effects` 唯一消费族(改名自 _enemy_shield_apply,组合次序冻结);敌方攻击/棋盘直伤/地形三路径接入;grant_segment 删本地 _terrain_damage_reduce | `test_all_hero_damage_paths_emit_once`;`test_no_duplicate_damage_taken_event` | 集成 | 敌方攻击、dig danger、seal wrong、terrain 四真实入口 | 每路径承伤单据在案且计数器==行数(逐单恰一次);context 标记闸防地形二次发射 | 绿 |
|
||||||
|
| V6 石层缓冲 | round_swap_guard 计数+damage_taken when≥1 damage_reduce 2(round 桶轮始自清) | `test_stone_mitigation_all_damage` | 集成 | 刷新→危险格(pierce 8)/地形两真实受伤路径 | pierce 8→6(双刷不叠加)、未刷新轮不减(8)、地形 max(下限, effect_value−2) 逐单闭合 | 绿 |
|
||||||
|
| V7 崩壁冲击 | apply_board_refreshed open_ticket blast→reward_grant.defer_ticket(案C 同队列);SOURCE_EXEMPT_CLASSES+fx_board_refresh;wall_blast 行 | `test_blast_defers_to_combat_segment` | 集成 | 刷新→停手 | 刷新时点零伤害行;战斗段末恰一单 ×2 全体存活敌 | 绿 |
|
||||||
|
| V8 蓄力周转 | swap_charge_layers 战斗桶+attack_bonus value_counter+set0(既有消费面零新码) | `test_charge_layers_consumed_same_round` | 接口 | 两次刷新→停手(主角先手攻击) | 双刷 2 层→攻击后 0;白盒预置 3 层时刷新被闸(上限 3);战斗新建 0 | 绿 |
|
||||||
|
| V9 坑道小憩 | round_swap_rest+round_end heal value_counter(grant_segment 新增 `_round_end_heal_value_counter`——先于 set0 行消费层值) | `test_rest_rotation_cap_and_clear` | 集成 | 刷新→停手(轮末授予段) | 回复 min(3,缺血)==3、层清 0、满血发未生效行、自然累计 2 路径 | 绿 |
|
||||||
|
| V10 矿道红利 | board_refreshed grant_gp→escrow+fx_grant_gp 行(apply_board_refreshed 消费);mine_profit 行 | `test_mine_profit_gp` | 集成 | `player_request_refresh()` | escrow 每成功+1(0→1→2)、fx_grant_gp 行在案、拒绝零变化 | 绿 |
|
||||||
|
| V11 数据与池 | feats.tres 七行(rarity 14/13/10/0·cap 1);adventure_feats 30→37/牌库 132→139 | `test_seven_feats_validate_and_pool`;adventure_feats `_smoke_feats` | 单元 | ConfigDB 整表与冒险牌库 | rows 37、relic 37、14/13/10、deck 139、validate 空、禁词 0、七件计数器引用闭合 | 绿(85/138 checks) |
|
||||||
|
| V12 无专长回归 | round_flow/seal 旧基线零改动(刷新体迁移编排器单行转发) | `_smoke_active_refresh`;`_smoke_refresh_provenance_guard`;`test_no_feats_baseline` | 单元+集成 | 现行刷新/清图入口 | RAP8、AP 保留、上限 2、刷新图零清图、挖格零产 RAP、表基线扣费 | 绿(134/70 checks) |
|
||||||
|
|
||||||
|
**窄测实跑记录**:refresh_feats 85 · adventure_feats 138 · round_flow 134 · seal_polarity 70 · damage_controller 44 · effect_words 20 · forge_table 41 · config_db 29——全绿。批末 `--code` 闸 00/01/02/03/05/09/10 全绿(回执 45b53d68)。
|
||||||
|
|
||||||
|
## 三、卡B 实际对照表(改动—验收)
|
||||||
|
|
||||||
|
| # | 需求点 | 改动 | 验收测试 | 结果 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 1 | 名字行字号提档 FS_HINT 14 | standee_body.gd 五取值点+name_line_probe 增 name_font_size | 〔名字行 V10〕探针实读==FS_HINT+两绘制点源码锚+FS_CAPTION 计数锚==1 | 绿 |
|
||||||
|
| 2 | 名字行几何随档联动 | _name_baseline 偏移 17→18+注释换代 | V6 换代读数 150±0.51(8px 档)且 < plinth_top_y | 绿 |
|
||||||
|
| 3 | 受光线常量退役改指 BRICK | ui_kit 删 STAND_STONE_RIM;standee_body:386+cell_base_layer:176 改指 | 〔名字行 V11〕常量表零键+BRICK==#C9AF83+两绘制点源码锚;grep 反证(仅剩测试断言串——预期例外) | 绿 |
|
||||||
|
| 4 | 描边常量退役改指新 token | ui_kit 删 STAND_STONE_EDGE+增 PLAZA_EDGE=Color8(76,64,56);standee_body:385 改指 | 〔名字行 V12〕PLAZA_EDGE 逐位+常量表零旧键+描边源码锚 | 绿 |
|
||||||
|
| 5 | 板面 golden 与换值一致 | (预期重锚——实际无需) | render_gate 板面三张 0.00%(改后跑,基线未动) | 绿(见实现决定 #4) |
|
||||||
|
| 6 | 批末收口 | 全批 | 迭代级 --code 闸(待卡C 裁决后统一走) | 待迭代出口 |
|
||||||
|
|
||||||
|
卡B 改前读数(T0):standee_present_values_test 18 checks 绿 · gdstyle 5 键全基线内 · 渲染闸绿(--code 回执 45b53d68 内 05 闸)· 字体度量 ts(14px)=(14,20)。
|
||||||
|
|
||||||
|
## 四、卡C 停摆实况(改动已验未提交)
|
||||||
|
|
||||||
|
- T0 完成:行号重锚(board_view set_cell_area 现 :206-224、layout_probe MIN_CELL :15、冒烟 :53-69);取档对账 resolve_row(1,0)/(2,0)/(3,0)/(3,9)=10/12/13/14 全符;改前读数 board_gen_reward_test 62 绿+layout_probe 两档绿+gdstyle board_view max-public-methods 27 持平。
|
||||||
|
- T1 算式单点:`BoardView._cell_for(n, avail)` 提取(逐位同式)+set_cell_area 改调——等价性由 board_gen_reward_test 既有断言承载(62 checks 绿)+gdstyle 持平。
|
||||||
|
- T2 探针:MIN_CELL 36→30+注释改指 battle.md+BoardViewScript preload+零尺寸前置+13/14 推定断言——**推定断言触发 L3 #2(14×14=28<30)**。
|
||||||
|
- T3 冒烟四档:取样表 [{1,0,10},{2,0,10→12},{3,0,13},{3,9,14}]+板型一致断言+算式单点等价断言——74 checks 绿(单元面全过:钳制 [26,46] 面无越界)。
|
||||||
|
- 三件改动**未提交**(停摆卡不提交),随裁决处置(可复用——行为零变化)。
|
||||||
|
- 停机呈报:主区 `reports/停机呈报-棋盘格边长口径收口批-2026-10-31.md`(分歧读法/建议案/待办回退拟稿)。
|
||||||
|
|
||||||
|
## 五、结构预算(S13 前后)
|
||||||
|
|
||||||
|
| 触面 | 前测 | 后测 | 判定 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| battle_orchestrator.gd | 811 行/类变量 15(卡A 写 38 系笔误) | 803 行(−8 达标)/类变量 15 | 只降不升 ✓ |
|
||||||
|
| reward_grant.gd | 类变量 15 | 15(defer_ticket 为方法零增员) | ✓ |
|
||||||
|
| grant_segment.gd | 类变量 3/公开方法 1 | 3/2(apply_board_refreshed);私有新增 1(_round_end_heal_value_counter)≤4 | ✓ |
|
||||||
|
| damage_controller.gd | 公开方法 9 | 9(私有净增 1:_emit_hero_damage_taken)≤2 | ✓ |
|
||||||
|
| action_economy.gd | 公开方法 15 | 15(命中环抽私有 _apply_dig_paid_hits——S13 分支超限重构,分支本体逐字迁入) | ✓ |
|
||||||
|
| battle_refresh_flow.gd(新件) | — | 公开函数 2(can_refresh/execute)/类变量 0 | ≤20/≤15 ✓ |
|
||||||
|
| gdstyle_gate | 5 键全基线内 | 5 键全基线内零新增零恶化 | ✓ |
|
||||||
|
|
||||||
|
## 六、旧 damage_taken 专长覆盖面变化
|
||||||
|
|
||||||
|
全局统一后,订阅 `damage_taken` 的既有专长(背水 hp_ratio when/石肤 ac_bonus+counter_add/保险丝 counter_add)与换图系石层缓冲的覆盖面变化:
|
||||||
|
|
||||||
|
- **敌方攻击路径**:不变(原即经 _enemy_shield_apply 全组合消费)。
|
||||||
|
- **地形路径**:消费族从本地三件(damage_reduce/ac_bonus/counter_add)扩为全局五件(+shield)——**铁壁(shield)自此对地形伤害生效**(用户已裁全局语义;卡A §十二削弱申报第 2 条在案)。
|
||||||
|
- **危险格/错误封印资源直伤路径(board_flat_hero)**:原零事件,现统一发射——上述专长自此对这些路径生效(同上申报面)。
|
||||||
|
- 无专长基线逐位零漂移(V12 全绿;同一伤害恰一次由 V5 钉死)。
|
||||||
|
|
||||||
|
## 七、实机目检总清单(登记不阻塞)
|
||||||
|
|
||||||
|
1. 〔卡A〕玩家持基座与收益件后,是否主动规划刷新而非只在坏图刷新(行为观感)。
|
||||||
|
2. 〔卡B〕站桩名字行 14px 清晰可读且长名不截字(验收底线句)。
|
||||||
|
3. 〔卡B〕墙格口沿亮线换值后观感无异(#C4AC84→#C9AF83)。
|
||||||
|
4. 〔卡B〕贴图缺位回退描边与顶缘线观感无异(PLAZA_EDGE/BRICK 程序化回退)。
|
||||||
|
5. 〔卡C·停摆批登记〕第三关 14×14 板格面数字在基座档的可辨读性(28px 实况——随裁决)。
|
||||||
|
|
||||||
|
## 八、实现决定记录(超白名单/偏差备案)
|
||||||
|
|
||||||
|
1. **卡A 自洽扫描笔误三处(不改合同)**:「类变量 38」实为 15;gdstyle_baseline.txt:9 引用实无 battle_orchestrator 键(该文件现无 S13 键);稀有度断言实挂 :476 非 :465。T0 按实数重锚记录,合同(只降不升/换代判据)不受影响。
|
||||||
|
2. **卡A T0 工具名**:卡写 `bash tools/gdstyle.sh game`——该文件不存在,实际工具 `python tools/gdstyle_gate.py`(与卡B/C 一致),按实跑。
|
||||||
|
3. **MINE_EVENTS 同步口径**:卡A T1「同步 :19 的 MINE_EVENTS」取「两新事件入集」读法——弃图刷新属扫雷侧动作面,流派铁律订阅集随之认列(零机械消费面差异,mine_covered 生产无强制点)。
|
||||||
|
4. **卡B golden 预期红未现**:卡预期「板面三张红→重锚」,实测三张 0.00%——口沿高光换值 #C4AC84→#C9AF83 每通道差 ≤5,低于逐像素容差(≤16 差/≤2%),golden 判定零变化;重锚步骤无需执行(基线未动、零差留痕达成 V5 判据;禁借重锚夹带红线零触碰)。
|
||||||
|
5. **S9 修正**:四处显式非参数化容器注解改 `:=` 推断(R1)——零行为改动。
|
||||||
|
6. **S7 域集增补**:`static_scan.sh` S7_B1_SET 增补 battle_refresh_flow.gd——换图刷新流程件=卡A C13 拆分边界的 B1 域拆件(承载 retire_mapping/装板编排调用,卡A §八明列新结算族件);判据口径不变(B2/B3/B4 对外 API 仍限 B1 域调用)。**architecture.md「模块归属」需补 BattleRefreshFlow 行——已入反哺点清单**。
|
||||||
|
7. **V11 引用闭合判据域**:全表扫描发现预存行完美保险 when 读 insurance_used 无 counter_add 写入方(消费点落账合法形态——feats.tres 注记在案)——判据域收窄为「换图系七件自身引用闭合」,预存形态不入判据。
|
||||||
|
8. **apply_board_refreshed 访问形态**:BattleRefreshFlow 经 `oc._rounds._grant_seg` 直调(round_cycle 不在卡A §八改动文件清单内,不加转发壳——沿结算族直调惯例)。
|
||||||
|
9. **卡A T8「代拍板追认单」不另立**:iter 技能与 process-conventions §2 定「超白名单记实现报告实现决定记录,不另立追认单」——本批全部决定在上列备案,白名单内决定零追认面。
|
||||||
|
10. **测试断言口径修正(开发期自纠)**:V5②③④ 聚合判据从「两来源行合计」修正为「全 hero 行数」(盲挖途经的其他直伤单据一并入账);V2 辅证改「RAP 增量==受理挖数」(移位耗 AP 不入账);V4 时序修正(停手再入 RAP)——均为测试侧修正,生产码零回退。
|
||||||
|
11. **V9 上限 3 承载形态**:每轮刷新上限 2(表值)使自然路径层值至多 2——「上限 3」判据由白盒计数器预置+真实刷新入口承载(test-conventions §5.4 计数器直改属数据面注入),自然累计 2 另设战场钉死。
|
||||||
|
|
||||||
|
## 九、独立验收审计结论
|
||||||
|
|
||||||
|
- **审计方**:未参与实现的子代理(独立会话上下文),2026-10-31 执行。
|
||||||
|
- **报告**:`reports/iter46-验收审计-2026-10-31.md`(本目录)。
|
||||||
|
- **结论**:**审计通过(零缺漏)**——卡A V1–V12 与卡B 6 行对照表逐行通过;尾缀全可 grep;真实入口零绕过(V2 真挖/V3·V4·V10 经 player_request_refresh/V5 四路径真实战斗驱动);自跑复核 refresh_feats 85/standee 26/adventure_feats 138/round_flow 134/seal_polarity 70/effect_words 20/forge_table 41 全绿+gdstyle 5 键零新增+render_gate 三张 0.00% 实跑;削弱核对全部落在两卡申报面内(round_flow/seal 零进 diff,禁删面原样在案);无变异/撤销类取证。
|
||||||
|
- **审计附加发现处置**:①两枚新件 .uid 边车未随批提交——已补提交(.gitignore 要求);②static_scan S8_FILES 与 S7_B1_SET 未同步增补新件——已同步(新件零 await,扫描面实跑绿);③V10 断言消息在求值前格式化的显示瑕疵(判据本体正确,零处置);④V9 私有读前置守卫附注(审计方判例外不阻塞——判据主面走公共 step_row 信号)。
|
||||||
|
|
||||||
|
## 十、遗留读数义务
|
||||||
|
|
||||||
|
- 卡C 裁决后:探针两档 bv.size/L1 格边/13·14 推定读数已录本报告第四节与停机呈报——重写卡可直接引用,无需重采。
|
||||||
|
- 无 MC/数值验证义务(本批无数值改动,feats.tres 七行为卡内 provisional 值——归调参批)。
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# 迭代 46 独立验收审计报告
|
||||||
|
|
||||||
|
> 审计方:独立验收审计子代理(未参与本批实现——迭代 loop 内启动,符合 test-conventions §8.7 两入口铁规)。
|
||||||
|
> 日期:2026-10-31。
|
||||||
|
> 工作区:`D:\code\g-plan2-iter46`(分支 iter46,基点 b5b2c555)。Godot:`Godot_v4.7.2-stable_win64_console.exe`。
|
||||||
|
> 审计范围:合并候选=卡A(换图系双基座七件批)+卡B(站桩名字行提档与地台色合并批)已提交 diff=`git diff b5b2c555..c10bbc11`(10 笔提交=9 实现+1 收尾文档;20 文件,+1231/−96)。卡C(棋盘格边长)停摆不计。
|
||||||
|
> 审计方式:不采信实现报告「已全绿」自述——逐行核对对照表两端、自跑最窄套件与闸件、对照 diff 逐处核对削弱申报。本审计只写本报告,零源码/测试/数据文件改动。
|
||||||
|
|
||||||
|
## 一、审计自跑记录(当轮真实执行)
|
||||||
|
|
||||||
|
| 套件/闸 | 结果 | 与报告申报对照 |
|
||||||
|
|---|---|---|
|
||||||
|
| refresh_feats_test | **all green (85 checks)**,exit 0 | 与报告§二 申报 85 一致 |
|
||||||
|
| standee_present_values_test | **all green (26 checks)**,exit 0 | 与报告§三 申报 26 一致 |
|
||||||
|
| adventure_feats_test | **all green (138 checks)**,exit 0 | 与报告§二 申报 138 一致 |
|
||||||
|
| round_flow_skill_pool_test | **all green (134 checks)**,exit 0 | 与报告§二 申报 134 一致 |
|
||||||
|
| seal_polarity_test(附加) | all green (70 checks),exit 0 | 与报告§二 申报 70 一致 |
|
||||||
|
| effect_words_t1_test(附加,V1) | all green (20 checks),exit 0 | 与报告§二 申报 20 一致 |
|
||||||
|
| forge_table_test(附加,V1) | all green (41 checks),exit 0 | 与报告§二 申报 41 一致 |
|
||||||
|
| gdstyle_gate | 5 键全基线内、基线外新违规 0、恶化 0 | 与报告§五「零新增零恶化」一致 |
|
||||||
|
| static_scan.sh | exit 0——S7 ok(B2/B3/B4 符号收敛 B1 文件集)/S8 ok/S13 ok | S7 新域集生效见§六① |
|
||||||
|
| render_gate.sh(窗口化实跑) | 板面三张+menu_boot 全 **changed=0.00%**,全绿 | 与报告§八.4 一致,见§三行 5 |
|
||||||
|
|
||||||
|
## 二、卡A 对照表逐行核对(V1–V12)
|
||||||
|
|
||||||
|
| 需求点 | 测试存在 | 尾缀可 grep | 真实入口无绕过 | 判定 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| V1 词表扩签 | `effect_words_t1_test.gd:_init` 新增①-a 两断言+`forge_table_test.gd:_test_effect_kinds_closed` 22/34 换代 | 〔换图系批 V1〕×2+〔锻造工坊批 V11〕×2 | ConfigDB 整表加载+词表常量直读 | **通过**(实跑 20/41 checks 绿;diff 核对 effect_words 纯增量 5 行,旧出域负例零删) |
|
||||||
|
| V2 矿脉储能 | `test_reserve_on_accepted_dig` | 〔换图系批 V2〕×3 | 经 `_auto_dig`→`settle_batch([move]+[dig])` 真实付费挖格;辅证 `_drain_round_rap_accounting` 仍走 settle_batch 逐挖计数 | **通过**(RAP 8→9、AP 按表扣、耗尽轮增量==受理挖数三面全钉) |
|
||||||
|
| V3 省力周转 | `test_refresh_discount_gate_and_charge`+`test_double_base_stack` | 〔换图系批 V3〕×6 | 经 `orch.player_request_refresh()` 唯一入口;can 与执行同源(`_effective_refresh_cost` 单点,diff 核实编排器两公开方法单行转发) | **通过**(折后 −4×2、门槛拒 can=false 且执行拒、双基座同场生效) |
|
||||||
|
| V4 成功事实 | `test_board_refresh_fact_only_on_success` | 〔换图系批 V4〕×6 | 经 `player_request_refresh()`;非 Active 拒=decay 行内同步探测(round_flow_skill_pool_test:161 先例同款),上限拒/RAP 不足拒均在 Active 相位真入口 | **通过**(三拒面 probe_br_counter==0+board_changed 计数冻结双观测;成功恰一次×2+新轮×2) |
|
||||||
|
| V5 全局受伤 | `test_all_hero_damage_paths_emit_once`+`test_no_duplicate_damage_taken_event` | 〔换图系批 V5〕×8 | **四路径全真实战斗驱动、零直调 `_emit`**:①敌方攻击=boss 战零挖掘连停三轮(enemy_action 行在案);②地形=AutoPlayer 盲挖 flat_damage 变异格+stop;③危险格=真挖直接伤型雷格;④错误封印=真 `seal_place`+stop | **通过**(每路径「承伤行数==计数器」恰一次+无重复消费两样本;实跑读数 行2=计2/行9=计9/行2=计2/行2=计2) |
|
||||||
|
| V6 石层缓冲 | `test_stone_mitigation_all_damage` | 〔换图系批 V6〕×3 | 刷新经 `player_request_refresh`;危险格/地形经真挖+stop 结算 | **通过**(pierce 8→6 双刷不叠=叠加态 4 反证钉死;未刷轮不减=round 桶轮始清空;地形 max(下限, effect_value−2) 逐单闭合) |
|
||||||
|
| V7 崩壁冲击 | `test_blast_defers_to_combat_segment` | 〔换图系批 V7〕×2 | 刷新+停手(轮末授予段→战斗段末) | **通过**(刷新时点零伤害行=授予段不结账;停手后恰一单 value 2 全体存活敌) |
|
||||||
|
| V8 蓄力周转 | `test_charge_layers_consumed_same_round` | 〔换图系批 V8〕×2 | 双刷+停手(主角先手攻击)真实消费 | **通过**(白盒预置 3 层上限闸=实现决定 #11 申报面;双刷 2 层攻击后清 0;战斗新建 0) |
|
||||||
|
| V9 坑道小憩 | `test_rest_rotation_cap_and_clear` | 〔换图系批 V9〕×3 | 刷新经真入口;轮末恢复在授予段(stop 触发) | **通过(附未注例外,见§五)**(回复 min(3,缺血)==3 且层清 0;满血未生效行+零 heal 行;自然累计 2 路径) |
|
||||||
|
| V10 矿道红利 | `test_mine_profit_gp` | 〔换图系批 V10〕×4 | 经 `player_request_refresh()`;拒绝=RAP 不足真拒 | **通过**(escrow 每次+1、fx_grant_gp 行恰 1、拒绝零变化;消息文案时点瑕疵见§七.3,不涉判据) |
|
||||||
|
| V11 数据与池 | `test_seven_feats_validate_and_pool`+adventure_feats `_smoke_feats` | 〔换图系批 V11〕×5+〔专长批 V2〕换代句 | ConfigDB 整表+冒险牌库真实启动(seed 71) | **通过**(validate 空/七行稀有度 cap 对表/desc 禁词/七件计数器引用闭合/三计数器在册;rows 37、14/13/10/0、deck 139;feats.tres 七行 trigger 结构逐行对照卡§十.T6 零出入) |
|
||||||
|
| V12 无专长回归 | `test_no_feats_baseline`+round_flow `_smoke_active_refresh`+seal `_smoke_refresh_provenance_guard` | 〔换图系批 V12〕×4 | 挖格/刷新/停手全真入口 | **通过**(挖格零 RAP、表基线扣 8、AP 保留、第 3 次拒;round_flow/seal 两文件**零进 diff**——RAP8/上限2/AP保留/刷新图零清图/五段序/回放断言原样在案,134/70 checks 实跑绿) |
|
||||||
|
|
||||||
|
## 三、卡B 对照表逐行核对(6 行)
|
||||||
|
|
||||||
|
| # | 需求点 | 判定 |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | 名字行字号提档 FS_HINT 14 | **通过**——diff 核对五取值点(`:334-336` 两测量+`:467` 测量+`:474` 护甲绘制+`:476` 名字绘制)全改 `UiKit.FS_HINT`;`:114` 消散提示零触碰(FS_CAPTION 计数锚==1 断言在案且实跑绿);`name_line_probe` 增 `name_font_size` 键(探针实读==FS_HINT 绿) |
|
||||||
|
| 2 | 名字行几何随档联动 | **通过**——`_name_baseline` 偏移 17→18+注释换代(diff 核实);V6 读数 150±0.51 与「8px 档」句换代=卡B§8 申报第 1 条面内;`baseline_y < plinth_top_y` 文字不压地台判据零改动且绿 |
|
||||||
|
| 3 | 受光线常量退役改指 BRICK | **通过**——ui_kit 真删 `STAND_STONE_RIM` 无别名占位;`standee_body` 顶缘线与 `cell_base_layer:176` 口沿高光两绘制点改指 `BRICK`;V11=常量表零键+BRICK 逐位 c9af83+两处源码锚全绿;grep 全仓退役常量仅剩测试断言串(与报告§三「预期例外」申报一致) |
|
||||||
|
| 4 | 描边常量退役改指新 token | **通过**——ui_kit 删 `STAND_STONE_EDGE`+原位增 `PLAZA_EDGE := Color8(76, 64, 56)`;`standee_body` 回退描边改指;V12=PLAZA_EDGE 逐位 4c4038+源码锚全绿 |
|
||||||
|
| 5 | 板面 golden 与换值一致 | **通过**——审计方自跑 `bash tools/render_gate.sh`(窗口化):board_L1/L2/L3 与 menu_boot 全 0.00%;golden 五文件 git 零改动=基线未动、重锚未执行(与实现决定 #4 备案一致——#C4AC84→#C9AF83 灰度差约 2,低于容差 ≤16,0.00% 为必然);level_map.png 不在 render_gate 对比域、git 零改动 |
|
||||||
|
| 6 | 批末收口 | **通过(遗留义务明示)**——迭代级 `--code` 闸未跑(卡B 行 6 申报「待迭代出口」);批内证据=commit 点 docs 域回执(fde1795d)全绿含 08c 流水线闸+审计方补跑 gdstyle/static_scan/render_gate/五套件全绿。合回后本地 CI 全量域闸为唯一强制验证点(process-conventions §1),本行遗留义务随合回 CI 承接 |
|
||||||
|
|
||||||
|
## 四、抽查断言原样输出行(当轮实跑摘录)
|
||||||
|
|
||||||
|
```
|
||||||
|
[refresh_feats_test] all green (85 checks)
|
||||||
|
[PASS] V5① 敌方攻击路径:承伤单据在案且逐单恰一次(行 2=计数 2)〔换图系批 V5〕
|
||||||
|
[PASS] V5② 地形路径:地形单据在案且全单据逐张恰一次(行 9=计数 9——盲挖途经单据一并计入)〔换图系批 V5〕
|
||||||
|
[PASS] V5③ 危险格路径:直伤单据在案且全单据逐张恰一次(行 2=计数 2)〔换图系批 V5〕
|
||||||
|
[PASS] V5④ 错误封印路径:直伤单据在案且全单据逐张恰一次(行 2=计数 2)〔换图系批 V5〕
|
||||||
|
[PASS] V2 受理付费挖格 RAP 恰 +1(8→9)〔换图系批 V2〕
|
||||||
|
[PASS] V3 折后单次扣费(8→4,−4)〔换图系批 V3〕
|
||||||
|
[PASS] V4 第 1 次成功(事实 +1 换图 +1)〔换图系批 V4〕
|
||||||
|
[PASS] V6 S1 危险格减伤恰 2 且多刷不叠加(行值 [6]∈[6]——叠加态应为 4)〔换图系批 V6〕
|
||||||
|
[PASS] V9 R1 轮末回复=min(3,缺血102) 且层清 0(heal 行值 [3])〔换图系批 V9〕
|
||||||
|
[PASS] V10 第 1 次成功 escrow +1(0→0)〔换图系批 V10〕
|
||||||
|
[PASS] V12 刷新受理并按表基线扣(−8)〔换图系批 V12〕
|
||||||
|
[standee_present_values] all green (26 checks)
|
||||||
|
[PASS] 基线读数与实装几何同源(基线 = 地台顶上方 8px 档:150 < 158——14px 档牌顶+18)〔样张批 V6〕
|
||||||
|
[PASS] 名字行探针字号实读 = FS_HINT(几何单点出口 name_font_size)〔名字行 V10〕
|
||||||
|
[PASS] 全文件 FS_CAPTION 仅剩消散提示一处(计数锚==1——三处宽度测量点强制换代)〔名字行 V10〕
|
||||||
|
[PASS] 退役常量不在册(STAND_STONE_RIM/STAND_STONE_EDGE 零键)〔名字行 V11〕
|
||||||
|
[PASS] PLAZA_EDGE 值逐位 #4C4038(合并目标②)〔名字行 V12〕
|
||||||
|
[adventure_feats_test] all green (138 checks)
|
||||||
|
[PASS] 专长带入池:37 件四级直映射(普通 14/优秀 13/稀有 10/传说 0)——牌库 102+37=139〔专长批 V2+迭代 46 换图系七件换代〕
|
||||||
|
[round_flow_skill_pool_test] all green (134 checks)
|
||||||
|
[seal_polarity_test] all green (70 checks)
|
||||||
|
[effect_words_t1] all green (20 checks)
|
||||||
|
[forge_table_test] all green (41 checks)
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
[render_gate] [golden] board_L1_dungeon.png — changed=0.00%(容差 ≤16 差/≤2%)OK
|
||||||
|
[render_gate] [golden] board_L2_underdark.png — changed=0.00%(容差 ≤16 差/≤2%)OK
|
||||||
|
[render_gate] [golden] board_L3_deep_underdark.png — changed=0.00%(容差 ≤16 差/≤2%)OK
|
||||||
|
[render_gate] [golden] menu_boot.png — changed=0.00%(容差 ≤16 差/≤2%)OK
|
||||||
|
[render_gate] 全绿
|
||||||
|
```
|
||||||
|
|
||||||
|
注:V10 消息「escrow +1(0→0)」的两个读数均在 `player_request_refresh()` 求值前格式化——显示瑕疵;断言本体绑刷新后 `escrow == escrow0 + 1`,判据面正确(绿即真值)。
|
||||||
|
|
||||||
|
## 五、内部字段直查受控例外核对(test-conventions §80/§8.7 第 4 条)
|
||||||
|
|
||||||
|
| 直查行 | 性质 | 判定 |
|
||||||
|
|---|---|---|
|
||||||
|
| `orch.fx_host().counters_ctx`(refresh_feats_test 5 处:读计数器×3+白盒预置×2) | `counters_ctx` 是 BattleFxHost **公开成员**(无下划线),读经公共 API `EffectEngine.counter` | 非私有直查;存量套件 adventure_feats_test 同款先例 |
|
||||||
|
| `EffectEngineScript.counter_add(...)` 白盒预置(V9/V8 上限 3 闸) | 计数器直改=数据面注入 | **已申报**——实现决定 #11(test-conventions §5.4 引用)+对照表 V8/V9 行「白盒预置」在案 |
|
||||||
|
| `orch1._enemy_system.hero_unit()`(refresh_feats_test:518,V9 缺血读数) | **私有面直查 1 处** | **例外(手续缺注,不阻塞)**——对照表 V9 行与实现决定 #11 均未注该行理由。依据:①判据主面(heal 行/未生效行)经公共 `step_row` 信号在案,私有读只作前置量(`missing >= 3` 守卫),行为并非钉在私有面上;②存量套件 roll_multiplier_test.gd:51 同款先例;③公开替代面存在(`presentation_snapshot()` 含 hero.hp)。建议实现报告 V9 行补注一句(随合回流程,本审计不代改) |
|
||||||
|
| 生产码 `oc._damage._emit_hero_damage_taken`/`oc._rounds._grant_seg` 直调 | 结算族内部协作 | 实现决定 #8 备案(结算族直调惯例)——非测试面,不在本节判域 |
|
||||||
|
|
||||||
|
## 六、附加核对(实现决定复核三项+卡C 脏面)
|
||||||
|
|
||||||
|
1. **S7_B1_SET 增补(实现决定 #6)——判据口径未变,通过**:static_scan.sh diff 仅 1 行清单+2 行注释;B2/B3/B4 三族符号交替式(14/5/9 符号)逐字未动;豁免语义(自家文件、注释/字符串排除、s7-b1-exempt)未动;`battle_refresh_flow.gd` 调 `oc._reward_economy.retire_mapping()` 命中 B4 符号,入 B1 域集与 C13 拆分边界自洽。审计实跑 static_scan:S7 ok(50 文件扫描面)。
|
||||||
|
2. **S9 修正(f498d2ec)——零行为变化,通过**:4 处显式非参数化容器注解(`damage_controller.gd` 3 处 `: Dictionary`/`: Array`、`grant_segment.gd` 1 处)改 `:=` 推断,静态注解改写,无逻辑分支变化。
|
||||||
|
3. **卡C 停摆脏面核对——申报一致,另见§七.1**:`git status` 修改面恰为申报的三件(`board_view.gd`/`layout_probe.gd`/`board_gen_reward_test.gd`),均未提交、不在审计 diff 内;另有 2 个未跟踪 `.uid` 文件(§七.1)。
|
||||||
|
4. **golden 申报制核对**:golden 五文件(三张板面+level_map+menu_boot)git 零改动=重锚申报制零触发(实现决定 #4「无需重锚」与实跑 0.00% 互证);不存在借重锚夹带。
|
||||||
|
|
||||||
|
## 七、测试削弱核对清单(diff 被改/被删旧断言逐处 → 申报面)
|
||||||
|
|
||||||
|
| 位置 | 改动 | 申报依据 | 判定 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| adventure_feats_test :464/:476 | 行数 30→37、稀有度 12/9/9→14/13/10、牌库 132→139 | 卡A§十二第 1 条(计数换代申报) | **通过**——零放宽:validate/禁词/退役钉面零删(diff 核对) |
|
||||||
|
| forge_table_test :202 附近 | EFFECT_KINDS 32→34、EVENTS 20→22 | 卡A§十二第 1 条 | **通过** |
|
||||||
|
| effect_words_t1_test :17-21 | 纯增量 5 行(两事件两效果在册) | 增量非削弱 | **通过**(旧负例全保留,diff 核对) |
|
||||||
|
| standee_present_values_test V6 | 读数 149→150+「9px 档→8px 档」句 | 卡B§8 申报第 1 条 | **通过**(文字不压地台判据零改动) |
|
||||||
|
| standee_present_values_test V10–V12 | 纯增量 8 断言+文件头注释换代 | 卡B§8 申报第 2 条 | **通过**(V5/V7/V8/V9 判据零改动,diff 核对) |
|
||||||
|
| run_all.gd :191 | 套件注释 V5–V9→V5–V12 | 非行为改动(卡B§6 预告) | **通过** |
|
||||||
|
| 禁删面(卡A§十二):RAP8 基线/每轮上限 2/AP 保留/刷新图零清图/伤害下限/五段序/同种子回放 | round_flow/seal 两文件零进 diff;伤害下限在 `_resolve_board_flat_hero` 保位(`maxi(下限, dealt)` 减后过下限=卡C4 同款) | 卡A§十二禁删令 | **通过**(134/70 checks 实跑绿承载) |
|
||||||
|
| 生产面删除项 | `_terrain_damage_reduce` 删/编排器旧刷新体删/`_enemy_shield_apply` 改名/ui_kit 两常量删 | 卡A T2/T3 步骤明列+卡B§1/§3 | **通过**(全部卡面内,无未申报删改) |
|
||||||
|
| 变异/撤销类取证 | 全 diff 无回退 hunk/注入破坏证明 | 卡A§十二+卡B§3 | **通过**(临时专长经 ConfigDB 表行追加=卡§十二「正常数据配置路径」申报一致) |
|
||||||
|
|
||||||
|
## 八、附加发现(不属验收对照面、不阻塞合并——处置建议随合回流程)
|
||||||
|
|
||||||
|
1. **批内新件两枚 `.uid` 未随批提交**:`game/core/settlement/battle_refresh_flow.gd.uid` 与 `game/tests/unit/battle_settlement/refresh_feats_test.gd.uid` 未跟踪(mtime 07:49,早于 T3 提交 07:52——非审计跑测所生成)。`.gitignore` 第 6 行明文「Godot 4 的 .uid 文件必须提交,不要忽略」,全仓其余 `.gd` 均有 `.uid` 入库伴件。已核零 UID 引用(grep 两 uid 串零命中),风险低;建议合回前补提交(本审计受只读约束不代提交)。
|
||||||
|
2. **S8_FILES 未与 S7_B1_SET 同步增补**:await 零命中扫描集仍为 10 文件,新 B1 拆件不在其内。实查新件零 await(grep 证),当日无漏扫;但两清单同源 B1 域集出现分叉,建议 architecture「模块归属」BattleRefreshFlow 行落文(反哺点 #5)时同批补 S8_FILES。
|
||||||
|
3. **V10 消息文案时点**(§四注):纯文案瑕疵,判据不受影响。
|
||||||
|
4. **V9 私有读未注理由**(§五):建议补注。
|
||||||
|
|
||||||
|
## 九、结论
|
||||||
|
|
||||||
|
**审计通过(零缺漏)**——卡A 对照表 V1–V12 与卡B 对照表 6 行逐行通过(例外 1 处=V9 私有读手续缺注,附依据不阻塞);四套件+三附加套件+gdstyle+static_scan+render_gate 当轮自跑全绿;测试削弱全部在两卡申报面内,无未申报删改,无变异/撤销类取证;结构预算申报(803 行/类变量 15/各件增员)经实数核对全部相符。§八附加发现四条均不属验收对照面,随合回流程处置。
|
||||||
Reference in new issue
Block a user