feat: W2 经济核心+数据管线+灰盒 — Balance Bible v1.0 八表逐值落地,EconomyState/sim 14 门/灰盒主场景/单测(GDScript 待本机引擎验证)
This commit is contained in:
1 parent
856326a766
commit
24e5b0c4cc
58 files changed
+7099
-22
No files matched your search
@@ -0,0 +1,336 @@
|
||||
class_name BalanceData
|
||||
extends RefCounted
|
||||
|
||||
# ============================================================================
|
||||
# BalanceData —— data/build/*.json 载入器(架构 §3.1 管线第三段)
|
||||
#
|
||||
# 单向数据管线的运行时入口:
|
||||
# data/sheets/*.csv ──SheetImporter──► data/build/*.json ──本类──► def 对象
|
||||
# sim(core/)与游戏(game/DataRegistry)读同一份文件 = 零漂移(架构 §3.1 铁律②)
|
||||
#
|
||||
# 纪律:
|
||||
# - core 层:无 Node/无场景树依赖;sim 与单测可无头构造(架构 §2.2)
|
||||
# - 只读:游戏永不写 data/(铁律①)
|
||||
# - 载入即做跨表引用完整性检查(check_references),违规进 load_errors
|
||||
#
|
||||
# 知识缺口:4.7.x 超出训练数据——只用 4.x 稳定 API(FileAccess/JSON/DirAccess)。
|
||||
# ============================================================================
|
||||
|
||||
const SHEET_FILES: Array[String] = [
|
||||
"generators", "upgrades", "halls", "hall_palettes",
|
||||
"boss_battles", "events", "meta_upgrades", "balance_tuning",
|
||||
]
|
||||
|
||||
var generators: Array[GeneratorDef] = []
|
||||
var upgrades: Array[UpgradeDef] = [] # 108 条非 meta 升级
|
||||
var meta_upgrades: Array[UpgradeDef] = [] # 12 条宿慧商店(type=meta)
|
||||
var halls: Array[HallDef] = []
|
||||
var palettes: Array[PaletteDef] = []
|
||||
var bosses: Array[BossDef] = []
|
||||
var events: Array[EventDef] = []
|
||||
|
||||
var load_errors: Array[String] = []
|
||||
|
||||
var _gen_by_id := {}
|
||||
var _up_by_id := {}
|
||||
var _meta_by_id := {}
|
||||
var _hall_by_id := {}
|
||||
var _hall_by_no := {}
|
||||
var _boss_by_hall := {}
|
||||
var _palette_by_hall := {}
|
||||
var _event_by_id := {}
|
||||
var _tune := {} # id -> {"kind": String, "num": float, "text": String}
|
||||
|
||||
|
||||
## 从目录载入全部表(缺表 = load_errors,不静默)
|
||||
static func load_from_dir(dir: String) -> BalanceData:
|
||||
var bd := BalanceData.new()
|
||||
for sheet in SHEET_FILES:
|
||||
var path := "%s/%s.json" % [dir, sheet]
|
||||
if not FileAccess.file_exists(path):
|
||||
bd.load_errors.append("缺少 build 产物:%s(先运行 SheetImporter import_all)" % path)
|
||||
continue
|
||||
var text := FileAccess.get_file_as_string(path)
|
||||
var doc: Variant = JSON.parse_string(text)
|
||||
if not (doc is Dictionary):
|
||||
bd.load_errors.append("JSON 解析失败:%s" % path)
|
||||
continue
|
||||
var rows: Variant = doc.get("rows", [])
|
||||
if not (rows is Array):
|
||||
bd.load_errors.append("%s:缺少 rows 数组" % path)
|
||||
continue
|
||||
match sheet:
|
||||
"generators": bd._load_generators(rows)
|
||||
"upgrades": bd._load_upgrades(rows)
|
||||
"halls": bd._load_halls(rows)
|
||||
"hall_palettes": bd._load_palettes(rows)
|
||||
"boss_battles": bd._load_bosses(rows)
|
||||
"events": bd._load_events(rows)
|
||||
"meta_upgrades": bd._load_meta(rows)
|
||||
"balance_tuning": bd._load_tuning(rows)
|
||||
bd._build_indexes()
|
||||
bd.load_errors.append_array(bd.check_references())
|
||||
return bd
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 各表载入(字段名与 CSV 列同名;JSON 数字按 int/float 到达,统一 float() 收敛)
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
func _load_generators(rows: Array) -> void:
|
||||
for r in rows:
|
||||
var d := GeneratorDef.new()
|
||||
d.id = str(r.get("id", ""))
|
||||
d.display_cn = str(r.get("display_cn", ""))
|
||||
d.display_en = str(r.get("display_en", ""))
|
||||
d.base_cost = float(r.get("base_cost", 0))
|
||||
d.cost_ratio = float(r.get("cost_ratio", 1))
|
||||
d.base_rate = float(r.get("base_rate", 0))
|
||||
d.unlock_hall = str(r.get("unlock_hall", "none"))
|
||||
d.unlock_gate = str(r.get("unlock_gate", "none"))
|
||||
d.sprite_row = str(r.get("sprite_row", ""))
|
||||
generators.append(d)
|
||||
|
||||
|
||||
func _load_upgrades(rows: Array) -> void:
|
||||
for r in rows:
|
||||
var d := UpgradeDef.new()
|
||||
d.id = str(r.get("id", ""))
|
||||
d.name_cn = str(r.get("name_cn", ""))
|
||||
d.name_en = str(r.get("name_en", ""))
|
||||
d.type = str(r.get("type", ""))
|
||||
d.target_gen = str(r.get("target_gen", "none"))
|
||||
d.unlock_hall = str(r.get("unlock_hall", "none"))
|
||||
d.effect_type = str(r.get("effect_type", ""))
|
||||
d.effect_param = float(r.get("effect_param", 0))
|
||||
d.requires = str(r.get("requires", "none"))
|
||||
d.cost = int(r.get("cost", 0))
|
||||
d.desc_cn = str(r.get("desc_cn", ""))
|
||||
upgrades.append(d)
|
||||
|
||||
|
||||
func _load_halls(rows: Array) -> void:
|
||||
for r in rows:
|
||||
var d := HallDef.new()
|
||||
d.id = str(r.get("id", ""))
|
||||
d.no = int(r.get("no", 0))
|
||||
d.king_cn = str(r.get("king_cn", ""))
|
||||
d.king_en = str(r.get("king_en", ""))
|
||||
d.motif_cn = str(r.get("motif_cn", ""))
|
||||
d.motif_en = str(r.get("motif_en", ""))
|
||||
d.target_min = float(r.get("target_min", 0))
|
||||
d.gate_merit = float(r.get("gate_merit", 0))
|
||||
d.unlock_gen = str(r.get("unlock_gen", "none"))
|
||||
d.unlock_gate = str(r.get("unlock_gate", "none"))
|
||||
d.palette_id = str(r.get("palette_id", ""))
|
||||
d.is_prestige_gate = bool(r.get("is_prestige_gate", false))
|
||||
halls.append(d)
|
||||
|
||||
|
||||
func _load_palettes(rows: Array) -> void:
|
||||
for r in rows:
|
||||
var d := PaletteDef.new()
|
||||
d.id = str(r.get("id", ""))
|
||||
d.hall_id = str(r.get("hall_id", ""))
|
||||
d.name_cn = str(r.get("name_cn", ""))
|
||||
d.ink_hex = str(r.get("ink_hex", "#1A1A1A"))
|
||||
d.bone_hex = str(r.get("bone_hex", "#F2EDE4"))
|
||||
d.cinnabar_hex = str(r.get("cinnabar_hex", "#B4402C"))
|
||||
d.indigo_hex = str(r.get("indigo_hex", "#2C4A6E"))
|
||||
d.accent_hex = str(r.get("accent_hex", "#1A1A1A"))
|
||||
d.accent_name_cn = str(r.get("accent_name_cn", ""))
|
||||
d.flow = str(r.get("flow", "seep_down"))
|
||||
d.lut_index = int(r.get("lut_index", 0))
|
||||
d.ink_pct = int(r.get("ink_pct", 65))
|
||||
d.bone_pct = int(r.get("bone_pct", 25))
|
||||
d.cinnabar_max_pct = int(r.get("cinnabar_max_pct", 8))
|
||||
d.indigo_max_pct = int(r.get("indigo_max_pct", 7))
|
||||
palettes.append(d)
|
||||
|
||||
|
||||
func _load_bosses(rows: Array) -> void:
|
||||
for r in rows:
|
||||
var d := BossDef.new()
|
||||
d.id = str(r.get("id", ""))
|
||||
d.hall_id = str(r.get("hall_id", ""))
|
||||
d.hall_factor = float(r.get("hall_factor", 1))
|
||||
d.hp_factor = float(r.get("hp_factor", 15))
|
||||
d.army_dps_factor = float(r.get("army_dps_factor", 1))
|
||||
d.intro_sec = float(r.get("intro_sec", 3))
|
||||
d.intro_dmg_souls_factor = float(r.get("intro_dmg_souls_factor", 0.5))
|
||||
d.intro_dmg_cap_ratio = float(r.get("intro_dmg_cap_ratio", 0.2))
|
||||
d.active_sec = float(r.get("active_sec", 30))
|
||||
d.combo_window_sec = float(r.get("combo_window_sec", 0.6))
|
||||
d.combo_step = float(r.get("combo_step", 0.02))
|
||||
d.combo_cap = float(r.get("combo_cap", 0.6))
|
||||
d.retry_cooldown_sec = float(r.get("retry_cooldown_sec", 60))
|
||||
d.reward_hp_ratio = float(r.get("reward_hp_ratio", 0.2))
|
||||
bosses.append(d)
|
||||
|
||||
|
||||
func _load_events(rows: Array) -> void:
|
||||
for r in rows:
|
||||
var d := EventDef.new()
|
||||
d.id = str(r.get("id", ""))
|
||||
d.name_cn = str(r.get("name_cn", ""))
|
||||
d.name_en = str(r.get("name_en", ""))
|
||||
d.trigger_min_min = float(r.get("trigger_min_min", 1))
|
||||
d.trigger_max_min = float(r.get("trigger_max_min", 1))
|
||||
d.duration_sec = float(r.get("duration_sec", 0))
|
||||
d.type = str(r.get("type", ""))
|
||||
d.p_mult = float(r.get("p_mult", 0))
|
||||
d.p_clicks = float(r.get("p_clicks", 0))
|
||||
d.p_window_sec = float(r.get("p_window_sec", 0))
|
||||
d.p_penalty = float(r.get("p_penalty", 0))
|
||||
d.p_penalty_dur_sec = float(r.get("p_penalty_dur_sec", 0))
|
||||
d.opt_a_type = str(r.get("opt_a_type", "none"))
|
||||
d.opt_a_param = float(r.get("opt_a_param", 0))
|
||||
d.opt_b_type = str(r.get("opt_b_type", "none"))
|
||||
d.opt_b_param = float(r.get("opt_b_param", 0))
|
||||
d.flavor_cn = str(r.get("flavor_cn", ""))
|
||||
events.append(d)
|
||||
|
||||
|
||||
func _load_meta(rows: Array) -> void:
|
||||
for r in rows:
|
||||
var d := UpgradeDef.new()
|
||||
d.id = str(r.get("id", ""))
|
||||
d.name_cn = str(r.get("name_cn", ""))
|
||||
d.name_en = str(r.get("name_en", ""))
|
||||
d.type = "meta"
|
||||
d.target_gen = str(r.get("target_id", ""))
|
||||
d.unlock_hall = "none"
|
||||
d.effect_type = str(r.get("effect_type", ""))
|
||||
d.effect_param = float(r.get("effect_param", 0))
|
||||
d.requires = str(r.get("requires", "none"))
|
||||
d.cost = int(r.get("cost_suihui", 0))
|
||||
d.cost_is_suihui = true
|
||||
d.max_rank = int(r.get("max_rank", 1))
|
||||
d.condition = str(r.get("condition", "none"))
|
||||
d.desc_cn = str(r.get("desc_cn", ""))
|
||||
meta_upgrades.append(d)
|
||||
|
||||
|
||||
func _load_tuning(rows: Array) -> void:
|
||||
for r in rows:
|
||||
var id := str(r.get("id", ""))
|
||||
if id == "":
|
||||
continue
|
||||
_tune[id] = {
|
||||
"kind": str(r.get("kind", "number")),
|
||||
"num": float(r.get("num", 0)),
|
||||
"text": str(r.get("text", "")),
|
||||
}
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 索引与查询
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
func _build_indexes() -> void:
|
||||
for d in generators:
|
||||
_gen_by_id[d.id] = d
|
||||
for d in upgrades:
|
||||
_up_by_id[d.id] = d
|
||||
for d in meta_upgrades:
|
||||
_meta_by_id[d.id] = d
|
||||
for d in halls:
|
||||
_hall_by_id[d.id] = d
|
||||
_hall_by_no[d.no] = d
|
||||
for d in bosses:
|
||||
_boss_by_hall[d.hall_id] = d
|
||||
for d in palettes:
|
||||
_palette_by_hall[d.hall_id] = d
|
||||
for d in events:
|
||||
_event_by_id[d.id] = d
|
||||
|
||||
|
||||
func generator(id: String) -> GeneratorDef:
|
||||
return _gen_by_id.get(id)
|
||||
|
||||
|
||||
func upgrade(id: String) -> UpgradeDef:
|
||||
return _up_by_id.get(id)
|
||||
|
||||
|
||||
func meta(id: String) -> UpgradeDef:
|
||||
return _meta_by_id.get(id)
|
||||
|
||||
|
||||
func hall(id_or_no: Variant) -> HallDef:
|
||||
if id_or_no is int:
|
||||
return _hall_by_no.get(id_or_no)
|
||||
return _hall_by_id.get(str(id_or_no))
|
||||
|
||||
|
||||
func hall_by_no(no: int) -> HallDef:
|
||||
return _hall_by_no.get(no)
|
||||
|
||||
|
||||
func boss_for_hall(hall_id: String) -> BossDef:
|
||||
return _boss_by_hall.get(hall_id)
|
||||
|
||||
|
||||
func palette_for_hall(hall_id: String) -> PaletteDef:
|
||||
return _palette_by_hall.get(hall_id)
|
||||
|
||||
|
||||
func event(id: String) -> EventDef:
|
||||
return _event_by_id.get(id)
|
||||
|
||||
|
||||
## 全局调参数(balance_tuning.csv);缺失返回 default 并记 load_errors
|
||||
func tune_num(key: String, default_value: float) -> float:
|
||||
var row: Variant = _tune.get(key)
|
||||
if row == null:
|
||||
load_errors.append("tune 缺失:%s(用默认 %s)" % [key, str(default_value)])
|
||||
return default_value
|
||||
return float(row.num)
|
||||
|
||||
|
||||
func tune_text(key: String, default_value: String) -> String:
|
||||
var row: Variant = _tune.get(key)
|
||||
if row == null:
|
||||
load_errors.append("tune 缺失:%s(用默认 %s)" % [key, default_value])
|
||||
return default_value
|
||||
return str(row.text)
|
||||
|
||||
|
||||
func has_tune(key: String) -> bool:
|
||||
return _tune.has(key)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 跨表引用完整性(schema 管单表;跨表在这里查)
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
func check_references() -> Array[String]:
|
||||
var errs: Array[String] = []
|
||||
for g in generators:
|
||||
if g.unlock_hall != "none" and _hall_by_id.get(g.unlock_hall) == null:
|
||||
errs.append("generators.%s.unlock_hall 引用了不存在的殿:%s" % [g.id, g.unlock_hall])
|
||||
if g.unlock_gate != "none" and _up_by_id.get(g.unlock_gate) == null:
|
||||
errs.append("generators.%s.unlock_gate 引用了不存在的路引:%s" % [g.id, g.unlock_gate])
|
||||
for u in upgrades:
|
||||
if u.target_gen != "none" and _gen_by_id.get(u.target_gen) == null:
|
||||
errs.append("upgrades.%s.target_gen 引用了不存在的班底:%s" % [u.id, u.target_gen])
|
||||
if u.unlock_hall != "none" and _hall_by_id.get(u.unlock_hall) == null:
|
||||
errs.append("upgrades.%s.unlock_hall 引用了不存在的殿:%s" % [u.id, u.unlock_hall])
|
||||
for h in halls:
|
||||
if h.unlock_gen != "none" and _gen_by_id.get(h.unlock_gen) == null:
|
||||
errs.append("halls.%s.unlock_gen 引用了不存在的班底:%s" % [h.id, h.unlock_gen])
|
||||
if h.unlock_gate != "none" and _up_by_id.get(h.unlock_gate) == null:
|
||||
errs.append("halls.%s.unlock_gate 引用了不存在的路引:%s" % [h.id, h.unlock_gate])
|
||||
if _palette_by_hall.get(h.id) == null:
|
||||
errs.append("halls.%s 缺少色板行(hall_palettes.csv)" % h.id)
|
||||
for b in bosses:
|
||||
if _hall_by_id.get(b.hall_id) == null:
|
||||
errs.append("boss_battles.%s.hall_id 引用了不存在的殿:%s" % [b.id, b.hall_id])
|
||||
for m in meta_upgrades:
|
||||
if m.target_gen != "" and _gen_by_id.get(m.target_gen) == null:
|
||||
errs.append("meta_upgrades.%s.target_id 引用了不存在的班底:%s" % [m.id, m.target_gen])
|
||||
# 每殿恰一场殿王战
|
||||
for h in halls:
|
||||
if _boss_by_hall.get(h.id) == null:
|
||||
errs.append("殿 %s 缺少 boss_battles 行" % h.id)
|
||||
return errs
|
||||
@@ -0,0 +1,39 @@
|
||||
class_name BossDef
|
||||
extends RefCounted
|
||||
|
||||
# ============================================================================
|
||||
# BossDef —— 殿王战参数数据行(架构 §4.3;[B§5])
|
||||
#
|
||||
# 公式([B§5],参数全部来自本行,代码零硬编码):
|
||||
# HP = hp_factor × 进入时产出/s × hall_factor (15×产出/s×殿系数 → ~30s 的仗)
|
||||
# 军列 DPS = 进入时产出/s × army_dps_factor (阴兵在打,不是你一个人)
|
||||
# 点击伤害 = 点击价值 × 连击(combo_step/层,combo_cap 封顶)
|
||||
# INTRO = intro_sec 秒冲锋过场;开场伤害 = min(超度×intro_dmg_souls_factor, HP×intro_dmg_cap_ratio)
|
||||
# ACTIVE = active_sec 秒主动阶段
|
||||
# 通过奖励 = 阴德爆发 = HP × reward_hp_ratio
|
||||
# 失败 = retry_cooldown_sec 冷却可再战(H7 前败即等推 → up_gate_boss_retry)
|
||||
#
|
||||
# 注:hp_factor=15 是 [B§11 差异#4] 对 GDD 50× 的修复值(50× 按 DPS=产出/s
|
||||
# 推演 30s 打不完)。架构文档 §1.2/§4.3 中残留的 "50×" 为陈旧引用,以本表与
|
||||
# Balance Bible v1.0 §5 为准。
|
||||
# ============================================================================
|
||||
|
||||
var id: String # "boss_hall_01"
|
||||
var hall_id: String # halls.csv 行 ID
|
||||
var hall_factor: float # 殿系数 1.0→2.2
|
||||
var hp_factor: float # HP 系数(15)
|
||||
var army_dps_factor: float # 军列 DPS 系数(1.0)
|
||||
var intro_sec: float # INTRO 秒数(3)
|
||||
var intro_dmg_souls_factor: float # 开场伤害 = 超度 × 本值(0.5)
|
||||
var intro_dmg_cap_ratio: float # 开场伤害封顶 = HP × 本值(0.2)
|
||||
var active_sec: float # ACTIVE 秒数(30)
|
||||
var combo_window_sec: float # 连击窗口(0.6s)
|
||||
var combo_step: float # 连击每层 +2%
|
||||
var combo_cap: float # 连击封顶 +60%
|
||||
var retry_cooldown_sec: float # 失败冷却(60s)
|
||||
var reward_hp_ratio: float # 通过阴德爆发 = HP × 本值(0.2)
|
||||
|
||||
|
||||
## 进入时 HP(架构 §4.3 start() 口径)
|
||||
func hp_at(output_per_sec: float) -> float:
|
||||
return hp_factor * output_per_sec * hall_factor
|
||||
@@ -0,0 +1,37 @@
|
||||
class_name EventDef
|
||||
extends RefCounted
|
||||
|
||||
# ============================================================================
|
||||
# EventDef —— 事件数据行(架构 §4.4;[B§7])
|
||||
#
|
||||
# 事件倍率走独立瞬时区,不进全局乘区([B§0.3/§7])。出现前提 =
|
||||
# up_gate_events(过 H2)。参数列按 type 取用:
|
||||
# output_mult p_mult = 全产出倍率(百鬼夜行 ×5)
|
||||
# click_challenge p_clicks = 窗口内需点击次数;p_window_sec = 窗口;
|
||||
# p_penalty = 失败惩罚倍率(-50%);p_penalty_dur_sec = 持续
|
||||
# (失败目标 = 随机已解锁班底,工程裁定 P-9)
|
||||
# choice opt_a = 超度(merit_burst_sec:阴德 = 产出/s × 秒数);
|
||||
# opt_b = 吞噬(wrath_gain:冤气 = lerp(p_clicks, p_window_sec,
|
||||
# 殿序进度));超时默认 opt_a([B§7])
|
||||
# click_output_mult p_clicks = 点击倍率;p_mult = 产出倍率(天狗食月 ×10/×2)
|
||||
#
|
||||
# EventDirector 运行时为 W3 交付;本文件 + events.csv 先行落定数据。
|
||||
# ============================================================================
|
||||
|
||||
var id: String # "ev_01_baigui"
|
||||
var name_cn: String
|
||||
var name_en: String # 临时译名,待文策渊 glossary
|
||||
var trigger_min_min: float # 触发间隔下限(分钟)
|
||||
var trigger_max_min: float # 触发间隔上限(分钟,含)
|
||||
var duration_sec: float # 持续秒数(choice 为非阻塞决策窗)
|
||||
var type: String # output_mult/click_challenge/choice/click_output_mult
|
||||
var p_mult: float
|
||||
var p_clicks: float
|
||||
var p_window_sec: float
|
||||
var p_penalty: float
|
||||
var p_penalty_dur_sec: float
|
||||
var opt_a_type: String # none/merit_burst_sec/wrath_gain
|
||||
var opt_a_param: float
|
||||
var opt_b_type: String
|
||||
var opt_b_param: float
|
||||
var flavor_cn: String
|
||||
@@ -0,0 +1,23 @@
|
||||
class_name HallDef
|
||||
extends RefCounted
|
||||
|
||||
# ============================================================================
|
||||
# HallDef —— 十殿 band 数据行(架构 §2.1/§4.3;[B§6])
|
||||
#
|
||||
# 殿门 = 单计数器闸门:本轮累计阴德 merit_run ≥ gate_merit([B§0.1],
|
||||
# 挂机也能过;与殿王战解耦——[B§5] 注)。is_prestige_gate=true 的行
|
||||
# (仅 H10 转轮王)是轮回转世触发门([B§4.1])。
|
||||
# ============================================================================
|
||||
|
||||
var id: String # "hall_01_qinguang"(W1 已分配,永不重编号)
|
||||
var no: int # 1-10
|
||||
var king_cn: String # 殿王名(秦广王…)
|
||||
var king_en: String # 临时译名,待文策渊 glossary
|
||||
var motif_cn: String # 刑典母题(剪影+文字暗示路线)
|
||||
var motif_en: String
|
||||
var target_min: float # 到达目标时长(分钟,[B§6] pacing 表 25→180)
|
||||
var gate_merit: float # 殿门条件:本轮累计阴德(2K→1.5B)
|
||||
var unlock_gen: String # 过殿解锁班底 ID 或 "none"
|
||||
var unlock_gate: String # 过殿出现路引 ID 或 "none"
|
||||
var palette_id: String # hall_palettes.csv 行 ID
|
||||
var is_prestige_gate: bool # true = 轮回触发门(仅 H10)
|
||||
@@ -0,0 +1,32 @@
|
||||
class_name PaletteDef
|
||||
extends RefCounted
|
||||
|
||||
# ============================================================================
|
||||
# PaletteDef —— per-hall 色板数据行(架构 §5.3;[B§6 色板列])
|
||||
#
|
||||
# 水墨四色面积纪律([B]):墨 ~65% / 骨白 ~25% / 朱砂 ≤8% / 靛 ≤7%,
|
||||
# 由 W5 look-dev 冻结评审的截图直方图检查执行(架构 §10.1 评审 #1)。
|
||||
# hex 为工程起始值(provenance P-10),lut_generator 消费本表生成
|
||||
# assets/luts/hall_XX.png;换殿 = 换一行数据 + shader 换一张 LUT(ADR-3)。
|
||||
# ============================================================================
|
||||
|
||||
var id: String # "pal_hall_01"
|
||||
var hall_id: String # halls.csv 行 ID(每殿恰一行)
|
||||
var name_cn: String # 色板行名(冷靛/冰青/…/骨白+金)
|
||||
var ink_hex: String # 墨(#1A1A1A)
|
||||
var bone_hex: String # 骨白(纸底)
|
||||
var cinnabar_hex: String # 朱砂(强调)
|
||||
var indigo_hex: String # 靛(次强调;阴德方印编码)
|
||||
var accent_hex: String # 该殿色温
|
||||
var accent_name_cn: String
|
||||
var flow: String # seep_down(自上而下渗)/ bleed_left(向左横溢洶)
|
||||
var lut_index: int
|
||||
var ink_pct: int # 面积目标%
|
||||
var bone_pct: int
|
||||
var cinnabar_max_pct: int # 面积硬顶
|
||||
var indigo_max_pct: int
|
||||
|
||||
|
||||
## "#RRGGBB" → Color(供 shader/LUT 生成用)
|
||||
func to_color(hex: String) -> Color:
|
||||
return Color.html(hex)
|
||||
Reference in new issue
Block a user