T1 数据面:AuxItemTable schema(七字段+retired/deferred 两 additive 标记+真视窄域/board_wide 双承重护栏)+六行 tres(三有效+罗盘 retired 留痕+显影尘/全图卷 deferred)+ConfigDB 加载/访问器+RewardTable 十型扩值(aux_item)+三投放行 5/4/1+aux_none 负占位行(雷格极性字典兜底零兑付)+双表悬空校验(reward_dangling_aux,负占位豁免)+F2 投放维度覆盖(豁免面 plan/info/counter 登记)+两 fatal 夹具(expect=1 复制真表注入违规)+run_all manifest+2 T2 core 骨架:AuxItems 类(api 8 入口+probe 探针;零随机零特判;Used 不可逆;真视恒可用/预读点击级零信息拒绝;3x3 边缘窗口∩板)+action_economy.grant_ap 第 9 API(当轮加量,区别 queue_ap_delta 排队) T3 授予路由+生命周期:_grant_routed aux 分支(正=入库存发「获得」行;负=零兑付 effect 型「道具随雷沉没」行)+use_aux 四步受理(相位 gate→validate→逐 kind 空间先 AP 后→consume;结算明细无道具行)+叠加层三清(装板点/战斗结束)+LevelRunner 关卡域库存持有注入+关卡末 reset+aux_cleared 快照计数扩签 冒烟锚随词表扩值更新:九型→十型封闭/字典 18→22 行;负向字典扫描三处稳健化(aux 不入五型轴+break 改 percent_map 三型齐备+rng 每板新实例——原第一板 break 掩盖 set_master_seed _counter 守卫违例) 闸:冒烟 577/0(净增 1)+单测 11 套全过(含两新夹具)+四探针 PASS
70 lines
2.4 KiB
GDScript
70 lines
2.4 KiB
GDScript
# 致命类用例(独立进程,期望退出码 1)
|
|
# reward_dangling_aux——RewardTable aux 投放行双表校验(迭代 7 T1):aux 行 effect_id 引用 retired
|
|
# 道具(loot_compass——2026-09-29 拍板①乙退役)时,config_db 跨表校验必须报 reward_dangling_aux、
|
|
# load_tables 整体拒绝(退役件/失义件不得出现在掉落投放面)。
|
|
# 运行:godot --headless --path game --script res://tests/unit/data_config/reward_dangling_aux_test.gd
|
|
# 退出码协议:1 = 校验正确拒绝(预期);2 = 违规表被放行(回归信号)。
|
|
extends SceneTree
|
|
|
|
const ConfigDBScript := preload("res://core/config_db.gd")
|
|
|
|
const SRC_DIR := "res://data/tables"
|
|
const BAD_DIR := "user://aux_fixture_dangling_aux"
|
|
|
|
|
|
func _copy_tables() -> bool:
|
|
DirAccess.make_dir_recursive_absolute(BAD_DIR)
|
|
var dir := DirAccess.open(SRC_DIR)
|
|
if dir == null:
|
|
return false
|
|
dir.list_dir_begin()
|
|
var f := dir.get_next()
|
|
while f != "":
|
|
if not dir.current_is_dir():
|
|
if dir.copy(SRC_DIR + "/" + f, BAD_DIR + "/" + f) != OK:
|
|
return false
|
|
f = dir.get_next()
|
|
return true
|
|
|
|
|
|
func _inject_violation() -> bool:
|
|
var path := BAD_DIR + "/reward.tres"
|
|
var f := FileAccess.open(path, FileAccess.READ)
|
|
if f == null:
|
|
return false
|
|
var s := f.get_as_text()
|
|
f.close()
|
|
# 工兵令投放行 → 引用已退役的 loot_compass:双表校验须拦(retired 不入投放面)
|
|
var blocks: PackedStringArray = s.split("}, {")
|
|
var hit := false
|
|
for i in blocks.size():
|
|
if '"reward_id": "aux_sapper_order"' in blocks[i]:
|
|
blocks[i] = blocks[i].replace('"effect_id": "sapper_order"', '"effect_id": "loot_compass"')
|
|
hit = true
|
|
s = "}, {".join(blocks)
|
|
var w := FileAccess.open(path, FileAccess.WRITE)
|
|
if w == null or not hit:
|
|
return false
|
|
w.store_string(s)
|
|
w.close()
|
|
return true
|
|
|
|
|
|
func _init() -> void:
|
|
if not _copy_tables() or not _inject_violation():
|
|
push_error("[reward_dangling_aux_test] 夹具目录准备失败")
|
|
quit(2)
|
|
return
|
|
var db = ConfigDBScript.new()
|
|
var ok: bool = db.load_tables(BAD_DIR, false)
|
|
if ok:
|
|
push_error("[reward_dangling_aux_test] 违规表被放行——aux 双表校验失守(回归)")
|
|
quit(2)
|
|
return
|
|
if db.diagnostics_count("error") >= 1:
|
|
print("[reward_dangling_aux_test] 双表校验触发(reward_dangling_aux 诊断在案,加载被拒)")
|
|
quit(1)
|
|
return
|
|
push_error("[reward_dangling_aux_test] 加载被拒但无 reward_dangling_aux 诊断——校验通道静默化(回归)")
|
|
quit(2)
|