- data_config: ConfigDB 表加载+跨表护栏零诊断/进度坐标 ceil-clamp 解析与警告去重/变异池门控/shop·meta 消费面/manifest↔目录同步/越域 fatal 退出码(独立进程 expect=1,deferred 触发保 quit 通道)/统一随机流(派生确定性/计数器账本/标签不参与派生/负种子归一化) - battle_settlement: DamageMath 四纯函数(池乘区/克制分量/掷点查档/吃掷点分量)+DamageController 伤害单据五配方+B/A 双结账模式+来源敌死作废+requires 前置门+主角倒下 halt+AC 合流三通道+池 clamp 与 refresh 政策 - contracts: 五配方/三目标/双政策词表封闭+掷点标签∈RNG 封闭词表(EC12)+单据九栏缺省+增益布尔受理口径(EC1/EC2) - 红线: 期望值全由表读值推导(零玩法数值字面量)/零自建 RNG(夹具种子仅求复现) - 轻闸: 冒烟 557/0 + ui_input_probe 十一断言全绿(基线零漂移)
96 lines
3.6 KiB
GDScript
96 lines
3.6 KiB
GDScript
# tests/unit/data_config/rng_service_test.gd — 统一随机流单元测试:单盐派生确定性/计数器账本/标签词表封闭
|
||
# 运行:godot --headless --path game --script res://tests/unit/data_config/rng_service_test.gd
|
||
# 红线:零自建 RNG——本测试只经 RngService 统一流取数(确定性契约:同种子逐位复现)。
|
||
# 测试种子为夹具基建值(非玩法数值),与冒烟黄金向量锚(smoke_all m=42 scope 0-4)分工:本文件测
|
||
# 「同种子两实例逐位一致」的相对确定性,绝对向量锚归冒烟单点,避免双处维护同一常量。
|
||
extends SceneTree
|
||
|
||
const RngServiceScript := preload("res://core/rng_service.gd")
|
||
|
||
const TEST_SEED := 20260928 # 夹具种子(任意固定值;语义 = 可复现性,非玩法数值)
|
||
|
||
var _failures := 0
|
||
var _checks := 0
|
||
|
||
|
||
func _init() -> void:
|
||
_test_derive_determinism()
|
||
_test_counter_ledger()
|
||
_test_stream_draw_determinism()
|
||
_test_negative_seed_normalization()
|
||
if _failures > 0:
|
||
push_error("[rng_service_test] %d/%d 项失败" % [_failures, _checks])
|
||
quit(1)
|
||
return
|
||
print("[rng_service_test] all green (%d checks)" % _checks)
|
||
quit(0)
|
||
|
||
|
||
func _check(desc: String, ok: bool) -> void:
|
||
_checks += 1
|
||
if ok:
|
||
print(" [PASS] " + desc)
|
||
else:
|
||
_failures += 1
|
||
push_error(" [FAIL] " + desc)
|
||
|
||
|
||
func _fresh_service():
|
||
var rng = RngServiceScript.new()
|
||
rng.set_master_seed(TEST_SEED)
|
||
return rng
|
||
|
||
|
||
# 单盐派生纯函数:两全新实例同种子逐位一致(derive_at 不推计数器——纯函数契约)
|
||
func _test_derive_determinism() -> void:
|
||
var a = _fresh_service()
|
||
var b = _fresh_service()
|
||
var same := true
|
||
for scope in range(8):
|
||
if a.derive_at(scope) != b.derive_at(scope):
|
||
same = false
|
||
_check("同种子两实例 derive_at(0..7) 逐位一致", same)
|
||
var a2 = _fresh_service()
|
||
var before: int = a2.counter()
|
||
a2.derive_at(0)
|
||
_check("derive_at 为纯函数不推计数器", a2.counter() == before)
|
||
|
||
|
||
# 全局取数计数器:唯一账本——open_stream 逐次 +1(scope 单调)
|
||
func _test_counter_ledger() -> void:
|
||
var rng = _fresh_service()
|
||
var c0: int = rng.counter()
|
||
rng.open_stream("armor_roll")
|
||
var c1: int = rng.counter()
|
||
rng.open_stream("armor_roll")
|
||
var c2: int = rng.counter()
|
||
_check("open_stream 逐次推进全局计数器(+1/次)", c1 == c0 + 1 and c2 == c0 + 2)
|
||
# 用途标签只进事件日志不参与派生——同 scope 同派生种子(ADR-0007 裁丙口径)
|
||
var rng_x = _fresh_service()
|
||
var rng_y = _fresh_service()
|
||
var s1: int = int(rng_x.open_stream("board_gen").seed)
|
||
var s2: int = int(rng_y.open_stream("reward_roll").seed)
|
||
_check("同 scope 异用途标签派生种子一致(标签不参与派生)", s1 == s2)
|
||
|
||
|
||
# 同种子两实例同标签流的实际取数序列逐位一致(回放确定性契约——存档快照=主种子+计数器的前提)
|
||
func _test_stream_draw_determinism() -> void:
|
||
var faces := 20 # d20 探针面数(夹具值,非玩法表值——只用于产生可比较序列)
|
||
var a = _fresh_service()
|
||
var b = _fresh_service()
|
||
var same := true
|
||
for i in range(12):
|
||
var ra: int = a.open_stream("armor_roll").randi_range(1, faces)
|
||
var rb: int = b.open_stream("armor_roll").randi_range(1, faces)
|
||
if ra != rb:
|
||
same = false
|
||
_check("同种子两实例同标签流取数 12 次逐位一致", same)
|
||
|
||
|
||
# 负输入 abs 归一化(警告通道)——|−s| 与 s 同派生
|
||
func _test_negative_seed_normalization() -> void:
|
||
var pos = _fresh_service()
|
||
var neg = RngServiceScript.new()
|
||
neg.set_master_seed(-TEST_SEED)
|
||
_check("负种子 abs 归一化后派生与正种子一致", pos.derive_at(0) == neg.derive_at(0))
|