491 lines
30 KiB
Python
491 lines
30 KiB
Python
#!/usr/bin/env python3
|
|
"""Build the bilingual RG-04 runtime catalog from accepted design authority."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SOURCE = ROOT / "prototype/site/data/catalog.json"
|
|
OUTPUT = ROOT / "runtime/fg4/rg04_catalog.json"
|
|
|
|
|
|
EQUIPMENT_ZH = {
|
|
"EQ-121": ("护卫转专注阀", "首次溢出的护卫转化为专注。", "佩戴者下一节拍无法获得护卫。"),
|
|
"EQ-122": ("药剂瓶架", "消耗药剂会刷新最早进入冷却的治疗装备。", "装备治疗不能生成药剂。"),
|
|
"EQ-123": ("零件回收器", "友方装置被摧毁时,向佩戴者返还 1 点零件。", "若装置持续至完整时长,佩戴者会陷入迟滞。"),
|
|
"EQ-124": ("诗律杯", "消耗诗律时,向速度更慢的相邻单位转交 1 点本地资源。", "佩戴者失去下一次来自该相邻单位的诗律。"),
|
|
"EQ-125": ("锋刃剑鞘", "目标被击败时,未消耗的锋刃会转移给最近目标并施加标记。", "被标记目标同时获得护卫。"),
|
|
"EQ-126": ("定势配重", "遭受强制位移时消耗定势以取消位移并保持目标。", "所有剩余定势都会失去。"),
|
|
"EQ-127": ("动量瓶", "消耗动量时同时刷新最早的位置触发器。", "佩戴者下一节拍无法获得动量。"),
|
|
"EQ-128": ("瞄准分流器", "对最后方目标消耗瞄准时,同时标记最前方目标。", "这次射击不能施加其他状态。"),
|
|
"EQ-129": ("蓄能槽", "被打断的蓄能转入装备,并在下次普通攻击后返还。", "返还蓄能有上限,且佩戴者会陷入迟滞。"),
|
|
"EQ-130": ("羁绊计数牌", "连接断裂时,将 1 点羁绊转为原双方的护卫。", "该连接一节拍内不能重建。"),
|
|
"EQ-131": ("预兆沙漏", "消耗预兆会暂停目标负面状态在下一次蓄力期间的到期。", "该窗口内目标不能再获得其他负面状态。"),
|
|
"EQ-132": ("补给箱钥匙", "消耗补给会立即解除一个相邻装备的冷却。", "同一装备在下一节拍会被锁定。"),
|
|
"EQ-133": ("空槽鼓", "本地资源降至零时,提前下一次普通攻击。", "普通攻击前获得资源会取消提前并造成迟滞。"),
|
|
"EQ-134": ("满槽封印", "本地资源满值时,下一名敌人不能偷取或削减它。", "该敌人行动结束前佩戴者不能消耗资源。"),
|
|
"EQ-135": ("邻位借贷券", "每场一次,向拥有相同资源类型的相邻单位借出 1 点本地资源。", "一节拍内未消耗时,双方各失去 1 点该资源。"),
|
|
"EQ-136": ("混合钱袋", "队伍首次获得三种不同资源时生成 1 点补给。", "完成前重复同类资源会重置计数。"),
|
|
"EQ-137": ("资源制动器", "直接命中不能把本地资源降到 1 以下。", "制动触发时佩戴者会暴露。"),
|
|
"EQ-138": ("溢流嘴", "资源在上限时继续获得,会流向可接收同类资源的相邻单位。", "没有合法接收者时佩戴者会迟滞。"),
|
|
"EQ-139": ("逆向账册", "受伤获得的资源下次优先用于敌方;自身行动获得的资源下次优先用于友方。", "只能作用于错误一侧的职业行动无法消耗该资源。"),
|
|
"EQ-140": ("末枚令牌坠", "消耗最后一点资源时保留至行动结算,使附带效果仍可触发。", "保留资源随后消失且不能返还。"),
|
|
"EQ-141": ("中点净化器", "状态持续过半后净化会移除状态并获得护卫。", "过早净化会把状态改为较短的迟滞。"),
|
|
"EQ-142": ("标记接力针", "标记到期时跳到最近的未标记敌人。", "跳转后的标记只持续 1 节拍。"),
|
|
"EQ-143": ("迟滞钩", "命中迟滞目标时把它向前拉 1 位。", "拉动会移除迟滞。"),
|
|
"EQ-144": ("暴露丝线", "佩戴者护卫破裂时标记破坏者,并使自己暴露。", "暴露结束时标记也会结束。"),
|
|
"EQ-145": ("空白石板", "同一节拍首次获得的正面与负面标签互相抵消。", "佩戴者不会从两者获得本地资源。"),
|
|
"EQ-146": ("久病符", "重复施加同一负面状态会刷新时长,但不叠加强度。", "状态到期后目标获得 1 节拍免疫。"),
|
|
"EQ-147": ("短祝福", "正面标签只持续 1 节拍,但到期附带效果会立即触发。", "该标签不能延长。"),
|
|
"EQ-148": ("状态棱镜", "佩戴者首次获得标签时,在合法情况下把相反符号复制给最近敌人。", "原标签保持不变。"),
|
|
"EQ-149": ("断根刀", "对根系来源的普通攻击会解除佩戴者的移动锁定。", "该次普通攻击不造成伤害或状态。"),
|
|
"EQ-150": ("静音铃舌", "对声音使用者施加迟滞时,同时打断其当前蓄力。", "迟滞时长缩短为 1 节拍。"),
|
|
"EQ-151": ("护卫毒瓶", "佩戴者有护卫时施加的负面状态,1 节拍内不能被净化。", "施加时消耗佩戴者全部护卫。"),
|
|
"EQ-152": ("开放伤口贴", "目标尝试接受治疗时,禁疗标签会揭示并标记其来源。", "这次治疗仍然会被阻止。"),
|
|
"EQ-153": ("到期铃", "最早的标签到期时,提前佩戴者下一次行动。", "主动净化不会触发铃声。"),
|
|
"EQ-154": ("净化镜", "敌方首次净化时,也会移除佩戴者最早的负面状态。", "敌方净化仍会完整生效。"),
|
|
"EQ-155": ("标记路线图", "朝被标记敌人移动会保持标记时长。", "远离目标会移除标记。"),
|
|
"EQ-156": ("状态分割器", "范围行动只对首尾目标施加选定状态。", "中间目标不会获得该状态。"),
|
|
"EQ-157": ("双净化法则", "同一友方第二次被净化时,同时刷新一个装备冷却。", "该友方第一次净化会延迟 1 节拍。"),
|
|
"EQ-158": ("传染隔栏", "阻止状态跨过佩戴者在相邻单位间传播。", "佩戴者会以较短时长承受两次被阻止的传播标签。"),
|
|
"EQ-159": ("真相药杯", "净化隐匿时会揭示目标下一次行动类别。", "目标在揭示后获得护卫。"),
|
|
"EQ-160": ("恒久圣甲虫", "首次完整持续到期的负面状态,会转化为本场对该标签的免疫。", "该免疫也会阻止友方把该标签转为增益。"),
|
|
}
|
|
|
|
|
|
EQUIPMENT_TAGS = {
|
|
121: ["guard_conversion", "focus_timing", "guard_lock"],
|
|
122: ["remedy_spend", "healing_cooldown", "heal_source_limit"],
|
|
123: ["device_salvage", "parts_return", "duration_hinder"],
|
|
124: ["verse_spend", "neighbor_resource", "tempo_debt"],
|
|
125: ["edge_transfer", "nearest_mark", "enemy_guard"],
|
|
126: ["poise_spend", "movement_cancel", "resource_empty"],
|
|
127: ["momentum_spend", "position_refresh", "cadence_lock"],
|
|
128: ["aim_spend", "endpoint_mark", "status_exclusion"],
|
|
129: ["charge_store", "basic_return", "hinder_return"],
|
|
130: ["broken_link", "bond_conversion", "relink_lock"],
|
|
131: ["omen_spend", "expiry_pause", "hindrance_lock"],
|
|
132: ["supply_spend", "adjacent_cooldown", "followup_lock"],
|
|
133: ["empty_resource", "basic_advance", "gain_cancel"],
|
|
134: ["full_resource", "drain_immunity", "spend_lock"],
|
|
135: ["neighbor_loan", "matching_resource", "loan_expiry"],
|
|
136: ["resource_diversity", "supply_creation", "repeat_reset"],
|
|
137: ["resource_floor", "direct_hit", "exposed_cost"],
|
|
138: ["resource_overflow", "neighbor_transfer", "no_target_hinder"],
|
|
139: ["resource_source", "target_inversion", "side_restriction"],
|
|
140: ["final_resource", "rider_enable", "no_refund"],
|
|
141: ["midpoint_cleanse", "guard_reward", "early_hinder"],
|
|
142: ["mark_expiry", "nearest_relay", "one_tick_mark"],
|
|
143: ["hindered_target", "forward_pull", "cleanse_on_pull"],
|
|
144: ["guard_break", "source_mark", "linked_exposed"],
|
|
145: ["opposite_tags", "same_cadence_cancel", "resource_denial"],
|
|
146: ["hindrance_refresh", "no_strength_stack", "expiry_immunity"],
|
|
147: ["short_positive", "expiry_rider", "no_extension"],
|
|
148: ["first_status", "opposite_copy", "source_preserved"],
|
|
149: ["root_source", "movement_cleanse", "zero_damage_basic"],
|
|
150: ["sound_interrupt", "hinder_apply", "short_duration"],
|
|
151: ["guarded_hindrance", "cleanse_lock", "guard_spend"],
|
|
152: ["healing_denial", "source_reveal", "attempt_mark"],
|
|
153: ["oldest_expiry", "action_advance", "manual_cleanse_excluded"],
|
|
154: ["enemy_cleanse", "mirrored_cleanse", "enemy_effect_preserved"],
|
|
155: ["marked_target", "direction_choice", "mark_duration"],
|
|
156: ["area_status", "endpoint_targets", "middle_exclusion"],
|
|
157: ["second_cleanse", "item_refresh", "first_cleanse_delay"],
|
|
158: ["status_spread", "neighbor_barrier", "shortened_backlash"],
|
|
159: ["conceal_cleanse", "action_reveal", "target_guard"],
|
|
160: ["full_duration", "tag_immunity", "conversion_block"],
|
|
}
|
|
|
|
|
|
ARTIFACT_ZH = {
|
|
"AR-023": ("清澈流向", "双方首次施加的负面状态只持续 1 节拍,且不能延长。", "与 AR-024 冲突;中立规则条会标出已消耗的首个标签。"),
|
|
"AR-024": ("深层流向", "双方首次施加的负面状态在持续中点前不能被净化。", "与 AR-023 冲突;状态环会明确显示中点锁。"),
|
|
"AR-025": ("透明屏障", "所有屏障少阻挡一次行动,但阻挡时会揭示来源与下一目标。", "与 AR-026 冲突;界面显示剩余次数和揭示卡。"),
|
|
"AR-026": ("不透明屏障", "屏障多阻挡一次行动;存在期间隐藏拥有者的下一目标。", "与 AR-025 冲突;隐藏目标会明确标为遗物代价。"),
|
|
"AR-027": ("共享节奏", "每个节拍边界,双方最快普通攻击都等待各自较慢的相邻单位。", "与 AR-028 冲突;成对时钟显示受影响行动。"),
|
|
"AR-028": ("破碎节律", "重复同一行动节奏带时,会提前对方最慢单位。", "与 AR-027 冲突;重复计数与受益者会提前显示。"),
|
|
"AR-029": ("落地竞逐", "双方腾空与隐匿窗口缩短 1 节拍,强制位移效果延长 1 节拍。", "与 AR-030 冲突;预览标签显示全部时长变化。"),
|
|
"AR-030": ("掠过竞逐", "双方首次强制位移无视抗性,腾空窗口延长 1 节拍。", "与 AR-029 冲突;首次尝试标记与飞行时钟保持可见。"),
|
|
}
|
|
|
|
|
|
ARTIFACT_TAGS = {
|
|
23: ["first_hindrance", "one_tick_duration", "no_extension"],
|
|
24: ["first_hindrance", "midpoint_lock", "cleanse_delay"],
|
|
25: ["screen_count_down", "source_reveal", "target_reveal"],
|
|
26: ["screen_count_up", "target_hidden", "declared_cost"],
|
|
27: ["cadence_boundary", "fast_waits", "neighbor_sync"],
|
|
28: ["cadence_repeat", "enemy_advance", "recipient_forecast"],
|
|
29: ["airborne_shorter", "conceal_shorter", "movement_longer"],
|
|
30: ["first_movement_open", "airborne_longer", "resistance_disabled"],
|
|
}
|
|
|
|
|
|
ENEMY_ZH = {
|
|
"EN-R4-01": ("页边蛾", "以较低效果为敌方复制最近一次装备触发,并沿用同类合法目标。", "先喂给它无害触发,或在关键触发前击败它。", "出现三种不同触发后,复制恢复完整时序。"),
|
|
"EN-R4-02": ("根系索引员", "给友方位置标记 A/B/C;无论移动与否,攻击都会转向下一个有人占据的标签。", "把护卫移入下一标签,或在循环抵达脆弱区前击败它。", "空标签会让指针一次前进两格。"),
|
|
"EN-R4-03": ("擦除蛞蝓", "移除敌方最新的标记或迟滞,并在来源位置留下墨迹。", "最后施加一个可牺牲标签,或在其待命时改用直接伤害。", "墨迹会缩短该位置之后的标签时长。"),
|
|
"EN-R4-04": ("脚注豺", "在最慢友方行动后立刻行动,并把该友方作为目标。", "保护慢速单位,或提前、取消那次慢行动。", "连续攻击同一目标时获得额外射程。"),
|
|
"EN-R4-05": ("条款甲虫", "预览声明重复或交替;遵守会给甲虫护卫,违背会使其暴露但让行动者迟滞。", "用耐久单位违背,或遵守到终结技就绪。", "每次暴露后条款会切换。"),
|
|
"EN-R4-06": ("叶片计数器", "统计目标切换;第三次切换会把该友方行动改向最前方。", "固定目标,或用灵活的范围行动主动触发。", "计数不会因敌人死亡而清除。"),
|
|
"EN-R4-07": ("引文鸦", "标记每个正面标签的来源;标签到期时若可达便啄击来源。", "由受保护来源施加短标签,或净化乌鸦的来源标记。", "多个到期事件会排成独立啄击队列。"),
|
|
"EN-R4-08": ("装订藤", "连接双方最前方单位;任一方推拉时,另一方在合法情况下镜像移动。", "避免位移,或利用镜像把敌方前排拉出。", "非法镜像会使发起者迟滞。"),
|
|
"EN-R4-09": ("改稿影", "首次落空的行动会被它复制,并作用于己方等价目标。", "避免落空,或故意让恢复、无害行动落空。", "完成复制后,它会进入阵线射程。"),
|
|
"EN-R4-10": ("书架守卫", "保护其正后方敌人;该敌人移动时,守卫换位并保护下一个。", "把受保护目标拉动两格,或攻击守卫、使用绕过。", "每次完成换位都会获得护卫。"),
|
|
"EN-R4-11": ("术语守护者", "声明一个已定义行动标签;只有该标签能打断它,但每次使用也会加速大招。", "保留一次定义标签打断,或用其他标签竞速并承受施法。", "每次被打断后,定义标签都会变化。"),
|
|
"EN-R4-12": ("重写雄鹿", "记录前三次友方行动,然后以相反顺序把其目标规则作为敌方攻击重放。", "预先写入安全目标规则,或在反向重放前调整顺序。", "半血后记录四次行动,但所有槽位仍会显示。"),
|
|
}
|
|
|
|
|
|
ENEMY_TAGS = {
|
|
1: ["copy_equipment", "safe_trigger", "copy_timing"],
|
|
2: ["position_labels", "next_occupied", "cycle_interrupt"],
|
|
3: ["cleanse_newest", "ink_source", "raw_damage"],
|
|
4: ["slowest_followup", "source_guard", "action_cancel"],
|
|
5: ["clause_choice", "obey_guard", "break_expose"],
|
|
6: ["target_change", "third_redirect", "stable_target"],
|
|
7: ["positive_source", "expiry_attack", "source_cleanse"],
|
|
8: ["front_link", "mirror_movement", "illegal_hinder"],
|
|
9: ["fizzle_copy", "safe_seed", "equivalent_target"],
|
|
10: ["rear_protect", "swap_guard", "bypass"],
|
|
11: ["defined_tag", "interrupt_only", "cast_acceleration"],
|
|
12: ["action_record", "reverse_replay", "safe_target_seed"],
|
|
}
|
|
|
|
|
|
BOSS_ROWS = {
|
|
"BO-R4-01": {
|
|
"name": ("The Walking Index", "行走索引"),
|
|
"rule": ("Labels every position and advances a pointer after each action; erase, copy, bind or strike resolves from the current label.", "标记每个位置,并在每次行动后推进指针;当前标签决定擦除、复制、束缚或打击。"),
|
|
"responses": ("Steer action count onto a safe label, or interrupt to freeze the pointer for one action.", "操纵行动次数让指针落到安全标签,或打断并冻结指针一次行动。"),
|
|
"failure": ("An unplanned action crossed the disclosed dangerous label.", "计划外行动越过了已公开的危险标签。"),
|
|
"phase": ("Below half vitality, equipment triggers move the pointer backward.", "半血后,装备触发会让指针反向移动。"),
|
|
"tags": ["action_count", "safe_label", "pointer_interrupt"],
|
|
},
|
|
"BO-R4-02": {
|
|
"name": ("Redaction Hart", "删节雄鹿"),
|
|
"rule": ("Records four allied rules, redacts one target clause from each, then replays them against equivalent targets.", "记录四条友方规则,从每条中涂黑一个目标条款,再对等价目标重放。"),
|
|
"responses": ("Seed rules whose missing clause stays harmless, or reorder and interrupt the most dangerous replay.", "预置删去条款后仍无害的规则,或在重放前重排并打断最危险的一条。"),
|
|
"failure": ("A seeded action became a hostile target rule after redaction.", "预置行动在删节后变成了敌对目标规则。"),
|
|
"phase": ("Below half vitality, six rules are recorded while the replay order remains visible.", "半血后记录六条规则,但重放顺序始终可见。"),
|
|
"tags": ["safe_rule_seed", "replay_order", "danger_interrupt"],
|
|
},
|
|
}
|
|
|
|
|
|
MARKET_NAMES = {
|
|
"CTR-R4-01": [
|
|
("First Margin Copy", "初次页边复制"), ("Root Label Walk", "根系标签道"),
|
|
("Erasure Shelf", "擦除书架"), ("Footnote Turn", "脚注转角"),
|
|
("Clause Stamp", "条款印章"), ("Leaf Count", "叶片计数"),
|
|
("Citation Aisle", "引文长廊"), ("Binding Shelf", "装订书架"),
|
|
("Redraft Hall", "改稿大厅"), ("Warden Stack", "守卫书垛"),
|
|
("Glossary Gate", "术语门"), ("Walking Index", "行走索引"),
|
|
],
|
|
"CTR-R4-02": [
|
|
("Redacted Entry", "删节入口"), ("Four-Rule Draft", "四规则草稿"),
|
|
("Black Clause", "黑条款"), ("Reverse Target", "反向目标"),
|
|
("Ink Antler", "墨角"), ("Preserve Trap", "保持陷阱"),
|
|
("Alternate Replay", "交替重放"), ("Cleanse Decoy", "净化诱饵"),
|
|
("Six-Slot Record", "六槽记录"), ("Dangerous Entry", "危险条目"),
|
|
("Final Redaction", "最终删节"), ("Hart's Ledger", "雄鹿账册"),
|
|
],
|
|
}
|
|
|
|
|
|
CLAUSE_KINDS = ["repeat", "alternate", "preserve"]
|
|
AFFECTED_FIELDS = ["action_tag", "target_rule", "status_source", "equipment_trigger"]
|
|
PRESSURE_FAMILIES = ["copy", "redirect", "cleanse_trap", "clause"]
|
|
PRESSURE_LABEL_ZH = {
|
|
"copy": "复制",
|
|
"redirect": "重定向",
|
|
"cleanse_trap": "净化陷阱",
|
|
"clause": "条款判定",
|
|
}
|
|
PRESSURE_COPY = {
|
|
"copy": (
|
|
"The disclosed source action will be copied at reduced strength onto the same legal target class.",
|
|
"已公开的来源行动会以较低强度复制到同类合法目标。",
|
|
["feed_safe_source", "deny_copy", "copy_window"],
|
|
),
|
|
"redirect": (
|
|
"The disclosed target rule will redirect to the next indexed position when its pointer resolves.",
|
|
"指针结算时,已公开的目标规则会重定向到下一个索引位置。",
|
|
["steer_pointer", "guard_next_index", "redirect_window"],
|
|
),
|
|
"cleanse_trap": (
|
|
"The first cleanse records its source and replays the removed tag on an equivalent target.",
|
|
"首次净化会记录来源,并把被移除标签重放到等价目标。",
|
|
["bait_cleanse", "raw_response", "source_protection"],
|
|
),
|
|
"clause": (
|
|
"The archive judges the declared action field after the visible clause counter completes.",
|
|
"可见条款计数完成后,档案会判定已声明的行动字段。",
|
|
["obey_clause", "break_clause", "counter_timing"],
|
|
),
|
|
}
|
|
|
|
|
|
def localized(en: str, zh: str) -> dict[str, str]:
|
|
return {"en": en, "zh_CN": zh}
|
|
|
|
|
|
def numeric_id(identifier: str) -> int:
|
|
return int(identifier.rsplit("-", 1)[-1])
|
|
|
|
|
|
def fit_tags(raw: str) -> list[str]:
|
|
values = []
|
|
for part in raw.replace("`", "").split(";"):
|
|
value = re.sub(r"[^a-z0-9]+", "_", part.strip().lower()).strip("_")
|
|
values.append(value)
|
|
return values
|
|
|
|
|
|
def source_rows(catalog: dict, family: str, prefix: str, first: int, last: int) -> list[dict]:
|
|
return [
|
|
row for row in catalog[family]
|
|
if row["id"].startswith(prefix) and first <= numeric_id(row["id"]) <= last
|
|
]
|
|
|
|
|
|
def equipment_rows(catalog: dict) -> list[dict]:
|
|
rows = []
|
|
for source in source_rows(catalog, "equipment", "EQ-", 121, 160):
|
|
identifier = source["id"]
|
|
name_zh, effect_zh, tradeoff_zh = EQUIPMENT_ZH[identifier]
|
|
rows.append({
|
|
"id": identifier,
|
|
"name": localized(source["name"], name_zh),
|
|
"fit": fit_tags(source["fit"]),
|
|
"class": source["class"],
|
|
"effect": localized(source["effect"], effect_zh),
|
|
"tradeoff": localized(source["tradeoff"], tradeoff_zh),
|
|
"preview": localized(
|
|
f"{source['effect']} Plan around this cost: {source['tradeoff']}",
|
|
f"{effect_zh}需要同时承担:{tradeoff_zh}",
|
|
),
|
|
"response_tags": EQUIPMENT_TAGS[numeric_id(identifier)],
|
|
})
|
|
return rows
|
|
|
|
|
|
def artifact_rows(catalog: dict) -> list[dict]:
|
|
conflicts = {23: 24, 24: 23, 25: 26, 26: 25, 27: 28, 28: 27, 29: 30, 30: 29}
|
|
rows = []
|
|
for source in source_rows(catalog, "artifacts", "AR-", 23, 30):
|
|
identifier = source["id"]
|
|
value = numeric_id(identifier)
|
|
name_zh, rule_zh, tradeoff_zh = ARTIFACT_ZH[identifier]
|
|
rows.append({
|
|
"id": identifier,
|
|
"name": localized(source["name"], name_zh),
|
|
"rule": localized(source["rule"], rule_zh),
|
|
"tradeoff": localized(source["tradeoff"], tradeoff_zh),
|
|
"conflict_id": f"AR-{conflicts[value]:03d}",
|
|
"preview": localized(
|
|
f"Apply this run law symmetrically: {source['rule']} Cost: {source['tradeoff']}",
|
|
f"该规则对双方对称生效:{rule_zh}代价:{tradeoff_zh}",
|
|
),
|
|
"response_tags": ARTIFACT_TAGS[value],
|
|
})
|
|
return rows
|
|
|
|
|
|
def enemy_rows(catalog: dict) -> list[dict]:
|
|
rows = []
|
|
for source in source_rows(catalog, "enemies", "EN-R4-", 1, 12):
|
|
identifier = source["id"]
|
|
name_zh, rule_zh, responses_zh, escalation_zh = ENEMY_ZH[identifier]
|
|
escalation_en = source["production"].split(" P/A/V/S", 1)[0]
|
|
rows.append({
|
|
"id": identifier,
|
|
"name": localized(source["name"], name_zh),
|
|
"class": source["class"],
|
|
"rule": localized(source["rule"], rule_zh),
|
|
"responses": localized(source["responses"], responses_zh),
|
|
"escalation": localized(escalation_en, escalation_zh),
|
|
"preview": localized(
|
|
f"Rule: {source['rule']} Responses: {source['responses']}",
|
|
f"规则:{rule_zh}应对:{responses_zh}",
|
|
),
|
|
"response_tags": ENEMY_TAGS[numeric_id(identifier)],
|
|
})
|
|
return rows
|
|
|
|
|
|
def boss_rows() -> list[dict]:
|
|
rows = []
|
|
for identifier, source in BOSS_ROWS.items():
|
|
rows.append({
|
|
"id": identifier,
|
|
"name": localized(*source["name"]),
|
|
"rule": localized(*source["rule"]),
|
|
"responses": localized(*source["responses"]),
|
|
"failure": localized(*source["failure"]),
|
|
"phase_change": localized(*source["phase"]),
|
|
"executable": True,
|
|
"preview": localized(
|
|
f"Inspect the disclosed rule, response timing and failure boundary before the fixed Muster.",
|
|
"进入固定集结前,查看已公开的规则、应对时机与失败边界。",
|
|
),
|
|
"response_tags": source["tags"],
|
|
})
|
|
return rows
|
|
|
|
|
|
def clause(contract_index: int, market_index: int) -> dict:
|
|
kind = CLAUSE_KINDS[(market_index + contract_index - 2) % len(CLAUSE_KINDS)]
|
|
field = AFFECTED_FIELDS[(market_index + 2 * contract_index - 3) % len(AFFECTED_FIELDS)]
|
|
kind_zh = {"repeat": "重复", "alternate": "交替", "preserve": "保持"}[kind]
|
|
field_zh = {
|
|
"action_tag": "行动标签", "target_rule": "目标规则",
|
|
"status_source": "状态来源", "equipment_trigger": "装备触发",
|
|
}[field]
|
|
field_en = field.replace("_", " ")
|
|
if kind == "repeat":
|
|
obey_en = f"Repeat the disclosed {field_en} twice to expose the archive source."
|
|
break_en = f"Change {field_en} before the second use to expose the judging enemy instead."
|
|
obey_zh = f"连续两次使用已公开的{field_zh},以暴露档案来源。"
|
|
break_zh = f"第二次前更换{field_zh},改为暴露判定敌人。"
|
|
elif kind == "alternate":
|
|
obey_en = f"Alternate the disclosed {field_en} until the counter resolves to gain a safe target window."
|
|
break_en = f"Repeat {field_en} deliberately to shorten the enemy copy window at a Guard cost."
|
|
obey_zh = f"交替使用已公开的{field_zh}直到计数结算,获得安全目标窗口。"
|
|
break_zh = f"主动重复{field_zh},以护卫代价缩短敌方复制窗口。"
|
|
else:
|
|
obey_en = f"Preserve the disclosed {field_en} until resolution to lock the current legal target."
|
|
break_en = f"Change {field_en} once to redirect the archive effect onto the next indexed target."
|
|
obey_zh = f"保持已公开的{field_zh}直至结算,锁定当前合法目标。"
|
|
break_zh = f"更换一次{field_zh},把档案效果重定向到下一索引目标。"
|
|
return {
|
|
"id": f"CL-R4-C{contract_index:02d}-M{market_index:02d}",
|
|
"kind": kind,
|
|
"affected_field": field,
|
|
"obey_result": localized(obey_en, obey_zh),
|
|
"break_result": localized(break_en, break_zh),
|
|
"counter_limit": 2 + ((market_index - 1) % 3),
|
|
}
|
|
|
|
|
|
def enemy_sets(market_index: int, contract_index: int) -> list[list[str]]:
|
|
start = (market_index * 2 + contract_index * 3) % 12
|
|
ids = [f"EN-R4-{((start + offset) % 12) + 1:02d}" for offset in range(8)]
|
|
return [ids[0:3], ids[2:5], [ids[1], ids[4], ids[6], ids[7]]]
|
|
|
|
|
|
def market_rows(contract_id: str) -> list[dict]:
|
|
contract_index = 1 if contract_id == "CTR-R4-01" else 2
|
|
rows = []
|
|
for market_index, (name_en, name_zh) in enumerate(MARKET_NAMES[contract_id], start=1):
|
|
pressure_family = PRESSURE_FAMILIES[(market_index + contract_index - 2) % 4]
|
|
pressure_en, pressure_zh, family_tags = PRESSURE_COPY[pressure_family]
|
|
pressure_label_zh = PRESSURE_LABEL_ZH[pressure_family]
|
|
clause_row = clause(contract_index, market_index)
|
|
clause_kind = clause_row["kind"]
|
|
clause_zh = {"repeat": "重复", "alternate": "交替", "preserve": "保持"}[clause_kind]
|
|
item_first = 121 + ((market_index - 1 + (contract_index - 1) * 17) % 40)
|
|
item_second = 121 + ((market_index + 12 + contract_index * 3) % 40)
|
|
if item_second == item_first:
|
|
item_second = 121 + ((item_second - 120) % 40)
|
|
copy_id = f"CP-R4-C{contract_index:02d}-M{market_index:02d}"
|
|
rows.append({
|
|
"market_index": market_index,
|
|
"round_index": market_index,
|
|
"line_capacity": 10,
|
|
"offer_level": 1 + (market_index - 1) // 3,
|
|
"name": localized(name_en, name_zh),
|
|
"pressure": localized(pressure_en, pressure_zh),
|
|
"failure": localized(
|
|
f"The committed response ignored the disclosed {clause_kind} clause and {pressure_family} resolution.",
|
|
f"已提交应对忽略了公开的{clause_zh}条款与{pressure_label_zh}结算。",
|
|
),
|
|
"preview": localized(
|
|
f"Obey the {clause_kind} clause for its safe result, or break it deliberately while answering {pressure_family} pressure.",
|
|
f"遵守{clause_zh}条款取得安全结果,或在处理{pressure_label_zh}压力时主动违背。",
|
|
),
|
|
"milestone": localized(
|
|
f"Contract {contract_index} archive decision {market_index} locks one disclosed clause and copy source.",
|
|
f"第 {contract_index} 份契约第 {market_index} 次档案决策会锁定一条公开条款与复制来源。",
|
|
),
|
|
"response_tags": [*family_tags, f"{clause_kind}_clause"],
|
|
"required_equipment_ids": [f"EQ-{item_first:03d}", f"EQ-{item_second:03d}"],
|
|
"archive_clause": clause_row,
|
|
"copy_disclosure": {
|
|
"id": copy_id,
|
|
"pressure_family": pressure_family,
|
|
"source_rule": AFFECTED_FIELDS[(market_index + contract_index) % 4],
|
|
"target_rule": ["foremost", "rearmost", "same_index", "next_occupied"][(market_index - 1) % 4],
|
|
"warning_ticks": 14 + (market_index % 4) * 2,
|
|
"resolve_tick": 42 + market_index + contract_index * 3,
|
|
"fixed_tick_ms": 100,
|
|
},
|
|
"enemy_sets": enemy_sets(market_index, contract_index),
|
|
})
|
|
return rows
|
|
|
|
|
|
def main() -> int:
|
|
source_bytes = SOURCE.read_bytes()
|
|
catalog = json.loads(source_bytes)
|
|
output = {
|
|
"schema": 1,
|
|
"contract_revision": "fg4-midgame-r1",
|
|
"source": {
|
|
"prototype_catalog_sha256": hashlib.sha256(source_bytes).hexdigest(),
|
|
"region_id": "RG-04",
|
|
},
|
|
"region4": {
|
|
"id": "RG-04",
|
|
"name": localized("Quillroot Archive", "根羽档案馆"),
|
|
"preview": localized(
|
|
"Restore public navigation copies by inspecting copied actions, redirects, cleanse traps and archive clauses.",
|
|
"查看复制行动、目标重定向、净化陷阱与档案条款,修复公共导航副本。",
|
|
),
|
|
"response_tags": ["copy_inspection", "target_redirect", "archive_clause"],
|
|
},
|
|
"contract3": {
|
|
"id": "CTR-R4-01", "region_id": "RG-04", "market_start": 1,
|
|
"market_end": 12, "boss_market": 12, "boss_id": "BO-R4-01",
|
|
"name": localized("First Quillroot Contract", "根羽第一契约"),
|
|
"preview": localized(
|
|
"Carry the RG-03 Company through twelve disclosed copy-and-clause Rounds before The Walking Index.",
|
|
"沿用 RG-03 队伍,完成十二轮公开复制与条款挑战,抵达“行走索引”。",
|
|
),
|
|
"response_tags": ["same_company", "clause_inspection", "walking_index"],
|
|
},
|
|
"contract4": {
|
|
"id": "CTR-R4-02", "region_id": "RG-04", "market_start": 1,
|
|
"market_end": 12, "boss_market": 12, "boss_id": "BO-R4-02",
|
|
"name": localized("Second Quillroot Contract", "根羽第二契约"),
|
|
"preview": localized(
|
|
"Carry the surviving Company through twelve redaction-and-replay Rounds before Redaction Hart.",
|
|
"沿用存活队伍,完成十二轮删节与重放挑战,抵达“删节雄鹿”。",
|
|
),
|
|
"response_tags": ["same_run_company", "safe_rule_seed", "redaction_replay"],
|
|
},
|
|
"equipment": equipment_rows(catalog),
|
|
"artifacts": artifact_rows(catalog),
|
|
"enemies": enemy_rows(catalog),
|
|
"bosses": boss_rows(),
|
|
"rg04_markets": market_rows("CTR-R4-01"),
|
|
"rg04_contract2_markets": market_rows("CTR-R4-02"),
|
|
}
|
|
OUTPUT.write_text(json.dumps(output, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
print(
|
|
"ABG_FG4_RG04_CATALOG_BUILD_OK "
|
|
f"equipment={len(output['equipment'])} artifacts={len(output['artifacts'])} "
|
|
f"enemies={len(output['enemies'])} bosses={len(output['bosses'])} "
|
|
f"markets={len(output['rg04_markets']) + len(output['rg04_contract2_markets'])}"
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|