diff --git a/design/registry/entities.yaml b/design/registry/entities.yaml index ad10a76d..1e4ee037 100644 --- a/design/registry/entities.yaml +++ b/design/registry/entities.yaml @@ -622,7 +622,7 @@ entities: row_families: forge_products {attr ∈ {max_hp, atk, armor_class} 恰三, display_name, flat_value, base_price, price_ratio_pct ∈ [100,400]} · boss_keystone {level_index 连续从 1, first_kill ≥ repeat_kill} · workshop_items {item_id ∈ registry aux_items 六件词表恰全, display_name, clear_threshold ∈ [1,99]} · conversion {attr 与 forge 词表一致恰三, per_level_shift_pct ∈ [0,100]} provisional: 数值全 provisional 归元进度设计批配读数(骨架先行先例 = GP+商店房批)——现行落表值:flat 血+3/攻+1/甲+1、base 10 ratio 150 三行同;Boss 两档 L1 5/1 · L2 8/2 · L3 12/3;工坊阈值 1/1/2/2/3/3(按总通关次数);换算率全 0 占位 impl: core/meta_progression.gd(MetaProgression 账本服务——余额/几何定价〔tier 级价格 = 迭代 floor(p × ratio/100)〕/购买/工坊解锁〔总通关次数渠道〕/settle_adventure Boss 两档入账/hero_override 注入计算;Core 层零 UI 零信号) - workshop_unlock_rule: 首版只挂总通关次数渠道(Σ level_clear_counts 达阈值——2026-09-21 实现批代拍板 2,GDD 未细分);钥石直购价随定价批后补 + workshop_unlock_rule: 首版只挂总通关次数渠道(Σ level_clear_counts 达阈值——2026-09-21 实现批代拍板 2 并经用户确认,GDD 未细分);**channel 可替换轴**(同日用户拍板:解锁条件须抽象、可替换、可扩展——渠道由 workshop_items.channel 字段声明〔缺省 total_clears,词表 MetaTable.WORKSHOP_CHANNELS 封闭〕,判定量换算单点 = MetaProgression._workshop_progress,换/加渠道 = 词表加值 + 一 match 分支,阈值比较与解锁写档零改动;未知渠道恒 0 = 永不解锁 fail-safe;候选 adventure_depth 最高通关深度 / keystone 直购双渠道已预留语义未实现);钥石直购价随定价批后补 referenced_by: [元进度, 存档序列化, 数据配置] # 2026-09-09 奖励道具经济 full 评审修订批(owner=奖励道具经济 代用户修订,NEEDS REVISION 14 blocking 同日核销): diff --git a/prototypes/dungeon-sweeper-vertical-slice/core/meta_progression.gd b/prototypes/dungeon-sweeper-vertical-slice/core/meta_progression.gd index 363581b8..d0a0f141 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/core/meta_progression.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/core/meta_progression.gd @@ -4,8 +4,9 @@ # Date: 2026-09-21(元进度实现批——设计权威 design/gdd/systems/冒险循环/元进度.md 定稿 v1.2) # 元进度账本服务(Core 层零 UI、纯被动服务——同 StatusContainer 先例:零信号零外发,读存档算账)。 # 职责三件:① 属性锻造定价与购买(几何递增——tier 级价格 = 迭代 floor(p × ratio/100),tier 0 = -# base_price,floor 单一取整对齐 round_direction 纪律);② 道具工坊解锁(首版只挂总通关次数渠道—— -# Σ level_clear_counts 达标,代拍板 2;六件局内效果归 Alpha 魔法道具重设计批,本类只有解锁状态); +# base_price,floor 单一取整对齐 round_direction 纪律);② 道具工坊解锁(渠道由 meta.tres channel +# 字段声明、判定量换算单点 _workshop_progress——2026-09-21 用户拍板可替换轴;首版 = 总通关次数, +# 钥石直购价随定价批后补最终双渠道;六件局内效果归 Alpha 魔法道具重设计批,本类只有解锁状态); # ③ 冒险收束入账(Boss 两档 = 表值 first_kill/repeat_kill,首杀标记读存档判first;调 SaveManager # apply_adventure_summary 入账并附 boss_drops 副本——复盘可归因,验收 5)。 # 注入面:hero_override() = 三档 forge flat(全零 = 白板合法态;override 不写表——表运行期只读, @@ -64,17 +65,24 @@ func buy_forge(attr: String) -> bool: # ---- 道具工坊(首版全走通关次数渠道——钥石直购价随定价批后补,最终双渠道并存)---- -# 总通关次数 = Σ level_clear_counts(工坊解锁判定量——代拍板 2) -func _total_clears() -> int: - var total := 0 - for i in db.level.rows.size(): - total += save.clear_count(i + 1) - return total +# 解锁判定量单点(可替换轴——2026-09-21 用户拍板:解锁条件须抽象、可替换、可扩展,禁与渠道实现耦合)。 +# 契约:渠道由 meta.tres workshop_items.channel 声明(缺省 total_clears,词表 MetaTable.WORKSHOP_CHANNELS +# 封闭);本方法是渠道 → 判定数值的唯一换算点——换/加渠道 = 词表加值 + 此处增一 match 分支, +# 阈值比较(refresh/workshop_status)与解锁写档零改动。候选渠道(语义已预留未实现): +# adventure_depth 最高通关深度 / keystone 钥石直购双渠道。 +func _workshop_progress(channel: String) -> int: + match channel: + "total_clears": + var total := 0 + for i in db.level.rows.size(): + total += save.clear_count(i + 1) + return total + _: + return 0 # 未知渠道恒 0 = 永不解锁(fail-safe 非致命——渠道词表校验兜底,不静默误解锁) # 工坊六行只读投影(呈现层数据源):{item_id, display_name, threshold, unlocked, progress} func workshop_status() -> Array: - var total := _total_clears() var out: Array = [] for row in db.meta_workshop_items(): var iid := String(row.get("item_id", "")) @@ -83,7 +91,7 @@ func workshop_status() -> Array: "display_name": String(row.get("display_name", "")), "threshold": int(row.get("clear_threshold", 0)), "unlocked": save.item_unlocked(iid), - "progress": total, + "progress": _workshop_progress(String(row.get("channel", "total_clears"))), }) return out @@ -91,12 +99,12 @@ func workshop_status() -> Array: # 达标未解锁 → unlock_item + 写盘;返回新解锁 item_id 列(冒险收束后与营地打开时各调一次) func refresh_workshop_unlocks() -> Array: var new_unlocks: Array = [] - var total := _total_clears() for row in db.meta_workshop_items(): var iid := String(row.get("item_id", "")) if save.item_unlocked(iid): continue - if total >= int(row.get("clear_threshold", 99)): + var progress := _workshop_progress(String(row.get("channel", "total_clears"))) + if progress >= int(row.get("clear_threshold", 99)): save.unlock_item(iid) new_unlocks.append(iid) if not new_unlocks.is_empty(): diff --git a/prototypes/dungeon-sweeper-vertical-slice/data/schemas/meta_table.gd b/prototypes/dungeon-sweeper-vertical-slice/data/schemas/meta_table.gd index af5a8401..5afd4483 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/data/schemas/meta_table.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/data/schemas/meta_table.gd @@ -23,6 +23,10 @@ extends Resource const FORGE_ATTRS := ["max_hp", "atk", "armor_class"] const WORKSHOP_ITEM_IDS := ["probe_dust", "sapper_order", "loot_compass", "nest_pendulum", "catacomb_scroll", "blast_kit"] +# 工坊解锁渠道词表(可替换轴——2026-09-21 用户拍板:解锁条件须抽象、可替换、可扩展。 +# 现行唯一合法值 total_clears = 总通关次数;扩新渠道 = 词表加值 + MetaProgression._workshop_progress +# 增一分支,阈值比较与解锁写档零改动。缺省(行无 channel 字段)= total_clears) +const WORKSHOP_CHANNELS := ["total_clears"] func validate() -> Array: @@ -87,6 +91,10 @@ func validate() -> Array: var th: int = int(row.get("clear_threshold", -1)) if th < 1 or th > 99: errs.append({"category": "domain", "message": "workshop_items %s clear_threshold 越域 [1,99]: %d" % [iid, th]}) + # channel:可替换解锁渠道(缺省 total_clears——词表封闭,扩渠道 = 词表加值 + 判定量分支) + var channel: String = row.get("channel", "total_clears") + if not WORKSHOP_CHANNELS.has(channel): + errs.append({"category": "enum", "message": "workshop_items %s channel 出域 %s: %s" % [iid, str(WORKSHOP_CHANNELS), channel]}) if not workshop_items.is_empty(): for iid_key in WORKSHOP_ITEM_IDS: if not seen_item.has(iid_key): diff --git a/prototypes/dungeon-sweeper-vertical-slice/data/tables/meta.tres b/prototypes/dungeon-sweeper-vertical-slice/data/tables/meta.tres index ccd52393..22cb847a 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/data/tables/meta.tres +++ b/prototypes/dungeon-sweeper-vertical-slice/data/tables/meta.tres @@ -39,27 +39,33 @@ boss_keystone = [{ workshop_items = [{ "item_id": "probe_dust", "display_name": "显影尘", -"clear_threshold": 1 +"clear_threshold": 1, +"channel": "total_clears" }, { "item_id": "sapper_order", "display_name": "工兵令", -"clear_threshold": 1 +"clear_threshold": 1, +"channel": "total_clears" }, { "item_id": "loot_compass", "display_name": "探宝罗盘", -"clear_threshold": 2 +"clear_threshold": 2, +"channel": "total_clears" }, { "item_id": "nest_pendulum", "display_name": "探巢摆钟", -"clear_threshold": 2 +"clear_threshold": 2, +"channel": "total_clears" }, { "item_id": "catacomb_scroll", "display_name": "地穴全图卷", -"clear_threshold": 3 +"clear_threshold": 3, +"channel": "total_clears" }, { "item_id": "blast_kit", "display_name": "爆破工兵包", -"clear_threshold": 3 +"clear_threshold": 3, +"channel": "total_clears" }] conversion = [{ "attr": "max_hp", diff --git a/prototypes/dungeon-sweeper-vertical-slice/smoke/smoke_all.gd b/prototypes/dungeon-sweeper-vertical-slice/smoke/smoke_all.gd index dd52ec98..8a331154 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/smoke/smoke_all.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/smoke/smoke_all.gd @@ -3231,6 +3231,9 @@ func _meta_settle_and_workshop() -> void: got_n += 1 if bool(st_row.get("unlocked", false)) else 0 check(got_n == expect_n and mp.refresh_workshop_unlocks().is_empty(), "工坊解锁数 = 总通关 %d 次达阈值件数(%d 件)且重复 refresh 幂等" % [total_clears, expect_n]) + # 渠道可替换轴(2026-09-21 用户拍板):判定量单点正控 + 未知渠道 fail-safe(恒 0 = 永不解锁,非致命) + check(mp._workshop_progress("total_clears") == total_clears and mp._workshop_progress("adventure_depth") == 0, + "解锁判定量单点:total_clears 正控 + 未知渠道恒 0(channel 可替换轴 fail-safe)") var sv_r = _meta_new_save(_META_SMOKE_PATHS[2]) var r_n := 0 for st_row in load("res://core/meta_progression.gd").new(_db, sv_r).workshop_status():