Files
gplan-2/game/data/schemas/treasure_item_table.gd
T
DKChan 3333187351 feat(close): 迭代20 T5+T8+T9+T10 C段收尾(专长实装批)
T5 Boss 兑付面换代:通关渠道三选一改按档配比加权抽取(无放回加权抽 boss_feat_weight
100/45/15/5 provisional 归调参批——奇物.md v3.1 rarity 语义约束兑付面);信号注释换代
「提升三选一」→「专长三选一」(V11 正名);V12 频带断言(300 停点档位频率出带±15pp)。

T8 MC 对账适配:formed 画像 14 旧件→五事件族 14 新件(开格 4/插旗 3/踩雷 4/节奏 2/战斗 1);
新基线取证:normal/off 100 局 71000 档 通关 0%(master 对照同为 0%=零配基线本相),
止步分布 90/10→93/7 可归因曝光槽候选构成;同种子重放 30 局逐位复现 ✓(V15)。

T9 迁出收尾:treasure_items 删 venom_edge/flag_burst 两行(同调目录收窄);schema 词面
venom_rider/flag_burst 摘除(EFFECT_KINDS/MAGNITUDE_KINDS);rewrite_board 效果类型除名
(净化工兵退役——V14 连带清理闭环);V13 断言入 smoke。

T10 空配置验收闸:三选一池空 skip_empty_curio 消化+非空拒绝反例+商店空货架合法态断言。

测试:pool_case_d 17 checks(+V12/T10)/feat_vouchers 23 checks(+V16 断点往返);
冒烟 864/0+run_all 21 套全绿。
2026-10-12 00:28:22 +08:00

211 lines
14 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: 挖掘期撞上推理断头时,玩家能否从掉落池捡到秘宝、在关键时刻花掉买回情报或行动点(P0)
# Date: 2026-09-29(迭代 7 T1 数据面——秘宝内容批;设计权威 design/gdd/systems/排雷循环/秘宝.md v0.4+
# + registry 秘宝条目六行 mvp_content;2026-09-29 用户拍板:①乙 探宝罗盘退役留痕 ④甲 显影尘/地穴全图卷失义件
# 六行全建零投放「已解锁·能力封存中」)
# 数据配置 TreasureItemTable schema(秘宝 回填 owner)。
# 行 = Dictionary,键 item_id(Rule 4 豁免:定义表——registry 秘宝条目 Rule 4 豁免先例)。
# 七字段(registry fields 全量):item_id 主键 / display_name / dimension_tags ⊆ 五维 非空 /
# effect_kinds ⊆ {mine_reveal, reward_preview, ap_grant}(活性校验见下)/ target ∈ {none, area_3x3, board_wide} /
# magnitude [0,999](仅 ap_grant 行消费,加载校验 ≥1)/ use_phase ∈ {active_dig}。
# additive 两标记(迭代 7 T1——拍板①④的表内表达,非 registry fields 预登记字段的扩列随批登记):
# retired = 退役留痕行(loot_compass——未开格类型微光常显覆盖预读价值,2026-09-29 用户拍板退役;
# 行名保留=meta.tres workshop_items 六件词表恰全校验兼容,存档 item_unlocks 旧键无害;
# 免活性校验、不入投放校验、不入 F2 覆盖面、奖励节点/掉落池排除)
# deferred = 效果失义待重定行(probe_dust/catacomb_scroll——原显影半边随雾格移除失义,效果与目标
# 随秘宝整体重设计归 Alpha(2026-09-20 用户拍板);免 effect_kinds 非空校验、不入投放与 F2)
# 承重护栏(秘宝 GDD §结构冻结,schema 校验强制不靠运行时拦截):
# 真视窄域——effect_kinds 含 mine_reveal 的活行 target 必须 area_3x3(真视一次最多 3×3,禁全图看雷);
# board_wide 仅恢复推理原料类——target=board_wide 的活行 effect_kinds 不得含 mine_reveal(现行无此类活行,
# 显影载体待 Alpha 重定后落位)。
# 同调实施批扩列(迭代 11 第①批 T1,2026-10-03——需求卡片 v1.1;设计权威 proposal-magic-item-attunement-2026-09-22):
# form ∈ {"", "passive", "active_charged"}(空 = 切片期过渡形态行——掉落/停点渠道现行承载,T7 退役;
# passive = 装配整局生效;active_charged = 每冒险充能、挖掘期发起);
# charges_per_adventure(active_charged ≥1 / passive 恒 0);slot_cost(同调行恒 1——底稿裁决 5,schema 强制);
# unlock_requires 谓词行 [{key ∈ AttunementGrowthTable.COUNTER_KEYS, threshold ∈ [1,999]}](空 = 已解锁——
# 首批全解锁合法态,底稿 §2.2 用户裁;正式解锁渠道归 元进度 道具工坊柜台);
# effective_rounds(前 N 轮限时件轮数——P4 甲表值化;仅 ap_rounds_front 行消费);
# charge_n(充能进度阈值 N——仅 ap_charge_safe 行消费)。
# 🔴 零裸 flat 结构护栏(需求卡片 §三 1——机制轴禁无条件属性加项):行键白名单封闭(KEY_WHITELIST 外键 =
# 加载致命)+ effect_kinds 封闭枚举只收条件触发/机制修正类 kind——任何「无条件属性加项」类效果不得以
# 新 kind 或新键入表(返工红线)。
class_name TreasureItemTable
extends Resource
@export var rows: Array = [] # Array[Dictionary] 键 item_id
const DIMENSIONS := ["info", "risk", "action", "plan", "counter"]
# 同调批扩 enum(迭代 11 第①批 T1)——新增九 kind 全部条件触发/机制修正类;mine_reveal/ap_grant 语义不变。
# cell_removal = 爆破工兵包中立移除格(T7 原位换装时启用——T6 影响评估前置);mine_hit_halve 系固定
# 「减半」语义不消费 magnitude(反弹 N 由 reflect_damage 行 magnitude 承载)。
# 内容数据驱动扩展批 T2 挂账:主动件 kind 词族换代(reveal_tiles/preview/grant_ap/remove_mines/
# refresh_pick——效果引擎 ACTIVE_KINDS 同名闭集),被动件 effect_kinds 词族随 T4 秘宝迁移退役
# (passive 行改 triggers 事件订阅形态);旧词保留共存至迁移完成。
const EFFECT_KINDS := ["mine_reveal", "reward_preview", "ap_grant",
"mine_hit_halve", "reflect_damage", "ap_rounds_front", "ap_charge_safe", "cell_removal",
"reward_weight_bias", "bonus_refresh",
"reveal_tiles", "preview", "grant_ap", "remove_mines", "refresh_pick"]
const TARGETS := ["none", "area_3x3", "board_wide"]
const USE_PHASES := ["active_dig"]
const FORMS := ["", "passive", "active_charged"]
# magnitude 消费面(≥1 校验):grant_ap 与同调 N 值 kind;其余 kind 活行 magnitude 恒 0
const MAGNITUDE_KINDS := ["ap_grant", "reflect_damage", "ap_rounds_front",
"ap_charge_safe", "reward_weight_bias", "bonus_refresh", "grant_ap"]
# 🔴 零裸 flat 结构护栏:行键白名单封闭(键出域=加载致命)——机制轴禁无条件属性加项的表结构承载
const KEY_WHITELIST := ["item_id", "display_name", "dimension_tags", "effect_kinds", "target",
"magnitude", "use_phase", "retired", "deferred", "form", "charges_per_adventure", "slot_cost",
"unlock_requires", "effective_rounds", "charge_n", "triggers", "desc"]
func validate() -> Array:
var errs: Array = []
if rows.is_empty():
# 空表合法态(2026-09-17 用户拍板——切片期无道具内容;F2 覆盖检查仅对非空清单执行)
return errs
var seen := {}
for row in rows:
var iid: String = row.get("item_id", "")
if iid == "":
errs.append({"category": "domain", "message": "item_id 空"})
continue
if seen.has(iid):
errs.append({"category": "dup_key", "message": "item_id 重复: %s" % iid})
continue
seen[iid] = true
if String(row.get("display_name", "")) == "":
errs.append({"category": "domain", "message": "%s display_name 空" % iid})
var retired: bool = bool(row.get("retired", false))
var deferred: bool = bool(row.get("deferred", false))
# dimension_tags:五维词表 + 非空(退役/失义行仍须登记维度——语义面保留)
var tags: Array = row.get("dimension_tags", [])
if tags.is_empty():
errs.append({"category": "domain", "message": "%s dimension_tags 空" % iid})
for tag in tags:
if not DIMENSIONS.has(String(tag)):
errs.append({"category": "enum", "message": "%s dimension_tag 出域 %s: %s" % [iid, str(DIMENSIONS), str(tag)]})
var kinds: Array = row.get("effect_kinds", [])
var target: String = String(row.get("target", "none"))
if not TARGETS.has(target):
errs.append({"category": "enum", "message": "%s target 出域 %s: %s" % [iid, str(TARGETS), target]})
var phase: String = String(row.get("use_phase", "active_dig"))
if not USE_PHASES.has(phase):
errs.append({"category": "enum", "message": "%s use_phase 出域 %s: %s" % [iid, str(USE_PHASES), phase]})
# 🔴 零裸 flat 结构护栏:键白名单封闭(同调批 T1——机制轴禁无条件属性加项)
for k in row.keys():
if not KEY_WHITELIST.has(String(k)):
errs.append({"category": "zero_flat", "message": "%s 行键出白名单: %s(KEY_WHITELIST 外键禁入)" % [iid, String(k)]})
# triggers 事件订阅行(内容数据驱动扩展批 T2/T4——被动件换代形态;与 effect_kinds 互斥校验
# 面=passive 行(换代单形态——效果行为只走订阅);active_charged 行两形合法并存
# (effect_kinds=主动分发通道 + triggers=受理闸订阅,掘进蓄能先例))
if row.has("triggers"):
if (row.get("effect_kinds", []) as Array).size() > 0 and String(row.get("form", "")) != "active_charged" and not bool(row.get("retired", false)):
errs.append({"category": "domain", "message": "%s triggers 与 effect_kinds 并存(换代单形态——被动件迁移期禁双挂)" % iid})
EffectEngine.validate_trigger_row(iid, row, row.get("triggers", []) as Array, errs)
var form: String = String(row.get("form", ""))
if not FORMS.has(form):
errs.append({"category": "enum", "message": "%s form 出域 %s: %s" % [iid, str(FORMS), form]})
if retired or deferred:
# 退役/失义行:免 effect_kinds 非空与承重护栏校验(登记面保留,不入投放与 F2)
continue
# ---- 活行校验 ----
if kinds.is_empty() and not row.has("triggers"):
errs.append({"category": "domain", "message": "%s effect_kinds 空(活行;失义件应标 deferred,被动件应挂 triggers 订阅)" % iid})
var kind_seen := {}
for k in kinds:
var kind: String = String(k)
if not EFFECT_KINDS.has(kind):
errs.append({"category": "enum", "message": "%s effect_kind 出域 %s: %s" % [iid, str(EFFECT_KINDS), kind]})
if kind_seen.has(kind):
errs.append({"category": "dup_key", "message": "%s effect_kind 重复: %s" % [iid, kind]})
kind_seen[kind] = true
# 承重护栏①真视窄域:mine_reveal 活行 target 必须 area_3x3(一次最多 3×3,禁全图看雷)
if kind_seen.has("mine_reveal") and target != "area_3x3":
errs.append({"category": "load_bearing", "message": "%s 真视窄域护栏:mine_reveal 行 target 必须 area_3x3,实得 %s(秘宝 GDD 结构冻结)" % [iid, target]})
# 承重护栏②board_wide 仅恢复推理类:全图域行禁替推理效果
if target == "board_wide" and kind_seen.has("mine_reveal"):
errs.append({"category": "load_bearing", "message": "%s board_wide 行 effect_kinds 不得含 mine_reveal(全图域仅恢复推理原料类——Alpha 重定后落位)" % iid})
# magnitude:MAGNITUDE_KINDS 行消费(≥1);其余活行恒 0(同调批 T1——消费面自 ap_grant 扩至 N 值 kind)
var mag: int = int(row.get("magnitude", 0))
if mag < 0 or mag > 999:
errs.append({"category": "domain", "message": "%s magnitude 越域 [0,999]: %d" % [iid, mag]})
var mag_consuming := false
for k in kind_seen.keys():
if MAGNITUDE_KINDS.has(String(k)):
mag_consuming = true
if mag_consuming and mag < 1:
errs.append({"category": "cross_field", "message": "%s N 值 kind 行 magnitude 应 ≥1,实得 %d" % [iid, mag]})
if not mag_consuming and mag != 0 and not row.has("triggers"):
errs.append({"category": "cross_field", "message": "%s 非 N 值 kind 活行 magnitude 应 0(trigger 行 magnitude=value_from 取数源豁免),实得 %d" % [iid, mag]})
# ---- 同调行约束(form 非空——装配面;过渡行 form="" 不校验)----
if form != "":
if int(row.get("slot_cost", 1)) != 1:
errs.append({"category": "load_bearing", "message": "%s 同调占位统一 1 槽(slot_cost 恒 1——底稿裁决 5,schema 强制)" % iid})
var charges: int = int(row.get("charges_per_adventure", 0))
if form == "passive" and charges != 0:
errs.append({"category": "cross_field", "message": "%s passive 行 charges_per_adventure 应 0,实得 %d" % [iid, charges]})
if form == "active_charged" and charges < 1:
errs.append({"category": "cross_field", "message": "%s active_charged 行 charges_per_adventure 应 ≥1,实得 %d" % [iid, charges]})
var eff_rounds: int = int(row.get("effective_rounds", 0))
if eff_rounds < 0 or eff_rounds > 999:
errs.append({"category": "domain", "message": "%s effective_rounds 越域 [0,999]: %d" % [iid, eff_rounds]})
if kind_seen.has("ap_rounds_front") and eff_rounds < 1:
errs.append({"category": "cross_field", "message": "%s ap_rounds_front 行 effective_rounds 应 ≥1(P4 甲表值化),实得 %d" % [iid, eff_rounds]})
if not kind_seen.has("ap_rounds_front") and eff_rounds != 0 and not row.has("triggers"):
errs.append({"category": "cross_field", "message": "%s 非 ap_rounds_front 行 effective_rounds 应 0(trigger 行=when value_from 取数源豁免),实得 %d" % [iid, eff_rounds]})
var charge_n: int = int(row.get("charge_n", 0))
if charge_n < 0 or charge_n > 999:
errs.append({"category": "domain", "message": "%s charge_n 越域 [0,999]: %d" % [iid, charge_n]})
if kind_seen.has("ap_charge_safe") and charge_n < 1:
errs.append({"category": "cross_field", "message": "%s ap_charge_safe 行 charge_n 应 ≥1(每次充能需 N 安全格),实得 %d" % [iid, charge_n]})
if not kind_seen.has("ap_charge_safe") and charge_n != 0:
errs.append({"category": "cross_field", "message": "%s 非 ap_charge_safe 行 charge_n 应 0,实得 %d" % [iid, charge_n]})
# unlock_requires 谓词行:空 = 已解锁(首批全解锁合法态,底稿 §2.2);非空键 ∈ 计数键封闭枚举
var ureqs: Array = row.get("unlock_requires", [])
var ukeys := {}
for req in ureqs:
var ukey: String = String(req.get("key", ""))
if not AttunementGrowthTable.COUNTER_KEYS.has(ukey):
errs.append({"category": "enum", "message": "%s unlock_requires 计数键出域 %s: %s" % [iid, str(AttunementGrowthTable.COUNTER_KEYS), ukey]})
if ukeys.has(ukey):
errs.append({"category": "dup_key", "message": "%s unlock_requires 计数键重复: %s" % [iid, ukey]})
ukeys[ukey] = true
var uth: int = int(req.get("threshold", 0))
if uth < 1 or uth > 999:
errs.append({"category": "domain", "message": "%s unlock_requires 阈值越域 [1,999]: %d" % [iid, uth]})
return errs
# 同调行词表(form 非空)——装配覆盖层/效果接线/同调位上限的唯一取数口径(迭代 11 第①批 T1)
func attunement_rows() -> Array:
var out: Array = []
for row in rows:
if String(row.get("form", "")) != "" and not bool(row.get("retired", false)) and not bool(row.get("deferred", false)):
out.append(row)
return out
# 过渡行词表(form 空 = 切片期过渡形态活行)——掉落投放/停点池/过渡覆盖面的取数口径;
# 同调行永不入过渡渠道。T7 退役面切换后本口径随渠道消亡。
func transitional_rows() -> Array:
var out: Array = []
for row in rows:
if String(row.get("form", "")) == "" and not bool(row.get("retired", false)) and not bool(row.get("deferred", false)):
out.append(row)
return out
# 活行词表(非 retired 非 deferred)——投放校验/奖励节点池/F2 覆盖面的唯一取数口径
func active_rows() -> Array:
var out: Array = []
for row in rows:
if not bool(row.get("retired", false)) and not bool(row.get("deferred", false)):
out.append(row)
return out
func row_by_id(item_id: String) -> Dictionary:
for row in rows:
if String(row.get("item_id", "")) == item_id:
return row
return {}