merge: 迭代13工程债与测试基建批落籍(D1探针三态+D2只读出口+D3 ArmedTimer+D4拆件1119+T5判不合+D5四件+D6爆破工兵包启用——六条目销账+P1甲/P2甲落文;worktree m4iter13-debt-20261004)
This commit is contained in:
commit
eff8cb8792
23 files changed
+799
-396
No files matched your search
+17
-15
@@ -46,9 +46,11 @@
|
|||||||
|
|
||||||
**现状**:同调批 T6 前置半日影响评估(P5 甲)破面判定=**大**:中立移除格态触排雷玩法(第三格态「已移除」——挖掘/插旗/和弦/邻接数字/清空判定全要问它)+战斗结算(完美排雷读数与旗判定基线需定义移除格语义)+清图判定(移除安全格改分母——BoardData 清空判定+MC 清图率读数口径)三面,且底稿欠规格两处口径(被移除雷格是否仍计入邻接数字/移除格数字呈现)实做需追加拍板。用户裁(2026-10-03):拆后批+blast_kit 行标 deferred 留位(effect_kinds cell_removal 已在 schema 枚举、行位保留,后批立项时换装 form=active_charged 即启)。后批开工前置=两处口径拍板+三面改动面任务书。
|
**现状**:同调批 T6 前置半日影响评估(P5 甲)破面判定=**大**:中立移除格态触排雷玩法(第三格态「已移除」——挖掘/插旗/和弦/邻接数字/清空判定全要问它)+战斗结算(完美排雷读数与旗判定基线需定义移除格语义)+清图判定(移除安全格改分母——BoardData 清空判定+MC 清图率读数口径)三面,且底稿欠规格两处口径(被移除雷格是否仍计入邻接数字/移除格数字呈现)实做需追加拍板。用户裁(2026-10-03):拆后批+blast_kit 行标 deferred 留位(effect_kinds cell_removal 已在 schema 枚举、行位保留,后批立项时换装 form=active_charged 即启)。后批开工前置=两处口径拍板+三面改动面任务书。
|
||||||
|
|
||||||
**归属**:后批(迭代 12 或独立 mini-loop,与反制批/双层 BD 批次排序待用户发令)
|
**两口径裁决落文(2026-10-04 用户,任务书 v1.2 P1/P2)**:**P1 甲**=被移除雷格仍计入邻接数字——邻接数字=原始雷数历史真相冻结(digit_of 静态板值零重算);**P2 甲**=移除格保留原数字、视觉退色降档(CellUnit 呈现态,暗淡记忆态同族)。**实施归属 P3 甲=迭代 13 工程债与测试基建批随批做完**(超推荐案乙系知情裁决)。
|
||||||
|
|
||||||
**状态**:待立项(拆后批)
|
**归属**:~~后批(迭代 12 或独立 mini-loop)~~→迭代 13 工程债与测试基建批(P3 甲裁 2026-10-04)
|
||||||
|
|
||||||
|
**状态**:**已落籍销账(2026-10-04,迭代 13 T7+T8,worktree m4iter13-debt-20261004 提交 89ed0d1)**——blast_kit 换装(form=active_charged+slot_cost 1+charges 2+effect_kinds 挂 cell_removal+清 deferred);cell_removal 语义=移除集仅「未揭示·无旗·是雷」→清图分母/连锁/C_dig 模拟天然零涉(判定逻辑零改动,P1 甲兑付),交互守卫两点(dig/flag sub=target_removed 零消耗拒绝);呈现=CellUnit removed_dim 退色暗幕(P2 甲)+_diggable 排除;充能编排零新代码(表驱动+营地同调位装配+use_aux consume 扣减全现成);MC 口径注记=换装后 form 非空被 transitional_rows 过滤,模拟投放池零涉零漂移(静态证明零跑测)。闸=冒烟 718/0+run_all 15+click 三种子+探针绿。
|
||||||
|
|
||||||
## 战斗循环
|
## 战斗循环
|
||||||
|
|
||||||
@@ -235,9 +237,9 @@
|
|||||||
|
|
||||||
**现状**:ui_click_probe 现行断言基于短局夹具,未适配完整局——结算演播硬锁 v3.1(锁期点击缓存+演播完重放)落地后,探针的合成点击时序与硬锁交互存在语义差(perf 审计 2026-10-02 实测双态全红归 T17 在飞批,落籍后未重适配)。需把探针点击注入改为完整局语境(停点/锁期/重放三态断言)。
|
**现状**:ui_click_probe 现行断言基于短局夹具,未适配完整局——结算演播硬锁 v3.1(锁期点击缓存+演播完重放)落地后,探针的合成点击时序与硬锁交互存在语义差(perf 审计 2026-10-02 实测双态全红归 T17 在飞批,落籍后未重适配)。需把探针点击注入改为完整局语境(停点/锁期/重放三态断言)。
|
||||||
|
|
||||||
**归属**:M-4 迭代 11 窗口评估(测试基建,与奖励重构读数批同窗排序)
|
**归属**:~~M-4 迭代 11 窗口评估~~(**归属修订 2026-10-04**:迭代 11 已落籍,归属失效改判迭代 13 工程债与测试基建批——任务书 §一)
|
||||||
|
|
||||||
**状态**:待立项(挂账勿重开——2026-10-02 出口段定性,本条为补登记)
|
**状态**:**已落籍销账(2026-10-04,迭代 13 T1,worktree m4iter13-debt-20261004 提交 cd7d3ea)**——探针分派链序修正(shop 停点先于 level_screen,迭代 11 商店房双 visible 实证烧预算根因)+停点/锁期/重放三态断言(_locktest_tick 脚本化:旗布尔=恰一次送达探测器)+PROBE_SEED 三种子绿(7/11/23)+演播期豁免保留注记;冒烟 718/0+run_all 15+t17_defer 绿。
|
||||||
|
|
||||||
#### 资产升档重产批(art-bible v3.0 中修挂账③,2026-09-29 登记)
|
#### 资产升档重产批(art-bible v3.0 中修挂账③,2026-09-29 登记)
|
||||||
|
|
||||||
@@ -302,38 +304,38 @@
|
|||||||
|
|
||||||
**现状**:三处武装确认语义异构(AP 条 chips+4s tween 自动回落 / 选路页放弃钮 3s _process 倒计时+危险红呼吸+减动效静态变体 / 战斗总结结算框纯载荷暂存无计时无视觉)——保语义收口需可插拔视觉+双计时机制+可选脉冲,共享件本体 40-60 行而每处仅省约 6 行,且动 press_confirm_for_probe/confirm_visible 探针语义,修复批 T4 降级不做。来源:代码质量审计(ap_bar_view/level_map_screen/main 三份手抄)
|
**现状**:三处武装确认语义异构(AP 条 chips+4s tween 自动回落 / 选路页放弃钮 3s _process 倒计时+危险红呼吸+减动效静态变体 / 战斗总结结算框纯载荷暂存无计时无视觉)——保语义收口需可插拔视觉+双计时机制+可选脉冲,共享件本体 40-60 行而每处仅省约 6 行,且动 press_confirm_for_probe/confirm_visible 探针语义,修复批 T4 降级不做。来源:代码质量审计(ap_bar_view/level_map_screen/main 三份手抄)
|
||||||
|
|
||||||
**归属**:表现层打磨批(M-4 窗口)
|
**归属**:~~表现层打磨批(M-4 窗口)~~→迭代 13 工程债批(P4 乙裁 2026-10-04)
|
||||||
|
|
||||||
**状态**:待办(2026-10-02 T4 降级登记,做了半套不如不做)
|
**状态**:**已落籍销账(2026-10-04,迭代 13 T3,worktree m4iter13-debt-20261004 提交 fec3064)**——P4 乙=ArmedTimer helper(武装/回落/超时/减动效档四能力,motion_still 档判单点)三处计时机制收口:AP 条 tween 秒表→timer(三探针缝语义等价零动)、放弃钮 _process 倒计时→timer.tick+elapsed 相位(1Hz 呼吸视觉零变化)、结算框裸布尔→挂起态属性化(窗口 0);视觉三处各自保留;ui_click_probe :513-515 零改全绿实证。
|
||||||
|
|
||||||
#### main.gd 组合根二期瘦身(1237 → ~900)
|
#### main.gd 组合根二期瘦身(1237 → ~900)
|
||||||
|
|
||||||
**现状**:T3 已迁排播泵/aux 两步交互/GP 影子账三件;到 ~900 还差三选一与替换子交互编排(约 115 行)、debug 模式接线、营地/商店投影 handler 等家族。来源:T3 执行报告
|
**现状**:T3 已迁排播泵/aux 两步交互/GP 影子账三件;到 ~900 还差三选一与替换子交互编排(约 115 行)、debug 模式接线、营地/商店投影 handler 等家族。来源:T3 执行报告。**行数基线校正(2026-10-04)**:1237 系 T3 拆后读数——开工日实测 1378(迭代 11/12 增量),批末 **1119**(净减 259)。
|
||||||
|
|
||||||
**归属**:下一表现层结构批
|
**归属**:~~下一表现层结构批~~→迭代 13 工程债批
|
||||||
|
|
||||||
**状态**:待办(2026-10-02 登记,ADR-0001 接线留组合根前提不变)
|
**状态**:**已落籍销账(2026-10-04,迭代 13 T4,worktree m4iter13-debt-20261004 提交 4c79310)**——家族①=bonus_interact_controller.gd(241 行,三选一/替换子编排+TP0 复合键卡片装配)、家族③=camp_shop_presenter.gd(172 行,营地/商店投影与四写口);接线留组合根(ADR-0001,十三信号组合根接至 on_* 入口,meta/save_manager/runner 现值回调注入——探针 isolate_save 换注实证);家族②debug 接线=契约 3 组合根绑定面不迁移(节头注记记档)。**~900±50 锚差归因**:三家族可迁清单实测上限 ~336 行(任务书自估 315),锚位与族级清单不自洽——1333−315+胶水≈1018 已高于锚,净减 259 为清单内全量;再深切属新立项面。闸=冒烟 718/0 断言链全绿+三种子 click+run_all 15。
|
||||||
|
|
||||||
#### aux 替换子交互容器操作二期合并
|
#### aux 替换子交互容器操作二期合并
|
||||||
|
|
||||||
**现状**:通关渠道(carry 序号寻址+hp 账本收尾)与节点渠道(库存 handle 寻址+node 收尾)已合共用前置谓词,丢弃/授予/收尾三步仍分轨——强合需适配层,收益低。来源:T5 执行报告保守注记
|
**现状**:通关渠道(carry 序号寻址+hp 账本收尾)与节点渠道(库存 handle 寻址+node 收尾)已合共用前置谓词,丢弃/授予/收尾三步仍分轨——强合需适配层,收益低。来源:T5 执行报告保守注记
|
||||||
|
|
||||||
**归属**:随同调位实施批(attunement)顺路评估
|
**归属**:~~随同调位实施批(attunement)顺路评估~~(**归属变更 2026-10-04**:同调批已落籍未做此评估,改判迭代 13 工程债批——任务书 §一)
|
||||||
|
|
||||||
**状态**:待办
|
**状态**:**评估收口·判不合(2026-10-04,迭代 13 T5,合法出口非缩水)**——三轴语义相异(寻址 carry 序号 vs 库存句柄/持久域 跨关 vs 单关/收尾 断点快照 vs 不载),原实现已带三处「保守不合注记在案」(adventure_runner :479/:495/:566/:676);强合需适配层且触碰核心 RNG 邻接重构(契约 1 高危区)换 ~30-50 行收益。零代码,零 MC。
|
||||||
|
|
||||||
#### 探针白盒私有域访问改 *_probe() 只读出口
|
#### 探针白盒私有域访问改 *_probe() 只读出口
|
||||||
|
|
||||||
**现状**:ui_input_probe 等直入 BoardView._cells/_diggable/_layout 等私有域(重构即碎),fx_probe 已有只读出口先例。来源:代码质量审计(登记债务,修复批边界外)
|
**现状**:ui_input_probe 等直入 BoardView._cells/_diggable/_layout 等私有域(重构即碎),fx_probe 已有只读出口先例。来源:代码质量审计(登记债务,修复批边界外)
|
||||||
|
|
||||||
**归属**:探针基建批
|
**归属**:~~探针基建批~~→迭代 13 工程债批
|
||||||
|
|
||||||
**状态**:待办
|
**状态**:**已落籍销账(2026-10-04,迭代 13 T2,worktree m4iter13-debt-20261004 提交 2241778)**——BoardView cells_probe/diggable_probe/hover_probe/fog_params_probe+level_map_screen layout_probe/canvas_probe 六只读出口(fx_probe 先例形态);直入点改写 26 处(任务书盘点 ~22+断言消息实参隐性直读 4——_hover 1/_fog_lantern·_fog_enabled 2/_cells 消息实参 3 内含);grep 复核零残留;06 原条目「BoardView._layout」系笔误(_layout/_canvas 属 level_map_screen)随批勘正。
|
||||||
|
|
||||||
#### 杂项尾差(一批打包)
|
#### 杂项尾差(一批打包)
|
||||||
|
|
||||||
adventure_table.tres 补 bonus_candidate_count 字段落档(T5 注:脚本默认 3=现行值,落档属结构改动);grant_aux_item 返回值接留痕同型件 level_runner :438 区(T5 注);README.md:402 提及已删 _autoplay_battle_perfect 旧名(T6 注);critical-paths.md:6「切片 96/96」过期字样(T7 注);hero_info_panel_view:29 散文提及已删 PANEL_HERO 色名(T7 注)。来源:T4-T7 执行报告遗留观察
|
adventure_table.tres 补 bonus_candidate_count 字段落档(T5 注:脚本默认 3=现行值,落档属结构改动);grant_aux_item 返回值接留痕同型件 level_runner :438 区(T5 注);README.md:402 提及已删 _autoplay_battle_perfect 旧名(T6 注);critical-paths.md:6「切片 96/96」过期字样(T7 注);hero_info_panel_view:29 散文提及已删 PANEL_HERO 色名(T7 注)。来源:T4-T7 执行报告遗留观察
|
||||||
|
|
||||||
**归属**:下一清扫批顺路
|
**归属**:~~下一清扫批顺路~~→迭代 13 工程债批
|
||||||
|
|
||||||
**状态**:待办
|
**状态**:**已落籍销账(2026-10-04,迭代 13 T6,worktree m4iter13-debt-20261004 提交 d78b8d4)**——首件 adventure_table.tres bonus_candidate_count 核证已落档销账(迭代 7-11 间落地,字段在档/schema 校验在位/消费点已读表值);余四件全清=grant_aux_item 节点渠道返回值接留痕(镜像 :580 防御兜底范式)/README 旧名 _autoplay_battle_perfect 改 _decide_act 分派口径/critical-paths「切片 96/96」换代 718/0 口径/hero_info_panel_view 死色名 PANEL_HERO 注记校正。
|
||||||
+1
-1
@@ -401,7 +401,7 @@ Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/s
|
|||||||
**落地变更**:
|
**落地变更**:
|
||||||
- `auto_player.gd` 增完美策略求解器(`perfect_action`/`_deduce`):single + subset 推理不动点(A1 F1 `measure_inferability` 同族规则集,**去 safe_set oracle**——约束未知邻格含雷格,比生成期闸保守);**零盲挖**(只挖已证安全格 = 构造保证零踩雷,扫描末断言兜底违例 quit(1))· **已证雷格全标旗**(旗先于挖——轮末 weaken + 魔法 首次充能,雷上旗不阻连锁)· 卡死且资源可 → `b1.player_request_refresh()` 轮内刷新 · 否则停手(残 AP 1:1 入 RAP)。输入只经 A2 查询面(reveal_record/is_revealed/has_flag),**禁读棋盘雷区真值**——测推理上界,非全知作弊。core 层零改动;
|
- `auto_player.gd` 增完美策略求解器(`perfect_action`/`_deduce`):single + subset 推理不动点(A1 F1 `measure_inferability` 同族规则集,**去 safe_set oracle**——约束未知邻格含雷格,比生成期闸保守);**零盲挖**(只挖已证安全格 = 构造保证零踩雷,扫描末断言兜底违例 quit(1))· **已证雷格全标旗**(旗先于挖——轮末 weaken + 魔法 首次充能,雷上旗不阻连锁)· 卡死且资源可 → `b1.player_request_refresh()` 轮内刷新 · 否则停手(残 AP 1:1 入 RAP)。输入只经 A2 查询面(reveal_record/is_revealed/has_flag),**禁读棋盘雷区真值**——测推理上界,非全知作弊。core 层零改动;
|
||||||
- `window_selection` 增 `heal_no_waste` 选项(缺口 < 治疗量不用;完美档启用)+ 完美档轮次门解除(round_gate=0);
|
- `window_selection` 增 `heal_no_waste` 选项(缺口 < 治疗量不用;完美档启用)+ 完美档轮次门解除(round_gate=0);
|
||||||
- `level_autoplay.gd` 增 `policy` 参数(默认 "normal"——tune_sim_controller/defense_scan/level_debug 既有调用零改动);完美档驱动 `_autoplay_battle_perfect`(move/dig/flag 走 A2 settle_batch、refresh 走 B1 编排、连续拒绝 ≥3 停手防死循环);st 增 `mine_hits`/`refreshes` 计数(mine_hit 证据行埋点);
|
- `level_autoplay.gd` 增 `policy` 参数(默认 "normal"——tune_sim_controller/defense_scan/level_debug 既有调用零改动);完美档经 `policy="perfect"` 由 `_decide_act` 分派承载(原专职驱动 `_autoplay_battle_perfect` 已随样板收口并入分派——move/dig/flag 走 A2 settle_batch、refresh 走 B1 编排、连续拒绝 ≥3 停手防死循环);st 增 `mine_hits`/`refreshes` 计数(mine_hit 证据行埋点);
|
||||||
- `level_monte_carlo.gd` 增第 3 CLI 参 `[policy]`(normal/perfect/both,缺省 normal 兼容既有文档命令)+ 策略/踩雷/刷新列;脚注 F5 带文案同步 [30,50] 带锚定稿。
|
- `level_monte_carlo.gd` 增第 3 CLI 参 `[policy]`(normal/perfect/both,缺省 normal 兼容既有文档命令)+ 策略/踩雷/刷新列;脚注 F5 带文案同步 [30,50] 带锚定稿。
|
||||||
|
|
||||||
**完美档基线(N=30 · 种子 4200–4229 · 耗时 597.6s)**:**0 加成通关率 100%**(弱策略基线档 60%)、通关剩余 HP 均 **47.4/50**、整关承伤均 3.5(p90 18.0)、普通场承伤 0.5%(带 ≤20%)、Boss 场 5.3%(带 ≤50%)、踩雷 0.00/局(断言 ✓)、刷新 0.23/局、跳战率 20%。+50%~+200% 全档位恒 100% 满血通关(+200% 承伤均 0.4)——**技能轴上界完全碾压现行曲线:F5 带与 F6 S/K 区间对完美档零分辨力**(区间模型无策略水平轴的结构性证实;calibration_gap 获得机制解释与定量上界)。
|
**完美档基线(N=30 · 种子 4200–4229 · 耗时 597.6s)**:**0 加成通关率 100%**(弱策略基线档 60%)、通关剩余 HP 均 **47.4/50**、整关承伤均 3.5(p90 18.0)、普通场承伤 0.5%(带 ≤20%)、Boss 场 5.3%(带 ≤50%)、踩雷 0.00/局(断言 ✓)、刷新 0.23/局、跳战率 20%。+50%~+200% 全档位恒 100% 满血通关(+200% 承伤均 0.4)——**技能轴上界完全碾压现行曲线:F5 带与 F6 S/K 区间对完美档零分辨力**(区间模型无策略水平轴的结构性证实;calibration_gap 获得机制解释与定量上界)。
|
||||||
|
|||||||
@@ -594,7 +594,9 @@ func _resolve_node_replace(discard_handle: int) -> bool:
|
|||||||
push_error("[冒险结构] resolve_node_replace 拒绝——句柄不在持有集: %d" % discard_handle)
|
push_error("[冒险结构] resolve_node_replace 拒绝——句柄不在持有集: %d" % discard_handle)
|
||||||
return false
|
return false
|
||||||
for iid in incoming:
|
for iid in incoming:
|
||||||
_runner.grant_aux_item(String(iid), "reward_node")
|
var rec: Dictionary = _runner.grant_aux_item(String(iid), "reward_node")
|
||||||
|
if rec.is_empty():
|
||||||
|
push_warning("[冒险结构] 替换子道具授予被拒(防御兜底留痕): %s" % String(iid))
|
||||||
# 放弃新件(discard_handle < 0)= 原持有集不动、新件不获取
|
# 放弃新件(discard_handle < 0)= 原持有集不动、新件不获取
|
||||||
_finish_node_bonus("aux_item", incoming.size())
|
_finish_node_bonus("aux_item", incoming.size())
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -632,6 +632,13 @@ func use_aux(handle: int, target: Vector2i) -> Dictionary:
|
|||||||
# 掘进蓄能兑付(迭代 11 同调批 T6——临时 AP 当轮生效,grant_ap 同 sapper_order 通道)
|
# 掘进蓄能兑付(迭代 11 同调批 T6——临时 AP 当轮生效,grant_ap 同 sapper_order 通道)
|
||||||
if not action_economy.grant_ap(int(v.magnitude)):
|
if not action_economy.grant_ap(int(v.magnitude)):
|
||||||
return {"ok": false, "reason": "行动点加量被拒(相位防御)"}
|
return {"ok": false, "reason": "行动点加量被拒(相位防御)"}
|
||||||
|
if kinds.has("cell_removal"):
|
||||||
|
# 爆破工兵包兑付(迭代 13 T8/D6——中立移除雷格:未揭示/无旗/是雷过滤归排雷玩法;
|
||||||
|
# P1 甲邻接数字冻结零重算、清图分母零涉(雷格不入分母);空集拒绝零消耗——
|
||||||
|
# consume 之前 return(拒绝面注记扩「③内零消耗分支」),拒绝 reason 可呈现)
|
||||||
|
var blasted: Array = board.remove_mines_in(v.resolved)
|
||||||
|
if blasted.is_empty():
|
||||||
|
return {"ok": false, "reason": "爆破工兵包:3×3 区域内无可移除雷格(须未揭示且未插旗)"}
|
||||||
var rec: Dictionary = aux_inventory.consume(handle)
|
var rec: Dictionary = aux_inventory.consume(handle)
|
||||||
# 同调主动件充能扣减(迭代 11 第①批 T6——共享账本直改:adventure/level/settlement 同一字典引用)
|
# 同调主动件充能扣减(迭代 11 第①批 T6——共享账本直改:adventure/level/settlement 同一字典引用)
|
||||||
if not rec.is_empty() and String(rec.get("source_class", "")) == "attunement":
|
if not rec.is_empty() and String(rec.get("source_class", "")) == "attunement":
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ var action_economy: ActionEconomy
|
|||||||
var _board: BoardData = null
|
var _board: BoardData = null
|
||||||
var _revealed := {} # Vector2i → {"kind": "safe"/"mine", "digit": int}
|
var _revealed := {} # Vector2i → {"kind": "safe"/"mine", "digit": int}
|
||||||
var _flags := {} # Vector2i → true(棋盘单态;跨轮保留随图存续)
|
var _flags := {} # Vector2i → true(棋盘单态;跨轮保留随图存续)
|
||||||
|
var _removed := {} # Vector2i → true(迭代 13 T8/D6——中立移除雷格集;随 install/retire 清空)
|
||||||
var _hero := Vector2i.ZERO
|
var _hero := Vector2i.ZERO
|
||||||
var _spawn: Dictionary[Vector2i, bool] = {}
|
var _spawn: Dictionary[Vector2i, bool] = {}
|
||||||
|
|
||||||
@@ -73,6 +74,26 @@ func clear_map_cleared() -> bool:
|
|||||||
return safe_remaining() == 0
|
return safe_remaining() == 0
|
||||||
|
|
||||||
|
|
||||||
|
# ---- 中立移除(迭代 13 T8/D6——P1 甲:邻接数字=原始雷数冻结零重算;P2 甲:移除格呈现退色降档;
|
||||||
|
# 移除集=未揭示、无旗、是雷——旗判定基线零回溯;雷格不入清图分母/连锁/C_dig 模拟,判定逻辑零改动) ----
|
||||||
|
|
||||||
|
func remove_mines_in(cells: Array) -> Array:
|
||||||
|
var out: Array = []
|
||||||
|
for c in cells:
|
||||||
|
var cell: Vector2i = c
|
||||||
|
if not _board.in_bounds(cell) or is_revealed(cell) or has_flag(cell) or not _board.is_mine(cell):
|
||||||
|
continue
|
||||||
|
if _removed.has(cell):
|
||||||
|
continue
|
||||||
|
_removed[cell] = true
|
||||||
|
out.append(cell)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
func is_removed(c: Vector2i) -> bool:
|
||||||
|
return _removed.has(c)
|
||||||
|
|
||||||
|
|
||||||
# 未开格存在查询(aux 道具 usable 预判消费——全板扫描只读面)
|
# 未开格存在查询(aux 道具 usable 预判消费——全板扫描只读面)
|
||||||
func has_unrevealed() -> bool:
|
func has_unrevealed() -> bool:
|
||||||
for y in _board.height:
|
for y in _board.height:
|
||||||
@@ -100,6 +121,7 @@ func install_board(b: BoardData, during_round := false) -> Dictionary:
|
|||||||
_board = b
|
_board = b
|
||||||
_revealed.clear()
|
_revealed.clear()
|
||||||
_flags.clear()
|
_flags.clear()
|
||||||
|
_removed.clear()
|
||||||
_spawn = {}
|
_spawn = {}
|
||||||
for c in b.spawn_cells():
|
for c in b.spawn_cells():
|
||||||
_spawn[c] = true
|
_spawn[c] = true
|
||||||
@@ -127,6 +149,7 @@ func retire_board(during_round := false) -> Dictionary:
|
|||||||
_board = null
|
_board = null
|
||||||
_revealed.clear()
|
_revealed.clear()
|
||||||
_flags.clear()
|
_flags.clear()
|
||||||
|
_removed.clear()
|
||||||
return _outcome(true, "executed", "", "")
|
return _outcome(true, "executed", "", "")
|
||||||
|
|
||||||
|
|
||||||
@@ -303,6 +326,8 @@ func _board_face_dig(c: Vector2i) -> String:
|
|||||||
return "not_adjacent"
|
return "not_adjacent"
|
||||||
if is_revealed(c):
|
if is_revealed(c):
|
||||||
return "target_revealed"
|
return "target_revealed"
|
||||||
|
if _removed.has(c):
|
||||||
|
return "target_removed" # 迭代 13 T8/D6——中立移除格不可交互(拒绝先于消耗,pattern 1 board_face 腿)
|
||||||
# 旗不拦挖掘(插旗不可逆批 2026-09-19):有旗格直接挖 = 对承诺的兑现,兑付改判归 战斗结算。
|
# 旗不拦挖掘(插旗不可逆批 2026-09-19):有旗格直接挖 = 对承诺的兑现,兑付改判归 战斗结算。
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@@ -356,6 +381,8 @@ func _do_flag_place(c: Vector2i) -> Dictionary:
|
|||||||
sub = "target_revealed"
|
sub = "target_revealed"
|
||||||
elif has_flag(c):
|
elif has_flag(c):
|
||||||
sub = "flag_present"
|
sub = "flag_present"
|
||||||
|
elif _removed.has(c):
|
||||||
|
sub = "target_removed" # 迭代 13 T8/D6——中立移除格不可交互
|
||||||
if sub != "":
|
if sub != "":
|
||||||
return _outcome(false, "rejected", "board_face", sub)
|
return _outcome(false, "rejected", "board_face", sub)
|
||||||
if not action_economy.try_spend("flag_place"):
|
if not action_economy.try_spend("flag_place"):
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
; 迭代 11 第①批 T1(2026-10-03):追加七件同调行(form 非空——永不入过渡渠道);值全 provisional 归 难度曲线与Boss。
|
; 迭代 11 第①批 T1(2026-10-03):追加七件同调行(form 非空——永不入过渡渠道);值全 provisional 归 难度曲线与Boss。
|
||||||
; nest_pendulum/blast_kit 维持过渡行,T7 渠道退役时原位换装 form=active_charged;sapper_order/显影尘/地穴全图卷/罗盘 T7 逐件销账。
|
; nest_pendulum 已换装 active_charged(迭代 11 第①批);blast_kit 已换装 cell_removal+active_charged(迭代 13 T8/D6——中立移除雷格启用);sapper_order/显影尘/地穴全图卷/罗盘 T7 逐件销账。
|
||||||
rows = [{
|
rows = [{
|
||||||
"item_id": "probe_dust",
|
"item_id": "probe_dust",
|
||||||
"display_name": "显影尘",
|
"display_name": "显影尘",
|
||||||
@@ -57,11 +57,13 @@ rows = [{
|
|||||||
"item_id": "blast_kit",
|
"item_id": "blast_kit",
|
||||||
"display_name": "爆破工兵包",
|
"display_name": "爆破工兵包",
|
||||||
"dimension_tags": ["action", "risk"],
|
"dimension_tags": ["action", "risk"],
|
||||||
"effect_kinds": ["ap_grant", "mine_reveal"],
|
"effect_kinds": ["ap_grant", "mine_reveal", "cell_removal"],
|
||||||
"target": "area_3x3",
|
"target": "area_3x3",
|
||||||
"magnitude": 4,
|
"magnitude": 4,
|
||||||
"use_phase": "active_dig",
|
"use_phase": "active_dig",
|
||||||
"deferred": true
|
"form": "active_charged",
|
||||||
|
"slot_cost": 1,
|
||||||
|
"charges_per_adventure": 2
|
||||||
}, {
|
}, {
|
||||||
"item_id": "venom_edge",
|
"item_id": "venom_edge",
|
||||||
"display_name": "淬毒刃纹",
|
"display_name": "淬毒刃纹",
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://4avok0unhaek
|
||||||
@@ -95,6 +95,7 @@ func attempt_use(handle: int, cell: Variant = null) -> void:
|
|||||||
_use_selection = 0
|
_use_selection = 0
|
||||||
refresh_slots()
|
refresh_slots()
|
||||||
refresh_overlays()
|
refresh_overlays()
|
||||||
|
board_view.refresh() # 迭代 13 T8/D6——移除格即时落图(无移除态的件 diff 零变化幂等)
|
||||||
var arow: Dictionary = db.aux_item_row(String(v.get("item_id", "")))
|
var arow: Dictionary = db.aux_item_row(String(v.get("item_id", "")))
|
||||||
toast.show_toast("已使用 %s" % String(arow.get("display_name", "")), "info")
|
toast.show_toast("已使用 %s" % String(arow.get("display_name", "")), "info")
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,241 @@
|
|||||||
|
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||||
|
# 提升三选一与替换子交互控制器(迭代 13 T4/D4——二期家族① 自 main.gd 迁出;规范 1.1 单一职责):
|
||||||
|
# 三选一停点编排(presented→pick→confirm/refresh→resolved→continue)+ 同调位替换子态
|
||||||
|
#(presented→chosen)+ TP0 令牌池卡片文案装配("kind|tier|relic_id" 复合键)+ 工坊显示名投影。
|
||||||
|
# 装配纪律(ADR-0001,仿 aux_interact_controller 先例):信号连接不在此发生——bonus_overlay
|
||||||
|
# 四信号与 adventure 两信号由组合根接至本类 on_* 入口;模态栈/演播后排窗/呈现刷新/终局面/
|
||||||
|
# 推进失败 fatal 经组合根回调注入;adventure/runner 随局更替——现值回调注入;兑付写调用唯一
|
||||||
|
# 经 adventure.pick_bonus/apply_node_bonus/resolve_bonus_replace(core 侧单口,本类零直写)。
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
const Narrator := preload("res://presentation/battle_narrator.gd")
|
||||||
|
|
||||||
|
var db
|
||||||
|
var _meta_get: Callable # () → 元进度账本现值(组合根 lambda——探针 isolate_save 换注面,迭代13 T4 实证)
|
||||||
|
var bonus_overlay: BonusPickOverlay
|
||||||
|
var end_overlay: BattleEndOverlay
|
||||||
|
var board_view: BoardView
|
||||||
|
var toast: ToastStack
|
||||||
|
var _save_get: Callable # () → 存档承载现值(同上——Attunement.slots 读数面)
|
||||||
|
var _adventure_get: Callable # () → 冒险结构现值(组合根 lambda——随局更替)
|
||||||
|
var _runner_get: Callable # () → 关卡推进器现值(组合根 lambda——reward_node 继续推进)
|
||||||
|
var _adventure_mode_get: Callable # () → 冒险模式标记现值(组合根 lambda)
|
||||||
|
var _open_modal: Callable # 模态栈单口(组合根)
|
||||||
|
var _open_modal_after_playback: Callable # 演播后排窗单口(T17——组合根)
|
||||||
|
var _close_modal: Callable
|
||||||
|
var _refresh_presentations: Callable
|
||||||
|
var _fatal_screen: Callable
|
||||||
|
var _gp_summary: Callable # () → 战斗总结 GP 行文案(组合根 lambda——_last_gain_amount 取数面在根)
|
||||||
|
|
||||||
|
var _pending_bonus := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用)
|
||||||
|
var _bonus_context := "level_clear" # 三选一面上下文(level_clear=通关停点→推进下一关 / reward_node=奖励节点→回图选路)
|
||||||
|
var _last_replace_items: Array = [] # 替换子态续玩重建缓存(aux_replace_presented 载荷)
|
||||||
|
var _last_replace_incoming := {} # 同上——incoming 记录
|
||||||
|
|
||||||
|
|
||||||
|
func _init(db_ref, meta_get: Callable, bonus_overlay_ref: BonusPickOverlay, end_overlay_ref: BattleEndOverlay,
|
||||||
|
board_view_ref: BoardView, toast_ref: ToastStack, save_get: Callable,
|
||||||
|
adventure_get: Callable, runner_get: Callable, adventure_mode_get: Callable,
|
||||||
|
open_modal: Callable, open_modal_after_playback: Callable, close_modal: Callable,
|
||||||
|
refresh_presentations: Callable, fatal_screen: Callable, gp_summary: Callable) -> void:
|
||||||
|
db = db_ref
|
||||||
|
_meta_get = meta_get
|
||||||
|
bonus_overlay = bonus_overlay_ref
|
||||||
|
end_overlay = end_overlay_ref
|
||||||
|
board_view = board_view_ref
|
||||||
|
toast = toast_ref
|
||||||
|
_save_get = save_get
|
||||||
|
_adventure_get = adventure_get
|
||||||
|
_runner_get = runner_get
|
||||||
|
_adventure_mode_get = adventure_mode_get
|
||||||
|
_open_modal = open_modal
|
||||||
|
_open_modal_after_playback = open_modal_after_playback
|
||||||
|
_close_modal = close_modal
|
||||||
|
_refresh_presentations = refresh_presentations
|
||||||
|
_fatal_screen = fatal_screen
|
||||||
|
_gp_summary = gp_summary
|
||||||
|
|
||||||
|
|
||||||
|
# 停点在途判据(组合根 _on_battle_end_continue 分派面——战斗总结确认后开三选一)
|
||||||
|
func has_pending() -> bool:
|
||||||
|
return not _pending_bonus.is_empty()
|
||||||
|
|
||||||
|
|
||||||
|
# 续玩停点恢复入口(true=已接手呈现,调用方中止后续分派)
|
||||||
|
func resume_if_pending() -> bool:
|
||||||
|
if _pending_bonus.is_empty():
|
||||||
|
return false
|
||||||
|
show_pick()
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
# 三选一停点(非末关通关恰一次):Boss 胜利先经战斗总结结算面(settlement-summary v1.1——钥石
|
||||||
|
# 增量行在其上),确认后进三选一面(level-clear.md v2);续玩停点恢复同入口(零自动推进契约)
|
||||||
|
func on_presented(payload: Dictionary) -> void:
|
||||||
|
if int(payload.get("aux_cleared", 0)) > 0:
|
||||||
|
toast.show_toast("上关剩余 %d 件魔法道具已随关卡结束清空" % int(payload.get("aux_cleared", 0)), "info") # 迭代 7 T4 清空反馈
|
||||||
|
_pending_bonus = payload.duplicate(true)
|
||||||
|
_bonus_context = "level_clear"
|
||||||
|
if bool(payload.get("replace_pending", false)):
|
||||||
|
# 续玩:替换子交互在途中断——重建三选一面后直落替换子态(候选不重抽)
|
||||||
|
show_pick()
|
||||||
|
return
|
||||||
|
end_overlay.open_victory(Narrator.battle_end_text("victory", false), "继续",
|
||||||
|
board_view.get_global_rect(),
|
||||||
|
"%s\n%s" % [String(_gp_summary.call()), Narrator.bonus_keystone_line(payload)])
|
||||||
|
_open_modal_after_playback.call(end_overlay)
|
||||||
|
_refresh_presentations.call()
|
||||||
|
|
||||||
|
|
||||||
|
# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 aux_replace_presented 续发直落子态)
|
||||||
|
# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;BonusPickOverlay 共用面,
|
||||||
|
# 道具件满位替换子交互同构)。原「魔法道具投放渠道」三选一(迭代 7 过渡形态)随批退役。
|
||||||
|
func present_node_bonus() -> void:
|
||||||
|
var adventure = _adventure_get.call()
|
||||||
|
_bonus_context = "reward_node"
|
||||||
|
var node_draw: Dictionary = adventure.node_bonus_candidates()
|
||||||
|
var node_cands: Array = node_draw.get("candidates", []) as Array
|
||||||
|
_open_modal.call(bonus_overlay)
|
||||||
|
bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(node_cands, node_draw.get("aux_detail", []) as Array))
|
||||||
|
adventure.present_node_bonus(node_cands, node_draw.get("aux_detail", []) as Array)
|
||||||
|
bonus_overlay.set_refresh_info(adventure.attune_refresh_left()) # 迭代 11 第①批 T5b——刷新余量投喂
|
||||||
|
|
||||||
|
|
||||||
|
# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 aux_replace_presented 续发直落子态)
|
||||||
|
func show_pick() -> void:
|
||||||
|
var payload: Dictionary = _pending_bonus
|
||||||
|
_open_modal.call(bonus_overlay)
|
||||||
|
bonus_overlay.open_pick(Narrator.bonus_pick_header(payload), _bonus_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array))
|
||||||
|
var adventure = _adventure_get.call()
|
||||||
|
bonus_overlay.set_refresh_info(adventure.attune_refresh_left() if adventure != null else 0) # 迭代 11 第①批 T5b——刷新余量投喂
|
||||||
|
_refresh_presentations.call()
|
||||||
|
|
||||||
|
|
||||||
|
func _bonus_card_texts(candidates: Array, aux_detail: Array = []) -> Array:
|
||||||
|
# TP0 令牌池——候选 = {"kind","tier"} 令牌字典;卡片 id = "kind|tier|relic_id" 复合键(同 kind
|
||||||
|
# 大小两档/同档多 relic 可同屏,kind 单键不构成唯一身份),确认回程拆键(on_pick_confirmed)
|
||||||
|
var out: Array = []
|
||||||
|
var aux_i := 0
|
||||||
|
for tok in candidates:
|
||||||
|
var kind := String((tok as Dictionary).get("kind", ""))
|
||||||
|
var tier := String((tok as Dictionary).get("tier", ""))
|
||||||
|
var rid := String((tok as Dictionary).get("relic_id", ""))
|
||||||
|
var cid := "%s|%s|%s" % [kind, tier, rid]
|
||||||
|
var text := ""
|
||||||
|
if kind == "relic":
|
||||||
|
text = Narrator.relic_card_text(db.relic_row(rid))
|
||||||
|
elif kind == "aux_item":
|
||||||
|
var iid := String(aux_detail[aux_i]) if aux_i < aux_detail.size() else ""
|
||||||
|
aux_i += 1
|
||||||
|
text = Narrator.bonus_card_text(kind, tier, int(db.adventure.bonus_value(kind, tier)), _aux_card_line(iid))
|
||||||
|
else:
|
||||||
|
text = Narrator.bonus_card_text(kind, tier, int(db.adventure.bonus_value(kind, tier)))
|
||||||
|
out.append({"id": cid, "text": text})
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# 道具件卡片行(掷定前置——具体道具名 + 效果摘要;空 iid = 无预掷回退态显示通用文案)
|
||||||
|
func _aux_card_line(iid: String) -> String:
|
||||||
|
if iid == "":
|
||||||
|
return ""
|
||||||
|
var row: Dictionary = db.aux_item_row(iid)
|
||||||
|
if row.is_empty():
|
||||||
|
return ""
|
||||||
|
var parts: Array[String] = []
|
||||||
|
for k in (row.get("effect_kinds", []) as Array):
|
||||||
|
if String(k) == "mine_reveal":
|
||||||
|
parts.append("真视")
|
||||||
|
elif String(k) == "reward_preview":
|
||||||
|
parts.append("预读")
|
||||||
|
elif String(k) == "ap_grant":
|
||||||
|
parts.append("AP +%d" % int(row.get("magnitude", 0)))
|
||||||
|
var tgt := String(row.get("target", "none"))
|
||||||
|
if tgt == "area_3x3":
|
||||||
|
parts.append("3×3")
|
||||||
|
elif tgt == "board_wide":
|
||||||
|
parts.append("全图")
|
||||||
|
return "%s · %s" % [String(row.get("display_name", iid)), " ".join(parts)]
|
||||||
|
|
||||||
|
|
||||||
|
# 三选一刷新(迭代 11 第①批 T5b——P1 甲 X/Y 双源;通关停点经重发 bonus_choice_presented 自然重建,
|
||||||
|
# 节点停点在此读在途态重 present——零自动推进契约不破,停点仍在途)
|
||||||
|
func on_refresh() -> void:
|
||||||
|
var adventure = _adventure_get.call()
|
||||||
|
if adventure == null:
|
||||||
|
return
|
||||||
|
if not adventure.refresh_bonus():
|
||||||
|
return
|
||||||
|
if _bonus_context == "reward_node":
|
||||||
|
var st: Dictionary = adventure.node_bonus_pending_state()
|
||||||
|
bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(st.get("candidates", []) as Array, st.get("aux_detail", []) as Array))
|
||||||
|
bonus_overlay.set_refresh_info(adventure.attune_refresh_left())
|
||||||
|
|
||||||
|
|
||||||
|
func on_pick_confirmed(item_id: String) -> void:
|
||||||
|
var adventure = _adventure_get.call()
|
||||||
|
if adventure == null:
|
||||||
|
return
|
||||||
|
# TP0 令牌池——卡片 id "kind|tier|relic_id" 拆键(缺 | = 旧载荷防御归普通档;relic 三段)
|
||||||
|
var parts := item_id.split("|")
|
||||||
|
var kind := parts[0]
|
||||||
|
var tier := parts[1] if parts.size() > 1 else ""
|
||||||
|
var rid := parts[2] if parts.size() > 2 else ""
|
||||||
|
var ok: bool = adventure.apply_node_bonus(kind, tier, rid) if _bonus_context == "reward_node" else adventure.pick_bonus(kind, tier, rid)
|
||||||
|
if not ok:
|
||||||
|
push_error("[bonus_interact] 三选一兑付被拒——见输出日志")
|
||||||
|
|
||||||
|
|
||||||
|
func on_aux_replace_presented(items: Array, incoming: Dictionary) -> void:
|
||||||
|
_last_replace_items = items.duplicate(true)
|
||||||
|
_last_replace_incoming = incoming.duplicate(true)
|
||||||
|
if not bonus_overlay.visible:
|
||||||
|
_open_modal.call(bonus_overlay) # 续玩替换在途直落——三选一面未开时先入栈(模态栈纪律)
|
||||||
|
_open_replace(items, incoming)
|
||||||
|
|
||||||
|
|
||||||
|
func _open_replace(items: Array, incoming: Dictionary) -> void:
|
||||||
|
var named: Array = []
|
||||||
|
for it in items:
|
||||||
|
var rec: Dictionary = it.duplicate()
|
||||||
|
rec["display_name"] = String(item_name_lookup().get(String(rec.get("item_id", "")), String(rec.get("item_id", ""))))
|
||||||
|
named.append(rec)
|
||||||
|
bonus_overlay.open_replace(named, String(item_name_lookup().get(String(incoming.get("item_id", "")), String(incoming.get("item_id", "")))),
|
||||||
|
named.size(), Attunement.slots(db, _save_get.call())) # 迭代 11 第①批 T7——呈现读数源切谓词表求值(过渡键退役)
|
||||||
|
|
||||||
|
|
||||||
|
func on_replace_chosen(discard: int) -> void:
|
||||||
|
var adventure = _adventure_get.call()
|
||||||
|
if adventure == null or not adventure.resolve_bonus_replace(discard):
|
||||||
|
push_error("[bonus_interact] 同调位替换受理被拒——见输出日志")
|
||||||
|
|
||||||
|
|
||||||
|
# 兑付反馈(bonus_resolved 到达——结果行上屏,唯一出口 = 继续)
|
||||||
|
func on_resolved(result: Dictionary) -> void:
|
||||||
|
_pending_bonus = {}
|
||||||
|
bonus_overlay.show_result(Narrator.bonus_result_text(result))
|
||||||
|
_refresh_presentations.call()
|
||||||
|
|
||||||
|
|
||||||
|
# 继续钮:收三选一面 → 上下文分派(通关停点=推进下一关 / 奖励节点=兑付确认回图选路——零自动推进)
|
||||||
|
func on_continue() -> void:
|
||||||
|
_close_modal.call(bonus_overlay)
|
||||||
|
bonus_overlay.close_pick()
|
||||||
|
var adventure = _adventure_get.call()
|
||||||
|
if _bonus_context == "reward_node":
|
||||||
|
var runner = _runner_get.call()
|
||||||
|
if runner != null:
|
||||||
|
runner.continue_after_node()
|
||||||
|
return
|
||||||
|
if bool(_adventure_mode_get.call()) and adventure != null:
|
||||||
|
if not adventure.continue_to_next_level():
|
||||||
|
_fatal_screen.call("关卡推进失败——见输出日志")
|
||||||
|
|
||||||
|
|
||||||
|
# 工坊 item_id → 显示名投影(keystone_settle_text/替换子命名文案数据源——拼句归 Narrator)
|
||||||
|
func item_name_lookup() -> Dictionary:
|
||||||
|
var lookup := {}
|
||||||
|
var meta = _meta_get.call()
|
||||||
|
if meta != null:
|
||||||
|
for row in db.meta_workshop_items():
|
||||||
|
lookup[String(row.get("item_id", ""))] = String(row.get("display_name", ""))
|
||||||
|
return lookup
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cp308xdkheml3
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||||
|
# 营地/商店投影控制器(迭代 13 T4/D4——二期家族③ 自 main.gd 迁出;规范 1.1 单一职责):
|
||||||
|
# 营地面开合/快照投影/锻造·同调·天赋树·Fork 购买与切换 + 商店房购买/离开(pool 与 product 双面)。
|
||||||
|
# 装配纪律(ADR-0001,仿 aux_interact_controller 先例):信号连接不在此发生——campbtn/
|
||||||
|
# camp_overlay 四信号/shop_overlay 两信号由组合根接至本类 on_* 入口;模态栈/呈现刷新/续玩按钮
|
||||||
|
# 刷新经组合根回调注入;runner 随局更替——现值回调注入;写调用唯一经 MetaProgression 购买原语
|
||||||
|
# 与 LevelRunner.buy_*(core 侧单口,本类零直写——拒绝可见 pattern 1 兜底 toast 保留原地)。
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
var db
|
||||||
|
var _meta_get: Callable # () → 元进度账本现值(组合根 lambda——探针 isolate_save 换注面,迭代13 T4 实证)
|
||||||
|
var _save_get: Callable # () → 存档承载现值(同上——营地 recent 摘要读数面)
|
||||||
|
var camp_overlay: CampOverlay
|
||||||
|
var shop_overlay: ShopOverlay
|
||||||
|
var toast: ToastStack
|
||||||
|
var start_overlay: Control # 开始层互斥面(开合仿 TunePanel 先例——非模态且树序在后,规范 2.3)
|
||||||
|
var logger # 试玩日志(ux_reject 取数面)
|
||||||
|
var _runner_get: Callable # () → 关卡推进器现值(组合根 lambda——随局更替)
|
||||||
|
var _open_modal: Callable
|
||||||
|
var _close_modal: Callable
|
||||||
|
var _refresh_presentations: Callable
|
||||||
|
var _refresh_resume_button: Callable
|
||||||
|
|
||||||
|
|
||||||
|
func _init(db_ref, meta_get: Callable, save_get: Callable, camp_overlay_ref: CampOverlay,
|
||||||
|
shop_overlay_ref: ShopOverlay, toast_ref: ToastStack, start_overlay_ref: Control,
|
||||||
|
logger_ref, runner_get: Callable, open_modal: Callable, close_modal: Callable,
|
||||||
|
refresh_presentations: Callable, refresh_resume_button: Callable) -> void:
|
||||||
|
db = db_ref
|
||||||
|
_meta_get = meta_get
|
||||||
|
_save_get = save_get
|
||||||
|
camp_overlay = camp_overlay_ref
|
||||||
|
shop_overlay = shop_overlay_ref
|
||||||
|
toast = toast_ref
|
||||||
|
start_overlay = start_overlay_ref
|
||||||
|
logger = logger_ref
|
||||||
|
_runner_get = runner_get
|
||||||
|
_open_modal = open_modal
|
||||||
|
_close_modal = close_modal
|
||||||
|
_refresh_presentations = refresh_presentations
|
||||||
|
_refresh_resume_button = refresh_resume_button
|
||||||
|
|
||||||
|
|
||||||
|
# 打开营地(主菜单入口——开合与开始层互斥仿 TunePanel 先例:开始覆盖层非模态且树序在后,
|
||||||
|
# 规范 2.3 遮挡纪律;打开时先刷工坊解锁——达标未解锁就地入账)
|
||||||
|
func open_camp() -> void:
|
||||||
|
var meta = _meta_get.call()
|
||||||
|
if meta == null:
|
||||||
|
return
|
||||||
|
meta.refresh_workshop_unlocks()
|
||||||
|
start_overlay.visible = false
|
||||||
|
_open_modal.call(camp_overlay) # 模态栈单口(T3 收口批——登记层开合一律走栈)
|
||||||
|
camp_overlay.open_camp(camp_data())
|
||||||
|
_refresh_presentations.call()
|
||||||
|
|
||||||
|
|
||||||
|
func on_camp_closed() -> void:
|
||||||
|
_close_modal.call(camp_overlay) # 模态栈单口(T3 收口批)
|
||||||
|
start_overlay.visible = true
|
||||||
|
_refresh_resume_button.call() # 迭代 8 卡②——回菜单按断点在档态刷新
|
||||||
|
_refresh_presentations.call() # 关闭后刷新顶栏(工坊解锁可能变了余额侧读数面)
|
||||||
|
|
||||||
|
|
||||||
|
# 营地快照投影(纯读——锻造三行/工坊六行/余额/最近冒险;拼句归 CampOverlay,账面归 MetaProgression)
|
||||||
|
func camp_data() -> Dictionary:
|
||||||
|
var meta = _meta_get.call()
|
||||||
|
var forge_rows: Array[Dictionary] = []
|
||||||
|
if meta != null:
|
||||||
|
for attr in MetaTable.FORGE_ATTRS:
|
||||||
|
var row: Dictionary = db.meta_forge_row(attr)
|
||||||
|
forge_rows.append({
|
||||||
|
"attr": attr,
|
||||||
|
"display_name": String(row.get("display_name", "")),
|
||||||
|
"level": meta.forge_level(attr),
|
||||||
|
"flat": int(row.get("flat_value", 0)),
|
||||||
|
"price": meta.forge_price(attr),
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
"balance": meta.keystone_balance() if meta != null else 0,
|
||||||
|
"forge": forge_rows,
|
||||||
|
"workshop": meta.workshop_status() if meta != null else [],
|
||||||
|
"attune": meta.attune_camp_status() if meta != null else {}, # 迭代 11 第①批 T4——同调装配面投影
|
||||||
|
"tree": meta.tree_camp_rows() if meta != null else [], # 迭代 12 T9——天赋树面投影
|
||||||
|
"fork_groups": meta.tree_fork_groups() if meta != null else [],
|
||||||
|
"recent": _save_get.call().recent_summaries() if _save_get.call() != null else [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func on_camp_buy(attr: String) -> void:
|
||||||
|
var meta = _meta_get.call()
|
||||||
|
if meta == null:
|
||||||
|
return
|
||||||
|
if attr == "attune_refresh":
|
||||||
|
# Y 刷新升级行购买(迭代 11 第①批 T4——P1 甲;按钮禁用为常态防线,兜底拒绝可见 pattern 1)
|
||||||
|
if not meta.buy_attune_refresh():
|
||||||
|
toast.show_toast("钥石不足——买不起这次升级", "warn")
|
||||||
|
return
|
||||||
|
camp_overlay.refresh_camp(camp_data())
|
||||||
|
_refresh_presentations.call()
|
||||||
|
return
|
||||||
|
if not meta.buy_forge(attr):
|
||||||
|
# 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1)
|
||||||
|
toast.show_toast("钥石不足——买不起这件强化", "warn")
|
||||||
|
return
|
||||||
|
camp_overlay.refresh_camp(camp_data())
|
||||||
|
_refresh_presentations.call()
|
||||||
|
|
||||||
|
|
||||||
|
# 同调装配切换(迭代 11 第①批 T4——装配关系唯一写口经 MetaProgression.toggle_attunement;满位兜底拒绝可见)
|
||||||
|
func on_camp_attune_toggle(item_id: String) -> void:
|
||||||
|
var meta = _meta_get.call()
|
||||||
|
if meta == null:
|
||||||
|
return
|
||||||
|
if not meta.toggle_attunement(item_id):
|
||||||
|
toast.show_toast("同调位已满——先卸下再装配", "warn")
|
||||||
|
return
|
||||||
|
camp_overlay.refresh_camp(camp_data())
|
||||||
|
|
||||||
|
|
||||||
|
# 天赋树购买(迭代 12 T9——写口唯一归 MetaProgression.tree_buy/tree_buy_fork;Fork 节点购=激活侧)
|
||||||
|
func on_camp_tree_buy(node_id: String) -> void:
|
||||||
|
var meta = _meta_get.call()
|
||||||
|
if meta == null:
|
||||||
|
return
|
||||||
|
var ok: bool = meta.tree_buy_fork(node_id) if String(db.tree_node(node_id).get("kind", "")) == "fork" else meta.tree_buy(node_id)
|
||||||
|
if not ok:
|
||||||
|
toast.show_toast("购买被拒——前置/行门槛/余额不足", "warn")
|
||||||
|
return
|
||||||
|
camp_overlay.refresh_camp(camp_data())
|
||||||
|
_refresh_presentations.call()
|
||||||
|
|
||||||
|
|
||||||
|
# A/B 免费切换(迭代 12 T8——同组换侧零币;未激活组走购买面)
|
||||||
|
func on_camp_fork_toggle(group: String, side: String) -> void:
|
||||||
|
var meta = _meta_get.call()
|
||||||
|
if meta == null:
|
||||||
|
return
|
||||||
|
if not meta.tree_toggle_fork(group, side):
|
||||||
|
toast.show_toast("切换被拒——未激活组请先购买", "warn")
|
||||||
|
return
|
||||||
|
camp_overlay.refresh_camp(camp_data())
|
||||||
|
_refresh_presentations.call()
|
||||||
|
|
||||||
|
|
||||||
|
func on_shop_buy(product_id: String) -> void:
|
||||||
|
var runner = _runner_get.call()
|
||||||
|
if runner == null:
|
||||||
|
return
|
||||||
|
if runner.is_shop_pool():
|
||||||
|
# pool 面:买 1 → 核心全重抽——货架整架重投喂(重抽反馈;余额不足核心拒绝零副作用)
|
||||||
|
if not runner.buy_pool_item(product_id):
|
||||||
|
logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"})
|
||||||
|
toast.show_toast("金币不足——买不起这件商品", "warn")
|
||||||
|
return
|
||||||
|
shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp))
|
||||||
|
_refresh_presentations.call()
|
||||||
|
return
|
||||||
|
if not runner.buy_product(product_id):
|
||||||
|
# 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1)
|
||||||
|
logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"})
|
||||||
|
toast.show_toast("金币不足——买不起这件商品", "warn")
|
||||||
|
return
|
||||||
|
shop_overlay.refresh_state(runner.shop_products(), int(runner.gp))
|
||||||
|
_refresh_presentations.call()
|
||||||
|
|
||||||
|
|
||||||
|
func on_shop_leave() -> void:
|
||||||
|
_close_modal.call(shop_overlay)
|
||||||
|
var runner = _runner_get.call()
|
||||||
|
if runner != null:
|
||||||
|
# 复用兑付确认纪律(接口漂移清偿):离开 ≠ 推进——确认后经 choices_presented 弹出选路口
|
||||||
|
runner.continue_after_node()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b3t0ocw10p5rs
|
||||||
+56
-307
@@ -38,6 +38,8 @@ const DebugSessionScript := preload("res://presentation/debug_session.gd")
|
|||||||
const RowPacer := preload("res://presentation/row_pacer.gd") # 结算行排播泵(T3 拆件批)
|
const RowPacer := preload("res://presentation/row_pacer.gd") # 结算行排播泵(T3 拆件批)
|
||||||
const AuxInteractController := preload("res://presentation/aux_interact_controller.gd") # 魔法道具两步交互(T3 拆件批)
|
const AuxInteractController := preload("res://presentation/aux_interact_controller.gd") # 魔法道具两步交互(T3 拆件批)
|
||||||
const GpShadowLedgerScript := preload("res://presentation/gp_shadow_ledger.gd") # GP 影子账本(T3 拆件批)
|
const GpShadowLedgerScript := preload("res://presentation/gp_shadow_ledger.gd") # GP 影子账本(T3 拆件批)
|
||||||
|
const BonusInteractController := preload("res://presentation/bonus_interact_controller.gd") # 三选一与替换子交互(T4/D4 拆件批)
|
||||||
|
const CampShopPresenter := preload("res://presentation/camp_shop_presenter.gd") # 营地/商店投影(T4/D4 拆件批)
|
||||||
const SaveManagerScript := preload("res://core/save_manager.gd") # 存档承载(preload 避开全局类缓存时序——与 DebugSessionScript 同款)
|
const SaveManagerScript := preload("res://core/save_manager.gd") # 存档承载(preload 避开全局类缓存时序——与 DebugSessionScript 同款)
|
||||||
const MetaProgressionScript := preload("res://core/meta_progression.gd") # 元进度账本服务(元进度实现批——同 preload 纪律)
|
const MetaProgressionScript := preload("res://core/meta_progression.gd") # 元进度账本服务(元进度实现批——同 preload 纪律)
|
||||||
|
|
||||||
@@ -53,12 +55,18 @@ var _level_active := false
|
|||||||
var _seed_edit: LineEdit
|
var _seed_edit: LineEdit
|
||||||
var _resume_btn: Button # 继续上局按钮句柄(迭代 8 卡②——有断点才亮)
|
var _resume_btn: Button # 继续上局按钮句柄(迭代 8 卡②——有断点才亮)
|
||||||
var confirm_dialog: ConfirmDialog # 通用确认弹窗(二轮修改批 A——新冒险互斥确认弹窗化)
|
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 _pending_candidates: Array = [] # 结算框挂起期的选路候选暂存(choices_presented 载荷)
|
||||||
var _last_gain_amount := 0 # 最近一笔钱包入账(卡⑥ GP 增量取数面——wallet_changed gain 载荷)
|
var _last_gain_amount := 0 # 最近一笔钱包入账(卡⑥ GP 增量取数面——wallet_changed gain 载荷)
|
||||||
var _pending_bonus := {} # 三选一停点载荷暂存(迭代 10——战斗总结结算确认后开三选一面;续玩停点复用)
|
|
||||||
var bonus_overlay: BonusPickOverlay # 提升三选一覆盖层(迭代 10——level-clear.md v2 选择面承载;追加批起奖励节点共用)
|
var bonus_overlay: BonusPickOverlay # 提升三选一覆盖层(迭代 10——level-clear.md v2 选择面承载;追加批起奖励节点共用)
|
||||||
var _bonus_context := "level_clear" # 三选一面上下文(level_clear=通关停点→推进下一关 / reward_node=奖励节点→回图选路)
|
|
||||||
var _start_overlay: Control
|
var _start_overlay: Control
|
||||||
var _end_overlay: BattleEndOverlay
|
var _end_overlay: BattleEndOverlay
|
||||||
var _inter_overlay: Control
|
var _inter_overlay: Control
|
||||||
@@ -83,6 +91,8 @@ var settlement_fx: SettlementFxView # 结算演出覆盖层(跨区弹道
|
|||||||
# T3 拆件批(2026-10-02)——三件迁出后的组合根持有面(职责见各类头注;装配见 _build_ui)
|
# T3 拆件批(2026-10-02)——三件迁出后的组合根持有面(职责见各类头注;装配见 _build_ui)
|
||||||
var _row_pacer = null # 结算行排播泵(row_pacer.gd——本类只入队/排空/读队列)
|
var _row_pacer = null # 结算行排播泵(row_pacer.gd——本类只入队/排空/读队列)
|
||||||
var _aux = null # 魔法道具两步交互(aux_interact_controller.gd——本类只接线与转发意图)
|
var _aux = null # 魔法道具两步交互(aux_interact_controller.gd——本类只接线与转发意图)
|
||||||
|
var _bonus = null # 三选一与替换子交互(bonus_interact_controller.gd——本类只接线与回调注入)
|
||||||
|
var _camp_pres = null # 营地/商店投影(camp_shop_presenter.gd——本类只接线、探针缝保缝转发)
|
||||||
var _gp_ledger := GpShadowLedgerScript.new() # GP 冒险钱包影子账(gp_shadow_ledger.gd——wallet 事件投喂)
|
var _gp_ledger := GpShadowLedgerScript.new() # GP 冒险钱包影子账(gp_shadow_ledger.gd——wallet 事件投喂)
|
||||||
|
|
||||||
# GP 影子账委托面(探针/终局面板取数保留——ui_probe 合成注入经 set 转发账本,读面即账本实况)
|
# GP 影子账委托面(探针/终局面板取数保留——ui_probe 合成注入经 set 转发账本,读面即账本实况)
|
||||||
@@ -251,13 +261,6 @@ func _build_ui() -> void:
|
|||||||
node_overlay.continue_pressed.connect(_on_node_continue)
|
node_overlay.continue_pressed.connect(_on_node_continue)
|
||||||
level_screen.route_chosen.connect(_on_route_chosen)
|
level_screen.route_chosen.connect(_on_route_chosen)
|
||||||
level_screen.abandon_requested.connect(_on_abandon_requested) # 放弃入口(2026-09-21 用户拍板——台账 119)
|
level_screen.abandon_requested.connect(_on_abandon_requested) # 放弃入口(2026-09-21 用户拍板——台账 119)
|
||||||
shop_overlay.buy_requested.connect(_on_shop_buy)
|
|
||||||
shop_overlay.leave_pressed.connect(_on_shop_leave)
|
|
||||||
camp_overlay.buy_requested.connect(_on_camp_buy)
|
|
||||||
camp_overlay.attune_toggled.connect(_on_camp_attune_toggle)
|
|
||||||
camp_overlay.tree_buy_requested.connect(_on_camp_tree_buy)
|
|
||||||
camp_overlay.tree_fork_toggled.connect(_on_camp_fork_toggle)
|
|
||||||
camp_overlay.closed.connect(_on_camp_closed)
|
|
||||||
_end_overlay.review_pressed.connect(_on_end_review)
|
_end_overlay.review_pressed.connect(_on_end_review)
|
||||||
_end_overlay.continue_pressed.connect(_on_battle_end_continue)
|
_end_overlay.continue_pressed.connect(_on_battle_end_continue)
|
||||||
tune_panel.closed.connect(_close_tune_panel)
|
tune_panel.closed.connect(_close_tune_panel)
|
||||||
@@ -269,10 +272,17 @@ func _build_ui() -> void:
|
|||||||
# 提升三选一覆盖层(迭代 10——z 序 = 节点层之上、终局层之下;确认后同拍推进)
|
# 提升三选一覆盖层(迭代 10——z 序 = 节点层之上、终局层之下;确认后同拍推进)
|
||||||
bonus_overlay = BonusPickOverlay.new()
|
bonus_overlay = BonusPickOverlay.new()
|
||||||
add_child(bonus_overlay)
|
add_child(bonus_overlay)
|
||||||
bonus_overlay.pick_confirmed.connect(_on_bonus_pick_confirmed)
|
# 提升三选一与替换子交互(T4/D4 拆件批——迁 bonus_interact_controller.gd;本类只接线与回调注入,
|
||||||
bonus_overlay.replace_chosen.connect(_on_bonus_replace_chosen)
|
# ADR-0001——模态栈/演播后排窗/呈现刷新/fatal 经回调注入,adventure/runner 现值回调)
|
||||||
bonus_overlay.continue_pressed.connect(_on_bonus_continue)
|
_bonus = BonusInteractController.new(db, func() -> Variant: return meta, bonus_overlay, _end_overlay, board_view,
|
||||||
bonus_overlay.refresh_requested.connect(_on_bonus_refresh)
|
board_zone.toast, func() -> Variant: return save_manager, func() -> Variant: return adventure,
|
||||||
|
func() -> Variant: return runner, func() -> Variant: return _adventure_mode,
|
||||||
|
_open_modal, _open_modal_after_playback, _close_modal, _refresh_presentations,
|
||||||
|
_fatal_screen, func() -> String: return _gp_summary_text({"gp_dropped": _last_gain_amount}))
|
||||||
|
bonus_overlay.pick_confirmed.connect(_bonus.on_pick_confirmed)
|
||||||
|
bonus_overlay.replace_chosen.connect(_bonus.on_replace_chosen)
|
||||||
|
bonus_overlay.continue_pressed.connect(_bonus.on_continue)
|
||||||
|
bonus_overlay.refresh_requested.connect(_bonus.on_refresh)
|
||||||
move_child(bonus_overlay, _end_overlay.get_index())
|
move_child(bonus_overlay, _end_overlay.get_index())
|
||||||
|
|
||||||
var start: Dictionary = UiAssembler.build_start_overlay(self, db)
|
var start: Dictionary = UiAssembler.build_start_overlay(self, db)
|
||||||
@@ -284,7 +294,19 @@ func _build_ui() -> void:
|
|||||||
start.tunebtn.pressed.connect(_show_tune_panel)
|
start.tunebtn.pressed.connect(_show_tune_panel)
|
||||||
start.dbgbtn.pressed.connect(_start_debug_levels)
|
start.dbgbtn.pressed.connect(_start_debug_levels)
|
||||||
start.dbgbtn.visible = OS.get_environment("DS_DEBUG") == "1" # 迭代 8 卡④——调试入口默认隐藏门控(DS_DEBUG=1 点亮)
|
start.dbgbtn.visible = OS.get_environment("DS_DEBUG") == "1" # 迭代 8 卡④——调试入口默认隐藏门控(DS_DEBUG=1 点亮)
|
||||||
start.campbtn.pressed.connect(_open_camp)
|
# 营地/商店投影(T4/D4 拆件批——迁 camp_shop_presenter.gd;本类只接线、探针缝保缝转发;
|
||||||
|
# 购买写调用唯一经 MetaProgression/LevelRunner core 单口——组合根 lambda 现值注入)
|
||||||
|
_camp_pres = CampShopPresenter.new(db, func() -> Variant: return meta, func() -> Variant: return save_manager, camp_overlay, shop_overlay,
|
||||||
|
board_zone.toast, _start_overlay, logger, func() -> Variant: return runner,
|
||||||
|
_open_modal, _close_modal, _refresh_presentations, _refresh_resume_button)
|
||||||
|
shop_overlay.buy_requested.connect(_camp_pres.on_shop_buy)
|
||||||
|
shop_overlay.leave_pressed.connect(_camp_pres.on_shop_leave)
|
||||||
|
camp_overlay.buy_requested.connect(_camp_pres.on_camp_buy)
|
||||||
|
camp_overlay.attune_toggled.connect(_camp_pres.on_camp_attune_toggle)
|
||||||
|
camp_overlay.tree_buy_requested.connect(_camp_pres.on_camp_tree_buy)
|
||||||
|
camp_overlay.tree_fork_toggled.connect(_camp_pres.on_camp_fork_toggle)
|
||||||
|
camp_overlay.closed.connect(_camp_pres.on_camp_closed)
|
||||||
|
start.campbtn.pressed.connect(_camp_pres.open_camp)
|
||||||
start.settingsbtn.pressed.connect(_open_settings) # 迭代 8 卡④——最小设置面入口
|
start.settingsbtn.pressed.connect(_open_settings) # 迭代 8 卡④——最小设置面入口
|
||||||
start.quitbtn.pressed.connect(_quit_game) # 二轮修改批 D1——退出游戏提级一级菜单
|
start.quitbtn.pressed.connect(_quit_game) # 二轮修改批 D1——退出游戏提级一级菜单
|
||||||
|
|
||||||
@@ -427,9 +449,9 @@ func _wire_adventure() -> void:
|
|||||||
adventure.choices_presented.connect(_on_choices_presented)
|
adventure.choices_presented.connect(_on_choices_presented)
|
||||||
adventure.wallet_changed.connect(_on_wallet_changed)
|
adventure.wallet_changed.connect(_on_wallet_changed)
|
||||||
# 迭代 10 奖励重构批——big_reward_paid 退役,三选一信号族接线
|
# 迭代 10 奖励重构批——big_reward_paid 退役,三选一信号族接线
|
||||||
adventure.bonus_choice_presented.connect(_on_bonus_presented)
|
adventure.bonus_choice_presented.connect(_bonus.on_presented)
|
||||||
adventure.bonus_resolved.connect(_on_bonus_resolved)
|
adventure.bonus_resolved.connect(_bonus.on_resolved)
|
||||||
adventure.aux_replace_presented.connect(_on_aux_replace_presented)
|
adventure.aux_replace_presented.connect(_bonus.on_aux_replace_presented)
|
||||||
adventure.adventure_finished.connect(_on_adventure_finished)
|
adventure.adventure_finished.connect(_on_adventure_finished)
|
||||||
adventure.level_started.connect(_on_adventure_level_started)
|
adventure.level_started.connect(_on_adventure_level_started)
|
||||||
adventure.set_save_manager(save_manager) # 断点承载注入(迭代 8 卡①——停点快照写盘;调试/模拟不经过此路径)
|
adventure.set_save_manager(save_manager) # 断点承载注入(迭代 8 卡①——停点快照写盘;调试/模拟不经过此路径)
|
||||||
@@ -482,15 +504,9 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void:
|
|||||||
_show_board_fx_for_node(t)
|
_show_board_fx_for_node(t)
|
||||||
log_view.append_bb(Narrator.node_banner(runner, node, t))
|
log_view.append_bb(Narrator.node_banner(runner, node, t))
|
||||||
elif t == "reward":
|
elif t == "reward":
|
||||||
# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;BonusPickOverlay 共用面,
|
# 奖励节点三选一(迭代 10 追加批——与通关三选一共享同池同权重同上限;BonusPickOverlay 共用面)。
|
||||||
# 道具件满位替换子交互同构)。原「魔法道具投放渠道」三选一(迭代 7 过渡形态)随批退役。
|
# T4/D4 拆件:呈现编排归 bonus_interact_controller.present_node_bonus(_bonus_context 随迁)。
|
||||||
_bonus_context = "reward_node"
|
_bonus.present_node_bonus()
|
||||||
var node_draw: Dictionary = adventure.node_bonus_candidates()
|
|
||||||
var node_cands: Array = node_draw.get("candidates", []) as Array
|
|
||||||
_open_modal(bonus_overlay)
|
|
||||||
bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(node_cands, node_draw.get("aux_detail", []) as Array))
|
|
||||||
adventure.present_node_bonus(node_cands, node_draw.get("aux_detail", []) as Array)
|
|
||||||
bonus_overlay.set_refresh_info(adventure.attune_refresh_left()) # 迭代 11 第①批 T5b——刷新余量投喂
|
|
||||||
log_view.append_bb(Narrator.reward_line())
|
log_view.append_bb(Narrator.reward_line())
|
||||||
elif t == "rest":
|
elif t == "rest":
|
||||||
var healed: int = int(info.get("healed", 0))
|
var healed: int = int(info.get("healed", 0))
|
||||||
@@ -513,151 +529,6 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void:
|
|||||||
|
|
||||||
# ---- 冒险模式终局面(三选一选择面 / 冒险结算屏——迭代 10 奖励重构批)----
|
# ---- 冒险模式终局面(三选一选择面 / 冒险结算屏——迭代 10 奖励重构批)----
|
||||||
|
|
||||||
# 三选一停点(非末关通关恰一次):Boss 胜利先经战斗总结结算面(settlement-summary v1.1——钥石
|
|
||||||
# 增量行在其上),确认后进三选一面(level-clear.md v2);续玩停点恢复同入口(零自动推进契约)
|
|
||||||
func _on_bonus_presented(payload: Dictionary) -> void:
|
|
||||||
if int(payload.get("aux_cleared", 0)) > 0:
|
|
||||||
board_zone.toast.show_toast("上关剩余 %d 件魔法道具已随关卡结束清空" % int(payload.get("aux_cleared", 0)), "info") # 迭代 7 T4 清空反馈
|
|
||||||
_pending_bonus = payload.duplicate(true)
|
|
||||||
_bonus_context = "level_clear"
|
|
||||||
if bool(payload.get("replace_pending", false)):
|
|
||||||
# 续玩:替换子交互在途中断——重建三选一面后直落替换子态(候选不重抽)
|
|
||||||
_show_bonus_pick()
|
|
||||||
return
|
|
||||||
_end_overlay.open_victory(Narrator.battle_end_text("victory", false), "继续",
|
|
||||||
board_view.get_global_rect(),
|
|
||||||
"%s\n%s" % [_gp_summary_text({"gp_dropped": _last_gain_amount}), Narrator.bonus_keystone_line(payload)])
|
|
||||||
_open_modal_after_playback(_end_overlay)
|
|
||||||
_refresh_presentations()
|
|
||||||
|
|
||||||
|
|
||||||
# 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 aux_replace_presented 续发直落子态)
|
|
||||||
func _show_bonus_pick() -> void:
|
|
||||||
var payload: Dictionary = _pending_bonus
|
|
||||||
_open_modal(bonus_overlay)
|
|
||||||
bonus_overlay.open_pick(Narrator.bonus_pick_header(payload), _bonus_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array))
|
|
||||||
bonus_overlay.set_refresh_info(adventure.attune_refresh_left() if adventure != null else 0) # 迭代 11 第①批 T5b——刷新余量投喂
|
|
||||||
_refresh_presentations()
|
|
||||||
|
|
||||||
var _last_replace_items: Array = [] # 替换子态续玩重建缓存(aux_replace_presented 载荷)
|
|
||||||
var _last_replace_incoming := {} # 同上——incoming 记录
|
|
||||||
|
|
||||||
|
|
||||||
func _bonus_card_texts(candidates: Array, aux_detail: Array = []) -> Array:
|
|
||||||
# TP0 令牌池——候选 = {"kind","tier"} 令牌字典;卡片 id = "kind|tier|relic_id" 复合键(同 kind
|
|
||||||
# 大小两档/同档多 relic 可同屏,kind 单键不构成唯一身份),确认回程拆键(_on_bonus_pick_confirmed)
|
|
||||||
var out: Array = []
|
|
||||||
var aux_i := 0
|
|
||||||
for tok in candidates:
|
|
||||||
var kind := String((tok as Dictionary).get("kind", ""))
|
|
||||||
var tier := String((tok as Dictionary).get("tier", ""))
|
|
||||||
var rid := String((tok as Dictionary).get("relic_id", ""))
|
|
||||||
var cid := "%s|%s|%s" % [kind, tier, rid]
|
|
||||||
var text := ""
|
|
||||||
if kind == "relic":
|
|
||||||
text = Narrator.relic_card_text(db.relic_row(rid))
|
|
||||||
elif kind == "aux_item":
|
|
||||||
var iid := String(aux_detail[aux_i]) if aux_i < aux_detail.size() else ""
|
|
||||||
aux_i += 1
|
|
||||||
text = Narrator.bonus_card_text(kind, tier, int(db.adventure.bonus_value(kind, tier)), _aux_card_line(iid))
|
|
||||||
else:
|
|
||||||
text = Narrator.bonus_card_text(kind, tier, int(db.adventure.bonus_value(kind, tier)))
|
|
||||||
out.append({"id": cid, "text": text})
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
# 道具件卡片行(掷定前置——具体道具名 + 效果摘要;空 iid = 无预掷回退态显示通用文案)
|
|
||||||
func _aux_card_line(iid: String) -> String:
|
|
||||||
if iid == "":
|
|
||||||
return ""
|
|
||||||
var row: Dictionary = db.aux_item_row(iid)
|
|
||||||
if row.is_empty():
|
|
||||||
return ""
|
|
||||||
var parts: Array[String] = []
|
|
||||||
for k in (row.get("effect_kinds", []) as Array):
|
|
||||||
if String(k) == "mine_reveal":
|
|
||||||
parts.append("真视")
|
|
||||||
elif String(k) == "reward_preview":
|
|
||||||
parts.append("预读")
|
|
||||||
elif String(k) == "ap_grant":
|
|
||||||
parts.append("AP +%d" % int(row.get("magnitude", 0)))
|
|
||||||
var tgt := String(row.get("target", "none"))
|
|
||||||
if tgt == "area_3x3":
|
|
||||||
parts.append("3×3")
|
|
||||||
elif tgt == "board_wide":
|
|
||||||
parts.append("全图")
|
|
||||||
return "%s · %s" % [String(row.get("display_name", iid)), " ".join(parts)]
|
|
||||||
|
|
||||||
|
|
||||||
# 三选一刷新(迭代 11 第①批 T5b——P1 甲 X/Y 双源;通关停点经重发 bonus_choice_presented 自然重建,
|
|
||||||
# 节点停点在此读在途态重 present——零自动推进契约不破,停点仍在途)
|
|
||||||
func _on_bonus_refresh() -> void:
|
|
||||||
if adventure == null:
|
|
||||||
return
|
|
||||||
if not adventure.refresh_bonus():
|
|
||||||
return
|
|
||||||
if _bonus_context == "reward_node":
|
|
||||||
var st: Dictionary = adventure.node_bonus_pending_state()
|
|
||||||
bonus_overlay.open_pick(Narrator.node_bonus_header(), _bonus_card_texts(st.get("candidates", []) as Array, st.get("aux_detail", []) as Array))
|
|
||||||
bonus_overlay.set_refresh_info(adventure.attune_refresh_left())
|
|
||||||
|
|
||||||
|
|
||||||
func _on_bonus_pick_confirmed(item_id: String) -> void:
|
|
||||||
if adventure == null:
|
|
||||||
return
|
|
||||||
# TP0 令牌池——卡片 id "kind|tier|relic_id" 拆键(缺 | = 旧载荷防御归普通档;relic 三段)
|
|
||||||
var parts := item_id.split("|")
|
|
||||||
var kind := parts[0]
|
|
||||||
var tier := parts[1] if parts.size() > 1 else ""
|
|
||||||
var rid := parts[2] if parts.size() > 2 else ""
|
|
||||||
var ok: bool = adventure.apply_node_bonus(kind, tier, rid) if _bonus_context == "reward_node" else adventure.pick_bonus(kind, tier, rid)
|
|
||||||
if not ok:
|
|
||||||
push_error("[组合根] 三选一兑付被拒——见输出日志")
|
|
||||||
|
|
||||||
|
|
||||||
func _on_aux_replace_presented(items: Array, incoming: Dictionary) -> void:
|
|
||||||
_last_replace_items = items.duplicate(true)
|
|
||||||
_last_replace_incoming = incoming.duplicate(true)
|
|
||||||
if not bonus_overlay.visible:
|
|
||||||
_open_modal(bonus_overlay) # 续玩替换在途直落——三选一面未开时先入栈(模态栈纪律)
|
|
||||||
_open_bonus_replace(items, incoming)
|
|
||||||
|
|
||||||
|
|
||||||
func _open_bonus_replace(items: Array, incoming: Dictionary) -> void:
|
|
||||||
var named: Array = []
|
|
||||||
for it in items:
|
|
||||||
var rec: Dictionary = it.duplicate()
|
|
||||||
rec["display_name"] = String(_item_name_lookup().get(String(rec.get("item_id", "")), String(rec.get("item_id", ""))))
|
|
||||||
named.append(rec)
|
|
||||||
bonus_overlay.open_replace(named, String(_item_name_lookup().get(String(incoming.get("item_id", "")), String(incoming.get("item_id", "")))),
|
|
||||||
named.size(), Attunement.slots(db, save_manager)) # 迭代 11 第①批 T7——呈现读数源切谓词表求值(过渡键退役)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_bonus_replace_chosen(discard: int) -> void:
|
|
||||||
if adventure == null or not adventure.resolve_bonus_replace(discard):
|
|
||||||
push_error("[组合根] 同调位替换受理被拒——见输出日志")
|
|
||||||
|
|
||||||
|
|
||||||
# 兑付反馈(bonus_resolved 到达——结果行上屏,唯一出口 = 继续)
|
|
||||||
func _on_bonus_resolved(result: Dictionary) -> void:
|
|
||||||
_pending_bonus = {}
|
|
||||||
bonus_overlay.show_result(Narrator.bonus_result_text(result))
|
|
||||||
_refresh_presentations()
|
|
||||||
|
|
||||||
|
|
||||||
# 继续钮:收三选一面 → 上下文分派(通关停点=推进下一关 / 奖励节点=兑付确认回图选路——零自动推进)
|
|
||||||
func _on_bonus_continue() -> void:
|
|
||||||
_close_modal(bonus_overlay)
|
|
||||||
bonus_overlay.close_pick()
|
|
||||||
if _bonus_context == "reward_node":
|
|
||||||
if runner != null:
|
|
||||||
runner.continue_after_node()
|
|
||||||
return
|
|
||||||
if _adventure_mode and adventure != null:
|
|
||||||
if not adventure.continue_to_next_level():
|
|
||||||
_fatal_screen("关卡推进失败——见输出日志")
|
|
||||||
|
|
||||||
|
|
||||||
# 冒险结算(completed/failed/abandoned):复用 BattleEndOverlay——摘要五件事 + 钥石入账明细进文案
|
# 冒险结算(completed/failed/abandoned):复用 BattleEndOverlay——摘要五件事 + 钥石入账明细进文案
|
||||||
func _on_adventure_finished(summary: Dictionary) -> void:
|
func _on_adventure_finished(summary: Dictionary) -> void:
|
||||||
_level_active = false
|
_level_active = false
|
||||||
@@ -673,7 +544,7 @@ func _on_adventure_finished(summary: Dictionary) -> void:
|
|||||||
detail.assign(meta.settle_adventure(summary))
|
detail.assign(meta.settle_adventure(summary))
|
||||||
var text := Narrator.adventure_summary_text(summary)
|
var text := Narrator.adventure_summary_text(summary)
|
||||||
if not detail.is_empty():
|
if not detail.is_empty():
|
||||||
text += "\n" + Narrator.keystone_settle_text(detail, _item_name_lookup())
|
text += "\n" + Narrator.keystone_settle_text(detail, _bonus.item_name_lookup())
|
||||||
if result == "completed":
|
if result == "completed":
|
||||||
_end_overlay.open_victory(text, "返回冒险", board_view.get_global_rect(),
|
_end_overlay.open_victory(text, "返回冒险", board_view.get_global_rect(),
|
||||||
"本局 GP 入账 · 合计 %d" % _adventure_gp_final) # 迭代 6 T7 卡⑩——GP 行只上冒险终局面板(C5)
|
"本局 GP 入账 · 合计 %d" % _adventure_gp_final) # 迭代 6 T7 卡⑩——GP 行只上冒险终局面板(C5)
|
||||||
@@ -686,22 +557,12 @@ func _on_adventure_finished(summary: Dictionary) -> void:
|
|||||||
_refresh_presentations()
|
_refresh_presentations()
|
||||||
|
|
||||||
|
|
||||||
# 工坊 item_id → 显示名投影(keystone_settle_text 文案数据源——装配归本根,拼句归 Narrator)
|
|
||||||
func _item_name_lookup() -> Dictionary:
|
|
||||||
var lookup := {}
|
|
||||||
if meta != null:
|
|
||||||
for row in db.meta_workshop_items():
|
|
||||||
lookup[String(row.get("item_id", ""))] = String(row.get("display_name", ""))
|
|
||||||
return lookup
|
|
||||||
|
|
||||||
|
|
||||||
func _on_battle_end_continue() -> void:
|
func _on_battle_end_continue() -> void:
|
||||||
# 终局出口:返回开始界面(冒险摘要留档试玩日志;钥石已在收束时入存档——存档序列化批)
|
# 终局出口:返回开始界面(冒险摘要留档试玩日志;钥石已在收束时入存档——存档序列化批)
|
||||||
_close_modal(_end_overlay)
|
_close_modal(_end_overlay)
|
||||||
_end_overlay.close()
|
_end_overlay.close()
|
||||||
# 迭代 10——三选一停点挂起分支:战斗总结结算(Boss 胜利钥石增量行)确认后进三选一面
|
# 迭代 10——三选一停点挂起分支:战斗总结结算(Boss 胜利钥石增量行)确认后进三选一面
|
||||||
if not _pending_bonus.is_empty():
|
if _bonus.resume_if_pending(): # T4/D4 拆件——三选一停点在途恢复入口(bonus_interact_controller)
|
||||||
_show_bonus_pick()
|
|
||||||
return
|
return
|
||||||
# 卡⑥ 挂起分派:战斗胜利总结结算框的「下一节点」≠ 终局——进选路页(暂存候选恢复呈现)
|
# 卡⑥ 挂起分派:战斗胜利总结结算框的「下一节点」≠ 终局——进选路页(暂存候选恢复呈现)
|
||||||
if _settlement_stop_pending:
|
if _settlement_stop_pending:
|
||||||
@@ -732,7 +593,10 @@ func _on_end_review() -> void:
|
|||||||
|
|
||||||
|
|
||||||
# ==================== 调试模式(连续两层——编排归 debug_session.gd;
|
# ==================== 调试模式(连续两层——编排归 debug_session.gd;
|
||||||
# 单场遭遇用户入口已删(2026-09-13 用户裁决)——debug_session.start_single 原语仅探针使用)====================
|
# 单场遭遇用户入口已删(2026-09-13 用户裁决)——debug_session.start_single 原语仅探针使用)
|
||||||
|
# T4/D3 收口注记:本节处理器(battle_created/battle_ready/override/seed)系组合根绑定面——
|
||||||
|
# settlement 写与 _wire_battle=ADR-0001 组合根 essence,override/seed 取数面在组合根 UI 态
|
||||||
|
#(tune_panel/_seed_edit)——不迁移,信号连接留本根(契约 3)。====================
|
||||||
|
|
||||||
func _start_debug_levels() -> void:
|
func _start_debug_levels() -> void:
|
||||||
tune_panel.reset_sliders()
|
tune_panel.reset_sliders()
|
||||||
@@ -1020,25 +884,6 @@ func _on_abandon_requested() -> void:
|
|||||||
|
|
||||||
# ---- 营地覆盖层与钥石账本(元进度实现批——写调用唯一归组合根)----
|
# ---- 营地覆盖层与钥石账本(元进度实现批——写调用唯一归组合根)----
|
||||||
|
|
||||||
# 打开营地(主菜单入口——开合与开始层互斥仿 TunePanel 先例:开始覆盖层非模态且树序在后,
|
|
||||||
# 规范 2.3 遮挡纪律;打开时先刷工坊解锁——达标未解锁就地入账)
|
|
||||||
func _open_camp() -> void:
|
|
||||||
if meta == null:
|
|
||||||
return
|
|
||||||
meta.refresh_workshop_unlocks()
|
|
||||||
_start_overlay.visible = false
|
|
||||||
_open_modal(camp_overlay) # 模态栈单口(T3 收口批——登记层开合一律走栈)
|
|
||||||
camp_overlay.open_camp(_camp_data())
|
|
||||||
_refresh_presentations()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_camp_closed() -> void:
|
|
||||||
_close_modal(camp_overlay) # 模态栈单口(T3 收口批)
|
|
||||||
_start_overlay.visible = true
|
|
||||||
_refresh_resume_button() # 迭代 8 卡②——回菜单按断点在档态刷新
|
|
||||||
_refresh_presentations() # 关闭后刷新顶栏(工坊解锁可能变了余额侧读数面)
|
|
||||||
|
|
||||||
|
|
||||||
# ---- 最小设置面(迭代 8 卡④——入口/开合仿营地先例:开始层互斥;生效与持久化归 SettingsStore)----
|
# ---- 最小设置面(迭代 8 卡④——入口/开合仿营地先例:开始层互斥;生效与持久化归 SettingsStore)----
|
||||||
|
|
||||||
func _open_settings() -> void:
|
func _open_settings() -> void:
|
||||||
@@ -1052,110 +897,14 @@ func _on_settings_closed() -> void:
|
|||||||
_refresh_resume_button()
|
_refresh_resume_button()
|
||||||
|
|
||||||
|
|
||||||
# 营地快照投影(纯读——锻造三行/工坊六行/余额/最近冒险;拼句归 CampOverlay,账面归 MetaProgression)
|
# ---- 探针缝保缝(T4/D4 拆件——click/ui_probe 营地段直呼组合根面;实现归 camp_shop_presenter.gd)----
|
||||||
|
|
||||||
|
func _on_camp_closed() -> void:
|
||||||
|
_camp_pres.on_camp_closed()
|
||||||
|
|
||||||
|
|
||||||
func _camp_data() -> Dictionary:
|
func _camp_data() -> Dictionary:
|
||||||
var forge_rows: Array[Dictionary] = []
|
return _camp_pres.camp_data()
|
||||||
if meta != null:
|
|
||||||
for attr in MetaTable.FORGE_ATTRS:
|
|
||||||
var row: Dictionary = db.meta_forge_row(attr)
|
|
||||||
forge_rows.append({
|
|
||||||
"attr": attr,
|
|
||||||
"display_name": String(row.get("display_name", "")),
|
|
||||||
"level": meta.forge_level(attr),
|
|
||||||
"flat": int(row.get("flat_value", 0)),
|
|
||||||
"price": meta.forge_price(attr),
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
"balance": meta.keystone_balance() if meta != null else 0,
|
|
||||||
"forge": forge_rows,
|
|
||||||
"workshop": meta.workshop_status() if meta != null else [],
|
|
||||||
"attune": meta.attune_camp_status() if meta != null else {}, # 迭代 11 第①批 T4——同调装配面投影
|
|
||||||
"tree": meta.tree_camp_rows() if meta != null else [], # 迭代 12 T9——天赋树面投影
|
|
||||||
"fork_groups": meta.tree_fork_groups() if meta != null else [],
|
|
||||||
"recent": save_manager.recent_summaries() if save_manager != null else [],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func _on_camp_buy(attr: String) -> void:
|
|
||||||
if meta == null:
|
|
||||||
return
|
|
||||||
if attr == "attune_refresh":
|
|
||||||
# Y 刷新升级行购买(迭代 11 第①批 T4——P1 甲;按钮禁用为常态防线,兜底拒绝可见 pattern 1)
|
|
||||||
if not meta.buy_attune_refresh():
|
|
||||||
board_zone.toast.show_toast("钥石不足——买不起这次升级", "warn")
|
|
||||||
return
|
|
||||||
camp_overlay.refresh_camp(_camp_data())
|
|
||||||
_refresh_presentations()
|
|
||||||
return
|
|
||||||
if not meta.buy_forge(attr):
|
|
||||||
# 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1)
|
|
||||||
board_zone.toast.show_toast("钥石不足——买不起这件强化", "warn")
|
|
||||||
return
|
|
||||||
camp_overlay.refresh_camp(_camp_data())
|
|
||||||
_refresh_presentations()
|
|
||||||
|
|
||||||
|
|
||||||
# 同调装配切换(迭代 11 第①批 T4——装配关系唯一写口经 MetaProgression.toggle_attunement;满位兜底拒绝可见)
|
|
||||||
func _on_camp_attune_toggle(item_id: String) -> void:
|
|
||||||
if meta == null:
|
|
||||||
return
|
|
||||||
if not meta.toggle_attunement(item_id):
|
|
||||||
board_zone.toast.show_toast("同调位已满——先卸下再装配", "warn")
|
|
||||||
return
|
|
||||||
camp_overlay.refresh_camp(_camp_data())
|
|
||||||
|
|
||||||
|
|
||||||
# 天赋树购买(迭代 12 T9——写口唯一归 MetaProgression.tree_buy/tree_buy_fork;Fork 节点购=激活侧)
|
|
||||||
func _on_camp_tree_buy(node_id: String) -> void:
|
|
||||||
if meta == null:
|
|
||||||
return
|
|
||||||
var ok: bool = meta.tree_buy_fork(node_id) if String(db.tree_node(node_id).get("kind", "")) == "fork" else meta.tree_buy(node_id)
|
|
||||||
if not ok:
|
|
||||||
board_zone.toast.show_toast("购买被拒——前置/行门槛/余额不足", "warn")
|
|
||||||
return
|
|
||||||
camp_overlay.refresh_camp(_camp_data())
|
|
||||||
_refresh_presentations()
|
|
||||||
|
|
||||||
|
|
||||||
# A/B 免费切换(迭代 12 T8——同组换侧零币;未激活组走购买面)
|
|
||||||
func _on_camp_fork_toggle(group: String, side: String) -> void:
|
|
||||||
if meta == null:
|
|
||||||
return
|
|
||||||
if not meta.tree_toggle_fork(group, side):
|
|
||||||
board_zone.toast.show_toast("切换被拒——未激活组请先购买", "warn")
|
|
||||||
return
|
|
||||||
camp_overlay.refresh_camp(_camp_data())
|
|
||||||
_refresh_presentations()
|
|
||||||
|
|
||||||
|
|
||||||
# ---- 商店覆盖层与 GP 钱包(GP+商店房实施批——写调用唯一归组合根)----
|
|
||||||
|
|
||||||
func _on_shop_buy(product_id: String) -> void:
|
|
||||||
if runner == null:
|
|
||||||
return
|
|
||||||
if runner.is_shop_pool():
|
|
||||||
# pool 面:买 1 → 核心全重抽——货架整架重投喂(重抽反馈;余额不足核心拒绝零副作用)
|
|
||||||
if not runner.buy_pool_item(product_id):
|
|
||||||
logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"})
|
|
||||||
board_zone.toast.show_toast("金币不足——买不起这件商品", "warn")
|
|
||||||
return
|
|
||||||
shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp))
|
|
||||||
_refresh_presentations()
|
|
||||||
return
|
|
||||||
if not runner.buy_product(product_id):
|
|
||||||
# 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1)
|
|
||||||
logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"})
|
|
||||||
board_zone.toast.show_toast("金币不足——买不起这件商品", "warn")
|
|
||||||
return
|
|
||||||
shop_overlay.refresh_state(runner.shop_products(), int(runner.gp))
|
|
||||||
_refresh_presentations()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_shop_leave() -> void:
|
|
||||||
_close_modal(shop_overlay)
|
|
||||||
if runner != null:
|
|
||||||
# 复用兑付确认纪律(接口漂移清偿):离开 ≠ 推进——确认后经 choices_presented 弹出选路口
|
|
||||||
runner.continue_after_node()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_wallet_changed(event: String, gp: int, extra: Dictionary) -> void:
|
func _on_wallet_changed(event: String, gp: int, extra: Dictionary) -> void:
|
||||||
|
|||||||
@@ -31,12 +31,15 @@ var _rap_label: Label
|
|||||||
var _rap_feedback: Label
|
var _rap_feedback: Label
|
||||||
var _last_rap := 0 # 上次投影值(清零反馈判据——禁解析自身 label 文本当状态)
|
var _last_rap := 0 # 上次投影值(清零反馈判据——禁解析自身 label 文本当状态)
|
||||||
var refresh_cost: int = 0 # 表值驱动(装配经 set_refresh_cost 注入 ActionEconomyTable.rap_refresh_cost)
|
var refresh_cost: int = 0 # 表值驱动(装配经 set_refresh_cost 注入 ActionEconomyTable.rap_refresh_cost)
|
||||||
var _confirm_armed := false
|
var _arm_timer := ArmedTimer.new() # 弃图刷新确认窗(T3/D3 P4 乙——tween 秒表机制收口;窗口=REFRESH_CONFIRM_WINDOW_S)
|
||||||
var _arm_tween: Tween
|
|
||||||
|
|
||||||
|
const REFRESH_CONFIRM_WINDOW_S := 4.0 # 弃图刷新确认窗(原 tween_interval 字面量升常量——语义零变化)
|
||||||
|
|
||||||
|
|
||||||
func _init() -> void:
|
func _init() -> void:
|
||||||
custom_minimum_size = Vector2(0, 44)
|
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))
|
var sb := UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, 0, 1, Vector4(8, 4, 8, 4))
|
||||||
add_theme_stylebox_override("panel", sb)
|
add_theme_stylebox_override("panel", sb)
|
||||||
|
|
||||||
@@ -186,7 +189,7 @@ func confirm_visible() -> bool:
|
|||||||
|
|
||||||
|
|
||||||
func press_confirm_for_probe() -> void:
|
func press_confirm_for_probe() -> void:
|
||||||
if _confirm_armed:
|
if _arm_timer.is_armed():
|
||||||
_disarm_and_fire()
|
_disarm_and_fire()
|
||||||
|
|
||||||
|
|
||||||
@@ -200,12 +203,7 @@ func _on_refresh_pressed() -> void:
|
|||||||
if _btn_refresh.disabled:
|
if _btn_refresh.disabled:
|
||||||
return
|
return
|
||||||
_confirm_row.visible = true
|
_confirm_row.visible = true
|
||||||
_confirm_armed = true
|
_arm_timer.arm(REFRESH_CONFIRM_WINDOW_S)
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
func _disarm_and_fire() -> void:
|
func _disarm_and_fire() -> void:
|
||||||
@@ -214,10 +212,15 @@ func _disarm_and_fire() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _disarm() -> void:
|
func _disarm() -> void:
|
||||||
_confirm_armed = false
|
_arm_timer.disarm()
|
||||||
_confirm_row.visible = false
|
_confirm_row.visible = false
|
||||||
|
|
||||||
|
|
||||||
|
# 帧驱动(T3/D3——超时回落走 ArmedTimer;unarmed 零开销直返)
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
_arm_timer.tick(delta)
|
||||||
|
|
||||||
|
|
||||||
# 资源面拒绝反馈(pattern 1 resource_face 腿):AP 数字脉冲
|
# 资源面拒绝反馈(pattern 1 resource_face 腿):AP 数字脉冲
|
||||||
func pulse_ap() -> void:
|
func pulse_ap() -> void:
|
||||||
_pulse(_ap_label)
|
_pulse(_ap_label)
|
||||||
|
|||||||
@@ -118,6 +118,10 @@ var fog_state := 0:
|
|||||||
if fog_state != v:
|
if fog_state != v:
|
||||||
fog_state = v
|
fog_state = v
|
||||||
queue_redraw()
|
queue_redraw()
|
||||||
|
var removed_dim := false: # 中立移除退色层(T8/D6——BoardView paint 按排雷玩法 is_removed 写入)
|
||||||
|
set(v):
|
||||||
|
removed_dim = v
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
func _draw() -> void:
|
func _draw() -> void:
|
||||||
@@ -256,6 +260,10 @@ func _draw() -> void:
|
|||||||
draw_rect(Rect2(Vector2.ZERO, Vector2(edge, edge)), FOG_MEMORY_PLATE, true)
|
draw_rect(Rect2(Vector2.ZERO, Vector2(edge, edge)), FOG_MEMORY_PLATE, true)
|
||||||
if glyph != "":
|
if glyph != "":
|
||||||
_draw_glyph(glyph, FOG_MEMORY_INK, maxi(18, int(ceil(edge * 0.5)))) # 字号下限 18(§7.4 冻结公式同源)
|
_draw_glyph(glyph, FOG_MEMORY_INK, maxi(18, int(ceil(edge * 0.5)))) # 字号下限 18(§7.4 冻结公式同源)
|
||||||
|
# ⑦ 中立移除退色层(迭代 13 T8/D6——P2 甲退色降档:暗幕压顶全层覆盖,暗淡记忆态同族呈现语义;
|
||||||
|
# 顶层于迷雾之上——移除格不再参与迷雾语义。本实现移除集=未揭示雷格(无数字无旗印))
|
||||||
|
if removed_dim:
|
||||||
|
draw_rect(Rect2(Vector2.ZERO, Vector2(edge, edge)), Color(0.12, 0.10, 0.09, 0.62), true)
|
||||||
|
|
||||||
|
|
||||||
# 南立面(D 档件一——墙格南缘内侧,自底边向上:双段暗带在下、口沿高光线贴其上沿):
|
# 南立面(D 档件一——墙格南缘内侧,自底边向上:双段暗带在下、口沿高光线贴其上沿):
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ func refresh() -> void:
|
|||||||
var hero: Vector2i = _minesweeper.hero_pos()
|
var hero: Vector2i = _minesweeper.hero_pos()
|
||||||
_diggable.clear()
|
_diggable.clear()
|
||||||
for n in _board.neighbors8(hero):
|
for n in _board.neighbors8(hero):
|
||||||
if not _minesweeper.is_revealed(n):
|
if not _minesweeper.is_revealed(n) and not _minesweeper.is_removed(n):
|
||||||
_diggable[n] = true
|
_diggable[n] = true
|
||||||
for y in _board.height:
|
for y in _board.height:
|
||||||
for x in _board.width:
|
for x in _board.width:
|
||||||
@@ -684,6 +684,7 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
|
|||||||
return
|
return
|
||||||
var revealed: bool = _minesweeper.is_revealed(c)
|
var revealed: bool = _minesweeper.is_revealed(c)
|
||||||
var flag: bool = _minesweeper.has_flag(c)
|
var flag: bool = _minesweeper.has_flag(c)
|
||||||
|
var removed: bool = _minesweeper.is_removed(c) # 迭代 13 T8/D6——中立移除格(P2 甲退色降档)
|
||||||
var is_hero: bool = c == hero
|
var is_hero: bool = c == hero
|
||||||
var glyph := ""
|
var glyph := ""
|
||||||
var bg: Color
|
var bg: Color
|
||||||
@@ -691,6 +692,10 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
|
|||||||
# 旗槽统一维护(T17 修复):旗格=封印旗纹理;其余格清 null(插旗格挖开后残留在 tex_flag
|
# 旗槽统一维护(T17 修复):旗格=封印旗纹理;其余格清 null(插旗格挖开后残留在 tex_flag
|
||||||
# 会让 _draw 无条件把旗印画在已开格上——tex_flag 绘制无 revealed 条件)。缺盘走字形回退。
|
# 会让 _draw 无条件把旗印画在已开格上——tex_flag 绘制无 revealed 条件)。缺盘走字形回退。
|
||||||
u.tex_flag = _flag_tex if (flag and not revealed and _flag_tex != null) else null
|
u.tex_flag = _flag_tex if (flag and not revealed and _flag_tex != null) else null
|
||||||
|
if removed:
|
||||||
|
revealed = false # 移除格固定未开呈现基座(退色暗幕层承载;雷格本无数字——P1 甲周边数字冻结)
|
||||||
|
flag = false
|
||||||
|
u.tex_flag = null
|
||||||
if revealed:
|
if revealed:
|
||||||
var rec: Dictionary = _minesweeper.reveal_record(c) # E2 收口:已开记录经查询面
|
var rec: Dictionary = _minesweeper.reveal_record(c) # E2 收口:已开记录经查询面
|
||||||
if rec.get("kind", "") == "mine":
|
if rec.get("kind", "") == "mine":
|
||||||
@@ -809,6 +814,7 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
|
|||||||
u.fog_state = (2 if revealed else 1) if fogged else 0
|
u.fog_state = (2 if revealed else 1) if fogged else 0
|
||||||
else:
|
else:
|
||||||
u.fog_state = 0
|
u.fog_state = 0
|
||||||
|
u.removed_dim = removed
|
||||||
u.queue_redraw()
|
u.queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
@@ -1117,3 +1123,26 @@ func _make_cursor_texture(glyph: String) -> ImageTexture:
|
|||||||
if f != null:
|
if f != null:
|
||||||
f.draw_string(img, Vector2(2, 18), glyph, HORIZONTAL_ALIGNMENT_LEFT, 20, 18, COL_DIGIT)
|
f.draw_string(img, Vector2(2, 18), glyph, HORIZONTAL_ALIGNMENT_LEFT, 20, 18, COL_DIGIT)
|
||||||
return ImageTexture.create_from_image(img)
|
return ImageTexture.create_from_image(img)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- 探针只读出口(D2 改造 2026-10-04——_cells/_diggable 私有容器直入退役;形态仿 fx_probe 先例:只读快照) ----
|
||||||
|
|
||||||
|
# 格件映射只读快照(Vector2i → CellUnit;键面同构——断言语义零变化;破私有面的只是容器字典,
|
||||||
|
# 格件字段本为公有呈现面,快照值即原格件引用)
|
||||||
|
func cells_probe() -> Dictionary:
|
||||||
|
return _cells.duplicate()
|
||||||
|
|
||||||
|
|
||||||
|
# 可挖域只读快照(Vector2i → true;8 向相邻未开格=呼吸微光作用集)
|
||||||
|
func diggable_probe() -> Dictionary:
|
||||||
|
return _diggable.duplicate()
|
||||||
|
|
||||||
|
|
||||||
|
# 悬停格只读出口(case① 悬停到达断言消费面——_hover 私有域直入退役,D2 清单外同类项随批收口)
|
||||||
|
func hover_probe() -> Vector2i:
|
||||||
|
return _hover
|
||||||
|
|
||||||
|
|
||||||
|
# 迷雾参数只读快照(case⑮ 参数漂移断言消费面——_fog_lantern/_fog_enabled 私有域直入退役,D2 清单外同类项随批收口)
|
||||||
|
func fog_params_probe() -> Dictionary:
|
||||||
|
return {"lantern": _fog_lantern, "enabled": _fog_enabled}
|
||||||
@@ -28,7 +28,7 @@ var _relic_list: VBoxContainer
|
|||||||
func _init() -> void:
|
func _init() -> void:
|
||||||
custom_minimum_size = Vector2(240, 0)
|
custom_minimum_size = Vector2(240, 0)
|
||||||
clip_contents = true
|
clip_contents = true
|
||||||
# 主面板穿石板戏服(样张 v2 冻结批 T2——纹理本色≈PANEL_HERO;数据框仍素面 §7.1;缺位回退 flat)
|
# 主面板穿石板戏服(样张 v2 冻结批 T2——纹理本色;数据框仍素面 §7.1;缺位回退 flat)
|
||||||
var sb := UiKit.panel_skinned(Vector4(8, 6, 8, 6))
|
var sb := UiKit.panel_skinned(Vector4(8, 6, 8, 6))
|
||||||
add_theme_stylebox_override("panel", sb)
|
add_theme_stylebox_override("panel", sb)
|
||||||
var col := VBoxContainer.new()
|
var col := VBoxContainer.new()
|
||||||
|
|||||||
@@ -61,14 +61,14 @@ var _cand_ids: Array[int] = [] # 本停点候选 id 集(图上命
|
|||||||
var _content_size := Vector2(620, 240) # 图内容包络(open_choices 按图拟合——画布 min 尺寸随它)
|
var _content_size := Vector2(620, 240) # 图内容包络(open_choices 按图拟合——画布 min 尺寸随它)
|
||||||
var _hover_id := -1 # 悬停预承诺候选(-1 = 无)
|
var _hover_id := -1 # 悬停预承诺候选(-1 = 无)
|
||||||
var abandon_btn: Button # 放弃入口(可见性归组合根 set_abandon_available——只在冒险模式开)
|
var abandon_btn: Button # 放弃入口(可见性归组合根 set_abandon_available——只在冒险模式开)
|
||||||
var _abandon_armed := false # 二次确认态(首击置位、二击提交、超时/重开回落)
|
var _abandon_timer := ArmedTimer.new() # 放弃确认窗(T3/D3 P4 乙——_process 帧驱动倒计时机制收口;窗口=CONFIRM_WINDOW_S)
|
||||||
var _confirm_left := 0.0 # 确认态剩余秒数(_process 倒计时+脉冲驱动)
|
|
||||||
var _node_icons := {} # 节点图标贴图(load_tex 守卫——缺位 null → 字形回退,冒烟/无素材检出安全)
|
var _node_icons := {} # 节点图标贴图(load_tex 守卫——缺位 null → 字形回退,冒烟/无素材检出安全)
|
||||||
var _noise_rect: TextureRect # 屏底石纹(换层色板渐变承载面——null = 纯色回退无挂点)
|
var _noise_rect: TextureRect # 屏底石纹(换层色板渐变承载面——null = 纯色回退无挂点)
|
||||||
var _tint_level := 0 # 当前已应用 tint 的层号(0 = 未应用)
|
var _tint_level := 0 # 当前已应用 tint 的层号(0 = 未应用)
|
||||||
|
|
||||||
|
|
||||||
func _init() -> void:
|
func _init() -> void:
|
||||||
|
_abandon_timer.expired.connect(_reset_abandon_confirm)
|
||||||
# 节点图标贴图装载(样张 v1 冻结形制——load_tex 守卫,缺位回退现行字形)
|
# 节点图标贴图装载(样张 v1 冻结形制——load_tex 守卫,缺位回退现行字形)
|
||||||
for t in TEX_NODE_ICONS:
|
for t in TEX_NODE_ICONS:
|
||||||
_node_icons[t] = UiKit.load_tex(TEX_NODE_ICONS[t])
|
_node_icons[t] = UiKit.load_tex(TEX_NODE_ICONS[t])
|
||||||
@@ -201,34 +201,33 @@ func set_abandon_available(v: bool) -> void:
|
|||||||
# 放弃二次确认(布局决策 3):首击转确认态(文案+危险红脉冲约 3 秒超时回落),二击才提交。
|
# 放弃二次确认(布局决策 3):首击转确认态(文案+危险红脉冲约 3 秒超时回落),二击才提交。
|
||||||
# 单击不提交——ui_probe 战斗中 pressed.emit() 单击路径保持「未受理」语义(战斗中拒绝归组合根)。
|
# 单击不提交——ui_probe 战斗中 pressed.emit() 单击路径保持「未受理」语义(战斗中拒绝归组合根)。
|
||||||
func _on_abandon_pressed() -> void:
|
func _on_abandon_pressed() -> void:
|
||||||
if not _abandon_armed:
|
if not _abandon_timer.is_armed():
|
||||||
_abandon_armed = true
|
_abandon_timer.arm(CONFIRM_WINDOW_S)
|
||||||
_confirm_left = CONFIRM_WINDOW_S
|
|
||||||
abandon_btn.text = "再点一次确认放弃——不可逆"
|
abandon_btn.text = "再点一次确认放弃——不可逆"
|
||||||
|
if ArmedTimer.motion_still():
|
||||||
|
abandon_btn.modulate = Color(1, 1, 1, 0.75) # 减动效静态基色(脉冲层不再驱动)
|
||||||
return
|
return
|
||||||
_reset_abandon_confirm()
|
_reset_abandon_confirm()
|
||||||
abandon_requested.emit()
|
abandon_requested.emit()
|
||||||
|
|
||||||
|
|
||||||
func _reset_abandon_confirm() -> void:
|
func _reset_abandon_confirm() -> void:
|
||||||
_abandon_armed = false
|
_abandon_timer.disarm()
|
||||||
_confirm_left = 0.0
|
|
||||||
abandon_btn.modulate = Color(1, 1, 1, 1)
|
abandon_btn.modulate = Color(1, 1, 1, 1)
|
||||||
abandon_btn.text = "放弃" # 打磨包 2026-09-29——文案收敛 level-map 4 字上限(确认态转义面见 _on_abandon_pressed)
|
abandon_btn.text = "放弃" # 打磨包 2026-09-29——文案收敛 level-map 4 字上限(确认态转义面见 _on_abandon_pressed)
|
||||||
|
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
if not _abandon_armed:
|
if not _abandon_timer.is_armed():
|
||||||
return
|
return
|
||||||
_confirm_left -= delta
|
_abandon_timer.tick(delta)
|
||||||
if _confirm_left <= 0.0:
|
if not _abandon_timer.is_armed():
|
||||||
_reset_abandon_confirm()
|
return # 窗口到点回落(expired 信号已驱动 _reset_abandon_confirm)
|
||||||
return
|
# 危险红边框脉冲(单段呼吸约 1Hz——闪烁基线 ≤3 次/秒内;减动效 = 静态半透不闪——档判据单点 motion_still)
|
||||||
# 危险红边框脉冲(单段呼吸约 1Hz——闪烁基线 ≤3 次/秒内;减动效 = 静态半透不闪)
|
if ArmedTimer.motion_still():
|
||||||
if UiKit.motion_scale < 1.0:
|
|
||||||
abandon_btn.modulate = Color(1, 1, 1, 0.75)
|
abandon_btn.modulate = Color(1, 1, 1, 0.75)
|
||||||
else:
|
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)
|
abandon_btn.modulate = Color(1, 1, 1, 0.65 + 0.35 * k)
|
||||||
|
|
||||||
|
|
||||||
@@ -468,6 +467,18 @@ func _layout(nodes: Array, canvas: Control) -> Dictionary:
|
|||||||
return pos
|
return pos
|
||||||
|
|
||||||
|
|
||||||
|
# ---- 探针只读出口(D2 改造 2026-10-04——_layout/_canvas 私有域直入退役;形态仿 fx_probe 先例:只读快照) ----
|
||||||
|
|
||||||
|
# 选路布局只读快照(node → 局部位置;画布固定取现行 _canvas,调用侧零私有域触达)
|
||||||
|
func layout_probe(nodes: Array) -> Dictionary:
|
||||||
|
return _layout(nodes, _canvas)
|
||||||
|
|
||||||
|
|
||||||
|
# 路由画布只读快照(origin=全局原点;min_size=最小拟合尺寸——画布拟合断言消费面)
|
||||||
|
func canvas_probe() -> Dictionary:
|
||||||
|
return {"origin": _canvas.get_global_position(), "min_size": _canvas.custom_minimum_size}
|
||||||
|
|
||||||
|
|
||||||
# 分层(layout/fit 共享件):layer = 入口 BFS 深度;同层 slot 垂直排布;未达节点兜底排末层
|
# 分层(layout/fit 共享件):layer = 入口 BFS 深度;同层 slot 垂直排布;未达节点兜底排末层
|
||||||
func _assign_layers(nodes: Array, layer: Dictionary, slot: Dictionary) -> void:
|
func _assign_layers(nodes: Array, layer: Dictionary, slot: Dictionary) -> void:
|
||||||
var layer_counts := {}
|
var layer_counts := {}
|
||||||
|
|||||||
+104
-11
@@ -30,6 +30,9 @@ var _tested_tooltip := false
|
|||||||
var _inter_confirms := 0 # 大奖励屏确认计数(冒险推进停点——首见确认推进 L2,再见 = L2 通关收口)
|
var _inter_confirms := 0 # 大奖励屏确认计数(冒险推进停点——首见确认推进 L2,再见 = L2 通关收口)
|
||||||
var _settlement_confirms := 0 # 卡⑥ 胜利结算框确认计数(观测面——每场普通战斗胜利恰一)
|
var _settlement_confirms := 0 # 卡⑥ 胜利结算框确认计数(观测面——每场普通战斗胜利恰一)
|
||||||
var _route_clicks := 0 # 选路覆盖层点击计数(岔路使用率观测面)
|
var _route_clicks := 0 # 选路覆盖层点击计数(岔路使用率观测面)
|
||||||
|
var _locktest_state := 0 # T1 D1 锁期/重放断言状态机(0=待注入 1=等重放 2=完结)——每 run 一次
|
||||||
|
var _locktest_cell := Vector2i(-9, -9)
|
||||||
|
var _locktest_frames := 0
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -174,7 +177,7 @@ func _ready() -> void:
|
|||||||
var first_cell_ms := t_entry
|
var first_cell_ms := t_entry
|
||||||
if first_cell_ms > 500:
|
if first_cell_ms > 500:
|
||||||
_problems.append("进入战斗屏首格可点超时 %dms > 500ms" % first_cell_ms)
|
_problems.append("进入战斗屏首格可点超时 %dms > 500ms" % first_cell_ms)
|
||||||
if main.board_view._cells.is_empty():
|
if main.board_view.cells_probe().is_empty():
|
||||||
_problems.append("首格未可点(cells=0)")
|
_problems.append("首格未可点(cells=0)")
|
||||||
print("[click_probe] 首格可点计时 %dms(进入战斗屏口径,≤500ms AC)· 场景冷启动 %dms(含 UI 构建 %dms,观测项)" % [
|
print("[click_probe] 首格可点计时 %dms(进入战斗屏口径,≤500ms AC)· 场景冷启动 %dms(含 UI 构建 %dms,观测项)" % [
|
||||||
first_cell_ms, t_total, t_ui_ready])
|
first_cell_ms, t_total, t_ui_ready])
|
||||||
@@ -190,9 +193,12 @@ func _ready() -> void:
|
|||||||
_flags_placed.clear()
|
_flags_placed.clear()
|
||||||
continue
|
continue
|
||||||
if main.bonus_overlay.visible:
|
if main.bonus_overlay.visible:
|
||||||
# 迭代 10 三选一停点(BonusPickOverlay——奖励节点/通关兑付共用面;T17 硬锁批适配——
|
# 迭代 10 三选一停点(BonusPickOverlay——奖励节点/通关兑付共用面;T17 硬锁批适配——
|
||||||
# 本探针此前未适配该层,走到即卡死)。三态分派:替换子态(满位——放弃新件)/
|
# 本探针此前未适配该层,走到即卡死)。三态分派:替换子态(满位——放弃新件)/
|
||||||
# result 态(兑付反馈——点继续推进)/ 待选态(点首件+确认)。真实信号路径全线。
|
# result 态(兑付反馈——点继续推进)/ 待选态(点首件+确认)。真实信号路径全线。
|
||||||
|
# T1 D1——三选一停点输入锁定断言(停点态断言面:替换子/待选/兑付三态共用此层)。
|
||||||
|
if not main._input_locked():
|
||||||
|
_problems.append("三选一停点打开时输入未锁定")
|
||||||
if main.bonus_overlay._replace_box.visible:
|
if main.bonus_overlay._replace_box.visible:
|
||||||
main.bonus_overlay.replace_chosen.emit(-1)
|
main.bonus_overlay.replace_chosen.emit(-1)
|
||||||
elif main.bonus_overlay._continue_btn.visible:
|
elif main.bonus_overlay._continue_btn.visible:
|
||||||
@@ -204,17 +210,20 @@ func _ready() -> void:
|
|||||||
main.bonus_overlay.press_continue() # 停点已消费的残面——继续关屏
|
main.bonus_overlay.press_continue() # 停点已消费的残面——继续关屏
|
||||||
_flags_placed.clear()
|
_flags_placed.clear()
|
||||||
continue
|
continue
|
||||||
|
if main.shop_overlay.visible:
|
||||||
|
# 商店房覆盖层(GP+商店房批——L2 商店为 Boss 前必经位):零购离开(真实 leave 信号路径)放行选路。
|
||||||
|
# T1 D1 分派链序修正:商店层=选路页上的模态覆盖(迭代 11 商店房重构后停点呈现为
|
||||||
|
# level_screen+shop 双 visible)——必须先于 level_screen 分派,否则下层候选点击
|
||||||
|
# 空转烧尽预算(实证:lv2 商店停点双 visible 循环 12000 帧 done=false)。
|
||||||
|
main.shop_overlay.leave_pressed.emit()
|
||||||
|
_flags_placed.clear()
|
||||||
|
continue
|
||||||
if main.level_screen.visible:
|
if main.level_screen.visible:
|
||||||
# 节点图选路口(零自动推进停点)——图上命中路径交替选候选(岔路使用行为;布局决策 2 改造)
|
# 节点图选路口(零自动推进停点)——图上命中路径交替选候选(岔路使用行为;布局决策 2 改造)
|
||||||
main.level_screen.press_candidate(_route_clicks % 2)
|
main.level_screen.press_candidate(_route_clicks % 2)
|
||||||
_route_clicks += 1
|
_route_clicks += 1
|
||||||
_flags_placed.clear()
|
_flags_placed.clear()
|
||||||
continue
|
continue
|
||||||
if main.shop_overlay.visible:
|
|
||||||
# 商店房覆盖层(GP+商店房批——L2 商店为 Boss 前必经位):零购离开(真实 leave 信号路径)放行选路
|
|
||||||
main.shop_overlay.leave_pressed.emit()
|
|
||||||
_flags_placed.clear()
|
|
||||||
continue
|
|
||||||
if main._inter_overlay.visible:
|
if main._inter_overlay.visible:
|
||||||
# 大奖励屏(冒险级停点——非末关通关恰一次):首见确认推进 L2(真实按钮信号路径);再见 = L2 已通关,收口
|
# 大奖励屏(冒险级停点——非末关通关恰一次):首见确认推进 L2(真实按钮信号路径);再见 = L2 已通关,收口
|
||||||
if not main._input_locked():
|
if not main._input_locked():
|
||||||
@@ -281,6 +290,8 @@ func _ready() -> void:
|
|||||||
# 排播演播期豁免(T17 硬锁 v3 随批——合法停顿态):演播期棋盘锁生效、probe 点击被吞、
|
# 排播演播期豁免(T17 硬锁 v3 随批——合法停顿态):演播期棋盘锁生效、probe 点击被吞、
|
||||||
# 逻辑态恒定属预期。判据=纯队列(与 main 锁同源)。Waiting 收尾等待期**不豁免**——
|
# 逻辑态恒定属预期。判据=纯队列(与 main 锁同源)。Waiting 收尾等待期**不豁免**——
|
||||||
# 锁若把收尾链卡死,此处 stall 照报(探测面)。
|
# 锁若把收尾链卡死,此处 stall 照报(探测面)。
|
||||||
|
# T1 D1 处置=保留豁免+注记:冻结/缓存/重放语义不再靠豁免绕行,由 _locktest_tick
|
||||||
|
# 脚本化断言正面承载(每 run 一次;逻辑态恒定本豁免继续作 stall 判据)。
|
||||||
if main._row_queue.is_empty():
|
if main._row_queue.is_empty():
|
||||||
_stall_frames += 1
|
_stall_frames += 1
|
||||||
if _stall_frames == 30:
|
if _stall_frames == 30:
|
||||||
@@ -290,9 +301,14 @@ func _ready() -> void:
|
|||||||
_stall_frames = 0
|
_stall_frames = 0
|
||||||
_ap_spent_this_round = 0
|
_ap_spent_this_round = 0
|
||||||
_last_state = st
|
_last_state = st
|
||||||
|
_locktest_tick()
|
||||||
if main.settlement.battle_result == "" and main.settlement.action_economy.phase == "Active":
|
if main.settlement.battle_result == "" and main.settlement.action_economy.phase == "Active":
|
||||||
_run_modal_probes_once()
|
# T1 D1——锁测在飞期(state 1)冻结探针动作面:排空帧 autoplay 的免费 move 会抢在
|
||||||
_play_one_action()
|
# 帧末 deferred 重放之前改变英雄位(flag 拒收 not_adjacent 实证)——「注入→重放」
|
||||||
|
# 窗口内局面零变化正是 v3.1 被测语义(演播期冻结),动作面恢复待断言收口。
|
||||||
|
if _locktest_state != 1:
|
||||||
|
_run_modal_probes_once()
|
||||||
|
_play_one_action()
|
||||||
# ---- 预算耗尽收口(T17 硬锁批适配):选件变强后 probe 可能活过预算不再战败——断点段前提
|
# ---- 预算耗尽收口(T17 硬锁批适配):选件变强后 probe 可能活过预算不再战败——断点段前提
|
||||||
# (冒险终局面在案)须主动满足。收口驱动:任何停点层逐个推进(复用主循环分支语义)至
|
# (冒险终局面在案)须主动满足。收口驱动:任何停点层逐个推进(复用主循环分支语义)至
|
||||||
# level_screen 选路停点→真实放弃二击→终局面。战斗中则继续 autoplay 打完。
|
# level_screen 选路停点→真实放弃二击→终局面。战斗中则继续 autoplay 打完。
|
||||||
@@ -527,6 +543,8 @@ func _play_one_action() -> void:
|
|||||||
var settlement = main.settlement
|
var settlement = main.settlement
|
||||||
if settlement == null:
|
if settlement == null:
|
||||||
return # 结算竞态防抖:battle_finished 与本轮动作调度同帧到达时 settlement 可能已被置空(基线既有噪声——加固)
|
return # 结算竞态防抖:battle_finished 与本轮动作调度同帧到达时 settlement 可能已被置空(基线既有噪声——加固)
|
||||||
|
if not main._row_queue.is_empty():
|
||||||
|
return # T1 D1——演播期不出手:此时点击只会进锁期缓存被「最后一次」覆盖(原逐帧连点语义无行为覆盖价值,且毁锁期断言注入的独占前提)
|
||||||
var minesweeper = settlement.minesweeper
|
var minesweeper = settlement.minesweeper
|
||||||
# 行为①:随机插旗(右键——未开相邻格),约 20% 轮次
|
# 行为①:随机插旗(右键——未开相邻格),约 20% 轮次
|
||||||
if _ap_spent_this_round == 0 and _rand.randf() < 0.2:
|
if _ap_spent_this_round == 0 and _rand.randf() < 0.2:
|
||||||
@@ -581,6 +599,81 @@ func _click_stop() -> void:
|
|||||||
main.ap_bar._btn_stop.pressed.emit()
|
main.ap_bar._btn_stop.pressed.emit()
|
||||||
|
|
||||||
|
|
||||||
|
# ---- T1 D1:锁期/重放断言(每 run 一次,脚本化场景——演播期豁免的断言化承载) ----
|
||||||
|
# 锁期断言(排播队列活跃=锁面):注入旗击 → 直落被冻结不受理(旗未置)+ `_locked_click`
|
||||||
|
# 缓存命中且为本注入(「最后一次」语义——注入帧内无并发点击)。
|
||||||
|
# 重放断言(演播完边沿 pump_ended→_replay_locked_click 重入唯一动作通道):缓存清空 +
|
||||||
|
# 受理恰一次——旗布尔=单次送达探测器(双发=两次翻转回 false、零发=false,均暴露)。
|
||||||
|
# 前提消失守卫:等重放期间出现停点层/战斗收场(注入时点恰逢终结演播)→ 静默弃测不计
|
||||||
|
# problem(旗动作不杀伤,正常局不触发;断言实现形态取舍在代拍白名单内)。
|
||||||
|
func _locktest_tick() -> void:
|
||||||
|
if _locktest_state == 2:
|
||||||
|
return
|
||||||
|
var settlement = main.settlement
|
||||||
|
if settlement == null or settlement.minesweeper.current_board() == null:
|
||||||
|
return
|
||||||
|
if _locktest_state == 0:
|
||||||
|
if main._row_queue.is_empty() or main._input_locked():
|
||||||
|
return
|
||||||
|
if String(settlement.battle_result) != "" or String(settlement.action_economy.phase) != "Active":
|
||||||
|
return
|
||||||
|
if int(settlement.action_economy.ap) < 1:
|
||||||
|
return
|
||||||
|
var target := _locktest_pick_cell(settlement.minesweeper)
|
||||||
|
if target.x < 0 or settlement.minesweeper.has_flag(target):
|
||||||
|
return # 无合法邻格/目标被并发旗行为占位——下帧重挑
|
||||||
|
_locktest_cell = target
|
||||||
|
_locktest_frames = 0
|
||||||
|
main.board_view.click_cell(target, MOUSE_BUTTON_RIGHT)
|
||||||
|
if settlement.minesweeper.has_flag(target):
|
||||||
|
_problems.append("锁期断言:演播期点击未被冻结(旗即时受理——v3 冻结失守)")
|
||||||
|
_locktest_state = 2
|
||||||
|
return
|
||||||
|
if main._locked_click.is_empty() or main._locked_click.get("cell") != target:
|
||||||
|
_problems.append("锁期断言:演播期点击未入缓存(v3.1 缓存失守)")
|
||||||
|
_locktest_state = 2
|
||||||
|
return
|
||||||
|
_locktest_state = 1
|
||||||
|
elif _locktest_state == 1:
|
||||||
|
if main.bonus_overlay.visible or main._inter_overlay.visible or main._end_overlay.is_open() \
|
||||||
|
or String(settlement.battle_result) != "":
|
||||||
|
_locktest_state = 2 # 前提消失守卫:终结演播竞态——静默弃测
|
||||||
|
return
|
||||||
|
if not main._row_queue.is_empty():
|
||||||
|
_locktest_frames = 0 # 演播未完——重放耐心只在排空后起算(链式大演播数百帧不占预算)
|
||||||
|
return
|
||||||
|
_locktest_frames += 1
|
||||||
|
if _locktest_frames < 3:
|
||||||
|
return # 排空边沿的 deferred 重放(pump_ended 帧末执行)在途宽限
|
||||||
|
if _locktest_frames > 90:
|
||||||
|
_problems.append("重放断言:演播完边沿后缓存未清(%s)" % str(main._locked_click))
|
||||||
|
_locktest_state = 2
|
||||||
|
return
|
||||||
|
if main._locked_click.is_empty():
|
||||||
|
if not settlement.minesweeper.has_flag(_locktest_cell):
|
||||||
|
var b = settlement.minesweeper.current_board()
|
||||||
|
print("[lockdiag] 重放后旗未置:br='%s' phase=%s ap=%d cell=%s revealed=%s board=%dx%d hero=%s" % [
|
||||||
|
str(settlement.battle_result), str(settlement.action_economy.phase),
|
||||||
|
int(settlement.action_economy.ap), str(_locktest_cell),
|
||||||
|
str(settlement.minesweeper.is_revealed(_locktest_cell)),
|
||||||
|
b.width, b.height, str(settlement.minesweeper.hero_pos())])
|
||||||
|
_problems.append("重放断言:锁期缓存点击未受理恰一次(旗未置位——零发或双发)")
|
||||||
|
_locktest_state = 2
|
||||||
|
main.board_view.click_cell(_locktest_cell, MOUSE_BUTTON_RIGHT) # 拔旗恢复现场(尽力而为不判定)
|
||||||
|
|
||||||
|
|
||||||
|
func _locktest_pick_cell(minesweeper) -> Vector2i:
|
||||||
|
var hero: Vector2i = minesweeper.hero_pos()
|
||||||
|
for dy in range(-1, 2):
|
||||||
|
for dx in range(-1, 2):
|
||||||
|
if dx == 0 and dy == 0:
|
||||||
|
continue
|
||||||
|
var c: Vector2i = hero + Vector2i(dx, dy)
|
||||||
|
if minesweeper.current_board().in_bounds(c) and not minesweeper.is_revealed(c) and not minesweeper.has_flag(c):
|
||||||
|
return c
|
||||||
|
return Vector2i(-1, -1)
|
||||||
|
|
||||||
|
|
||||||
func _report_stall(st: Dictionary) -> void:
|
func _report_stall(st: Dictionary) -> void:
|
||||||
var why := ""
|
var why := ""
|
||||||
if st.phase == "Waiting":
|
if st.phase == "Waiting":
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ func _ready() -> void:
|
|||||||
for n in nds:
|
for n in nds:
|
||||||
if main.runner.node_id_of(n) == int(cands[0]):
|
if main.runner.node_id_of(n) == int(cands[0]):
|
||||||
tgt = n
|
tgt = n
|
||||||
var npos: Vector2 = ro._layout(nds, ro._canvas)[tgt]
|
var npos: Vector2 = ro.layout_probe(nds)[tgt]
|
||||||
var gpos: Vector2 = ro._canvas.get_global_position() + npos
|
var gpos: Vector2 = ro.canvas_probe()["origin"] + npos
|
||||||
var evm := InputEventMouseMotion.new()
|
var evm := InputEventMouseMotion.new()
|
||||||
evm.position = gpos
|
evm.position = gpos
|
||||||
evm.global_position = gpos
|
evm.global_position = gpos
|
||||||
@@ -97,12 +97,13 @@ func _ready() -> void:
|
|||||||
# 常数锚(画布拟合探针):70/122/26/8 与 presentation/views/level_map_screen.gd 的 _content_size
|
# 常数锚(画布拟合探针):70/122/26/8 与 presentation/views/level_map_screen.gd 的 _content_size
|
||||||
# 公式(70 + maxl*122 + NODE_HALF.x + 8)同源复刻——改常数须两侧同步(本探针只读断言层,真源在屏)
|
# 公式(70 + maxl*122 + NODE_HALF.x + 8)同源复刻——改常数须两侧同步(本探针只读断言层,真源在屏)
|
||||||
var need_x2: float = 70.0 + maxl2 * 122.0 + 26.0 + 8.0
|
var need_x2: float = 70.0 + maxl2 * 122.0 + 26.0 + 8.0
|
||||||
if float(ro2._canvas.custom_minimum_size.x) < need_x2 - 0.5:
|
var cvmin: Dictionary = ro2.canvas_probe()
|
||||||
_pre_fail("画布拟合不足(min.x=%.0f < 最深内容 %.0f——深层节点不可点回归)" % [ro2._canvas.custom_minimum_size.x, need_x2])
|
if float(cvmin["min_size"].x) < need_x2 - 0.5:
|
||||||
|
_pre_fail("画布拟合不足(min.x=%.0f < 最深内容 %.0f——深层节点不可点回归)" % [cvmin["min_size"].x, need_x2])
|
||||||
var bv = main.board_view
|
var bv = main.board_view
|
||||||
var ms = main.settlement.minesweeper if main.settlement != null else null
|
var ms = main.settlement.minesweeper if main.settlement != null else null
|
||||||
if ms == null or bv._cells.is_empty():
|
if ms == null or bv.cells_probe().is_empty():
|
||||||
_pre_fail("战斗未就绪(cells=%d settlement=%s)" % [bv._cells.size(), str(main.settlement)])
|
_pre_fail("战斗未就绪(cells=%d settlement=%s)" % [bv.cells_probe().size(), str(main.settlement)])
|
||||||
return
|
return
|
||||||
bv.action_requested.connect(func(c: Vector2i, kind: String): _actions.append([c, kind]))
|
bv.action_requested.connect(func(c: Vector2i, kind: String): _actions.append([c, kind]))
|
||||||
bv.chord_attempted.connect(func(c: Vector2i): _chords.append(c))
|
bv.chord_attempted.connect(func(c: Vector2i): _chords.append(c))
|
||||||
@@ -130,7 +131,7 @@ func _ready() -> void:
|
|||||||
# 并流批(ADR-0007 裁丙 2026-09-27)后首战板面种子随全局序漂移——_diggable(主角 8 邻未开域)
|
# 并流批(ADR-0007 裁丙 2026-09-27)后首战板面种子随全局序漂移——_diggable(主角 8 邻未开域)
|
||||||
# 不足两枚时,沿已开区真点击移位到「≥2 未开邻」的前沿格再选;移位失败即收手交由选取失败分支报状态
|
# 不足两枚时,沿已开区真点击移位到「≥2 未开邻」的前沿格再选;移位失败即收手交由选取失败分支报状态
|
||||||
for _expand in range(16):
|
for _expand in range(16):
|
||||||
if bv._diggable.size() >= 2:
|
if bv.diggable_probe().size() >= 2:
|
||||||
break
|
break
|
||||||
var mv_t := Vector2i(-9, -9)
|
var mv_t := Vector2i(-9, -9)
|
||||||
for y in bv._board.height:
|
for y in bv._board.height:
|
||||||
@@ -157,7 +158,7 @@ func _ready() -> void:
|
|||||||
break
|
break
|
||||||
var flag_t := Vector2i(-9, -9)
|
var flag_t := Vector2i(-9, -9)
|
||||||
var dig_t := Vector2i(-9, -9)
|
var dig_t := Vector2i(-9, -9)
|
||||||
for c in bv._diggable:
|
for c in bv.diggable_probe():
|
||||||
if not ms.has_flag(c):
|
if not ms.has_flag(c):
|
||||||
if flag_t.x == -9:
|
if flag_t.x == -9:
|
||||||
flag_t = c
|
flag_t = c
|
||||||
@@ -172,9 +173,9 @@ func _ready() -> void:
|
|||||||
# ---- case① 悬停可挖域:motion → 悬停态到达(金框 + 十字光标)----
|
# ---- case① 悬停可挖域:motion → 悬停态到达(金框 + 十字光标)----
|
||||||
_push_motion(_pos(bv, flag_t))
|
_push_motion(_pos(bv, flag_t))
|
||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
if bv._hover != flag_t:
|
if bv.hover_probe() != flag_t:
|
||||||
_problems.append("case① 悬停未到达(_hover=%s 应=%s)" % [bv._hover, flag_t])
|
_problems.append("case① 悬停未到达(_hover=%s 应=%s)" % [bv.hover_probe(), flag_t])
|
||||||
var u1 = bv._cells[flag_t]
|
var u1 = bv.cells_probe()[flag_t]
|
||||||
if not u1.hl_visible or u1.hl_color != UiKit.GOLD:
|
if not u1.hl_visible or u1.hl_color != UiKit.GOLD:
|
||||||
_problems.append("case① 悬停金框缺失(visible=%s color=%s)" % [str(u1.hl_visible), str(u1.hl_color)])
|
_problems.append("case① 悬停金框缺失(visible=%s color=%s)" % [str(u1.hl_visible), str(u1.hl_color)])
|
||||||
if bv.mouse_default_cursor_shape != Input.CURSOR_CROSS:
|
if bv.mouse_default_cursor_shape != Input.CURSOR_CROSS:
|
||||||
@@ -184,14 +185,14 @@ func _ready() -> void:
|
|||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
if _forbids.is_empty() or _forbids[_forbids.size() - 1] != forbid_t:
|
if _forbids.is_empty() or _forbids[_forbids.size() - 1] != forbid_t:
|
||||||
_problems.append("case② hover_forbidden 未达(%s)" % [str(_forbids)])
|
_problems.append("case② hover_forbidden 未达(%s)" % [str(_forbids)])
|
||||||
if bv._cells[forbid_t].hl_color != UiKit.RED_FORBID:
|
if bv.cells_probe()[forbid_t].hl_color != UiKit.RED_FORBID:
|
||||||
_problems.append("case② 禁入红框缺失(color=%s)" % str(bv._cells[forbid_t].hl_color))
|
_problems.append("case② 禁入红框缺失(color=%s)" % str(bv.cells_probe()[forbid_t].hl_color))
|
||||||
# ---- case③ 悬停已开格:路径预览点迹 + 移动光标 ----
|
# ---- case③ 悬停已开格:路径预览点迹 + 移动光标 ----
|
||||||
_push_motion(_pos(bv, reveal_t))
|
_push_motion(_pos(bv, reveal_t))
|
||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
var dots := 0
|
var dots := 0
|
||||||
for c in bv._cells:
|
for c in bv.cells_probe():
|
||||||
if bv._cells[c].dot_visible:
|
if bv.cells_probe()[c].dot_visible:
|
||||||
dots += 1
|
dots += 1
|
||||||
if dots < 1:
|
if dots < 1:
|
||||||
_problems.append("case③ 路径预览点迹未亮(dots=0)")
|
_problems.append("case③ 路径预览点迹未亮(dots=0)")
|
||||||
@@ -208,7 +209,7 @@ func _ready() -> void:
|
|||||||
# ---- case④b 旗纹理槽回归断言(P0-1 修复批 2026-10-02):T13 批曾在 paint 尾部无条件
|
# ---- case④b 旗纹理槽回归断言(P0-1 修复批 2026-10-02):T13 批曾在 paint 尾部无条件
|
||||||
# `u.tex_flag = null` 覆盖 T17 批的条件赋值=素材在位时插旗不可见(headless 缺盘走字形回退
|
# `u.tex_flag = null` 覆盖 T17 批的条件赋值=素材在位时插旗不可见(headless 缺盘走字形回退
|
||||||
# 掩盖)——有盘断言纹理在槽、缺盘断言字形回退,两分支防双写踩踏复活 ----
|
# 掩盖)——有盘断言纹理在槽、缺盘断言字形回退,两分支防双写踩踏复活 ----
|
||||||
var flag_unit = bv._cells.get(flag_t)
|
var flag_unit = bv.cells_probe().get(flag_t)
|
||||||
if bv._flag_tex != null:
|
if bv._flag_tex != null:
|
||||||
if flag_unit == null or flag_unit.tex_flag != bv._flag_tex:
|
if flag_unit == null or flag_unit.tex_flag != bv._flag_tex:
|
||||||
_problems.append("case④b 旗纹理槽未挂封印旗纹理(tex_flag=%s)" % ["无单元" if flag_unit == null else str(flag_unit.tex_flag)])
|
_problems.append("case④b 旗纹理槽未挂封印旗纹理(tex_flag=%s)" % ["无单元" if flag_unit == null else str(flag_unit.tex_flag)])
|
||||||
@@ -298,7 +299,7 @@ func _ready() -> void:
|
|||||||
# 必须回退「人」(数字格仍显数字——回退判据与抑制判据同源);验毕还原纹理重绘。 ----
|
# 必须回退「人」(数字格仍显数字——回退判据与抑制判据同源);验毕还原纹理重绘。 ----
|
||||||
if bv._world.hero != null:
|
if bv._world.hero != null:
|
||||||
var hp11: Vector2i = ms.hero_pos()
|
var hp11: Vector2i = ms.hero_pos()
|
||||||
var u11 = bv._cells[hp11]
|
var u11 = bv.cells_probe()[hp11]
|
||||||
var tex_saved: Texture2D = bv._hero_tex_idle
|
var tex_saved: Texture2D = bv._hero_tex_idle
|
||||||
bv._hero_tex_idle = null
|
bv._hero_tex_idle = null
|
||||||
bv.refresh()
|
bv.refresh()
|
||||||
@@ -315,23 +316,25 @@ func _ready() -> void:
|
|||||||
if mark_t.x >= 0:
|
if mark_t.x >= 0:
|
||||||
bv.set_variant_mark_resolver(func(_c): return "flat_damage")
|
bv.set_variant_mark_resolver(func(_c): return "flat_damage")
|
||||||
bv.refresh()
|
bv.refresh()
|
||||||
if String(bv._cells[mark_t].mutation_mark) != "flat_damage":
|
if String(bv.cells_probe()[mark_t].mutation_mark) != "flat_damage":
|
||||||
_problems.append("case⑭ 变异角标未落位(mutation_mark=%s)" % str(bv._cells[mark_t].mutation_mark))
|
_problems.append("case⑭ 变异角标未落位(mutation_mark=%s)" % str(bv.cells_probe()[mark_t].mutation_mark))
|
||||||
bv.set_variant_mark_resolver(Callable())
|
bv.set_variant_mark_resolver(Callable())
|
||||||
bv.refresh()
|
bv.refresh()
|
||||||
if String(bv._cells[mark_t].mutation_mark) != "":
|
if String(bv.cells_probe()[mark_t].mutation_mark) != "":
|
||||||
_problems.append("case⑭ 角标未随 resolver 复位(mutation_mark=%s)" % str(bv._cells[mark_t].mutation_mark))
|
_problems.append("case⑭ 角标未随 resolver 复位(mutation_mark=%s)" % str(bv.cells_probe()[mark_t].mutation_mark))
|
||||||
else:
|
else:
|
||||||
_problems.append("case⑭ 无未开格可验(板面异常)")
|
_problems.append("case⑭ 无未开格可验(板面异常)")
|
||||||
# ---- case⑮ 视野迷雾(迭代 11 第④批 T5——三态呈态/半径圈/移动跟随/开关两态零扰动/提灯插值位;
|
# ---- case⑮ 视野迷雾(迭代 11 第④批 T5——三态呈态/半径圈/移动跟随/开关两态零扰动/提灯插值位;
|
||||||
# 纯呈现零逻辑——迷雾开关切换前后 minesweeper 状态摘要逐位一致) ----
|
# 纯呈现零逻辑——迷雾开关切换前后 minesweeper 状态摘要逐位一致) ----
|
||||||
if int(bv.FOG_RADIUS) != 8 or int(bv.FOG_LANTERN_BONUS) != 2 or int(bv._fog_lantern) != 0 or not bv._fog_enabled:
|
var fogp: Dictionary = bv.fog_params_probe()
|
||||||
|
if int(bv.FOG_RADIUS) != 8 or int(bv.FOG_LANTERN_BONUS) != 2 or int(fogp["lantern"]) != 0 or not fogp["enabled"]:
|
||||||
_problems.append("case⑮ 迷雾参数漂移(R=%s bonus=%s lantern=%s enabled=%s 应=8/2/0/true)"
|
_problems.append("case⑮ 迷雾参数漂移(R=%s bonus=%s lantern=%s enabled=%s 应=8/2/0/true)"
|
||||||
% [str(bv.FOG_RADIUS), str(bv.FOG_LANTERN_BONUS), str(bv._fog_lantern), str(bv._fog_enabled)])
|
% [str(bv.FOG_RADIUS), str(bv.FOG_LANTERN_BONUS), str(fogp["lantern"]), str(fogp["enabled"])])
|
||||||
else:
|
else:
|
||||||
var hero15: Vector2i = ms.hero_pos()
|
var hero15: Vector2i = ms.hero_pos()
|
||||||
var b15 = ms.current_board()
|
var b15 = ms.current_board()
|
||||||
var fog_ok := true
|
var fog_ok := true
|
||||||
|
var cells_f1: Dictionary = bv.cells_probe()
|
||||||
for y in b15.height:
|
for y in b15.height:
|
||||||
for x in b15.width:
|
for x in b15.width:
|
||||||
var c15 := Vector2i(x, y)
|
var c15 := Vector2i(x, y)
|
||||||
@@ -339,7 +342,7 @@ func _ready() -> void:
|
|||||||
var expect15: int = 0
|
var expect15: int = 0
|
||||||
if d15 > bv.FOG_RADIUS:
|
if d15 > bv.FOG_RADIUS:
|
||||||
expect15 = 2 if ms.is_revealed(c15) else 1
|
expect15 = 2 if ms.is_revealed(c15) else 1
|
||||||
var got15: int = int(bv._cells[c15].fog_state)
|
var got15: int = int(cells_f1[c15].fog_state)
|
||||||
if got15 != expect15:
|
if got15 != expect15:
|
||||||
fog_ok = false
|
fog_ok = false
|
||||||
_problems.append("case⑮ 迷雾呈态不符(%s dist=%d got=%d expect=%d)" % [str(c15), d15, got15, expect15])
|
_problems.append("case⑮ 迷雾呈态不符(%s dist=%d got=%d expect=%d)" % [str(c15), d15, got15, expect15])
|
||||||
@@ -348,11 +351,12 @@ func _ready() -> void:
|
|||||||
break
|
break
|
||||||
# 半径边界格恰在圈内(曼哈顿距离恰 =R 的格 fog_state=0——存在性校验,12×12 板必有)
|
# 半径边界格恰在圈内(曼哈顿距离恰 =R 的格 fog_state=0——存在性校验,12×12 板必有)
|
||||||
var has_boundary := false
|
var has_boundary := false
|
||||||
|
var cells_f2: Dictionary = bv.cells_probe()
|
||||||
for y in b15.height:
|
for y in b15.height:
|
||||||
for x in b15.width:
|
for x in b15.width:
|
||||||
if absi(x - hero15.x) + absi(y - hero15.y) == int(bv.FOG_RADIUS):
|
if absi(x - hero15.x) + absi(y - hero15.y) == int(bv.FOG_RADIUS):
|
||||||
has_boundary = true
|
has_boundary = true
|
||||||
if int(bv._cells[Vector2i(x, y)].fog_state) != 0:
|
if int(cells_f2[Vector2i(x, y)].fog_state) != 0:
|
||||||
_problems.append("case⑮ 边界圈内格被误遮(dist==R 应 fog_state=0)")
|
_problems.append("case⑮ 边界圈内格被误遮(dist==R 应 fog_state=0)")
|
||||||
break
|
break
|
||||||
if has_boundary:
|
if has_boundary:
|
||||||
@@ -369,9 +373,10 @@ func _ready() -> void:
|
|||||||
digest_before.append(ms.is_revealed(c16))
|
digest_before.append(ms.is_revealed(c16))
|
||||||
bv.set_fog_enabled(false)
|
bv.set_fog_enabled(false)
|
||||||
var all_clear := true
|
var all_clear := true
|
||||||
|
var cells_f3: Dictionary = bv.cells_probe()
|
||||||
for y in b15.height:
|
for y in b15.height:
|
||||||
for x in b15.width:
|
for x in b15.width:
|
||||||
if int(bv._cells[Vector2i(x, y)].fog_state) != 0:
|
if int(cells_f3[Vector2i(x, y)].fog_state) != 0:
|
||||||
all_clear = false
|
all_clear = false
|
||||||
if not all_clear:
|
if not all_clear:
|
||||||
_problems.append("case⑮ 迷雾关未全板归零(off 态应 fog_state=0)")
|
_problems.append("case⑮ 迷雾关未全板归零(off 态应 fog_state=0)")
|
||||||
@@ -383,7 +388,7 @@ func _ready() -> void:
|
|||||||
if digest_off != digest_before:
|
if digest_off != digest_before:
|
||||||
_problems.append("case⑮ 迷雾开关改写逻辑态(两态结算逐位一致 🔴 契约破)")
|
_problems.append("case⑮ 迷雾开关改写逻辑态(两态结算逐位一致 🔴 契约破)")
|
||||||
bv.set_fog_enabled(true)
|
bv.set_fog_enabled(true)
|
||||||
if int(bv._cells[Vector2i(0, 0)].fog_state) == 0 \
|
if int(bv.cells_probe()[Vector2i(0, 0)].fog_state) == 0 \
|
||||||
and absi(0 - hero15.x) + absi(0 - hero15.y) > int(bv.FOG_RADIUS):
|
and absi(0 - hero15.x) + absi(0 - hero15.y) > int(bv.FOG_RADIUS):
|
||||||
_problems.append("case⑮ 迷雾复开后远角未复原遮蔽(fog_state=0)")
|
_problems.append("case⑮ 迷雾复开后远角未复原遮蔽(fog_state=0)")
|
||||||
if _problems.is_empty():
|
if _problems.is_empty():
|
||||||
@@ -405,10 +410,11 @@ func _has_action(cell: Vector2i, kind: String) -> bool:
|
|||||||
|
|
||||||
func _pick_forbidden(bv, ms) -> Vector2i:
|
func _pick_forbidden(bv, ms) -> Vector2i:
|
||||||
var b = ms.current_board()
|
var b = ms.current_board()
|
||||||
|
var digp: Dictionary = bv.diggable_probe()
|
||||||
for y in b.height:
|
for y in b.height:
|
||||||
for x in b.width:
|
for x in b.width:
|
||||||
var c := Vector2i(x, y)
|
var c := Vector2i(x, y)
|
||||||
if not ms.is_revealed(c) and not bv._diggable.has(c):
|
if not ms.is_revealed(c) and not digp.has(c):
|
||||||
return c
|
return c
|
||||||
return Vector2i(-9, -9)
|
return Vector2i(-9, -9)
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ func _ready() -> void:
|
|||||||
if main.adventure == null or String(main.adventure.state) != "running":
|
if main.adventure == null or String(main.adventure.state) != "running":
|
||||||
problems.append("首战中放弃未被拒(战斗域单事务在飞——state 应保持 running)")
|
problems.append("首战中放弃未被拒(战斗域单事务在飞——state 应保持 running)")
|
||||||
# ---- 既有断言(回归基线;A 区进度条断言随 停点前移批 2026-09-23 退役——节点链进度由关卡画面独立屏承载)----
|
# ---- 既有断言(回归基线;A 区进度条断言随 停点前移批 2026-09-23 退役——节点链进度由关卡画面独立屏承载)----
|
||||||
if main.board_view._cells.is_empty():
|
if main.board_view.cells_probe().is_empty():
|
||||||
problems.append("棋盘格未生成(cells=0)")
|
problems.append("棋盘格未生成(cells=0)")
|
||||||
if main.board_view.board() == null:
|
if main.board_view.board() == null:
|
||||||
problems.append("board 未装载")
|
problems.append("board 未装载")
|
||||||
@@ -228,7 +228,7 @@ func _ready() -> void:
|
|||||||
main._close_modal(main._end_overlay)
|
main._close_modal(main._end_overlay)
|
||||||
if problems.is_empty():
|
if problems.is_empty():
|
||||||
print("[ui_probe] PASS — cells=%d board=%dx%d hp='%s' enemies=%d ap_dots=%d" % [
|
print("[ui_probe] PASS — cells=%d board=%dx%d hp='%s' enemies=%d ap_dots=%d" % [
|
||||||
main.board_view._cells.size(), main.board_view.board().width, main.board_view.board().height,
|
main.board_view.cells_probe().size(), main.board_view.board().width, main.board_view.board().height,
|
||||||
main.info_panel.attack_text(),
|
main.info_panel.attack_text(),
|
||||||
main.stage.enemy_box.get_child_count(), main.ap_bar._dots.get_child_count()])
|
main.stage.enemy_box.get_child_count(), main.ap_bar._dots.get_child_count()])
|
||||||
get_tree().quit(0)
|
get_tree().quit(0)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
**Purpose**: QA 交接前 15 分钟内人工跑完的关键路径清单。
|
**Purpose**: QA 交接前 15 分钟内人工跑完的关键路径清单。
|
||||||
**Run via**: `/smoke-check`(读本文件)。
|
**Run via**: `/smoke-check`(读本文件)。
|
||||||
**Update**: 新核心系统落地后增补条目。
|
**Update**: 新核心系统落地后增补条目。
|
||||||
**分工注记**:自动化冒烟(切片 96/96)覆盖规则正确性;本清单面向**表现与手感**(自动化探针不可 G-W-T 的部分)。
|
**分工注记**:自动化冒烟(全量断言,现行口径 718/0——2026-10-04 迭代 13 T6 随批换代,随批末闸走)覆盖规则正确性;本清单面向**表现与手感**(自动化探针不可 G-W-T 的部分)。
|
||||||
|
|
||||||
## Core Stability(恒跑)
|
## Core Stability(恒跑)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user