From 98c61d50c3c07f2e15b94de48d4fcb9ea3466f63 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sat, 24 Oct 2026 16:08:57 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E8=BF=AD=E4=BB=A335=E5=8D=A1=E2=91=A1?= =?UTF-8?q?T2=C2=B7=E4=B8=80=E5=8F=A3):=20=E8=B7=A8=E8=A1=A8=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=E2=91=A3=E3=80=8C=E5=85=B3=E5=8D=A1=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E2=86=92=E6=95=8C/=E6=9D=BF=E8=A6=86=E7=9B=96=E4=B8=8E?= =?UTF-8?q?=E5=A4=9A=E7=BB=88=E7=82=B9=20Boss=20=E5=85=B1=E5=B8=A6?= =?UTF-8?q?=E3=80=8D=E9=9A=8F=E9=9D=99=E6=80=81=E8=8A=82=E7=82=B9=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=B6=88=E4=BA=A1=E6=95=B4=E4=BD=93=E9=80=80=E5=BD=B9?= =?UTF-8?q?=E2=80=94=E2=80=94=E4=B8=A4=E6=9D=A1=E6=8B=A6=E6=88=AA=E8=AF=AD?= =?UTF-8?q?=E4=B9=89=E6=94=B9=E7=94=B1=E8=BF=90=E8=A1=8C=E6=97=B6=E6=96=AD?= =?UTF-8?q?=E8=A8=80=E6=89=BF=E6=8E=A5=EF=BC=9A=E9=A1=B6=20clamp=20?= =?UTF-8?q?=E6=B6=88=E8=A7=A3=E7=A6=81=EF=BC=9D=E3=80=94v11=E3=80=95?= =?UTF-8?q?=E3=80=8C=E8=A7=A3=E6=9E=90=E8=90=BD=E5=B8=A6=20=E2=89=A5=20?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E6=B7=B1=E5=BA=A6=E3=80=8D=E3=80=81=E5=A4=9A?= =?UTF-8?q?=E7=BB=88=E7=82=B9=20Boss=20=E5=B8=A6=E9=94=AE=E4=BA=92?= =?UTF-8?q?=E5=BC=82=EF=BC=9D=E3=80=94v11=E3=80=95=E9=80=90=E7=BB=88?= =?UTF-8?q?=E7=82=B9=E5=B8=A6=E9=94=AE=E4=B8=A4=E4=B8=A4=E6=AF=94=E5=AF=B9?= =?UTF-8?q?=EF=BC=88=E6=8B=A6=E6=88=AA=E9=9D=A2=E4=BB=8E=E8=A1=A8=E5=86=85?= =?UTF-8?q?=E9=9D=99=E6=80=81=E4=BB=A3=E7=90=86=E5=8D=87=E7=BA=A7=E5=88=B0?= =?UTF-8?q?=E6=B6=88=E8=B4=B9=E5=9B=BE=E6=9C=AC=E4=BD=93=EF=BC=8C=E6=97=A0?= =?UTF-8?q?=E5=89=8A=E5=BC=B1=EF=BC=89=EF=BC=9B=E5=85=B6=E5=8D=95=E6=B5=8B?= =?UTF-8?q?=E6=AE=B5=20=5Ftest=5Flevel=5Fnode=5Fband=5Frules=20=E4=B8=8E?= =?UTF-8?q?=E9=9A=8F=E5=AE=83=E8=80=8C=E5=AD=98=E7=9A=84=E5=9B=9B=E4=B8=AA?= =?UTF-8?q?=20preload=20=E4=B8=80=E5=B9=B6=E9=80=80=E5=BD=B9=EF=BC=88fv15/?= =?UTF-8?q?fv16/fv17=20=E4=B8=89=E6=9D=A1=E5=88=A4=E6=8D=AE=E7=9A=84?= =?UTF-8?q?=E7=8E=B0=E5=BD=B9=E7=AD=89=E4=BB=B7=E9=9D=A2=E5=9C=A8=20level?= =?UTF-8?q?=5Fshop=5Feconomy=E3=80=94v11=E3=80=95=E4=B8=8E=20level=5Ftable?= =?UTF-8?q?=5Fvalidate=E3=80=94v1=E3=80=95=EF=BC=89=20-=20=E8=A6=86?= =?UTF-8?q?=E7=9B=96=E4=B8=8D=E9=99=8D=E7=BA=A7=E7=9A=84=E4=BE=9D=E6=8D=AE?= =?UTF-8?q?=EF=BC=9A=E7=94=9F=E6=88=90=E5=9B=BE=E6=B7=B1=E5=BA=A6=E5=9F=9F?= =?UTF-8?q?=E5=AE=9E=E6=B5=8B=20L1=200=E2=80=936=EF=BC=8FL2=200=E2=80=939?= =?UTF-8?q?=EF=BC=8FL3=200=E2=80=9313=20=E4=B8=BA=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E6=B1=A0=E6=B7=B1=E5=BA=A6=E5=9F=9F=EF=BC=880=E2=80=934?= =?UTF-8?q?=EF=BC=8F0=E2=80=939=EF=BC=8F0=E2=80=939=EF=BC=89=E7=9A=84?= =?UTF-8?q?=E8=B6=85=E9=9B=86=EF=BC=8C=E4=B8=94=20=E3=80=94v11=E3=80=95?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E9=9D=A2=E4=B8=89=E6=A1=A3=C3=9720=20?= =?UTF-8?q?=E7=A7=8D=E5=AD=90=E9=80=90=E8=8A=82=E7=82=B9=E6=A0=B8=E6=95=8C?= =?UTF-8?q?=E5=B8=A6=E4=B8=8E=E6=9D=BF=E7=B2=BE=E7=A1=AE=E8=A1=8C=20-=20sa?= =?UTF-8?q?ve=5Fmanager=20=E9=A6=96=E6=AC=A1=E5=86=92=E9=99=A9=E4=BD=8D?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E9=9A=8F=E7=A1=AE=E5=AE=9A=E6=80=A7=E6=89=BF?= =?UTF-8?q?=E8=BD=BD=E6=8D=A2=E4=BB=A3=E6=94=B9=E5=8F=A3=E5=BE=84=EF=BC=88?= =?UTF-8?q?=E9=A6=96=E5=B1=80=E5=9B=BA=E5=AE=9A=E7=A7=8D=E5=AD=90=2042?= =?UTF-8?q?=EF=BC=8B=E5=8F=82=E6=95=B0=E6=A1=A3=E7=94=9F=E6=88=90=E5=9B=BE?= =?UTF-8?q?=EF=BC=8C=E6=97=A0=E5=85=9C=E5=BA=95=E6=A8=A1=E6=9D=BF=EF=BC=89?= =?UTF-8?q?=EF=BC=8C=E9=94=AE=E4=B8=8E=E8=A1=8C=E4=B8=BA=E9=9B=B6=E6=94=B9?= =?UTF-8?q?=E5=8A=A8=20-=20run=5Fall=2059=20=E5=A5=97=E4=BB=B6=E5=85=A8?= =?UTF-8?q?=E7=BB=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/config_db_cross_validator.gd | 54 +---------------- game/core/save_manager.gd | 7 ++- .../core/config_db_cross_validator_test.gd | 59 ------------------- 3 files changed, 5 insertions(+), 115 deletions(-) diff --git a/game/core/config_db_cross_validator.gd b/game/core/config_db_cross_validator.gd index 7bb4b95f..4e7d8912 100644 --- a/game/core/config_db_cross_validator.gd +++ b/game/core/config_db_cross_validator.gd @@ -51,60 +51,8 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void: for s in required_sources: if not st_tab.source_label_map.has(s): push_diag.call("error", "segorder_violation", "source_label_map 缺必需键: %s" % s) - # ④ LevelTable 节点悬空引用(battle/boss 节点:board_gen 侧须有精确行;enemy 侧按 - # (关卡序号, 行类) 带寻址——落带即设计语义,节点深度高于顶带键=顶 clamp 消解属调参事故,加载期直接拦。 - # 同关同一张图多终点 Boss 节点解析带键须互异(共带=两终点命中同一 Boss 行集,「走哪条路打谁」落空)) - var lv: LevelTable = loaded.get("level") - var bg: BoardGenTable = loaded.get("board_gen") - var en: EnemyTable = loaded.get("enemy") - if lv != null and bg != null and en != null: - var bg_keys := {} - for row in bg.rows: - bg_keys["%d_%d" % [int(row.level_index), int(row.node_depth)]] = true - var en_bands := {} # "%d_%s" % [L, row_kind] → 升序带键数组 - for row in en.rows: - var ek := "%d_%s" % [int(row.level_index), String(row.get("row_kind", ""))] - if not en_bands.has(ek): - en_bands[ek] = [] - var eb := int(row.get("depth_band", -1)) - if not (en_bands[ek] as Array).has(eb): - (en_bands[ek] as Array).append(eb) - for ek in en_bands.keys(): - (en_bands[ek] as Array).sort() - for lrow in lv.rows: - var li: int = int(lrow.level_index) - # 模板池感知(关卡节点图生成 v0.4——池内全模板跨表校验,不只 row.nodes) - var node_lists: Array[Array] = [] - var tpls: Array[Variant] = lrow.get("templates", []) - if tpls.is_empty(): - node_lists.append(lrow.get("nodes", [])) - else: - for tpl in tpls: - node_lists.append(tpl.get("nodes", [])) - for tpl_i in range(node_lists.size()): - var boss_bands := {} # 本图已占 Boss 带键 - for node in node_lists[tpl_i]: - var t: String = String(node.get("type", "")) - if t != "battle" and t != "boss": - continue - var nd: int = int(node.get("node_depth", -1)) - var key := "%d_%d" % [li, nd] - if not bg_keys.has(key): - push_diag.call("error", "level_node_dangling", "level %d %s 节点 (%s) 在 board_gen 无精确行" % [li, t, key]) - var nk := "%d_%s" % [li, t] - if not en_bands.has(nk): - push_diag.call("error", "level_node_dangling", "level %d %s 节点 (%d) 在 enemy 无该行类行集" % [li, t, nd]) - continue - var hit_band: int = EnemyTable.band_of(en_bands[nk], nd) - if hit_band < 0: - push_diag.call("error", "level_node_dangling", "level %d %s 节点深度 %d 高于 (level %d, %s) 顶带键 %d——顶 clamp 消解" % [ - li, t, nd, li, t, int((en_bands[nk] as Array)[-1])]) - continue - if t == "boss": - if boss_bands.has(hit_band): - push_diag.call("error", "rowset_rule", "level %d 模板 %d 多终点 Boss 节点同带(band %d)——共带使「走哪条路打谁」落空" % [li, tpl_i, hit_band]) - boss_bands[hit_band] = true # ⑤ 技能池引用完整性(敌人技能池批——敌人表池 × 技能表;独立入口 validate_skill_pool_links 供冒烟直验) + var en: EnemyTable = loaded.get("enemy") var skt = loaded.get("skill") if skt != null and en != null: for e in validate_skill_pool_links(skt, en, loaded.get("status_effects")): diff --git a/game/core/save_manager.gd b/game/core/save_manager.gd index 01e0dc26..ef3f9f86 100644 --- a/game/core/save_manager.gd +++ b/game/core/save_manager.gd @@ -299,9 +299,10 @@ func _default_data() -> Dictionary[String, Variant]: } -# ---- 首次冒险布尔位(关卡节点图生成 v0.4)---- -# false = 首次脚本模式(全关零抽签取池内模板 0=现行固定图);置位只在冒险收束挂点(MetaProgression.settle_adventure)。 -# 老档迁移:缺字段 = 该档已有游玩史 → true(不重放教学脚本)。 +# ---- 首次冒险布尔位(关卡节点图生成 v1.1 确定性承载)---- +# false = 本档尚无游玩史 → 首局走固定种子 42 + 参数档生成图(教学与存档复现的固定图由此承载,无兜底模板); +# 置位只在冒险收束挂点(MetaProgression.settle_adventure)。 +# 老档迁移:缺字段 = 该档已有游玩史 → true(不重放首局固定图)。 func is_first_adventure_done() -> bool: return bool(_data["meta"].get("first_adventure_done", true)) diff --git a/game/tests/unit/core/config_db_cross_validator_test.gd b/game/tests/unit/core/config_db_cross_validator_test.gd index f3fac232..e2b7731c 100644 --- a/game/tests/unit/core/config_db_cross_validator_test.gd +++ b/game/tests/unit/core/config_db_cross_validator_test.gd @@ -16,7 +16,6 @@ func _init() -> void: _test_all_links_valid() _test_dangling_skill_id() _test_cast_side_mismatch() - _test_level_node_band_rules() quit(Kit.finish("config_db_cross_validator_test")) @@ -83,61 +82,3 @@ func _test_cast_side_mismatch() -> void: var status := _status([{"effect_id": "st_hero_side", "target_side": "hero"}]) var errs: Array = ValidatorScript.new().validate_skill_pool_links(skills, enemies, status) Kit.check("buff 挂主角侧状态被报(施放类 side 守卫)", _cats(errs).has("cast_side")) - - -# ---- ④ 节点 × 敌人表带寻址(单终点零误报/同带双 Boss 报错/顶 clamp 消解报错)---- -const LevelTableScript := preload("res://data/schemas/level_table.gd") -const BoardGenTableScript := preload("res://data/schemas/board_gen_table.gd") -const RewardTableScript := preload("res://data/schemas/reward_table.gd") -const SettlementTableScript := preload("res://data/schemas/settlement_table.gd") - - -func _loaded(level_nodes: Array, bg_depths: Array, boss_bands: Array, battle_bands: Array) -> Dictionary: - var lv := LevelTableScript.new() - lv.rows.assign([{"level_index": 1, "display_name": "夹具关", "templates": [{"nodes": level_nodes}]}]) - var bg := BoardGenTableScript.new() - var bg_rows: Array = [] - for d in bg_depths: - bg_rows.append({"level_index": 1, "node_depth": int(d), "board_width": 10}) - bg.rows.assign(bg_rows) - var en := EnemyTableScript.new() - var en_rows: Array = [] - for b in battle_bands: - en_rows.append({"level_index": 1, "row_kind": "battle", "depth_band": int(b), "enemy_id": "goblin", - "unit_count": 1, "hp": 10, "atk": 2, "pool": [{"skill_id": "attack", "weight": 9}]}) - for b in boss_bands: - en_rows.append({"level_index": 1, "row_kind": "boss", "depth_band": int(b), "enemy_id": "ogre_chief", - "unit_count": 1, "hp": 10, "atk": 2, "pool": [{"skill_id": "attack", "weight": 9}]}) - en.rows.assign(en_rows) - var st := SettlementTableScript.new() - st.source_label_map.assign({"safe_cell": "a", "seal_hit": "b", "mine_hit": "c", "clear_reward": "d", - "hero_attack": "e", "enemy_action": "f", "seal_miss": "g"}) - return {"status_effects": StatusTableScript.new(), "reward": RewardTableScript.new(), - "settlement": st, "level": lv, "board_gen": bg, "enemy": en} - - -func _cross_cats(loaded: Dictionary, needle: String) -> Array[String]: - var out: Array[String] = [] - ValidatorScript.new().cross_validate(loaded, - func(_sev, cat, msg) -> void: - if String(msg).contains(needle): - out.append(String(cat))) - return out - - -func _test_level_node_band_rules() -> void: - # 正向:单终点三关同构(battle 节点落 band 0、boss 节点精确落顶带)→ ④ 零误报 - var ok_nodes := [{"type": "battle", "node_depth": 0}, {"type": "boss", "node_depth": 9}] - var ok_cats := _cross_cats(_loaded(ok_nodes, [0, 9], [9], [0]), "level 1") - Kit.check("单终点图 ④ 敌侧带寻址零误报〔fv17〕", ok_cats.is_empty()) - # 负向:一路两终点落同一 Boss 带(深度 3/4 都落带 4)→ rowset_rule - var same_band_nodes := [{"type": "battle", "node_depth": 0}, - {"type": "boss", "node_depth": 3}, {"type": "boss", "node_depth": 4}] - var same_band_cats := _cross_cats(_loaded(same_band_nodes, [0, 3, 4], [4, 9], [0]), "同带") - Kit.check("同关双终点共带报 rowset_rule〔fv15〕", - same_band_cats.has("rowset_rule")) - # 负向:Boss 节点深度高于顶带键(顶 clamp 消解)→ level_node_dangling - var over_nodes := [{"type": "battle", "node_depth": 0}, {"type": "boss", "node_depth": 10}] - var over_cats := _cross_cats(_loaded(over_nodes, [0, 10], [9], [0]), "顶 clamp 消解") - Kit.check("Boss 节点越顶带报 level_node_dangling〔fv16〕", - over_cats.has("level_node_dangling"))