feat(attr): AttributeFormula 公式模块——hybrid 连乘 / inverse 反向下限 / add_int 拒 pct

公式集中于单一纯静态模块,无状态零依赖,故可脱离游戏进程单元断言。
乘算用连乘而非线性求和:三条 +20% 得 ×1.728 而非 ×1.6,避免后期线性失控。
add_int 对离散预算拒绝 pct 并 push_error,而非静默取整掩盖配置错误。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 10:46:20 +08:00
co-authored by Claude Opus 5
parent 9bc51795c4
commit ecf765d03e
2 changed files with 55 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
## AttributeFormula — 属性合成公式的唯一实现处
## 纯静态函数:无状态、不依赖 PlayerStats / EventBus / 场景树,故可脱离游戏进程单元测试
## 权威来源:docs_dev/specs/2026-07-31-player-attributes-design.md §2.1 / §2.3
class_name AttributeFormula
extends RefCounted
enum Combine { HYBRID, INVERSE, ADD_INT }
const _COMBINE_NAMES: Dictionary = {
"hybrid": Combine.HYBRID,
"inverse": Combine.INVERSE,
"add_int": Combine.ADD_INT,
}
## JSON 的 combine 字符串 → 枚举;未知值 push_error 并回退 HYBRID
static func combine_from_string(s: String) -> Combine:
if _COMBINE_NAMES.has(s):
return _COMBINE_NAMES[s]
push_error("AttributeFormula: 未知 combine「%s」,回退 hybrid" % s)
return Combine.HYBRID
## 唯一的公式入口
## base —— attributes.json 的基准值
## mods —— [{"mode": "flat"|"pct", "value": float}, ...];调用方负责只传本属性的加成
## attr_def —— attributes.json 中该属性的定义(读 combine / hard
## 返回统一为 floatadd_int 属性由调用方做 int() 转换(公式模块不感知目标字段类型)
static func compute(base: float, mods: Array, attr_def: Dictionary) -> float:
var combine: Combine = combine_from_string(String(attr_def.get("combine", "hybrid")))
var hard: float = float(attr_def.get("hard", 0.0))
var flat_sum: float = 0.0
var pct_prod: float = 1.0
for m in mods:
var mode: String = String(m.get("mode", "flat"))
var v: float = float(m.get("value", 0.0))
if mode == "flat":
flat_sum += v
elif mode == "pct":
if combine == Combine.ADD_INT:
push_error("AttributeFormula: add_int 属性不接受 pct 加成(value=%f),已忽略" % v)
continue
# 连乘而非线性求和:三条 +20% = ×1.728 而非 ×1.6,避免后期线性失控
pct_prod *= (1.0 + v) if combine == Combine.HYBRID else (1.0 - v)
else:
push_error("AttributeFormula: 未知 mode「%s」,已忽略" % mode)
match combine:
Combine.ADD_INT:
var ri: float = floorf(base + flat_sum)
return clampf(ri, 0.0, hard) if hard > 0.0 else maxf(0.0, ri)
Combine.INVERSE:
# 越低越快:hard 是**下限**
return maxf(hard, (base + flat_sum) * pct_prod)
_:
var r: float = maxf(0.0, (base + flat_sum) * pct_prod)
return minf(r, hard) if hard > 0.0 else r # hard=0.0 约定为「不钳制」
+1
View File
@@ -0,0 +1 @@
uid://c41seas4fosx2