## VERTICAL SLICE - NOT FOR PRODUCTION ## 商店表 schema:GP 掉落/关级加值/商品/共享池货架行族 + 爬价因子的类型化读取(行值 provisional;垂直切片验证:商店房 GP 购买兑现局内成长)。 ## 消费面一律走 ConfigDB.shop_* 访问器,禁直读行集。 ## 行族不记库存:货架=上限即库存,账本单点在冒险侧;products+加法定价=legacy 面(shop=off 对照),现役走 pool_shelf。 class_name ShopTable extends Resource @export var gp_drops: Array[Dictionary] = [] # Array[Dictionary] 键 tier @export var level_gp_bonus: Array[Dictionary] = [] # Array[Dictionary] 键 level_index @export var products: Array[Dictionary] = [] # Array[Dictionary] 键 product_id @export var price_level_bonus: Array[Dictionary] = [] # Array[Dictionary] 键 level_index @export var pool_shelf: Array[Dictionary] = [] # Array[Dictionary] 键 kind_id @export var price_a: float = 0.0 # 进店已购次数爬价因子(provisional——0 = 未投值即致命域) @export var price_b: float = 0.0 # 跨关递增因子(provisional) @export var relic_base_price: float = 15.0 # relic 保底卡基价(provisional——池令牌非货架种,词表外专枝) @export var spend_discount: float = 0.8 # 财富之道·散 economy_spend 激活时的购买折扣(provisional) const GP_TIERS := ["normal", "elite", "boss"] const PRODUCT_ATTRS := ["atk", "max_hp", "armor_class"] const SHELF_KINDS := ["attack", "max_hp", "armor", "ap", "feat_ticket", "heal"] # 货架候选词表(四切边后种 + 专长凭证行 + heal 补回复〔默认原则:分类内成员都入该渠道池〕) func validate() -> Array: var errs: Array[Dictionary] = [] _validate_families(errs) _validate_gp_drops(errs) _validate_level_bonus(errs) _validate_products(errs) _validate_pool_shelf(errs) _validate_price_factors(errs) return errs # 行族空护栏(legacy 四轴 + pool_shelf 皆为 ConfigDB 消费面——缺族即致命) func _validate_families(errs: Array[Dictionary]) -> void: for family in ["gp_drops", "level_gp_bonus", "products", "price_level_bonus", "pool_shelf"]: if get(family).is_empty(): errs.append({"category": "empty_table", "message": "ShopTable 行族为空: %s" % family}) # gp_drops:tier 词表封闭 + 掷定域 + 均值锚 + tier 唯一 func _validate_gp_drops(errs: Array[Dictionary]) -> void: var seen_tier := {} for row in gp_drops: var tier: String = row.get("tier", "") if not GP_TIERS.has(tier): errs.append({"category": "enum", "message": "gp_drops tier 出域 {normal, elite, boss}: %s" % tier}) if seen_tier.has(tier): errs.append({"category": "dup_key", "message": "gp_drops tier 重复: %s" % tier}) seen_tier[tier] = true if String(row.get("display_name", "")) == "": errs.append({"category": "domain", "message": "gp_drops %s display_name 空" % tier}) var rmin: int = int(row.get("roll_min", -1)) var mean: int = int(row.get("mean", -1)) var rmax: int = int(row.get("roll_max", -1)) if rmin < 0 or mean < 0 or rmax < 0 or rmax > 999: errs.append({"category": "domain", "message": "gp_drops %s 掷定域越域 [0,999]: %d..%d..%d" % [tier, rmin, mean, rmax]}) if rmin > mean or mean > rmax: errs.append({"category": "cross_field", "message": "gp_drops %s 违反 roll_min(%d) ≤ mean(%d) ≤ roll_max(%d)——均值恒等期望锚" % [tier, rmin, mean, rmax]}) # level_gp_bonus / price_level_bonus:level_index 连续从 1 递增 + 加值域 func _validate_level_bonus(errs: Array[Dictionary]) -> void: for family in ["level_gp_bonus", "price_level_bonus"]: var arr: Array[Variant] = get(family) var seen := {} for row in arr: var li: int = int(row.get("level_index", 0)) if seen.has(li): errs.append({"category": "dup_key", "message": "%s level_index 重复: %d" % [family, li]}) seen[li] = true var bonus: int = int(row.get("bonus", -1)) if bonus < 0 or bonus > 999: errs.append({"category": "domain", "message": "%s level %d bonus 越域 [0,999]: %d" % [family, li, bonus]}) for i in arr.size(): var li: int = int(arr[i].get("level_index", 0)) if li != i + 1: errs.append({"category": "structural", "message": "%s level_index 不连续(须从 1 连续递增): 位 %d = %d" % [family, i + 1, li]}) break # products:product_id 唯一 + attr 词表 + 价格域 [1,999] func _validate_products(errs: Array[Dictionary]) -> void: var seen_pid := {} for row in products: var pid: String = row.get("product_id", "") if pid == "": errs.append({"category": "domain", "message": "products product_id 空"}) continue if seen_pid.has(pid): errs.append({"category": "dup_key", "message": "products product_id 重复: %s" % pid}) seen_pid[pid] = true if String(row.get("display_name", "")) == "": errs.append({"category": "domain", "message": "%s display_name 空" % pid}) var attr: String = row.get("attr", "") if not PRODUCT_ATTRS.has(attr): errs.append({"category": "enum", "message": "%s attr 出域 {atk, max_hp, armor_class}: %s" % [pid, attr]}) var flat: int = int(row.get("flat_value", 0)) if flat < 1 or flat > 999: errs.append({"category": "domain", "message": "%s flat_value 越域 [1,999]: %d" % [pid, flat]}) var base: int = int(row.get("base_price", 0)) if base < 1 or base > 999: errs.append({"category": "domain", "message": "%s base_price 越域 [1,999]: %d" % [pid, base]}) var inc: int = int(row.get("price_increment", -1)) if inc < 0 or inc > 999: errs.append({"category": "domain", "message": "%s price_increment 越域 [0,999]: %d" % [pid, inc]}) # pool_shelf:kind_id 词表封闭(四切边后种)+ 唯一 + display_name + base_price 域 [1,999] func _validate_pool_shelf(errs: Array[Dictionary]) -> void: var seen_kind := {} for row in pool_shelf: var kind: String = row.get("kind_id", "") if not SHELF_KINDS.has(kind): errs.append({"category": "enum", "message": "pool_shelf kind_id 出域(四切边后种封闭词表): %s" % kind}) if seen_kind.has(kind): errs.append({"category": "dup_key", "message": "pool_shelf kind_id 重复: %s" % kind}) seen_kind[kind] = true if String(row.get("display_name", "")) == "": errs.append({"category": "domain", "message": "pool_shelf %s display_name 空" % kind}) var base: int = int(row.get("base_price", 0)) if base < 1 or base > 999: errs.append({"category": "domain", "message": "pool_shelf %s base_price 越域 [1,999]: %d" % [kind, base]}) # 价格因子域(乘法语义——A/b 均 ≥1 单调不降;上界防手滑投值;provisional 值本体归难度曲线与Boss) func _validate_price_factors(errs: Array[Dictionary]) -> void: if price_a < 1.0 or price_a > 3.0: errs.append({"category": "domain", "message": "price_a 越域 [1.0,3.0]: %f" % price_a}) if price_b < 1.0 or price_b > 2.0: errs.append({"category": "domain", "message": "price_b 越域 [1.0,2.0]: %f" % price_b}) if relic_base_price < 1.0 or relic_base_price > 99.0: errs.append({"category": "domain", "message": "relic_base_price 越域 [1.0,99.0]: %f" % relic_base_price}) if spend_discount <= 0.0 or spend_discount > 1.0: errs.append({"category": "domain", "message": "spend_discount 越域 (0.0,1.0]: %f" % spend_discount})