From df666ddf300af8a0bf13f64dd80847453b5f0aec Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 12 Oct 2026 23:23:16 +0800 Subject: [PATCH 1/8] =?UTF-8?q?feat(=E8=BF=AD=E4=BB=A323-T1):=20=E9=94=BB?= =?UTF-8?q?=E9=80=A0=E5=B7=A5=E5=9D=8A=E9=85=8D=E7=BD=AE=E8=A1=A8=E8=90=BD?= =?UTF-8?q?=E4=BD=8D=E2=80=94=E2=80=94ForgeTable=20=E6=96=B0=E8=A1=A8?= =?UTF-8?q?=EF=BC=88=E9=BB=98=E8=AE=A4=E4=B8=A4=E8=BD=B4=E5=B8=B8=E9=A9=BB?= =?UTF-8?q?=E6=8C=81=E6=9C=89+=E5=85=AD=E8=B7=AF=E7=BA=BF=E8=A1=8C?= =?UTF-8?q?=E6=97=8F=EF=BC=89+ConfigDB=20=E8=AE=BF=E9=97=AE=E5=99=A8+V1/V1?= =?UTF-8?q?1=20=E5=8D=95=E6=B5=8B=2041=20=E6=96=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新表 forge.tres(schema forge_table.gd):default_attack_type/armor_type 默认值(契约 2 所有权移交——hero.tres 两轴随后批删除)+routes 六行(sword/spear/staff/light/heavy/ward) - provisional 沿需求卡片 T1 锚表:cap 5/每级增量 +1/基础价 20×150%^n(20/30/45/67/101)/解锁价 200/甲型基础 AC 10-12-9/初始持有 sword+light/并联双通道前置 spear·staff←sword、heavy·ward←light - schema validate 22 类正反(行 id 唯一/类型轴分族枚举/恰六行/初始持有恰一武器一甲/默认轴=初始路线类型轴/前置类型相符…) - ConfigDB:TABLE_MANIFEST 登 forge+类型化持用+forge_route/forge_routes/forge_default_*/forge_weapon_route_by_type/forge_armor_route_by_type 访问器 - 新单测 forge_table_test 41 断言(V1 生产面+22 类负向/V11 行字段闭集+效果类型词表 31 钉现/V2 价格梯);run_all 22→23 套 - 事实订正(L2):卡片 F11「效果类型闭集 24」系 2026-10-07 旧快照——现行 31(iter20 +7/iter22 +1),本批零扩签钉 31 - 轻闸:冒烟 898/0 零扰动 --- game/core/config_db.gd | 45 ++++ game/data/schemas/forge_table.gd | 131 ++++++++++++ game/data/schemas/forge_table.gd.uid | 1 + game/data/tables/forge.tres | 98 +++++++++ game/tests/run_all.gd | 1 + .../unit/data_config/forge_table_test.gd | 200 ++++++++++++++++++ .../unit/data_config/forge_table_test.gd.uid | 1 + 7 files changed, 477 insertions(+) create mode 100644 game/data/schemas/forge_table.gd create mode 100644 game/data/schemas/forge_table.gd.uid create mode 100644 game/data/tables/forge.tres create mode 100644 game/tests/unit/data_config/forge_table_test.gd create mode 100644 game/tests/unit/data_config/forge_table_test.gd.uid diff --git a/game/core/config_db.gd b/game/core/config_db.gd index 6267e53d..e3a21806 100644 --- a/game/core/config_db.gd +++ b/game/core/config_db.gd @@ -27,6 +27,7 @@ const TABLE_MANIFEST := { "treasure_items": "treasure_items.tres", # 迭代 7 T1 秘宝内容批(TreasureItemTable 六行——定义表 Rule 4 豁免;罗盘 retired/失义件 deferred 标记见 schema 头注) "adventure": "adventure.tres", # 冒险结构实现批 2026-09-21(AdventureTable 建表——大奖励与钥石行值单点) "feats": "feats.tres", # 迭代 12 T1 双层 BD 规则改写型特性基建(FeatTable 定义表 Rule 4 豁免——TreasureItemTable 先例;空表合法态=主时间线零扰动) + "forge": "forge.tres", # 迭代 23 T1 锻造工坊实装批(ForgeTable——两根类型轴默认值+六路线行族;攻防所有权切分唯一数据面) } var action_economy: ActionEconomyTable @@ -49,6 +50,7 @@ var meta_tree: MetaTreeTable # 迭代 12 T8 类型化持用(消费面走 tre var treasure_items: TreasureItemTable # 迭代 7 T1 类型化持用(消费面走 treasure_* 访问器) var adventure: AdventureTable var feats: FeatTable # 迭代 12 T1 类型化持用(消费面走 feat_* 访问器) +var forge: ForgeTable # 迭代 23 T1 锻造工坊配置(默认两轴+六路线行族——消费面走 forge_* 访问器) var is_ready := false var _diagnostics: Array[Dictionary] = [] # {severity, category, message} @@ -140,6 +142,7 @@ func load_tables(root: String, collect_only: bool) -> bool: treasure_items = loaded.get("treasure_items") adventure = loaded.get("adventure") feats = loaded.get("feats") + forge = loaded.get("forge") meta_tree = loaded.get("meta_tree") is_ready = not has_errors return not has_errors @@ -387,6 +390,48 @@ func _meta_rows(family: String) -> Array: return arr if arr is Array else [] +# ---- 锻造工坊(迭代 23 T1——ForgeTable 消费面:两根类型轴默认值+六路线行族;禁自扫行集)---- +# 设计权威 design/gdd/systems/跨局循环/锻造工坊.md v0.6:默认值归本表常驻持有(hero.tres 两轴已删—— +# 契约 2);路线行族=攻防数值成长唯一数据面(契约 1)。数值全 provisional 归 M4。 + +# 两根类型轴默认值(表未落籍=裸体态兜底 normal/light——与 ForgeTable 缺省值同口径) +func forge_default_attack_type() -> String: + return String(forge.default_attack_type) if forge != null else "normal" + + +func forge_default_armor_type() -> String: + return String(forge.default_armor_type) if forge != null else "light" + + +# 六路线行族(副本——调用方禁改) +func forge_routes() -> Array: + return forge.routes.duplicate() if forge != null else [] + + +# 路线行单点(键 route_id;缺行=空字典) +func forge_route(route_id: String) -> Dictionary: + for row in forge_routes(): + if String(row.get("route_id", "")) == route_id: + return row + return {} + + +# 按类型轴反查武器路线(attack_type → 路线行;甲路线 id=甲型同值的显式化入口,避免消费侧依赖该巧合) +func forge_weapon_route_by_type(attack_type: String) -> Dictionary: + for row in forge_routes(): + if String(row.get("kind", "")) == "weapon" and String(row.get("type_axis", "")) == attack_type: + return row + return {} + + +# 按类型轴反查护甲路线(armor_type → 路线行——入战 AC 三层之「甲型基础 AC」取数口) +func forge_armor_route_by_type(armor_type: String) -> Dictionary: + for row in forge_routes(): + if String(row.get("kind", "")) == "armor" and String(row.get("type_axis", "")) == armor_type: + return row + return {} + + # 技能行单点读取(敌人技能池批——单位容器执行上报 / 战斗结算兑现的消费面) func skill_row(skill_id: String) -> Dictionary: return skill.row_by_id(skill_id) diff --git a/game/data/schemas/forge_table.gd b/game/data/schemas/forge_table.gd new file mode 100644 index 00000000..4106ee8a --- /dev/null +++ b/game/data/schemas/forge_table.gd @@ -0,0 +1,131 @@ +# VERTICAL SLICE - NOT FOR PRODUCTION +# Date: 2026-10-12(迭代 23 锻造工坊实装批 T1——设计权威 design/gdd/systems/跨局循环/锻造工坊.md v0.6) +# 锻造工坊配置表(ForgeTable):两根类型轴默认值(常驻持有——契约 2)+ 六条锻造路线行族(三武器+三甲)。 +# 所有权切分(契约 1 红线):攻击唯一入口=武器路线、AC 唯一入口=甲路线——树零攻防来源,本表是攻防 +# 数值成长的唯一数据面。等级语义(Q1 用户裁):未解锁/未锻造=0 加值(裸体态,类型=默认轴值); +# 解锁并锻造第一级=「获得装备」=1 级=首个 +1;逐级递进(2 级=+2 量级)。 +# 数值全 provisional(需求卡片 T1 锚表投值——终值归 M4 数值批):cap 5/每级增量统一 +1(v1.2 用户裁)/ +# 基础价 20/公比 150%(各级价格 20/30/45/67/101)/一次性解锁价 200/甲型基础 AC 10-12-9(现役结构值)。 +# 表运行期只读(铁律):永久强化只写存档(forge_routes 域),本表零写面。 +class_name ForgeTable +extends Resource + +# 路线 id 词表(代拍板立项:甲路线 id=甲型值本身——ward=魔法护甲,3a 裁统一命名) +const WEAPON_IDS := ["sword", "spear", "staff"] +const ARMOR_IDS := ["light", "heavy", "ward"] +const ROUTE_IDS := ["sword", "spear", "staff", "light", "heavy", "ward"] +# 两根类型轴枚举域(hero.tres 静态表两轴随本批删除——词表归本表常驻持有) +const ATTACK_TYPES := ["normal", "pierce", "magic"] +const ARMOR_TYPES := ["light", "ward", "heavy"] +# 行字段闭集(V11:满级特效本期不做——行族无特效参数位;键集出域即拒) +const ROW_KEYS := ["route_id", "display_name", "kind", "type_axis", "cap", "per_level_gain", + "base_ac", "base_price", "price_ratio_pct", "initial_owned", "prereq_route", "unlock_price"] + +@export var default_attack_type: String = "normal" # 攻击类型轴默认值(常驻持有——与柜台解锁状态解耦:未点亮=无交互入口但默认值照常注入) +@export var default_armor_type: String = "light" # 甲型轴默认值(常驻持有) +@export var routes: Array[Dictionary] = [] # 六路线行族(键 route_id) + + +# 各级价格(几何递增:基础价 × 150%^已购级数——锚表 20/30/45/67/101;1.5^n 二进制精确,无浮点尘) +static func level_price(row: Dictionary, owned_levels: int) -> int: + var base := int(row.get("base_price", 0)) + var ratio := float(int(row.get("price_ratio_pct", 100))) / 100.0 + return int(floor(float(base) * pow(ratio, maxi(0, owned_levels)))) + + +func validate() -> Array: + var errs: Array[Dictionary] = [] + # 默认两轴枚举域 + if not ATTACK_TYPES.has(default_attack_type): + errs.append({"category": "enum", "message": "default_attack_type 出域 %s: %s" % [ATTACK_TYPES, default_attack_type]}) + if not ARMOR_TYPES.has(default_armor_type): + errs.append({"category": "enum", "message": "default_armor_type 出域 %s: %s" % [ARMOR_TYPES, default_armor_type]}) + # 行族规模恰六(三武器+三甲——需求卡片 §1①) + if routes.size() != 6: + errs.append({"category": "structural", "message": "锻造路线行族规模 %d ≠ 6(三武器+三甲)" % routes.size()}) + var seen := {} + var type_seen := {} + var kind_by_id := {} + var initial_ids := {} + for row in routes: # 预扫:route_id→kind 全集(前置反查需前向引用——轻甲行在长枪行之后) + kind_by_id[String(row.get("route_id", ""))] = String(row.get("kind", "")) + for row in routes: + var rid := String(row.get("route_id", "")) + if not ROUTE_IDS.has(rid): + errs.append({"category": "enum", "message": "锻造路线 route_id 出域 %s: %s" % [ROUTE_IDS, rid]}) + if seen.has(rid): + errs.append({"category": "dup_key", "message": "锻造路线 route_id 重复: %s" % rid}) + seen[rid] = true + var kind := String(row.get("kind", "")) + if not ["weapon", "armor"].has(kind): + errs.append({"category": "enum", "message": "%s kind 出域 {weapon, armor}: %s" % [rid, kind]}) + continue + # 类型轴:武器=attack_type 域、甲=armor_type 域;每型恰一路线(三武器三甲各一) + var axis := String(row.get("type_axis", "")) + var domain: Array = ATTACK_TYPES if kind == "weapon" else ARMOR_TYPES + if not domain.has(axis): + errs.append({"category": "enum", "message": "%s type_axis 出域 %s: %s" % [rid, domain, axis]}) + var type_key := "%s|%s" % [kind, axis] + if type_seen.has(type_key): + errs.append({"category": "dup_key", "message": "%s 类型轴重复(每型恰一路线): %s %s" % [kind, axis, rid]}) + type_seen[type_key] = true + # 等级/增量/价格/基础 AC 值域 + var cap_v: int = int(row.get("cap", -1)) + if cap_v < 1 or cap_v > 9: + errs.append({"category": "domain", "message": "%s cap 越域 [1,9]: %d" % [rid, cap_v]}) + var gain: int = int(row.get("per_level_gain", -1)) + if gain < 0 or gain > 9: + errs.append({"category": "domain", "message": "%s per_level_gain 越域 [0,9]: %d" % [rid, gain]}) + var base_ac: int = int(row.get("base_ac", -1)) + if kind == "armor": + if base_ac < 0 or base_ac > 999: + errs.append({"category": "domain", "message": "%s base_ac 越域 [0,999]: %d" % [rid, base_ac]}) + elif base_ac != 0: + errs.append({"category": "cross_field", "message": "%s 武器行 base_ac 须为 0(甲型基础 AC 只属护甲路线): %d" % [rid, base_ac]}) + var base_price: int = int(row.get("base_price", -1)) + if base_price < 1 or base_price > 999: + errs.append({"category": "domain", "message": "%s base_price 越域 [1,999]: %d" % [rid, base_price]}) + var ratio: int = int(row.get("price_ratio_pct", -1)) + if ratio < 100 or ratio > 500: + errs.append({"category": "domain", "message": "%s price_ratio_pct 越域 [100,500]: %d" % [rid, ratio]}) + var unlock_price: int = int(row.get("unlock_price", -1)) + if unlock_price < 0 or unlock_price > 999: + errs.append({"category": "domain", "message": "%s unlock_price 越域 [0,999]: %d" % [rid, unlock_price]}) + # 解锁通道(并联双通道):初始持有行无前置无解锁价;待解锁行须有一次性解锁价+合法前置 + var initial: bool = bool(row.get("initial_owned", false)) + var prereq := String(row.get("prereq_route", "")) + if initial: + if prereq != "": + errs.append({"category": "cross_field", "message": "%s 初始持有行不得带前置: %s" % [rid, prereq]}) + if unlock_price != 0: + errs.append({"category": "cross_field", "message": "%s 初始持有行解锁价须为 0: %d" % [rid, unlock_price]}) + initial_ids[rid] = kind + else: + if unlock_price < 1: + errs.append({"category": "cross_field", "message": "%s 待解锁行须有一次性解锁价(并联双通道②): %d" % [rid, unlock_price]}) + if not ROUTE_IDS.has(prereq): + errs.append({"category": "enum", "message": "%s prereq_route 出域 %s: %s" % [rid, ROUTE_IDS, prereq]}) + elif prereq == rid: + errs.append({"category": "cross_field", "message": "%s prereq_route 不得自指" % rid}) + elif kind_by_id.has(prereq) and String(kind_by_id[prereq]) != kind: + errs.append({"category": "cross_field", "message": "%s 前置 %s 类型不符(武器←武器/甲←甲): %s vs %s" % [rid, prereq, kind, String(kind_by_id[prereq])]}) + # 初始持有恰两行且一武器一甲(长剑/轻甲——语义继承自已退役属性锻造行) + var init_weapons := 0 + var init_armors := 0 + for k in initial_ids.keys(): + if String(initial_ids[k]) == "weapon": + init_weapons += 1 + elif String(initial_ids[k]) == "armor": + init_armors += 1 + if init_weapons != 1 or init_armors != 1: + errs.append({"category": "coverage", "message": "初始持有须恰一武器一甲(现 sword/light): 武器 %d / 甲 %d" % [init_weapons, init_armors]}) + # 默认轴=初始持有路线类型轴(Q1 裸体态语义:未锻造=0 加值且类型=普通/轻甲) + if init_weapons == 1 and init_armors == 1: + for row in routes: + if not bool(row.get("initial_owned", false)): + continue + if String(row.get("kind", "")) == "weapon" and String(row.get("type_axis", "")) != default_attack_type: + errs.append({"category": "cross_field", "message": "default_attack_type 须=初始武器路线类型轴(裸体态普通): %s vs %s" % [default_attack_type, String(row.get("type_axis", ""))]}) + if String(row.get("kind", "")) == "armor" and String(row.get("type_axis", "")) != default_armor_type: + errs.append({"category": "cross_field", "message": "default_armor_type 须=初始甲路线类型轴(裸体态轻甲): %s vs %s" % [default_armor_type, String(row.get("type_axis", ""))]}) + return errs diff --git a/game/data/schemas/forge_table.gd.uid b/game/data/schemas/forge_table.gd.uid new file mode 100644 index 00000000..3c773035 --- /dev/null +++ b/game/data/schemas/forge_table.gd.uid @@ -0,0 +1 @@ +uid://08fuv4rzuos2h diff --git a/game/data/tables/forge.tres b/game/data/tables/forge.tres new file mode 100644 index 00000000..1d629efb --- /dev/null +++ b/game/data/tables/forge.tres @@ -0,0 +1,98 @@ +[gd_resource type="Resource" script_class="ForgeTable" load_steps=2 format=3] + +[ext_resource type="Script" path="res://data/schemas/forge_table.gd" id="1"] + +[resource] +script = ExtResource("1") +default_attack_type = "normal" +default_armor_type = "light" +routes = [{ +"route_id": "sword", +"display_name": "长剑锻造", +"kind": "weapon", +"type_axis": "normal", +"cap": 5, +"per_level_gain": 1, +"base_ac": 0, +"base_price": 20, +"price_ratio_pct": 150, +"initial_owned": true, +"prereq_route": "", +"unlock_price": 0 +}, { +"route_id": "spear", +"display_name": "长枪锻造", +"kind": "weapon", +"type_axis": "pierce", +"cap": 5, +"per_level_gain": 1, +"base_ac": 0, +"base_price": 20, +"price_ratio_pct": 150, +"initial_owned": false, +"prereq_route": "sword", +"unlock_price": 200 +}, { +"route_id": "staff", +"display_name": "法杖锻造", +"kind": "weapon", +"type_axis": "magic", +"cap": 5, +"per_level_gain": 1, +"base_ac": 0, +"base_price": 20, +"price_ratio_pct": 150, +"initial_owned": false, +"prereq_route": "sword", +"unlock_price": 200 +}, { +"route_id": "light", +"display_name": "轻甲锻造", +"kind": "armor", +"type_axis": "light", +"cap": 5, +"per_level_gain": 1, +"base_ac": 10, +"base_price": 20, +"price_ratio_pct": 150, +"initial_owned": true, +"prereq_route": "", +"unlock_price": 0 +}, { +"route_id": "heavy", +"display_name": "重甲锻造", +"kind": "armor", +"type_axis": "heavy", +"cap": 5, +"per_level_gain": 1, +"base_ac": 12, +"base_price": 20, +"price_ratio_pct": 150, +"initial_owned": false, +"prereq_route": "light", +"unlock_price": 200 +}, { +"route_id": "ward", +"display_name": "魔法护甲锻造", +"kind": "armor", +"type_axis": "ward", +"cap": 5, +"per_level_gain": 1, +"base_ac": 9, +"base_price": 20, +"price_ratio_pct": 150, +"initial_owned": false, +"prereq_route": "light", +"unlock_price": 200 +}] + +; 锻造工坊实装批 T1(2026-10-12——迭代 23;设计权威 design/gdd/systems/跨局循环/锻造工坊.md v0.6, +; 需求卡片 design/research/需求卡片-锻造工坊实装批-2026-10-12.md T1 锚表): +; 六路线 provisional 投值——cap 5/每级增量统一 +1(v1.2 用户裁:provisional 线性起步,4a 前缓后陡为 +; 终值形状方向归 M4)/甲型基础 AC 10-12-9(现役结构值 settlement.tres 矩阵同行)/基础价 20/公比 150% +; (历史考古参数 meta.tres:82-84 退役批注)/各级价格 20/30/45/67/101/一次性解锁价 200(中段三级 213 九折)。 +; 初始持有=长剑+轻甲(语义继承自已退役属性锻造行——迭代 21 T5 行族删除后的新建行族,非改造)。 +; 并联双通道(4b-1 裁):长枪/法杖←长剑练满、重甲/魔法护甲←轻甲练满,或一次性解锁价直购,互不影响。 +; 默认两轴(契约 2 所有权移交):hero.tres 静态表两轴随本批删除,默认值归本表常驻持有——normal/light +; = 裸体态类型(Q1:未解锁/未锻造=0 加值)。全部数值 provisional 终值归 M4 数值批。 +; 表运行期只读(铁律):永久强化只写存档 forge_routes 域,本表零写面。 diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index a006b27f..278b6079 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -28,6 +28,7 @@ func _suites() -> Array: return [ {"path": "res://tests/unit/example_test.gd", "expect": 0}, {"path": "res://tests/unit/data_config/config_db_test.gd", "expect": 0}, + {"path": "res://tests/unit/data_config/forge_table_test.gd", "expect": 0}, # 迭代 23 锻造工坊批 T1——六路线 schema 正反(V1)+特效留空(V11)+几何价格梯(V2 半边) {"path": "res://tests/unit/data_config/config_db_fatal_oob_test.gd", "expect": 1}, {"path": "res://tests/unit/data_config/treasure_target_violation_test.gd", "expect": 1}, # 迭代 7 T1——真视窄域护栏 {"path": "res://tests/unit/data_config/reward_dangling_treasure_test.gd", "expect": 1}, # 迭代 7 T1——秘宝双表校验 diff --git a/game/tests/unit/data_config/forge_table_test.gd b/game/tests/unit/data_config/forge_table_test.gd new file mode 100644 index 00000000..8bf9d299 --- /dev/null +++ b/game/tests/unit/data_config/forge_table_test.gd @@ -0,0 +1,200 @@ +# tests/unit/data_config/forge_table_test.gd — 锻造工坊配置表单测(迭代 23 T1) +# 覆盖(需求卡片 §7.2):V1 六路线 schema 零违例(id 唯一/类型轴枚举/值域——出域行逐类构造各一行全部被拒) +# + V11 满级特效留空(行字段闭集=无特效参数位 + 效果类型闭集 24 种零新增)+ 几何价格梯 20/30/45/67/101。 +# 运行:godot --headless --path game --script res://tests/unit/data_config/forge_table_test.gd(expect 0) +# 协议(ADR-0004 + tests/README):extends SceneTree + quit(code);负向用例资源直构不碰生产表(enemy_table 先例)。 +extends SceneTree + +const ConfigDBScript := preload("res://core/config_db.gd") +const ForgeTableScript := preload("res://data/schemas/forge_table.gd") +const EffectEngineScript := preload("res://core/effect_engine.gd") +const Kit := preload("res://tests/unit/test_kit.gd") + + +func _init() -> void: + var db = ConfigDBScript.new() + var load_ok: bool = db.load_tables("res://data/tables", true) + Kit.check("生产 forge.tres 随全表加载 + validate 零违例〔锻造工坊批 V1〕", load_ok and db.diagnostics_count("error") == 0) + if load_ok: + _test_production_rows(db) + _test_price_ladder(db) + _test_negative_cases() + _test_effect_kinds_closed() + quit(Kit.finish("forge_table_test")) + + +# ---- 生产表六行正向(V1 生产面 + V11 行字段闭集)---- + +func _test_production_rows(db) -> void: + var rows: Array = db.forge_routes() + Kit.check("六路线行族规模恰 6(三武器+三甲)〔锻造工坊批 V1〕", rows.size() == 6) + var ids := [] + for row in rows: + ids.append(String(row.get("route_id", ""))) + ids.sort() + Kit.check("路线 id 全集 = {sword, spear, staff, light, heavy, ward}〔锻造工坊批 V1〕", + ids == ["heavy", "light", "spear", "staff", "sword", "ward"]) + # V11:行字段闭集——键集恰 12 项,无特效参数位(满级特效本期不做,契约 7) + var expect_keys := ForgeTableScript.ROW_KEYS.duplicate() + expect_keys.sort() + var keys_closed := true + for row in rows: + var ks := (row as Dictionary).keys() + ks.sort() + if ks != expect_keys: + keys_closed = false + Kit.check("六行字段键集恰 = ROW_KEYS 闭集(无特效参数位)〔锻造工坊批 V11〕", keys_closed) + # 初始持有恰长剑+轻甲(语义继承自已退役属性锻造行) + var initial := [] + for row in rows: + if bool(row.get("initial_owned", false)): + initial.append(String(row.get("route_id", ""))) + initial.sort() + Kit.check("初始持有恰 sword+light〔锻造工坊批 V1〕", initial == ["light", "sword"]) + # 默认两轴(契约 2 所有权移交——裸体态 normal/light) + Kit.check("默认攻击类型轴 = normal(常驻持有)〔锻造工坊批 V1〕", db.forge_default_attack_type() == "normal") + Kit.check("默认甲型轴 = light(常驻持有)〔锻造工坊批 V1〕", db.forge_default_armor_type() == "light") + # 类型轴分族与并联双通道前置(4b-1 裁) + Kit.check("长枪/法杖 ← 长剑练满(并联双通道①)〔锻造工坊批 V1〕", + String(db.forge_route("spear").get("prereq_route", "")) == "sword" + and String(db.forge_route("staff").get("prereq_route", "")) == "sword") + Kit.check("重甲/魔法护甲 ← 轻甲练满(并联双通道①)〔锻造工坊批 V1〕", + String(db.forge_route("heavy").get("prereq_route", "")) == "light" + and String(db.forge_route("ward").get("prereq_route", "")) == "light") + Kit.check("四待解锁路线一次性解锁价 = 200〔锻造工坊批 V1〕", + int(db.forge_route("spear").get("unlock_price", 0)) == 200 + and int(db.forge_route("staff").get("unlock_price", 0)) == 200 + and int(db.forge_route("heavy").get("unlock_price", 0)) == 200 + and int(db.forge_route("ward").get("unlock_price", 0)) == 200) + # 甲型基础 AC(provisional 现役结构值——settlement.tres 矩阵同行) + Kit.check("甲型基础 AC = 轻 10 / 重 12 / 魔法护甲 9〔锻造工坊批 V1〕", + int(db.forge_route("light").get("base_ac", 0)) == 10 + and int(db.forge_route("heavy").get("base_ac", 0)) == 12 + and int(db.forge_route("ward").get("base_ac", 0)) == 9) + # 按类型轴反查(甲路线 id=甲型同值的显式化入口) + Kit.check("forge_armor_route_by_type(ward) = 魔法护甲锻造〔锻造工坊批 V1〕", + String(db.forge_armor_route_by_type("ward").get("route_id", "")) == "ward") + Kit.check("forge_weapon_route_by_type(pierce) = 长枪锻造〔锻造工坊批 V1〕", + String(db.forge_weapon_route_by_type("pierce").get("route_id", "")) == "spear") + # 缺行/未知轴 = 空字典合法态 + Kit.check("未知 route_id / 未知类型轴 = 空字典〔锻造工坊批 V1〕", + db.forge_route("blade").is_empty() and db.forge_armor_route_by_type("plate").is_empty()) + + +# ---- 几何价格梯(V2 价格面单测半边——购买写在 T3 冒烟)---- + +func _test_price_ladder(db) -> void: + var row: Dictionary = db.forge_route("sword") + var prices := [] + for owned in range(5): + prices.append(ForgeTableScript.level_price(row, owned)) + Kit.check("长剑各级价格 = 20/30/45/67/101(基础 20×150%^已购级数)〔锻造工坊批 V2〕", + prices == [20, 30, 45, 67, 101]) + Kit.check("已满级(owned=5)再购价照算 151(cap 闸归写口,价格函数纯几何)〔锻造工坊批 V2〕", + ForgeTableScript.level_price(row, 5) == 151) + + +# ---- 负向:出域行逐类构造各一行全部被拒(V1)---- + +func _base_rows() -> Array[Dictionary]: + var out: Array[Dictionary] = [] + var mk := func(rid: String, kind: String, axis: String, initial: bool, prereq: String, unlock: int) -> Dictionary: + return {"route_id": rid, "display_name": rid + "锻造", "kind": kind, "type_axis": axis, + "cap": 5, "per_level_gain": 1, "base_ac": 10 if kind == "armor" else 0, + "base_price": 20, "price_ratio_pct": 150, "initial_owned": initial, + "prereq_route": prereq, "unlock_price": unlock} + out.append(mk.call("sword", "weapon", "normal", true, "", 0)) + out.append(mk.call("spear", "weapon", "pierce", false, "sword", 200)) + out.append(mk.call("staff", "weapon", "magic", false, "sword", 200)) + out.append(mk.call("light", "armor", "light", true, "", 0)) + out.append(mk.call("heavy", "armor", "heavy", false, "light", 200)) + out.append(mk.call("ward", "armor", "ward", false, "light", 200)) + return out + + +func _has_cat(errs: Array, cat: String) -> bool: + for e in errs: + if String(e.get("category", "")) == cat: + return true + return false + + +# 多字段变异器(lambda 单表达式装不下的用例——命名函数承载) +func _mut_spear_initial(t) -> void: + t.routes[1]["initial_owned"] = true + t.routes[1]["prereq_route"] = "" + t.routes[1]["unlock_price"] = 0 + + +func _test_negative_cases() -> void: + # 夹具本身须零违例(否则负向判读失效) + var t_ok = ForgeTableScript.new() + t_ok.routes = _base_rows() + Kit.check("负向夹具(合法六行)validate 零违例〔锻造工坊批 V1〕", t_ok.validate().is_empty()) + + var cases := [] + # route_id 出域 + cases.append(["route_id 出域即拒", func(t): t.routes[0]["route_id"] = "blade", "enum"]) + # route_id 重复 + cases.append(["route_id 重复即拒", func(t): t.routes[1]["route_id"] = "sword", "dup_key"]) + # kind 出域 + cases.append(["kind 出域即拒", func(t): t.routes[1]["kind"] = "shield", "enum"]) + # 武器类型轴出域 + cases.append(["武器 type_axis 出域即拒", func(t): t.routes[1]["type_axis"] = "ward", "enum"]) + # 同攻击类型两路线 + cases.append(["同攻击类型第二路线即拒", func(t): t.routes[2]["type_axis"] = "normal", "dup_key"]) + # cap 越域 + cases.append(["cap 越域即拒", func(t): t.routes[0]["cap"] = 0, "domain"]) + # 每级增量越域 + cases.append(["per_level_gain 越域即拒", func(t): t.routes[0]["per_level_gain"] = -1, "domain"]) + # 基础价越域 + cases.append(["base_price 越域即拒", func(t): t.routes[0]["base_price"] = 0, "domain"]) + # 公比越域 + cases.append(["price_ratio_pct 越域即拒", func(t): t.routes[0]["price_ratio_pct"] = 99, "domain"]) + # 武器行 base_ac 非零 + cases.append(["武器行 base_ac 非零即拒", func(t): t.routes[0]["base_ac"] = 5, "cross_field"]) + # 甲行 base_ac 越域 + cases.append(["甲行 base_ac 越域即拒", func(t): t.routes[3]["base_ac"] = -1, "domain"]) + # 待解锁行无解锁价 + cases.append(["待解锁行无一次性解锁价即拒", func(t): t.routes[1]["unlock_price"] = 0, "cross_field"]) + # 初始持有行带前置 + cases.append(["初始持有行带前置即拒", func(t): t.routes[0]["prereq_route"] = "spear", "cross_field"]) + # 初始持有行带解锁价 + cases.append(["初始持有行带解锁价即拒", func(t): t.routes[3]["unlock_price"] = 100, "cross_field"]) + # 前置出域 + cases.append(["prereq_route 出域即拒", func(t): t.routes[1]["prereq_route"] = "blade", "enum"]) + # 前置类型不符(武器←甲) + cases.append(["前置类型不符即拒(武器行前置甲路线)", func(t): t.routes[1]["prereq_route"] = "light", "cross_field"]) + # 前置自指 + cases.append(["前置自指即拒", func(t): t.routes[1]["prereq_route"] = "spear", "cross_field"]) + # 规模非六 + cases.append(["行族规模非六即拒", func(t): t.routes.pop_back(), "structural"]) + # 初始持有非一武器一甲(spear 转初始且清前置/解锁价——隔离出 coverage 单类) + cases.append(["初始持有非一武器一甲即拒", Callable(self, "_mut_spear_initial"), "coverage"]) + # 默认轴≠初始路线类型轴 + cases.append(["默认攻击轴≠初始武器类型轴即拒", func(t): t.default_attack_type = "pierce", "cross_field"]) + # 默认甲轴出域 + cases.append(["默认甲轴出域即拒", func(t): t.default_armor_type = "plate", "enum"]) + # 同甲型两路线 + cases.append(["同甲型第二路线即拒", func(t): t.routes[4]["type_axis"] = "light", "dup_key"]) + + for c in cases: + var label: String = c[0] + var mutate: Callable = c[1] + var expect_cat: String = c[2] + var t = ForgeTableScript.new() + t.routes = _base_rows() + mutate.call(t) + var errs: Array = t.validate() + Kit.check("锻造 schema 负向:%s〔锻造工坊批 V1〕" % label, _has_cat(errs, expect_cat)) + + +# ---- V11:效果类型闭集零新增(满级特效本期不开新词)---- +# 实数订正(L2 在册):卡片 F11「24 种」系 2026-10-07 设计文档事实快照——迭代 20 T1 +7、迭代 22 T3 +1 +# 后现行 31;本批零扩签,钉现行数(将来任何一批加词即转红提示复核)。 + +func _test_effect_kinds_closed() -> void: + Kit.check("效果引擎效果类型闭集 31 种零新增(锻造工坊批不开效果类型词)〔锻造工坊批 V11〕", + EffectEngineScript.EFFECT_KINDS.size() == 31) + Kit.check("效果引擎事件词表在册 20 种(本批零扩签参照面)〔锻造工坊批 V11〕", + EffectEngineScript.EVENTS.size() == 20) diff --git a/game/tests/unit/data_config/forge_table_test.gd.uid b/game/tests/unit/data_config/forge_table_test.gd.uid new file mode 100644 index 00000000..f843b059 --- /dev/null +++ b/game/tests/unit/data_config/forge_table_test.gd.uid @@ -0,0 +1 @@ +uid://frunrpvqgocl3 From 00d98b6f74a46c3214e11873b1bdc393637b172b Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 12 Oct 2026 23:31:50 +0800 Subject: [PATCH 2/8] =?UTF-8?q?feat(=E8=BF=AD=E4=BB=A323-T2):=20=E4=B8=A4?= =?UTF-8?q?=E6=A0=B9=E7=B1=BB=E5=9E=8B=E8=BD=B4=E6=89=80=E6=9C=89=E6=9D=83?= =?UTF-8?q?=E7=A7=BB=E4=BA=A4=E2=80=94=E2=80=94hero.tres=20=E4=B8=A4?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=88=A0=E9=99=A4+ForgeTable=20=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=80=BC=E5=B8=B8=E9=A9=BB=E6=8C=81=E6=9C=89+?= =?UTF-8?q?=E5=8D=95=E4=BD=8D=E5=BF=AB=E7=85=A7=E6=94=B9=E8=AF=BB=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hero.tres:armor_type/attack_type 两字段删除(含 2026-09-18 前置批登记注记退役);armor_class=10 保留为甲路线缺失兜底基准 - hero_table.gd schema:两 @export 字段与枚举校验同步删除(三数值轴 max_hp/atk/armor_class) - unit_container.gd:spawn 签名 +2(hero_attack_type/hero_armor_type,空串=db.forge_default_* 回退)——三参旧调用方零适配,行为逐位连续(normal/light) - smoke_all.gd::1901 hero_attack 行类型键主语换代为 forge_default_attack_type();新增 _smoke_forge_axes 段(V5 前半:字段不在位+默认值+零注入回退快照)4 断言 - 轻闸:冒烟 902/0(898 基线 +4)+run_all 23 套全绿 --- game/core/unit_container.gd | 8 +++++--- game/data/schemas/hero_table.gd | 9 +++------ game/data/tables/hero.tres | 18 +++++++++--------- game/smoke/smoke_all.gd | 26 +++++++++++++++++++++++--- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/game/core/unit_container.gd b/game/core/unit_container.gd index ae00ed95..f438f036 100644 --- a/game/core/unit_container.gd +++ b/game/core/unit_container.gd @@ -10,7 +10,8 @@ # (pattern 环数组 + steps 指针 + heavy_multiplier)随技能池整体退役——拍结构保留(每结算每存活敌恰行动一次、 # 行序、先作用后推进),"轮到什么"改由开轮摇号决定(死亡即不动手:摇定后兑现前死亡 → 本拍作废、不 commit)。 # 切片登记接口漂移:heal() 为切片新增(战斗结算 治疗 clamp 需要写入口);spawn 签名 +3 参数(E4/区间批/ -# 遭遇骰——正式侧 GDD/registry 修订随对应批)。 +# 遭遇骰——正式侧 GDD/registry 修订随对应批);迭代 23 T2 +2 参数(hero_attack_type/hero_armor_type—— +# 两根类型轴注入面:hero.tres 静态表两轴删除〔契约 2 所有权移交〕,空串=锻造工坊配置默认值回退)。 # 实现 GDD:design/gdd/systems/战斗循环/敌人系统.md(registry 追溯锚,命名规范见 CLAUDE.md) class_name UnitContainer extends RefCounted @@ -44,7 +45,8 @@ func _init(db_ref: ConfigDb) -> void: # 逐行实例化、生成序 = 文件行序(行序即契约,敌人系统 v0.15);行集实例总数(各行传入数量求和) # ≤ ENEMY_ROWSET_INSTANCE_MAX 守卫 fail-fast。 func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_overrides: Array = [], - atk_override: int = -1, max_hp_override: int = -1, enemy_atk_overrides: Array = []) -> bool: + atk_override: int = -1, max_hp_override: int = -1, enemy_atk_overrides: Array = [], + hero_attack_type: String = "", hero_armor_type: String = "") -> bool: if _spawned: push_warning("[敌人系统] Alive 相位重复 spawn(相位守卫)") return false @@ -61,7 +63,7 @@ func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_overrides return false # max_hp = 生效上限(非入参 hero_hp)——2026-09-11 关卡扩展批修复:跨战斗携带 HP 时上限不得压扁 # (恢复点/治疗包的满血守卫与治疗上限随之失效——切片登记接口语义澄清) - _hero = {"hp": hero_hp, "max_hp": eff_max_hp, "atk": eff_atk, "is_hero": true, "alive": true, "display_name": "探险者", "enemy_id": "hero", "armor_type": String(db.hero.get("armor_type")) if db.hero.get("armor_type") else "light", "attack_type": String(db.hero.get("attack_type")) if db.hero.get("attack_type") else "normal"} # 护甲/攻击类型轴快照(2026-09-18 营地扩展前置批——可配置;甲效矩阵消费面) + _hero = {"hp": hero_hp, "max_hp": eff_max_hp, "atk": eff_atk, "is_hero": true, "alive": true, "display_name": "探险者", "enemy_id": "hero", "armor_type": hero_armor_type if hero_armor_type != "" else db.forge_default_armor_type(), "attack_type": hero_attack_type if hero_attack_type != "" else db.forge_default_attack_type()} # 护甲/攻击类型轴快照(迭代 23 T2——hero.tres 两轴删除后改读注入值,空串回退锻造工坊配置默认值 normal/light=行为连续;甲效矩阵消费面) _enemies.clear() # 难度曲线与Boss DifficultyTable 修饰(2026-09-12 结构位兑现):施加于行值、行值快照之前——MVP 空表 = 恒 1.0 无行为(AC-10);混编行集内全部敌种同乘((L,D) 精确匹配一次取值——F9 同乘不改变异,敌人系统 v0.15) var mod: Dictionary = db.resolve_difficulty_modifiers(level_index, node_depth) var hp_mult: float = float(mod.get("hp_mult", 1.0)) diff --git a/game/data/schemas/hero_table.gd b/game/data/schemas/hero_table.gd index 0c953498..11ad18c3 100644 --- a/game/data/schemas/hero_table.gd +++ b/game/data/schemas/hero_table.gd @@ -3,14 +3,15 @@ # Date: 2026-09-10 # 数据配置 HeroTable schema(敌人系统 接口漂移新增;单行全局,Rule 4 豁免)。 # atk = 基础攻击恒存在(2026-09-11 用户裁决):域 [1,999]、0 = 加载拒绝——零基线死锁 schema 层杜绝。 +# 〔2026-10-12 迭代 23 锻造工坊实装批 T2:armor_type/attack_type 两根类型轴字段删除——契约 2 +# 所有权移交,默认值归锻造工坊配置(forge.tres)常驻持有、选中态写存档经参数面注入;本表只剩 +# 三数值轴 max_hp/atk/armor_class(armor_class=甲路线缺失时兜底基准,甲型基础 AC 归 forge.tres 行)。〕 class_name HeroTable extends Resource @export var max_hp: int = 50 # [1,999] knob(provisional,溯源 = 原型锚) @export var atk: int = 6 # [1,999] knob——基础攻击(溯源 = 概念原型 HERO_BASE_ATK,provisional 归 难度曲线与Boss);消费面 = 战斗结算·战斗段先手攻击 @export var armor_class: int = 10 # [0,999] knob——防守方护甲等级基准(2026-09-16 护甲等级掷点批,暂定 10 归 难度曲线与Boss);消费面 = 战斗结算掷点判定阈值基数(ac_up 效果加算其上) -@export var armor_type: String = "light" # enum {light, ward, heavy}——主角护甲类型轴(2026-09-18 营地扩展前置批:可配置可修改;缺省 light 向后兼容);消费面 = 战斗结算甲效矩阵(现行敌方攻击恒 normal 档 = 数值中性的结构位) -@export var attack_type: String = "normal" # enum {normal, pierce, magic}——主角基础攻击类型轴(2026-09-18 营地扩展前置批:基础攻击量(atk)与攻击类型抽象分离——营地永久强化换攻击类型 = 改此字段,量轴不动);消费面 = 战斗结算·战斗段基础分量查甲效矩阵 func validate() -> Array: @@ -21,8 +22,4 @@ func validate() -> Array: errs.append({"category": "domain", "message": "atk 越域 [1,999]: %d" % atk}) if armor_class < 0 or armor_class > 999: errs.append({"category": "domain", "message": "armor_class 越域 [0,999]: %d" % armor_class}) - if not ["light", "ward", "heavy"].has(armor_type): - errs.append({"category": "enum", "message": "armor_type 出域 {light, ward, heavy}: %s" % armor_type}) - if not ["normal", "pierce", "magic"].has(attack_type): - errs.append({"category": "enum", "message": "attack_type 出域 {normal, pierce, magic}: %s" % attack_type}) return errs diff --git a/game/data/tables/hero.tres b/game/data/tables/hero.tres index c78839f4..b7889025 100644 --- a/game/data/tables/hero.tres +++ b/game/data/tables/hero.tres @@ -7,8 +7,12 @@ script = ExtResource("1") max_hp = 26 atk = 2 armor_class = 10 -armor_type = "light" -attack_type = "normal" + +; 〔2026-10-12 迭代 23 锻造工坊实装批 T2——两根类型轴删除(契约 2 所有权移交):armor_type/ +; attack_type 两字段随本批从静态表删除,默认值归锻造工坊配置常驻持有(forge.tres +; default_attack_type=normal/default_armor_type=light),选中态写存档域 forge_routes、冒险开始 +; 经参数面注入;单位快照读取面改读注入值(缺省回退锻造配置默认值——行为连续)。原 2026-09-18 +; 营地扩展前置批「两根可配置轴」登记随字段删除退役,历史记载归 git 考古。〕 ; 数值压缩批(2026-09-17 用户拍板暂定——整体绝对值轴 ÷3): ; max_hp 50→17(16.67 四舍五入)、atk 6→2。armor_class 10 不动(掷点阈值轴,相对量纲不压缩—— @@ -18,13 +22,9 @@ attack_type = "normal" ; 护甲等级掷点批(2026-09-16 用户拍板):armor_class = 10(暂定基准,归 难度曲线与Boss; ; EV 参考数按判定式复算 = 期望减免 21.25%);ac_up 增益 +1 AC/层(×2 层 = AC+2)在其上加算。 ; -; 营地扩展前置批(2026-09-18 用户指令):新增两根可配置轴—— -; armor_type = 主角护甲类型(light/ward/heavy,缺省 light):轴现行休眠——甲效矩阵消费面 = 主角侧, -; 敌方攻击不查矩阵(2026-09-18 甲效矩阵调参批归因改述,原「恒 normal 档故三甲全 100% 中性」随旧三档 -; 结构退役),类型轴生效待敌方技能攻击类型批; -; attack_type = 主角基础攻击类型(normal/pierce/magic,缺省 normal):基础攻击量(atk)与攻击类型抽象分离—— -; 营地永久强化"换成魔法/穿刺攻击"= 改此字段(atk 量轴零改动),战斗段基础分量即按此查甲效矩阵。 -; 两轴全部 provisional,营地建设批(元进度侧)拍板正式投值。 +; 〔营地扩展前置批(2026-09-18 用户指令)两根可配置轴 armor_type/attack_type 已于 2026-10-12 +; 迭代 23 锻造工坊实装批 T2 删除——所有权移交锻造工坊配置(见顶部注记);甲型基础 AC 随甲路线 +; 行承载(轻 10/重 12/魔法护甲 9),本表 armor_class=10 保留为甲路线缺失时的兜底基准。〕 ; ; Package B 迭代·第 2 轮(2026-09-22——计划书 §6.2,用户拍板一B):max_hp 17→20。第 1 轮读数 ; 触发 §6.2(L1 精英承伤 43-55% vs 靶 ≤20%、L1 净消耗 17.7/17=104% 破产、normal L1 通过率 diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index 13034700..23ff8106 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -1462,8 +1462,8 @@ func _ac11_tier_samples() -> Array: # 同种子停手轮恒同掷点(效果只改乘区、不动掷点流)→ 落地伤害精确关系: # 增伤 = 基线 ×1.5、乏力 = ×0.75(hero_offense 池乘攻击合计——2026-09-19 充能退役后 = 单分量,直乘旁路退役); # 硬化 = 基线 ×0.5 且 < 基线(enemy_defense 池负值折半——+100 翻倍符号 bug 随批纠正)。 -# 基线构成(加法核 2026-09-18 + 充能退役 2026-09-19):基线 100——hero attack_type=normal 对轻甲中性 0 修正 -# (目标 = L1 地精轻甲;AC 0 恒全额非暴击种子)。 +# 基线构成(加法核 2026-09-18 + 充能退役 2026-09-19):基线 100——hero 攻击类型=normal(锻造工坊配置默认值, +# 迭代 23 T2 起 hero.tres 两轴删除)对轻甲中性 0 修正(目标 = L1 地精轻甲;AC 0 恒全额非暴击种子)。 func _smoke_pool_wiring() -> void: print("--- 主角乘区接线 + 硬化折半方向 ---") var base_row := _hero_attack_row_with({}, 100, 20260918) @@ -1898,7 +1898,8 @@ func _smoke_full_battle(L: int, D: int, label: String) -> void: if row.get("source_class", "") == "hero_attack": hero_attacks += 1 # 单分量伤害构成行(2026-09-19 充能退役批):hero_attack 行必须携带实际攻击类型键 + 掷点读数(P2 归因) - var atk_t: String = String(_db.hero.attack_type) + # 迭代 23 T2:类型键主语换代——hero.tres 两轴删除,实际攻击类型=锻造工坊配置默认值(本夹具零注入) + var atk_t: String = String(_db.forge_default_attack_type()) if not (row.has(atk_t) and row.has("roll") and row.has("ac") and row.has("tier")): hero_breakdown_ok = false check(has_verdict, "%s 步骤流含判定行" % label) @@ -4408,10 +4409,29 @@ func _smoke_meta() -> void: _meta_pricing_and_buy() _meta_settle_and_workshop() _meta_injection() + _smoke_forge_axes() _pause_overlay_projection() _career_overlay_projection() +# ---- 锻造工坊·两根类型轴所有权移交(迭代 23 T2——V5 前半:hero.tres 两轴删除+锻造配置默认值)---- +# 后半(build_adventure_params 注入默认值/选中覆盖)随 T4 注入接线同批断言;写口/解锁面归 T3。 +func _smoke_forge_axes() -> void: + print("--- 锻造工坊·类型轴所有权移交(T2)---") + # V5:hero.tres 静态表两轴已删(全表加载零违例——config_db_test 同款闸;此处钉字段不在位) + check(not ("attack_type" in _db.hero) and not ("armor_type" in _db.hero), + "hero.tres 无 attack_type/armor_type 字段(契约 2——静态表两轴删除)〔锻造工坊批 V5〕") + # 锻造配置默认值 = normal/light(常驻持有——与柜台解锁状态解耦,未点亮也照常可读) + check(_db.forge_default_attack_type() == "normal" and _db.forge_default_armor_type() == "light", + "锻造配置默认值 attack_type=normal / armor_type=light(常驻持有)〔锻造工坊批 V5〕") + # 单位快照回退面:spawn 三参形态(零注入)= 锻造配置默认值(行为连续——原 hero.tres normal/light) + var uc = load("res://core/unit_container.gd").new(_db) + check(uc.spawn(1, 0, int(_db.hero.max_hp)), "spawn 受理(三参形态——两轴缺省回退面)") + check(String(uc.hero_unit().get("attack_type", "")) == "normal" + and String(uc.hero_unit().get("armor_type", "")) == "light", + "主角单位快照两轴 = 锻造配置默认值 normal/light(零注入回退——行为连续)〔锻造工坊批 V5〕") + + # ---- 暂停层只读投影一致性(迭代 17 卡③——R4 数据只读同源:暂停层展示值逐项 == MetaProgression # 访问器读数;投影拼装与 main._pause_data 同构(同访问器族同序)。只读性连接巡检与零动效 # 断言归 ui_struct_probe 呈现侧,本段只管数据面)---- From 6def3544a05c242e46ade616847064be6da5cbdc Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 12 Oct 2026 23:44:03 +0800 Subject: [PATCH 3/8] =?UTF-8?q?feat(=E8=BF=AD=E4=BB=A323-T5):=20=E5=AD=98?= =?UTF-8?q?=E6=A1=A3=E5=9F=9F=20forge=5Froutes=20v7=E2=80=94=E2=80=94?= =?UTF-8?q?=E5=85=AD=E8=B7=AF=E7=BA=BF=E7=AD=89=E7=BA=A7+=E7=9B=B4?= =?UTF-8?q?=E8=B4=AD=E8=A7=A3=E9=94=81=E6=B8=85=E5=8D=95+=E9=80=89?= =?UTF-8?q?=E4=B8=AD=E6=A0=87=E8=AE=B0=EF=BC=88=E6=97=A0=E7=89=B9=E6=95=88?= =?UTF-8?q?=E5=9F=9F=EF=BC=89+=E6=97=A7=E6=A1=A3=E7=BC=BA=E9=94=AE?= =?UTF-8?q?=E8=A1=A5=E9=BD=90=3D=E5=88=9D=E5=A7=8B=E6=8C=81=E6=9C=89?= =?UTF-8?q?=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 〔执行序调整:T5 先行于 T3——写口族硬依赖存档域,台账记录〕 - SAVE_FORMAT_VERSION 6→7 + _migrate_v6_v7 空迁移(缺键补齐模式,v3→v4 先例;幂等零漂移) - _default_data 新域 forge_routes={levels 六键/unlocked 直购清单/weapon+armor 选中};_normalize 经默认模型自动补齐(旧档=初始持有态 sword/light 在档余四锁)+退役键白名单自动收口 - 访问器组:forge_route_level/set_forge_route_level/forge_unlocked_purchases/add_forge_unlock/forge_weapon/set_forge_weapon/forge_armor/set_forge_armor/forge_routes_snapshot(值拷贝出户) - 解锁态派生不落档:初始持有(表 initial_owned)∨直购(unlocked)∨练满前一档(levels[prereq]≥cap)——派生单点归 T3 MetaProgression - 冒烟 V8:往返逐域相等+v6 旧档既有域零折算/缺键补齐初始态+域键集恰四项(无特效域)+levels 六键恰全+快照值拷贝;迁移链版本断言 6→7 三处换代 - 轻闸:冒烟 908/0(902 基线 +6)+run_all 23 套全绿 --- game/core/save_manager.gd | 80 ++++++++++++++++++++++++++++++++++++++- game/smoke/smoke_all.gd | 54 +++++++++++++++++++++----- 2 files changed, 124 insertions(+), 10 deletions(-) diff --git a/game/core/save_manager.gd b/game/core/save_manager.gd index 8b054c6a..968b4a39 100644 --- a/game/core/save_manager.gd +++ b/game/core/save_manager.gd @@ -21,6 +21,10 @@ # tree_nodes / fork_sides / tree_struct —— 内容 owner = 天赋树.md(迭代 12 T9 建域、迭代 21 T5 # 换代承载:tree_nodes=node_id→已购等级;fork_sides=fork_group→激活态;tree_struct=树结构 # 版本标记〔缺失=换代前结构→加载时树域破坏性废弃重置——开发阶段无迁移,Q3 用户裁〕) +# forge_routes —— 内容 owner = 锻造工坊.md(迭代 23 T5:levels 六路线等级+unlocked 直购解锁清单 +# +weapon/armor 当前选中标记;**无特效域**(裁 6——满级特效本期不做);解锁态派生不落档: +# 初始持有〔表 initial_owned〕∨ 直购〔unlocked〕∨ 练满前一档〔levels[prereq]≥cap〕——派生单点 +# 在 MetaProgression.forge_route_unlocked;旧档缺键补齐=初始持有态,开发阶段禁迁移禁折算) # breakpoint.attunement_charges(主动件充能余量)——每冒险重置语义的断点瞬态附随件,内容装配归 # AdventureRunner(随 T6 接线入断点),本类照单承载;契约 2 域界注记同断点域豁免先例 # recent_summaries(冒险摘要五件事副本)—— 内容 owner = 冒险结构.md(摘要五件事口径) @@ -46,7 +50,7 @@ class_name SaveManager extends RefCounted -const SAVE_FORMAT_VERSION := 6 # registry schema_version:v6=生涯终身计数域(迭代 18 卡④——meta 两新域 adventure_total〔冒险总局数,开局即计含放弃与失败〕/adventure_cleared〔通关局数,三关全通才计 1——判定字段 summary.cleared〕,旧档缺键由 _normalize 按默认补零,收束自增唯一入账=apply_adventure_summary);v5=天赋树域(迭代 12 T9——meta 三新域 tree_nodes〔node_id→已购等级〕/fork_sides〔fork_group→a|b〕/migration_note〔三体系迁移补偿注记〕——M-4 迭代 12 批次二;需求卡片「v3→v4」系同调批未落时行文,v4 已被同调批占用顺延 v5 披露);v4=同调批扩域(meta 三新域 boss_kill_count/attunement_equipped/attune_refresh_level——M-4 迭代 11 第①批 T2;断点充能余量随 T6 接线,旧档缺键 = 合法空态);v3=断点附随件扩列+摘要 keystone_drops(M-3 迭代 10 奖励重构);v2=断点域出生(M-3 迭代 8 卡①);v1=首版;高版本拒读 owner=存档序列化 +const SAVE_FORMAT_VERSION := 7 # registry schema_version:v7=锻造工坊域(迭代 23 T5——meta 新域 forge_routes〔六路线等级 levels+直购解锁清单 unlocked+当前武器/甲选中标记 weapon/armor;无特效域——裁 6 满级特效本期不做〕,旧档缺键由 _normalize 按默认补齐=初始持有态〔sword/light 在档余四锁、选中=初始路线〕,v6→v7 空迁移=缺键补齐模式——v3→v4 先例);v6=生涯终身计数域(迭代 18 卡④——meta 两新域 adventure_total〔冒险总局数,开局即计含放弃与失败〕/adventure_cleared〔通关局数,三关全通才计 1——判定字段 summary.cleared〕,旧档缺键由 _normalize 按默认补零,收束自增唯一入账=apply_adventure_summary);v5=天赋树域(迭代 12 T9——meta 三新域 tree_nodes〔node_id→已购等级〕/fork_sides〔fork_group→a|b〕/migration_note〔三体系迁移补偿注记〕——M-4 迭代 12 批次二;需求卡片「v3→v4」系同调批未落时行文,v4 已被同调批占用顺延 v5 披露);v4=同调批扩域(meta 三新域 boss_kill_count/attunement_equipped/attune_refresh_level——M-4 迭代 11 第①批 T2;断点充能余量随 T6 接线,旧档缺键 = 合法空态);v3=断点附随件扩列+摘要 keystone_drops(M-3 迭代 10 奖励重构);v2=断点域出生(M-3 迭代 8 卡①);v1=首版;高版本拒读 owner=存档序列化 const SUMMARY_CAP := 10 # 最近冒险摘要保留条数(最新在前) const TREE_STRUCT_VERSION := 2 # 树结构版本标记(迭代 21 T5——12 节点 v0.8 结构;加载检测缺失 @@ -67,6 +71,7 @@ func _init(path: String = "user://save.json") -> void: register_migration(3, _migrate_v3_v4) register_migration(4, _migrate_v4_v5) register_migration(5, _migrate_v5_v6) + register_migration(6, _migrate_v6_v7) # v1→v2:断点域出生(旧档无断点 = 合法空态——「继续上局」隐藏,非损坏非债务) @@ -114,6 +119,14 @@ func _migrate_v5_v6(data: Dictionary) -> Dictionary: return data +# v6→v7:锻造工坊域(迭代 23 T5——meta 新域 forge_routes,旧档缺键由 _normalize 按默认补齐= +# 初始持有态〔开发阶段无迁移禁折算——契约 4〕,无结构迁移体;版本号推进仅为 schema_version 对齐 +# ——v3→v4 先例。幂等性:重复迁移只覆写 version,既有值零改动;加载后 _normalize 补齐缺省——旧档可载可玩) +func _migrate_v6_v7(data: Dictionary) -> Dictionary: + data["version"] = SAVE_FORMAT_VERSION + return data + + # ---- 默认数据模型(新档合法态——全零;白板存档 = 完整可玩〔元进度 单局循环零依赖〕)---- func _default_data() -> Dictionary[String, Variant]: @@ -136,6 +149,12 @@ func _default_data() -> Dictionary[String, Variant]: "adventure_cleared": 0, # 生涯终身计数·通关局数(三关全通才计 1——判定字段 summary.cleared) "vision_used": 0, # 生涯终身计数·真视类工具累计使用次数(迭代 22 T2——信标网络解锁行为条件; # 受理成功唯一计数点=battle 层经冒险层聚合、收束入账;缺键补零不升版本——迭代 21 先例) + "forge_routes": { # 锻造工坊域(迭代 23 T5——六路线等级+直购解锁清单+当前选中标记;无特效域) + "levels": {"sword": 0, "spear": 0, "staff": 0, "light": 0, "heavy": 0, "ward": 0}, + "unlocked": [], # 直购解锁路线 id 清单(初始持有与练满前一档=派生解锁不落档) + "weapon": "sword", # 当前选中武器路线(初始持有=sword) + "armor": "light", # 当前选中甲路线(初始持有=light) + }, }, "breakpoint": {}, # 冒险断点快照(空 = 无断点合法态——「继续上局」隐藏;迭代 8 卡①) } @@ -314,6 +333,65 @@ func set_fork_side(group: String, side: String) -> void: _data["meta"]["fork_sides"] = fs +# ---- 锻造工坊域 forge_routes(迭代 23 T5——MetaProgression 锻造写口读写面;无特效域)---- +# 解锁态不落档(派生):初始持有=表 initial_owned、练满前一档=levels[prereq]≥cap——判定单点在 +# MetaProgression.forge_route_unlocked(读表+读档);本类只承载直购清单 unlocked 与选中标记。 +# 域结构:{levels:{六路线 id→等级}, unlocked:[直购路线 id], weapon:当前武器路线 id, armor:当前甲路线 id} + +func _forge_domain() -> Dictionary: + return _data["meta"].get("forge_routes", {}) as Dictionary + + +func forge_routes_snapshot() -> Dictionary: + return _forge_domain().duplicate(true) # 值拷贝出户(本类契约「禁碰内部结构」——消费方直改不得渗入存档内存态) + + +func forge_route_level(route_id: String) -> int: + var lv: Dictionary = _forge_domain().get("levels", {}) as Dictionary + return int(lv.get(route_id, 0)) + + +func set_forge_route_level(route_id: String, level: int) -> void: + var dom: Dictionary = _forge_domain() + var lv: Dictionary = dom.get("levels", {}) as Dictionary + lv[route_id] = maxi(0, level) + dom["levels"] = lv + _data["meta"]["forge_routes"] = dom + + +func forge_unlocked_purchases() -> Array: + return (_forge_domain().get("unlocked", []) as Array).duplicate() + + +func add_forge_unlock(route_id: String) -> void: + var dom: Dictionary = _forge_domain() + var un: Array = dom.get("unlocked", []) as Array + if not un.has(route_id): + un.append(route_id) + dom["unlocked"] = un + _data["meta"]["forge_routes"] = dom + + +func forge_weapon() -> String: + return String(_forge_domain().get("weapon", "sword")) + + +func set_forge_weapon(route_id: String) -> void: + var dom: Dictionary = _forge_domain() + dom["weapon"] = route_id + _data["meta"]["forge_routes"] = dom + + +func forge_armor() -> String: + return String(_forge_domain().get("armor", "light")) + + +func set_forge_armor(route_id: String) -> void: + var dom: Dictionary = _forge_domain() + dom["armor"] = route_id + _data["meta"]["forge_routes"] = dom + + # 装配关系(同调目录 item_id 有序数组——装配序即呈现序;跨冒险持久,冒险中途不可换装〔底稿 §8.5〕)。 # 局外进度事实(元进度域同族),非契约 2 反面清单的「局内道具库存」——存档序列化.md 契约 2 随批注记。 func attunement_equipped() -> Array: diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index 23ff8106..fa3ee5f1 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -5077,7 +5077,8 @@ func _smoke_save() -> void: "user://smoke_save_domain.json", "user://smoke_save_cap.json", "user://smoke_save_integration.json", "user://smoke_save_breakpoint.json", "user://smoke_save_v2compat.json", "user://smoke_save_v5upgrade.json", - "user://smoke_save_v5legacy.json", "user://smoke_save_v16discard.json"] + "user://smoke_save_v5legacy.json", "user://smoke_save_v16discard.json", + "user://smoke_save_forge_v6legacy.json"] var stale: Array = [] for p in paths: stale.append(p) @@ -5093,6 +5094,10 @@ func _smoke_save() -> void: w.set_tree_level("sp_hp", 2) w.set_tree_level("sp_hit", 1) w.set_fork_side("fg_posture", "b") # 树域写入(属性锻造域已退役——forge_levels 域清退换代件) + w.set_forge_route_level("spear", 3) # 锻造工坊域写入(迭代 23 T5——等级+直购解锁+选中标记) + w.add_forge_unlock("spear") + w.set_forge_weapon("spear") + w.set_forge_armor("ward") w.unlock_item("probe_compass") w.apply_adventure_summary({"result": "failed", "cleared": false, "levels_cleared": 2, "stopped_at": 3, "keystone_total": 3, "master_seed": 99}) @@ -5108,6 +5113,10 @@ func _smoke_save() -> void: check(r.keystone_balance() == 10, "钥石余额往返相等(10)") check(r.tree_level("sp_hp") == 2 and r.tree_level("sp_hit") == 1 and r.fork_side("fg_posture") == "b", "树域往返相等(tree_nodes/fork_sides——迭代 21 换代承载)") + check(r.forge_route_level("spear") == 3 and r.forge_route_level("sword") == 0 + and r.forge_unlocked_purchases() == ["spear"] + and r.forge_weapon() == "spear" and r.forge_armor() == "ward", + "锻造工坊域往返相等(levels/unlocked/weapon/armor——迭代 23 T5)〔锻造工坊批 V8〕") check(r.item_unlocked("probe_compass") and not r.item_unlocked("rope"), "道具解锁往返相等") check(r.clear_count(1) == 1 and r.clear_count(2) == 1 and r.clear_count(3) == 0, "通关次数表往返相等") check(r.boss_first_kill_marked(1) and r.boss_first_kill_marked(2) and not r.boss_first_kill_marked(3), @@ -5162,8 +5171,8 @@ func _smoke_save() -> void: fm.store_string(JSON.stringify({"version": 0, "keystone_old": 12})) fm.close() check(f7.load_from_disk() and f7.keystone_balance() == 12, "v0 旧档经迁移链入账(keystone_old 12 → 余额 12)") - check(f7.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[5], FileAccess.READ).get_as_text()).get("version", -1)) == 6, - "迁移链 0→1(测试钩)→6(生产) 写盘 version == 6(v6=生涯终身计数域,迭代 18 卡④——v5=天赋树域迭代 12 T9)") + check(f7.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[5], FileAccess.READ).get_as_text()).get("version", -1)) == 7, + "迁移链 0→1(测试钩)→7(生产) 写盘 version == 7(v7=锻造工坊域,迭代 23 T5——v6=生涯终身计数域迭代 18 卡④)") # ⑧ 域界干净:meta 永久进度域不含局内资源键(行动点/RAP/GP/道具库存/生命账本不入档——契约 2; # 迭代 8 卡①修订:扫描收窄到 meta 域——断点域=冒险进行中瞬态快照,生命/GP 按拍板面入域豁免) @@ -5259,9 +5268,9 @@ func _smoke_save() -> void: fold.store_string("{\"version\": 1, \"meta\": {\"keystone_balance\": 4}}") fold.close() check(fo.load_from_disk() and fo.keystone_balance() == 4 and not fo.has_breakpoint(), - "v1 旧档迁移 v4:断点域补空(合法空态,无断点域不炸)") - check(fo.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[9], FileAccess.READ).get_as_text()).get("version", -1)) == 6, - "v1 旧档迁移后写盘 version == 6(v1→v6 链式——生涯终身计数域 2026-10-07)") + "v1 旧档迁移 v7:断点域补空(合法空态,无断点域不炸)") + check(fo.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[9], FileAccess.READ).get_as_text()).get("version", -1)) == 7, + "v1 旧档迁移后写盘 version == 7(v1→v7 链式——锻造工坊域 2026-10-12)") # ⑫ v2 旧档→v3 兼容(迭代 10 g 裁——semver 向后兼容:旧断点零丢失、新代码可续玩) var fv2 = sm_script.new(paths[10]) @@ -5288,11 +5297,11 @@ func _smoke_save() -> void: fv5u.close() check(f13.load_from_disk() and f13.adventure_total() == 0 and f13.adventure_cleared() == 0 and f13.keystone_balance() == 9, - "v5 旧档升 v6:计数域缺省补零且既有余额原样(迁移只增字段不改值)") + "v5 旧档升 v7:计数域缺省补零且既有余额原样(迁移只增字段不改值——v5→v6→v7 链式)") check(f13.write_to_disk() and f13.load_from_disk() - and int(JSON.parse_string(FileAccess.open(paths[11], FileAccess.READ).get_as_text()).get("version", -1)) == 6 + and int(JSON.parse_string(FileAccess.open(paths[11], FileAccess.READ).get_as_text()).get("version", -1)) == 7 and f13.adventure_total() == 0 and f13.adventure_cleared() == 0, - "升档幂等:v6 档写盘再载零漂移(重复迁移零副作用)") + "升档幂等:v7 档写盘再载零漂移(重复迁移零副作用——锻造工坊域同批补齐)") var f13b = sm_script.new(paths[12]) f13b.load_from_disk() f13b.apply_adventure_summary({"result": "completed", "cleared": true, "levels_cleared": 3, @@ -5319,6 +5328,33 @@ func _smoke_save() -> void: f15.add_keystone(50) check(mp16.tree_buy("sp_hp") and f15.tree_level("sp_hp") == 1, "废弃重置后后续购买正常受理(无残留脏节点)") + # ⑯ 锻造工坊域 forge_routes(迭代 23 T5——V8):v6 旧档缺键补齐=初始持有态+无特效域+域界干净 + var f16 = sm_script.new(paths[14]) + var fv6 := FileAccess.open(paths[14], FileAccess.WRITE) + fv6.store_string(JSON.stringify({"version": 6, "meta": {"keystone_balance": 21, + "tree_nodes": {"sp_hp": 1}, "tree_struct": 2, "adventure_total": 4, "adventure_cleared": 1}})) + fv6.close() + check(f16.load_from_disk() and f16.keystone_balance() == 21 + and f16.tree_level("sp_hp") == 1 and f16.adventure_total() == 4 and f16.adventure_cleared() == 1, + "v6 旧档升 v7:既有域逐位不动(钥石/树/生涯计数零折算——迁移只增不改)〔锻造工坊批 V8〕") + check(f16.forge_route_level("sword") == 0 and f16.forge_route_level("spear") == 0 + and f16.forge_route_level("light") == 0 and f16.forge_route_level("ward") == 0 + and f16.forge_unlocked_purchases().is_empty() + and f16.forge_weapon() == "sword" and f16.forge_armor() == "light", + "v6 旧档缺键补齐=初始持有态(六路线 0 级/直购清单空/选中=sword+light)〔锻造工坊批 V8〕") + var fr_keys: Array = (f16.forge_routes_snapshot() as Dictionary).keys() + fr_keys.sort() + check(fr_keys == ["armor", "levels", "unlocked", "weapon"], + "forge_routes 域键集恰四项(无特效域——满级特效本期不做,契约 7)〔锻造工坊批 V8/V11〕") + var lv_keys: Array = ((f16.forge_routes_snapshot() as Dictionary).get("levels", {}) as Dictionary).keys() + lv_keys.sort() + check(lv_keys == ["heavy", "light", "spear", "staff", "sword", "ward"], + "levels 六路线键集恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V8〕") + # 值拷贝出户(改快照不渗档——本类契约) + var snap: Dictionary = f16.forge_routes_snapshot() + (snap.get("levels", {}) as Dictionary)["sword"] = 99 + check(f16.forge_route_level("sword") == 0, "forge_routes_snapshot 值拷贝出户(改快照不渗档)〔锻造工坊批 V8〕") + # ⑭ 收束链入账(迭代 18 卡④ T2——真实收束挂点全链恰一:主流程 adventure_finished → # MetaProgression.settle_adventure → apply_adventure_summary 单口;两夹具经同链入账) var mp13 = load("res://core/meta_progression.gd").new(_db, f13b) From a6270bac541891becc66a2c25d96d939bd393f44 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 12 Oct 2026 23:50:55 +0800 Subject: [PATCH 4/8] =?UTF-8?q?feat(=E8=BF=AD=E4=BB=A323-T3):=20=E9=94=BB?= =?UTF-8?q?=E9=80=A0=E5=86=99=E5=8F=A3=E6=97=8F=E2=80=94=E2=80=94=E8=B7=AF?= =?UTF-8?q?=E7=BA=BF=E8=B4=AD=E4=B9=B0=EF=BC=88=E5=87=A0=E4=BD=95=E4=BB=B7?= =?UTF-8?q?=EF=BC=89+=E5=B9=B6=E8=81=94=E5=8F=8C=E9=80=9A=E9=81=93?= =?UTF-8?q?=E8=A7=A3=E9=94=81+=E9=80=89=E4=B8=AD=E5=88=87=E6=8D=A2?= =?UTF-8?q?=EF=BC=88=E5=85=8D=E8=B4=B9=EF=BC=89+=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E8=BD=B4=E8=AF=BB=E6=95=B0=EF=BC=88MetaProgression=20=E5=8D=95?= =?UTF-8?q?=E7=82=B9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 契约 9 写口唯一(UI 只发意图——意图信号接线随 T6 UI 同批落): - forge_route_unlocked:解锁态派生单点(初始持有∨直购清单∨练满前一档 levels[prereq]≥cap——不落档) - forge_buy_level:几何价=ForgeTable.level_price(20/30/45/67/101),扣钥石+等级+1+即时写盘;未解锁/满级/余额不足=拒绝零副作用 - forge_buy_unlock:一次性解锁价 200 直购;已解锁(含派生)=拒绝零收费——两通道互不影响、不设串联 - forge_select_weapon/armor:免费切换只受理已解锁路线(冒险锁死归参数面快照,契约 8) - forge_weapon_atk_bonus/forge_armor_ac_bonus/hero_attack_type/hero_armor_type:T4 注入面取数单点(异常选中态防御回退 0/默认值) - 冒烟 V2/V3/V4/V6 共 15 断言(隔离档 forge/forge2/forge3——add_keystone 置余额+直写存档域注入口径) - 轻闸:冒烟 923/0(908 基线 +15) --- game/core/meta_progression.gd | 108 ++++++++++++++++++++++++++++++++++ game/smoke/smoke_all.gd | 74 ++++++++++++++++++++++- 2 files changed, 181 insertions(+), 1 deletion(-) diff --git a/game/core/meta_progression.gd b/game/core/meta_progression.gd index e8975e74..4a07359e 100644 --- a/game/core/meta_progression.gd +++ b/game/core/meta_progression.gd @@ -123,6 +123,114 @@ func treasure_shop_status() -> Array: return out +# ---- 锻造工坊写口族(迭代 23 T3——契约 9:扣币/升级/解锁/选中/写盘只经本组;UI 只发意图信号)---- +# 设计权威 design/gdd/systems/跨局循环/锻造工坊.md v0.6:钥石单币(契约 3——GP 局内一次性货币 +# 永不互兑)、并联双通道解锁(4b-1——练满前一档 OR 一次性解锁价,互不影响不设串联)、 +# 一次冒险一武器一甲(契约 8——营地免费切换、冒险开始锁死由参数面快照保证)。 +# 等级语义(Q1):未解锁/未锻造=0 加值(裸体态);锻造第一级=「获得装备」=首个 +1;逐级递进。 + +# 路线解锁态派生(不落档——初始持有∨直购∨练满前一档;派生单点只此一处) +func forge_route_unlocked(route_id: String) -> bool: + var row: Dictionary = db.forge_route(route_id) + if row.is_empty(): + return false + if bool(row.get("initial_owned", false)): + return true + if save.forge_unlocked_purchases().has(route_id): + return true + var prereq := String(row.get("prereq_route", "")) + if prereq != "": + var prow: Dictionary = db.forge_route(prereq) + if not prow.is_empty() and save.forge_route_level(prereq) >= int(prow.get("cap", 0)): + return true + return false + + +# 路线购买(几何价=基础价×150%^已购级数——锚表 20/30/45/67/101;等级+1 即时写盘)。 +# 未解锁/已满级/余额不足/未知路线 = 拒绝且零副作用(spend_keystone 内嵌余额闸,失败不扣币不写盘)。 +func forge_buy_level(route_id: String) -> bool: + var row: Dictionary = db.forge_route(route_id) + if row.is_empty() or not forge_route_unlocked(route_id): + return false + var lvl := save.forge_route_level(route_id) + if lvl >= int(row.get("cap", 0)): + return false + var price := ForgeTable.level_price(row, lvl) + if not save.spend_keystone(price): + return false + save.set_forge_route_level(route_id, lvl + 1) + return save.write_to_disk() + + +# 一次性解锁价直购(并联双通道②)。已解锁(含练满前一档派生)= 拒绝零收费——两通道互不影响: +# 未练满也可直购;直购后练满不重复收(练满通道本就是派生、零收费)。初始持有行无解锁面。 +func forge_buy_unlock(route_id: String) -> bool: + var row: Dictionary = db.forge_route(route_id) + if row.is_empty() or bool(row.get("initial_owned", false)): + return false + if forge_route_unlocked(route_id): + return false + var price := int(row.get("unlock_price", 0)) + if price < 1 or not save.spend_keystone(price): + return false + save.add_forge_unlock(route_id) + return save.write_to_disk() + + +# 当前武器/甲选中切换(免费——只受理已解锁路线;写存档域+即时写盘。冒险开始锁死归参数面快照, +# 契约 8;冒险中无切换入口=UI 面无路径可达本口) +func forge_select_weapon(route_id: String) -> bool: + var row: Dictionary = db.forge_route(route_id) + if row.is_empty() or String(row.get("kind", "")) != "weapon" or not forge_route_unlocked(route_id): + return false + save.set_forge_weapon(route_id) + return save.write_to_disk() + + +func forge_select_armor(route_id: String) -> bool: + var row: Dictionary = db.forge_route(route_id) + if row.is_empty() or String(row.get("kind", "")) != "armor" or not forge_route_unlocked(route_id): + return false + save.set_forge_armor(route_id) + return save.write_to_disk() + + +# 当前选中武器攻击加值 / 当前甲 AC 加值(路线等级×每级增量——0=裸体态/未锻造合法态)。 +# 选中态异常(未解锁/表缺行)防御回退 0——注入面只认合法态,禁把脏选中带进冒险。 +func forge_weapon_atk_bonus() -> int: + var rid := save.forge_weapon() + var row: Dictionary = db.forge_route(rid) + if row.is_empty() or String(row.get("kind", "")) != "weapon" or not forge_route_unlocked(rid): + return 0 + return save.forge_route_level(rid) * int(row.get("per_level_gain", 0)) + + +func forge_armor_ac_bonus() -> int: + var rid := save.forge_armor() + var row: Dictionary = db.forge_route(rid) + if row.is_empty() or String(row.get("kind", "")) != "armor" or not forge_route_unlocked(rid): + return 0 + return save.forge_route_level(rid) * int(row.get("per_level_gain", 0)) + + +# 两根类型轴当前生效值(选中路线类型轴;选中态异常/未解锁=锻造配置默认值——契约 2 回退面)。 +# 默认值与柜台解锁状态解耦:树钥匙未点亮时本函数照常返回默认值(配置常驻可读)。 +func hero_attack_type() -> String: + var rid := save.forge_weapon() + var row: Dictionary = db.forge_route(rid) + if row.is_empty() or String(row.get("kind", "")) != "weapon" or not forge_route_unlocked(rid): + return db.forge_default_attack_type() + return String(row.get("type_axis", "")) + + +func hero_armor_type() -> String: + var rid := save.forge_armor() + var row: Dictionary = db.forge_route(rid) + if row.is_empty() or String(row.get("kind", "")) != "armor" or not forge_route_unlocked(rid): + return db.forge_default_armor_type() + return String(row.get("type_axis", "")) + + # ---- Boss 两档与收束入账(结算屏文案数据源)---- # 明细 {big_reward, boss_drops, total, new_unlocks};入账与标记经 save.apply_adventure_summary # (keystone_drops 副本随摘要入 recent_summaries)。 diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index fa3ee5f1..82796d08 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -4431,6 +4431,77 @@ func _smoke_forge_axes() -> void: and String(uc.hero_unit().get("armor_type", "")) == "light", "主角单位快照两轴 = 锻造配置默认值 normal/light(零注入回退——行为连续)〔锻造工坊批 V5〕") + # ---- T3 写口族(V2 购买/几何价 · V3 并联双通道 · V4 两币分工 · V6 选中切换)---- + # 注入口径(两分法):add_keystone 置余额/直写存档域构造状态 → 直调写口 → 断余额/等级/读回 + var sv2 = _meta_new_save(_META_SMOKE_PATHS[13]) + var mp2 = load("res://core/meta_progression.gd").new(_db, sv2) + # V2①:长剑首购=20(0 级位几何价)、等级 0→1、扣币即盘、reload 持久 + sv2.add_keystone(100) + check(mp2.forge_buy_level("sword") and sv2.forge_route_level("sword") == 1 + and sv2.keystone_balance() == 80, + "长剑锻造首购:扣 20 钥石、等级 0→1(Q1——第一级=获得装备=首个 +1)〔锻造工坊批 V2〕") + var sv2r = _meta_new_save(_META_SMOKE_PATHS[13]) + check(sv2r.forge_route_level("sword") == 1 and sv2r.keystone_balance() == 80, + "购买持久(reload 后等级与余额不变——即时写盘)〔锻造工坊批 V2〕") + # V2②:二级=30(1 级位);六路线独立(买长剑不动轻甲) + check(mp2.forge_buy_level("sword") and sv2.forge_route_level("sword") == 2 + and sv2.keystone_balance() == 50 and sv2.forge_route_level("light") == 0, + "长剑二级购:扣 30、等级→2;轻甲独立零扰动(六路线各练各的)〔锻造工坊批 V2〕") + # V2③:余额不足拒且零副作用(等级/选中/解锁三面零变化) + var sv2d = _meta_new_save(_META_SMOKE_PATHS[14]) + var mp2d = load("res://core/meta_progression.gd").new(_db, sv2d) + var fr_dry: Dictionary = sv2d.forge_routes_snapshot() + check(not mp2d.forge_buy_level("sword") and sv2d.keystone_balance() == 0 + and sv2d.forge_routes_snapshot() == fr_dry, + "余额不足购买拒绝且零副作用(0 余额——三域零变化)〔锻造工坊批 V2〕") + # V2④:已满级拒购(cap 闸);未解锁拒购 + sv2.set_forge_route_level("sword", 5) + check(not mp2.forge_buy_level("sword") and not mp2d.forge_buy_level("spear"), + "已满级拒购/未解锁拒购(cap 闸+解锁闸——零副作用)〔锻造工坊批 V2〕") + # V3① 通道一:练满前一档(长剑 cap)→ 长枪/法杖派生解锁(零收费);轻甲未满→甲线仍锁 + check(mp2.forge_route_unlocked("spear") and mp2.forge_route_unlocked("staff") + and not mp2.forge_route_unlocked("heavy") and not mp2.forge_route_unlocked("ward"), + "通道一:长剑练满→长枪/法杖派生解锁;轻甲未练满→重甲/魔法护甲仍锁〔锻造工坊批 V3〕") + check(mp2.forge_buy_level("spear") and sv2.forge_route_level("spear") == 1 + and sv2.keystone_balance() == 30, + "派生解锁后长枪可购(首购 20——余额 50−20)〔锻造工坊批 V3〕") + # V3② 通道二:一次性解锁价直购(未练满也可买——两通道互不影响) + var sv3 = _meta_new_save(_META_SMOKE_PATHS[15]) + var mp3 = load("res://core/meta_progression.gd").new(_db, sv3) + sv3.add_keystone(500) + check(not mp3.forge_route_unlocked("ward") and mp3.forge_buy_unlock("ward") + and sv3.keystone_balance() == 300 and mp3.forge_route_unlocked("ward"), + "通道二:魔法护甲一次性解锁价 200 直购开(轻甲未练满也可买)〔锻造工坊批 V3〕") + # V3③ 直购后练满不重复收(练满通道=派生零收费;已解锁再直购=拒绝) + sv3.set_forge_route_level("light", 5) + check(mp3.forge_route_unlocked("ward") and not mp3.forge_buy_unlock("ward") + and sv3.keystone_balance() == 300, + "直购后练满不重复收(已解锁=拒绝零收费——两通道互不影响)〔锻造工坊批 V3〕") + # V3④ 不设串联门槛(法杖直购开——长枪零状态不阻;dry 档充值走直购) + sv2d.add_keystone(500) + check(mp2d.forge_buy_unlock("staff") and mp2d.forge_route_unlocked("staff") + and sv2d.forge_route_level("spear") == 0 and not mp2d.forge_route_unlocked("spear"), + "不设串联门槛(法杖直购开——长枪零状态不阻)〔锻造工坊批 V3〕") + # V4:任一购买/解锁只扣钥石——GP 侧零触碰(meta 域无 GP 键=域界扫描同款;此处钉余额差=当级价) + var ks4: int = sv3.keystone_balance() + check(mp3.forge_buy_level("ward") and sv3.keystone_balance() == ks4 - 20 + and sv3.forge_route_level("ward") == 1, + "两币分工:购买只扣钥石(余额差=当级价 20——GP 零触碰、无材料计数)〔锻造工坊批 V4〕") + # V6①:营地切换=写存档域+免费(余额零动);旧路线练度保留(只不生效) + var ks6: int = sv3.keystone_balance() + check(mp3.forge_select_armor("ward") and sv3.forge_armor() == "ward" + and sv3.keystone_balance() == ks6, + "选中切换免费(余额零动——写存档域即盘)〔锻造工坊批 V6〕") + check(sv3.forge_route_level("light") == 5, + "切换后旧路线练度保留(轻甲 5 级不丢——回家待命只不生效)〔锻造工坊批 V6〕") + # V6②:未解锁路线拒选零副作用(dry 档:长枪未解锁) + check(not mp2d.forge_select_weapon("spear") and sv2d.forge_weapon() == "sword", + "未解锁路线拒选零副作用〔锻造工坊批 V6〕") + # V6③:类型轴读数随选中走(切换后读 ward;未解锁选中态防御回退默认值) + check(mp3.hero_armor_type() == "ward" and mp2d.hero_attack_type() == "normal" + and mp2d.hero_armor_type() == "light", + "类型轴当前生效值随选中走(ward;未解锁档回退默认 normal/light)〔锻造工坊批 V5/V6〕") + # ---- 暂停层只读投影一致性(迭代 17 卡③——R4 数据只读同源:暂停层展示值逐项 == MetaProgression # 访问器读数;投影拼装与 main._pause_data 同构(同访问器族同序)。只读性连接巡检与零动效 @@ -4564,7 +4635,8 @@ const _META_SMOKE_PATHS := ["user://smoke_meta_buy.json", "user://smoke_meta_dry "user://smoke_meta_cap.json", "user://smoke_meta_white.json", "user://smoke_meta_attune.json", "user://smoke_meta_attune2.json", "user://smoke_meta_tree.json", "user://smoke_meta_career.json", - "user://smoke_meta_career_v5.json", "user://smoke_meta_treasure_unlock.json", "user://smoke_meta_treasure_blank.json"] + "user://smoke_meta_career_v5.json", "user://smoke_meta_treasure_unlock.json", "user://smoke_meta_treasure_blank.json", + "user://smoke_meta_forge.json", "user://smoke_meta_forge2.json", "user://smoke_meta_forge3.json"] func _meta_smoke_cleanup() -> void: From ed0629236ae2aee65970e61924711a808685caa6 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 13 Oct 2026 00:09:17 +0800 Subject: [PATCH 5/8] =?UTF-8?q?feat(=E8=BF=AD=E4=BB=A323-T4):=20=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=E6=8E=A5=E7=BA=BF=E2=80=94=E2=80=94hero=5Foverride=20?= =?UTF-8?q?=E5=85=AC=E5=BC=8F=E6=8D=A2=E4=BB=A3=EF=BC=88FORGE=5FROUTE=5FBO?= =?UTF-8?q?NUS=20=E9=80=80=E5=BD=B9=EF=BC=89+=E4=B8=A4=E8=BD=B4=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E9=9D=A2=E5=85=A8=E9=93=BE+=E5=85=A5=E6=88=98=20AC=20?= =?UTF-8?q?=E4=B8=89=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - meta_progression.hero_override:atk=forge_weapon_atk_bonus+树 flat / armor_class=forge_armor_ac_bonus+树 flat / max_hp=树 flat;FORGE_ROUTE_BONUS 预留常量退役(公式不再出现,注释留痕) - main.build_adventure_params:+attack_type/armor_type 两键(meta.hero_attack_type/hero_armor_type——存档选中路线类型轴,异常回退锻造配置默认值) - adventure_runner:params 两键快照入公有 hero_attack_type/hero_armor_type(契约 8 锁死可断言面;start/resume 双路同),_start_current_level 经 set_hero_axes 下传 - level_runner:set_hero_axes 注入+effective_ac 换代=甲型基础 AC(forge_armor_route_by_type.base_ac)+路线加值(base_override)+局内加算区(bonus)三层;enc 携带两键 - battle_orchestrator:spawn 传 enc 两轴(空串=锻造配置默认值回退——行为连续) - 冒烟 V5 后半/V6 锁死/V7 公式对拍+V10 树零攻防复核共 15 断言(含 enc 末端 hero_attack 行 pierce 键+spawn 显式注入) - 轻闸:冒烟 938/0(923 基线 +15)+run_all 23 套全绿;删除标识符 live 引用零残留(FORGE_ROUTE_BONUS/hero 两轴仅存退役注记与 README 文档面=归 T7) --- game/core/adventure_runner.gd | 9 ++++ game/core/battle_orchestrator.gd | 3 +- game/core/level_runner.gd | 28 +++++++++++- game/core/meta_progression.gd | 23 +++++----- game/presentation/main.gd | 4 ++ game/smoke/smoke_all.gd | 76 ++++++++++++++++++++++++++++++-- 6 files changed, 127 insertions(+), 16 deletions(-) diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index a84134eb..7cb22e12 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -62,6 +62,10 @@ var level_index := 0 # 当前关卡序号(1 起) var levels_total := 0 # 序列总关数(读 level 表行数) var keystone := 0 # 钥石入账累计(摘要第三件事的账面) var master_seed := 0 # 本局主种子(摘要第五件事) +# 两根类型轴冒险开始快照(迭代 23 T4——契约 8:营地选中态经参数面注入、冒险开始后锁死; +# 公有=参数面快照的可断言面〔契约 9 写口唯一,本字段只读不改〕。空串=未注入→消费面回退锻造配置默认值) +var hero_attack_type := "" +var hero_armor_type := "" var summary := {} # 最近一次结算的冒险摘要(idle 后保留——重开才新生成) var _runner: LevelRunner # 当前关卡行进器(每关新建——关卡域状态天然隔离) var _pending_next_level := false # 大奖励已兑付、等 continue_to_next_level 确认 @@ -128,6 +132,8 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool: level_index = 1 _pending_next_level = false _base_override = _normalize_override(params.get("hero_override", {})) + hero_attack_type = String(params.get("attack_type", "")) # 迭代 23 T4——两根类型轴快照(契约 8 锁死:开局后不随存档选中变化) + hero_armor_type = String(params.get("armor_type", "")) _first_script = bool(params.get("first_script", false)) # 缺省 = 非首次(抽签路径;真实侧由呈现层读存档布尔位传入) _shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 迭代 11 第②批 T3——缺省 legacy(off 对照零扰动) _treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {} @@ -211,6 +217,8 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: _feats.append(String(r)) _keystone_drops = (bp.get("keystone_drops", []) as Array).duplicate(true) _base_override = _normalize_override(params.get("hero_override", {})) + hero_attack_type = String(params.get("attack_type", "")) # 迭代 23 T4——续玩参数面同 start_adventure(账号层选中态随 resume 重注入) + hero_armor_type = String(params.get("armor_type", "")) # 生命账本越界钳制(T17——spawn 守卫 EC16 免疫):断点可能由旧口径版本写出(如调试上限 # 调高期带出的账本值 > 新口径生效上限,实例:hero_hp=28 > 基础 26)——恢复后钳到生效上限, # 否则首战 spawn「hero_hp 入参越界」拒收 → 「敌人系统 spawn 被拒」fatal。上限 = 表值+永久 @@ -936,6 +944,7 @@ func _start_current_level() -> bool: _runner.set_vision_counter(vision_counter) # 迭代 22 秘宝批 T3——真视使用计数共享 dict 下传(编排器受理成功 +1) _runner.set_feats(_feats) # 迭代 12 T1——特性持有集下传(begin_battle enc.feats 求值面) _runner.set_tree_rules(_tree_rules) # 迭代 12 T10——树 rule 键集下传(A/B 面消费) + _runner.set_hero_axes(hero_attack_type, hero_armor_type) # 迭代 23 T4——两根类型轴快照下传(effective_ac 甲型基础 AC 层+enc 携带进 spawn) if _shop_mode == "pool": # 商店共享池注入(迭代 11 第②批 T3——库存账本引用+消费 handler 双下传;legacy 模式零注入零扰动。 # 迭代 20 T2——cap 检查回调四参注入) diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index ab02386f..8c328c92 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -394,7 +394,8 @@ func begin_battle(enc: Dictionary) -> bool: # E4 收口:加成经 encounter 显式携带(hero_atk/hero_max_hp;缺省 = 表值——冒烟/工具直呼路径) _hero_atk = int(enc.get("hero_atk", int(db.hero.atk))) if not _enemy_system.spawn(int(enc.level_index), int(enc.node_depth), int(enc.hero_hp), count_overrides, - _hero_atk, int(enc.get("hero_max_hp", -1)), atk_overrides): + _hero_atk, int(enc.get("hero_max_hp", -1)), atk_overrides, + String(enc.get("attack_type", "")), String(enc.get("armor_type", ""))): # 迭代 23 T4——两根类型轴(空串=锻造配置默认值回退,行为连续) _fatal("敌人系统 spawn 被拒") return false if not _install_new_board(): diff --git a/game/core/level_runner.gd b/game/core/level_runner.gd index aa524ccf..264868ac 100644 --- a/game/core/level_runner.gd +++ b/game/core/level_runner.gd @@ -68,6 +68,10 @@ var _attunement: Array = [] # 同调装配集(迭代 11 第 var _attune_charges: Dictionary = {} # 同调主动件充能账本(迭代 11 第①批 T6——adventure 共享 dict 引用;settlement use_treasure 扣减直改同引用) # 关卡末清空+快照计数入 level_finished 载荷) var _vision_counter: Dictionary = {"used": 0} # 真视类工具使用计数(迭代 22 秘宝批 T3——adventure 共享 dict 引用;编排器受理成功 +1,收束聚合入档) +# 两根类型轴(迭代 23 T4——adventure 参数面快照经 set_hero_axes 下传;空串=未注入→锻造配置默认值回退。 +# 消费面:effective_ac 甲型基础 AC 层 + begin_battle enc 携带进 unit_container.spawn 英雄快照) +var _hero_attack_type := "" +var _hero_armor_type := "" # ---- 商店共享池模式态(迭代 11 第②批 T3——缺省 legacy=固定三件加定价 off 对照承载,零 RNG 零池消费;TP0 令牌池换代)---- var _shop_pool_mode := false # pool 模式旗(set_shop_pool 注入时置位——AdventureRunner 按 params.shop_mode 下传) var _pool_deck: Array = [] # 共享池令牌库共享引用(AdventureRunner._curio_deck——库存=剩余令牌数单点) @@ -120,9 +124,21 @@ func effective_max_hp() -> int: return maxi(1, int(round(float(base) * (100.0 + float(hero_hp_pct)) / 100.0))) -# 护甲等级生效值(永久强化 flat + 加算区 armor_class 轴——无调试浮窗百分比通道,纯 flat) +# 护甲等级生效值(入战 AC 三层可加——迭代 23 T4:甲型基础 AC〔forge.tres 选中甲路线 base_ac〕+ +# 甲路线等级加值〔base_override.armor_class——hero_override 锻造分量〕+ 加算区 armor_class 轴; +# 无调试浮窗百分比通道,纯 flat) func effective_ac() -> int: - return maxi(0, int(db.hero.armor_class) + int(base_override.get("armor_class", 0)) + int(bonus.get("armor_class", 0))) + return maxi(0, _armor_base_ac() + int(base_override.get("armor_class", 0)) + int(bonus.get("armor_class", 0))) + + +# 甲型基础 AC(三层之第一层——选中甲路线行 base_ac;未注入/缺行=锻造配置默认值路线=轻甲 10, +# 与 hero.tres 两轴删除前 armor_class=10 行为连续) +func _armor_base_ac() -> int: + var at: String = _hero_armor_type if _hero_armor_type != "" else db.forge_default_armor_type() + var row: Dictionary = db.forge_armor_route_by_type(at) + if row.is_empty(): + return int(db.hero.armor_class) # 表缺失防御兜底(现役基准) + return int(row.get("base_ac", 0)) # 行动点加成生效值(迭代 10 奖励重构批——统一冒险加算区第四轴 ap;经战斗开始入参 hero_ap_bonus @@ -480,6 +496,7 @@ func _start_battle(node: Dictionary) -> bool: if not _settlement.begin_battle({"level_index": int(level_row.level_index), "node_depth": int(node.get("node_depth", 0)), "hero_hp": hero_hp, "hero_atk": effective_atk(), "hero_max_hp": effective_max_hp(), "hero_armor_class": effective_ac(), "hero_ap_bonus": effective_ap_bonus(), + "attack_type": _hero_attack_type, "armor_type": _hero_armor_type, # 迭代 23 T4——两根类型轴(spawn 英雄快照;空串=锻造配置默认值回退) "attunement": _attunement, "attune_charges": _attune_charges, "vision_counter": _vision_counter, # 迭代 22 秘宝批 T3——真视使用计数共享 dict(编排器受理成功 +1,收束入档) @@ -637,6 +654,13 @@ func set_tree_rules(rules: Array) -> void: _tree_rules = rules.duplicate() +# 两根类型轴注入(迭代 23 T4——adventure 参数面快照;空串=未注入→锻造配置默认值回退。 +# 消费面:effective_ac 甲型基础 AC 层 + begin_battle enc 携带进 unit_container.spawn 英雄快照) +func set_hero_axes(attack_type: String, armor_type: String) -> void: + _hero_attack_type = attack_type + _hero_armor_type = armor_type + + # adventure 域计数器共享引用注入(迭代 20 T6——AdventureRunner 冒险级账本引用下传; # begin_battle enc.adventure_counters 携带进效果引擎 rebuild 回灌与战末 counters_export 回写—— # **共享引用不复制**,attune_charges 同款;缺省空 dict=零扰动) diff --git a/game/core/meta_progression.gd b/game/core/meta_progression.gd index 4a07359e..a9139a7a 100644 --- a/game/core/meta_progression.gd +++ b/game/core/meta_progression.gd @@ -9,8 +9,9 @@ # 钥石直购价随定价批后补最终双渠道;六件局内效果归 Alpha 秘宝重设计批,本类只有解锁状态); # ③ 冒险收束入账(Boss 两档 = 表值 first_kill/repeat_kill,首杀标记读存档判first;调 SaveManager # apply_adventure_summary 入账并附 boss_drops 副本——复盘可归因,验收 5)。 -# 注入面:hero_override() = 三档 forge flat(全零 = 白板合法态;override 不写表——表运行期只读, -# 消费方 AdventureRunner/LevelRunner 经冒险开始参数面显式传参)。 +# 注入面:hero_override() = 树生命/命中淬炼 flat + 锻造路线加值(当前选中武器/甲——迭代 23 T4 换代, +# 全零 = 白板合法态;override 不写表——表运行期只读,消费方 AdventureRunner/LevelRunner 经冒险开始 +# 参数面显式传参)。 # 纪律:数值零字面量——价格/档位/阈值/两档金额只读 db.meta_* 访问器;存档只经 SaveManager 访问器, # 禁读内部结构;行集解析禁自扫表(一律走 ConfigDB 访问器)。 class_name MetaProgression @@ -255,18 +256,20 @@ func settle_adventure(summary: Dictionary) -> Dictionary: # ---- 跨局注入(冒险开始参数面)---- -# 迭代 21 T4 注入公式换代:基础值 + 树生命/命中淬炼 flat(命中线在账未注入——契约 6,消费面归 -# 数值批)+ 锻造路线加值位(本批预留 0——锻造工坊实装批按当前武器/甲路线等级填充)+ 局内加算区 -# (bonus 区域不经本面)。旧属性柜台 forge_flat 项退役移除(V7/V12);攻/AC 树来源随所有权切分 -# 砍除(契约 9——树零攻防来源,现表零 atk/armor_class 行,tree_stat_flat 二轴恒 0 保留通式)。 +# 迭代 23 T4 注入公式换代(锻造工坊实装批——契约 1 攻防所有权切分落地):基础值 + 树生命/命中淬炼 +# flat(命中线在账未注入——契约 6,消费面归数值批)+ **锻造路线加值**(当前选中武器攻击加值 + 当前甲 +# AC 加值——读存档 forge_routes 域+路线表)+ 局内加算区(bonus 区域不经本面)。入战 AC 三层可加: +# 甲型基础 AC(forge.tres 行 base_ac)+ 甲路线等级加值(本面 armor_class 分量)+ 局内加算区。 +# 旧属性柜台 forge_flat 项退役移除(V7/V12);攻/AC 树来源随所有权切分砍除(契约 9——树零攻防来源, +# 现表零 atk/armor_class 行,tree_stat_flat 二轴恒 0 保留通式)。原 FORGE_ROUTE_BONUS 预留常量随本批 +# 退役(公式不再出现——V7/V12)。 # 全零 = 白板合法态(无解锁不得改变任何局内规则);override 禁写表(表运行期只读)。 -const FORGE_ROUTE_BONUS := {"atk": 0, "max_hp": 0, "armor_class": 0} # 锻造路线加值位(预留 0——锻造工坊实装批填充) func hero_override() -> Dictionary: return { - "atk": int(FORGE_ROUTE_BONUS.get("atk", 0)) + tree_stat_flat("atk"), - "max_hp": int(FORGE_ROUTE_BONUS.get("max_hp", 0)) + tree_stat_flat("max_hp"), - "armor_class": int(FORGE_ROUTE_BONUS.get("armor_class", 0)) + tree_stat_flat("armor_class"), + "atk": forge_weapon_atk_bonus() + tree_stat_flat("atk"), + "max_hp": tree_stat_flat("max_hp"), + "armor_class": forge_armor_ac_bonus() + tree_stat_flat("armor_class"), } diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 7fcc3eea..4851e8b0 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -496,11 +496,15 @@ func _wire_adventure() -> void: # 取值口径与营地投影完全同源:装配集=save.attunement_equipped(save_manager.gd 访问器)、Y 刷新池= # meta.attune_refresh_charges(当前等级行表值)、树 rule 键集=meta.tree_rule_keys(Fork 激活侧 effects); # hero_override 沿用既有 meta 现算口径。static 形态=冒烟直呼单测本函数,免实例化整菜单场景)。 +# 迭代 23 T4:两根类型轴随开局下传(attack_type/armor_type——契约 2 所有权移交+契约 8 冒险开始快照; +# 取值=meta.hero_attack_type/hero_armor_type=存档选中路线类型轴,未解锁/异常选中态回退锻造配置默认值)。 # 注:runner 侧 resume_adventure 受理面现收 attunement/attune_refresh_y/tree_rules 三键 # (2026-10-08 尾裁微批b 卡⓪受理闭环——tree_rules 受理补齐,传参/受理两侧对齐,披露句退役)。 static func build_adventure_params(save_manager_ref, meta_ref, extra: Dictionary = {}) -> Dictionary: var params: Dictionary = { "hero_override": meta_ref.hero_override() if meta_ref != null else {}, + "attack_type": meta_ref.hero_attack_type() if meta_ref != null else "", + "armor_type": meta_ref.hero_armor_type() if meta_ref != null else "", "attunement": save_manager_ref.attunement_equipped() if save_manager_ref != null else [], "attune_refresh_y": meta_ref.attune_refresh_charges() if meta_ref != null else 0, "tree_rules": meta_ref.tree_rule_keys() if meta_ref != null else [], diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index 82796d08..b3bfa71d 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -1909,7 +1909,7 @@ func _smoke_full_battle(L: int, D: int, label: String) -> void: check(hero_breakdown_ok, "%s 主角攻击行携带类型键与掷点读数(单分量——充能退役批)" % label) -func _run_battle(L: int, D: int) -> Dictionary: +func _run_battle(L: int, D: int, axes: Dictionary = {}) -> Dictionary: # 每遍全新 RngService(同 master 种子)——回放断言的隔离前提(地图战斗资源 AC"两套全新 RngService+地图战斗资源"同款) var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(42) @@ -1923,7 +1923,9 @@ func _run_battle(L: int, D: int) -> Dictionary: settlement.action_economy.round_end_stopped.connect(func(p): out.events.append({"ev": "round_end_stopped", "path_tag": p.path_tag})) var minesweeper = settlement.minesweeper var action_economy = settlement.action_economy - if not settlement.begin_battle({"level_index": L, "node_depth": D, "hero_hp": _db.hero.max_hp}): + var enc := {"level_index": L, "node_depth": D, "hero_hp": _db.hero.max_hp} + enc.merge(axes, true) # 迭代 23 T4——两根类型轴注入尾参(锻造工坊批对拍用;缺省零注入=回退面) + if not settlement.begin_battle(enc): out.stream.append({"type": "verdict", "value": "begin_failed"}) return out var max_rounds := 40 @@ -4502,6 +4504,73 @@ func _smoke_forge_axes() -> void: and mp2d.hero_armor_type() == "light", "类型轴当前生效值随选中走(ward;未解锁档回退默认 normal/light)〔锻造工坊批 V5/V6〕") + # ---- T4 注入接线(V5 后半 · V6 锁死 · V7 公式对拍 · V10 树零攻防复核)---- + # V5:build_adventure_params 注入默认值(新档=树钥匙未点亮)+点亮后读存档选中覆盖 + var sv4 = _meta_new_save(_META_SMOKE_PATHS[16]) + var mp4 = load("res://core/meta_progression.gd").new(_db, sv4) + var bp_params: Dictionary = load("res://presentation/main.gd").build_adventure_params(sv4, mp4) + check(String(bp_params.get("attack_type", "")) == "normal" + and String(bp_params.get("armor_type", "")) == "light", + "build_adventure_params 注入默认值 normal/light(树钥匙未点亮——新档初始持有态)〔锻造工坊批 V5〕") + sv4.add_keystone(1000) + check(mp4.forge_buy_unlock("spear") and mp4.forge_buy_level("spear") and mp4.forge_select_weapon("spear"), + "长枪直购解锁+首购+选中(注入面夹具——200+20 钥石)") + var bp_sel: Dictionary = load("res://presentation/main.gd").build_adventure_params(sv4, mp4) + check(String(bp_sel.get("attack_type", "")) == "pierce" + and String(bp_sel.get("armor_type", "")) == "light", + "build_adventure_params 读存档选中覆盖(选中长枪→pierce)〔锻造工坊批 V5〕") + # V7:hero_override 公式对拍=树 flat+锻造路线加值(旧路线练度待命不生效;切回即生效) + check(int(mp4.hero_override().get("atk", -1)) == 1 + and int(mp4.hero_override().get("max_hp", -1)) == 0 + and int(mp4.hero_override().get("armor_class", -1)) == 0 + and not mp4.hero_override().has("hit"), + "hero_override=锻造路线加值(选中长枪 Lv1→atk +1;甲 0 级→AC +0;树零分量;无 hit 键)〔锻造工坊批 V7〕") + sv4.set_forge_route_level("sword", 3) # 长剑练度待命(未选中) + check(int(mp4.hero_override().get("atk", -1)) == 1, + "换当前武器→攻击加值随切换(长剑 3 级待命不生效——只认选中)〔锻造工坊批 V7〕") + check(mp4.forge_select_weapon("sword") and int(mp4.hero_override().get("atk", -1)) == 3, + "切回长剑→加值=3 级量(旧路线练度保留且生效)〔锻造工坊批 V7〕") + # V7 三层 AC:重甲(基础 12)+路线 Lv1 加值+局内 0——经参数面起冒险断 runner 生效值 + var sv5 = _meta_new_save(_META_SMOKE_PATHS[17]) + var mp5 = load("res://core/meta_progression.gd").new(_db, sv5) + sv5.add_keystone(1000) + check(mp5.forge_buy_unlock("heavy") and mp5.forge_buy_level("heavy") and mp5.forge_select_armor("heavy"), + "重甲直购解锁+首购+选中(三层 AC 夹具)") + var adr5 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(77)) + var p5: Dictionary = load("res://presentation/main.gd").build_adventure_params(sv5, mp5) + check(adr5.start_adventure(77, p5), "注入参数冒险启动(两轴+override 同面)") + var r5 = adr5.runner() + check(int(r5.effective_ac()) == 12 + 1, + "入战 AC 三层:甲型基础 12(重甲)+路线加值 1+局内 0〔锻造工坊批 V7〕") + check(adr5.hero_attack_type == "normal" and adr5.hero_armor_type == "heavy", + "参数面快照入 runner(attack_type=normal/armor_type=heavy)〔锻造工坊批 V6〕") + # V6 锁死:开冒险后改存档选中→参数面/注入值不变(快照冻结;light=初始持有已解锁可切) + check(mp5.forge_select_armor("light") and adr5.hero_armor_type == "heavy" + and int(r5.effective_ac()) == 12 + 1, + "冒险开始后锁死:改存档选中不影响参数面快照与生效值(契约 8)〔锻造工坊批 V6〕") + # V5/V7 末端:enc 两轴直达 spawn 英雄快照(hero_attack 行携带选中类型键) + var run_ax = _run_battle(1, 0, {"attack_type": "pierce", "armor_type": "ward"}) + var pierce_rows := 0 + for row in run_ax.stream: + if row.get("source_class", "") == "hero_attack" and row.has("pierce"): + pierce_rows += 1 + check(pierce_rows >= 1, + "enc 两轴直达 spawn 英雄快照(hero_attack 行携带 pierce 键——注入链末端)〔锻造工坊批 V5〕") + # spawn 显式注入面(三参旧形态零注入已在上方覆盖——此处钉显式值) + var uc2 = load("res://core/unit_container.gd").new(_db) + check(uc2.spawn(1, 0, int(_db.hero.max_hp), [], -1, -1, [], "magic", "heavy") + and String(uc2.hero_unit().get("attack_type", "")) == "magic" + and String(uc2.hero_unit().get("armor_type", "")) == "heavy", + "spawn 显式两轴注入(magic/heavy 直入英雄快照)〔锻造工坊批 V5〕") + # V10 复核:树表 12 行 effects 键集无 atk/armor_class(契约 1——树零攻防来源) + var tree_atk_ac := false + for trow in _db.tree_rows(): + var eff: Dictionary = trow.get("effects", {}) + if eff.has("atk") or eff.has("armor_class"): + tree_atk_ac = true + check(not tree_atk_ac, + "树零攻防来源复核(12 行 effects 键集无 atk/armor_class——契约 1)〔锻造工坊批 V10〕") + # ---- 暂停层只读投影一致性(迭代 17 卡③——R4 数据只读同源:暂停层展示值逐项 == MetaProgression # 访问器读数;投影拼装与 main._pause_data 同构(同访问器族同序)。只读性连接巡检与零动效 @@ -4636,7 +4705,8 @@ const _META_SMOKE_PATHS := ["user://smoke_meta_buy.json", "user://smoke_meta_dry "user://smoke_meta_attune.json", "user://smoke_meta_attune2.json", "user://smoke_meta_tree.json", "user://smoke_meta_career.json", "user://smoke_meta_career_v5.json", "user://smoke_meta_treasure_unlock.json", "user://smoke_meta_treasure_blank.json", - "user://smoke_meta_forge.json", "user://smoke_meta_forge2.json", "user://smoke_meta_forge3.json"] + "user://smoke_meta_forge.json", "user://smoke_meta_forge2.json", "user://smoke_meta_forge3.json", + "user://smoke_meta_forge4.json", "user://smoke_meta_forge5.json"] func _meta_smoke_cleanup() -> void: From 2915f19c4b87d4d92495512d693541004eed91d6 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 13 Oct 2026 00:21:41 +0800 Subject: [PATCH 6/8] =?UTF-8?q?feat(=E8=BF=AD=E4=BB=A323-T6):=20=E8=90=A5?= =?UTF-8?q?=E5=9C=B0=E9=94=BB=E9=80=A0=E5=B7=A5=E5=9D=8A=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E9=97=B4=E2=80=94=E2=80=94=E4=B8=A4=E5=8C=BA=E5=85=AD=E8=B7=AF?= =?UTF-8?q?=E7=BA=BF=E8=A1=8C=E6=9D=BF=E5=AE=9E=E8=A3=85=EF=BC=88=E5=8D=A0?= =?UTF-8?q?=E4=BD=8D=E9=9D=A2=E6=9D=BF=E6=9B=BF=E6=8D=A2=EF=BC=89+?= =?UTF-8?q?=E4=B8=89=E6=84=8F=E5=9B=BE=E4=BF=A1=E5=8F=B7=E6=8E=A5=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MetaProgression.forge_shop_status():只读投影(等级/cap/解锁态/选中态/下级几何价/解锁价/前置通道进度——纯结构面,余额禁用归视图) - CampOverlay:_forge_workbench 占位面板→武器锻造区/护甲锻造区两区六路线行板(件名+Lv+解锁态文案+锻造/解锁/选中钮);_rebuild_forge 随 open/refresh 重建;默认不可见+门控重建=锁定态零行板零购买路径(占位纪律延续);三信号 forge_buy/unlock/select_requested(契约 9 UI 只发意图) - camp_shop_presenter:camp_data +forge 投影;on_camp_forge_buy/unlock/select 三处理器(写口唯一归 MetaProgression 锻造写口族;拒绝可见 pattern 1 toast) - main.gd:三信号接 presenter - 新单测 forge_workbench_test 12 断言(锁定态纪律/两区结构/钮态/余额禁用/满级/派生解锁出钮/三意图外发);run_all 23→24 套 - 轻闸:冒烟 938/0+run_all 24 套+probe --click 全绿(presentation 触碰加跑) --- game/core/meta_progression.gd | 37 ++++++ game/presentation/camp_shop_presenter.gd | 45 ++++++- game/presentation/main.gd | 3 + game/presentation/views/camp_overlay_view.gd | 119 +++++++++++++++--- game/tests/run_all.gd | 1 + .../unit/presentation/forge_workbench_test.gd | 109 ++++++++++++++++ .../presentation/forge_workbench_test.gd.uid | 1 + 7 files changed, 298 insertions(+), 17 deletions(-) create mode 100644 game/tests/unit/presentation/forge_workbench_test.gd create mode 100644 game/tests/unit/presentation/forge_workbench_test.gd.uid diff --git a/game/core/meta_progression.gd b/game/core/meta_progression.gd index a9139a7a..dbc001f4 100644 --- a/game/core/meta_progression.gd +++ b/game/core/meta_progression.gd @@ -232,6 +232,43 @@ func hero_armor_type() -> String: return String(row.get("type_axis", "")) +# ---- 锻造工坊柜台只读投影(迭代 23 T6——呈现层数据源:两区六路线行板;余额联动禁用归视图)---- +# 每行 {route_id, display_name, kind, type_axis, level, cap, unlocked, initial_owned, selected, +# next_price(未满级已解锁=下级几何价/否则 0), unlock_price, prereq_route, prereq_name, +# prereq_level, prereq_cap, prereq_maxed}——纯结构面,禁嵌余额判断(视图按 balance 禁用,同秘宝匣先例)。 +func forge_shop_status() -> Array: + var out: Array[Dictionary] = [] + for row in db.forge_routes(): + var rid := String(row.get("route_id", "")) + var lvl := save.forge_route_level(rid) + var cap_v := int(row.get("cap", 0)) + var unlocked := forge_route_unlocked(rid) + var prereq := String(row.get("prereq_route", "")) + var plvl := save.forge_route_level(prereq) if prereq != "" else 0 + var pcap := int(db.forge_route(prereq).get("cap", 0)) if prereq != "" else 0 + var selected := (String(row.get("kind", "")) == "weapon" and save.forge_weapon() == rid) \ + or (String(row.get("kind", "")) == "armor" and save.forge_armor() == rid) + out.append({ + "route_id": rid, + "display_name": String(row.get("display_name", "")), + "kind": String(row.get("kind", "")), + "type_axis": String(row.get("type_axis", "")), + "level": lvl, + "cap": cap_v, + "unlocked": unlocked, + "initial_owned": bool(row.get("initial_owned", false)), + "selected": selected, + "next_price": ForgeTable.level_price(row, lvl) if (unlocked and lvl < cap_v) else 0, + "unlock_price": int(row.get("unlock_price", 0)), + "prereq_route": prereq, + "prereq_name": String(db.forge_route(prereq).get("display_name", "")) if prereq != "" else "", + "prereq_level": plvl, + "prereq_cap": pcap, + "prereq_maxed": prereq != "" and plvl >= pcap, + }) + return out + + # ---- Boss 两档与收束入账(结算屏文案数据源)---- # 明细 {big_reward, boss_drops, total, new_unlocks};入账与标记经 save.apply_adventure_summary # (keystone_drops 副本随摘要入 recent_summaries)。 diff --git a/game/presentation/camp_shop_presenter.gd b/game/presentation/camp_shop_presenter.gd index b64c6857..945a6dbf 100644 --- a/game/presentation/camp_shop_presenter.gd +++ b/game/presentation/camp_shop_presenter.gd @@ -61,13 +61,15 @@ func on_camp_closed() -> void: _refresh_presentations.call() # 关闭后刷新顶栏(工坊解锁可能变了余额侧读数面) -# 营地快照投影(纯读——秘宝匣 12 件/同调装配/树面/余额/最近冒险;拼句归 CampOverlay,账面归 -# MetaProgression。属性锻造三行投影已删——迭代 21 T4 柜台退役;工坊六行换代=迭代 22 T2 秘宝匣) +# 营地快照投影(纯读——秘宝匣 12 件/同调装配/树面/锻造工坊两区六路线/余额/最近冒险;拼句归 +# CampOverlay,账面归 MetaProgression。属性锻造三行投影已删——迭代 21 T4 柜台退役;工坊六行换代 +# = 迭代 22 T2 秘宝匣;forge 键=迭代 23 T6 锻造工坊路线行板投影) func camp_data() -> Dictionary: var meta = _meta_get.call() return { "balance": meta.keystone_balance() if meta != null else 0, "workshop": meta.treasure_shop_status() if meta != null else [], # 键名沿 workshop(视图消费键不变——行内容=秘宝匣 12 件解锁投影) + "forge": meta.forge_shop_status() if meta != null else [], # 迭代 23 T6——锻造工坊两区六路线投影 "attune": meta.attune_camp_status() if meta != null else {}, # 迭代 11 第①批 T4——同调装配面投影 "tree": meta.tree_camp_rows() if meta != null else [], # 树面投影(含 forge_workshop/ks_workshop 钥匙门判据行) "fork_groups": meta.tree_fork_groups() if meta != null else [], @@ -133,6 +135,45 @@ func on_camp_fork_toggle(group: String, side: String) -> void: _refresh_presentations.call() +# ---- 锻造工坊三意图(迭代 23 T6——契约 9:写口唯一归 MetaProgression 锻造写口族;UI 只发意图)---- +# 路线购买(几何价;余额不足/未解锁/满级=核心拒绝零副作用,兜底拒绝可见 pattern 1) +func on_camp_forge_buy(route_id: String) -> void: + var meta = _meta_get.call() + if meta == null: + return + if not meta.forge_buy_level(route_id): + toast.show_toast("锻造被拒——钥石不足或路线不可锻造", "warn") + return + camp_overlay.refresh_camp(camp_data()) + _refresh_presentations.call() + + +# 一次性解锁价直购(并联双通道②;已解锁=拒绝零收费) +func on_camp_forge_unlock(route_id: String) -> void: + var meta = _meta_get.call() + if meta == null: + return + if not meta.forge_buy_unlock(route_id): + toast.show_toast("解锁被拒——钥石不足或已解锁", "warn") + return + camp_overlay.refresh_camp(camp_data()) + _refresh_presentations.call() + + +# 当前武器/甲选中切换(免费;未解锁拒选) +func on_camp_forge_select(route_id: String) -> void: + var meta = _meta_get.call() + if meta == null: + return + var ok: bool = meta.forge_select_weapon(route_id) if String(db.forge_route(route_id).get("kind", "")) == "weapon" \ + else meta.forge_select_armor(route_id) + if not ok: + toast.show_toast("切换被拒——路线未解锁", "warn") + return + camp_overlay.refresh_camp(camp_data()) + _refresh_presentations.call() + + func on_shop_buy(product_id: String) -> void: var runner = _runner_get.call() if runner == null: diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 4851e8b0..a59f046f 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -317,6 +317,9 @@ func _build_ui() -> void: camp_overlay.attune_toggled.connect(_camp_pres.on_camp_attune_toggle) camp_overlay.tree_buy_requested.connect(_camp_pres.on_camp_tree_buy) camp_overlay.tree_fork_toggled.connect(_camp_pres.on_camp_fork_toggle) + camp_overlay.forge_buy_requested.connect(_camp_pres.on_camp_forge_buy) # 迭代 23 T6——锻造三意图(写口唯一归 MetaProgression) + camp_overlay.forge_unlock_requested.connect(_camp_pres.on_camp_forge_unlock) + camp_overlay.forge_select_requested.connect(_camp_pres.on_camp_forge_select) camp_overlay.closed.connect(_camp_pres.on_camp_closed) start.campbtn.pressed.connect(_camp_pres.open_camp) start.careerbtn.pressed.connect(_open_career) # 迭代 18 卡④——生涯页一级入口(营地同组) diff --git a/game/presentation/views/camp_overlay_view.gd b/game/presentation/views/camp_overlay_view.gd index 5b62eecf..40338066 100644 --- a/game/presentation/views/camp_overlay_view.gd +++ b/game/presentation/views/camp_overlay_view.gd @@ -1,14 +1,16 @@ # VERTICAL SLICE - NOT FOR PRODUCTION # Date: 2026-09-21(元进度实现批——营地覆盖层呈现;2026-10-12 迭代 21 T6 营地统一重排—— # 四柜台并排〔2026-10-11 锻造工坊裁决会话 A② 裁〕:树淬炼柜台=树脊柱界面形态=主界面 / -# 锻造工坊柜台〔钥匙门:forge_workshop 点亮前锁定态,点亮=工作间占位面板〕/ 秘宝匣柜台 -# 〔钥匙门:ks_workshop 点亮前锁定态,点亮=现工坊内容整体迁入——六行+同调装配段+Y 升级行〕/ +# 锻造工坊柜台〔钥匙门:forge_workshop 点亮前锁定态,点亮=工作间两区六路线行板——迭代 23 T6 实装〕/ +# 秘宝匣柜台〔钥匙门:ks_workshop 点亮前锁定态,点亮=现工坊内容整体迁入——六行+同调装配段+Y 升级行〕/ # 奖励格图鉴柜台常开独立位;宽度不足横向滚动条。旧属性锻造柜台退役〔攻防所有权切分—— # 生命唯一入口=树脊柱、攻/AC 唯一入口=锻造工坊路线〕)。 -# 占位面板硬要求(2026-10-11 用户令):锻造工坊工作间占位面板打开/关闭零副作用——零写档、 -# 零报错、面板内无任何可交互购买路径(纯文案标签,视觉终稿归 HUD 营地视觉批)。 +# 锁定态零副作用纪律(2026-10-11 用户令,延续):锻造工坊工作间锁定态(钥匙未点亮)开/关零副作用 +# ——零写档、零报错、面板内无任何可交互购买路径(锁定行纯文案;点亮后交互面归 T6 行板意图信号)。 # 信号面/投影契约:buy_requested(attr)〔现仅 Y 刷新升级行消费——装配段双入口之一 Q1 裁乙〕/closed、 -# attune_toggled/tree_buy_requested/tree_fork_toggled、open_camp(data)/refresh_camp(data)。 +# attune_toggled/tree_buy_requested/tree_fork_toggled、forge_buy_requested/forge_unlock_requested/ +# forge_select_requested(迭代 23 T6——锻造三意图;写口唯一归 MetaProgression 锻造写口族)、 +# open_camp(data)/refresh_camp(data)。 # 挂接方式:开合与开始层互斥仿设置面先例(开始覆盖层非模态且树序在后——打开本层前由组合根 # 隐藏开始层,规范 2.3 遮挡纪律)。 # 高内聚边界:纯视图 setter 面——账本快照经组合根喂入(MetaProgression 只读投影),购买/关闭 @@ -21,6 +23,9 @@ signal closed signal attune_toggled(item_id: String) # 同调装配切换意图(迭代 11 第①批 T4——写口唯一归 MetaProgression.toggle_attunement) signal tree_buy_requested(node_id: String) # 天赋树购买意图(迭代 12 T9——写口唯一归 MetaProgression.tree_buy) signal tree_fork_toggled(group: String, side: String) # 双态切换意图(一节点双态——零币切态) +signal forge_buy_requested(route_id: String) # 锻造路线购买意图(迭代 23 T6——写口唯一归 MetaProgression.forge_buy_level) +signal forge_unlock_requested(route_id: String) # 锻造路线解锁意图(迭代 23 T6——写口唯一归 MetaProgression.forge_buy_unlock) +signal forge_select_requested(route_id: String) # 锻造路线选中意图(迭代 23 T6——写口唯一归 MetaProgression.forge_select_weapon/armor) # 方向稿取色(柜台头带深棕/行板暖米;双色墨=亮板配深墨)——值单点收 UiKit(T4 裸色收口批) const HEADER_BAND_COL := UiKit.CAMP_HEADER_BAND @@ -31,7 +36,10 @@ var _tree_holder: VBoxContainer # 树淬炼柜台内容(主界面—— var _tree_buy_btns: Array[Dictionary] = [] # [{node_id, btn}]——树购买/解锁钮登记面(open/refresh 重建时刷新;探针消费) var _tree_fork_btns: Array[Dictionary] = [] # [{group, side, btn}]——Fork 双态切换钮登记面(探针消费) var _forge_lock_l: Label # 锻造工坊柜台锁定态行(钥匙未点亮) -var _forge_workbench: VBoxContainer # 锻造工坊工作间占位面板(点亮后展开——零交互纯文案) +var _forge_workbench: VBoxContainer # 锻造工坊工作间(钥匙点亮后展开——两区六路线行板,迭代 23 T6 实装) +var _forge_zone_weapon: VBoxContainer # 武器锻造区行板容器(三路线) +var _forge_zone_armor: VBoxContainer # 护甲锻造区行板容器(三路线) +var _forge_rows: Array[Dictionary] = [] # [{route_id, name_l, lvl_l, status_l, buy_btn, unlock_btn, select_btn, next_price, unlock_price}](open/refresh 重建;探针消费) var _ws_scroll: ScrollContainer # 秘宝匣柜台内容容器(钥匙点亮后可见) var _ws_lock_l: Label # 秘宝匣柜台锁定态行 var _workshop_holder: VBoxContainer # 秘宝匣柜台行板(六行+同调装配段+Y 升级行——迭代 11 第①批 T4 结构随迁) @@ -112,20 +120,23 @@ func _init() -> void: tree_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL mid.add_child(tree_panel) - # 柜二·锻造工坊(钥匙门——forge_workshop 点亮前锁定态;点亮=工作间占位面板,零交互) + # 柜二·锻造工坊(钥匙门——forge_workshop 点亮前锁定态;点亮=工作间两区六路线行板,迭代 23 T6 实装) _forge_lock_l = UiKit.label("🔒 锻造工坊未开放\n\n点亮树上「锻造工坊」钥匙节点后展开工作间。\n(钥匙免费、无前置——树上直接点亮)",UiKit.FS_HINT, false, UiKit.STONE_DEEP) _forge_lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _forge_lock_l.custom_minimum_size = Vector2(280, 0) _forge_workbench = VBoxContainer.new() _forge_workbench.add_theme_constant_override("separation", 6) - var wb_hint := UiKit.label("工作间两区并列:武器锻造区 / 护甲锻造区",UiKit.FS_HINT, true, UiKit.INK) - wb_hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - wb_hint.custom_minimum_size = Vector2(280, 0) - _forge_workbench.add_child(wb_hint) - var wb_body := UiKit.label("武器/护甲路线升级随「锻造工坊实装批」开放——本批为占位面板,无任何购买入口。",UiKit.FS_HINT, false, UiKit.STONE_DEEP) - wb_body.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - wb_body.custom_minimum_size = Vector2(280, 0) - _forge_workbench.add_child(wb_body) + _forge_workbench.visible = false # 默认锁定态(钥匙门未判前零行板——门开才建,迭代 23 T6 占位纪律延续) + _forge_zone_weapon = VBoxContainer.new() + _forge_zone_weapon.add_theme_constant_override("separation", 4) + _forge_zone_armor = VBoxContainer.new() + _forge_zone_armor.add_theme_constant_override("separation", 4) + var wb_wh := UiKit.label("── 武器锻造区 ──",UiKit.FS_HINT, true, UiKit.GOLD_INK) # 尾裁微批a 同款深金墨(对柜台板 5.21:1) + _forge_workbench.add_child(wb_wh) + _forge_workbench.add_child(_forge_zone_weapon) + var wb_ah := UiKit.label("── 护甲锻造区 ──",UiKit.FS_HINT, true, UiKit.GOLD_INK) + _forge_workbench.add_child(wb_ah) + _forge_workbench.add_child(_forge_zone_armor) var forge_holder := VBoxContainer.new() forge_holder.add_theme_constant_override("separation", 6) forge_holder.add_child(_forge_lock_l) @@ -284,10 +295,87 @@ func _refresh_key_gates(data: Dictionary) -> void: _ws_scroll.visible = ws_open +# 锻造工坊行板重建(迭代 23 T6——两区六路线;open_camp/refresh_camp 调用。行结构仿秘宝匣行板: +# 件名+等级+解锁态+购买/解锁/选中钮。UI 只发意图信号〔契约 9——零核心写调用、零 MetaProgression +# 持有〕;钮禁用=常态防线(余额/未解锁/满级/已选中),兜底拒绝可见归 presenter pattern 1) +func _rebuild_forge(data: Dictionary) -> void: + for zone in [_forge_zone_weapon, _forge_zone_armor]: + for c in zone.get_children(): + c.queue_free() + _forge_rows.clear() + if not _forge_workbench.visible: + return # 锁定态(钥匙未点亮):零行板零购买路径——占位纪律延续(2026-10-11 用户令) + var bal := int(data.get("balance", 0)) + for row in data.get("forge", []): + var rid := String(row.get("route_id", "")) + var kind := String(row.get("kind", "")) + var zone: VBoxContainer = _forge_zone_weapon if kind == "weapon" else _forge_zone_armor + var unlocked := bool(row.get("unlocked", false)) + var initial := bool(row.get("initial_owned", false)) + var lvl := int(row.get("level", 0)) + var cap_v := int(row.get("cap", 0)) + var next_price := int(row.get("next_price", 0)) + var unlock_price := int(row.get("unlock_price", 0)) + var card := PanelContainer.new() + card.add_theme_stylebox_override("panel", UiKit.panel_flat(ROW_PLATE_COL, UiKit.PANEL_BORDER, UiKit.RADIUS_OBJECT, 1, Vector4(10, 4, 10, 4))) + var line := HBoxContainer.new() + line.add_theme_constant_override("separation", 6) + card.add_child(line) + var name_l := UiKit.label(String(row.get("display_name", "")), UiKit.FS_HINT, true, UiKit.INK) + name_l.custom_minimum_size = Vector2(104, 0) + name_l.clip_text = true + name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + line.add_child(name_l) + var lvl_l := UiKit.label("Lv%d/%d" % [lvl, cap_v], 13, false, UiKit.INK_LABEL) + lvl_l.custom_minimum_size = Vector2(44, 0) + line.add_child(lvl_l) + # 解锁态/解锁通道呈现(并联双通道:练满前一档 or 一次性解锁价直购) + var st_text := "已解锁" + if not unlocked: + if initial: + st_text = "未解锁" + else: + st_text = "未解锁 · 练满%s 或 %d 钥石直购" % [String(row.get("prereq_name", "")), unlock_price] + elif lvl >= cap_v: + st_text = "已满级" + var status_l := UiKit.label(st_text, UiKit.FS_HINT, false, UiKit.STONE_DEEP) + status_l.clip_text = true + status_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + status_l.size_flags_horizontal = Control.SIZE_EXPAND_FILL + line.add_child(status_l) + # 购买钮(已解锁未满级——几何价显示;余额不足禁用) + var buy_btn: Button = null + if unlocked and lvl < cap_v: + buy_btn = UiKit.button("锻造 %d" % next_price, 0.0) # 行板紧凑钮豁免 + buy_btn.pressed.connect(forge_buy_requested.emit.bind(rid)) + buy_btn.disabled = bal < next_price + line.add_child(buy_btn) + # 解锁钮(待解锁非初始——一次性解锁价) + var unlock_btn: Button = null + if not unlocked and not initial: + unlock_btn = UiKit.button("解锁 %d" % unlock_price, 0.0) # 行板紧凑钮豁免 + unlock_btn.pressed.connect(forge_unlock_requested.emit.bind(rid)) + unlock_btn.disabled = bal < unlock_price + line.add_child(unlock_btn) + # 选中钮(已解锁——免费切换;当前=禁用态标记) + var select_btn: Button = null + if unlocked: + var sel := bool(row.get("selected", false)) + select_btn = UiKit.button("当前" if sel else "选中", 0.0) # 行板紧凑钮豁免 + select_btn.pressed.connect(forge_select_requested.emit.bind(rid)) + select_btn.disabled = sel + line.add_child(select_btn) + zone.add_child(card) + _forge_rows.append({"route_id": rid, "name_l": name_l, "lvl_l": lvl_l, "status_l": status_l, + "buy_btn": buy_btn, "unlock_btn": unlock_btn, "select_btn": select_btn, + "next_price": next_price, "unlock_price": unlock_price}) + + # 打开营地(全量重建四柜台行 + 同调装配段 + 最近冒险;树段与钥匙门随 refresh_camp 重建—— # 数据 = 组合根 _camp_data() 投影) func open_camp(data: Dictionary) -> void: _rebuild_workshop(data) # 段尾链 _rebuild_attune(同调装配段+Y 升级行随秘宝匣柜台内容) + _rebuild_forge(data) # 迭代 23 T6——锻造工坊两区六路线行板 _rebuild_recent(data) refresh_camp(data) visible = true @@ -300,6 +388,7 @@ func refresh_camp(data: Dictionary) -> void: _balance_label.text = "钥石 %d" % balance _rebuild_tree(data) _refresh_key_gates(data) + _rebuild_forge(data) # 迭代 23 T6——行板随刷重建(购买/解锁/选中后等级/价/禁用态同步) var ws_by_id := {} for row in data.get("workshop", []): ws_by_id[String(row.get("item_id", ""))] = row diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index 278b6079..7299f26d 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -38,6 +38,7 @@ func _suites() -> Array: {"path": "res://tests/unit/core/curio_refresh_paid_test.gd", "expect": 0}, # 2026-10-06——三选一刷新免费双源+GP 付费档(对齐商店 MC-1:价曲线/优先序/停点清零/钱包单口) {"path": "res://tests/unit/presentation/pacing_queue_test.gd", "expect": 0}, # 迭代 8 卡⑦ AC⑤/T9——排空零分叉/零丢行/延迟双态 {"path": "res://tests/unit/presentation/reward_overlay_test.gd", "expect": 0}, # 迭代 7 hotfix——奖励节点 UI 直验(盲区补漏) + {"path": "res://tests/unit/presentation/forge_workbench_test.gd", "expect": 0}, # 迭代 23 锻造工坊批 T6——工作间两区六路线 UI 直验(V13 自动化前置面) {"path": "res://tests/unit/battle_settlement/damage_math_test.gd", "expect": 0}, {"path": "res://tests/unit/battle_settlement/damage_controller_test.gd", "expect": 0}, {"path": "res://tests/unit/battle_settlement/skill_unlock_test.gd", "expect": 0}, # 迭代14④ T6——Boss 半血解锁技·容器机制(门谓词/闩不可逆/双面过滤/保底不逼锁定/零扰动) diff --git a/game/tests/unit/presentation/forge_workbench_test.gd b/game/tests/unit/presentation/forge_workbench_test.gd new file mode 100644 index 00000000..b1b95e38 --- /dev/null +++ b/game/tests/unit/presentation/forge_workbench_test.gd @@ -0,0 +1,109 @@ +extends SceneTree +# 锻造工坊工作间 UI 直验套件(迭代 23 T6——V13 人闸的自动化前置面:两区结构/钮态/意图信号/锁定态纪律) +# 覆盖:锁定态零行板零购买路径(占位纪律延续)· 两区各三路线 · 初始持有行购买钮(几何价 20)· +# 待解锁行解锁钮(200)+无购买钮 · 选中钮当前态禁用 · 余额不足禁用态 · 满级无购买钮 · +# 三意图信号外发(UI 只发意图——契约 9,写口归 MetaProgression 由冒烟侧覆盖)。 +# 运行:godot --headless --path game --script res://tests/unit/presentation/forge_workbench_test.gd(expect 0) +const Kit := preload("res://tests/unit/test_kit.gd") + +const _TEST_SAVE := "user://test_forge_workbench.json" + + +func _initialize() -> void: + call_deferred("_run") + + +func _row_by_id(ov) -> Dictionary: + var out := {} + for r in ov._forge_rows: + out[String(r.route_id)] = r + return out + + +func _run() -> void: + var db = load("res://core/config_db.gd").new() + var load_ok: bool = db.load_tables("res://data/tables", true) + if not load_ok: + Kit.check("ConfigDb 加载(工作间测试夹具前提)", false) + quit(Kit.finish("forge_workbench_test")) + return + var sv = load("res://core/save_manager.gd").new(_TEST_SAVE) + sv.load_from_disk() + var mp = load("res://core/meta_progression.gd").new(db, sv) + var ov = load("res://presentation/views/camp_overlay_view.gd").new() + root.add_child(ov) + var buys := [] + var unlocks := [] + var selects := [] + ov.forge_buy_requested.connect(func(rid): buys.append(rid)) + ov.forge_unlock_requested.connect(func(rid): unlocks.append(rid)) + ov.forge_select_requested.connect(func(rid): selects.append(rid)) + + # ① 锁定态:树钥匙未点亮 → 工作间不可见、两区零行板(无任何可交互购买路径) + ov.open_camp({"balance": 999, "forge": mp.forge_shop_status(), "tree": []}) + Kit.check("锁定态:工作间不可见(forge_workshop 钥匙未点亮)〔锻造工坊批 V13〕", + not ov._forge_workbench.visible and ov._forge_lock_l.visible) + Kit.check("锁定态:两区零行板(占位纪律——零购买路径)〔锻造工坊批 V13〕", + ov._forge_zone_weapon.get_child_count() == 0 and ov._forge_zone_armor.get_child_count() == 0) + + # ② 解锁态:钥匙点亮 → 两区各三路线 + ov.open_camp({"balance": 500, "forge": mp.forge_shop_status(), + "tree": [{"node_id": "forge_workshop", "level": 1}]}) + Kit.check("解锁态:工作间可见(钥匙点亮展开)〔锻造工坊批 V13〕", ov._forge_workbench.visible) + Kit.check("两区各三路线(武器锻造区 3 + 护甲锻造区 3)〔锻造工坊批 V13〕", + ov._forge_zone_weapon.get_child_count() == 3 and ov._forge_zone_armor.get_child_count() == 3) + var rows: Dictionary = _row_by_id(ov) + Kit.check("六路线行册恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V13〕", + rows.has("sword") and rows.has("spear") and rows.has("staff") + and rows.has("light") and rows.has("heavy") and rows.has("ward")) + + # ③ 初始持有行(长剑/轻甲):购买钮现形带几何价、选中钮=当前禁用 + var rsw = rows.get("sword", {}) + var rli = rows.get("light", {}) + Kit.check("长剑行:购买钮「锻造 20」(0 级位几何价)+选中钮「当前」禁用〔锻造工坊批 V13〕", + rsw.get("buy_btn") != null and String(rsw.buy_btn.text) == "锻造 20" and not rsw.buy_btn.disabled + and rsw.get("select_btn") != null and String(rsw.select_btn.text) == "当前" and rsw.select_btn.disabled) + Kit.check("轻甲行:购买钮「锻造 20」+选中钮「当前」禁用〔锻造工坊批 V13〕", + rli.get("buy_btn") != null and String(rli.buy_btn.text) == "锻造 20" + and String(rli.select_btn.text) == "当前" and rli.select_btn.disabled) + + # ④ 待解锁行(长枪/法杖/重甲/魔法护甲):解锁钮 200、无购买钮、无选中钮、状态含双通道文案 + var rsp = rows.get("spear", {}) + var rwa = rows.get("ward", {}) + Kit.check("长枪行:解锁钮「解锁 200」+无购买钮+无选中钮〔锻造工坊批 V13〕", + rsp.get("unlock_btn") != null and String(rsp.unlock_btn.text) == "解锁 200" and not rsp.unlock_btn.disabled + and rsp.get("buy_btn") == null and rsp.get("select_btn") == null) + Kit.check("魔法护甲行:解锁钮「解锁 200」+状态含并联双通道文案(练满轻甲锻造 或 200 钥石直购)〔锻造工坊批 V13〕", + rwa.get("unlock_btn") != null and String(rwa.status_l.text).contains("练满轻甲锻造") + and String(rwa.status_l.text).contains("200")) + + # ⑤ 余额不足禁用态(常态防线) + ov.refresh_camp({"balance": 0, "forge": mp.forge_shop_status(), + "tree": [{"node_id": "forge_workshop", "level": 1}]}) + rows = _row_by_id(ov) + Kit.check("余额 0:长剑购买钮禁用+长枪解锁钮禁用(余额联动常态防线)〔锻造工坊批 V13〕", + rows.sword.buy_btn.disabled and rows.spear.unlock_btn.disabled) + + # ⑥ 满级行:无购买钮、状态=已满级(直置存档域构造——两分法调试口径) + sv.set_forge_route_level("sword", 5) + ov.refresh_camp({"balance": 500, "forge": mp.forge_shop_status(), + "tree": [{"node_id": "forge_workshop", "level": 1}]}) + rows = _row_by_id(ov) + Kit.check("长剑满级:无购买钮+状态「已满级」+长枪派生解锁出解锁钮消失(练满通道)〔锻造工坊批 V13〕", + rows.sword.get("buy_btn") == null and String(rows.sword.status_l.text) == "已满级" + and rows.spear.get("unlock_btn") == null and rows.spear.get("buy_btn") != null) + + # ⑦ 三意图信号外发(UI 只发意图——按真实按钮路径;选中钮直发信号面) + buys.clear(); unlocks.clear(); selects.clear() + rows.spear.buy_btn.pressed.emit() # 长枪已派生解锁——真实按钮路径 + rows.heavy.unlock_btn.pressed.emit() # 重甲待解锁——真实按钮路径 + ov.forge_select_requested.emit("spear") # 选中意图(当前行选中钮禁用态——信号面直发) + Kit.check("三意图信号外发(buy=spear/unlock=heavy/select=spear——UI 只发意图)〔锻造工坊批 V13〕", + buys == ["spear"] and unlocks == ["heavy"] and selects == ["spear"]) + + # 尾清(测试文件绝不残留) + ov.queue_free() + for suffix in ["", ".tmp", ".corrupt.bak"]: + if FileAccess.file_exists(_TEST_SAVE + suffix): + DirAccess.remove_absolute(_TEST_SAVE + suffix) + quit(Kit.finish("forge_workbench_test")) diff --git a/game/tests/unit/presentation/forge_workbench_test.gd.uid b/game/tests/unit/presentation/forge_workbench_test.gd.uid new file mode 100644 index 00000000..b32cca60 --- /dev/null +++ b/game/tests/unit/presentation/forge_workbench_test.gd.uid @@ -0,0 +1 @@ +uid://f8k2m4qx7wvtn From 31e12c88136f8c1db389b58c8f8a4782581feea3 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 13 Oct 2026 01:21:56 +0800 Subject: [PATCH 7/8] =?UTF-8?q?docs(=E8=BF=AD=E4=BB=A323-T7):=20=E6=80=BB?= =?UTF-8?q?=E6=94=B6=E5=8F=A3=E2=80=94=E2=80=94=E5=9B=9B=E9=97=B8=E5=85=A8?= =?UTF-8?q?=E7=BB=BF=E7=95=99=E7=97=95+=E6=9C=BA=E6=A2=B0=E9=9D=A2?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E4=B8=83=E4=BB=B6+=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E6=8A=A5=E5=91=8A=E8=90=BD=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 批末重闸全绿:冒烟 938/0+run_all 24 套+probe --click/--full+render_gate golden 0.00%+doc 三闸/熵增对基线零新增;MC 同种子配对 80 局逐位零漂移+锻造满配档方向性补充(30/80 局差) - 机械面同步:roadmap 迭代 23 行入库态/entities.yaml forge_workshop 回填实态(impl+save_domain+currency)/06 状态+①② v0.6 措势订正/存档序列化 v1.6(forge_routes 落地态+v7 沿革)/07 ㉒ 目检节+版本行/game/README 批次段 - 实现报告=design/qa/iter23-实现报告-2026-10-12.md(代拍板追认单九条+GDD 反哺点三条+残留人闸) - active.md 快照换代+状态记录 479/480 --- active.md | 22 ++++--- design/gdd/06-待办清单.md | 4 +- design/gdd/07-体验验证清单.md | 10 ++++ design/gdd/systems/表现与支撑/存档序列化.md | 7 ++- design/qa/iter23-实现报告-2026-10-12.md | 66 +++++++++++++++++++++ design/registry/entities.yaml | 5 +- game/README.md | 14 +++++ roadmap.md | 6 +- 8 files changed, 115 insertions(+), 19 deletions(-) create mode 100644 design/qa/iter23-实现报告-2026-10-12.md diff --git a/active.md b/active.md index 7c48276f..190fba43 100644 --- a/active.md +++ b/active.md @@ -17,21 +17,21 @@ ## 当前状态快照(收口批先更新本节,再追加状态记录;≤10 行) -- **更新于**:2026-10-12(active.md 状态记录清理批——完结条目 462-468 与锻造澄清 469 共八条删除,结论均在快照/06/git 史;迭代 21 开工条保留在飞。快照事实面仍反映迭代 20 总收口后现状)。 -- **批末闸全绿**:冒烟 864/0+run_all 21 套+probe --click/--full+render_gate+doc 三闸零新增+熵增 PASS;合并后主区复跑同绿。 -- **代拍板追认单十条已追认(2026-10-12 用户逐裁,06 在册)**:全数通过+连带裁定(血税保留/疼痛记忆「下场战斗」系笔误改「下一次攻击」/血税 desc 补生效时点修正案/流派=对外不显示对内保留事件族划分);**V20/V21/V22 人工核对半+实机目检五件仍待用户**(07 在册;V23 配额数字已随②③追认闭环)。 -- **GDD 反哺点①-④全部落文(2026-10-12 用户逐裁令)**:①奇物.md v3.16+血税 desc fe0970d(冒烟 864/0);②冒险结构.md v0.20(关卡大奖励=专长三选一按档配比加权+回血,提升池机制段移交奇物.md);③难度曲线与Boss.md v0.28(投放读数只测不判,数值不动);④02-系统清单.md v1.24(奇物行换代);⑤归奇物呈现条(06 在册)。 -- **回补带出四条待裁点**(秘宝卡两条+天赋树卡两条不阻塞;专长卡 §10.4 已随追认闭环)。 -- **现行文档事实源**:秘宝.md v2.9+奇物.md v3.16+**天赋树.md v0.8(迭代 21 已按此实装入库)**+锻造工坊.md v0.5+术语表 v1.8;迭代粒度规矩=迭代周期 ≈ 一周左右。 +- **更新于**:2026-10-12(迭代 23 锻造工坊实装批总收口——worktree g-plan2-iter23-forge 八提交,T0-T7 全量交付)。 +- **批末闸全绿**:冒烟 938/0+run_all 24 套(含 forge_table_test/forge_workbench_test 两新套件)+probe --click/--full 八探针+render_gate(golden 0.00%)+doc 三闸对基线零新增+熵增 PASS;MC 同种子配对 80 局逐位零漂移(锻造满配档方向性补充 30/80 局差)。 +- **现行文档事实源**:秘宝.md v2.9(目录 v1=12 件已实装)+奇物.md v3.16+天赋树.md v0.8+锻造工坊.md v0.6+术语表 v1.8。 +- **代拍板追认单九条+GDD 反哺点三条**待用户(design/qa/iter23-实现报告-2026-10-12.md);**残留人闸=实机目检锻造工坊工作间形态与购买交互(07 ㉒)**。 +- **迭代 21/22 残留人闸不变**:实机目检营地四柜台〔07 ⑳〕+秘宝解锁-装配流程〔07 ㉑〕;迭代 20 残留人闸=实机目检五件+V20/V21/V22 人工核对半(07 在册)。 +- **已裁在册**:M4 数值批顺延迭代 24+ 未排期(锻造工坊 provisional 终值归其统一标定)。 ## 项目当前位置(2026-10-12 刷新;实时细节以根目录 `roadmap.md` 与 06-待办清单 为准) - **里程碑**:M3=Alpha 版本的完整实现(2026-10-09 立规:用户未明令宣布 M3 完成前停留 M3,迭代只排 M3 需求,M4 一律不排期) -- **迭代**:迭代 19 五卡已入库(2026-10-08);迭代 20 专长实装批已入库(2026-10-11 总收口,反哺①-④已落文);**迭代 21 天赋树实装批已入库(2026-10-12,goal 会话 U0 开工总收口)**;迭代 22=秘宝实装批已编入待 U0 发令;M4=迭代 23+ 未排期 -- **近期主战场**:**迭代 21 残留人闸(实机目检营地四柜台〔V22〕+实现报告反哺点六条/追认单十二条待用户)**;迭代 22 待 U0 发令;迭代 20 残留人闸=实机目检五件+V20/V21/V22 人工核对半;文档治理连续批详情见 git 提交史 +- **迭代**:迭代 19 五卡已入库(2026-10-08);迭代 20 专长实装批已入库(2026-10-11 总收口,反哺①-④已落文);**迭代 21 天赋树实装批已入库(2026-10-12,goal 会话 U0 开工总收口)**;**迭代 22 秘宝体系实现重构落地批已入库(2026-10-12,T1-T7 全量交付)**;**迭代 23 锻造工坊实装批已入库(2026-10-12,/goal+iter U0 开工单会话总收口)**;M4=迭代 24+ 未排期 +- **近期主战场**:迭代 20/21/22/23 四批残留人闸(实机目检:专长五件 / 营地四柜台〔07 ⑳〕/ 秘宝解锁-装配〔07 ㉑〕/ 锻造工坊工作间〔07 ㉒〕)+ 各批实现报告代拍板追认单与 GDD 反哺点待用户;**下一前沿=M4 数值批,待 U0 发令** ## 未决面(进行中/待人闸/待裁点指针;现行事实源登记处=`design/gdd/06-待办清单.md`) - 待用户裁决:美术审计两项 P0(art-bible 死样张指涉+主角像素管线重产;06 在册);去毒残留词扫否(魔法道具约 30 处/罕见约 18 处,呈报在案);喂雷流 R5 甲重启条件回落文(一句话件);「通关 vs 通过第 N 关」全库口径统一 273 处(待裁是否补登记 06);迭代 20 实机目检五件+V20/V21/V22 人工核对半(07 在册) -- 待发令:迭代 22 秘宝实装批开工(U0);锻造工坊实装批(17 裁点清零、06 在册,与天赋树批排期合并评估) +- 待发令:M4 数值批(迭代 24+,未排期——锻造工坊 provisional 终值/钥石产出重标/削弱档终值/元进度定价余面归其统一执行) - 进行中:黑话清扫第二波(GDD 正文流程动词族约三千处,随时可派) ## 状态记录(滚动窗口:仅当前迭代;退役规则见 doc-conventions §10) @@ -51,4 +51,8 @@ - **475(2026-10-12,奇物呈现修复批——僵尸呈现整改落地,呈现改法=甲逐件行)**:用户令「解决呈现问题」,按呈报推荐案甲落地(改乙/丙一句话另调)。代码两文件:`hud_presenter.gd` feat_holdings_lines 聚合行(新 29 行无 archetype 下空键兜底「流派 ×N · 档一」僵尸文案)→ 逐件行「件名 ×N」;`battle_narrator.gd` feat_card_text 摘「件名 · 流派」族名行(卡片=件名+效果摘要两行,兑付反馈行随之「件名 · 摘要」)+payload 死键 archetype 摘除;注释面五处换代(hud_presenter/hero_info_panel_view/level_map_screen)。断言面预查:smoke:982 只钉 desc 零族名断言,免改。文档同步:battle.md C 区两行(归 06 指针摘除改已整改事实)/07 HUD 条(验修复后逐件行)/报告 §五.5(同)/README 段(473 勘误句再换代为已修复)/06 奇物呈现条**收口移除**(三处违规+档位三路+呈现改法三面全清,去向=本条+git)/反哺点⑤✅销账(①-⑤全出清;反哺点条与追认单条随下次收口清理批一并移除)。FeatsEngine.tiers_for 呈现侧调用摘除后全仓零调用方(引擎库保留不动)。(worktree presentfix-20261012;**编号注记:474 已被并行改名批占用,本条顺延 475**) +- **480(2026-10-12,迭代 23 锻造工坊实装批总收口)**:T0-T7 八提交全量交付(worktree g-plan2-iter23-forge;T1 行族 df666dd/T2 两轴移交 00d98b6/T5 存档 v7 6def354〔序调先行——写口硬依赖〕/T3 写口族 a6270ba/T4 注入链 ed06292/T6 工作间 UI 2915f19/T7 收口笔)。T1 新表 forge.tres(ForgeTable 默认两轴+六路线行族,ConfigDB forge_* 六访问器)+单测 41 断言;T2 hero.tres 两字段删除+spawn 注入缝(空串=锻造配置默认值回退,行为逐位连续);T5 SAVE_FORMAT_VERSION 6→7+forge_routes 域{levels/unlocked/weapon/armor}(无特效域)+v6→v7 空迁移缺键补齐=初始持有态;T3 写口族(forge_buy_level 几何价 20/30/45/67/101/forge_buy_unlock 一次性 200/forge_select_* 免费/forge_route_unlocked 派生单点);T4 hero_override 公式换代(FORGE_ROUTE_BONUS 退役)+build_adventure_params 两键+参数面快照锁死+effective_ac 三层(甲型基础 AC+路线加值+局内)+enc→spawn 末端;T6 占位面板→两区六路线行板+三意图信号+presenter 处理器+新单测 12 断言。验证:冒烟 938/0(基线 898+40 锻造断言)+run_all 24 套+probe --click/--full 全绿残留 0+render_gate golden 0.00%+doc 三闸/熵增对基线零新增(3 个 gdd_lint 存量红同款;master 第 10 个扫描文件=并行会话未跟踪件 08-Alpha版本标准.md,禁碰);**MC 同种子配对 80 局逐位零漂移**(na=40 seed0=9000 双画像:normal 0/40↔0/40、perfect 28/40↔28/40)+方向性补充(锻造满配档 atk+5/ac+5 同种子 30/80 局结局差:perfect 28→33/40)——默认态行为连续铁证+注入面生效证据。机械面文档七件同步(roadmap/entities.yaml 回填实态/06 状态+①② v0.6 措辞订正/存档序列化 v1.6/07 ㉒+game/README 批次段);实现报告=design/qa/iter23-实现报告-2026-10-12.md(代拍板追认单九条+GDD 反哺点三条待用户);残留人闸=实机目检工作间形态与购买交互(07 ㉒)。环境注记:render_gate 需 gamedev venv(PIL)。 + +- **479(2026-10-12,迭代 23 锻造工坊实装批开工)**:/goal+iter 技能调用=U0 开工令(用户目标=总收口全绿:冒烟/单测/探针/读数对拍四闸全过+实现报告落档)。开工检查全过:卡片 v1.2 零阻塞待裁点(Q1=0 级起步+锻造第一级=首个 +1 已裁;Q2=provisional 沿锚表投值已裁);V1-V14 验收清单三要素齐(V9 静态 grep 人工核对+V13 用户目检=纪律类人工项,余自动闸)。自洽扫描两处 L2 记录继续(不触红线):①卡片 F8「meta 域 13 件」清单滞后——现行实态 15 键(迭代 22 T2 同批新增 vision_used 未及登卡),对 forge_routes 新域零影响;②06 条目①「forge_products 两行改造」系 v0.5 旧文——设计 v0.6/卡片 F1 已订正为「行族已退役、新建行族」,卡片优先,T7 机械面同步时订正 06 措辞。事实抽查全符(F1 三处退役登记在册/F3 FORGE_ROUTE_BONUS 与 hero_override 在役行号漂移至 :155/:157/F5 两轴在役/F8 v6+_default_data 15 键/F9 forge_workshop 钥匙节点+营地占位面板接缝在册/F7 敌方轴未开)。T0 实查结案:①基线复读=冒烟 898/0+run_all 22 套全绿+probe_gate --click 全绿残留 0(存量 fx_reveal 已随 iter21 修复,零存量红);②buy_forge/forge_products 断言已随 iter21 T5 全摘(余件皆注释/退役登记——smoke:4398「MetaTable 行族规模 3/3/2」断言不受影响:新行族落新表);③hero 两轴读取面全清单=运行时独一处 unit_container.gd:64(db.hero 两轴快照)+冒烟断言 smoke_all.gd:1901(_db.hero.attack_type)+battle_orchestrator.gd:1410(消费 hero_unit 快照非表直读)+README 文档面,测试层零直读;④meta.tres 现役三行族+boss_keystone/conversion/attune_refresh_upgrade,FORGE_ATTRS 常驻;⑤树钥匙+占位面板接缝在册(camp_overlay_view.gd:275-284 钥匙门);⑥设计 v0.6 两节(裁决结果清单+工作间内容清单)复核无冲突。注入链已探明:main.build_adventure_params(:501)→ adventure params → _runner.set_* 族(:934-945 模式)→ level_runner enc(:480-494)→ begin_battle(battle_orchestrator.gd:344/:396 spawn)。代拍板立项(白名单内):新表 forge.tres(ForgeTable:默认两轴+六路线行族);路线 id=sword/spear/staff/light/heavy/ward(甲路线 id=甲型值本身);存档域 forge_routes={levels 六键+unlocked 直购清单+weapon/armor 选中}(初始持有+练满前一档=派生解锁、直购=入档)。定义面已入库(b9cca33,卡片单笔);主区残留脏件全属他批遗产零碰(.zcode/active.md/锻造工坊.md v0.6 续裁行/playnote 等)。worktree g-plan2-iter23-forge 开工,T1 起。 + - **476(2026-10-12,迭代 21 天赋树实装批总收口〔编号顺延——470-475 系并行批次占用,471/472 并行撞号已在彼条注记〕)**:T1-T7 全量交付(worktree g-plan2-iter21-tree 基于开工批 43f7230;master 并行十八笔已并入分支解 active 冲突后回合)。T1 schema 换代(FORK_GROUPS 4→3/RULE_KEYS 2→6/fork 单行双态 effects_a·b/row 退役门槛换轴/R1 裁撤案执行)+33→12 行重排(easter 摘攻防=契约 9);T2 购买面(easter 拒购/Fork 解锁默认 A/零币切态/彩蛋特化判定);T3 六键接线(EVENTS+flag_missed/FACTS+battle_index=L2 扩签两处/posture 每关首轮窗/sense 限次 counter 闸 domain:adventure 按关重置/守侧去重闸后发射=契约12/聚入账前×1.5/散双计价点×80%——数值全 provisional 归 M4);T4 注入换代(树淬炼 flat+锻造加值位预留 0/槽位判定源切树点亮数/ks_refresh 同账路由);T5 删除批(migrate_tree/buy_forge 家族/forge_products 行族/forge_levels 域/attunement_growth 谓词表/生涯三投影全删;tree_struct=2 标记废弃重置禁迁移);T6 四柜台并排(树=主界面/两钥匙门+零交互占位面板/图鉴常开/暂停层锻造段连带删)。存量红修复=fx_reveal 三处空 source_class(a57489e9 落地件,基线 51 行脚本错误清零——474 条 FAIL 注记随之销账)。验证:冒烟 886/0(V1-V10/V16 断言随批)+run_all 22 套(新增 V11 六键闭集单测)+probe --click --full 八探针全绿+render_gate golden 0.00%+doc 三闸/熵增闸对基线零新增(worktree 缺 ignored 素材两坑照 463 先例拷 artwork+game/assets/art 后绿)+V19 MC 三组同种子配对×60 局 perfect+max 档全部 61.7%、配对差 0.0pp 在带(normal 档基率地板读数换档记验证记录);守侧五场景=编排 rig 直注+引擎直构双承载。追认单十二条+反哺点六条+欠账三条落实现报告 design/qa/iter21-实现报告-2026-10-12.md 待用户;残留人闸=实机目检四柜台(V22)。 diff --git a/design/gdd/06-待办清单.md b/design/gdd/06-待办清单.md index 0d7e9fc4..031edb47 100644 --- a/design/gdd/06-待办清单.md +++ b/design/gdd/06-待办清单.md @@ -116,11 +116,11 @@ #### 锻造工坊实装批 — 待办(冻结:迭代规划冻结中,重启=用户发令)(2026-10-09 随成文批登记,时名「武器锻造系统实装批」;2026-10-11 随系统更名换代) -**内容**:按 [锻造工坊](systems/跨局循环/锻造工坊.md) **v0.5** 定案口径实装(需求卡片输入=其「裁决结果清单」+「工作间内容清单」两节)——迁移面八项:① `meta.tres` forge_products 攻击/护甲两行改造为武器与护甲独立等级路线(生命行清退归「天赋树实装批」旧柜台退役——生命唯一入口=树脊柱生命线,2026-10-11 A① 裁;同表其余四行族零触碰);② `hero.tres` 攻击类型/甲型两休眠轴启用(营地选中即写轴,既有预留位启用;一次冒险一个武器类型+一个甲型,营地免费切换、冒险开始后锁死);③ `feats.tres` magic_vs_magic 死条件行——复活案作废(2026-10-11 核结:所属件魔网织者在素材库表二 27 件退役删除名单),随专长退役删行批消解,本批零触碰;④ 存档域新增三武器+三甲型等级与当前选中标记(**不设特效域**——2026-10-12 裁 6;开发阶段无迁移——旧档废弃口径随批登记存档序列化文档与 registry);⑤ **满级特效本期不实装**(2026-10-12 裁 6:不开三条新参数位、不加效果类型词,扩展位留空,启用需另立项);⑥ **敌方攻击类型轴填值(3b 裁连带,跨系统汇合点)**——敌方攻击单据 `attack_type` 按「未设置=默认 normal」填值使其参与克制矩阵查表(现役为 `""` 恒不查矩阵;`enemy.tres:527` 轴未开、全部 normal 档):**执行面归敌人系统/战斗结算侧**,本批只登记需求与数值影响(全局平移主角受伤点数:穿重甲 −1、穿魔法护甲 +1,须进⑧配对读数验证);排期与敌人系统/战斗结算侧协商(可同批做,也可先行);敌方 attack_type 为敌方定义数据(与敌种名字/血量同批同时读写、无独立时序),填值归敌人系统/战斗结算侧自然推进;本批只按 3b 裁落结构面(矩阵查表现役已支持)(2026-10-12 订正:原「两批排期协商(可同批做,也可先行)」句删除——用户裁:时序问题不存在);⑦ 术语登记——三型甲统一命名(轻甲/重甲/魔法护甲;**「魔法甲」系笔误不登记别名**)+ 工作间两区定名(武器锻造区/护甲锻造区)登记 registry entities.yaml;⑧ 数值验证义务=改前/改后同随机种子配对读数验证难度影响,统一调整归 M4 数值批。**解锁通道**:四条待解锁路线(长枪/法杖/重甲/魔法护甲)均走并联双通道(练满前一档 OR 一次性解锁价,互不影响、玩家自选),不设串联门槛。**前置**:17 裁点用户逐点裁决——**已清零(2026-10-12 B 组 15 点逐裁落文 v0.5;2c/5a 于 v0.4 结案)**。**关联(2026-10-11 换代)**:锻造工坊钥匙节点改无前置直接可点亮(2026-10-11 用户裁,原等级阈值前置作废);树脊柱砍攻/护两线(攻防所有权切分——攻击唯一入口=武器路线、AC 唯一入口=甲路线,树侧落文归天赋树会话交接批)后本批=攻防数值唯一实装面;树结构换代(33→12)已随迭代 21 入库——本批④存档域 forge_routes 独立成域(已预登记存档序列化.md v7 计划行),无共域冲突(2026-10-12 订正:原「排期两批合并评估」语境随树批入库消失——用户裁:非裁点)。**前置信息(2026-10-12 用户裁定:不在本批考虑)**:3b 后现行数据三型成纯序(重甲正向 > 轻甲中性 > 魔法护甲负向,敌方现行无魔法攻击故暂无优势窗口)——系**后续开发改造的前置事实**,敌方攻击类型差异化与魔法护甲的正当性归后续批次(含 M4 在基础 AC / 解锁价上的取值);本批只按统一伤害轴落结构与字段。 +**内容**:按 [锻造工坊](systems/跨局循环/锻造工坊.md) **v0.6** 定案口径实装(需求卡片输入=其「裁决结果清单」+「工作间内容清单」两节)——迁移面八项:① **新建锻造路线行族**(forge_products 已随迭代 21 T5 整体退役、无改造对象——v0.6 事实订正:行族/访问器/购买面三处退役登记在册,本批新建而非改造;落位=新表 forge.tres〔ForgeTable〕,钥石定价历史考古 20/150%、provisional 投值归 M4 终值);② **两根类型轴所有权移交**(默认值归锻造工坊配置常驻持有、hero.tres 静态表两轴删除、选中态写存档域 forge_routes+冒险开始参数面注入;一次冒险一个武器类型+一个甲型,营地免费切换、冒险开始后锁死);③ `feats.tres` magic_vs_magic 死条件行——复活案作废(2026-10-11 核结:所属件魔网织者在素材库表二 27 件退役删除名单),随专长退役删行批消解,本批零触碰;④ 存档域新增三武器+三甲型等级与当前选中标记(**不设特效域**——2026-10-12 裁 6;开发阶段无迁移——旧档缺键补齐=初始持有态,v6→v7 空迁移;存档序列化文档与 registry 随批登记);⑤ **满级特效本期不实装**(2026-10-12 裁 6:不开三条新参数位、不加效果类型词,扩展位留空,启用需另立项);⑥ **敌方攻击类型轴填值(3b 裁连带,跨系统汇合点)**——敌方攻击单据 `attack_type` 按「未设置=默认 normal」填值使其参与克制矩阵查表(现役为 `""` 恒不查矩阵;`enemy.tres:527` 轴未开、全部 normal 档):**执行面归敌人系统/战斗结算侧**,本批只登记需求与数值影响(全局平移主角受伤点数:穿重甲 −1、穿魔法护甲 +1,须进⑧配对读数验证);敌方 attack_type 为敌方定义数据(与敌种名字/血量同批同时读写、无独立时序),填值归敌人系统/战斗结算侧自然推进;本批只按 3b 裁落结构面(矩阵查表现役已支持)(2026-10-12 订正:原「两批排期协商(可同批做,也可先行)」句删除——用户裁:时序问题不存在);⑦ 术语登记——三型甲统一命名(轻甲/重甲/魔法护甲;**「魔法甲」系笔误不登记别名**)+ 工作间两区定名(武器锻造区/护甲锻造区)登记 registry entities.yaml;⑧ 数值验证义务=改前/改后同随机种子配对读数验证难度影响,统一调整归 M4 数值批。**解锁通道**:四条待解锁路线(长枪/法杖/重甲/魔法护甲)均走并联双通道(练满前一档 OR 一次性解锁价,互不影响、玩家自选),不设串联门槛。**前置**:17 裁点用户逐点裁决——**已清零(2026-10-12 B 组 15 点逐裁落文 v0.5;2c/5a 于 v0.4 结案)**。**关联(2026-10-11 换代)**:锻造工坊钥匙节点改无前置直接可点亮(2026-10-11 用户裁,原等级阈值前置作废);树脊柱砍攻/护两线(攻防所有权切分——攻击唯一入口=武器路线、AC 唯一入口=甲路线,树侧落文归天赋树会话交接批)后本批=攻防数值唯一实装面;树结构换代(33→12)已随迭代 21 入库——本批④存档域 forge_routes 独立成域(已预登记存档序列化.md v7 计划行),无共域冲突(2026-10-12 订正:原「排期两批合并评估」语境随树批入库消失——用户裁:非裁点)。**前置信息(2026-10-12 用户裁定:不在本批考虑)**:3b 后现行数据三型成纯序(重甲正向 > 轻甲中性 > 魔法护甲负向,敌方现行无魔法攻击故暂无优势窗口)——系**后续开发改造的前置事实**,敌方攻击类型差异化与魔法护甲的正当性归后续批次(含 M4 在基础 AC / 解锁价上的取值);本批只按统一伤害轴落结构与字段。 **归属**:[锻造工坊](systems/跨局循环/锻造工坊.md) 实装(跨 meta/hero/feats/存档/战斗开场参数五面) -**状态**:**已编入迭代 23**(2026-10-12 用户令——开工门=U0 发令;设计面 v0.6:17 裁点清零+完备性检查批〔现役基线换代/两轴所有权移交/术语登记完毕〕,commit f5215c7) +**状态**:✅ **已入库(2026-10-12,/goal+iter U0 开工单会话交付——T1 行族/T2 两轴移交/T3 写口族/T5 存档 v7〔序调先行:写口硬依赖〕/T4 注入链/T6 工作间 UI/T7 收口七提交;冒烟 938/0+run_all 24 套+重闸全绿+MC 同种子配对零漂移;实现报告=design/qa/iter23-实现报告-2026-10-12.md;残留人闸=实机目检工作间形态与购买交互〔07 在册〕;代拍板追认单+GDD 反哺点待用户)** #### 玩家向显示名改名小批(局内三轴 + 临时 AC 状态)——跨循环名称冲突清偿(2026-10-12 用户逐裁,锻造工坊裁决会话续 8a 连带) diff --git a/design/gdd/07-体验验证清单.md b/design/gdd/07-体验验证清单.md index 5d0921a5..23f50bc9 100644 --- a/design/gdd/07-体验验证清单.md +++ b/design/gdd/07-体验验证清单.md @@ -209,6 +209,15 @@ - **装配不可变**:冒险中无任何换装入口(三选一/奖励节点/战斗内零替换交互——旧替换子交互已拆)。 - 验证方式:用户实机目检(本批自动闸外唯一人闸;能力验证半已由自动化闸覆盖=V1-V19)。 +### ㉒ 锻造工坊工作间形态与购买交互(迭代 23 锻造工坊实装批随批登记——2026-10-12) + +- **门与形态**:树上「锻造工坊」钥匙未点亮=锁定态(纯文案、无任何可交互购买路径);点亮后工作间展开=武器锻造区/护甲锻造区两区并列,六路线行板(行名=件名+锻造:长剑锻造/长枪锻造/法杖锻造/轻甲锻造/重甲锻造/魔法护甲锻造;每行件名+Lv+解锁态+钮组)。 +- **购买面**:已解锁路线「锻造 N 钥石」钮(几何价 20/30/45/67/101 逐级涨);余额不足钮禁用;买后等级+1、行板即时刷新、钥石余额扣减。 +- **解锁面**:待解锁路线双通道呈现——「练满长剑锻造 或 200 钥石直购」:长剑练满→长枪/法杖解锁钮消失可直接锻造(零收费);或 200 钥石直购开;两通道互不影响(未练满可直购、直购后练满不重复收)。 +- **选中面**:已解锁路线「选中」钮免费切换(当前路线=「当前」禁用态);一次冒险只带一个武器类型+一个甲型;冒险开始后无切换入口(锁死)。 +- **注入抽查**:营地选长枪→开局攻击类型=穿刺(对敌护甲查克制矩阵生效);选重甲→入战 AC=12+路线加值;旧路线练度保留(换回即生效)。 +- 验证方式:用户实机目检(本批自动闸外唯一人闸;能力验证半已由自动化闸覆盖=V1-V14)。 + ## 验收读数重测义务(履行留痕 + 现行义务) **已履行**(按序换代,旧读数各随其批作废留痕不入本单):①验收读数重测批(2026-09-19——Boss 单独出列/承伤带/通关率基线全量重测,硬化翻倍期·乏力死效果期·充能期旧读数作废);②迭代 5 基线重测(统一随机流后 MC+verifier 双窗);③**数值修复批新尺重测(2026-10-01)**——模拟档位重构后全量重测,新锚 `sim-data/runsum_numfix_final_n200_20261001.jsonl`,全旧带作废只测不判(numfix 报告(已退役,git 历史可查));④**迭代 10 奖励重构批重锚(2026-10-01)**——四代锚 77000/78000/79000/80000(改前/重构后/共享池后/掷定前置后,各 400 局),普通 L1 41.8%/L2 10.2% 带内(迭代10通关率读数(已退役,git 历史可查));⑤**T19 真人标定局收口(2026-10-03 用户裁)**——有效局 2(1 通关 1 死于 L3 首战,T19真人试玩读数(已退役,git 历史可查)),用户裁定**佐证级收口不再补局**;k 预期带同日立(**正常 >0.5、预警 <0.3**),字段随 81000 重锚入库后补算终值。 @@ -226,3 +235,4 @@ | v0.3 | 2026-10-09 | 版本历史表补建——本文件此前无版本表(2026-10-09 审查批呈报在案),本卫生批补建本表并补登上两行 | | v0.4 | 2026-10-09 | **文档正确性审查批·独立复核(worktree docaudit-2026-10-09)**:①三处「链接语法但目标非路径」伪死链去链接改纯文本(numfix 报告/迭代10通关率读数/T19真人试玩读数——三份读数均已退役,git 历史可查口径不变);②「难度修正批」批次名全替「数值批(Beta 阶段,未排期)」——与 06 现行归属称呼对齐(同批=M4 数值批需求卡片,未排期态不变) | | v0.5 | 2026-10-10 | 生涯页呈现行柜台名扫改(2026-10-10 天赋树设计会话裁「道具工坊」改名「秘宝匣」):「道具工坊已解锁 X/6 件」改「秘宝匣已解锁 X/6 件」;计数语义(旧六件→新目录)随实装批换代,见 06「天赋树结构重构批」改名连带面 | +| v0.6 | 2026-10-12 | 新增 ㉒ 锻造工坊工作间形态与购买交互目检节(迭代 23 锻造工坊实装批随批登记——V13 人闸观测项:门与形态/购买/解锁双通道/选中与锁死/注入抽查) | diff --git a/design/gdd/systems/表现与支撑/存档序列化.md b/design/gdd/systems/表现与支撑/存档序列化.md index e673fa54..1331287e 100644 --- a/design/gdd/systems/表现与支撑/存档序列化.md +++ b/design/gdd/systems/表现与支撑/存档序列化.md @@ -101,7 +101,7 @@ SaveManager 不依赖任何玩法系统就能工作:它是被动的承载方 | `adventure_total` | 冒险总局数终身计数(开局即计、含放弃与失败,v6) | [生涯](../跨局循环/生涯.md) | | `adventure_cleared` | 通关局数终身计数(三关全通才计 1,v6) | [生涯](../跨局循环/生涯.md) | | ~~`forge_levels`~~ | ~~属性锻造三档等级~~ | **已退役**(迭代 21 T5:行族/访问器/域随攻防所有权切分删除——`save_manager.gd:12-14/:399-401` 白名单收口;继任 = 锻造工坊路线等级域,见下行计划登记) | -| `forge_routes`(**计划新增**——锻造工坊实装批,v7;域名以实装批定名为准) | 三武器等级 + 三甲等级 + 当前武器/甲选中标记(各 1;无特效域;旧档破坏性废弃口径归该批) | [锻造工坊](../跨局循环/锻造工坊.md) | +| `forge_routes`(**已落地 v7——锻造工坊实装批 T5,迭代 23**) | {levels: 六路线等级(sword/spear/staff/light/heavy/ward), unlocked: 直购解锁路线 id 清单, weapon: 当前武器路线 id, armor: 当前甲路线 id};无特效域;旧档缺键补齐 = 初始持有态 | [锻造工坊](../跨局循环/锻造工坊.md) | | `breakpoint` | 冒险断点快照(停点粒度续玩域;空字典 = 无断点合法态——「继续上局」隐藏) | [冒险结构](../冒险循环/冒险结构.md) | v4/v5/v6 各域缺键由读取方按默认值补齐(空集合/0),旧档零迁移损失;收束自增域(通关次数/Boss 计数/终身计数)的唯一入账点 = `apply_adventure_summary`。 @@ -128,7 +128,7 @@ v4/v5/v6 各域缺键由读取方按默认值补齐(空集合/0),旧档零 ### 版本与迁移 -存档带版本字段(`version`,当前 = **6**;版本沿革:v2 = 断点域出生〔迭代 8 卡①〕→ v3 = 断点附随件扩列 + 摘要 keystone_drops〔迭代 10 奖励重构〕→ v4 = 同调批扩域〔boss_kill_count / attunement_equipped / attune_refresh_level,迭代 11 第①批〕→ v5 = 天赋树域〔tree_nodes / fork_sides,迭代 12〕→ v6 = 生涯终身计数域〔adventure_total / adventure_cleared,迭代 18 卡④〕;与 registry `schema_version` 同源)。**(计划)v7 = 锻造路线域**〔三武器/三甲等级 + 当前选中标记——锻造工坊实装批,2026-10-12 v1.5 预登记;届时旧档破坏性废弃口径归该批,禁迁移/折算〕。三条走向: +存档带版本字段(`version`,当前 = **7**;版本沿革:v2 = 断点域出生〔迭代 8 卡①〕→ v3 = 断点附随件扩列 + 摘要 keystone_drops〔迭代 10 奖励重构〕→ v4 = 同调批扩域〔boss_kill_count / attunement_equipped / attune_refresh_level,迭代 11 第①批〕→ v5 = 天赋树域〔tree_nodes / fork_sides,迭代 12〕→ v6 = 生涯终身计数域〔adventure_total / adventure_cleared,迭代 18 卡④〕→ **v7 = 锻造工坊域〔forge_routes,迭代 23 T5——已落地:{levels 六路线等级 + unlocked 直购解锁清单 + weapon/armor 当前选中标记},无特效域;旧档缺键由 _normalize 按默认补齐 = 初始持有态(sword/light 在档、余四锁、选中=初始路线),v6→v7 空迁移 = 缺键补齐模式(v3→v4 先例);开发阶段禁迁移禁折算——契约 4〕**;与 registry `schema_version` 同源)。三条走向: | 情形 | 处置 | |---|---| @@ -136,7 +136,7 @@ v4/v5/v6 各域缺键由读取方按默认值补齐(空集合/0),旧档零 | 版本更高(未来版本的档) | **拒读且禁写**:不加载、后续写盘一律拒绝、内存以默认新档运行——保护未来版本的档不被当前版本覆写 | | 版本更低(旧档) | 读档时逐级迁移:从旧版本起逐个调用注册的迁移函数升到当前版本;任一步失败按损坏兜底处理 | -迁移函数经 `register_migration(旧版本号, 函数)` 注册:函数把数据从「旧版本」改写成「旧版本 + 1」。生产迁移链现行五件(v2 起自注册):v1→v2 = 断点域补空;v2→v3 = 断点附随件与摘要 keystone_drops 补键(读取方 .get 兜底);v3→v4 = 同调三域补键;v4→v5 = 树域两键补齐(原挂接的「三体系迁移」体已随迭代 21 T5 攻防所有权切分删除——`save_manager.gd:96-98`、`meta_progression.gd:294-295` 实证;forge_levels 旧域同批清退,加载时按默认模型键集白名单收口,`save_manager.gd:399-401`);v5→v6 = 生涯两终身计数域补零。任一步失败按损坏兜底处理。 +迁移函数经 `register_migration(旧版本号, 函数)` 注册:函数把数据从「旧版本」改写成「旧版本 + 1」。生产迁移链现行六件(v2 起自注册):v1→v2 = 断点域补空;v2→v3 = 断点附随件与摘要 keystone_drops 补键(读取方 .get 兜底);v3→v4 = 同调三域补键;v4→v5 = 树域两键补齐(原挂接的「三体系迁移」体已随迭代 21 T5 攻防所有权切分删除——`save_manager.gd:96-98`、`meta_progression.gd:294-295` 实证;forge_levels 旧域同批清退,加载时按默认模型键集白名单收口,`save_manager.gd:399-401`);v5→v6 = 生涯两终身计数域补零;**v6→v7 = 锻造工坊域 forge_routes 缺键补齐(迭代 23 T5——无结构迁移体,版本号推进仅为 schema_version 对齐;解锁态派生不落档:初始持有〔表 initial_owned〕∨ 直购〔unlocked 清单〕∨ 练满前一档〔levels[prereq]≥cap〕)**。任一步失败按损坏兜底处理。 ### 损坏兜底 @@ -192,3 +192,4 @@ MVP 不要求本系统的闭环([不做清单](../../05-不做清单.md)把单 | v1.3 | 2026-10-01 | **版本兼容口径批(迭代 10 追加批 g 裁——用户拍板:版本兼容对齐 semantic version 标准)**:①版本策略成文口径——本域版本号语义 = **向后兼容的次序版本**:升版只允许「旧档在新代码可读可玩可续玩」(缺新键=合法空态、内容零丢失、断点可续),禁「新档在旧代码可读」承诺(向前兼容不做);②v2→v3 迁移体=空迁移(断点附随件扩列〔bonus_taken/bonus_pending/bonus_result/bonus_aux_carry/keystone_drops + bonus.ap 轴〕与摘要 additive 键 keystone_drops 均由读取方 .get 兜底——兼容性由冒烟 v1→v3 与 v2→v3 断点续玩断言承载,2026-10-01 增);③中断节点兼容义务:每升版必须在冒烟断言「旧版断点→新版续玩受理」(现行三档 v1/v2/v3 齐) | | v1.4 | 2026-10-09 | **文档正确性审查批·独立复核(worktree docaudit-2026-10-09——版本滞后四代换代,全部代码实证 save_manager.gd SAVE_FORMAT_VERSION=6)**:①数据模型 JSON 与域清单表换代 v2→v6——补 v4 同调三域(boss_kill_count/attunement_equipped/attune_refresh_level,迭代 11①)、v5 树域(tree_nodes/fork_sides,迭代 12)、v6 生涯两终身计数域(adventure_total/adventure_cleared,迭代 18 卡④)逐域 owner 行;②版本与迁移节换代——当前=6、迁移链五件(v1→v2…v5→v6,含 v4→v5 三体系迁移指路天赋树);③调参表版本常量 2→6;④缺键补齐纪律句补记(读取方默认补零/空集合,收束自增唯一入账=apply_adventure_summary)。注:v3/v4/v5/v6 四次升版当时均未回写本文,本批一次性清偿 | | v1.5 | 2026-10-12 | **锻造工坊完备性检查批随迁(用户令:新加内容现在补、不等实装批)**:①**预登记锻造路线域**(计划 v7):域清单表 + 版本沿革补 `forge_routes`(三武器等级+三甲等级+当前选中标记、无特效域、旧档破坏性废弃归锻造工坊实装批;域名以实装批定名为准);②**清偿 forge_levels 残留**——JSON 示例、域清单表删该域(代码侧已随迭代 21 T5 删除:`save_manager.gd:12-14/:399-401` 白名单收口);迁移链 v4→v5 的「三体系迁移」体改述为已删除(`save_manager.gd:96-98`、`meta_progression.gd:294-295`);③**补登两漏域**(代码实证 `save_manager.gd:119-139`):`tree_struct`(树结构版本标记,迭代 21 T5,缺失=换代前结构加载废弃重置,owner 天赋树)、`first_adventure_done`(首冒险完成标记,新档默认 false、旧档缺省按已完成兜底,`meta_progression.gd:100` 置位,owner 元进度);④消费面纪律句「读写锻造等级」→「读写树等级」(访问器已随行族退役删除)。版本号常量仍 =6(v7 未落地) | +| v1.6 | 2026-10-12 | **锻造工坊实装批随迁(迭代 23 T5——v7 落地)**:①域清单表 `forge_routes` 计划行→实际落地态({levels 六路线等级 + unlocked 直购解锁清单 + weapon/armor 当前选中标记},域内键名以实装批定名);②版本与迁移节——当前 =7、迁移链补 v6→v7(空迁移=缺键补齐模式,解锁态派生不落档三通道);③旧档口径订正——「破坏性废弃」→「缺键补齐 = 初始持有态」(开发阶段禁迁移禁折算纪律不变,契约 4);④域界注记——forge_routes 属 meta 永久进度域(契约 2 反面清单不涉) | diff --git a/design/qa/iter23-实现报告-2026-10-12.md b/design/qa/iter23-实现报告-2026-10-12.md new file mode 100644 index 00000000..b2c45716 --- /dev/null +++ b/design/qa/iter23-实现报告-2026-10-12.md @@ -0,0 +1,66 @@ +# 迭代 23 实现报告——锻造工坊实装批 + +> 日期:2026-10-12 | 迭代:23(M3=Alpha 完整实现面)| 开工令:/goal + iter 技能 U0(用户目标=总收口全绿:冒烟/单测/探针/读数对拍四闸全过+实现报告落档) +> 需求卡片:`design/research/需求卡片-锻造工坊实装批-2026-10-12.md`(v1.2,Q1/Q2 已裁、零待裁点) +> 设计权威:`design/gdd/systems/跨局循环/锻造工坊.md` v0.6(17 裁点清零+完备性检查批,commit f5215c7) +> Worktree:`g-plan2-iter23-forge`(分支 iter23-forge-20261012,基点 b9cca33)| 批内提交:T0 定义入库 b9cca33 + T1 df666dd / T2 00d98b6 / T5 6def354 / T3 a6270ba / T4 ed06292 / T6 2915f19 / T7 收口笔 +> 状态:**✅ 已全批入库(单会话交付)**——四闸全绿+重闸全绿;残留人闸=实机目检工作间形态与购买交互(07 ㉒)+代拍板追认单九项+GDD 反哺点三条待用户 + +## 一、开工检查结论(第 2 步) + +- **门**:roadmap 迭代 23 行「开工门=U0 发令」——本会话 /goal 调用即 U0,门解。 +- **待裁点**:卡片 v1.2 零阻塞待裁点(Q1=等级 0 起步+锻造第一级=首个 +1;Q2=provisional 沿锚表投值——均已裁)。 +- **验收清单**:V1-V14 逐条三要素(测什么/怎么测/谁验)齐;V9(静态 grep 人工核对)与 V13(用户目检)为纪律类人工项,余自动闸。 +- **自洽扫描两处 L2(记录继续,不触红线)**:①卡片 F8「meta 域 13 件」清单滞后——现行 15 键(vision_used 系迭代 22 T2 同日新增未及登卡),对 forge_routes 新域零影响;②卡片 F11「效果类型闭集 24 种」系 2026-10-07 设计文档事实快照——现行 31(迭代 20 T1 +7、迭代 22 T3 +1),本批零扩签,断言钉 31。另:06 条目①「forge_products 两行改造」系 v0.5 旧文(v0.6/卡片 F1 已订正为「行族已退役、新建行族」)——T7 机械面同步时订正。 +- **T0 实查**:基线复读=冒烟 898/0+run_all 22 套+probe --click 全绿残留 0(存量 fx_reveal 已随 iter21 修复,零存量红);buy_forge/forge_products 断言已随迭代 21 T5 全摘;hero 两轴读取面全清单(运行时 unit_container.gd:64 + 冒烟 smoke_all.gd:1901 + battle_orchestrator 间接消费 + README 文档面);注入链探明(build_adventure_params → adventure params → set_* 族 → enc → spawn)。 + +## 二、任务卡状态(T0-T7) + +| 卡 | 状态 | 交付要点 | 提交 | +|---|---|---|---| +| T0 前置实查 | ✅ | 基线三件套+F1-F13 事实核对+断言摘除清单+两轴读取面清单+注入链探明;定义面入库(b9cca33) | b9cca33 | +| T1 路线行族 | ✅ | 新表 forge.tres(ForgeTable:默认两轴+六路线行族)+ConfigDB 六访问器+单测 41 断言(V1 正反 22 类/V11 行字段闭集+词表 31 钉现/V2 价格梯) | df666dd | +| T2 两轴移交 | ✅ | hero.tres 两字段删除(schema 同步)+spawn 注入缝(空串=锻造配置默认值回退,行为逐位连续)+冒烟 4 断言(V5 前半) | 00d98b6 | +| T5 存档 v7 | ✅ **(序调先行——T3 写口硬依赖存档域)** | SAVE_FORMAT_VERSION 6→7+_migrate_v6_v7 空迁移+forge_routes 域(levels/unlocked/weapon/armor,无特效域)+访问器组+冒烟 V8 六断言(往返/v6 旧档补齐/键集/值拷贝) | 6def354 | +| T3 写口族 | ✅ | MetaProgression 锻造写口(forge_buy_level 几何价/forge_buy_unlock 一次性解锁价/forge_select_weapon·armor 免费选中/forge_route_unlocked 派生单点)+冒烟 V2/V3/V4/V6 共 15 断言 | a6270ba | +| T4 注入接线 | ✅ | hero_override 公式换代(FORGE_ROUTE_BONUS 退役)+build_adventure_params 两键+参数面快照(契约 8 锁死)+effective_ac 三层(甲型基础 AC+路线加值+局内)+enc→spawn 末端;冒烟 V5 后半/V6/V7/V10 共 15 断言 | ed06292 | +| T6 工作间 UI | ✅ | 占位面板→两区六路线行板(锻造/解锁/选中三钮+余额联动+锁定态零行板零购买路径)+presenter 三意图处理器+main 接线+新单测 forge_workbench_test 12 断言(V13 自动化前置面) | 2915f19 | +| T7 收口 | ✅ | 重闸全绿+MC 配对+机械面文档七件+本报告 | 收口笔 | + +## 三、验证闸总账(批末重闸) + +| 闸 | 结果 | +|---|---| +| 冒烟(run_smoke 三闸:退出码/摘要/脚本错误扫描) | **938 项检查 0 项失败**(基线 898/0 + 本批 40 断言:T2×4/T3×15/T4×15/T5×6) | +| 单元(run_all) | **24 套全绿**(基线 22 + forge_table_test 41 断言 + forge_workbench_test 12 断言) | +| 探针(probe_gate) | **--click + --full 全绿、残留 Godot=0**(ui_input/ui_struct/ui_contrast/click/ui_probe/ui_tune/t17_defer/fx_probe 八件) | +| 读数对拍(MC 同种子配对,verifier_batch adventure 段 na=40 seed0=9000 双画像) | **A) 改前 vs 改后白板:80 局同种子逐位零差异**(normal 0/40↔0/40 均关 0.10;perfect 28/40↔28/40 均关 2.42)——默认态行为连续零漂移铁证。**B) 方向性补充**:白板 vs 锻造满配档(atk+5/ac+5 同种子)80 局中 30 局结局变化(normal 0/40→1/40、perfect 28/40→33/40)——锻造加值注入面生效且方向正确(数值 provisional 归 M4) | +| render_gate(窗口化渲染闸) | **全绿**——shot_checker A-D 断言过 + 棋盘 golden 三屏 changed=0.00%(容差内) | +| doc 三闸(gdd_lint/doc_link/doc_term) | **对基线零新增**——gdd_lint 3 个存量红同款(06/07 表格占比、术语表,master 同);doc_link 已知死链 1(存量);doc_term 单 hit 存量(06:37 拔旗,非本批编辑区) | +| 熵增闸(repo_structure_audit) | **PASS**(四查全过) | + +> 环境注记:render_gate 的 PIL 依赖需 gamedev venv(`PATH=/d/dev/python/envs/gamedev/Scripts:$PATH`)——WorkBuddy 裸 python 无 PIL;本机 Godot 4.7.2 路径 `D:\code\godot\Godot_v4.7.2-stable_win64_console.exe`。 + +## 四、代拍板追认单(白名单内,待用户追认) + +1. **行族落位选型**:新表 `forge.tres`(ForgeTable)而非 meta.tres 内新行族——T2「锻造工坊配置表新建」与 T1 行族同表承载(默认两轴+六路线);访问器连续性沿 treasure_items 先例(`db.forge_*`)。meta.tres 三行族零触碰。 +2. **路线 id 命名**:sword/spear/staff/light/heavy/ward——甲路线 id=甲型值本身(ward=魔法护甲,3a 裁统一命名);存档域 levels 六键同。 +3. **存档域键名**:`forge_routes = {levels:{六路线→等级}, unlocked:[直购路线 id], weapon/armor:选中路线 id}`——unlocked 只记直购;初始持有(表 initial_owned)与练满前一档(levels[prereq]≥cap)为派生解锁不落档(解锁态派生单点=MetaProgression.forge_route_unlocked)。 +4. **provisional 行值**:沿 T1 锚表投值(cap 5/每级增量统一 +1/基础价 20×150%^n=20/30/45/67/101/一次性解锁价 200/甲型基础 AC 10-12-9/初始持有 sword+light/前置 spear·staff←sword、heavy·ward←light)——Q2 已裁沿锚,终值归 M4。 +5. **v6→v7 空迁移形态**:缺键补齐模式(v3→v4 先例)——无结构迁移体,_normalize 按默认模型补 forge_routes=初始持有态;幂等零漂移(冒烟断言)。 +6. **执行序调整**:T5 存档域先于 T3 写口施工(写口族硬依赖存档域——不满足则 T3 无法落盘),台账记录。 +7. **工作间行板布局**(程序占位):件名+Lv+解锁态双通道文案+锻造/解锁/选中三钮横排;锁定态(钥匙未点亮)默认不可见+门控重建=零行板零购买路径(占位纪律延续,2026-10-11 用户令);视觉终稿归 HUD 营地视觉批。 +8. **V11 词表钉 31**:卡片 F11「效果类型闭集 24 种」系 2026-10-07 旧快照(现行 31)——L2 事实订正,断言钉现行数(将来加词即转红提示复核)。 +9. **F8 域计数订正**:卡片「meta 域 13 件」实为 15 键(vision_used 迭代 22 T2 同日新增)——L2 事实订正,零需求影响。 + +## 五、GDD 反哺点(设计权威文档不动,逐点待用户裁决后另批落文) + +1. **锻造工坊.md**:①「每条路线的内容·等级:1 → 上限」正文宜补 Q1「0=未练合法态」衔接表述(Q1 已在版本历史续裁行落文,正文未同步);②「现役代码事实」表「效果引擎效果类型闭集 24 种」滞后(现行 31);③实装边界 1「行族命名、字段结构与落表位置归实装批定义」已兑现(新表 forge.tres/ROUTE_IDS 六 id/12 字段闭集)——可回填实态句。 +2. **元进度.md**:①「跨局注入」节四层公式宜补「甲型基础 AC=选中甲路线行 base_ac(effective_ac 第一层)」终态句(树 flat+锻造路线加值已实装);②营地钥石入口族宜登记锻造三写口(forge_buy_level/forge_buy_unlock/forge_select_*——buy_forge 退役后的新族,契约 9 写口唯一)。 +3. **术语表.md**:实现引入路线 id 与存档域内键名(levels/unlocked/weapon/armor)——非玩家可见术语,预计零新词;若需登记补登待裁。 + +## 六、残留人闸与遗留 + +- **残留人闸(07 ㉒,用户实机目检)**:工作间门与形态(锁定态零购买路径/点亮后两区六路线)/购买面(几何价逐级涨、余额禁用、即时刷新)/解锁面(双通道:练满派生零收费 vs 200 直购、互不重复收)/选中面(免费切换、当前态、冒险锁死)/注入抽查(选长枪=穿刺生效、选重甲 AC=12+加值、旧路线练度保留)。 +- **遗留(在册不阻)**:①敌方 attack_type 填值(3b 裁连带)归敌人系统/战斗结算侧——本批只落结构面(矩阵查表现役已支持),06 在册;②全部数值 provisional 终值归 M4 数值批(未排期);③满级特效本期不做(六路线扩展位留空——不开参数位/不加效果类型词/存档无特效域);④工作间视觉终稿归 HUD 营地视觉批。 +- **本批零触碰面**:meta.tres 三行族、克制矩阵九格、局内货架三行(奇物属性件)、天赋树、效果引擎词表(零扩签)。 diff --git a/design/registry/entities.yaml b/design/registry/entities.yaml index 6d3beb71..ac0a3378 100644 --- a/design/registry/entities.yaml +++ b/design/registry/entities.yaml @@ -717,10 +717,11 @@ entities: kind: 营地四柜台之三(树「锻造工坊」钥匙节点 free 无前置、点亮展开;未点亮 = 锁定态但配置常驻可读——默认值与解锁状态解耦) routes: 六条独立等级路线——武器区 长剑锻造(普通/初始)/长枪锻造(穿刺/待解锁)/法杖锻造(魔法/待解锁);护甲区 轻甲锻造(基础 AC10/初始)/重甲锻造(AC12/待解锁)/魔法护甲锻造(ward AC9/待解锁);行名统一「件名+锻造」(8a 裁) unlock_channels: 并联双通道——练满前一档 OR 一次性解锁价,互不影响、不设串联门槛(4b-1 裁) - currency: 纯钥石单币(5b 裁;几何递增定价历史考古参数 = 20 钥石/150% 公比——forge_products 行族已随迭代 21 T5 退役、无现役计价字段可沿,实装投值归 M4) + currency: 纯钥石单币(5b 裁;几何递增定价 provisional = 基础 20 × 150%^已购级数〔20/30/45/67/101〕/一次性解锁价 200/甲型基础 AC 10-12-9——迭代 23 T1 沿锚表投值,终值归 M4) selection: 一次冒险 = 一个武器类型 + 一个甲型(1a 裁);营地内免费切换、冒险开始后锁死(1b/1c 裁) axis_ownership: attack_type/armor_type 默认值由锻造工坊配置常驻持有(默认值 = normal/light)、hero.tres 静态表两轴随实装删除;营地选中态写存档域、冒险开始经参数面注入(v0.6 订正——与现役「表运行期只读」纪律 meta_progression.gd:12-13/119 一致) - save_domain: forge_routes(计划 v7——三武器等级 + 三甲等级 + 当前选中标记;无特效域;旧档破坏性废弃归实装批;已预登记 存档序列化.md) + save_domain: forge_routes(已落地 v7——迭代 23 T5:{levels 六路线等级 + unlocked 直购解锁清单 + weapon/armor 当前选中标记};无特效域;_normalize 缺键补齐 = 初始持有态(sword/light 在档余四锁),v6→v7 空迁移缺键补齐模式;旧档禁迁移禁折算——契约 4) + impl: 实装落位(迭代 23 实装批)——data/schemas/forge_table.gd + data/tables/forge.tres(ForgeTable:默认两轴常驻持有 + 六路线行族,ConfigDB.forge_* 访问器)· core/meta_progression.gd 锻造写口族(forge_buy_level 几何价/forge_buy_unlock 一次性解锁价/forge_select_weapon/armor 免费选中/forge_route_unlocked 派生解锁单点/forge_shop_status 投影)· presentation/views/camp_overlay_view.gd 工作间两区六路线行板(forge_buy/unlock/select_requested 三意图信号,契约 9 UI 只发意图)· core/save_manager.gd forge_routes 域读写访问器 red_lines: 攻防所有权切分——攻击唯一入口 = 武器路线、AC 唯一入口 = 甲路线、树零攻防来源(契约 9 红线);局内无武器/护甲提升(货架三行 = 奇物属性件,现役显示名 攻击祝福/生命祝福/护甲祝福) max_effect: 满级特效本期不做、只留扩展位(6 裁——不设计清单/不定值/不实装/不开参数位/存档不设特效域) referenced_by: [元进度, 天赋树, 存档序列化, 术语表] diff --git a/game/README.md b/game/README.md index 9e727dd5..7c37ac99 100644 --- a/game/README.md +++ b/game/README.md @@ -577,3 +577,17 @@ Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/a **营地统一**:四柜台并排(树淬炼=树脊柱界面形态主界面/锻造工坊柜台钥匙门+零交互占位面板/秘宝匣柜台钥匙门+工坊六行+同调装配段+Y 升级行迁入/图鉴常开);刷新升级双入口同账(树上 ks_refresh 路由 buy_attune_refresh)。 **验证**:冒烟 886/0(基线 864/0+存量红修复——fx_reveal 三处空 source_class 补标签,a57489e9 落地件 51 行脚本错误清零)+ run_all 22 套(新增 tree_rules_v11 六键闭集校验)+ probe --click 全绿(树购买/双态切换点击路径换代);V10 行为段=引擎直构+编排授予段直注(守侧五场景/聚散同种子配对/限次生命周期)。存量红修复与帐目细节见 design/qa/iter21-实现报告-2026-10-12.md。 + +## 2026-10-12 迭代 23 锻造工坊实装批(U0——需求卡片 v1.2;设计权威 = 锻造工坊.md v0.6) + +**六路线行族新建**:新表 `forge.tres`(schema `forge_table.gd`/ForgeTable——默认两轴常驻持有 + routes 六行;ConfigDB `forge_*` 访问器:forge_route/forge_routes/forge_default_attack_type/forge_default_armor_type/forge_weapon_route_by_type/forge_armor_route_by_type)。路线 id=sword/spear/staff/light/heavy/ward(甲路线 id=甲型值本身);provisional 沿锚表:cap 5/每级增量统一 +1/基础价 20×150%^n(20/30/45/67/101)/一次性解锁价 200/甲型基础 AC 10-12-9/初始持有 sword+light/并联双通道前置 spear·staff←sword、heavy·ward←light。 + +**两根类型轴所有权移交**:`hero.tres` armor_type/attack_type 两字段删除(schema 同步删)——默认值归 forge.tres 常驻持有(normal/light);选中态写存档域 forge_routes、冒险开始经参数面注入。单位快照读取面=unit_container.spawn 新两参(空串=锻造配置默认值回退,行为逐位连续);冒烟 hero_attack 行类型键主语换代为 forge_default_attack_type()。 + +**写口族与注入链**:MetaProgression 锻造写口(契约 9 唯一)——forge_buy_level(几何价扣钥石即盘)/forge_buy_unlock(一次性解锁价,已解锁含派生=拒绝零收费)/forge_select_weapon·armor(免费切换只受理已解锁)/forge_route_unlocked(派生单点:初始持有∨直购∨练满前一档)。hero_override 公式换代(FORGE_ROUTE_BONUS 预留常量退役):atk=当前选中武器攻击加值+树 flat、armor_class=当前甲 AC 加值+树 flat、max_hp=树 flat;main.build_adventure_params +attack_type/armor_type 两键 → AdventureRunner 参数面快照(契约 8 冒险开始锁死)→ LevelRunner.set_hero_axes → enc → spawn;入战 AC 三层=甲型基础 AC(选中甲路线 base_ac)+甲路线等级加值+局内加算区。 + +**存档 v7**:SAVE_FORMAT_VERSION 6→7 + 新域 forge_routes({levels 六键/unlocked 直购清单/weapon/armor 选中},无特效域)+ _migrate_v6_v7 空迁移(缺键补齐=初始持有态)+ 访问器组;旧档缺键补齐零折算。 + +**营地工作间**:锻造工坊柜台占位面板→两区六路线行板(武器锻造区/护甲锻造区;件名+Lv+解锁态双通道文案+锻造/解锁/选中钮;余额联动禁用;锁定态零行板零购买路径——占位纪律延续);三意图信号 forge_buy/unlock/select_requested 经 camp_shop_presenter 走写口唯一路径。 + +**验证**:冒烟 938/0(基线 898/0+40 锻造断言)+ run_all 24 套(新增 forge_table_test 41 断言+forge_workbench_test 12 断言)+ probe --click/--full 全绿 + render_gate + doc 三闸/熵增对基线零新增 + MC 同种子配对零漂移。实现报告=design/qa/iter23-实现报告-2026-10-12.md。 diff --git a/roadmap.md b/roadmap.md index 6f06a6fe..d092be61 100644 --- a/roadmap.md +++ b/roadmap.md @@ -24,8 +24,8 @@ ## 当前位置(2026-10-11 刷新) - **里程碑语义(2026-10-09 立规换代,详见下方里程碑表的换代注)**:现行里程碑 = **M3「Alpha 版本的完整实现」**——用户未明令宣布完成前停留 M3,迭代只排 M3 需求;M4=Beta 版本的完整实现,一律不排期。下文里程碑表的旧编号(M-3/M-4)保留作历史序列,语义以本节为准。 -- **迭代进度**:迭代 19 五卡全部已入库(2026-10-08);**迭代 20=专长实装批 ✅ 已入库**(2026-10-11,出口闸全绿;残留人闸=实机目检五件+V20/V21/V22 人工核对半,07 在册);**迭代 21=天赋树实装批 ✅ 已入库(2026-10-12 U0 开工单会话交付,T0-T7 全绿;残留人闸=实机目检四柜台)**;**迭代 22=秘宝体系实现重构落地批已编入**(卡片 v1.2 零待裁),**开工门=U0 发令**(原「规划冻结维持」句已由 2026-10-11 编入令取代);**迭代 23=锻造工坊实装批已编入**(2026-10-12 用户令——设计面 v0.6 就绪〔17 裁点清零+完备性检查批,commit f5215c7〕,开工门=U0 发令);**迭代 24+**=M4 数值批(Alpha 收官位·未排期——用户令 2026-10-07:后续新迭代一律不先做 M4)。 -- **近期主战场**:迭代 20 残留人闸(实机目检五件+V20/V21/V22 人工核对半);**下一前沿=迭代 22 秘宝实装批、迭代 23 锻造工坊实装批,均待 U0 发令**;锻造工坊实装批已编入迭代 23(设计面 v0.6:17 裁点清零+完备性检查批〔现役基线换代/两轴所有权移交/术语登记完毕〕)。 +- **迭代进度**:迭代 19 五卡全部已入库(2026-10-08);**迭代 20=专长实装批 ✅ 已入库**(2026-10-11,出口闸全绿;残留人闸=实机目检五件+V20/V21/V22 人工核对半,07 在册);**迭代 21=天赋树实装批 ✅ 已入库(2026-10-12 U0 开工单会话交付,T0-T7 全绿;残留人闸=实机目检四柜台)**;**迭代 22=秘宝体系实现重构落地批 ✅ 已入库(2026-10-12 单会话交付,T1-T7 全绿;残留人闸=实机目检秘宝解锁-装配流程)**;**迭代 23=锻造工坊实装批 ✅ 已入库(2026-10-12 单会话交付,T0-T7 全绿;残留人闸=实机目检工作间形态与购买交互)**;**迭代 24+**=M4 数值批(Alpha 收官位·未排期——用户令 2026-10-07:后续新迭代一律不先做 M4)。 +- **近期主战场**:迭代 20/21/22/23 残留人闸(实机目检四批:专长五件+营地四柜台+秘宝解锁装配+锻造工作间,07 在册);**下一前沿=M4 数值批,待 U0 发令**(锻造工坊批 provisional 数值终值归其统一标定)。 - **实时状态**:以根目录 [active.md](active.md) 顶部「当前状态快照」节为准(`stage.txt` 已定性为旧阶段开关,仅存档)。 ## 主线路线(里程碑 × 迭代序列) @@ -79,7 +79,7 @@ | 迭代 21 | **天赋树实装批(2026-10-11 用户令编入——整卡不切分,迭代容量放宽容纳全卡)**:按 [天赋树](design/gdd/systems/跨局循环/天赋树.md) **v0.8** 设计权威文档实装——**33→12 节点**结构换代(三轮换代:攻防所有权切分 29→21〔树零攻防来源=红线,攻/AC 唯一入口=锻造工坊路线〕/一线一节点 21→15〔生命·命中淬炼各一节点×4 级、row 字段退役〕/Fork 一节点双态 15→12〔开局姿态·危险直觉·财富之道三组各一节点、购买解锁后 A/B 零币切换〕;schema=Fork 词表 4→3 组/规则键词表 2→6 键、「等级阈值前置」形态去留=R1 开放待裁点〔推荐裁撤,**不阻开工门**〕)+三组 Fork 真效果(开局姿态每关首轮窗/危险直觉与财富之道·限次计数随冒险持久按关重置)+营地统一案甲(树=营地主界面/属性升级唯一入口/工作间钥匙模型/图鉴常开/占位面板零副作用硬要求)+注入公式换代(基础值+树淬炼+锻造路线加值位预留+局内加算)+旧属性柜台退役+旧档废弃与旧逻辑删除(开发阶段无迁移,Q3 裁决)+同调位槽位判定源切树节点(旧条件删除随批清扫)+树上刷新升级接线(双入口并存写同账)。需求卡片=design/research/需求卡片-天赋树实装批-2026-10-11.md(**v1.7**——grilling 五裁+歧义扫描六裁+**2026-10-12 完备性/跨系统歧义审查十一裁**全落;零阻塞待裁点);锻造工坊(原「武器锻造」)**17 裁点已清零**(2026-10-12 逐裁落文 v0.5,06 在册),Q5 排期前置已满足、默认本批先行;T0 的「标错机制存在性」与「boss_kill_count 消费面」两项**已预查结案**(标错机制查实存在且有代价,守侧 contingency 作废) | ✅ **已入库(2026-10-12,/goal+iter U0 开工令——T0-T7 全量交付;R1 按既定默认裁撤案执行;实现报告=design/qa/iter21-实现报告-2026-10-12.md;残留人闸=实机目检营地四柜台〔07 在册〕)** | T0-T7≈7.0-8.0d(整卡单迭代——实际 2026-10-12 单会话交付) | | 迭代 22 | **秘宝体系实现重构落地批**——需求卡片=design/research/需求卡片-秘宝体系实现重构落地批-2026-10-11.md(v1.3 C1-C6 全裁);**2026-10-11 用户令编入**:新目录 12 件实装+解锁混合制(逐件分配锁定表在卡)+装配不可变(冒险中零更换)+旧数据行清理+效果引擎接入;槽位来源不在本批(树接口接缝——迭代 21 已实装树点亮数槽位源,本批直接消费)+装配档位格约束;估时 6d(区间 5-7d)——**✅ 已入库(2026-10-12,/goal+iter U0 开工单会话交付:T1 数据层 12 行换代+schema 扩签/T2 解锁服务+工坊面退役+档位格约束/T3 效果引擎接入+扩签+常时参数面+试炼镐积点口+充能周期/T4 呈现换代+呈现桥+试炼镐 HUD 入口/T5 替换子交互拆除+库存保留注记/T6 验收闸断言;冒烟 898/0+run_all 22 套+重闸全绿;实现报告=design/qa/iter22-实现报告-2026-10-12.md;残留人闸=实机目检秘宝解锁-装配正常流程〔07 ㉑〕)** | 迭代 23+ | **M4 数值批(**原 22+;迭代 21 已编入天赋树实装批、迭代 22 已编入秘宝实装批,均 2026-10-11 用户令**——**Alpha 收官位·未排期**〔2026-10-09 语义换代:M4=Beta 阶段、非 Alpha 收官位,M3 未由用户明令关闭前不排期维持〕——2026-10-05 用户令编入迭代 17 卡②;2026-10-06 用户令移出独立成批并定序「Alpha 阶段最后——内容补全先行、统一数值调整殿后」〔当日曾被编入迭代 18 为收官卡,2026-10-07 用户令撤回〕;**2026-10-07 用户令二次后移:后续新迭代(迭代 20 起)一律不先做 M4,除非用户明确要求启动 M4 批次**)**:四件数值欠账合一批——①削弱档位终值(weaken 现值 −65/duration 2/refresh 考证在册;叠层改续时语义已定案,差 duration 终值+当量锚复算)②调参批数值点评(06 条目现行登记五条——任务口径「八条」与条目五条不符,需求卡片 §2.2 已呈报差异以条目为准)③迭代 10 奖励重构联动欠账①②(钥石产出重标 boss_keystone 首杀 3/3/3·复杀 2/2/3 provisional + 通关率带重扫〔通关回血 20%+AP 件首轮 +1〕)④元进度定价剩余面(forge base 20/比 150 考证在册——06 条目文本「base 10」滞后于 2026-09-22 对齿批已呈报;Boss 两档终值)。读数义务随批=迭代 14 两件后置复算(T6 MC 四带复算+T7 技能池节奏重测,均挂 07);顺路修复 damage_probe A1 改前既有红灯(判式换代批出口报告 §四.7 在册)。**硬约束:一切 MC/verifier 跑批发起前须用户批准;调参联动禁独立调参网络(β 带 [1.2,2.5] 逐 u 检算)**。需求卡片=`design/research/需求卡片-M4数值批-2026-10-05.md`(**v1.2 2026-10-06 案C 增补+联审修订**:v1.1 三事实与第五推算件照旧;联审=P3 转正顺延至重扫读数后/检算工具 WEAKEN_PCT 入同步面/F11 两句死文本全刷/T3·T5 四带复算并账一次跑批双认账/R5 改前锚=本批新立案C 锚/估时段一勘误 4.25d;案C 增补内容照旧=§2.6 三事实〔行为换代/锚全作废/难度预判〕+T0 推算稿第五件=对拍锚按新序重推导+T6 四带复算按案C 新行为首采;T0-T8≈6.25d,天然切分点 T4 末;🔴 契约 5 条含跑批批准制;**✅ 裁点全裁(2026-10-06 用户令:P1甲=维持 2 场·magnitude [60,85] 带内当量锚复算、具体值随出口 provisional 追认/P2甲 不对称有意/P3甲 3-3-3·2-2-3 维持、转正顺延至重扫读数到账且带内(Q4 二裁)/P4甲 不立直购价);开工=U0**) | 迭代 18 全部内容卡(③④⑤⑦)出口+全部内容批行为变更落定+**用户发令(未排期)**——重锚窗口须收尽全部内容批行为变更 | T0-T8≈6.25d(超软盒预挂,段一 T0-T4≈4.25d〔v1.2 勘误,原 4.5 系加总错误〕/段二 T5-T8≈2.0d) | -| 迭代 23 | **锻造工坊实装批(2026-10-12 用户令编入——设计面 v0.6 就绪)**:按 [锻造工坊](design/gdd/systems/跨局循环/锻造工坊.md) **v0.6** 设计权威文档实装——迁移面:①新建锻造路线行族(现役 `meta.tres` forge_products 已随迭代 21 T5 退役、无改造对象;钥石定价历史考古 20/150%、投值归 M4)②两根类型轴所有权移交(默认值归锻造工坊配置常驻持有、hero.tres 静态表两轴删除、选中态写存档域+冒险开始参数面注入)③存档域 forge_routes(已预登记存档序列化.md v7 计划行)④满级特效本期不实装(只留扩展位)⑤敌方 attack_type 填值归敌人系统/战斗结算侧(非本批——敌方定义数据、与名字血量同时读写、无独立时序)⑥术语登记(v0.6 已做完:术语表+registry)⑦数值验证义务=改前/改后同种子配对读数。解锁通道=并联双通道;一次冒险=一武器类型+一甲型。开工门=U0 发令 | 已编入待 U0 发令(设计面 v0.6:17 裁点清零+完备性检查批,commit f5215c7) | 估时随需求卡片立项 | +| 迭代 23 | **锻造工坊实装批(2026-10-12 用户令编入——设计面 v0.6 就绪)**:按 [锻造工坊](design/gdd/systems/跨局循环/锻造工坊.md) **v0.6** 设计权威文档实装——迁移面:①新建锻造路线行族(现役 `meta.tres` forge_products 已随迭代 21 T5 退役、无改造对象;钥石定价历史考古 20/150%、投值归 M4)②两根类型轴所有权移交(默认值归锻造工坊配置常驻持有、hero.tres 静态表两轴删除、选中态写存档域+冒险开始参数面注入)③存档域 forge_routes(已预登记存档序列化.md v7 计划行)④满级特效本期不实装(只留扩展位)⑤敌方 attack_type 填值归敌人系统/战斗结算侧(非本批——敌方定义数据、与名字血量同时读写、无独立时序)⑥术语登记(v0.6 已做完:术语表+registry)⑦数值验证义务=改前/改后同种子配对读数。解锁通道=并联双通道;一次冒险=一武器类型+一甲型。**✅ 已全批入库(2026-10-12,/goal+iter U0 开工单会话交付——worktree g-plan2-iter23-forge 七提交;T1 行族+T2 两轴移交+T3 写口族+T5 存档 v7(序调先行)+T4 注入链+T6 工作间 UI+T7 收口;冒烟 938/0+run_all 24 套+probe click/full+render_gate+doc 三闸/熵增对基线零新增+MC 同种子配对零漂移;实现报告=design/qa/iter23-实现报告-2026-10-12.md;残留人闸=实机目检工作间形态与购买交互〔07 在册〕;代拍板追认单+GDD 反哺点待用户)** | ✅ 已入库(出口闸全绿+实机目检归用户 07 在册) | 估时随需求卡片立项(实际单会话) | | 迭代 24+ | **M4 数值批(**原 22+;迭代 21 已编入天赋树实装批、迭代 22 已编入秘宝实装批(均 2026-10-11 用户令)、迭代 23 已编入锻造工坊实装批(2026-10-12 用户令)**——**Alpha 收官位·未排期**〔2026-10-09 语义换代:M4=Beta 阶段、非 Alpha 收官位,M3 未由用户明令关闭前不排期维持〕——2026-10-05 用户令编入迭代 17 卡②;2026-10-06 用户令移出独立成批并定序「Alpha 阶段最后——内容补全先行、统一数值调整殿后」〔当日曾被编入迭代 18 为收官卡,2026-10-07 用户令撤回〕;**2026-10-07 用户令二次后移:后续新迭代(迭代 20 起)一律不先做 M4,除非用户明确要求启动 M4 批次**)**:四件数值欠账合一批——①削弱档位终值(weaken 现值 −65/duration 2/refresh 考证在册;叠层改续时语义已定案,差 duration 终值+当量锚复算)②调参批数值点评(06 条目现行登记五条——任务口径「八条」与条目五条不符,需求卡片 §2.2 已呈报差异以条目为准)③迭代 10 奖励重构联动欠账①②(钥石产出重标 boss_keystone 首杀 3/3/3·复杀 2/2/3 provisional + 通关率带重扫〔通关回血 20%+AP 件首轮 +1〕)④元进度定价剩余面(forge base 20/比 150 考证在册——06 条目文本「base 10」滞后于 2026-09-22 对齿批已呈报;Boss 两档终值)。读数义务随批=迭代 14 两件后置复算(T6 MC 四带复算+T7 技能池节奏重测,均挂 07);顺路修复 damage_probe A1 改前既有红灯(判式换代批出口报告 §四.7 在册)。**硬约束:一切 MC/verifier 跑批发起前须用户批准;调参联动禁独立调参网络(β 带 [1.2,2.5] 逐 u 检算)**。需求卡片=`design/research/需求卡片-M4数值批-2026-10-05.md`(**v1.2 2026-10-06 案C 增补+联审修订**:v1.1 三事实与第五推算件照旧;联审=P3 转正顺延至重扫读数后/检算工具 WEAKEN_PCT 入同步面/F11 两句死文本全刷/T3·T5 四带复算并账一次跑批双认账/R5 改前锚=本批新立案C 锚/估时段一勘误 4.25d;案C 增补内容照旧=§2.6 三事实〔行为换代/锚全作废/难度预判〕+T0 推算稿第五件=对拍锚按新序重推导+T6 四带复算按案C 新行为首采;T0-T8≈6.25d,天然切分点 T4 末;🔴 契约 5 条含跑批批准制;**✅ 裁点全裁(2026-10-06 用户令:P1甲=维持 2 场·magnitude [60,85] 带内当量锚复算、具体值随出口 provisional 追认/P2甲 不对称有意/P3甲 3-3-3·2-2-3 维持、转正顺延至重扫读数到账且带内(Q4 二裁)/P4甲 不立直购价);开工=U0**) | 迭代 18 全部内容卡(③④⑤⑦)出口+全部内容批行为变更落定+**用户发令(未排期)**——重锚窗口须收尽全部内容批行为变更 | T0-T8≈6.25d(超软盒预挂,段一 T0-T4≈4.25d〔v1.2 勘误,原 4.5 系加总错误〕/段二 T5-T8≈2.0d) | ## 执行纪律(2026-09-27 用户立规) From f2f7e375c54c45c500fff6882f2d6193a0c130da Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 13 Oct 2026 01:40:43 +0800 Subject: [PATCH 8/8] =?UTF-8?q?docs(=E8=BF=AD=E4=BB=A323):=20active.md=20?= =?UTF-8?q?=E6=94=B6=E5=8F=A3=E6=9D=A1=20480=E2=86=92481=20=E6=94=B9?= =?UTF-8?q?=E5=8F=B7=EF=BC=88480=20=E5=B7=B2=E8=A2=AB=E5=B9=B6=E8=A1=8C=20?= =?UTF-8?q?06=20=E6=B8=85=E7=90=86=E6=89=B9=E5=8D=A0=E7=94=A8=E2=80=94?= =?UTF-8?q?=E2=80=94=E4=B8=BB=E5=8C=BA=E6=9C=AA=E6=8F=90=E4=BA=A4=E9=81=97?= =?UTF-8?q?=E4=BA=A7=E6=92=9E=E5=8F=B7=EF=BC=8C=E9=A1=BA=E5=BB=B6=E6=B3=A8?= =?UTF-8?q?=E8=AE=B0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- active.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/active.md b/active.md index 190fba43..4b58986d 100644 --- a/active.md +++ b/active.md @@ -51,7 +51,7 @@ - **475(2026-10-12,奇物呈现修复批——僵尸呈现整改落地,呈现改法=甲逐件行)**:用户令「解决呈现问题」,按呈报推荐案甲落地(改乙/丙一句话另调)。代码两文件:`hud_presenter.gd` feat_holdings_lines 聚合行(新 29 行无 archetype 下空键兜底「流派 ×N · 档一」僵尸文案)→ 逐件行「件名 ×N」;`battle_narrator.gd` feat_card_text 摘「件名 · 流派」族名行(卡片=件名+效果摘要两行,兑付反馈行随之「件名 · 摘要」)+payload 死键 archetype 摘除;注释面五处换代(hud_presenter/hero_info_panel_view/level_map_screen)。断言面预查:smoke:982 只钉 desc 零族名断言,免改。文档同步:battle.md C 区两行(归 06 指针摘除改已整改事实)/07 HUD 条(验修复后逐件行)/报告 §五.5(同)/README 段(473 勘误句再换代为已修复)/06 奇物呈现条**收口移除**(三处违规+档位三路+呈现改法三面全清,去向=本条+git)/反哺点⑤✅销账(①-⑤全出清;反哺点条与追认单条随下次收口清理批一并移除)。FeatsEngine.tiers_for 呈现侧调用摘除后全仓零调用方(引擎库保留不动)。(worktree presentfix-20261012;**编号注记:474 已被并行改名批占用,本条顺延 475**) -- **480(2026-10-12,迭代 23 锻造工坊实装批总收口)**:T0-T7 八提交全量交付(worktree g-plan2-iter23-forge;T1 行族 df666dd/T2 两轴移交 00d98b6/T5 存档 v7 6def354〔序调先行——写口硬依赖〕/T3 写口族 a6270ba/T4 注入链 ed06292/T6 工作间 UI 2915f19/T7 收口笔)。T1 新表 forge.tres(ForgeTable 默认两轴+六路线行族,ConfigDB forge_* 六访问器)+单测 41 断言;T2 hero.tres 两字段删除+spawn 注入缝(空串=锻造配置默认值回退,行为逐位连续);T5 SAVE_FORMAT_VERSION 6→7+forge_routes 域{levels/unlocked/weapon/armor}(无特效域)+v6→v7 空迁移缺键补齐=初始持有态;T3 写口族(forge_buy_level 几何价 20/30/45/67/101/forge_buy_unlock 一次性 200/forge_select_* 免费/forge_route_unlocked 派生单点);T4 hero_override 公式换代(FORGE_ROUTE_BONUS 退役)+build_adventure_params 两键+参数面快照锁死+effective_ac 三层(甲型基础 AC+路线加值+局内)+enc→spawn 末端;T6 占位面板→两区六路线行板+三意图信号+presenter 处理器+新单测 12 断言。验证:冒烟 938/0(基线 898+40 锻造断言)+run_all 24 套+probe --click/--full 全绿残留 0+render_gate golden 0.00%+doc 三闸/熵增对基线零新增(3 个 gdd_lint 存量红同款;master 第 10 个扫描文件=并行会话未跟踪件 08-Alpha版本标准.md,禁碰);**MC 同种子配对 80 局逐位零漂移**(na=40 seed0=9000 双画像:normal 0/40↔0/40、perfect 28/40↔28/40)+方向性补充(锻造满配档 atk+5/ac+5 同种子 30/80 局结局差:perfect 28→33/40)——默认态行为连续铁证+注入面生效证据。机械面文档七件同步(roadmap/entities.yaml 回填实态/06 状态+①② v0.6 措辞订正/存档序列化 v1.6/07 ㉒+game/README 批次段);实现报告=design/qa/iter23-实现报告-2026-10-12.md(代拍板追认单九条+GDD 反哺点三条待用户);残留人闸=实机目检工作间形态与购买交互(07 ㉒)。环境注记:render_gate 需 gamedev venv(PIL)。 +- **481(2026-10-12,迭代 23 锻造工坊实装批总收口〔编号顺延——480 已被并行 06 清理批占用,主区未提交遗产撞号注记〕)**:T0-T7 八提交全量交付(worktree g-plan2-iter23-forge;T1 行族 df666dd/T2 两轴移交 00d98b6/T5 存档 v7 6def354〔序调先行——写口硬依赖〕/T3 写口族 a6270ba/T4 注入链 ed06292/T6 工作间 UI 2915f19/T7 收口笔)。T1 新表 forge.tres(ForgeTable 默认两轴+六路线行族,ConfigDB forge_* 六访问器)+单测 41 断言;T2 hero.tres 两字段删除+spawn 注入缝(空串=锻造配置默认值回退,行为逐位连续);T5 SAVE_FORMAT_VERSION 6→7+forge_routes 域{levels/unlocked/weapon/armor}(无特效域)+v6→v7 空迁移缺键补齐=初始持有态;T3 写口族(forge_buy_level 几何价 20/30/45/67/101/forge_buy_unlock 一次性 200/forge_select_* 免费/forge_route_unlocked 派生单点);T4 hero_override 公式换代(FORGE_ROUTE_BONUS 退役)+build_adventure_params 两键+参数面快照锁死+effective_ac 三层(甲型基础 AC+路线加值+局内)+enc→spawn 末端;T6 占位面板→两区六路线行板+三意图信号+presenter 处理器+新单测 12 断言。验证:冒烟 938/0(基线 898+40 锻造断言)+run_all 24 套+probe --click/--full 全绿残留 0+render_gate golden 0.00%+doc 三闸/熵增对基线零新增(3 个 gdd_lint 存量红同款;master 第 10 个扫描文件=并行会话未跟踪件 08-Alpha版本标准.md,禁碰);**MC 同种子配对 80 局逐位零漂移**(na=40 seed0=9000 双画像:normal 0/40↔0/40、perfect 28/40↔28/40)+方向性补充(锻造满配档 atk+5/ac+5 同种子 30/80 局结局差:perfect 28→33/40)——默认态行为连续铁证+注入面生效证据。机械面文档七件同步(roadmap/entities.yaml 回填实态/06 状态+①② v0.6 措辞订正/存档序列化 v1.6/07 ㉒+game/README 批次段);实现报告=design/qa/iter23-实现报告-2026-10-12.md(代拍板追认单九条+GDD 反哺点三条待用户);残留人闸=实机目检工作间形态与购买交互(07 ㉒)。环境注记:render_gate 需 gamedev venv(PIL)。 - **479(2026-10-12,迭代 23 锻造工坊实装批开工)**:/goal+iter 技能调用=U0 开工令(用户目标=总收口全绿:冒烟/单测/探针/读数对拍四闸全过+实现报告落档)。开工检查全过:卡片 v1.2 零阻塞待裁点(Q1=0 级起步+锻造第一级=首个 +1 已裁;Q2=provisional 沿锚表投值已裁);V1-V14 验收清单三要素齐(V9 静态 grep 人工核对+V13 用户目检=纪律类人工项,余自动闸)。自洽扫描两处 L2 记录继续(不触红线):①卡片 F8「meta 域 13 件」清单滞后——现行实态 15 键(迭代 22 T2 同批新增 vision_used 未及登卡),对 forge_routes 新域零影响;②06 条目①「forge_products 两行改造」系 v0.5 旧文——设计 v0.6/卡片 F1 已订正为「行族已退役、新建行族」,卡片优先,T7 机械面同步时订正 06 措辞。事实抽查全符(F1 三处退役登记在册/F3 FORGE_ROUTE_BONUS 与 hero_override 在役行号漂移至 :155/:157/F5 两轴在役/F8 v6+_default_data 15 键/F9 forge_workshop 钥匙节点+营地占位面板接缝在册/F7 敌方轴未开)。T0 实查结案:①基线复读=冒烟 898/0+run_all 22 套全绿+probe_gate --click 全绿残留 0(存量 fx_reveal 已随 iter21 修复,零存量红);②buy_forge/forge_products 断言已随 iter21 T5 全摘(余件皆注释/退役登记——smoke:4398「MetaTable 行族规模 3/3/2」断言不受影响:新行族落新表);③hero 两轴读取面全清单=运行时独一处 unit_container.gd:64(db.hero 两轴快照)+冒烟断言 smoke_all.gd:1901(_db.hero.attack_type)+battle_orchestrator.gd:1410(消费 hero_unit 快照非表直读)+README 文档面,测试层零直读;④meta.tres 现役三行族+boss_keystone/conversion/attune_refresh_upgrade,FORGE_ATTRS 常驻;⑤树钥匙+占位面板接缝在册(camp_overlay_view.gd:275-284 钥匙门);⑥设计 v0.6 两节(裁决结果清单+工作间内容清单)复核无冲突。注入链已探明:main.build_adventure_params(:501)→ adventure params → _runner.set_* 族(:934-945 模式)→ level_runner enc(:480-494)→ begin_battle(battle_orchestrator.gd:344/:396 spawn)。代拍板立项(白名单内):新表 forge.tres(ForgeTable:默认两轴+六路线行族);路线 id=sword/spear/staff/light/heavy/ward(甲路线 id=甲型值本身);存档域 forge_routes={levels 六键+unlocked 直购清单+weapon/armor 选中}(初始持有+练满前一档=派生解锁、直购=入档)。定义面已入库(b9cca33,卡片单笔);主区残留脏件全属他批遗产零碰(.zcode/active.md/锻造工坊.md v0.6 续裁行/playnote 等)。worktree g-plan2-iter23-forge 开工,T1 起。