feat(迭代23-T3): 锻造写口族——路线购买(几何价)+并联双通道解锁+选中切换(免费)+类型轴读数(MetaProgression 单点)
契约 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)
This commit is contained in:
1 parent
6def3544a0
commit
a6270bac54
2 files changed
+181
-1
No files matched your search
@@ -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)。
|
||||
|
||||
+73
-1
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user