merge: T17反馈批收官——硬锁v3.1点击重放+血量/选路修复平反恢复+减动效按钮语义化+出度≤2全套(第七检+十模板重建+层宽4+文档)+probe适配
This commit is contained in:
commit
c9075f70fa
10 files changed
+220
-28
No files matched your search
@@ -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()
|
||||
Reference in new issue
Block a user