fix(mana): heat 依真实扣蓝(ctx标记)覆盖 LOOP/CIRCUIT 子路径+卖血不升热;死亡防重入
复审 nit: - heat 之前仅在主循环 cast_fired 记录,LOOP 体/CIRCUIT 分支扣蓝漏记 → 递增蓝耗对这两类拓扑失效。 改为 _charge_mana 真实 spend_mana 成功时置 ctx.mana_spent_this_cast,施法末据此升 heat。 - 顺带修正 infinite 卖血(未真实扣蓝)也升 heat 的问题:卖血不升热。 - combat_manager._on_player_died 加 GAME_OVER 防重入(子弹命中+卖血同帧双死不重复结算)。 MCP 实测:LOOP heat 0.010(修复前0)、卖血 heat 0、线性回归 0.010。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -228,6 +228,8 @@ func close_shop_and_next_wave() -> void:
|
|||||||
_transition_to_battle(next_wave)
|
_transition_to_battle(next_wave)
|
||||||
|
|
||||||
func _on_player_died(_payload: Dictionary) -> void:
|
func _on_player_died(_payload: Dictionary) -> void:
|
||||||
|
if state == GameState.GAME_OVER:
|
||||||
|
return # 防重入:同帧内多路死亡(子弹命中 + 卖血施法)不重复结算战绩/发以太
|
||||||
state = GameState.GAME_OVER
|
state = GameState.GAME_OVER
|
||||||
# 先采集本局战绩(reset 前),写入排行榜
|
# 先采集本局战绩(reset 前),写入排行榜
|
||||||
var wave_reached: int = max(1, WaveManager.current_wave)
|
var wave_reached: int = max(1, WaveManager.current_wave)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ var stats: CastStats = null # 本次施法的修改层
|
|||||||
## R1-R4:供 LOGIC_EVERY_N_SHOTS 等指令跨帧计数
|
## R1-R4:供 LOGIC_EVERY_N_SHOTS 等指令跨帧计数
|
||||||
var registers: PackedFloat32Array = PackedFloat32Array()
|
var registers: PackedFloat32Array = PackedFloat32Array()
|
||||||
var hp_cost_accum: float = 0.0 # 本次施法累积 HP 代价(heavy_cost + infinite),施法末结算
|
var hp_cost_accum: float = 0.0 # 本次施法累积 HP 代价(heavy_cost + infinite),施法末结算
|
||||||
|
var mana_spent_this_cast: bool = false # 本次施法是否真实扣过蓝(含 LOOP/CIRCUIT 子路径)→ 决定 heat 上升
|
||||||
|
|
||||||
func _init() -> void:
|
func _init() -> void:
|
||||||
stats = CastStats.new()
|
stats = CastStats.new()
|
||||||
@@ -23,6 +24,7 @@ func reset() -> void:
|
|||||||
else:
|
else:
|
||||||
stats = CastStats.new()
|
stats = CastStats.new()
|
||||||
hp_cost_accum = 0.0
|
hp_cost_accum = 0.0
|
||||||
|
mana_spent_this_cast = false
|
||||||
# registers 小心:仅当 Core 没有 PERSISTENT_MEMORY Feature 时才清零
|
# registers 小心:仅当 Core 没有 PERSISTENT_MEMORY Feature 时才清零
|
||||||
# 具体清空逻辑在 SpellContextPool.acquire_for_wand() 中处理
|
# 具体清空逻辑在 SpellContextPool.acquire_for_wand() 中处理
|
||||||
|
|
||||||
|
|||||||
@@ -324,9 +324,9 @@ func execute_compiled(compiled: CompiledDeck, caster_id: int, spawn_pos: Vector2
|
|||||||
var persistent: bool = compiled.feature_tags == CoreFeatureTag.PERSISTENT_MEMORY
|
var persistent: bool = compiled.feature_tags == CoreFeatureTag.PERSISTENT_MEMORY
|
||||||
var ctx: SpellContext = _acquire_ctx(caster_id, persistent)
|
var ctx: SpellContext = _acquire_ctx(caster_id, persistent)
|
||||||
ctx.hp_cost_accum = 0.0
|
ctx.hp_cost_accum = 0.0
|
||||||
|
ctx.mana_spent_this_cast = false
|
||||||
var infinite: bool = compiled.feature_tags == CoreFeatureTag.INFINITE_SPELLS
|
var infinite: bool = compiled.feature_tags == CoreFeatureTag.INFINITE_SPELLS
|
||||||
var heat_mult: float = 1.0 + PlayerStats.mana_heat
|
var heat_mult: float = 1.0 + PlayerStats.mana_heat
|
||||||
var cast_fired: bool = false
|
|
||||||
var deck: SpellDeck = compiled.make_runtime_deck()
|
var deck: SpellDeck = compiled.make_runtime_deck()
|
||||||
var ops_count: int = 0
|
var ops_count: int = 0
|
||||||
var max_ops: int = MAX_OPS_PER_CPU * 5
|
var max_ops: int = MAX_OPS_PER_CPU * 5
|
||||||
@@ -337,8 +337,6 @@ func execute_compiled(compiled: CompiledDeck, caster_id: int, spawn_pos: Vector2
|
|||||||
var mana_cost: float = float(node.meta.get("mana_cost", 0.0))
|
var mana_cost: float = float(node.meta.get("mana_cost", 0.0))
|
||||||
if not _charge_mana(mana_cost, ctx, infinite, heat_mult):
|
if not _charge_mana(mana_cost, ctx, infinite, heat_mult):
|
||||||
break # 蓝不足且非 infinite → 停施
|
break # 蓝不足且非 infinite → 停施
|
||||||
if mana_cost > 0.0:
|
|
||||||
cast_fired = true
|
|
||||||
match node.type:
|
match node.type:
|
||||||
SpellNode.SpellType.ACTION:
|
SpellNode.SpellType.ACTION:
|
||||||
_push_projectile(node, ctx, spawn_pos, -1)
|
_push_projectile(node, ctx, spawn_pos, -1)
|
||||||
@@ -364,7 +362,7 @@ func execute_compiled(compiled: CompiledDeck, caster_id: int, spawn_pos: Vector2
|
|||||||
break
|
break
|
||||||
if ctx.hp_cost_accum > 0.0:
|
if ctx.hp_cost_accum > 0.0:
|
||||||
PlayerStats.spend_hp_cost(minf(ctx.hp_cost_accum, PlayerStats.hp_max * SettingsManager.hp_cost_cap_ratio()))
|
PlayerStats.spend_hp_cost(minf(ctx.hp_cost_accum, PlayerStats.hp_max * SettingsManager.hp_cost_cap_ratio()))
|
||||||
if cast_fired:
|
if ctx.mana_spent_this_cast:
|
||||||
PlayerStats.mana_heat = minf(SettingsManager.mana_heat_max(), PlayerStats.mana_heat + SettingsManager.mana_heat_per_cast())
|
PlayerStats.mana_heat = minf(SettingsManager.mana_heat_max(), PlayerStats.mana_heat + SettingsManager.mana_heat_per_cast())
|
||||||
if ops_count >= max_ops:
|
if ops_count >= max_ops:
|
||||||
push_warning("SpellEvaluator: MAX_OPS reached (caster=%d)" % caster_id)
|
push_warning("SpellEvaluator: MAX_OPS reached (caster=%d)" % caster_id)
|
||||||
@@ -501,6 +499,7 @@ func _charge_mana(base_cost: float, ctx: SpellContext, infinite: bool, heat_mult
|
|||||||
if base_cost <= 0.0:
|
if base_cost <= 0.0:
|
||||||
return true
|
return true
|
||||||
if PlayerStats.spend_mana(base_cost * heat_mult):
|
if PlayerStats.spend_mana(base_cost * heat_mult):
|
||||||
|
ctx.mana_spent_this_cast = true # 真实扣蓝才记(infinite 卖血不算)→ heat 只对真实施法上升
|
||||||
return true
|
return true
|
||||||
if infinite:
|
if infinite:
|
||||||
ctx.hp_cost_accum += SettingsManager.infinite_hp_per_shot()
|
ctx.hp_cost_accum += SettingsManager.infinite_hp_per_shot()
|
||||||
|
|||||||
Reference in New Issue
Block a user