feat(迭代23-T1): 锻造工坊配置表落位——ForgeTable 新表(默认两轴常驻持有+六路线行族)+ConfigDB 访问器+V1/V11 单测 41 断言
- 新表 forge.tres(schema forge_table.gd):default_attack_type/armor_type 默认值(契约 2 所有权移交——hero.tres 两轴随后批删除)+routes 六行(sword/spear/staff/light/heavy/ward) - provisional 沿需求卡片 T1 锚表:cap 5/每级增量 +1/基础价 20×150%^n(20/30/45/67/101)/解锁价 200/甲型基础 AC 10-12-9/初始持有 sword+light/并联双通道前置 spear·staff←sword、heavy·ward←light - schema validate 22 类正反(行 id 唯一/类型轴分族枚举/恰六行/初始持有恰一武器一甲/默认轴=初始路线类型轴/前置类型相符…) - ConfigDB:TABLE_MANIFEST 登 forge+类型化持用+forge_route/forge_routes/forge_default_*/forge_weapon_route_by_type/forge_armor_route_by_type 访问器 - 新单测 forge_table_test 41 断言(V1 生产面+22 类负向/V11 行字段闭集+效果类型词表 31 钉现/V2 价格梯);run_all 22→23 套 - 事实订正(L2):卡片 F11「效果类型闭集 24」系 2026-10-07 旧快照——现行 31(iter20 +7/iter22 +1),本批零扩签钉 31 - 轻闸:冒烟 898/0 零扰动
This commit is contained in:
1 parent
b9cca33e9d
commit
df666ddf30
7 files changed
+477
No files matched your search
@@ -0,0 +1,131 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# Date: 2026-10-12(迭代 23 锻造工坊实装批 T1——设计权威 design/gdd/systems/跨局循环/锻造工坊.md v0.6)
|
||||
# 锻造工坊配置表(ForgeTable):两根类型轴默认值(常驻持有——契约 2)+ 六条锻造路线行族(三武器+三甲)。
|
||||
# 所有权切分(契约 1 红线):攻击唯一入口=武器路线、AC 唯一入口=甲路线——树零攻防来源,本表是攻防
|
||||
# 数值成长的唯一数据面。等级语义(Q1 用户裁):未解锁/未锻造=0 加值(裸体态,类型=默认轴值);
|
||||
# 解锁并锻造第一级=「获得装备」=1 级=首个 +1;逐级递进(2 级=+2 量级)。
|
||||
# 数值全 provisional(需求卡片 T1 锚表投值——终值归 M4 数值批):cap 5/每级增量统一 +1(v1.2 用户裁)/
|
||||
# 基础价 20/公比 150%(各级价格 20/30/45/67/101)/一次性解锁价 200/甲型基础 AC 10-12-9(现役结构值)。
|
||||
# 表运行期只读(铁律):永久强化只写存档(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 = 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
|
||||
@@ -0,0 +1 @@
|
||||
uid://08fuv4rzuos2h
|
||||
@@ -0,0 +1,98 @@
|
||||
[gd_resource type="Resource" script_class="ForgeTable" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://data/schemas/forge_table.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
default_attack_type = "normal"
|
||||
default_armor_type = "light"
|
||||
routes = [{
|
||||
"route_id": "sword",
|
||||
"display_name": "长剑锻造",
|
||||
"kind": "weapon",
|
||||
"type_axis": "normal",
|
||||
"cap": 5,
|
||||
"per_level_gain": 1,
|
||||
"base_ac": 0,
|
||||
"base_price": 20,
|
||||
"price_ratio_pct": 150,
|
||||
"initial_owned": true,
|
||||
"prereq_route": "",
|
||||
"unlock_price": 0
|
||||
}, {
|
||||
"route_id": "spear",
|
||||
"display_name": "长枪锻造",
|
||||
"kind": "weapon",
|
||||
"type_axis": "pierce",
|
||||
"cap": 5,
|
||||
"per_level_gain": 1,
|
||||
"base_ac": 0,
|
||||
"base_price": 20,
|
||||
"price_ratio_pct": 150,
|
||||
"initial_owned": false,
|
||||
"prereq_route": "sword",
|
||||
"unlock_price": 200
|
||||
}, {
|
||||
"route_id": "staff",
|
||||
"display_name": "法杖锻造",
|
||||
"kind": "weapon",
|
||||
"type_axis": "magic",
|
||||
"cap": 5,
|
||||
"per_level_gain": 1,
|
||||
"base_ac": 0,
|
||||
"base_price": 20,
|
||||
"price_ratio_pct": 150,
|
||||
"initial_owned": false,
|
||||
"prereq_route": "sword",
|
||||
"unlock_price": 200
|
||||
}, {
|
||||
"route_id": "light",
|
||||
"display_name": "轻甲锻造",
|
||||
"kind": "armor",
|
||||
"type_axis": "light",
|
||||
"cap": 5,
|
||||
"per_level_gain": 1,
|
||||
"base_ac": 10,
|
||||
"base_price": 20,
|
||||
"price_ratio_pct": 150,
|
||||
"initial_owned": true,
|
||||
"prereq_route": "",
|
||||
"unlock_price": 0
|
||||
}, {
|
||||
"route_id": "heavy",
|
||||
"display_name": "重甲锻造",
|
||||
"kind": "armor",
|
||||
"type_axis": "heavy",
|
||||
"cap": 5,
|
||||
"per_level_gain": 1,
|
||||
"base_ac": 12,
|
||||
"base_price": 20,
|
||||
"price_ratio_pct": 150,
|
||||
"initial_owned": false,
|
||||
"prereq_route": "light",
|
||||
"unlock_price": 200
|
||||
}, {
|
||||
"route_id": "ward",
|
||||
"display_name": "魔法护甲锻造",
|
||||
"kind": "armor",
|
||||
"type_axis": "ward",
|
||||
"cap": 5,
|
||||
"per_level_gain": 1,
|
||||
"base_ac": 9,
|
||||
"base_price": 20,
|
||||
"price_ratio_pct": 150,
|
||||
"initial_owned": false,
|
||||
"prereq_route": "light",
|
||||
"unlock_price": 200
|
||||
}]
|
||||
|
||||
; 锻造工坊实装批 T1(2026-10-12——迭代 23;设计权威 design/gdd/systems/跨局循环/锻造工坊.md v0.6,
|
||||
; 需求卡片 design/research/需求卡片-锻造工坊实装批-2026-10-12.md T1 锚表):
|
||||
; 六路线 provisional 投值——cap 5/每级增量统一 +1(v1.2 用户裁:provisional 线性起步,4a 前缓后陡为
|
||||
; 终值形状方向归 M4)/甲型基础 AC 10-12-9(现役结构值 settlement.tres 矩阵同行)/基础价 20/公比 150%
|
||||
; (历史考古参数 meta.tres:82-84 退役批注)/各级价格 20/30/45/67/101/一次性解锁价 200(中段三级 213 九折)。
|
||||
; 初始持有=长剑+轻甲(语义继承自已退役属性锻造行——迭代 21 T5 行族删除后的新建行族,非改造)。
|
||||
; 并联双通道(4b-1 裁):长枪/法杖←长剑练满、重甲/魔法护甲←轻甲练满,或一次性解锁价直购,互不影响。
|
||||
; 默认两轴(契约 2 所有权移交):hero.tres 静态表两轴随本批删除,默认值归本表常驻持有——normal/light
|
||||
; = 裸体态类型(Q1:未解锁/未锻造=0 加值)。全部数值 provisional 终值归 M4 数值批。
|
||||
; 表运行期只读(铁律):永久强化只写存档 forge_routes 域,本表零写面。
|
||||
Reference in new issue
Block a user