iter55-卡1:QA覆盖余量收口批: 46→45真缺口断言落地(违约词负面套件/结算与cap与换算率钉/配额满/rap端到端/布局/天赋树/状态行/L3变异/reward行)+p_perfect读表接线(行为零漂移)+豁免登记(ontology 154/controls 19)+探针扩面十一组真输入+登记件开发面过滤(DebugOverlay/调试角标/inter三根, seen开发面键0)+枚举器scan_screens基类分类(绘制域件与调试浮窗出圈, 868实体/未豁免未补测=0)+cell_mark_overlay渲染通道(采集态六类型注入全入帧, 棋盘三图基线换代用户过目, 渲染闸绿);卡修轮2用户裁两处不豁免(渲染通道+开发面出圈);独立审计U1修复闭合+增量轮通过

This commit is contained in:
DKChan committed 2026-11-07 19:56:13 +08:00
1 parent 095bb4c91b
commit c41ce9cfc5
25 files changed
+1502 -26

No files matched your search

@@ -577,6 +577,7 @@ func _smoke_feats() -> void:
offered = true
break
Kit.check("A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)", offered)
_check_ticket_quota_exhausted(a_t) # A6-补 配额满分支(迭代 55 T3/V4——生产配额值驱动恒 false)
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.stop().pick_curio("relic", "rare", "x3") and (a_r.feats_held() as Array).has("x3") and a_r.deck().pool_stock("relic") == deck_relic - 1)
# 断点往返三条(基点 1420–1426:relics 快照/计数器键/余库逐枚还原)按主会话裁决②改判跨系统闭环宿主——
@@ -915,3 +916,26 @@ func _fresh_rng(seed_val: int):
func _level_active_nodes(level_index: int) -> Array:
var gen = NodeMapGenerator.new(_fresh_rng(42))
return (gen.generate(_db.run.level_gen_params.row_by_level_index(level_index), 42) as Dictionary).get("nodes", [])
# A6-补 配额满分支(迭代 55「QA覆盖余量收口批」T3/V4):ticket_appeared 达生产配额后恒不再出现——
# 生产 feat_ticket_quota==2 读表驱动;保底同置满使 false 只能来自配额分支(先于概率掷点)。
# 真实入口链 = node_curio_candidates→draw_candidates("reward_node")→feat_ticket_consider。
func _check_ticket_quota_exhausted(a_t) -> void:
# 判据出处: design/engineering/test-conventions.md:133 (真实入口断言——配额满分支售满后恒 false)
Kit.check("生产配额值在役:feat_ticket_quota==2〔QA余 V4〕", int(a_t.db.run.adventure.feat_ticket_quota) == 2)
a_t.stop()._deck.ticket_appeared = int(a_t.db.run.adventure.feat_ticket_quota) # 配额满注入(A6 同款测试通道直填)
a_t.stop()._deck.ticket_pity = int(a_t.db.run.adventure.feat_ticket_pity_step) # 保底同置满——false 唯一来源=配额分支
var rg_q: RandomNumberGenerator = a_t.rng.open_stream("reward_pick")
Kit.check("配额满后 consider 恒 false(保底已满仍拒——分支先于概率掷点)〔QA余 V4〕", not a_t.deck().feat_ticket_consider(rg_q))
for i in 3:
var cands: Array = (a_t.stop().node_curio_candidates().get("candidates", []) as Array)
var has_ticket := false
for cd in cands:
if String((cd as Dictionary).get("kind", "")) == "feat_ticket":
has_ticket = true
Kit.check("配额满后第 %d 次停点候选零专长凭证(注入链零凭证)〔QA余 V4〕" % (i + 1), not has_ticket)
Kit.check("配额满分支零记账(ticket_appeared 停在配额值——不随展示递增)〔QA余 V4〕",
int(a_t.stop()._deck.ticket_appeared) == int(a_t.db.run.adventure.feat_ticket_quota))
a_t.stop()._deck.ticket_appeared = 0 # 夹具还原(后续段零污染)
a_t.stop()._deck.ticket_pity = 0
@@ -42,6 +42,10 @@ func _init() -> void:
_test_blessing_no_forge_leak()
_test_default_axes_pre_key()
_test_armor_switch_dual_axes()
_check_ks_workshop_row()
_check_ks_refresh_row()
_check_fork_rows()
_check_fork_side_projection()
quit(Kit.finish("meta_progression_forge_test"))
@@ -56,7 +60,9 @@ const _META_SMOKE_PATHS := ["user://smoke_meta_buy.json", "user://smoke_meta_dry
"user://smoke_meta_career_v5.json", "user://smoke_meta_treasure_unlock.json", "user://smoke_meta_treasure_blank.json",
"user://smoke_meta_forge.json", "user://smoke_meta_forge2.json", "user://smoke_meta_forge3.json",
"user://smoke_meta_forge4.json", "user://smoke_meta_forge5.json",
"user://smoke_meta_reject.json", "user://smoke_meta_blessing.json", "user://smoke_meta_axesw.json"]
"user://smoke_meta_reject.json", "user://smoke_meta_blessing.json", "user://smoke_meta_axesw.json",
"user://smoke_meta_ks_workshop.json", "user://smoke_meta_ks_refresh.json",
"user://smoke_meta_fork_rows.json", "user://smoke_meta_fork_side.json"]
func _meta_smoke_cleanup() -> void:
@@ -852,3 +858,115 @@ func _test_armor_switch_dual_axes() -> void:
return
Kit.check("甲型双轴:切回 light 复原(AC 10+0)且 heavy 等级保留(Lv1 不丢)〔断言补齐批 V16〕",
int(adr2.runner().effective_ac()) == 10 and sv.meta_state.forge_state().levels.get("heavy", 0) == 1)
# ---- G5 天赋树行与投影断言(迭代 55「QA覆盖余量收口批」T5——ks_workshop/ks_refresh/fork 两行/侧投影+spine 读行)----
func _check_ks_workshop_row() -> void:
# 判据出处: design/gdd/systems/跨局循环/天赋树.md:30 (钥匙门行——前置/零价/柜台展开)
var row: Dictionary = _db.crossrun.meta_tree.row_by_id("ks_workshop")
Kit.check("ks_workshop 行值:keystone 脉络 unlock 行且前置=[sp_hp](脊柱生命第 1 级)〔QA余 V7〕",
String(row.get("vein", "")) == "keystone" and String(row.get("kind", "")) == "unlock"
and (row.get("requires", []) as Array) == ["sp_hp"])
Kit.check("ks_workshop 零价行值:currency=free 且 price=0(初始零购零币可点亮族)〔QA余 V7〕",
String(row.get("currency", "")) == "free" and int(row.get("price", -1)) == 0)
var sv = _meta_new_save("user://smoke_meta_ks_workshop.json")
var mp = load("res://core/meta_progression.gd").new(_db, sv)
sv.meta_state._add_keystone(1000)
Kit.check("前置未满足不可购(sp_hp 未点亮——钥匙门锁定)", not mp.tree_buy("ks_workshop"))
Kit.check("脊柱生命 L1 购买受理(前置行)", mp.tree_buy("sp_hp"))
Kit.check("购买前投影 level=0(锁定态行在投影)", _ks_row_level(mp, "ks_workshop") == 0)
var bal_pre: int = sv.meta_state.keystone_balance()
Kit.check("ks_workshop 购买受理(零价 free)", mp.tree_buy("ks_workshop"))
Kit.check("购买后投影 level=1(钥匙门开——柜台展开判据)且零币(余额差分零)",
_ks_row_level(mp, "ks_workshop") == 1 and int(sv.meta_state.keystone_balance()) == bal_pre)
_meta_smoke_cleanup()
# 树面投影行 level 读数(_check_ks_workshop_row/_check_ks_refresh_row 共用读数面)
func _ks_row_level(mp, node_id: String) -> int:
for r in (mp.tree_camp_status()["rows"] as Array):
if String((r as Dictionary).get("node_id", "")) == node_id:
return int((r as Dictionary).get("level", 0))
return -1
func _check_ks_refresh_row() -> void:
# 判据出处: design/gdd/systems/跨局循环/天赋树.md:30 (刷新升级行——同账路由与零刷新分支)
var row: Dictionary = _db.crossrun.meta_tree.row_by_id("ks_refresh")
Kit.check("ks_refresh 行值:rule 行 keystone 币 price=30 cap=2 前置=[ks_workshop]〔QA余 V7〕",
String(row.get("kind", "")) == "rule" and String(row.get("currency", "")) == "keystone"
and int(row.get("price", -1)) == 30 and int(row.get("cap", -1)) == 2
and (row.get("requires", []) as Array) == ["ks_workshop"])
var sv = _meta_new_save("user://smoke_meta_ks_refresh.json")
var mp = load("res://core/meta_progression.gd").new(_db, sv)
sv.meta_state._add_keystone(1000)
var bal0: int = sv.meta_state.keystone_balance()
Kit.check("ks_refresh 购买受理(tree_buy 同账路由→buy_attune_refresh)", mp.tree_buy("ks_refresh"))
Kit.check("同账写读:refresh_level=1(树上+秘宝匣装配段双入口同一账本)", int(sv.meta_state.attune_state().refresh_level) == 1)
Kit.check("同账币值:扣 meta 行 L1 keystone_cost=30(非树行 price 面定夺)", int(sv.meta_state.keystone_balance()) == bal0 - 30)
Kit.check("树节点账零写入(同账语义——nodes 无 ks_refresh)", int(sv.meta_state.tree_state().nodes.get("ks_refresh", 0)) == 0)
Kit.check("第二次购买受理(L2 keystone_cost=60)", mp.tree_buy("ks_refresh") and int(sv.meta_state.attune_state().refresh_level) == 2)
var bal2: int = sv.meta_state.keystone_balance()
Kit.check("零刷新分支:L2 后再购被拒(attune_refresh_row(3) 空返——升级行族耗尽)〔QA余 V7〕", not mp.tree_buy("ks_refresh"))
Kit.check("零刷新分支零消费零升账", int(sv.meta_state.attune_state().refresh_level) == 2 and int(sv.meta_state.keystone_balance()) == bal2)
Kit.check("同账投影:树面 ks_refresh 行 level 读 Y 账本(=2)", _ks_row_level(mp, "ks_refresh") == 2)
_meta_smoke_cleanup()
func _check_fork_rows() -> void:
# 判据出处: design/gdd/systems/跨局循环/天赋树.md:30 (fork 一节点双态行——行值经购买面驱动)
for spec in [{"id": "fork_sense", "group": "fg_sense", "na": "锋", "nb": "守"},
{"id": "fork_economy", "group": "fg_economy", "na": "聚", "nb": "散"}]:
var row: Dictionary = _db.crossrun.meta_tree.row_by_id(String(spec["id"]))
Kit.check("%s 行值:fork 行 price=25 前置=[att_socket1] 组=%s〔QA余 V7〕" % [spec["id"], spec["group"]],
String(row.get("kind", "")) == "fork" and int(row.get("price", -1)) == 25
and (row.get("requires", []) as Array) == ["att_socket1"]
and String(row.get("fork_group", "")) == String(spec["group"]))
Kit.check("%s 侧名行值:name_a=%s / name_b=%s〔QA余 V7〕" % [spec["id"], spec["na"], spec["nb"]],
String(row.get("name_a", "")) == String(spec["na"]) and String(row.get("name_b", "")) == String(spec["nb"]))
var sv = _meta_new_save("user://smoke_meta_fork_rows.json")
var mp = load("res://core/meta_progression.gd").new(_db, sv)
sv.meta_state._add_keystone(1000)
Kit.check("同调位 1 购买受理(fork 前置行——根节点无前置)", mp.tree_buy("att_socket1"))
var bal0: int = sv.meta_state.keystone_balance()
Kit.check("fork_sense 购买受理(tree_buy_fork 真链——解锁+默认 A 态)",
mp.tree_buy_fork("fork_sense") and int(sv.meta_state.tree_state().nodes.get("fork_sense", 0)) == 1
and String(sv.meta_state.tree_state().fork_sides.get("fg_sense", "")) == "a")
Kit.check("fork_economy 购买受理(同链)",
mp.tree_buy_fork("fork_economy") and int(sv.meta_state.tree_state().nodes.get("fork_economy", 0)) == 1
and String(sv.meta_state.tree_state().fork_sides.get("fg_economy", "")) == "a")
Kit.check("两 fork 行扣币共 50(表行 price 驱动)", int(sv.meta_state.keystone_balance()) == bal0 - 50)
_meta_smoke_cleanup()
func _check_fork_side_projection() -> void:
# 判据出处: design/gdd/systems/跨局循环/天赋树.md:30 (组侧投影与脉络行板——真实表行读出)
var sv = _meta_new_save("user://smoke_meta_fork_side.json")
var mp = load("res://core/meta_progression.gd").new(_db, sv)
sv.meta_state._add_keystone(1000)
Kit.check("投影夹具:同调位 1+fork_economy 购买受理", mp.tree_buy("att_socket1") and mp.tree_buy_fork("fork_economy"))
var fg_entry: Dictionary = {}
for g in (mp.tree_camp_status()["fork_groups"] as Array):
if String((g as Dictionary).get("group", "")) == "fg_economy":
fg_entry = g
Kit.check("fg_economy 组投影在册:display_name=财富之道 owned=true active_side=a〔QA余 V7〕",
String(fg_entry.get("display_name", "")) == "财富之道" and bool(fg_entry.get("owned", false))
and String(fg_entry.get("active_side", "")) == "a")
var sides: Array = fg_entry.get("sides", []) as Array
Kit.check("侧名投影:A 侧=聚(economy_hoard)/B 侧=散(economy_spend)〔QA余 V7〕",
sides.size() == 2 and String((sides[0] as Dictionary).get("display_name", "")) == "聚"
and ((sides[0] as Dictionary).get("effects", {}) as Dictionary).has("economy_hoard")
and String((sides[1] as Dictionary).get("display_name", "")) == "散"
and ((sides[1] as Dictionary).get("effects", {}) as Dictionary).has("economy_spend"))
Kit.check("A 态规则键=economy_hoard(聚)", (mp.tree_rules() as Dictionary).has("economy_hoard"))
Kit.check("切 b 受理(零币换侧)", mp.tree_toggle_fork("fg_economy", "b"))
Kit.check("B 态规则键=economy_spend(散)且 A 键退场〔QA余 V7〕",
(mp.tree_rules() as Dictionary).has("economy_spend") and not (mp.tree_rules() as Dictionary).has("economy_hoard"))
var spine_names := {}
for r in (mp.tree_camp_status()["rows"] as Array):
if String((r as Dictionary).get("vein", "")) == "spine":
spine_names[String((r as Dictionary).get("node_id", ""))] = String((r as Dictionary).get("display_name", ""))
Kit.check("spine 脉络投影读行:sp_hp=生命淬炼/sp_hit=命中淬炼(生产表行名非夹具喂值)〔QA余 V7〕",
String(spine_names.get("sp_hp", "")) == "生命淬炼" and String(spine_names.get("sp_hit", "")) == "命中淬炼")
_meta_smoke_cleanup()
@@ -144,6 +144,7 @@ func _init() -> void:
ae6c.request_stop()
ae6c.finish_round_stop(125)
Kit.check_quiet(ae6c.rap == 3, "储备罐边界:3 AP→3(floor 3.75——不越参数面直改停手结算)")
_test_rap_transfer_row_e2e(db) # ⑥-补 生产行 e2e(迭代 55 T2——装配链行值主证,替上文字面量路径作主证并存)
# ⑦ 巢穴预告(装板后一次报数量——明细行经 step_row 信号收集)
var rng7 = load("res://core/rng_service.gd").new(); rng7.set_master_seed(271)
@@ -417,3 +418,26 @@ class StubAutoInvRunner:
extends RefCounted
func query_inventory(_cb: Callable) -> Array:
return []
# ⑥-补 储备罐生产行 e2e(迭代 55「QA覆盖余量收口批」T2/V5):装配链行值主证——上文字面量路径(125 直调)
# 保留并存;本函数断言全链依赖生产行值:add_item 入库→装配集直注(套件既定调试注入口径)→attune 参数和
# ==行值 125→转存兑付按 grant_segment 生产入参形态(装配面读数为入参)兑现。行值变更即红。
func _test_rap_transfer_row_e2e(db) -> void:
# 判据出处: design/gdd/systems/跨局循环/秘宝.md:246 (秘宝逐件行为——储备罐转存率行值)
var inv = load("res://core/treasure_items.gd").new()
var got: Dictionary = inv.add_item("reserve_jar", "clear_reward", Vector2i(-1, -1), 0)
Kit.check_quiet(not got.is_empty() and String(got.get("item_id", "")) == "reserve_jar",
"储备罐 e2e:add_item 入库(真实入口——受理记录 item_id 命中)〔QA余 V5〕")
var rng = load("res://core/rng_service.gd").new(); rng.set_master_seed(281)
var ob = load("res://core/battle_orchestrator.gd").new(db, rng)
Kit.check_quiet(bool(ob.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(db.combat.hero.max_hp), "attunement": ["reserve_jar"]})),
"储备罐 e2e:装配集直注起战〔QA余 V5〕")
Kit.check_quiet(int(ob.attune_param_sum("rap_transfer_mult")) == 125,
"储备罐 e2e:装配→attune 参数和==生产行值 125(行值主证)〔QA余 V5〕")
var ae = ob.action_economy
var ap_left: int = int(ae.ap)
Kit.check_quiet(bool(ae.request_stop()), "储备罐 e2e:停手记账受理〔QA余 V5〕")
ae.finish_round_stop(int(ob.attune_param_sum("rap_transfer_mult"))) # grant_segment 生产入参形态(装配面读数→转存率入参)
Kit.check_quiet(int(ae.rap) == ap_left * 125 / 100,
"储备罐 e2e:转存增量==行值倍率(%d AP→RAP %d=125%% 整除口径;行值或接线变更即红)〔QA余 V5〕" % [ap_left, int(ae.rap)])