主卡T2(拆件批wr2/wr3): CurioStop建件+CurioDeck纯池化——curio_stop.gd新件(停点三态/刷新三源账/兑付族/凭证出箱voucher_outbox/信号面自带/rebind_level_runner每关注入GP回调防调死对象/open_level_clear收编关卡完成停点段);adventure_runner停点面迁出(停点方法族留组合根薄委托——T5 C类收口;stop()件出口;信号中继法转发——T3直连前承转;_resume_pending迁stop.restore_pending含token_migration;resume已兑付分支stop.restore_resolved;_bp_parts停点键改stop片段取数);CurioDeck纯池化(bind_handlers/consume_pool_purchase反向回调退役——商店消费链ShopSession→注入Callable→stop.consume_pool_purchase兑付单点);测试挂死级改址随批(直读写已迁字段30+处→stop()件测试构造面,语义=原直写——G2b清单停点域全部);cs1/cs2/cs3现役断言承继尾缀落点(拆件批 V2/V3)。验收:bp1对拍292dumps×27键逐位零漂移(改前/after_T1/after_T2三时点同种子逐位一致)+九套件零红+csf集成绿+冒烟17项0失败〔拆件批 V2〕〔拆件批 V3〕
This commit is contained in:
1 parent
8d7b12e063
commit
4a9e4300ad
31 files changed
+189353
-709
No files matched your search
@@ -62,16 +62,16 @@ func _drive_dump_run(db, seed_val: int) -> Dictionary:
|
||||
var guard := 2000
|
||||
while String(ctx["result"]) == "" and guard > 0:
|
||||
guard -= 1
|
||||
if not (adr._node_curio_pending as Dictionary).is_empty():
|
||||
if not (adr.stop()._node_pending as Dictionary).is_empty():
|
||||
_dump(ctx, "node_curio_pending")
|
||||
var ncands: Array = (adr._node_curio_pending.get("candidates", []) as Array)
|
||||
var ncands: Array = (adr.stop()._node_pending.get("candidates", []) as Array)
|
||||
var npick: Dictionary = Autoplay._pick_token(ncands, "off")
|
||||
if not adr.apply_node_curio(String(npick.get("kind", "")), String(npick.get("tier", "")), String(npick.get("feat_id", ""))):
|
||||
ctx["result"] = "node_advance_failed"
|
||||
continue
|
||||
if not (adr._curio_pending as Dictionary).is_empty():
|
||||
if not (adr.stop()._pending as Dictionary).is_empty():
|
||||
_dump(ctx, "curio_pending")
|
||||
var cands: Array = (adr._curio_pending.get("candidates", []) as Array)
|
||||
var cands: Array = (adr.stop()._pending.get("candidates", []) as Array)
|
||||
var pick: Dictionary = Autoplay._pick_token(cands, "off")
|
||||
if not adr.pick_curio(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("feat_id", ""))):
|
||||
ctx["result"] = "advance_failed"
|
||||
|
||||
@@ -77,7 +77,7 @@ func _feats_breakpoint_roundtrip() -> void:
|
||||
_db.feats = rt
|
||||
var a_r = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(72))
|
||||
a_r.start_adventure(72) # 源 :723(受理断言留单元宿主)
|
||||
a_r._curio_pending = {"candidates": [{"kind": "relic", "tier": "rare", "feat_id": "x3"}, {"kind": "gp", "tier": "common"}], "payload": {}}
|
||||
a_r.stop()._pending = {"candidates": [{"kind": "relic", "tier": "rare", "feat_id": "x3"}, {"kind": "gp", "tier": "common"}], "payload": {}}
|
||||
a_r.pick_curio("relic", "rare", "x3") # 源 :781(兑付断言留单元宿主)
|
||||
# 断点往返(持有集+余库 feat_id 随行)
|
||||
var bp_path := "user://_smoke_feats_bp.json" # 段首清场(存档域隔离纪律——上轮残留断点先删)
|
||||
@@ -86,7 +86,7 @@ func _feats_breakpoint_roundtrip() -> void:
|
||||
var sm_r = load("res://core/save_manager.gd").new(bp_path)
|
||||
sm_r.load_from_disk()
|
||||
a_r.set_save_manager(sm_r)
|
||||
a_r._on_level_finished({"result": "victory", "hp": int(a_r.ledger.get("hero_hp", 26)), "node_index": 1, "node_id": 1, "node_type": "shop"})
|
||||
a_r._on_level_finished({"result": "victory", "hp": int(a_r.hero().hero_hp), "node_index": 1, "node_id": 1, "node_type": "shop"})
|
||||
var bp_r: Dictionary = sm_r.breakpoint_snapshot()
|
||||
Kit.check("断点附随件:relics 持有集入快照(迭代 12 T1 扩列)", (bp_r.get("relics", []) as Array).has("x3"))
|
||||
Kit.check("断点附随件:adventure 计数器账本键在案(T6/T7 扩列)", bp_r.has("adventure_counters"))
|
||||
|
||||
@@ -56,8 +56,9 @@ func _smoke_generated_map_determinism() -> void:
|
||||
var rng_c = _fresh_rng(910005)
|
||||
var counter_before: int = rng_c.counter()
|
||||
var runner2 = load("res://core/level_runner.gd").new(_db, rng_c)
|
||||
runner2.hero_hp = 20 # 冒险模式生命经主角账预置(旧 ledger 注入参数的等效构造面)
|
||||
Kit.check("冒险模式起关受理(入口停点)", runner2.start_level_external(3,
|
||||
{"hero_hp": 20, "gp": 0}, {}, Callable(self, "_smoke_rest_stub"), {}, true))
|
||||
Callable(self, "_smoke_rest_stub"), true))
|
||||
Kit.check("起关产图零计数器消费(同图不依赖取数号位)", rng_c.counter() == counter_before)
|
||||
var params3: Dictionary = _db.level_gen_params_row(3)
|
||||
var entry_count: int = int(params3.get("entry_count", -1))
|
||||
|
||||
@@ -56,6 +56,7 @@ func _suites() -> Array:
|
||||
{"path": "res://tests/unit/core/node_map_growth_test.gd", "expect": 0}, # 生成图生长递增+参数无解 fail-fast 子进程通道(卡① V8/V4)
|
||||
{"path": "res://tests/unit/core/level_table_validate_test.gd", "expect": 0}, # 消费侧加载校验四检+良构基底正负向(卡② T1/V1〔v1〕)
|
||||
{"path": "res://tests/unit/core/breakpoint_resume_test.gd", "expect": 0}, # 断点往返/续玩确定性/幂等/退出清断点
|
||||
{"path": "res://tests/unit/core/hero_ledger_test.gd", "expect": 0}, # 主角账公式逐值/三通道日记/断点片段往返与旧档回退(拆件批 V1:hr1/hr1-s/hr2)
|
||||
{"path": "res://tests/unit/core/curio_refresh_paid_test.gd", "expect": 0}, # 三选一刷新免费双源+GP 付费档(对齐商店 MC-1:价曲线/优先序/停点清零/钱包单口)
|
||||
{"path": "res://tests/unit/core/level_map_type_vis_test.gd", "expect": 0}, # 信息迷雾——类型可见性(层距 N+Boss/候选豁免+单圈+N 读表+续玩同值)
|
||||
{"path": "res://tests/unit/presentation/pacing_queue_test.gd", "expect": 0}, # AC⑤/T9——排空零分叉/零丢行/延迟双态
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://ehnthaqk7h12
|
||||
@@ -348,20 +348,20 @@ func _smoke_adventure() -> void:
|
||||
l2_price_bonus = int(pb.get("bonus", 0))
|
||||
Kit.check("进店余额 ≥ hp_up L2 首价(%d = 基价 %d + 关卡加成 %d——配置推导)" % [hp_price + l2_price_bonus, hp_price, l2_price_bonus], gp0 >= hp_price + l2_price_bonus)
|
||||
Kit.check("购买 hp_up 受理(出账 + 加算区)", r8.buy_product("hp_up"))
|
||||
var acc_shop := int(a8.run_ledger.bonus.get("max_hp", -1)) # L1 三选一后 + 商店 3(首件随种子)
|
||||
var acc_shop: int = a8.hero().bonus_total("max_hp") # L1 三选一后 + 商店 3(首件随种子)
|
||||
Kit.check("统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" % acc_shop, acc_shop >= 3)
|
||||
a8._on_level_finished({"result": "victory", "hp": int(r8.hero_hp), "node_index": r8.node_index,
|
||||
"node_id": r8.current_node_id(), "node_type": "shop"}) # 模拟 L2 关卡完成(victory 载荷直调——非末关)
|
||||
Kit.check("L2 关卡完成三选一停点在案(直调兑付前——零自动推进)", not a8._curio_pending.is_empty())
|
||||
Kit.check("L2 关卡完成三选一停点在案(直调兑付前——零自动推进)〔拆件批 V2〕", not a8.stop()._pending.is_empty())
|
||||
Kit.check("未兑付推进拒绝(选择停点零自动推进契约)", not a8.continue_to_next_level())
|
||||
var l2_cands: Array = a8._curio_pending["candidates"]
|
||||
var l2_cands: Array = a8.stop()._pending["candidates"]
|
||||
var l2_pick: Dictionary = l2_cands[0]
|
||||
Kit.check("三选一点选兑付受理(候选取首件)", a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))))
|
||||
var picked2: String = String(a8._curio_result.get("picked", ""))
|
||||
var expect_acc: int = acc_shop + (int(a8._curio_result.get("value", 0)) if picked2 == "max_hp" else 0)
|
||||
Kit.check("三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" % [int(a8.run_ledger.bonus.get("max_hp", -1)), acc_shop, picked2], int(a8.run_ledger.bonus.get("max_hp", -1)) == expect_acc)
|
||||
var picked2: String = String(a8.stop()._resolved.get("picked", ""))
|
||||
var expect_acc: int = acc_shop + (int(a8.stop()._resolved.get("value", 0)) if picked2 == "max_hp" else 0)
|
||||
Kit.check("三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" % [a8.hero().bonus_total("max_hp"), acc_shop, picked2], a8.hero().bonus_total("max_hp") == expect_acc)
|
||||
var max_eff8: int = a8.effective_max_hp() # 账本生效上限含调试浮窗 pct(a8 已 set 400)
|
||||
Kit.check("关卡完成部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" % [int(a8.ledger["hero_hp"]), max_eff8], int(a8.ledger["hero_hp"]) <= max_eff8)
|
||||
Kit.check("关卡完成部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" % [a8.hero().hero_hp, max_eff8], a8.hero().hero_hp <= max_eff8)
|
||||
Kit.check("兑付后推进受理(下一关开始)", a8.continue_to_next_level())
|
||||
# ⑨ 恢复点数学(接口③封顶公式三读数 + 同式两落点一致)
|
||||
var a9 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(99))
|
||||
@@ -391,7 +391,7 @@ func _smoke_adventure() -> void:
|
||||
var dk := String((a11._curio_deck[di] as Dictionary).get("kind", ""))
|
||||
if dk == "attack" or dk == "ap":
|
||||
a11._curio_deck.remove_at(di)
|
||||
var draw: Array = a11._draw_curio_candidates()
|
||||
var draw: Array = a11.stop().draw_candidates()
|
||||
var draw_kinds := {}
|
||||
for t in draw:
|
||||
draw_kinds[String((t as Dictionary).get("kind", ""))] = true
|
||||
@@ -401,7 +401,7 @@ func _smoke_adventure() -> void:
|
||||
# white-box 测试属停点渠道,零投放零行为)
|
||||
var treasure_seen := false
|
||||
for i in 40:
|
||||
for t in a11._draw_curio_candidates():
|
||||
for t in a11.stop().draw_candidates():
|
||||
if String((t as Dictionary).get("kind", "")) == "treasure_item":
|
||||
treasure_seen = true
|
||||
break
|
||||
@@ -411,8 +411,8 @@ func _smoke_adventure() -> void:
|
||||
a12.start_adventure(12)
|
||||
var cap := {"payload": {}}
|
||||
a12.curio_choice_presented.connect(func(p): cap["payload"] = p)
|
||||
var max_full := int(_db.hero.max_hp) + int(a12._base_override.get("max_hp", 0))
|
||||
a12.ledger["hero_hp"] = max_full
|
||||
var max_full: int = int(_db.hero.max_hp) + int(a12.hero().base_override_flat().get("max_hp", 0))
|
||||
a12.hero().hero_hp = max_full
|
||||
a12._on_level_finished({"result": "victory", "hp": max_full, "node_index": 1, "node_id": 1, "node_type": "shop"})
|
||||
Kit.check("满血关卡完成回血 0 合法(封顶口径——heal_gained=0 非负溢出)", int(cap["payload"].get("heal_gained", -1)) == 0)
|
||||
# ⑬ 奖励节点共享池(同库同池同摘牌;TP0:attack 令牌耗尽后节点域同样零出现)
|
||||
@@ -431,9 +431,9 @@ func _smoke_adventure() -> void:
|
||||
Kit.check("奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" % str(ncands13), not has_attack13)
|
||||
a13.present_node_curio(ncands13)
|
||||
a13.present_node_curio([{"kind": "gp", "tier": "common"}])
|
||||
var gp13 := int(a13.ledger.get("gp", 0))
|
||||
Kit.check("奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" % int(_db.adventure.curio_value("gp", "common")), a13.apply_node_curio("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.curio_value("gp", "common")))
|
||||
Kit.check("节点停点态兑付后清零(恰一次语义同构)", a13._node_curio_pending.is_empty())
|
||||
var gp13: int = a13.hero().gp
|
||||
Kit.check("奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" % int(_db.adventure.curio_value("gp", "common")), a13.apply_node_curio("gp", "common") and a13.hero().gp == gp13 + int(_db.adventure.curio_value("gp", "common")))
|
||||
Kit.check("节点停点态兑付后清零(恰一次语义同构)", a13.stop()._node_pending.is_empty())
|
||||
var s10: Dictionary = a10.summary
|
||||
Kit.check("放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)", String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)))
|
||||
|
||||
@@ -513,10 +513,10 @@ func _smoke_feats() -> void:
|
||||
var feat_seen: Dictionary = {}
|
||||
var node_relic_seen := false
|
||||
for i in 60:
|
||||
for t in a_r._draw_curio_candidates("level_clear"):
|
||||
for t in a_r.stop().draw_candidates("level_clear"):
|
||||
if String((t as Dictionary).get("kind", "")) == "relic":
|
||||
feat_seen[String(t.get("feat_id", ""))] = true
|
||||
for t in a_r._draw_curio_candidates("reward_node"):
|
||||
for t in a_r.stop().draw_candidates("reward_node"):
|
||||
if String((t as Dictionary).get("kind", "")) == "relic":
|
||||
node_relic_seen = true
|
||||
Kit.check("关卡完成渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)", feat_seen.size() >= 2)
|
||||
@@ -527,7 +527,7 @@ func _smoke_feats() -> void:
|
||||
var stock_before := {}
|
||||
for kk in ["attack", "gp", "heal"]:
|
||||
stock_before[kk] = a_t.pool_stock(kk)
|
||||
a_t._node_curio_pending = {"candidates": [{"kind": "feat_ticket", "tier": "excellent"}, {"kind": "attack", "tier": "common"}]}
|
||||
a_t.stop()._node_pending = {"candidates": [{"kind": "feat_ticket", "tier": "excellent"}, {"kind": "attack", "tier": "common"}]}
|
||||
Kit.check("A5 专长凭证兑付受理(节点渠道——拿到即开)", a_t.apply_node_curio("feat_ticket", "excellent"))
|
||||
var tkt_cands: Array = (a_t.node_curio_pending_state().get("candidates", []) as Array)
|
||||
var tkt_all_relic := tkt_cands.size() > 0
|
||||
@@ -541,23 +541,23 @@ func _smoke_feats() -> void:
|
||||
a_t.apply_node_curio("relic", String((tkt_cands[0] as Dictionary).get("tier", "rare")), String((tkt_cands[0] as Dictionary).get("feat_id", "")))
|
||||
Kit.check("A5 专长面兑付受理(专长凭证开出候选可正常入持有集)", (a_t.feats_held() as Array).size() == feats_n + 1)
|
||||
# A6 保底:pity 直填 5 → 下次展示必出(第 6 次必出口径);限购满 → 恒不触发
|
||||
a_t._feat_ticket_sold = 0 # A5 已兑付一枚专长凭证(sold=1=limit)——A6 段重置计数隔离验证
|
||||
a_t._feat_ticket_pity = int(a_t.db.adventure.feat_ticket_pity_step)
|
||||
a_t.stop()._deck.ticket_sold = 0 # A5 已兑付一枚专长凭证(sold=1=limit)——A6 段重置计数隔离验证
|
||||
a_t.stop()._deck.ticket_pity = int(a_t.db.adventure.feat_ticket_pity_step)
|
||||
var rg_t: RandomNumberGenerator = a_t.rng.open_stream("reward_pick")
|
||||
Kit.check("A6 保底触发(pity=5 → 第 6 次展示必出——概率无关)", a_t._feat_ticket_consider(rg_t))
|
||||
Kit.check("A6 触发后清零", a_t._feat_ticket_pity == 0)
|
||||
a_t._feat_ticket_sold = int(a_t.db.adventure.feat_ticket_limit)
|
||||
a_t._feat_ticket_pity = int(a_t.db.adventure.feat_ticket_pity_step)
|
||||
Kit.check("A6 限购满不再触发(保底冻结)", not a_t._feat_ticket_consider(rg_t))
|
||||
a_t._feat_ticket_sold = 0
|
||||
a_t._feat_ticket_pity = 0
|
||||
Kit.check("A6 保底触发(pity=5 → 第 6 次展示必出——概率无关)", a_t.stop().feat_ticket_consider(rg_t))
|
||||
Kit.check("A6 触发后清零", a_t.stop()._deck.ticket_pity == 0)
|
||||
a_t.stop()._deck.ticket_sold = int(a_t.db.adventure.feat_ticket_limit)
|
||||
a_t.stop()._deck.ticket_pity = int(a_t.db.adventure.feat_ticket_pity_step)
|
||||
Kit.check("A6 限购满不再触发(保底冻结)", not a_t.stop().feat_ticket_consider(rg_t))
|
||||
a_t.stop()._deck.ticket_sold = 0
|
||||
a_t.stop()._deck.ticket_pity = 0
|
||||
var offered := false
|
||||
for i in 200:
|
||||
if a_t._feat_ticket_consider(rg_t):
|
||||
if a_t.stop().feat_ticket_consider(rg_t):
|
||||
offered = true
|
||||
break
|
||||
Kit.check("A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)", offered)
|
||||
a_r._curio_pending = {"candidates": [{"kind": "relic", "tier": "rare", "feat_id": "x3"}, {"kind": "gp", "tier": "common"}], "payload": {}}
|
||||
a_r.stop()._pending = {"candidates": [{"kind": "relic", "tier": "rare", "feat_id": "x3"}, {"kind": "gp", "tier": "common"}], "payload": {}}
|
||||
Kit.check("relic 兑付:入持有集 + 牌库摘牌(选中踢池——池库存 %d→%d)" % [deck_relic, deck_relic - 1], a_r.pick_curio("relic", "rare", "x3") and (a_r.feats_held() as Array).has("x3") and a_r.pool_stock("relic") == deck_relic - 1)
|
||||
# 断点往返三条(基点 1420–1426:relics 快照/计数器键/余库逐枚还原)按主会话裁决②改判集成宿主——
|
||||
# 驱动代码(save_manager 装配+_on_level_finished+快照取读)随三条同去,集成批落位时重建。
|
||||
@@ -774,8 +774,8 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool:
|
||||
return false
|
||||
if a._pending_next_level:
|
||||
# 三选一停点确认(确定性选件后推进,与 run_adventure 自动策略同口径;TP0 令牌化)
|
||||
if not a._curio_pending.is_empty():
|
||||
var bcands: Array = a._curio_pending["candidates"]
|
||||
if not a.stop()._pending.is_empty():
|
||||
var bcands: Array = a.stop()._pending["candidates"]
|
||||
var bpick: Dictionary = autoplay._pick_token(bcands)
|
||||
a.pick_curio(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("feat_id", "")))
|
||||
a.continue_to_next_level()
|
||||
@@ -839,7 +839,7 @@ func _drive_to_pref(autoplay, prefer: String) -> Dictionary:
|
||||
var cands: Array = (adr.node_curio_candidates() as Dictionary).get("candidates", []) as Array
|
||||
adr.present_node_curio(cands)
|
||||
out["cands"] = cands
|
||||
out["pending"] = not (adr._node_curio_pending as Dictionary).is_empty()
|
||||
out["pending"] = not (adr.stop()._node_pending as Dictionary).is_empty()
|
||||
return out
|
||||
var ids: Array = r.next_candidate_ids()
|
||||
if ids.is_empty():
|
||||
|
||||
@@ -95,7 +95,7 @@ func _init() -> void:
|
||||
Kit.check_quiet(adr_e.start_adventure(4321, params_e), "P5 冒险启动(seed 4321)")
|
||||
adr_e._curio_taken["attack"] = 1 # 白盒预置计数(断点附随件往返的判据面——计数不可由加算区反推)
|
||||
adr_e._on_level_finished({"result": "victory", "hp": 30, "node_index": 1, "node_id": 1, "node_type": "shop"})
|
||||
var cands_e: Array = (adr_e._curio_pending.get("candidates", []) as Array).duplicate()
|
||||
var cands_e: Array = (adr_e.stop()._pending.get("candidates", []) as Array).duplicate()
|
||||
Kit.check_quiet(not cands_e.is_empty(), "P5 非末关的关卡完成三选一停点在案(直调 victory——L1 非末关)")
|
||||
var sm_f = SaveManagerScript.new(_bp_path)
|
||||
Kit.check_quiet(sm_f.load_from_disk() and sm_f.has_breakpoint(), "P5 选择停点断点在档(选择停点可中断契约)")
|
||||
@@ -114,7 +114,7 @@ func _init() -> void:
|
||||
Kit.check_quiet(not adr_f.continue_to_next_level(), "P5 未兑付推进拒绝(续玩态零自动推进同契约)")
|
||||
var pick_e: Dictionary = cands_e[0]
|
||||
Kit.check_quiet(adr_f.pick_curio(String(pick_e.get("kind", "")), String(pick_e.get("tier", "")), String(pick_e.get("feat_id", ""))), "P5 点选兑付受理")
|
||||
var picked_e: String = String(adr_f._curio_result.get("picked", ""))
|
||||
var picked_e: String = String(adr_f.stop()._resolved.get("picked", ""))
|
||||
if adr_f._curio_taken.has(picked_e):
|
||||
Kit.check_quiet(int(adr_f._curio_taken[picked_e]) == (2 if picked_e == "attack" else 1),
|
||||
"P5 兑付计数一致(%s——预置 1 + 本次兑付)" % picked_e)
|
||||
|
||||
@@ -24,24 +24,24 @@ func _init() -> void:
|
||||
Kit.check_quiet(adr.refresh_curio() == false and adr.refresh_curio_paid() == false, "无停点:免费/付费刷新皆拒绝零副作用")
|
||||
|
||||
# ---- ② 免费位优先序 X→Y(白盒造关卡完成停点——smoke_all ⑫ 同构)----
|
||||
adr._curio_pending = {"candidates": _cands(), "payload": {}}
|
||||
adr.stop()._pending = {"candidates": _cands(), "payload": {}}
|
||||
Kit.check_quiet(adr.curio_stop_pending() == true, "关卡完成停点在途=谓词 true")
|
||||
adr._attune_refresh_x = 1
|
||||
adr._attune_refresh_y = 2
|
||||
Kit.check_quiet(adr.refresh_curio() and adr._attune_refresh_x == 0 and adr._attune_refresh_y == 2, "X 优先消耗(x1y2→x0y2)")
|
||||
Kit.check_quiet(adr.refresh_curio() and adr._attune_refresh_y == 1, "X 穷转 Y 池(y2→y1)")
|
||||
adr.stop()._refresh_x = 1
|
||||
adr.stop()._refresh_y = 2
|
||||
Kit.check_quiet(adr.refresh_curio() and adr.stop()._refresh_x == 0 and adr.stop()._refresh_y == 2, "X 优先消耗(x1y2→x0y2)")
|
||||
Kit.check_quiet(adr.refresh_curio() and adr.stop()._refresh_y == 1, "X 穷转 Y 池(y2→y1)")
|
||||
|
||||
# ---- ③ GP 付费档:计数递增爬价 + 零触碰免费位 ----
|
||||
Kit.check_quiet(adr.curio_refresh_gp_cost() == base, "首刷报价=基价(免费位尚余 y1,报价独立于免费位)")
|
||||
Kit.check_quiet(adr.refresh_curio_paid() and adr._attune_refresh_y == 1, "付费刷新不触碰免费位(y1 保持)")
|
||||
Kit.check_quiet(adr.refresh_curio_paid() and adr.stop()._refresh_y == 1, "付费刷新不触碰免费位(y1 保持)")
|
||||
Kit.check_quiet(adr.curio_refresh_gp_cost() == int(ceil(float(base) * curve)), "二刷价爬升=ceil(基价×曲线)(已付费刷=1)")
|
||||
Kit.check_quiet(adr.refresh_curio_paid() and adr.curio_refresh_gp_cost() == int(ceil(float(base) * curve * curve)), "三刷价再爬升=ceil(基价×曲线²)(已付费刷=2)")
|
||||
|
||||
# ---- ④ 停点重建清零付费计数(对齐商店进店清零——不结转)----
|
||||
adr.present_node_curio(_cands())
|
||||
Kit.check_quiet(adr._curio_paid_refreshes == 0 and adr.curio_refresh_gp_cost() == base, "节点停点重建=付费计数清零回基价")
|
||||
Kit.check_quiet(adr.stop()._paid_refreshes == 0 and adr.curio_refresh_gp_cost() == base, "节点停点重建=付费计数清零回基价")
|
||||
adr.refresh_curio_paid()
|
||||
Kit.check_quiet(adr._curio_paid_refreshes == 1, "付费刷新计数在途递增(=1)")
|
||||
Kit.check_quiet(adr.stop()._paid_refreshes == 1, "付费刷新计数在途递增(=1)")
|
||||
adr.present_node_curio(_cands())
|
||||
Kit.check_quiet(adr.curio_refresh_gp_cost() == base, "再碰三选一付费计数仍清零(同 X 余量不结转口径)")
|
||||
|
||||
@@ -56,10 +56,10 @@ func _init() -> void:
|
||||
var bp_y := {"level_index": 1, "hero_hp": 10, "attune_refresh_y": 2, "graph_gen_v2": 1}
|
||||
var adr_r = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
|
||||
Kit.check_quiet(adr_r.resume_adventure(bp_y, {"hero_override": {}, "attune_refresh_y": 3}), "续玩受理(最小断点+参数面)")
|
||||
Kit.check_quiet(adr_r._attune_refresh_y == 2, "断点有键=Y 余量随断点还原(花 1 剩 2——不再整额重注入=满血复活堵死)")
|
||||
Kit.check_quiet(adr_r.stop()._refresh_y == 2, "断点有键=Y 余量随断点还原(花 1 剩 2——不再整额重注入=满血复活堵死)")
|
||||
var adr_o = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
|
||||
Kit.check_quiet(adr_o.resume_adventure({"level_index": 1, "hero_hp": 10, "graph_gen_v2": 1}, {"hero_override": {}, "attune_refresh_y": 3}), "旧断点续玩受理(快照缺键)")
|
||||
Kit.check_quiet(adr_o._attune_refresh_y == 3, "旧断点缺键=回退参数面整额注入(旧档行为零变化)")
|
||||
Kit.check_quiet(adr_o.stop()._refresh_y == 3, "旧断点缺键=回退参数面整额注入(旧档行为零变化)")
|
||||
|
||||
quit(Kit.finish("curio_refresh_paid"))
|
||||
|
||||
|
||||
@@ -27,42 +27,42 @@ func _init() -> void:
|
||||
quit(Kit.finish("feat_vouchers"))
|
||||
return
|
||||
# V2——adventure 桶新冒险置空:start_adventure 后宿主字典为空(寿命随 RunLedger,无残留)
|
||||
Kit.check_quiet(a.run_ledger.adventure_counters.is_empty(),
|
||||
"V2:新冒险 adventure 桶置空(start_adventure 后 run_ledger.adventure_counters 为空)〔无状态化 V2〕")
|
||||
Kit.check_quiet(a.adventure_counters.is_empty(),
|
||||
"V2:新冒险 adventure 桶置空(start_adventure 后宿主桶 adventure_counters 为空)〔无状态化 V2〕")
|
||||
|
||||
# V9-1 刷新券:入存量账(冒险级)——反例=写进 _attune_refresh_x(X 每停点重置会吞存量)
|
||||
a._reset_refresh_x() # X 归零基线(模拟新停点)
|
||||
Kit.check_quiet(a._apply_curio_pick("refresh", "excellent", 1), "刷新券兑付受理〔专长批 V9〕")
|
||||
Kit.check_quiet(a.refresh_stock() == 1 and a._attune_refresh_x == 0,
|
||||
a.stop()._reset_refresh_x() # X 归零基线(模拟新停点)
|
||||
Kit.check_quiet(a.stop()._apply_curio_pick("refresh", "excellent", 1), "刷新券兑付受理〔专长批 V9〕")
|
||||
Kit.check_quiet(a.refresh_stock() == 1 and a.stop()._refresh_x == 0,
|
||||
"F-1 修复:刷新券入存量账、X 账零写入(写错账反例不复发)〔专长批 V9〕")
|
||||
a._reset_refresh_x() # 再碰停点——X 重置但存量不吞
|
||||
a.stop()._reset_refresh_x() # 再碰停点——X 重置但存量不吞
|
||||
Kit.check_quiet(a.refresh_stock() == 1 and a.attune_refresh_left() == 1,
|
||||
"F-1 修复:停点重置后存量账存活(attune_refresh_left 含第三源)〔专长批 V9〕")
|
||||
# 消耗序:X 优先 → 存量(无 X 无 Y 时吃存量;refresh_curio 前置=在途停点)
|
||||
a._refresh_stock = 1
|
||||
a._attune_refresh_y = 0
|
||||
a._node_curio_pending = {"candidates": [{"kind": "attack", "tier": "common"}]}
|
||||
a.stop()._refresh_stock = 1
|
||||
a.stop()._refresh_y = 0
|
||||
a.stop()._node_pending = {"candidates": [{"kind": "attack", "tier": "common"}]}
|
||||
Kit.check_quiet(a.refresh_curio() and a.refresh_stock() == 0,
|
||||
"刷新消耗序:X/Y 空时吃存量账〔专长批 V9〕")
|
||||
a._node_curio_pending = {}
|
||||
a.stop()._node_pending = {}
|
||||
# 摘牌:选中即消尽(禁早退路径零摘牌)
|
||||
var deck_refresh_before: int = a.pool_stock("refresh")
|
||||
Kit.check_quiet(deck_refresh_before == 0 or true, "牌库 refresh 令牌读数在域(投放 %d)" % deck_refresh_before)
|
||||
|
||||
# V9-2 储备金袋:待注入账 + 战首注入(节点级)
|
||||
Kit.check_quiet(a._apply_curio_pick("rap", "rare", 3), "储备金袋兑付受理〔专长批 V9〕")
|
||||
Kit.check_quiet(a._pending_rap == 3, "储备金袋:待注入 RAP +3(节点级账)〔专长批 V9〕")
|
||||
Kit.check_quiet(a.stop()._apply_curio_pick("rap", "rare", 3), "储备金袋兑付受理〔专长批 V9〕")
|
||||
Kit.check_quiet(a.stop()._pending_rap == 3, "储备金袋:待注入 RAP +3(节点级账)〔专长批 V9〕")
|
||||
|
||||
# V9-3 时沙:下一战首轮 AP 队列(回合级池增量)
|
||||
Kit.check_quiet(a._apply_curio_pick("time_sand", "rare", 2), "时沙兑付受理〔专长批 V9〕")
|
||||
Kit.check_quiet(a._time_sand_queue == 2, "时沙:首轮 AP 队列 +2(池增量非上限)〔专长批 V9〕")
|
||||
Kit.check_quiet(a.stop()._apply_curio_pick("time_sand", "rare", 2), "时沙兑付受理〔专长批 V9〕")
|
||||
Kit.check_quiet(a.stop()._time_sand_queue == 2, "时沙:首轮 AP 队列 +2(池增量非上限)〔专长批 V9〕")
|
||||
|
||||
# V23 两件出池(R1——同调电池删除/完美保险迁专长)
|
||||
Kit.check_quiet(not db.adventure.CURIO_KINDS.has("attune_battery")
|
||||
and not db.adventure.CURIO_KINDS.has("perfect_insurance"),
|
||||
"出池词表:CURIO_KINDS 零两件(三选一池物理不可再出)〔卡② R1〕")
|
||||
Kit.check_quiet(not a._apply_curio_pick("attune_battery", "rare", 1)
|
||||
and not a._apply_curio_pick("perfect_insurance", "rare", 1),
|
||||
Kit.check_quiet(not a.stop()._apply_curio_pick("attune_battery", "rare", 1)
|
||||
and not a.stop()._apply_curio_pick("perfect_insurance", "rare", 1),
|
||||
"出池反例:两件兑付不再受理(牌库无令牌=拒绝零副作用)〔卡② R1〕")
|
||||
Kit.check_quiet(not a.has_method("choose_battery_target") and not a.has_method("battery_pending"),
|
||||
"电池符号清零:runner 无点选函数与 getter(编译级孤儿面)〔卡② R1〕")
|
||||
@@ -88,34 +88,34 @@ func _init() -> void:
|
||||
"配额锚:稀有档合计 7、投放品种={ap,rap,time_sand}、令牌池总量 102(validate 硬断言同批)〔卡② R3〕")
|
||||
Kit.check_quiet(db.adventure.validate().is_empty(), "AdventureTable.validate() 零违例(两件出池后)〔卡② R3〕")
|
||||
|
||||
# V9-6 全链:凭证令牌兑付=牌库摘牌(选中即消尽)
|
||||
# V9-6 全链:凭证令牌兑付=牌库摘牌(选中即消尽)〔拆件批 V2——cs3 凭证拿到即开现役断言承继〕
|
||||
a._curio_deck = [{"kind": "rap", "tier": "rare"}, {"kind": "attack", "tier": "common"}]
|
||||
a._curio_pending = {"candidates": a._curio_deck.duplicate()}
|
||||
a.stop()._pending = {"candidates": a._curio_deck.duplicate()}
|
||||
Kit.check_quiet(a.pick_curio("rap", "rare") and a.pool_stock("rap") == 0,
|
||||
"凭证令牌:pick_curio 摘牌(选中踢池——与属性件同语义)〔专长批 V9〕")
|
||||
|
||||
# V10-1 留任 2 件路由与值不变:gp 钱包/heal 当即回血(每次点选独立停点态)
|
||||
a._curio_deck = [{"kind": "gp", "tier": "common"}, {"kind": "heal", "tier": "excellent"}]
|
||||
a._curio_pending = {"candidates": [{"kind": "gp", "tier": "common"}]}
|
||||
a.stop()._pending = {"candidates": [{"kind": "gp", "tier": "common"}]}
|
||||
var gp_taken_before := int(a._curio_taken.get("gp", 0))
|
||||
Kit.check_quiet(a.pick_curio("gp", "common") and int(a._curio_taken.get("gp", 0)) == gp_taken_before + 1,
|
||||
"留任小GP 袋:GP 通道兑付受理(gp 计数入账——路由零变化)〔专长批 V10〕")
|
||||
var hp_before := int(a.ledger.get("hero_hp", 0))
|
||||
a._curio_pending = {"candidates": [{"kind": "heal", "tier": "excellent"}]}
|
||||
Kit.check_quiet(a.pick_curio("heal", "excellent") and int(a.ledger.get("hero_hp", 0)) == mini(hp_before + int(db.adventure.curio_value("heal", "excellent")), a.effective_max_hp()),
|
||||
var hp_before: int = a.hero().hero_hp
|
||||
a.stop()._pending = {"candidates": [{"kind": "heal", "tier": "excellent"}]}
|
||||
Kit.check_quiet(a.pick_curio("heal", "excellent") and a.hero().hero_hp == mini(hp_before + int(db.adventure.curio_value("heal", "excellent")), a.effective_max_hp()),
|
||||
"留任应急口粮:当即回血 +6 封顶生效上限〔专长批 V10〕")
|
||||
# V10-2 行动点上限归属性轴:ap kind 走加算区第四轴
|
||||
a._curio_deck = [{"kind": "ap", "tier": "rare"}]
|
||||
a._curio_pending = {"candidates": a._curio_deck.duplicate()}
|
||||
var ap_bonus_before := int(a.run_ledger.bonus.get("ap", 0))
|
||||
Kit.check_quiet(a.pick_curio("ap", "rare") and int(a.run_ledger.bonus.get("ap", 0)) == ap_bonus_before + int(db.adventure.curio_value("ap", "rare")),
|
||||
a.stop()._pending = {"candidates": a._curio_deck.duplicate()}
|
||||
var ap_bonus_before: int = a.hero().bonus_total("ap")
|
||||
Kit.check_quiet(a.pick_curio("ap", "rare") and a.hero().bonus_total("ap") == ap_bonus_before + int(db.adventure.curio_value("ap", "rare")),
|
||||
"行动点上限归属性轴:ap 兑付写加算区第四轴(bonus.ap 承载)〔专长批 V10〕")
|
||||
|
||||
# V16——断点扩容往返:新账本随断点快照/续玩回填(breakpoint_resume_test P5 同构)
|
||||
var sm = load("res://core/save_manager.gd").new("user://_feat_vouchers_bp.json")
|
||||
sm.load_from_disk()
|
||||
a.run_ledger.adventure_counters = {"pain_layers": 3, "adv_twin_digs": 5} # 四账住 RunLedger
|
||||
a._refresh_stock = 2
|
||||
a.adventure_counters = {"pain_layers": 3, "adv_twin_digs": 5} # 冒险桶宿主字典直写(测试构造面)
|
||||
a.stop()._refresh_stock = 2
|
||||
a.set_save_manager(sm)
|
||||
a._on_level_finished({"result": "victory", "hp": 10, "node_index": 1, "node_id": 1, "node_type": "shop"})
|
||||
var bp16: Dictionary = sm.breakpoint_snapshot()
|
||||
@@ -138,7 +138,7 @@ func _init() -> void:
|
||||
var b4 = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
|
||||
Kit.check_quiet(b4.resume_adventure(bp_legacy2, {}),
|
||||
"V13:旧档 adventure_counters 含改名前旧键续玩受理零崩溃(无迁移——旧键留桶成死键)〔卡② V13/H1〕")
|
||||
Kit.check_quiet(int(b2.run_ledger.adventure_counters.get("pain_layers", -1)) == 3 and b2.refresh_stock() == 2,
|
||||
Kit.check_quiet(int(b2.adventure_counters.get("pain_layers", -1)) == 3 and b2.refresh_stock() == 2,
|
||||
"V16:续玩回填逐项一致(adventure 桶随断点往返·计数恢复)〔无状态化 V3〕")
|
||||
# V25 完美保险专长验收组(R2 全项:装载/限次一次/计数纯净/两账相减/跨战清零/事件不吞)
|
||||
var fi: Dictionary = db.feats.row_by_id("perfect_insurance")
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 主角实体账单测(拆件批 T1——hr1 公式逐值/hr1-s 商店加算同值/hr2 三通道日记与断点片段)。
|
||||
# 公式判据值全部由表读值推导(禁数值字面量断言);真链段走 start_adventure 与 runner 接口③。
|
||||
# 运行:godot --headless --path game --script res://tests/unit/core/hero_ledger_test.gd(expect 0)
|
||||
extends SceneTree
|
||||
|
||||
const Kit := preload("res://tests/unit/test_kit.gd")
|
||||
const HeroLedgerScript := preload("res://core/hero_ledger.gd")
|
||||
const AdventureRunnerScript := preload("res://core/adventure_runner.gd")
|
||||
const LevelRunnerScript := preload("res://core/level_runner.gd")
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
var db = load("res://core/config_db.gd").new()
|
||||
if not db.load_tables("res://data/tables", false):
|
||||
print("[hero_ledger_test] FAIL — 数值表加载失败")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
# ---- hr1:生效公式逐值(表值+永久强化 flat 后乘调试 pct——与改前同式)----
|
||||
var hero = HeroLedgerScript.new(db, {"atk": 3, "max_hp": 5, "armor_class": 2})
|
||||
Kit.check_quiet(hero.effective_max_hp() == int(db.hero.max_hp) + 5,
|
||||
"hr1 生效上限 = 表值+永久强化 flat(pct=0 乘法恒等)")
|
||||
Kit.check_quiet(hero.effective_atk() == int(db.hero.atk) + 3,
|
||||
"hr1 生效攻击 = 表值+永久强化 flat")
|
||||
Kit.check_quiet(hero.effective_ac() == int(db.forge_armor_route_by_type(db.forge_default_armor_type()).get("base_ac", 0)) + 2,
|
||||
"hr1 生效护甲 = 甲型基础 AC+甲路线等级加值(纯 flat 无 pct)")
|
||||
hero.set_debug_override(50, 50)
|
||||
Kit.check_quiet(hero.effective_max_hp() == int(round(float(int(db.hero.max_hp) + 5) * 1.5)),
|
||||
"hr1 调试 pct=50 后乘——上限 ×1.5(叠加次序:flat 先加 pct 后乘)")
|
||||
hero.set_debug_override(0, 0)
|
||||
hero.gain_bonus("max_hp", 4, "battle_export", "max_hp")
|
||||
Kit.check_quiet(hero.effective_max_hp() == int(db.hero.max_hp) + 5 + 4,
|
||||
"hr1 加算区 flat 与永久强化 flat 同列基数(三段叠加)")
|
||||
|
||||
# hr1 回血封顶公式逐值(算术宿主=HeroLedger.rest_heal 单点——改前 LevelRunner.rest_heal 同式)
|
||||
Kit.check_quiet(HeroLedgerScript.rest_heal(30, 25, 26) == mini(int(floor(26.0 * 30.0 / 100.0)), 26 - 25),
|
||||
"hr1 回血 = floor(上限×比例÷100) 后封顶缺口(比例部分 7 > 缺口 1 → 1)")
|
||||
Kit.check_quiet(HeroLedgerScript.rest_heal(30, 10, 26) == int(floor(26.0 * 30.0 / 100.0)),
|
||||
"hr1 回血比例部分未触封顶时按比例整值")
|
||||
|
||||
# ---- hr1 真链段:start_adventure 置满+接口③治疗封顶回报(runner._rest_handler → 主角账公式)----
|
||||
var rng = load("res://core/rng_service.gd").new()
|
||||
rng.set_master_seed(1234)
|
||||
var adr = AdventureRunnerScript.new(db, rng)
|
||||
Kit.check_quiet(adr.start_adventure(1234, {}), "hr1 真链冒险启动(seed 1234)")
|
||||
var runner = adr.runner()
|
||||
Kit.check_quiet(runner.hero_hp == adr.hero().effective_max_hp(),
|
||||
"hr1 冒险开始生命账本置满生效上限(含 pct 口径——与战斗内同源)")
|
||||
var healed: int = adr.apply_rest(30, runner.hero_hp, runner.effective_max_hp()) # 接口③真链(关卡恢复点兑付的同一 handler)
|
||||
Kit.check_quiet(healed == HeroLedgerScript.rest_heal(30, runner.hero_hp - healed, runner.effective_max_hp()),
|
||||
"hr1 接口③真链回报 = 主角账回血公式(恢复点兑付同链)")
|
||||
|
||||
# ---- hr1-s:商店属性商品加算走主角账单写原语(购买后 bonus_total 与旧径同值)----
|
||||
runner._gain_wallet(1000) # 钱包注入(测试直调写口——测试构造惯例)
|
||||
var products: Array = runner.shop_products()
|
||||
var bought := false
|
||||
for p in products:
|
||||
if int(p.get("price", 1 << 30)) <= 1000 and String(p.get("attr", "")) != "":
|
||||
var attr := String(p.get("attr", ""))
|
||||
var before: int = runner.hero().bonus_total(attr)
|
||||
bought = runner.buy_product(String(p.get("product_id", "")))
|
||||
if bought:
|
||||
Kit.check_quiet(runner.hero().bonus_total(attr) == before + int(p.get("flat_value", 0)),
|
||||
"hr1-s 商店购买后 bonus_total 增量 = 商品 flat(加算走 gain_bonus 单写原语——同值承载)")
|
||||
Kit.check_quiet(runner.hero().purchase_tier(String(p.get("product_id", ""))) == 1,
|
||||
"hr1-s 购买档数记主角账(递增定价输入单点)")
|
||||
break
|
||||
Kit.check_quiet(bought, "hr1-s 真链购买受理(GP 充足路径)")
|
||||
|
||||
# ---- hr2:三通道日记(明细来源指回+汇总同步维护)----
|
||||
var h2 = HeroLedgerScript.new(db, {})
|
||||
h2.gain_bonus("atk", 5, "curio_pick", "attack")
|
||||
h2.gain_bonus("atk", 2, "shop_product", "p_atk_1")
|
||||
h2.gain_bonus("armor_class", 1, "battle_export", "armor_class")
|
||||
Kit.check_quiet(h2.bonus_total("atk") == 7 and h2.bonus_total("armor_class") == 1,
|
||||
"hr2 三通道汇总累计(三选一件+商店+战末同落加算区)")
|
||||
var log_arr: Array = h2._gain_log
|
||||
Kit.check_quiet(log_arr.size() == 3, "hr2 获得记录列表三通道各一条")
|
||||
Kit.check_quiet(String((log_arr[0] as Dictionary).get("source_class", "")) == "curio_pick"
|
||||
and String((log_arr[1] as Dictionary).get("source_class", "")) == "shop_product"
|
||||
and String((log_arr[2] as Dictionary).get("source_class", "")) == "battle_export",
|
||||
"hr2 记录来源指回正确(curio_pick/shop_product/battle_export 词表)")
|
||||
var details: Array = h2._bonus_detail
|
||||
Kit.check_quiet(details.size() == 3 and int((details[0] as Dictionary).get("gain_id", 0)) == int((log_arr[0] as Dictionary).get("id", -1)),
|
||||
"hr2 明细带来源指回(gain_id ↔ 记录 id)")
|
||||
Kit.check_quiet(h2.keystone_gained() == 0 and h2.vision_used() == 0,
|
||||
"hr2 钥石/真视零通道时零账")
|
||||
|
||||
# hr2 钥石/真视/购买记账
|
||||
h2.record_keystone_drop({"level": 1, "first": true, "amount": 3})
|
||||
Kit.check_quiet(h2.keystone_gained() == 3 and (h2.keystone_drop_log()[0] as Dictionary).get("amount", 0) == 3,
|
||||
"hr2 钥石逐杀明细+累计单写")
|
||||
h2.record_vision_use()
|
||||
Kit.check_quiet(h2.vision_used() == 1, "hr2 真视使用恰计一次")
|
||||
|
||||
# hr2 断点片段往返(日记在档=正主)
|
||||
var frag: Dictionary = h2.breakpoint_fragment()
|
||||
var h3 = HeroLedgerScript.new(db, {})
|
||||
h3.restore_fragment(frag, {})
|
||||
Kit.check_quiet(h3.bonus_total("atk") == 7 and h3.bonus_total("armor_class") == 1,
|
||||
"hr2 片段往返:日记在档由明细推导汇总(逐轴相等)")
|
||||
Kit.check_quiet(h3.hero_hp == h2.hero_hp and h3.gp == h2.gp and h3.keystone_gained() == 3 and h3.vision_used() == 1,
|
||||
"hr2 片段往返:账面键逐值(生命/钱包/钥石/真视)")
|
||||
Kit.check_quiet((h3._gain_log as Array).size() == 3 and (h3._bonus_detail as Array).size() == 3,
|
||||
"hr2 片段往返:日记全量还原")
|
||||
|
||||
# hr2 旧档回退(无日记键→汇总直载跳过比对)
|
||||
var legacy: Dictionary = frag.duplicate(true)
|
||||
legacy.erase("gain_journal")
|
||||
var h4 = HeroLedgerScript.new(db, {})
|
||||
h4.restore_fragment(legacy, {})
|
||||
Kit.check_quiet(h4.bonus_total("atk") == 7 and h4.bonus_total("armor_class") == 1 and (h4._gain_log as Array).is_empty(),
|
||||
"hr2 旧档缺日记→汇总键直载(跳过比对——旧档行为零变化)")
|
||||
|
||||
# hr2 日记在档推导比对不符→拒绝载入(fail-fast——汇总不回填)
|
||||
var tampered: Dictionary = frag.duplicate(true)
|
||||
(tampered["bonus"] as Dictionary)["atk"] = 99
|
||||
var h5 = HeroLedgerScript.new(db, {})
|
||||
h5.restore_fragment(tampered, {})
|
||||
Kit.check_quiet(h5.bonus_total("atk") == 0,
|
||||
"hr2 汇总与日记推导不符→拒载(汇总不回填——日记为正主)")
|
||||
|
||||
quit(Kit.finish("hero_ledger_test", "PASS — 主角账公式/三通道日记/断点片段往返与旧档回退 全绿"))
|
||||
@@ -203,9 +203,9 @@ func _test_resume_rebuild(db) -> void:
|
||||
t.info_visible_layers = 2
|
||||
t.rows = [{"level_index": 1, "display_name": "夹具关", "rest_heal_pct": 30}]
|
||||
db.level = t
|
||||
var ledger := {"hero_hp": 10, "gp": 0}
|
||||
var resumed = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new())
|
||||
var ok: bool = resumed.start_level_external(1, ledger, {}, Callable(), {}, false,
|
||||
resumed.hero_hp = 10 # 冒险模式生命经主角账预置(旧 ledger 注入参数的等效构造面)
|
||||
var ok: bool = resumed.start_level_external(1, Callable(), false,
|
||||
{"node_id": 2, "visited_ids": [0, 1, 2]})
|
||||
Kit.check_quiet(ok, "续玩用例:resume_state 重建停点成功")
|
||||
if not ok:
|
||||
|
||||
@@ -721,8 +721,7 @@ func _smoke_level_graph() -> void:
|
||||
# ⑬ 真图类型可见性(信息迷雾 v2.1):L2 入口停点纯核心面读数——候选 ⊆ 可见集 /
|
||||
# 终点恒可见 / 入口 BFS 层距 > N 的非豁免节点入雾。N 取数据表(代码零硬编码)。
|
||||
var vis_runner = load("res://core/level_runner.gd").new(_db, _fresh_rng(ACTIVE_GRAPH_SEED))
|
||||
var vis_ledger := {"hero_hp": 10, "gp": 0}
|
||||
var vis_ok: bool = vis_runner.start_level_external(2, vis_ledger, {}, Callable(), {}, true)
|
||||
var vis_ok: bool = vis_runner.start_level_external(2, Callable(), true)
|
||||
Kit.check("可见性冒烟:L2 入口停点就位(defer_entry——无当前节点,锚层 0)", vis_ok and vis_runner.awaiting_choice() and vis_runner.current_node_id() == -1)
|
||||
var vis_ids: Array = vis_runner.type_visible_ids()
|
||||
var vis_cand_in := true
|
||||
@@ -1034,7 +1033,7 @@ func _smoke_shop_room() -> void:
|
||||
Kit.check("购买 atk_up 受理(现价 6)", runner.buy_product("atk_up"))
|
||||
Kit.check("钱包扣现价 6(%d → %d)" % [gp0, runner.gp], runner.gp == gp0 - 6)
|
||||
# 加算区住 RunLedger
|
||||
Kit.check("统一冒险加算区 atk +1(bonus={atk,max_hp,armor_class} 账本)", int(runner.run_ledger.bonus.get("atk", -1)) == 1 and int(runner.run_ledger.bonus.get("max_hp", -1)) == 0 and int(runner.run_ledger.bonus.get("armor_class", -1)) == 0)
|
||||
Kit.check("统一冒险加算区 atk +1(加算区住主角账)", runner.hero().bonus_total("atk") == 1 and runner.hero().bonus_total("max_hp") == 0 and runner.hero().bonus_total("armor_class") == 0)
|
||||
by_id = _shop_products_by_id(runner.shop_products())
|
||||
Kit.check("atk_up 二购现价 = 4+2+2=8(步长 2 × 已购 1 档,现价现算)", int(by_id["atk_up"]["price"]) == 8 and int(by_id["atk_up"]["purchased"]) == 1)
|
||||
# 行进段百分比归零(购买/生效断言按加算区裸值口径——调试浮窗百分比后乘;hp 快照钳到新口径上限防 spawn 越界,测试通道)
|
||||
@@ -1319,10 +1318,10 @@ func _smoke_shop_pool() -> void:
|
||||
var val_map := {"attack": "atk", "max_hp": "max_hp", "armor": "armor_class", "ap": "ap"}
|
||||
var gp0: int = int(r8.gp)
|
||||
var taken0: int = int(a8._curio_taken.get(kind, 0))
|
||||
var val0: int = int(a8.run_ledger.bonus.get(String(val_map[kind]), -999))
|
||||
var val0: int = a8.hero().bonus_total(String(val_map[kind]))
|
||||
Kit.check("购买受理(pool 口——%s|%s)" % [kind, tier], r8.buy_pool_item(kind))
|
||||
Kit.check("钱包扣现价(%d → %d)" % [gp0, int(r8.gp)], int(r8.gp) == gp0 - int(buy_c.get("price", 0)))
|
||||
Kit.check("加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.adventure.curio_value(kind, tier))], int(a8.run_ledger.bonus.get(String(val_map[kind]), -999)) == val0 + int(_db.adventure.curio_value(kind, tier)))
|
||||
Kit.check("加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.adventure.curio_value(kind, tier))], a8.hero().bonus_total(String(val_map[kind])) == val0 + int(_db.adventure.curio_value(kind, tier)))
|
||||
Kit.check("共享池账本 -1(跨渠道单点——taken %d → %d)" % [taken0, int(a8._curio_taken.get(kind, 0))], int(a8._curio_taken.get(kind, 0)) == taken0 + 1)
|
||||
# A 链:同visit 第二购现价 = ceil(base×b^(l2−1)×A^1)(货架已全重抽——新卡新价同公式)
|
||||
# A 链:白盒抬 GP 保证第二购场景确定构造(直改测试基建——非生产行为)
|
||||
@@ -1359,12 +1358,12 @@ func _smoke_shop_pool() -> void:
|
||||
break
|
||||
Kit.check("库存尽剔牌库(attack 令牌耗尽——池消耗即上限跨渠道共库 reward→shop 向)", not attack_seen)
|
||||
var stock_before: int = a8.pool_stock("attack")
|
||||
Kit.check("池空消费拒绝(handler 零副作用——买光即枯竭硬闸)", not a8._consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before)
|
||||
Kit.check("池空消费拒绝(handler 零副作用——买光即枯竭硬闸)", not a8.stop().consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before)
|
||||
# ⑥ 跨渠道共库 shop→reward 向:回填一枚 attack 令牌 → 消费口受理 → 奖励牌库同步摘牌
|
||||
a8._curio_deck.append({"kind": "attack", "tier": "common"})
|
||||
Kit.check("商店消费口受理(_apply_curio_pick 同口——真单点库)", a8._consume_pool_purchase("attack", "common"))
|
||||
Kit.check("商店消费口受理(停点件兑付单点同口——真单点库)〔拆件批 V3〕", a8.stop().consume_pool_purchase("attack", "common"))
|
||||
var reward_attack_seen := false
|
||||
for t6 in a8._draw_curio_candidates():
|
||||
for t6 in a8.stop().draw_candidates():
|
||||
if String((t6 as Dictionary).get("kind", "")) == "attack":
|
||||
reward_attack_seen = true
|
||||
break
|
||||
@@ -1405,8 +1404,8 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool:
|
||||
return false
|
||||
if a._pending_next_level:
|
||||
# 三选一停点确认(确定性选件后推进,与 run_adventure 自动策略同口径;TP0 令牌化)
|
||||
if not a._curio_pending.is_empty():
|
||||
var bcands: Array = a._curio_pending["candidates"]
|
||||
if not a.stop()._pending.is_empty():
|
||||
var bcands: Array = a.stop()._pending["candidates"]
|
||||
var bpick: Dictionary = autoplay._pick_token(bcands)
|
||||
a.pick_curio(String(bpick.get("kind", "")), String(bpick.get("tier", "")), String(bpick.get("feat_id", "")))
|
||||
a.continue_to_next_level()
|
||||
|
||||
@@ -290,17 +290,17 @@ func _meta_settle_and_workshop() -> void:
|
||||
Kit.check("充能账本按装备 active_charged 件重置(新目录表值:线香 battle 级 1 次/天眼 level 级 1 次——周期重置点归 V8 逐形态面)", # 充能账住 RunLedger
|
||||
int(a12.run_ledger.attune_charges.get("incense_stick", -1)) == 1 and int(a12.run_ledger.attune_charges.get("sky_eye", -1)) == 1)
|
||||
a12._attunement = []
|
||||
a12._attune_refresh_x = 1
|
||||
a12._attune_refresh_y = 2
|
||||
a12._curio_pending = {"candidates": [{"kind": "gp", "tier": "common"}, {"kind": "attack", "tier": "common"}, {"kind": "max_hp", "tier": "common"}], "payload": {}}
|
||||
a12.stop()._refresh_x = 1
|
||||
a12.stop()._refresh_y = 2
|
||||
a12.stop()._pending = {"candidates": [{"kind": "gp", "tier": "common"}, {"kind": "attack", "tier": "common"}, {"kind": "max_hp", "tier": "common"}], "payload": {}}
|
||||
Kit.check("刷新双计数器——X 优先消耗(x1y2 → x0y2)",
|
||||
a12.refresh_curio() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2)
|
||||
a12.refresh_curio() and a12.stop()._refresh_x == 0 and a12.stop()._refresh_y == 2)
|
||||
Kit.check("刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)",
|
||||
a12.refresh_curio() and a12.refresh_curio() and a12._attune_refresh_y == 0
|
||||
a12.refresh_curio() and a12.refresh_curio() and a12.stop()._refresh_y == 0
|
||||
and a12.refresh_curio() == false)
|
||||
a12._reset_refresh_x()
|
||||
a12.stop()._reset_refresh_x()
|
||||
Kit.check("刷新 X 每碰三选一重置(不结转;新目录无 bonus_refresh 行源——X 机制保留恒 0 空转,存量加仓走 _refresh_stock=刷新练习 V8)〔秘宝批换代〕",
|
||||
a12._attune_refresh_x == 0 and a12.attune_refresh_left() == 0)
|
||||
a12.stop()._refresh_x == 0 and a12.attune_refresh_left() == 0)
|
||||
# ⑬ 主菜单开局传参面(开新/续玩两入口同调三键随开局下传防回归:真实入口曾
|
||||
# 三键恒空(缺陷①),冒烟夹具直呼 runner 参数面拦不住——此处直呼 main.build_adventure_params
|
||||
# 单测统一构造面(static 免实例化整菜单场景;真实两入口共用本构造),取值与营地投影同源)
|
||||
@@ -365,7 +365,7 @@ func _meta_injection() -> void:
|
||||
int(r.effective_atk()) == int(_db.hero.atk) + int(ov.get("atk", 0)))
|
||||
Kit.check("首战 hero_max_hp 与开局生命账本 = 表值 + 注入档",
|
||||
int(r.effective_max_hp()) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0))
|
||||
and int(adr.ledger["hero_hp"]) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0)))
|
||||
and int(adr.hero().hero_hp) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0)))
|
||||
# 白板:同种子下 空参数 = 零强化参数 = 纯表值(逐位一致——注入通道默认值漏改在此转红)
|
||||
var adr_w = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(920251))
|
||||
Kit.check("白板冒险启动(无 params 实参——B1 空参数路径)", adr_w.start_adventure(920251))
|
||||
@@ -511,10 +511,10 @@ func _smoke_forge_axes() -> void:
|
||||
Kit.check("入战 AC 三层:甲型基础 12(重甲)+路线加值 1+局内 0〔锻造工坊批 V7〕",
|
||||
int(r5.effective_ac()) == 12 + 1)
|
||||
Kit.check("参数面快照入 runner(attack_type=normal/armor_type=heavy)〔锻造工坊批 V6〕",
|
||||
adr5.hero_attack_type == "normal" and adr5.hero_armor_type == "heavy")
|
||||
adr5.hero().attack_type == "normal" and adr5.hero().armor_type == "heavy")
|
||||
# V6 锁死:开冒险后改存档选中→参数面/注入值不变(快照冻结;light=初始持有已解锁可切)
|
||||
Kit.check("冒险开始后锁死:改存档选中不影响参数面快照与生效值(契约 8)〔锻造工坊批 V6〕",
|
||||
mp5.forge_select_armor("light") and adr5.hero_armor_type == "heavy"
|
||||
mp5.forge_select_armor("light") and adr5.hero().armor_type == "heavy"
|
||||
and int(r5.effective_ac()) == 12 + 1)
|
||||
# V5/V7 末端:enc 两轴直达 spawn 英雄快照(hero_attack 行携带选中类型键)
|
||||
var run_ax = _run_battle(1, 0, {"attack_type": "pierce", "armor_type": "ward"})
|
||||
|
||||
@@ -35,10 +35,10 @@ func _init() -> void:
|
||||
# T10——空配置验收闸(三选一池空兜底/商店空货架)
|
||||
var e = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
|
||||
e.start_adventure(501)
|
||||
e._curio_pending = {"candidates": []}
|
||||
Kit.check_quiet(e.skip_empty_curio() and e._curio_pending.is_empty(),
|
||||
e.stop()._pending = {"candidates": []}
|
||||
Kit.check_quiet(e.skip_empty_curio() and e.stop()._pending.is_empty(),
|
||||
"空配置闸:三选一池空=停点整体跳过(skip_empty_curio 消化)〔专长批 T10〕")
|
||||
e._curio_pending = {"candidates": [{"kind": "gp", "tier": "common"}]}
|
||||
e.stop()._pending = {"candidates": [{"kind": "gp", "tier": "common"}]}
|
||||
Kit.check_quiet(not e.skip_empty_curio(),
|
||||
"空配置闸反例:非空停点拒绝跳过(零自动推进契约不破)〔专长批 T10〕")
|
||||
var lr = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new())
|
||||
@@ -73,7 +73,7 @@ func check_v7(a, db) -> void:
|
||||
_tok("armor", "common"), _tok("heal", "excellent"), _tok("refresh", "excellent"), _tok("ap", "rare")]
|
||||
var hi_seen := false
|
||||
for i in 40:
|
||||
var cands: Array = a._draw_curio_candidates("reward_node")
|
||||
var cands: Array = a.stop().draw_candidates("reward_node")
|
||||
if cands.size() != 3:
|
||||
continue
|
||||
var hi := 0
|
||||
@@ -87,7 +87,7 @@ func check_v7(a, db) -> void:
|
||||
a._curio_deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common")]
|
||||
var ok_fb := true
|
||||
for i in 20:
|
||||
var cands2: Array = a._draw_curio_candidates("reward_node")
|
||||
var cands2: Array = a.stop().draw_candidates("reward_node")
|
||||
if cands2.size() != 3:
|
||||
ok_fb = false
|
||||
break
|
||||
@@ -100,13 +100,13 @@ func check_v7(a, db) -> void:
|
||||
_tok("feat_ticket", "excellent")]
|
||||
var ticket_seen := false
|
||||
for i in 30:
|
||||
for t in a._draw_curio_candidates("reward_node"):
|
||||
for t in a.stop().draw_candidates("reward_node"):
|
||||
if String(t.get("kind", "")) == "feat_ticket":
|
||||
ticket_seen = true
|
||||
Kit.check_quiet(not ticket_seen, "凭证不进抽签池(feat_ticket 只经保底注入通道)〔专长批 V7〕")
|
||||
# 构成④:池空=零候选合法态(同族兜底入口)
|
||||
a._curio_deck = []
|
||||
Kit.check_quiet(a._draw_curio_candidates("reward_node").is_empty(), "池空=零候选合法态(skip_empty_curio 兜底入口)〔专长批 V7〕")
|
||||
Kit.check_quiet(a.stop().draw_candidates("reward_node").is_empty(), "池空=零候选合法态(skip_empty_curio 兜底入口)〔专长批 V7〕")
|
||||
|
||||
|
||||
func check_v6(b, db) -> void:
|
||||
@@ -116,21 +116,21 @@ func check_v6(b, db) -> void:
|
||||
# 拿了消耗:直置停点候选后 pick_curio——牌库摘牌单点
|
||||
b._curio_deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "excellent")]
|
||||
var gp_before: int = b.pool_stock("gp")
|
||||
b._curio_pending = {"candidates": [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "excellent")]}
|
||||
b.stop()._pending = {"candidates": [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "excellent")]}
|
||||
Kit.check_quiet(b.pick_curio("gp", "common") and b.pool_stock("gp") == gp_before - 1,
|
||||
"拿了消耗:pick_curio 后牌库摘牌(选中踢池)〔专长批 V6〕")
|
||||
# 没拿回池:抽候选不摘牌——重复抽签同令牌可再现
|
||||
var deck_size: int = b._curio_deck.size()
|
||||
var seen_again := false
|
||||
for i in 30:
|
||||
var cands: Array = b._draw_curio_candidates("reward_node")
|
||||
var cands: Array = b.stop().draw_candidates("reward_node")
|
||||
if cands.size() == 2 and b._curio_deck.size() == deck_size:
|
||||
seen_again = true
|
||||
Kit.check_quiet(seen_again, "没拿回池:抽签不摘牌(未选候选再次可现,牌库余量不变)〔专长批 V6〕")
|
||||
# 只刷出不计:抽签后 _curio_taken 零变化
|
||||
var taken_before: Dictionary = b._curio_taken.duplicate()
|
||||
for i in 10:
|
||||
b._draw_curio_candidates("reward_node")
|
||||
b.stop().draw_candidates("reward_node")
|
||||
var unchanged := true
|
||||
for k in taken_before.keys():
|
||||
if int(b._curio_taken.get(k, 0)) != int(taken_before[k]):
|
||||
@@ -145,7 +145,7 @@ func check_v12(m, db) -> void:
|
||||
var tier_n := {"common": 0, "excellent": 0, "rare": 0, "legendary": 0}
|
||||
var total := 0
|
||||
for i in 300:
|
||||
for t in m._draw_curio_candidates("level_clear"):
|
||||
for t in m.stop().draw_candidates("level_clear"):
|
||||
tier_n[String(t.get("tier", ""))] = int(tier_n.get(String(t.get("tier", "")), 0)) + 1
|
||||
total += 1
|
||||
Kit.check_quiet(total == 900, "V12 前置:300 停点 ×3 候选=900 席(实 %d)" % total)
|
||||
@@ -169,11 +169,11 @@ func check_v8(c, db) -> void:
|
||||
# cap 只在拿了才计:刷出不计
|
||||
c._curio_deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common")]
|
||||
for i in 20:
|
||||
c._draw_curio_candidates("reward_node")
|
||||
c.stop().draw_candidates("reward_node")
|
||||
Kit.check_quiet(bool(c._curio_cap_ok("gp", "")) and int(c._curio_taken.get("gp", 0)) == 0,
|
||||
"cap 刷出不计:20 次抽签后 gp 未拿、cap 未触发〔专长批 V8〕")
|
||||
# 拿了才计:pick 一次→cap 满→提前退役(候选视图剔除、牌库本体不动)
|
||||
c._curio_pending = {"candidates": [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common")]}
|
||||
c.stop()._pending = {"candidates": [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common")]}
|
||||
Kit.check_quiet(c.pick_curio("gp", "common"), "V8 前置:兑付 gp 一枚")
|
||||
Kit.check_quiet(not bool(c._curio_cap_ok("gp", "")), "cap 拿了才计:兑付后 cap 满(taken 1/1)〔专长批 V8〕")
|
||||
var gp_in_deck := 0
|
||||
@@ -182,7 +182,7 @@ func check_v8(c, db) -> void:
|
||||
gp_in_deck += 1
|
||||
var gp_cand := false
|
||||
for i in 30:
|
||||
for t in c._draw_curio_candidates("reward_node"):
|
||||
for t in c.stop().draw_candidates("reward_node"):
|
||||
if String(t.get("kind", "")) == "gp":
|
||||
gp_cand = true
|
||||
Kit.check_quiet(gp_in_deck >= 0 and not gp_cand,
|
||||
|
||||
@@ -119,7 +119,7 @@ func _init() -> void:
|
||||
"刷新练习:装配冒险启动")
|
||||
Kit.check_quiet(int(adr5.refresh_stock()) == 1 and int(adr5.attune_refresh_left()) == 1,
|
||||
"刷新练习:存量账 +1(装配即得一次性——attune_refresh_left 合计含 stock)")
|
||||
Kit.check_quiet(int(adr5._attune_refresh_x) == 0,
|
||||
Kit.check_quiet(int(adr5.stop()._refresh_x) == 0,
|
||||
"刷新练习反例:临时账 X 不动(写临时账/重复加仓=违规形态)")
|
||||
|
||||
# ⑥ 储备罐(转存率 125=1.25:1 整除口径 / 100=1:1 逐位同旧)
|
||||
@@ -269,13 +269,12 @@ func _init() -> void:
|
||||
Kit.check_quiet(ob12b.minesweeper.seal_range_free == false,
|
||||
"信标网络反例:未装配封印可达豁免=缺省 false(零装配零扰动)")
|
||||
|
||||
# ⑬ 真视使用计数链(受理成功恰计一次——RunLedger 单类持账)
|
||||
# ⑬ 真视使用计数链(受理成功恰计一次——主角账单点记录)
|
||||
var rng13 = load("res://core/rng_service.gd").new(); rng13.set_master_seed(321)
|
||||
var ob13 = orch_s.new(db, rng13)
|
||||
var lg13 = load("res://core/run_ledger.gd").new()
|
||||
lg13.vision_counter = {"used": 0}
|
||||
Kit.check_quiet(ob13.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(db.hero.max_hp), "attunement": ["incense_stick"], "ledger": lg13}),
|
||||
"计数链:装配起战(RunLedger 携带)")
|
||||
var lg13 = load("res://core/hero_ledger.gd").new(db, {})
|
||||
Kit.check_quiet(ob13.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(db.hero.max_hp), "attunement": ["incense_stick"], "hero": lg13}),
|
||||
"计数链:装配起战(HeroLedger 携带)")
|
||||
var inv13 = load("res://core/treasure_items.gd").new(db)
|
||||
var rec13: Dictionary = inv13.add_item("incense_stick", "attunement", Vector2i(-1, -1), 0)
|
||||
ob13.treasure_inventory = inv13
|
||||
@@ -283,7 +282,7 @@ func _init() -> void:
|
||||
var tgt13: Vector2i = _first_closed_safe(ob13)
|
||||
if tgt13 != Vector2i(-9, -9):
|
||||
ob13.use_treasure(h13, tgt13)
|
||||
Kit.check_quiet(int(lg13.vision_counter.get("used", 0)) == 1,
|
||||
Kit.check_quiet(lg13.vision_used() == 1,
|
||||
"计数链正常:受理成功恰 +1(信标网络解锁行为条件数据源)")
|
||||
|
||||
# ⑭ 装配不可变(V6:冒险全程装配快照三面同源且零写口——
|
||||
|
||||
Reference in new issue
Block a user