feat(pool): 迭代20 T2 池结构案D 换代(专长实装批 A段二)
四级档位:TIERS common/excellent/rare/legendary(跨层框架档名映射承袭)——TIER_QUOTAS 70/25/3/0=98 直映射零偏离已裁结构锚(传说=头奖位留空);配额/件值字段物理改名 (旧 rare→excellent、旧 rarest→rare)+凭证四 kind 占位(rap/attune_battery/time_sand/ perfect_insurance,配额暂 0=T4 落路由同批投放)。 概率与上限解耦(案②令牌池+提前退役):curio_cap_* 每 kind 独立字段(99=无上限占位, 实数归调参批)+专长 cap 行字段面;taken 身份维计数(relic:<feat_id>)拿了才计刷出不计; cap 满在候选视图剔除(牌库本体不动=概率位独立存活);断点 bonus_taken 身份维键随档还原。 曝光槽:三选一=2 格全池+1 格优秀档以上子池(子池独立洗牌同 stream 续消费;子池空回退 全池;池空直通同族兜底);凭证(feat_ticket)不进抽签池只经保底注入;商店货架同 cap 过滤。 潜伏缺陷修正:效果引擎 has 算子 null 安全(敌技伤害载荷无 terrain_kind 事实回落 null 时 判「不在场」而非 String(null) 抛错——候选流变化后 devour 件首次被持有而暴露)。 测试:pool_case_d_test 新套(V6 令牌三态/V7 曝光槽回退/V8 解耦提前退役)入 run_all 20 套; smoke 三档锚/数值对/池构成/上限带四处断言换代(V14),冒烟 848/0+probe_gate --click 全绿。
This commit is contained in:
1 parent
a57489e970
commit
0640b0c06d
10 files changed
+384
-103
No files matched your search
@@ -0,0 +1,152 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 池结构案D 行为单测(迭代 20 T2——需求卡片 V6/V7/V8 验收条目)。
|
||||
# V6 令牌三态(拿了消耗/没拿回池/只刷出不计);V7 曝光槽 2+1+子池空回退+凭证不进子池;
|
||||
# V8 概率与上限解耦(改 cap 不动概率位/改概率位不动 cap/cap 满提前退役只影响候选视图)。
|
||||
# 运行:godot --headless --path game --script res://tests/unit/core/pool_case_d_test.gd(expect 0)
|
||||
extends SceneTree
|
||||
|
||||
const Kit := preload("res://tests/unit/test_kit.gd")
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
var db = load("res://core/config_db.gd").new()
|
||||
if not db.load_tables("res://data/tables", false):
|
||||
print("[pool_case_d_test] FAIL — 数值表加载失败")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
# ---- V7 曝光槽:直改 _curio_deck 档位构成后直调候选抽签面 ----
|
||||
var a = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
|
||||
check_v7(a, db)
|
||||
|
||||
# ---- V6 令牌三态:真牌库 + 停点直调 pick_curio ----
|
||||
var b = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
|
||||
check_v6(b, db)
|
||||
|
||||
# ---- V8 解耦:cap 独立可设 + 提前退役只影响候选视图 ----
|
||||
var c = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new())
|
||||
check_v8(c, db)
|
||||
|
||||
quit(Kit.finish("pool_case_d"))
|
||||
|
||||
|
||||
func _tok(kind: String, tier: String, feat_id: String = "") -> Dictionary:
|
||||
var t := {"kind": kind, "tier": tier}
|
||||
if feat_id != "":
|
||||
t["feat_id"] = feat_id
|
||||
return t
|
||||
|
||||
|
||||
func _tiers_of(cands: Array) -> Array:
|
||||
var out: Array = []
|
||||
for t in cands:
|
||||
out.append(String((t as Dictionary).get("tier", "")))
|
||||
return out
|
||||
|
||||
|
||||
func check_v7(a, db) -> void:
|
||||
if not a.start_adventure(201, {"first_script": true}):
|
||||
Kit.check_quiet(false, "V7 前置:冒险启动")
|
||||
return
|
||||
# 构成①:全池含优秀档以上——曝光槽应出 1 格高档(rank≥excellent)
|
||||
a._curio_deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common"),
|
||||
_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")
|
||||
if cands.size() != 3:
|
||||
continue
|
||||
var hi := 0
|
||||
for t in cands:
|
||||
if db.adventure.tier_rank(String(t.get("tier", ""))) >= db.adventure.tier_rank(db.adventure.TIER_EXCELLENT):
|
||||
hi += 1
|
||||
if hi >= 1:
|
||||
hi_seen = true
|
||||
Kit.check_quiet(hi_seen, "曝光槽:全池含高档时三选一呈现 ≥1 格优秀档以上〔专长批 V7〕")
|
||||
# 构成②:高档子池空——该格回退全池(候选仍 3 格全普通)
|
||||
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")
|
||||
if cands2.size() != 3:
|
||||
ok_fb = false
|
||||
break
|
||||
for t in cands2:
|
||||
if String(t.get("tier", "")) != "common":
|
||||
ok_fb = false
|
||||
Kit.check_quiet(ok_fb, "子池空回退全池:全普通池候选恒 3 格普通档〔专长批 V7〕")
|
||||
# 构成③:凭证不进子池也不进全池视图(独立计数体系——只经门票注入出现)
|
||||
a._curio_deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common"),
|
||||
_tok("feat_ticket", "excellent")]
|
||||
var ticket_seen := false
|
||||
for i in 30:
|
||||
for t in a._draw_curio_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〕")
|
||||
|
||||
|
||||
func check_v6(b, db) -> void:
|
||||
if not b.start_adventure(202, {"first_script": true}):
|
||||
Kit.check_quiet(false, "V6 前置:冒险启动")
|
||||
return
|
||||
# 拿了消耗:直置停点候选后 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")]}
|
||||
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")
|
||||
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")
|
||||
var unchanged := true
|
||||
for k in taken_before.keys():
|
||||
if int(b._curio_taken.get(k, 0)) != int(taken_before[k]):
|
||||
unchanged = false
|
||||
Kit.check_quiet(unchanged, "只刷出不计:抽签不改已拿账〔专长批 V6〕")
|
||||
|
||||
|
||||
func check_v8(c, db) -> void:
|
||||
if not c.start_adventure(203, {"first_script": true}):
|
||||
Kit.check_quiet(false, "V8 前置:冒险启动")
|
||||
return
|
||||
# 概率位与 cap 两属性独立:改 cap 不动牌库(概率位)、改牌库不动 cap 字段
|
||||
var cap_field_before := int(db.adventure.curio_cap_gp)
|
||||
db.adventure.curio_cap_gp = 1 # 独立设 cap(概率位=gp 令牌数不动)
|
||||
var gp_tokens: int = c.pool_stock("gp")
|
||||
Kit.check_quiet(gp_tokens > 0 and int(db.adventure.curio_value("gp", "common")) == 20,
|
||||
"改 cap 不动概率位:牌库 gp 令牌 %d 枚在库、件值字段不受影响〔专长批 V8〕" % gp_tokens)
|
||||
# cap 只在拿了才计:刷出不计
|
||||
c._curio_deck = [_tok("gp", "common"), _tok("attack", "common"), _tok("max_hp", "common")]
|
||||
for i in 20:
|
||||
c._draw_curio_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")]}
|
||||
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
|
||||
for t in c._curio_deck:
|
||||
if String((t as Dictionary).get("kind", "")) == "gp":
|
||||
gp_in_deck += 1
|
||||
var gp_cand := false
|
||||
for i in 30:
|
||||
for t in c._draw_curio_candidates("reward_node"):
|
||||
if String(t.get("kind", "")) == "gp":
|
||||
gp_cand = true
|
||||
Kit.check_quiet(gp_in_deck >= 0 and not gp_cand,
|
||||
"提前退役:cap 满后候选视图零 gp(牌库本体张数不动=%d——概率位独立存活)〔专长批 V8〕" % gp_in_deck)
|
||||
db.adventure.curio_cap_gp = cap_field_before # 还原表(同进程后续套件防御)
|
||||
Reference in new issue
Block a user