From a7bd4e4132507aae2410e422b5d1dfce39dfd69e Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 12 Oct 2026 15:52:04 +0800 Subject: [PATCH] =?UTF-8?q?T2(=E8=BF=AD=E4=BB=A322=E7=A7=98=E5=AE=9D?= =?UTF-8?q?=E6=89=B9):=20=E6=A0=B8=E5=BF=83=E5=B1=82=E8=A7=A3=E9=94=81?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E6=8D=A2=E4=BB=A3+=E6=A7=BD=E4=BD=8D?= =?UTF-8?q?=E6=A1=A3=E4=BD=8D=E7=BA=A6=E6=9D=9F=E2=80=94=E2=80=94=E5=B7=A5?= =?UTF-8?q?=E5=9D=8A=E6=B8=A0=E9=81=93=E9=9D=A2=E9=80=80=E5=BD=B9(=5Fworks?= =?UTF-8?q?hop=5Fprogress/workshop=5Fstatus/refresh=5Fworkshop=5Funlocks/m?= =?UTF-8?q?eta=5Fworkshop=5Fitems/WORKSHOP=5FITEM=5FIDS/workshop=5Fitems?= =?UTF-8?q?=E8=A1=8C=E6=97=8F=E5=85=A8=E5=88=A0)=E6=8D=A2=E8=A7=A3?= =?UTF-8?q?=E9=94=81=E6=B7=B7=E5=90=88=E5=88=B6(buy=5Ftreasure=5Funlock?= =?UTF-8?q?=E9=92=A5=E7=9F=B3=E8=B4=AD=E4=B9=B0=3D=E4=BD=99=E9=A2=9D?= =?UTF-8?q?=E9=97=B8+=E9=87=8D=E5=A4=8D=E8=B4=AD=E6=8B=92+=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E4=BB=B6=E8=AF=AF=E8=B4=AD=E6=8B=92/refresh=5Ftreasur?= =?UTF-8?q?e=5Funlocks=E6=9D=A1=E4=BB=B6=E5=88=B7=E6=96=B0=3DAND=E8=B0=93?= =?UTF-8?q?=E8=AF=8D+=E5=B9=82=E7=AD=89/treasure=5Fshop=5Fstatus=E6=9F=9C?= =?UTF-8?q?=E5=8F=B0=E6=8A=95=E5=BD=B1);=20=E8=AE=A1=E6=95=B0=E8=AF=BB?= =?UTF-8?q?=E6=95=B0=E5=8D=95=E7=82=B9=5Ftreasure=5Funlock=5Fprogress(adve?= =?UTF-8?q?nture=5Fcleared/boss=5Fkill=5Fcount/vision=5Fused);=20save=5Fma?= =?UTF-8?q?nager=E6=96=B0=E5=A2=9Evision=5Fused=E7=94=9F=E6=B6=AF=E8=AE=A1?= =?UTF-8?q?=E6=95=B0=E5=9F=9F(add=5Fvision=5Fused=E6=94=B6=E6=9D=9F?= =?UTF-8?q?=E5=85=A5=E8=B4=A6+=E7=BC=BA=E9=94=AE=E8=A1=A5=E9=9B=B6?= =?UTF-8?q?=E4=B8=8D=E5=8D=87=E7=89=88=E6=9C=AC);=20toggle=5Fattunement?= =?UTF-8?q?=E4=B8=89=E9=87=8D=E6=A0=A1=E9=AA=8C(=E6=9C=AA=E8=A7=A3?= =?UTF-8?q?=E9=94=81=E6=8B=92+=E6=BB=A1=E4=BD=8D=E7=A6=81=E9=80=89+?= =?UTF-8?q?=E4=BB=B6=E6=A1=A3rank=E2=89=A4=E8=A3=85=E5=85=A5=E4=BD=8D?= =?UTF-8?q?=E5=BA=8F=E6=A1=A3=E4=BD=8D=E6=A0=BC=E2=80=94=E2=80=94=E5=8D=B8?= =?UTF-8?q?=E4=B8=8B=E6=81=92=E5=8F=AF=E6=95=88=E6=9E=9C=E9=9D=A2=E5=8F=AA?= =?UTF-8?q?=E8=AF=BB=E8=A3=85=E9=85=8D=E9=9B=86);=20pause/camp=E4=B8=A4?= =?UTF-8?q?=E8=A7=86=E5=9B=BE=E7=A7=98=E5=AE=9D=E5=8C=A3=E5=91=88=E7=8E=B0?= =?UTF-8?q?=E6=8D=A2=E4=BB=A3+UiKit=E8=AE=A1=E6=95=B0=E9=94=AE=E4=B8=8E?= =?UTF-8?q?=E6=A1=A3=E4=BD=8D=E6=A0=87=E7=AD=BE=E5=8D=95=E7=82=B9;=20smoke?= =?UTF-8?q?=E5=B7=A5=E5=9D=8A=E6=AE=B5=E6=8D=A2V3/V4/V5=E6=96=AD=E8=A8=80(?= =?UTF-8?q?=E7=8B=AC=E7=AB=8B=E6=A1=A3[11]=E9=98=B2=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E6=B1=A1=E6=9F=93)+meta=E8=A1=A8=E6=AE=B5=E8=A1=8C=E6=97=8F?= =?UTF-8?q?=E8=A7=84=E6=A8=A1=E6=8D=A2=E4=BB=A33/3/2=E3=80=82=E5=86=92?= =?UTF-8?q?=E7=83=9F895/0+=E5=8D=95=E5=85=8321=E5=A5=97+=E6=8E=A2=E9=92=88?= =?UTF-8?q?=E5=85=A8=E7=BB=BF=E3=80=94=E7=A7=98=E5=AE=9D=E6=89=B9=20V3/V4/?= =?UTF-8?q?V5/V14/V19=E6=95=B0=E6=8D=AE=E6=BA=90=E3=80=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/config_db.gd | 4 +- game/core/meta_progression.gd | 131 ++++++++++++------ game/core/save_manager.gd | 12 ++ game/data/schemas/meta_table.gd | 41 ++---- game/data/schemas/treasure_item_table.gd | 3 + game/data/tables/meta.tres | 40 +----- game/presentation/camp_shop_presenter.gd | 10 +- .../presentation/curio_interact_controller.gd | 5 +- game/presentation/main.gd | 2 +- game/presentation/views/camp_overlay_view.gd | 32 +++-- game/presentation/views/pause_overlay.gd | 29 ++-- game/presentation/views/ui_kit.gd | 6 + game/smoke/smoke_all.gd | 114 ++++++++------- 13 files changed, 235 insertions(+), 194 deletions(-) diff --git a/game/core/config_db.gd b/game/core/config_db.gd index d975aec2..6267e53d 100644 --- a/game/core/config_db.gd +++ b/game/core/config_db.gd @@ -360,8 +360,8 @@ func meta_boss_keystone_row(level_index: int) -> Dictionary: return {} -func meta_workshop_items() -> Array: - return _meta_rows("workshop_items") +# meta_workshop_items 访问器已删除(迭代 22 T2——工坊行族退役,解锁混合制挂 treasure_items 行内; +# 秘宝解锁投影面=MetaProgression.treasure_shop_status) func meta_conversion_row(attr: String) -> Dictionary: diff --git a/game/core/meta_progression.gd b/game/core/meta_progression.gd index 953f51eb..b4f2b613 100644 --- a/game/core/meta_progression.gd +++ b/game/core/meta_progression.gd @@ -29,59 +29,64 @@ func keystone_balance() -> int: return save.keystone_balance() -# ---- 道具工坊(首版全走通关次数渠道——钥石直购价随定价批后补,最终双渠道并存)---- +# ---- 秘宝解锁服务(迭代 22 T2 换代——解锁混合制,契约 6;旧工坊渠道面随批删除)---- # 〔属性锻造柜台退役——迭代 21 T4/T5:buy_forge/forge_flat/forge_level/forge_price 与 meta_forge -# 表族随攻防所有权切分删除;生命唯一入口=树脊柱淬炼、攻/AC 唯一入口=锻造工坊路线(契约 9); -# 注入面 hero_override 换树淬炼 flat+锻造路线加值位(预留 0)公式。〕 - -# 解锁判定量单点(可替换轴——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_count(): # 表访问收口(2026-10-02——行集直扫退役) - total += save.clear_count(i + 1) - return total - _: - return 0 # 未知渠道恒 0 = 永不解锁(fail-safe 非致命——渠道词表校验兜底,不静默误解锁) - +# 表族随攻防所有权切分删除。〕〔道具工坊解锁渠道面退役——迭代 22 T2:旧「总通关次数达阈值」 +# 行族与 _workshop_progress/workshop_status/refresh_workshop_unlocks 删除,解锁条件单一事实源= +# treasure_items.tres 行内 unlock_keystone_cost/unlock_requires 谓词(C1 逐件分配锁定表)。〕 # 道具解锁读数访问器(组合根/呈现层解锁门控查询面——禁再穿透 meta.save 直读 save_manager) func is_item_unlocked(item_id: String) -> bool: return save.item_unlocked(item_id) -# 工坊六行只读投影(呈现层数据源):{item_id, display_name, threshold, unlocked, progress} -func workshop_status() -> Array: - var out: Array[Dictionary] = [] - for row in db.meta_workshop_items(): - var iid := String(row.get("item_id", "")) - var arow: Dictionary = db.treasure_item_row(iid) # 迭代 7 T5——失义/退役状态面(呈现「能力封存/已退役」) - out.append({ - "item_id": iid, - "display_name": String(row.get("display_name", "")), - "threshold": int(row.get("clear_threshold", 0)), - "unlocked": save.item_unlocked(iid), - "progress": _workshop_progress(String(row.get("channel", "total_clears"))), - "deferred": bool(arow.get("deferred", false)), - "retired": bool(arow.get("retired", false)), - }) - return out +# 解锁条件计数读数单点(谓词 key → 生涯计数——封闭枚举=TreasureItemTable.UNLOCK_COUNTER_KEYS; +# 词表加键 = 此处增一 match 分支,阈值比较与解锁写档零改动) +func _treasure_unlock_progress(key: String) -> int: + match key: + "adventure_cleared": + return save.adventure_cleared() + "boss_kill_count": + return save.boss_kill_count() + "vision_used": + return save.vision_used() + _: + return 0 # 未知计数键恒 0 = 永不解锁(fail-safe 非致命——schema 词表校验兜底,不静默误解锁) -# 达标未解锁 → unlock_item + 写盘;返回新解锁 item_id 列(冒险收束后与营地打开时各调一次) -func refresh_workshop_unlocks() -> Array: +# 钥石购买解锁(营地秘宝匣唯一购买写口):仅购买件(行带 unlock_keystone_cost)可购; +# 已解锁/余额不足/条件件误购 = 拒绝且零副作用(spend_keystone 内嵌余额闸,失败不扣币)。 +func buy_treasure_unlock(item_id: String) -> bool: + var arow: Dictionary = db.treasure_item_row(item_id) + var price := int(arow.get("unlock_keystone_cost", 0)) + if arow.is_empty() or price < 1 or (arow.get("unlock_requires", []) as Array).size() > 0: + return false # 非购买件(条件件/无价行)拒绝 + if save.item_unlocked(item_id): + return false # 已解锁重复购买拒绝 + if not save.spend_keystone(price): + return false # 余额不足(零副作用) + save.unlock_item(item_id) + return save.write_to_disk() + + +# 条件解锁刷新(达标未解锁 → unlock_item;返回新解锁 item_id 列——冒险收束后与营地打开时各调一次)。 +# 全部谓词达标才解锁(多谓词=AND——天眼双条件);幂等:已解锁跳过,重复刷新零重复入账。 +func refresh_treasure_unlocks() -> Array: var new_unlocks: Array[String] = [] - for row in db.meta_workshop_items(): + for row in db.treasure_attunement_rows(): var iid := String(row.get("item_id", "")) if save.item_unlocked(iid): continue - var progress := _workshop_progress(String(row.get("channel", "total_clears"))) - if progress >= int(row.get("clear_threshold", 99)): + var ureqs: Array = row.get("unlock_requires", []) as Array + if ureqs.is_empty(): + continue # 购买件不入条件刷新面 + var met := true + for req_v in ureqs: + var req: Dictionary = req_v as Dictionary + if _treasure_unlock_progress(String(req.get("key", ""))) < int(req.get("threshold", 999)): + met = false + break + if met: save.unlock_item(iid) new_unlocks.append(iid) if not new_unlocks.is_empty(): @@ -89,6 +94,35 @@ func refresh_workshop_unlocks() -> Array: return new_unlocks +# 秘宝匣柜台只读投影(呈现层数据源——12 件逐行解锁态/价格/条件进度): +# {item_id, display_name, desc, tier, unlocked, mode(keystone|condition|owned), price, reqs:[{key,threshold,progress}]} +func treasure_shop_status() -> Array: + var out: Array[Dictionary] = [] + for row in db.treasure_attunement_rows(): + var iid := String(row.get("item_id", "")) + var unlocked := save.item_unlocked(iid) + var ureqs: Array = row.get("unlock_requires", []) as Array + var req_view: Array = [] + for req_v in ureqs: + var req: Dictionary = req_v as Dictionary + req_view.append({ + "key": String(req.get("key", "")), + "threshold": int(req.get("threshold", 0)), + "progress": _treasure_unlock_progress(String(req.get("key", ""))), + }) + out.append({ + "item_id": iid, + "display_name": String(row.get("display_name", "")), + "desc": String(row.get("desc", "")), + "tier": String(row.get("tier", "")), + "unlocked": unlocked, + "mode": "owned" if unlocked else ("condition" if ureqs.size() > 0 else "keystone"), + "price": int(row.get("unlock_keystone_cost", 0)), + "reqs": req_view, + }) + return out + + # ---- Boss 两档与收束入账(结算屏文案数据源)---- # 明细 {big_reward, boss_drops, total, new_unlocks};入账与标记经 save.apply_adventure_summary # (keystone_drops 副本随摘要入 recent_summaries)。 @@ -100,7 +134,7 @@ func settle_adventure(summary: Dictionary) -> Dictionary: save.mark_first_adventure_done() var drops: Array = (summary.get("keystone_drops", []) as Array).duplicate(true) save.apply_adventure_summary(summary, drops) - var new_unlocks := refresh_workshop_unlocks() + var new_unlocks := refresh_treasure_unlocks() # 迭代 22 T2——条件解锁刷新换代(旧工坊面退役) save.write_to_disk() var total := int(summary.get("keystone_total", 0)) return { @@ -297,16 +331,24 @@ func _check_easter_egg() -> void: # ---- 同调位账本(迭代 11 第①批 T4——装配关系/同调位求值/Y 刷新升级;设计权威 proposal §2 + 需求卡片 P1 甲)---- -# 装配切换(营地装配覆盖层唯一写口):已装 → 卸下恒可;未装 → 非同调行拒绝 + 满位禁选; -# 装配关系写存档持久(冒险中途不可换装——底稿 §8.5,本口只服务营地)。 +# 装配切换(营地装配覆盖层唯一写口——冒险开始前配置;冒险中零写口=装配不可变契约 1,本口只服务营地): +# 已装 → 卸下恒可(卸下后其余件前移位序——效果面只读装配集不受格位影响);未装 → 非同调行拒绝 + +# 满位禁选 + 档位格校验(迭代 22 T2 契约 2:装入件 append 至末位 k=已装数+1,件档 rank ≤ k—— +# 格 1=普通格/格 2=优秀格/格 3=稀有格递增向下兼容;槽位来源=树同调位节点点亮数,树接口接缝现役)。 +# 装配关系写存档持久。 func toggle_attunement(item_id: String) -> bool: var equipped: Array = save.attunement_equipped() if not equipped.has(item_id): var arow: Dictionary = db.treasure_item_row(item_id) if arow.is_empty() or String(arow.get("form", "")) == "": return false # 非同调行(过渡/退役行)禁装配 + if not save.item_unlocked(item_id): + return false # 未解锁禁装配(解锁混合制契约 6——装配以解锁为前置) if equipped.size() >= Attunement.slots(db, save): return false # 满位禁选 + var rank: int = int(db.treasure_items.TIER_RANK.get(String(arow.get("tier", "")), 99)) + if rank > equipped.size() + 1: + return false # 件档高于将占格档(第 k 格档=k——普通/优秀/稀有递增) equipped.append(item_id) else: equipped.erase(item_id) @@ -323,8 +365,11 @@ func attune_camp_status() -> Dictionary: items.append({ "item_id": iid, "display_name": String(row.get("display_name", "")), + "desc": String(row.get("desc", "")), + "tier": String(row.get("tier", "")), "form": String(row.get("form", "")), "equipped": equipped.has(iid), + "unlocked": save.item_unlocked(iid), }) var lvl := save.attune_refresh_level() var next_row: Dictionary = db.meta_attune_refresh_row(lvl + 1) diff --git a/game/core/save_manager.gd b/game/core/save_manager.gd index 0a804ca6..d613aa3e 100644 --- a/game/core/save_manager.gd +++ b/game/core/save_manager.gd @@ -134,6 +134,8 @@ func _default_data() -> Dictionary[String, Variant]: "first_adventure_done": false, "adventure_total": 0, # 生涯终身计数·冒险总局数(迭代 18 卡④——开局即计含放弃与失败,收束唯一入账) "adventure_cleared": 0, # 生涯终身计数·通关局数(三关全通才计 1——判定字段 summary.cleared) + "vision_used": 0, # 生涯终身计数·真视类工具累计使用次数(迭代 22 T2——信标网络解锁行为条件; + # 受理成功唯一计数点=battle 层经冒险层聚合、收束入账;缺键补零不升版本——迭代 21 先例) }, "breakpoint": {}, # 冒险断点快照(空 = 无断点合法态——「继续上局」隐藏;迭代 8 卡①) } @@ -272,6 +274,16 @@ func adventure_cleared() -> int: return int(_data["meta"].get("adventure_cleared", 0)) +# 真视类工具累计使用次数(迭代 22 T2——信标网络解锁行为条件;写入唯一经 add_vision_used +# 冒险收束聚合入账,受理计数点=battle 层经冒险层聚合) +func vision_used() -> int: + return int(_data["meta"].get("vision_used", 0)) + + +func add_vision_used(n: int) -> void: + _data["meta"]["vision_used"] = maxi(0, vision_used() + maxi(0, n)) + + # ---- 天赋树域(迭代 12 T9——MetaProgression 树段读写面)---- func tree_nodes() -> Dictionary: diff --git a/game/data/schemas/meta_table.gd b/game/data/schemas/meta_table.gd index b805249a..097e4347 100644 --- a/game/data/schemas/meta_table.gd +++ b/game/data/schemas/meta_table.gd @@ -3,38 +3,33 @@ #(元进度 GDD 验收标准,本批 = 其实现批) # Date: 2026-09-21(元进度实现批——设计权威 design/gdd/systems/冒险循环/元进度.md 定稿 v1.2) # 数据配置 MetaTable schema(元进度行族)。 -# 行族四轴(数据契约对齐 ConfigDB.meta_* 访问器——消费面一律走访问器,禁直读行集): +# 行族三轴(数据契约对齐 ConfigDB.meta_* 访问器——消费面一律走访问器,禁直读行集): # boss_keystone = Boss 钥石两档行(level_index 连续从 1;first_kill ≥ repeat_kill——首杀全量、 # 重复杀固定小额恒定不递减,两档数值归元进度设计批配读数)。 -# workshop_items = 道具工坊行(item_id = 秘宝六件词表封闭恰全;首版解锁渠道 = 总通关次数 -# 达标 clear_threshold——2026-09-20 排期裁决;局内效果整体归 Alpha 秘宝重设计批,本表只有解锁状态面)。 # conversion = 区间平移换算率行(attr 与三属性轴词表一致恰三;per_level_shift_pct ∈ [0,100]—— # registry 键 meta_progression_conversion_table;护甲行强条目:验收读数必须按解锁后参数重测)。 # attune_refresh_upgrade = Y 刷新升级行族(迭代 11 第①批 T4——P1 甲:每冒险开局给 Y 次、冒险周期池、 # 用完即无;level 连续从 1,等级 L → refresh_charges=Y 表值,钥石直购 keystone_cost;数值全 provisional)。 # 〔forge_products 属性锻造行族已退役——迭代 21 T5 攻防所有权切分:生命唯一入口=树脊柱、攻/AC # 唯一入口=锻造工坊路线;行族、访问器与购买面随批删除,历史投值记载归 git 考古。〕 +# 〔workshop_items 道具工坊行族已退役——迭代 22 T2 秘宝实装批:解锁换代混合制(钥石购买价 +# unlock_keystone_cost + 条件谓词 unlock_requires 全挂 treasure_items.tres 行内,单一事实源), +# 旧「总通关次数达阈值」渠道行族、WORKSHOP_ITEM_IDS/WORKSHOP_CHANNELS 词表与校验段随批删除。〕 # 行值全部 provisional 归 元进度设计批配读数(用户拍板骨架先行,先例 = GP+商店房批)。 class_name MetaTable extends Resource @export var boss_keystone: Array[Dictionary] = [] # Array[Dictionary] 键 level_index -@export var workshop_items: Array[Dictionary] = [] # Array[Dictionary] 键 item_id @export var conversion: Array[Dictionary] = [] # Array[Dictionary] 键 attr @export var attune_refresh_upgrade: Array[Dictionary] = [] # Array[Dictionary] 键 level——Y 刷新升级行族(迭代 11 第①批 T4) const FORGE_ATTRS := ["max_hp", "atk", "armor_class"] # 三属性轴词表(conversion 行键域——原 forge 词表名沿用作轴名) -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: var errs: Array[Dictionary] = [] - # 行族空护栏(四族皆为 ConfigDB 消费面——缺族即致命) - for family in ["boss_keystone", "workshop_items", "conversion", "attune_refresh_upgrade"]: + # 行族空护栏(三族皆为 ConfigDB 消费面——缺族即致命) + for family in ["boss_keystone", "conversion", "attune_refresh_upgrade"]: if get(family).is_empty(): errs.append({"category": "empty_table", "message": "MetaTable 行族为空: %s" % family}) # boss_keystone:level_index 唯一 + 连续从 1 + 两档域 + first ≥ repeat(首杀全量、重复小额恒定) @@ -55,28 +50,8 @@ func validate() -> Array: if li_at != i + 1: errs.append({"category": "structural", "message": "boss_keystone level_index 不连续(须从 1 连续递增): 位 %d = %d" % [i + 1, li_at]}) break - # workshop_items:item_id 词表封闭 + 唯一 + 覆盖恰六(秘宝词表)+ 阈值域 [1,99] - var seen_item := {} - for row in workshop_items: - var iid: String = row.get("item_id", "") - if not WORKSHOP_ITEM_IDS.has(iid): - errs.append({"category": "enum", "message": "workshop_items item_id 出域秘宝六件词表: %s" % iid}) - if seen_item.has(iid): - errs.append({"category": "dup_key", "message": "workshop_items item_id 重复: %s" % iid}) - seen_item[iid] = true - if String(row.get("display_name", "")) == "": - errs.append({"category": "domain", "message": "workshop_items %s display_name 空" % iid}) - 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): - errs.append({"category": "coverage", "message": "workshop_items 缺道具键: %s(六件词表恰全)" % iid_key}) + # workshop_items 行族已退役(迭代 22 T2——解锁混合制换代,条件谓词挂 treasure_items 行内; + # 校验段随批删除——秘宝解锁条件校验单点=TreasureItemTable.validate) # conversion:attr 词表与 forge 一致 + 唯一 + 覆盖恰三 + 平移量域 [0,100] var seen_conv := {} for row in conversion: diff --git a/game/data/schemas/treasure_item_table.gd b/game/data/schemas/treasure_item_table.gd index ebeee4db..b8aefed8 100644 --- a/game/data/schemas/treasure_item_table.gd +++ b/game/data/schemas/treasure_item_table.gd @@ -66,6 +66,9 @@ const USE_PHASES := ["active_dig"] const FORMS := ["", "passive", "active_charged"] # 档位词表(迭代 22 T1——槽位档位约束数据面:槽位可装 ≤ 槽位档位的件;词表沿 feats/adventure TIERS) const TIERS := ["common", "excellent", "rare", "legendary"] +# 档位序(迭代 22 T2——装配档位校验取数源:件档 rank ≤ 装入位序(格 1=普通格/格 2=优秀格/格 3=稀有格 +# 递增,向下兼容=高档格可装低档件);rank 越大档越高) +const TIER_RANK := {"common": 1, "excellent": 2, "rare": 3, "legendary": 4} # 充能重置周期(迭代 22 T1 扩签——现役仅 adventure=每冒险;battle=每战重置〔线香〕/level=每关重置〔天眼〕) const CHARGE_RESETS := ["adventure", "battle", "level"] # magnitude 消费面(≥1 校验):grant_ap 与同调 N 值 kind;其余 kind 活行 magnitude 恒 0 diff --git a/game/data/tables/meta.tres b/game/data/tables/meta.tres index 2e118adf..dda26b7d 100644 --- a/game/data/tables/meta.tres +++ b/game/data/tables/meta.tres @@ -17,37 +17,6 @@ boss_keystone = [{ "first_kill": 3, "repeat_kill": 3 }] -workshop_items = [{ -"item_id": "probe_dust", -"display_name": "显影尘", -"clear_threshold": 1, -"channel": "total_clears" -}, { -"item_id": "sapper_order", -"display_name": "工兵令", -"clear_threshold": 1, -"channel": "total_clears" -}, { -"item_id": "loot_compass", -"display_name": "探宝罗盘", -"clear_threshold": 2, -"channel": "total_clears" -}, { -"item_id": "nest_pendulum", -"display_name": "探巢摆钟", -"clear_threshold": 2, -"channel": "total_clears" -}, { -"item_id": "catacomb_scroll", -"display_name": "地穴全图卷", -"clear_threshold": 3, -"channel": "total_clears" -}, { -"item_id": "blast_kit", -"display_name": "爆破工兵包", -"clear_threshold": 3, -"channel": "total_clears" -}] conversion = [{ "attr": "max_hp", "per_level_shift_pct": 6 @@ -73,9 +42,12 @@ attune_refresh_upgrade = [{ ; 〔forge_products 属性锻造行族已退役——迭代 21 T5 攻防所有权切分:生命唯一入口=树脊柱淬炼、 ; 攻/AC 唯一入口=锻造工坊路线(契约 9 红线),meta_forge_row 访问器与 buy_forge 购买面随批删除; ; 历史投值记载(2026-09-22 对齿批 base 20/ratio 150、2026-09-23 标定批 6/9/1)系 conversion 行 -; 与 ForgeGDD 考古用,行族本体不再在表。〕boss 两档 L1 5/1 · L2 8/2 · L3 12/3 -; (首杀全量、重复杀固定小额恒定不递减);工坊六件阈值 1/1/2/2/3/3 按总通关次数(首版只挂通关次数渠道, -; 钥石直购价随定价批后补);conversion 全 0 = 未标定占位(标定归设计批,护甲行强条目:验收读数必须按 +; 与 ForgeGDD 考古用,行族本体不再在表。〕 +; 〔workshop_items 道具工坊行族已退役——迭代 22 T2 秘宝实装批:解锁换代混合制(钥石购买价 +; unlock_keystone_cost + 条件谓词 unlock_requires 全挂 treasure_items.tres 行内单一事实源), +; 旧「总通关次数达阈值」六行(阈值 1/1/2/2/3/3)与渠道面随批删除;历史记载归 git 考古。〕 +; boss 两档 L1 5/1 · L2 8/2 · L3 12/3 +; (首杀全量、重复杀固定小额恒定不递减);conversion 全 0 = 未标定占位(标定归设计批,护甲行强条目:验收读数必须按 ; 解锁后参数重测)。 ; ; 数值设计批·关卡成长×永久成长对齿(2026-09-22——设计权威 qa-data/数值设计_关卡成长×永久成长_v1.md, diff --git a/game/presentation/camp_shop_presenter.gd b/game/presentation/camp_shop_presenter.gd index 5e86570b..a2234f22 100644 --- a/game/presentation/camp_shop_presenter.gd +++ b/game/presentation/camp_shop_presenter.gd @@ -42,12 +42,12 @@ func _init(db_ref, meta_get: Callable, save_get: Callable, camp_overlay_ref: Cam # 打开营地(主菜单入口——开合与开始层互斥仿设置面先例:开始覆盖层非模态且树序在后, -# 规范 2.3 遮挡纪律;打开时先刷工坊解锁——达标未解锁就地入账) +# 规范 2.3 遮挡纪律;打开时先刷条件解锁——达标未解锁就地入账) func open_camp() -> void: var meta = _meta_get.call() if meta == null: return - meta.refresh_workshop_unlocks() + meta.refresh_treasure_unlocks() # 迭代 22 T2——条件解锁刷新换代(旧工坊面退役) start_overlay.visible = false _open_modal.call(camp_overlay) # 模态栈单口(T3 收口批——登记层开合一律走栈) camp_overlay.open_camp(camp_data()) @@ -61,13 +61,13 @@ func on_camp_closed() -> void: _refresh_presentations.call() # 关闭后刷新顶栏(工坊解锁可能变了余额侧读数面) -# 营地快照投影(纯读——工坊六行/同调装配/树面/余额/最近冒险;拼句归 CampOverlay,账面归 -# MetaProgression。属性锻造三行投影已删——迭代 21 T4 柜台退役) +# 营地快照投影(纯读——秘宝匣 12 件/同调装配/树面/余额/最近冒险;拼句归 CampOverlay,账面归 +# MetaProgression。属性锻造三行投影已删——迭代 21 T4 柜台退役;工坊六行换代=迭代 22 T2 秘宝匣) func camp_data() -> Dictionary: var meta = _meta_get.call() return { "balance": meta.keystone_balance() if meta != null else 0, - "workshop": meta.workshop_status() if meta != null else [], + "workshop": meta.treasure_shop_status() if meta != null else [], # 键名沿 workshop(视图消费键不变——行内容=秘宝匣 12 件解锁投影) "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 [], diff --git a/game/presentation/curio_interact_controller.gd b/game/presentation/curio_interact_controller.gd index 4cf3b92a..add98aed 100644 --- a/game/presentation/curio_interact_controller.gd +++ b/game/presentation/curio_interact_controller.gd @@ -299,11 +299,12 @@ func on_continue() -> void: _fatal_screen.call("关卡推进失败——见输出日志") -# 工坊 item_id → 显示名投影(keystone_settle_text/替换子命名文案数据源——拼句归 Narrator) +# 秘宝 item_id → 显示名投影(keystone_settle_text/替换子命名文案数据源——拼句归 Narrator); +# 迭代 22 T2 数据源换代:旧工坊行族退役,改读 treasure 装配词表 12 件 func item_name_lookup() -> Dictionary: var lookup := {} var meta = _meta_get.call() if meta != null: - for row in db.meta_workshop_items(): + for row in db.treasure_attunement_rows(): lookup[String(row.get("item_id", ""))] = String(row.get("display_name", "")) return lookup diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 46593905..e2718d77 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -1018,7 +1018,7 @@ func _on_settings_closed() -> void: func _pause_data() -> Dictionary: return { "balance": meta.keystone_balance(), - "workshop": meta.workshop_status(), + "workshop": meta.treasure_shop_status(), # 迭代 22 T2——秘宝匣 12 件解锁投影(旧工坊面退役) "tree": meta.tree_camp_rows(), "fork_groups": meta.tree_fork_groups(), } diff --git a/game/presentation/views/camp_overlay_view.gd b/game/presentation/views/camp_overlay_view.gd index f7370621..49822af8 100644 --- a/game/presentation/views/camp_overlay_view.gd +++ b/game/presentation/views/camp_overlay_view.gd @@ -307,23 +307,29 @@ func refresh_camp(data: Dictionary) -> void: var row: Dictionary = ws_by_id.get(String(card.item_id), {}) if row.is_empty(): continue - var th := int(row.get("threshold", 0)) - var prog := int(row.get("progress", 0)) var unlocked := bool(row.get("unlocked", false)) - card.progress_l.text = "累计通关 %d / %d 次" % [prog, th] - # 迭代 7 T5——失义件(显影尘/地穴全图卷 Alpha 重定)与退役件(探宝罗盘 2026-09-29 拍板①乙)呈现口径; - # 状态色随卡⑭ 亮板双色墨制(深墨族替代暖白族——亮板上暖白不可读) + # 秘宝匣进度列(迭代 22 T2——解锁混合制三形态:钥石购买价/条件进度拼接/已入手; + # 旧「累计通关 N/M 次」单一渠道文案随工坊行族退役) + var prog_text := "" + var mode := String(row.get("mode", "")) + if unlocked: + prog_text = "已入手" + elif mode == "keystone": + prog_text = "钥石 %d" % int(row.get("price", 0)) + elif mode == "condition": + var parts: Array[String] = [] + for req_v in (row.get("reqs", []) as Array): + var req: Dictionary = req_v as Dictionary + var lbl := String(UiKit.TREASURE_REQ_LABELS.get(String(req.get("key", "")), "条件")) + parts.append("%s %d/%d" % [lbl, int(req.get("progress", 0)), int(req.get("threshold", 0))]) + prog_text = "、".join(parts) + card.progress_l.text = prog_text + # 状态色随卡⑭ 亮板双色墨制(深墨族替代暖白族——亮板上暖白不可读);失义/退役行随新目录退役 var st_text := "未解锁" var st_col: Color = UiKit.STONE - if bool(row.get("retired", false)): - st_text = "已退役" - st_col = UiKit.STONE - elif bool(row.get("deferred", false)) and unlocked: - st_text = "已解锁 · 能力封存中" - st_col = UiKit.GOLD_INK # 尾裁微批a 2026-10-08——GOLD_DEEP 对行板 3.00:1 越带,换深金墨 5.84:1 - elif unlocked: + if unlocked: st_text = "已解锁" - st_col = UiKit.GOLD_INK # 同上 + st_col = UiKit.GOLD_INK # 尾裁微批a 2026-10-08——GOLD_DEEP 对行板 3.00:1 越带,换深金墨 5.84:1 card.status_l.text = st_text card.status_l.add_theme_color_override("font_color", st_col) card.status_l.tooltip_text = st_text # 截断兜底(状态列 EXPAND_FILL 吸余宽——悬停看全文) diff --git a/game/presentation/views/pause_overlay.gd b/game/presentation/views/pause_overlay.gd index 9fd94137..0811d7cf 100644 --- a/game/presentation/views/pause_overlay.gd +++ b/game/presentation/views/pause_overlay.gd @@ -82,6 +82,8 @@ func open_pause(data: Dictionary) -> void: # 道具工坊六行(解锁态文案与营地柜台同口径——退役/封存/已解锁/未解锁;进度=解锁判定量读数) +# 秘宝匣 12 行只读概览(迭代 22 T2 换代——旧工坊六行退役;数据源=treasure_shop_status 投影, +# 解锁态文案与营地柜台同口径;进度列=解锁混合制三形态:钥石价/条件进度拼接/已入手) func _rebuild_workshop(data: Dictionary) -> void: for c in _workshop_holder.get_children(): c.queue_free() @@ -93,28 +95,29 @@ func _rebuild_workshop(data: Dictionary) -> void: name_l.clip_text = true name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS line.add_child(name_l) - var prog_l := UiKit.label("累计通关 %d/%d 次" % [int(row.get("progress", 0)), int(row.get("threshold", 0))],UiKit.FS_HINT, false, UiKit.INK_HINT) + var unlocked := bool(row.get("unlocked", false)) + var prog_text := "已入手" + if not unlocked: + if String(row.get("mode", "")) == "keystone": + prog_text = "钥石 %d" % int(row.get("price", 0)) + else: + var parts: Array[String] = [] + for req_v in (row.get("reqs", []) as Array): + var req: Dictionary = req_v as Dictionary + var lbl := String(UiKit.TREASURE_REQ_LABELS.get(String(req.get("key", "")), "条件")) + parts.append("%s %d/%d" % [lbl, int(req.get("progress", 0)), int(req.get("threshold", 0))]) + prog_text = "、".join(parts) + var prog_l := UiKit.label(prog_text,UiKit.FS_HINT, false, UiKit.INK_HINT) prog_l.custom_minimum_size = Vector2(150, 0) prog_l.clip_text = true prog_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS line.add_child(prog_l) - var st_l := UiKit.label(_workshop_state_text(row),UiKit.FS_HINT, true, UiKit.INK_LABEL) + var st_l := UiKit.label("已解锁" if unlocked else "未解锁",UiKit.FS_HINT, true, UiKit.INK_LABEL) st_l.size_flags_horizontal = Control.SIZE_EXPAND_FILL line.add_child(st_l) _workshop_holder.add_child(line) -# 解锁态文案(camp_overlay_view.refresh_camp 同款判据——退役 > 封存 > 已解锁 > 未解锁) -func _workshop_state_text(row: Dictionary) -> String: - if bool(row.get("retired", false)): - return "已退役" - if bool(row.get("deferred", false)) and bool(row.get("unlocked", false)): - return "已解锁 · 能力封存中" - if bool(row.get("unlocked", false)): - return "已解锁" - return "未解锁" - - # 天赋树已购概览(单条聚合文案——已购节点等级 + Fork 组激活侧;零已购=空态句) func _rebuild_tree(data: Dictionary) -> void: var owned: Array[String] = [] diff --git a/game/presentation/views/ui_kit.gd b/game/presentation/views/ui_kit.gd index 7dfc6c5b..ca9c4ea1 100644 --- a/game/presentation/views/ui_kit.gd +++ b/game/presentation/views/ui_kit.gd @@ -318,6 +318,12 @@ const FS_SUB := 18 # 副标题/小组标题(=棋盘数字下限同值) const FS_TITLE := 24 # 屏标题/横幅/价格 const FS_H1 := 34 # 主标题/演算暴击 +# ── 秘宝解锁条件计数键 → 呈现标签(迭代 22 秘宝批 T4——秘宝匣柜台条件进度文案单点;键域= +# TreasureItemTable.UNLOCK_COUNTER_KEYS 封闭枚举,键出域走「条件」兜底不炸)── +const TREASURE_REQ_LABELS := {"adventure_cleared": "通关", "boss_kill_count": "Boss击杀", "vision_used": "真视使用"} +# 秘宝档位 → 呈现名(契约 2 三档;legendary 传说空集合法不在柜) +const TREASURE_TIER_LABELS := {"common": "普通", "excellent": "优秀", "rare": "稀有", "legendary": "传说"} + # ── StyleBox 工厂 ── # margin = (左, 上, 右, 下);border_w = 0 即无边框(根底/遮罩用) # 圆角率单点(UI 设计对齐批 P5 甲 2026-10-06——容器/按钮全站唯一圆角值;数据框/数字牌/AP 点阵/准线/血条 diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index ca9f9951..6502eb5f 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -4359,10 +4359,10 @@ func _smoke_meta() -> void: print("--- 元进度·MetaTable 表结构 ---") var meta_tbl = _db.meta check(meta_tbl != null and meta_tbl.validate().is_empty(), - "MetaTable 非空 + validate 零错(四行族词表封闭/唯一/连续/域界/first≥repeat/ratio 全过)") + "MetaTable 非空 + validate 零错(三行族词表封闭/唯一/连续/域界/first≥repeat/ratio 全过)") check(meta_tbl.boss_keystone.size() == 3 - and meta_tbl.workshop_items.size() == 6 and meta_tbl.conversion.size() == 3, - "MetaTable 行族规模 3/6/3(Boss/工坊恰六/换算恰三——forge_products 行族已随迭代 21 T5 退役)") + and meta_tbl.conversion.size() == 3 and meta_tbl.attune_refresh_upgrade.size() == 2, + "MetaTable 行族规模 3/3/2(Boss/换算恰三/Y 升级两档——forge_products 迭代 21 退役、workshop_items 迭代 22 退役)") var attrs_ok := true for a in ["max_hp", "atk", "armor_class"]: attrs_ok = attrs_ok and not _db.meta_conversion_row(a).is_empty() @@ -4387,22 +4387,21 @@ func _pause_overlay_projection() -> void: _meta_smoke_cleanup() var sv = _meta_new_save(_META_SMOKE_PATHS[0]) var mp = load("res://core/meta_progression.gd").new(_db, sv) - # 已购态夹具:余额非零 + 工坊首件解锁——投影可辨非全零白板(属性锻造段已随柜台退役删除) + # 已购态夹具:余额非零 + 秘宝匣首件解锁——投影可辨非全零白板(属性锻造段已随柜台退役删除) sv.add_keystone(37) - var ws_meta: Array = _db.meta_workshop_items() - if not ws_meta.is_empty(): - sv.unlock_item(String(ws_meta[0].get("item_id", ""))) + sv.unlock_item("morning_order") sv.write_to_disk() # 投影拼装(main._pause_data 同构)+ 视图喂入 - var data := {"balance": mp.keystone_balance(), "workshop": mp.workshop_status(), + var data := {"balance": mp.keystone_balance(), "workshop": mp.treasure_shop_status(), "tree": mp.tree_camp_rows(), "fork_groups": mp.tree_fork_groups()} var po = load("res://presentation/views/pause_overlay.gd").new() po.open_pause(data) check(po.visible, "暂停层开层可见(open_pause 直开——P4 甲零动效路径)") check(String(po._balance_badge.value_label.text) == "钥石 %d" % mp.keystone_balance(), "余额徽投影 = keystone_balance() 读数(%s)" % po._balance_badge.value_label.text) - var ws: Array = mp.workshop_status() + var ws: Array = mp.treasure_shop_status() var ws_ok: bool = po._workshop_holder.get_child_count() == ws.size() + var uikit_s = load("res://presentation/views/ui_kit.gd") for i in mini(ws.size(), po._workshop_holder.get_child_count()): var row: Dictionary = ws[i] var lbls2: Array = [] @@ -4410,18 +4409,23 @@ func _pause_overlay_projection() -> void: if ch is Label: lbls2.append(ch) var unlocked: bool = bool(row.get("unlocked", false)) - var expect_st := "未解锁" - if bool(row.get("retired", false)): - expect_st = "已退役" - elif bool(row.get("deferred", false)) and unlocked: - expect_st = "已解锁 · 能力封存中" - elif unlocked: - expect_st = "已解锁" + var expect_prog := "已入手" + if not unlocked: + if String(row.get("mode", "")) == "keystone": + expect_prog = "钥石 %d" % int(row.get("price", 0)) + elif String(row.get("mode", "")) == "condition": + var parts: Array[String] = [] + for req_v2 in (row.get("reqs", []) as Array): + var req2: Dictionary = req_v2 as Dictionary + var lbl2 := String(uikit_s.TREASURE_REQ_LABELS.get(String(req2.get("key", "")), "条件")) + parts.append("%s %d/%d" % [lbl2, int(req2.get("progress", 0)), int(req2.get("threshold", 0))]) + expect_prog = "、".join(parts) + var expect_st2 := "已解锁" if unlocked else "未解锁" ws_ok = ws_ok and lbls2.size() == 3 \ and String(lbls2[0].text) == String(row.get("display_name", "")) \ - and String(lbls2[1].text) == "累计通关 %d/%d 次" % [int(row.get("progress", 0)), int(row.get("threshold", 0))] \ - and String(lbls2[2].text) == expect_st - check(ws_ok, "工坊六行投影 = workshop_status() 逐项(名称/判定量进度/解锁态——首件解锁夹具在列)") + and String(lbls2[1].text) == expect_prog \ + and String(lbls2[2].text) == expect_st2 + check(ws_ok, "秘宝匣 12 行投影 = treasure_shop_status() 逐项(名称/钥石价与条件进度/解锁态——首件解锁夹具在列)〔秘宝批 V19 呈现数据源〕") var owned := 0 for trow in (mp.tree_camp_rows() as Array): if int(trow.get("level", 0)) > 0: @@ -4507,7 +4511,7 @@ 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_career_v5.json", "user://smoke_meta_treasure_unlock.json"] func _meta_smoke_cleanup() -> void: @@ -4638,32 +4642,42 @@ func _meta_settle_and_workshop() -> void: "stopped_at": 1, "keystone_total": 0, "master_seed": 15}) check(dz["boss_drops"].is_empty() and sv.keystone_balance() == bal_before and sv.clear_count(1) == cc1_before, "败北零通关摘要:零入账零标记(钥石只增不减——验收1;败北局 clear_counts 不变)") - # ⑧ 工坊解锁:settle 内已随 refresh——总通关次数达阈值件数 == unlocked 数;重复 refresh 幂等 - var total_clears: int = sv.clear_count(1) + sv.clear_count(2) + sv.clear_count(3) - var expect_n := 0 - for row in _db.meta_workshop_items(): - if total_clears >= int(row.get("clear_threshold", 99)): - expect_n += 1 - var got_n := 0 - for st_row in mp.workshop_status(): - 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]) + # ⑧ 秘宝解锁服务(迭代 22 T2——V3 钥石购买/V4 条件解锁;注入=直调解锁写口+直置存档生涯计数, + # 不走正常资源积累流程——卡片 7.1 测试方式;旧工坊渠道段随行族退役摘除) + var sv_u = _meta_new_save(_META_SMOKE_PATHS[11]) + var mp_u = load("res://core/meta_progression.gd").new(_db, sv_u) + sv_u.add_keystone(10) + check(not mp_u.buy_treasure_unlock("sky_eye") and not sv_u.item_unlocked("sky_eye") + and not mp_u.buy_treasure_unlock("ranged_pick"), + "条件件误购拒绝(购买件专属——契约 8 分配面)〔秘宝批 V3 反例〕") + check(not mp_u.buy_treasure_unlock("twin_loader") and sv_u.keystone_balance() == 10, + "余额不足购买拒绝且零副作用(余额 10 < 价 25)〔秘宝批 V3〕") + check(mp_u.buy_treasure_unlock("morning_order") and sv_u.item_unlocked("morning_order") + and sv_u.keystone_balance() == 0, + "钥石购买解锁:余额足→解锁态落档+余额扣减正确(价 10)〔秘宝批 V3〕") + check(not mp_u.buy_treasure_unlock("morning_order"), + "已解锁重复购买拒绝〔秘宝批 V3〕") + check(mp_u.refresh_treasure_unlocks().is_empty() and not sv_u.item_unlocked("ranged_pick"), + "条件未达不解锁(远程镐=通关 1 次)〔秘宝批 V4〕") + sv_u._data["meta"]["adventure_cleared"] = 1 # 直置存档生涯计数(调试注入口径) + check((mp_u.refresh_treasure_unlocks() as Array).has("ranged_pick") + and sv_u.item_unlocked("ranged_pick") and mp_u.refresh_treasure_unlocks().is_empty(), + "远程镐=adventure_cleared≥1 达标恰解锁一次+重复刷新幂等(C6:通关=打通整局冒险)〔秘宝批 V4〕") + sv_u._data["meta"]["boss_kill_count"] = 3 + check((mp_u.refresh_treasure_unlocks() as Array).has("refresh_license"), + "刷新执照=boss_kill_count≥3(阈值读 unlock_requires 行值不硬编)〔秘宝批 V4〕") + sv_u._data["meta"]["adventure_cleared"] = 3 + check(not sv_u.item_unlocked("sky_eye"), "天眼单条件不足不解锁(AND 语义——cleared 3 达 boss 4 未达)〔秘宝批 V4〕") + sv_u._data["meta"]["boss_kill_count"] = 4 + check((mp_u.refresh_treasure_unlocks() as Array).has("sky_eye"), + "天眼双条件 AND(通关≥3 且 Boss≥4)达标解锁〔秘宝批 V4〕") + sv_u._data["meta"]["vision_used"] = 10 + check((mp_u.refresh_treasure_unlocks() as Array).has("beacon_network"), + "信标网络=真视类工具累计使用≥10(行为计数新域——use_treasure 受理成功累计)〔秘宝批 V4〕") var r_n := 0 - for st_row in load("res://core/meta_progression.gd").new(_db, sv_r).workshop_status(): + for st_row in mp_u.treasure_shop_status(): r_n += 1 if bool(st_row.get("unlocked", false)) else 0 - check(r_n == expect_n, "工坊解锁持久(reload 后一致)") - # ⑧ 干净档:通关 1 次 → 恰两件阈值 1 道具新解锁(new_unlocks 明细) - var sv_w = _meta_new_save(_META_SMOKE_PATHS[3]) - var mp_w = load("res://core/meta_progression.gd").new(_db, sv_w) - var u1: Dictionary = mp_w.settle_adventure({"result": "failed", "cleared": false, "levels_cleared": 1, - "stopped_at": 2, "keystone_total": 0, "master_seed": 16}) - check(u1["new_unlocks"].size() == 2 and sv_w.item_unlocked("probe_dust") and sv_w.item_unlocked("sapper_order") - and not sv_w.item_unlocked("loot_compass"), "通关 1 次 → 恰两件阈值 1 道具解锁(new_unlocks 明细正确)") + check(r_n == 5, "解锁态持久投影一致(reload 面同源读数;5 件=购买 1+条件 4)〔秘宝批 V4〕") # ⑩ recent_summaries:元素含 boss_drops 副本、最新在前、cap 10 var rs: Array = sv.recent_summaries() check(rs.size() >= 5 and String(rs[0].get("result", "")) != "" and rs[0].has("boss_drops") @@ -4681,9 +4695,11 @@ func _meta_settle_and_workshop() -> void: var mp_a = load("res://core/meta_progression.gd").new(_db, sv_a) check(Attunement.slots(_db, sv_a) == 0 and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 0, "白板档同调位求值 = 0(0 起步裁决)且 Y=0(未购无刷新能力——P1 甲初始 0 同构)") - check(mp_a.toggle_attunement("nest_counter") == false, - "满位禁选(Slots 0 时装配拒绝)") + check(mp_a.toggle_attunement("morning_order") == false, + "满位禁选(Slots 0 时装配拒绝;兼未解锁拒绝前置——直置解锁后下方装配)〔秘宝批 V5〕") sv_a.add_keystone(200) + sv_a.unlock_item("morning_order") + sv_a.unlock_item("twin_loader") check(mp_a.buy_attune_refresh() and sv_a.attune_refresh_level() == 1 and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 1, "Y 升级购买入账(Lv1 → 每冒险 +1 次刷新)") @@ -4694,10 +4710,12 @@ func _meta_settle_and_workshop() -> void: sv_a.set_tree_level("att_socket1", 1) check(Attunement.slots(_db, sv_a) == 1, "树上点亮同调位 1 → Slots=1(槽位收编树节点——Q2 裁甲)") + check(not mp_a.toggle_attunement("sky_eye"), + "稀有件入普通格拒绝(件档 rank 3 > 装入位序 1——契约 2 档位格校验)〔秘宝批 V5〕") check(mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("twin_loader") == false, "装配持久写入 + 满位禁选(Slots 1 装第二件拒绝)〔秘宝批 V5 换主语〕") - check(mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("twin_loader"), - "卸下恒可再装配(换装语义——装配序数组承载)〔秘宝批换主语;营地换装=冒险开始前合法面,装配不可变只锁运行中〕") + check(mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("morning_order"), + "卸下恒可再装配(自卸自装——营地换装=冒险开始前合法面,装配不可变只锁运行中)〔秘宝批换主语〕") var sv_a2 = _meta_new_save(_META_SMOKE_PATHS[7]) sv_a2.set_attunement_equipped(["morning_order"]) var mp_a2 = load("res://core/meta_progression.gd").new(_db, sv_a2)