diff --git a/CLAUDE.md b/CLAUDE.md index e169de63..f49fb3b4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,7 +32,7 @@ - `design/registry/` — 跨系统事实登记(entities.yaml 术语与已锁数值、architecture.yaml 架构登记) - `design/art/` — 美术**纯文档**:定稿规范 md(圣经/清单/提示词卡)+ `references/` 参考池;评估/调研 md 一律 `design/research/`;**不放任何图片** - `design/dev/` — 工程规范:code-conventions / doc-conventions / architecture(ADR) / engine-reference -- `design/qa/` — 验证报告 md(balance-verifier/playtest/等价证明;raw 数据归 `sim-data/`) +- `design/qa/` — 验证报告 md(balance-verifier/playtest/等价证明;raw 数据归 `sim-data/`)+QA 呈现产物图(目检预览拼图等——2026-09-29 迭代 5 返工批收编图像白名单) - `design/research/` — 调研/提案/评估记录/历史验收/执行工单 - `artwork/` — **在途图像素材唯一工作区**(2026-09-27 用户三层裁定:文档归文档/在途图归一处/终稿归游戏):生图候选批次(每批一目录+每资产一目录+vN 命名)、像素稿、样张渲染、实机截图全在这里;图不入 git(md 指引例外);收口归档 `archive/` - `game/assets/art/` — 已验收终稿(入包被游戏加载) diff --git a/_commit_rework.sh b/_commit_rework.sh new file mode 100644 index 00000000..e5c2ee4d --- /dev/null +++ b/_commit_rework.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# 返工批 worktree 提交 +set -e +cd /d/code/g-plan2-iter5-rework +git add -A +git commit -m "rework(iter5): U2返工批 T-A/T-B/T-C——C1两刀终态(percent回退5)+AC v1硬梯度重投(重击手8/重装手9/Boss 8/10/12,杂鱼按T4§一软梯度代拟恢复)+registry三处同步;节点图池按U2两规则全池重生成(L1前三位纯战斗/L2L3层宽4-5,模板0随重排名保持现行后缀)+level_table两规则加载校验+钉固断言期望随新图7处更新;完美L1新杠杆提案(子路②荆棘外延推荐)落design/research;新15张预览图+渲染器两修(纵向自适应/拼图3x5);熵增闸design/qa图像白名单收编(CLAUDE.md同批);冒烟576/0+五探针PASS+doc三闸零新增+熵增闸PASS" +git log --oneline -1 diff --git a/design/qa/t5-skeleton-preview2-20260929/0-目检清单.md b/design/qa/t5-skeleton-preview2-20260929/0-目检清单.md new file mode 100644 index 00000000..803c14c2 --- /dev/null +++ b/design/qa/t5-skeleton-preview2-20260929/0-目检清单.md @@ -0,0 +1,19 @@ +# T5 骨架 15 张目检清单(U2 人闸第三件,2026-09-29) + +数据源=game/data/tables/level.tres;逐张给「过/不过+原因」,回填记台账。 + +- [ ] L1-模板0-地精洞窟主线洞道现行.png(地精洞窟·主线洞道(现行)) +- [ ] L1-模板1-地精洞窟先赏后整.png(地精洞窟·先赏后整) +- [ ] L1-模板2-地精洞窟先整后赏.png(地精洞窟·先整后赏) +- [ ] L1-模板3-地精洞窟赏战整.png(地精洞窟·赏战整) +- [ ] L1-模板4-地精洞窟整战赏.png(地精洞窟·整战赏) +- [ ] L2-模板0-幽暗地域五岔环廊现行.png(幽暗地域·五岔环廊(现行)) +- [ ] L2-模板1-幽暗地域四岔宽门.png(幽暗地域·四岔宽门) +- [ ] L2-模板2-幽暗地域深层四岔.png(幽暗地域·深层四岔) +- [ ] L2-模板3-幽暗地域宽门赏后置.png(幽暗地域·宽门赏后置) +- [ ] L2-模板4-幽暗地域双干汇流.png(幽暗地域·双干汇流) +- [ ] L3-模板0-灰矮人矿城五岔主竖井现行.png(灰矮人矿城·五岔主竖井(现行)) +- [ ] L3-模板1-灰矮人矿城四岔宽门.png(灰矮人矿城·四岔宽门) +- [ ] L3-模板2-灰矮人矿城深层四岔.png(灰矮人矿城·深层四岔) +- [ ] L3-模板3-灰矮人矿城宽门赏后置.png(灰矮人矿城·宽门赏后置) +- [ ] L3-模板4-灰矮人矿城双干汇流.png(灰矮人矿城·双干汇流) diff --git a/design/qa/t5-skeleton-preview2-20260929/00-总览拼图.png b/design/qa/t5-skeleton-preview2-20260929/00-总览拼图.png new file mode 100644 index 00000000..18c696e1 Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/00-总览拼图.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/L1-模板0-地精洞窟主线洞道现行.png b/design/qa/t5-skeleton-preview2-20260929/L1-模板0-地精洞窟主线洞道现行.png new file mode 100644 index 00000000..7da8ed0a Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/L1-模板0-地精洞窟主线洞道现行.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/L1-模板1-地精洞窟先赏后整.png b/design/qa/t5-skeleton-preview2-20260929/L1-模板1-地精洞窟先赏后整.png new file mode 100644 index 00000000..604dec01 Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/L1-模板1-地精洞窟先赏后整.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/L1-模板2-地精洞窟先整后赏.png b/design/qa/t5-skeleton-preview2-20260929/L1-模板2-地精洞窟先整后赏.png new file mode 100644 index 00000000..6864bca5 Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/L1-模板2-地精洞窟先整后赏.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/L1-模板3-地精洞窟赏战整.png b/design/qa/t5-skeleton-preview2-20260929/L1-模板3-地精洞窟赏战整.png new file mode 100644 index 00000000..362189f7 Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/L1-模板3-地精洞窟赏战整.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/L1-模板4-地精洞窟整战赏.png b/design/qa/t5-skeleton-preview2-20260929/L1-模板4-地精洞窟整战赏.png new file mode 100644 index 00000000..5103771f Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/L1-模板4-地精洞窟整战赏.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/L2-模板0-幽暗地域五岔环廊现行.png b/design/qa/t5-skeleton-preview2-20260929/L2-模板0-幽暗地域五岔环廊现行.png new file mode 100644 index 00000000..e640f784 Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/L2-模板0-幽暗地域五岔环廊现行.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/L2-模板1-幽暗地域四岔宽门.png b/design/qa/t5-skeleton-preview2-20260929/L2-模板1-幽暗地域四岔宽门.png new file mode 100644 index 00000000..8ca59799 Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/L2-模板1-幽暗地域四岔宽门.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/L2-模板2-幽暗地域深层四岔.png b/design/qa/t5-skeleton-preview2-20260929/L2-模板2-幽暗地域深层四岔.png new file mode 100644 index 00000000..ba0b5762 Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/L2-模板2-幽暗地域深层四岔.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/L2-模板3-幽暗地域宽门赏后置.png b/design/qa/t5-skeleton-preview2-20260929/L2-模板3-幽暗地域宽门赏后置.png new file mode 100644 index 00000000..e71dfc13 Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/L2-模板3-幽暗地域宽门赏后置.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/L2-模板4-幽暗地域双干汇流.png b/design/qa/t5-skeleton-preview2-20260929/L2-模板4-幽暗地域双干汇流.png new file mode 100644 index 00000000..29c967ed Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/L2-模板4-幽暗地域双干汇流.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/L3-模板0-灰矮人矿城五岔主竖井现行.png b/design/qa/t5-skeleton-preview2-20260929/L3-模板0-灰矮人矿城五岔主竖井现行.png new file mode 100644 index 00000000..f1d4855e Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/L3-模板0-灰矮人矿城五岔主竖井现行.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/L3-模板1-灰矮人矿城四岔宽门.png b/design/qa/t5-skeleton-preview2-20260929/L3-模板1-灰矮人矿城四岔宽门.png new file mode 100644 index 00000000..08e25bb0 Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/L3-模板1-灰矮人矿城四岔宽门.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/L3-模板2-灰矮人矿城深层四岔.png b/design/qa/t5-skeleton-preview2-20260929/L3-模板2-灰矮人矿城深层四岔.png new file mode 100644 index 00000000..5642ecbf Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/L3-模板2-灰矮人矿城深层四岔.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/L3-模板3-灰矮人矿城宽门赏后置.png b/design/qa/t5-skeleton-preview2-20260929/L3-模板3-灰矮人矿城宽门赏后置.png new file mode 100644 index 00000000..971b2c26 Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/L3-模板3-灰矮人矿城宽门赏后置.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/L3-模板4-灰矮人矿城双干汇流.png b/design/qa/t5-skeleton-preview2-20260929/L3-模板4-灰矮人矿城双干汇流.png new file mode 100644 index 00000000..564c2d06 Binary files /dev/null and b/design/qa/t5-skeleton-preview2-20260929/L3-模板4-灰矮人矿城双干汇流.png differ diff --git a/design/qa/t5-skeleton-preview2-20260929/render_preview.py b/design/qa/t5-skeleton-preview2-20260929/render_preview.py new file mode 100644 index 00000000..4b14df65 --- /dev/null +++ b/design/qa/t5-skeleton-preview2-20260929/render_preview.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""T5 节点图骨架 15 张一次性目检预览(M-3 迭代 5 U2 人闸第三件,2026-09-29)。 + +数据源 = game/data/tables/level.tres(游戏内实际落表,非生成器重算)。 +产物 = 本目录 15 张单图 + 00-总览拼图.png + 0-目检清单.md。一次性工具,随产物留档可删。 +用法:python design/qa/t5-skeleton-preview-20260929/render_preview.py(仓库根执行) +""" +import json +import math +import os +import re +from PIL import Image, ImageDraw, ImageFont + +ROOT = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "..")) +TRES = os.path.join(ROOT, "game", "data", "tables", "level.tres") +FONT = os.path.join(ROOT, "game", "assets", "fonts", "ui_font_fusion_pixel_zh_hans.ttf") +OUT = os.path.dirname(os.path.abspath(__file__)) + +BG = (30, 27, 34) +INK = (240, 234, 220) +DIM = (150, 142, 132) +EDGE = (168, 158, 142) +TYPE_STYLE = { + "battle": {"label": "战斗", "fill": (62, 86, 113), "border": (120, 148, 180)}, + "reward": {"label": "奖励", "fill": (176, 127, 44), "border": (222, 178, 96)}, + "rest": {"label": "休整", "fill": (74, 124, 89), "border": (128, 186, 148)}, + "shop": {"label": "商店", "fill": (58, 110, 140), "border": (118, 172, 200)}, + "boss": {"label": "Boss", "fill": (122, 44, 72), "border": (226, 120, 150)}, +} + + +def parse_rows(path): + text = open(path, encoding="utf-8").read() + body = text.split("rows = [", 1)[1].rsplit("\n]", 1)[0] + rows = [] + depth, start = 0, None + for i, ch in enumerate(body): + if ch == "{": + if depth == 0: + start = i + depth += 1 + elif ch == "}": + depth -= 1 + if depth == 0 and start is not None: + rows.append(json.loads(body[start:i + 1])) + start = None + return rows + + +def f(size): + return ImageFont.truetype(FONT, size) + + +def arrow(d, p0, p1, color, w=3): + d.line([p0, p1], fill=color, width=w) + ang = math.atan2(p1[1] - p0[1], p1[0] - p0[0]) + L, W = 14, 5 + a1 = (p1[0] - L * math.cos(ang) + W * math.sin(ang), p1[1] - L * math.sin(ang) - W * math.cos(ang)) + a2 = (p1[0] - L * math.cos(ang) - W * math.sin(ang), p1[1] - L * math.sin(ang) + W * math.cos(ang)) + d.polygon([p1, a1, a2], fill=color) + + +def polyline_arrow(d, pts, color, w=3): + d.line(pts, fill=color, width=w) + ang = math.atan2(pts[-1][1] - pts[-2][1], pts[-1][0] - pts[-2][0]) + L, W = 14, 5 + a1 = (pts[-1][0] - L * math.cos(ang) + W * math.sin(ang), pts[-1][1] - L * math.sin(ang) - W * math.cos(ang)) + a2 = (pts[-1][0] - L * math.cos(ang) - W * math.sin(ang), pts[-1][1] - L * math.sin(ang) + W * math.cos(ang)) + d.polygon([pts[-1], a1, a2], fill=color) + + +def node_box(d, cx, cy, ntype, depth, big=False): + st = TYPE_STYLE[ntype] + w, h = (132, 72) if big else (112, 60) + x0, y0, x1, y1 = cx - w // 2, cy - h // 2, cx + w // 2, cy + h // 2 + d.rounded_rectangle([x0, y0, x1, y1], radius=10, fill=st["fill"], + outline=st["border"], width=4 if big else 2) + label = st["label"] + fnt = f(24 if big else 21) + bb = d.textbbox((0, 0), label, font=fnt) + d.text((cx - (bb[2] - bb[0]) // 2, cy - (bb[3] - bb[1]) // 2 - 4), label, font=fnt, fill=INK) + if depth is not None: + tag = "#%d" % depth + fnt2 = f(15) + bb2 = d.textbbox((0, 0), tag, font=fnt2) + tw, th = bb2[2] - bb2[0], bb2[3] - bb2[1] + bx, by = x0 + 2, y0 - 12 + d.rounded_rectangle([bx, by, bx + tw + 12, by + th + 8], radius=7, fill=BG, + outline=st["border"], width=1) + d.text((bx + 6, by + 4), tag, font=fnt2, fill=INK) + return (x0, y0, x1, y1) + + +def render_chain(name, level_name, nodes, path): + W, H = 1280, 320 + img = Image.new("RGB", (W, H), BG) + d = ImageDraw.Draw(img) + d.text((24, 18), "%s — %s" % (level_name, name), font=f(30), fill=INK) + d.text((24, 64), "单链:自左向右依次推进|#N = 遭遇表行(战斗深度序)|入口 = 最左", font=f(17), fill=DIM) + n = len(nodes) + xs = [140 + i * ((W - 280) // (n - 1)) for i in range(n)] + cy = 190 + boxes = [node_box(d, xs[i], cy, nd["type"], nd.get("node_depth"), + big=nd["type"] == "boss") for i, nd in enumerate(nodes)] + for i in range(n - 1): + arrow(d, (boxes[i][2] + 2, cy), (boxes[i + 1][0] - 3, cy), EDGE) + d.text((xs[0] - 62, cy + 46), "▶ 入口", font=f(17), fill=DIM) + img.save(path) + + +def render_graph(name, level_name, nodes, path): + ids = [nd["node_depth"] for nd in nodes] + inbound = {} + for nd in nodes: + for t in nd.get("next", []): + inbound.setdefault(t, []).append(nd["node_depth"]) + layer = {} + + def lay(i): + if i in layer: + return layer[i] + layer[i] = 0 if i not in inbound else max(lay(p) for p in inbound[i]) + 1 + return layer[i] + + for i in ids: + lay(i) + maxl = max(layer.values()) + W, H = 1280, 480 + img = Image.new("RGB", (W, H), BG) + d = ImageDraw.Draw(img) + d.text((24, 18), "%s — %s" % (level_name, name), font=f(30), fill=INK) + d.text((24, 64), "网状:箭头=可走的下一节点|#N = 遭遇表行(战斗深度)|入口 = 最左|商店→Boss 必经", + font=f(17), fill=DIM) + cols = {} + for i in sorted(ids, key=lambda x: (layer[x], x)): + cols.setdefault(layer[i], []).append(i) + xs = {l: 150 + l * ((W - 300) / max(maxl, 1)) for l in cols} + pos = {} + for l, members in cols.items(): + k = len(members) + for j, i in enumerate(members): + sp = min(120, 330 / max(k - 1, 1)) + y = 280 + (j - (k - 1) / 2.0) * (sp if k > 1 else 0) + pos[i] = (int(xs[l]), int(y)) + for nd in nodes: + i = nd["node_depth"] + for t in nd.get("next", []): + (x0, y0), (x1, y1) = pos[i], pos[t] + if x1 - x0 >= 120: + arrow(d, (x0 + 60, y0), (x1 - 62, y1), EDGE, w=3) + else: + bow = 40 if y1 >= y0 else -40 + pts = [(x0 + (x1 - x0) * u / 20.0, + y0 + (y1 - y0) * u / 20.0 + bow * math.sin(math.pi * u / 20.0)) + for u in range(21)] + polyline_arrow(d, pts, EDGE) + entry = [i for i in ids if i not in inbound][0] + for nd in nodes: + i = nd["node_depth"] + node_box(d, pos[i][0], pos[i][1], nd["type"], i, big=nd["type"] == "boss") + if i == entry: + d.text((pos[i][0] - 32, pos[i][1] + 42), "▶ 入口", font=f(17), fill=DIM) + img.save(path) + + +def main(): + rows = parse_rows(TRES) + assert len(rows) == 3, "rows=%d" % len(rows) + files = [] + for row in rows: + li = row["level_index"] + for ti, tpl in enumerate(row["templates"]): + nodes = tpl["nodes"] + fn = "L%d-模板%d-%s.png" % (li, ti, re.sub(r"[\\/:*?\"<>|()·]", "", tpl["display_name"])) + path = os.path.join(OUT, fn) + is_graph = any("next" in n for n in nodes) + if is_graph: + render_graph(tpl["display_name"], row["display_name"], nodes, path) + else: + render_chain(tpl["display_name"], row["display_name"], nodes, path) + files.append((li, ti, tpl["display_name"], fn)) + assert len(files) == 15, len(files) + CW, CH = 388, 178 + sheet = Image.new("RGB", (CW * 5 + 8, 40 + CH * 3), BG) + sd = ImageDraw.Draw(sheet) + sd.text((16, 8), "T5 骨架池总览(行=关 L1/L2/L3,列=模板 0-4;逐张单图见同目录)", font=f(22), fill=INK) + for li, ti, name, fn in files: + im = Image.open(os.path.join(OUT, fn)) + im.thumbnail((CW - 6, CH - 6)) + sheet.paste(im, (4 + ti * CW, 36 + (li - 1) * CH)) + sheet.save(os.path.join(OUT, "00-总览拼图.png")) + body = "\n".join("- [ ] %s(%s)" % (fn, name) for li, ti, name, fn in files) + open(os.path.join(OUT, "0-目检清单.md"), "w", encoding="utf-8").write( + "# T5 骨架 15 张目检清单(U2 人闸第三件,2026-09-29)\n\n" + "数据源=game/data/tables/level.tres;逐张给「过/不过+原因」,回填记台账。\n\n" + body + "\n") + print("rendered %d files + sheet" % len(files)) + for li, ti, name, fn in files: + print(" L%d-模板%d %s -> %s" % (li, ti, name, fn)) + + +if __name__ == "__main__": + main() diff --git a/design/registry/entities.yaml b/design/registry/entities.yaml index f918ee5b..c357d69c 100644 --- a/design/registry/entities.yaml +++ b/design/registry/entities.yaml @@ -104,8 +104,8 @@ entities: owner: design/gdd/systems/排雷循环/地形变异.md attributes: table: VariationTable(数据配置 承载,键 = variant_id;地形变异 回填 owner,2026-09-08 定稿回填;2026-09-20 首批类型定稿批字段集枚举化重写) - fields: variant_id(String 主键唯一)/ display_name / unlock_level(int [1,999],knob)/ scope(enum region|global,global 预留 难度曲线与Boss)/ effect(enum none|flat_damage|hide_number,词表可扩充)/ effect_value(int [1,9],仅 flat_damage 消费,加法核小整数 knob)/ trigger(enum on_open|on_number_shown,词表可扩充)/ placement(enum closed_only|any,词表可扩充)/ board_types(StringList,∅=通配,knob——2026-09-20 用户裁定由 biome_tag 更名对齐 board_type 词表)/ counter_item_ids(StringList,魔法道具 预留,非 knob——首批四行恒空,Alpha 反制批回填)/ 表头布置参数 percent_of_cells=4 · per_level_bonus=0 · pack_max=4(knob,难度曲线与Boss——2026-09-28 迭代 5 判读锁值批自 5/2 收带,读数 design/qa/m3-iter5-lever-scan-2026-09-28.md) - mvp_content: 六行两轴×三关(2026-09-20 首批类型定稿批四行 + **第三关内容批两行**)——thorn 荆棘丛(flat_damage/on_open/closed_only/dungeon/1)· vine 藤蔓缠绕(hide_number/on_number_shown/any/dungeon)· magma 岩浆(flat_damage/on_open/closed_only/underdark/1,登记名沿用——2026-09-28 迭代 5 判读锁值批自 2 收带)· ash 烬覆(hide_number/on_number_shown/any/underdark)· rockfall 落石(flat_damage/on_open/closed_only/deep_underdark/3——伤害型关间递进 1→2→3)· mine_dust 矿尘(hide_number/on_number_shown/any/deep_underdark/3,信息型);counter_items 恒空(反制归 Alpha 反制批);布量走既有公式(4%+per_level_bonus×0——2026-09-21 基数修正批:百分比基数=候选池格数〔安全格−初始展开区〕非总格数,percent 语义=占可挖格比例);行值 provisional 归 难度曲线与Boss;冰冻占位名退役 + fields: variant_id(String 主键唯一)/ display_name / unlock_level(int [1,999],knob)/ scope(enum region|global,global 预留 难度曲线与Boss)/ effect(enum none|flat_damage|hide_number,词表可扩充)/ effect_value(int [1,9],仅 flat_damage 消费,加法核小整数 knob)/ trigger(enum on_open|on_number_shown,词表可扩充)/ placement(enum closed_only|any,词表可扩充)/ board_types(StringList,∅=通配,knob——2026-09-20 用户裁定由 biome_tag 更名对齐 board_type 词表)/ counter_item_ids(StringList,魔法道具 预留,非 knob——首批四行恒空,Alpha 反制批回填)/ 表头布置参数 percent_of_cells=5 · per_level_bonus=0 · pack_max=4(knob,难度曲线与Boss——2026-09-28 迭代 5 判读锁值批自 5/2 收带〔读数 design/qa/m3-iter5-lever-scan-2026-09-28.md〕;2026-09-29 返工批 U2 推翻 C3 裁 C1 两刀,percent 自 4 回退 5——裁断 design/qa/m3-iter5-u2-gate-2026-09-29.md) + mvp_content: 六行两轴×三关(2026-09-20 首批类型定稿批四行 + **第三关内容批两行**)——thorn 荆棘丛(flat_damage/on_open/closed_only/dungeon/1)· vine 藤蔓缠绕(hide_number/on_number_shown/any/dungeon)· magma 岩浆(flat_damage/on_open/closed_only/underdark/1,登记名沿用——2026-09-28 迭代 5 判读锁值批自 2 收带)· ash 烬覆(hide_number/on_number_shown/any/underdark)· rockfall 落石(flat_damage/on_open/closed_only/deep_underdark/3——伤害型关间递进 1→2→3)· mine_dust 矿尘(hide_number/on_number_shown/any/deep_underdark/3,信息型);counter_items 恒空(反制归 Alpha 反制批);布量走既有公式(5%+per_level_bonus×0——2026-09-21 基数修正批:百分比基数=候选池格数〔安全格−初始展开区〕非总格数,percent 语义=占可挖格比例;2026-09-29 返工批回退 5%——C1 终态);行值 provisional 归 难度曲线与Boss;冰冻占位名退役 zero_special_casing: 连锁/数字计数/清图/旗/价目/结算零特判——既有系统行为不感知变异状态(排雷玩法 执行逻辑无变异分支;地形变异 Rule 3 的推论);读取方按 effect/trigger/placement 枚举路由(战斗结算 兑付段 / 呈现层 遮蔽回调),不按类型名分支 rule4_exemption: 定义表非进度成长表,键 = variant_id,进度语义承载于 unlock_level 列(数据配置 Rule 4 豁免,schema 注释承载) cross_field_validation: flat_damage ⇒ trigger=on_open ∧ placement=closed_only ∧ effect_value∈[1,9];hide_number ⇒ trigger=on_number_shown——VariationTable schema validate() 跨字段类别,违规加载期致命(2026-09-20 首批类型定稿批重立;旧 hide_numbers⇒safe_nonzero 闸随字段集退役) @@ -279,7 +279,7 @@ entities: atk: int [0,999](knob——attack 行为伤害基值;MVP 4 provisional 出处待查;0 合法不拒——2026-09-20 八型批去"纯伏击伤敌种"旧注) pool: Array of {skill_id, weight}(**2026-09-16 敌人技能池批——池模板**,定义层;weight ∈ (0,9999] knob、池内 skill_id 不重复;每池恰一行普通攻击 + 增益技 ≤1 + skill_id 悬空致命〔加载校验在 config_db.validate_skill_pool_links 跨表通道〕;实例池〔快照+冷却状态〕在 单位容器 spawn——三敌种建议池构成与权重不列于本 registry,读 data/tables/enemy.tres(provisional 归 难度曲线与Boss;EV 锚与含闸长期速率实测读数随验收批另测登记〔2026-09-18 指路化收口〕))。**历史(2026-09-16 退役)**:heavy_multiplier int [1,16](重击倍率归技能行 damage_mult)与 pattern StringList(行为环环数组)两字段随行为环整体退役——固定出招剧本废除 rowset_rule: 每行集恰一敌种(行集内 enemy_id 唯一 = 表结构校验,违规加载期致命)——同层多遭遇已由节点编号消解(2026-09-17 关卡节点地图 v1.0:node_depth 语义 = 关卡内唯一节点编号,同层不同节点各查各行、键结构零改动);同场多敌种(同一编号行集多行)行集校验放开归 敌人表行集多行批(混编伏击上限取值口径随 2026-09-20 上限删除消解)(战斗侧,排技能池实施批落地后;06-待办清单 敌人系统节同名条目单点登记);精英/Boss 不设编码位——Boss = 最大编号节点(图唯一终点)、精英 P2 内容承载(强敌行集) - mvp_content: **八敌种(2026-09-14 难度曲线与Boss 修订批转正 goblin / bugbear / ogre_chief 三敌种〔DND 更名批用户指令:grub→goblin / ambusher→bugbear / boss_guardian→ogre_chief〕;2026-09-17 分支节点图批新增 giant_spider / drider,本条 2026-09-18 补收编;2026-09-20 第三关内容批新增 duergar / hook_horror / duergar_king,本条随批补收编;敌人系统 物种集断言随批更新为 {goblin, bugbear, ogre_chief, giant_spider, drider, duergar, hook_horror, duergar_king})**:goblin(地精,armor=light)/ bugbear(熊地精,armor=ward)/ ogre_chief(食人魔酋长,armor=heavy)/ giant_spider(巨蜘蛛)/ drider(蛛化卓尔精灵,Boss = 最大编号节点图唯一终点)/ duergar(灰矮人,单体重甲中坚——重甲首次登台,armor=heavy)/ hook_horror(钩爪恐魔,群行捕食者,armor=light)/ duergar_king(灰矮人石君王,L3 Boss,重甲收束「轻甲→魔法甲→重甲」教学梯度)。技能池构成与权重、armor_type 行映射**不列于本 registry**——〔**2026-09-28 M-3 迭代 5 T4 AC 投值批**:armor_class 全表投值=杂鱼全 0·Boss 台阶 8/9/12(Boss-only 终版——v1 硬/v2 软梯度普通 L2 越下带挂账,演化链见 design/qa/m3-iter5-t4-ac-2026-09-28.md;普通 L2 17.1% 挂账与完美 L1 丙并列 U2 追认);冒烟新断言守带宽+台阶〕读 data/tables/enemy.tres(L3 行值语义:duergar 关间台阶 ≈L2 单体 ×1.5 落 [1.3,1.6] 域、hook_horror 复用 bite 共享技能行、两敌种首遇位〔节点 0/1〕固定数量不加骰、Boss 血 75 = Σ普通战生命期望 91 × r_boss≈0.82 provisional 锚;行值全 provisional 归 难度曲线与Boss)。**2026-09-17 数值压缩批·文档同步——hp/atk 等数值列改指路存根**:L1/L2 节点编号行集(D0–D8)共 12 行的 hp · atk · atk_min · atk_max · unit_count · unit_count_max · advantage_per_round_cap · armor_class 等现行值**不列于本 registry**——读 `data/tables/enemy.tres`(数值压缩批注释登记:`L1 D0 24/7 → 8/2`、`L1 D1 13/5 → 4/2`、`L1 D2 40/9 → 13/3`、`L1 D3 18/6 → 6/2`、`L1 D4 150/9 → 50/3` Boss、`L2 D0 30/7 → 10/2`、`L2 D1 15/5 → 5/2`、`L2 D4 14/5 → 5/2`、`L2 D5 38/8 → 13/3`、`L2 D6 12/4 → 4/1`、`L2 D7 13/5 → 4/2`、`L2 D8 175/8 → 58/3` drider);armor_type / armor_class / unit_count 区间 / 技能池权重 / advantage_per_round_cap 不压缩(相对轴与概率轴)。**2026-09-16 敌人技能池批**:行为环拍序列([attack] 单拍环 / [attack,charge,heavy] 三拍环 / [attack,attack,charge,heavy] 四拍环)与 heavy_multiplier=2 退役,改池模板 + 技能表共享技能行(重击倍率 2 = 技能行 damage_mult);出处 = 切片 enemy.tres(技能池批改型),曲线数值与池权重归 难度曲线与Boss + mvp_content: **八敌种(2026-09-14 难度曲线与Boss 修订批转正 goblin / bugbear / ogre_chief 三敌种〔DND 更名批用户指令:grub→goblin / ambusher→bugbear / boss_guardian→ogre_chief〕;2026-09-17 分支节点图批新增 giant_spider / drider,本条 2026-09-18 补收编;2026-09-20 第三关内容批新增 duergar / hook_horror / duergar_king,本条随批补收编;敌人系统 物种集断言随批更新为 {goblin, bugbear, ogre_chief, giant_spider, drider, duergar, hook_horror, duergar_king})**:goblin(地精,armor=light)/ bugbear(熊地精,armor=ward)/ ogre_chief(食人魔酋长,armor=heavy)/ giant_spider(巨蜘蛛)/ drider(蛛化卓尔精灵,Boss = 最大编号节点图唯一终点)/ duergar(灰矮人,单体重甲中坚——重甲首次登台,armor=heavy)/ hook_horror(钩爪恐魔,群行捕食者,armor=light)/ duergar_king(灰矮人石君王,L3 Boss,重甲收束「轻甲→魔法甲→重甲」教学梯度)。技能池构成与权重、armor_type 行映射**不列于本 registry**——〔**2026-09-28 M-3 迭代 5 T4 AC 投值批→2026-09-29 返工批 U2 裁 v1 重投**:初版 Boss-only(杂鱼全 0·Boss 8/9/12)被 U2 推翻——终版 **v1 硬梯度**=杂鱼按 T4 报告 §一 软梯度恢复(0~5 带内)+重击手 8/重装手 9(逐行手设值,非精英编码位——解读 A 追认)+Boss 台阶 8/10/12;v1 中间态未入 git,杂鱼行按报告表值代拟恢复(台账 247);演化链与三版读数见 design/qa/m3-iter5-t4-ac-2026-09-28.md,裁断全录 design/qa/m3-iter5-u2-gate-2026-09-29.md;冒烟带宽断言随新形态更新(杂鱼 0~5·重击手/重装手 8~9·Boss 8~12 台阶)〕读 data/tables/enemy.tres(L3 行值语义:duergar 关间台阶 ≈L2 单体 ×1.5 落 [1.3,1.6] 域、hook_horror 复用 bite 共享技能行、两敌种首遇位〔节点 0/1〕固定数量不加骰、Boss 血 75 = Σ普通战生命期望 91 × r_boss≈0.82 provisional 锚;行值全 provisional 归 难度曲线与Boss)。**2026-09-17 数值压缩批·文档同步——hp/atk 等数值列改指路存根**:L1/L2 节点编号行集(D0–D8)共 12 行的 hp · atk · atk_min · atk_max · unit_count · unit_count_max · advantage_per_round_cap · armor_class 等现行值**不列于本 registry**——读 `data/tables/enemy.tres`(数值压缩批注释登记:`L1 D0 24/7 → 8/2`、`L1 D1 13/5 → 4/2`、`L1 D2 40/9 → 13/3`、`L1 D3 18/6 → 6/2`、`L1 D4 150/9 → 50/3` Boss、`L2 D0 30/7 → 10/2`、`L2 D1 15/5 → 5/2`、`L2 D4 14/5 → 5/2`、`L2 D5 38/8 → 13/3`、`L2 D6 12/4 → 4/1`、`L2 D7 13/5 → 4/2`、`L2 D8 175/8 → 58/3` drider);armor_type / armor_class / unit_count 区间 / 技能池权重 / advantage_per_round_cap 不压缩(相对轴与概率轴)。**2026-09-16 敌人技能池批**:行为环拍序列([attack] 单拍环 / [attack,charge,heavy] 三拍环 / [attack,attack,charge,heavy] 四拍环)与 heavy_multiplier=2 退役,改池模板 + 技能表共享技能行(重击倍率 2 = 技能行 damage_mult);出处 = 切片 enemy.tres(技能池批改型),曲线数值与池权重归 难度曲线与Boss load_bearing_rules: 多敌模型(1–3 同敌种单位,增益状态 单敌假设回验不成立——见 status_effect.single_enemy_assumption)· 行为环确定性轮转(零随机;先作用后推进;死亡即不动手——轮到前被杀本拍不打指针冻结;同步轮转定理)· 效果 side-wide(原伏击池 = side 共享、增益状态 零接口变更——伏击池随 2026-09-20 八型批退役,side-wide 承载改由增益状态 alert 行承接)· 行值快照抗热重载(增益状态 EC9 同款——池模板同在快照范围,实例池冷却全零起步)· **技能池摇号出招(2026-09-16 敌人技能池批改写——原"行为环确定性轮转/同步轮转定理"退役)**:每拍做什么 = 实例池权重摇号〔开轮时点,随机消费在 战斗结算 换算层 behavior_roll 封闭流;本系统语义零随机不变、单位容器收确定值〕;两道闸 = 全技能各自冷却 + 保底 M;死亡即不动手(摇定后兑现前死亡 → 本拍作废不进账);拍结构保留(每结算每存活敌恰行动一次/行序/先作用后推进)· 零随机零 UI 零事件订阅(排雷玩法 事件面不含 敌人系统——事实经 战斗结算 转化) settlement_obligations: 六项(敌人系统 GDD 门禁评审缺项即 blocking)——① 行动每结算恰一次(敌单位专属——主角无环)+ 结算内调用序成文 按摇定技能行兑现→apply_damage→commit_action〔2026-09-16 技能池批改型——原 query_attack→apply_damage→advance 随行为环退役〕 ② 雷→伏击恰一次转化 ③ clamp/负值/溢杀与死亡判定时机成文 ④ F1 条款 3 闭合对账(cap 表值 + 量纲 + 棋盘生成 actuals p10 非 target 参数)⑤ 默认目标消费与单体/AOE 分流 ⑥ 伏击 cap 溢出"溢出未生效"明细行协议。**履行回执(2026-09-10)**:六项已全数落位 战斗结算 GDD(Core Rules 5/6/9/10/12 + Formulas F3/F4——落位映射 = 战斗结算 AC 覆盖矩阵)。**2026-09-20 修订**:④⑥ 随伏击单轮上限删除(用户拍板)退役——对账面去 cap 表值、溢出行协议作废;八型批再修订(2026-09-20 格面资源八型批):② 雷→伏击恰一次转化改 雷→警觉恰一次转化 hud_obligations: 四项(视觉推测型呈现,缺项即 blocking)——① enemy_id→图像线索映射清单成文(预告时序=下一拍预告、挖掘期全程可见;含强度档位/环长直觉编码说明;形状+色相双通道)② 映射全局恒定(enemy_id 键——跨 run 禁重掷)③ 顺位与存活可读 ④ 敌方 side 池呈现口径 diff --git a/design/research/perfect-l1-lever-proposal-2026-09-29.md b/design/research/perfect-l1-lever-proposal-2026-09-29.md new file mode 100644 index 00000000..4fc2418b --- /dev/null +++ b/design/research/perfect-l1-lever-proposal-2026-09-29.md @@ -0,0 +1,33 @@ +# 完美 L1 加难杠杆设计提案(M-3 迭代 5 返工批 T-B,2026-09-29) + +- **裁决来源**:U2 人闸 a 项推翻丙裁**甲=新杠杆授权**;待批点乙两子路按推荐取 **② L1 专属加难内容**(①敌方攻击基线上探会连坐普通档 L1——其落带余量仅 ~15 点,定向性差)。 +- **本提案状态**:**待用户批准**(行值不在代拍板白名单——批准前零投值)。 +- **目标**:完美档 L1 条件通关率 85–89% → 带内 [35,65];普通档 L1 保持带内 [35,55](现行读数 46–50%,余量 ~11–15 点)。 + +## 一、设计空间复盘(T3 扫描结论为底) + +完美 L1 的强势来源 = 10×10 板形 × 完美画像挖掘效率 × 奖励经济学,不在已注册旋钮的已扫量程内。L1 定向杠杆实测:荆棘 +1 = 完美 −7pp/普通 −2pp(**定向比 3.5:1,全网格最优**);酋长加血对完美无感(L1 Boss 胜率 98%);群战上探被校验域封死。 + +## 二、推荐方案:阶段一 = 荆棘 effect_value 扫描外延(零新注册) + +**关键发现**:荆棘(thorn)为 L1 dungeon 池**独占**变异类型(L2/L3 池无 thorn)——effect_value 天然就是 L1 专属杠杆,且 registry 域为 [1,9]、现行值 1。T3 扫描只试了 1→2(「弱」的结论是相对 −24pp 的目标缺口,不是旋钮本身弱)。 + +- **投值候选**:effect_value 1→3(预估完美 −14pp → ~75%,可能仍差带缘)或 1→4(预估完美 −21pp → ~68%,贴带上缘;普通 L1 预估 −4~−6pp → 44–46% 仍在带)。 +- **线性外推警示**:−7pp/格 的斜率按踩雷频次≈常数假设;实际挖掘策略会自适应(完美画像可能少开低价值邻格),真实读数须配对种子扫描实测,外推仅供立项参考。 +- **配套义务**:荆棘踩雷直伤加大后,与「挖掘风险不等式」风险端定价联动(该挂账本来就在册——风险端定价不足,本投值顺向收窄缺口);恢复数学(恢复概率 1/4 · 恢复量 1/6)不动。 +- **成本**:一次配对种子 OFAT + 组合扫描(对 C1+v1 现行表态),~1 小时;若 1→4 入带即收口,**零新内容、零新注册**。 + +## 三、备用方案:阶段二 = 新增 L1 专属变异类型(仅当阶段一不够) + +若荆棘拉到域内合理上限(建议 ≤5,超过后单格 5 伤在小血量池手感过罚)仍不达带,再立新杠杆——候选形态:**「塌顶」**(rockfall 的 L1 姊妹型:flat_damage on_open,dungeon 池新行,走杠杆注册+类型定稿批流程)。成本 = 新变异类型批(表行+呈现+反制预留),~1 个内容批。 + +## 四、否决面(不予推荐) + +- 敌方攻击基线上探(子路①):全敌轴,普通 L1 连坐超余量,且与 AC v1 重投互相叠加难以归因。 +- L1 Boss 血量/数量:完美对酋长胜率 98%,血量轴已证无感。 +- 板形回退(L1 恢复混编板):推翻棋盘统一批裁决,代价外溢。 + +## 五、批准点 + +1. 阶段一荆棘外延是否批准(批准即跑扫描,入带行值随批落表+registry 同步); +2. 若阶段一不足,阶段二「塌顶」是否立项(或接受完美 L1 残余越带挂账——届时带缺口数字已知,可再次三选一)。 diff --git a/game/data/schemas/level_table.gd b/game/data/schemas/level_table.gd index 20a10522..5ac5a397 100644 --- a/game/data/schemas/level_table.gd +++ b/game/data/schemas/level_table.gd @@ -91,6 +91,11 @@ func _validate_level_nodes(L: int, ctx: String, nodes: Array) -> Array: # 图合法性(单链与图同一套算法——单链 = 出边恰一的退化形态,违规同样加载致命) for e in _validate_graph(L, nodes, is_graph, tag): errs.append(e) + # U2 目检新规则(迭代 5 返工批 2026-09-29 两条,加载期同检):①单链前三位纯战斗(奖励/休整禁入前三) + if not is_graph and nodes.size() >= 3: + for i in 3: + if String(nodes[i].get("type", "")) != "battle": + errs.append({"category": "structural", "message": "level %d%s 第 %d 位非战斗(单链前三位纯战斗——U2 目检规则)" % [L, tag, i + 1]}) return errs @@ -169,6 +174,24 @@ func _validate_graph(L: int, nodes: Array, is_graph: bool, tag: String = "") -> for i in nodes.size(): if not to_boss.has(i): errs.append({"category": "structural", "message": "level %d 节点 %d 沿连线不可达 Boss" % [L, ids[i]]}) + # ⑧ U2 目检新规则(迭代 5 返工批 2026-09-29):BFS 同深度层节点数上限 5(分支宽度天花板) + var layer_of := {} + var bfs: Array = [entries[0]] + layer_of[entries[0]] = 0 + while not bfs.is_empty(): + var cur: int = bfs.pop_front() + for tid in nexts[cur]: + var tidx: int = int(id_to_idx.get(tid, -1)) + if tidx >= 0 and not layer_of.has(tidx): + layer_of[tidx] = int(layer_of[cur]) + 1 + bfs.append(tidx) + var width := {} + for i in nodes.size(): + if layer_of.has(i): + width[layer_of[i]] = int(width.get(layer_of[i], 0)) + 1 + for d in width.keys(): + if int(width[d]) > 5: + errs.append({"category": "structural", "message": "level %d BFS 第 %d 层 %d 节点(同层上限 5——U2 目检规则)" % [L, d, int(width[d])]}) return errs diff --git a/game/data/tables/enemy.tres b/game/data/tables/enemy.tres index fa35ff84..31d80670 100644 --- a/game/data/tables/enemy.tres +++ b/game/data/tables/enemy.tres @@ -43,7 +43,7 @@ rows = [{ "weight": 1 }], "armor_type": "light", -"armor_class": 0 +"armor_class": 1 }, { "level_index": 1, "node_depth": 2, @@ -65,7 +65,7 @@ rows = [{ "weight": 2 }], "armor_type": "light", -"armor_class": 0 +"armor_class": 3 }, { "level_index": 1, "node_depth": 3, @@ -88,7 +88,7 @@ rows = [{ "weight": 2 }], "armor_type": "light", -"armor_class": 0 +"armor_class": 2 }, { "level_index": 1, "node_depth": 4, @@ -132,7 +132,7 @@ rows = [{ "weight": 2 }], "armor_type": "light", - "armor_class": 0 + "armor_class": 1 }, { "level_index": 2, "node_depth": 1, @@ -155,7 +155,7 @@ rows = [{ "weight": 2 }], "armor_type": "light", -"armor_class": 0 +"armor_class": 2 }, { "level_index": 2, "node_depth": 4, @@ -177,7 +177,7 @@ rows = [{ "weight": 1 }], "armor_type": "light", -"armor_class": 0 +"armor_class": 2 }, { "level_index": 2, "node_depth": 5, @@ -199,7 +199,7 @@ rows = [{ "weight": 2 }], "armor_type": "light", -"armor_class": 0 +"armor_class": 8 }, { "level_index": 2, "node_depth": 6, @@ -222,7 +222,7 @@ rows = [{ "weight": 1 }], "armor_type": "light", -"armor_class": 0 +"armor_class": 2 }, { "level_index": 2, "node_depth": 7, @@ -245,14 +245,14 @@ rows = [{ "weight": 1 }], "armor_type": "light", -"armor_class": 0 +"armor_class": 3 }, { "level_index": 2, "node_depth": 9, "enemy_id": "drider", "display_name": "蛛化卓尔精灵", "unit_count": 1, - "hp": 100, + "hp": 130, "atk": 3, "atk_min": 2, "atk_max": 4, @@ -270,7 +270,7 @@ rows = [{ "weight": 2 }], "armor_type": "ward", -"armor_class": 9 +"armor_class": 10 }, { "level_index": 3, "node_depth": 0, @@ -292,7 +292,7 @@ rows = [{ "weight": 2 }], "armor_type": "heavy", -"armor_class": 0 +"armor_class": 2 }, { "level_index": 3, "node_depth": 1, @@ -314,7 +314,7 @@ rows = [{ "weight": 1 }], "armor_type": "light", -"armor_class": 0 +"armor_class": 3 }, { "level_index": 3, "node_depth": 4, @@ -337,7 +337,7 @@ rows = [{ "weight": 1 }], "armor_type": "light", -"armor_class": 0 +"armor_class": 3 }, { "level_index": 3, "node_depth": 5, @@ -359,7 +359,7 @@ rows = [{ "weight": 2 }], "armor_type": "heavy", -"armor_class": 0 +"armor_class": 9 }, { "level_index": 3, "node_depth": 6, @@ -382,7 +382,7 @@ rows = [{ "weight": 1 }], "armor_type": "light", -"armor_class": 0 +"armor_class": 3 }, { "level_index": 3, "node_depth": 7, @@ -405,7 +405,7 @@ rows = [{ "weight": 2 }], "armor_type": "heavy", -"armor_class": 0 +"armor_class": 4 }, { "level_index": 3, "node_depth": 9, @@ -563,3 +563,4 @@ rows = [{ ; r_boss 锚漂移加重 100/389≈0.26 低于域 [0.5,1.1] 下限,收锚裁决挂迭代 5 出口人闸;读数见 ; design/qa/m3-iter5-lever-scan-2026-09-28.md)。 ; M-3 迭代 5 T4 AC 投值批·终版 Boss-only(2026-09-28:v1 硬梯度普通 L2 14.3% 越带/v2 软梯度 15.7% 仍越带——杂鱼税对普通画像〔普通分量为主力输出〕系统性超压;终版=杂鱼全 0〔带 0~5 下缘,掷点戏剧性集中 Boss 战〕+Boss 台阶 8/9/12 严格递增;三版演化链读数见 design/qa/m3-iter5-t4-ac-2026-09-28.md,随出口人闸追认)。 +; M-3 迭代 5 返工批·U2 裁 v1 硬梯度重投(2026-09-29 用户推翻 Boss-only:重击手 8/重装手 9〔逐行手设值,非精英编码位——解读 A 追认,精英 P2 承载〕+Boss 台阶 8/10/12;杂鱼行按 T4 报告 §一 软梯度恢复〔v1 中间态未入 git,v1↔v2 唯一差异=重击手/重装手,代拟面记台账 247〕;卓尔 hp 100→130 随 C3 回退复原;组合读数 C1+v1 无先测,全量确认归返工批——裁断全录 design/qa/m3-iter5-u2-gate-2026-09-29.md)。 diff --git a/game/data/tables/level.tres b/game/data/tables/level.tres index 4473278a..a2b175fa 100644 --- a/game/data/tables/level.tres +++ b/game/data/tables/level.tres @@ -5,16 +5,20 @@ [resource] script = ExtResource("1") rows = [ -{"level_index": 1, "display_name": "第一层 · 地精洞窟", "rest_heal_pct": 30, "templates": [{"display_name": "地精洞窟·主线洞道(现行)", "nodes": [{"node_depth": 0, "type": "battle"}, {"node_depth": 1, "type": "battle"}, {"type": "reward"}, {"node_depth": 2, "type": "battle"}, {"node_depth": 3, "type": "battle"}, {"type": "rest"}, {"node_depth": 4, "type": "boss"}]}, {"display_name": "地精洞窟·休整前移", "nodes": [{"node_depth": 0, "type": "battle"}, {"node_depth": 1, "type": "battle"}, {"type": "rest"}, {"node_depth": 2, "type": "battle"}, {"type": "reward"}, {"node_depth": 3, "type": "battle"}, {"node_depth": 4, "type": "boss"}]}, {"display_name": "地精洞窟·先赏后战", "nodes": [{"node_depth": 0, "type": "battle"}, {"type": "reward"}, {"node_depth": 1, "type": "battle"}, {"type": "rest"}, {"node_depth": 2, "type": "battle"}, {"node_depth": 3, "type": "battle"}, {"node_depth": 4, "type": "boss"}]}, {"display_name": "地精洞窟·连战直入", "nodes": [{"node_depth": 0, "type": "battle"}, {"node_depth": 1, "type": "battle"}, {"type": "reward"}, {"type": "rest"}, {"node_depth": 2, "type": "battle"}, {"node_depth": 3, "type": "battle"}, {"node_depth": 4, "type": "boss"}]}, {"display_name": "地精洞窟·缓入长坡", "nodes": [{"node_depth": 0, "type": "battle"}, {"type": "rest"}, {"node_depth": 1, "type": "battle"}, {"type": "reward"}, {"node_depth": 2, "type": "battle"}, {"node_depth": 3, "type": "battle"}, {"node_depth": 4, "type": "boss"}]}]}, -{"level_index": 2, "display_name": "第二层 · 幽暗地域", "rest_heal_pct": 30, "templates": [{"display_name": "幽暗地域·四岔环廊(现行)", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2]}, {"node_depth": 1, "type": "battle", "next": [3, 4]}, {"node_depth": 2, "type": "reward", "next": [4, 5]}, {"node_depth": 3, "type": "rest", "next": [6]}, {"node_depth": 4, "type": "battle", "next": [6, 7]}, {"node_depth": 5, "type": "battle", "next": [7]}, {"node_depth": 6, "type": "battle", "next": [8]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "幽暗地域·奖励窄门", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2]}, {"node_depth": 1, "type": "battle", "next": [3, 4]}, {"node_depth": 2, "type": "reward", "next": [4]}, {"node_depth": 3, "type": "rest", "next": [5]}, {"node_depth": 4, "type": "battle", "next": [5, 6]}, {"node_depth": 5, "type": "battle", "next": [7]}, {"node_depth": 6, "type": "battle", "next": [8]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "幽暗地域·直入深层", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2]}, {"node_depth": 1, "type": "battle", "next": [4]}, {"node_depth": 2, "type": "reward", "next": [3, 4]}, {"node_depth": 3, "type": "rest", "next": [6]}, {"node_depth": 4, "type": "battle", "next": [5, 6]}, {"node_depth": 5, "type": "battle", "next": [7]}, {"node_depth": 6, "type": "battle", "next": [7]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "幽暗地域·休整远路", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2]}, {"node_depth": 1, "type": "battle", "next": [2, 4]}, {"node_depth": 2, "type": "reward", "next": [3]}, {"node_depth": 3, "type": "rest", "next": [6]}, {"node_depth": 4, "type": "battle", "next": [5, 6]}, {"node_depth": 5, "type": "battle", "next": [7]}, {"node_depth": 6, "type": "battle", "next": [8]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "幽暗地域·双线归束", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2]}, {"node_depth": 1, "type": "battle", "next": [3]}, {"node_depth": 2, "type": "reward", "next": [3, 4]}, {"node_depth": 3, "type": "rest", "next": [4, 5]}, {"node_depth": 4, "type": "battle", "next": [6]}, {"node_depth": 5, "type": "battle", "next": [6]}, {"node_depth": 6, "type": "battle", "next": [7]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}]}, -{"level_index": 3, "display_name": "第三层 · 灰矮人矿城", "rest_heal_pct": 30, "templates": [{"display_name": "灰矮人矿城·主竖井(现行)", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2]}, {"node_depth": 1, "type": "battle", "next": [3, 4]}, {"node_depth": 2, "type": "reward", "next": [4, 5]}, {"node_depth": 3, "type": "rest", "next": [6]}, {"node_depth": 4, "type": "battle", "next": [6, 7]}, {"node_depth": 5, "type": "battle", "next": [7]}, {"node_depth": 6, "type": "battle", "next": [8]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "灰矮人矿城·奖励窄门", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2]}, {"node_depth": 1, "type": "battle", "next": [3, 4]}, {"node_depth": 2, "type": "reward", "next": [4]}, {"node_depth": 3, "type": "rest", "next": [5]}, {"node_depth": 4, "type": "battle", "next": [5, 6]}, {"node_depth": 5, "type": "battle", "next": [7]}, {"node_depth": 6, "type": "battle", "next": [8]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "灰矮人矿城·直入深层", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2]}, {"node_depth": 1, "type": "battle", "next": [4]}, {"node_depth": 2, "type": "reward", "next": [3, 4]}, {"node_depth": 3, "type": "rest", "next": [6]}, {"node_depth": 4, "type": "battle", "next": [5, 6]}, {"node_depth": 5, "type": "battle", "next": [7]}, {"node_depth": 6, "type": "battle", "next": [7]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "灰矮人矿城·休整远路", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2]}, {"node_depth": 1, "type": "battle", "next": [2, 4]}, {"node_depth": 2, "type": "reward", "next": [3]}, {"node_depth": 3, "type": "rest", "next": [6]}, {"node_depth": 4, "type": "battle", "next": [5, 6]}, {"node_depth": 5, "type": "battle", "next": [7]}, {"node_depth": 6, "type": "battle", "next": [8]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "灰矮人矿城·双线归束", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2]}, {"node_depth": 1, "type": "battle", "next": [3]}, {"node_depth": 2, "type": "reward", "next": [3, 4]}, {"node_depth": 3, "type": "rest", "next": [4, 5]}, {"node_depth": 4, "type": "battle", "next": [6]}, {"node_depth": 5, "type": "battle", "next": [6]}, {"node_depth": 6, "type": "battle", "next": [7]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}]} +{"level_index": 1, "display_name": "第一层 · 地精洞窟", "rest_heal_pct": 30, "templates": [{"display_name": "地精洞窟·主线洞道(现行)", "nodes": [{"node_depth": 0, "type": "battle"}, {"node_depth": 1, "type": "battle"}, {"node_depth": 2, "type": "battle"}, {"node_depth": 3, "type": "battle"}, {"type": "reward"}, {"type": "rest"}, {"node_depth": 4, "type": "boss"}]}, {"display_name": "地精洞窟·先赏后整", "nodes": [{"node_depth": 0, "type": "battle"}, {"node_depth": 1, "type": "battle"}, {"node_depth": 2, "type": "battle"}, {"type": "reward"}, {"type": "rest"}, {"node_depth": 3, "type": "battle"}, {"node_depth": 4, "type": "boss"}]}, {"display_name": "地精洞窟·先整后赏", "nodes": [{"node_depth": 0, "type": "battle"}, {"node_depth": 1, "type": "battle"}, {"node_depth": 2, "type": "battle"}, {"type": "rest"}, {"type": "reward"}, {"node_depth": 3, "type": "battle"}, {"node_depth": 4, "type": "boss"}]}, {"display_name": "地精洞窟·赏战整", "nodes": [{"node_depth": 0, "type": "battle"}, {"node_depth": 1, "type": "battle"}, {"node_depth": 2, "type": "battle"}, {"type": "reward"}, {"node_depth": 3, "type": "battle"}, {"type": "rest"}, {"node_depth": 4, "type": "boss"}]}, {"display_name": "地精洞窟·整战赏", "nodes": [{"node_depth": 0, "type": "battle"}, {"node_depth": 1, "type": "battle"}, {"node_depth": 2, "type": "battle"}, {"type": "rest"}, {"node_depth": 3, "type": "battle"}, {"type": "reward"}, {"node_depth": 4, "type": "boss"}]}]}, +{"level_index": 2, "display_name": "第二层 · 幽暗地域", "rest_heal_pct": 30, "templates": [{"display_name": "幽暗地域·五岔环廊(现行)", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2, 3, 4, 5]}, {"node_depth": 1, "type": "battle", "next": [6]}, {"node_depth": 2, "type": "reward", "next": [6, 7]}, {"node_depth": 3, "type": "rest", "next": [6]}, {"node_depth": 4, "type": "battle", "next": [7]}, {"node_depth": 5, "type": "battle", "next": [6, 7]}, {"node_depth": 6, "type": "battle", "next": [8]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "幽暗地域·四岔宽门", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2, 3, 4]}, {"node_depth": 1, "type": "battle", "next": [5]}, {"node_depth": 2, "type": "reward", "next": [5, 6]}, {"node_depth": 3, "type": "rest", "next": [6, 7]}, {"node_depth": 4, "type": "battle", "next": [7]}, {"node_depth": 5, "type": "battle", "next": [8]}, {"node_depth": 6, "type": "battle", "next": [8]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "幽暗地域·深层四岔", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2]}, {"node_depth": 1, "type": "battle", "next": [3, 4, 5, 6]}, {"node_depth": 2, "type": "reward", "next": [3, 4]}, {"node_depth": 3, "type": "rest", "next": [7]}, {"node_depth": 4, "type": "battle", "next": [7]}, {"node_depth": 5, "type": "battle", "next": [7]}, {"node_depth": 6, "type": "battle", "next": [7]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "幽暗地域·宽门赏后置", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 3, 4, 5]}, {"node_depth": 1, "type": "battle", "next": [2, 6]}, {"node_depth": 2, "type": "reward", "next": [7]}, {"node_depth": 3, "type": "rest", "next": [6]}, {"node_depth": 4, "type": "battle", "next": [6, 7]}, {"node_depth": 5, "type": "battle", "next": [7]}, {"node_depth": 6, "type": "battle", "next": [8]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "幽暗地域·双干汇流", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2]}, {"node_depth": 1, "type": "battle", "next": [4, 5, 6]}, {"node_depth": 2, "type": "reward", "next": [3, 4, 5]}, {"node_depth": 3, "type": "rest", "next": [6]}, {"node_depth": 4, "type": "battle", "next": [6, 7]}, {"node_depth": 5, "type": "battle", "next": [7]}, {"node_depth": 6, "type": "battle", "next": [8]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}]}, +{"level_index": 3, "display_name": "第三层 · 灰矮人矿城", "rest_heal_pct": 30, "templates": [{"display_name": "灰矮人矿城·五岔主竖井(现行)", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2, 3, 4, 5]}, {"node_depth": 1, "type": "battle", "next": [6]}, {"node_depth": 2, "type": "reward", "next": [6, 7]}, {"node_depth": 3, "type": "rest", "next": [6]}, {"node_depth": 4, "type": "battle", "next": [7]}, {"node_depth": 5, "type": "battle", "next": [6, 7]}, {"node_depth": 6, "type": "battle", "next": [8]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "灰矮人矿城·四岔宽门", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2, 3, 4]}, {"node_depth": 1, "type": "battle", "next": [5]}, {"node_depth": 2, "type": "reward", "next": [5, 6]}, {"node_depth": 3, "type": "rest", "next": [6, 7]}, {"node_depth": 4, "type": "battle", "next": [7]}, {"node_depth": 5, "type": "battle", "next": [8]}, {"node_depth": 6, "type": "battle", "next": [8]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "灰矮人矿城·深层四岔", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2]}, {"node_depth": 1, "type": "battle", "next": [3, 4, 5, 6]}, {"node_depth": 2, "type": "reward", "next": [3, 4]}, {"node_depth": 3, "type": "rest", "next": [7]}, {"node_depth": 4, "type": "battle", "next": [7]}, {"node_depth": 5, "type": "battle", "next": [7]}, {"node_depth": 6, "type": "battle", "next": [7]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "灰矮人矿城·宽门赏后置", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 3, 4, 5]}, {"node_depth": 1, "type": "battle", "next": [2, 6]}, {"node_depth": 2, "type": "reward", "next": [7]}, {"node_depth": 3, "type": "rest", "next": [6]}, {"node_depth": 4, "type": "battle", "next": [6, 7]}, {"node_depth": 5, "type": "battle", "next": [7]}, {"node_depth": 6, "type": "battle", "next": [8]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}, {"display_name": "灰矮人矿城·双干汇流", "nodes": [{"node_depth": 0, "type": "battle", "next": [1, 2]}, {"node_depth": 1, "type": "battle", "next": [4, 5, 6]}, {"node_depth": 2, "type": "reward", "next": [3, 4, 5]}, {"node_depth": 3, "type": "rest", "next": [6]}, {"node_depth": 4, "type": "battle", "next": [6, 7]}, {"node_depth": 5, "type": "battle", "next": [7]}, {"node_depth": 6, "type": "battle", "next": [8]}, {"node_depth": 7, "type": "battle", "next": [8]}, {"node_depth": 8, "type": "shop", "next": [9]}, {"node_depth": 9, "type": "boss", "next": []}]}]} ] ; 关卡节点图生成 v0.4 台阶一内容批(M-3 迭代 5 T5,2026-09-28——15 张骨架 = 3 关 × 5,拍板点④): -; 模板 0 = 现行固定图(回滚档成员——首次冒险脚本/池缩 N=1 时逐位等价迁移前行为); -; L1 五模板 = 单链排列变体(图形态两不可全:Boss 须最大编号 vs Boss 遭遇深度 4——战斗深度序单调 -; 保持难度坡,变化面 = 奖励/恢复位次);L2/L3 五模板 = 网状图变体(战斗节点编号恒 ∈ 遭遇表行集 -; {0,1,4,5,6,7}、Boss 9 最大编号、商店房 8→Boss 必经位承袭;遭遇表按编号共用——零新增敌人数值)。 +; L2/L3 网状图约束(承袭):战斗节点编号恒 ∈ 遭遇表行集 {0,1,4,5,6,7}、Boss 9 最大编号、 +; 商店房 8→Boss 必经位承袭;遭遇表按编号共用——零新增敌人数值。L1 五模板 = 单链排列变体 +; (战斗深度序单调保难度坡,变化面 = 奖励/恢复位次)。 ; 池内全模板加载期校验(level_table.gd validate + config_db 跨表校验模板感知); ; 行为读 LevelRunner._resolve_template_row(node_map 标签恰一 scope 抽签 / template_pick 强制)。 ; 生成器 = tools/t5_build_templates.py(生成前自校验,一次性内容批工具随批入库留档)。 +; U2 目检返工(M-3 迭代 5 返工批,2026-09-29——U2 目检 15/15 不过两规则缺口,全池重生成): +; ① L1 奖励/休整禁入前三位(用户目检裁决)——五链全部前三位纯战斗,位 4-6 = {战斗3,奖励,休整} 排列; +; ② L2/L3 BFS 同深度层上限 5 且应用足(每张最大层宽 4-5,旧池层宽最大 3 被否);模板 0 一并重排 +; (待批点甲按推荐=随新规则重生成——首次冒险/钉固断言期望随新图更新;模板 0 名保持「(现行)」后缀=新现行图锚)。 +; 加载校验两规则同落 level_table.gd validate(单链前三纯战斗/图形态层宽 ≤5)。 diff --git a/game/data/tables/variation.tres b/game/data/tables/variation.tres index baa5e908..3d0895dd 100644 --- a/game/data/tables/variation.tres +++ b/game/data/tables/variation.tres @@ -4,7 +4,7 @@ [resource] script = ExtResource("1") -percent_of_cells = 4 +percent_of_cells = 5 per_level_bonus = 0 pack_max = 4 rows = [{ @@ -96,3 +96,5 @@ rows = [{ ; M-3 迭代 5 判读锁值批(2026-09-28):岩浆 effect_value 2→1 · per_level_bonus 2→0 · percent_of_cells 5→4 ; (杠杆扫描 C3 组合收带 L2——配对种子读数见 design/qa/m3-iter5-lever-scan-2026-09-28.md;L3 侧效应= ; 变异密度 9%→4%、完美 L3 0→34%,L3 无带记观察态;行值 provisional→正式随迭代 5 出口人闸追认)。 +; M-3 迭代 5 返工批(2026-09-29 U2 推翻 C3 裁 C1 两刀):percent_of_cells 4→5 回退;岩浆 1 与 per_level_bonus 0 维持——C1 终态; +; C1+v1 AC 组合读数归返工批全量确认(裁断全录 design/qa/m3-iter5-u2-gate-2026-09-29.md)。 diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index a7f519fb..1bb5d022 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -662,17 +662,20 @@ func _smoke_ac_swap_guard() -> void: "主角 armor_class 在域内(%d——暂定基准随表走)" % int(_db.hero.armor_class)) var ac_band := true var ac_detail := "" + var heavy_names := ["熊地精重击手", "灰矮人重装手"] # U2 裁 v1 硬梯度(2026-09-29):逐行手设值 8/9,非精英编码位 for row in _db.enemy.rows: var ac: int = int(row.get("armor_class", -1)) + var dname := String(row.get("display_name", "?")) var boss_row: bool = String(row.get("enemy_id", "")).ends_with("_chief") or \ String(row.get("enemy_id", "")) == "drider" or String(row.get("enemy_id", "")) == "duergar_king" - # 精英编码位现行不存在(registry/敌人系统:精英 P2 内容承载)——非 Boss 行全按杂鱼带 0~5 - var lo := 8 if boss_row else 0 - var hi := 12 if boss_row else 5 + var heavy_row: bool = dname in heavy_names + # 杂鱼 0~5;重击手/重装手 8~9(U2 返工批手设精英值);Boss 8~12 台阶 + var lo := 8 if (boss_row or heavy_row) else 0 + var hi := 12 if boss_row else (9 if heavy_row else 5) if ac < lo or ac > hi: ac_band = false - ac_detail += " %s:%d" % [String(row.get("display_name", "?")), ac] - check(ac_band, "敌方 AC 行值在已裁带宽内(杂鱼 0~5·Boss 8~12——M-3 迭代 5 T4 投值批,越带行%s)" % [ + ac_detail += " %s:%d" % [dname, ac] + check(ac_band, "敌方 AC 行值在已裁带宽内(杂鱼 0~5·重击手/重装手 8~9·Boss 8~12——迭代 5 返工批 v1 重投,越带行%s)" % [ ac_detail if ac_detail != "" else "无"]) # ③b Boss 台阶断言(关卡即台阶——L1 8 < L2 10 < L3 12)+ 首遇位教学位低 AC(≤3) var boss_ac := {} @@ -2708,17 +2711,17 @@ func _smoke_level_graph() -> void: var win_min = autoplay.run_level(_db, 42, 2, 200, 100, false, null, "normal", "min", 0) check(win_min.result == "victory", "L2·满配 min 路径通关(实测:%s)" % win_min.result) check(win_min.boss_depth == 9, "L2 Boss 以编号 9 解析(终点编号最大——商店房批重编号 8→9)") - check(win_min.visited_ids == [0, 1, 3, 6, 8, 9], "min 路径 = 0→1→3(恢复)→6→8(商店零购离开)→9(实测 %s)" % str(win_min.visited_ids)) + check(win_min.visited_ids == [0, 1, 6, 8, 9], "min 路径 = 0→1→6→8(商店零购离开)→9(实测 %s——U2 返工批模板 0)" % str(win_min.visited_ids)) check(win_min.choices >= 3, "选路口发生 %d 次(每节点兑付后——零自动推进)" % win_min.choices) var win_max := {} var max_seed := -1 for s in range(42, 300): win_max = autoplay.run_level(_db, s, 2, 200, 100, false, null, "normal", "max", 0) - if win_max.result == "victory" and win_max.visited_ids == [0, 2, 5, 7, 8, 9]: + if win_max.result == "victory" and win_max.visited_ids == [0, 5, 7, 8, 9]: max_seed = s break - check(max_seed > 0 and win_max.result == "victory" and win_max.visited_ids == [0, 2, 5, 7, 8, 9], - "L2·满配 max 路径(经奖励点 2)通关 = 0→2→5→7→8(商店零购离开)→9(seed=%d,实测 %s %s)" % [max_seed, win_max.result, str(win_max.visited_ids)]) + check(max_seed > 0 and win_max.result == "victory" and win_max.visited_ids == [0, 5, 7, 8, 9], + "L2·满配 max 路径(max 取最大编号候选)通关 = 0→5(重击手)→7→8(商店零购离开)→9(seed=%d,实测 %s %s——U2 返工批模板 0)" % [max_seed, win_max.result, str(win_max.visited_ids)]) # ⑤ 零自动推进状态机(手动驱动——兑付确认 / 选路拒绝语义逐拍断言) var runner = load("res://core/level_runner.gd").new(_db, _fresh_rng(42)) runner.set_hero_override(200, 100) @@ -2733,14 +2736,14 @@ func _smoke_level_graph() -> void: autoplay._autoplay_battle(runner, first, st, null) check(String(res_box.get("result", "")) == "victory" and runner.current_battle() == null and runner.awaiting_choice(), "首战胜利后:settlement 释放 + 进选路态(战斗胜利不再自动推进——接口漂移清偿)") - check(runner.next_candidate_ids() == [1, 2], "入口岔路候选 = [1, 2]") - check(not runner.pick_next_node(5) and runner.awaiting_choice(), "候选外编号拒绝且仍待选(拒绝语义)") + check(runner.next_candidate_ids() == [1, 2, 3, 4, 5], "入口岔路候选 = [1, 2, 3, 4, 5](U2 返工批模板 0 五岔环廊——层宽 5)") + check(not runner.pick_next_node(8) and runner.awaiting_choice(), "候选外编号拒绝且仍待选(拒绝语义)") check(runner.pick_next_node(2), "选编号 2(奖励点)受理") check(String(runner.current_node().get("type", "")) == "reward" and not runner.awaiting_choice(), "到达奖励点:直通兑付(确认前不出选路口)") - check(runner.continue_after_node() and runner.awaiting_choice() and runner.next_candidate_ids() == [4, 5], - "兑付确认后才出现选路口(continue_after_node 泛化语义)候选 = [4, 5]") - check(runner.pick_next_node(4) and runner.current_battle() != null, "选编号 4(巨蜘蛛首遇)开战") + check(runner.continue_after_node() and runner.awaiting_choice() and runner.next_candidate_ids() == [6, 7], + "兑付确认后才出现选路口(continue_after_node 泛化语义)候选 = [6, 7]") + check(runner.pick_next_node(6) and runner.current_battle() != null, "选编号 6(巨蜘蛛巢群)开战") # ⑥ L2 锚行首遇战:基线 0 加成 · 完美档(零盲挖 = 零踩雷上限口径)多种子扫描 # (Package B 第 7 轮阶段拔高后锚行 45→50 退出绿档——13/12 全胜改多数胜 + 零踩雷恒要求, # 2026-09-23 拍板 B 接受现曲线;还原绿档需回调锚行血量,归用户决策) @@ -2805,7 +2808,7 @@ func _smoke_level_graph() -> void: branch3 += 1 check(int(tcount3.battle) == 6 and int(tcount3.boss) == 1, "L3 普通战斗节点 = 6 + Boss 恒一(编号最大恒终点)") check(int(tcount3.reward) == 1 and int(tcount3.rest) == 1 and int(tcount3.shop) == 1, "L3 恢复节点 1(rest_heal_pct 行字段)+ 奖励节点 1(直通空态)+ 商店房节点 1(商店房批)") - check(branch3 == 4, "L3 真实岔路 = %d 处(0→[1,2] · 1→[3,4] · 2→[4,5] · 4→[6,7])" % branch3) + check(branch3 == 3, "L3 真实岔路 = %d 处(0→[1,2,3,4,5] · 2→[6,7] · 5→[6,7]——U2 返工批五岔环廊层宽 5)" % branch3) # ⑨ 跨表行完整:L3 每战斗/Boss 节点 (3, 编号) 在 board_gen 与 enemy 精确行(⑦ 的 bg_keys/en_keys 复用) var cross_ok3 := true for node in _level_active_nodes(3): diff --git a/tools/repo_structure_audit.py b/tools/repo_structure_audit.py index 881834f0..e5d48042 100644 --- a/tools/repo_structure_audit.py +++ b/tools/repo_structure_audit.py @@ -26,8 +26,10 @@ TOP_DIRS = {"archive", "artwork", "design", "game", "sim-data", "tools"} ROOT_FILES = {"CLAUDE.md", "active.md", "roadmap.md", "stage.txt", "review-mode.txt", ".gitignore", ".zcodeignore"} # 3) 图像允许目录(相对路径前缀;archive/ 为归档 zip 不扫) +# design/qa/ 收编(迭代 5 返工批 2026-09-29——U2 目检预览图/QA 呈现产物归位,commit 留痕) IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".webp", ".gif", ".svg", ".pxo", ".psd", ".bmp"} -IMAGE_PREFIXES = ("artwork/", "game/assets/", "design/art/references/", "design/dev/architecture/") +IMAGE_PREFIXES = ("artwork/", "game/assets/", "design/art/references/", "design/dev/architecture/", + "design/qa/") # 4) 退役路径(目录或文件任一再现即 FAIL) RETIRED = ["production", "docs", "tests", "qa-data", "design/art/candidates", "production/mockups", diff --git a/tools/t5_build_templates.py b/tools/t5_build_templates.py index df256a39..18cdfeb8 100644 --- a/tools/t5_build_templates.py +++ b/tools/t5_build_templates.py @@ -2,7 +2,9 @@ # -*- coding: utf-8 -*- """T5 骨架池生成器(M-3 迭代 5——一次性内容批工具,生成 game/data/tables/level.tres 模板池)。 -15 张骨架 = 3 关 × 5(拍板点④),模板 0 = 现行固定图(回滚档成员)。 +15 张骨架 = 3 关 × 5(拍板点④)。U2 目检返工(2026-09-29)两条新规则: +① L1 单链前三位纯战斗(奖励/休整禁入前三——用户目检裁决); +② L2/L3 网状图 BFS 同深度层节点数上限 5,新池应用足宽度(每张最大层宽 ≥4——分支加宽)。 约束:战斗/Boss node_depth = 遭遇表行;图形态 id = node_depth → L2/L3 战斗 ∈ {0,1,4,5,6,7}、Boss=9; L1 图形态两不可全(Boss 须最大 id vs 深度 4)→ L1 单链排列变体(战斗深度序单调)。 商店房每关恰 1、Boss 前必经位(L1 设计态 = 零商店——商店房批教学期修订面)。 @@ -32,54 +34,77 @@ def graph(spec): return nodes +# 前三位纯战斗(U2 规则①)——位 4-6 = {战斗3, 奖励, 休整} 排列变体,末位 Boss L1_TEMPLATES = [ - ("地精洞窟·主线洞道(现行)", chain([("battle", 0), ("battle", 1), ("reward", None), - ("battle", 2), ("battle", 3), ("rest", None), ("boss", 4)])), - ("地精洞窟·休整前移", chain([("battle", 0), ("battle", 1), ("rest", None), - ("battle", 2), ("reward", None), ("battle", 3), ("boss", 4)])), - ("地精洞窟·先赏后战", chain([("battle", 0), ("reward", None), ("battle", 1), ("rest", None), - ("battle", 2), ("battle", 3), ("boss", 4)])), - ("地精洞窟·连战直入", chain([("battle", 0), ("battle", 1), ("reward", None), ("rest", None), - ("battle", 2), ("battle", 3), ("boss", 4)])), - ("地精洞窟·缓入长坡", chain([("battle", 0), ("rest", None), ("battle", 1), ("reward", None), - ("battle", 2), ("battle", 3), ("boss", 4)])), + ("地精洞窟·主线洞道(现行)", chain([("battle", 0), ("battle", 1), ("battle", 2), ("battle", 3), + ("reward", None), ("rest", None), ("boss", 4)])), + ("地精洞窟·先赏后整", chain([("battle", 0), ("battle", 1), ("battle", 2), ("reward", None), + ("rest", None), ("battle", 3), ("boss", 4)])), + ("地精洞窟·先整后赏", chain([("battle", 0), ("battle", 1), ("battle", 2), ("rest", None), + ("reward", None), ("battle", 3), ("boss", 4)])), + ("地精洞窟·赏战整", chain([("battle", 0), ("battle", 1), ("battle", 2), ("reward", None), + ("battle", 3), ("rest", None), ("boss", 4)])), + ("地精洞窟·整战赏", chain([("battle", 0), ("battle", 1), ("battle", 2), ("rest", None), + ("battle", 3), ("reward", None), ("boss", 4)])), ] def w0(): - return graph({0: ("battle", 0, [1, 2]), 1: ("battle", 1, [3, 4]), 2: ("reward", 2, [4, 5]), - 3: ("rest", 3, [6]), 4: ("battle", 4, [6, 7]), 5: ("battle", 5, [7]), + return graph({0: ("battle", 0, [1, 2, 3, 4, 5]), 1: ("battle", 1, [6]), 2: ("reward", 2, [6, 7]), + 3: ("rest", 3, [6]), 4: ("battle", 4, [7]), 5: ("battle", 5, [6, 7]), 6: ("battle", 6, [8]), 7: ("battle", 7, [8]), 8: ("shop", 8, [9]), 9: ("boss", 9, [])}) def w1(): - return graph({0: ("battle", 0, [1, 2]), 1: ("battle", 1, [3, 4]), 2: ("reward", 2, [4]), - 3: ("rest", 3, [5]), 4: ("battle", 4, [5, 6]), 5: ("battle", 5, [7]), + return graph({0: ("battle", 0, [1, 2, 3, 4]), 1: ("battle", 1, [5]), 2: ("reward", 2, [5, 6]), + 3: ("rest", 3, [6, 7]), 4: ("battle", 4, [7]), 5: ("battle", 5, [8]), 6: ("battle", 6, [8]), 7: ("battle", 7, [8]), 8: ("shop", 8, [9]), 9: ("boss", 9, [])}) def w2(): - return graph({0: ("battle", 0, [1, 2]), 1: ("battle", 1, [4]), 2: ("reward", 2, [3, 4]), - 3: ("rest", 3, [6]), 4: ("battle", 4, [5, 6]), 5: ("battle", 5, [7]), - 6: ("battle", 6, [7]), 7: ("battle", 7, [8]), 8: ("shop", 8, [9]), 9: ("boss", 9, [])}) + return graph({0: ("battle", 0, [1, 2]), 1: ("battle", 1, [3, 4, 5, 6]), 2: ("reward", 2, [3, 4]), + 3: ("rest", 3, [7]), 4: ("battle", 4, [7]), 5: ("battle", 5, [7]), 6: ("battle", 6, [7]), + 7: ("battle", 7, [8]), 8: ("shop", 8, [9]), 9: ("boss", 9, [])}) def w3(): - return graph({0: ("battle", 0, [1, 2]), 1: ("battle", 1, [2, 4]), 2: ("reward", 2, [3]), - 3: ("rest", 3, [6]), 4: ("battle", 4, [5, 6]), 5: ("battle", 5, [7]), + return graph({0: ("battle", 0, [1, 3, 4, 5]), 1: ("battle", 1, [2, 6]), 2: ("reward", 2, [7]), + 3: ("rest", 3, [6]), 4: ("battle", 4, [6, 7]), 5: ("battle", 5, [7]), 6: ("battle", 6, [8]), 7: ("battle", 7, [8]), 8: ("shop", 8, [9]), 9: ("boss", 9, [])}) def w4(): - return graph({0: ("battle", 0, [1, 2]), 1: ("battle", 1, [3]), 2: ("reward", 2, [3, 4]), - 3: ("rest", 3, [4, 5]), 4: ("battle", 4, [6]), 5: ("battle", 5, [6]), - 6: ("battle", 6, [7]), 7: ("battle", 7, [8]), 8: ("shop", 8, [9]), 9: ("boss", 9, [])}) + return graph({0: ("battle", 0, [1, 2]), 1: ("battle", 1, [4, 5, 6]), 2: ("reward", 2, [3, 4, 5]), + 3: ("rest", 3, [6]), 4: ("battle", 4, [6, 7]), 5: ("battle", 5, [7]), + 6: ("battle", 6, [8]), 7: ("battle", 7, [8]), 8: ("shop", 8, [9]), 9: ("boss", 9, [])}) -L2_WIRINGS = [("幽暗地域·四岔环廊(现行)", w0()), ("幽暗地域·奖励窄门", w1()), - ("幽暗地域·直入深层", w2()), ("幽暗地域·休整远路", w3()), ("幽暗地域·双线归束", w4())] -L3_WIRINGS = [("灰矮人矿城·主竖井(现行)", w0()), ("灰矮人矿城·奖励窄门", w1()), - ("灰矮人矿城·直入深层", w2()), ("灰矮人矿城·休整远路", w3()), ("灰矮人矿城·双线归束", w4())] +L2_WIRINGS = [("幽暗地域·五岔环廊(现行)", w0()), ("幽暗地域·四岔宽门", w1()), + ("幽暗地域·深层四岔", w2()), ("幽暗地域·宽门赏后置", w3()), ("幽暗地域·双干汇流", w4())] +L3_WIRINGS = [("灰矮人矿城·五岔主竖井(现行)", w0()), ("灰矮人矿城·四岔宽门", w1()), + ("灰矮人矿城·深层四岔", w2()), ("灰矮人矿城·宽门赏后置", w3()), ("灰矮人矿城·双干汇流", w4())] + + +def bfs_widths(nodes): + """BFS 分层(图形态)→ 各层宽度列表。""" + ids = [n["node_depth"] for n in nodes] + adj = {n["node_depth"]: list(n.get("next", [])) for n in nodes} + inbound = set() + for nxt in adj.values(): + inbound |= set(nxt) + entry = [i for i in ids if i not in inbound][0] + layer = {entry: 0} + frontier = [entry] + while frontier: + cur = frontier.pop(0) + for t in adj[cur]: + if t not in layer: + layer[t] = layer[cur] + 1 + frontier.append(t) + widths = {} + for i in ids: + widths[layer[i]] = widths.get(layer[i], 0) + 1 + return [widths[d] for d in sorted(widths)] def validate(name, nodes, battles, boss_depth, has_shop): @@ -117,6 +142,17 @@ def validate(name, nodes, battles, boss_depth, has_shop): frontier += list(adj.get(cur, [])) if reach != set(ids): errs.append("存在不可达节点: %s" % (set(ids) - reach)) + # U2 规则②:BFS 同层 ≤5,且最大层宽 ≥4(分支应用足) + ws = bfs_widths(nodes) + if max(ws) > 5: + errs.append("层宽越上限 5: %s" % ws) + if max(ws) < 4: + errs.append("分支不足(最大层宽 %d < 4——U2 加宽裁决)" % max(ws)) + else: + # U2 规则①:单链前三位纯战斗 + for i in range(3): + if nodes[i]["type"] != "battle": + errs.append("第 %d 位非战斗(前三位纯战斗)" % (i + 1)) bt = sorted(n["node_depth"] for n in nodes if n["type"] in ("battle", "boss")) if bt != sorted(battles + [boss_depth]): errs.append("战斗深度集合不符: %s" % bt) @@ -140,7 +176,7 @@ for name, nodes in L1_TEMPLATES: all_ok &= not e for name, nodes in L2_WIRINGS + L3_WIRINGS: e = validate(name, nodes, L2_BATTLES, 9, True) - print(("PASS" if not e else "FAIL"), name, e) + print(("PASS" if not e else "FAIL"), name, "层宽=%s" % bfs_widths(nodes), e) all_ok &= not e print("ALL OK" if all_ok else "HAS FAILURES") if not all_ok: @@ -186,13 +222,17 @@ footer = ( "\n]\n" "\n" "; 关卡节点图生成 v0.4 台阶一内容批(M-3 迭代 5 T5,2026-09-28——15 张骨架 = 3 关 × 5,拍板点④):\n" - "; 模板 0 = 现行固定图(回滚档成员——首次冒险脚本/池缩 N=1 时逐位等价迁移前行为);\n" - "; L1 五模板 = 单链排列变体(图形态两不可全:Boss 须最大编号 vs Boss 遭遇深度 4——战斗深度序单调\n" - "; 保持难度坡,变化面 = 奖励/恢复位次);L2/L3 五模板 = 网状图变体(战斗节点编号恒 ∈ 遭遇表行集\n" - "; {0,1,4,5,6,7}、Boss 9 最大编号、商店房 8→Boss 必经位承袭;遭遇表按编号共用——零新增敌人数值)。\n" + "; L2/L3 网状图约束(承袭):战斗节点编号恒 ∈ 遭遇表行集 {0,1,4,5,6,7}、Boss 9 最大编号、\n" + "; 商店房 8→Boss 必经位承袭;遭遇表按编号共用——零新增敌人数值。L1 五模板 = 单链排列变体\n" + "; (战斗深度序单调保难度坡,变化面 = 奖励/恢复位次)。\n" "; 池内全模板加载期校验(level_table.gd validate + config_db 跨表校验模板感知);\n" "; 行为读 LevelRunner._resolve_template_row(node_map 标签恰一 scope 抽签 / template_pick 强制)。\n" - "; 生成器 = tools/t5_build_templates.py(生成前自校验,一次性内容批工具随批入库留档)。\n") + "; 生成器 = tools/t5_build_templates.py(生成前自校验,一次性内容批工具随批入库留档)。\n" + "; U2 目检返工(M-3 迭代 5 返工批,2026-09-29——U2 目检 15/15 不过两规则缺口,全池重生成):\n" + "; ① L1 奖励/休整禁入前三位(用户目检裁决)——五链全部前三位纯战斗,位 4-6 = {战斗3,奖励,休整} 排列;\n" + "; ② L2/L3 BFS 同深度层上限 5 且应用足(每张最大层宽 4-5,旧池层宽最大 3 被否);模板 0 一并重排\n" + "; (待批点甲按推荐=随新规则重生成——首次冒险/钉固断言期望随新图更新;模板 0 名保持「(现行)」后缀=新现行图锚)。\n" + "; 加载校验两规则同落 level_table.gd validate(单链前三纯战斗/图形态层宽 ≤5)。\n") with open("game/data/tables/level.tres", "w", encoding="utf-8", newline="\n") as f: f.write(header + ",\n".join(rows) + footer)