merge: T17反馈批收官——硬锁v3.1点击重放+血量/选路修复平反恢复+减动效按钮语义化+出度≤2全套(第七检+十模板重建+层宽4+文档)+probe适配

This commit is contained in:
DKChan committed 2026-10-02 08:11:23 +08:00
commit c9075f70fa
10 files changed
+220 -28

No files matched your search

+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""T17 出度≤2 模板池重建器(2026-10-01 用户拍板「节点出度最大为 2」——一次性内容批工具)。
背景:现行 L2/L3 十张网状模板存在出度 3-5 的节点(2026-09-29 层宽 4-5 规则的副作用——
无出边上限约束时「前层广连全层」是层宽加宽的几何必然)。用户 T17 目检重申「一个节点
只有后续两个分叉」→本工具按新规则全池重生成 L2/L3 十张:
新规则两条件:
① 单节点出度 ≤2(用户 2026-10-01 拍板);
② BFS 层宽上限随出度约束调整为 4(数学:入口唯一下层宽序列 ≤1,2,4——原「上限 5」与
出度 ≤2 不相容:层 2 达 5 宽需前层 3 节点各出 2 边,回溯入口即违 ①)。
五张骨架(层宽序列 1-2-4-1-1 / 1-2-3-2-1 / 1-2-2-2-1——最大层宽 4 应用足):
类型序列保持现行(depth 0-9 = b,b,reward,rest,b,b,b,b,shop,boss——五张原表全同),
仅重连 next。L1 五链出度恒 1 零改动。
自校验(python 侧四检):出度 ≤2 / 层宽 ≤4 / 全节点可达 Boss / 入口唯一。
Godot 端 LevelTable.validate(含本批新增第七检出度检)为最终闸。
"""
import json
import re
from collections import deque
TRES = "game/data/tables/level.tres"
# 五骨架:depth → (type, next)——类型序列与现行五张逐 depth 一致
TYPE_SEQ = {0: "battle", 1: "battle", 2: "reward", 3: "rest", 4: "battle",
5: "battle", 6: "battle", 7: "battle", 8: "shop", 9: "boss"}
SKELETONS = {
"A": {0: [1, 2], 1: [3, 4], 2: [5, 6], 3: [7], 4: [7], 5: [7], 6: [7], 7: [8], 8: [9], 9: []},
"B": {0: [1, 2], 1: [3, 5], 2: [4, 5], 3: [6], 4: [6, 7], 5: [7], 6: [8], 7: [8], 8: [9], 9: []},
"C": {0: [1, 2], 1: [3, 5], 2: [4, 6], 3: [7], 4: [7], 5: [7], 6: [7], 7: [8], 8: [9], 9: []},
"D": {0: [1, 2], 1: [3, 4], 2: [4, 5], 3: [6, 7], 4: [7], 5: [7], 6: [8], 7: [8], 8: [9], 9: []},
"E": {0: [1, 2], 1: [3, 4], 2: [5], 3: [5, 6], 4: [6], 5: [7], 6: [7], 7: [8], 8: [9], 9: []},
}
# L2/L3 模板名 → 骨架(「(现行)」后缀钉模板 0 断言锚,随 A 位保持;
# L3 首张名「五岔主竖井」与 L2「五岔环廊」同构=w0 复制,同映射 A)
NAME_TO_SKEL = [
("五岔环廊", "A"), ("主竖井", "A"), ("四岔宽门", "B"), ("深层四岔", "C"),
("宽门赏后置", "D"), ("双干汇流", "E"),
]
NEW_NAMES = {
"A": "宽廊双干(现行)", "B": "三干中廊", "C": "宽廊斜岔",
"D": "三干宽门", "E": "窄廊短支",
}
def build_nodes(skel):
nodes = []
for d in range(10):
n = {"node_depth": d, "type": TYPE_SEQ[d], "next": list(SKELETONS[skel][d])}
nodes.append(n)
return nodes
def validate(nodes, label):
"""python 侧四检:出度 / 层宽 / 可达 Boss / 入口唯一。"""
errs = []
outs = {n["node_depth"]: n["next"] for n in nodes}
for d, nx in outs.items():
if len(nx) > 2:
errs.append("%s 节点 %d 出度 %d > 2" % (label, d, len(nx)))
# 层宽(BFS)
layer = {0: 0}
q = deque([0])
widths = {}
while q:
cur = q.popleft()
for t in outs.get(cur, []):
if t not in layer:
layer[t] = layer[cur] + 1
q.append(t)
for d, lv in layer.items():
widths[lv] = widths.get(lv, 0) + 1
for lv, w in widths.items():
if w > 4:
errs.append("%s 层 %d 宽 %d > 4" % (label, lv, w))
# 可达 9
seen = set(layer.keys())
if 9 not in seen:
errs.append("%s Boss 9 不可达" % label)
if len(seen) != len(nodes):
errs.append("%s 有节点不可达:%s" % (label, sorted(set(outs) - seen)))
# 入口唯一(无入边恰一 = 0)
inbound = {t for nx in outs.values() for t in nx}
entries = [d for d in outs if d not in inbound]
if entries != [0]:
errs.append("%s 入口非唯一/非 0:%s" % (label, entries))
return errs
def rebuild_line(line, level_tag):
"""重写一行 level 的 templates(类型序列保持、next 按骨架重连)。"""
obj = json.loads(line.rstrip().rstrip(","))
new_templates = []
for tpl in obj["templates"]:
old_name = tpl["display_name"]
skel = None
for key, s in NAME_TO_SKEL:
if key in old_name:
skel = s
break
if skel is None:
raise SystemExit("%s: 未知模板名 %s" % (level_tag, old_name))
# 类型序列保持校验(现行表与本工具 TYPE_SEQ 必须逐 depth 一致)
for n in tpl["nodes"]:
d = n["node_depth"]
if n["type"] != TYPE_SEQ[d]:
raise SystemExit("%s/%s depth %d 类型漂移:表 %s vs 工具 %s"
% (level_tag, old_name, d, n["type"], TYPE_SEQ[d]))
new_templates.append({"display_name": NEW_NAMES[skel], "nodes": build_nodes(skel)})
obj["templates"] = new_templates
errs = []
for tpl in new_templates:
errs += validate(tpl["nodes"], "%s/%s" % (level_tag, tpl["display_name"]))
if errs:
raise SystemExit("自校验失败:\n" + "\n".join(errs))
return json.dumps(obj, ensure_ascii=False)
def main():
with open(TRES, "r", encoding="utf-8") as f:
content = f.read()
out_lines = []
lines = content.splitlines()
for i, line in enumerate(lines):
stripped = line.strip()
if stripped.startswith('{"level_index"'):
m = re.search(r'"level_index": (\d+)', stripped)
if m and m.group(1) in ("2", "3"):
rebuilt = rebuild_line(line, m.group(1))
had_comma = line.rstrip().endswith(",") # 行尾逗号语义保持(tres 行序列)
out_lines.append(rebuilt + ("," if had_comma else ""))
continue
out_lines.append(line)
with open(TRES, "w", encoding="utf-8", newline="\n") as f:
f.write("\n".join(out_lines) + "\n")
print("[t17_fanout2] L2/L3 十张模板已按出度≤2/层宽≤4 重建,自校验四检全过")
if __name__ == "__main__":
main()