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

This commit is contained in:
DKChan committed 2026-09-29 04:16:50 +08:00
1 parent 700f2ff6cd
commit c3c1725db8
29 files changed
+415 -78

No files matched your search

+1 -1
View File
@@ -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/` — 已验收终稿(入包被游戏加载)
+7
View File
@@ -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
@@ -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(灰矮人矿城·双干汇流)
Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@@ -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()
+3 -3
View File
@@ -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 池呈现口径
@@ -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 残余越带挂账——届时带缺口数字已知,可再次三选一)。
+23
View File
@@ -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
+18 -17
View File
@@ -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)。
+11 -7
View File
@@ -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)。
+3 -1
View File
@@ -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)。
+18 -15
View File
@@ -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):
+3 -1
View File
@@ -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",
+73 -33
View File
@@ -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)