## VERTICAL SLICE - NOT FOR PRODUCTION ## 锻造工坊表 schema:两根类型轴默认值 + 六条锻造路线行族的类型化读取(数值 provisional)。 ## 所有权切分(红线):攻击唯一入口=武器路线、AC 唯一入口=甲路线——树零攻防来源,本表是攻防数值成长唯一数据面。 ## 表运行期只读(铁律):永久强化只写存档 forge_routes 域,本表零写面。 class_name ForgeTable extends Resource # 路线 id 词表(代拍板立项:甲路线 id=甲型值本身——ward=魔法护甲,3a 裁统一命名) const WEAPON_IDS := ["sword", "spear", "staff"] const ARMOR_IDS := ["light", "heavy", "ward"] const ROUTE_IDS := ["sword", "spear", "staff", "light", "heavy", "ward"] # 两根类型轴枚举域(hero.tres 静态表两轴随本批删除——词表归本表常驻持有) const ATTACK_TYPES := ["normal", "pierce", "magic"] const ARMOR_TYPES := ["light", "ward", "heavy"] # 行字段闭集(V11:满级特效本期不做——行族无特效参数位;键集出域即拒) const ROW_KEYS := ["route_id", "display_name", "kind", "type_axis", "cap", "per_level_gain", "base_ac", "base_price", "price_ratio_pct", "initial_owned", "prereq_route", "unlock_price"] @export var default_attack_type: String = "normal" # 攻击类型轴默认值(常驻持有——与柜台解锁状态解耦:未点亮=无交互入口但默认值照常注入) @export var default_armor_type: String = "light" # 甲型轴默认值(常驻持有) @export var routes: Array[Dictionary] = [] # 六路线行族(键 route_id) # 各级价格(几何递增:基础价 × 150%^已购级数——锚表 20/30/45/67/101;1.5^n 二进制精确,无浮点尘) static func level_price(row: Dictionary, owned_levels: int) -> int: var base := int(row.get("base_price", 0)) var ratio := float(int(row.get("price_ratio_pct", 100))) / 100.0 return int(floor(float(base) * pow(ratio, maxi(0, owned_levels)))) func validate() -> Array: var errs: Array[Dictionary] = [] # 默认两轴枚举域 if not ATTACK_TYPES.has(default_attack_type): errs.append({"category": "enum", "message": "default_attack_type 出域 %s: %s" % [ATTACK_TYPES, default_attack_type]}) if not ARMOR_TYPES.has(default_armor_type): errs.append({"category": "enum", "message": "default_armor_type 出域 %s: %s" % [ARMOR_TYPES, default_armor_type]}) # 行族规模恰六(三武器+三甲——需求卡片 §1①) if routes.size() != 6: errs.append({"category": "structural", "message": "锻造路线行族规模 %d ≠ 6(三武器+三甲)" % routes.size()}) var seen := {} var type_seen := {} var kind_by_id := {} var initial_ids := {} for row in routes: # 预扫:route_id→kind 全集(前置反查需前向引用——轻甲行在长枪行之后) kind_by_id[String(row.get("route_id", ""))] = String(row.get("kind", "")) for row in routes: var rid := String(row.get("route_id", "")) if not ROUTE_IDS.has(rid): errs.append({"category": "enum", "message": "锻造路线 route_id 出域 %s: %s" % [ROUTE_IDS, rid]}) if seen.has(rid): errs.append({"category": "dup_key", "message": "锻造路线 route_id 重复: %s" % rid}) seen[rid] = true var kind := String(row.get("kind", "")) if not ["weapon", "armor"].has(kind): errs.append({"category": "enum", "message": "%s kind 出域 {weapon, armor}: %s" % [rid, kind]}) continue # 类型轴:武器=attack_type 域、甲=armor_type 域;每型恰一路线(三武器三甲各一) var axis := String(row.get("type_axis", "")) var domain: Array[Variant] = ATTACK_TYPES if kind == "weapon" else ARMOR_TYPES if not domain.has(axis): errs.append({"category": "enum", "message": "%s type_axis 出域 %s: %s" % [rid, domain, axis]}) var type_key := "%s|%s" % [kind, axis] if type_seen.has(type_key): errs.append({"category": "dup_key", "message": "%s 类型轴重复(每型恰一路线): %s %s" % [kind, axis, rid]}) type_seen[type_key] = true # 等级/增量/价格/基础 AC 值域 var cap_v: int = int(row.get("cap", -1)) if cap_v < 1 or cap_v > 9: errs.append({"category": "domain", "message": "%s cap 越域 [1,9]: %d" % [rid, cap_v]}) var gain: int = int(row.get("per_level_gain", -1)) if gain < 0 or gain > 9: errs.append({"category": "domain", "message": "%s per_level_gain 越域 [0,9]: %d" % [rid, gain]}) var base_ac: int = int(row.get("base_ac", -1)) if kind == "armor": if base_ac < 0 or base_ac > 999: errs.append({"category": "domain", "message": "%s base_ac 越域 [0,999]: %d" % [rid, base_ac]}) elif base_ac != 0: errs.append({"category": "cross_field", "message": "%s 武器行 base_ac 须为 0(甲型基础 AC 只属护甲路线): %d" % [rid, base_ac]}) var base_price: int = int(row.get("base_price", -1)) if base_price < 1 or base_price > 999: errs.append({"category": "domain", "message": "%s base_price 越域 [1,999]: %d" % [rid, base_price]}) var ratio: int = int(row.get("price_ratio_pct", -1)) if ratio < 100 or ratio > 500: errs.append({"category": "domain", "message": "%s price_ratio_pct 越域 [100,500]: %d" % [rid, ratio]}) var unlock_price: int = int(row.get("unlock_price", -1)) if unlock_price < 0 or unlock_price > 999: errs.append({"category": "domain", "message": "%s unlock_price 越域 [0,999]: %d" % [rid, unlock_price]}) # 解锁通道(并联双通道):初始持有行无前置无解锁价;待解锁行须有一次性解锁价+合法前置 var initial: bool = bool(row.get("initial_owned", false)) var prereq := String(row.get("prereq_route", "")) if initial: if prereq != "": errs.append({"category": "cross_field", "message": "%s 初始持有行不得带前置: %s" % [rid, prereq]}) if unlock_price != 0: errs.append({"category": "cross_field", "message": "%s 初始持有行解锁价须为 0: %d" % [rid, unlock_price]}) initial_ids[rid] = kind else: if unlock_price < 1: errs.append({"category": "cross_field", "message": "%s 待解锁行须有一次性解锁价(并联双通道②): %d" % [rid, unlock_price]}) if not ROUTE_IDS.has(prereq): errs.append({"category": "enum", "message": "%s prereq_route 出域 %s: %s" % [rid, ROUTE_IDS, prereq]}) elif prereq == rid: errs.append({"category": "cross_field", "message": "%s prereq_route 不得自指" % rid}) elif kind_by_id.has(prereq) and String(kind_by_id[prereq]) != kind: errs.append({"category": "cross_field", "message": "%s 前置 %s 类型不符(武器←武器/甲←甲): %s vs %s" % [rid, prereq, kind, String(kind_by_id[prereq])]}) # 初始持有恰两行且一武器一甲(长剑/轻甲——语义继承自已退役属性锻造行) var init_weapons := 0 var init_armors := 0 for k in initial_ids.keys(): if String(initial_ids[k]) == "weapon": init_weapons += 1 elif String(initial_ids[k]) == "armor": init_armors += 1 if init_weapons != 1 or init_armors != 1: errs.append({"category": "coverage", "message": "初始持有须恰一武器一甲(现 sword/light): 武器 %d / 甲 %d" % [init_weapons, init_armors]}) # 默认轴=初始持有路线类型轴(Q1 裸体态语义:未锻造=0 加值且类型=普通/轻甲) if init_weapons == 1 and init_armors == 1: for row in routes: if not bool(row.get("initial_owned", false)): continue if String(row.get("kind", "")) == "weapon" and String(row.get("type_axis", "")) != default_attack_type: errs.append({"category": "cross_field", "message": "default_attack_type 须=初始武器路线类型轴(裸体态普通): %s vs %s" % [default_attack_type, String(row.get("type_axis", ""))]}) if String(row.get("kind", "")) == "armor" and String(row.get("type_axis", "")) != default_armor_type: errs.append({"category": "cross_field", "message": "default_armor_type 须=初始甲路线类型轴(裸体态轻甲): %s vs %s" % [default_armor_type, String(row.get("type_axis", ""))]}) return errs