diff --git a/game/presentation/armed_timer.gd b/game/presentation/armed_timer.gd new file mode 100644 index 00000000..3c9b7e24 --- /dev/null +++ b/game/presentation/armed_timer.gd @@ -0,0 +1,47 @@ +# 武装确认计时器(迭代 13 T3/D3——P4 乙裁决:只统一计时+脉冲机制,视觉三处各自保留)。 +# 收口债:三处二次确认计时机制异构(AP 条弃图刷新=tween 秒表 / 选路放弃钮=_process 帧驱动 +# 倒计时 / 战斗总结结算框=纯载荷暂存裸布尔)——本件消掉「计时机制三种实现」漂移源。 +# 保缝纪律:AP 条三探针缝(press_refresh_for_probe/confirm_visible/press_confirm_for_probe) +# 语义等价零动;放弃钮真实二击路径零改;结算框挂起态经组合根属性继续可读写(探针接缝)。 +# 减动效档:档判据单点 motion_still()——脉冲相位由视觉层取 elapsed 自算(1Hz 连续呼吸 +# 视觉零变化),减动效档视觉层走静态呈现(放弃钮静态半透先例)。 +class_name ArmedTimer +extends RefCounted + +signal expired # 窗口到点回落边沿(仅 window_s > 0 时发出;宿主接回落视觉复位) + +var window_s := 0.0 # 超时窗口秒(<=0 = 无窗口——挂起直到显式回落,结算框语义) +var elapsed := 0.0 # 武装起算累计秒(视觉层呼吸相位取数;tick 累计) + +var _armed := false + + +# 档判据单点(减动效档四能力之一——两处视觉层共用,替代各自内联 UiKit.motion_scale 判断) +static func motion_still() -> bool: + return UiKit.motion_scale < 1.0 + + +func arm(window_override := -1.0) -> void: + if window_override >= 0.0: + window_s = window_override + _armed = true + elapsed = 0.0 + + +func disarm() -> void: + _armed = false + elapsed = 0.0 + + +func is_armed() -> bool: + return _armed + + +# 帧驱动(宿主 _process 委托;unarmed 或无窗口时零开销直返——结算框挂起态无需接 tick) +func tick(delta: float) -> void: + if not _armed or window_s <= 0.0: + return + elapsed += delta + if elapsed >= window_s: + disarm() + expired.emit() diff --git a/game/presentation/armed_timer.gd.uid b/game/presentation/armed_timer.gd.uid new file mode 100644 index 00000000..9df7ad34 --- /dev/null +++ b/game/presentation/armed_timer.gd.uid @@ -0,0 +1 @@ +uid://4avok0unhaek diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 724ac29e..691f1587 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -53,7 +53,15 @@ var _level_active := false var _seed_edit: LineEdit var _resume_btn: Button # 继续上局按钮句柄(迭代 8 卡②——有断点才亮) var confirm_dialog: ConfirmDialog # 通用确认弹窗(二轮修改批 A——新冒险互斥确认弹窗化) -var _settlement_stop_pending := false # 卡⑥ 战斗胜利总结结算框挂起态(确认「下一节点」后进选路页) +var _settlement_stop_timer := ArmedTimer.new() # 卡⑥ 结算框挂起计时(T3/D3 P4 乙——裸布尔暂存机制收口;窗口 0=挂起到显式回落) +var _settlement_stop_pending: bool: # 挂起态属性化(探针/冒烟直读写接缝语义零变化——读=armed,写=arm/disarm) + get: + return _settlement_stop_timer.is_armed() + set(v): + if v: + _settlement_stop_timer.arm() + else: + _settlement_stop_timer.disarm() var _pending_candidates: Array = [] # 结算框挂起期的选路候选暂存(choices_presented 载荷) var _last_gain_amount := 0 # 最近一笔钱包入账(卡⑥ GP 增量取数面——wallet_changed gain 载荷) var _pending_bonus := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用) diff --git a/game/presentation/views/ap_bar_view.gd b/game/presentation/views/ap_bar_view.gd index 128057c1..cba6b97b 100644 --- a/game/presentation/views/ap_bar_view.gd +++ b/game/presentation/views/ap_bar_view.gd @@ -31,12 +31,15 @@ var _rap_label: Label var _rap_feedback: Label var _last_rap := 0 # 上次投影值(清零反馈判据——禁解析自身 label 文本当状态) var refresh_cost: int = 0 # 表值驱动(装配经 set_refresh_cost 注入 ActionEconomyTable.rap_refresh_cost) -var _confirm_armed := false -var _arm_tween: Tween +var _arm_timer := ArmedTimer.new() # 弃图刷新确认窗(T3/D3 P4 乙——tween 秒表机制收口;窗口=REFRESH_CONFIRM_WINDOW_S) + + +const REFRESH_CONFIRM_WINDOW_S := 4.0 # 弃图刷新确认窗(原 tween_interval 字面量升常量——语义零变化) func _init() -> void: custom_minimum_size = Vector2(0, 44) + _arm_timer.expired.connect(_disarm) var sb := UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, 0, 1, Vector4(8, 4, 8, 4)) add_theme_stylebox_override("panel", sb) @@ -186,7 +189,7 @@ func confirm_visible() -> bool: func press_confirm_for_probe() -> void: - if _confirm_armed: + if _arm_timer.is_armed(): _disarm_and_fire() @@ -200,12 +203,7 @@ func _on_refresh_pressed() -> void: if _btn_refresh.disabled: return _confirm_row.visible = true - _confirm_armed = true - if _arm_tween != null and _arm_tween.is_valid(): - _arm_tween.kill() - _arm_tween = create_tween() - _arm_tween.tween_interval(4.0) - _arm_tween.tween_callback(_disarm) + _arm_timer.arm(REFRESH_CONFIRM_WINDOW_S) func _disarm_and_fire() -> void: @@ -214,10 +212,15 @@ func _disarm_and_fire() -> void: func _disarm() -> void: - _confirm_armed = false + _arm_timer.disarm() _confirm_row.visible = false +# 帧驱动(T3/D3——超时回落走 ArmedTimer;unarmed 零开销直返) +func _process(delta: float) -> void: + _arm_timer.tick(delta) + + # 资源面拒绝反馈(pattern 1 resource_face 腿):AP 数字脉冲 func pulse_ap() -> void: _pulse(_ap_label) diff --git a/game/presentation/views/level_map_screen.gd b/game/presentation/views/level_map_screen.gd index 74155e7e..a03bb209 100644 --- a/game/presentation/views/level_map_screen.gd +++ b/game/presentation/views/level_map_screen.gd @@ -61,14 +61,14 @@ var _cand_ids: Array[int] = [] # 本停点候选 id 集(图上命 var _content_size := Vector2(620, 240) # 图内容包络(open_choices 按图拟合——画布 min 尺寸随它) var _hover_id := -1 # 悬停预承诺候选(-1 = 无) var abandon_btn: Button # 放弃入口(可见性归组合根 set_abandon_available——只在冒险模式开) -var _abandon_armed := false # 二次确认态(首击置位、二击提交、超时/重开回落) -var _confirm_left := 0.0 # 确认态剩余秒数(_process 倒计时+脉冲驱动) +var _abandon_timer := ArmedTimer.new() # 放弃确认窗(T3/D3 P4 乙——_process 帧驱动倒计时机制收口;窗口=CONFIRM_WINDOW_S) var _node_icons := {} # 节点图标贴图(load_tex 守卫——缺位 null → 字形回退,冒烟/无素材检出安全) var _noise_rect: TextureRect # 屏底石纹(换层色板渐变承载面——null = 纯色回退无挂点) var _tint_level := 0 # 当前已应用 tint 的层号(0 = 未应用) func _init() -> void: + _abandon_timer.expired.connect(_reset_abandon_confirm) # 节点图标贴图装载(样张 v1 冻结形制——load_tex 守卫,缺位回退现行字形) for t in TEX_NODE_ICONS: _node_icons[t] = UiKit.load_tex(TEX_NODE_ICONS[t]) @@ -201,34 +201,33 @@ func set_abandon_available(v: bool) -> void: # 放弃二次确认(布局决策 3):首击转确认态(文案+危险红脉冲约 3 秒超时回落),二击才提交。 # 单击不提交——ui_probe 战斗中 pressed.emit() 单击路径保持「未受理」语义(战斗中拒绝归组合根)。 func _on_abandon_pressed() -> void: - if not _abandon_armed: - _abandon_armed = true - _confirm_left = CONFIRM_WINDOW_S + if not _abandon_timer.is_armed(): + _abandon_timer.arm(CONFIRM_WINDOW_S) abandon_btn.text = "再点一次确认放弃——不可逆" + if ArmedTimer.motion_still(): + abandon_btn.modulate = Color(1, 1, 1, 0.75) # 减动效静态基色(脉冲层不再驱动) return _reset_abandon_confirm() abandon_requested.emit() func _reset_abandon_confirm() -> void: - _abandon_armed = false - _confirm_left = 0.0 + _abandon_timer.disarm() abandon_btn.modulate = Color(1, 1, 1, 1) abandon_btn.text = "放弃" # 打磨包 2026-09-29——文案收敛 level-map 4 字上限(确认态转义面见 _on_abandon_pressed) func _process(delta: float) -> void: - if not _abandon_armed: + if not _abandon_timer.is_armed(): return - _confirm_left -= delta - if _confirm_left <= 0.0: - _reset_abandon_confirm() - return - # 危险红边框脉冲(单段呼吸约 1Hz——闪烁基线 ≤3 次/秒内;减动效 = 静态半透不闪) - if UiKit.motion_scale < 1.0: + _abandon_timer.tick(delta) + if not _abandon_timer.is_armed(): + return # 窗口到点回落(expired 信号已驱动 _reset_abandon_confirm) + # 危险红边框脉冲(单段呼吸约 1Hz——闪烁基线 ≤3 次/秒内;减动效 = 静态半透不闪——档判据单点 motion_still) + if ArmedTimer.motion_still(): abandon_btn.modulate = Color(1, 1, 1, 0.75) else: - var k := 0.5 + 0.5 * sin(_confirm_left * TAU) + var k := 0.5 + 0.5 * sin(_abandon_timer.elapsed * TAU) abandon_btn.modulate = Color(1, 1, 1, 0.65 + 0.35 * k)