diff --git a/design/gdd/systems/跨局循环/生涯.md b/design/gdd/systems/跨局循环/生涯.md index b0491d2d..036b986f 100644 --- a/design/gdd/systems/跨局循环/生涯.md +++ b/design/gdd/systems/跨局循环/生涯.md @@ -6,13 +6,15 @@ ## 一、信息架构 -生涯入口是主菜单一级按钮,与营地平级(2026-10-06 用户裁,备选案与淘汰依据见任务书 P1)。现行主菜单按钮组由 `build_start_overlay` 构造(`game/presentation/ui_assembler.gd:104`):「冒险」组(继续上局 :138 / 开始冒险 :144)与「营地与系统」组(营地 :150 / 设置 :152 / 退出游戏 :156),另有右下角调试角标(DS_DEBUG=1 门控,与主按钮组非平级)。生涯按钮与营地平级同列一级;具体组帽归属与按钮排位属布局细节,归期一实装批(本文不画布局稿)。 +生涯入口是主菜单一级按钮,与营地平级(2026-10-06 用户裁,备选案与淘汰依据见任务书 P1)。现行主菜单按钮组由 `build_start_overlay` 构造(`game/presentation/ui_assembler.gd:104`):「冒险」组(继续上局 :138 / 开始冒险 :144)与「营地与系统」组(营地 :150 / 设置 :152 / 退出游戏 :156)。生涯按钮与营地平级同列一级;具体组帽归属与按钮排位属布局细节,归期一实装批(本文不画布局稿)。 ``` 主菜单(开始覆盖层) ├─ 冒险组:继续上局 · 开始冒险 - ├─ 营地与系统组:营地 · 生涯(本提纲新增) · 设置 · 退出游戏 - └─ 调试角标(DS_DEBUG=1 门控,非平级) + └─ 营地与系统组:营地 · 生涯(本提纲新增) · 设置 · 退出游戏 + +调试浮窗(迭代 18 卡⑦ P2 甲——入口迁宿主:开始层零调试入口;DS_DEBUG=1 会话浮窗启动即开, + 战斗屏 HUD 与关卡地图屏各一枚「调试」角标可再开) 生涯(局外只读面) ├─ 跨局汇总(累计战绩与解锁总览) diff --git a/design/qa/iter18-seed-exit-report-2026-10-07.md b/design/qa/iter18-seed-exit-report-2026-10-07.md new file mode 100644 index 00000000..95bb5cca --- /dev/null +++ b/design/qa/iter18-seed-exit-report-2026-10-07.md @@ -0,0 +1,110 @@ +# 出口报告:种子随机化与调试浮窗升级批(迭代 18 卡⑦) + +> 批次:功能性呈现+组合根批(纯呈现与组合根接线,零数值改动=零 MC;核心层 rng_service/冒险 runner/存档 schema 零 diff)。 +> worktree:`g-plan2-iter18-seed`(分支 `iter18-seed`,基点 = master adadf56)。 +> 任务书:`design/research/任务书-种子随机化与调试浮窗升级批-2026-10-07.md`(v1.2 裁点全裁 P1-P6 甲,U0 已发)。 +> 本报告按「吸收即死」纪律属中间文档,寿命=迭代寿命;结论同步见 roadmap 卡⑦行与本文 §十/§十一(台账与快照草稿,供主控誊入 active.md——active.md 本批零触碰)。 + +## 一、完成定义六条逐条判定(任务书 §1.2) + +| # | 判定项 | 判定 | 依据 | +|---|---|---|---| +| 1 | 非首次冒险+种子框空 → 主种子随机;同种子行为逐位不变 | ✅ | `_seed_value()` 三分单点(main.gd):空/非法框×非首局(存档布尔 is_first_adventure_done=true)→ 引擎全局熵 `randi()`;冒烟 811/0 两次(改前基线+批末)逐位同数=同种子确定性零漂移;探针⑩连抽 4 次不全等 42 断言绿 | +| 2 | 首局固定种子(P5 甲=42);显式整数全场景优先(含首局) | ✅ | 首局(新档 false)恒 42;显式合法整数第一优先(探针⑩:777 优先断言+⑧真实路径 master_seed==42 断言)。**随批抓获并修复:初版分支条件写反(首局随机/非首局 42)——探针钉死显式值故轻闸未暴露,⑩断言面价值实证(见 §四-3)** | +| 3 | 断点续玩逐位不变(存档 master_seed 回填路径零改动) | ✅ | `_resume_from_menu` 零触碰种子判定(bp 载 master_seed 直入 adventure.resume_adventure);ui_struct_probe ⑦resume 专项绿(合计 24 守卫断言原样) | +| 4 | DS_DEBUG=1 启动即开;对局中可开(P2 甲);遮挡按 P1 甲 | ✅ | `_ready` 尾 `_show_debug_overlay()`(探针①自设环境变量断言启动即开绿);两屏角标(战斗屏 HUD 右下角标对+地图屏顶栏)真实信号路径开窗(探针⑨绿);遮挡=ModalStack 登记制零机制改动(他模态开暂隐/关后恢复——探针⑥+ui_click 模态遮挡族断言绿) | +| 5 | 当局数据只读区块(P4 甲六项)数值与只读访问器逐项一致 | ✅ | 区块=种子(adventure.master_seed 现值,非 _seed_value 重算;非对局=RNG 服务现值,未播种显「—」)/关卡(adventure.level_index)/节点/HP/GP(runner 成文出口)/钥石(adventure.keystone);探针⑨六项值对断言绿;零写通路(喂入制 update_run_data,纯标签零信号连接零动效)+白名单外零连接巡检(连接数=10 全在册)绿 | +| 6 | ui_tune_probe 换代全绿;冒烟全量绿;MC 零跑 | ✅ | 专闸 ui_tune_probe exit=0(§三);冒烟 811/0;MC 零跑(§五静态论证) | + +## 二、任务卡完成态(T0-T5) + +| 卡 | 状态 | 提交/落点 | +|---|---|---| +| T0 | ✅ | worktree 基线复读:冒烟 811/0 三闸全过(=主控 master adadf56 实测同数);§2 逐条复核全数在案(modal 登记列表现值、DS_DEBUG 门控点、五路消费面、遮挡机制 F6 一致——S1 未触发);HEAD 时点=adadf56 | +| T1 | ✅ | 40fb4a5(C1 三分单点+种子框默认空+占位提示+五探针种子钉死+ui_click 断言改向;五探针=审计点名单三件+ui_probe/ui_struct 同病灶随批修复——`_debug.start_single` 第五路与冒险段同吃种子框) | +| T2 | ✅ | dd2c57c(输入锁防护全套+P2 甲角标迁宿主+开始层 dbgbtn 退役)+ 5b3a8fd(两处轻闸红修复:角标对 HBox 锚定改入树后延迟预设+struct overlap 滚动区跨界豁免) | +| T3 | ✅ | dd2c57c 同批(当局数据只读区块+`_current_master_seed()` 单点+旧 :1174 诊断种子重算冒充病灶改向+模态开合单口挂 `_sync_debug_overlay`);keystone 取数改向随 T4 批修(c36daa6) | +| T4 | ✅ | c36daa6(ui_tune_probe 断言面换代全绿+fx lambda 转命名方法+三分写反修复+钥石域修正) | +| T5 | ✅ | 本报告+roadmap 卡⑦状态列+生涯.md 文档同步(提交见 §九) | + +## 三、闸读数(T0 基线 → 批末) + +| 闸 | 基线(T0) | 批末 | 判 | +|---|---|---|---| +| 冒烟(run_smoke 全量) | 811/0 三闸全过 | **811/0 三闸全过**(零漂移=S2 确定性反证通过) | ✅ | +| ui_input_probe | 十七断言绿 | 十七断言绿(含 case⑯ 暂停角标真点击——角标对 HBox 锚定修复后复验) | ✅ | +| ui_click_probe | 问题=0 | done=true 问题=0(含 P2 甲迁宿主改向断言:开始层角标退役+DS_DEBUG 未设不默认开) | ✅ | +| ui_layout_probe | exit 0 | exit 0 | ✅ | +| ui_struct_probe / ui_contrast_probe(probe_gate 默认段顺带) | 绿 | 零红 / 12 对全绿 | ✅ | +| **专闸 ui_tune_probe** | 基线态(八段旧断言面,本批换代对象) | **换代后 exit=0 全链路 PASS**(①启动即开+树序新基准+⑧显式种子真实路径+开冒险关窗+⑨对局中可开/输入不锁/角标不置灰/两钮禁用/当局数据六项值对+只读巡检零非法连接+零动效+⑩种子三分) | ✅ | +| run_all(game/tests) | **17 套全绿**(批末补测——见 §六-1 环境备注) | **17 套全绿**(exit 0;ui_font_asset_test 经导入缓存刷新后 PASS) | ✅ | +| doc 三闸 | — | link 死链 2(奇物.md:130,存量=主区同源)/ gdd_lint 2 不通过(06/07 两文件,存量=iter17 移交面)/ term --strict 疑似现行 1(拔旗,06 存量)——**本批零新增**(本批唯一文档 diff=生涯.md 两处事实同步,lint 绿) | ✅(零新增口径) | +| 熵增闸 | — | design/diagrams 越界 ×1(存量=iter17 移交第 1 条,主区同红已实证) | 移交披露 | +| MC | — | 零跑(§五) | ✅ | + +对拍口径注:冒烟 811/0 在 T0(adadf56)、T1 后(40fb4a5)、T2/P2/T3 后(dd2c57c)、批末(含全部修复)四时点同数——同种子行为逐位不变的直接反证。 + +## 四、L1/L2 修复记录(过程红灯全披露,均为断言口径或组合根码缺陷、非核心层) + +1. **T1 后 ui_click 暂态红(预期内)**:断言先行(开始层角标退役断言在 T1 提交、P2 甲迁宿主在 T2 落码)——T2 落码即消,非缺陷。 +2. **角标对零尺寸锚角(T2 首轮轻闸两红)**:容器(HBox)出树时最小尺寸缓存陈旧为 0,MINSIZE 预设算出零尺寸锚角→暂停角标悬出屏边→ui_input case⑯ 角标真点击不中;struct 侧=[debug] 屏 overlap 假阳(当局数据区块加高列表后,滚动区内容未剪裁矩形跨界与滚区外关闭钮相交=滚动态样貌)。修复=预设改入树后延迟调用+grow 向左上(5b3a8fd)+struct overlap 规则补滚动区跨界豁免(恰好一枚在 ScrollContainer 内才豁免,同滚区两钮真重叠照红)。 +3. **🔴 `_seed_value()` 三分分支写反(T4 探针换代抓获)**:初版 `not is_first_adventure_done()` 时返回 randi()=首局随机/非首局 42,与 P5 甲相反。轻闸未暴露的原因=全部探针已钉死显式种子值(显式优先分支先行);⑩种子三分断言(首局空档恒 42/非首局连抽不全等 42)双红灯抓获,c36daa6 修复。**此为探针换代义务(任务书 T4)的直接产出——断言面先行的价值实证。** +4. **当局数据钥石取数域错(T4 换代抓获)**:`_debug_run_data` 原读 `runner.keystone`——LevelRunner 无 keystone 域(钥石账在 AdventureRunner)→ 运行期脚本错误中断探针 `_ready` 协程=探针永不退出(「挂死」表象,实为静默中断)。两次按挂起纪律处置(禁杀他卡进程、按命令行精确匹配仅杀本 worktree 探针进程、隔 60s 重试)后经实时输出定位,修复=改读 `adventure.keystone`(c36daa6)。 + +## 五、MC 零跑静态论证(任务书 §6——零跑批发起禁令遵守) + +1. **玩法行为零变更**:核心层(rng_service/adventure_runner/level_runner/存档)零 diff(`git diff adadf56 HEAD --stat` 可证,12 文件全在 presentation/ + smoke/ + 文档)。RNG 消费点零增减——随机种子生成走引擎全局熵 `randi()`,不触碰 rng_service 封闭流(C3 豁免面:组合根建种子不进玩法 RNG 消费)。 +2. **同种子逐位不变**:冒烟全量(含统一流黄金向量、确定性复放、断点续玩 400 局对拍既有断言)四时点 811/0 零漂移——「如需复放锚,固定种子冒烟即对拍面」的条件已满足且零锚作废。 +3. **随机化本身只改「新冒险种子取值分布」**:非首局空框时从恒 42 变为 [0, 2^31) 均匀取值——这是本批的功能目标而非数值改动(无任何表值/当量/概率变化);种子空间的统计性质(通关率分布等)属 M4 数值批统一调整范畴(2026-10-06 用户令殿后,未排期),本批无取证义务。 +4. 综上:无数值改动=零 MC(两入口铁规+MC 触发细则),无 MC 对拍基准变更。 + +## 六、存量问题移交(均非本批产生,基线同态实证) + +1. **ui_font_asset_test 批末初跑 2/2 红=worktree 环境性存量**:worktree 拷入的 `.godot` 导入缓存缺字体 .ttf 导入产物(「No loader found」)。已在基线 adadf56 分离检出对拍复现(同 2/2 红)=与本批零因果;按 probe_gate 冷检出同款 `--editor --quit` 刷导入后 PASS、run_all 17 套全绿。**移交项=四个并行 worktree 若同样报此红,同款一条命令可解**;主区缓存态未检(本批未触碰主区)。 +2. gdd_lint 存量红 2 文件(06/07)/ doc_link 死链 2(奇物.md:130)/ doc_term 拔旗 1(06)/ 熵增闸 diagrams 越界 1——全部=iter17 出口报告 §五同名移交项,本批零触碰零新增。 +3. ui_click_probe 旧断言「DS_DEBUG 门控失守」已随 P2 甲改向(卡④ AC④ 的开始层入口形态退役,门控语义移两屏角标+启动即开)——设置与本地化.md v0.2 版本历史行保留历史记录不回改(历史 changelog 非现行语义)。 + +## 七、代拍板记录(白名单内决断,commit 留痕,实机目检可推翻——追认单) + +| # | 项 | 决断 | 出处 | +|---|---|---|---| +| 1 | 种子框占位提示词 | 「留空=随机 · 首局恒 42」 | T1 40fb4a5 | +| 2 | 当局数据区块标题与行文案 | 标题「当局数据(只读)」;行名 种子/关卡/节点/HP/GP/钥石;HP 显示「当前/上限」;缺位显「—」 | T3 dd2c57c | +| 3 | 浮窗 desc 文案换代 | 「开发调试专用(DS_DEBUG=1 会话:启动即开;战斗屏/地图屏「调试」角标可再开)……」 | T3 dd2c57c | +| 4 | 角标落点形制微调 | 战斗屏=右下角「调试+暂停」HBox 并排(角距 16px、钮距 8px、暂停居角位——17卡③同形制不变);地图屏=顶栏暂停角标右侧 | T2 dd2c57c | +| 5 | 当局数据区块位次 | 列表首位(胜利演出区块之前——当局速查置顶) | T3 dd2c57c | +| 6 | 断言编号与文案 | ui_tune_probe ①-⑩ 段(⑨=对局中可开+当局数据+只读巡检、⑩=种子三分);struct overlap 跨界豁免规则文案 | T4 c36daa6 | + +## 八、实机目检清单(唯一硬人闸——DS_DEBUG=1 全流程) + +前置:以 `DS_DEBUG=1` 环境变量启动游戏(真实档或测试档均可)。 + +- [ ] A 启动即开:主菜单出现悬浮「调试」浮窗,菜单本身可见可点(浮窗悬浮其上);开始菜单**无**「调试」角标(已迁宿主)。 +- [ ] B 启动菜单直接点「调试 · 连续关卡两层」→ 浮窗收起 → 两层调试对局正常开打。 +- [ ] C 对局中开合:战斗屏右下角「调试」角标点亮(DS_DEBUG 会话)→ 点击开浮窗 → 棋盘变暗但**关窗后立即可挖**(无锁死);浮窗开着时「暂停」角标**不置灰**可点(点开暂停层时浮窗暂隐、暂停关闭后浮窗自动恢复)。 +- [ ] D 当局数据一致性:对局中开浮窗,六项(种子/关卡/节点/HP/GP/钥石)与实际局况一致;发生战斗/挖格/入账后重开浮窗数值随动。 +- [ ] E 种子查看位:当局数据「种子」值 = 本局实际种子(DS_DEBUG 会话首局=42;非首局关浮窗开局后重开核对为随机值,非 42 即达意)。 +- [ ] F 对局中按钮禁用态:对局中开浮窗,「运行模拟」与「调试·连续关卡两层」两钮置灰;回开始菜单(终局返回)后开浮窗两钮恢复可点。 +- [ ] G 种子框行为(非首局档):空框点「开始冒险」→ 当局数据种子为随机值;手填 777 → 种子=777;断点续玩 → 种子=存档原种子。 +- [ ] H 地图屏角标:关卡地图停点顶栏「调试」角标点亮可点(浮窗开时选路照常,关窗不扰动选路态)。 +- [ ] I 玩家 UI 零变化确认:不开 DS_DEBUG 启动 → 全流程与改前观感一致(零调试元素、零种子露出)。 + +## 九、提交哈希清单(worktree 内全部) + +| 提交 | 内容 | +|---|---| +| 40fb4a5 | ⑦T1:种子三分判定单点+种子框默认空+占位+五探针种子钉死+ui_click 断言改向 | +| dd2c57c | ⑦T2:输入锁死防护+P2 甲角标迁宿主+T3 当局数据只读区块(三件互锁一批) | +| 5b3a8fd | ⑦T2修复:角标对锚定入树后延迟预设+struct overlap 滚动区跨界豁免 | +| c36daa6 | ⑦T4:ui_tune_probe 断言面换代全绿+三分写反修复+钥石域修正+fx 命名方法 | +| (收口提交) | ⑦T5:出口报告+roadmap 卡⑦状态列+生涯.md 文档同步+main.gd 头注口径更新 | + +## 十、台账条目草稿(供主控誊入 active.md——本批未触碰 active.md) + +> 卡⑦(种子随机化+调试浮窗升级批)落地(2026-10-07,worktree g-plan2-iter18-seed,分支 iter18-seed,基点 adadf56,五提交 40fb4a5/dd2c57c/5b3a8fd/c36daa6/收口)。四件交付:①新冒险主种子三分随机化——判定单点 `_seed_value()`(显式合法整数全场景优先含首局/首局存档布尔判定恒 42〔P5 甲〕/非首局空框引擎全局熵 randi() 不走 rng_service 封闭流);种子框默认文本 42 改空+占位提示「留空=随机 · 首局恒 42」。②调试浮窗升级 P1-P6 甲全落——DS_DEBUG=1 启动即开(P3)+入口迁宿主两屏角标(战斗屏 HUD 右下「调试+暂停」标对+地图屏顶栏,P2 甲,开始层角标退役)+遮挡维持 ModalStack 登记制(P1 甲)+当局数据只读区块六项(种子=adventure.master_seed 现值〔诊断转储同病灶 :1174 随批改向 `_current_master_seed()` 单点〕/关卡/节点/HP/GP/钥石,喂入制零写通路零动效)+对局中开窗配套(输入锁/暂停角标判据改 has_open_excluding 排除式防「默认开×遮挡复活×has_open」棋盘锁死——审计🔴防线;对局中批量模拟与两层调试入口禁用+守卫兜底)。③探针面换代——ui_tune_probe 断言面 ①-⑩ 段换代全绿(启动即开/对局中可开+S1+ 停机点回归/当局数据六项值对/只读巡检零非法连接/零动效/种子三分单点断言),五探针种子钉死显式值(审计🔴——含 ui_probe/ui_struct 审计名单外同病灶两件随批修复)。④闸面——批末重闸全绿:冒烟 811/0(四时点零漂移=确定性反证)+四探针+run_all 17 套+doc 三闸/熵增闸存量零新增+MC 零跑(静态论证随出口报告)。过程红灯全披露随出口报告 §四(含 🔴 `_seed_value` 三分分支写反由探针换代抓获修复、钥石域错致探针静默中断的「挂死」归因);存量移交两项(ui_font_asset_test 系 worktree 导入缓存缺字体产物、基线对拍复现非本批因果+一条命令可解;doc/熵增存量面同 iter17)。人闸=DS_DEBUG=1 实机全流程目检(清单随出口报告 §八)。出口报告=`design/qa/iter18-seed-exit-report-2026-10-07.md`。 + +## 十一、快照草稿(供主控誊入 active.md「当前状态快照」节) + +> 迭代 18 卡⑦(种子随机化与调试浮窗升级)已落地待收口:worktree g-plan2-iter18-seed 全部提交(未合并、未 push),批末重闸全绿(冒烟 811/0+ui_input/ui_click/ui_layout/ui_struct/ui_contrast/ui_tune 全绿+run_all 17 套+doc 三闸与熵增闸零新增存量+MC 零跑)。下一步=①用户实机人闸(DS_DEBUG=1 目检清单见 design/qa/iter18-seed-exit-report-2026-10-07.md §八)→②主控合并落回 master+主区全量复跑→③active.md 快照/台账誊记(草稿在出口报告 §十/§十一)。代拍板追认单六项在出口报告 §七(种子框占位词/区块文案/角标落点/断言编号等——目检可推翻)。并行四卡(③④⑤与本文卡)互不阻塞;卡⑦与⑥收官批零耦合。 + +终态 = worktree 内全部提交,**未合并、未 push**(合并落回 master 由用户/编排会话执行)。 diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 6bc64d3c..aa679905 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -22,7 +22,8 @@ # 探针/测试委托面 _row_queue/pace_delay_for/_flush_row_queue 原位保留);秘宝两步交互迁 # treasure_interact_controller.gd(本类只接线与转发意图,无目标使用显式化——哨兵坐标不出控制器); # GP 冒险钱包影子账迁 gp_shadow_ledger.gd(本类留探针同名读写委托面)。模态开合全量走 -# _open_modal/_close_modal 单口,输入锁=modal_stack.has_open() 开态查询+三例外;battle 收尾反馈与 +# _open_modal/_close_modal 单口,输入锁=modal_stack.has_open_excluding(debug_overlay) 排除式 +# 开态查询+三例外(迭代 18 卡⑦——浮窗开态不计入锁);battle 收尾反馈与 # 结算视觉 setter 收敛 hud_presenter 单通道(时序敏感演出触发留本类并注释);settlement 相位读取改经 # presentation_snapshot 成文面、变异表读取直读本根 db(settlement.db 即同一实例)——禁 # settlement.action_economy / settlement.db 链式穿透。 @@ -50,6 +51,7 @@ var adventure = null # 冒险结构编排器(冒险结构实现 var save_manager = null # 存档序列化承载(存档序列化批——启动读盘/冒险收束写盘;只经访问器与 apply 原语,禁读内部结构) var meta = null # 元进度账本服务(元进度实现批——余额/定价/购买/工坊解锁/Boss两档入账/注入计算;零信号纯被动) var _adventure_mode := false # 冒险模式标记(inter 层确认分派与终局分派) +var _debug_entry := false # 调试会话门控(迭代 18 卡⑦——DS_DEBUG=1:启动即开浮窗+两屏角标点亮;_ready 赋值) var logger = null # 试玩日志器(JSONL 持久化 + 卡死诊断转储——呈现层组件) var _level_active := false var _seed_edit: LineEdit @@ -191,6 +193,9 @@ func _ready() -> void: _build_ui() _refresh_resume_button() # 迭代 8 卡②——启动读盘后按断点在档态点亮「继续上局」 _start_overlay.visible = true + _debug_entry = OS.get_environment("DS_DEBUG") == "1" # 迭代 18 卡⑦ P3 甲——DS_DEBUG=1 调试会话门控 + if _debug_entry: + _show_debug_overlay() # 启动即开(手动可关、两屏角标可再开——P3 甲) # ==================== 视图接线(battle.md 四区布局 1280×800;节点构建归 UiAssembler) ==================== @@ -225,6 +230,7 @@ func _build_ui() -> void: board_zone.action_requested.connect(_on_board_action) board_zone.pause_badge.pressed.connect(_on_pause_badge_pressed) # 暂停层入口(迭代 17 卡③——战斗屏 HUD 角标) + board_zone.debug_badge.pressed.connect(_on_debug_badge_pressed) # 调试浮窗入口(迭代 18 卡⑦ P2 甲——战斗屏 HUD 角标) info_panel.treasure_slot_clicked.connect(_treasure_ui.on_slot_clicked) # 迭代 7 T4——两步交互第一步 board_zone.stop_pressed.connect(_on_stop_pressed) board_zone.refresh_confirmed.connect(_on_refresh_confirmed) @@ -258,6 +264,7 @@ func _build_ui() -> void: node_overlay.continue_pressed.connect(_on_node_continue) level_screen.route_chosen.connect(_on_route_chosen) level_screen.pause_badge.pressed.connect(_on_pause_badge_pressed) # 暂停层入口(迭代 17 卡③——地图屏顶栏角标) + level_screen.debug_badge.pressed.connect(_on_debug_badge_pressed) # 调试浮窗入口(迭代 18 卡⑦ P2 甲——地图屏顶栏角标) level_screen.abandon_requested.connect(_on_abandon_requested) # 放弃入口(2026-09-21 用户拍板——台账 119) _end_overlay.continue_pressed.connect(_on_battle_end_continue) settings_overlay = SettingsOverlay.new(settings_store) # 迭代 8 卡④——最小设置面(音量/全屏/减动效) @@ -289,8 +296,8 @@ func _build_ui() -> void: _resume_btn = start.resumebtn start.lvbtn.pressed.connect(_start_adventure_from_menu) start.resumebtn.pressed.connect(_resume_from_menu) # 迭代 8 卡②——续玩入口 - start.dbgbtn.pressed.connect(_show_debug_overlay) # 迭代 14.5——调试角标入口(浮窗,非平级主按钮) - start.dbgbtn.visible = OS.get_environment("DS_DEBUG") == "1" # 迭代 8 卡④——调试入口默认隐藏门控(DS_DEBUG=1 点亮) + # 调试角标入口退役(迭代 18 卡⑦ P2 甲——迁宿主:战斗屏 HUD+关卡地图屏各一枚, + # 接线见两屏装配段;开始层不再有调试入口,DS_DEBUG=1 会话=启动即开浮窗) # 营地/商店投影(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, @@ -324,9 +331,10 @@ func _build_ui() -> void: confirm_dialog.confirmed.connect(_on_confirm_new_adventure) confirm_dialog.cancelled.connect(_on_confirm_cancelled) - # 调试浮窗(迭代 14.5 调试整合批——主菜单三散件收一:随机种子/数值调整/批量模拟/两层调试入口)。 - # 树序在开始层与确认弹窗之后 = 浮于菜单之上的 Dialog(不隐藏开始层——与确认弹窗同款形态, - # 旧 TunePanel「与开始层互斥」形态退役);入口仅开始覆盖层角标(DS_DEBUG=1 门控) + # 调试浮窗(迭代 14.5 调试整合批——主菜单三散件收一:随机种子/数值调整/批量模拟/两层调试入口; + # 迭代 18 卡⑦——升级为全局可开浮窗:DS_DEBUG=1 启动即开〔_ready〕+两屏角标再开〔P2 甲〕+当局数据只读区块〔T3〕)。 + # 树序在开始层与确认弹窗之后 = 浮于菜单之上的 Dialog(不隐藏开始层——与确认弹窗同款形态); + # 开窗唯一入口 = _show_debug_overlay(C6 单口——角标/探针/启动路径全收口) debug_overlay = DebugOverlay.new(db, int(db.hero.atk), int(db.hero.max_hp)) add_child(debug_overlay) _seed_edit = debug_overlay.seed_edit # 种子读取面随浮窗迁入(_seed_value 唯一消费点不变) @@ -370,11 +378,13 @@ func _open_modal(m: Control) -> void: _flush_row_queue() # 卡⑦——任何覆盖层开启前排空排播队列(明细带恒完整,回看/停点面不缺行) _modals.open(m) _sync_pause_entry() # 迭代 17 卡③——暂停层入口角标可见性/置灰随开态同步 + _sync_debug_overlay() # 迭代 18 卡⑦——浮窗当局数据/按钮态随开态同步(T3 刷新=模态开合信号驱动) func _close_modal(m: Control) -> void: _modals.close(m) _sync_pause_entry() # 迭代 17 卡③——关闭恢复角标可点态(无残留——R3 同源口径) + _sync_debug_overlay() # 迭代 18 卡⑦——他层关闭复活浮窗时同步刷新(P1 甲关后自动恢复语义配套) func _on_modal_closed(m: Control) -> void: @@ -384,7 +394,9 @@ func _on_modal_closed(m: Control) -> void: func _input_locked() -> bool: # 模态/非战斗期棋盘输入锁(T3 收口批——开态查询归 modal_stack.has_open(),登记册单一来源, # 不再手工罗列各层;例外三件:开始层/过场层(非模态全屏态,不在登记册)与战斗缺位(棋盘输入无载体)) - return _modals.has_open() or _start_overlay.visible or _inter_overlay.visible or settlement == null + # 迭代 18 卡⑦ T2 防护(审计🔴):判据改排除式——调试浮窗对局中可开(P2 甲),其开态不计入锁 + #(否则 DS_DEBUG 默认开×他层关闭复活浮窗×has_open 组合=棋盘永久锁死——S1+ 停机点防线) + return _modals.has_open_excluding(debug_overlay) or _start_overlay.visible or _inter_overlay.visible or settlement == null # ==================== 冒险模式入口(唯一试玩入口——单关测试直入口退役 2026-09-23 用户拍板)==================== @@ -437,6 +449,7 @@ func _resume_from_menu() -> void: if save_manager == null or not save_manager.has_breakpoint(): return var bp: Dictionary = save_manager.breakpoint_snapshot() + _close_modal(debug_overlay) # 开冒险路径显式关浮窗(迭代 18 卡⑦ T2 防护——续玩同口径) _start_overlay.visible = false _wire_adventure() _level_active = true @@ -473,6 +486,7 @@ func _wire_adventure() -> void: # 冒险模式(三关连探——AdventureRunner 编排;转发信号签名与 runner 一致,接线照原样对接; # 信号连接只在组合根——ADR-0001) func _start_adventure() -> void: + _close_modal(debug_overlay) # 开冒险路径显式关浮窗(迭代 18 卡⑦ T2 防护——DS_DEBUG 默认开态下开局不带窗;对局中经角标再开) _wire_adventure() _level_active = true _sync_pause_entry() # 迭代 17 卡③——进对局点亮暂停入口角标 @@ -605,6 +619,10 @@ func _on_battle_end_continue() -> void: func _start_debug_levels() -> void: + # 守卫(迭代 18 卡⑦ T2——浮窗对局中可开后,调试对局与活跃冒险/结算并存不开放:并发会清写 + # settlement/编排态=状态机破坏;随开随收逻辑改向——对局中开窗只读浮窗,不再视为开局前置) + if _adventure_mode or settlement != null: + return debug_overlay.reset_sliders() _close_modal(debug_overlay) # 浮窗内入口发起——随开随收(对局期不可再开:门禁在 _show_debug_overlay) _start_overlay.visible = false @@ -976,8 +994,10 @@ func _on_pause_closed() -> void: # 探针 pressed.emit 绕过 disabled 时由此兜住;地图屏自身的登记开态不计——has_open_excluding) func _on_pause_badge_pressed() -> void: var map_on: bool = level_screen != null and level_screen.visible - if _modals == null or (map_on and _modals.has_open_excluding(level_screen)) \ - or (not map_on and _modals.has_open()): + # P1 守卫兜底——其他模态开着即拒开(常态防线=角标置灰,探针 pressed.emit 绕过 disabled 时由此兜住; + # 地图屏自身的登记开态不计;迭代 18 卡⑦ T2——判据排除调试浮窗:对局中浮窗开着时暂停角标仍可点) + if _modals == null or (map_on and _modals.has_open_excluding_any([level_screen, debug_overlay])) \ + or (not map_on and _modals.has_open_excluding(debug_overlay)): return _open_pause_layer() @@ -989,10 +1009,18 @@ func _sync_pause_entry() -> void: if pause_overlay == null or _modals == null or board_zone == null or level_screen == null: return var map_on: bool = level_screen.visible + var battle_live: bool = _level_active or settlement != null # 调试两层无 _level_active——settlement 判据补位(迭代 18 卡⑦) board_zone.pause_badge.visible = _level_active and not map_on level_screen.pause_badge.visible = map_on - board_zone.pause_badge.disabled = _modals.has_open() - level_screen.pause_badge.disabled = _modals.has_open_excluding(level_screen) + # 置灰判据改排除式(迭代 18 卡⑦ T2——调试浮窗开态不计入:对局中浮窗开着时角标仍可点) + board_zone.pause_badge.disabled = _modals.has_open_excluding(debug_overlay) + level_screen.pause_badge.disabled = _modals.has_open_excluding_any([level_screen, debug_overlay]) + # 调试浮窗角标(迭代 18 卡⑦ P2 甲——迁宿主两屏,DS_DEBUG 门控;17卡③暂停角标同形制同守卫): + # 战斗屏=对局内(冒险或调试两层)且地图屏未激活;地图屏=随屏;开始菜单零角标(DS_DEBUG=1 启动即开) + board_zone.debug_badge.visible = _debug_entry and not map_on and battle_live + level_screen.debug_badge.visible = _debug_entry and map_on + board_zone.debug_badge.disabled = board_zone.pause_badge.disabled + level_screen.debug_badge.disabled = level_screen.pause_badge.disabled # ---- 探针缝保缝(T4/D4 拆件——click/ui_probe 营地段直呼组合根面;实现归 camp_shop_presenter.gd)---- @@ -1171,7 +1199,8 @@ func _on_legend_toggled() -> void: # ==================== 诊断转储(卡死时点击——全量核心状态 + 环形缓冲)==================== func _on_diagnose_pressed() -> void: - var ctx := {"mode": "level" if _level_active else "debug", "seed": _seed_value()} + # 迭代 18 卡⑦——本局种子现值(原 _seed_value() 重算冒充病灶改向 _current_master_seed 只读单点) + var ctx := {"mode": "level" if _level_active else "debug", "seed": _current_master_seed()} if _level_active and runner != null: ctx["runner"] = {"node_index": runner.node_index, "level_result": String(runner.level_result), "hero_hp": runner.hero_hp, "settlement_is_null": runner.current_battle() == null} @@ -1184,11 +1213,53 @@ func _on_diagnose_pressed() -> void: # ==================== 调试浮窗(迭代 14.5 整合批——原数值调整板节收编;开合/应用/模拟编排组合根承接)==================== func _show_debug_overlay() -> void: - # 门禁(用户裁 2026-10-04:仅开始菜单可开,对局中不可达):入口角标宿主即开始层, - # 此护栏防御未来调用面(含探针直呼);浮窗树序在开始层之后 = 悬浮其上,不隐藏菜单 - if not _start_overlay.visible: + # 门禁改向(迭代 18 卡⑦ P2/P3 甲——对局中可开;本函数仍是唯一开窗入口 C6,门禁逻辑收函数内): + # 旧「仅开始菜单可开」门随入口迁宿主退役——棋盘输入锁/暂停角标判据已排除浮窗(T2 防护), + # 遮挡语义=P1 甲 ModalStack 登记制(他模态开时暂隐、关后自动恢复),此处零场景限制 + if debug_overlay == null: return - _open_modal(debug_overlay) # 模态栈单口 + _open_modal(debug_overlay) + _sync_debug_overlay() + + +# 调试浮窗角标点击(两屏同款处理器——17卡③暂停角标同形制守卫;C6:仍收口 _show_debug_overlay 单口) +func _on_debug_badge_pressed() -> void: + var map_on: bool = level_screen != null and level_screen.visible + if _modals == null or (map_on and _modals.has_open_excluding_any([level_screen, debug_overlay])) \ + or (not map_on and _modals.has_open_excluding(debug_overlay)): + return + _show_debug_overlay() + + +# 浮窗上下文同步(迭代 18 卡⑦——当局数据刷新+对局中按钮禁用单点):调用面=开窗口与模态开合 +# 单口(凡影响开态/对局态的路径必经其一——与 _sync_pause_entry 同款纪律) +func _sync_debug_overlay() -> void: + if debug_overlay == null: + return + debug_overlay.set_context_locked(settlement != null or _adventure_mode) # 对局中批量模拟/两层调试入口禁用(长程异步模拟与活跃结算并存态不开放——T2) + debug_overlay.update_run_data(_debug_run_data()) # T3 当局数据只读区块喂入(开窗+模态开合信号驱动) + + +# 当局数据六项只读投影(P4 甲——C4 零直读内部状态、零写通路):种子=adventure.master_seed 现值 +# (非 _seed_value() 重算);调试两层/非对局无冒险对象=RNG 服务 master_seed 现值(E5 只读出口, +# 未播种 -1 显「—」);关卡/节点/HP/GP=runner 成文出口、钥石=adventure 冒险账本(LevelRunner 无此域); +# 缺位=「—」占位。零动效(纯标签重查——17卡③契约)。 +func _debug_run_data() -> Dictionary: + var r = adventure.runner() if adventure != null else null + var seed_now: int = _current_master_seed() + var seed_txt: String = str(seed_now) if seed_now >= 0 else "—" + if r == null: + return {"seed": seed_txt, "level": "—", "node": "—", "hp": "—", "gp": "—", "keystone": "—"} + return {"seed": seed_txt, "level": int(adventure.level_index), "node": int(r.node_index), + "hp": "%d/%d" % [int(r.hero_hp), int(r.effective_max_hp())], "gp": int(r.gp), "keystone": int(adventure.keystone)} + + +# 本局种子现值(只读单点——当局数据区块与诊断转储共用):冒险=adventure.master_seed 访问器现值; +# 调试两层=RNG 服务 master_seed。禁 _seed_value() 重算冒充本局种子(main.gd 旧 :1174 病灶随批改向)。 +func _current_master_seed() -> int: + if adventure != null: + return int(adventure.master_seed) + return int(get_node("/root/RngService").master_seed) func _close_debug_overlay() -> void: @@ -1201,13 +1272,25 @@ func _on_tune_apply(atk_pct: int, hp_pct: int) -> void: func _on_tune_simulate(n: int) -> void: - # 批量模拟编排 = 控制器承接(tune_sim_controller.gd);E4 后无表态需复位——模拟走 runner 覆盖参数 + # 批量模拟编排 = 控制器承接(tune_sim_controller.gd);E4 后无表态需复位——模拟走 runner 覆盖参数。 + # 对局中禁用兜底(迭代 18 卡⑦ T2——长程异步模拟与活跃结算并存态不开放;探针 emit 绕过 disabled 时由此守住) + if settlement != null: + return _tune_sim.run(n) # ==================== 工具 ==================== func _seed_value() -> int: + # 种子三分判定单点(迭代 18 卡⑦ C1——全工程唯一种子框读取处,禁第二处读框): + # ①显式合法整数全场景优先(含首局——测试/调试接口显式指定;第五路 DebugSession Callable + # 经本函数=调试对局与玩家同口径,P6 甲); + # ②首局固定 42(P5 甲——存档现役布尔 is_first_adventure_done:新档显式 false=教学局恒 42〔教学 + # 重放模板 0 即在此种子下标定〕;置位 true=教学局已收束的非首局走 ③ 随机); + # ③非首局空/非法框=引擎全局熵 randi()(Godot 4 启动自播种子,headless 同;不走 rng_service + # 封闭流、禁 randomize()/自建 RNG——组合根建种子属 ADR-0007 豁免面,不进玩法 RNG 消费) if _seed_edit.text.is_valid_int(): return int(_seed_edit.text) + if save_manager != null and save_manager.is_first_adventure_done(): + return randi() return 42 diff --git a/game/presentation/modal_stack.gd b/game/presentation/modal_stack.gd index 80f1ac55..45b8ce68 100644 --- a/game/presentation/modal_stack.gd +++ b/game/presentation/modal_stack.gd @@ -47,8 +47,16 @@ func has_open() -> bool: #(关卡地图屏系登记册模态成员=常态屏态,其上的地图屏角标必须可点;其余任一层开着=角标置灰)。 # 只读查询零副作用——登记序/开合记账语义零改动。 func has_open_excluding(skip: Control) -> bool: + return has_open_excluding_any([skip]) + + +# 多排除式开态查询(迭代 18 卡⑦ T2 防护——调试浮窗对局中可开后,棋盘输入锁与暂停角标置灰 +# 判据须同时排除调试浮窗自身〔否则 DS_DEBUG 默认开×遮挡复活×has_open 组合=棋盘永久锁死—— +# 审计🔴 S1+ 停机点防线〕;地图屏场景=地图屏+调试浮窗双排除)。语义同 has_open_excluding, +# skip 集参数化;只读查询零副作用,登记序/开合记账语义零改动。 +func has_open_excluding_any(skips: Array) -> bool: for m in _list: - if m == skip: + if skips.has(m): continue if is_instance_valid(m) and m.visible: return true diff --git a/game/presentation/ui_assembler.gd b/game/presentation/ui_assembler.gd index 8c0dbfac..b2ec6267 100644 --- a/game/presentation/ui_assembler.gd +++ b/game/presentation/ui_assembler.gd @@ -92,9 +92,10 @@ static func build_overlays(host: Control, db) -> Dictionary: } -# 开始覆盖层(返回 {overlay, box, lvbtn, resumebtn, campbtn, settingsbtn, quitbtn, dbgbtn}——按钮接线归组合根; +# 开始覆盖层(返回 {overlay, box, lvbtn, resumebtn, campbtn, settingsbtn, quitbtn}——按钮接线归组合根; # 迭代 14.5 调试整合批:随机种子/数值调整板/两层调试三散件撤出主按钮组,收进调试浮窗—— -# 本层只留右下角「调试」角标入口(DS_DEBUG=1 门控归组合根),dbgbtn 即该角标。 +# 迭代 18 卡⑦ P2 甲:右下角「调试」角标亦退役(入口迁宿主=战斗屏 HUD+关卡地图屏两枚角标, +# DS_DEBUG=1 会话浮窗启动即开——本层零调试入口) # UI 落码批 M2(2026-10-04):单列 VBox→双列(左操作 56% + 右舞台);按钮动词化+悬停解释 #(解释全迁 tooltip,G5);lvbtn 升主操作钮(烛光黄 CANDLE);右列=立绘看台。**玩法速览牌已退役 #(2026-10-06 用户令:界面禁玩法文字墙——教学内容归 Beta 帮助手册〔教学引导定稿批 P4 丙〕,历史文案在 git)**; @@ -156,12 +157,9 @@ static func build_start_overlay(host: Control, db = null) -> Dictionary: var quitbtn := UiKit.button("退出游戏") box.add_child(quitbtn) - # 迭代 14.5 调试整合批——调试入口收右下角标(与主按钮组非平级;DS_DEBUG=1 点亮,可见性归组合根)。 - # margin 16px 角距;字号降档与主按钮拉开层级 - var dbgbtn := UiKit.button("调试", 0.0) # 角标豁免宽度基准(0=不设 min/flags——角落小件) - dbgbtn.add_theme_font_size_override("font_size", UiKit.FS_HINT) - dbgbtn.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_RIGHT, Control.PRESET_MODE_MINSIZE, 16) - overlay.add_child(dbgbtn) + # 迭代 18 卡⑦ P2 甲——旧右下角调试角标退役(原迭代 14.5 装配段删除):入口迁宿主两屏 + #(board_zone_view.debug_badge / level_map_screen.debug_badge),DS_DEBUG 门控与可见性 + # 同步归组合根 _sync_pause_entry;开始层零调试入口(DS_DEBUG=1 会话=浮窗启动即开) # 右列:立绘看台 + 玩法速览牌(M2——帮助全文保留表值驱动,仅容器化;内容含地形变异段不删) var right := VBoxContainer.new() @@ -198,7 +196,7 @@ static func build_start_overlay(host: Control, db = null) -> Dictionary: return {"overlay": overlay, "box": box, "lvbtn": lvbtn, "resumebtn": resumebtn, - "campbtn": campbtn, "settingsbtn": settingsbtn, "quitbtn": quitbtn, "dbgbtn": dbgbtn} + "campbtn": campbtn, "settingsbtn": settingsbtn, "quitbtn": quitbtn} # 调试两层模式的层间过渡覆盖层(历史入口专用)。返回 {overlay, label, next_btn}——接线归组合根 diff --git a/game/presentation/views/board_zone_view.gd b/game/presentation/views/board_zone_view.gd index 301bf3bb..1fef6e35 100644 --- a/game/presentation/views/board_zone_view.gd +++ b/game/presentation/views/board_zone_view.gd @@ -12,6 +12,7 @@ var board_view: BoardView var toast: ToastStack var fx: BoardFxView var pause_badge: Button # 暂停层入口角标(迭代 17 卡③ P1 丁——debug 角标同形制;连接归组合根) +var debug_badge: Button # 调试浮窗入口角标(迭代 18 卡⑦ P2 甲——DS_DEBUG 门控归组合根;暂停角标同形制) var _board_area: Control # resized 命名转发用(原 lambda 局部捕获——P1-3 转命名 2026-09-29) signal action_requested(cell: Vector2i, kind: String) @@ -42,15 +43,29 @@ func _init() -> void: board_area.add_child(fx) toast = ToastStack.new() board_area.add_child(toast) - # 暂停层入口角标(迭代 17 卡③ P1 丁——战斗屏 HUD 右下角常驻;debug 角标同形制:紧凑钮豁免 - # 宽度基准+字号降档+角距 16px。可见性/置灰归组合根 _sync_pause_entry(其他模态开着=置灰 - # 不可点——has_open 守卫);信号连接归组合根(ADR-0001——装配器/视图零连接) + # 角标对(迭代 18 卡⑦ P2 甲——调试+暂停两枚右下角常驻,HBox 并排锚角距 16px): + # 17卡③暂停角标同形制:紧凑钮豁免宽度基准+字号降档。可见性/置灰归组合根 _sync_pause_entry + #(DS_DEBUG 门控/其他模态开着=置灰不可点——排除式开态查询);信号连接归组合根(ADR-0001)。 + # preset 在子件全部入列后调用——MINSIZE 锚按当时最小尺寸计偏移(空容器调用=零尺寸锚外溢屏边) + var corner := HBoxContainer.new() + corner.add_theme_constant_override("separation", 8) + debug_badge = UiKit.button("调试", 0.0) # 角落小件豁免宽度基准(同暂停角标) + debug_badge.add_theme_font_size_override("font_size", UiKit.FS_HINT) + debug_badge.tooltip_text = "打开调试浮窗(当局数据/种子/数值调整)" + debug_badge.visible = false # 显式初始化——非调试会话不误现(可见性归组合根同步) + corner.add_child(debug_badge) pause_badge = UiKit.button("暂停", 0.0) # 角落小件豁免宽度基准(同调试角标) pause_badge.add_theme_font_size_override("font_size", UiKit.FS_HINT) pause_badge.tooltip_text = "查看钥石与永久强化进度(只读)" pause_badge.visible = false # 显式初始化——非对局期不误现(可见性归组合根同步) - pause_badge.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_RIGHT, Control.PRESET_MODE_MINSIZE, 16) - board_area.add_child(pause_badge) + corner.add_child(pause_badge) + # 右下角锚定(迭代 18 卡⑦):MINSIZE 预设须在入树后调用——容器出树的最小尺寸缓存陈旧为 0 + #(裸 Button 无此病,容器延迟到首轮排序才重算),零尺寸锚角会使钮对悬出屏边; + # grow 向左上=后续最小尺寸变化向屏内生长。预设延迟帧尾——届时容器最小尺寸已就绪 + corner.grow_horizontal = Control.GROW_DIRECTION_BEGIN + corner.grow_vertical = Control.GROW_DIRECTION_BEGIN + corner.set_anchors_and_offsets_preset.call_deferred(Control.PRESET_BOTTOM_RIGHT, Control.PRESET_MODE_MINSIZE, 16) + board_area.add_child(corner) # M8(UI 落码批):棋盘底常驻操作提示条(demo ⑦——文案经 board_view 输入代码证实; # 旧 chord 一次性 toast 提示退役归此常驻条;审计第 7 条用户批) var hint := PanelContainer.new() diff --git a/game/presentation/views/debug_overlay.gd b/game/presentation/views/debug_overlay.gd index a98143ec..ef0fff8c 100644 --- a/game/presentation/views/debug_overlay.gd +++ b/game/presentation/views/debug_overlay.gd @@ -1,12 +1,14 @@ # VERTICAL SLICE - NOT FOR PRODUCTION # Date: 2026-10-04(迭代 14.5 调试整合批——主菜单三散件(随机种子/角色数值调整板/两层调试入口)收一张调试浮窗, # 原 views/tune_panel.gd 整体吸收退役) -# 开发调试浮窗:入口 = 开始覆盖层右下角「调试」角标(DS_DEBUG=1 门控归组合根)——非主按钮组平级; -# 仅开始菜单可开(浮于菜单之上的 Dialog 形态,不隐藏开始层——与确认弹窗同款;对局中不可达)。 +# 开发调试浮窗:全局可开(迭代 18 卡⑦——DS_DEBUG=1 会话启动即开〔组合根 _ready〕+战斗屏 HUD/关卡地图屏 +# 两枚角标再开〔P2 甲,17卡③暂停角标同形制〕;遮挡语义=ModalStack 登记制:他模态开时暂隐、关后自动恢复; +# 旧「仅开始菜单可开」门随入口迁宿主退役)。当局数据只读区块=本局种子唯一查看位(T3,P4 甲六项)。 # 列表页形态可扩展:后续调试项按区块往下追加,超高滚动。 # 纯视图边界(规范 1.3):数值应用/批量模拟/对局启动全部经信号外发由组合根承接 #(apply_requested / simulate_requested / debug_levels_requested / closed); -# 模拟进度与报告经 setter 注入——零核心写、不拼报告文案(装配归 HudPresenter)。 +# 当局数据经 update_run_data setter 喂入(只读投影零回读);模拟进度与报告经 setter 注入—— +# 零核心写、不拼报告文案(装配归 HudPresenter)。零动效(零 tween/AnimationPlayer——17卡③契约)。 # 探针接缝(ADR-0004"合成输入走真实翻译出口"口径):press_apply_for_probe 等三键 = 按钮真实信号路径。 class_name DebugOverlay extends Control @@ -28,7 +30,12 @@ var _hp_val: Label var _eff: Label var _sim_count: SpinBox var _sim_btn: Button +var _dbg_btn: Button # 两层调试入口钮(迭代 18 卡⑦——对局中禁用承载面) var _sim_out: RichTextLabel +var _sim_running := false # 批量模拟进行中(is_running 判据——与对局中禁用态分离防互扰) +var _ctx_locked := false # 对局中上下文锁定(组合根 _sync_debug_overlay 喂入——批量模拟/两层调试入口禁用) +var _run_labels := {} # 当局数据六项标签(P4 甲——只读投影,组合根 update_run_data 喂入) +var _run_data := {} # 最近一次投喂快照(探针值对面 run_data_probe) func _init(db_ref, base_atk: int, base_hp: int) -> void: @@ -70,12 +77,37 @@ func set_sim_count(n: int) -> void: func is_running() -> bool: - return _sim_btn.disabled + return _sim_running # 批量模拟进行中(迭代 18 卡⑦——与对局中禁用态分离,置灰 ≠ 在跑) func set_sim_running(running: bool) -> void: - _sim_btn.disabled = running - _sim_btn.text = "模拟中…" if running else "运行模拟" + _sim_running = running + _sync_sim_button() + + +# 对局中上下文锁定(迭代 18 卡⑦ T2——组合根 _sync_debug_overlay 喂入):批量模拟与两层调试入口 +# 在活跃对局并存态不开放(长程异步模拟/并发对局编排与活跃结算并存态破坏状态机) +func set_context_locked(locked: bool) -> void: + _ctx_locked = locked + _dbg_btn.disabled = _ctx_locked + _sync_sim_button() + + +# 当局数据只读投影喂入(T3 P4 甲六项——组合根 _debug_run_data 构造;纯标签重查零动效) +func update_run_data(data: Dictionary) -> void: + _run_data = data.duplicate() + for key in _run_labels: + (_run_labels[key] as Label).text = str(data.get(key, "—")) + + +# 当局数据快照读面(探针值对面——ui_tune_probe 断言六项与只读访问器逐项一致) +func run_data_probe() -> Dictionary: + return _run_data.duplicate() + + +func _sync_sim_button() -> void: + _sim_btn.disabled = _sim_running or _ctx_locked + _sim_btn.text = "模拟中…" if _sim_running else "运行模拟" func set_sim_progress(text: String) -> void: @@ -131,7 +163,7 @@ func _build() -> void: var title := UiKit.label("调试",UiKit.FS_TITLE, true, UiKit.WARM) box.add_child(title) var desc := UiKit.label( - "开发调试专用——仅开始菜单可开(DS_DEBUG=1 时入口点亮)。\n百分比叠加基础值:攻击 %d / 生命 %d。影响所有模式(手动与模拟),不落盘。" % [_base_atk, _base_hp], + "开发调试专用(DS_DEBUG=1 会话:启动即开;战斗屏/地图屏「调试」角标可再开)。当局数据=本局只读投影,种子即本局种子唯一查看位。\n百分比叠加基础值:攻击 %d / 生命 %d。影响所有模式(手动与模拟),不落盘。" % [_base_atk, _base_hp], 13, false, UiKit.WARM_SOFT) box.add_child(desc) box.add_child(HSeparator.new()) @@ -146,6 +178,25 @@ func _build() -> void: list.add_theme_constant_override("separation", 8) scroll.add_child(list) + # 区块·当局数据(迭代 18 卡⑦ T3 P4 甲——六项只读投影:种子=本局种子唯一查看位〔adventure + # .master_seed 现值,非 _seed_value() 重算〕/关卡/节点/HP/GP/钥石;数据由组合根开窗与模态开合 + # 时 update_run_data 喂入;纯标签零信号连接零动效——17卡③只读契约) + var run_head := UiKit.label("当局数据(只读)",UiKit.FS_HINT, true, UiKit.WARM) + list.add_child(run_head) + var run_grid := GridContainer.new() + run_grid.columns = 2 + run_grid.add_theme_constant_override("h_separation", 14) + run_grid.add_theme_constant_override("v_separation", 2) + list.add_child(run_grid) + for item in [["seed", "种子"], ["level", "关卡"], ["node", "节点"], ["hp", "HP"], ["gp", "GP"], ["keystone", "钥石"]]: + var cap := UiKit.label(String(item[1]),UiKit.FS_HINT, false, UiKit.WARM_FAINT) + cap.custom_minimum_size = Vector2(52, 0) + run_grid.add_child(cap) + var val := UiKit.label("—",UiKit.FS_HINT, false, UiKit.WARM) + _run_labels[item[0]] = val + run_grid.add_child(val) + list.add_child(HSeparator.new()) + # 区块⓪胜利演出风格(P-C5 两套并存——纹章拼合[默认]/尘金粒子;会话级不落盘) var fx_row := HBoxContainer.new() fx_row.add_theme_constant_override("separation", 10) @@ -153,17 +204,17 @@ func _build() -> void: fx_row.add_child(UiKit.label("胜利演出",UiKit.FS_HINT, false, UiKit.WARM)) var fx_btn := UiKit.button("纹章拼合" if UiKit.victory_fx_style == "crest" else "尘金粒子") fx_btn.tooltip_text = "两套演出并存——点击切换(会话级,不落盘)" - fx_btn.pressed.connect(func() -> void: - UiKit.victory_fx_style = "dust" if UiKit.victory_fx_style == "crest" else "crest" - fx_btn.text = "纹章拼合" if UiKit.victory_fx_style == "crest" else "尘金粒子") + fx_btn.pressed.connect(_on_fx_toggle.bind(fx_btn)) # 迭代 18 卡⑦——lambda 转命名方法(只读巡检白名单方法名精确可判) fx_row.add_child(fx_btn) - # 区块①随机种子(原开始覆盖层种子行迁入——非法定值回退 42 的口径归组合根 _seed_value) + # 区块①随机种子(原开始覆盖层种子行迁入——种子三分判定归组合根 _seed_value 单点): + # 默认空框(迭代 18 卡⑦ C1——空=随全局规则:首局恒 42/非首局随机;显式填入全场景优先) var seed_row := HBoxContainer.new() list.add_child(seed_row) seed_row.add_child(UiKit.label("随机种子",UiKit.FS_BODY, false)) seed_edit = LineEdit.new() - seed_edit.text = "42" + seed_edit.text = "" + seed_edit.placeholder_text = "留空=随机 · 首局恒 42" seed_edit.custom_minimum_size = Vector2(120, 0) seed_row.add_child(seed_edit) list.add_child(HSeparator.new()) @@ -209,10 +260,10 @@ func _build() -> void: list.add_child(_sim_out) list.add_child(HSeparator.new()) - # 区块④两层调试对局入口(原开始覆盖层调试按钮迁入——HP 关内延续) - var dbg_btn := UiKit.button("调试 · 连续关卡两层(HP 关内延续)") - dbg_btn.pressed.connect(_emit_debug_levels) - list.add_child(dbg_btn) + # 区块④两层调试对局入口(原开始覆盖层调试按钮迁入——HP 关内延续;迭代 18 卡⑦——对局中禁用) + _dbg_btn = UiKit.button("调试 · 连续关卡两层(HP 关内延续)") + _dbg_btn.pressed.connect(_emit_debug_levels) + list.add_child(_dbg_btn) var close_btn := UiKit.button("关闭") close_btn.pressed.connect(_emit_closed) @@ -257,3 +308,9 @@ func _emit_closed() -> void: func _on_slider_value_changed(_value: float) -> void: _sync_labels() + + +# 胜利演出切换(原 _build 内 lambda——迭代 18 卡⑦ 转命名方法,只读巡检白名单承载) +func _on_fx_toggle(btn: Button) -> void: + UiKit.victory_fx_style = "dust" if UiKit.victory_fx_style == "crest" else "crest" + btn.text = "纹章拼合" if UiKit.victory_fx_style == "crest" else "尘金粒子" diff --git a/game/presentation/views/level_map_screen.gd b/game/presentation/views/level_map_screen.gd index a627361b..2ad0cb6f 100644 --- a/game/presentation/views/level_map_screen.gd +++ b/game/presentation/views/level_map_screen.gd @@ -62,6 +62,7 @@ var _content_size := Vector2(620, 240) # 图内容包络(open_choices 按图 var _hover_id := -1 # 悬停预承诺候选(-1 = 无) var abandon_btn: Button # 放弃入口(可见性归组合根 set_abandon_available——只在冒险模式开) var pause_badge: Button # 暂停层入口角标(迭代 17 卡③ P1 丁——顶栏 GP 徽右侧;连接归组合根) +var debug_badge: Button # 调试浮窗入口角标(迭代 18 卡⑦ P2 甲——DS_DEBUG 门控;暂停角标同形制) var _abandon_timer := ArmedTimer.new() # 放弃确认窗(T3/D3 P4 乙——_process 帧驱动倒计时机制收口;窗口=CONFIRM_WINDOW_S) var _node_icons := {} # 节点图标贴图(load_tex 守卫——缺位 null → 字形回退,冒烟/无素材检出安全) var _noise_rect: TextureRect # 屏底石纹(换层色板渐变承载面——null = 纯色回退无挂点) @@ -121,6 +122,14 @@ func _init() -> void: pause_badge.tooltip_text = "查看钥石与永久强化进度(只读)" pause_badge.size_flags_vertical = Control.SIZE_SHRINK_CENTER top.add_child(pause_badge) + # 调试浮窗入口角标(迭代 18 卡⑦ P2 甲——顶栏暂停角标右侧;17卡③同形制:紧凑钮豁免宽度基准+ + # 字号降档。可见性/置灰归组合根 _sync_pause_entry(DS_DEBUG 门控/其他模态开着=置灰——排除式 + # 开态查询,本屏自身开态不计);信号连接归组合根(ADR-0001——视图零连接)) + debug_badge = UiKit.button("调试", 0.0) + debug_badge.add_theme_font_size_override("font_size", UiKit.FS_HINT) + debug_badge.tooltip_text = "打开调试浮窗(当局数据/种子/数值调整)" + debug_badge.size_flags_vertical = Control.SIZE_SHRINK_CENTER + top.add_child(debug_badge) _title = UiKit.label(TITLE_FORK,UiKit.FS_SUB, true, UiKit.WARM) _title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER box.add_child(_title) diff --git a/game/smoke/ui_click_probe.gd b/game/smoke/ui_click_probe.gd index 99b315c9..8e6e03a8 100644 --- a/game/smoke/ui_click_probe.gd +++ b/game/smoke/ui_click_probe.gd @@ -131,10 +131,12 @@ func _ready() -> void: _problems.append("音量滑杆未实时生效(master bus db %f ≠ %f)" % [AudioServer.get_bus_volume_db(master_idx), expect_db]) if absf(iso_s.volume_linear - 0.3) > 0.001: _problems.append("音量未入 store(%f ≠ 0.3)" % iso_s.volume_linear) - # 调试入口默认隐藏门控(卡④ AC④——探针环境 DS_DEBUG 未设) - var dbg_btn: Button = ProbeUiUtil.find_button(main._start_overlay, "调试", "prefix") - if dbg_btn != null and dbg_btn.visible: - _problems.append("调试入口未隐藏(DS_DEBUG 门控失守——卡④ AC④)") + # 调试入口断言改向(迭代 18 卡⑦ P2 甲——开始层角标退役迁宿主两屏;探针环境 DS_DEBUG 未设 + # =浮窗不默认开〔P3 门控〕、DS_DEBUG 门控由开始层角标移两屏角标) + if ProbeUiUtil.find_button(main._start_overlay, "调试", "prefix") != null: + _problems.append("开始层调试角标未退役(迭代 18 卡⑦ P2 甲迁宿主失守)") + if main.debug_overlay.visible: + _problems.append("DS_DEBUG 未设时浮窗默认开(P3 启动门控失守)") var settings_close: Button = ProbeUiUtil.find_button(main.settings_overlay, "返回", "prefix") if settings_close == null: _problems.append("设置面返回按钮缺失") @@ -160,6 +162,7 @@ func _ready() -> void: elif resume_btn0.visible: _problems.append("无断点态继续上局按钮未隐藏(卡② AC①——新档合法空态)") main.debug_overlay.set_sliders(400, 200) # 高加成保 L1 可通关——L2 岔路选路段只能经冒险推进到达(单关直入口退役) + main.debug_overlay.seed_edit.text = "42" # 种子钉死(迭代 18 卡⑦审计🔴——种子框默认改空后空框=非首局随机,行为走查确定性禁依赖默认分支) var t1 := Time.get_ticks_msec() main._start_adventure_from_menu() await get_tree().process_frame diff --git a/game/smoke/ui_input_probe.gd b/game/smoke/ui_input_probe.gd index f4cba8e5..a3291bd5 100644 --- a/game/smoke/ui_input_probe.gd +++ b/game/smoke/ui_input_probe.gd @@ -31,6 +31,7 @@ func _ready() -> void: # 同 ui_click_probe 隔离纪律。首次冒险布尔位置位=对齐真实档游玩史口径,模板抽签路径不变; # isolate_save 先删后建——上次运行的残留断点会触发互斥确认早退) ProbeUiUtil.isolate_save(main, "smoke_input_probe_save", true, false, true) + main.debug_overlay.seed_edit.text = "42" # 种子钉死(迭代 18 卡⑦审计🔴——种子框默认改空后空框=非首局随机,探针确定性禁依赖默认分支) main._start_adventure_from_menu() await get_tree().process_frame # ---- ⑫ 图上命中真实输入(关卡画面实装批 2026-09-28 回归闸):入口停点候选节点中心 diff --git a/game/smoke/ui_layout_probe.gd b/game/smoke/ui_layout_probe.gd index cbdc2538..582f4d6d 100644 --- a/game/smoke/ui_layout_probe.gd +++ b/game/smoke/ui_layout_probe.gd @@ -15,6 +15,7 @@ func _ready() -> void: # 存档隔离(迭代 8 卡①②——停点断点写盘随 main.save_manager;探针禁碰真实档,同族探针隔离纪律; # isolate_save 先删后建——上次运行的残留断点会触发互斥确认早退) ProbeUiUtil.isolate_save(main, "smoke_layout_probe_save", true, false, true) + main.debug_overlay.seed_edit.text = "42" # 种子钉死(迭代 18 卡⑦审计🔴——种子框默认改空后空框=非首局随机,探针确定性禁依赖默认分支) main._start_adventure_from_menu() for i in range(3): await get_tree().process_frame diff --git a/game/smoke/ui_probe.gd b/game/smoke/ui_probe.gd index eed02c8a..43f43e61 100644 --- a/game/smoke/ui_probe.gd +++ b/game/smoke/ui_probe.gd @@ -54,6 +54,7 @@ func _ready() -> void: problems.append("营地关闭后未恢复开始层(开合往返失守)") # 换回全新零档(营地段购买态不泄入后续冒险段——确定性;先删后建防陈旧档泄入) ProbeUiUtil.isolate_save(main, "smoke_ui_probe_camp", true, false, true) + main.debug_overlay.seed_edit.text = "42" # 种子钉死(迭代 18 卡⑦——种子框默认改空后空框=非首局随机;本探针 start_single 第五路同吃此框,确定性禁依赖默认分支) main._start_adventure_from_menu() await get_tree().process_frame await get_tree().process_frame diff --git a/game/smoke/ui_struct_probe.gd b/game/smoke/ui_struct_probe.gd index 370068a3..26ce81f9 100644 --- a/game/smoke/ui_struct_probe.gd +++ b/game/smoke/ui_struct_probe.gd @@ -62,6 +62,7 @@ func _ready() -> void: # ---- 冒险簇(真流推进:开冒险→地图选路态→选战斗节点→战斗 HUD)---- ProbeUiUtil.isolate_save(main, "smoke_ui_struct_probe_adv", true, false, true) + main.debug_overlay.seed_edit.text = "42" # 种子钉死(迭代 18 卡⑦——种子框默认改空后空框=非首局随机,结构采样确定性禁依赖默认分支) main._start_adventure_from_menu() _settle(4) await _audit("level_map", [main.level_screen]) @@ -344,6 +345,11 @@ func _audit(screen: String, roots: Array) -> void: var b: Control = all_buttons[j] if _wl(screen, "overlap", a) or _wl(screen, "overlap", b): continue + # 滚动区跨界豁免(迭代 18 卡⑦——当局数据区块加高列表后实证:恰好一枚在 ScrollContainer + # 内时,其未剪裁内容矩形与滚区外控件的全局矩形交叠是滚动态样貌非布局缺陷; + # 同滚区两钮仍查——同一容器 stacking 真重叠照红) + if _in_scroll(a) != _in_scroll(b): + continue if a.get_global_rect().intersects(b.get_global_rect()): problems.append("[%s][overlap] %s 与 %s 矩形相交(%s ∩ %s)" % [ screen, a.name, b.name, a.get_global_rect(), b.get_global_rect()]) diff --git a/game/smoke/ui_tune_probe.gd b/game/smoke/ui_tune_probe.gd index a86abfd2..0bfc9cfa 100644 --- a/game/smoke/ui_tune_probe.gd +++ b/game/smoke/ui_tune_probe.gd @@ -1,9 +1,14 @@ # VERTICAL SLICE - NOT FOR PRODUCTION # Date: 2026-09-11(2026-09-12 code-review 修复批重写——加成应用契约改为组合根单一应用路径) -# 调试浮窗场景探针(2026-10-04 迭代 14.5 调试整合批随批改接——原数值调整板整体收进 debug_overlay, -# 浮窗形态悬浮于开始菜单之上不隐藏菜单;playtest 缺陷⑤回归载体沿袭): -# 驱动真实 UI 路径——打开可见性(浮于菜单之上+树序在后)/ 滑条联动 / 应用(apply_requested → 组合根)/ -# 重置 / 批量模拟全链路(N=10)/ E4 回归断言(hero 表运行期不可变 + 单次加成经 encounter 生效)。 +# 调试浮窗场景探针(2026-10-04 迭代 14.5 调试整合批随批改接——原数值调整板整体收进 debug_overlay; +# 2026-10-07 迭代 18 卡⑦ T4 断言面换代——P2 甲角标迁宿主 / P3 甲启动即开 / T3 当局数据只读区块 / +# T2 输入锁防护落码配套): +# 驱动真实 UI 路径——①启动即开(DS_DEBUG=1 默认打开,探针自设环境变量)+悬浮菜单之上+登记册末位树序 +# ②滑条联动 ③应用 ④重置 ⑤批量模拟 N=10 ⑥关闭 ⑦hero 表运行期不可变 +# ⑧单次加成生效(经 _start_adventure_from_menu 真实路径+显式种子钉死)+开冒险路径显式关浮窗 +# ⑨对局中可开(战斗屏角标真实信号路径)+棋盘输入不锁+暂停角标不置灰(T2 防护=S1+ 停机点回归闸) +# +对局中两钮禁用+当局数据六项值对(与只读访问器逐项一致)+只读巡检(白名单外零连接)+零动效契约 +# ⑩种子三分单点断言(显式输入优先/首局恒 42/非首局空框连抽 4 次不全等 42——C1/P5 甲/P6 甲载体) # 运行:Godot_console.exe --headless --path . res://smoke/ui_tune_probe.tscn extends Node @@ -11,8 +16,21 @@ const ProbeUiUtil := preload("res://smoke/probe_ui_util.gd") # T6 收口:隔 const SIM_GUARD_FRAMES := 1800 # ⑤ N=10 headless 模拟防挂死帧预算上限(ADR-0004:探针内停滞检测第一道——坏态不等满预算才报,超时即断言失败) +# 只读巡检信号名清单(17卡③「白名单外零连接」形制沿用——玩家交互可携带写意图的通道全集 +# +本层外发 closed;引擎内部布局生命线信号不计) +const _OVERLAY_READONLY_SIGNALS: Array[String] = [ + "pressed", "button_down", "button_up", "toggled", + "text_changed", "text_submitted", "value_changed", "item_selected", + "gui_input", "focus_entered", "focus_exited", "closed", +] +# 视图内白名单方法(fx 切换/应用/重置/模拟/两层调试/关闭/滑条联动——全部视图自身信号外发前置, +# 零跨对象写;清单外任何新连接一律红须入册) +const _OVERLAY_VIEW_METHODS: Array[String] = ["_on_fx_toggle", "_apply_btn_pressed", "_reset_btn_pressed", + "_emit_simulate_requested", "_emit_debug_levels", "_emit_closed", "_on_slider_value_changed"] + func _ready() -> void: + OS.set_environment("DS_DEBUG", "1") # P3 甲断言前提——探针自设调试会话环境(headless 进程内生效) await get_tree().process_frame var main: Node = load("res://presentation/main.tscn").instantiate() add_child(main) @@ -23,15 +41,15 @@ func _ready() -> void: var base_hp := int(main.db.hero.max_hp) var boosted_atk := base_atk * 2 # 攻击滑条 100 → ×2 var boosted_hp := int(round(base_hp * 1.5)) # 生命滑条 50 → +50% - # ① 打开可见性(14.5 浮窗形态:悬浮于菜单之上——开始层不隐藏,树序必须在开始层之后 = 绘制在上; - # 旧「打开必须隐藏开始层」断言随互斥形态退役) - main._show_debug_overlay() + # ① 启动即开(18卡⑦ P3 甲——DS_DEBUG=1 会话组合根 _ready 即开;悬浮于菜单之上——开始层不隐藏; + # 树序新基准=登记册末位(确认弹窗之后=绘制序最上);旧「_show_debug_overlay 打开+开始层在屏」 + # 断言随入口迁宿主换代——打开动作本身移至 ⑨ 角标真实路径承载) if not main.debug_overlay.visible: - problems.append("调试浮窗未打开") + problems.append("DS_DEBUG=1 启动浮窗未即开(P3 甲失守)") if not main._start_overlay.visible: problems.append("开始覆盖层被隐藏——浮窗应悬浮于菜单之上(14.5 形态失守)") - if main.debug_overlay.get_index() < main._start_overlay.get_index(): - problems.append("调试浮窗树序在开始层之前——遮挡关系失守") + if main.debug_overlay.get_index() < main.confirm_dialog.get_index(): + problems.append("调试浮窗树序不在确认弹窗之后——登记册末位失守") # ② 滑条 → 生效预览联动 main.debug_overlay.set_sliders(100, 50) if str(boosted_atk) not in main.debug_overlay.effect_text() or str(boosted_hp) not in main.debug_overlay.effect_text(): @@ -41,7 +59,8 @@ func _ready() -> void: # ④ 重置(重开浮窗 → 重置按钮真实路径;面板百分比归零——下一场战斗生效值回到基线) main._show_debug_overlay() main.debug_overlay.press_reset_for_probe() - # ⑤ 批量模拟全链路(N=10——走 level_autoplay 与 headless 扫描同口径;表基线由组合根复位) + # ⑤ 批量模拟全链路(N=10——走 level_autoplay 与 headless 扫描同口径;表基线由组合根复位; + # 菜单态上下文未锁定=按钮可点,is_running 语义=模拟在跑〔18卡⑦ 与对局禁用态拆分〕) main.debug_overlay.set_sliders(100, 0) main.debug_overlay.set_sim_count(10) main.debug_overlay.press_simulate_for_probe() @@ -72,9 +91,15 @@ func _ready() -> void: ProbeUiUtil.isolate_save(main, "smoke_tune_probe_save", true, false, false) main.debug_overlay.set_sliders(100, 50) main.debug_overlay.press_apply_for_probe() + main.debug_overlay.seed_edit.text = "42" # 种子钉死(18卡⑦审计🔴——空框=非首局随机,显式优先经真实路径断言 master_seed) main._start_adventure_from_menu() # 完整冒险路径(唯一试玩入口;_start_single 按语义清零滑条 = 恒基线,不承载加成断言) await get_tree().process_frame await get_tree().process_frame + # ⑧b 开冒险路径显式关浮窗(18卡⑦ T2 防护——DS_DEBUG 会话默认开态下开局不带窗,对局中经角标再开) + if main.debug_overlay.visible: + problems.append("开冒险路径未显式关浮窗(T2 防护失守)") + if int(main.adventure.master_seed) != 42: + problems.append("显式种子未达冒险(master_seed=%s ≠ 42——真实路径显式优先失守)" % str(main.adventure.master_seed)) # 停点前移批 2026-09-23:关卡开始 = 入口选路停点——真实按钮路径选入口候选进首战再读呈现快照 if main.level_screen.visible: main.level_screen.press_candidate(0) @@ -84,11 +109,128 @@ func _ready() -> void: if int(hero.get("atk", -1)) != boosted_atk or int(hero.get("max_hp", -1)) != boosted_hp: problems.append("战斗加成单次生效违例(atk=%s max_hp=%s,期望 %d/%d)" % [ str(hero.get("atk", "?")), str(hero.get("max_hp", "?")), boosted_atk, boosted_hp]) + # ⑨ 对局中可开(18卡⑦ P2 甲/T2 防护——S1+ 停机点回归闸):战斗屏角标真实信号路径开窗; + # 棋盘输入不锁、暂停角标不置灰(旧 has_open 判据在「默认开×遮挡复活」下必锁死的破约回归面); + # 对局中批量模拟/两层调试两钮禁用;当局数据六项与只读访问器逐项一致;关闭钮真实路径收窗恢复 + var dbg_badge: Button = main.board_zone.debug_badge + if dbg_badge == null or not dbg_badge.visible: + problems.append("战斗屏调试角标未点亮(P2 甲 DS_DEBUG 门控失守)") + else: + if dbg_badge.disabled: + problems.append("战斗屏调试角标被置灰(T2 排除式判据失守)") + dbg_badge.pressed.emit() # 真实按钮信号路径 → 组合根 _on_debug_badge_pressed → C6 单口 + if not main.debug_overlay.visible: + problems.append("对局中角标开浮窗失败(P2 甲对局中可达失守)") + if main._input_locked(): + problems.append("对局中开浮窗后棋盘输入锁死(T2 防护失守——S1+ 停机点)") + if main.board_zone.pause_badge.disabled: + problems.append("对局中开浮窗后暂停角标置灰(T2 防护失守——S1+ 停机点)") + if main.debug_overlay.is_running(): + problems.append("对局中上下文锁定误判为模拟在跑(is_running 语义互扰)") + if not main.debug_overlay._sim_btn.disabled or not main.debug_overlay._dbg_btn.disabled: + problems.append("对局中批量模拟/两层调试按钮未禁用(T2 并存态不开放失守)") + # 当局数据六项值对(T3——数值与只读访问器逐项一致;种子=adventure.master_seed 现值非 _seed_value 重算) + var rd: Dictionary = main.debug_overlay.run_data_probe() + var r = main.adventure.runner() + if String(rd.get("seed", "")) != "42": + problems.append("当局数据种子 ≠ master_seed 现值(%s)" % str(rd.get("seed"))) + if int(rd.get("level", -1)) != int(main.adventure.level_index): + problems.append("当局数据关卡失对(%s ≠ %s)" % [str(rd.get("level")), str(main.adventure.level_index)]) + if int(rd.get("node", -999)) != int(r.node_index): + problems.append("当局数据节点失对(%s ≠ %s)" % [str(rd.get("node")), str(r.node_index)]) + if String(rd.get("hp", "")) != "%d/%d" % [int(r.hero_hp), int(r.effective_max_hp())]: + problems.append("当局数据 HP 失对(%s)" % str(rd.get("hp"))) + if int(rd.get("gp", -1)) != int(r.gp): + problems.append("当局数据 GP 失对(%s ≠ %s)" % [str(rd.get("gp")), str(r.gp)]) + if int(rd.get("keystone", -1)) != int(main.adventure.keystone): + problems.append("当局数据钥石失对(%s ≠ %s)" % [str(rd.get("keystone")), str(main.adventure.keystone)]) + var close_btn: Button = ProbeUiUtil.find_button(main.debug_overlay, "关闭", "prefix") + if close_btn == null: + problems.append("浮窗关闭钮缺失(真实收窗路径中断)") + else: + close_btn.pressed.emit() + if main.debug_overlay.visible or main._input_locked(): + problems.append("对局中关闭浮窗后态失守(visible=%s locked=%s)" + % [str(main.debug_overlay.visible), str(main._input_locked())]) + # ⑨b 只读巡检(17卡③形制)+零动效契约(17卡③ R5——零 AnimationPlayer+源码零 tween) + _overlay_readonly_contract(main, problems) + _overlay_zero_motion_contract(main, problems) + # ⑩ 种子三分单点断言(18卡⑦ C1——判定只落 _seed_value() 单点;对局态不影响本判定面): + # 显式合法整数全场景优先(含首局)→ 首局(存档布尔 false)恒 42 → 非首局空框引擎熵连抽不全等 42 + main.debug_overlay.seed_edit.text = "777" + if main._seed_value() != 777: + problems.append("显式种子输入未优先(_seed_value != 777——C1 ①失守)") + var fresh_sm = load("res://core/save_manager.gd").new("user://smoke_tune_probe_seed.json") + fresh_sm.load_from_disk() # 全新空档——first_adventure_done=false(首局态;新档缺省面) + var keep_sm = main.save_manager + main.save_manager = fresh_sm + main.debug_overlay.seed_edit.text = "" + if main._seed_value() != 42: + problems.append("首局空框种子 != 42(P5 甲失守——教学态可复现破约)") + main.save_manager = keep_sm # 恢复隔离档(非首局态) + var draws: Array[int] = [] + for i in range(4): + draws.append(main._seed_value()) + var all42 := true + for d in draws: + if d == 42: + all42 = all42 and true + else: + all42 = false + if all42: + problems.append("非首局空框连抽 4 次全等 42(随机化失守——C1 ③)") + main.debug_overlay.seed_edit.text = "42" # 钉回显式值(现场整洁) + for f0 in ["user://smoke_tune_probe_seed.json", "user://smoke_tune_probe_seed.json.tmp"]: + if FileAccess.file_exists(f0): + DirAccess.remove_absolute(f0) if problems.is_empty(): - print("[tune_probe] PASS — 打开/联动/应用/重置/模拟/关闭/基线无残留/单次加成 全链路通过(模拟 N=10 输出 %.30s…)" % out) + print("[tune_probe] PASS — 启动即开/联动/应用/重置/模拟/关闭/基线无残留/单次加成/对局中可开+当局数据+只读巡检/种子三分 全链路通过(模拟 N=10 输出 %.30s…)" % out) get_tree().quit(0) else: for p in problems: push_error("[tune_probe] FAIL — " + p) print("[tune_probe] FAIL — " + "; ".join(problems)) get_tree().quit(1) + + +# 只读巡检(18卡⑦ T3——17卡③「白名单外零连接」形制沿用):浮窗子树全控件 × 游戏语义信号名清单 +# 全连接巡检——白名单={视图自身七方法 + 组合根 _on_modal_closed(closed 外发消费)}; +# 当局数据区块=纯标签零连接在此被一并约束(新增任何连接=红须入册审查)。 +func _overlay_readonly_contract(main: Node, problems: Array) -> void: + var overlay: Control = main.debug_overlay + var stack: Array = [overlay] + var conn_count := 0 + while not stack.is_empty(): + var n: Node = stack.pop_back() + for sig_name in _OVERLAY_READONLY_SIGNALS: + for conn in n.get_signal_connection_list(sig_name): + conn_count += 1 + var cb: Callable = conn["callable"] + var host: Object = cb.get_object() + var m := String(cb.get_method()) + var ok := (host == overlay and m in _OVERLAY_VIEW_METHODS) \ + or (host == main and m == "_on_modal_closed") \ + or (host is VScrollBar or host is HScrollBar) # 引擎滚动机构内部 drag 接线(RichTextLabel 自动滚动条——17卡③布局生命线同类豁免,零游戏写通路) + if not ok: + problems.append("[readonly] 非法信号连接 %s.%s → %s.%s(白名单外零连接巡检——18卡⑦ T3)" + % [n.name, sig_name, host, m]) + for ch in n.get_children(): + stack.append(ch) + if conn_count != 10: + problems.append("[readonly] 信号连接数=%d 应=10(fx切换/应用/重置/模拟/两层调试/关闭六钮+滑条2+closed 外发+引擎滚动条 drag 1——多连接=写通路嫌疑)" % conn_count) + + +# 零动效契约(17卡③ R5 形制——子树零 AnimationPlayer + 视图源码零 tween/动效包装调用) +func _overlay_zero_motion_contract(main: Node, problems: Array) -> void: + var overlay: Control = main.debug_overlay + var stack: Array = [overlay] + while not stack.is_empty(): + var n: Node = stack.pop_back() + if n is AnimationPlayer: + problems.append("[motion] 浮窗子树含 AnimationPlayer(零动效契约破)") + for ch in n.get_children(): + stack.append(ch) + var src := FileAccess.get_file_as_string("res://presentation/views/debug_overlay.gd") + for token in ["create_tween", "UiKit.motion"]: + if src.contains(token): + problems.append("[motion] 浮窗视图源码含「%s」(零动效直开直关破——17卡③契约)" % token) diff --git a/roadmap.md b/roadmap.md index 704db292..64f26fbf 100644 --- a/roadmap.md +++ b/roadmap.md @@ -71,7 +71,7 @@ | 迭代 18 | **③武器锻造系统设计提案批(2026-10-06 用户令入迭代 18;纯文档零代码——提案源=playnote 2026-10-06)**:用户提案=攻击力提升改造为「武器锻造」——剑(普攻)锻造升级加攻击、满级附带额外效果;后续增列长枪/法杖,攻击类型分别走穿刺/魔法;护甲由轻甲起级(先只提 AC)→解锁重甲/魔法甲(基础 AC 各异、可续升、满级各带额外效果)——武器/护甲差异化。产出=系统设计提案文档(与奇物属性值提升分类/护甲克制轴/营地柜台/锻造经济边界对齐,数值框架 provisional 终值归 Alpha 收官 M4 数值批统一调整〔未排期〕),裁点呈用户裁决;实装批另行立项。06 已登记专条。任务书=`design/research/任务书-武器锻造系统设计提案批-2026-10-07.md`(提纲版 v1.0——设计裁点在提案产物内全备选呈裁,任务书零待裁点;开工=U0) | 🔨 提案已成文交付(2026-10-07)待盘问裁决(异步不阻塞) | T0-T2≈1.5d(一轮呈裁基线,盘问轮次扩展另计) | | 迭代 18 | **④生涯界面实装批(2026-10-06 用户令入迭代 18)**:按迭代 17 卡④设计提纲(design/gdd/systems/跨局循环/生涯.md v0.1,master 44328b1)实装「生涯」界面——跨局汇总/历史冒险回看/图鉴承接;提纲在册要点=recent_summaries 现役 cap10 扩容+用时埋点/总局数与通关局数(涉存档 schema)/营地最近冒险清单去留(P6 必答项随评审定);分期建议随提纲。任务书=`design/research/任务书-生涯界面实装批-2026-10-07.md`(v1.0——范围=提纲期一〔汇总页+主菜单入口+终身计数域存档 v6〕,期二/期三零触碰;裁点 P1 通关局数口径/P2 汇总条目集/P3 模态登记序待裁;提纲评审若改期一范围则任务书升版) | **✅ 提纲期一追认+裁点全裁(2026-10-07 用户令代解/全甲;P6 随期二)**——开工=U0 | T0-T5≈2.5d | | 迭代 18 | **⑤混编遭遇首批投放批(2026-10-06 用户令入迭代 18;纯内容批——机制前置=迭代 17 卡①敌人表行集多行批)**:07「后续内容批义务」落地——首个混编遭遇行+Boss 多行内容投放(敌种候选与坐标=本批裁点);首遇教学位纪律与 expect_pools 断言面逐项复核(巨蜘蛛首遇位「固定 2 不加骰」等表注释为准);难度带读数义务随批登记 07,MC 取证跑批**顺延 Alpha 收官 M4 数值批统一执行**〔未排期,届时另令——2026-10-06 用户令:数值统一调整殿后;投放本身不阻塞〕。任务书=`design/research/任务书-混编遭遇首批投放批-2026-10-07.md`(v1.0——纯内容批零代码,机制面全由卡①承载,机制缺口=L3 停机;裁点 P1 首批投放方案〔荐甲=L2 单点试水〕/P2 Boss 多行不入首批/P3 教学界保护待裁) | 迭代 17 卡①落籍(✅ 已满足 9cc4b8b)+**✅ 裁点全裁(P1-P4 甲,2026-10-07)**——开工=U0 | T0-T5≈1.75d(MC 取证顺延另计)——**✅ 已全批落表(2026-10-07,worktree g-plan2-iter18-mixenc 分支 iter18-mixenc 基点 adadf56,批内提交 ad36e35(T1 设计稿)/6272fc8(T3 落表)/43b1306(T4 断言)/⑤T5 收口批;出口报告=[iter18-mixenc-exit-report](design/qa/iter18-mixenc-exit-report-2026-10-07.md))**:(2,5) 双行混编落表〔重击手+巨蜘蛛巢群(行值复制 (2,6),P4 甲零数值新增),R6 行序=重击手在前〕;d5/d7 候选与行值来源全案留痕(d5 案选定);断言面随迁(expect_pools 三元键新池模板+行集构成/行序/Σ≤4 断言);首遇教学位 (2,4)/(3,0)/(3,1) 零触碰;07 ⑱ 目检与读数义务登记+:174 逐行骰措辞订正;闸=冒烟 814/0(基线 811/0,+3)+run_all+doc 三闸/熵增零新增(批末重闸数字见出口报告);实机目检三项归用户(同场两敌种/行序/总数 ≤4);MC 零跑(顺延 Alpha 收官 M4 数值批在案) | -| 迭代 18 | **⑦新冒险种子随机化+调试浮窗升级(2026-10-07 用户令入迭代 18,同日两裁点全裁+浮窗目标态已定)**:**(a) 种子随机化**——现状考证 main.gd:1210 `_seed_value()` 调试种子框为空时硬编码返回 42=每次新冒险同一种子全程可复现;改法=玩家界面入口(主菜单开新冒险)默认分支随机生成;**✅ 裁点全裁(2026-10-07 用户令):①首次冒险维持固定种子(教学态可复现)/②测试脚本与调试接口维持显式指定种子(冒烟·MC·探针全链显式传种子零影响,锚零作废)/③种子不进玩家 UI**;断点续玩零影响(存档已载 master_seed 回填,main.gd:447 现役);与⑥收官批零耦合、排序自由。**(b) 调试浮窗升级**(承载「调试浮窗可查当局数据」裁点=种子的唯一查看位;**本项修订迭代 15 卡① P3 甲「仅开始菜单可开·对局不可达」裁决**)——目标态三件:独立于整体界面的**最高层浮窗**(对局中也可开)/DS_DEBUG=1 调试态下**默认打开**/新增**当局数据只读区块**(当前局种子/关卡/节点/HP/GP/钥石等——玩家 UI 不展示、调试浮窗可查)。考证在册(2026-10-07 实查):现行=开始覆盖层角标入口〔main.gd:292-293,DS_DEBUG=1 点亮〕+main.gd:1189 门禁「仅开始菜单可开」+Dialog 形态走 ModalStack 模态栈登记〔非最高层〕+visible=false 初始非默认开+五区块(胜利演出/种子输入/数值滑条/批量模拟/两层调试)无当局数据面。任务书待裁点:最高层与模态栈登记制的关系〔置顶/登记序末+move_child 或独立层〕/输入吞噬边界〔浮窗矩形外透传游戏输入〕/对局发起「随开随收」逻辑改向/ui_tune_probe 断言面换代/入口形态(角标宿主迁移或热键)。任务书=`design/research/任务书-种子随机化与调试浮窗升级批-2026-10-07.md`(v1.0——实装级裁点 P1 遮挡语义〔荐甲=维持登记制〕/P2 入口形态〔荐甲=角标迁宿主〕/P3 默认打开范围〔荐甲=启动即开〕/P4 当局数据字段集〔荐甲=六项〕/P5 首局种子值〔荐甲=沿用 42〕待裁;估时勘误 1d→2.0d 含探针换代与收口) | **✅ 裁点全裁(2026-10-07 用户令全按推荐:P1-P6 甲)**——开工=U0 | T0-T5≈2.0d | +| 迭代 18 | **⑦新冒险种子随机化+调试浮窗升级(2026-10-07 用户令入迭代 18,同日两裁点全裁+浮窗目标态已定)**:**(a) 种子随机化**——现状考证 main.gd:1210 `_seed_value()` 调试种子框为空时硬编码返回 42=每次新冒险同一种子全程可复现;改法=玩家界面入口(主菜单开新冒险)默认分支随机生成;**✅ 裁点全裁(2026-10-07 用户令):①首次冒险维持固定种子(教学态可复现)/②测试脚本与调试接口维持显式指定种子(冒烟·MC·探针全链显式传种子零影响,锚零作废)/③种子不进玩家 UI**;断点续玩零影响(存档已载 master_seed 回填,main.gd:447 现役);与⑥收官批零耦合、排序自由。**(b) 调试浮窗升级**(承载「调试浮窗可查当局数据」裁点=种子的唯一查看位;**本项修订迭代 15 卡① P3 甲「仅开始菜单可开·对局不可达」裁决**)——目标态三件:独立于整体界面的**最高层浮窗**(对局中也可开)/DS_DEBUG=1 调试态下**默认打开**/新增**当局数据只读区块**(当前局种子/关卡/节点/HP/GP/钥石等——玩家 UI 不展示、调试浮窗可查)。考证在册(2026-10-07 实查):现行=开始覆盖层角标入口〔main.gd:292-293,DS_DEBUG=1 点亮〕+main.gd:1189 门禁「仅开始菜单可开」+Dialog 形态走 ModalStack 模态栈登记〔非最高层〕+visible=false 初始非默认开+五区块(胜利演出/种子输入/数值滑条/批量模拟/两层调试)无当局数据面。任务书待裁点:最高层与模态栈登记制的关系〔置顶/登记序末+move_child 或独立层〕/输入吞噬边界〔浮窗矩形外透传游戏输入〕/对局发起「随开随收」逻辑改向/ui_tune_probe 断言面换代/入口形态(角标宿主迁移或热键)。任务书=`design/research/任务书-种子随机化与调试浮窗升级批-2026-10-07.md`(v1.0——实装级裁点 P1 遮挡语义〔荐甲=维持登记制〕/P2 入口形态〔荐甲=角标迁宿主〕/P3 默认打开范围〔荐甲=启动即开〕/P4 当局数据字段集〔荐甲=六项〕/P5 首局种子值〔荐甲=沿用 42〕待裁;估时勘误 1d→2.0d 含探针换代与收口) | **✅ 已落地(2026-10-07 worktree iter18-seed:C1 种子三分+浮窗升级 P1-P6 甲全落;批末重闸全绿=冒烟 811/0+四探针+run_all 17 套+doc 三闸/熵增闸零新增;MC 零跑;实机人闸挂账待用户〔不阻塞落籍与后续卡④〕)** | T0-T5≈2.0d | | 迭代 19+ | **M4 数值批(**Alpha 收官位·未排期**——2026-10-05 用户令编入迭代 17 卡②;2026-10-06 用户令移出独立成批并定序「Alpha 阶段最后——内容补全先行、统一数值调整殿后」〔当日曾被编入迭代 18 为收官卡,2026-10-07 用户令撤回〕;2026-10-07 用户令:**暂不启动**,移出迭代 18,保持 Alpha 收官位待用户发令)**:四件数值欠账合一批——①削弱档位终值(weaken 现值 −65/duration 2/refresh 考证在册;叠层改续时语义已定案,差 duration 终值+当量锚复算)②调参批数值点评(06 条目现行登记五条——任务口径「八条」与条目五条不符,任务书 §2.2 已呈报差异以条目为准)③迭代 10 奖励重构联动欠账①②(钥石产出重标 boss_keystone 首杀 3/3/3·复杀 2/2/3 provisional + 通关率带重扫〔通关回血 20%+AP 件首轮 +1〕)④元进度定价剩余面(forge base 20/比 150 考证在册——06 条目文本「base 10」滞后于 2026-09-22 对齿批已呈报;Boss 两档终值)。读数义务随批=迭代 14 两件后置复算(T6 MC 四带复算+T7 技能池节奏重测,均挂 07);顺路修复 damage_probe A1 改前既有红灯(判式换代批出口报告 §四.7 在册)。**硬约束:一切 MC/verifier 跑批发起前须用户批准;调参联动禁独立调参网络(β 带 [1.2,2.5] 逐 u 检算)**。任务书=`design/research/任务书-M4数值批-2026-10-05.md`(**v1.2 2026-10-06 案C 增补+联审修订**:v1.1 三事实与第五推算件照旧;联审=P3 转正顺延至重扫读数后/检算工具 WEAKEN_PCT 入同步面/F11 两句死文本全刷/T3·T5 四带复算并账一次跑批双认账/R5 改前锚=本批新立案C 锚/估时段一勘误 4.25d;案C 增补内容照旧=§2.6 三事实〔行为换代/锚全作废/难度预判〕+T0 推算稿第五件=对拍锚按新序重推导+T6 四带复算按案C 新行为首采;T0-T8≈6.25d,天然切分点 T4 末;🔴 契约 5 条含跑批批准制;**✅ 裁点全裁(2026-10-06 用户令:P1甲=维持 2 场·magnitude [60,85] 带内当量锚复算、具体值随出口 provisional 追认/P2甲 不对称有意/P3甲 3-3-3·2-2-3 维持、转正顺延至重扫读数到账且带内(Q4 二裁)/P4甲 不立直购价);开工=U0**) | 迭代 18 全部内容卡(③④⑤⑦)出口+全部内容批行为变更落定+**用户发令(未排期)**——重锚窗口须收尽全部内容批行为变更 | T0-T8≈6.25d(超软盒预挂,段一 T0-T4≈4.25d〔v1.2 勘误,原 4.5 系加总错误〕/段二 T5-T8≈2.0d) | ## 执行纪律(2026-09-27 用户立规)