Files
gplan-2/game/data/schemas/attunement_growth_table.gd
T

82 lines
4.2 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VERTICAL SLICE - NOT FOR PRODUCTION
# Validation Question: 同调位增长是否由「通关次数 + Boss 击杀数」经数据驱动的里程碑谓词表实时求值,
# 试玩后调增长曲线 = 改表不动码(proposal-magic-item-attunement-2026-09-22 §2.1 用户点名要求)
# Date: 2026-10-03(迭代 11 第①批 秘宝同调实施批 T1——任务书 v1.1 §六 T1;设计权威底稿 proposal §2)
# 数据配置 AttunementGrowthTable schema(秘宝 回填 owner)。
# 单行全局常量表(slot_cap + milestones 行族——AdventureTable 单行全局先例,Rule 4 豁免)。
# 行 = Dictionary:{id: String 唯一, grant: int, requires: [{key: String, threshold: int}]}——
# requires 为 (计数键, 阈值) 对的 AND 列表,全部满足即授予 grant 槽;
# 同调位读数唯一口径 = Σ已达成 grant clamp slot_cap(slots())。
# 承重契约(任务书 §三 7 + 底稿 §2.1):
# 计数键封闭枚举(COUNTER_KEYS——预留可插拔位 COUNTER_KEYS_RESERVED 本期不实现、加行即扩展不破坏
# schema);阈值 [1,999];Σgrant ≤ slot_cap(超出部分永不可达,禁);数值全 provisional 归
# 难度曲线与Boss——本表只锁形态,试玩调平衡的全部动作落在表上(改阈值/换计数键/slot_cap 加行)。
# OR/复合需求出现时升级谓词形态走任务书修订(底稿 §2.1 同款约束)。
class_name AttunementGrowthTable
extends Resource
@export var slot_cap: int = 3
@export var milestones: Array = [] # Array[Dictionary] 键 id/grant/requires
# 计数键封闭枚举(底稿 §2.1——save_file 逐域记账的键词表;T2 落 boss_kill_count 记账)
const COUNTER_KEYS := ["clear_count", "boss_kill_count"]
# 预留可插拔位(本期不实现,schema 枚举不收——底稿 §2.1 明列四件)
const COUNTER_KEYS_RESERVED := ["boss_firstkill_count", "adventure_count", "no_damage_clear_count", "run_depth_best"]
func validate() -> Array:
var errs: Array = []
if slot_cap < 0 or slot_cap > 9:
errs.append({"category": "domain", "message": "slot_cap 越域 [0,9]: %d" % slot_cap})
var seen := {}
var grant_sum := 0
for row in milestones:
var mid: String = String(row.get("id", ""))
if mid == "":
errs.append({"category": "domain", "message": "milestone id 空"})
continue
if seen.has(mid):
errs.append({"category": "dup_key", "message": "milestone id 重复: %s" % mid})
continue
seen[mid] = true
var grant: int = int(row.get("grant", 0))
if grant < 1 or grant > maxi(1, slot_cap):
errs.append({"category": "domain", "message": "%s grant 越域 [1,slot_cap=%d]: %d" % [mid, slot_cap, grant]})
grant_sum += maxi(0, grant)
var reqs: Array = row.get("requires", [])
if reqs.is_empty():
errs.append({"category": "domain", "message": "%s requires 空(无条件授予非法——里程碑必有谓词)" % mid})
var key_seen := {}
for req in reqs:
var key: String = String(req.get("key", ""))
if not COUNTER_KEYS.has(key):
errs.append({"category": "enum", "message": "%s 计数键出域 %s: %s" % [mid, str(COUNTER_KEYS), key]})
if key_seen.has(key):
errs.append({"category": "dup_key", "message": "%s 计数键重复: %s" % [mid, key]})
key_seen[key] = true
var th: int = int(req.get("threshold", 0))
if th < 1 or th > 999:
errs.append({"category": "domain", "message": "%s 阈值越域 [1,999]: %d" % [mid, th]})
if grant_sum > slot_cap:
errs.append({"category": "cross_field", "message": "Σgrant(%d) > slot_cap(%d)——超出部分永不可达,schema 禁" % [grant_sum, slot_cap]})
return errs
# 实时求值:Σ已达成 grant(谓词 = requires 全键达标;counters = {计数键: int},缺键按 0 计)
func granted_slots(counters: Dictionary) -> int:
var total := 0
for row in milestones:
var ok := true
for req in row.get("requires", []):
if int(counters.get(String(req.get("key", "")), 0)) < int(req.get("threshold", 0)):
ok = false
break
if ok:
total += maxi(0, int(row.get("grant", 0)))
return total
# 同调位读数唯一口径:Σ达成 grant clamp slot_cap(求值服务/装配覆盖层/运行期上限共读此口径)
func slots(counters: Dictionary) -> int:
return mini(granted_slots(counters), slot_cap)