iter8-T2/T3/T4 卡②③④:继续上局按钮(有断点亮起+文本带关卡序号+真实信号路径续玩)+新冒险互斥二次确认(首击换面3s回落二击提交+提交即弃旧断点)+T3 三结局清断点(_settle 统一收口)+最小设置面(SettingsStore 独立存储 user://settings.cfg 三节+SettingsOverlay 音量 master bus 实时/全屏/减动效转正/退出游戏+DS_DEBUG 调试门控+调整板旧开关退役)——探针隔离补全(click/input/layout/ui_probe/ui_tune 五探针 main.save_manager 隔离+首次布尔置位,真实档探针污染事故两起已清〔教训:断点写盘面随 save_manager 引用走,probe 换 meta 不换 save_manager 即漏〕)——闸:冒烟 593/0+五探针全绿+click 断点段问题=0——代拍板:互斥确认文案/设置面排布/确认窗口 3s(追认清单呈报)+设置与本地化 v0.2 随批

This commit is contained in:
DKChan committed 2026-09-29 19:25:19 +08:00
1 parent 04ef6a54ee
commit d599743433
15 files changed
+452 -21

No files matched your search

+92 -5
View File
@@ -39,6 +39,9 @@ var _adventure_mode := false # 冒险模式标记(inter 层确认分派
var logger = null # 试玩日志器(JSONL 持久化 + 卡死诊断转储——呈现层组件)
var _level_active := false
var _seed_edit: LineEdit
var _lvbtn: Button # 开始冒险按钮句柄(迭代 8 卡③——有断点时互斥二次确认文案换面)
var _resume_btn: Button # 继续上局按钮句柄(迭代 8 卡②——有断点才亮)
var _lv_start_armed := false # 新冒险二次确认态(首击置位、二击提交、3s 超时回落——仿放弃确认)
var _start_overlay: Control
var _end_overlay: BattleEndOverlay
var _inter_overlay: Control
@@ -70,6 +73,8 @@ var _adventure_gp_final := 0 # 收束清零拍暂存的最终合计
var _aux_use_selection := 0 # 魔法道具选中句柄(迭代 7 T4——0=未选中;选中期棋盘点击=使用目标拾取)
var _aux_unlocked := {} # aux 解锁门控集(迭代 7 T5——工坊解锁×活行交集,冒险开始组装)
var camp_overlay: CampOverlay # 营地覆盖层(元进度实现批——主菜单营地入口兑付呈现)
var settings_store # 系统级设置独立存储(迭代 8 卡④——音量/全屏/减动效)
var settings_overlay: SettingsOverlay # 设置覆盖层(迭代 8 卡④——最小设置面)
var tune_panel: TunePanel
var _modals = null # 模态层级状态机(modal_stack.gd——缺陷⑤纪律载体)
@@ -91,6 +96,10 @@ func _ready() -> void:
# (fatal 只留给数值表——fail-fast 边界在表加载,不在档);高版本拒读态后续写盘被类内拒绝。
save_manager = SaveManagerScript.new()
save_manager.load_from_disk()
# 系统级设置(迭代 8 卡④——独立存储不随存档):启动即恢复音量/全屏/减动效引擎态
settings_store = SettingsStore.new()
settings_store.load_settings()
settings_store.apply()
# 元进度账本(元进度实现批):存档就绪后建——余额/定价/购买/入账/注入全经此单点
meta = MetaProgressionScript.new(db, save_manager)
_debug = DebugSessionScript.new(db, get_node("/root/RngService"),
@@ -98,6 +107,7 @@ func _ready() -> void:
_debug.battle_created.connect(_on_debug_battle_created)
_debug.battle_ready.connect(_on_debug_battle_ready)
_build_ui()
_refresh_resume_button() # 迭代 8 卡②——启动读盘后按断点在档态点亮「继续上局」
_start_overlay.visible = true
@@ -162,18 +172,26 @@ func _build_ui() -> void:
tune_panel.closed.connect(_close_tune_panel)
tune_panel.apply_requested.connect(_on_tune_apply)
tune_panel.simulate_requested.connect(_on_tune_simulate)
settings_overlay = SettingsOverlay.new(settings_store) # 迭代 8 卡④——最小设置面(音量/全屏/减动效/退出)
add_child(settings_overlay)
settings_overlay.closed.connect(_on_settings_closed)
_modals = ModalStack.new()
for m: Control in [legend_overlay, review_overlay, node_overlay, level_screen, _end_overlay, shop_overlay, camp_overlay, tune_panel]:
for m: Control in [legend_overlay, review_overlay, node_overlay, level_screen, _end_overlay, shop_overlay, camp_overlay, tune_panel, settings_overlay]:
_register_modal(m)
_tune_sim = TuneSimController.new(db, tune_panel, int(db.hero.max_hp))
var start: Dictionary = UiAssembler.build_start_overlay(self, db)
_start_overlay = start.overlay
_seed_edit = start.seed_edit
_lvbtn = start.lvbtn
_resume_btn = start.resumebtn
start.lvbtn.pressed.connect(_start_adventure_from_menu)
start.resumebtn.pressed.connect(_resume_from_menu) # 迭代 8 卡②——续玩入口
start.tunebtn.pressed.connect(_show_tune_panel)
start.dbgbtn.pressed.connect(_start_debug_levels)
start.dbgbtn.visible = OS.get_environment("DS_DEBUG") == "1" # 迭代 8 卡④——调试入口默认隐藏门控(DS_DEBUG=1 点亮)
start.campbtn.pressed.connect(_open_camp)
start.settingsbtn.pressed.connect(_open_settings) # 迭代 8 卡④——最小设置面入口
var inter: Dictionary = UiAssembler.build_inter_overlay(self)
_inter_overlay = inter.overlay
@@ -224,13 +242,60 @@ func _input_locked() -> bool:
# ==================== 冒险模式入口(唯一试玩入口——单关测试直入口退役 2026-09-23 用户拍板)====================
func _start_adventure_from_menu() -> void:
# 互斥二次确认(迭代 8 卡②③):有断点时开新冒险须两击——首击转确认态(文案换面+3s 超时回落,
# 仿放弃确认形态),二击才提交;提交即弃旧断点(clear+写盘——「开新局」覆盖语义对盘面即时生效)
if save_manager != null and save_manager.has_breakpoint() and not _lv_start_armed:
_lv_start_armed = true
if _lvbtn != null:
_lvbtn.text = "已有未完冒险——再点一次开新冒险(旧断点将被覆盖)"
get_tree().create_timer(3.0).timeout.connect(_reset_lv_confirm)
return
if save_manager != null:
save_manager.clear_breakpoint()
save_manager.write_to_disk()
_lv_start_armed = false
_start_overlay.visible = false
_start_adventure()
# 冒险模式(三关连探——AdventureRunner 编排;转发信号签名与 runner 一致,接线照原样对接;
# 信号连接只在组合根——ADR-0001)
func _start_adventure() -> void:
func _reset_lv_confirm() -> void:
_lv_start_armed = false
if _lvbtn != null:
_lvbtn.text = "▶ 开始冒险(三关连探:地精洞窟 → 幽暗地域 → 灰矮人矿城)"
# 继续上局按钮可见性 = 断点在档态(迭代 8 卡②——启动读盘后与每次回开始菜单刷新;确认态随面复位)
func _refresh_resume_button() -> void:
if _resume_btn == null or save_manager == null:
return
var has: bool = save_manager.has_breakpoint()
_resume_btn.visible = has
if has:
var bp: Dictionary = save_manager.breakpoint_snapshot()
_resume_btn.text = "▶ 继续上局(第 %d 关 · 自最近停点恢复)" % maxi(1, int(bp.get("level_index", 1)))
_reset_lv_confirm()
# 继续上局(迭代 8 卡②——卡① resume_adventure 的消费入口):断点重建至最近停点
func _resume_from_menu() -> void:
if save_manager == null or not save_manager.has_breakpoint():
return
var bp: Dictionary = save_manager.breakpoint_snapshot()
_start_overlay.visible = false
_wire_adventure()
_level_active = true
level_screen.set_abandon_available(true)
_reset_run_scope()
log_view.clear()
logger.start_session(int(bp.get("master_seed", 0)))
adventure.set_hero_override(tune_panel.atk_pct(), tune_panel.hp_pct())
if not adventure.resume_adventure(bp, {"hero_override": meta.hero_override() if meta != null else {}, "aux_unlocked": _aux_unlocked_set()}):
_fatal_screen("续玩恢复失败——见输出日志")
return
# 冒险编排构建+接线(开新局/续玩共用——信号连接只在组合根,ADR-0001;断点注入同口)
func _wire_adventure() -> void:
adventure = AdventureRunner.new(db, get_node("/root/RngService"))
_adventure_mode = true
adventure.step_row.connect(_on_step_row)
@@ -242,6 +307,13 @@ func _start_adventure() -> void:
adventure.big_reward_paid.connect(_on_big_reward)
adventure.adventure_finished.connect(_on_adventure_finished)
adventure.level_started.connect(_on_adventure_level_started)
adventure.set_save_manager(save_manager) # 断点承载注入(迭代 8 卡①——停点快照写盘;调试/模拟不经过此路径)
# 冒险模式(三关连探——AdventureRunner 编排;转发信号签名与 runner 一致,接线照原样对接;
# 信号连接只在组合根——ADR-0001)
func _start_adventure() -> void:
_wire_adventure()
_level_active = true
level_screen.set_abandon_available(true) # 放弃入口挂选路停点(天然非战斗期——2026-09-21 用户拍板)
_reset_run_scope()
@@ -252,7 +324,6 @@ func _start_adventure() -> void:
# 永久强化注入(元进度实现批):冒险开始参数面——meta 现算 override 显式传参(全零 = 白板)
# 首次冒险脚本(关卡节点图生成 v0.4):存档布尔位 false = 全关固定模板 0(教学重放);置位后逐关抽签
var first_script: bool = not save_manager.is_first_adventure_done()
adventure.set_save_manager(save_manager) # 断点承载注入(迭代 8 卡①——停点快照写盘;调试/模拟不经过此路径)
if not adventure.start_adventure(seed_val, {"hero_override": meta.hero_override() if meta != null else {}, "first_script": first_script, "aux_unlocked": _aux_unlocked_set()}):
_fatal_screen("冒险启动失败——见输出日志")
return
@@ -370,6 +441,7 @@ func _on_battle_end_continue() -> void:
_adventure_mode = false
tune_panel.reset_sliders()
_start_overlay.visible = true
_refresh_resume_button() # 迭代 8 卡②——回菜单按断点在档态刷新(收束后已清=隐藏)
_refresh_presentations()
@@ -675,9 +747,23 @@ func _open_camp() -> void:
func _on_camp_closed() -> void:
camp_overlay.visible = false
_start_overlay.visible = true
_refresh_resume_button() # 迭代 8 卡②——回菜单按断点在档态刷新
_refresh_presentations() # 关闭后刷新顶栏(工坊解锁可能变了余额侧读数面)
# ---- 最小设置面(迭代 8 卡④——入口/开合仿营地先例:开始层互斥;生效与持久化归 SettingsStore)----
func _open_settings() -> void:
_start_overlay.visible = false
settings_overlay.visible = true
func _on_settings_closed() -> void:
settings_overlay.visible = false
_start_overlay.visible = true
_refresh_resume_button()
# 营地快照投影(纯读——锻造三行/工坊六行/余额/最近冒险;拼句归 CampOverlay,账面归 MetaProgression)
func _camp_data() -> Dictionary:
var forge_rows: Array[Dictionary] = []
@@ -1037,6 +1123,7 @@ func _show_tune_panel() -> void:
func _close_tune_panel() -> void:
tune_panel.visible = false
_start_overlay.visible = true
_refresh_resume_button() # 迭代 8 卡②——回菜单按断点在档态刷新
func _on_tune_apply(atk_pct: int, hp_pct: int) -> void:
+50
View File
@@ -0,0 +1,50 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# Date: 2026-09-29(迭代 8 卡④ 最小设置面——设置与本地化.md P1 两项兑现:音量入口+减动效开关)
# 系统级设置独立存储(设置与本地化.md——2026-09-19 用户拍板:一安装一份、删游戏才消失,
# 不进存档与生涯数据两分)。三件最小面:音量(master bus)/全屏/减动效。
# 生效侧分工:入口与持久化归本类;音量对声音的生效口径归 音频.md(master bus 单点);
# 减动效对动画的分档兑现归 battle.md §Transitions(UiKit.motion_scale 既有承载,结算逐行
# 兑现=信息载体只降速不跳过)。
# 探针隔离:path 可注入(冒烟/探针禁碰真实 user://settings.cfg——同存档隔离纪律)。
class_name SettingsStore
extends RefCounted
const DEFAULT_PATH := "user://settings.cfg"
var path: String = DEFAULT_PATH
var volume_linear := 1.0 # master bus 线性 0-1(0=静音)
var fullscreen := false
var reduced_motion := false
func _init(path_override: String = "") -> void:
if path_override != "":
path = path_override
func load_settings() -> void:
var cf := ConfigFile.new()
if cf.load(path) != OK:
return # 缺文件 = 出厂默认(合法态——非损坏不兜底)
volume_linear = clampf(float(cf.get_value("audio", "volume_linear", 1.0)), 0.0, 1.0)
fullscreen = bool(cf.get_value("display", "fullscreen", false))
reduced_motion = bool(cf.get_value("motion", "reduced", false))
func save_settings() -> bool:
var cf := ConfigFile.new()
cf.set_value("audio", "volume_linear", volume_linear)
cf.set_value("display", "fullscreen", fullscreen)
cf.set_value("motion", "reduced", reduced_motion)
return cf.save(path) == OK
# 应用到引擎态(启动恢复与实时调节同一口——headless 下窗口面跳过)
func apply() -> void:
var master := AudioServer.get_bus_index("Master")
AudioServer.set_bus_mute(master, volume_linear <= 0.001)
AudioServer.set_bus_volume_db(master, linear_to_db(maxf(volume_linear, 0.001)))
UiKit.motion_scale = 0.5 if reduced_motion else 1.0
if DisplayServer.get_name() != "headless":
DisplayServer.window_set_mode(
DisplayServer.WINDOW_MODE_FULLSCREEN if fullscreen else DisplayServer.WINDOW_MODE_WINDOWED)
+1
View File
@@ -0,0 +1 @@
uid://clxn7dqtxo313
+7 -2
View File
@@ -131,6 +131,9 @@ static func build_start_overlay(host: Control, db = null) -> Dictionary:
seed_row.add_child(seed_edit)
# 唯一试玩入口 = 正式「开始冒险」(三关序列);单关测试直入口(L2/L3)退役(2026-09-23 用户拍板)
var resumebtn := UiKit.button("▶ 继续上局(自最近停点恢复)") # 迭代 8 卡②——有断点才亮(可见性归组合根刷新)
resumebtn.visible = false
box.add_child(resumebtn)
var lvbtn := UiKit.button("▶ 开始冒险(三关连探:地精洞窟 → 幽暗地域 → 灰矮人矿城)")
box.add_child(lvbtn)
var tunebtn := UiKit.button("测试用 · 角色数值调整板(模拟天赋树属性提升)")
@@ -139,6 +142,8 @@ static func build_start_overlay(host: Control, db = null) -> Dictionary:
box.add_child(dbgbtn)
var campbtn := UiKit.button("营地 · 永久强化(钥石换永久属性——跨局成长账本)") # 元进度实现批——主菜单营地入口(双入口框架的主菜单一侧)
box.add_child(campbtn)
var settingsbtn := UiKit.button("设置(音量 / 全屏 / 减动效 / 退出游戏)") # 迭代 8 卡④——最小设置面入口
box.add_child(settingsbtn)
# 玩法帮助文案(表值驱动——2026-09-16 AP 压速批遗留硬编码 8 随手修正;护甲等级掷点批同步"减伤"→"护甲强化")
var help_text := "玩法:每轮 %d AP——挖开(1AP)/插旗(1AP)/移动(免费)/连锁(免费)。\n每个非封锁格都藏一种格面资源(增伤/护甲强化/回血/爆裂/削弱)。开安全格、标中雷 = 正向兑付;\n开雷格、标错安全格 = 负向兑付——全部在轮末结算逐行兑现;吃掷点的基础攻击按护甲等级判定,无 miss、最低保底 1 点。\n停手:剩余 AP 转存 RAP;弃图刷新:RAP−%d 当场换图继续挖(每轮剩 %d 次)。\n敌人全灭胜、主角倒下负。悬停可看三态光标与可挖域微光。" % [
@@ -148,8 +153,8 @@ static func build_start_overlay(host: Control, db = null) -> Dictionary:
box.add_child(help)
return {"overlay": overlay, "box": box, "seed_edit": seed_edit,
"lvbtn": lvbtn, "tunebtn": tunebtn, "dbgbtn": dbgbtn,
"campbtn": campbtn}
"lvbtn": lvbtn, "resumebtn": resumebtn, "tunebtn": tunebtn, "dbgbtn": dbgbtn,
"campbtn": campbtn, "settingsbtn": settingsbtn}
# 调试两层模式的层间过渡覆盖层(历史入口专用)。返回 {overlay, label, next_btn}——接线归组合根
+121
View File
@@ -0,0 +1,121 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# Date: 2026-09-29(迭代 8 卡④ 最小设置面)
# 设置覆盖层(纯视图边界规范 1.3 同款):音量滑杆/全屏/减动效三件+退出游戏+返回。
# 生效与持久化全经 SettingsStore 单点(实时应用+即改即存);本类零核心触碰。
# 减动效转正承载(测试调整板旧开关退役——设置面唯一入口,卡④ AC③)。
# 探针接缝:retarget(store) = 隔离存档换注口(同 camp 换注先例);控件引用公有只读供断言。
class_name SettingsOverlay
extends Control
signal closed
var _store
var _volume: HSlider
var _volume_val: Label
var _fs_btn: CheckButton
var _motion_btn: CheckButton
func _init(store) -> void:
_store = store
_build()
_sync()
visible = false
# 探针隔离换注(冒烟/探针禁碰真实 user://settings.cfg——换注后按新 store 重挂控件态)
func retarget(store) -> void:
_store = store
_sync()
func _build() -> void:
var dim := ColorRect.new()
dim.color = UiKit.BG_OVERLAY
dim.set_anchors_preset(Control.PRESET_FULL_RECT)
add_child(dim)
var center := CenterContainer.new()
center.set_anchors_preset(Control.PRESET_FULL_RECT)
dim.add_child(center)
var box := VBoxContainer.new()
box.custom_minimum_size = Vector2(560, 0)
box.add_theme_constant_override("separation", 8)
center.add_child(box)
var title := UiKit.label("设置", 26, true)
title.add_theme_color_override("font_color", UiKit.WARM)
box.add_child(title)
var hint := UiKit.label("系统级设置——独立存储不随存档(一安装一份,删游戏才消失)", 13, false)
hint.add_theme_color_override("font_color", UiKit.WARM_SOFT)
box.add_child(hint)
box.add_child(HSeparator.new())
# 音量(master bus 实时生效——入口归设置、生效口径归 音频.md)
var vol_row := HBoxContainer.new()
box.add_child(vol_row)
vol_row.add_child(UiKit.label("音量", 15, false))
_volume = HSlider.new()
_volume.min_value = 0
_volume.max_value = 100
_volume.step = 1
_volume.custom_minimum_size = Vector2(280, 24)
_volume.value_changed.connect(_on_volume_changed)
vol_row.add_child(_volume)
_volume_val = UiKit.label("100%", 15, true)
_volume_val.custom_minimum_size = Vector2(60, 0)
vol_row.add_child(_volume_val)
# 全屏(art-bible 1920 档缩放策略冲突条目挂起注记——现行视口恒等拉伸,全屏切换即窗口模式切换)
_fs_btn = CheckButton.new()
_fs_btn.text = "全屏"
_fs_btn.toggled.connect(_on_fullscreen_toggled)
box.add_child(_fs_btn)
# 减动效(battle.md §Transitions 承诺转正——时长减半,结算逐行兑现=信息载体不整体跳过)
_motion_btn = CheckButton.new()
_motion_btn.text = "减动效(动画时长减半——运动敏感/演示用)"
_motion_btn.toggled.connect(_on_motion_toggled)
box.add_child(_motion_btn)
box.add_child(HSeparator.new())
var quit_btn := UiKit.button("退出游戏")
quit_btn.pressed.connect(_on_quit_pressed)
box.add_child(quit_btn)
var close_btn := UiKit.button("返回")
close_btn.pressed.connect(_emit_closed)
box.add_child(close_btn)
func _sync() -> void:
_volume.set_value_no_signal(roundf(_store.volume_linear * 100.0))
_volume_val.text = "%d%%" % roundi(_store.volume_linear * 100.0)
_fs_btn.set_pressed_no_signal(_store.fullscreen)
_motion_btn.set_pressed_no_signal(_store.reduced_motion)
func _on_volume_changed(v: float) -> void:
_store.volume_linear = v / 100.0
_store.apply()
_store.save_settings()
_volume_val.text = "%d%%" % int(v)
func _on_fullscreen_toggled(on: bool) -> void:
_store.fullscreen = on
_store.apply()
_store.save_settings()
func _on_motion_toggled(on: bool) -> void:
_store.reduced_motion = on
_store.apply()
_store.save_settings()
func _on_quit_pressed() -> void:
_store.save_settings()
get_tree().quit()
func _emit_closed() -> void:
closed.emit()
@@ -0,0 +1 @@
uid://b7570rrk1jl7h
+1 -12
View File
@@ -159,14 +159,7 @@ func _build() -> void:
var reset_btn := UiKit.button("重置为 0")
reset_btn.pressed.connect(_reset_btn_pressed)
btn_row.add_child(reset_btn)
# 减动效开关(battle.md §Transitions 内建承诺——切片期入口;正式设置层归设置批,两分制)
var motion_row := HBoxContainer.new()
box.add_child(motion_row)
motion_row.add_child(UiKit.label("减动效(动画时长减半——运动敏感/演示用)", 14, false))
var motion_btn := CheckButton.new()
motion_btn.button_pressed = UiKit.motion_scale < 1.0
motion_btn.toggled.connect(_on_motion_toggled)
motion_row.add_child(motion_btn)
# 减动效开关已转正迁出(迭代 8 卡④——设置面唯一入口,本面板退役该开关;motion_scale 引擎态由设置面承载)
box.add_child(HSeparator.new())
var sim_row := HBoxContainer.new()
@@ -221,10 +214,6 @@ func _scaled(base: int, pct: int) -> int:
return maxi(1, int(round(float(base) * (100.0 + float(pct)) / 100.0)))
func _on_motion_toggled(on: bool) -> void:
UiKit.motion_scale = 0.5 if on else 1.0
func _emit_simulate_requested() -> void:
simulate_requested.emit(int(_sim_count.value))