fix(shop): 可售属性排除未接线项,修正路线图「零代码」表述

get_sellable_attrs() 此前只看 attributes.json 是否有 shop 段,不看 PlayerStats
是否真的实现了该属性。运行时实测:给一个合成属性加 shop 段(不改任何代码)会被列出、
可买、扣钱、购买计数增加,但 get_attr_value 永远读到 0.00——玩家买到的是空气,
且 _at_soft_cap 因为 0.0 < soft 永远不封顶,变相无限花钱买无效果。

PlayerStats 新增 has_attr(attr_id) 谓词(查 _attr_effective,只有 _recompute_attrs
真正算过的属性才算数,不是查 _attr_def 有没有这一节);get_sellable_attrs 用它做
第二道过滤。诊断特意拆进独立函数 _report_unwired_shop_attrs 才调用 push_error+assert——
实测 assert(false) 在本项目运行环境下会让「当前函数」提前返回声明类型的默认值:若诊断写
在收集循环里,一旦命中就会让 get_sellable_attrs 本身连同已收集好的合法属性一起返回空数组,
比原来的「静默卖空气」更糟(整个货架消失);拆成独立函数调用后,中断只发生在诊断函数
自己的调用帧内,调用方(get_sellable_attrs)仍会正常继续并返回过滤后的正确列表。
已注入未接线属性验证:4 个合法属性照常出现,注入项被排除,无一致性问题。

顺带订正路线图 docs_dev/plans/2026-07-23-missing-features-roadmap.md 里「加 shop 段
即可上架、零代码」的表述——该结论只在属性已先接入 PlayerStats 框架(player_stats.gd
的裸字段 + _recompute_attrs 分支 + _attr_effective 条目,以及 attribute_tab.gd 的
ATTR_ORDER)之后才成立,E3-① 延后的 7 个属性都还没有这一步;同时记录商店属性子面板
当前坐标最多容纳 6 行的限制,供后续实现者提前规划。
This commit is contained in:
2026-08-03 12:51:13 +08:00
parent 56d7eefdc8
commit 5889b3c1e5
3 changed files with 42 additions and 4 deletions
+9
View File
@@ -263,3 +263,12 @@ func get_attr_value(attr_id: String) -> float:
push_error("PlayerStats: 未知属性「%s」,无生效值" % attr_id)
return 0.0
return float(_attr_effective[attr_id])
## 该属性是否已在框架内实装(即 _recompute_attrs 会为它算出生效值)。
## 供 ShopManager 等外部读者判定:只有 attributes.json 有 shop 段还不够卖——
## 若该属性根本没接进 PlayerStats(无裸字段/无 _recompute_attrs 分支/无 _attr_effective 条目),
## 卖出的加成会调用 add_modifier 写入 _modifiers 但永远没有对应的 _compute_attr 分支读取它,
## 玩家花钱买了一个不生效的空气条目。用 _attr_effective(而非 _attr_def)判定,因为
## _attr_def 只反映 JSON 有没有这一节、不反映代码有没有真的消费它。
func has_attr(attr_id: String) -> bool:
return _attr_effective.has(attr_id)