diff --git a/game/presentation/main.gd b/game/presentation/main.gd index d8d7d584..764448b4 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -87,6 +87,16 @@ var _refresh_used_round := 0 # 本轮已受理刷新次数(遥测 round_r var _boss_active := false # Boss 遭遇变体(阶段指示附 Boss 名) var _was_settling := false # 结算相位上升沿检测(结算轮光带触发——演出链批) +# ---- 结算行排播队列(迭代 8 卡⑦——演出层排播:逻辑先行零回压,行按段分组阶梯延迟进明细带)---- +# 硬契约:核心层零 await 不破——本队列只做信号的"后置消费",结算顺序/结果/时序逻辑零改动(§3 契约 1); +# 减动效开=延迟归零走同帧直落快路(行呈现不跳过——§3 契约 5);模态开启前强制排空(明细回看/停点 +# 覆盖层所见明细带恒完整)。 +var _row_queue: Array = [] # 待排播行(step_row 载荷原样入队;__round_line 标记=轮首分隔行) +var _row_pace_wait := 0.0 # 距下一行排播的剩余秒(_process 消耗) +var _pace_prev_segment := "" # 上一排播行的段型(段间/段内延迟判据) +const PACE_SEGMENT_GAP := 0.3 # 段间阶梯延迟(代拍板:白名单 0.2–0.4s 内取值,追认清单呈报) +const PACE_ROW_GAP := 0.06 # 段内快排间隔(同行段连读性——非白名单约束面) + func _ready() -> void: db = get_node("/root/ConfigDB") @@ -224,6 +234,7 @@ func _register_modal(m: Control) -> void: func _open_modal(m: Control) -> void: # 打开时显式隐藏树序在其上方的可见层(遮挡物隐藏),关闭恢复——单一模态栈 + _flush_row_queue() # 卡⑦——任何覆盖层开启前排空排播队列(明细带恒完整,回看/停点面不缺行) _modals.open(m) @@ -600,6 +611,19 @@ func _wire_battle(settlement_ref) -> void: func _on_step_row(row: Dictionary) -> void: + # 卡⑦ 排播入口:减动效=延迟归零同帧直落(行呈现不跳过);常态入队按段阶梯播放 + if UiKit.motion_scale < 1.0: + _consume_row(row) + return + _row_queue.append(row) + + +# 行消费(排播队列的出队步 / 减动效快路共用):明细带入带+演出链+呈现刷新——与卡⑦前排位逐位一致 +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) # 跨区因果演出(结算演出链批):开格之因 → 站桩之果 @@ -611,6 +635,37 @@ func _on_step_row(row: Dictionary) -> void: _refresh_presentations() +func _process(delta: float) -> void: + # 排播泵(卡⑦——演出层帧驱动;队列空=零开销直返) + if _row_queue.is_empty(): + _row_pace_wait = 0.0 + _pace_prev_segment = "" + return + _row_pace_wait -= delta + if _row_pace_wait > 0.0: + return + var entry: Dictionary = _row_queue.pop_front() + _consume_row(entry) + var seg := String(entry.get("type", "")) + _row_pace_wait = 0.0 if UiKit.motion_scale < 1.0 else (PACE_SEGMENT_GAP if seg != _pace_prev_segment else PACE_ROW_GAP) + _pace_prev_segment = seg + + +# 段间延迟取数面(减动效双态断言用——开=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_row_queue() -> void: + while not _row_queue.is_empty(): + _consume_row(_row_queue.pop_front()) + _row_pace_wait = 0.0 + _pace_prev_segment = "" + + # 跨区因果演出(§Transitions 结算逐行兑现——动画密度终稿归战斗表现样张批): # 伤害行 → ⚔ 弹道自棋盘飞向目标敌方站桩(crit 金档加重);治疗行 → ✚ 弹道飞向主角站桩; # 命中反馈(闪白/血条递减/灰化)由站桩侧 on_step_row_fx/set_data 承接 @@ -637,8 +692,14 @@ func _play_cross_zone_fx(row: Dictionary) -> void: func _on_round_started(ri: int) -> void: - log_view.finish_settlement_round() # 上轮摘要行(挖掘期 1 行——明细带 pattern 3) - log_view.append_bb("[color=#e0b860]── 第 %d 轮 ──[/color]" % ri) + # 卡⑦:轮首分隔行随排播队列走(落在前轮各行之后——带序正确);轮横幅即轮切换时刻发射 + #(view 内判减动效=静态化变体)。减动效快路=分隔行同帧直落(不跳过)。 + if UiKit.motion_scale < 1.0: + log_view.finish_settlement_round() + log_view.append_bb("[color=#e0b860]── 第 %d 轮 ──[/color]" % ri) + else: + _row_queue.append({"__round_line": ri}) + settlement_fx.play_round_banner(ri) _refresh_used_round = 0 _refresh_presentations() diff --git a/game/presentation/views/settlement_fx_view.gd b/game/presentation/views/settlement_fx_view.gd index 24e0c932..006d523f 100644 --- a/game/presentation/views/settlement_fx_view.gd +++ b/game/presentation/views/settlement_fx_view.gd @@ -65,3 +65,42 @@ func play_projectile(from: Vector2, to: Vector2, glyph: String, col: Color, crit tw.tween_property(shot, "position", to - shot.size * 0.5, UiKit.motion(0.19)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) tw.parallel().tween_property(shot, "modulate:a", 0.0, UiKit.motion(0.12)).set_delay(UiKit.motion(0.23)) tw.tween_callback(shot.queue_free) + + +# ---- 轮切换横幅(迭代 8 卡⑦——「第 N 轮」驻留 0.6s 后消隐进下一轮;白名单 0.5–0.8s 内取值)---- +# 减动效=静态呈现(驻留后即隐,零位移/零透明度动画——行呈现不跳过同语义,卡⑦ AC③)。 +const BANNER_DWELL := 0.6 + +var _fx_banners := 0 # 演出探针计数(fx_probe 断言面) + + +# 演出探针出口(卡⑦——轮横幅发射计数) +func probe_banners() -> int: + return _fx_banners + + +func play_round_banner(ri: int) -> void: + _fx_banners += 1 + var banner := Panel.new() + banner.mouse_filter = Control.MOUSE_FILTER_IGNORE + banner.add_theme_stylebox_override("panel", UiKit.panel_flat(Color(UiKit.BG_OVERLAY, 0.88), UiKit.GOLD_DEEP, 8, 2)) + var lab := UiKit.label("第 %d 轮" % ri, 22, true) + lab.add_theme_color_override("font_color", UiKit.GOLD) + lab.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + lab.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + lab.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + banner.add_child(lab) + banner.size = Vector2(190, 46) + banner.position = Vector2((size.x - banner.size.x) * 0.5, size.y * 0.30) # 棋盘中带上缘——不压明细带 + banner.z_index = 4 + add_child(banner) + if UiKit.motion_scale < 1.0: + var st := get_tree().create_timer(BANNER_DWELL) + st.timeout.connect(banner.queue_free) + return + banner.modulate.a = 0.0 + var tw := banner.create_tween() + tw.tween_property(banner, "modulate:a", 1.0, UiKit.motion(0.15)) + tw.tween_interval(UiKit.motion(BANNER_DWELL)) + tw.tween_property(banner, "modulate:a", 0.0, UiKit.motion(0.25)) + tw.tween_callback(banner.queue_free) diff --git a/game/smoke/fx_probe.gd b/game/smoke/fx_probe.gd index f64a0102..59cf07c7 100644 --- a/game/smoke/fx_probe.gd +++ b/game/smoke/fx_probe.gd @@ -77,6 +77,25 @@ func _ready() -> void: if n_fx < 2: problems.append("结算光带应含带体+前导缘线 ≥2 节点,实得 %d" % n_fx) + # ---- ④b 轮切换横幅双态(迭代 8 卡⑦——常态=淡入驻留消隐动画链;减动效=静态呈现零动画)---- + sfx.play_round_banner(3) + await get_tree().process_frame + var banner_nodes := 0 + for ch in sfx.get_children(): + if ch is Panel: + banner_nodes += 1 + if banner_nodes != 1: + problems.append("轮横幅应落位 1 面(卡⑦),实得 %d" % banner_nodes) + if int(sfx.probe_banners()) != 1: + problems.append("轮横幅计数失守(probe_banners=%s)" % sfx.probe_banners()) + var saved_motion: float = UiKit.motion_scale + UiKit.motion_scale = 0.5 + sfx.play_round_banner(4) + await get_tree().process_frame + if int(sfx.probe_banners()) != 2: + problems.append("减动效轮横幅未发射(卡⑦ AC③——静态化仍呈现不跳过)") + UiKit.motion_scale = saved_motion + # ---- ⑤ 主角三态切换(迭代 6 T3——attack/hit 短驻自回 + 死亡复活态回 idle) ---- var hero_sb = sb_script.new("hero") hero_sb.box = Vector2(150, 176) @@ -129,7 +148,7 @@ func _ready() -> void: # ---- 汇总 ---- if problems.is_empty(): - print("[fx_probe] PASS — 死亡碎裂5块/命中火花6/意图三档+零表示/胜利纹章10/失败掸灰8+旗光8/结算光带+缘线/主角三态切换 全链路通过") + print("[fx_probe] PASS — 死亡碎裂5块/命中火花6/意图三档+零表示/胜利纹章10/失败掸灰8+旗光8/结算光带+缘线/轮横幅双态/主角三态切换 全链路通过") get_tree().quit(0) else: for p in problems: