Files
gplan-2/game/presentation/row_pacer.gd
T
DKChan 0529e1691c fix(iter37①): T2 呈现/组合根缺陷修——蓄力档分档·庆祝句柄·互斥四口·模态 fail-fast·站桩按 id 解析
- d2 蓄力档 `_intent_charge` 布尔分档(颜色相等判据退役,深浅金同值不再误判);d4 庆祝浮动 tween 句柄 kill 纪律(close 必杀)
- d5/d6/d7/d8 互斥四口:调试钮判据并入 `_sync_sim_button` 单点、组合根两守卫补模拟态/冒险态、`is_running()` 运行态查询面
- d9 ModalStack open/close fail-fast+遮挡账合并非覆盖(重复 open 不再吞旧账);d12 target_id 产发(damage_controller 四处)+站桩两口 id 先名兜底;d17 await 后实例守卫;d18 参数类型注解
- 断言:standee_fx_sweep_test(新增,18 项)/modal_flow_test V6-5(5 项 wd9,V6-1~4 原样同绿)/damage_controller_test target_id/pacing_queue_test 排播 target_id/system_test wd6+wd7
- 代拍板:d18 卡片字面 `lumas_out: Array[Variant] = null` 在 Godot 4.7 解析报错(typed array 不可默认 null),改用 `Variant = null`——唯一合法注法,零行为变化,随代拍板追认单留痕
2026-10-25 03:49:05 +08:00

153 lines
7.6 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## VERTICAL SLICE - NOT FOR PRODUCTION
## 结算行排播泵:逻辑先行零回压,行按段分组阶梯延迟进明细带。
## 硬契约:只做信号的后置消费——结算顺序/结果/时序逻辑零改动;减动效开=延迟归零同帧直落,行呈现不跳过。
## 开模态前必须 flush()——明细回看/停点/终局面所见明细带恒完整。
## 视图句柄与 refresh_request/pump_ended 回调由组合根注入——本类零信号连接。
extends Node
const PACE_SEGMENT_GAP := 0.3 # 段间阶梯延迟(取值须落白名单 0.2–0.4s 档位带内)
const PACE_ROW_GAP := 0.22 # 段内间隔(三轮 #3 上调 0.06→0.22——逐条演出可感知,配合数字浮现/状态脉冲)
var _rows: Array[Variant] = [] # 待排播行(step_row 载荷原样入队;__round_line 标记=轮首分隔行)
var _pace_wait := 0.0 # 距下一行排播的剩余秒(_process 消耗)
var _prev_segment := "" # 上一排播行的段型(段间/段内延迟判据)
var _was_pumping := false # 排播活跃边沿(T17——末行消费帧触发结算视觉回挖掘态刷新)
# 组合根注入面(_build_ui 装配——呈现句柄 + 演播完边沿的呈现刷新回调)
var log_view: BattleLogView
var stage: BattleStageView
var board_zone: BoardZoneView
var settlement_fx: SettlementFxView
var board_view: BoardView
var refresh_request := Callable() # () → 组合根 _refresh_presentations(行尾直呼/演播完边沿 call_deferred)
var pump_ended := Callable() # () → 组合根锁期点击重放入口(T17 v3.1——演播完边沿 call_deferred;空回调安全)
# 队列只读面(探针/测试经组合根委托属性读——共享同一 Array 引用,读数即实况)
func pending_rows() -> Array:
return _rows
# 入队(常态路径——减动效关;step_row 载荷原样,零加工)
func enqueue(row: Dictionary) -> void:
_rows.append(row)
# 同帧直落(减动效快路——延迟归零,行呈现不跳过;轮首分隔行减动效分支同用此口)
func consume_now(row: Dictionary) -> void:
_consume_row(row)
# 段间延迟取数面(减动效双态断言用——开=0 归零,行不跳过;常态=段间/段内两档)
func pace_delay_for(prev_seg: String, next_seg: String) -> float:
if UiKit.motion_scale < 1.0:
return 0.0
return PACE_SEGMENT_GAP if prev_seg != next_seg else PACE_ROW_GAP
# 强制排空(模态开启前调用——明细回看/停点覆盖层/终局面所见明细带恒完整)
func flush() -> void:
while not _rows.is_empty():
_consume_row(_rows.pop_front())
_pace_wait = 0.0
_prev_segment = ""
func _process(delta: float) -> void:
# 排播泵(演出层帧驱动;队列空=零开销直返)
if _rows.is_empty():
_pace_wait = 0.0
_prev_segment = ""
if _was_pumping:
# 演播完边沿:结算视觉期由排播队列承载(_is_settling 排播项),队列排空的翻转
# 无核心信号驱动——此处主动刷回挖掘态(去饱和解除/明细带收起/mode=dig)。
_was_pumping = false
refresh_request.call_deferred()
# 锁期点击重放(T17 v3.1——玩家操作零丢失):最后一次锁期点击延迟一帧重入
# _on_board_action(此刻队列已空=锁已解,正常受理;重放入口由组合根注入)。
if pump_ended.is_valid():
pump_ended.call_deferred()
return
_was_pumping = true
_pace_wait -= delta
if _pace_wait > 0.0:
return
var entry: Dictionary[Variant, Variant] = _rows.pop_front()
_consume_row(entry)
var seg := String(entry.get("type", ""))
_pace_wait = 0.0 if UiKit.motion_scale < 1.0 else (PACE_SEGMENT_GAP if seg != _prev_segment else PACE_ROW_GAP)
_prev_segment = seg
# 行消费(排播队列的出队步 / 减动效快路共用):明细带入带+演出链+呈现刷新
func _consume_row(row: Dictionary) -> void:
if row.has("__round_line"):
log_view.finish_settlement_round() # 上轮摘要行(明细带 pattern 3)随轮首分隔行落带
log_view.append_bb("[color=#e0b860]── 第 %d 轮 ──[/color]" % int(row["__round_line"]))
return
log_view.append_row(row)
stage.on_step_row_fx(row)
_play_cross_zone_fx(row) # 跨区因果演出(结算演出链批):开格之因 → 站桩之果
_play_settlement_mark(row) # 三轮 #3——逐条标出:Buff/Debuff 色环脉冲 → 伤害/治疗数字浮现
# 高光演出(战斗结算×3①② 占位):完美封印横幅 / 封印命中加重弹道
if String(row.get("type", "")) == "highlight" and String(row.get("highlight", "")) != "skill_unlock":
board_zone.fx.play_perfect_sweep()
elif String(row.get("highlight", "")) == "seal_hit":
board_zone.fx.play_seal_hit_projectile(true)
# Boss 半血解锁技提示(D8 呈现面:skill_unlock 行弹一次性技能名 toast,
# 轻量非演出零动画资产;行文本 =「XX 解锁了 YY」,只报技能名不报数值——规格=敌人系统 意图呈现节)
if String(row.get("highlight", "")) == "skill_unlock":
board_zone.toast.show_toast(String(row.get("display_name", "")), "info")
refresh_request.call()
# 逐条结算标出(按结算顺序演出:先 Buff/Debuff 特效、再基础伤害数字;
# 打中谁标谁、主角被打标主角;道具/奖励行逐条冒出)。目标解析失败兜底棋盘中心。
func _play_settlement_mark(row: Dictionary) -> void:
var mark_type := String(row.get("type", ""))
if mark_type != "effect" and mark_type != "damage" and mark_type != "heal":
return
var target := String(row.get("target", ""))
var target_id := String(row.get("target_id", ""))
var center: Vector2
if target == "hero":
center = stage.hero_standee_center()
else:
var v = stage.enemy_standee_center(target_id, target) if target != "" and target != "all_enemies" else null
center = v if v != null else board_view.get_global_rect().get_center()
match mark_type:
"effect":
var col: Color = Color("7cc47c") if int(row.get("value", 0)) > 0 else Color("c47c7c")
settlement_fx.play_status_pulse(center, col)
"damage":
settlement_fx.play_damage_number(center, "-%d" % int(row.get("value", 0)),
Color("d9402a"), bool(row.get("crit", false)))
"heal":
settlement_fx.play_damage_number(center, "+%d" % int(row.get("value", 0)), Color("7cc47c"), false)
# 跨区因果演出(§Transitions 结算逐行兑现——动画密度终稿归战斗表现样张批):
# 伤害行 → ⚔ 弹道自棋盘飞向目标敌方站桩(crit 金档加重);治疗行 → ✚ 弹道飞向主角站桩;
# 命中反馈(闪白/血条递减/灰化)由站桩侧 on_step_row_fx/set_data 承接
func _play_cross_zone_fx(row: Dictionary) -> void:
var from_v: Vector2 = board_view.get_global_rect().get_center()
match String(row.get("type", "")):
"damage":
var target := String(row.get("target", ""))
var target_id := String(row.get("target_id", ""))
var crit := bool(row.get("crit", false))
if target == "" or target == "hero":
# 主角受击弹道(禁「敌→主角零反馈」早退):⚔ 危险红飞向主角站桩;
# crit 只加重字号不加金(金=收益/胜利语义位,不用于敌方对主角输出)
settlement_fx.play_projectile(from_v, stage.hero_standee_center(), "⚔",
UiKit.RED_MINE, crit)
return
var to_v = stage.enemy_standee_center(target_id, target)
if to_v != null:
settlement_fx.play_projectile(from_v, to_v, "⚔",
UiKit.GOLD if crit else UiKit.RED_MINE, crit)
"heal":
if String(row.get("target", "")) != "hero" and String(row.get("target", "")) != "":
return
settlement_fx.play_projectile(from_v, stage.hero_standee_center(), "✚", Color("7cc47c"))