From 16c776e9f3f7fe9dbb112344367112b6d091e662 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sat, 24 Oct 2026 05:54:40 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E8=BF=AD=E4=BB=A335=E5=8D=A1=E2=91=A0):=20?= =?UTF-8?q?=E8=87=B4=E5=91=BD=E9=9D=A2=E6=94=B9=E6=B2=BF=20ConfigDB=20?= =?UTF-8?q?=E6=97=A2=E6=9C=89=E4=BD=93=E4=BE=8B=EF=BC=88Core=20=E7=BD=AE?= =?UTF-8?q?=E8=87=B4=E5=91=BD=E5=B0=81=E5=8D=B0=EF=BC=8B=E6=96=AD=E8=A8=80?= =?UTF-8?q?=EF=BC=8C=E5=81=9C=E6=9C=BA=E5=86=B3=E7=AD=96=E5=BD=92=E7=BB=84?= =?UTF-8?q?=E5=90=88=E6=A0=B9=E2=80=94=E2=80=94OS.crash=20=E4=BC=9A?= =?UTF-8?q?=E9=98=BB=E5=A1=9E=E8=B7=91=E6=89=B9=E4=B8=94=E4=B8=8D=E5=8F=AF?= =?UTF-8?q?=E6=96=AD=E8=A8=80=EF=BC=89=EF=BC=9Bfv4=20=E5=88=A4=E6=8D=AE?= =?UTF-8?q?=E6=94=B9=E3=80=8C=E8=87=B4=E5=91=BD=E7=BD=AE=E4=BD=8D=E2=86=92?= =?UTF-8?q?=E7=BB=84=E5=90=88=E6=A0=B9=E5=81=9C=E6=9C=BA=E7=A0=81=E6=81=B0?= =?UTF-8?q?=201=E3=80=8D=EF=BC=9B=E7=94=9F=E6=88=90=E5=99=A8=E6=96=AD?= =?UTF-8?q?=E8=A8=80=E8=A1=A5=E4=B8=A4=E5=A4=84=E5=88=A4=E5=88=AB=E5=8A=9B?= =?UTF-8?q?=EF=BC=88=E9=9D=9E=20Boss=20=E7=BB=88=E7=82=B9=E6=8C=89?= =?UTF-8?q?=E5=88=A4=E6=8D=AE=E6=B6=88=E6=81=AF=E4=B8=93=E6=96=AD=EF=BC=8B?= =?UTF-8?q?=E5=90=88=E6=B5=81=E5=9B=BE=E6=B7=B1=E5=BA=A6=E9=9D=9E=E6=9C=80?= =?UTF-8?q?=E9=95=BF=E8=B7=AF=E5=BE=84=E5=80=BC=E8=A2=AB=E6=8B=92=EF=BC=89?= =?UTF-8?q?=EF=BC=9B=E5=8F=8D=E5=93=BA=E7=82=B9=E6=B8=85=E5=8D=95=E4=B8=83?= =?UTF-8?q?=E6=9D=A1=E8=90=BD=20reports/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/node_map_generator.gd | 12 +++++++++- .../unit/core/node_map_generator_test.gd | 21 ++++++++++++++++-- game/tests/unit/core/node_map_growth_test.gd | 12 ++++++---- reports/iter35-卡①-反哺点-2026-10-24.md | 22 +++++++++++++++++++ 4 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 reports/iter35-卡①-反哺点-2026-10-24.md diff --git a/game/core/node_map_generator.gd b/game/core/node_map_generator.gd index c3f2cacb..5b6d3d9d 100644 --- a/game/core/node_map_generator.gd +++ b/game/core/node_map_generator.gd @@ -23,6 +23,7 @@ var _by_id: Dictionary = {} var _next_id := 0 var _detour_heads: Array = [] # 岔口侧链首节点(下一跳类型互异的修型面) var _detour_ids: Dictionary = {} # 侧链节点 id 集(配比填余不动侧链——战斗保底只由链上槽承载) +var _fatal_flag := false # 致命封印(沿 ConfigDB._fatal_flag 体例——参数无解/产出不合格置位) func _init(rng_service = null) -> void: @@ -31,6 +32,7 @@ func _init(rng_service = null) -> void: ## 产图主入口(§7 接口块定稿签名——不收 level_index,契约 6) func generate(params: Dictionary, seed_val: int) -> Dictionary: + _fatal_flag = false var errs := check_params(params) if not errs.is_empty(): for e in errs: @@ -533,5 +535,13 @@ func _descendants(start: int, by_id: Dictionary) -> Dictionary: func _fatal(msg: String) -> void: + # 致命面沿 ConfigDB._fatal 体例:Core 层置致命封印+push_error(调用方已落),退出决策归组合根 + # (main.gd `_fatal_screen`/卡② 起关拒绝路径)。generate 已致命中止时不产出任何图, + # 空返回不是兜底图——消费侧必须按 is_fatal() 拒收(契约 4 零软回退)。 + _fatal_flag = true assert(false, msg) - OS.crash("NodeMapGenerator: " + msg) + + +## 致命封印读面(沿 ConfigDB.is_fatal 体例)——组合根/消费侧据此停机或拒收起关 +func is_fatal() -> bool: + return _fatal_flag diff --git a/game/tests/unit/core/node_map_generator_test.gd b/game/tests/unit/core/node_map_generator_test.gd index adca0af1..6021fe82 100644 --- a/game/tests/unit/core/node_map_generator_test.gd +++ b/game/tests/unit/core/node_map_generator_test.gd @@ -334,11 +334,21 @@ func _test_negative_fixtures() -> void: {"id": 0, "type": "mine", "depth": 0, "next": [1]}, {"id": 1, "type": "boss", "depth": 1, "next": []}]) Kit.check("类型出域夹具报 enum〔fv1〕", _has_cat(_gen.validate_output(badty), "enum")) - # 非 Boss 终点(断头链末不是 boss) + # 非 Boss 终点(断头链末不是 boss)——判据消息专一断言(防「别的检查顺带兜住」的假拦截) var noBoss := _fx([ {"id": 0, "type": "battle", "depth": 0, "next": [1]}, {"id": 1, "type": "battle", "depth": 1, "next": []}]) - Kit.check("非 Boss 终点夹具报 structural〔fv1〕", _has_cat(_gen.validate_output(noBoss), "structural")) + Kit.check("非 Boss 终点夹具命中「终点(无出边)非 Boss」判据〔fv1〕", + _has_msg(_gen.validate_output(noBoss), "终点(无出边)非 Boss")) + # 合流图深度赋值语义(契约 2):跳边让某节点的最长路径值高于其声明值 → 必须被拒 + var conf_short := _fx([ + {"id": 0, "type": "battle", "depth": 0, "next": [1, 3]}, + {"id": 1, "type": "battle", "depth": 1, "next": [2]}, + {"id": 2, "type": "reward", "depth": 2, "next": [3]}, + {"id": 3, "type": "shop", "depth": 1, "next": [4]}, + {"id": 4, "type": "boss", "depth": 4, "next": []}]) + Kit.check("合流图深度写 shortest(=1)被拒(须最长路径步数)〔fv1〕", + _has_msg(_gen.validate_output(conf_short), "自入口最长路径步数")) # 环/回边:深度不前进 var loop := _fx([ {"id": 0, "type": "battle", "depth": 0, "next": [1]}, @@ -405,3 +415,10 @@ func _has_cat(errs: Array, cat: String) -> bool: if String(e["category"]) == cat: return true return false + + +func _has_msg(errs: Array, needle: String) -> bool: + for e in errs: + if String(e["message"]).contains(needle): + return true + return false diff --git a/game/tests/unit/core/node_map_growth_test.gd b/game/tests/unit/core/node_map_growth_test.gd index b0d3fe0b..ef8f7608 100644 --- a/game/tests/unit/core/node_map_growth_test.gd +++ b/game/tests/unit/core/node_map_growth_test.gd @@ -85,10 +85,10 @@ func _test_fatal_subprocess() -> void: var out := [] var code: int = OS.execute(bin, ["--headless", "--path", game_dir, "--script", "res://tests/unit/core/node_map_growth_test.gd", "--", "fatal-fixture"], out, true) - # 退出码三分:0 = 子进程正常收尾(致命通道静默);2 = 无解后仍走完 quit(2) 的失效分支; - # 其余非零(含 assert/OS.crash 中止码)=致命中止真实发生。 + # 退出码三分:0 = 子进程正常收尾(致命通道静默);2 = 无解后仍走到失效分支 quit(2); + # 1 = 致命封印置位、由夹具扮演组合根停机(既有 fatal 路径的承接形态)。 print("[fv4] 无解子进程退出码 = %d" % code) - Kit.check("无解参数档产图进程致命中止(退出码既非 0 亦非失效分支 2,无残图交付)〔fv4〕", code != 0 and code != 2) + Kit.check("无解参数档置致命封印并由组合根停机(退出码恰 1,无残图交付)〔fv4〕", code == 1) func _trigger_fatal() -> void: @@ -98,7 +98,11 @@ func _trigger_fatal() -> void: "rest_ratio": 0.2, "shop_ratio": 0.1, "entry_count": 1, "exit_count": 3, "battle_floor": 4, "fork_ways_min": 2, "fork_ways_max": 3} gen.generate(unsolvable, 42) - # 走到这里=致命通道未止住(软回退缺陷)→ 显式退出码 2,父断言按「非 0 且非 2」判致命是否真发生 + # 致命封印未置位=fail-fast 静默化(缺陷)→ 显式退出码 2 与致命停机码 1 区分 + if gen.is_fatal(): + print("[fatal-fixture] 致命封印已置位(参数无解致命中止),组合根角色据此停机") + quit(1) + return push_error("[fatal-fixture] 参数无解未致命中止(fail-fast 失效回归)") quit(2) diff --git a/reports/iter35-卡①-反哺点-2026-10-24.md b/reports/iter35-卡①-反哺点-2026-10-24.md new file mode 100644 index 00000000..cf0e3cd9 --- /dev/null +++ b/reports/iter35-卡①-反哺点-2026-10-24.md @@ -0,0 +1,22 @@ +# 迭代 35 卡① 反哺点清单(节点图生成器核心批) + +- 日期:2026-10-24 +- 出处:`reports/需求卡片-节点图生成器核心批-2026-10-21.md` §6 关联文档同步面六条 + 实现中新发现一条设计面事实 +- 状态:待用户在会话里逐条裁决;裁决后由文档批写进 `design/`,本文件随之退役(doc-conventions §11 吸收即死) +- 依据代号沿卡片 §2:D1=关卡节点图生成.md v1.1,D2=关卡节点地图.md v2.1,D3=冒险结构.md + +| # | 文件与节 | 改成什么 | 依据 | +|---|---|---|---| +| ① | `design/gdd/systems/冒险循环/关卡节点图生成.md` 产出约束节(D1 :41-49 五条之后) | 增一条产出约束「多终点落在互异节点深度」;并在层高条目处成文「节点深度=自入口最长路径步数(多入口取距任一入口的最长路径)」 | 卡① 契约 2;D2 :119「定位信息只有两样(关卡序号+节点深度)」+D1 :96「多终点各配 Boss 行、走哪条路决定打谁」合取——同深度多终点在只有两样定位信息下不可区分;卡③ 契约 6 推论条款 A 的图侧兑现(卡③ §6 反哺点③ 派发) | +| ② | `design/gdd/systems/冒险循环/关卡节点地图.md` :65 | 「节点深度=从入口沿路走过来的步数」补注为「自入口最长路径步数」,并说明合流图上为何不能用最短路径 | 卡① §11 推导:最短路径赋值下合流/多入口节点可出现「出边目标深度 ≤ 源」,使无环判据(D1 :51 连线只朝前进方向)失效;实现侧断言 `node_map_generator_test`〔fv1〕已按最长路径口径写死 | +| ③ | `design/gdd/systems/冒险循环/冒险结构.md` :293 覆盖义务行 | 登记生成图实际深度域供覆盖核对:**L1 深度 0–6/L2 0–9/L3 0–13**(三档参数档 `layers` = 6/9/13 实测,2026-10-24 卡① 落地读数);覆盖义务措辞换代(精确行→敌侧带命中+板侧解析命中)仍归卡③ 反哺线,本条只补深度域数值 | D3 :293 覆盖义务+卡① §0 交付序列;`game/data/tables/level_gen_params.tres` 现值 | +| ④ | `design/gdd/ux/level-map.md` | 登记呈现面随动需求:多入口首跳候选、多终点标记、含岔口图与深度域 0–13 的画布自适应;本批未改任何呈现代码 | 卡① §6 表行(06 条目连带面②);落地归卡④/level-map 文档批 | +| ⑤ | `待办清单.md` 06「关卡节点图重构——可选节点太少」条目 | 状态推进为「卡① 已实现待合回;卡② 停工退回(覆盖批未立卡)」,并把第 143 行缺口段更新为「已实现卡① 深度域实测」版本——拟稿见 `reports/iter35-卡2-停机呈报-2026-10-24.md` 第五节 | 实现批对流程清单零改动(AGENTS.md 两类批红线),条目状态只由流水线各段推进 | +| ⑥ | `roadmap.md` 迭代 35 行 | 卡片列与状态换代:卡① 标实现批已交付(合回后改「已完成」)、卡② 标停工退回上游;顶部「当前位」三行随之更新 | 卡① §6 表行;roadmap 属文档批职权,本批只列点不落笔 | +| ⑦ | `design/gdd/systems/冒险循环/关卡节点图生成.md` 配比与摆放约束节(D1 :74-88) | 成文一条结构上限:出口个数 3 不只是调参表的取值域,而是摆放约束③「每支路至少一个别的支路没有的类型」的天花板——五类节点里商店与 Boss 每条支路必有,可供支路互斥的唯一类型只剩 battle/reward/rest 三类,故一条链上的岔口最多切出 3 条互异支路 | 实现实证:卡① 三档构型中,多终点图必须给每个归属区域指定互斥签名类型才能过支路互异断言(`node_map_generator.gd` REGION_SIGNATURES 三元素);D1 :107 出口 1–3 已锁范围与该推论一致,但文档未写理由 | + +## 与实现差异/缺口逐条自证 + +- ①②:文档只写「步数」未写「最长」,实现取最长路径赋值——差异已按契约 2 落码并写断言,属文档缺位。 +- ⑦:文档「岔路取舍·支路构成」条目在三终点情形下隐含唯一可行解(每区域独占一类),实现按此构型;文档若不加此句,后续调参批可能误配四出口。 +- 本批未触碰 GDD 任何条款文本,也未新增/修改 `design/` 与两份根流程清单。