feat: W2 经济核心+数据管线+灰盒 — Balance Bible v1.0 八表逐值落地,EconomyState/sim 14 门/灰盒主场景/单测(GDScript 待本机引擎验证)

This commit is contained in:
程基岩(engineering-lead) committed 2026-10-19 17:44:53 +08:00
1 parent 856326a766
commit 24e5b0c4cc
58 files changed
+7099 -22

No files matched your search

+223
View File
@@ -0,0 +1,223 @@
{
"sheet": "balance_tuning",
"schema_version": 0,
"columns": [
"id",
"kind",
"num",
"text",
"desc_cn"
],
"rows": [
{
"id": "tune_click_base",
"kind": "number",
"num": 1,
"text": "",
"desc_cn": "[B§2.2] 点击基础值 = 1(与 gen1 成本 15 配对 → 15 次点击首购)"
},
{
"id": "tune_click_zone_cap",
"kind": "number",
"num": 16,
"text": "",
"desc_cn": "[B§1/§2.2] 点击区软上限 ×16(4 级 ×2 满)"
},
{
"id": "tune_gen_zone_cap",
"kind": "number",
"num": 8,
"text": "",
"desc_cn": "[B§1] 班底乘区软上限 ×8(6 级效率满 = ×7,留 1 级余量)"
},
{
"id": "tune_global_zone_cap",
"kind": "number",
"num": 64,
"text": "",
"desc_cn": "[B§1] 全局乘区软上限 ×64"
},
{
"id": "tune_offline_band1_h",
"kind": "number",
"num": 2,
"text": "",
"desc_cn": "[B§8] 离线 0-2h 速率 100%"
},
{
"id": "tune_offline_band1_rate",
"kind": "number",
"num": 1,
"text": "",
"desc_cn": "[B§8] 离线 0-2h 速率"
},
{
"id": "tune_offline_band2_h",
"kind": "number",
"num": 8,
"text": "",
"desc_cn": "[B§8] 离线 2-8h 速率 70%"
},
{
"id": "tune_offline_band2_rate",
"kind": "number",
"num": 0.7,
"text": "",
"desc_cn": "[B§8] 离线 2-8h 速率"
},
{
"id": "tune_offline_band3_h",
"kind": "number",
"num": 24,
"text": "",
"desc_cn": "[B§8] 离线 8-24h 速率 40%"
},
{
"id": "tune_offline_band3_rate",
"kind": "number",
"num": 0.4,
"text": "",
"desc_cn": "[B§8] 离线 8-24h 速率"
},
{
"id": "tune_offline_band4_rate",
"kind": "number",
"num": 0.1,
"text": "",
"desc_cn": "[B§8] 离线 >24h 速率 10%"
},
{
"id": "tune_offline_cap_h",
"kind": "number",
"num": 72,
"text": "",
"desc_cn": "[B§8] 离线硬上限 72h 封顶"
},
{
"id": "tune_wrath_layers",
"kind": "number",
"num": 22,
"text": "",
"desc_cn": "[B§3.2] 冤煞层总数 22"
},
{
"id": "tune_wrath_cost_base",
"kind": "number",
"num": 8,
"text": "",
"desc_cn": "[B§3.2] 第 n 层成本 = round(8 × n^1.8)"
},
{
"id": "tune_wrath_cost_exp",
"kind": "number",
"num": 1.8,
"text": "",
"desc_cn": "[B§3.2] 冤煞层成本指数"
},
{
"id": "tune_wrath_bonus",
"kind": "number",
"num": 0.25,
"text": "",
"desc_cn": "[B§3.2] 每层全局 +25%(加法,进全局乘区)"
},
{
"id": "tune_wrath_penalty",
"kind": "number",
"num": 0.03,
"text": "",
"desc_cn": "[B§3.2] 每层宿慧获取削减 +3%"
},
{
"id": "tune_wrath_penalty_cap",
"kind": "number",
"num": 0.3,
"text": "",
"desc_cn": "[B§3.2] 宿慧削减封顶 30%"
},
{
"id": "tune_suihui_div",
"kind": "number",
"num": 1000000,
"text": "",
"desc_cn": "[B§4.1] 宿慧 = floor(√(本轮累计阴德/1e6) × 5)"
},
{
"id": "tune_suihui_mult",
"kind": "number",
"num": 5,
"text": "",
"desc_cn": "[B§4.1] 宿慧公式乘数"
},
{
"id": "tune_suihui_global_pct",
"kind": "number",
"num": 0.03,
"text": "",
"desc_cn": "[B§1/§4.2] 每级宿慧全局产出 +3%(加法,进全局乘区)"
},
{
"id": "tune_enthrone_bonus",
"kind": "number",
"num": 0.25,
"text": "",
"desc_cn": "[B§4.4] 阴司敕封自动全局恩典 +25%(与 m08 商品字面重叠,见 provenance P-4)"
},
{
"id": "tune_skip_interval",
"kind": "number",
"num": 5,
"text": "",
"desc_cn": "[B§4.2] 每 5 级宿慧 → 前 3 档班底各 +25 级(m04 可加快为 4)"
},
{
"id": "tune_skip_gens",
"kind": "number",
"num": 3,
"text": "",
"desc_cn": "[B§4.2] 跳过机制覆盖前 3 档班底"
},
{
"id": "tune_skip_levels",
"kind": "number",
"num": 25,
"text": "",
"desc_cn": "[B§4.2] 跳过机制每档 +25 级"
},
{
"id": "tune_enthrone_rebirths",
"kind": "number",
"num": 2,
"text": "",
"desc_cn": "[B§4.4] 阴司敕封触发:转世 ≥2(约第 2-3 轮,累计 8-12h 为其 pacing 后果)"
},
{
"id": "tune_restart_curve",
"kind": "text",
"num": 0,
"text": "1:0.6:0.4:0.3",
"desc_cn": "[B§4.3] 重开曲线目标(第 5 轮触底 ≥0.25×);模拟器门 #13 断言"
},
{
"id": "tune_event_timeout_default",
"kind": "text",
"num": 0,
"text": "opt_a",
"desc_cn": "[B§7] 冤魂申诉超时默认 = 超度(opt_a)"
},
{
"id": "tune_souls_div",
"kind": "number",
"num": 10000,
"text": "",
"desc_cn": "[B§6] 超度亡魂 = floor(本轮累计阴德 ÷ 10,000)(叙事计数器,不设闸门;P-14)"
},
{
"id": "tune_enthrone_suihui_req",
"kind": "number",
"num": 60,
"text": "",
"desc_cn": "[G§3.4] 阴司敕封解锁条件:宿慧 ≥60([B§4.4] 仅给 8-12h 体感,采信 GDD 数值)"
}
]
}
+182
View File
@@ -0,0 +1,182 @@
{
"sheet": "boss_battles",
"schema_version": 0,
"columns": [
"id",
"hall_id",
"hall_factor",
"hp_factor",
"army_dps_factor",
"intro_sec",
"intro_dmg_souls_factor",
"intro_dmg_cap_ratio",
"active_sec",
"combo_window_sec",
"combo_step",
"combo_cap",
"retry_cooldown_sec",
"reward_hp_ratio"
],
"rows": [
{
"id": "boss_hall_01",
"hall_id": "hall_01_qinguang",
"hall_factor": 1,
"hp_factor": 15,
"army_dps_factor": 1,
"intro_sec": 3,
"intro_dmg_souls_factor": 0.5,
"intro_dmg_cap_ratio": 0.2,
"active_sec": 30,
"combo_window_sec": 0.6,
"combo_step": 0.02,
"combo_cap": 0.6,
"retry_cooldown_sec": 60,
"reward_hp_ratio": 0.2
},
{
"id": "boss_hall_02",
"hall_id": "hall_02_chujiang",
"hall_factor": 1.1,
"hp_factor": 15,
"army_dps_factor": 1,
"intro_sec": 3,
"intro_dmg_souls_factor": 0.5,
"intro_dmg_cap_ratio": 0.2,
"active_sec": 30,
"combo_window_sec": 0.6,
"combo_step": 0.02,
"combo_cap": 0.6,
"retry_cooldown_sec": 60,
"reward_hp_ratio": 0.2
},
{
"id": "boss_hall_03",
"hall_id": "hall_03_songdi",
"hall_factor": 1.2,
"hp_factor": 15,
"army_dps_factor": 1,
"intro_sec": 3,
"intro_dmg_souls_factor": 0.5,
"intro_dmg_cap_ratio": 0.2,
"active_sec": 30,
"combo_window_sec": 0.6,
"combo_step": 0.02,
"combo_cap": 0.6,
"retry_cooldown_sec": 60,
"reward_hp_ratio": 0.2
},
{
"id": "boss_hall_04",
"hall_id": "hall_04_wuguan",
"hall_factor": 1.3,
"hp_factor": 15,
"army_dps_factor": 1,
"intro_sec": 3,
"intro_dmg_souls_factor": 0.5,
"intro_dmg_cap_ratio": 0.2,
"active_sec": 30,
"combo_window_sec": 0.6,
"combo_step": 0.02,
"combo_cap": 0.6,
"retry_cooldown_sec": 60,
"reward_hp_ratio": 0.2
},
{
"id": "boss_hall_05",
"hall_id": "hall_05_yama",
"hall_factor": 1.4,
"hp_factor": 15,
"army_dps_factor": 1,
"intro_sec": 3,
"intro_dmg_souls_factor": 0.5,
"intro_dmg_cap_ratio": 0.2,
"active_sec": 30,
"combo_window_sec": 0.6,
"combo_step": 0.02,
"combo_cap": 0.6,
"retry_cooldown_sec": 60,
"reward_hp_ratio": 0.2
},
{
"id": "boss_hall_06",
"hall_id": "hall_06_biancheng",
"hall_factor": 1.6,
"hp_factor": 15,
"army_dps_factor": 1,
"intro_sec": 3,
"intro_dmg_souls_factor": 0.5,
"intro_dmg_cap_ratio": 0.2,
"active_sec": 30,
"combo_window_sec": 0.6,
"combo_step": 0.02,
"combo_cap": 0.6,
"retry_cooldown_sec": 60,
"reward_hp_ratio": 0.2
},
{
"id": "boss_hall_07",
"hall_id": "hall_07_taishan",
"hall_factor": 1.8,
"hp_factor": 15,
"army_dps_factor": 1,
"intro_sec": 3,
"intro_dmg_souls_factor": 0.5,
"intro_dmg_cap_ratio": 0.2,
"active_sec": 30,
"combo_window_sec": 0.6,
"combo_step": 0.02,
"combo_cap": 0.6,
"retry_cooldown_sec": 60,
"reward_hp_ratio": 0.2
},
{
"id": "boss_hall_08",
"hall_id": "hall_08_dushi",
"hall_factor": 2,
"hp_factor": 15,
"army_dps_factor": 1,
"intro_sec": 3,
"intro_dmg_souls_factor": 0.5,
"intro_dmg_cap_ratio": 0.2,
"active_sec": 30,
"combo_window_sec": 0.6,
"combo_step": 0.02,
"combo_cap": 0.6,
"retry_cooldown_sec": 60,
"reward_hp_ratio": 0.2
},
{
"id": "boss_hall_09",
"hall_id": "hall_09_pingdeng",
"hall_factor": 2.1,
"hp_factor": 15,
"army_dps_factor": 1,
"intro_sec": 3,
"intro_dmg_souls_factor": 0.5,
"intro_dmg_cap_ratio": 0.2,
"active_sec": 30,
"combo_window_sec": 0.6,
"combo_step": 0.02,
"combo_cap": 0.6,
"retry_cooldown_sec": 60,
"reward_hp_ratio": 0.2
},
{
"id": "boss_hall_10",
"hall_id": "hall_10_zhuanlun",
"hall_factor": 2.2,
"hp_factor": 15,
"army_dps_factor": 1,
"intro_sec": 3,
"intro_dmg_souls_factor": 0.5,
"intro_dmg_cap_ratio": 0.2,
"active_sec": 30,
"combo_window_sec": 0.6,
"combo_step": 0.02,
"combo_cap": 0.6,
"retry_cooldown_sec": 60,
"reward_hp_ratio": 0.2
}
]
}
+101
View File
@@ -0,0 +1,101 @@
{
"sheet": "events",
"schema_version": 0,
"columns": [
"id",
"name_cn",
"name_en",
"trigger_min_min",
"trigger_max_min",
"duration_sec",
"type",
"p_mult",
"p_clicks",
"p_window_sec",
"p_penalty",
"p_penalty_dur_sec",
"opt_a_type",
"opt_a_param",
"opt_b_type",
"opt_b_param",
"flavor_cn"
],
"rows": [
{
"id": "ev_01_baigui",
"name_cn": "百鬼夜行",
"name_en": "Night of the Hundred Ghosts",
"trigger_min_min": 8,
"trigger_max_min": 15,
"duration_sec": 60,
"type": "output_mult",
"p_mult": 5,
"p_clicks": 0,
"p_window_sec": 0,
"p_penalty": 0,
"p_penalty_dur_sec": 0,
"opt_a_type": "none",
"opt_a_param": 0,
"opt_b_type": "none",
"opt_b_param": 0,
"flavor_cn": "百鬼倾巢而出,阴阳秩序暂时失序"
},
{
"id": "ev_02_naoxiang",
"name_cn": "阴兵闹饷",
"name_en": "Ghost Mutiny",
"trigger_min_min": 10,
"trigger_max_min": 18,
"duration_sec": 15,
"type": "click_challenge",
"p_mult": 0,
"p_clicks": 10,
"p_window_sec": 15,
"p_penalty": 0.5,
"p_penalty_dur_sec": 60,
"opt_a_type": "none",
"opt_a_param": 0,
"opt_b_type": "none",
"opt_b_param": 0,
"flavor_cn": "鬼卒也要三牲祭,不然哗变"
},
{
"id": "ev_03_shensu",
"name_cn": "冤魂申诉",
"name_en": "Grievance of the Wronged",
"trigger_min_min": 6,
"trigger_max_min": 12,
"duration_sec": 45,
"type": "choice",
"p_mult": 0,
"p_clicks": 15,
"p_window_sec": 40,
"p_penalty": 0,
"p_penalty_dur_sec": 0,
"opt_a_type": "merit_burst_sec",
"opt_a_param": 30,
"opt_b_type": "wrath_gain",
"opt_b_param": 0,
"flavor_cn": "他跪在地上喊冤枉。你选择……"
},
{
"id": "ev_04_tiangou",
"name_cn": "天狗食月",
"name_en": "Heavenly Dog Eats the Moon",
"trigger_min_min": 20,
"trigger_max_min": 35,
"duration_sec": 30,
"type": "click_output_mult",
"p_mult": 2,
"p_clicks": 10,
"p_window_sec": 0,
"p_penalty": 0,
"p_penalty_dur_sec": 0,
"opt_a_type": "none",
"opt_a_param": 0,
"opt_b_type": "none",
"opt_b_param": 0,
"flavor_cn": "天狗噬月,阴气最盛——动手"
}
]
}
+9 -9
View File
@@ -52,7 +52,7 @@
"display_en": "Death-Scribe Clerks", "display_en": "Death-Scribe Clerks",
"base_cost": 12000, "base_cost": 12000,
"cost_ratio": 1.15, "cost_ratio": 1.15,
"base_rate": 50, "base_rate": 64,
"unlock_hall": "hall_02_chujiang", "unlock_hall": "hall_02_chujiang",
"unlock_gate": "up_gate_biliao", "unlock_gate": "up_gate_biliao",
"sprite_row": "res://assets/sprites/units/gen_04_biliao" "sprite_row": "res://assets/sprites/units/gen_04_biliao"
@@ -63,7 +63,7 @@
"display_en": "Meng Po's Soup Stall", "display_en": "Meng Po's Soup Stall",
"base_cost": 120000, "base_cost": 120000,
"cost_ratio": 1.16, "cost_ratio": 1.16,
"base_rate": 400, "base_rate": 512,
"unlock_hall": "hall_03_songdi", "unlock_hall": "hall_03_songdi",
"unlock_gate": "up_gate_tangpu", "unlock_gate": "up_gate_tangpu",
"sprite_row": "res://assets/sprites/units/gen_05_tangpu" "sprite_row": "res://assets/sprites/units/gen_05_tangpu"
@@ -74,7 +74,7 @@
"display_en": "Refugee Camp of the Wrongfully Dead", "display_en": "Refugee Camp of the Wrongfully Dead",
"base_cost": 1200000, "base_cost": 1200000,
"cost_ratio": 1.16, "cost_ratio": 1.16,
"base_rate": 3000, "base_rate": 4096,
"unlock_hall": "hall_04_wuguan", "unlock_hall": "hall_04_wuguan",
"unlock_gate": "up_gate_nanmin", "unlock_gate": "up_gate_nanmin",
"sprite_row": "res://assets/sprites/units/gen_06_nanmin" "sprite_row": "res://assets/sprites/units/gen_06_nanmin"
@@ -85,7 +85,7 @@
"display_en": "Clerks of the Nether Court", "display_en": "Clerks of the Nether Court",
"base_cost": 12000000, "base_cost": 12000000,
"cost_ratio": 1.17, "cost_ratio": 1.17,
"base_rate": 24000, "base_rate": 32768,
"unlock_hall": "hall_05_yama", "unlock_hall": "hall_05_yama",
"unlock_gate": "up_gate_shuban", "unlock_gate": "up_gate_shuban",
"sprite_row": "res://assets/sprites/units/gen_07_shuban" "sprite_row": "res://assets/sprites/units/gen_07_shuban"
@@ -96,7 +96,7 @@
"display_en": "Armory of the Dark Capital", "display_en": "Armory of the Dark Capital",
"base_cost": 120000000, "base_cost": 120000000,
"cost_ratio": 1.17, "cost_ratio": 1.17,
"base_rate": 180000, "base_rate": 262144,
"unlock_hall": "hall_06_biancheng", "unlock_hall": "hall_06_biancheng",
"unlock_gate": "up_gate_bingfang", "unlock_gate": "up_gate_bingfang",
"sprite_row": "res://assets/sprites/units/gen_08_bingfang" "sprite_row": "res://assets/sprites/units/gen_08_bingfang"
@@ -107,7 +107,7 @@
"display_en": "Sleeper Agents in the Ten Courts", "display_en": "Sleeper Agents in the Ten Courts",
"base_cost": 1200000000, "base_cost": 1200000000,
"cost_ratio": 1.18, "cost_ratio": 1.18,
"base_rate": 1400000, "base_rate": 2097152,
"unlock_hall": "hall_07_taishan", "unlock_hall": "hall_07_taishan",
"unlock_gate": "up_gate_wodi", "unlock_gate": "up_gate_wodi",
"sprite_row": "res://assets/sprites/units/gen_09_wodi" "sprite_row": "res://assets/sprites/units/gen_09_wodi"
@@ -118,7 +118,7 @@
"display_en": "Rift Walkers of the Wheel", "display_en": "Rift Walkers of the Wheel",
"base_cost": 12000000000, "base_cost": 12000000000,
"cost_ratio": 1.18, "cost_ratio": 1.18,
"base_rate": 11000000, "base_rate": 16777216,
"unlock_hall": "hall_08_dushi", "unlock_hall": "hall_08_dushi",
"unlock_gate": "up_gate_xingzhe", "unlock_gate": "up_gate_xingzhe",
"sprite_row": "res://assets/sprites/units/gen_10_xingzhe" "sprite_row": "res://assets/sprites/units/gen_10_xingzhe"
@@ -129,7 +129,7 @@
"display_en": "Soul Guides of the Six Paths", "display_en": "Soul Guides of the Six Paths",
"base_cost": 120000000000, "base_cost": 120000000000,
"cost_ratio": 1.19, "cost_ratio": 1.19,
"base_rate": 85000000, "base_rate": 134217728,
"unlock_hall": "hall_09_pingdeng", "unlock_hall": "hall_09_pingdeng",
"unlock_gate": "up_gate_yinhun", "unlock_gate": "up_gate_yinhun",
"sprite_row": "res://assets/sprites/units/gen_11_yinhun" "sprite_row": "res://assets/sprites/units/gen_11_yinhun"
@@ -140,7 +140,7 @@
"display_en": "Emperor of the Netherworld", "display_en": "Emperor of the Netherworld",
"base_cost": 1200000000000, "base_cost": 1200000000000,
"cost_ratio": 1.2, "cost_ratio": 1.2,
"base_rate": 650000000, "base_rate": 1073741824,
"unlock_hall": "none", "unlock_hall": "none",
"unlock_gate": "up_gate_tianzi", "unlock_gate": "up_gate_tianzi",
"sprite_row": "res://assets/sprites/units/gen_12_tianzi" "sprite_row": "res://assets/sprites/units/gen_12_tianzi"
+193
View File
@@ -0,0 +1,193 @@
{
"sheet": "hall_palettes",
"schema_version": 0,
"columns": [
"id",
"hall_id",
"name_cn",
"ink_hex",
"bone_hex",
"cinnabar_hex",
"indigo_hex",
"accent_hex",
"accent_name_cn",
"flow",
"lut_index",
"ink_pct",
"bone_pct",
"cinnabar_max_pct",
"indigo_max_pct"
],
"rows": [
{
"id": "pal_hall_01",
"hall_id": "hall_01_qinguang",
"name_cn": "冷靛",
"ink_hex": "#1A1A1A",
"bone_hex": "#F2EDE4",
"cinnabar_hex": "#B4402C",
"indigo_hex": "#2C4A6E",
"accent_hex": "#3D5A80",
"accent_name_cn": "冷靛",
"flow": "bleed_left",
"lut_index": 1,
"ink_pct": 65,
"bone_pct": 25,
"cinnabar_max_pct": 8,
"indigo_max_pct": 7
},
{
"id": "pal_hall_02",
"hall_id": "hall_02_chujiang",
"name_cn": "冰青",
"ink_hex": "#1A1A1A",
"bone_hex": "#F2EDE4",
"cinnabar_hex": "#B4402C",
"indigo_hex": "#2C4A6E",
"accent_hex": "#8FC1C9",
"accent_name_cn": "冰青",
"flow": "seep_down",
"lut_index": 2,
"ink_pct": 65,
"bone_pct": 25,
"cinnabar_max_pct": 8,
"indigo_max_pct": 7
},
{
"id": "pal_hall_03",
"hall_id": "hall_03_songdi",
"name_cn": "暗红",
"ink_hex": "#1A1A1A",
"bone_hex": "#F2EDE4",
"cinnabar_hex": "#B4402C",
"indigo_hex": "#2C4A6E",
"accent_hex": "#7E2E2E",
"accent_name_cn": "暗红",
"flow": "bleed_left",
"lut_index": 3,
"ink_pct": 65,
"bone_pct": 25,
"cinnabar_max_pct": 8,
"indigo_max_pct": 7
},
{
"id": "pal_hall_04",
"hall_id": "hall_04_wuguan",
"name_cn": "锈橙",
"ink_hex": "#1A1A1A",
"bone_hex": "#F2EDE4",
"cinnabar_hex": "#B4402C",
"indigo_hex": "#2C4A6E",
"accent_hex": "#A85C32",
"accent_name_cn": "锈橙",
"flow": "seep_down",
"lut_index": 4,
"ink_pct": 65,
"bone_pct": 25,
"cinnabar_max_pct": 8,
"indigo_max_pct": 7
},
{
"id": "pal_hall_05",
"hall_id": "hall_05_yama",
"name_cn": "玄黑",
"ink_hex": "#1A1A1A",
"bone_hex": "#F2EDE4",
"cinnabar_hex": "#B4402C",
"indigo_hex": "#2C4A6E",
"accent_hex": "#141414",
"accent_name_cn": "玄黑",
"flow": "bleed_left",
"lut_index": 5,
"ink_pct": 65,
"bone_pct": 25,
"cinnabar_max_pct": 8,
"indigo_max_pct": 7
},
{
"id": "pal_hall_06",
"hall_id": "hall_06_biancheng",
"name_cn": "铅灰",
"ink_hex": "#1A1A1A",
"bone_hex": "#F2EDE4",
"cinnabar_hex": "#B4402C",
"indigo_hex": "#2C4A6E",
"accent_hex": "#6E7276",
"accent_name_cn": "铅灰",
"flow": "seep_down",
"lut_index": 6,
"ink_pct": 65,
"bone_pct": 25,
"cinnabar_max_pct": 8,
"indigo_max_pct": 7
},
{
"id": "pal_hall_07",
"hall_id": "hall_07_taishan",
"name_cn": "烬红",
"ink_hex": "#1A1A1A",
"bone_hex": "#F2EDE4",
"cinnabar_hex": "#B4402C",
"indigo_hex": "#2C4A6E",
"accent_hex": "#8C3A2B",
"accent_name_cn": "烬红",
"flow": "bleed_left",
"lut_index": 7,
"ink_pct": 65,
"bone_pct": 25,
"cinnabar_max_pct": 8,
"indigo_max_pct": 7
},
{
"id": "pal_hall_08",
"hall_id": "hall_08_dushi",
"name_cn": "墨绿",
"ink_hex": "#1A1A1A",
"bone_hex": "#F2EDE4",
"cinnabar_hex": "#B4402C",
"indigo_hex": "#2C4A6E",
"accent_hex": "#2F4A3E",
"accent_name_cn": "墨绿",
"flow": "seep_down",
"lut_index": 8,
"ink_pct": 65,
"bone_pct": 25,
"cinnabar_max_pct": 8,
"indigo_max_pct": 7
},
{
"id": "pal_hall_09",
"hall_id": "hall_09_pingdeng",
"name_cn": "黛紫",
"ink_hex": "#1A1A1A",
"bone_hex": "#F2EDE4",
"cinnabar_hex": "#B4402C",
"indigo_hex": "#2C4A6E",
"accent_hex": "#4A3A5C",
"accent_name_cn": "黛紫",
"flow": "bleed_left",
"lut_index": 9,
"ink_pct": 65,
"bone_pct": 25,
"cinnabar_max_pct": 8,
"indigo_max_pct": 7
},
{
"id": "pal_hall_10",
"hall_id": "hall_10_zhuanlun",
"name_cn": "骨白+金",
"ink_hex": "#1A1A1A",
"bone_hex": "#F2EDE4",
"cinnabar_hex": "#B4402C",
"indigo_hex": "#2C4A6E",
"accent_hex": "#C9A227",
"accent_name_cn": "暗金",
"flow": "seep_down",
"lut_index": 10,
"ink_pct": 65,
"bone_pct": 25,
"cinnabar_max_pct": 8,
"indigo_max_pct": 7
}
]
}
+160
View File
@@ -0,0 +1,160 @@
{
"sheet": "halls",
"schema_version": 0,
"columns": [
"id",
"no",
"king_cn",
"king_en",
"motif_cn",
"motif_en",
"target_min",
"gate_merit",
"unlock_gen",
"unlock_gate",
"palette_id",
"is_prestige_gate"
],
"rows": [
{
"id": "hall_01_qinguang",
"no": 1,
"king_cn": "秦广王",
"king_en": "King Qinguang",
"motif_cn": "孽镜台(照心)",
"motif_en": "Mirror of Karma",
"target_min": 25,
"gate_merit": 2000,
"unlock_gen": "gen_03_guizu",
"unlock_gate": "up_gate_guizu",
"palette_id": "pal_hall_01",
"is_prestige_gate": "false"
},
{
"id": "hall_02_chujiang",
"no": 2,
"king_cn": "楚江王",
"king_en": "King Chujiang",
"motif_cn": "寒冰狱",
"motif_en": "Ice Hell",
"target_min": 42,
"gate_merit": 30000,
"unlock_gen": "gen_04_biliao",
"unlock_gate": "up_gate_biliao",
"palette_id": "pal_hall_02",
"is_prestige_gate": "false"
},
{
"id": "hall_03_songdi",
"no": 3,
"king_cn": "宋帝王",
"king_en": "King Songdi",
"motif_cn": "剥皮亭",
"motif_en": "Flaying Pavilion",
"target_min": 62,
"gate_merit": 300000,
"unlock_gen": "gen_05_tangpu",
"unlock_gate": "up_gate_tangpu",
"palette_id": "pal_hall_03",
"is_prestige_gate": "false"
},
{
"id": "hall_04_wuguan",
"no": 4,
"king_cn": "五官王",
"king_en": "King Wuguan",
"motif_cn": "沸汤/毒蛇",
"motif_en": "Scalding Cauldron",
"target_min": 84,
"gate_merit": 2000000,
"unlock_gen": "gen_06_nanmin",
"unlock_gate": "up_gate_nanmin",
"palette_id": "pal_hall_04",
"is_prestige_gate": "false"
},
{
"id": "hall_05_yama",
"no": 5,
"king_cn": "阎罗王",
"king_en": "King Yama",
"motif_cn": "叫唤地狱/铁床",
"motif_en": "Wailing Hell",
"target_min": 105,
"gate_merit": 10000000,
"unlock_gen": "gen_07_shuban",
"unlock_gate": "up_gate_shuban",
"palette_id": "pal_hall_05",
"is_prestige_gate": "false"
},
{
"id": "hall_06_biancheng",
"no": 6,
"king_cn": "卞城王",
"king_en": "King Biancheng",
"motif_cn": "石碾",
"motif_en": "Stone Roller",
"target_min": 125,
"gate_merit": 40000000,
"unlock_gen": "gen_08_bingfang",
"unlock_gate": "up_gate_bingfang",
"palette_id": "pal_hall_06",
"is_prestige_gate": "false"
},
{
"id": "hall_07_taishan",
"no": 7,
"king_cn": "泰山王",
"king_en": "King Taishan",
"motif_cn": "热油火坑",
"motif_en": "Boiling Oil Pit",
"target_min": 143,
"gate_merit": 120000000,
"unlock_gen": "gen_09_wodi",
"unlock_gate": "up_gate_wodi",
"palette_id": "pal_hall_07",
"is_prestige_gate": "false"
},
{
"id": "hall_08_dushi",
"no": 8,
"king_cn": "都市王",
"king_en": "King Dushi",
"motif_cn": "泥犁/大石",
"motif_en": "Avici Stone",
"target_min": 160,
"gate_merit": 350000000,
"unlock_gen": "gen_10_xingzhe",
"unlock_gate": "up_gate_xingzhe",
"palette_id": "pal_hall_08",
"is_prestige_gate": "false"
},
{
"id": "hall_09_pingdeng",
"no": 9,
"king_cn": "平等王",
"king_en": "King Pingdeng",
"motif_cn": "拔舌/铁树",
"motif_en": "Tongue-Ripping Iron Tree",
"target_min": 175,
"gate_merit": 800000000,
"unlock_gen": "gen_11_yinhun",
"unlock_gate": "up_gate_yinhun",
"palette_id": "pal_hall_09",
"is_prestige_gate": "false"
},
{
"id": "hall_10_zhuanlun",
"no": 10,
"king_cn": "转轮王",
"king_en": "King of the Wheel",
"motif_cn": "轮回道(六道环)",
"motif_en": "Wheel of Rebirth",
"target_min": 180,
"gate_merit": 1500000000,
"unlock_gen": "none",
"unlock_gate": "none",
"palette_id": "pal_hall_10",
"is_prestige_gate": "true"
}
]
}
+162
View File
@@ -0,0 +1,162 @@
{
"sheet": "meta_upgrades",
"schema_version": 0,
"columns": [
"id",
"name_cn",
"name_en",
"effect_type",
"target_id",
"effect_param",
"cost_suihui",
"max_rank",
"condition",
"desc_cn"
],
"rows": [
{
"id": "meta_01",
"name_cn": "枕旧梦",
"name_en": "Pillow of Old Dreams",
"effect_type": "start_levels",
"target_id": "gen_01_youhun",
"effect_param": 10,
"cost_suihui": 3,
"max_rank": 1,
"condition": "none",
"desc_cn": "新轮回开局自带游魂 ×10 级"
},
{
"id": "meta_02",
"name_cn": "携阴兵",
"name_en": "Bring Ghost Soldiers",
"effect_type": "start_levels",
"target_id": "gen_02_yinbing",
"effect_param": 10,
"cost_suihui": 5,
"max_rank": 1,
"condition": "none",
"desc_cn": "开局自带阴兵 ×10 级"
},
{
"id": "meta_03",
"name_cn": "宿慧通透",
"name_en": "Perspicacious Wisdom",
"effect_type": "suihui_gain_pct",
"target_id": "",
"effect_param": 0.15,
"cost_suihui": 8,
"max_rank": 3,
"condition": "none",
"desc_cn": "宿慧获取 +15%(每阶)"
},
{
"id": "meta_04",
"name_cn": "天生熟练",
"name_en": "Born Skilled",
"effect_type": "skip_interval",
"target_id": "",
"effect_param": 4,
"cost_suihui": 12,
"max_rank": 1,
"condition": "none",
"desc_cn": "跳过机制从每 5 级加快为每 4 级"
},
{
"id": "meta_05",
"name_cn": "阴司人脉",
"name_en": "Netherworld Connections",
"effect_type": "offline_pct",
"target_id": "",
"effect_param": 0.25,
"cost_suihui": 6,
"max_rank": 1,
"condition": "none",
"desc_cn": "离线效率 +25%"
},
{
"id": "meta_06",
"name_cn": "斩鬼刃",
"name_en": "Ghost-Slaying Blade",
"effect_type": "boss_click_pct",
"target_id": "",
"effect_param": 0.3,
"cost_suihui": 10,
"max_rank": 1,
"condition": "none",
"desc_cn": "殿王战点击伤害 +30%"
},
{
"id": "meta_07",
"name_cn": "心安理得",
"name_en": "Easy Conscience",
"effect_type": "wrath_penalty_set",
"target_id": "",
"effect_param": 0.15,
"cost_suihui": 15,
"max_rank": 1,
"condition": "none",
"desc_cn": "冤气对宿慧的削减 30%→15%"
},
{
"id": "meta_08",
"name_cn": "敕封恩典",
"name_en": "Grace of Enthronement",
"effect_type": "global_pct",
"target_id": "",
"effect_param": 0.25,
"cost_suihui": 20,
"max_rank": 2,
"condition": "none",
"desc_cn": "全局 +25%(每阶)"
},
{
"id": "meta_09",
"name_cn": "阴阳眼",
"name_en": "Yin-Yang Eye",
"effect_type": "event_freq_pct",
"target_id": "",
"effect_param": 0.2,
"cost_suihui": 10,
"max_rank": 1,
"condition": "none",
"desc_cn": "事件触发频率 +20%"
},
{
"id": "meta_10",
"name_cn": "超度加速",
"name_en": "Salvation Acceleration",
"effect_type": "souls_mult",
"target_id": "",
"effect_param": 2,
"cost_suihui": 8,
"max_rank": 1,
"condition": "none",
"desc_cn": "超度计数获取 ×2"
},
{
"id": "meta_11",
"name_cn": "老马识途",
"name_en": "Old Horse Knows the Way",
"effect_type": "bulk_unlock",
"target_id": "",
"effect_param": 0,
"cost_suihui": 5,
"max_rank": 1,
"condition": "none",
"desc_cn": "开局即可用批量 ×10"
},
{
"id": "meta_12",
"name_cn": "幽冥权柄",
"name_en": "Authority of the Netherworld",
"effect_type": "conditional_global_pct",
"target_id": "gen_12_tianzi",
"effect_param": 0.1,
"cost_suihui": 25,
"max_rank": 1,
"condition": "gen12_unlocked",
"desc_cn": "愿景档(gen12)解锁后全局再 +10%"
}
]
}
File diff suppressed because it is too large. Load diff
+40
View File
@@ -0,0 +1,40 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "balance_tuning.schema.json",
"title": "balance_tuning 表 schema v0(全局调参数,Balance Bible v1.0 §1/§3.2/§4/§8)",
"description": "架构 §2.1 已列名;容纳七张内容表无归属行的全局参数:三乘区软上限/点击基础值([B§1/§2.2])、离线衰减表 + 72h 封顶([B§8])、冤煞层 22 层公式参数([B§3.2])、宿慧公式/跳过机制/敕封触发/重开曲线([B§4])。kind=number 时 num 有效;kind=text 时 text 有效(另一列填 0/空串)。新增参数 = 加行(稳定 ID)。",
"x-supported-keywords": [
"type", "properties", "required", "items", "enum", "pattern",
"minimum", "maximum", "minLength", "minItems", "maxItems",
"additionalProperties", "const"
],
"x-unique": ["id"],
"type": "object",
"required": ["sheet", "schema_version", "columns", "rows"],
"properties": {
"sheet": { "const": "balance_tuning" },
"schema_version": { "const": 0 },
"columns": {
"type": "array",
"items": { "type": "string" },
"minItems": 5
},
"rows": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["id", "kind", "num", "text", "desc_cn"],
"properties": {
"id": { "type": "string", "pattern": "^tune_[a-z0-9_]+$", "description": "稳定字符串 ID(永不重编号)" },
"kind": { "type": "string", "enum": ["number", "text"] },
"num": { "type": "number", "minimum": 0, "description": "kind=number 时的值;kind=text 时填 0" },
"text": { "type": "string", "description": "kind=text 时的值(如重开曲线 1:0.6:0.4:0.3);kind=number 时为空串" },
"desc_cn": { "type": "string", "minLength": 1 }
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
+67
View File
@@ -0,0 +1,67 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "boss_battles.schema.json",
"title": "boss_battles 表 schema v0(十场殿王战参数,数值抄录自 Balance Bible v1.0 §5)",
"description": "架构 §3.2 CSV 规范落盘。HP = hp_factor × 进入时产出/s × hall_factor([B§5]:强度跟随产出,任何水平 ~30s 的仗;hp_factor=15 为 [B§11 差异#4] 修复值,GDD 旧值 50 已废止)。军列 DPS = 产出/s × army_dps_factor。点击伤害 = 点击价值 × 点击区倍率 × 连击(combo_step/层,combo_cap 封顶)。INTRO 开场伤害 = min(超度值×intro_dmg_souls_factor, HP×intro_dmg_cap_ratio)。通过奖励 = 阴德爆发 HP×reward_hp_ratio。",
"x-supported-keywords": [
"type", "properties", "required", "items", "enum", "pattern",
"minimum", "maximum", "minLength", "minItems", "maxItems",
"additionalProperties", "const"
],
"x-unique": ["id", "hall_id"],
"type": "object",
"required": ["sheet", "schema_version", "columns", "rows"],
"properties": {
"sheet": { "const": "boss_battles" },
"schema_version": { "const": 0 },
"columns": {
"type": "array",
"items": { "type": "string" },
"minItems": 14
},
"rows": {
"type": "array",
"minItems": 10,
"maxItems": 10,
"items": {
"type": "object",
"required": [
"id", "hall_id", "hall_factor", "hp_factor", "army_dps_factor",
"intro_sec", "intro_dmg_souls_factor", "intro_dmg_cap_ratio",
"active_sec", "combo_window_sec", "combo_step", "combo_cap",
"retry_cooldown_sec", "reward_hp_ratio"
],
"properties": {
"id": { "type": "string", "pattern": "^boss_hall_[0-9]{2}$" },
"hall_id": {
"type": "string",
"pattern": "^hall_[0-9]{2}_[a-z]+$",
"description": "halls.csv 行 ID(每殿恰一场)"
},
"hall_factor": {
"type": "number",
"minimum": 1,
"maximum": 3,
"description": "殿系数([B§5] H1-H10 = 1.0/1.1/1.2/1.3/1.4/1.6/1.8/2.0/2.1/2.2)"
},
"hp_factor": {
"const": 15,
"description": "HP 系数([B§5]:15×产出/s×殿系数;[B§11] 由 GDD 50 修正)"
},
"army_dps_factor": { "type": "number", "minimum": 0, "description": "军列 DPS = 产出/s × 本值([B§5] 1.0)" },
"intro_sec": { "type": "integer", "minimum": 0, "description": "INTRO 冲锋过场秒数(3s)" },
"intro_dmg_souls_factor": { "type": "number", "minimum": 0, "description": "开场伤害 = 超度值 × 本值(0.5)" },
"intro_dmg_cap_ratio": { "type": "number", "minimum": 0, "maximum": 1, "description": "开场伤害封顶 = HP × 本值(0.2)" },
"active_sec": { "type": "integer", "minimum": 1, "description": "ACTIVE 主动阶段秒数(30s)" },
"combo_window_sec": { "type": "number", "minimum": 0, "description": "连击窗口(0.6s)" },
"combo_step": { "type": "number", "minimum": 0, "description": "连击每层加成(+2%)" },
"combo_cap": { "type": "number", "minimum": 0, "maximum": 1, "description": "连击封顶(+60%)" },
"retry_cooldown_sec": { "type": "integer", "minimum": 0, "description": "失败后再战冷却(60s;H7 前败即等推 → up_gate_boss_retry)" },
"reward_hp_ratio": { "type": "number", "minimum": 0, "maximum": 1, "description": "通过奖励阴德爆发 = HP × 本值(0.2)" }
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
+61
View File
@@ -0,0 +1,61 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "events.schema.json",
"title": "events 表 schema v0(4 事件,数值抄录自 Balance Bible v1.0 §7)",
"description": "架构 §3.2 CSV 规范落盘。事件倍率走独立瞬时区,不进全局乘区([B§0.3/§7])。参数列语义(按 type 取用):p_mult=output_mult 全产出倍率 / click_output_mult 产出倍率部分;p_clicks=click_challenge 需点击次数 / click_output_mult 点击倍率 / choice 冤气增益下限;p_window_sec=click_challenge 窗口秒 / choice 冤气增益上限(随殿序 lerp);p_penalty+p_penalty_dur_sec=click_challenge 失败惩罚;opt_a_*/opt_b_*=choice 二选一(merit_burst_sec=阴德爆发秒数 / wrath_gain=冤气增益)。出现前提 = up_gate_events(过 H2,[B§2.3])。冤魂申诉超时默认 opt_a(超度)。",
"x-supported-keywords": [
"type", "properties", "required", "items", "enum", "pattern",
"minimum", "maximum", "minLength", "minItems", "maxItems",
"additionalProperties", "const"
],
"x-unique": ["id"],
"type": "object",
"required": ["sheet", "schema_version", "columns", "rows"],
"properties": {
"sheet": { "const": "events" },
"schema_version": { "const": 0 },
"columns": {
"type": "array",
"items": { "type": "string" },
"minItems": 17
},
"rows": {
"type": "array",
"minItems": 4,
"maxItems": 4,
"items": {
"type": "object",
"required": [
"id", "name_cn", "name_en", "trigger_min_min", "trigger_max_min",
"duration_sec", "type", "p_mult", "p_clicks", "p_window_sec",
"p_penalty", "p_penalty_dur_sec", "opt_a_type", "opt_a_param",
"opt_b_type", "opt_b_param", "flavor_cn"
],
"properties": {
"id": { "type": "string", "pattern": "^ev_[0-9]{2}_[a-z]+$" },
"name_cn": { "type": "string", "minLength": 1 },
"name_en": { "type": "string", "minLength": 1, "description": "临时译名,待文策渊 glossary" },
"trigger_min_min": { "type": "integer", "minimum": 1, "description": "触发间隔下限(分钟)" },
"trigger_max_min": { "type": "integer", "minimum": 1, "description": "触发间隔上限(分钟,含)" },
"duration_sec": { "type": "integer", "minimum": 1, "description": "持续秒数(冤魂申诉为非阻塞决策窗)" },
"type": {
"type": "string",
"enum": ["output_mult", "click_challenge", "choice", "click_output_mult"]
},
"p_mult": { "type": "number", "minimum": 0 },
"p_clicks": { "type": "integer", "minimum": 0 },
"p_window_sec": { "type": "number", "minimum": 0 },
"p_penalty": { "type": "number", "minimum": 0, "maximum": 1, "description": "click_challenge 失败惩罚倍率(0.5 = -50%)" },
"p_penalty_dur_sec": { "type": "integer", "minimum": 0 },
"opt_a_type": { "type": "string", "enum": ["none", "merit_burst_sec", "wrath_gain"] },
"opt_a_param": { "type": "number", "minimum": 0 },
"opt_b_type": { "type": "string", "enum": ["none", "merit_burst_sec", "wrath_gain"] },
"opt_b_param": { "type": "number", "minimum": 0 },
"flavor_cn": { "type": "string", "minLength": 1 }
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
+3 -3
View File
@@ -1,8 +1,8 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "generators.schema.json", "$id": "generators.schema.json",
"title": "generators 表 schema v0(十二班底发生器,数值抄录自 GDD §3.1)", "title": "generators 表 schema v0(十二班底发生器,数值抄录自 Balance Bible v1.0 §1)",
"description": "架构 §3.2 CSV 规范落盘。数值列禁千分位;引用列填目标 ID 或 none;ID 一旦分配永不重编号。v0 仅做单表校验:unlock_hall/unlock_gate 引用的 halls.csv / upgrades.csv 目标表 W2 落盘,届时激活跨表引用完整性校验。", "description": "架构 §3.2 CSV 规范落盘。数值列禁千分位;引用列填目标 ID 或 none;ID 一旦分配永不重编号。速率 = 8 的纯幂阶梯(gen1=0.1;gen_n=8^(n-2),[B§1]/[B§11 差异#1] 修复 GDD 6.25× 异常档);成本 gen1→gen2 为 8×(点击养成档,设计意图),gen2 起恒 10×。v0 仅做单表校验:unlock_hall/unlock_gate 引用的 halls.csv / upgrades.csv 目标表已落盘(W2),跨表引用完整性由 DataRegistry 载入时检查。",
"x-supported-keywords": [ "x-supported-keywords": [
"type", "properties", "required", "items", "enum", "pattern", "type", "properties", "required", "items", "enum", "pattern",
"minimum", "maximum", "minLength", "minItems", "maxItems", "minimum", "maximum", "minLength", "minItems", "maxItems",
@@ -68,7 +68,7 @@
"unlock_gate": { "unlock_gate": {
"type": "string", "type": "string",
"pattern": "^(none|up_gate_[a-z0-9_]+)$", "pattern": "^(none|up_gate_[a-z0-9_]+)$",
"description": "路引闸门 ID(upgrades.csv 行 ID)或 none;gen_12 的敕封前置(宿慧≥60 且转世≥2)属 upgrades.csv 行内规则,W2 建模" "description": "路引闸门 ID(upgrades.csv 行 ID)或 none;gen_12 的敕封前置 = up_gate_tianzi 需转世≥2([B§4.4],W2 已建模为 upgrades.csv 行内条件)"
}, },
"sprite_row": { "sprite_row": {
"type": "string", "type": "string",
+63
View File
@@ -0,0 +1,63 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "hall_palettes.schema.json",
"title": "hall_palettes 表 schema v0(per-hall 色板行,架构 §5.3 + Balance Bible v1.0 §6 色板列)",
"description": "架构 §3.2 CSV 规范落盘。一行 = 一殿:水墨四色(墨/骨白/朱砂/靛)+ 该殿色温 accent + 流向向量 + LUT 索引 + 四色面积纪律百分比。hex 为工程起始值,W5 look-dev 冻结评审按截图直方图校准(架构 §10.1 评审清单 #1)。lut_generator.tres 消费本表。",
"x-supported-keywords": [
"type", "properties", "required", "items", "enum", "pattern",
"minimum", "maximum", "minLength", "minItems", "maxItems",
"additionalProperties", "const"
],
"x-unique": ["id", "hall_id", "lut_index"],
"type": "object",
"required": ["sheet", "schema_version", "columns", "rows"],
"properties": {
"sheet": { "const": "hall_palettes" },
"schema_version": { "const": 0 },
"columns": {
"type": "array",
"items": { "type": "string" },
"minItems": 15
},
"rows": {
"type": "array",
"minItems": 10,
"maxItems": 10,
"items": {
"type": "object",
"required": [
"id", "hall_id", "name_cn", "ink_hex", "bone_hex", "cinnabar_hex",
"indigo_hex", "accent_hex", "accent_name_cn", "flow", "lut_index",
"ink_pct", "bone_pct", "cinnabar_max_pct", "indigo_max_pct"
],
"properties": {
"id": { "type": "string", "pattern": "^pal_hall_[0-9]{2}$" },
"hall_id": {
"type": "string",
"pattern": "^hall_[0-9]{2}_[a-z]+$",
"description": "halls.csv 行 ID(每殿恰一行)"
},
"name_cn": { "type": "string", "minLength": 1, "description": "色板行名([B§6]:冷靛/冰青/暗红/锈橙/玄黑/铅灰/烬红/墨绿/黛紫/骨白+金)" },
"ink_hex": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$", "description": "墨(默认 #1A1A1A,架构 §7.3 管线规格)" },
"bone_hex": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$", "description": "骨白(纸底)" },
"cinnabar_hex": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$", "description": "朱砂(强调,面积 ≤8%)" },
"indigo_hex": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$", "description": "靛(次强调,面积 ≤7%;阴德方印编码用)" },
"accent_hex": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$", "description": "该殿色温 accent(工程起始值,W5 校准)" },
"accent_name_cn": { "type": "string", "minLength": 1 },
"flow": {
"type": "string",
"enum": ["seep_down", "bleed_left"],
"description": "纸纹流向向量:自上而下渗 vs 向左横溢洇(架构 §5.3;工程起始值,W5 校准)"
},
"lut_index": { "type": "integer", "minimum": 1, "maximum": 10, "description": "LUT 索引(assets/luts/hall_XX.png)" },
"ink_pct": { "type": "integer", "minimum": 0, "maximum": 100, "description": "墨面积目标%([B] 65)" },
"bone_pct": { "type": "integer", "minimum": 0, "maximum": 100, "description": "骨白面积目标%([B] 25)" },
"cinnabar_max_pct": { "type": "integer", "minimum": 0, "maximum": 100, "description": "朱砂面积硬顶([B] ≤8)" },
"indigo_max_pct": { "type": "integer", "minimum": 0, "maximum": 100, "description": "靛面积硬顶([B] ≤7)" }
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
+79
View File
@@ -0,0 +1,79 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "halls.schema.json",
"title": "halls 表 schema v0(十殿 band 数据行,数值抄录自 Balance Bible v1.0 §6)",
"description": "架构 §3.2 CSV 规范落盘。每殿 = 一个横向 band(2-3 屏宽 @1920,四层组装)。殿门 = 单计数器闸门(本轮累计阴德 merit_run ≥ gate_merit,[B§0.1]);is_prestige_gate=true 的行是轮回转世触发门(仅 H10)。unlock_gen/unlock_gate = 过殿后解锁批。",
"x-supported-keywords": [
"type", "properties", "required", "items", "enum", "pattern",
"minimum", "maximum", "minLength", "minItems", "maxItems",
"additionalProperties", "const"
],
"x-unique": ["id"],
"type": "object",
"required": ["sheet", "schema_version", "columns", "rows"],
"properties": {
"sheet": { "const": "halls" },
"schema_version": { "const": 0 },
"columns": {
"type": "array",
"items": { "type": "string" },
"minItems": 12
},
"rows": {
"type": "array",
"minItems": 10,
"maxItems": 10,
"items": {
"type": "object",
"required": [
"id", "no", "king_cn", "king_en", "motif_cn", "motif_en",
"target_min", "gate_merit", "unlock_gen", "unlock_gate",
"palette_id", "is_prestige_gate"
],
"properties": {
"id": {
"type": "string",
"pattern": "^hall_[0-9]{2}_[a-z]+$",
"description": "稳定字符串 ID(W1 已分配);永不重编号(年更=加行)"
},
"no": { "type": "integer", "minimum": 1, "maximum": 10 },
"king_cn": { "type": "string", "minLength": 1 },
"king_en": { "type": "string", "minLength": 1, "description": "临时译名,待文策渊 glossary" },
"motif_cn": { "type": "string", "minLength": 1, "description": "刑典母题(剪影+文字暗示路线,GDD §0.3 IP 红线)" },
"motif_en": { "type": "string", "minLength": 1 },
"target_min": {
"type": "integer",
"minimum": 1,
"description": "到达目标时长(分钟,[B§6] pacing 表:25→180min);模拟器门 #3/#5/#14 对照"
},
"gate_merit": {
"type": "integer",
"minimum": 0,
"description": "殿门条件:本轮累计阴德(单计数器;[B§6] 2K→1.5B)"
},
"unlock_gen": {
"type": "string",
"pattern": "^(none|gen_[0-9]{2}_[a-z0-9]+)$",
"description": "过殿解锁的班底 ID;H10 = none(prestige 门)"
},
"unlock_gate": {
"type": "string",
"pattern": "^(none|up_gate_[a-z0-9_]+)$",
"description": "过殿出现的路引闸门 ID(upgrades.csv);H10 = none"
},
"palette_id": {
"type": "string",
"pattern": "^pal_hall_[0-9]{2}$",
"description": "hall_palettes.csv 行 ID(per-hall 色板 + 流向向量)"
},
"is_prestige_gate": {
"type": "boolean",
"description": "true = 轮回转世触发门([B§4.1]:仅 H10 转轮王)"
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
+64
View File
@@ -0,0 +1,64 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "meta_upgrades.schema.json",
"title": "meta_upgrades 表 schema v0(12 条宿慧商店,数值抄录自 Balance Bible v1.0 §2.5)",
"description": "架构 §3.2 CSV 规范落盘。货币 = 宿慧(跨轮回永久,永不离场——身份资产层)。max_rank>1 可重复购买(每阶独立计费)。condition=gen12_unlocked 表示效果仅 gen12 解锁后生效(meta_12)。effect_type 语义:start_levels=新轮回开局 target_gen 自带 param 级;suihui_gain_pct=宿慧获取 +param;skip_interval=跳过机制间隔改为 param 级;offline_pct=离线效率 +param;boss_click_pct=殿王战点击伤害 +param;wrath_penalty_set=冤气削减设为 param(替代默认 0.3);global_pct=全局 +param/阶;event_freq_pct=事件频率 +param;souls_mult=超度计数 ×param;bulk_unlock=开局批量×10;conditional_global_pct=条件全局 +param。",
"x-supported-keywords": [
"type", "properties", "required", "items", "enum", "pattern",
"minimum", "maximum", "minLength", "minItems", "maxItems",
"additionalProperties", "const"
],
"x-unique": ["id"],
"type": "object",
"required": ["sheet", "schema_version", "columns", "rows"],
"properties": {
"sheet": { "const": "meta_upgrades" },
"schema_version": { "const": 0 },
"columns": {
"type": "array",
"items": { "type": "string" },
"minItems": 10
},
"rows": {
"type": "array",
"minItems": 12,
"maxItems": 12,
"items": {
"type": "object",
"required": [
"id", "name_cn", "name_en", "effect_type", "target_id",
"effect_param", "cost_suihui", "max_rank", "condition", "desc_cn"
],
"properties": {
"id": { "type": "string", "pattern": "^meta_[0-9]{2}$" },
"name_cn": { "type": "string", "minLength": 1 },
"name_en": { "type": "string", "minLength": 1, "description": "临时译名,待文策渊 glossary" },
"effect_type": {
"type": "string",
"enum": [
"start_levels", "suihui_gain_pct", "skip_interval", "offline_pct",
"boss_click_pct", "wrath_penalty_set", "global_pct", "event_freq_pct",
"souls_mult", "bulk_unlock", "conditional_global_pct"
]
},
"target_id": {
"type": "string",
"pattern": "^(|gen_[0-9]{2}_[a-z0-9]+)$",
"description": "start_levels/conditional_global_pct 的目标班底 ID;其余为空串"
},
"effect_param": { "type": "number", "minimum": 0 },
"cost_suihui": { "type": "integer", "minimum": 1, "description": "宿慧成本([B§2.5] 钉值 3-25)" },
"max_rank": { "type": "integer", "minimum": 1, "description": "可购买阶数上限(m03=3、m08=2,其余 1)" },
"condition": {
"type": "string",
"enum": ["none", "gen12_unlocked"],
"description": "效果生效条件"
},
"desc_cn": { "type": "string", "minLength": 1 }
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
+81
View File
@@ -0,0 +1,81 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "upgrades.schema.json",
"title": "upgrades 表 schema v0(108 条升级,数值抄录自 Balance Bible v1.0 §2)",
"description": "架构 §3.2 CSV 规范落盘。配比 [B§2]:效率 72(12 班底×6 级,+100%/级加法,成本 C0×r^(10+4n) 取整)+ 点击 12(L1-4 ×2;L5-12 +1% 产出/s)+ 路引 18(10 入职文书 + 8 系统闸门,纯解锁零效果)+ 离线 6(×1.15/级)。与 meta_upgrades.csv 合计 120。ID 稳定永不重编号;display_en 为临时译名待文策渊 glossary。requires=enthrone_ready 表示敕封动作就绪(转世≥2 且宿慧≥60)后才可购(仅 up_gate_tianzi,provenance P-12;W2 初版误置 enthroned 会造成死锁,已修)。",
"x-supported-keywords": [
"type", "properties", "required", "items", "enum", "pattern",
"minimum", "maximum", "minLength", "minItems", "maxItems",
"additionalProperties", "const"
],
"x-unique": ["id"],
"type": "object",
"required": ["sheet", "schema_version", "columns", "rows"],
"properties": {
"sheet": { "const": "upgrades" },
"schema_version": { "const": 0 },
"columns": {
"type": "array",
"items": { "type": "string" },
"minItems": 11
},
"rows": {
"type": "array",
"minItems": 108,
"items": {
"type": "object",
"required": [
"id", "name_cn", "name_en", "type", "target_gen", "unlock_hall",
"effect_type", "effect_param", "requires", "cost", "desc_cn"
],
"properties": {
"id": {
"type": "string",
"pattern": "^up_[a-z0-9_]+$",
"description": "稳定字符串 ID:up_eff_<gen_id>_<1-6> / up_click_<1-12> / up_offline_<1-6> / up_gate_<name>(入职文书 ID 与 generators.unlock_gate 引用一致,W1 已分配)"
},
"name_cn": { "type": "string", "minLength": 1 },
"name_en": { "type": "string", "minLength": 1, "description": "临时译名,待文策渊 glossary" },
"type": {
"type": "string",
"enum": ["efficiency", "click", "gate_hire", "gate_system", "offline"],
"description": "efficiency=班底效率;click=点击;gate_hire=班底入职文书(target_gen 解锁);gate_system=系统闸门;offline=离线效率"
},
"target_gen": {
"type": "string",
"pattern": "^(none|gen_[0-9]{2}_[a-z0-9]+)$",
"description": "efficiency/gate_hire 指向的班底 ID(generators.csv);其余 none"
},
"unlock_hall": {
"type": "string",
"pattern": "^(none|hall_[0-9]{2}_[a-z]+)$",
"description": "出现条件:通过哪殿后可见/可购;efficiency 随所属班底殿门,offline 随 H4(离线收益系统闸门),click 无门"
},
"effect_type": {
"type": "string",
"enum": ["gen_output_add", "click_mult", "click_output_pct", "offline_mult", "unlock"],
"description": "gen_output_add=班底产出加法(+param);click_mult=点击价值×param;click_output_pct=点击附加产出/s 的 param 比例;offline_mult=离线效率×param;unlock=纯解锁(param=0)"
},
"effect_param": {
"type": "number",
"minimum": 0,
"description": "效果参数(见 effect_type);efficiency=1(+100%),click_mult=2,click_output_pct=0.01,offline_mult=1.15,unlock=0"
},
"requires": {
"type": "string",
"enum": ["none", "enthrone_ready", "enthroned"],
"description": "购买前置条件:none=无;enthrone_ready=转世≥2 且宿慧≥60(敕封动作就绪,仅 up_gate_tianzi,provenance P-12);enthroned=需已受敕(预留枚举,现无行使用)"
},
"cost": {
"type": "integer",
"minimum": 0,
"description": "阴德成本(禁千分位)。效率 = round(C0×r^(10+4n));入职文书 = round(C0×2/3)(g03=800 钉值复现);系统闸门 = 殿门阈值÷2(工程外推, provenance P-5);点击/离线 = [B§2.2/§2.4] 钉值"
},
"desc_cn": { "type": "string", "minLength": 1 }
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
+31
View File
@@ -0,0 +1,31 @@
id,kind,num,text,desc_cn
tune_click_base,number,1,,[B§2.2] 点击基础值 = 1(与 gen1 成本 15 配对 → 15 次点击首购)
tune_click_zone_cap,number,16,,[B§1/§2.2] 点击区软上限 ×16(4 级 ×2 满)
tune_gen_zone_cap,number,8,,[B§1] 班底乘区软上限 ×8(6 级效率满 = ×7,留 1 级余量)
tune_global_zone_cap,number,64,,[B§1] 全局乘区软上限 ×64
tune_offline_band1_h,number,2,,[B§8] 离线 0-2h 速率 100%
tune_offline_band1_rate,number,1,,[B§8] 离线 0-2h 速率
tune_offline_band2_h,number,8,,[B§8] 离线 2-8h 速率 70%
tune_offline_band2_rate,number,0.7,,[B§8] 离线 2-8h 速率
tune_offline_band3_h,number,24,,[B§8] 离线 8-24h 速率 40%
tune_offline_band3_rate,number,0.4,,[B§8] 离线 8-24h 速率
tune_offline_band4_rate,number,0.1,,[B§8] 离线 >24h 速率 10%
tune_offline_cap_h,number,72,,[B§8] 离线硬上限 72h 封顶
tune_wrath_layers,number,22,,[B§3.2] 冤煞层总数 22
tune_wrath_cost_base,number,8,,[B§3.2] 第 n 层成本 = round(8 × n^1.8)
tune_wrath_cost_exp,number,1.8,,[B§3.2] 冤煞层成本指数
tune_wrath_bonus,number,0.25,,[B§3.2] 每层全局 +25%(加法,进全局乘区)
tune_wrath_penalty,number,0.03,,[B§3.2] 每层宿慧获取削减 +3%
tune_wrath_penalty_cap,number,0.3,,[B§3.2] 宿慧削减封顶 30%
tune_suihui_div,number,1000000,,[B§4.1] 宿慧 = floor(√(本轮累计阴德/1e6) × 5)
tune_suihui_mult,number,5,,[B§4.1] 宿慧公式乘数
tune_suihui_global_pct,number,0.03,,[B§1/§4.2] 每级宿慧全局产出 +3%(加法,进全局乘区)
tune_enthrone_bonus,number,0.25,,[B§4.4] 阴司敕封自动全局恩典 +25%(与 m08 商品字面重叠,见 provenance P-4)
tune_skip_interval,number,5,,[B§4.2] 每 5 级宿慧 → 前 3 档班底各 +25 级(m04 可加快为 4)
tune_skip_gens,number,3,,[B§4.2] 跳过机制覆盖前 3 档班底
tune_skip_levels,number,25,,[B§4.2] 跳过机制每档 +25 级
tune_enthrone_rebirths,number,2,,[B§4.4] 阴司敕封触发:转世 ≥2(约第 2-3 轮,累计 8-12h 为其 pacing 后果)
tune_restart_curve,text,0,1:0.6:0.4:0.3,[B§4.3] 重开曲线目标(第 5 轮触底 ≥0.25×);模拟器门 #13 断言
tune_event_timeout_default,text,0,opt_a,[B§7] 冤魂申诉超时默认 = 超度(opt_a)
tune_souls_div,number,10000,,"[B§6] 超度亡魂 = floor(本轮累计阴德 ÷ 10,000)(叙事计数器,不设闸门;P-14)"
tune_enthrone_suihui_req,number,60,,[G§3.4] 阴司敕封解锁条件:宿慧 ≥60([B§4.4] 仅给 8-12h 体感,采信 GDD 数值)
1 id kind num text desc_cn
2 tune_click_base number 1 [B§2.2] 点击基础值 = 1(与 gen1 成本 15 配对 → 15 次点击首购)
3 tune_click_zone_cap number 16 [B§1/§2.2] 点击区软上限 ×16(4 级 ×2 满)
4 tune_gen_zone_cap number 8 [B§1] 班底乘区软上限 ×8(6 级效率满 = ×7,留 1 级余量)
5 tune_global_zone_cap number 64 [B§1] 全局乘区软上限 ×64
6 tune_offline_band1_h number 2 [B§8] 离线 0-2h 速率 100%
7 tune_offline_band1_rate number 1 [B§8] 离线 0-2h 速率
8 tune_offline_band2_h number 8 [B§8] 离线 2-8h 速率 70%
9 tune_offline_band2_rate number 0.7 [B§8] 离线 2-8h 速率
10 tune_offline_band3_h number 24 [B§8] 离线 8-24h 速率 40%
11 tune_offline_band3_rate number 0.4 [B§8] 离线 8-24h 速率
12 tune_offline_band4_rate number 0.1 [B§8] 离线 >24h 速率 10%
13 tune_offline_cap_h number 72 [B§8] 离线硬上限 72h 封顶
14 tune_wrath_layers number 22 [B§3.2] 冤煞层总数 22
15 tune_wrath_cost_base number 8 [B§3.2] 第 n 层成本 = round(8 × n^1.8)
16 tune_wrath_cost_exp number 1.8 [B§3.2] 冤煞层成本指数
17 tune_wrath_bonus number 0.25 [B§3.2] 每层全局 +25%(加法,进全局乘区)
18 tune_wrath_penalty number 0.03 [B§3.2] 每层宿慧获取削减 +3%
19 tune_wrath_penalty_cap number 0.3 [B§3.2] 宿慧削减封顶 30%
20 tune_suihui_div number 1000000 [B§4.1] 宿慧 = floor(√(本轮累计阴德/1e6) × 5)
21 tune_suihui_mult number 5 [B§4.1] 宿慧公式乘数
22 tune_suihui_global_pct number 0.03 [B§1/§4.2] 每级宿慧全局产出 +3%(加法,进全局乘区)
23 tune_enthrone_bonus number 0.25 [B§4.4] 阴司敕封自动全局恩典 +25%(与 m08 商品字面重叠,见 provenance P-4)
24 tune_skip_interval number 5 [B§4.2] 每 5 级宿慧 → 前 3 档班底各 +25 级(m04 可加快为 4)
25 tune_skip_gens number 3 [B§4.2] 跳过机制覆盖前 3 档班底
26 tune_skip_levels number 25 [B§4.2] 跳过机制每档 +25 级
27 tune_enthrone_rebirths number 2 [B§4.4] 阴司敕封触发:转世 ≥2(约第 2-3 轮,累计 8-12h 为其 pacing 后果)
28 tune_restart_curve text 0 1:0.6:0.4:0.3 [B§4.3] 重开曲线目标(第 5 轮触底 ≥0.25×);模拟器门 #13 断言
29 tune_event_timeout_default text 0 opt_a [B§7] 冤魂申诉超时默认 = 超度(opt_a)
30 tune_souls_div number 10000 [B§6] 超度亡魂 = floor(本轮累计阴德 ÷ 10,000)(叙事计数器,不设闸门;P-14)
31 tune_enthrone_suihui_req number 60 [G§3.4] 阴司敕封解锁条件:宿慧 ≥60([B§4.4] 仅给 8-12h 体感,采信 GDD 数值)
+11
View File
@@ -0,0 +1,11 @@
id,hall_id,hall_factor,hp_factor,army_dps_factor,intro_sec,intro_dmg_souls_factor,intro_dmg_cap_ratio,active_sec,combo_window_sec,combo_step,combo_cap,retry_cooldown_sec,reward_hp_ratio
boss_hall_01,hall_01_qinguang,1,15,1,3,0.5,0.2,30,0.6,0.02,0.6,60,0.2
boss_hall_02,hall_02_chujiang,1.1,15,1,3,0.5,0.2,30,0.6,0.02,0.6,60,0.2
boss_hall_03,hall_03_songdi,1.2,15,1,3,0.5,0.2,30,0.6,0.02,0.6,60,0.2
boss_hall_04,hall_04_wuguan,1.3,15,1,3,0.5,0.2,30,0.6,0.02,0.6,60,0.2
boss_hall_05,hall_05_yama,1.4,15,1,3,0.5,0.2,30,0.6,0.02,0.6,60,0.2
boss_hall_06,hall_06_biancheng,1.6,15,1,3,0.5,0.2,30,0.6,0.02,0.6,60,0.2
boss_hall_07,hall_07_taishan,1.8,15,1,3,0.5,0.2,30,0.6,0.02,0.6,60,0.2
boss_hall_08,hall_08_dushi,2,15,1,3,0.5,0.2,30,0.6,0.02,0.6,60,0.2
boss_hall_09,hall_09_pingdeng,2.1,15,1,3,0.5,0.2,30,0.6,0.02,0.6,60,0.2
boss_hall_10,hall_10_zhuanlun,2.2,15,1,3,0.5,0.2,30,0.6,0.02,0.6,60,0.2
1 id hall_id hall_factor hp_factor army_dps_factor intro_sec intro_dmg_souls_factor intro_dmg_cap_ratio active_sec combo_window_sec combo_step combo_cap retry_cooldown_sec reward_hp_ratio
2 boss_hall_01 hall_01_qinguang 1 15 1 3 0.5 0.2 30 0.6 0.02 0.6 60 0.2
3 boss_hall_02 hall_02_chujiang 1.1 15 1 3 0.5 0.2 30 0.6 0.02 0.6 60 0.2
4 boss_hall_03 hall_03_songdi 1.2 15 1 3 0.5 0.2 30 0.6 0.02 0.6 60 0.2
5 boss_hall_04 hall_04_wuguan 1.3 15 1 3 0.5 0.2 30 0.6 0.02 0.6 60 0.2
6 boss_hall_05 hall_05_yama 1.4 15 1 3 0.5 0.2 30 0.6 0.02 0.6 60 0.2
7 boss_hall_06 hall_06_biancheng 1.6 15 1 3 0.5 0.2 30 0.6 0.02 0.6 60 0.2
8 boss_hall_07 hall_07_taishan 1.8 15 1 3 0.5 0.2 30 0.6 0.02 0.6 60 0.2
9 boss_hall_08 hall_08_dushi 2 15 1 3 0.5 0.2 30 0.6 0.02 0.6 60 0.2
10 boss_hall_09 hall_09_pingdeng 2.1 15 1 3 0.5 0.2 30 0.6 0.02 0.6 60 0.2
11 boss_hall_10 hall_10_zhuanlun 2.2 15 1 3 0.5 0.2 30 0.6 0.02 0.6 60 0.2
+5
View File
@@ -0,0 +1,5 @@
id,name_cn,name_en,trigger_min_min,trigger_max_min,duration_sec,type,p_mult,p_clicks,p_window_sec,p_penalty,p_penalty_dur_sec,opt_a_type,opt_a_param,opt_b_type,opt_b_param,flavor_cn
ev_01_baigui,百鬼夜行,Night of the Hundred Ghosts,8,15,60,output_mult,5,0,0,0,0,none,0,none,0,百鬼倾巢而出,阴阳秩序暂时失序
ev_02_naoxiang,阴兵闹饷,Ghost Mutiny,10,18,15,click_challenge,0,10,15,0.5,60,none,0,none,0,鬼卒也要三牲祭,不然哗变
ev_03_shensu,冤魂申诉,Grievance of the Wronged,6,12,45,choice,0,15,40,0,0,merit_burst_sec,30,wrath_gain,0,他跪在地上喊冤枉。你选择……
ev_04_tiangou,天狗食月,Heavenly Dog Eats the Moon,20,35,30,click_output_mult,2,10,0,0,0,none,0,none,0,天狗噬月,阴气最盛——动手
1 id name_cn name_en trigger_min_min trigger_max_min duration_sec type p_mult p_clicks p_window_sec p_penalty p_penalty_dur_sec opt_a_type opt_a_param opt_b_type opt_b_param flavor_cn
2 ev_01_baigui 百鬼夜行 Night of the Hundred Ghosts 8 15 60 output_mult 5 0 0 0 0 none 0 none 0 百鬼倾巢而出,阴阳秩序暂时失序
3 ev_02_naoxiang 阴兵闹饷 Ghost Mutiny 10 18 15 click_challenge 0 10 15 0.5 60 none 0 none 0 鬼卒也要三牲祭,不然哗变
4 ev_03_shensu 冤魂申诉 Grievance of the Wronged 6 12 45 choice 0 15 40 0 0 merit_burst_sec 30 wrath_gain 0 他跪在地上喊冤枉。你选择……
5 ev_04_tiangou 天狗食月 Heavenly Dog Eats the Moon 20 35 30 click_output_mult 2 10 0 0 0 none 0 none 0 天狗噬月,阴气最盛——动手
+9 -9
View File
@@ -2,12 +2,12 @@
gen_01_youhun,游魂,Wandering Souls,15,1.13,0.1,none,none,res://assets/sprites/units/gen_01_youhun gen_01_youhun,游魂,Wandering Souls,15,1.13,0.1,none,none,res://assets/sprites/units/gen_01_youhun
gen_02_yinbing,阴兵,Ghost Soldiers,120,1.14,1,none,none,res://assets/sprites/units/gen_02_yinbing gen_02_yinbing,阴兵,Ghost Soldiers,120,1.14,1,none,none,res://assets/sprites/units/gen_02_yinbing
gen_03_guizu,鬼卒班,Ghost Constables,1200,1.15,8,hall_01_qinguang,up_gate_guizu,res://assets/sprites/units/gen_03_guizu gen_03_guizu,鬼卒班,Ghost Constables,1200,1.15,8,hall_01_qinguang,up_gate_guizu,res://assets/sprites/units/gen_03_guizu
gen_04_biliao,勾魂笔僚,Death-Scribe Clerks,12000,1.15,50,hall_02_chujiang,up_gate_biliao,res://assets/sprites/units/gen_04_biliao gen_04_biliao,勾魂笔僚,Death-Scribe Clerks,12000,1.15,64,hall_02_chujiang,up_gate_biliao,res://assets/sprites/units/gen_04_biliao
gen_05_tangpu,孟婆汤铺,Meng Po's Soup Stall,120000,1.16,400,hall_03_songdi,up_gate_tangpu,res://assets/sprites/units/gen_05_tangpu gen_05_tangpu,孟婆汤铺,Meng Po's Soup Stall,120000,1.16,512,hall_03_songdi,up_gate_tangpu,res://assets/sprites/units/gen_05_tangpu
gen_06_nanmin,枉死城难民团,Refugee Camp of the Wrongfully Dead,1200000,1.16,3000,hall_04_wuguan,up_gate_nanmin,res://assets/sprites/units/gen_06_nanmin gen_06_nanmin,枉死城难民团,Refugee Camp of the Wrongfully Dead,1200000,1.16,4096,hall_04_wuguan,up_gate_nanmin,res://assets/sprites/units/gen_06_nanmin
gen_07_shuban,阴律司书办,Clerks of the Nether Court,12000000,1.17,24000,hall_05_yama,up_gate_shuban,res://assets/sprites/units/gen_07_shuban gen_07_shuban,阴律司书办,Clerks of the Nether Court,12000000,1.17,32768,hall_05_yama,up_gate_shuban,res://assets/sprites/units/gen_07_shuban
gen_08_bingfang,幽都兵坊,Armory of the Dark Capital,120000000,1.17,180000,hall_06_biancheng,up_gate_bingfang,res://assets/sprites/units/gen_08_bingfang gen_08_bingfang,幽都兵坊,Armory of the Dark Capital,120000000,1.17,262144,hall_06_biancheng,up_gate_bingfang,res://assets/sprites/units/gen_08_bingfang
gen_09_wodi,十殿卧底,Sleeper Agents in the Ten Courts,1200000000,1.18,1400000,hall_07_taishan,up_gate_wodi,res://assets/sprites/units/gen_09_wodi gen_09_wodi,十殿卧底,Sleeper Agents in the Ten Courts,1200000000,1.18,2097152,hall_07_taishan,up_gate_wodi,res://assets/sprites/units/gen_09_wodi
gen_10_xingzhe,轮回裂隙行者,Rift Walkers of the Wheel,12000000000,1.18,11000000,hall_08_dushi,up_gate_xingzhe,res://assets/sprites/units/gen_10_xingzhe gen_10_xingzhe,轮回裂隙行者,Rift Walkers of the Wheel,12000000000,1.18,16777216,hall_08_dushi,up_gate_xingzhe,res://assets/sprites/units/gen_10_xingzhe
gen_11_yinhun,六道引魂人,Soul Guides of the Six Paths,120000000000,1.19,85000000,hall_09_pingdeng,up_gate_yinhun,res://assets/sprites/units/gen_11_yinhun gen_11_yinhun,六道引魂人,Soul Guides of the Six Paths,120000000000,1.19,134217728,hall_09_pingdeng,up_gate_yinhun,res://assets/sprites/units/gen_11_yinhun
gen_12_tianzi,幽冥天子,Emperor of the Netherworld,1200000000000,1.20,650000000,none,up_gate_tianzi,res://assets/sprites/units/gen_12_tianzi gen_12_tianzi,幽冥天子,Emperor of the Netherworld,1200000000000,1.2,1073741824,none,up_gate_tianzi,res://assets/sprites/units/gen_12_tianzi
1 id display_cn display_en base_cost cost_ratio base_rate unlock_hall unlock_gate sprite_row
2 gen_01_youhun 游魂 Wandering Souls 15 1.13 0.1 none none res://assets/sprites/units/gen_01_youhun
3 gen_02_yinbing 阴兵 Ghost Soldiers 120 1.14 1 none none res://assets/sprites/units/gen_02_yinbing
4 gen_03_guizu 鬼卒班 Ghost Constables 1200 1.15 8 hall_01_qinguang up_gate_guizu res://assets/sprites/units/gen_03_guizu
5 gen_04_biliao 勾魂笔僚 Death-Scribe Clerks 12000 1.15 50 64 hall_02_chujiang up_gate_biliao res://assets/sprites/units/gen_04_biliao
6 gen_05_tangpu 孟婆汤铺 Meng Po's Soup Stall 120000 1.16 400 512 hall_03_songdi up_gate_tangpu res://assets/sprites/units/gen_05_tangpu
7 gen_06_nanmin 枉死城难民团 Refugee Camp of the Wrongfully Dead 1200000 1.16 3000 4096 hall_04_wuguan up_gate_nanmin res://assets/sprites/units/gen_06_nanmin
8 gen_07_shuban 阴律司书办 Clerks of the Nether Court 12000000 1.17 24000 32768 hall_05_yama up_gate_shuban res://assets/sprites/units/gen_07_shuban
9 gen_08_bingfang 幽都兵坊 Armory of the Dark Capital 120000000 1.17 180000 262144 hall_06_biancheng up_gate_bingfang res://assets/sprites/units/gen_08_bingfang
10 gen_09_wodi 十殿卧底 Sleeper Agents in the Ten Courts 1200000000 1.18 1400000 2097152 hall_07_taishan up_gate_wodi res://assets/sprites/units/gen_09_wodi
11 gen_10_xingzhe 轮回裂隙行者 Rift Walkers of the Wheel 12000000000 1.18 11000000 16777216 hall_08_dushi up_gate_xingzhe res://assets/sprites/units/gen_10_xingzhe
12 gen_11_yinhun 六道引魂人 Soul Guides of the Six Paths 120000000000 1.19 85000000 134217728 hall_09_pingdeng up_gate_yinhun res://assets/sprites/units/gen_11_yinhun
13 gen_12_tianzi 幽冥天子 Emperor of the Netherworld 1200000000000 1.20 1.2 650000000 1073741824 none up_gate_tianzi res://assets/sprites/units/gen_12_tianzi
+11
View File
@@ -0,0 +1,11 @@
id,hall_id,name_cn,ink_hex,bone_hex,cinnabar_hex,indigo_hex,accent_hex,accent_name_cn,flow,lut_index,ink_pct,bone_pct,cinnabar_max_pct,indigo_max_pct
pal_hall_01,hall_01_qinguang,冷靛,#1A1A1A,#F2EDE4,#B4402C,#2C4A6E,#3D5A80,冷靛,bleed_left,1,65,25,8,7
pal_hall_02,hall_02_chujiang,冰青,#1A1A1A,#F2EDE4,#B4402C,#2C4A6E,#8FC1C9,冰青,seep_down,2,65,25,8,7
pal_hall_03,hall_03_songdi,暗红,#1A1A1A,#F2EDE4,#B4402C,#2C4A6E,#7E2E2E,暗红,bleed_left,3,65,25,8,7
pal_hall_04,hall_04_wuguan,锈橙,#1A1A1A,#F2EDE4,#B4402C,#2C4A6E,#A85C32,锈橙,seep_down,4,65,25,8,7
pal_hall_05,hall_05_yama,玄黑,#1A1A1A,#F2EDE4,#B4402C,#2C4A6E,#141414,玄黑,bleed_left,5,65,25,8,7
pal_hall_06,hall_06_biancheng,铅灰,#1A1A1A,#F2EDE4,#B4402C,#2C4A6E,#6E7276,铅灰,seep_down,6,65,25,8,7
pal_hall_07,hall_07_taishan,烬红,#1A1A1A,#F2EDE4,#B4402C,#2C4A6E,#8C3A2B,烬红,bleed_left,7,65,25,8,7
pal_hall_08,hall_08_dushi,墨绿,#1A1A1A,#F2EDE4,#B4402C,#2C4A6E,#2F4A3E,墨绿,seep_down,8,65,25,8,7
pal_hall_09,hall_09_pingdeng,黛紫,#1A1A1A,#F2EDE4,#B4402C,#2C4A6E,#4A3A5C,黛紫,bleed_left,9,65,25,8,7
pal_hall_10,hall_10_zhuanlun,骨白+金,#1A1A1A,#F2EDE4,#B4402C,#2C4A6E,#C9A227,暗金,seep_down,10,65,25,8,7
1 id hall_id name_cn ink_hex bone_hex cinnabar_hex indigo_hex accent_hex accent_name_cn flow lut_index ink_pct bone_pct cinnabar_max_pct indigo_max_pct
2 pal_hall_01 hall_01_qinguang 冷靛 #1A1A1A #F2EDE4 #B4402C #2C4A6E #3D5A80 冷靛 bleed_left 1 65 25 8 7
3 pal_hall_02 hall_02_chujiang 冰青 #1A1A1A #F2EDE4 #B4402C #2C4A6E #8FC1C9 冰青 seep_down 2 65 25 8 7
4 pal_hall_03 hall_03_songdi 暗红 #1A1A1A #F2EDE4 #B4402C #2C4A6E #7E2E2E 暗红 bleed_left 3 65 25 8 7
5 pal_hall_04 hall_04_wuguan 锈橙 #1A1A1A #F2EDE4 #B4402C #2C4A6E #A85C32 锈橙 seep_down 4 65 25 8 7
6 pal_hall_05 hall_05_yama 玄黑 #1A1A1A #F2EDE4 #B4402C #2C4A6E #141414 玄黑 bleed_left 5 65 25 8 7
7 pal_hall_06 hall_06_biancheng 铅灰 #1A1A1A #F2EDE4 #B4402C #2C4A6E #6E7276 铅灰 seep_down 6 65 25 8 7
8 pal_hall_07 hall_07_taishan 烬红 #1A1A1A #F2EDE4 #B4402C #2C4A6E #8C3A2B 烬红 bleed_left 7 65 25 8 7
9 pal_hall_08 hall_08_dushi 墨绿 #1A1A1A #F2EDE4 #B4402C #2C4A6E #2F4A3E 墨绿 seep_down 8 65 25 8 7
10 pal_hall_09 hall_09_pingdeng 黛紫 #1A1A1A #F2EDE4 #B4402C #2C4A6E #4A3A5C 黛紫 bleed_left 9 65 25 8 7
11 pal_hall_10 hall_10_zhuanlun 骨白+金 #1A1A1A #F2EDE4 #B4402C #2C4A6E #C9A227 暗金 seep_down 10 65 25 8 7
+11
View File
@@ -0,0 +1,11 @@
id,no,king_cn,king_en,motif_cn,motif_en,target_min,gate_merit,unlock_gen,unlock_gate,palette_id,is_prestige_gate
hall_01_qinguang,1,秦广王,King Qinguang,孽镜台(照心),Mirror of Karma,25,2000,gen_03_guizu,up_gate_guizu,pal_hall_01,false
hall_02_chujiang,2,楚江王,King Chujiang,寒冰狱,Ice Hell,42,30000,gen_04_biliao,up_gate_biliao,pal_hall_02,false
hall_03_songdi,3,宋帝王,King Songdi,剥皮亭,Flaying Pavilion,62,300000,gen_05_tangpu,up_gate_tangpu,pal_hall_03,false
hall_04_wuguan,4,五官王,King Wuguan,沸汤/毒蛇,Scalding Cauldron,84,2000000,gen_06_nanmin,up_gate_nanmin,pal_hall_04,false
hall_05_yama,5,阎罗王,King Yama,叫唤地狱/铁床,Wailing Hell,105,10000000,gen_07_shuban,up_gate_shuban,pal_hall_05,false
hall_06_biancheng,6,卞城王,King Biancheng,石碾,Stone Roller,125,40000000,gen_08_bingfang,up_gate_bingfang,pal_hall_06,false
hall_07_taishan,7,泰山王,King Taishan,热油火坑,Boiling Oil Pit,143,120000000,gen_09_wodi,up_gate_wodi,pal_hall_07,false
hall_08_dushi,8,都市王,King Dushi,泥犁/大石,Avici Stone,160,350000000,gen_10_xingzhe,up_gate_xingzhe,pal_hall_08,false
hall_09_pingdeng,9,平等王,King Pingdeng,拔舌/铁树,Tongue-Ripping Iron Tree,175,800000000,gen_11_yinhun,up_gate_yinhun,pal_hall_09,false
hall_10_zhuanlun,10,转轮王,King of the Wheel,轮回道(六道环),Wheel of Rebirth,180,1500000000,none,none,pal_hall_10,true
1 id no king_cn king_en motif_cn motif_en target_min gate_merit unlock_gen unlock_gate palette_id is_prestige_gate
2 hall_01_qinguang 1 秦广王 King Qinguang 孽镜台(照心) Mirror of Karma 25 2000 gen_03_guizu up_gate_guizu pal_hall_01 false
3 hall_02_chujiang 2 楚江王 King Chujiang 寒冰狱 Ice Hell 42 30000 gen_04_biliao up_gate_biliao pal_hall_02 false
4 hall_03_songdi 3 宋帝王 King Songdi 剥皮亭 Flaying Pavilion 62 300000 gen_05_tangpu up_gate_tangpu pal_hall_03 false
5 hall_04_wuguan 4 五官王 King Wuguan 沸汤/毒蛇 Scalding Cauldron 84 2000000 gen_06_nanmin up_gate_nanmin pal_hall_04 false
6 hall_05_yama 5 阎罗王 King Yama 叫唤地狱/铁床 Wailing Hell 105 10000000 gen_07_shuban up_gate_shuban pal_hall_05 false
7 hall_06_biancheng 6 卞城王 King Biancheng 石碾 Stone Roller 125 40000000 gen_08_bingfang up_gate_bingfang pal_hall_06 false
8 hall_07_taishan 7 泰山王 King Taishan 热油火坑 Boiling Oil Pit 143 120000000 gen_09_wodi up_gate_wodi pal_hall_07 false
9 hall_08_dushi 8 都市王 King Dushi 泥犁/大石 Avici Stone 160 350000000 gen_10_xingzhe up_gate_xingzhe pal_hall_08 false
10 hall_09_pingdeng 9 平等王 King Pingdeng 拔舌/铁树 Tongue-Ripping Iron Tree 175 800000000 gen_11_yinhun up_gate_yinhun pal_hall_09 false
11 hall_10_zhuanlun 10 转轮王 King of the Wheel 轮回道(六道环) Wheel of Rebirth 180 1500000000 none none pal_hall_10 true
+13
View File
@@ -0,0 +1,13 @@
id,name_cn,name_en,effect_type,target_id,effect_param,cost_suihui,max_rank,condition,desc_cn
meta_01,枕旧梦,Pillow of Old Dreams,start_levels,gen_01_youhun,10,3,1,none,新轮回开局自带游魂 ×10 级
meta_02,携阴兵,Bring Ghost Soldiers,start_levels,gen_02_yinbing,10,5,1,none,开局自带阴兵 ×10 级
meta_03,宿慧通透,Perspicacious Wisdom,suihui_gain_pct,,0.15,8,3,none,宿慧获取 +15%(每阶)
meta_04,天生熟练,Born Skilled,skip_interval,,4,12,1,none,跳过机制从每 5 级加快为每 4 级
meta_05,阴司人脉,Netherworld Connections,offline_pct,,0.25,6,1,none,离线效率 +25%
meta_06,斩鬼刃,Ghost-Slaying Blade,boss_click_pct,,0.3,10,1,none,殿王战点击伤害 +30%
meta_07,心安理得,Easy Conscience,wrath_penalty_set,,0.15,15,1,none,冤气对宿慧的削减 30%→15%
meta_08,敕封恩典,Grace of Enthronement,global_pct,,0.25,20,2,none,全局 +25%(每阶)
meta_09,阴阳眼,Yin-Yang Eye,event_freq_pct,,0.2,10,1,none,事件触发频率 +20%
meta_10,超度加速,Salvation Acceleration,souls_mult,,2,8,1,none,超度计数获取 ×2
meta_11,老马识途,Old Horse Knows the Way,bulk_unlock,,0,5,1,none,开局即可用批量 ×10
meta_12,幽冥权柄,Authority of the Netherworld,conditional_global_pct,gen_12_tianzi,0.1,25,1,gen12_unlocked,愿景档(gen12)解锁后全局再 +10%
1 id name_cn name_en effect_type target_id effect_param cost_suihui max_rank condition desc_cn
2 meta_01 枕旧梦 Pillow of Old Dreams start_levels gen_01_youhun 10 3 1 none 新轮回开局自带游魂 ×10 级
3 meta_02 携阴兵 Bring Ghost Soldiers start_levels gen_02_yinbing 10 5 1 none 开局自带阴兵 ×10 级
4 meta_03 宿慧通透 Perspicacious Wisdom suihui_gain_pct 0.15 8 3 none 宿慧获取 +15%(每阶)
5 meta_04 天生熟练 Born Skilled skip_interval 4 12 1 none 跳过机制从每 5 级加快为每 4 级
6 meta_05 阴司人脉 Netherworld Connections offline_pct 0.25 6 1 none 离线效率 +25%
7 meta_06 斩鬼刃 Ghost-Slaying Blade boss_click_pct 0.3 10 1 none 殿王战点击伤害 +30%
8 meta_07 心安理得 Easy Conscience wrath_penalty_set 0.15 15 1 none 冤气对宿慧的削减 30%→15%
9 meta_08 敕封恩典 Grace of Enthronement global_pct 0.25 20 2 none 全局 +25%(每阶)
10 meta_09 阴阳眼 Yin-Yang Eye event_freq_pct 0.2 10 1 none 事件触发频率 +20%
11 meta_10 超度加速 Salvation Acceleration souls_mult 2 8 1 none 超度计数获取 ×2
12 meta_11 老马识途 Old Horse Knows the Way bulk_unlock 0 5 1 none 开局即可用批量 ×10
13 meta_12 幽冥权柄 Authority of the Netherworld conditional_global_pct gen_12_tianzi 0.1 25 1 gen12_unlocked 愿景档(gen12)解锁后全局再 +10%
+109
View File
@@ -0,0 +1,109 @@
id,name_cn,name_en,type,target_gen,unlock_hall,effect_type,effect_param,requires,cost,desc_cn
up_eff_gen_01_youhun_1,游魂 · 历练 I,Wandering Souls · Training I,efficiency,gen_01_youhun,none,gen_output_add,1,none,83,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_01_youhun_2,游魂 · 历练 II,Wandering Souls · Training II,efficiency,gen_01_youhun,none,gen_output_add,1,none,135,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_01_youhun_3,游魂 · 历练 III,Wandering Souls · Training III,efficiency,gen_01_youhun,none,gen_output_add,1,none,221,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_01_youhun_4,游魂 · 历练 IV,Wandering Souls · Training IV,efficiency,gen_01_youhun,none,gen_output_add,1,none,360,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_01_youhun_5,游魂 · 历练 V,Wandering Souls · Training V,efficiency,gen_01_youhun,none,gen_output_add,1,none,587,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_01_youhun_6,游魂 · 历练 VI,Wandering Souls · Training VI,efficiency,gen_01_youhun,none,gen_output_add,1,none,957,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_02_yinbing_1,阴兵 · 历练 I,Ghost Soldiers · Training I,efficiency,gen_02_yinbing,none,gen_output_add,1,none,751,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_02_yinbing_2,阴兵 · 历练 II,Ghost Soldiers · Training II,efficiency,gen_02_yinbing,none,gen_output_add,1,none,1269,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_02_yinbing_3,阴兵 · 历练 III,Ghost Soldiers · Training III,efficiency,gen_02_yinbing,none,gen_output_add,1,none,2143,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_02_yinbing_4,阴兵 · 历练 IV,Ghost Soldiers · Training IV,efficiency,gen_02_yinbing,none,gen_output_add,1,none,3620,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_02_yinbing_5,阴兵 · 历练 V,Ghost Soldiers · Training V,efficiency,gen_02_yinbing,none,gen_output_add,1,none,6114,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_02_yinbing_6,阴兵 · 历练 VI,Ghost Soldiers · Training VI,efficiency,gen_02_yinbing,none,gen_output_add,1,none,10326,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_03_guizu_1,鬼卒班 · 历练 I,Ghost Constables · Training I,efficiency,gen_03_guizu,hall_01_qinguang,gen_output_add,1,none,8491,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_03_guizu_2,鬼卒班 · 历练 II,Ghost Constables · Training II,efficiency,gen_03_guizu,hall_01_qinguang,gen_output_add,1,none,14851,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_03_guizu_3,鬼卒班 · 历练 III,Ghost Constables · Training III,efficiency,gen_03_guizu,hall_01_qinguang,gen_output_add,1,none,25974,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_03_guizu_4,鬼卒班 · 历练 IV,Ghost Constables · Training IV,efficiency,gen_03_guizu,hall_01_qinguang,gen_output_add,1,none,45428,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_03_guizu_5,鬼卒班 · 历练 V,Ghost Constables · Training V,efficiency,gen_03_guizu,hall_01_qinguang,gen_output_add,1,none,79454,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_03_guizu_6,鬼卒班 · 历练 VI,Ghost Constables · Training VI,efficiency,gen_03_guizu,hall_01_qinguang,gen_output_add,1,none,138966,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_04_biliao_1,勾魂笔僚 · 历练 I,Death-Scribe Clerks · Training I,efficiency,gen_04_biliao,hall_02_chujiang,gen_output_add,1,none,84908,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_04_biliao_2,勾魂笔僚 · 历练 II,Death-Scribe Clerks · Training II,efficiency,gen_04_biliao,hall_02_chujiang,gen_output_add,1,none,148505,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_04_biliao_3,勾魂笔僚 · 历练 III,Death-Scribe Clerks · Training III,efficiency,gen_04_biliao,hall_02_chujiang,gen_output_add,1,none,259737,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_04_biliao_4,勾魂笔僚 · 历练 IV,Death-Scribe Clerks · Training IV,efficiency,gen_04_biliao,hall_02_chujiang,gen_output_add,1,none,454282,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_04_biliao_5,勾魂笔僚 · 历练 V,Death-Scribe Clerks · Training V,efficiency,gen_04_biliao,hall_02_chujiang,gen_output_add,1,none,794541,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_04_biliao_6,勾魂笔僚 · 历练 VI,Death-Scribe Clerks · Training VI,efficiency,gen_04_biliao,hall_02_chujiang,gen_output_add,1,none,1389658,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_05_tangpu_1,孟婆汤铺 · 历练 I,Meng Po's Soup Stall · Training I,efficiency,gen_05_tangpu,hall_03_songdi,gen_output_add,1,none,958502,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_05_tangpu_2,孟婆汤铺 · 历练 II,Meng Po's Soup Stall · Training II,efficiency,gen_05_tangpu,hall_03_songdi,gen_output_add,1,none,1735502,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_05_tangpu_3,孟婆汤铺 · 历练 III,Meng Po's Soup Stall · Training III,efficiency,gen_05_tangpu,hall_03_songdi,gen_output_add,1,none,3142368,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_05_tangpu_4,孟婆汤铺 · 历练 IV,Meng Po's Soup Stall · Training IV,efficiency,gen_05_tangpu,hall_03_songdi,gen_output_add,1,none,5689695,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_05_tangpu_5,孟婆汤铺 · 历练 V,Meng Po's Soup Stall · Training V,efficiency,gen_05_tangpu,hall_03_songdi,gen_output_add,1,none,10301985,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_05_tangpu_6,孟婆汤铺 · 历练 VI,Meng Po's Soup Stall · Training VI,efficiency,gen_05_tangpu,hall_03_songdi,gen_output_add,1,none,18653180,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_06_nanmin_1,枉死城难民团 · 历练 I,Refugee Camp of the Wrongfully Dead · Training I,efficiency,gen_06_nanmin,hall_04_wuguan,gen_output_add,1,none,9585022,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_06_nanmin_2,枉死城难民团 · 历练 II,Refugee Camp of the Wrongfully Dead · Training II,efficiency,gen_06_nanmin,hall_04_wuguan,gen_output_add,1,none,17355017,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_06_nanmin_3,枉死城难民团 · 历练 III,Refugee Camp of the Wrongfully Dead · Training III,efficiency,gen_06_nanmin,hall_04_wuguan,gen_output_add,1,none,31423678,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_06_nanmin_4,枉死城难民团 · 历练 IV,Refugee Camp of the Wrongfully Dead · Training IV,efficiency,gen_06_nanmin,hall_04_wuguan,gen_output_add,1,none,56896947,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_06_nanmin_5,枉死城难民团 · 历练 V,Refugee Camp of the Wrongfully Dead · Training V,efficiency,gen_06_nanmin,hall_04_wuguan,gen_output_add,1,none,103019852,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_06_nanmin_6,枉死城难民团 · 历练 VI,Refugee Camp of the Wrongfully Dead · Training VI,efficiency,gen_06_nanmin,hall_04_wuguan,gen_output_add,1,none,186531799,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_07_shuban_1,阴律司书办 · 历练 I,Clerks of the Nether Court · Training I,efficiency,gen_07_shuban,hall_05_yama,gen_output_add,1,none,108089451,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_07_shuban_2,阴律司书办 · 历练 II,Clerks of the Nether Court · Training II,efficiency,gen_07_shuban,hall_05_yama,gen_output_add,1,none,202547440,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_07_shuban_3,阴律司书办 · 历练 III,Clerks of the Nether Court · Training III,efficiency,gen_07_shuban,hall_05_yama,gen_output_add,1,none,379551056,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_07_shuban_4,阴律司书办 · 历练 IV,Clerks of the Nether Court · Training IV,efficiency,gen_07_shuban,hall_05_yama,gen_output_add,1,none,711235870,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_07_shuban_5,阴律司书办 · 历练 V,Clerks of the Nether Court · Training V,efficiency,gen_07_shuban,hall_05_yama,gen_output_add,1,none,1332775800,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_07_shuban_6,阴律司书办 · 历练 VI,Clerks of the Nether Court · Training VI,efficiency,gen_07_shuban,hall_05_yama,gen_output_add,1,none,2497471526,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_08_bingfang_1,幽都兵坊 · 历练 I,Armory of the Dark Capital · Training I,efficiency,gen_08_bingfang,hall_06_biancheng,gen_output_add,1,none,1080894509,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_08_bingfang_2,幽都兵坊 · 历练 II,Armory of the Dark Capital · Training II,efficiency,gen_08_bingfang,hall_06_biancheng,gen_output_add,1,none,2025474395,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_08_bingfang_3,幽都兵坊 · 历练 III,Armory of the Dark Capital · Training III,efficiency,gen_08_bingfang,hall_06_biancheng,gen_output_add,1,none,3795510563,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_08_bingfang_4,幽都兵坊 · 历练 IV,Armory of the Dark Capital · Training IV,efficiency,gen_08_bingfang,hall_06_biancheng,gen_output_add,1,none,7112358700,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_08_bingfang_5,幽都兵坊 · 历练 V,Armory of the Dark Capital · Training V,efficiency,gen_08_bingfang,hall_06_biancheng,gen_output_add,1,none,13327758001,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_08_bingfang_6,幽都兵坊 · 历练 VI,Armory of the Dark Capital · Training VI,efficiency,gen_08_bingfang,hall_06_biancheng,gen_output_add,1,none,24974715257,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_09_wodi_1,十殿卧底 · 历练 I,Sleeper Agents in the Ten Courts · Training I,efficiency,gen_09_wodi,hall_07_taishan,gen_output_add,1,none,12176692765,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_09_wodi_2,十殿卧底 · 历练 II,Sleeper Agents in the Ten Courts · Training II,efficiency,gen_09_wodi,hall_07_taishan,gen_output_add,1,none,23607901124,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_09_wodi_3,十殿卧底 · 历练 III,Sleeper Agents in the Ten Courts · Training III,efficiency,gen_09_wodi,hall_07_taishan,gen_output_add,1,none,45770473659,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_09_wodi_4,十殿卧底 · 历练 IV,Sleeper Agents in the Ten Courts · Training IV,efficiency,gen_09_wodi,hall_07_taishan,gen_output_add,1,none,88738776396,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_09_wodi_5,十殿卧底 · 历练 V,Sleeper Agents in the Ten Courts · Training V,efficiency,gen_09_wodi,hall_07_taishan,gen_output_add,1,none,172044766126,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_09_wodi_6,十殿卧底 · 历练 VI,Sleeper Agents in the Ten Courts · Training VI,efficiency,gen_09_wodi,hall_07_taishan,gen_output_add,1,none,333556566289,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_10_xingzhe_1,轮回裂隙行者 · 历练 I,Rift Walkers of the Wheel · Training I,efficiency,gen_10_xingzhe,hall_08_dushi,gen_output_add,1,none,121766927654,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_10_xingzhe_2,轮回裂隙行者 · 历练 II,Rift Walkers of the Wheel · Training II,efficiency,gen_10_xingzhe,hall_08_dushi,gen_output_add,1,none,236079011240,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_10_xingzhe_3,轮回裂隙行者 · 历练 III,Rift Walkers of the Wheel · Training III,efficiency,gen_10_xingzhe,hall_08_dushi,gen_output_add,1,none,457704736595,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_10_xingzhe_4,轮回裂隙行者 · 历练 IV,Rift Walkers of the Wheel · Training IV,efficiency,gen_10_xingzhe,hall_08_dushi,gen_output_add,1,none,887387763957,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_10_xingzhe_5,轮回裂隙行者 · 历练 V,Rift Walkers of the Wheel · Training V,efficiency,gen_10_xingzhe,hall_08_dushi,gen_output_add,1,none,1720447661255,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_10_xingzhe_6,轮回裂隙行者 · 历练 VI,Rift Walkers of the Wheel · Training VI,efficiency,gen_10_xingzhe,hall_08_dushi,gen_output_add,1,none,3335565662885,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_11_yinhun_1,六道引魂人 · 历练 I,Soul Guides of the Six Paths · Training I,efficiency,gen_11_yinhun,hall_09_pingdeng,gen_output_add,1,none,1370372723145,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_11_yinhun_2,六道引魂人 · 历练 II,Soul Guides of the Six Paths · Training II,efficiency,gen_11_yinhun,hall_09_pingdeng,gen_output_add,1,none,2748062154037,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_11_yinhun_3,六道引魂人 · 历练 III,Soul Guides of the Six Paths · Training III,efficiency,gen_11_yinhun,hall_09_pingdeng,gen_output_add,1,none,5510796789008,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_11_yinhun_4,六道引魂人 · 历练 IV,Soul Guides of the Six Paths · Training IV,efficiency,gen_11_yinhun,hall_09_pingdeng,gen_output_add,1,none,11051016879340,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_11_yinhun_5,六道引魂人 · 历练 V,Soul Guides of the Six Paths · Training V,efficiency,gen_11_yinhun,hall_09_pingdeng,gen_output_add,1,none,22161037458512,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_11_yinhun_6,六道引魂人 · 历练 VI,Soul Guides of the Six Paths · Training VI,efficiency,gen_11_yinhun,hall_09_pingdeng,gen_output_add,1,none,44440397349832,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_12_tianzi_1,幽冥天子 · 历练 I,Emperor of the Netherworld · Training I,efficiency,gen_12_tianzi,none,gen_output_add,1,none,15407021574586,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_12_tianzi_2,幽冥天子 · 历练 II,Emperor of the Netherworld · Training II,efficiency,gen_12_tianzi,none,gen_output_add,1,none,31947999937062,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_12_tianzi_3,幽冥天子 · 历练 III,Emperor of the Netherworld · Training III,efficiency,gen_12_tianzi,none,gen_output_add,1,none,66247372669492,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_12_tianzi_4,幽冥天子 · 历练 IV,Emperor of the Netherworld · Training IV,efficiency,gen_12_tianzi,none,gen_output_add,1,none,137370551967459,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_12_tianzi_5,幽冥天子 · 历练 V,Emperor of the Netherworld · Training V,efficiency,gen_12_tianzi,none,gen_output_add,1,none,284851576559723,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_eff_gen_12_tianzi_6,幽冥天子 · 历练 VI,Emperor of the Netherworld · Training VI,efficiency,gen_12_tianzi,none,gen_output_add,1,none,590668229154242,该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
up_click_1,指诀 · 倍力 I,Gesture · Force I,click,none,none,click_mult,2,none,50,点击价值 ×2(点击区,软上限 ×16)
up_click_2,指诀 · 倍力 II,Gesture · Force II,click,none,none,click_mult,2,none,500,点击价值 ×2(点击区,软上限 ×16)
up_click_3,指诀 · 倍力 III,Gesture · Force III,click,none,none,click_mult,2,none,5000,点击价值 ×2(点击区,软上限 ×16)
up_click_4,指诀 · 倍力 IV,Gesture · Force IV,click,none,none,click_mult,2,none,50000,点击价值 ×2(点击区,软上限 ×16)
up_click_5,指诀 · 引阴 I,Gesture · Soul-Draw 1,click,none,none,click_output_pct,0.01,none,100000,点击附加 +1% 当前产出/s(仪式与殿王战弹药)
up_click_6,指诀 · 引阴 II,Gesture · Soul-Draw 2,click,none,none,click_output_pct,0.01,none,1000000,点击附加 +1% 当前产出/s(仪式与殿王战弹药)
up_click_7,指诀 · 引阴 III,Gesture · Soul-Draw 3,click,none,none,click_output_pct,0.01,none,10000000,点击附加 +1% 当前产出/s(仪式与殿王战弹药)
up_click_8,指诀 · 引阴 IV,Gesture · Soul-Draw 4,click,none,none,click_output_pct,0.01,none,100000000,点击附加 +1% 当前产出/s(仪式与殿王战弹药)
up_click_9,指诀 · 引阴 V,Gesture · Soul-Draw 5,click,none,none,click_output_pct,0.01,none,1000000000,点击附加 +1% 当前产出/s(仪式与殿王战弹药)
up_click_10,指诀 · 引阴 VI,Gesture · Soul-Draw 6,click,none,none,click_output_pct,0.01,none,10000000000,点击附加 +1% 当前产出/s(仪式与殿王战弹药)
up_click_11,指诀 · 引阴 VII,Gesture · Soul-Draw 7,click,none,none,click_output_pct,0.01,none,100000000000,点击附加 +1% 当前产出/s(仪式与殿王战弹药)
up_click_12,指诀 · 引阴 VIII,Gesture · Soul-Draw 8,click,none,none,click_output_pct,0.01,none,1000000000000,点击附加 +1% 当前产出/s(仪式与殿王战弹药)
up_gate_guizu,鬼卒班 · 入职文书,Ghost Constables · Hiring Letter,gate_hire,gen_03_guizu,hall_01_qinguang,unlock,0,none,800,编制闸门:购入后该班底可购置(需先过对应殿门)
up_gate_biliao,勾魂笔僚 · 入职文书,Death-Scribe Clerks · Hiring Letter,gate_hire,gen_04_biliao,hall_02_chujiang,unlock,0,none,8000,编制闸门:购入后该班底可购置(需先过对应殿门)
up_gate_tangpu,孟婆汤铺 · 入职文书,Meng Po's Soup Stall · Hiring Letter,gate_hire,gen_05_tangpu,hall_03_songdi,unlock,0,none,80000,编制闸门:购入后该班底可购置(需先过对应殿门)
up_gate_nanmin,枉死城难民团 · 入职文书,Refugee Camp of the Wrongfully Dead · Hiring Letter,gate_hire,gen_06_nanmin,hall_04_wuguan,unlock,0,none,800000,编制闸门:购入后该班底可购置(需先过对应殿门)
up_gate_shuban,阴律司书办 · 入职文书,Clerks of the Nether Court · Hiring Letter,gate_hire,gen_07_shuban,hall_05_yama,unlock,0,none,8000000,编制闸门:购入后该班底可购置(需先过对应殿门)
up_gate_bingfang,幽都兵坊 · 入职文书,Armory of the Dark Capital · Hiring Letter,gate_hire,gen_08_bingfang,hall_06_biancheng,unlock,0,none,80000000,编制闸门:购入后该班底可购置(需先过对应殿门)
up_gate_wodi,十殿卧底 · 入职文书,Sleeper Agents in the Ten Courts · Hiring Letter,gate_hire,gen_09_wodi,hall_07_taishan,unlock,0,none,800000000,编制闸门:购入后该班底可购置(需先过对应殿门)
up_gate_xingzhe,轮回裂隙行者 · 入职文书,Rift Walkers of the Wheel · Hiring Letter,gate_hire,gen_10_xingzhe,hall_08_dushi,unlock,0,none,8000000000,编制闸门:购入后该班底可购置(需先过对应殿门)
up_gate_yinhun,六道引魂人 · 入职文书,Soul Guides of the Six Paths · Hiring Letter,gate_hire,gen_11_yinhun,hall_09_pingdeng,unlock,0,none,80000000000,编制闸门:购入后该班底可购置(需先过对应殿门)
up_gate_tianzi,幽冥天子 · 敕封诏书,Enthronement Edict,gate_hire,gen_12_tianzi,none,unlock,0,enthrone_ready,800000000000,编制闸门:购入后该班底可购置(需先过对应殿门)
up_gate_wrath,开通 · 冤气系统,Unlock: Wrath System,gate_system,none,hall_01_qinguang,unlock,0,none,1000,系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
up_gate_events,开通 · 事件系统,Unlock: Event System,gate_system,none,hall_02_chujiang,unlock,0,none,15000,系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
up_gate_bulk10,开通 · 批量购置(×10),Unlock: Bulk Buy ×10,gate_system,none,hall_03_songdi,unlock,0,none,150000,系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
up_gate_offline,开通 · 离线收益,Unlock: Offline Income,gate_system,none,hall_04_wuguan,unlock,0,none,1000000,系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
up_gate_prestige,开通 · 轮回预告,Unlock: Rebirth Preview,gate_system,none,hall_06_biancheng,unlock,0,none,20000000,系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
up_gate_boss_retry,开通 · 殿王再战,Unlock: Boss Rematch,gate_system,none,hall_07_taishan,unlock,0,none,60000000,系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
up_gate_bulkmax,开通 · 批量购置(×尽),Unlock: Bulk Buy Max,gate_system,none,hall_08_dushi,unlock,0,none,175000000,系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
up_gate_enthrone,开通 · 敕封预告,Unlock: Enthronement Preview,gate_system,none,hall_09_pingdeng,unlock,0,none,400000000,系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
up_offline_1,离线收益 · 沉淀 I,Offline Income · Settling I,offline,none,hall_04_wuguan,offline_mult,1.15,none,2000,离线效率 ×1.15(作用于衰减表全部时段;6 级满 ×2.31)
up_offline_2,离线收益 · 沉淀 II,Offline Income · Settling II,offline,none,hall_04_wuguan,offline_mult,1.15,none,20000,离线效率 ×1.15(作用于衰减表全部时段;6 级满 ×2.31)
up_offline_3,离线收益 · 沉淀 III,Offline Income · Settling III,offline,none,hall_04_wuguan,offline_mult,1.15,none,200000,离线效率 ×1.15(作用于衰减表全部时段;6 级满 ×2.31)
up_offline_4,离线收益 · 沉淀 IV,Offline Income · Settling IV,offline,none,hall_04_wuguan,offline_mult,1.15,none,2000000,离线效率 ×1.15(作用于衰减表全部时段;6 级满 ×2.31)
up_offline_5,离线收益 · 沉淀 V,Offline Income · Settling V,offline,none,hall_04_wuguan,offline_mult,1.15,none,20000000,离线效率 ×1.15(作用于衰减表全部时段;6 级满 ×2.31)
up_offline_6,离线收益 · 沉淀 VI,Offline Income · Settling VI,offline,none,hall_04_wuguan,offline_mult,1.15,none,200000000,离线效率 ×1.15(作用于衰减表全部时段;6 级满 ×2.31)
1 id name_cn name_en type target_gen unlock_hall effect_type effect_param requires cost desc_cn
2 up_eff_gen_01_youhun_1 游魂 · 历练 I Wandering Souls · Training I efficiency gen_01_youhun none gen_output_add 1 none 83 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
3 up_eff_gen_01_youhun_2 游魂 · 历练 II Wandering Souls · Training II efficiency gen_01_youhun none gen_output_add 1 none 135 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
4 up_eff_gen_01_youhun_3 游魂 · 历练 III Wandering Souls · Training III efficiency gen_01_youhun none gen_output_add 1 none 221 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
5 up_eff_gen_01_youhun_4 游魂 · 历练 IV Wandering Souls · Training IV efficiency gen_01_youhun none gen_output_add 1 none 360 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
6 up_eff_gen_01_youhun_5 游魂 · 历练 V Wandering Souls · Training V efficiency gen_01_youhun none gen_output_add 1 none 587 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
7 up_eff_gen_01_youhun_6 游魂 · 历练 VI Wandering Souls · Training VI efficiency gen_01_youhun none gen_output_add 1 none 957 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
8 up_eff_gen_02_yinbing_1 阴兵 · 历练 I Ghost Soldiers · Training I efficiency gen_02_yinbing none gen_output_add 1 none 751 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
9 up_eff_gen_02_yinbing_2 阴兵 · 历练 II Ghost Soldiers · Training II efficiency gen_02_yinbing none gen_output_add 1 none 1269 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
10 up_eff_gen_02_yinbing_3 阴兵 · 历练 III Ghost Soldiers · Training III efficiency gen_02_yinbing none gen_output_add 1 none 2143 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
11 up_eff_gen_02_yinbing_4 阴兵 · 历练 IV Ghost Soldiers · Training IV efficiency gen_02_yinbing none gen_output_add 1 none 3620 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
12 up_eff_gen_02_yinbing_5 阴兵 · 历练 V Ghost Soldiers · Training V efficiency gen_02_yinbing none gen_output_add 1 none 6114 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
13 up_eff_gen_02_yinbing_6 阴兵 · 历练 VI Ghost Soldiers · Training VI efficiency gen_02_yinbing none gen_output_add 1 none 10326 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
14 up_eff_gen_03_guizu_1 鬼卒班 · 历练 I Ghost Constables · Training I efficiency gen_03_guizu hall_01_qinguang gen_output_add 1 none 8491 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
15 up_eff_gen_03_guizu_2 鬼卒班 · 历练 II Ghost Constables · Training II efficiency gen_03_guizu hall_01_qinguang gen_output_add 1 none 14851 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
16 up_eff_gen_03_guizu_3 鬼卒班 · 历练 III Ghost Constables · Training III efficiency gen_03_guizu hall_01_qinguang gen_output_add 1 none 25974 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
17 up_eff_gen_03_guizu_4 鬼卒班 · 历练 IV Ghost Constables · Training IV efficiency gen_03_guizu hall_01_qinguang gen_output_add 1 none 45428 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
18 up_eff_gen_03_guizu_5 鬼卒班 · 历练 V Ghost Constables · Training V efficiency gen_03_guizu hall_01_qinguang gen_output_add 1 none 79454 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
19 up_eff_gen_03_guizu_6 鬼卒班 · 历练 VI Ghost Constables · Training VI efficiency gen_03_guizu hall_01_qinguang gen_output_add 1 none 138966 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
20 up_eff_gen_04_biliao_1 勾魂笔僚 · 历练 I Death-Scribe Clerks · Training I efficiency gen_04_biliao hall_02_chujiang gen_output_add 1 none 84908 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
21 up_eff_gen_04_biliao_2 勾魂笔僚 · 历练 II Death-Scribe Clerks · Training II efficiency gen_04_biliao hall_02_chujiang gen_output_add 1 none 148505 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
22 up_eff_gen_04_biliao_3 勾魂笔僚 · 历练 III Death-Scribe Clerks · Training III efficiency gen_04_biliao hall_02_chujiang gen_output_add 1 none 259737 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
23 up_eff_gen_04_biliao_4 勾魂笔僚 · 历练 IV Death-Scribe Clerks · Training IV efficiency gen_04_biliao hall_02_chujiang gen_output_add 1 none 454282 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
24 up_eff_gen_04_biliao_5 勾魂笔僚 · 历练 V Death-Scribe Clerks · Training V efficiency gen_04_biliao hall_02_chujiang gen_output_add 1 none 794541 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
25 up_eff_gen_04_biliao_6 勾魂笔僚 · 历练 VI Death-Scribe Clerks · Training VI efficiency gen_04_biliao hall_02_chujiang gen_output_add 1 none 1389658 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
26 up_eff_gen_05_tangpu_1 孟婆汤铺 · 历练 I Meng Po's Soup Stall · Training I efficiency gen_05_tangpu hall_03_songdi gen_output_add 1 none 958502 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
27 up_eff_gen_05_tangpu_2 孟婆汤铺 · 历练 II Meng Po's Soup Stall · Training II efficiency gen_05_tangpu hall_03_songdi gen_output_add 1 none 1735502 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
28 up_eff_gen_05_tangpu_3 孟婆汤铺 · 历练 III Meng Po's Soup Stall · Training III efficiency gen_05_tangpu hall_03_songdi gen_output_add 1 none 3142368 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
29 up_eff_gen_05_tangpu_4 孟婆汤铺 · 历练 IV Meng Po's Soup Stall · Training IV efficiency gen_05_tangpu hall_03_songdi gen_output_add 1 none 5689695 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
30 up_eff_gen_05_tangpu_5 孟婆汤铺 · 历练 V Meng Po's Soup Stall · Training V efficiency gen_05_tangpu hall_03_songdi gen_output_add 1 none 10301985 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
31 up_eff_gen_05_tangpu_6 孟婆汤铺 · 历练 VI Meng Po's Soup Stall · Training VI efficiency gen_05_tangpu hall_03_songdi gen_output_add 1 none 18653180 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
32 up_eff_gen_06_nanmin_1 枉死城难民团 · 历练 I Refugee Camp of the Wrongfully Dead · Training I efficiency gen_06_nanmin hall_04_wuguan gen_output_add 1 none 9585022 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
33 up_eff_gen_06_nanmin_2 枉死城难民团 · 历练 II Refugee Camp of the Wrongfully Dead · Training II efficiency gen_06_nanmin hall_04_wuguan gen_output_add 1 none 17355017 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
34 up_eff_gen_06_nanmin_3 枉死城难民团 · 历练 III Refugee Camp of the Wrongfully Dead · Training III efficiency gen_06_nanmin hall_04_wuguan gen_output_add 1 none 31423678 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
35 up_eff_gen_06_nanmin_4 枉死城难民团 · 历练 IV Refugee Camp of the Wrongfully Dead · Training IV efficiency gen_06_nanmin hall_04_wuguan gen_output_add 1 none 56896947 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
36 up_eff_gen_06_nanmin_5 枉死城难民团 · 历练 V Refugee Camp of the Wrongfully Dead · Training V efficiency gen_06_nanmin hall_04_wuguan gen_output_add 1 none 103019852 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
37 up_eff_gen_06_nanmin_6 枉死城难民团 · 历练 VI Refugee Camp of the Wrongfully Dead · Training VI efficiency gen_06_nanmin hall_04_wuguan gen_output_add 1 none 186531799 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
38 up_eff_gen_07_shuban_1 阴律司书办 · 历练 I Clerks of the Nether Court · Training I efficiency gen_07_shuban hall_05_yama gen_output_add 1 none 108089451 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
39 up_eff_gen_07_shuban_2 阴律司书办 · 历练 II Clerks of the Nether Court · Training II efficiency gen_07_shuban hall_05_yama gen_output_add 1 none 202547440 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
40 up_eff_gen_07_shuban_3 阴律司书办 · 历练 III Clerks of the Nether Court · Training III efficiency gen_07_shuban hall_05_yama gen_output_add 1 none 379551056 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
41 up_eff_gen_07_shuban_4 阴律司书办 · 历练 IV Clerks of the Nether Court · Training IV efficiency gen_07_shuban hall_05_yama gen_output_add 1 none 711235870 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
42 up_eff_gen_07_shuban_5 阴律司书办 · 历练 V Clerks of the Nether Court · Training V efficiency gen_07_shuban hall_05_yama gen_output_add 1 none 1332775800 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
43 up_eff_gen_07_shuban_6 阴律司书办 · 历练 VI Clerks of the Nether Court · Training VI efficiency gen_07_shuban hall_05_yama gen_output_add 1 none 2497471526 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
44 up_eff_gen_08_bingfang_1 幽都兵坊 · 历练 I Armory of the Dark Capital · Training I efficiency gen_08_bingfang hall_06_biancheng gen_output_add 1 none 1080894509 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
45 up_eff_gen_08_bingfang_2 幽都兵坊 · 历练 II Armory of the Dark Capital · Training II efficiency gen_08_bingfang hall_06_biancheng gen_output_add 1 none 2025474395 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
46 up_eff_gen_08_bingfang_3 幽都兵坊 · 历练 III Armory of the Dark Capital · Training III efficiency gen_08_bingfang hall_06_biancheng gen_output_add 1 none 3795510563 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
47 up_eff_gen_08_bingfang_4 幽都兵坊 · 历练 IV Armory of the Dark Capital · Training IV efficiency gen_08_bingfang hall_06_biancheng gen_output_add 1 none 7112358700 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
48 up_eff_gen_08_bingfang_5 幽都兵坊 · 历练 V Armory of the Dark Capital · Training V efficiency gen_08_bingfang hall_06_biancheng gen_output_add 1 none 13327758001 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
49 up_eff_gen_08_bingfang_6 幽都兵坊 · 历练 VI Armory of the Dark Capital · Training VI efficiency gen_08_bingfang hall_06_biancheng gen_output_add 1 none 24974715257 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
50 up_eff_gen_09_wodi_1 十殿卧底 · 历练 I Sleeper Agents in the Ten Courts · Training I efficiency gen_09_wodi hall_07_taishan gen_output_add 1 none 12176692765 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
51 up_eff_gen_09_wodi_2 十殿卧底 · 历练 II Sleeper Agents in the Ten Courts · Training II efficiency gen_09_wodi hall_07_taishan gen_output_add 1 none 23607901124 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
52 up_eff_gen_09_wodi_3 十殿卧底 · 历练 III Sleeper Agents in the Ten Courts · Training III efficiency gen_09_wodi hall_07_taishan gen_output_add 1 none 45770473659 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
53 up_eff_gen_09_wodi_4 十殿卧底 · 历练 IV Sleeper Agents in the Ten Courts · Training IV efficiency gen_09_wodi hall_07_taishan gen_output_add 1 none 88738776396 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
54 up_eff_gen_09_wodi_5 十殿卧底 · 历练 V Sleeper Agents in the Ten Courts · Training V efficiency gen_09_wodi hall_07_taishan gen_output_add 1 none 172044766126 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
55 up_eff_gen_09_wodi_6 十殿卧底 · 历练 VI Sleeper Agents in the Ten Courts · Training VI efficiency gen_09_wodi hall_07_taishan gen_output_add 1 none 333556566289 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
56 up_eff_gen_10_xingzhe_1 轮回裂隙行者 · 历练 I Rift Walkers of the Wheel · Training I efficiency gen_10_xingzhe hall_08_dushi gen_output_add 1 none 121766927654 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
57 up_eff_gen_10_xingzhe_2 轮回裂隙行者 · 历练 II Rift Walkers of the Wheel · Training II efficiency gen_10_xingzhe hall_08_dushi gen_output_add 1 none 236079011240 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
58 up_eff_gen_10_xingzhe_3 轮回裂隙行者 · 历练 III Rift Walkers of the Wheel · Training III efficiency gen_10_xingzhe hall_08_dushi gen_output_add 1 none 457704736595 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
59 up_eff_gen_10_xingzhe_4 轮回裂隙行者 · 历练 IV Rift Walkers of the Wheel · Training IV efficiency gen_10_xingzhe hall_08_dushi gen_output_add 1 none 887387763957 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
60 up_eff_gen_10_xingzhe_5 轮回裂隙行者 · 历练 V Rift Walkers of the Wheel · Training V efficiency gen_10_xingzhe hall_08_dushi gen_output_add 1 none 1720447661255 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
61 up_eff_gen_10_xingzhe_6 轮回裂隙行者 · 历练 VI Rift Walkers of the Wheel · Training VI efficiency gen_10_xingzhe hall_08_dushi gen_output_add 1 none 3335565662885 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
62 up_eff_gen_11_yinhun_1 六道引魂人 · 历练 I Soul Guides of the Six Paths · Training I efficiency gen_11_yinhun hall_09_pingdeng gen_output_add 1 none 1370372723145 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
63 up_eff_gen_11_yinhun_2 六道引魂人 · 历练 II Soul Guides of the Six Paths · Training II efficiency gen_11_yinhun hall_09_pingdeng gen_output_add 1 none 2748062154037 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
64 up_eff_gen_11_yinhun_3 六道引魂人 · 历练 III Soul Guides of the Six Paths · Training III efficiency gen_11_yinhun hall_09_pingdeng gen_output_add 1 none 5510796789008 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
65 up_eff_gen_11_yinhun_4 六道引魂人 · 历练 IV Soul Guides of the Six Paths · Training IV efficiency gen_11_yinhun hall_09_pingdeng gen_output_add 1 none 11051016879340 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
66 up_eff_gen_11_yinhun_5 六道引魂人 · 历练 V Soul Guides of the Six Paths · Training V efficiency gen_11_yinhun hall_09_pingdeng gen_output_add 1 none 22161037458512 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
67 up_eff_gen_11_yinhun_6 六道引魂人 · 历练 VI Soul Guides of the Six Paths · Training VI efficiency gen_11_yinhun hall_09_pingdeng gen_output_add 1 none 44440397349832 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
68 up_eff_gen_12_tianzi_1 幽冥天子 · 历练 I Emperor of the Netherworld · Training I efficiency gen_12_tianzi none gen_output_add 1 none 15407021574586 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
69 up_eff_gen_12_tianzi_2 幽冥天子 · 历练 II Emperor of the Netherworld · Training II efficiency gen_12_tianzi none gen_output_add 1 none 31947999937062 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
70 up_eff_gen_12_tianzi_3 幽冥天子 · 历练 III Emperor of the Netherworld · Training III efficiency gen_12_tianzi none gen_output_add 1 none 66247372669492 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
71 up_eff_gen_12_tianzi_4 幽冥天子 · 历练 IV Emperor of the Netherworld · Training IV efficiency gen_12_tianzi none gen_output_add 1 none 137370551967459 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
72 up_eff_gen_12_tianzi_5 幽冥天子 · 历练 V Emperor of the Netherworld · Training V efficiency gen_12_tianzi none gen_output_add 1 none 284851576559723 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
73 up_eff_gen_12_tianzi_6 幽冥天子 · 历练 VI Emperor of the Netherworld · Training VI efficiency gen_12_tianzi none gen_output_add 1 none 590668229154242 该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)
74 up_click_1 指诀 · 倍力 I Gesture · Force I click none none click_mult 2 none 50 点击价值 ×2(点击区,软上限 ×16)
75 up_click_2 指诀 · 倍力 II Gesture · Force II click none none click_mult 2 none 500 点击价值 ×2(点击区,软上限 ×16)
76 up_click_3 指诀 · 倍力 III Gesture · Force III click none none click_mult 2 none 5000 点击价值 ×2(点击区,软上限 ×16)
77 up_click_4 指诀 · 倍力 IV Gesture · Force IV click none none click_mult 2 none 50000 点击价值 ×2(点击区,软上限 ×16)
78 up_click_5 指诀 · 引阴 I Gesture · Soul-Draw 1 click none none click_output_pct 0.01 none 100000 点击附加 +1% 当前产出/s(仪式与殿王战弹药)
79 up_click_6 指诀 · 引阴 II Gesture · Soul-Draw 2 click none none click_output_pct 0.01 none 1000000 点击附加 +1% 当前产出/s(仪式与殿王战弹药)
80 up_click_7 指诀 · 引阴 III Gesture · Soul-Draw 3 click none none click_output_pct 0.01 none 10000000 点击附加 +1% 当前产出/s(仪式与殿王战弹药)
81 up_click_8 指诀 · 引阴 IV Gesture · Soul-Draw 4 click none none click_output_pct 0.01 none 100000000 点击附加 +1% 当前产出/s(仪式与殿王战弹药)
82 up_click_9 指诀 · 引阴 V Gesture · Soul-Draw 5 click none none click_output_pct 0.01 none 1000000000 点击附加 +1% 当前产出/s(仪式与殿王战弹药)
83 up_click_10 指诀 · 引阴 VI Gesture · Soul-Draw 6 click none none click_output_pct 0.01 none 10000000000 点击附加 +1% 当前产出/s(仪式与殿王战弹药)
84 up_click_11 指诀 · 引阴 VII Gesture · Soul-Draw 7 click none none click_output_pct 0.01 none 100000000000 点击附加 +1% 当前产出/s(仪式与殿王战弹药)
85 up_click_12 指诀 · 引阴 VIII Gesture · Soul-Draw 8 click none none click_output_pct 0.01 none 1000000000000 点击附加 +1% 当前产出/s(仪式与殿王战弹药)
86 up_gate_guizu 鬼卒班 · 入职文书 Ghost Constables · Hiring Letter gate_hire gen_03_guizu hall_01_qinguang unlock 0 none 800 编制闸门:购入后该班底可购置(需先过对应殿门)
87 up_gate_biliao 勾魂笔僚 · 入职文书 Death-Scribe Clerks · Hiring Letter gate_hire gen_04_biliao hall_02_chujiang unlock 0 none 8000 编制闸门:购入后该班底可购置(需先过对应殿门)
88 up_gate_tangpu 孟婆汤铺 · 入职文书 Meng Po's Soup Stall · Hiring Letter gate_hire gen_05_tangpu hall_03_songdi unlock 0 none 80000 编制闸门:购入后该班底可购置(需先过对应殿门)
89 up_gate_nanmin 枉死城难民团 · 入职文书 Refugee Camp of the Wrongfully Dead · Hiring Letter gate_hire gen_06_nanmin hall_04_wuguan unlock 0 none 800000 编制闸门:购入后该班底可购置(需先过对应殿门)
90 up_gate_shuban 阴律司书办 · 入职文书 Clerks of the Nether Court · Hiring Letter gate_hire gen_07_shuban hall_05_yama unlock 0 none 8000000 编制闸门:购入后该班底可购置(需先过对应殿门)
91 up_gate_bingfang 幽都兵坊 · 入职文书 Armory of the Dark Capital · Hiring Letter gate_hire gen_08_bingfang hall_06_biancheng unlock 0 none 80000000 编制闸门:购入后该班底可购置(需先过对应殿门)
92 up_gate_wodi 十殿卧底 · 入职文书 Sleeper Agents in the Ten Courts · Hiring Letter gate_hire gen_09_wodi hall_07_taishan unlock 0 none 800000000 编制闸门:购入后该班底可购置(需先过对应殿门)
93 up_gate_xingzhe 轮回裂隙行者 · 入职文书 Rift Walkers of the Wheel · Hiring Letter gate_hire gen_10_xingzhe hall_08_dushi unlock 0 none 8000000000 编制闸门:购入后该班底可购置(需先过对应殿门)
94 up_gate_yinhun 六道引魂人 · 入职文书 Soul Guides of the Six Paths · Hiring Letter gate_hire gen_11_yinhun hall_09_pingdeng unlock 0 none 80000000000 编制闸门:购入后该班底可购置(需先过对应殿门)
95 up_gate_tianzi 幽冥天子 · 敕封诏书 Enthronement Edict gate_hire gen_12_tianzi none unlock 0 enthrone_ready 800000000000 编制闸门:购入后该班底可购置(需先过对应殿门)
96 up_gate_wrath 开通 · 冤气系统 Unlock: Wrath System gate_system none hall_01_qinguang unlock 0 none 1000 系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
97 up_gate_events 开通 · 事件系统 Unlock: Event System gate_system none hall_02_chujiang unlock 0 none 15000 系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
98 up_gate_bulk10 开通 · 批量购置(×10) Unlock: Bulk Buy ×10 gate_system none hall_03_songdi unlock 0 none 150000 系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
99 up_gate_offline 开通 · 离线收益 Unlock: Offline Income gate_system none hall_04_wuguan unlock 0 none 1000000 系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
100 up_gate_prestige 开通 · 轮回预告 Unlock: Rebirth Preview gate_system none hall_06_biancheng unlock 0 none 20000000 系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
101 up_gate_boss_retry 开通 · 殿王再战 Unlock: Boss Rematch gate_system none hall_07_taishan unlock 0 none 60000000 系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
102 up_gate_bulkmax 开通 · 批量购置(×尽) Unlock: Bulk Buy Max gate_system none hall_08_dushi unlock 0 none 175000000 系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
103 up_gate_enthrone 开通 · 敕封预告 Unlock: Enthronement Preview gate_system none hall_09_pingdeng unlock 0 none 400000000 系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)
104 up_offline_1 离线收益 · 沉淀 I Offline Income · Settling I offline none hall_04_wuguan offline_mult 1.15 none 2000 离线效率 ×1.15(作用于衰减表全部时段;6 级满 ×2.31)
105 up_offline_2 离线收益 · 沉淀 II Offline Income · Settling II offline none hall_04_wuguan offline_mult 1.15 none 20000 离线效率 ×1.15(作用于衰减表全部时段;6 级满 ×2.31)
106 up_offline_3 离线收益 · 沉淀 III Offline Income · Settling III offline none hall_04_wuguan offline_mult 1.15 none 200000 离线效率 ×1.15(作用于衰减表全部时段;6 级满 ×2.31)
107 up_offline_4 离线收益 · 沉淀 IV Offline Income · Settling IV offline none hall_04_wuguan offline_mult 1.15 none 2000000 离线效率 ×1.15(作用于衰减表全部时段;6 级满 ×2.31)
108 up_offline_5 离线收益 · 沉淀 V Offline Income · Settling V offline none hall_04_wuguan offline_mult 1.15 none 20000000 离线效率 ×1.15(作用于衰减表全部时段;6 级满 ×2.31)
109 up_offline_6 离线收益 · 沉淀 VI Offline Income · Settling VI offline none hall_04_wuguan offline_mult 1.15 none 200000000 离线效率 ×1.15(作用于衰减表全部时段;6 级满 ×2.31)
+6 -1
View File
@@ -14,10 +14,15 @@ config_version=5
config/name="WRATH OF THE DEAD" config/name="WRATH OF THE DEAD"
config/description="Wrongfully dead? Raise a ghost army and fight your way back to the living world." config/description="Wrongfully dead? Raise a ghost army and fight your way back to the living world."
run/main_scene="" run/main_scene="res://src/presentation/stage/stage_root.tscn"
config/features=PackedStringArray("4.7", "GL Compatibility") config/features=PackedStringArray("4.7", "GL Compatibility")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[autoload]
DataRegistry="*res://src/game/data_registry.gd"
GameState="*res://src/game/game_state.gd"
[display] [display]
window/size/viewport_width=1280 window/size/viewport_width=1280
+336
View File
@@ -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
+39
View File
@@ -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
+37
View File
@@ -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
+23
View File
@@ -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)
+32
View File
@@ -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)
+635
View File
@@ -0,0 +1,635 @@
class_name EconomyState
extends RefCounted
# ============================================================================
# EconomyState —— 权威经济状态(架构 §4.2;GDD §3 / Balance Bible v1.0 下称 [B§x])
#
# 唯一权威状态持有者(core 侧):游戏(game/GameState)与模拟器(core/sim)
# 驱动同一份 tick()——这就是"模拟器与游戏零漂移"的机制保证(架构 §4.2)。
#
# 纪律(架构 §2.2 core 作用域):
# - 纯 GDScript 逻辑:无 Node / 无场景树 / 无 Autoload 依赖
# - tick() 零分配:只碰预分配 typed 数组与浮点(UI 层 10Hz 拉快照兜底显示)
# - 数值全部来自 BalanceData(data/build/*.json);代码零硬编码
# - 购买是唯一写入口:校验 + 扣费 + 乘区重算都在这里完成(命令模式)
#
# 三乘区([B§1],MultiplierSet 钳制):
# output/s = Σ_g(rate_g × n_g) × M_gen(g) × M_global × 事件瞬时倍率
# 事件倍率是独立瞬时区(set_event_mult),不进全局乘区([B§1/§5])。
#
# W2 范围声明(诚实标注):
# - 殿王战 / 事件系统为 W3+;prestige 触发暂以"H10 殿门达成"为代理
# ([B§5] 殿门与殿王战解耦,W3 换真 boss 胜负判定)
# - 冤气入口(事件吞噬/天狗/放弃超度,[B§3.2])随 W3 事件到位;
# 本类已实现完整 devour / 冤煞层 API 供 sim 门 #12 与 W3 接线
# ============================================================================
## 购买结果 ok 值
const OK := "ok"
var data: BalanceData # 定义表(只读;game/sim 注入)
# ---- 货币与计数器([B§3.1] 阴德为主货币;本轮累计 = 殿门闸门计数器)----
var merit: float = 0.0 # 可花费阴德
var wrath: float = 0.0 # 冤气(第二货币)
var merit_run_total: float = 0.0 # 本轮累计阴德(monotonic;殿门/宿慧口径)
var merit_lifetime: float = 0.0 # 历史累计(红线观测用)
# ---- 班底级数(预分配 typed 数组,长度 = 数据行数 12)----
var gen_levels: Array[int] = []
# ---- 已购升级(id -> true)与宿慧商店阶数(id -> rank)----
var bought: Dictionary = {}
var meta_ranks: Dictionary = {}
# ---- meta 状态(跨轮回保留:[B§4.1])----
var suihui: int = 0 # 宿慧
var rebirths: int = 0 # 转世次数
var enthroned: bool = false # 阴司敕封旗标([B§4.4]/[G§3.4])
var devour_count: int = 0 # 吞噬次数(成就/叙事计数)
# ---- 进度 ----
var halls_passed: int = 0 # 0-10(按殿序单调)
var wrath_layers: int = 0 # 怨煞层([B§3.2] 22 层上限)
# ---- 事件瞬时区(game/EventDirector 写入;W2 默认 1.0)----
var event_mult: float = 1.0
# ---- 调参数缓存(init 时从 data 读取一次;禁硬编码)----
var _click_base: float = 1.0
var _suihui_div: float = 1e6
var _suihui_mult: float = 5.0
var _suihui_global_pct: float = 0.03
var _enthrone_bonus: float = 0.25
var _skip_interval: int = 5
var _skip_gens: int = 3
var _skip_levels: int = 25
var _enthrone_rebirths: int = 2
var _enthrone_suihui_req: int = 60
var _souls_div: float = 10000.0
var _wrath_bonus: float = 0.25
var _wrath_penalty: float = 0.03
var _wrath_penalty_cap: float = 0.3
var _mult: MultiplierSet # 三乘区计算器(输入由 recompute 写入)
func _init(p_data: BalanceData) -> void:
data = p_data
_mult = MultiplierSet.new(
data.tune_num("tune_click_zone_cap", 16.0),
data.tune_num("tune_gen_zone_cap", 8.0),
data.tune_num("tune_global_zone_cap", 64.0))
_click_base = data.tune_num("tune_click_base", 1.0)
_suihui_div = data.tune_num("tune_suihui_div", 1e6)
_suihui_mult = data.tune_num("tune_suihui_mult", 5.0)
_suihui_global_pct = data.tune_num("tune_suihui_global_pct", 0.03)
_enthrone_bonus = data.tune_num("tune_enthrone_bonus", 0.25)
_skip_interval = int(data.tune_num("tune_skip_interval", 5.0))
_skip_gens = int(data.tune_num("tune_skip_gens", 3.0))
_skip_levels = int(data.tune_num("tune_skip_levels", 25.0))
_enthrone_rebirths = int(data.tune_num("tune_enthrone_rebirths", 2.0))
_enthrone_suihui_req = int(data.tune_num("tune_enthrone_suihui_req", 60.0))
_souls_div = data.tune_num("tune_souls_div", 10000.0)
_wrath_bonus = data.tune_num("tune_wrath_bonus", 0.25)
_wrath_penalty = data.tune_num("tune_wrath_penalty", 0.03)
_wrath_penalty_cap = data.tune_num("tune_wrath_penalty_cap", 0.3)
gen_levels.resize(data.generators.size())
recompute()
# ----------------------------------------------------------------------------
# 产出([B§1] 总公式;tick 与快照共用)
# ----------------------------------------------------------------------------
## 全局乘区当前值(快照/调试用)
func global_mult() -> float:
return _mult.global_mult()
## 单班底乘区当前值
func gen_mult(gen_index: int) -> float:
return _mult.gen_mult(data.generators[gen_index].id)
## 总产出/s(含事件瞬时区)
func output_per_sec() -> float:
var sum := 0.0
for i in data.generators.size():
var lv: int = gen_levels[i]
if lv <= 0:
continue
sum += data.generators[i].base_rate * float(lv) * _mult.gen_mult(data.generators[i].id)
return sum * _mult.global_mult() * event_mult
## 点击价值([B§2.2]:基础值 × 点击区倍率;法器附加走全局区,P-15)
func click_value() -> float:
return _click_base * _mult.click_mult()
## 执行一次点击:加阴德(累计口径单调)+ 过殿轮询;返回获得量
func click() -> float:
var gain := click_value()
if gain <= 0.0:
return 0.0
merit += gain
merit_run_total += gain
merit_lifetime += gain
poll_halls()
return gain
## 超度亡魂叙事计数器([B§6]:floor(本轮累计阴德 ÷ 10,000);不设闸门)
func souls_saved() -> int:
return int(floor(merit_run_total / _souls_div))
# ----------------------------------------------------------------------------
# tick(零分配;game 10Hz 累加器步进,sim 虚拟时钟驱动同一函数)
# ----------------------------------------------------------------------------
func tick(dt: float) -> void:
if dt <= 0.0:
return
var gain := output_per_sec() * dt
if gain > 0.0:
merit += gain
merit_run_total += gain
merit_lifetime += gain
BigNumber.assert_within(merit_lifetime, "EconomyState.tick")
poll_halls()
## 殿门轮询(单计数器闸门,[B§0.1]):本轮累计阴德 ≥ gate_merit → 过殿。
## 返回本 tick 新通过的殿数(0 = 无;game 层据此发仪式信号)。
func poll_halls() -> int:
var newly := 0
while halls_passed < data.halls.size():
var h := data.halls[halls_passed]
if merit_run_total < h.gate_merit:
break
halls_passed += 1
newly += 1
return newly
# ----------------------------------------------------------------------------
# 购买命令(唯一写入口;全部返回 {ok=bool, reason=String, ...})
# ----------------------------------------------------------------------------
## 班底是否已解锁(双闸:过殿 = 许可 + 路引 = 编制,[B§2.3] 设计意图)
func generator_unlocked(gen_index: int) -> bool:
if gen_index < 0 or gen_index >= data.generators.size():
return false
var g := data.generators[gen_index]
if g.unlock_hall != "none" and not hall_passed_id(g.unlock_hall):
return false
if g.unlock_gate != "none":
if not bought.has(g.unlock_gate):
return false
# P-12:gen12 愿景档的闸门(敕封诏书,requires=enthrone_ready)
# 只把"受敕动作"备好;班底本身以 enthroned 旗标为最终解锁
# ([B§1] gen12 行"阴司敕封后" + [G§3.4] 效果 a)
var gd := data.upgrade(g.unlock_gate)
if gd != null and gd.requires == "enthrone_ready" and not enthroned:
return false
return true
## 班底第 level_index 级(0 起始)成本
func generator_cost(gen_index: int, level_index: int) -> float:
return data.generators[gen_index].cost_of_level(level_index)
## 从当前级数起连买 count 级的总成本;count = -1 → 最大可买
func generator_bulk_cost(gen_index: int, count: int) -> float:
var g := data.generators[gen_index]
if count < 0:
return g.cost_range(gen_levels[gen_index], max_affordable(gen_index))
return g.cost_range(gen_levels[gen_index], count)
## 当前阴德下该班底还能买几级(几何级数逐级累加;12 档内开销可忽略)
func max_affordable(gen_index: int) -> int:
var g := data.generators[gen_index]
var n := 0
var cost := 0.0
var lv := gen_levels[gen_index]
while n < 10000: # 安全阀:防 cost_ratio≈1 时无限循环
var next := cost + g.cost_of_level(lv + n)
if next > merit:
break
cost = next
n += 1
return n
## 买 count 级(count = -1 → 最大可买);返回 {ok, bought, spent, reason}
func buy_generator(gen_index: int, count: int) -> Dictionary:
if gen_index < 0 or gen_index >= data.generators.size():
return {"ok": false, "bought": 0, "spent": 0.0, "reason": "班底索引越界"}
if not generator_unlocked(gen_index):
return {"ok": false, "bought": 0, "spent": 0.0, "reason": "班底未解锁"}
var g := data.generators[gen_index]
var n := count
if n < 0:
n = max_affordable(gen_index)
if n <= 0:
return {"ok": false, "bought": 0, "spent": 0.0, "reason": "阴德不足"}
var cost := g.cost_range(gen_levels[gen_index], n)
if cost > merit:
# 部分成交:按实际可买数扣(bulk 语义,UI 显示实购数)
n = max_affordable(gen_index)
if n <= 0:
return {"ok": false, "bought": 0, "spent": 0.0, "reason": "阴德不足"}
cost = g.cost_range(gen_levels[gen_index], n)
merit -= cost
gen_levels[gen_index] += n
return {"ok": true, "bought": n, "spent": cost, "reason": OK}
## 升级是否可见(unlock_hall 达成;"none" = 开局)
func upgrade_visible(def: UpgradeDef) -> bool:
if def.unlock_hall == "none":
return true
return hall_passed_id(def.unlock_hall)
## 购买前置(requires 枚举,schema 同步)
func requires_ok(def: UpgradeDef) -> bool:
match def.requires:
"none":
return true
"enthrone_ready":
return enthrone_ready()
"enthroned":
return enthroned
return false
## 非 meta 升级是否可购
func upgrade_available(def: UpgradeDef) -> bool:
return upgrade_visible(def) and requires_ok(def) \
and not bought.has(def.id) and merit >= float(def.cost)
## 买一条升级;返回 {ok, reason}
func buy_upgrade(id: String) -> Dictionary:
var def := data.upgrade(id)
if def == null:
return {"ok": false, "reason": "升级不存在:%s" % id}
if bought.has(id):
return {"ok": false, "reason": "已购入"}
if not upgrade_visible(def):
return {"ok": false, "reason": "未达出现条件"}
if not requires_ok(def):
return {"ok": false, "reason": "未满足前置"}
if merit < float(def.cost):
return {"ok": false, "reason": "阴德不足"}
merit -= float(def.cost)
bought[id] = true
recompute()
return {"ok": true, "reason": OK}
## 宿慧商店商品是否可购(阶数/meta 条件)
func meta_available(def: UpgradeDef) -> bool:
var rank: int = meta_ranks.get(def.id, 0)
if rank >= def.max_rank:
return false
if suihui < def.cost:
return false
# m12 条件:gen12 解锁后才生效(condition = gen12_unlocked)
if def.condition == "gen12_unlocked" and not generator_unlocked(gen_index_of("gen_12_tianzi")):
return false
return true
## 买一阶宿慧商品;返回 {ok, reason, rank}
func buy_meta(id: String) -> Dictionary:
var def := data.meta(id)
if def == null:
return {"ok": false, "reason": "宿慧商品不存在:%s" % id, "rank": 0}
if not meta_available(def):
return {"ok": false, "reason": "不可购(阶数满/宿慧不足/条件未达)", "rank": int(meta_ranks.get(id, 0))}
suihui -= def.cost
var rank: int = int(meta_ranks.get(id, 0)) + 1
meta_ranks[id] = rank
recompute()
return {"ok": true, "reason": OK, "rank": rank}
# ----------------------------------------------------------------------------
# 冤气线([B§3.2];入口 = 事件三出口,W3 接线;sim 门 #12 直接用)
# ----------------------------------------------------------------------------
## 吞噬入口(事件调用):冤气 += lump;计数 +1
func devour(lump: float) -> void:
if lump <= 0.0:
return
wrath += lump
devour_count += 1
## 超度仪式入口(事件调用):阴德 lump(进累计口径,单调,[B§3.1])
func save_soul(lump: float) -> void:
if lump <= 0.0:
return
merit += lump
merit_run_total += lump
merit_lifetime += lump
poll_halls()
## 下一层怨煞成本([B§3.2]:round(8 × n^1.8),n 从 1 起)
func wrath_layer_cost() -> int:
return PrestigeModel.wrath_layer_cost(wrath_layers + 1,
data.tune_num("tune_wrath_cost_base", 8.0),
data.tune_num("tune_wrath_cost_exp", 1.8))
## 买一层怨煞;返回 {ok, reason, layers}
func buy_wrath_layer() -> Dictionary:
var max_layers := int(data.tune_num("tune_wrath_layers", 22.0))
if wrath_layers >= max_layers:
return {"ok": false, "reason": "怨煞层已满(%d 层)" % max_layers, "layers": wrath_layers}
var cost := wrath_layer_cost()
if wrath < float(cost):
return {"ok": false, "reason": "冤气不足", "layers": wrath_layers}
wrath -= float(cost)
wrath_layers += 1
recompute()
return {"ok": true, "reason": OK, "layers": wrath_layers}
## 冤气对宿慧的削减率(每层 +3% 封顶 30%;m07 改 15%,[B§3.2])
func wrath_penalty() -> float:
var override_cap := 0.0
var m07 := data.meta("meta_07")
if m07 != null and meta_ranks.get("meta_07", 0) > 0:
override_cap = m07.effect_param
return PrestigeModel.wrath_penalty(wrath_layers, _wrath_penalty, _wrath_penalty_cap, override_cap)
# ----------------------------------------------------------------------------
# Prestige:轮回转世 + 阴司敕封([B§4] / [G§3.4])
# ----------------------------------------------------------------------------
## 本轮宿慧收益([B§4.1] 公式;m03 加成 + 冤气削减)
func suihui_gain() -> int:
var gain_pct := 0.0
var m03 := data.meta("meta_03")
if m03 != null:
gain_pct = m03.effect_param * float(int(meta_ranks.get("meta_03", 0)))
return PrestigeModel.suihui_gain(merit_run_total, _suihui_div, _suihui_mult,
gain_pct, wrath_penalty())
## 轮回是否可用(W2 代理 = H10 殿门达成;W3 换殿王战胜败判定,[B§5] 解耦备注)
func prestige_available() -> bool:
var h10 := data.hall_by_no(10)
return h10 != null and merit_run_total >= h10.gate_merit
## 敕封动作就绪([G§3.4]:转世 ≥2 且 宿慧 ≥60)
func enthrone_ready() -> bool:
return rebirths >= _enthrone_rebirths and suihui >= _enthrone_suihui_req
## 受敕是否可用(诏书已购 + H10 达成 + 未受敕)
func enthrone_available() -> bool:
return not enthroned and bought.has("up_gate_tianzi") and prestige_available()
## 执行轮回转世:结算宿慧 → 重置本轮 → 应用跳过/开局加成 → 转世 +1
func do_prestige() -> Dictionary:
if not prestige_available():
return {"ok": false, "reason": "未达 H10 殿门"}
var gain := suihui_gain()
suihui += gain
rebirths += 1
# 重置本轮([B§4.1]:班底/升级/货币全清;保留宿慧/宿慧商店/成就/冤案卡册)
merit = 0.0
wrath = 0.0
merit_run_total = 0.0
wrath_layers = 0
devour_count = 0
bought.clear()
# meta_ranks 不清空(宿慧商店跨轮回保留,[B§4.1])
for i in gen_levels.size():
gen_levels[i] = 0
halls_passed = 0
_apply_rebirth_start_levels()
recompute()
return {"ok": true, "reason": OK, "suihui_gain": gain, "rebirths": rebirths}
## 执行阴司敕封([B§4.4]/[G§3.4]):永久旗标 + 全局恩典 + gen12 解锁
func do_enthrone() -> Dictionary:
if enthroned:
return {"ok": false, "reason": "已受敕"}
if not enthrone_available():
return {"ok": false, "reason": "条件未达(诏书/H10)"}
enthroned = true
recompute()
return {"ok": true, "reason": OK}
## 新一世开局自带级数([B§4.2] 跳过机制 + m01/m02 开局加成;P-11 叠加口径)
func _apply_rebirth_start_levels() -> void:
var interval := _skip_interval
var m04 := data.meta("meta_04")
if m04 != null and meta_ranks.get("meta_04", 0) > 0:
interval = int(m04.effect_param)
var skips := PrestigeModel.skip_count(suihui, interval)
var bonus := skips * _skip_levels
var cover := int(mini(float(_skip_gens), float(gen_levels.size())))
for i in cover:
gen_levels[i] += bonus
# m01/m02:开局自带游魂/阴兵 ×10 级(start_levels 效果)
for mid in ["meta_01", "meta_02"]:
var m := data.meta(mid)
if m == null or meta_ranks.get(mid, 0) <= 0:
continue
var gi := gen_index_of(m.target_gen)
if gi >= 0:
gen_levels[gi] += int(m.effect_param)
# ----------------------------------------------------------------------------
# 乘区重算(购买/轮回/敕封后调用;非热路径)
# ----------------------------------------------------------------------------
func recompute() -> void:
# 点击区:L1-4 为 ×2/级(计数);L5-12 法器附加进全局区(P-15,[B§1] 公式口径)
var click_levels := 0
for u in data.upgrades:
if bought.has(u.id) and u.effect_type == "click_mult":
click_levels += 1
_mult.set_click_levels(click_levels, 0.0, 0.01)
# 班底区:per-gen 效率升级数
var eff := {}
for u in data.upgrades:
if u.type != "efficiency" or not bought.has(u.id):
continue
eff[u.target_gen] = int(eff.get(u.target_gen, 0)) + 1
for gid in eff:
_mult.set_gen_eff(gid, eff[gid])
# 全局区加法总和([B§1]:宿慧×3% + 法器附加 + 冤煞层 + 敕封 25% + m08 + m12)
var gadd := float(suihui) * _suihui_global_pct
gadd += float(wrath_layers) * _wrath_bonus
if enthroned:
gadd += _enthrone_bonus
for u in data.upgrades:
if u.effect_type == "click_output_pct" and bought.has(u.id):
gadd += u.effect_param
var m08 := data.meta("meta_08")
if m08 != null:
gadd += m08.effect_param * float(int(meta_ranks.get("meta_08", 0)))
var m12 := data.meta("meta_12")
if m12 != null and meta_ranks.get("meta_12", 0) > 0:
var gi := gen_index_of("gen_12_tianzi")
if gi >= 0 and generator_unlocked(gi):
gadd += m12.effect_param
_mult.set_global_add(gadd)
# ----------------------------------------------------------------------------
# 查询辅助
# ----------------------------------------------------------------------------
## 殿门是否已通过(按殿序单调计数器,[B§0.1])
func hall_passed_id(hall_id: String) -> bool:
var h := data.hall(hall_id)
return h != null and h.no <= halls_passed
## 班底 ID → 索引(-1 = 不存在)
func gen_index_of(gen_id: String) -> int:
for i in data.generators.size():
if data.generators[i].id == gen_id:
return i
return -1
## 当前离线倍率([B§2.4] ×1.15/级链 × m05 加法;P-13 已标注)
func offline_mult() -> float:
var m := 1.0
for u in data.upgrades:
if u.type == "offline" and bought.has(u.id):
m *= u.effect_param
var m05 := data.meta("meta_05")
if m05 != null:
m *= 1.0 + m05.effect_param * float(int(meta_ranks.get("meta_05", 0)))
return m
## 事件瞬时区写入(game/EventDirector 调用;独立区不进全局乘区)
func set_event_mult(m: float) -> void:
event_mult = maxf(0.0, m)
# ----------------------------------------------------------------------------
# 快照(10Hz;UI 拉取。W2 为 Dictionary;W3 换预分配 RefCounted 结构,
# 字段与脏检查纪律见架构 §4.2——当前 UI 层自己做脏检查兜底)
# ----------------------------------------------------------------------------
func snapshot() -> Dictionary:
return {
"merit": merit,
"output_per_sec": output_per_sec(),
"click_value": click_value(),
"wrath": wrath,
"wrath_layers": wrath_layers,
"suihui": suihui,
"suihui_gain": suihui_gain(),
"rebirths": rebirths,
"enthroned": enthroned,
"halls_passed": halls_passed,
"souls_saved": souls_saved(),
"merit_run_total": merit_run_total,
"global_mult": global_mult(),
"gen_levels": gen_levels.duplicate(),
"prestige_available": prestige_available(),
"enthrone_ready": enthone_ready(),
"enthrone_available": enthrone_available(),
"wrath_layer_cost": wrath_layer_cost(),
"gens": _gens_view(),
"upgrades": _upgrades_view(),
"metas": _metas_view(),
"next_hall": _next_hall_view(),
}
## W2 便利视图字段(架构 §4.2 快照纪律的 W2 落地:UI 只读快照 + 发命令)。
## W3 换预分配 EconomySnapshot + UI 脏检查;此处 10Hz 重建小字典可接受。
func _gens_view() -> Array:
var out := []
for i in data.generators.size():
var g := data.generators[i]
var lv := gen_levels[i]
out.append({
"id": g.id,
"name_cn": g.display_cn,
"level": lv,
"cost_next": g.cost_of_level(lv),
"unlocked": generator_unlocked(i),
"affordable": merit >= g.cost_of_level(lv),
# 不含事件瞬时区(事件是瞬时表演,槽位显示稳态产出)
"output": g.base_rate * float(lv) * _mult.gen_mult(g.id) * _mult.global_mult(),
})
return out
func _upgrades_view() -> Array:
var out := []
for u in data.upgrades:
if bought.has(u.id) or not upgrade_visible(u):
continue
out.append({
"id": u.id,
"name_cn": u.name_cn,
"type": u.type,
"cost": u.cost,
"desc_cn": u.desc_cn,
"available": upgrade_available(u),
"requires": u.requires,
})
return out
func _metas_view() -> Array:
var out := []
for m in data.meta_upgrades:
out.append({
"id": m.id,
"name_cn": m.name_cn,
"cost": m.cost,
"rank": int(meta_ranks.get(m.id, 0)),
"max_rank": m.max_rank,
"available": meta_available(m),
"desc_cn": m.desc_cn,
"condition": m.condition,
})
return out
## 下一座未过殿的门进度(UI"距 XX 还差 X"梯度条,[B§6])
func _next_hall_view() -> Dictionary:
if halls_passed >= data.halls.size():
return {}
var h := data.halls[halls_passed]
var out := output_per_sec()
var eta := 0.0
if out > 0.0 and merit_run_total < h.gate_merit:
eta = (h.gate_merit - merit_run_total) / out
return {
"no": h.no,
"king_cn": h.king_cn,
"motif_cn": h.motif_cn,
"gate_merit": h.gate_merit,
"have": merit_run_total,
"eta_sec": eta,
"target_min": h.target_min,
}
+51
View File
@@ -0,0 +1,51 @@
class_name GeneratorDef
extends Resource
# ============================================================================
# GeneratorDef —— 班底(发生器)数据行 → 实例(架构 §3.3 签名级)
#
# 字段与 data/sheets/generators.csv 列一一对应;CSV 数值经 BalanceData
# 注入。.tres 导出为 W3 动作(SheetImporter.export_tres 届时启用);
# 现阶段 BalanceData 用代码构造本 Resource(字段同名,无缝衔接)。
#
# 曲线规则([B§1],模拟器逐行断言):
# - 成本 gen1→gen2 为 8×(点击养成档,设计意图);gen2 起恒 10×
# - 速率 gen1=0.1;gen_n=8^(n-2)(8 的纯幂阶梯,double 精确表示)
# - 单班底产出对级数线性(rate×n);成本指数 → 边际递减 → "买新不买旧"
# ============================================================================
@export var id: String # "gen_01_youhun"
@export var display_cn: String
@export var display_en: String
@export var base_cost: float # C0([B§1];1 级成本)
@export var cost_ratio: float # r(1.13→1.20)
@export var base_rate: float # rate/s(每级产出)
@export var unlock_hall: String # 殿门闸门 ID 或 "none"
@export var unlock_gate: String # 路引闸门 ID 或 "none"
@export var sprite_row: String # ★ ADR-3:唯一允许的逐资产代码 = sprite 路径前缀
## 第 level_index 级(0 起始)的购买成本;level 0 = base_cost
func cost_of_level(level_index: int) -> float:
return base_cost * pow(cost_ratio, float(level_index))
## 从 from_level 起连买 count 级的总成本(几何级数和)
func cost_range(from_level: int, count: int) -> float:
if count <= 0:
return 0.0
var first := cost_of_level(from_level)
var last := cost_of_level(from_level + count - 1)
return first * (pow(cost_ratio, float(count)) - 1.0) / (cost_ratio - 1.0) if cost_ratio > 1.0 else first * count
## 首级回本时间 = C0 / rate([B§1] 门 #8 断言 tier2-6 ∈[120,293]s)
func payback_seconds() -> float:
if base_rate <= 0.0:
return INF
return base_cost / base_rate
## 在持有 level 级、班底乘区 mult 下的产出/s(不含全局乘区)
func output_at(level: int, gen_mult: float) -> float:
return base_rate * float(level) * gen_mult
+83
View File
@@ -0,0 +1,83 @@
class_name MultiplierSet
extends RefCounted
# ============================================================================
# MultiplierSet —— 三显式乘区(架构 §4.2;[B§1] 产出总公式)
#
# output_per_sec = Σ_g (rate_g × n_g) × M_generator(g) × M_global
#
# 乘区([B§0.3] 加法叠加优先;乘性只存在于三个显式乘区且各有软上限):
# 点击区 M_click = 2^click_mult_levels 软上限 ×16(4 级满)
# 班底区 M_gen(g) = 1 + 效率升级数(加法 +100%/级) 软上限 ×8(6 级满 = ×7)
# 全局区 M_global = 1 + Σ(宿慧×3% + 冤煞层×25% + 敕封 25% + m08 阶×25%
# + m12 条件 10% + 法器附加) 软上限 ×64
# 事件倍率 = 独立瞬时区,不与上述叠加(EconomyState.tick 单独乘)
#
# ⚠ P-15(provenance):点击 L5-12(法器附加,CSV effect_type=click_output_pct)
# 的口径裁定——[B§1] 总公式与 GDD §3.2 均把"法器附加"列入 M_global(全局区),
# 与 [B§2.2] 表 prose"点击附加 +1% 当前产出/s"(点击区口径)冲突。
# 工程裁定采信公式层(全局区):EconomyState.recompute 将 click_output_pct
# 参数求和进 set_global_add;本类的 click_output_pct 输入保持 0,
# click_value 第二项暂为 dead。若文策渊裁定改回点击区,只需改 recompute
# 一行(set_click_levels 传计数与步长)——两解都保留在本类能力内。
#
# 本类是纯计算器:输入由 EconomyState 在购买/重生时写入(set_*),
# evaluate 只读钳制。软上限数值来自 balance_tuning.csv(禁硬编码)。
# ============================================================================
var click_zone_cap: float = 16.0
var gen_zone_cap: float = 8.0
var global_zone_cap: float = 64.0
var _click_mult_levels: int = 0 # 0-4(L1-4 点击 ×2)
var _click_output_pct: float = 0.0 # Σ(L5-12) × 0.01
var _gen_eff := {} # gen_id -> int(已购效率升级数 0-6)
var _global_add: float = 0.0 # 全局加法总和(EconomyState 组装)
func _init(p_click_cap: float = 16.0, p_gen_cap: float = 8.0, p_global_cap: float = 64.0) -> void:
click_zone_cap = p_click_cap
gen_zone_cap = p_gen_cap
global_zone_cap = p_global_cap
## 点击区输入(EconomyState 在购买点击升级后调用)
func set_click_levels(mult_levels: int, output_pct: float, pct_step: float) -> void:
_click_mult_levels = mult_levels
_click_output_pct = output_pct * pct_step
## 班底区输入(单班底效率升级数)
func set_gen_eff(gen_id: String, count: int) -> void:
_gen_eff[gen_id] = count
## 全局区加法总和(EconomyState 组装宿慧/冤煞/敕封/meta 后调用)
func set_global_add(total_add: float) -> void:
_global_add = total_add
## 点击区倍率(钳制 ×16)
func click_mult() -> float:
return minf(pow(2.0, float(_click_mult_levels)), click_zone_cap)
## 点击附加比例(+1%×级数;无软上限——[B§2.2] 只给点击区 ×16 上限)
func click_output_pct() -> float:
return _click_output_pct
## 单班底乘区(钳制 ×8)
func gen_mult(gen_id: String) -> float:
var eff: Variant = _gen_eff.get(gen_id, 0)
return minf(1.0 + float(eff), gen_zone_cap)
## 全局乘区(钳制 ×64)
func global_mult() -> float:
return minf(1.0 + _global_add, global_zone_cap)
## 点击价值 = 基础值 × 点击区倍率 + 产出/s × 点击附加比例([B§2.2])
func click_value(click_base: float, output_per_sec: float) -> float:
return click_base * click_mult() + output_per_sec * _click_output_pct
+58
View File
@@ -0,0 +1,58 @@
class_name OfflineModel
extends RefCounted
# ============================================================================
# OfflineModel —— 离线收益衰减表 + 时钟钳制(架构 §4.2/§6.5;[B§8])
#
# 衰减表([B§8],参数来自 balance_tuning.csv):
# 0-2h 100% / 2-8h 70% / 8-24h 40% / >24h 10%;硬上限 72h 封顶
# × 离线升级(最多 ×2.31);时钟回拨钳制为 0(负收益记 0)
#
# 契约校准点([B§8]):离线 8h ≈ 在线 1.5-2h 有效进度(门 #9 断言 ∈[1.5,2.5])。
# ⚠ 已知不一致(provenance P-8):按上表直接求值,8h 离线 = 2h×100% + 6h×70%
# = 6.2h 在线等效 → 比率 6.2 ∉ [1.5,2.5]。表与契约二者其一需文策渊裁定
# (候选:2-8h 降至 ~8% 以下,或契约改 [4,7])。本类按表实现,门 #9 如实报 FAIL。
#
# 静态纯函数:sim 门 #9 与游戏载入时共用(零漂移)。
# ============================================================================
## 分带结算:把秒数拆进各衰减带,返回 {seconds, merit, effective_hours}
static func settle(seconds: float, output_per_sec: float, offline_mult: float,
band1_h: float, band1_rate: float, band2_h: float, band2_rate: float,
band3_h: float, band3_rate: float, band4_rate: float, cap_h: float) -> Dictionary:
var out := {"seconds": 0.0, "merit": 0.0, "effective_hours": 0.0}
var s := seconds
if s <= 0.0:
return out # 时钟回拨/零时长 → 0
s = minf(s, cap_h * 3600.0) # 72h 硬顶
var effective := 0.0
var remaining := s
var bounds := [band1_h * 3600.0, band2_h * 3600.0, band3_h * 3600.0]
var rates := [band1_rate, band2_rate, band3_rate]
var prev := 0.0
for i in bounds.size():
var span: float = bounds[i] - prev
if remaining <= 0.0:
break
var take := minf(remaining, span)
effective += take * rates[i]
remaining -= take
prev = bounds[i]
if remaining > 0.0:
effective += remaining * band4_rate
out["seconds"] = s
out["effective_hours"] = effective / 3600.0
out["merit"] = effective * output_per_sec * offline_mult
return out
## 离线 8h ÷ 在线 1h 的等效比率(门 #9 直接用;offline_mult=1 口径)
static func ratio_8h_over_1h(band1_h: float, band1_rate: float, band2_h: float,
band2_rate: float, band3_h: float, band3_rate: float, band4_rate: float) -> float:
var r := settle(8.0 * 3600.0, 1.0, 1.0, band1_h, band1_rate, band2_h, band2_rate,
band3_h, band3_rate, band4_rate, 1e9)
var one := settle(3600.0, 1.0, 1.0, band1_h, band1_rate, band2_h, band2_rate,
band3_h, band3_rate, band4_rate, 1e9)
if one["merit"] <= 0.0:
return 0.0
return float(r["merit"]) / float(one["merit"])
+61
View File
@@ -0,0 +1,61 @@
class_name PrestigeModel
extends RefCounted
# ============================================================================
# PrestigeModel —— 轮回转世纯函数(架构 §4.2;[B§4])
#
# 公式([B§4],参数全部来自 balance_tuning.csv,代码零硬编码):
# 宿慧获得 = floor(√(本轮累计阴德 / div) × mult) × (1 + 宿慧获取加成)
# × (1 - 冤气削减) 削减封顶 30%(m07 可降至 15%)
# 跳过机制:每 skip_interval 级宿慧 → 前 skip_gens 档班底各 +skip_levels 级
# (m04 把间隔 5 改为 4;工程裁定叠加,见 provenance P-11)
# 敕封触发:转世 ≥ enthrone_rebirths([B§4.4] 约第 2-3 轮)
# 重开曲线目标:1 : 0.6 : 0.4 : 0.3,第 5 轮起触底 ≥0.25×(门 #13 断言)
#
# 静态纯函数:sim 与游戏共用(零漂移);无状态、无分配(除返回值)。
# ============================================================================
const RESTART_TARGETS: Array[float] = [1.0, 0.6, 0.4, 0.3]
const RESTART_FLOOR := 0.25
## 宿慧获得([B§4.1];gain_pct = m03 阶×0.15;penalty = 冤煞削减,已含封顶)
static func suihui_gain(merit_run: float, div: float, mult: float, gain_pct: float, penalty: float) -> int:
if merit_run <= 0.0 or div <= 0.0:
return 0
var raw := floor(sqrt(merit_run / div) * mult)
var adjusted := raw * (1.0 + gain_pct) * (1.0 - penalty)
return int(maxi(0, int(adjusted)))
## 冤煞层成本([B§3.2]:第 n 层 = round(base × n^exp),n 从 1 起)
static func wrath_layer_cost(layer_index: int, base: float, exp: float) -> int:
var n := float(maxi(1, layer_index))
return int(round(base * pow(n, exp)))
## 冤气对宿慧的削减率(每层 +3%,封顶 30%;m07 改为 15%)
static func wrath_penalty(layers: int, per_layer: float, cap: float, override_cap: float) -> float:
var effective_cap := cap
if override_cap > 0.0:
effective_cap = minf(cap, override_cap)
return minf(float(layers) * per_layer, effective_cap)
## 跳过机制:本级宿慧应获得的跳过次数([B§4.2])
static func skip_count(suihui: int, interval: int) -> int:
if interval <= 0:
return 0
return int(suihui / interval)
## 重开曲线目标倍率(第 rebirth_index 轮,0 起;第 5 轮起触底 RESTART_FLOOR)
static func restart_target(rebirth_index: int) -> float:
if rebirth_index < RESTART_TARGETS.size():
return RESTART_TARGETS[rebirth_index]
return RESTART_FLOOR
## 敕封是否可用([B§4.4])
static func enthrone_available(rebirths: int, min_rebirths: int) -> bool:
return rebirths >= min_rebirths
+42
View File
@@ -0,0 +1,42 @@
class_name UpgradeDef
extends Resource
# ============================================================================
# UpgradeDef —— 升级数据行 → 实例(架构 §3.3;[B§2] 108 条 + meta 12 条)
#
# type 枚举([B§2] 五类型 + meta):
# efficiency 班底效率(target_gen 产出 +effect_param,加法;6 级满 = ×7 < ×8 软上限)
# click 点击升级(click_mult=价值×param;click_output_pct=附加产出/s 的 param 比例)
# gate_hire 班底入职文书(纯解锁 target_gen;成本 = C0×2/3)
# gate_system 系统闸门(纯解锁系统;成本 = 殿门阈值÷2,工程外推 P-5)
# offline 离线效率(offline_mult=离线效率×param)
# meta 宿慧商店永久商品(meta_upgrades.csv;max_rank>1 可重复)
#
# 成本口径:效率 = round(C0×r^(10+4n))(0 起始级号第 10+4n 级价);点击/离线 =
# [B§2.2/§2.4] 钉值;入职文书 = round(C0×2/3)(g03=800 钉值复现)。
# ============================================================================
@export var id: String # "up_eff_gen_01_youhun_1" / "meta_01"
@export var name_cn: String
@export var name_en: String
@export var type: String # efficiency/click/gate_hire/gate_system/offline/meta
@export var target_gen: String # efficiency/gate_hire 指向班底 ID;其余 "none"/""
@export var unlock_hall: String # 出现条件:通过哪殿后可见/可购;"none" = 开局
@export var effect_type: String # gen_output_add/click_mult/click_output_pct/offline_mult/unlock/…
@export var effect_param: float # 效果参数(语义见 effect_type)
@export var cost: int # 阴德成本(meta 行为宿慧成本,见 cost_is_suihui)
@export var cost_is_suihui: bool = false # true = meta 行(cost 单位为宿慧)
@export var max_rank: int = 1 # meta 可重复购买阶数;其余 1
@export var requires: String = "none" # 购买前置:none / enthroned(仅 up_gate_tianzi)
@export var condition: String = "none" # meta 效果条件:none / gen12_unlocked
@export var desc_cn: String
## 是否一次性购入(非 meta)
func is_one_shot() -> bool:
return type != "meta"
## 是否纯解锁类(零数值效果)
func is_unlock() -> bool:
return type == "gate_hire" or type == "gate_system"
+36
View File
@@ -0,0 +1,36 @@
class_name BigNumber
extends RefCounted
# ============================================================================
# BigNumber —— 大数红线工具(架构 §4.2)
#
# 口径:double 全程(MVP 终局 ≤1e14 已锁定,技术评估 §3.1 预留的大数系统路径
# 不启用)。本类只做两件事:
# ① 红线断言:sim 门 #10 与 CI 用;超出 = push_error + 返回 false
# ② 科学计数辅助:日志/报告用
#
# 纪律:core 层无 Node 依赖;零热路径分配(仅静态函数)。
# 知识缺口:4.7.x 超出训练数据——本文件只用 4.x 稳定 API。
# ============================================================================
## 终局红线([B§10] 门 #10:终局 ≤1e14)
const RED_LINE := 1e14
## 断言 value 未超红线;超了返回 false 并 push_error(带上下文)。
static func assert_within(value: float, context: String = "") -> bool:
if value > RED_LINE:
push_error("BigNumber 超红线:%.3g > 1e14(上下文:%s)" % [value, context])
return false
return true
## 科学计数串(报告/日志用;游戏内显示走 NumberFormat.short)
static func sci(value: float, digits: int = 3) -> String:
if value == 0.0:
return "0"
var sign := "" if value > 0.0 else "-"
var v := absf(value)
var exp := int(floor(log(v) / log(10.0)))
var mant := v / pow(10.0, exp)
return "%s%.*f e%d" % [sign, digits, mant, exp]
+78
View File
@@ -0,0 +1,78 @@
class_name NumberFormat
extends RefCounted
# ============================================================================
# NumberFormat —— 数字显示格式化(架构 §4.2 / GDD §7.2②)
#
# 口径:
# - 游戏内短格式:<1000 原样整数;之后 K/M/B/T/Qa/Qi/Sx/Sp/Oc/No/Dc + aa/ab…
# ([B§10] 终局 ≤1e14 → 实际只用到 T/aa 区间, ladder 预留到 Dc+)
# - tabular-nums 对齐由字体层保证(JetBrains Mono,架构 §5.5);本类只产字符串
# - 时间格式:秒 → "1h 25m" / "25m 30s" / "42s"(殿门预计时间 UI 用)
#
# 纪律:core 层无 Node 依赖;静态函数;零分配仅指调用方复用(字符串必然分配,
# 热点路径由 UI 层脏检查兜底——架构 §4.2 快照纪律)。
# ============================================================================
const SUFFIXES: Array[String] = [
"", "K", "M", "B", "T",
"Qa", "Qi", "Sx", "Sp", "Oc", "No", "Dc",
"UDc", "DDc", "TDc", "QaDc", "QiDc",
]
## aa/ab/ac… 双字母梯(第 18 档起;MVP 不会到达,预留)
static func _letter_pair(index: int) -> String:
var a := char("a".unicode_at(0) + index / 26)
var b := char("a".unicode_at(0) + index % 26)
return "%s%s" % [a, b]
## 短格式:1234 → "1.23K";15 → "15";1.5e9 → "1.50B"
static func short(value: float, decimals: int = 2) -> String:
var v := absf(value)
if v < 1000.0:
return str(int(round(value)))
var tier := int(floor(log(v) / log(1000.0)))
if tier >= SUFFIXES.size():
# 超出 ladder:退到 aa/ab 双字母(index 从 0 起继续 SUFFIXES 之后)
var extra := tier - SUFFIXES.size()
var scaled := v / pow(1000.0, tier)
return "%.*f%s" % [decimals, scaled, _letter_pair(extra)]
var scaled := v / pow(1000.0, tier)
return "%.*f%s" % [decimals, scaled, SUFFIXES[tier]]
## 整数短格式(无小数点):用于 tabular 对齐要求更严的槽位
static func short_int(value: float) -> String:
var v := absf(value)
if v < 1000.0:
return str(int(round(value)))
var tier := int(floor(log(v) / log(1000.0)))
if tier >= SUFFIXES.size():
return short(value, 0)
return "%d%s" % [int(round(v / pow(1000.0, tier))), SUFFIXES[tier]]
## 秒 → "1h 25m" / "25m 30s" / "42s" / "1.2s"
static func duration(seconds: float) -> String:
var s := absf(seconds)
if s >= 3600.0:
var h := int(s / 3600.0)
var m := int((s - h * 3600.0) / 60.0)
if m == 0:
return "%dh" % h
return "%dh %dm" % [h, m]
if s >= 60.0:
var m2 := int(s / 60.0)
var s2 := int(s - m2 * 60.0)
if s2 == 0:
return "%dm" % m2
return "%dm %ds" % [m2, s2]
if s >= 10.0:
return "%ds" % int(round(s))
return "%.1fs" % s
## 分钟(整数)→ "2h 5m" / "42m"(殿门 target_min 显示)
static func minutes(minutes_v: float) -> String:
return duration(minutes_v * 60.0)
+221
View File
@@ -0,0 +1,221 @@
class_name SimGates
extends RefCounted
# ============================================================================
# SimGates —— 14 条模拟器验收门(架构 §9.3;GDD §12 / 草案 §2.7)
#
# 输入 = Simulator.run 报告(与游戏同一份 data/build/*.json 驱动)。
# W2 状态:#11(殿王战胜率)deferred——BossBattle 逻辑 W3 落地后接入;
# #12 用"策略级吞噬对照"近似(事件流 W3 接入后换真口径)。
# 硬门(W6 起 CI 红):#5 主线时长 / #10 数值安全;其余 informational(§7.2)。
# ============================================================================
const HARD_GATES: Array[int] = [5, 10] # W6 起 CI 硬门(架构 §7.2)
## 单报告可评的门(#1-6 / #8-10 / #14)
static func evaluate_report(report: Dictionary, data: BalanceData) -> Dictionary:
var g := {}
# #1 首购点击 ∈[10,20]
var c1 := int(report.get("clicks_before_first_purchase", 0))
g[1] = {"pass": c1 >= 10 and c1 <= 20, "detail": "首购点击 %d(目标 [10,20])" % c1}
# #2 前 5 分钟购买 ∈[3,6]
var c2 := int(report.get("purchases_first_5min", 0))
g[2] = {"pass": c2 >= 3 and c2 <= 6, "detail": "前 5min 购买 %d 次(目标 [3,6])" % c2}
# #3 解锁时间表(前 30min ±50%,之后 ±30%)
g[3] = _gate_hall_table(report)
# #4 购买间隔无 >60min 塌陷
g[4] = _gate_purchase_gap(report)
# #5 主线 ∈[2h,5h](到 H10)
var h10 := _hall_time(report, 10)
g[5] = {"pass": h10 >= 7200.0 and h10 <= 18000.0,
"detail": "到 H10 %.0f min(目标 120-300min)" % (h10 / 60.0)}
# #6 无卡墙:15min 窗口挂机产出 ≥ 最便宜可见升级 50%
g[6] = _gate_no_wall(report)
# #8 回收期 tier2-6 ∈[120,293]s 且全表单调(数据静态门)
g[8] = _gate_payback(data)
# #9 离线 8h÷1h ∈[1.5,2.5](P-8:按表求值预计 FAIL,如实报)
g[9] = _gate_offline(data)
# #10 终局 ≤1e14
var peak := float(report.get("peak_lifetime", 0.0))
g[10] = {"pass": peak <= BigNumber.RED_LINE,
"detail": "峰值累计 %.3g(红线 1e14)" % peak}
# #11 殿王战(W3 deferred)
g[11] = {"pass": true, "detail": "deferred:BossBattle 逻辑 W3 接入后评估", "deferred": true}
# #14 殿门标定 ∈[80%,120%]
g[14] = _gate_hall_calibration(report)
return g
## 跨策略门 #7(主导策略极差 <1.5×):入参 = 各策略报告数组
static func evaluate_cross(reports: Array) -> Dictionary:
var times: Array = []
for r in reports:
var t := _hall_time(r, 10)
if t > 0.0:
times.append(t)
if times.size() < 2:
return {"pass": false, "detail": "有效完成策略 <2(%d)" % times.size()}
var lo := INF
var hi := 0.0
for t in times:
lo = minf(lo, float(t))
hi = maxf(hi, float(t))
var spread := hi / lo if lo > 0.0 else INF
return {"pass": spread < 1.5, "detail": "H10 时长极差 %.2f×(%s;目标 <1.5×)" % [
spread, _join_times(reports)]}
## 门 #12(怨气/宿慧取舍真实):吞噬轮 vs 纯阴德轮对照
static func evaluate_wrath_tradeoff(pure_report: Dictionary, devour_report: Dictionary) -> Dictionary:
var a := _hall_time(pure_report, 10)
var b := _hall_time(devour_report, 10)
if a <= 0.0 or b <= 0.0:
return {"pass": false, "detail": "对照轮未完成(pure %.0fmin / devour %.0fmin)" % [a / 60.0, b / 60.0]}
var ratio := b / a
# 取舍"真实"= 两轮都能完成且时长有可感知差异(≥5%)
var differ := absf(ratio - 1.0) >= 0.05
return {"pass": differ and ratio > 0.3 and ratio < 3.0,
"detail": "devour/pure = %.2f×(%.0fmin vs %.0fmin)" % [ratio, b / 60.0, a / 60.0]}
## 门 #13(重开曲线):rebirths=4 的报告;各轮 H10 间隔比 ≈ 1:0.6:0.4:0.3
static func evaluate_restart(report: Dictionary) -> Dictionary:
var rebirths: Array = report.get("rebirths", [])
if rebirths.size() < 2:
return {"pass": false, "detail": "转世次数不足(%d)" % rebirths.size()}
var intervals: Array = []
var prev := 0.0
for r in rebirths:
intervals.append(float(r.at_sec) - prev)
prev = float(r.at_sec)
var base := float(intervals[0])
if base <= 0.0:
return {"pass": false, "detail": "首轮时长为 0"}
var targets := PrestigeModel.RESTART_TARGETS
var ok := true
var parts: Array = []
for i in intervals.size():
var ratio := float(intervals[i]) / base
var want: float = targets[i] if i < targets.size() else PrestigeModel.RESTART_FLOOR
# 容差:前 4 轮 ±40%,第 5 轮起只查触底 ≥0.25×
var good: bool
if i < targets.size():
good = absf(ratio - want) <= 0.4 * want
else:
good = ratio <= PrestigeModel.RESTART_FLOOR * 1.2
if not good:
ok = false
parts.append("R%d=%.2f(目标 %.2f)" % [i + 1, ratio, want])
return {"pass": ok, "detail": ";".join(parts)}
# ----------------------------------------------------------------------------
# 内部
# ----------------------------------------------------------------------------
static func _hall_time(report: Dictionary, no: int) -> float:
for h in report.get("halls", []):
if int(h.no) == no:
return float(h.at_sec)
return 0.0
static func _gate_hall_table(report: Dictionary) -> Dictionary:
var ok := true
var parts: Array = []
for h in report.get("halls", []):
var target_sec := float(h.target_min) * 60.0
if target_sec <= 0.0:
continue
var ratio := float(h.at_sec) / target_sec
var tol := 0.5 if target_sec <= 1800.0 else 0.3 # 目标 ≤30min 的殿 ±50%
if ratio < 1.0 - tol or ratio > 1.0 + tol:
ok = false
parts.append("H%d %.0f/%.0fmin" % [int(h.no), float(h.at_sec) / 60.0, target_sec / 60.0])
return {"pass": ok, "detail": ";".join(parts) if not parts.is_empty() else "无殿门事件"}
static func _gate_hall_calibration(report: Dictionary) -> Dictionary:
var ok := true
var parts: Array = []
for h in report.get("halls", []):
var target_sec := float(h.target_min) * 60.0
if target_sec <= 0.0:
continue
var ratio := float(h.at_sec) / target_sec
if ratio < 0.8 or ratio > 1.2:
ok = false
parts.append("H%d %.0f%%" % [int(h.no), ratio * 100.0])
return {"pass": ok, "detail": ";".join(parts) if not parts.is_empty() else "无殿门事件"}
static func _gate_purchase_gap(report: Dictionary) -> Dictionary:
var purchases: Array = report.get("purchases", [])
if purchases.size() < 2:
return {"pass": true, "detail": "购买事件 <2(跳过)"}
var max_gap := 0.0
var prev_t := float(purchases[0].t)
for i in range(1, purchases.size()):
var t := float(purchases[i].t)
max_gap = maxf(max_gap, t - prev_t)
prev_t = t
return {"pass": max_gap <= 3600.0, "detail": "最大购买间隔 %.0f min(上限 60min)" % (max_gap / 60.0)}
static func _gate_no_wall(report: Dictionary) -> Dictionary:
var timeline: Array = report.get("timeline", [])
if timeline.size() < 2:
return {"pass": false, "detail": "时间线不足"}
var worst := INF
var worst_t := 0.0
for s in timeline:
var output := float(s.output)
var cheap: Variant = s.get("min_upgrade_cost", 0.0)
if typeof(cheap) != TYPE_FLOAT and typeof(cheap) != TYPE_INT:
continue
if float(cheap) <= 0.0 or is_inf(float(cheap)):
continue # 无可见升级(全购完/未解锁)→ 不构成卡墙
var income_15m := output * 900.0
var ratio := income_15m / (0.5 * float(cheap))
if ratio < worst:
worst = ratio
worst_t = float(s.t)
return {"pass": worst >= 1.0,
"detail": "最差窗口 %.2f×(t=%.0fmin;≥1.0 = 15min 挂机 ≥ 最便宜升级 50%%)" % [worst, worst_t / 60.0]}
static func _gate_payback(data: BalanceData) -> Dictionary:
var paybacks: Array = []
for g in data.generators:
paybacks.append(g.payback_seconds())
var ok := true
var detail := ""
for i in range(1, 6): # tier2-6(0-indexed 1..5)
var pb := float(paybacks[i])
if pb < 120.0 or pb > 293.0:
ok = false
detail += "t%d %.0fs;" % [i + 1, pb]
for i in range(1, paybacks.size() - 1):
if float(paybacks[i]) >= float(paybacks[i + 1]):
ok = false
detail += "非单调@t%d;" % (i + 1)
return {"pass": ok, "detail": detail}
static func _gate_offline(data: BalanceData) -> Dictionary:
var r := OfflineModel.ratio_8h_over_1h(
data.tune_num("tune_offline_band1_h", 2.0), data.tune_num("tune_offline_band1_rate", 1.0),
data.tune_num("tune_offline_band2_h", 8.0), data.tune_num("tune_offline_band2_rate", 0.7),
data.tune_num("tune_offline_band3_h", 24.0), data.tune_num("tune_offline_band3_rate", 0.4),
data.tune_num("tune_offline_band4_rate", 0.1))
return {"pass": r >= 1.5 and r <= 2.5,
"detail": "离线 8h÷在线 1h = %.2f(契约 [1.5,2.5];P-8 已知不一致,按表求值)" % r}
static func _join_times(reports: Array) -> String:
var parts: Array = []
for r in reports:
var t := _hall_time(r, 10)
parts.append("%s %.0fmin" % [str(r.get("strategy", "?")), t / 60.0])
return ",".join(parts)
+85
View File
@@ -0,0 +1,85 @@
extends SceneTree
# ============================================================================
# sim_main.gd —— 无头平衡模拟器入口(架构 §10.3;探针 P11)
#
# 运行方式(CI 第 4 步 / 本地):
# godot --headless --path . -s res://src/core/sim/sim_main.gd
# 输出:tests/sim_reports/report-<date>.json(14 门 + 里程碑快照,git 跟踪)
#
# W2 口径(架构 §7.2):sim 报告 informational;W6 起门 #5/#10 设硬门。
# 因此本入口退出码恒 0(除非崩溃);硬门状态写在报告 hard_gates 字段。
# ============================================================================
func _initialize() -> void:
var data := BalanceData.load_from_dir("res://data/build/")
if not data.load_errors.is_empty():
for e in data.load_errors:
printerr("sim_main:数据载入错误:%s" % e)
quit(2)
return
var report := {
"date": _date_stamp(),
"engine_note": "Godot 4.7.x headless;EconomyState 与游戏同源",
"runs": [],
"gates": {},
"cross": {},
"restart": {},
"wrath_tradeoff": {},
}
# 三策略主线(4h 预算;H10 目标 3h)
var reports: Array = []
for strategy in [NoviceStrategy.new(), GreedyStrategy.new(), RandomStrategy.new(7)]:
var r := Simulator.run(data, strategy, {"max_sec": 4.0 * 3600.0})
reports.append(r)
report.runs.append(r)
# 每报告门(各策略全量评)
for r in reports:
report.gates[str(r.strategy)] = SimGates.evaluate_report(r, data)
# 主口径合并:门 #1/#2 为新手行为门,采 novice 报告;其余采 greedy 报告
var novice_gates: Dictionary = report.gates.get("novice", {})
var greedy_gates: Dictionary = report.gates.get("greedy", {})
var primary: Dictionary = greedy_gates.duplicate()
primary[1] = novice_gates.get(1, greedy_gates.get(1, {}))
primary[2] = novice_gates.get(2, greedy_gates.get(2, {}))
report.primary = primary
# 跨策略门 #7
report.cross = SimGates.evaluate_cross(reports)
# 门 #13 重开曲线(greedy 5 轮,6h 预算)
var restart_report := Simulator.run(data, GreedyStrategy.new(),
{"max_sec": 6.0 * 3600.0, "rebirths": 4})
report.restart = SimGates.evaluate_restart(restart_report)
report.runs.append(restart_report)
# 门 #12 怨气取舍对照(greedy devour vs pure,各 4h)
var pure := Simulator.run(data, GreedyStrategy.new(), {"max_sec": 4.0 * 3600.0})
var devour := Simulator.run(data, GreedyStrategy.new(),
{"max_sec": 4.0 * 3600.0, "devour": true})
report.wrath_tradeoff = SimGates.evaluate_wrath_tradeoff(pure, devour)
# 硬门状态汇总(W6 起 CI 红;W2 informational)
var hard := {}
for gate_no in SimGates.HARD_GATES:
var g: Dictionary = primary.get(gate_no, {})
hard[str(gate_no)] = bool(g.get("pass", false))
report.hard_gates = hard
var path := "res://tests/sim_reports/report-%s.json" % _date_stamp()
var f := FileAccess.open(path, FileAccess.WRITE)
if f == null:
printerr("sim_main:无法写报告 %s(Error %d)" % [path, FileAccess.get_open_error()])
quit(3)
return
f.store_string(JSON.stringify(report, "\t"))
f.close()
# 摘要行(CI 日志可读;详细看 JSON)
var fails: Array = []
for k in primary:
var gv: Dictionary = primary[k]
if not bool(gv.get("pass", false)) and not bool(gv.get("deferred", false)):
fails.append(k)
print("sim_main:报告已写 %s;主口径未过门 = %s;硬门 = %s" % [
path, str(fails), str(hard)])
quit(0)
func _date_stamp() -> String:
var d := Time.get_datetime_dict_from_system()
return "%04d-%02d-%02d" % [d.year, d.month, d.day]
+124
View File
@@ -0,0 +1,124 @@
class_name Simulator
extends RefCounted
# ============================================================================
# Simulator —— 无头平衡模拟器(架构 §10.3;W2 骨架)
#
# 与游戏零漂移的机制:驱动同一个 EconomyState.tick()(虚拟时钟 1s/步),
# 读同一份 data/build/*.json(BalanceData.load_from_dir)。
#
# W2 范围:策略 novice/greedy/random;事件/殿王战未接入(W3),
# 因此门 #11 在 gates.gd 标记 deferred;门 #12 用"策略级吞噬对照"近似
# (wrath 入口 devour API 已就绪,W3 换真事件流)。
# ============================================================================
const SNAPSHOT_INTERVAL := 60 # 里程碑快照间隔(秒)
## 跑一个策略。opts:{max_sec, seed, rebirths, devour}
## 返回报告 Dictionary( gates.gd 的输入)。
static func run(data: BalanceData, strategy: SimStrategy, opts: Dictionary = {}) -> Dictionary:
var max_sec := float(opts.get("max_sec", 4.0 * 3600.0))
var rebirths_max := int(opts.get("rebirths", 0))
var devour := bool(opts.get("devour", false))
var econ := EconomyState.new(data)
var t := 0.0
var halls: Array = []
var purchases: Array = []
var rebirths: Array = []
var timeline: Array = []
var clicks := 0
var clicks_before_first := 0
var first_purchase_sec := -1.0
var prev_bought := 0
var prev_levels_sum := 0
var prev_halls := 0
var abort_reason := "max_sec"
while t < max_sec:
econ.tick(1.0)
t += 1.0
# 策略点击(act 内含;此处仅计数用近似:click_rate × 秒)
clicks += int(ceil(strategy.click_rate))
strategy.act(econ)
if devour:
# W2 近似吞噬流:按 GDD §3.3a 口径,每 60s 一次 45×sqrt(产出/s)
if int(t) % 60 == 0:
econ.devour(45.0 * sqrt(maxf(0.0, econ.output_per_sec())))
econ.buy_wrath_layer()
# 殿门事件
if econ.halls_passed > prev_halls:
for no in range(prev_halls + 1, econ.halls_passed + 1):
halls.append({"no": no, "at_sec": t,
"target_min": data.hall_by_no(no).target_min if data.hall_by_no(no) else 0.0})
prev_halls = econ.halls_passed
# 购买事件(升级购入 + 班底级数增长)
if econ.bought.size() > prev_bought:
if first_purchase_sec < 0.0:
first_purchase_sec = t
clicks_before_first = clicks
purchases.append({"t": t, "kind": "upgrade", "count": econ.bought.size() - prev_bought})
prev_bought = econ.bought.size()
var lv_sum := 0
for lv in econ.gen_levels:
lv_sum += lv
if lv_sum > prev_levels_sum:
if first_purchase_sec < 0.0:
first_purchase_sec = t
clicks_before_first = clicks
purchases.append({"t": t, "kind": "generator", "count": lv_sum - prev_levels_sum})
prev_levels_sum = lv_sum
# 里程碑快照
if int(t) % SNAPSHOT_INTERVAL == 0:
timeline.append({
"t": t, "merit": econ.merit, "run_total": econ.merit_run_total,
"output": econ.output_per_sec(), "levels": econ.gen_levels.duplicate(),
"halls": econ.halls_passed, "suihui": econ.suihui,
"min_upgrade_cost": _min_visible_upgrade_cost(econ),
})
# prestige(H10 达成 → 转世,重计时钟)
if rebirths_max > 0 and econ.prestige_available() and rebirths.size() < rebirths_max:
var r := econ.do_prestige()
rebirths.append({"at_sec": t, "suihui_gain": int(r.get("suihui_gain", 0))})
prev_halls = 0
prev_bought = 0
prev_levels_sum = 0
if econ.halls_passed >= data.halls.size():
abort_reason = "h10_reached"
return {
"strategy": strategy.strategy_name,
"duration_sec": t,
"abort_reason": abort_reason,
"halls": halls,
"purchases": purchases,
"rebirths": rebirths,
"timeline": timeline,
"clicks_total": clicks,
"clicks_before_first_purchase": clicks_before_first,
"first_purchase_sec": first_purchase_sec,
"purchases_first_5min": _count_before(purchases, 300.0),
"peak_lifetime": econ.merit_lifetime,
"end": {
"merit": econ.merit, "output": econ.output_per_sec(),
"levels": econ.gen_levels.duplicate(), "suihui": econ.suihui,
"rebirths": econ.rebirths, "enthroned": econ.enthroned,
"wrath_layers": econ.wrath_layers, "souls_saved": econ.souls_saved(),
},
}
static func _count_before(purchases: Array, sec: float) -> int:
var n := 0
for p in purchases:
if float(p.t) <= sec:
n += int(p.count)
return n
## 当前最便宜"可见且未购"升级成本(门 #6 无卡墙检查用;INF = 无)
static func _min_visible_upgrade_cost(econ: EconomyState) -> float:
var best := INF
for u in econ.data.upgrades:
if econ.bought.has(u.id) or not econ.upgrade_visible(u):
continue
best = minf(best, float(u.cost))
return best
+67
View File
@@ -0,0 +1,67 @@
class_name GreedyStrategy
extends SimStrategy
# ============================================================================
# GreedyStrategy —— 回本最快策略(架构 §10.3)
#
# 行为模型:每秒点 1 次;每决策点比较
# ① 可见升级的"回本"(效率:cost / 该班底当前产出)
# ② 各班底下一级的回本(cost / Δoutput)
# 买最小者;bulk ×10 解锁后一次买 10 级(仍按回本选班底)。
# 门 #7(主导策略极差 <1.5×)的对照臂。
# ============================================================================
func _init() -> void:
strategy_name = "greedy"
click_rate = 1.0
func act(econ: EconomyState) -> void:
econ.click()
var bulk10 := econ.bought.has("up_gate_bulk10") or econ.meta_ranks.get("meta_11", 0) > 0
var count := 10 if bulk10 else 1
var best_kind := ""
var best_id := ""
var best_gi := -1
var best_pb := INF
var gm := econ.global_mult()
# 升级回本(效率类:产出增量 = 该班底当前产出 × param)
for u in econ.data.upgrades:
if econ.bought.has(u.id) or not econ.upgrade_visible(u) or not econ.requires_ok(u):
continue
if econ.merit < float(u.cost):
continue
var pb := INF
if u.effect_type == "gen_output_add":
var gi := econ.gen_index_of(u.target_gen)
if gi < 0:
continue
var g := econ.data.generators[gi]
var d_out := g.base_rate * float(econ.gen_levels[gi]) * u.effect_param * gm
pb = float(u.cost) / d_out if d_out > 0.0 else INF
else:
# 点击/闸门/离线类:给一个小回本估值(点击区×2 ≈ 点击收益翻倍)
pb = float(u.cost) / maxf(1.0, econ.click_value() * 2.0)
if pb < best_pb:
best_pb = pb
best_kind = "upgrade"
best_id = u.id
best_gi = -1
# 班底下一级回本
var best_gen := best_payback_generator(econ)
if best_gen >= 0:
var g := econ.data.generators[best_gen]
var cost := g.cost_range(econ.gen_levels[best_gen], count)
if econ.merit >= cost:
var d_out2 := g.base_rate * float(count) * econ.gen_mult(best_gen) * gm
var pb := cost / d_out2 if d_out2 > 0.0 else INF
if pb < best_pb:
best_pb = pb
best_kind = "generator"
best_id = g.id
best_gi = best_gen
match best_kind:
"upgrade":
econ.buy_upgrade(best_id)
"generator":
econ.buy_generator(best_gi, count)
+25
View File
@@ -0,0 +1,25 @@
class_name NoviceStrategy
extends SimStrategy
# ============================================================================
# NoviceStrategy —— 新手策略(架构 §10.3:只点 + 买最便宜)
#
# 行为模型:每秒点 click_rate 次;有钱就买当前最便宜的可购目标
#(班底级数 / 可见升级)。不规划、不攒钱、不用 bulk。
# 模拟器门 #1(首购点击 ∈[10,20])与 #2(前 5 分钟购买 ∈[3,6])的主载体。
# ============================================================================
func _init() -> void:
strategy_name = "novice"
click_rate = 2.0
func act(econ: EconomyState) -> void:
for _i in int(click_rate):
econ.click()
var t := cheapest_target(econ)
match t.kind:
"upgrade":
econ.buy_upgrade(t.id)
"generator":
econ.buy_generator(t.gen_index, 1)
+40
View File
@@ -0,0 +1,40 @@
class_name RandomStrategy
extends SimStrategy
# ============================================================================
# RandomStrategy —— 随机策略( seeded ;门 #7 对照臂 + 无主导策略验证)
#
# 行为模型:每秒点 1 次;在可购目标里按 seed 随机挑一个买。
# ============================================================================
var _rng := RandomNumberGenerator.new()
func _init(seed_value: int = 7) -> void:
strategy_name = "random"
click_rate = 1.0
_rng.seed = seed_value
func act(econ: EconomyState) -> void:
econ.click()
# 收集可购目标后随机选(目标数 ≤ ~120,每秒一次可接受)
var targets: Array = []
for u in econ.data.upgrades:
if econ.bought.has(u.id) or not econ.upgrade_visible(u) or not econ.requires_ok(u):
continue
if econ.merit >= float(u.cost):
targets.append({"kind": "upgrade", "id": u.id, "gen_index": -1})
for i in econ.data.generators.size():
if not econ.generator_unlocked(i):
continue
if econ.merit >= econ.data.generators[i].cost_of_level(econ.gen_levels[i]):
targets.append({"kind": "generator", "id": econ.data.generators[i].id, "gen_index": i})
if targets.is_empty():
return
var pick: Dictionary = targets[_rng.randi_range(0, targets.size() - 1)]
match pick.kind:
"upgrade":
econ.buy_upgrade(pick.id)
"generator":
econ.buy_generator(pick.gen_index, 1)
+57
View File
@@ -0,0 +1,57 @@
class_name SimStrategy
extends RefCounted
# ============================================================================
# SimStrategy —— 平衡模拟器策略基类(架构 §10.3)
#
# 契约:simulator 每模拟 1 秒调用一次 act(econ);策略可点击/购买/吞噬。
# 策略只通过 EconomyState 公共 API 操作——与游戏命令同源(零漂移)。
# ============================================================================
var strategy_name := "base"
var click_rate := 1.0 # 每秒点击次数(novice 2.0 / greedy 1.0)
func act(_econ: EconomyState) -> void:
pass
## 工具:买当前"最便宜"的可购目标(novice/random 共用)
func cheapest_target(econ: EconomyState) -> Dictionary:
var best := {"kind": "", "id": "", "gen_index": -1, "cost": INF}
for u in econ.data.upgrades:
if econ.bought.has(u.id) or not econ.upgrade_visible(u):
continue
if not econ.requires_ok(u):
continue
var c := float(u.cost)
if c < best.cost and econ.merit >= c:
best = {"kind": "upgrade", "id": u.id, "gen_index": -1, "cost": c}
for i in econ.data.generators.size():
if not econ.generator_unlocked(i):
continue
var c := econ.data.generators[i].cost_of_level(econ.gen_levels[i])
if c < best.cost and econ.merit >= c:
best = {"kind": "generator", "id": econ.data.generators[i].id, "gen_index": i, "cost": c}
return best
## 工具:买回本最快的一级班底(greedy 用;payback = Δcost / Δoutput)
func best_payback_generator(econ: EconomyState) -> int:
var best_i := -1
var best_pb := INF
var gm := econ.global_mult()
for i in econ.data.generators.size():
if not econ.generator_unlocked(i):
continue
var g := econ.data.generators[i]
var lv := econ.gen_levels[i]
var cost := g.cost_of_level(lv)
var d_out := g.base_rate * econ.gen_mult(i) * gm
if d_out <= 0.0 or econ.merit < cost:
continue
var pb := cost / d_out
if pb < best_pb:
best_pb = pb
best_i = i
return best_i
+89
View File
@@ -0,0 +1,89 @@
extends Node
# ============================================================================
# DataRegistry —— Autoload;data/build/*.json → 定义的唯一查询入口
# (架构 §2.3/§3.1;DataRegistry 只读,热重载入口)
#
# 依赖方向:game → core(BalanceData);本类不持有任何游戏状态。
# 用法:任何层 `DataRegistry.generator("gen_03_guizu")`;
# sim 与单测也可直接 new BalanceData().load_from_dir()(零漂移)。
#
# 热重载(架构 §3.6):dev 构建设置面板"重载数据"钮 → reload();
# 运行时(export 构建)禁热重载——tools/ 不打包,data/ 只读。
# ============================================================================
const BUILD_DIR := "res://data/build/"
var data: BalanceData
var load_ok: bool = false
signal data_reloaded(errors: int)
func _ready() -> void:
ensure_loaded()
## 幂等载入(GameState._ready 不依赖 autoload 顺序,显式调用)
func ensure_loaded() -> void:
if load_ok:
return
load_all()
func load_all() -> void:
data = BalanceData.load_from_dir(BUILD_DIR)
load_ok = data.load_errors.is_empty()
if not load_ok:
for e in data.load_errors:
push_error("DataRegistry:%s" % e)
push_error("DataRegistry:载入存在 %d 条错误(详见上列)" % data.load_errors.size())
data_reloaded.emit(data.load_errors.size())
## dev 热重载:重新读盘(GameState 监听后重建 EconomyState 由调用方负责)
func reload() -> void:
load_ok = false
load_all()
# ---- 查询透传(薄封装;真实索引逻辑在 core/BalanceData)----
func generator(id: String) -> GeneratorDef:
return data.generator(id)
func upgrade(id: String) -> UpgradeDef:
return data.upgrade(id)
func meta(id: String) -> UpgradeDef:
return data.meta(id)
func hall(id_or_no: Variant) -> HallDef:
return data.hall(id_or_no)
func hall_by_no(no: int) -> HallDef:
return data.hall_by_no(no)
func boss_for_hall(hall_id: String) -> BossDef:
return data.boss_for_hall(hall_id)
func palette_for_hall(hall_id: String) -> PaletteDef:
return data.palette_for_hall(hall_id)
func event(id: String) -> EventDef:
return data.event(id)
func tune_num(key: String, default_value: float) -> float:
return data.tune_num(key, default_value)
func tune_text(key: String, default_value: String) -> String:
return data.tune_text(key, default_value)
+138
View File
@@ -0,0 +1,138 @@
extends Node
# ============================================================================
# GameState —— Autoload;唯一权威状态容器 + 系统 tick(架构 §2.3/§4.1)
#
# 职责:
# - 持有 EconomyState(core 权威经济状态的唯一实例)
# - _process 累加器步进:经济固定 10Hz(架构 §4.2),与渲染帧率解耦
# - 命令 API(UI 唯一写入口):cmd_click / cmd_buy_* / cmd_prestige …
# - 快照发布:10Hz snapshot_changed 信号(presentation 拉取)
#
# 纪律:
# - game/ 不直连 Steam(W6 SteamService);不碰渲染(presentation 的活)
# - UI 永不直接写 econ;一切经 cmd_*(校验+扣费在 core 完成)
# - W2 不含事件导演/解锁导演/殿王战(W3+);殿门轮询在 EconomyState.tick 内
# ============================================================================
const STEP := 0.1 # 经济固定步长 10Hz(架构 §4.2)
var econ: EconomyState
var _acc: float = 0.0
var _last_halls_passed: int = 0
signal snapshot_changed(snap: Dictionary) # 10Hz
signal purchase_result(ok: bool, reason: String)
signal hall_passed(hall_no: int)
signal prestige_done(suihui_gain: int, rebirths: int)
signal enthrone_done()
func _ready() -> void:
DataRegistry.ensure_loaded()
econ = EconomyState.new(DataRegistry.data)
_last_halls_passed = econ.halls_passed
snapshot_changed.emit(econ.snapshot())
func _process(_dt: float) -> void:
_acc += _dt
# 掉帧保护:单帧最多追 10 步(防后台切回巨额追帧)
var steps := 0
while _acc >= STEP and steps < 10:
econ.tick(STEP)
_acc -= STEP
steps += 1
if steps >= 10:
_acc = 0.0
_emit_hall_events()
snapshot_changed.emit(econ.snapshot())
func _emit_hall_events() -> void:
while _last_halls_passed < econ.halls_passed:
_last_halls_passed += 1
hall_passed.emit(_last_halls_passed)
# ----------------------------------------------------------------------------
# 命令 API(UI 唯一写入口)
# ----------------------------------------------------------------------------
## 点击(返回获得阴德;UI 可做飘字)
func cmd_click() -> float:
var gain := econ.click()
_emit_hall_events()
return gain
## 买班底 count 级(-1 = 最大可买;bulk 解锁校验在 bulk_unlocked)
func cmd_buy_generator(gen_index: int, count: int) -> Dictionary:
var r := econ.buy_generator(gen_index, count)
purchase_result.emit(bool(r.get("ok", false)), str(r.get("reason", "")))
return r
func cmd_buy_upgrade(id: String) -> Dictionary:
var r := econ.buy_upgrade(id)
purchase_result.emit(bool(r.get("ok", false)), str(r.get("reason", "")))
return r
func cmd_buy_meta(id: String) -> Dictionary:
var r := econ.buy_meta(id)
purchase_result.emit(bool(r.get("ok", false)), str(r.get("reason", "")))
return r
func cmd_buy_wrath_layer() -> Dictionary:
var r := econ.buy_wrath_layer()
purchase_result.emit(bool(r.get("ok", false)), str(r.get("reason", "")))
return r
## 事件入口(W3 EventDirector 接线;API 先行)
func cmd_devour(lump: float) -> void:
econ.devour(lump)
func cmd_save_soul(lump: float) -> void:
econ.save_soul(lump)
func cmd_prestige() -> Dictionary:
var r := econ.do_prestige()
if bool(r.get("ok", false)):
_last_halls_passed = 0
prestige_done.emit(int(r.get("suihui_gain", 0)), int(r.get("rebirths", 0)))
else:
purchase_result.emit(false, str(r.get("reason", "")))
return r
func cmd_enthrone() -> Dictionary:
var r := econ.do_enthrone()
if bool(r.get("ok", false)):
enthrone_done.emit()
else:
purchase_result.emit(false, str(r.get("reason", "")))
return r
# ----------------------------------------------------------------------------
# bulk 解锁查询([B§2.3] 系统闸门 + m11;UI 按钮显隐用)
# ----------------------------------------------------------------------------
func bulk_unlocked(mode: int) -> bool:
if mode <= 1:
return true
if mode == 10:
return econ.bought.has("up_gate_bulk10") or econ.meta_ranks.get("meta_11", 0) > 0
if mode < 0: # max
return econ.bought.has("up_gate_bulkmax")
return false
## 当前快照(UI _draw/刷新时拉取;10Hz 信号已广播,此为按需补拉)
func get_snapshot() -> Dictionary:
return econ.snapshot()
+171
View File
@@ -0,0 +1,171 @@
class_name HudRoot
extends VBoxContainer
# ============================================================================
# HudRoot —— 灰盒 HUD:升级列表 + 宿慧商店 + 轮回/敕封/怨煞(W2)
#
# 纪律(架构 §4.5):只读快照 + 发命令;UI 不持有游戏状态(仅视图本地态)。
# 形态(§10.4):默认 Godot 主题 + 系统字体;按钮即行;零皮肤零图标。
# 行池按需创建、快照驱动刷新;不可见/已购的行隐藏(不销毁,避免 10Hz 重建)。
# ============================================================================
signal buy_upgrade_requested(id: String)
signal buy_meta_requested(id: String)
signal buy_wrath_layer_requested()
signal prestige_requested()
signal enthrone_requested()
var _upgrade_rows: Dictionary = {} # id -> {btn, label, last_text}
var _upgrade_box: VBoxContainer
var _meta_rows: Dictionary = {}
var _meta_box: VBoxContainer
var _prestige_btn: Button
var _enthrone_btn: Button
var _wrath_btn: Button
var _suihui_label: Label
var _wrath_label: Label
func _ready() -> void:
add_theme_constant_override("separation", 8)
var split := HSplitContainer.new()
split.size_flags_vertical = Control.SIZE_EXPAND_FILL
add_child(split)
# ---- 左:升级列表 ----
var left_scroll := ScrollContainer.new()
left_scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
left_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
split.add_child(left_scroll)
_upgrade_box = VBoxContainer.new()
_upgrade_box.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
left_scroll.add_child(_upgrade_box)
var up_title := Label.new()
up_title.text = "升级(操练/法器/路引/长明灯)"
up_title.add_theme_font_size_override("font_size", 18)
_upgrade_box.add_child(up_title)
# ---- 右:宿慧/轮回 ----
var right_scroll := ScrollContainer.new()
right_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
right_scroll.custom_minimum_size = Vector2(360, 0)
split.add_child(right_scroll)
_meta_box = VBoxContainer.new()
_meta_box.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
right_scroll.add_child(_meta_box)
var meta_title := Label.new()
meta_title.text = "宿慧商店 / 轮回"
meta_title.add_theme_font_size_override("font_size", 18)
_meta_box.add_child(meta_title)
_suihui_label = Label.new()
_suihui_label.add_theme_font_size_override("font_size", 16)
_meta_box.add_child(_suihui_label)
_wrath_label = Label.new()
_wrath_label.add_theme_font_size_override("font_size", 14)
_meta_box.add_child(_wrath_label)
_prestige_btn = Button.new()
_prestige_btn.pressed.connect(func(): prestige_requested.emit())
_meta_box.add_child(_prestige_btn)
_enthrone_btn = Button.new()
_enthrone_btn.pressed.connect(func(): enthrone_requested.emit())
_meta_box.add_child(_enthrone_btn)
_wrath_btn = Button.new()
_wrath_btn.pressed.connect(func(): buy_wrath_layer_requested.emit())
_meta_box.add_child(_wrath_btn)
## 快照刷新
func refresh(snap: Dictionary) -> void:
# 升级行池
var visible_ids := {}
for u in snap.get("upgrades", []):
var id := str(u.get("id", ""))
visible_ids[id] = true
var row: Variant = _upgrade_rows.get(id)
if row == null:
row = _create_upgrade_row(id)
var d: Dictionary = row
var avail := bool(u.get("available", false))
var text := "%s · %s" % [
str(u.get("name_cn", "")),
NumberFormat.short_int(float(u.get("cost", 0.0)))]
var btn: Button = d.btn
var label: Label = d.label
if label.text != text:
label.text = text
btn.tooltip_text = str(u.get("desc_cn", ""))
btn.disabled = not avail
btn.visible = true
for id in _upgrade_rows:
if not visible_ids.has(id):
var d2: Dictionary = _upgrade_rows[id]
(d2.btn as Button).visible = false
# 宿慧/轮回区
_suihui_label.text = "宿慧 %d(转世 %d 次%s)" % [
int(snap.get("suihui", 0)), int(snap.get("rebirths", 0)),
" · 已敕封" if bool(snap.get("enthroned", false)) else ""]
_wrath_label.text = "冤气 %s · 怨煞 %d 层(全局 ×%.2f)" % [
NumberFormat.short(float(snap.get("wrath", 0.0))),
int(snap.get("wrath_layers", 0)),
float(snap.get("global_mult", 1.0))]
_prestige_btn.disabled = not bool(snap.get("prestige_available", false))
_prestige_btn.text = "轮回转世(+%d 宿慧)" % int(snap.get("suihui_gain", 0))
var ent_avail := bool(snap.get("enthrone_available", false))
_enthrone_btn.visible = ent_avail or bool(snap.get("enthroned", false))
_enthrone_btn.disabled = not ent_avail
_enthrone_btn.text = "已受敕" if bool(snap.get("enthroned", false)) else "受敕(阴司敕封)"
_wrath_btn.disabled = float(snap.get("wrath", 0.0)) < float(snap.get("wrath_layer_cost", INF))
_wrath_btn.text = "叠怨煞层(%s 冤气)" % NumberFormat.short_int(float(snap.get("wrath_layer_cost", 0.0)))
# 宿慧商品行池
for m in snap.get("metas", []):
var mid := str(m.get("id", ""))
var mrow: Variant = _meta_rows.get(mid)
if mrow == null:
mrow = _create_meta_row(mid)
var md: Dictionary = mrow
var mbtn: Button = md.btn
var mlabel: Label = md.label
var rank := int(m.get("rank", 0))
var max_rank := int(m.get("max_rank", 1))
var mtext := "%s%s · %s 宿慧" % [
str(m.get("name_cn", "")),
" ×%d" % rank if max_rank > 1 else "",
NumberFormat.short_int(float(m.get("cost", 0.0)))]
if mlabel.text != mtext:
mlabel.text = mtext
mbtn.tooltip_text = str(m.get("desc_cn", ""))
mbtn.disabled = not bool(m.get("available", false))
mbtn.visible = rank < max_rank
func _create_upgrade_row(id: String) -> Dictionary:
var btn := Button.new()
btn.focus_mode = Control.FOCUS_NONE
btn.alignment = HorizontalAlignment.LEFT
btn.pressed.connect(func(): buy_upgrade_requested.emit(id))
var label := Label.new()
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
label.set_anchors_preset(Control.PRESET_FULL_RECT)
label.offset_left = 6.0
label.add_theme_font_size_override("font_size", 13)
label.autowrap_mode = TextServer.AUTOWRAP_OFF
btn.add_child(label)
_upgrade_box.add_child(btn)
var d := {"btn": btn, "label": label}
_upgrade_rows[id] = d
return d
func _create_meta_row(id: String) -> Dictionary:
var btn := Button.new()
btn.focus_mode = Control.FOCUS_NONE
btn.alignment = HorizontalAlignment.LEFT
btn.pressed.connect(func(): buy_meta_requested.emit(id))
var label := Label.new()
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
label.set_anchors_preset(Control.PRESET_FULL_RECT)
label.offset_left = 6.0
label.add_theme_font_size_override("font_size", 13)
btn.add_child(label)
_meta_box.add_child(btn)
var d := {"btn": btn, "label": label}
_meta_rows[id] = d
return d
+122
View File
@@ -0,0 +1,122 @@
class_name ArmyBand
extends HBoxContainer
# ============================================================================
# ArmyBand —— 军列带 12 槽灰盒版(架构 §4.3/§10.2 的 W2 落地;§10.4 灰盒规范)
#
# 12 槽数据驱动(图标位置 = 级数/成本/状态;GDD §7.1):
# 左键 = 买 ×1;右键 = 买 ×10(up_gate_bulk10 或 m11 解锁后);
# Shift+左键 = 买最大(up_gate_bulkmax 解锁后)。
# 三级分层可视化(GDD §2.4):可购 = 骨白边;锁定 = 40% 透明 + "未解锁"。
# W3 换 MultiMesh 像素阴兵实例(探针 P10);本版零精灵、100% 可弃。
# ============================================================================
signal buy_requested(gen_index: int, count: int)
const BONE := Color("F2EDE4")
const CINNABAR := Color("B4402C")
var _slots: Array[Control] = []
var _fills: Array[ColorRect] = []
var _frames: Array[ColorRect] = []
var _name_labels: Array[Label] = []
var _level_labels: Array[Label] = []
var _cost_labels: Array[Label] = []
var _out_labels: Array[Label] = []
func _ready() -> void:
alignment = BoxContainer.ALIGNMENT_BEGIN
add_theme_constant_override("separation", 6)
for i in DataRegistry.data.generators.size():
_build_slot(i, DataRegistry.data.generators[i])
func _build_slot(index: int, def: GeneratorDef) -> void:
var btn := Button.new()
btn.custom_minimum_size = Vector2(96, 132)
btn.tooltip_text = "%s:左键×1 / 右键×10 / Shift+左键最大" % def.display_cn
btn.set_meta("gen_index", index)
btn.focus_mode = Control.FOCUS_NONE
btn.gui_input.connect(_on_slot_gui_input.bind(btn))
var vbox := VBoxContainer.new()
vbox.mouse_filter = Control.MOUSE_FILTER_IGNORE
vbox.set_anchors_preset(Control.PRESET_FULL_RECT)
vbox.offset_left = 4.0
vbox.offset_top = 2.0
vbox.offset_right = -4.0
vbox.offset_bottom = -2.0
btn.add_child(vbox)
# 色块(一色一档,灰阶 ramp;§10.4"干净地丑")
var frame := ColorRect.new()
frame.color = BONE
frame.custom_minimum_size = Vector2(0, 4)
vbox.add_child(frame)
var fill := ColorRect.new()
fill.custom_minimum_size = Vector2(0, 34)
fill.color = Color.from_hsv(0.08, 0.06, 0.16 + 0.05 * index)
vbox.add_child(fill)
var name_l := Label.new()
name_l.add_theme_font_size_override("font_size", 13)
vbox.add_child(name_l)
var lv_l := Label.new()
lv_l.add_theme_font_size_override("font_size", 13)
vbox.add_child(lv_l)
var cost_l := Label.new()
cost_l.add_theme_font_size_override("font_size", 12)
vbox.add_child(cost_l)
var out_l := Label.new()
out_l.add_theme_font_size_override("font_size", 11)
vbox.add_child(out_l)
add_child(btn)
_slots.append(btn)
_fills.append(fill)
_frames.append(frame)
_name_labels.append(name_l)
_level_labels.append(lv_l)
_cost_labels.append(cost_l)
_out_labels.append(out_l)
func _on_slot_gui_input(event: InputEvent, btn: Control) -> void:
if not (event is InputEventMouseButton):
return
var mb := event as InputEventMouseButton
if not mb.pressed:
return
var index := int(btn.get_meta("gen_index"))
if mb.button_index == MOUSE_BUTTON_LEFT:
if mb.shift_pressed:
buy_requested.emit(index, -1) # 最大
else:
buy_requested.emit(index, 1)
elif mb.button_index == MOUSE_BUTTON_RIGHT:
buy_requested.emit(index, 10)
## 快照刷新(只改文本;Godot Label 内部对同文本不重排)
func refresh(snap: Dictionary) -> void:
var gens: Array = snap.get("gens", [])
for i in _slots.size():
if i >= gens.size():
_slots[i].visible = false
continue
var g: Dictionary = gens[i]
var unlocked := bool(g.get("unlocked", false))
var affordable := bool(g.get("affordable", false))
_name_labels[i].text = str(g.get("name_cn", ""))
if unlocked:
_level_labels[i].text = "Lv %d" % int(g.get("level", 0))
_cost_labels[i].text = NumberFormat.short_int(float(g.get("cost_next", 0.0)))
_out_labels[i].text = "%s/s" % NumberFormat.short(float(g.get("output", 0.0)))
else:
_level_labels[i].text = "—"
_cost_labels[i].text = "未解锁"
_out_labels[i].text = ""
# 三级分层:锁定 40% + 剪影感(纯色压暗);可购骨白边;其余无边框
_slots[i].modulate = Color(1, 1, 1, 1) if unlocked else Color(1, 1, 1, 0.4)
_frames[i].color = BONE if (unlocked and affordable) else Color("6E6E6E")
if unlocked:
_fills[i].color = Color.from_hsv(0.08, 0.06, 0.16 + 0.05 * i)
else:
_fills[i].color = Color("2A2A2A")
+87
View File
@@ -0,0 +1,87 @@
class_name HallBand
extends Control
# ============================================================================
# HallBand —— 单殿灰盒色带(架构 §4.3 的 W2 灰盒版;§10.4 灰盒规范)
#
# 形态:纯色块 + 文字(殿号/殿王/殿门阈值/状态)。色相取自 hall_palettes.csv
# 的 accent_hex(数据驱动:换殿 = 换一行数据 + 换一个色,零代码)。
# 状态:locked = 40% 透明 + "未解锁";current = 朱砂边;passed = 骨白边。
# 禁止:任何 sprite/半成品美术/shader(§10.4 退出条件 = 100% 可弃)。
# ============================================================================
const INK := Color("1A1A1A")
const BONE := Color("F2EDE4")
const CINNABAR := Color("B4402C")
var _fill: ColorRect
var _frame: ColorRect
var _labels: VBoxContainer
var _state_label: Label
static func make(def: HallDef, palette: PaletteDef) -> HallBand:
var band := HallBand.new()
band.custom_minimum_size = Vector2(112, 96)
band._build(def, palette)
return band
func _build(def: HallDef, palette: PaletteDef) -> void:
# 外框(边色随状态换)+ 内填充(殿 accent 色)
_frame = ColorRect.new()
_frame.color = INK
_frame.set_anchors_preset(Control.PRESET_FULL_RECT)
add_child(_frame)
_fill = ColorRect.new()
_fill.set_anchors_preset(Control.PRESET_FULL_RECT)
_fill.offset_left = 3.0
_fill.offset_top = 3.0
_fill.offset_right = -3.0
_fill.offset_bottom = -3.0
var accent := INK
if palette != null and palette.accent_hex != "":
accent = Color(palette.accent_hex)
_fill.color = accent
add_child(_fill)
_labels = VBoxContainer.new()
_labels.set_anchors_preset(Control.PRESET_FULL_RECT)
_labels.offset_left = 8.0
_labels.offset_top = 6.0
_labels.offset_right = -8.0
_labels.offset_bottom = -6.0
add_child(_labels)
var title := Label.new()
title.text = "H%d %s" % [def.no, def.king_cn]
title.add_theme_font_size_override("font_size", 16)
title.add_theme_color_override("font_color", BONE)
_labels.add_child(title)
var gate := Label.new()
gate.text = "门 %s" % NumberFormat.short_int(def.gate_merit)
gate.add_theme_font_size_override("font_size", 13)
gate.add_theme_color_override("font_color", BONE)
_labels.add_child(gate)
var target := Label.new()
target.text = "~%s" % NumberFormat.minutes(def.target_min)
target.add_theme_font_size_override("font_size", 12)
target.add_theme_color_override("font_color", BONE)
_labels.add_child(target)
_state_label = Label.new()
_state_label.add_theme_font_size_override("font_size", 12)
_state_label.add_theme_color_override("font_color", BONE)
_labels.add_child(_state_label)
## 状态刷新(快照驱动;passed/current/locked 三态,GDD §2.4 三级分层)
func set_state(passed: bool, current: bool, have: float, need: float) -> void:
if passed:
_frame.color = BONE
modulate = Color(1, 1, 1, 1)
_state_label.text = "已过"
return
modulate = Color(1, 1, 1, 0.4) if not current else Color(1, 1, 1, 1)
_frame.color = CINNABAR if current else INK
if current:
var pct := 0.0 if need <= 0.0 else clampf(have / need, 0.0, 1.0) * 100.0
_state_label.text = "%.0f%%" % pct
else:
_state_label.text = "未解锁"
+239
View File
@@ -0,0 +1,239 @@
extends Node
# ============================================================================
# stage_root.gd —— 灰盒主场景(W2 可玩性验证版;架构 §10.4 灰盒规范)
#
# 验证循环(W3 出口标准的提前落地):点 → 买 → 涨 → 解锁 → prestige 全循环,
# 零美术资产。布局即 §10.2 纸面原型的程序化落地:
# 顶栏(阴德/产出/点击/冤气/宿慧/转世/超度)|十殿色带|军列带 12 槽|
# 升级列表 + 宿慧商店(含轮回/敕封/怨煞)|日志行
#
# 纪律:只读 GameState 快照 + 发命令;本场景不持有任何游戏状态。
# ============================================================================
const INK := Color("1A1A1A")
const BONE := Color("F2EDE4")
const CINNABAR := Color("B4402C")
var _merit_label: Label
var _rate_label: Label
var _click_btn: Button
var _wrath_label: Label
var _suihui_label: Label
var _rebirth_label: Label
var _souls_label: Label
var _progress_label: Label
var _log_label: Label
var _army: ArmyBand
var _hud: HudRoot
var _hall_bands: Array[HallBand] = []
var _progress_shown := ""
func _ready() -> void:
if DataRegistry.data == null:
push_error("stage_root:DataRegistry 无数据")
return
_build_ui()
_connect()
func _build_ui() -> void:
var margin := MarginContainer.new()
margin.set_anchors_preset(Control.PRESET_FULL_RECT)
margin.add_theme_constant_override("margin_all", 8)
add_child(margin)
var root_v := VBoxContainer.new()
root_v.add_theme_constant_override("separation", 6)
margin.add_child(root_v)
# ---- 顶栏 ----
var top := HBoxContainer.new()
top.add_theme_constant_override("separation", 18)
root_v.add_child(top)
top.add_child(_stat("阴德", 14))
_merit_label = _big_label(26)
top.add_child(_merit_label)
top.add_child(_stat("产出/s", 14))
_rate_label = _big_label(18)
top.add_child(_rate_label)
_click_btn = Button.new()
_click_btn.custom_minimum_size = Vector2(150, 56)
_click_btn.add_theme_font_size_override("font_size", 22)
_click_btn.pressed.connect(_on_click_pressed)
top.add_child(_click_btn)
top.add_child(_stat("冤气", 14))
_wrath_label = _big_label(16)
top.add_child(_wrath_label)
top.add_child(_stat("宿慧", 14))
_suihui_label = _big_label(16)
top.add_child(_suihui_label)
top.add_child(_stat("转世", 14))
_rebirth_label = _big_label(16)
top.add_child(_rebirth_label)
top.add_child(_stat("超度", 14))
_souls_label = _big_label(16)
top.add_child(_souls_label)
# ---- 十殿色带(数据行 → 色带;§10.4:10 条色带 + 殿名文字)----
var hall_row := HBoxContainer.new()
hall_row.add_theme_constant_override("separation", 4)
hall_row.custom_minimum_size = Vector2(0, 100)
root_v.add_child(hall_row)
for h in DataRegistry.data.halls:
var pal := DataRegistry.data.palette_for_hall(h.id)
var band := HallBand.make(h, pal)
hall_row.add_child(band)
_hall_bands.append(band)
# ---- 军列带 12 槽 ----
_army = ArmyBand.new()
_army.custom_minimum_size = Vector2(0, 140)
root_v.add_child(_army)
# ---- HUD(升级 + 宿慧商店)----
_hud = HudRoot.new()
root_v.add_child(_hud)
# ---- 进度行(目标梯度:下一座殿门,[B§6]"距 XX 还差 X")----
_progress_label = Label.new()
_progress_label.add_theme_font_size_override("font_size", 14)
_progress_label.add_theme_color_override("font_color", BONE)
root_v.add_child(_progress_label)
# ---- 日志行 ----
_log_label = Label.new()
_log_label.add_theme_font_size_override("font_size", 13)
_log_label.add_theme_color_override("font_color", BONE)
_log_label.text = "灰盒版:点→买→涨→解锁→轮回。左键×1 / 右键×10 / Shift+左键最大。"
root_v.add_child(_log_label)
# 全局墨色底(灰盒唯一"视觉"决定:高对比)
var bg := ColorRect.new()
bg.color = INK
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
bg.z_index = -10
add_child(bg)
func _stat(text: String, size: int) -> Label:
var l := Label.new()
l.text = text
l.add_theme_font_size_override("font_size", size)
l.add_theme_color_override("font_color", BONE)
return l
func _big_label(size: int) -> Label:
var l := Label.new()
l.add_theme_font_size_override("font_size", size)
l.add_theme_color_override("font_color", BONE)
l.custom_minimum_size = Vector2(110, 0)
return l
func _connect() -> void:
_army.buy_requested.connect(_on_buy_generator)
_hud.buy_upgrade_requested.connect(_on_buy_upgrade)
_hud.buy_meta_requested.connect(_on_buy_meta)
_hud.buy_wrath_layer_requested.connect(func(): GameState.cmd_buy_wrath_layer())
_hud.prestige_requested.connect(func(): GameState.cmd_prestige())
_hud.enthrone_requested.connect(func(): GameState.cmd_enthrone())
GameState.snapshot_changed.connect(_on_snapshot)
GameState.hall_passed.connect(_on_hall_passed)
GameState.prestige_done.connect(_on_prestige_done)
GameState.enthrone_done.connect(_on_enthrone_done)
_on_snapshot(GameState.get_snapshot())
# ----------------------------------------------------------------------------
# 命令下行(UI 唯一写入口;校验与扣费在 core/game)
# ----------------------------------------------------------------------------
func _on_click_pressed() -> void:
var gain := GameState.cmd_click()
_log_label.text = "指诀一掐,阴德 +%s。" % NumberFormat.short(gain)
func _on_buy_generator(gen_index: int, count: int) -> void:
if not GameState.bulk_unlocked(count if count > 0 else -1) and count != 1:
_log_label.text = "批量购置未解锁(路引·批量 or 老马识途)。"
return
var r := GameState.cmd_buy_generator(gen_index, count)
if bool(r.get("ok", false)):
var gens: Array = GameState.get_snapshot().get("gens", [])
var name := str((gens[gen_index] as Dictionary).get("name_cn", "")) if gen_index < gens.size() else ""
_log_label.text = "%s 入列 ×%d(-%s 阴德)。" % [name, int(r.get("bought", 0)),
NumberFormat.short(float(r.get("spent", 0.0)))]
else:
_log_label.text = "购不得:%s" % str(r.get("reason", ""))
func _on_buy_upgrade(id: String) -> void:
var r := GameState.cmd_buy_upgrade(id)
_log_label.text = "已购 %s。" % id if bool(r.get("ok", false)) else "购不得:%s" % str(r.get("reason", ""))
func _on_buy_meta(id: String) -> void:
var r := GameState.cmd_buy_meta(id)
_log_label.text = "宿慧觉醒 %s(阶 %d)。" % [id, int(r.get("rank", 0))] if bool(r.get("ok", false)) \
else "购不得:%s" % str(r.get("reason", ""))
# ----------------------------------------------------------------------------
# 快照上行(10Hz;只读)
# ----------------------------------------------------------------------------
func _on_snapshot(snap: Dictionary) -> void:
_merit_label.text = NumberFormat.short(float(snap.get("merit", 0.0)))
_rate_label.text = "%s/s" % NumberFormat.short(float(snap.get("output_per_sec", 0.0)))
_click_btn.text = "点击 +%s" % NumberFormat.short(float(snap.get("click_value", 0.0)))
_wrath_label.text = "%s(%d 层)" % [NumberFormat.short(float(snap.get("wrath", 0.0))),
int(snap.get("wrath_layers", 0))]
_suihui_label.text = str(int(snap.get("suihui", 0)))
_rebirth_label.text = str(int(snap.get("rebirths", 0)))
_souls_label.text = NumberFormat.short_int(float(snap.get("souls_saved", 0)))
var nh: Dictionary = snap.get("next_hall", {})
if nh.is_empty():
_progress_label_text("十殿已穿——轮回转世,或受敕自立。")
else:
var eta := float(nh.get("eta_sec", 0.0))
_progress_label_text("下一目标:H%d %s 门 %s(持有 %s,约 %s)" % [
int(nh.get("no", 0)), str(nh.get("king_cn", "")),
NumberFormat.short_int(float(nh.get("gate_merit", 0.0))),
NumberFormat.short_int(float(nh.get("have", 0.0))),
NumberFormat.duration(eta) if eta > 0.0 else "—"])
_army.refresh(snap)
_hud.refresh(snap)
_refresh_hall_bands(snap)
func _progress_label_text(t: String) -> void:
if _progress_shown != t:
_progress_shown = t
_progress_label.text = t
func _refresh_hall_bands(snap: Dictionary) -> void:
var passed := int(snap.get("halls_passed", 0))
var nh: Dictionary = snap.get("next_hall", {})
for i in _hall_bands.size():
var is_passed := i < passed
var is_current := i == passed
var have := float(nh.get("have", 0.0)) if is_current else 0.0
var need := DataRegistry.data.halls[i].gate_merit if is_passed else \
float(nh.get("gate_merit", 0.0))
_hall_bands[i].set_state(is_passed, is_current, have, need)
func _on_hall_passed(hall_no: int) -> void:
var h := DataRegistry.data.hall_by_no(hall_no)
_log_label.text = "殿门开:H%d %s。" % [hall_no, h.king_cn if h else ""]
func _on_prestige_done(gain: int, rebirths: int) -> void:
_log_label.text = "轮回转世(第 %d 世):+%d 宿慧。" % [rebirths, gain]
func _on_enthrone_done() -> void:
_log_label.text = "阴司敕封:幽冥天子可用了。"
+6
View File
@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://src/presentation/stage/stage_root.gd" id="1_stage"]
[node name="StageRoot" type="Node"]
script = ExtResource("1_stage")
+645
View File
@@ -0,0 +1,645 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
r"""
gen_balance_sheets.py —— Balance Bible v1.0 → CSV 落表 + build JSON 生成
============================================================================
用途(架构 §3.1 数据管线第一段):
design/gdd/2026-10-19-F-balance-and-levels.md(Balance Bible v1.0,下称 [B§x])
→ data/sheets/*.csv(策划维护源,git 跟踪)
→ data/build/*.json(SheetImporter 产物;本脚本以与 SheetImporter
_canonical_json 逐字节一致的序列化器预生成,供 CI 漂移门;
用户首次跑 `godot --headless -s res://src/tools/import_main.gd`
后 `git diff --exit-code -- data/build/` 必须为空)
铁律:
- 数值列禁千分位/禁科学计数法(SheetImporter._convert 的正则只接受
^[+-]?([0-9]+(\.[0-9]+)?|\.[0-9]+)$)
- 稳定字符串 ID:永不重编号、永不复用(W1 已分配的 ID 原样保留)
- 本脚本只写 data/sheets/ 与 data/build/,不碰 src/
表清单(8 张):
[B§12] 七张内容表:generators / upgrades / halls / hall_palettes /
boss_battles / events / meta_upgrades
+ balance_tuning(架构 §2.1 已列名;容纳 [B§3.2 冤煞层 / §4 prestige 参数 /
§8 离线衰减表 / §1 三乘区软上限] —— 这些全局参数在七张内容表中无归属行,
按数据驱动红线"零硬编码"必须入表)
[B] prose 与表格不一致处的采信规则(详见 docs/engineering/2026-10-19-f-w2-csv-provenance.md):
P-1 速率 prose "gen2 起 = 8^(n-1)" 与表值(gen2=1=8^0)差一;采信表值 → rate=8^(n-2)
P-2 效率升级成本 prose "第 14/18/…/34 级" 与公式 r^(10+4n) 差一;采信公式
(0 起始级号:第 14 级 = C0×r^14,公式自洽)
P-3 效率升级成本为实数 → 取整到最近整数(工程取整规则,[B] 未规定)
P-4 敕封([B§4.4] "全局恩典+25%")与 m08 宿慧商品(同名 +25%×2)字面重叠;
按 [B] 字面两者都实现(auto +25% + m08 每阶 +25%),已在 provenance 标注
待模拟器门 #7/#12 与文策渊裁定
P-5 系统闸门成本 [B] 未给值 → 工程外推 = 对应殿门阈值 ÷2
P-6 入职文书成本 [B] 仅钉 g03=800 → 公式 = C0×2/3(对 g03 精确复现 800)
P-7 入职文书/系统闸门的 display_en 为临时译名([B§11 差异 #6]:英文名延后)
P-8 离线契约([B§8]"8h≈在线1.5-2h")与衰减表求值矛盾(8h÷1h=6.2)——
按表实现,门 #9 如实报 FAIL,待文策渊裁定(改表或改契约区间)
P-9 [B§2.3] 8 条系统闸门的身份与 GDD §3.2"机制路引清单"(鬼门关/枉死城/
望乡台/孽镜/幽都/轮回隙/幽冥/天子)不一致——[B] 为数值母本,采信 [B];
GDD 清单中的统计页/冤案卡册等入口去向待文策渊在 W3 事件范围一并裁定
P-10 [B§2.2] 点击 L5-12 = 8×(+1% 产出/s),GDD §3.2 同行为 4× (+5/10/15/10%,
满 +40%)——采信 [B] 表值(8×1%=+8%),差异已标注待门 #7 标定后裁定
P-11 跳过机制口径:[B§4.2]"每 5 级宿慧 → 前 3 档各 25 级"按
floor(宿慧/间隔)×25 级叠加实现(m04 间隔 5→4;工程裁定叠加语义)
P-12 up_gate_tianzi(敕封诏书)requires:W2 初版误置 "enthroned"(死锁:
未敕封不可买诏书 → 永不受敕)。GDD §3.4 触发链 = 买诏书 + 再败转轮王
→ 受敕;本版改为 "enthrone_ready"(转世≥2 且 宿慧≥60,[G§3.4] 解锁条件),
gen12 愿景档仍以 enthroned 旗标为最终解锁(EconomyState 闸门规则)
P-13 离线升级 [B§2.4] "每级 ×1.15" 为乘性链(6 级 ×2.31),与三乘区
"加法优先"纪律字面冲突——离线倍率不在三显式乘区内,按 [B] 表值实现,
provenance 标注待裁定
P-14 tune_souls_div=10000 新增([B§6]"超度 = floor(本轮累计阴德÷10,000)",
叙事计数器不设闸门);GDD §4.1 的超度事件计数口路由 W3 事件系统承接
P-15 法器附加(点击 L5-12,CSV click_output_pct)乘区口径:[B§1] 总公式与
GDD §3.2 均列入 M_global(全局区),[B§2.2] 表 prose 为"点击附加 +1%
当前产出/s"(点击区口径)——采信公式层(全局区),EconomyState.recompute
求和进 set_global_add;MultiplierSet 点击区输入保持 0(两解随时可换)
用法:
/c/Users/89268/.workbuddy/binaries/python/envs/default/Scripts/python.exe \\
src/tools/gen_balance_sheets.py
无 stdout 输出;自检失败会以非零退出码中止(不写半成品)。
"""
import math
import os
import sys
# ---------------------------------------------------------------------------
# 路径(相对本脚本位置解析,与 cwd 无关)
# ---------------------------------------------------------------------------
HERE = os.path.dirname(os.path.abspath(__file__))
PROJECT_ROOT = os.path.abspath(os.path.join(HERE, "..", ".."))
SHEETS_DIR = os.path.join(PROJECT_ROOT, "data", "sheets")
BUILD_DIR = os.path.join(PROJECT_ROOT, "data", "build")
# ---------------------------------------------------------------------------
# [B§1] 班底总表(12 档)。id/display 沿用 W1 已分配稳定值;C0/r 为 [B§1] 表值。
# rate:gen1=0.1;gen_n (n>=2) = 8^(n-2)([B§1] 表值 = 8 的纯幂阶梯,见 P-1)
# ---------------------------------------------------------------------------
GENERATORS = [
# (id, display_cn, display_en[临时], C0, r, unlock_hall, unlock_gate)
("gen_01_youhun", "游魂", "Wandering Souls", 15, 1.13, "none", "none"),
("gen_02_yinbing", "阴兵", "Ghost Soldiers", 120, 1.14, "none", "none"),
("gen_03_guizu", "鬼卒班", "Ghost Constables", 1200, 1.15, "hall_01_qinguang", "up_gate_guizu"),
("gen_04_biliao", "勾魂笔僚", "Death-Scribe Clerks", 12000, 1.15, "hall_02_chujiang", "up_gate_biliao"),
("gen_05_tangpu", "孟婆汤铺", "Meng Po's Soup Stall", 120000, 1.16, "hall_03_songdi", "up_gate_tangpu"),
("gen_06_nanmin", "枉死城难民团", "Refugee Camp of the Wrongfully Dead", 1200000, 1.16, "hall_04_wuguan", "up_gate_nanmin"),
("gen_07_shuban", "阴律司书办", "Clerks of the Nether Court", 12000000, 1.17, "hall_05_yama", "up_gate_shuban"),
("gen_08_bingfang", "幽都兵坊", "Armory of the Dark Capital", 120000000, 1.17, "hall_06_biancheng","up_gate_bingfang"),
("gen_09_wodi", "十殿卧底", "Sleeper Agents in the Ten Courts",1200000000,1.18, "hall_07_taishan", "up_gate_wodi"),
("gen_10_xingzhe", "轮回裂隙行者", "Rift Walkers of the Wheel", 12000000000, 1.18, "hall_08_dushi", "up_gate_xingzhe"),
("gen_11_yinhun", "六道引魂人", "Soul Guides of the Six Paths", 120000000000, 1.19, "hall_09_pingdeng","up_gate_yinhun"),
("gen_12_tianzi", "幽冥天子", "Emperor of the Netherworld", 1200000000000, 1.20, "none", "up_gate_tianzi"),
]
def gen_rate(n: int) -> float:
"""[B§1] 表值:gen1=0.1;gen_n (n>=2)=8^(n-2)。"""
if n == 1:
return 0.1
return float(8 ** (n - 2))
# ---------------------------------------------------------------------------
# [B§6] 十殿总表。gate_merit = 殿门条件(本轮累计阴德,单计数器 [B§0.1])
# ---------------------------------------------------------------------------
HALLS = [
# (id, no, king_cn, king_en[临时], motif_cn, motif_en[临时], target_min, gate_merit, unlock_gen, unlock_gate, palette_id, is_prestige_gate)
("hall_01_qinguang", 1, "秦广王", "King Qinguang", "孽镜台(照心)", "Mirror of Karma", 25, 2000, "gen_03_guizu", "up_gate_guizu", "pal_hall_01", "false"),
("hall_02_chujiang", 2, "楚江王", "King Chujiang", "寒冰狱", "Ice Hell", 42, 30000, "gen_04_biliao", "up_gate_biliao", "pal_hall_02", "false"),
("hall_03_songdi", 3, "宋帝王", "King Songdi", "剥皮亭", "Flaying Pavilion", 62, 300000, "gen_05_tangpu", "up_gate_tangpu", "pal_hall_03", "false"),
("hall_04_wuguan", 4, "五官王", "King Wuguan", "沸汤/毒蛇", "Scalding Cauldron", 84, 2000000, "gen_06_nanmin", "up_gate_nanmin", "pal_hall_04", "false"),
("hall_05_yama", 5, "阎罗王", "King Yama", "叫唤地狱/铁床", "Wailing Hell", 105, 10000000, "gen_07_shuban", "up_gate_shuban", "pal_hall_05", "false"),
("hall_06_biancheng",6, "卞城王", "King Biancheng","石碾", "Stone Roller", 125, 40000000, "gen_08_bingfang","up_gate_bingfang","pal_hall_06", "false"),
("hall_07_taishan", 7, "泰山王", "King Taishan", "热油火坑", "Boiling Oil Pit", 143, 120000000, "gen_09_wodi", "up_gate_wodi", "pal_hall_07", "false"),
("hall_08_dushi", 8, "都市王", "King Dushi", "泥犁/大石", "Avici Stone", 160, 350000000, "gen_10_xingzhe", "up_gate_xingzhe", "pal_hall_08", "false"),
("hall_09_pingdeng", 9, "平等王", "King Pingdeng", "拔舌/铁树", "Tongue-Ripping Iron Tree",175, 800000000, "gen_11_yinhun", "up_gate_yinhun", "pal_hall_09", "false"),
("hall_10_zhuanlun",10, "转轮王", "King of the Wheel", "轮回道(六道环)","Wheel of Rebirth", 180, 1500000000, "none", "none", "pal_hall_10", "true"),
]
# [B§5] 殿系数 H1-H10
BOSS_FACTORS = [1.0, 1.1, 1.2, 1.3, 1.4, 1.6, 1.8, 2.0, 2.1, 2.2]
# ---------------------------------------------------------------------------
# [B§2.1] 效率升级 72 条(12 班底 × 6 级)
# 效果:该班底产出 +100%(加法,进班底乘区;6 级满 = ×7 < ×8 软上限)
# 成本:cost_n(g) = C0_g × r_g^(10+4n),n=1..6,取整(P-2/P-3)
# 命名:up_eff_genNN_n(架构 §3.2 ID 示例口径);显示名 = 班底 · 历练 I-VI
# ---------------------------------------------------------------------------
ROMAN = ["I", "II", "III", "IV", "V", "VI"]
def eff_upgrade_cost(c0: float, r: float, n: int) -> int:
return int(round(c0 * (r ** (10 + 4 * n))))
# [B§2.2] 点击升级 12 条
CLICK_MULT_COSTS = [50, 500, 5000, 50000] # L1-4:点击价值 ×2
CLICK_ADD_COSTS = [100000, 1000000, 10000000, 100000000,
1000000000, 10000000000, 100000000000, 1000000000000] # L5-12:+1% 产出/s
CLICK_MULT_NAMES = ["指诀 · 倍力 I", "指诀 · 倍力 II", "指诀 · 倍力 III", "指诀 · 倍力 IV"]
CLICK_ADD_NAMES = ["指诀 · 引阴 I", "指诀 · 引阴 II", "指诀 · 引阴 III", "指诀 · 引阴 IV",
"指诀 · 引阴 V", "指诀 · 引阴 VI", "指诀 · 引阴 VII", "指诀 · 引阴 VIII"]
# [B§2.3] 系统闸门 8 条(成本外推 = 对应殿门阈值 ÷2,P-5)
SYSTEM_GATES = [
# (id, name_cn, name_en[临时], unlock_hall, cost)
("up_gate_wrath", "开通 · 冤气系统", "Unlock: Wrath System", "hall_01_qinguang", 1000),
("up_gate_events", "开通 · 事件系统", "Unlock: Event System", "hall_02_chujiang", 15000),
("up_gate_bulk10", "开通 · 批量购置(×10)", "Unlock: Bulk Buy ×10", "hall_03_songdi", 150000),
("up_gate_offline", "开通 · 离线收益", "Unlock: Offline Income", "hall_04_wuguan", 1000000),
("up_gate_prestige", "开通 · 轮回预告", "Unlock: Rebirth Preview", "hall_06_biancheng",20000000),
("up_gate_boss_retry", "开通 · 殿王再战", "Unlock: Boss Rematch", "hall_07_taishan", 60000000),
("up_gate_bulkmax", "开通 · 批量购置(×尽)", "Unlock: Bulk Buy Max", "hall_08_dushi", 175000000),
("up_gate_enthrone", "开通 · 敕封预告", "Unlock: Enthronement Preview", "hall_09_pingdeng", 400000000),
]
# [B§2.4] 离线升级 6 条(×1.15/级)
OFFLINE_COSTS = [2000, 20000, 200000, 2000000, 20000000, 200000000]
# ---------------------------------------------------------------------------
# [B§2.5] Prestige 升级 12 条(宿慧商店,跨轮回永久)
# effect_type / target / param 口径见 economy 代码与 schema description
# ---------------------------------------------------------------------------
META_UPGRADES = [
# (id, name_cn, name_en[临时], effect_type, target_id, param, cost, max_rank, condition, desc_cn)
("meta_01", "枕旧梦", "Pillow of Old Dreams", "start_levels", "gen_01_youhun", 10, 3, 1, "none", "新轮回开局自带游魂 ×10 级"),
("meta_02", "携阴兵", "Bring Ghost Soldiers", "start_levels", "gen_02_yinbing",10, 5, 1, "none", "开局自带阴兵 ×10 级"),
("meta_03", "宿慧通透", "Perspicacious Wisdom","suihui_gain_pct", "", 0.15, 8, 3, "none", "宿慧获取 +15%(每阶)"),
("meta_04", "天生熟练", "Born Skilled", "skip_interval", "", 4, 12, 1, "none", "跳过机制从每 5 级加快为每 4 级"),
("meta_05", "阴司人脉", "Netherworld Connections","offline_pct", "", 0.25, 6, 1, "none", "离线效率 +25%"),
("meta_06", "斩鬼刃", "Ghost-Slaying Blade", "boss_click_pct", "", 0.30, 10, 1, "none", "殿王战点击伤害 +30%"),
("meta_07", "心安理得", "Easy Conscience", "wrath_penalty_set", "", 0.15, 15, 1, "none", "冤气对宿慧的削减 30%→15%"),
("meta_08", "敕封恩典", "Grace of Enthronement","global_pct", "", 0.25, 20, 2, "none", "全局 +25%(每阶)"),
("meta_09", "阴阳眼", "Yin-Yang Eye", "event_freq_pct", "", 0.20, 10, 1, "none", "事件触发频率 +20%"),
("meta_10", "超度加速", "Salvation Acceleration","souls_mult", "", 2, 8, 1, "none", "超度计数获取 ×2"),
("meta_11", "老马识途", "Old Horse Knows the Way","bulk_unlock", "", 0, 5, 1, "none", "开局即可用批量 ×10"),
("meta_12", "幽冥权柄", "Authority of the Netherworld","conditional_global_pct","gen_12_tianzi", 0.10, 25, 1, "gen12_unlocked", "愿景档(gen12)解锁后全局再 +10%"),
]
# ---------------------------------------------------------------------------
# [B§3.2] 冤煞层 22 层参数 / [B§8] 离线衰减表 / [B§4] prestige 参数 / [B§1] 软上限
# → balance_tuning.csv(key-value 行;架构 §2.1)
# ---------------------------------------------------------------------------
TUNING = [
# (id, kind, num, text, desc_cn)
("tune_click_base", "number", 1, "", "[B§2.2] 点击基础值 = 1(与 gen1 成本 15 配对 → 15 次点击首购)"),
("tune_click_zone_cap", "number", 16, "", "[B§1/§2.2] 点击区软上限 ×16(4 级 ×2 满)"),
("tune_gen_zone_cap", "number", 8, "", "[B§1] 班底乘区软上限 ×8(6 级效率满 = ×7,留 1 级余量)"),
("tune_global_zone_cap", "number", 64, "", "[B§1] 全局乘区软上限 ×64"),
("tune_offline_band1_h", "number", 2, "", "[B§8] 离线 0-2h 速率 100%"),
("tune_offline_band1_rate","number", 1.0, "", "[B§8] 离线 0-2h 速率"),
("tune_offline_band2_h", "number", 8, "", "[B§8] 离线 2-8h 速率 70%"),
("tune_offline_band2_rate","number", 0.7, "", "[B§8] 离线 2-8h 速率"),
("tune_offline_band3_h", "number", 24, "", "[B§8] 离线 8-24h 速率 40%"),
("tune_offline_band3_rate","number", 0.4, "", "[B§8] 离线 8-24h 速率"),
("tune_offline_band4_rate","number", 0.1, "", "[B§8] 离线 >24h 速率 10%"),
("tune_offline_cap_h", "number", 72, "", "[B§8] 离线硬上限 72h 封顶"),
("tune_wrath_layers", "number", 22, "", "[B§3.2] 冤煞层总数 22"),
("tune_wrath_cost_base", "number", 8, "", "[B§3.2] 第 n 层成本 = round(8 × n^1.8)"),
("tune_wrath_cost_exp", "number", 1.8, "", "[B§3.2] 冤煞层成本指数"),
("tune_wrath_bonus", "number", 0.25, "", "[B§3.2] 每层全局 +25%(加法,进全局乘区)"),
("tune_wrath_penalty", "number", 0.03, "", "[B§3.2] 每层宿慧获取削减 +3%"),
("tune_wrath_penalty_cap", "number", 0.3, "", "[B§3.2] 宿慧削减封顶 30%"),
("tune_suihui_div", "number", 1000000, "", "[B§4.1] 宿慧 = floor(√(本轮累计阴德/1e6) × 5)"),
("tune_suihui_mult", "number", 5, "", "[B§4.1] 宿慧公式乘数"),
("tune_suihui_global_pct", "number", 0.03, "", "[B§1/§4.2] 每级宿慧全局产出 +3%(加法,进全局乘区)"),
("tune_enthrone_bonus", "number", 0.25, "", "[B§4.4] 阴司敕封自动全局恩典 +25%(与 m08 商品字面重叠,见 provenance P-4)"),
("tune_skip_interval", "number", 5, "", "[B§4.2] 每 5 级宿慧 → 前 3 档班底各 +25 级(m04 可加快为 4)"),
("tune_skip_gens", "number", 3, "", "[B§4.2] 跳过机制覆盖前 3 档班底"),
("tune_skip_levels", "number", 25, "", "[B§4.2] 跳过机制每档 +25 级"),
("tune_enthrone_rebirths", "number", 2, "", "[B§4.4] 阴司敕封触发:转世 ≥2(约第 2-3 轮,累计 8-12h 为其 pacing 后果)"),
("tune_restart_curve", "text", 0, "1:0.6:0.4:0.3", "[B§4.3] 重开曲线目标(第 5 轮触底 ≥0.25×);模拟器门 #13 断言"),
("tune_event_timeout_default","text", 0, "opt_a", "[B§7] 冤魂申诉超时默认 = 超度(opt_a)"),
("tune_souls_div", "number", 10000, "", "[B§6] 超度亡魂 = floor(本轮累计阴德 ÷ 10,000)(叙事计数器,不设闸门;P-14)"),
("tune_enthrone_suihui_req","number", 60, "", "[G§3.4] 阴司敕封解锁条件:宿慧 ≥60([B§4.4] 仅给 8-12h 体感,采信 GDD 数值)"),
]
# ---------------------------------------------------------------------------
# [B§7] 事件 4 条(倍率走独立瞬时区,不进全局乘区)
# 参数列语义(schema description 同步声明):
# p_mult 倍率参数:output_mult=全产出倍率;click_output_mult=产出倍率部分
# p_clicks 次数参数:click_challenge=需点击次数;click_output_mult=点击倍率;
# choice=冤气增益下限(随殿序 lerp 上限见 p_window_sec)
# p_window_sec 窗口参数:click_challenge=窗口秒;choice=冤气增益上限
# p_penalty/p_penalty_dur_sec click_challenge 失败惩罚(倍率/持续秒)
# opt_a_*/opt_b_* choice 事件二选一(type + param)
# ---------------------------------------------------------------------------
EVENTS = [
# (id, name_cn, name_en[临时], trig_min, trig_max, dur, type,
# p_mult, p_clicks, p_window, p_pen, p_pen_dur,
# opt_a_type, opt_a_param, opt_b_type, opt_b_param, flavor)
("ev_01_baigui", "百鬼夜行", "Night of the Hundred Ghosts", 8, 15, 60, "output_mult",
5, 0, 0, 0, 0, "none", 0, "none", 0,
"百鬼倾巢而出,阴阳秩序暂时失序"),
("ev_02_naoxiang", "阴兵闹饷", "Ghost Mutiny", 10, 18, 15, "click_challenge",
0, 10, 15, 0.5, 60, "none", 0, "none", 0,
"鬼卒也要三牲祭,不然哗变"),
("ev_03_shensu", "冤魂申诉", "Grievance of the Wronged", 6, 12, 45, "choice",
0, 15, 40, 0, 0, "merit_burst_sec", 30, "wrath_gain", 0,
"他跪在地上喊冤枉。你选择……"),
("ev_04_tiangou", "天狗食月", "Heavenly Dog Eats the Moon", 20, 35, 30, "click_output_mult",
2, 10, 0, 0, 0, "none", 0, "none", 0,
"天狗噬月,阴气最盛——动手"),
]
# ---------------------------------------------------------------------------
# [B§6/§5.3] per-hall 色板行(hex 为工程起始值,W5 look-dev 冻结评审校准;
# 四色面积纪律 [B] 墨65/骨白25/朱砂≤8/靛≤7)
# ---------------------------------------------------------------------------
INK_HEX, BONE_HEX, CINNABAR_HEX, INDIGO_HEX = "#1A1A1A", "#F2EDE4", "#B4402C", "#2C4A6E"
PALETTES = [
# (id, hall_id, name_cn, accent_hex, accent_name_cn, flow)
("pal_hall_01", "hall_01_qinguang", "冷靛", "#3D5A80", "冷靛", "bleed_left"),
("pal_hall_02", "hall_02_chujiang", "冰青", "#8FC1C9", "冰青", "seep_down"),
("pal_hall_03", "hall_03_songdi", "暗红", "#7E2E2E", "暗红", "bleed_left"),
("pal_hall_04", "hall_04_wuguan", "锈橙", "#A85C32", "锈橙", "seep_down"),
("pal_hall_05", "hall_05_yama", "玄黑", "#141414", "玄黑", "bleed_left"),
("pal_hall_06", "hall_06_biancheng", "铅灰", "#6E7276", "铅灰", "seep_down"),
("pal_hall_07", "hall_07_taishan", "烬红", "#8C3A2B", "烬红", "bleed_left"),
("pal_hall_08", "hall_08_dushi", "墨绿", "#2F4A3E", "墨绿", "seep_down"),
("pal_hall_09", "hall_09_pingdeng", "黛紫", "#4A3A5C", "黛紫", "bleed_left"),
("pal_hall_10", "hall_10_zhuanlun", "骨白+金", "#C9A227", "暗金", "seep_down"),
]
# ---------------------------------------------------------------------------
# CSV 列定义(顺序 = schema required 顺序 = build JSON 键序)
# ---------------------------------------------------------------------------
GEN_COLS = ["id", "display_cn", "display_en", "base_cost", "cost_ratio", "base_rate",
"unlock_hall", "unlock_gate", "sprite_row"]
HALL_COLS = ["id", "no", "king_cn", "king_en", "motif_cn", "motif_en", "target_min",
"gate_merit", "unlock_gen", "unlock_gate", "palette_id", "is_prestige_gate"]
UPGRADE_COLS = ["id", "name_cn", "name_en", "type", "target_gen", "unlock_hall",
"effect_type", "effect_param", "requires", "cost", "desc_cn"]
PALETTE_COLS = ["id", "hall_id", "name_cn", "ink_hex", "bone_hex", "cinnabar_hex",
"indigo_hex", "accent_hex", "accent_name_cn", "flow", "lut_index",
"ink_pct", "bone_pct", "cinnabar_max_pct", "indigo_max_pct"]
BOSS_COLS = ["id", "hall_id", "hall_factor", "hp_factor", "army_dps_factor",
"intro_sec", "intro_dmg_souls_factor", "intro_dmg_cap_ratio",
"active_sec", "combo_window_sec", "combo_step", "combo_cap",
"retry_cooldown_sec", "reward_hp_ratio"]
EVENT_COLS = ["id", "name_cn", "name_en", "trigger_min_min", "trigger_max_min",
"duration_sec", "type", "p_mult", "p_clicks", "p_window_sec",
"p_penalty", "p_penalty_dur_sec", "opt_a_type", "opt_a_param",
"opt_b_type", "opt_b_param", "flavor_cn"]
META_COLS = ["id", "name_cn", "name_en", "effect_type", "target_id", "effect_param",
"cost_suihui", "max_rank", "condition", "desc_cn"]
TUNING_COLS = ["id", "kind", "num", "text", "desc_cn"]
def fnum(v) -> str:
"""数值列序列化:整数不带小数点;小数保留(禁科学计数法)。"""
if isinstance(v, bool):
return "true" if v else "false"
if isinstance(v, int):
return str(v)
f = float(v)
if f == int(f) and abs(f) < 1e15:
return str(int(f))
return repr(f)
def build_rows():
"""返回 {sheet: (columns, rows)};rows 为 dict 列表(键序 = 列序)。"""
sheets = {}
# --- generators([B§1]) -------------------------------------------------
rows = []
for i, (gid, cn, en, c0, r, hall, gate) in enumerate(GENERATORS, start=1):
rows.append({
"id": gid, "display_cn": cn, "display_en": en,
"base_cost": c0, "cost_ratio": r, "base_rate": gen_rate(i),
"unlock_hall": hall, "unlock_gate": gate,
"sprite_row": "res://assets/sprites/units/%s" % gid,
})
sheets["generators"] = (GEN_COLS, rows)
# --- upgrades([B§2],108 条 = 效率72 + 点击12 + 路引18 + 离线6) ----------
rows = []
for i, (gid, cn, en, c0, r, hall, gate) in enumerate(GENERATORS, start=1):
for n in range(1, 7):
rows.append({
"id": "up_eff_%s_%d" % (gid, n),
"name_cn": "%s · 历练 %s" % (cn, ROMAN[n - 1]),
"name_en": "%s · Training %s" % (en, ROMAN[n - 1]),
"type": "efficiency", "target_gen": gid, "unlock_hall": hall,
"effect_type": "gen_output_add", "effect_param": 1.0, "requires": "none",
"cost": eff_upgrade_cost(c0, r, n),
"desc_cn": "该班底产出 +100%(加法;6 级满 = ×7,软上限 ×8)",
})
for i in range(4):
rows.append({
"id": "up_click_%d" % (i + 1), "name_cn": CLICK_MULT_NAMES[i],
"name_en": "Gesture · Force %s" % ROMAN[i],
"type": "click", "target_gen": "none", "unlock_hall": "none",
"effect_type": "click_mult", "effect_param": 2, "requires": "none",
"cost": CLICK_MULT_COSTS[i],
"desc_cn": "点击价值 ×2(点击区,软上限 ×16)",
})
for i in range(8):
rows.append({
"id": "up_click_%d" % (i + 5), "name_cn": CLICK_ADD_NAMES[i],
"name_en": "Gesture · Soul-Draw %d" % (i + 1),
"type": "click", "target_gen": "none", "unlock_hall": "none",
"effect_type": "click_output_pct", "effect_param": 0.01, "requires": "none",
"cost": CLICK_ADD_COSTS[i],
"desc_cn": "点击附加 +1% 当前产出/s(仪式与殿王战弹药)",
})
# 路引 · 10 条班底入职文书(ID 与 generators.unlock_gate 引用一致,W1 已分配)
for i, (gid, cn, en, c0, r, hall, gate) in enumerate(GENERATORS[2:], start=3):
name_cn = "幽冥天子 · 敕封诏书" if gid == "gen_12_tianzi" else "%s · 入职文书" % cn
name_en = "Enthronement Edict" if gid == "gen_12_tianzi" else "%s · Hiring Letter" % en
req = "enthrone_ready" if gid == "gen_12_tianzi" else "none" # P-12
rows.append({
"id": gate, "name_cn": name_cn, "name_en": name_en,
"type": "gate_hire", "target_gen": gid, "unlock_hall": hall,
"effect_type": "unlock", "effect_param": 0, "requires": req,
"cost": int(round(c0 * 2.0 / 3.0)), # P-6:对 g03 精确复现 [B§2.3] 的 800
"desc_cn": "编制闸门:购入后该班底可购置(需先过对应殿门)",
})
for sgid, scn, sen, shall, scost in SYSTEM_GATES:
rows.append({
"id": sgid, "name_cn": scn, "name_en": sen,
"type": "gate_system", "target_gen": "none", "unlock_hall": shall,
"effect_type": "unlock", "effect_param": 0, "requires": "none", "cost": scost,
"desc_cn": "系统闸门:纯解锁零效果([B§2.3];成本外推 = 殿门阈值÷2,P-5)",
})
for i in range(6):
rows.append({
"id": "up_offline_%d" % (i + 1),
"name_cn": "离线收益 · 沉淀 %s" % ROMAN[i],
"name_en": "Offline Income · Settling %s" % ROMAN[i],
"type": "offline", "target_gen": "none", "unlock_hall": "hall_04_wuguan",
"effect_type": "offline_mult", "effect_param": 1.15, "requires": "none",
"cost": OFFLINE_COSTS[i],
"desc_cn": "离线效率 ×1.15(作用于衰减表全部时段;6 级满 ×2.31)",
})
sheets["upgrades"] = (UPGRADE_COLS, rows)
# --- halls([B§6]) -------------------------------------------------------
rows = []
for (hid, no, kcn, ken, mcn, men, tmin, gate_merit, ugen, ugate, pal, pres) in HALLS:
rows.append({
"id": hid, "no": no, "king_cn": kcn, "king_en": ken,
"motif_cn": mcn, "motif_en": men, "target_min": tmin,
"gate_merit": gate_merit, "unlock_gen": ugen, "unlock_gate": ugate,
"palette_id": pal, "is_prestige_gate": pres,
})
sheets["halls"] = (HALL_COLS, rows)
# --- hall_palettes([B§6 色板列] + 架构 §5.3;hex 工程起始值) -------------
rows = []
for i, (pid, hid, name, accent, accent_cn, flow) in enumerate(PALETTES, start=1):
rows.append({
"id": pid, "hall_id": hid, "name_cn": name,
"ink_hex": INK_HEX, "bone_hex": BONE_HEX, "cinnabar_hex": CINNABAR_HEX,
"indigo_hex": INDIGO_HEX, "accent_hex": accent, "accent_name_cn": accent_cn,
"flow": flow, "lut_index": i,
"ink_pct": 65, "bone_pct": 25, "cinnabar_max_pct": 8, "indigo_max_pct": 7,
})
sheets["hall_palettes"] = (PALETTE_COLS, rows)
# --- boss_battles([B§5]) -------------------------------------------------
rows = []
for i, (hid, no, *_rest) in enumerate(HALLS):
rows.append({
"id": "boss_hall_%02d" % no, "hall_id": hid,
"hall_factor": BOSS_FACTORS[i],
"hp_factor": 15, "army_dps_factor": 1.0,
"intro_sec": 3, "intro_dmg_souls_factor": 0.5, "intro_dmg_cap_ratio": 0.2,
"active_sec": 30, "combo_window_sec": 0.6, "combo_step": 0.02,
"combo_cap": 0.6, "retry_cooldown_sec": 60, "reward_hp_ratio": 0.2,
})
sheets["boss_battles"] = (BOSS_COLS, rows)
# --- events([B§7]) -------------------------------------------------------
rows = []
for (eid, cn, en, tmin, tmax, dur, etype, p_mult, p_clicks, p_window,
p_pen, p_pen_dur, oa_type, oa_param, ob_type, ob_param, flavor) in EVENTS:
rows.append({
"id": eid, "name_cn": cn, "name_en": en,
"trigger_min_min": tmin, "trigger_max_min": tmax, "duration_sec": dur,
"type": etype, "p_mult": p_mult, "p_clicks": p_clicks,
"p_window_sec": p_window, "p_penalty": p_pen, "p_penalty_dur_sec": p_pen_dur,
"opt_a_type": oa_type, "opt_a_param": oa_param,
"opt_b_type": ob_type, "opt_b_param": ob_param, "flavor_cn": flavor,
})
sheets["events"] = (EVENT_COLS, rows)
# --- meta_upgrades([B§2.5]) ---------------------------------------------
rows = []
for (mid, cn, en, etype, target, param, cost, max_rank, cond, desc) in META_UPGRADES:
rows.append({
"id": mid, "name_cn": cn, "name_en": en, "effect_type": etype,
"target_id": target, "effect_param": param, "cost_suihui": cost,
"max_rank": max_rank, "condition": cond, "desc_cn": desc,
})
sheets["meta_upgrades"] = (META_COLS, rows)
# --- balance_tuning -------------------------------------------------------
rows = []
for (tid, kind, num, text, desc) in TUNING:
rows.append({
"id": tid, "kind": kind, "num": num, "text": text, "desc_cn": desc,
})
sheets["balance_tuning"] = (TUNING_COLS, rows)
return sheets
# ---------------------------------------------------------------------------
# 自检:Bible 表值断言(任何一条失败 → 中止,不写盘)
# ---------------------------------------------------------------------------
def self_check(sheets) -> None:
gens = sheets["generators"][1]
# [B§1] 速率阶梯:gen2 起 8 的纯幂
for i, g in enumerate(gens):
n = i + 1
expect = 0.1 if n == 1 else float(8 ** (n - 2))
assert float(g["base_rate"]) == expect, "rate %s" % g["id"]
assert float(gens[11]["base_rate"]) == 1073741824.0, "gen12 rate = 8^10"
# [B§1] 首级回本 = C0/rate:tier2-6 ∈ [120,293]
paybacks = [g["base_cost"] / float(g["base_rate"]) for g in gens]
for i in range(1, 6): # tier2..tier6(0-indexed 1..5)
assert 120.0 <= paybacks[i] <= 293.0, "payback tier%d = %.1f" % (i + 1, paybacks[i])
# [B§1] 全表单调递增(tier7+ 递增是设计意图)
for i in range(1, len(paybacks) - 1):
assert paybacks[i] < paybacks[i + 1], "payback 单调性 @tier%d" % (i + 1)
assert abs(paybacks[1] - 120.0) < 0.5, "gen2 回本 120s"
assert abs(paybacks[5] - 293.0) < 0.5, "gen6 回本 293s"
assert abs(paybacks[11] - 1118.0) < 0.5, "gen12 回本 1118s"
# [B§6] 殿门阈值
halls = sheets["halls"][1]
expect_gates = [2000, 30000, 300000, 2000000, 10000000, 40000000,
120000000, 350000000, 800000000, 1500000000]
for h, eg in zip(halls, expect_gates):
assert h["gate_merit"] == eg, "gate %s" % h["id"]
assert sum(1 for h in halls if h["is_prestige_gate"] == "true") == 1
assert halls[-1]["is_prestige_gate"] == "true"
# 升级 108 条 + meta 12 条([B§2] 配比)
ups = sheets["upgrades"][1]
assert len(ups) == 108, "upgrades 行数 %d" % len(ups)
from collections import Counter
c = Counter(u["type"] for u in ups)
assert c["efficiency"] == 72 and c["click"] == 12 and c["gate_hire"] == 10 \
and c["gate_system"] == 8 and c["offline"] == 6, dict(c)
# 效率成本抽检:gen1 n=1 = round(15×1.13^14)
eff1 = [u for u in ups if u["id"] == "up_eff_gen_01_youhun_1"][0]
assert eff1["cost"] == int(round(15 * 1.13 ** 14)), "eff gen1 n1 cost"
# 入职文书 g03 = 800([B§2.3] 唯一钉值)
g03 = [u for u in ups if u["id"] == "up_gate_guizu"][0]
assert g03["cost"] == 800, "g03 gate cost = 800(P-6 复现)"
# 跨表引用完整性:generators.unlock_gate ⊆ upgrades.id
up_ids = {u["id"] for u in ups}
for g in gens:
if g["unlock_gate"] != "none":
assert g["unlock_gate"] in up_ids, "dangling %s" % g["unlock_gate"]
hall_ids = {h["id"] for h in halls}
for g in gens:
if g["unlock_hall"] != "none":
assert g["unlock_hall"] in hall_ids, "dangling hall %s" % g["unlock_hall"]
# boss_battles 10 行 + [B§5] 殿系数
bosses = sheets["boss_battles"][1]
assert len(bosses) == 10
for b, h in zip(bosses, halls):
assert b["hall_id"] == h["id"]
assert b["hp_factor"] == 15 # [B§11 差异 #4]:50→15
# 冤煞成本公式 spot check(round(8×n^1.8) 由 core 代码求值,此处验公式输入)
tune = {t["id"]: t for t in sheets["balance_tuning"][1]}
assert tune["tune_wrath_layers"]["num"] == 22
assert tune["tune_wrath_cost_base"]["num"] == 8
assert tune["tune_offline_cap_h"]["num"] == 72
# P-14:超度除数 + 敕封宿慧门槛(GDD §3.4)
assert tune["tune_souls_div"]["num"] == 10000
assert tune["tune_enthrone_suihui_req"]["num"] == 60
assert tune["tune_enthrone_rebirths"]["num"] == 2
assert tune["tune_suihui_div"]["num"] == 1000000
assert tune["tune_suihui_mult"]["num"] == 5
assert tune["tune_suihui_global_pct"]["num"] == 0.03
assert tune["tune_enthrone_bonus"]["num"] == 0.25
assert tune["tune_skip_interval"]["num"] == 5
assert tune["tune_skip_gens"]["num"] == 3
assert tune["tune_skip_levels"]["num"] == 25
assert tune["tune_click_base"]["num"] == 1
assert tune["tune_click_zone_cap"]["num"] == 16
assert tune["tune_gen_zone_cap"]["num"] == 8
assert tune["tune_global_zone_cap"]["num"] == 64
assert tune["tune_restart_curve"]["text"] == "1:0.6:0.4:0.3"
assert tune["tune_event_timeout_default"]["text"] == "opt_a"
# meta 12 条
metas = sheets["meta_upgrades"][1]
assert len(metas) == 12
assert [m["cost_suihui"] for m in metas] == [3, 5, 8, 12, 6, 10, 15, 20, 10, 8, 5, 25]
# ---------------------------------------------------------------------------
# 序列化:CSV(utf-8-sig + LF,与 W1 generators.csv 一致)与 canonical JSON
# (逐字节复刻 SheetImporter._canonical_json:tab 缩进 / 键序 = 列序 /
# 整数无小数点 / 积分浮点写成整数 / 字符串 JSON.stringify 原样中文)
# ---------------------------------------------------------------------------
def to_cell(v) -> str:
if isinstance(v, bool):
return "true" if v else "false"
if isinstance(v, str):
return v
return fnum(v)
def csv_quote(s: str) -> str:
if any(ch in s for ch in [",", '"', "\n", "\r"]):
return '"' + s.replace('"', '""') + '"'
return s
def write_csv(path: str, cols, rows) -> None:
lines = [",".join(csv_quote(c) for c in cols)]
for r in rows:
lines.append(",".join(csv_quote(to_cell(r[c])) for c in cols))
with open(path, "w", encoding="utf-8-sig", newline="\n") as f:
f.write("\n".join(lines) + "\n")
def canon_json(value, indent: str = "") -> str:
if isinstance(value, dict):
if not value:
return "{}"
lines = ["%s\t%s: %s" % (indent, json_str(k), canon_json(v, indent + "\t"))
for k, v in value.items()]
return "{\n" + ",\n".join(lines) + "\n" + indent + "}"
if isinstance(value, (list, tuple)):
if not value:
return "[]"
lines = ["%s\t%s" % (indent, canon_json(v, indent + "\t")) for v in value]
return "[\n" + ",\n".join(lines) + "\n" + indent + "]"
if isinstance(value, bool):
return "true" if value else "false"
if isinstance(value, int):
return str(value)
if isinstance(value, float):
if value == int(value) and abs(value) < 1e15:
return str(int(value))
return repr(value)
return json_str(str(value))
def json_str(s: str) -> str:
"""复刻 Godot JSON.stringify:转义 \" 与 \\ 与控制字符;中文原样。"""
out = ['"']
for ch in s:
if ch == '"':
out.append('\\"')
elif ch == "\\":
out.append("\\\\")
elif ch == "\n":
out.append("\\n")
elif ch == "\r":
out.append("\\r")
elif ch == "\t":
out.append("\\t")
elif ord(ch) < 0x20:
out.append("\\u%04x" % ord(ch))
else:
out.append(ch)
out.append('"')
return "".join(out)
def typed_row(row: dict) -> dict:
"""模拟 SheetImporter._convert:数字列按 CSV 字符串 round-trip
(含 '.' → float,否则 int),保证 canonical JSON 字节一致。"""
out = {}
for k, v in row.items():
if isinstance(v, bool):
out[k] = v
elif isinstance(v, (int, float)):
s = fnum(v)
out[k] = float(s) if "." in s else int(s)
else:
out[k] = v
return out
def write_build(path: str, sheet: str, cols, rows, schema_version: int = 0) -> None:
doc = {
"sheet": sheet,
"schema_version": schema_version,
"columns": list(cols),
"rows": [typed_row(r) for r in rows],
}
with open(path, "w", encoding="utf-8", newline="\n") as f:
f.write(canon_json(doc) + "\n")
def main() -> int:
sheets = build_rows()
self_check(sheets)
os.makedirs(SHEETS_DIR, exist_ok=True)
os.makedirs(BUILD_DIR, exist_ok=True)
for sheet, (cols, rows) in sheets.items():
write_csv(os.path.join(SHEETS_DIR, sheet + ".csv"), cols, rows)
write_build(os.path.join(BUILD_DIR, sheet + ".json"), sheet, cols, rows)
return 0
if __name__ == "__main__":
sys.exit(main())
+40
View File
@@ -0,0 +1,40 @@
extends RefCounted
# 用例:BalanceData 载入 + 跨表引用 + CSV/Bible 关键值(数据门)
func run(a, data) -> void:
a.true_(data.load_errors.is_empty(), "load_errors 为空(%s)" % str(data.load_errors))
a.eq(data.generators.size(), 12, "12 班底")
a.eq(data.upgrades.size(), 108, "108 升级")
a.eq(data.meta_upgrades.size(), 12, "12 宿慧商品")
a.eq(data.halls.size(), 10, "10 殿")
a.eq(data.palettes.size(), 10, "10 色板")
a.eq(data.bosses.size(), 10, "10 殿王战")
a.eq(data.events.size(), 4, "4 事件")
# 关键数值(Balance Bible v1.0 逐值一致)
a.near(data.generators[11].base_rate, 1073741824.0, 0.5, "gen12 rate = 8^10")
a.near(data.generators[1].payback_seconds(), 120.0, 0.5, "gen2 回本 120s")
a.near(data.generators[5].payback_seconds(), 293.0, 0.5, "gen6 回本 293s")
a.near(data.generators[11].payback_seconds(), 1118.0, 0.5, "gen12 回本 1118s")
a.near(data.hall_by_no(1).gate_merit, 2000.0, 0.5, "H1 门 2K")
a.near(data.hall_by_no(10).gate_merit, 1.5e9, 0.5, "H10 门 1.5B")
a.true_(data.hall_by_no(10).is_prestige_gate, "H10 = prestige 门")
a.near(data.boss_for_hall("hall_01_qinguang").hp_factor, 15.0, 1e-9, "殿王 HP 系数 15([B§5])")
# 敕封诏书(P-12:requires = enthrone_ready)
var edict := data.upgrade("up_gate_tianzi")
a.true_(edict != null, "敕封诏书存在")
a.eq(edict.requires, "enthrone_ready", "诏书 requires = enthrone_ready")
a.eq(edict.cost, 800000000000, "诏书成本 800B")
# 调参数(balance_tuning)
a.near(data.tune_num("tune_souls_div", 0.0), 10000.0, 1e-9, "超度除数 10000")
a.near(data.tune_num("tune_enthrone_suihui_req", 0.0), 60.0, 1e-9, "敕封宿慧门槛 60")
a.near(data.tune_num("tune_enthrone_rebirths", 0.0), 2.0, 1e-9, "敕封转世门槛 2")
a.eq(data.tune_text("tune_restart_curve", ""), "1:0.6:0.4:0.3", "重开曲线文本")
a.near(data.tune_num("tune_wrath_layers", 0.0), 22.0, 1e-9, "冤煞 22 层")
a.near(data.tune_num("tune_click_base", 0.0), 1.0, 1e-9, "点击基础值 1")
# 跨表引用完整性(schema 管单表;这里查跨表)
a.true_(data.check_references().is_empty(), "跨表引用无悬挂(%s)" % str(data.check_references()))
# 每殿恰一场殿王战 + 色板
for h in data.halls:
a.true_(data.boss_for_hall(h.id) != null, "%s 有 boss 行" % h.id)
a.true_(data.palette_for_hall(h.id) != null, "%s 有色板行" % h.id)
+59
View File
@@ -0,0 +1,59 @@
extends RefCounted
# 用例:核心公式(MultiplierSet / PrestigeModel / OfflineModel)
func run(a, _data) -> void:
# ---- MultiplierSet 三乘区 + 软上限 ----
var m := MultiplierSet.new(16.0, 8.0, 64.0)
m.set_click_levels(4, 0.0, 0.01)
a.near(m.click_mult(), 16.0, 1e-9, "点击区 4 级 = ×16")
m.set_click_levels(5, 0.0, 0.01)
a.near(m.click_mult(), 16.0, 1e-9, "点击区 5 级仍钳 ×16")
m.set_gen_eff("gen_01_youhun", 6)
a.near(m.gen_mult("gen_01_youhun"), 7.0, 1e-9, "班底区 6 级 = ×7")
m.set_gen_eff("gen_01_youhun", 99)
a.near(m.gen_mult("gen_01_youhun"), 8.0, 1e-9, "班底区钳 ×8")
m.set_global_add(63.0)
a.near(m.global_mult(), 64.0, 1e-9, "全局区 ×64")
m.set_global_add(100.0)
a.near(m.global_mult(), 64.0, 1e-9, "全局区钳 ×64")
m.set_click_levels(4, 8.0, 0.01)
a.near(m.click_value(1.0, 1000.0), 96.0, 1e-6, "click_value = 1×16 + 1000×8%")
# ---- PrestigeModel ----
a.eq(PrestigeModel.suihui_gain(1.5e9, 1e6, 5.0, 0.0, 0.0), 193, "宿慧 1.5B → 193")
a.eq(PrestigeModel.suihui_gain(1.25e7, 1e6, 5.0, 0.0, 0.0), 17, "宿慧 12.5M → 17")
a.eq(PrestigeModel.suihui_gain(1.5e9, 1e6, 5.0, 0.0, 0.3), 135, "宿慧 30% 削减 → 135")
a.eq(PrestigeModel.suihui_gain(1.5e9, 1e6, 5.0, 0.45, 0.0), 279, "宿慧 m03×3 → 279")
a.eq(PrestigeModel.suihui_gain(0.0, 1e6, 5.0, 0.0, 0.0), 0, "零累计 → 0 宿慧")
a.eq(PrestigeModel.wrath_layer_cost(1, 8.0, 1.8), 8, "怨煞层 1 成本 8")
a.eq(PrestigeModel.wrath_layer_cost(2, 8.0, 1.8), 28, "怨煞层 2 成本 28")
a.eq(PrestigeModel.wrath_layer_cost(22, 8.0, 1.8), 2087, "怨煞层 22 成本 2087")
a.near(PrestigeModel.wrath_penalty(10, 0.03, 0.3, 0.0), 0.3, 1e-9, "冤气削减封顶 30%")
a.near(PrestigeModel.wrath_penalty(10, 0.03, 0.3, 0.15), 0.15, 1e-9, "m07 改 15%")
a.near(PrestigeModel.wrath_penalty(5, 0.03, 0.3, 0.0), 0.15, 1e-9, "5 层 = 15%")
a.eq(PrestigeModel.skip_count(23, 5), 4, "skip 23/5 = 4")
a.eq(PrestigeModel.skip_count(23, 4), 5, "skip 23/4 = 5(m04)")
a.near(PrestigeModel.restart_target(0), 1.0, 1e-9, "重开曲线 R1")
a.near(PrestigeModel.restart_target(3), 0.3, 1e-9, "重开曲线 R4")
a.near(PrestigeModel.restart_target(4), 0.25, 1e-9, "重开曲线 R5 触底")
a.true_(PrestigeModel.enthrone_available(2, 2), "敕封:转世 2 可")
a.true_(not PrestigeModel.enthrone_available(1, 2), "敕封:转世 1 不可")
# ---- OfflineModel 分带 + 72h 封顶 + 时钟回拨 ----
var r1: Dictionary = OfflineModel.settle(3600.0, 10.0, 1.0, 2.0, 1.0, 8.0, 0.7, 24.0, 0.4, 0.1, 72.0)
a.near(r1["effective_hours"], 1.0, 1e-9, "离线 1h = 1h 等效")
a.near(r1["merit"], 36000.0, 1e-6, "离线 1h × 10/s")
var r8: Dictionary = OfflineModel.settle(8.0 * 3600.0, 10.0, 1.0, 2.0, 1.0, 8.0, 0.7, 24.0, 0.4, 0.1, 72.0)
a.near(r8["effective_hours"], 6.2, 1e-9, "离线 8h = 6.2h 等效(P-8)")
a.near(r8["merit"], 223200.0, 1e-6, "离线 8h 收益")
var r100: Dictionary = OfflineModel.settle(100.0 * 3600.0, 10.0, 1.0, 2.0, 1.0, 8.0, 0.7, 24.0, 0.4, 0.1, 72.0)
a.near(r100["effective_hours"], 17.4, 1e-9, "离线 100h → 72h 封顶 = 17.4h 等效")
var rneg: Dictionary = OfflineModel.settle(-5.0, 10.0, 1.0, 2.0, 1.0, 8.0, 0.7, 24.0, 0.4, 0.1, 72.0)
a.near(rneg["merit"], 0.0, 1e-9, "时钟回拨 → 0")
a.near(OfflineModel.ratio_8h_over_1h(2.0, 1.0, 8.0, 0.7, 24.0, 0.4, 0.1), 6.2, 1e-9,
"门 #9 比率 6.2(契约 [1.5,2.5] 预计 FAIL,P-8)")
# ---- BigNumber 红线 ----
a.true_(BigNumber.assert_within(1e13, "test"), "1e13 不过红线")
a.true_(not BigNumber.assert_within(1e15, "test"), "1e15 触红线")
+159
View File
@@ -0,0 +1,159 @@
extends RefCounted
# 用例:EconomyState 状态机(点击/购买/殿门/冤气/prestige/敕封)
func run(a, data) -> void:
_test_first_purchase(a, data)
_test_hall_gate(a, data)
_test_upgrades_and_meta(a, data)
_test_wrath(a, data)
_test_prestige(a, data)
_test_enthrone(a, data)
_test_event_and_offline(a, data)
# ---- 首购循环:15 点击 → 买 gen1 → 产出 0.1/s(GDD demo 验证点)----
func _test_first_purchase(a, data) -> void:
var e := EconomyState.new(data)
a.near(e.merit, 0.0, 1e-9, "初始阴德 0")
a.near(e.output_per_sec(), 0.0, 1e-9, "初始产出 0")
for _i in 15:
e.click()
a.near(e.merit, 15.0, 1e-9, "15 次点击 = 15 阴德")
a.near(e.click_value(), 1.0, 1e-9, "点击价值 = 1")
var r: Dictionary = e.buy_generator(0, 1)
a.true_(bool(r.get("ok", false)), "15 阴德可买游魂×1")
a.eq(e.gen_levels[0], 1, "游魂 1 级")
a.near(e.merit, 0.0, 1e-9, "买后阴德 0")
a.near(e.output_per_sec(), 0.1, 1e-9, "产出 0.1/s")
var r2: Dictionary = e.buy_generator(0, 1)
a.true_(not bool(r2.get("ok", false)), "0 阴德再买失败")
e.merit = 100.0
var r3: Dictionary = e.buy_upgrade("up_click_1")
a.true_(bool(r3.get("ok", false)), "买指诀·倍力 I(50)")
a.near(e.click_value(), 2.0, 1e-9, "点击价值 ×2")
# ---- 殿门单计数器闸门 ----
func _test_hall_gate(a, data) -> void:
var e := EconomyState.new(data)
a.false_(e.generator_unlocked(2), "gen3 初始锁定")
e.merit_run_total = 1999.0
e.tick(0.1)
a.eq(e.halls_passed, 0, "1999 不过 H1")
e.merit_run_total = 2000.0
e.tick(0.1)
a.eq(e.halls_passed, 1, "2000 过 H1(单计数器闸门)")
a.true_(e.upgrade_visible(data.upgrade("up_eff_gen_03_guizu_1")), "H1 后 gen3 效率可见")
a.true_(e.upgrade_visible(data.upgrade("up_gate_guizu")), "H1 后入职文书可见")
a.false_(e.generator_unlocked(2), "过殿但未买文书 → gen3 仍锁")
e.merit = 1000.0
var r: Dictionary = e.buy_upgrade("up_gate_guizu")
a.true_(bool(r.get("ok", false)), "买鬼卒班入职文书(800)")
a.true_(e.generator_unlocked(2), "双闸齐 → gen3 解锁")
var r2: Dictionary = e.buy_generator(2, 1)
a.true_(bool(r2.get("ok", false)), "gen3 可购")
# ---- 升级/宿慧商店 ----
func _test_upgrades_and_meta(a, data) -> void:
var e := EconomyState.new(data)
e.suihui = 10
var r: Dictionary = e.buy_meta("meta_01")
a.true_(bool(r.get("ok", false)), "买枕旧梦(3 宿慧)")
a.eq(e.suihui, 7, "扣 3 宿慧")
a.eq(int(e.meta_ranks.get("meta_01", 0)), 1, "meta_01 阶 1")
var r2: Dictionary = e.buy_meta("meta_01")
a.true_(not bool(r2.get("ok", false)), "meta_01 一阶限购")
e.suihui = 100
var r3: Dictionary = e.buy_meta("meta_03")
a.true_(bool(r3.get("ok", false)), "买宿慧通透(8)")
# 效率升级进班底区
e.merit = 100000.0
e.gen_levels[0] = 10
var r4: Dictionary = e.buy_upgrade("up_eff_gen_01_youhun_1")
a.true_(bool(r4.get("ok", false)), "买游魂操练 I(83)")
a.near(e.gen_mult(0), 2.0, 1e-9, "班底区 ×2(+100%)")
# ---- 冤气线 ----
func _test_wrath(a, data) -> void:
var e := EconomyState.new(data)
e.devour(100.0)
a.near(e.wrath, 100.0, 1e-9, "吞噬 +100 冤气")
a.eq(e.devour_count, 1, "吞噬计数")
a.eq(e.wrath_layer_cost(), 8, "第 1 层成本 8")
var r: Dictionary = e.buy_wrath_layer()
a.true_(bool(r.get("ok", false)), "买第 1 层怨煞")
a.eq(e.wrath_layers, 1, "1 层")
a.near(e.global_mult(), 1.25, 1e-9, "全局 ×1.25(每层 +25%)")
a.near(e.wrath_penalty(), 0.03, 1e-9, "宿慧削减 3%")
e.wrath = 0.0
var r2: Dictionary = e.buy_wrath_layer()
a.true_(not bool(r2.get("ok", false)), "冤气不足买不了")
e.wrath = 1e6
for _i in 21:
e.buy_wrath_layer()
a.eq(e.wrath_layers, 22, "22 层上限")
var r3: Dictionary = e.buy_wrath_layer()
a.true_(not bool(r3.get("ok", false)), "满层后再买失败")
# ---- 轮回转世 ----
func _test_prestige(a, data) -> void:
var e := EconomyState.new(data)
a.false_(e.prestige_available(), "初始不可轮回")
e.merit_run_total = 1.5e9
a.true_(e.prestige_available(), "H10 门达成 → 可轮回")
a.eq(e.suihui_gain(), 193, "1.5B → 193 宿慧")
e.gen_levels[0] = 50
e.merit = 123.0
var r: Dictionary = e.do_prestige()
a.true_(bool(r.get("ok", false)), "执行轮回")
a.eq(e.suihui, 193, "宿慧入账")
a.eq(e.rebirths, 1, "转世 1 次")
a.near(e.merit, 0.0, 1e-9, "阴德清零")
a.eq(e.gen_levels[0], 950, "跳过机制:193/5=38 ×25 = 950 级")
a.eq(e.halls_passed, 0, "殿门进度重置")
a.eq(e.bought.size(), 0, "升级清空")
# 第二轮更快(宿慧全局 +3%)
a.near(e.global_mult(), 1.0 + 193 * 0.03, 1e-9, "宿慧全局加成")
# ---- 阴司敕封 + gen12 愿景档 ----
func _test_enthrone(a, data) -> void:
var e := EconomyState.new(data)
e.rebirths = 2
e.suihui = 100
a.true_(e.enthrone_ready(), "转世 2 + 宿慧 100 → 敕封就绪")
e.merit_run_total = 1.5e9
e.merit = 1e12
var r: Dictionary = e.buy_upgrade("up_gate_tianzi")
a.true_(bool(r.get("ok", false)), "买敕封诏书(800B)")
a.true_(e.enthrone_available(), "受敕可用")
a.false_(e.generator_unlocked(11), "未受敕 → gen12 仍锁(P-12)")
var r2: Dictionary = e.do_enthrone()
a.true_(bool(r2.get("ok", false)), "执行受敕")
a.true_(e.enthroned, "enthroned 旗标")
a.true_(e.generator_unlocked(11), "受敕 → gen12 解锁")
# 全局区 = 1 + 宿慧100×3% + 敕封 25% = 4.25
a.near(e.global_mult(), 4.25, 1e-9, "全局 ×4.25(宿慧+敕封)")
# gen12 产出接入
e.gen_levels[11] = 1
a.near(e.output_per_sec(), 1073741824.0 * 4.25, 1.0, "gen12 1 级产出")
# ---- 事件瞬时区 + 离线倍率 ----
func _test_event_and_offline(a, data) -> void:
var e := EconomyState.new(data)
e.gen_levels[0] = 10
var base_out := e.output_per_sec()
e.set_event_mult(5.0)
a.near(e.output_per_sec(), base_out * 5.0, 1e-9, "事件 ×5 瞬时区")
e.set_event_mult(1.0)
a.near(e.offline_mult(), 1.0, 1e-9, "无长明灯 → ×1")
e.halls_passed = 4 # 离线升级出现条件
e.merit = 1e9
var r: Dictionary = e.buy_upgrade("up_offline_1")
a.true_(bool(r.get("ok", false)), "买长明灯 I")
a.near(e.offline_mult(), 1.15, 1e-9, "离线 ×1.15(P-13 乘性链)")
+19
View File
@@ -0,0 +1,19 @@
extends RefCounted
# 用例:NumberFormat(core/numbers/number_format.gd)
func run(a, _data) -> void:
a.eq(NumberFormat.short(999.0), "999", "short 999")
a.eq(NumberFormat.short(1000.0), "1.00K", "short 1K")
a.eq(NumberFormat.short(1234.0), "1.23K", "short 1234")
a.eq(NumberFormat.short(1.5e9), "1.50B", "short 1.5B")
a.eq(NumberFormat.short(1.5e12), "1.50T", "short 1.5T")
a.eq(NumberFormat.short_int(1500.0), "2K", "short_int 1500 → 2K")
a.eq(NumberFormat.short_int(1400.0), "1K", "short_int 1400 → 1K")
a.eq(NumberFormat.duration(42.0), "42s", "duration 42s")
a.eq(NumberFormat.duration(90.0), "1m 30s", "duration 90s")
a.eq(NumberFormat.duration(3600.0), "1h", "duration 1h")
a.eq(NumberFormat.duration(5100.0), "1h 25m", "duration 1h25m")
a.eq(NumberFormat.minutes(25.0), "25m", "minutes 25")
a.eq(NumberFormat.minutes(180.0), "3h", "minutes 180")
a.eq(NumberFormat.minutes(125.0), "2h 5m", "minutes 125")
+78
View File
@@ -0,0 +1,78 @@
extends SceneTree
# ============================================================================
# test_runner.gd —— core 层无头单测入口(GUT 未装前的自包含方案;W3 换 GUT)
#
# 运行:godot --headless --path . -s res://tests/unit/test_runner.gd
# 退出码:0 = 全过;1 = 有失败;2 = 数据/环境错误。
# 每个用例文件 = tests/unit/cases/*.gd,契约:func run(a) -> void(a = TestAssert)。
# ============================================================================
class TestAssert extends RefCounted:
var failures: Array[String] = []
var checks := 0
func check(cond: bool, msg: String) -> void:
checks += 1
if not cond:
failures.append(msg)
func eq(got: Variant, want: Variant, msg: String) -> void:
checks += 1
if got != want:
failures.append("%s:got %s want %s" % [msg, str(got), str(want)])
func near(got: float, want: float, tol: float, msg: String) -> void:
checks += 1
if absf(float(got) - float(want)) > tol:
failures.append("%s:got %.6g want %.6g(tol %g)" % [msg, got, want, tol])
func true_(cond: bool, msg: String) -> void:
check(cond, msg)
func false_(cond: bool, msg: String) -> void:
check(not cond, msg)
const CASE_FILES: Array[String] = [
"res://tests/unit/cases/test_number_format.gd",
"res://tests/unit/cases/test_economy_formulas.gd",
"res://tests/unit/cases/test_balance_data.gd",
"res://tests/unit/cases/test_economy_state.gd",
]
func _initialize() -> void:
var data := BalanceData.load_from_dir("res://data/build/")
if not data.load_errors.is_empty():
for e in data.load_errors:
printerr("test_runner:数据错误:%s" % e)
quit(2)
return
var total_checks := 0
var total_fail: Array[String] = []
for path in CASE_FILES:
var script: Variant = load(path)
if script == null:
printerr("test_runner:用例缺失 %s" % path)
quit(2)
return
var case_obj: Variant = script.new()
if not (case_obj is RefCounted) or not case_obj.has_method("run"):
printerr("test_runner:用例契约不符 %s" % path)
quit(2)
return
var a := TestAssert.new()
case_obj.run(a, data)
total_checks += a.checks
for f in a.failures:
total_fail.append("%s → %s" % [path.get_file(), f])
print("test_runner:%s:%d 检查,%d 失败" % [path.get_file(), a.checks, a.failures.size()])
if total_fail.is_empty():
print("test_runner:PASS(%d 检查全过)" % total_checks)
quit(0)
return
print("test_runner:FAIL(%d/%d)" % [total_fail.size(), total_checks])
for f in total_fail:
printerr(" ✗ %s" % f)
quit(1)