From b3fa611c8e8071abbb41dacf5debaec3af26ad5b Mon Sep 17 00:00:00 2001 From: wusumac <736139669@qq.com> Date: Tue, 15 Sep 2026 05:53:22 +0800 Subject: [PATCH] feat(m79-a): region 2 content, staged catalogue, localisation authorities and region banding --- docs/EVIDENCE_LEDGER.md | 1 + docs/plans/M79_REGION_CONTENT.md | 18 +- runtime/p10_0/catalog.json | 4591 ++++++++++++------ runtime/p10_0/content_registry.gd | 82 +- runtime/p10_0/offer_generator.gd | 28 +- runtime/tests/p10_0/content_registry_test.gd | 33 +- tools/content/build_catalog_from_contract.py | 214 + tools/content/content_locale_zh.json | 1678 +++++++ 8 files changed, 5105 insertions(+), 1540 deletions(-) create mode 100644 tools/content/build_catalog_from_contract.py create mode 100644 tools/content/content_locale_zh.json diff --git a/docs/EVIDENCE_LEDGER.md b/docs/EVIDENCE_LEDGER.md index 7236fcce..152dfc27 100644 --- a/docs/EVIDENCE_LEDGER.md +++ b/docs/EVIDENCE_LEDGER.md @@ -12,6 +12,7 @@ | 2026-09-13 | M60 可玩性真相(W1) | `e1cad068`/`dac5de40` | `m60-playthrough` `20260913T145904Z-38281` | runtime_verified | 34 步 0 阻断;视频与逐步截图 | | 2026-09-13 | M61 设计/密度(W2) | `310d2620` | `m61-five-profile` `20260913T162625Z-2172` | simulation_verified | decisions=330, max_gap=31.3s, 反支配/存档/可达通过 | | 2026-09-13 | M62 组件统一(W3) | `2e5540ed` | `m62-ink-baselines` `20260913T164335Z-38711` | runtime_verified | 组件库+矩阵+探针接入;基线 Guild 0 / Contract 1 / Readiness+Result 6 / Market 严格 0 | +| 2026-09-14 | M79-A 区域 2 内容落地(数据+本地化+区域带) | `feat(m79-a)` | runtime_verified | 目录 80 装备/15 遗物/24 敌人/4 Boss/2 区域/4 合同 + 201 行中文名与标签;市场与战斗按区域带抽取 | | 2026-09-14 | M78 战斗演员立绘(替换头像贴纸,Owner 指出项) | `feat(m78)` | runtime_verified | 52 张(12 职业 + 12 敌人 + 2 Boss × 待机/出手);姿态随节拍切换 | | 2026-09-14 | M77-D 战斗字幕/无障碍/双档墨迹门 | `feat(m77-d)` `1fe13e7b` | `m77-battle-polish` `20260914T174012Z-45680` | runtime_verified | captions+detail 生效;color_cues 单墨色模式;战斗屏 1.0/1.3 双档 0 违规;立绘高度带 | | 2026-09-14 | M77-C 战斗音频(BAT 60 + 区域 1 战斗/BOSS 音乐) | `feat(m77-c)` `92e7cfde` | `m77-battle-audio` `20260914T162304Z-86257` | runtime_verified(待听感)| 清单达合同全量 204;battle/boss 音乐族;节拍→BAT 音效接线 | diff --git a/docs/plans/M79_REGION_CONTENT.md b/docs/plans/M79_REGION_CONTENT.md index f4128e06..69df88c3 100644 --- a/docs/plans/M79_REGION_CONTENT.md +++ b/docs/plans/M79_REGION_CONTENT.md @@ -17,7 +17,7 @@ ## Ordered steps -1. **M79-A(IN_PROGRESS)区域 2**:EQ-041..080、EN-R2-01..12、BO-R2-01/02、AR-009..015、 +1. **M79-A(数据+本地化 COMPLETE;立绘/音乐/可达性 PENDING)区域 2**:EQ-041..080、EN-R2-01..12、BO-R2-01/02、AR-009..015、 CTR-R2-01/02 的中文名 + 语义标签;注册表计数更新(80/24/4/15/2/4);区域带接线(战斗/装备按区域抽); 区域 2 敌人立绘(12×2)+ Boss(2×2);区域 2 战斗/BOSS 音乐;全流程门。 2. **M79-B..G 区域 3..8**:同样管线逐区域推进(每区域一片,含立绘/音乐/门)。 @@ -30,6 +30,18 @@ - 合同行(CTR-*)为派生(区域矩阵 + RG-01 基线),已在目录标记 `derived`,待 Owner/设计复核。 - 像素资产不复活;美术沿用已验收风格族。 +## M79-A 产出(2026-09-14,数据与本地化部分) + +- 抽取管线 `tools/content/build_catalog_from_contract.py`:按 `--through-region N` 从设计合同重建目录, + 行内文(装备/遗物/敌人/Boss/区域)全部来自合同;`tools/content/content_locale_zh.json` 提供每行的 + 中文名 + 语义标签(缺失即构建报错,不含占位)。 +- 合同行(CTR-*)按区域派生(2/区域 = 16),标记 `derived` 待复核。 +- 注册表改为**分阶段**:`CONTENT_STAGE_REGIONS = 2`(80 装备 / 15 遗物 / 24 敌人 / 4 Boss / 2 区域 / 4 合同; + 201 行本地化齐备);收口时改为 8(320/60/100/16/8/16)。 +- 区域带接线:市场装备只从本区域 40 件里抽;战斗报价只从本区域敌人里抽(按 `region` 字段过滤)。 +- 门:`ABG_P10_0_CONTENT_REGISTRY_OK professions=36 ... equipment=80 artifacts=15 enemies=24 bosses=4 contracts=4 localized=201` + (marker 现在打印真实计数);音频服务门与 34 步全流程不回归。 + ## Acceptance checks - 注册表测试通过:计数、连续 ID、name_zh、execution_tags、无填充词(含误报例外登记) @@ -42,3 +54,7 @@ | --- | --- | --- | | `M79-I01` | OPEN | 合同行(16 条)的 round_count/first_clear_renown 为派生值,待复核 | | `M79-I02` | OPEN | 填充词检查对 `copy of` 有误报(EQ-248、BO-R3-02 是机制描述),需登记例外或改写措辞 | +| `M79-I03` | OPEN(需 Owner 拍板范围)| 区域 2+ 内容目前在**运行时不可达**:合同循环只到首份合同(`contract_ordinal != 0` 即拒绝), +第二次合同/区域推进属"第二周目"范围(`M31-I03` 曾判定超出首个 MVP 停点)。按区域补内容要真正可玩,需要一步 +"合同推进/区域前进"(含区域 Boss Muster、区域音乐与立绘切换)。建议作为 M79 的下一步。 | +| `M79-I04` | OPEN | 区域 2 敌人/Boss 立绘(12×2 + 2×2 = 28 张)与区域战斗/BOSS 音乐(各 1 套)尚未生成 | diff --git a/runtime/p10_0/catalog.json b/runtime/p10_0/catalog.json index 7298c305..9d8e52bd 100644 --- a/runtime/p10_0/catalog.json +++ b/runtime/p10_0/catalog.json @@ -1,1505 +1,3104 @@ { - "schema": 1, - "source": { - "path": "prototype/site/data/catalog.json", - "sha256": "fb99bcd75ca6308091fa56b45f8da551276a496a1f96acbb44225efe491fdfef", - "authority_revision": "phase8-p8-3r2-contact-rear-actions-replacement" - }, - "names_zh": { - "PF-B01": "护心卫士", - "PF-B02": "锋刃手", - "PF-B03": "长枪使", - "PF-B04": "逐风行者", - "PF-B05": "远弦射手", - "PF-B06": "裂隙法师", - "PF-B07": "仁心医者", - "PF-B08": "结契师", - "PF-B09": "机关匠", - "PF-B10": "暮兆术士", - "PF-B11": "律钟师", - "PF-B12": "辎重官", - "PF-A01": "门垒卫", - "PF-A02": "冲城卫", - "PF-A03": "终光刃", - "PF-A04": "回锋剑士", - "PF-A05": "钩阵统领", - "PF-A06": "日耀枪骑", - "PF-A07": "传步刺客", - "PF-A08": "逆风刃", - "PF-A09": "定穴射手", - "PF-A10": "箭雨使", - "PF-A11": "风暴算师", - "PF-A12": "空星术士", - "PF-A13": "战地医师", - "PF-A14": "延疗师", - "PF-A15": "三联契师", - "PF-A16": "担契者", - "PF-A17": "哨械师", - "PF-A18": "陷阵工匠", - "PF-A19": "苍白先知", - "PF-A20": "苦兆术士", - "PF-A21": "行军钟师", - "PF-A22": "寂音师", - "PF-A23": "觅迹补给官", - "PF-A24": "阵线搬运师", - "TR-001": "先行一步", - "TR-002": "耐心起手", - "TR-003": "窄阵护卫", - "TR-004": "长久记忆", - "TR-005": "空闲之手", - "TR-006": "开放步法", - "TR-007": "深口袋", - "TR-008": "静默双手", - "TR-009": "配重步", - "TR-010": "共饮药杯", - "TR-011": "醒目目标", - "TR-012": "迟步补位", - "TR-013": "节俭节拍", - "TR-014": "厚重袖口", - "TR-015": "远听者", - "TR-016": "灰烬胃口", - "TR-017": "一丝不苟", - "TR-018": "薄血", - "TR-019": "后阵守望", - "TR-020": "器具誓约", - "TR-021": "松散阵形", - "TR-022": "石刻计数", - "TR-023": "借来节奏", - "TR-024": "净断", - "TR-025": "风险账本", - "TR-026": "空隙射程", - "TR-027": "定声", - "TR-028": "二次草拟", - "TR-029": "摊开手掌", - "TR-030": "反转带扣", - "TR-031": "风暴胆识", - "TR-032": "温和收尾", - "TR-033": "分心并顾", - "TR-034": "久经风霜", - "TR-035": "空剑鞘", - "TR-036": "最后见证", - "EQ-001": "退步靴", - "EQ-002": "钉前护胫", - "EQ-003": "传步腰带", - "EQ-004": "寻隙凉鞋", - "EQ-005": "铅坠束带", - "EQ-006": "后钩披风", - "EQ-007": "转轴脚环", - "EQ-008": "静路绑带", - "EQ-009": "救援环扣", - "EQ-010": "前位主张", - "EQ-011": "邻位带扣", - "EQ-012": "开阵系带", - "EQ-013": "交步徽记", - "EQ-014": "静默后甲", - "EQ-015": "接力马刺", - "EQ-016": "空位铃", - "EQ-017": "撑钩护腕", - "EQ-018": "远行缎带", - "EQ-019": "定步石", - "EQ-020": "守线标签", - "EQ-021": "威胁镜片", - "EQ-022": "前标粉笔", - "EQ-023": "后阵呼哨", - "EQ-024": "仁慈刻度盘", - "EQ-025": "分选硬币", - "EQ-026": "召唤刺针", - "EQ-027": "伤势测带", - "EQ-028": "破盾缝锥", - "EQ-029": "疾影墨水", - "EQ-030": "末目标绳结", - "EQ-031": "护卫标签", - "EQ-032": "显形粉末", - "EQ-033": "对面镜盾", - "EQ-034": "低潮量规", - "EQ-035": "开侧弦线", - "EQ-036": "双重准星", - "EQ-037": "打断账册", - "EQ-038": "旧伤罗盘", - "EQ-039": "崩解瞄具", - "EQ-040": "破选别针", - "AR-001": "开放账册", - "AR-002": "巧匠橱窗", - "AR-003": "共同目标", - "AR-004": "窄域搜寻", - "AR-005": "二次面谈", - "AR-006": "新生资质", - "AR-007": "满员长凳", - "AR-008": "精简队伍", - "EN-R1-01": "晶板啮兽", - "EN-R1-02": "疾风雀", - "EN-R1-03": "台地冲羊", - "EN-R1-04": "钟茎食草兽", - "EN-R1-05": "晶背兽", - "EN-R1-06": "原野潜猎者", - "EN-R1-07": "鸣哨飞蠓", - "EN-R1-08": "玻根幼芽", - "EN-R1-09": "横风书记", - "EN-R1-10": "崖沿跃客", - "EN-R1-11": "狂风执令官", - "EN-R1-12": "棱晶角兽", - "BO-R1-01": "千镜角鹿", - "BO-R1-02": "风织机", - "RG-01": "风镜原野", - "CTR-R1-01": "风镜初渡", - "CTR-R1-02": "风织机通路" - }, - "execution_tags": { - "PF-B01": ["front_hold", "guard", "intercept", "push_resist"], - "PF-B02": ["single_pressure", "edge", "execute", "overflow_convert"], - "PF-B03": ["reach_control", "poise", "pierce", "move_cost"], - "PF-B04": ["reorder", "momentum", "swap", "push_protect"], - "PF-B05": ["projectile", "aim", "rear_target", "line_stability"], - "PF-B06": ["area", "charge", "echo", "interrupt_cost"], - "PF-B07": ["restore", "remedy", "cleanse", "no_overheal_gain"], - "PF-B08": ["enable", "bond", "neighbor_link", "tag_variety"], - "PF-B09": ["construct", "parts", "device", "salvage_on_move"], - "PF-B10": ["impair", "omen", "hinder", "expiry_extend"], - "PF-B11": ["tempo", "verse", "ally_advance", "enemy_delay"], - "PF-B12": ["supply", "item_refresh", "retreat_step", "target_rotation"], - "PF-A01": ["front_hold", "directional_intercept", "guard_refund"], - "PF-A02": ["front_hold", "guard_spend", "push", "follow_step"], - "PF-A03": ["single_pressure", "edge_wager", "execute", "exposed_cost"], - "PF-A04": ["single_pressure", "edge_reserve", "block", "return_cut"], - "PF-A05": ["reach_control", "poise_spend", "pull", "enemy_reorder"], - "PF-A06": ["reach_control", "poise_spend", "line_pierce", "declining_damage"], - "PF-A07": ["reorder", "momentum_spend", "swap", "ally_advance"], - "PF-A08": ["reorder", "sprint", "multi_target", "exposed_cost"], - "PF-A09": ["projectile", "aim_spend", "movement_delay", "interrupt"], - "PF-A10": ["projectile", "aim_spend", "three_targets", "no_repeat"], - "PF-A11": ["area", "charge", "cadence_chain", "declining_damage"], - "PF-A12": ["area", "charge_preserve", "self_hinder", "focused_release"], - "PF-A13": ["restore", "remedy", "heal", "rear_swap"], - "PF-A14": ["restore", "remedy_spend", "delayed_heal", "damage_bonus"], - "PF-A15": ["enable", "bond", "three_link", "same_tag_fray"], - "PF-A16": ["enable", "bond_gain", "cost_transfer", "self_burden"], - "PF-A17": ["construct", "parts_spend", "rear_device", "repeat_target"], - "PF-A18": ["construct", "parts_spend", "position_trap", "forced_move_trigger"], - "PF-A19": ["impair", "omen", "linked_expiry", "cleanse_risk"], - "PF-A20": ["impair", "consume_hindrance", "burst", "expose"], - "PF-A21": ["tempo", "verse_spend", "neighbor_sync", "slowest_timing"], - "PF-A22": ["tempo", "verse_spend", "sound_interrupt", "self_silence"], - "PF-A23": ["supply", "enemy_sample", "shop_counter", "future_value"], - "PF-A24": ["supply", "item_trigger_transfer", "neighbor_only", "item_stays"], - "TR-001": ["position_trigger", "reach_bonus", "foremost_filter_cost"], - "TR-002": ["action_count", "resource_gain", "opening_delay"], - "TR-003": ["neighbor_count", "free_intercept", "guard_transfer_lock"], - "TR-004": ["repeat_target", "duration_bonus", "retarget_delay"], - "TR-005": ["swap", "carry_footprint", "object_action_loss"], - "TR-006": ["forced_move", "counterattack", "self_exposed"], - "TR-007": ["consumable", "duplicate_target", "cooldown_cost"], - "TR-008": ["rear_cover", "sound_immunity", "resource_lock"], - "TR-009": ["pull_resist", "movement_transfer", "failure_hinder"], - "TR-010": ["healing_received", "neighbor_splash", "self_heal_cost"], - "TR-011": ["target_priority", "survive_hit", "focus_gain"], - "TR-012": ["ally_fallen", "delayed_collapse", "action_lock"], - "TR-013": ["basic_count", "item_cooldown", "resource_reset"], - "TR-014": ["interrupt_convert", "delay", "slower_resolve"], - "TR-015": ["enable_range", "distant_receive", "adjacent_lock"], - "TR-016": ["hindered_kill", "resource_restore", "plain_kill_cost"], - "TR-017": ["reserved_target", "reach_validate", "forced_fizzle"], - "TR-018": ["area_damage", "action_advance", "single_heal_cost"], - "TR-019": ["rear_intruder", "auto_mark", "forward_reach_cost"], - "TR-020": ["item_priority", "first_trigger", "technique_wait"], - "TR-021": ["swap_role", "resource_gain", "repeat_partner_hinder"], - "TR-022": ["same_attacker_count", "guard_gain", "mark_clear"], - "TR-023": ["action_advance", "cadence_copy", "repeat_advance_lock"], - "TR-024": ["cleanse", "positive_tag_cost", "resource_gain"], - "TR-025": ["survive_exposed", "shop_discount", "clear_on_safe_end"], - "TR-026": ["summon_pass", "basic_reach", "summon_no_damage"], - "TR-027": ["stationary", "sound_uninterruptible", "move_silence"], - "TR-028": ["self_hindrance", "short_replace", "second_extend"], - "TR-029": ["resource_share", "extended_range", "adjacent_exclusion"], - "TR-030": ["overpush", "center_step", "positive_tag_drop"], - "TR-031": ["enemy_windup", "focus_gain", "interrupt_reset"], - "TR-032": ["overkill", "remedy_convert", "execute_lock"], - "TR-033": ["equipment_mark", "two_marks", "profession_mark_cost"], - "TR-034": ["region_hazard", "first_immunity", "second_extend"], - "TR-035": ["empty_armament", "alternate_target", "equip_cost"], - "TR-036": ["lone_band", "forecast", "reach_bonus", "concealment_lock"], - "EQ-001": ["first_direct_hit", "rear_step", "next_basic_delay"], - "EQ-002": ["first_push", "push_resist", "post_basic_hinder"], - "EQ-003": ["ally_swap", "ally_advance", "wearer_delay"], - "EQ-004": ["vacated_position", "forward_step", "guard_loss"], - "EQ-005": ["forced_move", "movement_reduce", "pace_cost"], - "EQ-006": ["rear_target", "front_pull", "next_action_delay"], - "EQ-007": ["position_change", "trigger_reset", "cooldown_cost"], - "EQ-008": ["stationary", "poise_gain", "move_reset"], - "EQ-009": ["ally_low_hp", "rescue_step", "self_delay"], - "EQ-010": ["front_entry", "target_priority", "guard_cost"], - "EQ-011": ["neighbor_hit", "guard_share", "pace_cost"], - "EQ-012": ["open_line", "reach_bonus", "guard_lock"], - "EQ-013": ["cross_swap", "action_advance", "self_hinder"], - "EQ-014": ["rear_position", "projectile_resist", "move_disable"], - "EQ-015": ["wearer_swap", "position_trigger_refresh", "partner_lock"], - "EQ-016": ["empty_position", "cadence_advance", "position_fill_reset"], - "EQ-017": ["guarded", "pull_resist", "guard_break_cost"], - "EQ-018": ["three_positions", "item_refresh", "repeat_reset"], - "EQ-019": ["stationary_count", "guard_gain", "forced_move_reset"], - "EQ-020": ["neighbor_move", "safe_step", "item_pause"], - "EQ-021": ["threat_preview", "target_reveal", "damage_cost"], - "EQ-022": ["foremost_mark", "ally_focus", "rear_exposed"], - "EQ-023": ["rear_threat", "target_redirect", "front_delay"], - "EQ-024": ["lowest_hp", "heal_target", "overheal_lock"], - "EQ-025": ["equal_targets", "alternate_family", "critical_lock"], - "EQ-026": ["summon", "target_mark", "owner_delay"], - "EQ-027": ["wounded_target", "damage_bonus", "healthy_cost"], - "EQ-028": ["guarded_enemy", "guard_strip", "raw_damage_cost"], - "EQ-029": ["fastest_enemy", "hinder", "slow_target_lock"], - "EQ-030": ["last_target", "repeat_bonus", "retarget_cost"], - "EQ-031": ["ally_targeted", "intercept", "self_exposed"], - "EQ-032": ["screened_enemy", "screen_remove", "damage_lock"], - "EQ-033": ["front_attack", "reflect", "rear_bypass"], - "EQ-034": ["low_resource", "resource_gain", "full_resource_delay"], - "EQ-035": ["open_flank", "rear_target", "foremost_damage_cost"], - "EQ-036": ["area_action", "second_target", "primary_damage_cost"], - "EQ-037": ["interrupt", "cooldown_refresh", "failed_interrupt_cost"], - "EQ-038": ["oldest_hindrance", "cleanse", "urgent_newer_cost"], - "EQ-039": ["enemy_collapse", "reserved_target", "double_collapse_fizzle"], - "EQ-040": ["equal_target_families", "alternate_default", "critical_lock"], - "AR-001": ["market_slots", "recruit_plus", "equipment_minus", "conflict_ar002"], - "AR-002": ["market_slots", "equipment_plus", "recruit_minus", "conflict_ar001"], - "AR-003": ["recruit_price", "common_discount", "rare_surcharge", "conflict_ar004"], - "AR-004": ["recruit_weights", "narrow_profession", "other_surcharge", "conflict_ar003"], - "AR-005": ["recruit_offer", "extra_candidate", "discard_one", "conflict_ar006"], - "AR-006": ["trait_reroll", "preserve_combat_fields", "once_per_market", "conflict_ar005"], - "AR-007": ["reserve_capacity", "capacity_plus", "upkeep_cost", "conflict_ar008"], - "AR-008": ["reserve_capacity", "capacity_minus", "death_guarantee", "conflict_ar007"], - "EN-R1-01": ["foremost_bite", "repeat_count", "neighbor_guard_chip"], - "EN-R1-02": ["rear_projectile", "rearmost_target", "forward_step"], - "EN-R1-03": ["headwind_charge", "foremost_target", "push"], - "EN-R1-04": ["positive_tag", "consume_oldest", "no_damage"], - "EN-R1-05": ["projectile_screen", "first_reflect", "no_chain"], - "EN-R1-06": ["reach_filter", "long_reach_target", "foremost_fallback"], - "EN-R1-07": ["band_count", "majority_delay", "order_pressure"], - "EN-R1-08": ["rear_growth", "forward_step", "rear_shield"], - "EN-R1-09": ["front_rear_line", "swap_crossing", "hinder"], - "EN-R1-10": ["enemy_reorder", "leap_front", "changed_target_strike"], - "EN-R1-11": ["elite", "indexed_position", "push_or_projectile"], - "EN-R1-12": ["elite", "two_footprint", "screen_rotation", "damage_family"], - "BO-R1-01": ["boss", "two_footprint", "three_panes", "sweep_push"], - "BO-R1-02": ["boss", "stationary", "gust_footprint", "spindle_reverse"], - "RG-01": ["region", "windglass", "push", "projectile_screen"], - "CTR-R1-01": ["contract", "twelve_rounds", "boss_bo_r1_01", "clear_rg01_c01"], - "CTR-R1-02": ["contract", "twelve_rounds", "boss_bo_r1_02", "clear_rg01_c02"] - }, - "professions": [ - { - "id": "PF-B01", - "name": "Hearthguard", - "family": "base", - "role": "front-hold; front", - "rules": "Shield Knock hits foremost reachable and Guard 1 neighbor. Guard gained when struck, spent to intercept one neighbor hit. Set Feet: cannot be pushed while Guarded, but steps slower.", - "cadence": "reach 1; steady: intercept lethal neighbor hit, refresh own Guard, then Knock nearest.", - "strategy": "Countered by rear pressure and Guard stripping; enables slow casters and adjacency items.", - "production": "P/O/A/V/S: broad shield overlay, brace/knock family, square amber guard cue, wood-metal block event." - }, - { - "id": "PF-B02", - "name": "Edgehand", - "family": "base", - "role": "single-pressure; front/center", - "rules": "Testing Cut hits nearest exposed enemy. Edge gained on alternating targets, spent to finish a marked target. No Waste: overflow damage becomes one Edge, not splash.", - "cadence": "reach 1; fast: finish legal low target, alternate target for Edge, then nearest exposed.", - "strategy": "Countered by decoys and heavy protection; likes marks and enemy reorder.", - "production": "P/O/A/V/S: paired short-blade overlay, alternating slash poses, chevron Edge cue, dry cut/finish pair." - }, - { - "id": "PF-B03", - "name": "Longspoke", - "family": "base", - "role": "reach-control; center", - "rules": "Measured Thrust strikes through one unit. Poise gained by attacking without moving, spent to extend reach or resist pull. Keep Distance: loses Poise after a step.", - "cadence": "reach 2; steady: interrupt approaching short-reach threat, pierce aligned front pair, then foremost.", - "strategy": "Countered by forced movement and rear divers; protects center damage through controlled reach.", - "production": "P/O/A/V/S: modular polearm, plant/thrust/pivot family, straight-line white cue, haft plant and air-cut events." - }, - { - "id": "PF-B04", - "name": "Wayrunner", - "family": "base", - "role": "reorder; any", - "rules": "Passing Strike swaps with the neighbor ahead after hitting. Momentum gained per distinct position visited, spent on a long step. Open Route: allies moved through are briefly unpushable.", - "cadence": "reach 1; fast: escape marked danger, fill exposed front gap, then cycle toward least-visited position.", - "strategy": "Countered by roots and narrow safe bands; enables position triggers and rescues fragile units.", - "production": "P/O/A/V/S: light hooked blade, run/brake/swap family, footprint cue, cloth rush and heel-stop events." - }, - { - "id": "PF-B05", - "name": "Farstring", - "family": "base", - "role": "projectile-pressure; rear", - "rules": "Draw Shot targets foremost legal enemy; each uninterrupted shot gains Aim, spent to select rearmost. Clear Arc: loses Aim when an ally is pulled behind the Farstring.", - "cadence": "line reach projectile; steady: spend Aim on dangerous rear caster, hit marked target, then foremost.", - "strategy": "Countered by projectile screens and rear intrusion; likes stable protection and target marks.", - "production": "P/O/A/V/S: bow family, draw/loose/reacquire, arc-and-notch cue, bow snap and distinct blocked shot." - }, - { - "id": "PF-B06", - "name": "Riftcaller", - "family": "base", - "role": "area-pressure; rear", - "rules": "Rift Spark hits foremost and echoes lightly to its neighbor. Charge gained while wind-up completes, spent to widen an area. Unstable Weave: interruption clears Charge.", - "cadence": "line spell reach; slow: widen against clustered pair, punish summoned unit, then Spark highest cluster value.", - "strategy": "Countered by interrupts and forced retarget; likes time protection and grouping effects.", - "production": "P/O/A/V/S: focus rod, two-stage channel/cast, forked diamond cue, rising tone with captioned discharge." - }, - { - "id": "PF-B07", - "name": "Kindhand", - "family": "base", - "role": "restore; center/rear", - "rules": "Tending Pulse heals lowest-vitality reachable ally. Remedy gained when healing actual loss, spent to cleanse one hindrance. Honest Care: overheal grants no Remedy.", - "cadence": "reach 2 allied; steady: prevent lethal forecast, cleanse action-blocking status, then heal lowest ratio.", - "strategy": "Countered by split pressure and heal denial; likes Guard, shared damage, and predictable cadence.", - "production": "P/O/A/V/S: satchel and hand-lamp, assess/tend/cleanse, open-circle cue, soft chime plus captioned cleanse." - }, - { - "id": "PF-B08", - "name": "Bondwright", - "family": "base", - "role": "enable; center", - "rules": "Joining Thread lightly damages an enemy and links the caster to one neighbor. Bond gained when linked units use different action tags, spent to share Guard or Remedy. Tension: same-tag repeats fray the link.", - "cadence": "reach 2; steady: repair broken useful link, share resource before threshold, then hit linked enemy.", - "strategy": "Countered by isolation and repetitive parties; amplifies mixed roles and alternating cadences.", - "production": "P/O/A/V/S: knot-frame focus, cast/tie/release, braided line cue with shape dashes, two-tone pluck event." - }, - { - "id": "PF-B09", - "name": "Coilsmith", - "family": "base", - "role": "construct; center/rear", - "rules": "Tool Tap attacks nearest threat. Parts gained every second basic, spent to deploy a one-footprint device behind self. Pack Up: stepping salvages half unspent Parts but removes device.", - "cadence": "reach 1 basic; steady: replace destroyed device, trigger safe utility, then Tap nearest device threat.", - "strategy": "Countered by rear compression and device hunters; likes stable order and Parts equipment.", - "production": "P/O/A/V/S: tool rig and compact device kit, place/tune/pack, cog-square cue, ratchet and device-state events." - }, - { - "id": "PF-B10", - "name": "Gloamreader", - "family": "base", - "role": "impair; rear", - "rules": "Dimming Word damages the enemy with highest current cadence and applies Hindered. Omen gained when a debuff expires, spent to extend a different debuff. No Echo: cannot extend own same tag twice.", - "cadence": "line spell reach; slow: stop imminent major action, extend ally-applied status, then dim fastest enemy.", - "strategy": "Countered by cleanse and staggered immunity; likes varied statuses and long fights.", - "production": "P/O/A/V/S: tablet veil, inscribe/close/release, broken-ring cue, whispered consonant texture with captions." - }, - { - "id": "PF-B11", - "name": "Bellkeeper", - "family": "base", - "role": "tempo; center", - "rules": "Time Bell strikes nearest and advances the slower neighbor's next basic. Verse gained when allies act in three different cadence bands, spent to delay an enemy wind-up. Offbeat: repeated band gives no Verse.", - "cadence": "reach 1/line delay; steady: delay major action, advance restorative neighbor, then strike nearest.", - "strategy": "Countered by silence and uniform-speed builds; enables slow wind-ups and cadence alternation.", - "production": "P/O/A/V/S: handbell frame, strike/listen/cutoff, three-bar cue, pitch-coded but fully captioned bell events." - }, - { - "id": "PF-B12", - "name": "Packwarden", - "family": "base", - "role": "supply; center/rear", - "rules": "Rationed Blow hits nearest; every third basic creates one Supply, spent to refresh a neighbor's item cooldown or grant a safe retreat step. Inventory Rule: cannot target self twice in succession.", - "cadence": "reach 1; steady: enable urgent item, move endangered rearward neighbor, then build Supply.", - "strategy": "Countered by item lock and isolation; supports proc gear and replaceable front lines.", - "production": "P/O/A/V/S: pack-frame and tally tags, handoff/strike/guide, stacked-tab cue, buckle and counted click events." - }, - { - "id": "PF-A01", - "name": "Gatebearer", - "family": "advanced", - "role": "PF-B01; interception becomes directional", - "rules": "front-hold/front; Gate Arc intercepts only attacks crossing the bearer, refunding Guard when prediction is correct.", - "cadence": "reach 1; reserve Guard for crossing attacks, then protect neighbor behind.", - "strategy": "Rear-origin attacks bypass it; superb with fragile unit directly behind. Opposes PF-A02 aggression.", - "production": "O/A/V/S: tall gate shield, swing-open brace, doorway cue, resonant latch block." - }, - { - "id": "PF-A02", - "name": "Ramward", - "family": "advanced", - "role": "PF-B01; Guard becomes forward displacement", - "rules": "control/front; Driving Wall spends Guard to push foremost enemy and follow into the new front.", - "cadence": "reach 1; push channeling or short-reach threat, otherwise maintain Guard.", - "strategy": "Immovable enemies waste its conversion; groups enemies for area roles. Opposes PF-A01 protection.", - "production": "O/A/V/S: convex shield, two-step drive, wedge cue, shove impact layers." - }, - { - "id": "PF-A03", - "name": "Lastlight", - "family": "advanced", - "role": "PF-B02; Edge wagers on execution", - "rules": "finish/front/center; Final Measure reserves all Edge; kill refunds half, survival leaves user Exposed.", - "cadence": "reach 1; wager only when visible forecast crosses defeat threshold.", - "strategy": "Barriers and death prevention punish wager; marks improve forecast. Opposes PF-A04 patience.", - "production": "O/A/V/S: narrow dueling blade, held finish pose, closing-line cue, silence-then-hit event." - }, - { - "id": "PF-A04", - "name": "Turnblade", - "family": "advanced", - "role": "PF-B02; Edge becomes a deterministic counter", - "rules": "pressure/front; Turn Aside reserves one Edge as an explicit Block charge against the next eligible direct component targeting self. Trigger TURNBLADE_RETURN declares observed_role=actual_recipient, outcome_ids=AO_BLOCKED, component_tags=direct+block_eligible, once_scope=action, and allow_derived=false; it queues Return Cut against the source next tick.", - "cadence": "reach 2 counter; reserve Edge only for a telegraphed Block-eligible component due before the next basic; fire Return Cut only while its source remains legal and reachable, otherwise log the suppressed source reason.", - "strategy": "Unblockable/area components and sources beyond reach deny the return; readable single-target telegraphs and reach control enable it. Opposes PF-A03 execution.", - "production": "O/A/V/S: guard-ring blade, brace/return family, AO_BLOCKED square-to-arrow cue, parry scrape and return cut." - }, - { - "id": "PF-A05", - "name": "Hookmarshal", - "family": "advanced", - "role": "PF-B03; Poise controls enemy order", - "rules": "reach-control/center; Crook Pull spends Poise to pull a reach-legal enemy one position forward.", - "cadence": "reach 2; pull protected caster when counterline can absorb it, then thrust.", - "strategy": "Pull immunity and front traps punish it; empowers Edgehand and short reach. Opposes PF-A06 static damage.", - "production": "O/A/V/S: hooked polearm, catch/haul, hooked-line cue, chainless wood-and-cloth pull." - }, - { - "id": "PF-A06", - "name": "Sunlance", - "family": "advanced", - "role": "PF-B03; Poise becomes piercing setup", - "rules": "pressure/center; Noon Line spends all Poise to pierce every intervening enemy with declining effect.", - "cadence": "line reach; fire only through two or more legal targets, else build Poise.", - "strategy": "Spacing and forced movement collapse value; grouping and stable front enable it. Opposes PF-A05 reorder.", - "production": "O/A/V/S: long bright spear, extended plant/cast, straight sunbar cue, layered pass-through hits." - }, - { - "id": "PF-A07", - "name": "Relayknife", - "family": "advanced", - "role": "PF-B04; movement hands timing to allies", - "rules": "enable/any; Pass The Turn spends Momentum on swap, advancing the swapped ally's basic while delaying own.", - "cadence": "adjacent ally; rescue endangered unit or advance a ready counter before cycling.", - "strategy": "Roots or selfish carry builds reduce value; slow high-impact basics benefit. Opposes PF-A08 self-routing.", - "production": "O/A/V/S: baton knife, tap-and-pass motion, paired footprints, handoff click." - }, - { - "id": "PF-A08", - "name": "Backdraft", - "family": "advanced", - "role": "PF-B04; full-line route becomes attack", - "rules": "reorder-pressure/any; Wake Cut damages each unique enemy passed while sprinting rear to front, then becomes Exposed.", - "cadence": "line traversal only when path is open; retreat after wake if threatened.", - "strategy": "Occupied path and focus fire punish it; Guard and pull setup open routes. Opposes PF-A07 ally timing.", - "production": "O/A/V/S: scarf blade, full-line sprint, tapered wake cue, sequential rush hits." - }, - { - "id": "PF-A09", - "name": "Pinseeker", - "family": "advanced", - "role": "PF-B05; Aim selects action parts", - "rules": "precision/rear; Joint Pin spends Aim to delay a telegraphed movement or interruptible wind-up instead of dealing full damage.", - "cadence": "line projectile; pin highest-impact legal telegraph, otherwise preserve Aim.", - "strategy": "Screens and uninterruptible actions counter it; Bellkeeper extends pin windows. Opposes PF-A10 coverage.", - "production": "O/A/V/S: pin bow, low draw, joint-cross cue, taut snap with delayed thunk." - }, - { - "id": "PF-A10", - "name": "Rainbow", - "family": "advanced", - "role": "PF-B05; Aim becomes distributed volley", - "rules": "area/rear; Falling String spends Aim to strike three distinct legal targets; cannot repeat one target.", - "cadence": "line projectile; volley at three targets, otherwise basic to build Aim.", - "strategy": "Single durable enemy wastes breadth; debuff-on-hit gear multiplies it. Opposes PF-A09 precision.", - "production": "O/A/V/S: split-limb bow, upward release, three-notch cue, staggered arrow rain." - }, - { - "id": "PF-A11", - "name": "Stormledger", - "family": "advanced", - "role": "PF-B06; Charge chains by cadence", - "rules": "area/rear; Rolling Sum jumps to the next enemy whose action is later, losing power each jump.", - "cadence": "line spell; start on fastest enemy when at least two later targets exist.", - "strategy": "Uniform timing and isolation limit chain; tempo delays reshape the sequence. Opposes PF-A12 focused channel.", - "production": "O/A/V/S: page-rod, sequential cast, numbered bolt cue, pitch-descending chain." - }, - { - "id": "PF-A12", - "name": "Hollowstar", - "family": "advanced", - "role": "PF-B06; Charge is stored through interruption at a cost", - "rules": "pressure/rear; Held Rift preserves Charge when interrupted but applies self Hindered; release focuses one target.", - "cadence": "line spell; retain through one interruption, release at marked high-threat enemy.", - "strategy": "Repeated interrupts compound delay; Guard and cleanse protect payoff. Opposes PF-A11 distributed chain.", - "production": "O/A/V/S: dark aperture focus, brace/release, hollow diamond cue, muted charge then bass impact." - }, - { - "id": "PF-A13", - "name": "Fieldmender", - "family": "advanced", - "role": "PF-B07; Remedy follows moving patients", - "rules": "restore/center; Walking Triage heals a target then swaps it one step rearward if the space relation is legal.", - "cadence": "allied reach 2; extract lowest-vitality exposed ally, otherwise cleanse.", - "strategy": "Reorder locks and rear pressure counter extraction; front replacements benefit. Opposes PF-A14 delayed care.", - "production": "O/A/V/S: sling lamp, support-and-step, rear arrow in circle, bandage rustle and step." - }, - { - "id": "PF-A14", - "name": "Aftercare", - "family": "advanced", - "role": "PF-B07; Remedy schedules future healing", - "rules": "restore/rear; Promise Dose spends Remedy now to heal after target's next action, stronger if it takes damage first.", - "cadence": "allied reach line; schedule on forecast target, never duplicate an active promise.", - "strategy": "Burst before trigger defeats it; predictable tanks and action acceleration enable it. Opposes PF-A13 immediate extraction.", - "production": "O/A/V/S: sealed vial lamp, tag-and-release, hourglass-cross cue, delayed glass chime." - }, - { - "id": "PF-A15", - "name": "Chainhost", - "family": "advanced", - "role": "PF-B08; one Bond links three different roles", - "rules": "enable/center; Third Knot extends a link through both neighbors; any same-tag repeat frays both sides.", - "cadence": "adjacent network; build only with three distinct action tags, share to weakest side.", - "strategy": "Displacement and role duplication break it; mixed cadence lines gain coverage. Opposes PF-A16 pair focus.", - "production": "O/A/V/S: triple knot frame, three-hand sweep, tri-braid cue, three-note pluck." - }, - { - "id": "PF-A16", - "name": "Burdenmate", - "family": "advanced", - "role": "PF-B08; Bond transfers a chosen cost", - "rules": "protect/center; Take The Clause moves one visible hindrance or item cooldown from neighbor to self, gaining Bond.", - "cadence": "neighbor only; take action-blocking cost if self can still act, then share resource.", - "strategy": "Isolation and nontransferable boss tags stop it; cleanse and self-cost equipment enable it. Opposes PF-A15 network breadth.", - "production": "O/A/V/S: split knot frame, pull-to-chest, divided-circle cue, tension snap." - }, - { - "id": "PF-A17", - "name": "Sentrymaker", - "family": "advanced", - "role": "PF-B09; Parts create rear projectile device", - "rules": "construct/rear; Needle Post occupies one footprint behind user and shoots user's last basic target.", - "cadence": "mirrored line reach; deploy only with rear room, retune when target invalid.", - "strategy": "Rear compression/device hunters remove it; marks stabilize target. Opposes PF-A18 frontline trap.", - "production": "O/A/V/S: fold-out post, plant/aim/fold, tripod cue, clockwork shot events." - }, - { - "id": "PF-A18", - "name": "Snarewright", - "family": "advanced", - "role": "PF-B09; Parts arm a vacated position", - "rules": "control/center; Latch Tile triggers when an enemy is pushed or pulled across its indexed line position.", - "cadence": "position target; arm ahead of predicted reorder, otherwise conserve Parts.", - "strategy": "No displacement means dormant value; Hookmarshal and Ramward trigger it. Opposes PF-A17 repeated shots.", - "production": "O/A/V/S: flat latch kit, place/arm/spring, toothed-square cue, clamp event." - }, - { - "id": "PF-A19", - "name": "Paleoracle", - "family": "advanced", - "role": "PF-B10; Omen copies expiry timing", - "rules": "impair/rear; Borrowed End makes a different hindrance expire when the copied source expires, sharing cleanse risk.", - "cadence": "line spell; pair long ally status with urgent enemy status.", - "strategy": "Cleanse removes both schedule value; varied debuff durations enable planning. Opposes PF-A20 consumption.", - "production": "O/A/V/S: twin tablet, cross-inscribe, linked broken rings, paired whisper cutoff." - }, - { - "id": "PF-A20", - "name": "Dreadtaster", - "family": "advanced", - "role": "PF-B10; consumes hindrance for burst", - "rules": "pressure/rear; Bitter Proof removes one enemy hindrance to damage and expose it based on remaining duration.", - "cadence": "line spell; consume just before cleanse or kill window, never erase needed control early.", - "strategy": "Premature use wastes setup; many short debuffs and finishers enable it. Opposes PF-A19 duration control.", - "production": "O/A/V/S: tasting needle and veil, draw/strike, collapsing ring cue, inhale-to-crack event." - }, - { - "id": "PF-A21", - "name": "Marchbell", - "family": "advanced", - "role": "PF-B11; Verse synchronizes basics", - "rules": "tempo/center; Common Step spends Verse so both neighbors' next basics resolve together after the slower one.", - "cadence": "neighbors; synchronize complementary basics, avoid simultaneous overkill.", - "strategy": "Area interrupts and redundant targets punish sync; combo tags and shared target rules benefit. Opposes PF-A22 enemy denial.", - "production": "O/A/V/S: broad bell, two-side conducting, paired bars cue, merged beat event." - }, - { - "id": "PF-A22", - "name": "Quietus", - "family": "advanced", - "role": "PF-B11; Verse cancels sound-tagged wind-ups", - "rules": "control/center/rear; Held Clapper spends all Verse to interrupt one sound or command telegraph, then self-silences.", - "cadence": "line control; reserve for preview-tagged major action, otherwise advance ally.", - "strategy": "Non-sound threats bypass it; forecast and cleanse support the self-cost. Opposes PF-A21 ally acceleration.", - "production": "O/A/V/S: wrapped bell, catch-clapper pose, crossed-wave cue, sudden mix duck with caption." - }, - { - "id": "PF-A23", - "name": "Trailfactor", - "family": "advanced", - "role": "PF-B12; Supply alters post-battle offers", - "rules": "supply/rear; Field Sample marks first defeated enemy family; post-battle shop guarantees one matching counter tag, spending stored Supply.", - "cadence": "battle reach 1; sample novel threat, use Supply on urgent item refresh.", - "strategy": "Repeated familiar enemies lower value; diverse routes and missing counter gear reward it. Opposes PF-A24 immediate line logistics.", - "production": "O/A/V/S: sample pack, tag/scoop/ledger, specimen-tab cue, cork and pencil events." - }, - { - "id": "PF-A24", - "name": "Lineporter", - "family": "advanced", - "role": "PF-B12; Supply moves equipment triggers", - "rules": "enable/center; Hand Carry transfers one unused once-per-battle item trigger to a legal neighbor; item itself stays equipped.", - "cadence": "neighbor only; move a trigger to the unit with legal target, else retreat-step endangered ally.", - "strategy": "Bound/self-only items cannot transfer; proc gear and mixed eligibility benefit. Opposes PF-A23 future offers.", - "production": "O/A/V/S: open-frame pack, pass token, hand-arrow cue, clasp-release event." - } + "schema": 1, + "source": { + "path": "prototype/site/data/catalog.json", + "sha256": "fb99bcd75ca6308091fa56b45f8da551276a496a1f96acbb44225efe491fdfef", + "authority_revision": "phase8-p8-3r2-contact-rear-actions-replacement" + }, + "names_zh": { + "PF-B01": "护心卫士", + "PF-B02": "锋刃手", + "PF-B03": "长枪使", + "PF-B04": "逐风行者", + "PF-B05": "远弦射手", + "PF-B06": "裂隙法师", + "PF-B07": "仁心医者", + "PF-B08": "结契师", + "PF-B09": "机关匠", + "PF-B10": "暮兆术士", + "PF-B11": "律钟师", + "PF-B12": "辎重官", + "PF-A01": "门垒卫", + "PF-A02": "冲城卫", + "PF-A03": "终光刃", + "PF-A04": "回锋剑士", + "PF-A05": "钩阵统领", + "PF-A06": "日耀枪骑", + "PF-A07": "传步刺客", + "PF-A08": "逆风刃", + "PF-A09": "定穴射手", + "PF-A10": "箭雨使", + "PF-A11": "风暴算师", + "PF-A12": "空星术士", + "PF-A13": "战地医师", + "PF-A14": "延疗师", + "PF-A15": "三联契师", + "PF-A16": "担契者", + "PF-A17": "哨械师", + "PF-A18": "陷阵工匠", + "PF-A19": "苍白先知", + "PF-A20": "苦兆术士", + "PF-A21": "行军钟师", + "PF-A22": "寂音师", + "PF-A23": "觅迹补给官", + "PF-A24": "阵线搬运师", + "TR-001": "先行一步", + "TR-002": "耐心起手", + "TR-003": "窄阵护卫", + "TR-004": "长久记忆", + "TR-005": "空闲之手", + "TR-006": "开放步法", + "TR-007": "深口袋", + "TR-008": "静默双手", + "TR-009": "配重步", + "TR-010": "共饮药杯", + "TR-011": "醒目目标", + "TR-012": "迟步补位", + "TR-013": "节俭节拍", + "TR-014": "厚重袖口", + "TR-015": "远听者", + "TR-016": "灰烬胃口", + "TR-017": "一丝不苟", + "TR-018": "薄血", + "TR-019": "后阵守望", + "TR-020": "器具誓约", + "TR-021": "松散阵形", + "TR-022": "石刻计数", + "TR-023": "借来节奏", + "TR-024": "净断", + "TR-025": "风险账本", + "TR-026": "空隙射程", + "TR-027": "定声", + "TR-028": "二次草拟", + "TR-029": "摊开手掌", + "TR-030": "反转带扣", + "TR-031": "风暴胆识", + "TR-032": "温和收尾", + "TR-033": "分心并顾", + "TR-034": "久经风霜", + "TR-035": "空剑鞘", + "TR-036": "最后见证", + "EQ-001": "退步靴", + "EQ-002": "钉前护胫", + "EQ-003": "传步腰带", + "EQ-004": "寻隙凉鞋", + "EQ-005": "铅坠束带", + "EQ-006": "后钩披风", + "EQ-007": "转轴脚环", + "EQ-008": "静路绑带", + "EQ-009": "救援环扣", + "EQ-010": "前位主张", + "EQ-011": "邻位带扣", + "EQ-012": "开阵系带", + "EQ-013": "交步徽记", + "EQ-014": "静默后甲", + "EQ-015": "接力马刺", + "EQ-016": "空位铃", + "EQ-017": "撑钩护腕", + "EQ-018": "远行缎带", + "EQ-019": "定步石", + "EQ-020": "守线标签", + "EQ-021": "威胁镜片", + "EQ-022": "前标粉笔", + "EQ-023": "后阵呼哨", + "EQ-024": "仁慈刻度盘", + "EQ-025": "分选硬币", + "EQ-026": "召唤刺针", + "EQ-027": "伤势测带", + "EQ-028": "破盾缝锥", + "EQ-029": "疾影墨水", + "EQ-030": "末目标绳结", + "EQ-031": "护卫标签", + "EQ-032": "显形粉末", + "EQ-033": "对面镜盾", + "EQ-034": "低潮量规", + "EQ-035": "开侧弦线", + "EQ-036": "双重准星", + "EQ-037": "打断账册", + "EQ-038": "旧伤罗盘", + "EQ-039": "崩解瞄具", + "EQ-040": "破选别针", + "EQ-041": "邻壁护墙", + "EQ-042": "疏孔圆盾", + "EQ-043": "护值导环", + "EQ-044": "露锋战旗", + "EQ-045": "二次受击甲", + "EQ-046": "医者护轨", + "EQ-047": "脆面巨盾", + "EQ-048": "后向神盾", + "EQ-049": "借来的掩护", + "EQ-050": "破露头盔", + "EQ-051": "静默拦截者", + "EQ-052": "蓄热披风", + "EQ-053": "空护笼", + "EQ-054": "三击灯台", + "EQ-055": "邻誓胸甲", + "EQ-056": "回推框架", + "EQ-057": "慈护屏", + "EQ-058": "低护围裙", + "EQ-059": "逆印盾", + "EQ-060": "仅邻共享甲", + "EQ-061": "提前钟", + "EQ-062": "屏息面罩", + "EQ-063": "断复剪", + "EQ-064": "后拍指环", + "EQ-065": "缓燃引信", + "EQ-066": "速引顶针", + "EQ-067": "正午刻度盘", + "EQ-068": "错拍扣具", + "EQ-069": "耐性笔羽", + "EQ-070": "断念烛", + "EQ-071": "邻位时钟", + "EQ-072": "三巡齿轮", + "EQ-073": "破晓复元带", + "EQ-074": "迟净药瓶", + "EQ-075": "未成节拍器", + "EQ-076": "双启纺线", + "EQ-077": "歇步鞋", + "EQ-078": "观首镜片", + "EQ-079": "短忆弹簧", + "EQ-080": "长忆弹簧", + "AR-001": "开放账册", + "AR-002": "巧匠橱窗", + "AR-003": "共同目标", + "AR-004": "窄域搜寻", + "AR-005": "二次面谈", + "AR-006": "新生资质", + "AR-007": "满员长凳", + "AR-008": "精简队伍", + "AR-009": "熟悉的面孔", + "AR-010": "广域招募", + "AR-011": "窄阵先锋", + "AR-012": "长列远征", + "AR-013": "固定两端", + "AR-014": "流转两端", + "AR-015": "成对执务", + "EN-R1-01": "晶板啮兽", + "EN-R1-02": "疾风雀", + "EN-R1-03": "台地冲羊", + "EN-R1-04": "钟茎食草兽", + "EN-R1-05": "晶背兽", + "EN-R1-06": "原野潜猎者", + "EN-R1-07": "鸣哨飞蠓", + "EN-R1-08": "玻根幼芽", + "EN-R1-09": "横风书记", + "EN-R1-10": "崖沿跃客", + "EN-R1-11": "狂风执令官", + "EN-R1-12": "棱晶角兽", + "EN-R2-01": "芦苇钩手", + "EN-R2-02": "泥灯水蛭", + "EN-R2-03": "淤泥拟态", + "EN-R2-04": "堤岸陆龟", + "EN-R2-05": "沼笛手", + "EN-R2-06": "咸水幽光", + "EN-R2-07": "渡船蟹", + "EN-R2-08": "溺种荚", + "EN-R2-09": "泥沼医者", + "EN-R2-10": "雾颌兽", + "EN-R2-11": "潮道寡妇", + "EN-R2-12": "闸门长者", + "BO-R1-01": "千镜角鹿", + "BO-R1-02": "风织机", + "BO-R2-01": "闸背母兽", + "BO-R2-02": "沉钟", + "RG-01": "风镜原野", + "RG-02": "淤醒沼泽", + "CTR-R1-01": "风镜初渡", + "CTR-R1-02": "风织机通路", + "CTR-R2-01": "淤醒渡口", + "CTR-R2-02": "沉钟水道" + }, + "execution_tags": { + "PF-B01": [ + "front_hold", + "guard", + "intercept", + "push_resist" ], - "traits": [ - { - "id": "TR-001", - "name": "First Through", - "benefit": "First action after becoming foremost gains reach +1.", - "cost": "That action cannot target the current foremost enemy.", - "evidence": "Front-arrow badge pulses; result names chosen farther target.", - "prohibited": "PF-B01, PF-A01, PF-H01; self-only target actions." - }, - { - "id": "TR-002", - "name": "Patient Start", - "benefit": "After two allied actions resolve, first basic gains one local resource.", - "cost": "Starts battle with its first basic delayed one cadence band.", - "evidence": "Two-notch timer under portrait; report shows delayed and paid tick.", - "prohibited": "PF-A21; opening-action equipment." - }, - { - "id": "TR-003", - "name": "Narrow Guard", - "benefit": "When only one neighbor exists, intercepts one non-area hit without spending resource.", - "cost": "While two neighbors exist, cannot receive transferred Guard.", - "evidence": "One/two-neighbor shield icon; intercept has trait source label.", - "prohibited": "PF-A15; forced-intercept equipment." - }, - { - "id": "TR-004", - "name": "Long Memory", - "benefit": "Repeating the last legal target increases status duration by one tick.", - "cost": "Changing target delays the next action by one tick.", - "evidence": "Prior-target ring and tick preview; report groups repeats/switches.", - "prohibited": "Target-randomizing equipment; PF-A10." - }, - { - "id": "TR-005", - "name": "Spare Hand", - "benefit": "May carry one construct or summon footprint through a swap.", - "cost": "Carried object loses its next automatic action.", - "evidence": "Object attaches visibly during swap; result logs skipped action.", - "prohibited": "PF-H04; immovable-device items." - }, - { - "id": "TR-006", - "name": "Open Stance", - "benefit": "After a push or pull, immediately basic-attacks the mover if reachable.", - "cost": "Becomes Exposed until that basic resolves.", - "evidence": "Bent-arrow badge and exposed outline; counter target logged.", - "prohibited": "Displacement immunity equipment; PF-A04." - }, - { - "id": "TR-007", - "name": "Deep Pocket", - "benefit": "First consumable equipment trigger each battle is preserved for a second legal target.", - "cost": "Both uses have one longer cooldown band.", - "evidence": "Two-charge item pip and extended clock; results split both uses.", - "prohibited": "PF-A24; single-use wager items." - }, - { - "id": "TR-008", - "name": "Quiet Hands", - "benefit": "Cannot be targeted by sound-tagged enemy actions while behind two allies.", - "cost": "Own sound-tagged actions generate no local resource.", - "evidence": "Muted-wave badge; rejected targets explain depth condition.", - "prohibited": "PF-A22; PF-B11." - }, - { - "id": "TR-009", - "name": "Counterweight", - "benefit": "Resists first pull and transfers its movement to the neighbor ahead.", - "cost": "If that neighbor cannot move, both become Hindered.", - "evidence": "Linked weight icon; preview shows legal recipient; failure logs blockage.", - "prohibited": "PF-B01; movement-lock equipment." - }, - { - "id": "TR-010", - "name": "Shared Cup", - "benefit": "Effective healing received also restores a smaller amount to lowest-vitality neighbor.", - "cost": "Direct self-healing is reduced.", - "evidence": "Cup arc identifies neighbor before resolution; result separates shared heal.", - "prohibited": "PF-H01; no-neighbor formation starts." - }, - { - "id": "TR-011", - "name": "Bright Target", - "benefit": "Enemies prefer this recruit over equal-reach candidates.", - "cost": "Each attack survived grants one Focus, capped until the recruit moves.", - "evidence": "Visible threat flare and Focus pips; report cites target-score source.", - "prohibited": "Untargetable-opening items; PF-A08." - }, - { - "id": "TR-012", - "name": "Late Step", - "benefit": "When neighbor ahead dies, steps into the gap only after completing current wind-up.", - "cost": "Cannot act during the subsequent collapse tick.", - "evidence": "Pending-step ghost silhouette; result shows preserved cast and lost tick.", - "prohibited": "Instant-collapse equipment; no-wind-up actions." - }, - { - "id": "TR-013", - "name": "Frugal Rhythm", - "benefit": "Every third basic that spends no resource shortens one equipment cooldown.", - "cost": "Resource-spending actions reset the three-basic count.", - "evidence": "Three-bead counter; reset and refresh named in results.", - "prohibited": "PF-B12; equipment without cooldowns." - }, - { - "id": "TR-014", - "name": "Heavy Sleeve", - "benefit": "First interrupt becomes a delay instead of cancellation.", - "cost": "Wind-up after the delay resolves one cadence band slower.", - "evidence": "Sleeve bar catches interrupt cue; report contrasts old/new completion tick.", - "prohibited": "Uninterruptible-cast equipment; PF-A12." - }, - { - "id": "TR-015", - "name": "Far Listener", - "benefit": "May receive enable actions from one additional position away.", - "cost": "Cannot receive them from an immediate neighbor while at full vitality.", - "evidence": "Distant-ear arc and rejected-neighbor reason.", - "prohibited": "PF-A15; adjacency-only bond equipment." - }, - { - "id": "TR-016", - "name": "Ash Appetite", - "benefit": "Defeating a Hindered enemy restores one local resource.", - "cost": "Defeating an unhindered enemy removes one local resource if available.", - "evidence": "Status-check flame icon; both gain/loss events captioned.", - "prohibited": "Professions with no local resource; status-immunity items." - }, - { - "id": "TR-017", - "name": "Exacting", - "benefit": "The first action each battle gains +1 reach only for completion validation against its reserved target; movement that remains inside that reach follows the same unit ID and records TARGET_MOVED_STILL_LEGAL on its terminal outcome.", - "cost": "If that reservation instead emits AO_INVALID_REACH, AO_INVALID_UNTARGETABLE, AO_INVALID_FALLEN, or AO_INVALID_FILTER, its policy is forced to fizzle, then emits AO_FIZZLED with zero resource or cooldown refund; it cannot retarget or ground-resolve.", - "evidence": "Reservation frame shows the one-use extended reach; results show the invalid record, AO_FIZZLED, or the followed-target detail and accepted wager.", - "prohibited": "Ground-resolve equipment; PF-A10." - }, - { - "id": "TR-018", - "name": "Thin Blood", - "benefit": "Taking area damage advances next action by one tick.", - "cost": "Direct single-target healing is less effective.", - "evidence": "Area-hit speed notch and cracked-drop badge; report quantifies lost care.", - "prohibited": "Area immunity equipment; PF-B07." - }, - { - "id": "TR-019", - "name": "Backwatch", - "benefit": "First enemy entering the allied rear is immediately Marked.", - "cost": "While no rear intruder exists, recruit's forward reach is reduced by one.", - "evidence": "Rear eye badge; reach preview updates; intrusion mark logged.", - "prohibited": "PF-B05; reach-1 professions." - }, - { - "id": "TR-020", - "name": "Oathbound Tool", - "benefit": "The Recruit's equipped item's first trigger gains priority over its profession action.", - "cost": "Until that trigger resolves or expires, the Recruit's first technique waits.", - "evidence": "One-slot oath ribbon and queue state; delayed technique has a reason.", - "prohibited": "Triggerless equipment; PF-A24." - }, - { - "id": "TR-021", - "name": "Loose Formation", - "benefit": "Gains one local resource after swapping with a different profession role.", - "cost": "Repeating a swap partner applies Hindered to self.", - "evidence": "Partner-role bead track; repeat warning in formation preview.", - "prohibited": "PF-H04; swap-lock equipment." - }, - { - "id": "TR-022", - "name": "Stone Count", - "benefit": "Third hit from the same enemy grants Guard and clears its Mark.", - "cost": "Changing attackers resets count.", - "evidence": "Attacker portrait with three stones; reset shown on target change.", - "prohibited": "Forced-target equipment; PF-A01." - }, - { - "id": "TR-023", - "name": "Borrowed Pace", - "benefit": "After receiving an action advance, copies the giver's cadence for one basic.", - "cost": "If copied cadence is slower, cannot be advanced again until that basic.", - "evidence": "Donor-color-independent bar shape; report records copied band.", - "prohibited": "PF-A21; cadence-fixed equipment." - }, - { - "id": "TR-024", - "name": "Clean Break", - "benefit": "First cleanse also removes the recruit's oldest positive tag and grants local resource.", - "cost": "If no positive tag exists, cleanse takes one extra tick.", - "evidence": "Paired plus/minus token; tooltip previews removed tag.", - "prohibited": "PF-A16; positive-tag-lock items." - }, - { - "id": "TR-025", - "name": "Risk Ledger", - "benefit": "Surviving while Exposed adds one discount step to this recruit's next shop equipment purchase.", - "cost": "Ending battle never Exposed clears all accumulated steps.", - "evidence": "Persistent personal tally visible in roster/shop; result states kept/cleared.", - "prohibited": "Exposed-immunity equipment; retreat-only encounters." - }, - { - "id": "TR-026", - "name": "Hollow Reach", - "benefit": "Basics may pass through a summoned footprint.", - "cost": "Basics deal no effect to summoned units.", - "evidence": "Hollow projectile/line cue; illegal summon target reason.", - "prohibited": "Summon-hunter equipment; PF-A17." - }, - { - "id": "TR-027", - "name": "Rooted Voice", - "benefit": "Sound and command actions cannot be interrupted while recruit has not moved.", - "cost": "Moving self-silences until next basic resolves.", - "evidence": "Rooted waveform icon; movement preview shows silence interval.", - "prohibited": "PF-B04, PF-A08, PF-H04." - }, - { - "id": "TR-028", - "name": "Second Draft", - "benefit": "First self-applied hindrance is replaced by Hindered of shorter duration.", - "cost": "Second self-applied hindrance lasts one tick longer.", - "evidence": "Two-page counter; replacement/extension source is explicit.", - "prohibited": "Self-cost-free equipment; PF-A16." - }, - { - "id": "TR-029", - "name": "Open Palm", - "benefit": "Resource-sharing actions can target through one intervening ally.", - "cost": "Sharing with a distant ally gives nothing to immediate neighbors that cadence.", - "evidence": "Straight handoff line; opportunity cost shown on neighbor pips.", - "prohibited": "PF-H06; no-resource professions." - }, - { - "id": "TR-030", - "name": "Turncoat Buckle", - "benefit": "When pushed behind the current rearmost position, immediately steps to center.", - "cost": "Drops one active positive equipment tag during the step.", - "evidence": "Buckle opens and center ghost appears; dropped tag named in log.", - "prohibited": "Position-lock equipment; PF-A08." - }, - { - "id": "TR-031", - "name": "Storm Nerve", - "benefit": "Each completed enemy major wind-up grants Focus.", - "cost": "An interrupted enemy major action removes all stored Focus.", - "evidence": "Major-action rings feed Focus pips; interruption loss bursts visibly.", - "prohibited": "Focusless professions; PF-A09." - }, - { - "id": "TR-032", - "name": "Gentle Finish", - "benefit": "Overkill is converted into Remedy for the lowest-vitality ally.", - "cost": "Cannot benefit from execution-threshold effects.", - "evidence": "Overkill trail bends to ally; threshold rejection is explicit.", - "prohibited": "PF-A03; execution-tag equipment." - }, - { - "id": "TR-033", - "name": "Split Attention", - "benefit": "Can maintain two Marks placed by equipment.", - "cost": "Profession-applied Marks last one tick less.", - "evidence": "Two half-eye pips; source-specific duration in inspect.", - "prohibited": "Equipment set without Marks; markless professions." - }, - { - "id": "TR-034", - "name": "Weathered", - "benefit": "First region hazard each battle has no effect.", - "cost": "Second hazard of a different tag has extended duration.", - "evidence": "Region hazard shield with empty/filled halves; result cites both tags.", - "prohibited": "Hazard-immunity equipment; single-hazard challenge rules." - }, - { - "id": "TR-035", - "name": "Empty Scabbard", - "benefit": "With no armament-tag item, basic gains a profession-specific alternate target rule.", - "cost": "Equipping an armament disables the trait and adds one cost tier to that shop action.", - "evidence": "Empty-slot target preview and explicit disabled state.", - "prohibited": "Required-armament professions; bound armament items." - }, - { - "id": "TR-036", - "name": "Last Witness", - "benefit": "When the only living member in a line band, reveals next enemy target and gains reach +1.", - "cost": "Cannot receive invisibility or untargetable tags.", - "evidence": "Lone-band eye and forecast line; rejected concealment has trait reason.", - "prohibited": "Concealment equipment; PF-B01." - } + "PF-B02": [ + "single_pressure", + "edge", + "execute", + "overflow_convert" ], - "equipment": [ - { - "id": "EQ-001", - "name": "Stepback Soles", - "fit": "garb; any", - "class": "B", - "effect": "After first direct hit, step one position rearward if legal.", - "tradeoff": "Next basic waits one tick; full rear makes trigger dormant.", - "production": "I/O/V/S: split-heel boots, rear arrow, heel scrape." - }, - { - "id": "EQ-002", - "name": "Frontpin Greaves", - "fit": "garb; front", - "class": "B", - "effect": "Resist the first enemy push until wearer completes a basic.", - "tradeoff": "The completed basic Hinders wearer for one tick.", - "production": "I/O/V/S: pegged greaves, brace notch, stone pin." - }, - { - "id": "EQ-003", - "name": "Passing Sash", - "fit": "garb; any", - "class": "B", - "effect": "First allied swap through wearer advances the passing ally's basic.", - "tradeoff": "Wearer's next basic is delayed equally.", - "production": "I/O/V/S: two-tail sash, handoff arrows, cloth snap." - }, - { - "id": "EQ-004", - "name": "Gapfinder Sandals", - "fit": "garb; any", - "class": "B", - "effect": "On a neighbor's defeat, choose collapse toward wearer instead of default once.", - "tradeoff": "Wearer becomes Exposed during the collapse tick.", - "production": "I/O/V/S: open-toe route marks, gap ghost, quick step." - }, - { - "id": "EQ-005", - "name": "Leadweight Belt", - "fit": "garb; front/center", - "class": "B", - "effect": "First pull on wearer pulls the neighbor ahead instead when legal.", - "tradeoff": "Illegal transfer Hinders both units.", - "production": "I/O/V/S: hanging weight, forked pull cue, buckle strain." - }, - { - "id": "EQ-006", - "name": "Rearhook Cloak", - "fit": "garb; rear", - "class": "B", - "effect": "First enemy entering allied rear is pulled one position forward after acting.", - "tradeoff": "Wearer must remain rearmost; movement disables hook until next basic.", - "production": "I/O/V/S: hooked hem, return arrow, cloth catch." - }, - { - "id": "EQ-007", - "name": "Pivot Anklet", - "fit": "charm; any", - "class": "B", - "effect": "After wearer swaps, next basic targets the unit opposite the swap direction.", - "tradeoff": "No legal opposite target causes a fizzle without resource gain.", - "production": "I/V/S: compass anklet, pivot reticle, metal tick." - }, - { - "id": "EQ-008", - "name": "Stillroad Wrap", - "fit": "garb; any", - "class": "B", - "effect": "Two cadences without movement grant immunity to next forced step.", - "tradeoff": "Moving voluntarily clears the stored immunity.", - "production": "I/O/V/S: wrapped calves, two-bead root cue, cloth tighten." - }, - { - "id": "EQ-009", - "name": "Rescue Loop", - "fit": "kit; support", - "class": "B", - "effect": "First heal on a foremost ally also pulls that ally one position rearward.", - "tradeoff": "Heal resolves weaker if pull is illegal.", - "production": "I/O/V/S: rope loop, heal-arrow cue, soft rope slide." - }, - { - "id": "EQ-010", - "name": "Forward Claim", - "fit": "charm; any", - "class": "B", - "effect": "First time wearer becomes foremost, mark current foremost enemy.", - "tradeoff": "Mark ends if wearer moves rearward.", - "production": "I/V/S: pennant clasp, paired front markers, flag flick." - }, - { - "id": "EQ-011", - "name": "Neighbor's Buckle", - "fit": "garb; any", - "class": "B", - "effect": "When wearer receives Guard, give one Guard token to neighbor behind.", - "tradeoff": "Wearer cannot receive Guard from that neighbor that cadence.", - "production": "I/O/V/S: twin buckle, rear handoff cue, clasp click." - }, - { - "id": "EQ-012", - "name": "Open-Line Cord", - "fit": "charm; any", - "class": "B", - "effect": "With no neighbor ahead, basics gain reach +1.", - "tradeoff": "Gaining a front neighbor immediately ends any reserved long target.", - "production": "I/V/S: loose cord, reach ruler, cord pluck." - }, - { - "id": "EQ-013", - "name": "Crossstep Token", - "fit": "charm; any", - "class": "B", - "effect": "First targeted enemy movement grants wearer one legal step after its action.", - "tradeoff": "Skipping the step delays wearer's next action.", - "production": "I/V/S: crossed feet token, follow cue, two-step tap." - }, - { - "id": "EQ-014", - "name": "Quiet Rearplate", - "fit": "guard; rear", - "class": "B", - "effect": "While rearmost, first area hit is converted to a direct hit.", - "tradeoff": "Direct hit may now trigger single-target enemy riders.", - "production": "I/O/V/S: narrow backplate, area-to-point cue, muted clang." - }, - { - "id": "EQ-015", - "name": "Relay Spur", - "fit": "garb; reorder", - "class": "B", - "effect": "A swap initiated by wearer refreshes the swapped ally's position trigger.", - "tradeoff": "Same ally cannot be chosen twice consecutively.", - "production": "I/O/V/S: forked spur, reset loop, bright heel click." - }, - { - "id": "EQ-016", - "name": "Empty-Space Bell", - "fit": "charm; any", - "class": "B", - "effect": "A vacant neighbor position makes wearer's next item trigger occur before basic.", - "tradeoff": "If vacancy collapses first, item goes on short cooldown unused.", - "production": "I/V/S: hollow bell, vacancy square, small cutoff chime." - }, - { - "id": "EQ-017", - "name": "Bracehook Cuff", - "fit": "guard; front/center", - "class": "B", - "effect": "When neighbor is pulled, wearer may share the pull as a swap.", - "tradeoff": "Both become Hindered after shared movement.", - "production": "I/O/V/S: hooked cuff, split arrow, leather strain." - }, - { - "id": "EQ-018", - "name": "Longway Ribbon", - "fit": "garb; any", - "class": "B", - "effect": "Traversing three unique positions refreshes one movement item.", - "tradeoff": "Returning to a repeated position resets progress.", - "production": "I/O/V/S: three-knot ribbon, footprint track, flutter count." - }, - { - "id": "EQ-019", - "name": "Settle Stone", - "fit": "charm; any", - "class": "B", - "effect": "After involuntary movement, wearer keeps reserved target if still reachable.", - "tradeoff": "Retarget is forbidden; illegal target fizzles normally.", - "production": "I/V/S: flat stone, solid target tether, dull knock." - }, - { - "id": "EQ-020", - "name": "Linekeeper Tab", - "fit": "kit; support", - "class": "B", - "effect": "Once per battle, prevent an allied swap that would expose lowest-vitality unit.", - "tradeoff": "Prevention spends wearer's next item opportunity.", - "production": "I/O/V/S: red tally tab, blocked swap cue, stamp click." - }, - { - "id": "EQ-021", - "name": "Threatglass Lens", - "fit": "charm; any", - "class": "B", - "effect": "First basic targets enemy with longest shown wind-up instead of default.", - "tradeoff": "If no wind-up exists, wearer waits one tick before defaulting.", - "production": "I/O/V/S: clear lens, clock reticle, glass focus." - }, - { - "id": "EQ-022", - "name": "Frontmark Chalk", - "fit": "kit; any", - "class": "B", - "effect": "Basic on foremost enemy Marks it for wearer's neighbor behind.", - "tradeoff": "Wearer cannot benefit from that Mark.", - "production": "I/V/S: chalk wedge, neighbor eye cue, chalk stroke." - }, - { - "id": "EQ-023", - "name": "Rearcall Whistle", - "fit": "kit; reach 2+", - "class": "B", - "effect": "First legal attack selects rearmost enemy and reveals intervening screens.", - "tradeoff": "Attack deals no status on that use.", - "production": "I/O/V/S: flat whistle, rear line cue, captioned chirp." - }, - { - "id": "EQ-024", - "name": "Mercy Dial", - "fit": "charm; restore", - "class": "B", - "effect": "Healing prefers ally whose next incoming hit is earliest, not lowest vitality.", - "tradeoff": "Unforecast damage is ignored by the dial.", - "production": "I/V/S: rotating cross dial, timeline reticle, soft click." - }, - { - "id": "EQ-025", - "name": "Split-Choice Coin", - "fit": "charm; any", - "class": "B", - "effect": "On exact target-score tie, choose rearmost rather than random tie rule.", - "tradeoff": "After tie resolves, wearer becomes Exposed one tick.", - "production": "I/V/S: split coin, rear tie arrow, coin flip." - }, - { - "id": "EQ-026", - "name": "Summon Pricker", - "fit": "armament; any", - "class": "B", - "effect": "Basics prefer enemy footprints with independent actions.", - "tradeoff": "Against no footprint, basic recovery is one tick longer.", - "production": "I/O/V/S: short pricker, footprint reticle, dry prick." - }, - { - "id": "EQ-027", - "name": "Woundreader Tape", - "fit": "kit; pressure", - "class": "B", - "effect": "First basic targets enemy most recently effectively healed.", - "tradeoff": "Target outside reach causes default with no item benefit.", - "production": "I/O/V/S: marked tape, heal-history cue, tape pull." - }, - { - "id": "EQ-028", - "name": "Guard-Seam Pick", - "fit": "armament; any", - "class": "B", - "effect": "Basic prefers target currently protecting a neighbor.", - "tradeoff": "Damage cannot defeat the protector on that item-triggered hit.", - "production": "I/O/V/S: seam pick, guard-link reticle, metal pry." - }, - { - "id": "EQ-029", - "name": "Fastest-Shadow Ink", - "fit": "charm; impair", - "class": "B", - "effect": "Debuffs prefer enemy with shortest next-action time.", - "tradeoff": "Applied duration is one tick shorter.", - "production": "I/V/S: ink drop, short clock cue, quick brush." - }, - { - "id": "EQ-030", - "name": "Last-Target Knot", - "fit": "charm; any", - "class": "B", - "effect": "If prior target dies, next action follows its nearest living neighbor.", - "tradeoff": "If both neighbors equidistant, action fizzles.", - "production": "I/V/S: linked knot, neighbor branch cue, string snap." - }, - { - "id": "EQ-031", - "name": "Bodyguard Tag", - "fit": "kit; support", - "class": "B", - "effect": "Choose one neighbor in Prepare; enable actions prefer that unit while below half vitality.", - "tradeoff": "Other allies receive weaker enable duration.", - "production": "I/O/V/S: name-free tag, chosen outline, buckle tap." - }, - { - "id": "EQ-032", - "name": "Unmasking Powder", - "fit": "kit; line reach", - "class": "B", - "effect": "First hit on untargetable-window enemy becomes a reveal instead of damage.", - "tradeoff": "Powder is spent for battle even if reveal ends same tick.", - "production": "I/V/S: powder cone, open-eye cloud, soft burst." - }, - { - "id": "EQ-033", - "name": "Counterface Mirror", - "fit": "guard; front", - "class": "B", - "effect": "First forced-target effect aimed at wearer is reflected as Mark on source.", - "tradeoff": "Wearer remains the legal target for that action.", - "production": "I/O/V/S: small mirror guard, return-eye cue, glass ping." - }, - { - "id": "EQ-034", - "name": "Lowwater Gauge", - "fit": "charm; any", - "class": "B", - "effect": "At action ready, target lowest current local-resource enemy if legal.", - "tradeoff": "Enemy resource type is revealed to wearer only after first gain.", - "production": "I/V/S: shallow gauge, meter reticle, liquid tick." - }, - { - "id": "EQ-035", - "name": "Open-Flank String", - "fit": "armament; reach 2+", - "class": "B", - "effect": "Attacks prefer enemy with no living neighbor ahead.", - "tradeoff": "A new front neighbor invalidates reserve; action retargets foremost.", - "production": "I/O/V/S: fork string, empty-front cue, twang." - }, - { - "id": "EQ-036", - "name": "Twin Reticle", - "fit": "charm; area", - "class": "B", - "effect": "Area action centers between two adjacent legal targets rather than foremost.", - "tradeoff": "Single-target state prevents item trigger.", - "production": "I/V/S: paired circles, midpoint cue, double click." - }, - { - "id": "EQ-037", - "name": "Interrupt Ledger", - "fit": "kit; control", - "class": "B", - "effect": "After any ally interrupts, wearer targets that interrupted enemy next.", - "tradeoff": "If unreachable, wearer skips one basic rather than defaulting.", - "production": "I/O/V/S: slim ledger, interrupt tether, page tick." - }, - { - "id": "EQ-038", - "name": "Oldest-Harm Compass", - "fit": "charm; restore", - "class": "B", - "effect": "Cleanse targets ally with oldest active hindrance.", - "tradeoff": "Newer action-blocking hindrance is not prioritized.", - "production": "I/V/S: aged compass, duration rings, slow click." - }, - { - "id": "EQ-039", - "name": "Collapse Sight", - "fit": "armament; any", - "class": "B", - "effect": "Reserved target follows one deterministic enemy collapse.", - "tradeoff": "Second collapse before resolution causes fizzle.", - "production": "I/O/V/S: sliding sight, one-step tether, rail slide." - }, - { - "id": "EQ-040", - "name": "Choicebreaker Pin", - "fit": "charm; any", - "class": "B", - "effect": "First action with two equally legal target families alternates from previewed default.", - "tradeoff": "Target receives no critical effect.", - "production": "I/V/S: half-turn pin, alternate reticle, pin turn." - } + "PF-B03": [ + "reach_control", + "poise", + "pierce", + "move_cost" ], - "artifacts": [ - { - "id": "AR-001", - "name": "Open Ledger", - "rule": "Every shop shows one extra recruit, but equipment offer count is one lower.", - "tradeoff": "Conflicts AR-002; shop header displays exchanged slots before entry.", - "production": "I/V/S: open roster pages, slot exchange cue, page fan." - }, - { - "id": "AR-002", - "name": "Toolwright's Window", - "rule": "Every shop shows one extra equipment item, but recruit offer count is one lower.", - "tradeoff": "Conflicts AR-001; shop header previews composition.", - "production": "I/V/S: tool window icon, reverse slot cue, latch slide." - }, - { - "id": "AR-003", - "name": "Common Cause", - "rule": "First Common recruit of each shop costs one product-defined tier less; Uncommon/Rare costs one tier more.", - "tradeoff": "Conflicts AR-004; every price shows artifact delta.", - "production": "I/V/S: one-notch banner, price arrows, soft stamp." - }, - { - "id": "AR-004", - "name": "Narrow Search", - "rule": "Shops guarantee one Uncommon-or-Rare recruit but suppress one base-profession role weight.", - "tradeoff": "Conflicts AR-003; suppressed role is chosen and visible for the run.", - "production": "I/V/S: narrow lens, rarity/role exchange cue, glass close." - }, - { - "id": "AR-005", - "name": "Second Interview", - "rule": "Once per shop, reject one recruit to generate a new presentation with the same combat record.", - "tradeoff": "Conflicts AR-006; no power reroll, replacement signature is previewed.", - "production": "I/V/S: two portrait frames, locked-rule cue, card slide." - }, - { - "id": "AR-006", - "name": "Fresh Aptitude", - "rule": "Once per shop, reroll one recruit's trait while keeping profession, grades, level, gear, and cost tier.", - "tradeoff": "Conflicts AR-005; old/new benefit and cost compare before acceptance.", - "production": "I/V/S: rotating trait badge, before/after cue, token turn." - }, - { - "id": "AR-007", - "name": "Full Bench", - "rule": "Owned reserve capacity is higher, but every undeployed recruit adds a small shop upkeep tier.", - "tradeoff": "Conflicts AR-008; upkeep total is shown before buying.", - "production": "I/V/S: long bench, reserve-to-cost cue, bench creak." - }, - { - "id": "AR-008", - "name": "Lean Company", - "rule": "Reserve capacity is lower; after a death, next legal recruit is guaranteed within recovery cost tier.", - "tradeoff": "Conflicts AR-007; capacity and guarantee state remain on roster/shop.", - "production": "I/V/S: short bench, vacancy guarantee cue, firm knock." - } + "PF-B04": [ + "reorder", + "momentum", + "swap", + "push_protect" ], - "enemies": [ - { - "id": "EN-R1-01", - "name": "Pane Nibbler", - "class": "ordinary", - "rule": "Bites the foremost unit; after two uninterrupted bites, chips Guard from that unit's neighbor.", - "responses": "Reorder the neighbor; interrupt the second bite. Teaches adjacency protection.", - "production": "Third bite becomes a two-target arc. P/A/V/S: low glass-jaw, double-bite ticks, chipped-square cue, brittle clicks." - }, - { - "id": "EN-R1-02", - "name": "Gust Finch", - "class": "ordinary", - "rule": "From rear, telegraphs a projectile at the rearmost allied unit and steps forward after firing.", - "responses": "Use projectile screen; pull the shooter into short reach. Tests rear safety.", - "production": "Each blocked shot shortens its next step. P/A/V/S: fork-wing silhouette, perch/loose, feather-arrow cue, wind snap." - }, - { - "id": "EN-R1-03", - "name": "Terrace Ram", - "class": "ordinary", - "rule": "Headwind charge pushes the foremost allied unit one position rearward, targeting foremost only.", - "responses": "Brace against push; leave a durable replacement directly behind. Combines with collapse.", - "production": "Failed push grants it Guard. P/A/V/S: wedge-horn body, paw/charge, chevron lane cue, hoof rumble." - }, - { - "id": "EN-R1-04", - "name": "Bellstem Grazer", - "class": "ordinary", - "rule": "Eats the oldest positive tag on the foremost reachable ally instead of dealing damage.", - "responses": "Delay positive tag until after bite; expose an untagged front. Introduces dispel timing.", - "production": "Consumed tag makes next bite reach 2. P/A/V/S: tall stem-antler, sniff/pluck, hollow-plus cue, plucked chime." - }, - { - "id": "EN-R1-05", - "name": "Shardback", - "class": "ordinary", - "rule": "Reflects the first projectile each cadence to its source; reflection is previewed and cannot chain.", - "responses": "Use melee/spell first; spend a low-value projectile to open the screen.", - "production": "At half vitality, screen resets once. P/A/V/S: faceted shell, flare/turn, mirrored arrow cue, glass ping." - }, - { - "id": "EN-R1-06", - "name": "Verge Stalker", - "class": "ordinary", - "rule": "Targets the first ally with reach greater than 1; otherwise attacks foremost.", - "responses": "Equip short reach; Guard the identified long-reach unit. Teaches target-filter preview.", - "production": "Marked target becomes Exposed after two hits. P/A/V/S: long forelegs, scan/pounce, reach-ruler cue, scrape/hiss." - }, - { - "id": "EN-R1-07", - "name": "Whistle Midge", - "class": "ordinary", - "rule": "A swarm delays the next action of whichever allied band contains the most units.", - "responses": "Balance band occupancy; cleanse the visible delay. Interacts with order bands.", - "production": "Repeats on a band only after another band is hit. P/A/V/S: triangular swarm, orbit/dive, band-bracket cue, filtered whistle." - }, - { - "id": "EN-R1-08", - "name": "Glassroot Sprout", - "class": "ordinary", - "rule": "Starts as a rear footprint and grows one position forward each cadence; on reaching front, shields all enemies behind it.", - "responses": "Defeat it early at range; push it backward before growth.", - "production": "Growth cannot be interrupted twice consecutively. P/A/V/S: staged crystal plant, three growth poses, numbered sprout cue, rising cracks." - }, - { - "id": "EN-R1-09", - "name": "Crosswind Scribe", - "class": "ordinary", - "rule": "Draws a line between allied foremost and rearmost; next swap crossing the line becomes Hindered.", - "responses": "Avoid swapping that cadence; cleanse then use movement anyway.", - "production": "Uncrossed line converts to a weak area hit. P/A/V/S: kite-robed caster, draw/release, dotted span cue, chalk-wind stroke." - }, - { - "id": "EN-R1-10", - "name": "Ledge Hopper", - "class": "ordinary", - "rule": "Leaps over its own foremost neighbor to become front, then strikes the first ally whose target changed.", - "responses": "Hold targets stable; intercept the announced leap strike.", - "production": "If no target changed, Hopper becomes Exposed. P/A/V/S: spring-legged silhouette, vault/stab, target-turn cue, stone tap." - }, - { - "id": "EN-R1-11", - "name": "Gale Bailiff", - "class": "elite", - "rule": "Declares Stand Order on one indexed allied position; leaving it causes a line-wide push, remaining causes focused projectiles.", - "responses": "Choose a braced occupant; deliberately leave before ranged wind-up and absorb push.", - "production": "Alternates ordered index front/rear. P/A/V/S: tall banner coat, point/stamp, numbered pennant cue, gavel-gust event." - }, - { - "id": "EN-R1-12", - "name": "Prism Ox", - "class": "elite", - "rule": "Occupies two enemy footprints; frontal damage rotates its prism, changing whether it screens projectiles, spells, or pushes.", - "responses": "Rotate with a cheap legal hit; attack through the currently unscreened family.", - "production": "At low vitality rotation occurs every hit. P/A/V/S: two-footprint ox, heavy pivot, three-shape prism cue, bass glass rotation." - } + "PF-B05": [ + "projectile", + "aim", + "rear_target", + "line_stability" ], - "bosses": [ - { - "id": "BO-R1-01", - "name": "The Many-Paned Stag", - "class": "RG-01", - "rule": "Rotates three panes that screen projectile, spell, or forced movement; antler sweep pushes the current foremost after each rotation.", - "responses": "Rotate with cheap unscreened hit, then use open family; brace sweep and punish recovery. Failure: wrong family fed pane timing.", - "production": "Half vitality rotates two steps. P/A/V/S/M: two-footprint stag, pane-antler kit, tri-screen cue, glass-horn boss motif." - }, - { - "id": "BO-R1-02", - "name": "Gale Loom", - "class": "RG-01", - "rule": "Stationary wind engine inserts a gust footprint front-to-rear; attacks on its weak spindle reverse gust direction.", - "responses": "Break spindle to redirect gust through enemies; hold stable order and race core. Failure: uncontrolled gust exposed rear.", - "production": "At half vitality two gusts move from opposite ends. P/A/V/S/M: loom-engine, spindle/gust phases, numbered wind paths, mechanical gale stem." - } + "PF-B06": [ + "area", + "charge", + "echo", + "interrupt_cost" ], - "region": { - "id": "RG-01", - "name": "Windglass Verge", - "framing": "open grass terraces where suspended mineral panes redirect aether gusts. The guild charts a safe trade crossing.", - "pressure": "Introduces front-to-rear reach, basic protection, visible projectile screens, and simple pushes. Pressures the player to field a front holder without making one profession mandatory.", - "progression": "Two-threat tutorials become three-unit compositions; glass-wind hazard telegraphs a push one cadence ahead. Shops emphasize EQ-001..EQ-040; first clear opens all base profession shop weights.", - "assignment": "EN-R1-01..EN-R1-12; BO-R1-01..BO-R1-02; artifacts AR-001..AR-008.", - "production": "E/P/A/V/S/M: bright terrace layers, wind direction shapes, 12 silhouette kits, two boss scale kits, calm-to-gale music stem." - }, - "contracts": [ - { - "id": "CTR-R1-01", - "name": "First Windglass Crossing", - "region_id": "RG-01", - "ordinal": 1, - "round_count": 12, - "boss_id": "BO-R1-01", - "clear_fact": "CLEAR-RG01-C01", - "first_clear_renown": 14 - }, - { - "id": "CTR-R1-02", - "name": "Gale Loom Passage", - "region_id": "RG-01", - "ordinal": 2, - "round_count": 12, - "boss_id": "BO-R1-02", - "clear_fact": "CLEAR-RG01-C02", - "first_clear_renown": 18 - } + "PF-B07": [ + "restore", + "remedy", + "cleanse", + "no_overheal_gain" + ], + "PF-B08": [ + "enable", + "bond", + "neighbor_link", + "tag_variety" + ], + "PF-B09": [ + "construct", + "parts", + "device", + "salvage_on_move" + ], + "PF-B10": [ + "impair", + "omen", + "hinder", + "expiry_extend" + ], + "PF-B11": [ + "tempo", + "verse", + "ally_advance", + "enemy_delay" + ], + "PF-B12": [ + "supply", + "item_refresh", + "retreat_step", + "target_rotation" + ], + "PF-A01": [ + "front_hold", + "directional_intercept", + "guard_refund" + ], + "PF-A02": [ + "front_hold", + "guard_spend", + "push", + "follow_step" + ], + "PF-A03": [ + "single_pressure", + "edge_wager", + "execute", + "exposed_cost" + ], + "PF-A04": [ + "single_pressure", + "edge_reserve", + "block", + "return_cut" + ], + "PF-A05": [ + "reach_control", + "poise_spend", + "pull", + "enemy_reorder" + ], + "PF-A06": [ + "reach_control", + "poise_spend", + "line_pierce", + "declining_damage" + ], + "PF-A07": [ + "reorder", + "momentum_spend", + "swap", + "ally_advance" + ], + "PF-A08": [ + "reorder", + "sprint", + "multi_target", + "exposed_cost" + ], + "PF-A09": [ + "projectile", + "aim_spend", + "movement_delay", + "interrupt" + ], + "PF-A10": [ + "projectile", + "aim_spend", + "three_targets", + "no_repeat" + ], + "PF-A11": [ + "area", + "charge", + "cadence_chain", + "declining_damage" + ], + "PF-A12": [ + "area", + "charge_preserve", + "self_hinder", + "focused_release" + ], + "PF-A13": [ + "restore", + "remedy", + "heal", + "rear_swap" + ], + "PF-A14": [ + "restore", + "remedy_spend", + "delayed_heal", + "damage_bonus" + ], + "PF-A15": [ + "enable", + "bond", + "three_link", + "same_tag_fray" + ], + "PF-A16": [ + "enable", + "bond_gain", + "cost_transfer", + "self_burden" + ], + "PF-A17": [ + "construct", + "parts_spend", + "rear_device", + "repeat_target" + ], + "PF-A18": [ + "construct", + "parts_spend", + "position_trap", + "forced_move_trigger" + ], + "PF-A19": [ + "impair", + "omen", + "linked_expiry", + "cleanse_risk" + ], + "PF-A20": [ + "impair", + "consume_hindrance", + "burst", + "expose" + ], + "PF-A21": [ + "tempo", + "verse_spend", + "neighbor_sync", + "slowest_timing" + ], + "PF-A22": [ + "tempo", + "verse_spend", + "sound_interrupt", + "self_silence" + ], + "PF-A23": [ + "supply", + "enemy_sample", + "shop_counter", + "future_value" + ], + "PF-A24": [ + "supply", + "item_trigger_transfer", + "neighbor_only", + "item_stays" + ], + "TR-001": [ + "position_trigger", + "reach_bonus", + "foremost_filter_cost" + ], + "TR-002": [ + "action_count", + "resource_gain", + "opening_delay" + ], + "TR-003": [ + "neighbor_count", + "free_intercept", + "guard_transfer_lock" + ], + "TR-004": [ + "repeat_target", + "duration_bonus", + "retarget_delay" + ], + "TR-005": [ + "swap", + "carry_footprint", + "object_action_loss" + ], + "TR-006": [ + "forced_move", + "counterattack", + "self_exposed" + ], + "TR-007": [ + "consumable", + "duplicate_target", + "cooldown_cost" + ], + "TR-008": [ + "rear_cover", + "sound_immunity", + "resource_lock" + ], + "TR-009": [ + "pull_resist", + "movement_transfer", + "failure_hinder" + ], + "TR-010": [ + "healing_received", + "neighbor_splash", + "self_heal_cost" + ], + "TR-011": [ + "target_priority", + "survive_hit", + "focus_gain" + ], + "TR-012": [ + "ally_fallen", + "delayed_collapse", + "action_lock" + ], + "TR-013": [ + "basic_count", + "item_cooldown", + "resource_reset" + ], + "TR-014": [ + "interrupt_convert", + "delay", + "slower_resolve" + ], + "TR-015": [ + "enable_range", + "distant_receive", + "adjacent_lock" + ], + "TR-016": [ + "hindered_kill", + "resource_restore", + "plain_kill_cost" + ], + "TR-017": [ + "reserved_target", + "reach_validate", + "forced_fizzle" + ], + "TR-018": [ + "area_damage", + "action_advance", + "single_heal_cost" + ], + "TR-019": [ + "rear_intruder", + "auto_mark", + "forward_reach_cost" + ], + "TR-020": [ + "item_priority", + "first_trigger", + "technique_wait" + ], + "TR-021": [ + "swap_role", + "resource_gain", + "repeat_partner_hinder" + ], + "TR-022": [ + "same_attacker_count", + "guard_gain", + "mark_clear" + ], + "TR-023": [ + "action_advance", + "cadence_copy", + "repeat_advance_lock" + ], + "TR-024": [ + "cleanse", + "positive_tag_cost", + "resource_gain" + ], + "TR-025": [ + "survive_exposed", + "shop_discount", + "clear_on_safe_end" + ], + "TR-026": [ + "summon_pass", + "basic_reach", + "summon_no_damage" + ], + "TR-027": [ + "stationary", + "sound_uninterruptible", + "move_silence" + ], + "TR-028": [ + "self_hindrance", + "short_replace", + "second_extend" + ], + "TR-029": [ + "resource_share", + "extended_range", + "adjacent_exclusion" + ], + "TR-030": [ + "overpush", + "center_step", + "positive_tag_drop" + ], + "TR-031": [ + "enemy_windup", + "focus_gain", + "interrupt_reset" + ], + "TR-032": [ + "overkill", + "remedy_convert", + "execute_lock" + ], + "TR-033": [ + "equipment_mark", + "two_marks", + "profession_mark_cost" + ], + "TR-034": [ + "region_hazard", + "first_immunity", + "second_extend" + ], + "TR-035": [ + "empty_armament", + "alternate_target", + "equip_cost" + ], + "TR-036": [ + "lone_band", + "forecast", + "reach_bonus", + "concealment_lock" + ], + "EQ-001": [ + "first_direct_hit", + "rear_step", + "next_basic_delay" + ], + "EQ-002": [ + "first_push", + "push_resist", + "post_basic_hinder" + ], + "EQ-003": [ + "ally_swap", + "ally_advance", + "wearer_delay" + ], + "EQ-004": [ + "vacated_position", + "forward_step", + "guard_loss" + ], + "EQ-005": [ + "forced_move", + "movement_reduce", + "pace_cost" + ], + "EQ-006": [ + "rear_target", + "front_pull", + "next_action_delay" + ], + "EQ-007": [ + "position_change", + "trigger_reset", + "cooldown_cost" + ], + "EQ-008": [ + "stationary", + "poise_gain", + "move_reset" + ], + "EQ-009": [ + "ally_low_hp", + "rescue_step", + "self_delay" + ], + "EQ-010": [ + "front_entry", + "target_priority", + "guard_cost" + ], + "EQ-011": [ + "neighbor_hit", + "guard_share", + "pace_cost" + ], + "EQ-012": [ + "open_line", + "reach_bonus", + "guard_lock" + ], + "EQ-013": [ + "cross_swap", + "action_advance", + "self_hinder" + ], + "EQ-014": [ + "rear_position", + "projectile_resist", + "move_disable" + ], + "EQ-015": [ + "wearer_swap", + "position_trigger_refresh", + "partner_lock" + ], + "EQ-016": [ + "empty_position", + "cadence_advance", + "position_fill_reset" + ], + "EQ-017": [ + "guarded", + "pull_resist", + "guard_break_cost" + ], + "EQ-018": [ + "three_positions", + "item_refresh", + "repeat_reset" + ], + "EQ-019": [ + "stationary_count", + "guard_gain", + "forced_move_reset" + ], + "EQ-020": [ + "neighbor_move", + "safe_step", + "item_pause" + ], + "EQ-021": [ + "threat_preview", + "target_reveal", + "damage_cost" + ], + "EQ-022": [ + "foremost_mark", + "ally_focus", + "rear_exposed" + ], + "EQ-023": [ + "rear_threat", + "target_redirect", + "front_delay" + ], + "EQ-024": [ + "lowest_hp", + "heal_target", + "overheal_lock" + ], + "EQ-025": [ + "equal_targets", + "alternate_family", + "critical_lock" + ], + "EQ-026": [ + "summon", + "target_mark", + "owner_delay" + ], + "EQ-027": [ + "wounded_target", + "damage_bonus", + "healthy_cost" + ], + "EQ-028": [ + "guarded_enemy", + "guard_strip", + "raw_damage_cost" + ], + "EQ-029": [ + "fastest_enemy", + "hinder", + "slow_target_lock" + ], + "EQ-030": [ + "last_target", + "repeat_bonus", + "retarget_cost" + ], + "EQ-031": [ + "ally_targeted", + "intercept", + "self_exposed" + ], + "EQ-032": [ + "screened_enemy", + "screen_remove", + "damage_lock" + ], + "EQ-033": [ + "front_attack", + "reflect", + "rear_bypass" + ], + "EQ-034": [ + "low_resource", + "resource_gain", + "full_resource_delay" + ], + "EQ-035": [ + "open_flank", + "rear_target", + "foremost_damage_cost" + ], + "EQ-036": [ + "area_action", + "second_target", + "primary_damage_cost" + ], + "EQ-037": [ + "interrupt", + "cooldown_refresh", + "failed_interrupt_cost" + ], + "EQ-038": [ + "oldest_hindrance", + "cleanse", + "urgent_newer_cost" + ], + "EQ-039": [ + "enemy_collapse", + "reserved_target", + "double_collapse_fizzle" + ], + "EQ-040": [ + "equal_target_families", + "alternate_default", + "critical_lock" + ], + "EQ-041": [ + "guard", + "intercept", + "neighbor_protect" + ], + "EQ-042": [ + "guard", + "area_share", + "neighbor" + ], + "EQ-043": [ + "guard", + "transfer", + "expiry" + ], + "EQ-044": [ + "exposed", + "taunt", + "ally_cover" + ], + "EQ-045": [ + "guard", + "repeat_hit_resist", + "class_b" + ], + "EQ-046": [ + "heal", + "push_resist", + "protection" + ], + "EQ-047": [ + "guard", + "projectile_screen", + "opening" + ], + "EQ-048": [ + "guard", + "rear_origin_block", + "front_protect" + ], + "EQ-049": [ + "guard", + "copy", + "behind_guard" + ], + "EQ-050": [ + "exposed", + "action_advance", + "class_b" + ], + "EQ-051": [ + "intercept", + "no_resource_gain", + "class_b" + ], + "EQ-052": [ + "area_store", + "release_on_move", + "class_b" + ], + "EQ-053": [ + "guard", + "strip_convert", + "class_b" + ], + "EQ-054": [ + "screen", + "spell_screen", + "hit_counter" + ], + "EQ-055": [ + "guard", + "choose_side", + "protection_scope" + ], + "EQ-056": [ + "push_reflect", + "front", + "class_b" + ], + "EQ-057": [ + "projectile", + "survive_at_one", + "support" + ], + "EQ-058": [ + "guard", + "share", + "low_vitality_neighbor" + ], + "EQ-059": [ + "mark_convert", + "guard", + "cleanse" + ], + "EQ-060": [ + "guard", + "share_damage", + "one_neighbor" + ], + "EQ-061": [ + "tempo", + "earlier_tick", + "class_b" + ], + "EQ-062": [ + "windup", + "interrupt_survive", + "class_b" + ], + "EQ-063": [ + "defeat", + "recovery_clear", + "class_b" + ], + "EQ-064": [ + "tempo", + "resource_gain", + "neighbor" + ], + "EQ-065": [ + "burst", + "delayed", + "follow" + ], + "EQ-066": [ + "projectile", + "no_windup", + "class_b" + ], + "EQ-067": [ + "cooldown_refresh", + "midpoint", + "class_b" + ], + "EQ-068": [ + "action_advance", + "store_release", + "class_b" + ], + "EQ-069": [ + "hindrance", + "lengthen", + "patience" + ], + "EQ-070": [ + "interrupt", + "immediate_trigger", + "class_b" + ], + "EQ-071": [ + "cadence", + "opposite_band", + "choose_neighbor" + ], + "EQ-072": [ + "device", + "repeat", + "multi_target" + ], + "EQ-073": [ + "recovery", + "interrupted", + "boss" + ], + "EQ-074": [ + "cleanse", + "action_advance", + "class_b" + ], + "EQ-075": [ + "fizzle", + "fast_basic", + "class_b" + ], + "EQ-076": [ + "linked", + "windup", + "neighbor" + ], + "EQ-077": [ + "reorder", + "recovery_end", + "step" + ], + "EQ-078": [ + "boss", + "cooldown_pause", + "telegraph" + ], + "EQ-079": [ + "repeat", + "windup_shorten", + "class_b" + ], + "EQ-080": [ + "alternate", + "recovery_shorten", + "class_b" + ], + "AR-001": [ + "market_slots", + "recruit_plus", + "equipment_minus", + "conflict_ar002" + ], + "AR-002": [ + "market_slots", + "equipment_plus", + "recruit_minus", + "conflict_ar001" + ], + "AR-003": [ + "recruit_price", + "common_discount", + "rare_surcharge", + "conflict_ar004" + ], + "AR-004": [ + "recruit_weights", + "narrow_profession", + "other_surcharge", + "conflict_ar003" + ], + "AR-005": [ + "recruit_offer", + "extra_candidate", + "discard_one", + "conflict_ar006" + ], + "AR-006": [ + "trait_reroll", + "preserve_combat_fields", + "once_per_market", + "conflict_ar005" + ], + "AR-007": [ + "reserve_capacity", + "capacity_plus", + "upkeep_cost", + "conflict_ar008" + ], + "AR-008": [ + "reserve_capacity", + "capacity_minus", + "death_guarantee", + "conflict_ar007" + ], + "AR-009": [ + "duplicate_pairs", + "shop_composition", + "core_rule" + ], + "AR-010": [ + "distinct_professions", + "shop_composition", + "core_rule" + ], + "AR-011": [ + "deploy_minus_one", + "front_guard", + "core_rule" + ], + "AR-012": [ + "deploy_plus_one", + "core_cap", + "core_rule" + ], + "AR-013": [ + "endpoint_lock", + "center_free", + "core_rule" + ], + "AR-014": [ + "endpoint_swap_free", + "once_per_battle", + "core_rule" + ], + "AR-015": [ + "pair_partition", + "enable_guard_share", + "core_rule" + ], + "EN-R1-01": [ + "foremost_bite", + "repeat_count", + "neighbor_guard_chip" + ], + "EN-R1-02": [ + "rear_projectile", + "rearmost_target", + "forward_step" + ], + "EN-R1-03": [ + "headwind_charge", + "foremost_target", + "push" + ], + "EN-R1-04": [ + "positive_tag", + "consume_oldest", + "no_damage" + ], + "EN-R1-05": [ + "projectile_screen", + "first_reflect", + "no_chain" + ], + "EN-R1-06": [ + "reach_filter", + "long_reach_target", + "foremost_fallback" + ], + "EN-R1-07": [ + "band_count", + "majority_delay", + "order_pressure" + ], + "EN-R1-08": [ + "rear_growth", + "forward_step", + "rear_shield" + ], + "EN-R1-09": [ + "front_rear_line", + "swap_crossing", + "hinder" + ], + "EN-R1-10": [ + "enemy_reorder", + "leap_front", + "changed_target_strike" + ], + "EN-R1-11": [ + "elite", + "indexed_position", + "push_or_projectile" + ], + "EN-R1-12": [ + "elite", + "two_footprint", + "screen_rotation", + "damage_family" + ], + "EN-R2-01": [ + "pull", + "rear_intrusion", + "order_pressure" + ], + "EN-R2-02": [ + "attach", + "low_vitality", + "healing_denial" + ], + "EN-R2-03": [ + "copy", + "target_rule", + "misdirect" + ], + "EN-R2-04": [ + "position_mark", + "index_hazard", + "movement_punish" + ], + "EN-R2-05": [ + "sound_windup", + "command", + "enemy_order" + ], + "EN-R2-06": [ + "hindrance", + "cleanse_punish", + "escalate" + ], + "EN-R2-07": [ + "sidestep", + "front_to_rear", + "reposition" + ], + "EN-R2-08": [ + "defeat_seed", + "delayed", + "proximity" + ], + "EN-R2-09": [ + "restore", + "hindrance_oldest", + "enemy_support" + ], + "EN-R2-10": [ + "untargetable", + "melee_only", + "reveal_by_attack" + ], + "EN-R2-11": [ + "thread", + "heal_transfer", + "linked_positions" + ], + "EN-R2-12": [ + "safe_index", + "advance", + "hazard_shift" + ], + "BO-R1-01": [ + "boss", + "two_footprint", + "three_panes", + "sweep_push" + ], + "BO-R1-02": [ + "boss", + "stationary", + "gust_footprint", + "spindle_reverse" + ], + "BO-R2-01": [ + "safe_index", + "eggs", + "hatch_pullers" + ], + "BO-R2-02": [ + "sound_pulse", + "linked_positions", + "phase_shift" + ], + "RG-01": [ + "region", + "windglass", + "push", + "projectile_screen" + ], + "RG-02": [ + "pull", + "index_hazard", + "hindrance_expiry", + "healing_denial" + ], + "CTR-R1-01": [ + "contract", + "twelve_rounds", + "boss_bo_r1_01", + "clear_rg01_c01" + ], + "CTR-R1-02": [ + "contract", + "twelve_rounds", + "boss_bo_r1_02", + "clear_rg01_c02" + ], + "CTR-R2-01": [ + "region_two", + "boss_sluice", + "region_r2" + ], + "CTR-R2-02": [ + "region_two", + "boss_bell", + "region_r2" ] + }, + "professions": [ + { + "id": "PF-B01", + "name": "Hearthguard", + "family": "base", + "role": "front-hold; front", + "rules": "Shield Knock hits foremost reachable and Guard 1 neighbor. Guard gained when struck, spent to intercept one neighbor hit. Set Feet: cannot be pushed while Guarded, but steps slower.", + "cadence": "reach 1; steady: intercept lethal neighbor hit, refresh own Guard, then Knock nearest.", + "strategy": "Countered by rear pressure and Guard stripping; enables slow casters and adjacency items.", + "production": "P/O/A/V/S: broad shield overlay, brace/knock family, square amber guard cue, wood-metal block event." + }, + { + "id": "PF-B02", + "name": "Edgehand", + "family": "base", + "role": "single-pressure; front/center", + "rules": "Testing Cut hits nearest exposed enemy. Edge gained on alternating targets, spent to finish a marked target. No Waste: overflow damage becomes one Edge, not splash.", + "cadence": "reach 1; fast: finish legal low target, alternate target for Edge, then nearest exposed.", + "strategy": "Countered by decoys and heavy protection; likes marks and enemy reorder.", + "production": "P/O/A/V/S: paired short-blade overlay, alternating slash poses, chevron Edge cue, dry cut/finish pair." + }, + { + "id": "PF-B03", + "name": "Longspoke", + "family": "base", + "role": "reach-control; center", + "rules": "Measured Thrust strikes through one unit. Poise gained by attacking without moving, spent to extend reach or resist pull. Keep Distance: loses Poise after a step.", + "cadence": "reach 2; steady: interrupt approaching short-reach threat, pierce aligned front pair, then foremost.", + "strategy": "Countered by forced movement and rear divers; protects center damage through controlled reach.", + "production": "P/O/A/V/S: modular polearm, plant/thrust/pivot family, straight-line white cue, haft plant and air-cut events." + }, + { + "id": "PF-B04", + "name": "Wayrunner", + "family": "base", + "role": "reorder; any", + "rules": "Passing Strike swaps with the neighbor ahead after hitting. Momentum gained per distinct position visited, spent on a long step. Open Route: allies moved through are briefly unpushable.", + "cadence": "reach 1; fast: escape marked danger, fill exposed front gap, then cycle toward least-visited position.", + "strategy": "Countered by roots and narrow safe bands; enables position triggers and rescues fragile units.", + "production": "P/O/A/V/S: light hooked blade, run/brake/swap family, footprint cue, cloth rush and heel-stop events." + }, + { + "id": "PF-B05", + "name": "Farstring", + "family": "base", + "role": "projectile-pressure; rear", + "rules": "Draw Shot targets foremost legal enemy; each uninterrupted shot gains Aim, spent to select rearmost. Clear Arc: loses Aim when an ally is pulled behind the Farstring.", + "cadence": "line reach projectile; steady: spend Aim on dangerous rear caster, hit marked target, then foremost.", + "strategy": "Countered by projectile screens and rear intrusion; likes stable protection and target marks.", + "production": "P/O/A/V/S: bow family, draw/loose/reacquire, arc-and-notch cue, bow snap and distinct blocked shot." + }, + { + "id": "PF-B06", + "name": "Riftcaller", + "family": "base", + "role": "area-pressure; rear", + "rules": "Rift Spark hits foremost and echoes lightly to its neighbor. Charge gained while wind-up completes, spent to widen an area. Unstable Weave: interruption clears Charge.", + "cadence": "line spell reach; slow: widen against clustered pair, punish summoned unit, then Spark highest cluster value.", + "strategy": "Countered by interrupts and forced retarget; likes time protection and grouping effects.", + "production": "P/O/A/V/S: focus rod, two-stage channel/cast, forked diamond cue, rising tone with captioned discharge." + }, + { + "id": "PF-B07", + "name": "Kindhand", + "family": "base", + "role": "restore; center/rear", + "rules": "Tending Pulse heals lowest-vitality reachable ally. Remedy gained when healing actual loss, spent to cleanse one hindrance. Honest Care: overheal grants no Remedy.", + "cadence": "reach 2 allied; steady: prevent lethal forecast, cleanse action-blocking status, then heal lowest ratio.", + "strategy": "Countered by split pressure and heal denial; likes Guard, shared damage, and predictable cadence.", + "production": "P/O/A/V/S: satchel and hand-lamp, assess/tend/cleanse, open-circle cue, soft chime plus captioned cleanse." + }, + { + "id": "PF-B08", + "name": "Bondwright", + "family": "base", + "role": "enable; center", + "rules": "Joining Thread lightly damages an enemy and links the caster to one neighbor. Bond gained when linked units use different action tags, spent to share Guard or Remedy. Tension: same-tag repeats fray the link.", + "cadence": "reach 2; steady: repair broken useful link, share resource before threshold, then hit linked enemy.", + "strategy": "Countered by isolation and repetitive parties; amplifies mixed roles and alternating cadences.", + "production": "P/O/A/V/S: knot-frame focus, cast/tie/release, braided line cue with shape dashes, two-tone pluck event." + }, + { + "id": "PF-B09", + "name": "Coilsmith", + "family": "base", + "role": "construct; center/rear", + "rules": "Tool Tap attacks nearest threat. Parts gained every second basic, spent to deploy a one-footprint device behind self. Pack Up: stepping salvages half unspent Parts but removes device.", + "cadence": "reach 1 basic; steady: replace destroyed device, trigger safe utility, then Tap nearest device threat.", + "strategy": "Countered by rear compression and device hunters; likes stable order and Parts equipment.", + "production": "P/O/A/V/S: tool rig and compact device kit, place/tune/pack, cog-square cue, ratchet and device-state events." + }, + { + "id": "PF-B10", + "name": "Gloamreader", + "family": "base", + "role": "impair; rear", + "rules": "Dimming Word damages the enemy with highest current cadence and applies Hindered. Omen gained when a debuff expires, spent to extend a different debuff. No Echo: cannot extend own same tag twice.", + "cadence": "line spell reach; slow: stop imminent major action, extend ally-applied status, then dim fastest enemy.", + "strategy": "Countered by cleanse and staggered immunity; likes varied statuses and long fights.", + "production": "P/O/A/V/S: tablet veil, inscribe/close/release, broken-ring cue, whispered consonant texture with captions." + }, + { + "id": "PF-B11", + "name": "Bellkeeper", + "family": "base", + "role": "tempo; center", + "rules": "Time Bell strikes nearest and advances the slower neighbor's next basic. Verse gained when allies act in three different cadence bands, spent to delay an enemy wind-up. Offbeat: repeated band gives no Verse.", + "cadence": "reach 1/line delay; steady: delay major action, advance restorative neighbor, then strike nearest.", + "strategy": "Countered by silence and uniform-speed builds; enables slow wind-ups and cadence alternation.", + "production": "P/O/A/V/S: handbell frame, strike/listen/cutoff, three-bar cue, pitch-coded but fully captioned bell events." + }, + { + "id": "PF-B12", + "name": "Packwarden", + "family": "base", + "role": "supply; center/rear", + "rules": "Rationed Blow hits nearest; every third basic creates one Supply, spent to refresh a neighbor's item cooldown or grant a safe retreat step. Inventory Rule: cannot target self twice in succession.", + "cadence": "reach 1; steady: enable urgent item, move endangered rearward neighbor, then build Supply.", + "strategy": "Countered by item lock and isolation; supports proc gear and replaceable front lines.", + "production": "P/O/A/V/S: pack-frame and tally tags, handoff/strike/guide, stacked-tab cue, buckle and counted click events." + }, + { + "id": "PF-A01", + "name": "Gatebearer", + "family": "advanced", + "role": "PF-B01; interception becomes directional", + "rules": "front-hold/front; Gate Arc intercepts only attacks crossing the bearer, refunding Guard when prediction is correct.", + "cadence": "reach 1; reserve Guard for crossing attacks, then protect neighbor behind.", + "strategy": "Rear-origin attacks bypass it; superb with fragile unit directly behind. Opposes PF-A02 aggression.", + "production": "O/A/V/S: tall gate shield, swing-open brace, doorway cue, resonant latch block." + }, + { + "id": "PF-A02", + "name": "Ramward", + "family": "advanced", + "role": "PF-B01; Guard becomes forward displacement", + "rules": "control/front; Driving Wall spends Guard to push foremost enemy and follow into the new front.", + "cadence": "reach 1; push channeling or short-reach threat, otherwise maintain Guard.", + "strategy": "Immovable enemies waste its conversion; groups enemies for area roles. Opposes PF-A01 protection.", + "production": "O/A/V/S: convex shield, two-step drive, wedge cue, shove impact layers." + }, + { + "id": "PF-A03", + "name": "Lastlight", + "family": "advanced", + "role": "PF-B02; Edge wagers on execution", + "rules": "finish/front/center; Final Measure reserves all Edge; kill refunds half, survival leaves user Exposed.", + "cadence": "reach 1; wager only when visible forecast crosses defeat threshold.", + "strategy": "Barriers and death prevention punish wager; marks improve forecast. Opposes PF-A04 patience.", + "production": "O/A/V/S: narrow dueling blade, held finish pose, closing-line cue, silence-then-hit event." + }, + { + "id": "PF-A04", + "name": "Turnblade", + "family": "advanced", + "role": "PF-B02; Edge becomes a deterministic counter", + "rules": "pressure/front; Turn Aside reserves one Edge as an explicit Block charge against the next eligible direct component targeting self. Trigger TURNBLADE_RETURN declares observed_role=actual_recipient, outcome_ids=AO_BLOCKED, component_tags=direct+block_eligible, once_scope=action, and allow_derived=false; it queues Return Cut against the source next tick.", + "cadence": "reach 2 counter; reserve Edge only for a telegraphed Block-eligible component due before the next basic; fire Return Cut only while its source remains legal and reachable, otherwise log the suppressed source reason.", + "strategy": "Unblockable/area components and sources beyond reach deny the return; readable single-target telegraphs and reach control enable it. Opposes PF-A03 execution.", + "production": "O/A/V/S: guard-ring blade, brace/return family, AO_BLOCKED square-to-arrow cue, parry scrape and return cut." + }, + { + "id": "PF-A05", + "name": "Hookmarshal", + "family": "advanced", + "role": "PF-B03; Poise controls enemy order", + "rules": "reach-control/center; Crook Pull spends Poise to pull a reach-legal enemy one position forward.", + "cadence": "reach 2; pull protected caster when counterline can absorb it, then thrust.", + "strategy": "Pull immunity and front traps punish it; empowers Edgehand and short reach. Opposes PF-A06 static damage.", + "production": "O/A/V/S: hooked polearm, catch/haul, hooked-line cue, chainless wood-and-cloth pull." + }, + { + "id": "PF-A06", + "name": "Sunlance", + "family": "advanced", + "role": "PF-B03; Poise becomes piercing setup", + "rules": "pressure/center; Noon Line spends all Poise to pierce every intervening enemy with declining effect.", + "cadence": "line reach; fire only through two or more legal targets, else build Poise.", + "strategy": "Spacing and forced movement collapse value; grouping and stable front enable it. Opposes PF-A05 reorder.", + "production": "O/A/V/S: long bright spear, extended plant/cast, straight sunbar cue, layered pass-through hits." + }, + { + "id": "PF-A07", + "name": "Relayknife", + "family": "advanced", + "role": "PF-B04; movement hands timing to allies", + "rules": "enable/any; Pass The Turn spends Momentum on swap, advancing the swapped ally's basic while delaying own.", + "cadence": "adjacent ally; rescue endangered unit or advance a ready counter before cycling.", + "strategy": "Roots or selfish carry builds reduce value; slow high-impact basics benefit. Opposes PF-A08 self-routing.", + "production": "O/A/V/S: baton knife, tap-and-pass motion, paired footprints, handoff click." + }, + { + "id": "PF-A08", + "name": "Backdraft", + "family": "advanced", + "role": "PF-B04; full-line route becomes attack", + "rules": "reorder-pressure/any; Wake Cut damages each unique enemy passed while sprinting rear to front, then becomes Exposed.", + "cadence": "line traversal only when path is open; retreat after wake if threatened.", + "strategy": "Occupied path and focus fire punish it; Guard and pull setup open routes. Opposes PF-A07 ally timing.", + "production": "O/A/V/S: scarf blade, full-line sprint, tapered wake cue, sequential rush hits." + }, + { + "id": "PF-A09", + "name": "Pinseeker", + "family": "advanced", + "role": "PF-B05; Aim selects action parts", + "rules": "precision/rear; Joint Pin spends Aim to delay a telegraphed movement or interruptible wind-up instead of dealing full damage.", + "cadence": "line projectile; pin highest-impact legal telegraph, otherwise preserve Aim.", + "strategy": "Screens and uninterruptible actions counter it; Bellkeeper extends pin windows. Opposes PF-A10 coverage.", + "production": "O/A/V/S: pin bow, low draw, joint-cross cue, taut snap with delayed thunk." + }, + { + "id": "PF-A10", + "name": "Rainbow", + "family": "advanced", + "role": "PF-B05; Aim becomes distributed volley", + "rules": "area/rear; Falling String spends Aim to strike three distinct legal targets; cannot repeat one target.", + "cadence": "line projectile; volley at three targets, otherwise basic to build Aim.", + "strategy": "Single durable enemy wastes breadth; debuff-on-hit gear multiplies it. Opposes PF-A09 precision.", + "production": "O/A/V/S: split-limb bow, upward release, three-notch cue, staggered arrow rain." + }, + { + "id": "PF-A11", + "name": "Stormledger", + "family": "advanced", + "role": "PF-B06; Charge chains by cadence", + "rules": "area/rear; Rolling Sum jumps to the next enemy whose action is later, losing power each jump.", + "cadence": "line spell; start on fastest enemy when at least two later targets exist.", + "strategy": "Uniform timing and isolation limit chain; tempo delays reshape the sequence. Opposes PF-A12 focused channel.", + "production": "O/A/V/S: page-rod, sequential cast, numbered bolt cue, pitch-descending chain." + }, + { + "id": "PF-A12", + "name": "Hollowstar", + "family": "advanced", + "role": "PF-B06; Charge is stored through interruption at a cost", + "rules": "pressure/rear; Held Rift preserves Charge when interrupted but applies self Hindered; release focuses one target.", + "cadence": "line spell; retain through one interruption, release at marked high-threat enemy.", + "strategy": "Repeated interrupts compound delay; Guard and cleanse protect payoff. Opposes PF-A11 distributed chain.", + "production": "O/A/V/S: dark aperture focus, brace/release, hollow diamond cue, muted charge then bass impact." + }, + { + "id": "PF-A13", + "name": "Fieldmender", + "family": "advanced", + "role": "PF-B07; Remedy follows moving patients", + "rules": "restore/center; Walking Triage heals a target then swaps it one step rearward if the space relation is legal.", + "cadence": "allied reach 2; extract lowest-vitality exposed ally, otherwise cleanse.", + "strategy": "Reorder locks and rear pressure counter extraction; front replacements benefit. Opposes PF-A14 delayed care.", + "production": "O/A/V/S: sling lamp, support-and-step, rear arrow in circle, bandage rustle and step." + }, + { + "id": "PF-A14", + "name": "Aftercare", + "family": "advanced", + "role": "PF-B07; Remedy schedules future healing", + "rules": "restore/rear; Promise Dose spends Remedy now to heal after target's next action, stronger if it takes damage first.", + "cadence": "allied reach line; schedule on forecast target, never duplicate an active promise.", + "strategy": "Burst before trigger defeats it; predictable tanks and action acceleration enable it. Opposes PF-A13 immediate extraction.", + "production": "O/A/V/S: sealed vial lamp, tag-and-release, hourglass-cross cue, delayed glass chime." + }, + { + "id": "PF-A15", + "name": "Chainhost", + "family": "advanced", + "role": "PF-B08; one Bond links three different roles", + "rules": "enable/center; Third Knot extends a link through both neighbors; any same-tag repeat frays both sides.", + "cadence": "adjacent network; build only with three distinct action tags, share to weakest side.", + "strategy": "Displacement and role duplication break it; mixed cadence lines gain coverage. Opposes PF-A16 pair focus.", + "production": "O/A/V/S: triple knot frame, three-hand sweep, tri-braid cue, three-note pluck." + }, + { + "id": "PF-A16", + "name": "Burdenmate", + "family": "advanced", + "role": "PF-B08; Bond transfers a chosen cost", + "rules": "protect/center; Take The Clause moves one visible hindrance or item cooldown from neighbor to self, gaining Bond.", + "cadence": "neighbor only; take action-blocking cost if self can still act, then share resource.", + "strategy": "Isolation and nontransferable boss tags stop it; cleanse and self-cost equipment enable it. Opposes PF-A15 network breadth.", + "production": "O/A/V/S: split knot frame, pull-to-chest, divided-circle cue, tension snap." + }, + { + "id": "PF-A17", + "name": "Sentrymaker", + "family": "advanced", + "role": "PF-B09; Parts create rear projectile device", + "rules": "construct/rear; Needle Post occupies one footprint behind user and shoots user's last basic target.", + "cadence": "mirrored line reach; deploy only with rear room, retune when target invalid.", + "strategy": "Rear compression/device hunters remove it; marks stabilize target. Opposes PF-A18 frontline trap.", + "production": "O/A/V/S: fold-out post, plant/aim/fold, tripod cue, clockwork shot events." + }, + { + "id": "PF-A18", + "name": "Snarewright", + "family": "advanced", + "role": "PF-B09; Parts arm a vacated position", + "rules": "control/center; Latch Tile triggers when an enemy is pushed or pulled across its indexed line position.", + "cadence": "position target; arm ahead of predicted reorder, otherwise conserve Parts.", + "strategy": "No displacement means dormant value; Hookmarshal and Ramward trigger it. Opposes PF-A17 repeated shots.", + "production": "O/A/V/S: flat latch kit, place/arm/spring, toothed-square cue, clamp event." + }, + { + "id": "PF-A19", + "name": "Paleoracle", + "family": "advanced", + "role": "PF-B10; Omen copies expiry timing", + "rules": "impair/rear; Borrowed End makes a different hindrance expire when the copied source expires, sharing cleanse risk.", + "cadence": "line spell; pair long ally status with urgent enemy status.", + "strategy": "Cleanse removes both schedule value; varied debuff durations enable planning. Opposes PF-A20 consumption.", + "production": "O/A/V/S: twin tablet, cross-inscribe, linked broken rings, paired whisper cutoff." + }, + { + "id": "PF-A20", + "name": "Dreadtaster", + "family": "advanced", + "role": "PF-B10; consumes hindrance for burst", + "rules": "pressure/rear; Bitter Proof removes one enemy hindrance to damage and expose it based on remaining duration.", + "cadence": "line spell; consume just before cleanse or kill window, never erase needed control early.", + "strategy": "Premature use wastes setup; many short debuffs and finishers enable it. Opposes PF-A19 duration control.", + "production": "O/A/V/S: tasting needle and veil, draw/strike, collapsing ring cue, inhale-to-crack event." + }, + { + "id": "PF-A21", + "name": "Marchbell", + "family": "advanced", + "role": "PF-B11; Verse synchronizes basics", + "rules": "tempo/center; Common Step spends Verse so both neighbors' next basics resolve together after the slower one.", + "cadence": "neighbors; synchronize complementary basics, avoid simultaneous overkill.", + "strategy": "Area interrupts and redundant targets punish sync; combo tags and shared target rules benefit. Opposes PF-A22 enemy denial.", + "production": "O/A/V/S: broad bell, two-side conducting, paired bars cue, merged beat event." + }, + { + "id": "PF-A22", + "name": "Quietus", + "family": "advanced", + "role": "PF-B11; Verse cancels sound-tagged wind-ups", + "rules": "control/center/rear; Held Clapper spends all Verse to interrupt one sound or command telegraph, then self-silences.", + "cadence": "line control; reserve for preview-tagged major action, otherwise advance ally.", + "strategy": "Non-sound threats bypass it; forecast and cleanse support the self-cost. Opposes PF-A21 ally acceleration.", + "production": "O/A/V/S: wrapped bell, catch-clapper pose, crossed-wave cue, sudden mix duck with caption." + }, + { + "id": "PF-A23", + "name": "Trailfactor", + "family": "advanced", + "role": "PF-B12; Supply alters post-battle offers", + "rules": "supply/rear; Field Sample marks first defeated enemy family; post-battle shop guarantees one matching counter tag, spending stored Supply.", + "cadence": "battle reach 1; sample novel threat, use Supply on urgent item refresh.", + "strategy": "Repeated familiar enemies lower value; diverse routes and missing counter gear reward it. Opposes PF-A24 immediate line logistics.", + "production": "O/A/V/S: sample pack, tag/scoop/ledger, specimen-tab cue, cork and pencil events." + }, + { + "id": "PF-A24", + "name": "Lineporter", + "family": "advanced", + "role": "PF-B12; Supply moves equipment triggers", + "rules": "enable/center; Hand Carry transfers one unused once-per-battle item trigger to a legal neighbor; item itself stays equipped.", + "cadence": "neighbor only; move a trigger to the unit with legal target, else retreat-step endangered ally.", + "strategy": "Bound/self-only items cannot transfer; proc gear and mixed eligibility benefit. Opposes PF-A23 future offers.", + "production": "O/A/V/S: open-frame pack, pass token, hand-arrow cue, clasp-release event." + } + ], + "traits": [ + { + "id": "TR-001", + "name": "First Through", + "benefit": "First action after becoming foremost gains reach +1.", + "cost": "That action cannot target the current foremost enemy.", + "evidence": "Front-arrow badge pulses; result names chosen farther target.", + "prohibited": "PF-B01, PF-A01, PF-H01; self-only target actions." + }, + { + "id": "TR-002", + "name": "Patient Start", + "benefit": "After two allied actions resolve, first basic gains one local resource.", + "cost": "Starts battle with its first basic delayed one cadence band.", + "evidence": "Two-notch timer under portrait; report shows delayed and paid tick.", + "prohibited": "PF-A21; opening-action equipment." + }, + { + "id": "TR-003", + "name": "Narrow Guard", + "benefit": "When only one neighbor exists, intercepts one non-area hit without spending resource.", + "cost": "While two neighbors exist, cannot receive transferred Guard.", + "evidence": "One/two-neighbor shield icon; intercept has trait source label.", + "prohibited": "PF-A15; forced-intercept equipment." + }, + { + "id": "TR-004", + "name": "Long Memory", + "benefit": "Repeating the last legal target increases status duration by one tick.", + "cost": "Changing target delays the next action by one tick.", + "evidence": "Prior-target ring and tick preview; report groups repeats/switches.", + "prohibited": "Target-randomizing equipment; PF-A10." + }, + { + "id": "TR-005", + "name": "Spare Hand", + "benefit": "May carry one construct or summon footprint through a swap.", + "cost": "Carried object loses its next automatic action.", + "evidence": "Object attaches visibly during swap; result logs skipped action.", + "prohibited": "PF-H04; immovable-device items." + }, + { + "id": "TR-006", + "name": "Open Stance", + "benefit": "After a push or pull, immediately basic-attacks the mover if reachable.", + "cost": "Becomes Exposed until that basic resolves.", + "evidence": "Bent-arrow badge and exposed outline; counter target logged.", + "prohibited": "Displacement immunity equipment; PF-A04." + }, + { + "id": "TR-007", + "name": "Deep Pocket", + "benefit": "First consumable equipment trigger each battle is preserved for a second legal target.", + "cost": "Both uses have one longer cooldown band.", + "evidence": "Two-charge item pip and extended clock; results split both uses.", + "prohibited": "PF-A24; single-use wager items." + }, + { + "id": "TR-008", + "name": "Quiet Hands", + "benefit": "Cannot be targeted by sound-tagged enemy actions while behind two allies.", + "cost": "Own sound-tagged actions generate no local resource.", + "evidence": "Muted-wave badge; rejected targets explain depth condition.", + "prohibited": "PF-A22; PF-B11." + }, + { + "id": "TR-009", + "name": "Counterweight", + "benefit": "Resists first pull and transfers its movement to the neighbor ahead.", + "cost": "If that neighbor cannot move, both become Hindered.", + "evidence": "Linked weight icon; preview shows legal recipient; failure logs blockage.", + "prohibited": "PF-B01; movement-lock equipment." + }, + { + "id": "TR-010", + "name": "Shared Cup", + "benefit": "Effective healing received also restores a smaller amount to lowest-vitality neighbor.", + "cost": "Direct self-healing is reduced.", + "evidence": "Cup arc identifies neighbor before resolution; result separates shared heal.", + "prohibited": "PF-H01; no-neighbor formation starts." + }, + { + "id": "TR-011", + "name": "Bright Target", + "benefit": "Enemies prefer this recruit over equal-reach candidates.", + "cost": "Each attack survived grants one Focus, capped until the recruit moves.", + "evidence": "Visible threat flare and Focus pips; report cites target-score source.", + "prohibited": "Untargetable-opening items; PF-A08." + }, + { + "id": "TR-012", + "name": "Late Step", + "benefit": "When neighbor ahead dies, steps into the gap only after completing current wind-up.", + "cost": "Cannot act during the subsequent collapse tick.", + "evidence": "Pending-step ghost silhouette; result shows preserved cast and lost tick.", + "prohibited": "Instant-collapse equipment; no-wind-up actions." + }, + { + "id": "TR-013", + "name": "Frugal Rhythm", + "benefit": "Every third basic that spends no resource shortens one equipment cooldown.", + "cost": "Resource-spending actions reset the three-basic count.", + "evidence": "Three-bead counter; reset and refresh named in results.", + "prohibited": "PF-B12; equipment without cooldowns." + }, + { + "id": "TR-014", + "name": "Heavy Sleeve", + "benefit": "First interrupt becomes a delay instead of cancellation.", + "cost": "Wind-up after the delay resolves one cadence band slower.", + "evidence": "Sleeve bar catches interrupt cue; report contrasts old/new completion tick.", + "prohibited": "Uninterruptible-cast equipment; PF-A12." + }, + { + "id": "TR-015", + "name": "Far Listener", + "benefit": "May receive enable actions from one additional position away.", + "cost": "Cannot receive them from an immediate neighbor while at full vitality.", + "evidence": "Distant-ear arc and rejected-neighbor reason.", + "prohibited": "PF-A15; adjacency-only bond equipment." + }, + { + "id": "TR-016", + "name": "Ash Appetite", + "benefit": "Defeating a Hindered enemy restores one local resource.", + "cost": "Defeating an unhindered enemy removes one local resource if available.", + "evidence": "Status-check flame icon; both gain/loss events captioned.", + "prohibited": "Professions with no local resource; status-immunity items." + }, + { + "id": "TR-017", + "name": "Exacting", + "benefit": "The first action each battle gains +1 reach only for completion validation against its reserved target; movement that remains inside that reach follows the same unit ID and records TARGET_MOVED_STILL_LEGAL on its terminal outcome.", + "cost": "If that reservation instead emits AO_INVALID_REACH, AO_INVALID_UNTARGETABLE, AO_INVALID_FALLEN, or AO_INVALID_FILTER, its policy is forced to fizzle, then emits AO_FIZZLED with zero resource or cooldown refund; it cannot retarget or ground-resolve.", + "evidence": "Reservation frame shows the one-use extended reach; results show the invalid record, AO_FIZZLED, or the followed-target detail and accepted wager.", + "prohibited": "Ground-resolve equipment; PF-A10." + }, + { + "id": "TR-018", + "name": "Thin Blood", + "benefit": "Taking area damage advances next action by one tick.", + "cost": "Direct single-target healing is less effective.", + "evidence": "Area-hit speed notch and cracked-drop badge; report quantifies lost care.", + "prohibited": "Area immunity equipment; PF-B07." + }, + { + "id": "TR-019", + "name": "Backwatch", + "benefit": "First enemy entering the allied rear is immediately Marked.", + "cost": "While no rear intruder exists, recruit's forward reach is reduced by one.", + "evidence": "Rear eye badge; reach preview updates; intrusion mark logged.", + "prohibited": "PF-B05; reach-1 professions." + }, + { + "id": "TR-020", + "name": "Oathbound Tool", + "benefit": "The Recruit's equipped item's first trigger gains priority over its profession action.", + "cost": "Until that trigger resolves or expires, the Recruit's first technique waits.", + "evidence": "One-slot oath ribbon and queue state; delayed technique has a reason.", + "prohibited": "Triggerless equipment; PF-A24." + }, + { + "id": "TR-021", + "name": "Loose Formation", + "benefit": "Gains one local resource after swapping with a different profession role.", + "cost": "Repeating a swap partner applies Hindered to self.", + "evidence": "Partner-role bead track; repeat warning in formation preview.", + "prohibited": "PF-H04; swap-lock equipment." + }, + { + "id": "TR-022", + "name": "Stone Count", + "benefit": "Third hit from the same enemy grants Guard and clears its Mark.", + "cost": "Changing attackers resets count.", + "evidence": "Attacker portrait with three stones; reset shown on target change.", + "prohibited": "Forced-target equipment; PF-A01." + }, + { + "id": "TR-023", + "name": "Borrowed Pace", + "benefit": "After receiving an action advance, copies the giver's cadence for one basic.", + "cost": "If copied cadence is slower, cannot be advanced again until that basic.", + "evidence": "Donor-color-independent bar shape; report records copied band.", + "prohibited": "PF-A21; cadence-fixed equipment." + }, + { + "id": "TR-024", + "name": "Clean Break", + "benefit": "First cleanse also removes the recruit's oldest positive tag and grants local resource.", + "cost": "If no positive tag exists, cleanse takes one extra tick.", + "evidence": "Paired plus/minus token; tooltip previews removed tag.", + "prohibited": "PF-A16; positive-tag-lock items." + }, + { + "id": "TR-025", + "name": "Risk Ledger", + "benefit": "Surviving while Exposed adds one discount step to this recruit's next shop equipment purchase.", + "cost": "Ending battle never Exposed clears all accumulated steps.", + "evidence": "Persistent personal tally visible in roster/shop; result states kept/cleared.", + "prohibited": "Exposed-immunity equipment; retreat-only encounters." + }, + { + "id": "TR-026", + "name": "Hollow Reach", + "benefit": "Basics may pass through a summoned footprint.", + "cost": "Basics deal no effect to summoned units.", + "evidence": "Hollow projectile/line cue; illegal summon target reason.", + "prohibited": "Summon-hunter equipment; PF-A17." + }, + { + "id": "TR-027", + "name": "Rooted Voice", + "benefit": "Sound and command actions cannot be interrupted while recruit has not moved.", + "cost": "Moving self-silences until next basic resolves.", + "evidence": "Rooted waveform icon; movement preview shows silence interval.", + "prohibited": "PF-B04, PF-A08, PF-H04." + }, + { + "id": "TR-028", + "name": "Second Draft", + "benefit": "First self-applied hindrance is replaced by Hindered of shorter duration.", + "cost": "Second self-applied hindrance lasts one tick longer.", + "evidence": "Two-page counter; replacement/extension source is explicit.", + "prohibited": "Self-cost-free equipment; PF-A16." + }, + { + "id": "TR-029", + "name": "Open Palm", + "benefit": "Resource-sharing actions can target through one intervening ally.", + "cost": "Sharing with a distant ally gives nothing to immediate neighbors that cadence.", + "evidence": "Straight handoff line; opportunity cost shown on neighbor pips.", + "prohibited": "PF-H06; no-resource professions." + }, + { + "id": "TR-030", + "name": "Turncoat Buckle", + "benefit": "When pushed behind the current rearmost position, immediately steps to center.", + "cost": "Drops one active positive equipment tag during the step.", + "evidence": "Buckle opens and center ghost appears; dropped tag named in log.", + "prohibited": "Position-lock equipment; PF-A08." + }, + { + "id": "TR-031", + "name": "Storm Nerve", + "benefit": "Each completed enemy major wind-up grants Focus.", + "cost": "An interrupted enemy major action removes all stored Focus.", + "evidence": "Major-action rings feed Focus pips; interruption loss bursts visibly.", + "prohibited": "Focusless professions; PF-A09." + }, + { + "id": "TR-032", + "name": "Gentle Finish", + "benefit": "Overkill is converted into Remedy for the lowest-vitality ally.", + "cost": "Cannot benefit from execution-threshold effects.", + "evidence": "Overkill trail bends to ally; threshold rejection is explicit.", + "prohibited": "PF-A03; execution-tag equipment." + }, + { + "id": "TR-033", + "name": "Split Attention", + "benefit": "Can maintain two Marks placed by equipment.", + "cost": "Profession-applied Marks last one tick less.", + "evidence": "Two half-eye pips; source-specific duration in inspect.", + "prohibited": "Equipment set without Marks; markless professions." + }, + { + "id": "TR-034", + "name": "Weathered", + "benefit": "First region hazard each battle has no effect.", + "cost": "Second hazard of a different tag has extended duration.", + "evidence": "Region hazard shield with empty/filled halves; result cites both tags.", + "prohibited": "Hazard-immunity equipment; single-hazard challenge rules." + }, + { + "id": "TR-035", + "name": "Empty Scabbard", + "benefit": "With no armament-tag item, basic gains a profession-specific alternate target rule.", + "cost": "Equipping an armament disables the trait and adds one cost tier to that shop action.", + "evidence": "Empty-slot target preview and explicit disabled state.", + "prohibited": "Required-armament professions; bound armament items." + }, + { + "id": "TR-036", + "name": "Last Witness", + "benefit": "When the only living member in a line band, reveals next enemy target and gains reach +1.", + "cost": "Cannot receive invisibility or untargetable tags.", + "evidence": "Lone-band eye and forecast line; rejected concealment has trait reason.", + "prohibited": "Concealment equipment; PF-B01." + } + ], + "equipment": [ + { + "id": "EQ-001", + "name": "Stepback Soles", + "fit": "garb; any", + "class": "B", + "effect": "After first direct hit, step one position rearward if legal.", + "tradeoff": "Next basic waits one tick; full rear makes trigger dormant.", + "production": "I/O/V/S: split-heel boots, rear arrow, heel scrape." + }, + { + "id": "EQ-002", + "name": "Frontpin Greaves", + "fit": "garb; front", + "class": "B", + "effect": "Resist the first enemy push until wearer completes a basic.", + "tradeoff": "The completed basic Hinders wearer for one tick.", + "production": "I/O/V/S: pegged greaves, brace notch, stone pin." + }, + { + "id": "EQ-003", + "name": "Passing Sash", + "fit": "garb; any", + "class": "B", + "effect": "First allied swap through wearer advances the passing ally's basic.", + "tradeoff": "Wearer's next basic is delayed equally.", + "production": "I/O/V/S: two-tail sash, handoff arrows, cloth snap." + }, + { + "id": "EQ-004", + "name": "Gapfinder Sandals", + "fit": "garb; any", + "class": "B", + "effect": "On a neighbor's defeat, choose collapse toward wearer instead of default once.", + "tradeoff": "Wearer becomes Exposed during the collapse tick.", + "production": "I/O/V/S: open-toe route marks, gap ghost, quick step." + }, + { + "id": "EQ-005", + "name": "Leadweight Belt", + "fit": "garb; front/center", + "class": "B", + "effect": "First pull on wearer pulls the neighbor ahead instead when legal.", + "tradeoff": "Illegal transfer Hinders both units.", + "production": "I/O/V/S: hanging weight, forked pull cue, buckle strain." + }, + { + "id": "EQ-006", + "name": "Rearhook Cloak", + "fit": "garb; rear", + "class": "B", + "effect": "First enemy entering allied rear is pulled one position forward after acting.", + "tradeoff": "Wearer must remain rearmost; movement disables hook until next basic.", + "production": "I/O/V/S: hooked hem, return arrow, cloth catch." + }, + { + "id": "EQ-007", + "name": "Pivot Anklet", + "fit": "charm; any", + "class": "B", + "effect": "After wearer swaps, next basic targets the unit opposite the swap direction.", + "tradeoff": "No legal opposite target causes a fizzle without resource gain.", + "production": "I/V/S: compass anklet, pivot reticle, metal tick." + }, + { + "id": "EQ-008", + "name": "Stillroad Wrap", + "fit": "garb; any", + "class": "B", + "effect": "Two cadences without movement grant immunity to next forced step.", + "tradeoff": "Moving voluntarily clears the stored immunity.", + "production": "I/O/V/S: wrapped calves, two-bead root cue, cloth tighten." + }, + { + "id": "EQ-009", + "name": "Rescue Loop", + "fit": "kit; support", + "class": "B", + "effect": "First heal on a foremost ally also pulls that ally one position rearward.", + "tradeoff": "Heal resolves weaker if pull is illegal.", + "production": "I/O/V/S: rope loop, heal-arrow cue, soft rope slide." + }, + { + "id": "EQ-010", + "name": "Forward Claim", + "fit": "charm; any", + "class": "B", + "effect": "First time wearer becomes foremost, mark current foremost enemy.", + "tradeoff": "Mark ends if wearer moves rearward.", + "production": "I/V/S: pennant clasp, paired front markers, flag flick." + }, + { + "id": "EQ-011", + "name": "Neighbor's Buckle", + "fit": "garb; any", + "class": "B", + "effect": "When wearer receives Guard, give one Guard token to neighbor behind.", + "tradeoff": "Wearer cannot receive Guard from that neighbor that cadence.", + "production": "I/O/V/S: twin buckle, rear handoff cue, clasp click." + }, + { + "id": "EQ-012", + "name": "Open-Line Cord", + "fit": "charm; any", + "class": "B", + "effect": "With no neighbor ahead, basics gain reach +1.", + "tradeoff": "Gaining a front neighbor immediately ends any reserved long target.", + "production": "I/V/S: loose cord, reach ruler, cord pluck." + }, + { + "id": "EQ-013", + "name": "Crossstep Token", + "fit": "charm; any", + "class": "B", + "effect": "First targeted enemy movement grants wearer one legal step after its action.", + "tradeoff": "Skipping the step delays wearer's next action.", + "production": "I/V/S: crossed feet token, follow cue, two-step tap." + }, + { + "id": "EQ-014", + "name": "Quiet Rearplate", + "fit": "guard; rear", + "class": "B", + "effect": "While rearmost, first area hit is converted to a direct hit.", + "tradeoff": "Direct hit may now trigger single-target enemy riders.", + "production": "I/O/V/S: narrow backplate, area-to-point cue, muted clang." + }, + { + "id": "EQ-015", + "name": "Relay Spur", + "fit": "garb; reorder", + "class": "B", + "effect": "A swap initiated by wearer refreshes the swapped ally's position trigger.", + "tradeoff": "Same ally cannot be chosen twice consecutively.", + "production": "I/O/V/S: forked spur, reset loop, bright heel click." + }, + { + "id": "EQ-016", + "name": "Empty-Space Bell", + "fit": "charm; any", + "class": "B", + "effect": "A vacant neighbor position makes wearer's next item trigger occur before basic.", + "tradeoff": "If vacancy collapses first, item goes on short cooldown unused.", + "production": "I/V/S: hollow bell, vacancy square, small cutoff chime." + }, + { + "id": "EQ-017", + "name": "Bracehook Cuff", + "fit": "guard; front/center", + "class": "B", + "effect": "When neighbor is pulled, wearer may share the pull as a swap.", + "tradeoff": "Both become Hindered after shared movement.", + "production": "I/O/V/S: hooked cuff, split arrow, leather strain." + }, + { + "id": "EQ-018", + "name": "Longway Ribbon", + "fit": "garb; any", + "class": "B", + "effect": "Traversing three unique positions refreshes one movement item.", + "tradeoff": "Returning to a repeated position resets progress.", + "production": "I/O/V/S: three-knot ribbon, footprint track, flutter count." + }, + { + "id": "EQ-019", + "name": "Settle Stone", + "fit": "charm; any", + "class": "B", + "effect": "After involuntary movement, wearer keeps reserved target if still reachable.", + "tradeoff": "Retarget is forbidden; illegal target fizzles normally.", + "production": "I/V/S: flat stone, solid target tether, dull knock." + }, + { + "id": "EQ-020", + "name": "Linekeeper Tab", + "fit": "kit; support", + "class": "B", + "effect": "Once per battle, prevent an allied swap that would expose lowest-vitality unit.", + "tradeoff": "Prevention spends wearer's next item opportunity.", + "production": "I/O/V/S: red tally tab, blocked swap cue, stamp click." + }, + { + "id": "EQ-021", + "name": "Threatglass Lens", + "fit": "charm; any", + "class": "B", + "effect": "First basic targets enemy with longest shown wind-up instead of default.", + "tradeoff": "If no wind-up exists, wearer waits one tick before defaulting.", + "production": "I/O/V/S: clear lens, clock reticle, glass focus." + }, + { + "id": "EQ-022", + "name": "Frontmark Chalk", + "fit": "kit; any", + "class": "B", + "effect": "Basic on foremost enemy Marks it for wearer's neighbor behind.", + "tradeoff": "Wearer cannot benefit from that Mark.", + "production": "I/V/S: chalk wedge, neighbor eye cue, chalk stroke." + }, + { + "id": "EQ-023", + "name": "Rearcall Whistle", + "fit": "kit; reach 2+", + "class": "B", + "effect": "First legal attack selects rearmost enemy and reveals intervening screens.", + "tradeoff": "Attack deals no status on that use.", + "production": "I/O/V/S: flat whistle, rear line cue, captioned chirp." + }, + { + "id": "EQ-024", + "name": "Mercy Dial", + "fit": "charm; restore", + "class": "B", + "effect": "Healing prefers ally whose next incoming hit is earliest, not lowest vitality.", + "tradeoff": "Unforecast damage is ignored by the dial.", + "production": "I/V/S: rotating cross dial, timeline reticle, soft click." + }, + { + "id": "EQ-025", + "name": "Split-Choice Coin", + "fit": "charm; any", + "class": "B", + "effect": "On exact target-score tie, choose rearmost rather than random tie rule.", + "tradeoff": "After tie resolves, wearer becomes Exposed one tick.", + "production": "I/V/S: split coin, rear tie arrow, coin flip." + }, + { + "id": "EQ-026", + "name": "Summon Pricker", + "fit": "armament; any", + "class": "B", + "effect": "Basics prefer enemy footprints with independent actions.", + "tradeoff": "Against no footprint, basic recovery is one tick longer.", + "production": "I/O/V/S: short pricker, footprint reticle, dry prick." + }, + { + "id": "EQ-027", + "name": "Woundreader Tape", + "fit": "kit; pressure", + "class": "B", + "effect": "First basic targets enemy most recently effectively healed.", + "tradeoff": "Target outside reach causes default with no item benefit.", + "production": "I/O/V/S: marked tape, heal-history cue, tape pull." + }, + { + "id": "EQ-028", + "name": "Guard-Seam Pick", + "fit": "armament; any", + "class": "B", + "effect": "Basic prefers target currently protecting a neighbor.", + "tradeoff": "Damage cannot defeat the protector on that item-triggered hit.", + "production": "I/O/V/S: seam pick, guard-link reticle, metal pry." + }, + { + "id": "EQ-029", + "name": "Fastest-Shadow Ink", + "fit": "charm; impair", + "class": "B", + "effect": "Debuffs prefer enemy with shortest next-action time.", + "tradeoff": "Applied duration is one tick shorter.", + "production": "I/V/S: ink drop, short clock cue, quick brush." + }, + { + "id": "EQ-030", + "name": "Last-Target Knot", + "fit": "charm; any", + "class": "B", + "effect": "If prior target dies, next action follows its nearest living neighbor.", + "tradeoff": "If both neighbors equidistant, action fizzles.", + "production": "I/V/S: linked knot, neighbor branch cue, string snap." + }, + { + "id": "EQ-031", + "name": "Bodyguard Tag", + "fit": "kit; support", + "class": "B", + "effect": "Choose one neighbor in Prepare; enable actions prefer that unit while below half vitality.", + "tradeoff": "Other allies receive weaker enable duration.", + "production": "I/O/V/S: name-free tag, chosen outline, buckle tap." + }, + { + "id": "EQ-032", + "name": "Unmasking Powder", + "fit": "kit; line reach", + "class": "B", + "effect": "First hit on untargetable-window enemy becomes a reveal instead of damage.", + "tradeoff": "Powder is spent for battle even if reveal ends same tick.", + "production": "I/V/S: powder cone, open-eye cloud, soft burst." + }, + { + "id": "EQ-033", + "name": "Counterface Mirror", + "fit": "guard; front", + "class": "B", + "effect": "First forced-target effect aimed at wearer is reflected as Mark on source.", + "tradeoff": "Wearer remains the legal target for that action.", + "production": "I/O/V/S: small mirror guard, return-eye cue, glass ping." + }, + { + "id": "EQ-034", + "name": "Lowwater Gauge", + "fit": "charm; any", + "class": "B", + "effect": "At action ready, target lowest current local-resource enemy if legal.", + "tradeoff": "Enemy resource type is revealed to wearer only after first gain.", + "production": "I/V/S: shallow gauge, meter reticle, liquid tick." + }, + { + "id": "EQ-035", + "name": "Open-Flank String", + "fit": "armament; reach 2+", + "class": "B", + "effect": "Attacks prefer enemy with no living neighbor ahead.", + "tradeoff": "A new front neighbor invalidates reserve; action retargets foremost.", + "production": "I/O/V/S: fork string, empty-front cue, twang." + }, + { + "id": "EQ-036", + "name": "Twin Reticle", + "fit": "charm; area", + "class": "B", + "effect": "Area action centers between two adjacent legal targets rather than foremost.", + "tradeoff": "Single-target state prevents item trigger.", + "production": "I/V/S: paired circles, midpoint cue, double click." + }, + { + "id": "EQ-037", + "name": "Interrupt Ledger", + "fit": "kit; control", + "class": "B", + "effect": "After any ally interrupts, wearer targets that interrupted enemy next.", + "tradeoff": "If unreachable, wearer skips one basic rather than defaulting.", + "production": "I/O/V/S: slim ledger, interrupt tether, page tick." + }, + { + "id": "EQ-038", + "name": "Oldest-Harm Compass", + "fit": "charm; restore", + "class": "B", + "effect": "Cleanse targets ally with oldest active hindrance.", + "tradeoff": "Newer action-blocking hindrance is not prioritized.", + "production": "I/V/S: aged compass, duration rings, slow click." + }, + { + "id": "EQ-039", + "name": "Collapse Sight", + "fit": "armament; any", + "class": "B", + "effect": "Reserved target follows one deterministic enemy collapse.", + "tradeoff": "Second collapse before resolution causes fizzle.", + "production": "I/O/V/S: sliding sight, one-step tether, rail slide." + }, + { + "id": "EQ-040", + "name": "Choicebreaker Pin", + "fit": "charm; any", + "class": "B", + "effect": "First action with two equally legal target families alternates from previewed default.", + "tradeoff": "Target receives no critical effect.", + "production": "I/V/S: half-turn pin, alternate reticle, pin turn." + }, + { + "id": "EQ-041", + "name": "Neighborwall", + "fit": "guard; front", + "class": "B", + "effect": "First direct hit on neighbor behind is intercepted.", + "tradeoff": "Wearer becomes Exposed until its next basic.", + "production": "I/O/V/S: half-wall shield, rear block cue, broad clang." + }, + { + "id": "EQ-042", + "name": "Porous Buckler", + "fit": "guard; any", + "class": "B", + "effect": "Area damage to wearer is shared with both neighbors.", + "tradeoff": "Empty neighbor share returns to wearer as Hindered.", + "production": "I/O/V/S: perforated buckler, three-way split, airy clang." + }, + { + "id": "EQ-043", + "name": "Guard Siphon Ring", + "fit": "charm; any", + "class": "B", + "effect": "On Guard expiry, transfer half remaining Guard to lowest-Guard neighbor.", + "tradeoff": "Wearer cannot refresh Guard that cadence.", + "production": "I/V/S: drain ring, expiry arrow, low chime." + }, + { + "id": "EQ-044", + "name": "Exposed Standard", + "fit": "kit; front", + "class": "B", + "effect": "While Exposed, enemies prefer wearer and allies behind gain projectile screen.", + "tradeoff": "Screen ends immediately when Exposed clears.", + "production": "I/O/V/S: open pennant, threat flare, flag crack." + }, + { + "id": "EQ-045", + "name": "Second Blow Plate", + "fit": "guard; any", + "class": "B", + "effect": "Ignores the rider effect of the second hit from same enemy.", + "tradeoff": "The hit's base damage remains and count resets on target change.", + "production": "I/O/V/S: double-dent plate, two-hit pip, muted second clang." + }, + { + "id": "EQ-046", + "name": "Mender's Guardrail", + "fit": "guard; support", + "class": "B", + "effect": "Effective healing grants recipient protection from next push.", + "tradeoff": "Healing wearer never grants this protection.", + "production": "I/O/V/S: rail bracer, heal-to-root cue, latch sound." + }, + { + "id": "EQ-047", + "name": "Brittle Pavise", + "fit": "guard; front", + "class": "B", + "effect": "Blocks all first-cadence projectiles crossing wearer.", + "tradeoff": "Shatters afterward and applies Hindered to wearer.", + "production": "I/O/V/S: tall cracked shield, break cue, layered impacts." + }, + { + "id": "EQ-048", + "name": "Rearward Aegis", + "fit": "guard; rear", + "class": "B", + "effect": "Protects neighbor ahead from attacks originating behind allied line.", + "tradeoff": "Gives no protection against normal front-origin attacks.", + "production": "I/O/V/S: backward shield, rear-origin arrow, reversed clang." + }, + { + "id": "EQ-049", + "name": "Borrowed Cover", + "fit": "charm; any", + "class": "B", + "effect": "When wearer steps behind a Guarded ally, copies one Guard token.", + "tradeoff": "Copied token expires on next movement.", + "production": "I/V/S: overlapping plates icon, step-copy cue, small scrape." + }, + { + "id": "EQ-050", + "name": "Strike-Open Helm", + "fit": "guard; pressure", + "class": "B", + "effect": "Becoming Exposed advances wearer's next basic.", + "tradeoff": "Exposed cannot be cleansed before that basic.", + "production": "I/O/V/S: split visor, speed crack cue, visor snap." + }, + { + "id": "EQ-051", + "name": "Quiet Interceptor", + "fit": "guard; any", + "class": "B", + "effect": "First intercepted hit generates no enemy on-hit resource.", + "tradeoff": "Wearer's own on-hit resource also does not trigger.", + "production": "I/O/V/S: padded guard, crossed resource cue, soft thud." + }, + { + "id": "EQ-052", + "name": "Heatcatch Mantle", + "fit": "garb; any", + "class": "B", + "effect": "First area burst is stored and released after wearer moves.", + "tradeoff": "If wearer cannot move in two cadences, burst resolves on wearer.", + "production": "I/O/V/S: ember-lined mantle, stored flame cue, cloth hiss." + }, + { + "id": "EQ-053", + "name": "Empty Guard Cage", + "fit": "guard; any", + "class": "B", + "effect": "With zero Guard, incoming Guard strip becomes one Guard instead.", + "tradeoff": "Cannot receive normal Guard same cadence.", + "production": "I/O/V/S: open cage bracer, inversion cue, metal catch." + }, + { + "id": "EQ-054", + "name": "Three-Hit Sconce", + "fit": "charm; front", + "class": "B", + "effect": "Third direct hit lights a screen against next spell.", + "tradeoff": "Moving resets hit count.", + "production": "I/O/V/S: three-wick sconce, count/flame cue, three taps." + }, + { + "id": "EQ-055", + "name": "Adjacent Oathplate", + "fit": "guard; any", + "class": "B", + "effect": "Choose ahead or behind in Prepare; protection effects only flow that direction but last longer.", + "tradeoff": "Other neighbor cannot be protected by wearer.", + "production": "I/O/V/S: asymmetrical plate, chosen arrow, locking clasp." + }, + { + "id": "EQ-056", + "name": "Pushback Frame", + "fit": "guard; front", + "class": "B", + "effect": "First blocked push is reflected onto source if legal.", + "tradeoff": "Reflection consumes all Guard on wearer.", + "production": "I/O/V/S: spring frame, return chevron, compressed clang." + }, + { + "id": "EQ-057", + "name": "Mercy Screen", + "fit": "guard; support", + "class": "B", + "effect": "Projectile that would defeat an ally instead leaves it at survival floor and breaks screen.", + "tradeoff": "Protected ally becomes untargetable to healing for one tick.", + "production": "I/O/V/S: translucent screen, survival line, crystal break." + }, + { + "id": "EQ-058", + "name": "Lowguard Apron", + "fit": "garb; center", + "class": "B", + "effect": "Neighbor ahead below half vitality receives wearer's first positive Guard gain.", + "tradeoff": "Wearer receives none of that gain.", + "production": "I/O/V/S: plated apron, forward handoff, buckle clink." + }, + { + "id": "EQ-059", + "name": "Counterseal Shield", + "fit": "guard; any", + "class": "B", + "effect": "First Mark applied to wearer becomes Guard and removes Mark.", + "tradeoff": "Next Mark lasts one tick longer.", + "production": "I/O/V/S: seal shield, mark inversion, wax crack." + }, + { + "id": "EQ-060", + "name": "Last Neighbor Plate", + "fit": "guard; any", + "class": "B", + "effect": "With exactly one living neighbor, both share first direct hit.", + "tradeoff": "Shared hit cannot be intercepted again.", + "production": "I/O/V/S: twin plate halves, shared-hit cue, paired clang." + }, + { + "id": "EQ-061", + "name": "Early Bell", + "fit": "charm; any", + "class": "B", + "effect": "First basic resolves one tick earlier.", + "tradeoff": "Second basic resolves one tick later.", + "production": "I/V/S: two-position bell clock, early/late chimes." + }, + { + "id": "EQ-062", + "name": "Held Breath Mask", + "fit": "garb; slow", + "class": "B", + "effect": "First wind-up may survive one interrupt as a delay.", + "tradeoff": "Delayed action loses status riders.", + "production": "I/O/V/S: slotted mask, held clock cue, inhale/cutoff." + }, + { + "id": "EQ-063", + "name": "Recovery Shears", + "fit": "kit; any", + "class": "B", + "effect": "Defeating a target removes current recovery time.", + "tradeoff": "If target survives, recovery doubles.", + "production": "I/O/V/S: clock shears, cut/wager cue, snip." + }, + { + "id": "EQ-064", + "name": "Afterbeat Ring", + "fit": "charm; tempo", + "class": "B", + "effect": "Acting immediately after a neighbor grants one local resource.", + "tradeoff": "Acting before both neighbors Hinders wearer.", + "production": "I/V/S: trailing ring, neighbor beat cue, delayed ping." + }, + { + "id": "EQ-065", + "name": "Slow Fuse", + "fit": "kit; area", + "class": "B", + "effect": "Equipment burst waits until target's next action, then follows it.", + "tradeoff": "Target defeat before acting cancels burst.", + "production": "I/O/V/S: long fuse vial, target clock, fuse hiss." + }, + { + "id": "EQ-066", + "name": "Quickdraw Thimble", + "fit": "charm; projectile", + "class": "B", + "effect": "First projectile has no wind-up.", + "tradeoff": "It cannot use Marks or critical riders.", + "production": "I/O/V/S: draw thimble, crossed wind-up cue, snap shot." + }, + { + "id": "EQ-067", + "name": "Noon Dial", + "fit": "charm; any", + "class": "B", + "effect": "At exact battle midpoint forecast, refresh oldest item cooldown.", + "tradeoff": "Before midpoint, item cooldowns recover one tick slower.", + "production": "I/V/S: split sun dial, midpoint flare, clock chime." + }, + { + "id": "EQ-068", + "name": "Staggered Buckle", + "fit": "garb; any", + "class": "B", + "effect": "Receiving action advance stores it; release after next neighbor acts.", + "tradeoff": "Stored advance expires if wearer moves.", + "production": "I/O/V/S: offset buckle, stored arrow, click-release." + }, + { + "id": "EQ-069", + "name": "Patience Quill", + "fit": "kit; control", + "class": "B", + "effect": "Each cadence without acting lengthens next hindrance by one tick, capped at two.", + "tradeoff": "Any fizzle clears stored length.", + "production": "I/O/V/S: notched quill, waiting pips, scratch count." + }, + { + "id": "EQ-070", + "name": "Interrupt Candle", + "fit": "charm; any", + "class": "B", + "effect": "After wearer is interrupted, its next item trigger occurs immediately.", + "tradeoff": "Trigger effect is reduced to non-damaging utility.", + "production": "I/O/V/S: cut-wick candle, interrupt spark, wick sputter." + }, + { + "id": "EQ-071", + "name": "Neighbor Clock", + "fit": "charm; support", + "class": "B", + "effect": "Choose a neighbor; wearer basic cadence becomes opposite band of neighbor.", + "tradeoff": "Choice locks for battle.", + "production": "I/V/S: linked clocks, fast/slow shapes, alternating ticks." + }, + { + "id": "EQ-072", + "name": "Third-Cycle Cog", + "fit": "kit; construct", + "class": "B", + "effect": "Every third device action occurs twice at different legal targets.", + "tradeoff": "Device pauses on following cadence.", + "production": "I/O/V/S: triple cog, double-action cue, ratchet pair." + }, + { + "id": "EQ-073", + "name": "Dawn Recovery Strap", + "fit": "garb; any", + "class": "B", + "effect": "First action recovery ends when first enemy major wind-up begins.", + "tradeoff": "If no major wind-up, recovery is one tick longer.", + "production": "I/O/V/S: sunrise strap, recovery-to-major tether, buckle slide." + }, + { + "id": "EQ-074", + "name": "Late Cleanse Vial", + "fit": "kit; restore", + "class": "B", + "effect": "Cleanse after status midpoint also advances target's next basic.", + "tradeoff": "Early cleanse delays that basic.", + "production": "I/O/V/S: half-mark vial, early/late arrows, glass flip." + }, + { + "id": "EQ-075", + "name": "Unfinished Metronome", + "fit": "charm; any", + "class": "B", + "effect": "A fizzle makes next legal basic fast instead of refunding time.", + "tradeoff": "No resource is refunded on fizzle.", + "production": "I/V/S: broken metronome, fizzle-to-speed cue, skipped beat." + }, + { + "id": "EQ-076", + "name": "Twin Windup Thread", + "fit": "charm; linked", + "class": "B", + "effect": "Linked neighbors begin their next wind-ups together.", + "tradeoff": "Faster action waits for slower; link breaks after use.", + "production": "I/V/S: twin clocks on thread, tie cue, synchronized pull." + }, + { + "id": "EQ-077", + "name": "Rest-Step Sole", + "fit": "garb; reorder", + "class": "B", + "effect": "Voluntary step during recovery ends recovery.", + "tradeoff": "Next wind-up is one tick longer.", + "production": "I/O/V/S: pause-mark sole, step-cut cue, heel stop." + }, + { + "id": "EQ-078", + "name": "Bosswatch Lens", + "fit": "charm; any", + "class": "B", + "effect": "First boss major telegraph pauses wearer's item cooldowns and readies inspect response.", + "tradeoff": "No cooldown progress occurs during telegraph.", + "production": "I/O/V/S: large clock lens, boss ring cue, deep focus tone." + }, + { + "id": "EQ-079", + "name": "Short Memory Spring", + "fit": "kit; any", + "class": "B", + "effect": "Repeating the same action tag shortens wind-up.", + "tradeoff": "Third repeat locks target to foremost for one action.", + "production": "I/O/V/S: compressed spring, repeat counter, spring ticks." + }, + { + "id": "EQ-080", + "name": "Long Memory Spring", + "fit": "kit; any", + "class": "B", + "effect": "Alternating action tags shortens recovery.", + "tradeoff": "Repeating a tag resets benefit and Hinders wearer.", + "production": "I/O/V/S: stretched spring, alternate counter, elastic twang." + } + ], + "artifacts": [ + { + "id": "AR-001", + "name": "Open Ledger", + "rule": "Every shop shows one extra recruit, but equipment offer count is one lower.", + "tradeoff": "Conflicts AR-002; shop header displays exchanged slots before entry.", + "production": "I/V/S: open roster pages, slot exchange cue, page fan." + }, + { + "id": "AR-002", + "name": "Toolwright's Window", + "rule": "Every shop shows one extra equipment item, but recruit offer count is one lower.", + "tradeoff": "Conflicts AR-001; shop header previews composition.", + "production": "I/V/S: tool window icon, reverse slot cue, latch slide." + }, + { + "id": "AR-003", + "name": "Common Cause", + "rule": "First Common recruit of each shop costs one product-defined tier less; Uncommon/Rare costs one tier more.", + "tradeoff": "Conflicts AR-004; every price shows artifact delta.", + "production": "I/V/S: one-notch banner, price arrows, soft stamp." + }, + { + "id": "AR-004", + "name": "Narrow Search", + "rule": "Shops guarantee one Uncommon-or-Rare recruit but suppress one base-profession role weight.", + "tradeoff": "Conflicts AR-003; suppressed role is chosen and visible for the run.", + "production": "I/V/S: narrow lens, rarity/role exchange cue, glass close." + }, + { + "id": "AR-005", + "name": "Second Interview", + "rule": "Once per shop, reject one recruit to generate a new presentation with the same combat record.", + "tradeoff": "Conflicts AR-006; no power reroll, replacement signature is previewed.", + "production": "I/V/S: two portrait frames, locked-rule cue, card slide." + }, + { + "id": "AR-006", + "name": "Fresh Aptitude", + "rule": "Once per shop, reroll one recruit's trait while keeping profession, grades, level, gear, and cost tier.", + "tradeoff": "Conflicts AR-005; old/new benefit and cost compare before acceptance.", + "production": "I/V/S: rotating trait badge, before/after cue, token turn." + }, + { + "id": "AR-007", + "name": "Full Bench", + "rule": "Owned reserve capacity is higher, but every undeployed recruit adds a small shop upkeep tier.", + "tradeoff": "Conflicts AR-008; upkeep total is shown before buying.", + "production": "I/V/S: long bench, reserve-to-cost cue, bench creak." + }, + { + "id": "AR-008", + "name": "Lean Company", + "rule": "Reserve capacity is lower; after an allied knockout, the next legal Recruit offer is guaranteed within its declared affordable tier.", + "tradeoff": "Conflicts AR-007; capacity and guarantee state remain on roster/shop.", + "production": "I/V/S: short bench, opportunity guarantee cue, firm knock." + }, + { + "id": "AR-009", + "name": "Familiar Faces", + "rule": "Profession+trait duplicates may appear in one shop; buying one suppresses that signature for next two shops.", + "tradeoff": "Conflicts AR-010; duplicate warning explains breadth tradeoff.", + "production": "I/V/S: paired silhouettes, suppression clock, double card tap." + }, + { + "id": "AR-010", + "name": "Wide Casting", + "rule": "Each shop must offer distinct base professions while legal, but trait variety protection is disabled.", + "tradeoff": "Conflicts AR-009; repeated-trait risk shown on artifact state.", + "production": "I/V/S: twelve-spoke roster, profession spread cue, radial clicks." + }, + { + "id": "AR-011", + "name": "Narrow Vanguard", + "rule": "Deploy one fewer recruit; foremost starts every battle Guarded and gains forced-target priority.", + "tradeoff": "Conflicts AR-012; capacity loss and threat arrow show in Prepare.", + "production": "I/V/S: narrow shield line, capacity minus cue, shield rise." + }, + { + "id": "AR-012", + "name": "Long Company", + "rule": "Deploy one additional recruit up to core cap; no unit starts with Guard.", + "tradeoff": "Conflicts AR-011; expanded line and unguarded warning appear in preview.", + "production": "I/V/S: extended line icon, capacity plus cue, marching taps." + }, + { + "id": "AR-013", + "name": "Fixed Ends", + "rule": "Foremost and rearmost cannot be moved voluntarily; center swaps have shorter recovery.", + "tradeoff": "Conflicts AR-014; endpoint locks shown on every formation view.", + "production": "I/V/S: pinned end brackets, center arrows, stone pins." + }, + { + "id": "AR-014", + "name": "Rolling Ends", + "rule": "First voluntary endpoint swap each battle is free; center units receive longer movement recovery.", + "tradeoff": "Conflicts AR-013; available swap pip and center cost display.", + "production": "I/V/S: rolling brackets, endpoint exchange cue, wheel click." + }, + { + "id": "AR-015", + "name": "Paired Duties", + "rule": "Prepare partitions line into adjacent pairs; enable and Guard sharing inside pairs gain priority.", + "tradeoff": "Conflicts AR-016; cross-pair sharing is delayed and pair brackets show.", + "production": "I/V/S: paired links, partition cue, paired pluck." + } + ], + "enemies": [ + { + "id": "EN-R1-01", + "region": 1, + "name": "Pane Nibbler", + "class": "ordinary", + "rule": "Bites the foremost unit; after two uninterrupted bites, chips Guard from that unit's neighbor.", + "responses": "Reorder the neighbor; interrupt the second bite. Teaches adjacency protection.", + "escalation": "Third bite becomes a two-target arc.", + "production": "P/A/V/S: low glass-jaw, double-bite ticks, chipped-square cue, brittle clicks." + }, + { + "id": "EN-R1-02", + "region": 1, + "name": "Gust Finch", + "class": "ordinary", + "rule": "From rear, telegraphs a projectile at the rearmost allied unit and steps forward after firing.", + "responses": "Use projectile screen; pull the shooter into short reach. Tests rear safety.", + "escalation": "Each blocked shot shortens its next step.", + "production": "P/A/V/S: fork-wing silhouette, perch/loose, feather-arrow cue, wind snap." + }, + { + "id": "EN-R1-03", + "region": 1, + "name": "Terrace Ram", + "class": "ordinary", + "rule": "Headwind charge pushes the foremost allied unit one position rearward, targeting foremost only.", + "responses": "Brace against push; leave a durable replacement directly behind. Combines with collapse.", + "escalation": "Failed push grants it Guard.", + "production": "P/A/V/S: wedge-horn body, paw/charge, chevron lane cue, hoof rumble." + }, + { + "id": "EN-R1-04", + "region": 1, + "name": "Bellstem Grazer", + "class": "ordinary", + "rule": "Eats the oldest positive tag on the foremost reachable ally instead of dealing damage.", + "responses": "Delay positive tag until after bite; expose an untagged front. Introduces dispel timing.", + "escalation": "Consumed tag makes next bite reach 2.", + "production": "P/A/V/S: tall stem-antler, sniff/pluck, hollow-plus cue, plucked chime." + }, + { + "id": "EN-R1-05", + "region": 1, + "name": "Shardback", + "class": "ordinary", + "rule": "Reflects the first projectile each cadence to its source; reflection is previewed and cannot chain.", + "responses": "Use melee/spell first; spend a low-value projectile to open the screen.", + "escalation": "At half vitality, screen resets once.", + "production": "P/A/V/S: faceted shell, flare/turn, mirrored arrow cue, glass ping." + }, + { + "id": "EN-R1-06", + "region": 1, + "name": "Verge Stalker", + "class": "ordinary", + "rule": "Targets the first ally with reach greater than 1; otherwise attacks foremost.", + "responses": "Equip short reach; Guard the identified long-reach unit. Teaches target-filter preview.", + "escalation": "Marked target becomes Exposed after two hits.", + "production": "P/A/V/S: long forelegs, scan/pounce, reach-ruler cue, scrape/hiss." + }, + { + "id": "EN-R1-07", + "region": 1, + "name": "Whistle Midge", + "class": "ordinary", + "rule": "A swarm delays the next action of whichever allied band contains the most units.", + "responses": "Balance band occupancy; cleanse the visible delay. Interacts with order bands.", + "escalation": "Repeats on a band only after another band is hit.", + "production": "P/A/V/S: triangular swarm, orbit/dive, band-bracket cue, filtered whistle." + }, + { + "id": "EN-R1-08", + "region": 1, + "name": "Glassroot Sprout", + "class": "ordinary", + "rule": "Starts as a rear footprint and grows one position forward each cadence; on reaching front, shields all enemies behind it.", + "responses": "Defeat it early at range; push it backward before growth.", + "escalation": "Growth cannot be interrupted twice consecutively.", + "production": "P/A/V/S: staged crystal plant, three growth poses, numbered sprout cue, rising cracks." + }, + { + "id": "EN-R1-09", + "region": 1, + "name": "Crosswind Scribe", + "class": "ordinary", + "rule": "Draws a line between allied foremost and rearmost; next swap crossing the line becomes Hindered.", + "responses": "Avoid swapping that cadence; cleanse then use movement anyway.", + "escalation": "Uncrossed line converts to a weak area hit.", + "production": "P/A/V/S: kite-robed caster, draw/release, dotted span cue, chalk-wind stroke." + }, + { + "id": "EN-R1-10", + "region": 1, + "name": "Ledge Hopper", + "class": "ordinary", + "rule": "Leaps over its own foremost neighbor to become front, then strikes the first ally whose target changed.", + "responses": "Hold targets stable; intercept the announced leap strike.", + "escalation": "If no target changed, Hopper becomes Exposed.", + "production": "P/A/V/S: spring-legged silhouette, vault/stab, target-turn cue, stone tap." + }, + { + "id": "EN-R1-11", + "region": 1, + "name": "Gale Bailiff", + "class": "elite", + "rule": "Declares Stand Order on one indexed allied position; leaving it causes a line-wide push, remaining causes focused projectiles.", + "responses": "Choose a braced occupant; deliberately leave before ranged wind-up and absorb push.", + "escalation": "Alternates ordered index front/rear.", + "production": "P/A/V/S: tall banner coat, point/stamp, numbered pennant cue, gavel-gust event." + }, + { + "id": "EN-R1-12", + "region": 1, + "name": "Prism Ox", + "class": "elite", + "rule": "Occupies two enemy footprints; frontal damage rotates its prism, changing whether it screens projectiles, spells, or pushes.", + "responses": "Rotate with a cheap legal hit; attack through the currently unscreened family.", + "escalation": "At low vitality rotation occurs every hit.", + "production": "P/A/V/S: two-footprint ox, heavy pivot, three-shape prism cue, bass glass rotation." + }, + { + "id": "EN-R2-01", + "region": 2, + "name": "Reed Hook", + "class": "ordinary", + "rule": "Pulls the rearmost allied unit one step forward, then attacks its new neighbor.", + "responses": "Brace/pull resist; place a durable unit at rear. Extends displacement lesson.", + "escalation": "Successful pull leaves a one-tick snare.", + "production": "P/A/V/S: hook-beak reed body, cast/haul, hooked-water cue, rope-wet snap." + }, + { + "id": "EN-R2-02", + "region": 2, + "name": "Mudlamp Leech", + "class": "ordinary", + "rule": "Attaches to lowest-vitality ally in reach 2 and converts incoming healing into its own Guard until removed by damage.", + "responses": "Hit the leech before healing; cleanse attachment then restore.", + "escalation": "At full Guard it detaches and moves rearward.", + "production": "P/A/V/S: glowing leech, latch/swell/drop, crossed-heal cue, wet pulse." + }, + { + "id": "EN-R2-03", + "region": 2, + "name": "Silt Mimic", + "class": "ordinary", + "rule": "Copies the last allied basic target rule, not its damage, and attacks the mirrored legal candidate.", + "responses": "Feed it a harmless target rule; change party order so copied candidate is guarded.", + "escalation": "Two identical copied rules grant it speed.", + "production": "P/A/V/S: mud double, trace/strike, mirrored reticle cue, muffled copy hit." + }, + { + "id": "EN-R2-04", + "region": 2, + "name": "Bank Tortoise", + "class": "ordinary", + "rule": "Marks its current indexed position; push or pull across that index creates a slowing mud patch.", + "responses": "Avoid displacement across mark; trigger it early then route around the known patch.", + "escalation": "Second crossing expands patch to neighbors.", + "production": "P/A/V/S: flat bank shell, stamp/tuck, muddy index cue, suction thud." + }, + { + "id": "EN-R2-05", + "region": 2, + "name": "Fen Piper", + "class": "ordinary", + "rule": "Sound wind-up commands the foremost enemy to step back, changing enemy order before allies resolve.", + "responses": "Interrupt sound; reserve actions with flexible retarget.", + "escalation": "Each successful command adds one farther enemy.", + "production": "P/A/V/S: reed-mask piper, inhale/play, wave-arrow cue, captioned reed call." + }, + { + "id": "EN-R2-06", + "region": 2, + "name": "Brackwater Wisp", + "class": "ordinary", + "rule": "Applies a hindrance that becomes stronger if cleansed before half its duration.", + "responses": "Let it expire under protection; cleanse only after the midpoint notch.", + "escalation": "Reapplication targets a different band.", + "production": "P/A/V/S: droplet light, tag/fade, split-duration ring, damp fizz." + }, + { + "id": "EN-R2-07", + "region": 2, + "name": "Ferry Crab", + "class": "ordinary", + "rule": "Side-steps from enemy front to rear after being hit, then targets the attacker through allies.", + "responses": "Use line reach to finish; alternate low-impact attackers behind Guard.", + "escalation": "Stops moving when Hindered.", + "production": "P/A/V/S: wide ferry shell, scuttle/claw, attacker tether cue, shell clack." + }, + { + "id": "EN-R2-08", + "region": 2, + "name": "Drownseed Pod", + "class": "ordinary", + "rule": "On defeat, places a visible seed at the defeating ally's position; after two cadences it bursts unless that ally moves.", + "responses": "Plan a safe move after defeat; delay killing until movement is ready.", + "escalation": "Burst pulls both neighbors toward seed.", + "production": "P/A/V/S: buoy pod, swell/burst, seed countdown cue, bubble pop/bass pull." + }, + { + "id": "EN-R2-09", + "region": 2, + "name": "Mire Medic", + "class": "ordinary", + "rule": "Restores the enemy with the oldest hindrance, but each heal shortens that hindrance.", + "responses": "Focus the medic; use short hindrances so its heal removes setup.", + "escalation": "With no hindered ally, attacks allied healer.", + "production": "P/A/V/S: reed satchel, scoop/pour, shrinking-ring cue, gourd splash." + }, + { + "id": "EN-R2-10", + "region": 2, + "name": "Fogjaw", + "class": "ordinary", + "rule": "Cannot be targeted beyond reach 1 until it attacks; its attack reveals it for one cadence and targets center band.", + "responses": "Hold a short-reach counter; expose a guarded center target to bait reveal.", + "escalation": "At half vitality reveal window shortens one tick.", + "production": "P/A/V/S: fog-maw silhouette, emerge/bite, open-eye cue, muffled-to-clear snap." + }, + { + "id": "EN-R2-11", + "region": 2, + "name": "Channel Widow", + "class": "elite", + "rule": "Threads two allied positions; healing either transfers the same amount as damage to the other until thread is cut by swapping them adjacent.", + "responses": "Perform the disclosed swap; stagger healing until one target is safe.", + "escalation": "Recasts on farthest pair after break.", + "production": "P/A/V/S: long-legged reed frame, thread/cinch, paired droplet cue, taut fiber tone." + }, + { + "id": "EN-R2-12", + "region": 2, + "name": "Sluice Elder", + "class": "elite", + "rule": "Moves the region safe index frontward each cadence; units outside it gain Wet, and three Wet causes a pull.", + "responses": "Reorder with the moving safe index; cleanse Wet before third stack.", + "escalation": "Below half vitality safe index reverses direction.", + "production": "P/A/V/S: gate-backed elder, valve walk, numbered waterline cue, gate groan." + } + ], + "bosses": [ + { + "id": "BO-R1-01", + "region": 1, + "class": "RG-01", + "name": "The Many-Paned Stag", + "rule": "Rotates three panes that screen projectile, spell, or forced movement; antler sweep pushes the current foremost after each rotation.", + "responses": "Rotate with cheap unscreened hit, then use open family; brace sweep and punish recovery. Failure: wrong family fed pane timing.", + "escalation": "Half vitality rotates two steps.", + "production": "P/A/V/S/M: two-footprint stag, pane-antler kit, tri-screen cue, glass-horn boss motif." + }, + { + "id": "BO-R1-02", + "region": 1, + "class": "RG-01", + "name": "Gale Loom", + "rule": "Stationary wind engine inserts a gust footprint front-to-rear; attacks on its weak spindle reverse gust direction.", + "responses": "Break spindle to redirect gust through enemies; hold stable order and race core. Failure: uncontrolled gust exposed rear.", + "escalation": "At half vitality two gusts move from opposite ends.", + "production": "P/A/V/S/M: loom-engine, spindle/gust phases, numbered wind paths, mechanical gale stem." + }, + { + "id": "BO-R2-01", + "region": 2, + "class": "RG-02", + "name": "Sluiceback Mother", + "rule": "Shell carries a moving safe index; eggs at unsafe positions hatch pullers, while direct attacks shift the index.", + "responses": "Follow index and clear eggs; deliberately shift index to hatch where area attack is ready. Failure: third Wet/pull broke care order.", + "escalation": "Half vitality reverses index and hatches existing eggs.", + "production": "P/A/V/S/M: broad shell, egg/valve kit, tide ruler, wet percussion boss stem." + }, + { + "id": "BO-R2-02", + "region": 2, + "class": "RG-02", + "name": "The Drowned Bell", + "rule": "Sound pulses link two allied positions; healing, movement, or cleanse on one echoes as a cost on the other.", + "responses": "Interrupt chosen pulse family; place two durable units in linked indices and stagger actions. Failure: linked cost chained through recovery.", + "escalation": "Each third pulse links three consecutive positions.", + "production": "P/A/V/S/M: submerged bell body, rope/current motions, link glyph suite, caption-first bell motif." + } + ], + "region": [ + { + "id": "RG-01", + "name": "Windglass Verge", + "framing": "open grass terraces where suspended mineral panes redirect aether gusts. The guild charts a safe trade crossing.", + "pressure": "Introduces front-to-rear reach, basic protection, visible projectile screens, and simple pushes. Pressures the player to field a front holder without making one profession mandatory.", + "progression": "Two-threat tutorials become three-unit compositions; glass-wind hazard telegraphs a push one cadence ahead. Shops emphasize EQ-001..EQ-040; first clear opens all base profession shop weights.", + "assignment": "EN-R1-01..EN-R1-12; BO-R1-01..BO-R1-02; artifacts AR-001..AR-008.", + "production": "E/P/A/V/S/M: bright terrace layers, wind direction shapes, 12 silhouette kits, two boss scale kits, calm-to-gale music stem." + }, + { + "id": "RG-02", + "name": "Siltwake Fens", + "framing": "reed channels carry luminous sediment between movable banks. The guild reopens seasonal ferry paths.", + "pressure": "Introduces pulls, position-indexed hazards, hindrance expiry, and healing denial. Pressures stable order versus deliberate movement.", + "progression": "Tide markers shift the safe line index after a disclosed countdown; enemy pairs exploit whichever end the player leaves unsupported. Shops add EQ-041..EQ-080; route rewards add AR-009..AR-015.", + "assignment": "EN-R2-01..EN-R2-12; BO-R2-01..BO-R2-02.", + "production": "E/P/A/V/S/M: layered reeds/waterline, non-color tide notches, wet movement family, low pulse/hazard events, reed-and-drum tension stem." + } + ], + "contracts": [ + { + "id": "CTR-R1-01", + "name": "Windglass Verge Contract 1", + "region_id": "RG-01", + "ordinal": 1, + "round_count": 12, + "boss_id": "BO-R1-01", + "clear_fact": "CLEAR-RG01-C01", + "first_clear_renown": 14, + "derived": "region matrix + RG-01 contract baseline" + }, + { + "id": "CTR-R1-02", + "name": "Windglass Verge Contract 2", + "region_id": "RG-01", + "ordinal": 2, + "round_count": 12, + "boss_id": "BO-R1-02", + "clear_fact": "CLEAR-RG01-C02", + "first_clear_renown": 14, + "derived": "region matrix + RG-01 contract baseline" + }, + { + "id": "CTR-R2-01", + "name": "Siltwake Fens Contract 1", + "region_id": "RG-02", + "ordinal": 1, + "round_count": 12, + "boss_id": "BO-R2-01", + "clear_fact": "CLEAR-RG02-C01", + "first_clear_renown": 18, + "derived": "region matrix + RG-01 contract baseline" + }, + { + "id": "CTR-R2-02", + "name": "Siltwake Fens Contract 2", + "region_id": "RG-02", + "ordinal": 2, + "round_count": 12, + "boss_id": "BO-R2-02", + "clear_fact": "CLEAR-RG02-C02", + "first_clear_renown": 18, + "derived": "region matrix + RG-01 contract baseline" + } + ], + "content_stage": { + "through_region": 2, + "expected_regions": 8 + } } diff --git a/runtime/p10_0/content_registry.gd b/runtime/p10_0/content_registry.gd index 0066730b..0ca50d9f 100644 --- a/runtime/p10_0/content_registry.gd +++ b/runtime/p10_0/content_registry.gd @@ -6,14 +6,19 @@ const SOURCE_PATH := "prototype/site/data/catalog.json" const SOURCE_SHA256 := "fb99bcd75ca6308091fa56b45f8da551276a496a1f96acbb44225efe491fdfef" const SOURCE_REVISION := "phase8-p8-3r2-contact-rear-actions-replacement" +## M79 staged roll-out: the catalogue carries the contract content up to +## `CONTENT_STAGE_REGIONS`; the final stage is 8 regions with 320 equipment, +## 60 artifacts, 100 enemies, 16 bosses and 16 contracts. +const CONTENT_STAGE_REGIONS := 2 +const CONTENT_EXPECTED_REGIONS := 8 const FAMILY_COUNTS := { "professions": 36, "traits": 36, - "equipment": 40, - "artifacts": 8, - "enemies": 12, - "bosses": 2, - "contracts": 2, + "equipment": 80, + "artifacts": 15, + "enemies": 24, + "bosses": 4, + "contracts": 4, } const MECHANIC_FIELDS := { @@ -74,11 +79,15 @@ static func validate(content: Dictionary) -> Dictionary: _validate_row(row, String(family), names, execution_tags, all_ids, by_id, errors) rows_by_family[family] = by_id - var region = content.get("region", {}) - _validate_row(region, "region", names, execution_tags, all_ids, {}, errors) + var region_rows = content.get("region", []) + if region_rows is Array: + for region_row in region_rows: + _validate_row(region_row, "region", names, execution_tags, all_ids, {}, errors) + elif region_rows is Dictionary and not region_rows.is_empty(): + _validate_row(region_rows, "region", names, execution_tags, all_ids, {}, errors) _validate_contiguous_ids(rows_by_family, errors) _validate_profession_tree(rows_by_family.get("professions", {}), errors) - _validate_references(rows_by_family, region, errors) + _validate_references(rows_by_family, region_rows, errors) _validate_authority_maps(all_ids, names, execution_tags, errors) _validate_rule_signatures(content, errors) _validate_catalog_hash(content, errors) @@ -100,8 +109,13 @@ static func localized_name(content: Dictionary, row_id: String, locale: String) for row in content.get(family, []): if String(row.get("id", "")) == row_id: return String(row.get("name", "")) - var region = content.get("region", {}) - return String(region.get("name", "")) if String(region.get("id", "")) == row_id else "" + var region_rows = content.get("region", []) + if region_rows is Dictionary: + region_rows = [region_rows] + for region_row in region_rows: + if String(region_row.get("id", "")) == row_id: + return String(region_row.get("name", "")) + return "" static func _enrich_rows(content: Dictionary) -> void: @@ -112,9 +126,12 @@ static func _enrich_rows(content: Dictionary) -> void: if not row is Dictionary: continue _enrich_row(row, String(family), names, execution_tags) - var region = content.get("region", {}) - if region is Dictionary: - _enrich_row(region, "region", names, execution_tags) + var region_rows = content.get("region", []) + if region_rows is Dictionary: + region_rows = [region_rows] + for region_row in region_rows: + if region_row is Dictionary: + _enrich_row(region_row, "region", names, execution_tags) static func _enrich_row(row: Dictionary, family: String, names: Dictionary, execution_tags: Dictionary) -> void: @@ -178,14 +195,20 @@ static func _validate_row(row, family: String, names: Dictionary, execution_tags static func _validate_contiguous_ids(rows: Dictionary, errors: Array[String]) -> void: + var equipment_count := int(FAMILY_COUNTS.equipment) + var locals := {"enemies": [], "bosses": [], "contracts": []} + for region in range(1, CONTENT_STAGE_REGIONS + 1): + locals.enemies.append_array(_id_range("EN-R%d-" % region, 12)) + locals.bosses.append_array(_id_range("BO-R%d-" % region, 2)) + locals.contracts.append_array(_id_range("CTR-R%d-" % region, 2)) var expected := { "professions": _id_range("PF-B", 12) + _id_range("PF-A", 24), "traits": _id_range("TR-", 36, 3), - "equipment": _id_range("EQ-", 40, 3), - "artifacts": _id_range("AR-", 8, 3), - "enemies": _id_range("EN-R1-", 12), - "bosses": _id_range("BO-R1-", 2), - "contracts": _id_range("CTR-R1-", 2), + "equipment": _id_range("EQ-", equipment_count, 3), + "artifacts": _id_range("AR-", int(FAMILY_COUNTS.artifacts), 3), + "enemies": locals.enemies, + "bosses": locals.bosses, + "contracts": locals.contracts, } for family in expected: var actual: Array = rows.get(family, {}).keys() @@ -217,23 +240,26 @@ static func _validate_profession_tree(professions: Dictionary, errors: Array[Str errors.append("profession_descendants:%s" % base_id) -static func _validate_references(rows: Dictionary, region, errors: Array[String]) -> void: - if not region is Dictionary or String(region.get("id", "")) != "RG-01": +static func _validate_references(rows: Dictionary, region_rows, errors: Array[String]) -> void: + var region_ids := {} + if region_rows is Dictionary: + region_rows = [region_rows] + for region_row in region_rows: + if region_row is Dictionary: + region_ids[String(region_row.get("id", ""))] = true + if region_ids.is_empty(): errors.append("region_id") return var bosses: Dictionary = rows.get("bosses", {}) for contract_id in rows.get("contracts", {}): var contract: Dictionary = rows.contracts[contract_id] - if String(contract.get("region_id", "")) != "RG-01": + var region_id := String(contract.get("region_id", "")) + if not region_ids.has(region_id): errors.append("contract_region:%s" % contract_id) if not bosses.has(String(contract.get("boss_id", ""))): errors.append("contract_boss:%s" % contract_id) if int(contract.get("round_count", 0)) != 12: errors.append("contract_rounds:%s" % contract_id) - if String(rows.contracts.get("CTR-R1-01", {}).get("boss_id", "")) != "BO-R1-01": - errors.append("contract_order:CTR-R1-01") - if String(rows.contracts.get("CTR-R1-02", {}).get("boss_id", "")) != "BO-R1-02": - errors.append("contract_order:CTR-R1-02") static func _validate_authority_maps(all_ids: Dictionary, names: Dictionary, execution_tags: Dictionary, errors: Array[String]) -> void: @@ -254,7 +280,11 @@ static func _validate_rule_signatures(content: Dictionary, errors: Array[String] for family in FAMILY_COUNTS: for row in content.get(family, []): _validate_rule_signature(row, String(family), seen, errors) - _validate_rule_signature(content.get("region", {}), "region", seen, errors) + var region_rows = content.get("region", []) + if region_rows is Dictionary: + region_rows = [region_rows] + for region_row in region_rows: + _validate_rule_signature(region_row, "region", seen, errors) static func _validate_rule_signature(row, family: String, seen: Dictionary, errors: Array[String]) -> void: diff --git a/runtime/p10_0/offer_generator.gd b/runtime/p10_0/offer_generator.gd index 4bc6b3a1..5187e176 100644 --- a/runtime/p10_0/offer_generator.gd +++ b/runtime/p10_0/offer_generator.gd @@ -108,7 +108,7 @@ func opening_offers(run_creation_draft: Dictionary, presentation_revision: int = return {"ok": true, "code": "ok", "materialization": materialization, "offers": offers} -func market_board(run_seed: String, contract_id: String, market_index: int, refresh_index: int = 0, presentation_revision: int = 0) -> Dictionary: +func market_board(run_seed: String, contract_id: String, market_index: int, refresh_index: int = 0, presentation_revision: int = 0, region_index: int = 1) -> Dictionary: if not bool(_validation.get("ok", false)): return {"ok": false, "code": "invalid_catalog", "errors": _validation.get("errors", []).duplicate()} if not _is_seed(run_seed): @@ -145,8 +145,8 @@ func market_board(run_seed: String, contract_id: String, market_index: int, refr used_pairs.append("%s|%s" % [record.profession_id, record.trait_id]) recruit_offers.append(record) - var equipment_offers := _equipment_offers(run_seed, contract_id, market_index, refresh_index) - var battle_offers := _battle_offers(run_seed, contract_id, market_index) + var equipment_offers := _equipment_offers(run_seed, contract_id, market_index, refresh_index, region_index) + var battle_offers := _battle_offers(run_seed, contract_id, market_index, region_index) var board := { "board_id": _low128(_digest(run_seed, "market-board", [contract_id, market_index, refresh_index])), "run_seed": run_seed, @@ -222,10 +222,23 @@ func _generate_recruit(generation_seed: String, recruit_id: String, offer_id: St return record -func _equipment_offers(run_seed: String, contract_id: String, market_index: int, refresh_index: int) -> Array[Dictionary]: +func _region_equipment_band(region_index: int) -> Array[String]: + # 40 equipment identities per region, assigned by the region matrix. + var band: Array[String] = [] + for slot in range(40): + var ordinal := (region_index - 1) * 40 + slot + 1 + band.append("EQ-%03d" % ordinal) + return band + + +func _equipment_offers(run_seed: String, contract_id: String, market_index: int, refresh_index: int, region_index: int) -> Array[Dictionary]: var equipment := ContentRegistry.rows_by_id(_catalog, "equipment") var available: Array[String] = [] - available.assign(equipment.keys()) + for candidate in _region_equipment_band(region_index): + if equipment.has(candidate): + available.append(candidate) + if available.is_empty(): + available.assign(equipment.keys()) available.sort() var selected := _draw_unique(available, run_seed, "market-equipment", 4, [contract_id, market_index, refresh_index]) var offers: Array[Dictionary] = [] @@ -244,11 +257,14 @@ func _equipment_offers(run_seed: String, contract_id: String, market_index: int, return offers -func _battle_offers(run_seed: String, contract_id: String, market_index: int) -> Array[Dictionary]: +func _battle_offers(run_seed: String, contract_id: String, market_index: int, region_index: int) -> Array[Dictionary]: var enemy_rows := ContentRegistry.rows_by_id(_catalog, "enemies") var ordinary: Array[String] = [] var elite: Array[String] = [] for enemy_id in enemy_rows: + var row_region := int(enemy_rows[enemy_id].get("region", 1)) + if row_region != region_index: + continue if String(enemy_rows[enemy_id].get("class", "")) == "elite": elite.append(enemy_id) else: diff --git a/runtime/tests/p10_0/content_registry_test.gd b/runtime/tests/p10_0/content_registry_test.gd index 56bd09c0..db17c48d 100644 --- a/runtime/tests/p10_0/content_registry_test.gd +++ b/runtime/tests/p10_0/content_registry_test.gd @@ -22,7 +22,7 @@ func run_test() -> void: verify_localization_and_execution(first) verify_negative_boundaries(first) if failures.is_empty(): - print("ABG_P10_0_CONTENT_REGISTRY_OK professions=36 base=12 advanced=24 descendants=2 traits=36 equipment=40 artifacts=8 enemies=12 bosses=2 contracts=2 localized=137 execution_tags=137 signatures=unique source=exact negative_cases=%d" % rejected_cases) + print("ABG_P10_0_CONTENT_REGISTRY_OK professions=%d base=12 advanced=24 descendants=2 traits=%d equipment=%d artifacts=%d enemies=%d bosses=%d contracts=%d localized=%d execution_tags=%d signatures=unique source=exact negative_cases=%d" % [first.professions.size(), first.traits.size(), first.equipment.size(), first.artifacts.size(), first.enemies.size(), first.bosses.size(), first.contracts.size(), first.names_zh.size(), first.execution_tags.size(), rejected_cases]) quit(0) else: for failure in failures: @@ -35,12 +35,25 @@ func verify_counts_and_identity(content: Dictionary) -> void: expect(content.professions.filter(func(row): return row.family == "base").size() == 12, "12 Professions are base") expect(content.professions.filter(func(row): return row.family == "advanced").size() == 24, "24 Professions are advanced") expect(content.traits.size() == 36, "36 Traits are exposed") - expect(content.equipment.size() == 40, "EQ-001..EQ-040 are exposed") - expect(content.artifacts.size() == 8, "AR-001..AR-008 are exposed") - expect(content.enemies.size() == 12, "EN-R1-01..12 are exposed") - expect(content.bosses.size() == 2, "both Windglass Bosses are exposed") - expect(content.contracts.size() == 2, "both ordered Contract descriptors are exposed") - expect(content.region.id == "RG-01", "Windglass Region identity is exact") + # M79 staged roll-out: the expected counts follow the registry's stage so + # the gate grows region by region with the catalogue. + var stage := int(Content.CONTENT_STAGE_REGIONS) + var equipment_per_region := 40 + var artifacts_per_region := [8, 7, 7, 8, 7, 8, 7, 8] + var enemies_per_region := [12, 12, 12, 12, 13, 12, 13, 13] + var expected_equipment := equipment_per_region * stage + var expected_artifacts := 0 + var expected_enemies := 0 + for index in range(stage): + expected_artifacts += int(artifacts_per_region[index]) + expected_enemies += int(enemies_per_region[index]) + expect(content.equipment.size() == expected_equipment, "EQ-001..EQ-%03d are exposed" % expected_equipment) + expect(content.artifacts.size() == expected_artifacts, "AR-001..AR-%03d are exposed" % expected_artifacts) + expect(content.enemies.size() == expected_enemies, "EN rows through region %d are exposed" % stage) + expect(content.bosses.size() == 2 * stage, "both bosses per shipped region are exposed") + expect(content.contracts.size() == 2 * stage, "both Contract descriptors per shipped region are exposed") + expect(content.region.size() == stage, "region rows through region %d are exposed" % stage) + expect(content.region[0].id == "RG-01", "Windglass Region identity is exact") expect(content.source.path == Content.SOURCE_PATH and content.source.sha256 == Content.SOURCE_SHA256 and content.source.authority_revision == Content.SOURCE_REVISION, "source metadata is exact") expect(content.contracts[0].boss_id == "BO-R1-01" and content.contracts[1].boss_id == "BO-R1-02", "Contract order resolves exact Bosses") @@ -59,13 +72,11 @@ func verify_profession_tree(content: Dictionary) -> void: func verify_localization_and_execution(content: Dictionary) -> void: var seen_signatures := {} var row_count := 0 - for family in ["professions", "traits", "equipment", "artifacts", "enemies", "bosses", "contracts"]: + for family in ["professions", "traits", "equipment", "artifacts", "enemies", "bosses", "contracts", "region"]: for row in content[family]: verify_runtime_row(row, seen_signatures) row_count += 1 - verify_runtime_row(content.region, seen_signatures) - row_count += 1 - expect(row_count == 137 and content.names_zh.size() == 137 and content.execution_tags.size() == 137, "all 137 rows have complete authorities") + expect(row_count == content.names_zh.size() and row_count == content.execution_tags.size(), "all %d rows have complete authorities" % row_count) expect(Content.localized_name(content, "PF-B01", "zh_CN") == "护心卫士", "zh_CN lookup uses catalog authority") expect(Content.localized_name(content, "PF-B01", "en") == "Hearthguard", "English lookup uses catalog authority") diff --git a/tools/content/build_catalog_from_contract.py b/tools/content/build_catalog_from_contract.py new file mode 100644 index 00000000..ee01e8a4 --- /dev/null +++ b/tools/content/build_catalog_from_contract.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +"""Implement `docs/05_CONTENT_CATALOG.md` content into the runtime catalog. + +The design contract is the authority; this script reads its tables and adds the +missing rows to `runtime/p10_0/catalog.json`. Existing rows are kept byte for +byte so the runtime keeps whatever was already reviewed. Nothing is invented +except the per-region Contract rows, which are derived from the region matrix +plus the RG-01 contract baseline and marked as derived. +""" +from __future__ import annotations + +import json +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +DOC = ROOT / "docs/05_CONTENT_CATALOG.md" +CATALOG = ROOT / "runtime/p10_0/catalog.json" + +EQUIPMENT_RE = re.compile(r"^\|\s*`(EQ-\d{3})`\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*$") +ARTIFACT_RE = re.compile(r"^\|\s*`(AR-\d{3})`\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*$") +ENEMY_RE = re.compile(r"^\|\s*`(EN-R(\d)-(\d{2}))`\s*\|\s*(ordinary|elite);\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*$") +BOSS_RE = re.compile(r"^\|\s*`(BO-R(\d)-(\d{2}))`\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*$") +REGION_RE = re.compile(r"^\|\s*`(RG-0(\d))`\s*\|\s*\*\*([^*]+)\*\*;\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*$") + + +def strip_markup(value: str) -> str: + return value.replace("**", "").replace("`", "").strip() + + +def split_escalation(column: str) -> tuple[str, str]: + """`escalation sentence. P/A/V/S: production` -> (escalation, production).""" + marker = column.find("P/A/V/S") + if marker < 0: + return strip_markup(column), "" + head = column[:marker].strip().rstrip(".") + tail = column[marker:].strip() + return strip_markup(head), strip_markup(tail) + + +def parse_rows(text: str) -> dict[str, list[dict]]: + equipment: list[dict] = [] + artifacts: list[dict] = [] + enemies: list[dict] = [] + bosses: list[dict] = [] + regions: list[dict] = [] + for line in text.splitlines(): + match = EQUIPMENT_RE.match(line) + if match: + equipment.append({ + "id": match.group(1), "name": strip_markup(match.group(2)), "fit": strip_markup(match.group(3)), + "class": strip_markup(match.group(4)), "effect": strip_markup(match.group(5)), + "tradeoff": strip_markup(match.group(6)), "production": strip_markup(match.group(7)), + }) + continue + match = ARTIFACT_RE.match(line) + if match: + artifacts.append({ + "id": match.group(1), "name": strip_markup(match.group(2)), "rule": strip_markup(match.group(3)), + "tradeoff": strip_markup(match.group(4)), "production": strip_markup(match.group(5)), + }) + continue + match = ENEMY_RE.match(line) + if match: + escalation, production = split_escalation(match.group(8)) + enemies.append({ + "id": match.group(1), "region": int(match.group(2)), "name": strip_markup(match.group(5)), + "class": match.group(4), "rule": strip_markup(match.group(6)), + "responses": strip_markup(match.group(7)), "escalation": escalation, "production": production, + }) + continue + match = BOSS_RE.match(line) + if match: + escalation, production = split_escalation(match.group(7)) + identity = strip_markup(match.group(4)) + identity = re.sub(r"^RG-\d{2}\s*;\s*", "", identity) + bosses.append({ + "id": match.group(1), "region": int(match.group(2)), "class": "RG-%02d" % int(match.group(2)), "name": identity, + "rule": strip_markup(match.group(5)), "responses": strip_markup(match.group(6)), + "escalation": escalation, "production": production, + }) + continue + match = REGION_RE.match(line) + if match: + regions.append({ + "id": match.group(1), "name": strip_markup(match.group(3)), "framing": strip_markup(match.group(4)), + "pressure": strip_markup(match.group(5)), "progression": strip_markup(match.group(6)), + "assignment": strip_markup(match.group(7)), "production": strip_markup(match.group(8)), + }) + return {"equipment": equipment, "artifacts": artifacts, "enemies": enemies, "bosses": bosses, "region": regions} + + +def merge(existing: list, incoming: list, family: str) -> tuple[list, int]: + """Contract rows replace same-id rows; unknown rows are appended in order.""" + incoming_by_id = {row["id"]: dict(row) for row in incoming} + kept: list[dict] = [] + seen: set[str] = set() + for row in existing: + row_id = row.get("id") + if row_id in incoming_by_id: + kept.append(incoming_by_id[row_id]) + seen.add(row_id) + elif row_id is not None: + kept.append(row) + seen.add(row_id) + added = 0 + for row in incoming: + if row["id"] in seen: + continue + kept.append(dict(row)) + added += 1 + return kept, added + + +def derive_contracts(regions: list[dict], bosses: list[dict]) -> list[dict]: + """Two contracts per region, paired to that region's bosses. + + Round count follows the RG-01 baseline; first-clear renown steps with the + region index. Marked derived so the owner review can confirm the numbers. + """ + by_region: dict[int, list[str]] = {} + for boss in bosses: + region_key = boss.get("region") + if region_key is None: + # rows added earlier only carry the region through their class column + region_key = int(str(boss.get("class", "RG-01")).split("-")[-1]) + by_region.setdefault(int(region_key), []).append(boss["id"]) + contracts: list[dict] = [] + for region in regions: + index = int(region["id"].split("-")[1]) + boss_ids = sorted(by_region.get(index, [])) + for slot, boss_id in enumerate(boss_ids, start=1): + contracts.append({ + "id": "CTR-R%d-%02d" % (index, slot), + "name": "%s Contract %d" % (region["name"], slot), + "region_id": region["id"], + "ordinal": slot, + "round_count": 12, + "boss_id": boss_id, + "clear_fact": "CLEAR-RG%02d-C%02d" % (index, slot), + "first_clear_renown": 14 + 4 * (index - 1), + "derived": "region matrix + RG-01 contract baseline", + }) + return contracts + + +REGION_EQUIPMENT = {1: (1, 40), 2: (41, 80), 3: (81, 120), 4: (121, 160), 5: (161, 200), 6: (201, 240), 7: (241, 280), 8: (281, 320)} +REGION_ARTIFACTS = {1: (1, 8), 2: (9, 15), 3: (16, 22), 4: (23, 30), 5: (31, 37), 6: (38, 45), 7: (46, 52), 8: (53, 60)} +LOCALE = ROOT / "tools/content/content_locale_zh.json" + + +def in_scope(through_region: int) -> dict: + """Row filters for a staged region roll-out.""" + equipment_hi = REGION_EQUIPMENT[through_region][1] + artifact_hi = REGION_ARTIFACTS[through_region][1] + return {"equipment_hi": equipment_hi, "artifact_hi": artifact_hi, "region": through_region} + + +def main() -> int: + import sys + through_region = 2 + for index, argument in enumerate(sys.argv): + if argument == "--through-region" and index + 1 < len(sys.argv): + through_region = int(sys.argv[index + 1]) + scope = in_scope(through_region) + text = DOC.read_text() + parsed = parse_rows(text) + parsed["equipment"] = [row for row in parsed["equipment"] if int(row["id"].split("-")[1]) <= scope["equipment_hi"]] + parsed["artifacts"] = [row for row in parsed["artifacts"] if int(row["id"].split("-")[1]) <= scope["artifact_hi"]] + parsed["enemies"] = [row for row in parsed["enemies"] if int(row["region"]) <= scope["region"]] + parsed["bosses"] = [row for row in parsed["bosses"] if int(row["region"]) <= scope["region"]] + parsed["region"] = [row for row in parsed["region"] if int(row["id"].split("-")[1]) <= scope["region"]] + locale = json.loads(LOCALE.read_text()) if LOCALE.exists() else {} + catalog = json.loads(CATALOG.read_text()) + report = {} + for family in ["equipment", "artifacts", "enemies", "bosses", "region"]: + # The contract is the authority: the scoped catalogue is rebuilt from it. + catalog[family] = parsed[family] + report[family] = {"parsed": len(parsed[family]), "total": len(catalog[family])} + expected = {"equipment": 320, "artifacts": 60, "enemies": 100, "bosses": 16, "region": 8} + for family, want in expected.items(): + if report[family]["total"] != want: + print(f"COUNT MISMATCH {family}: have {report[family]['total']} want {want}") + derived = derive_contracts(catalog["region"], catalog["bosses"]) + catalog["contracts"] = derived + # locale maps drive the localised names and semantic tags of every in-scope row + names_zh: dict[str, str] = {} + execution_tags: dict[str, list] = {} + missing: list[str] = [] + for family in ["professions", "traits", "equipment", "artifacts", "enemies", "bosses", "region", "contracts"]: + rows = catalog.get(family, []) + if isinstance(rows, dict): + rows = [rows] + for row in rows: + row_id = row.get("id") + entry = locale.get(family, {}).get(row_id) + if not entry or not entry.get("name_zh") or not entry.get("tags"): + missing.append("%s:%s" % (family, row_id)) + continue + names_zh[row_id] = entry["name_zh"] + execution_tags[row_id] = entry["tags"] + catalog["names_zh"] = names_zh + catalog["execution_tags"] = execution_tags + catalog["content_stage"] = {"through_region": scope["region"], "expected_regions": 8} + report["locale"] = {"mapped": len(names_zh), "missing": missing[:8], "missing_total": len(missing)} + report["contracts"] = {"total": len(derived)} + catalog.setdefault("source", {}) + CATALOG.write_text(json.dumps(catalog, ensure_ascii=False, indent=1) + "\n") + print(json.dumps(report, ensure_ascii=False, indent=1)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/content/content_locale_zh.json b/tools/content/content_locale_zh.json new file mode 100644 index 00000000..e92228a2 --- /dev/null +++ b/tools/content/content_locale_zh.json @@ -0,0 +1,1678 @@ +{ + "_note": "Authoritative zh_CN names and semantic execution tags per content id. The catalogue builder writes these into runtime/p10_0/catalog.json; every row in the shipped region scope must have an entry.", + "professions": { + "PF-B01": { + "name_zh": "护心卫士", + "tags": [ + "front_hold", + "guard", + "intercept", + "push_resist" + ] + }, + "PF-B02": { + "name_zh": "锋刃手", + "tags": [ + "single_pressure", + "edge", + "execute", + "overflow_convert" + ] + }, + "PF-B03": { + "name_zh": "长枪使", + "tags": [ + "reach_control", + "poise", + "pierce", + "move_cost" + ] + }, + "PF-B04": { + "name_zh": "逐风行者", + "tags": [ + "reorder", + "momentum", + "swap", + "push_protect" + ] + }, + "PF-B05": { + "name_zh": "远弦射手", + "tags": [ + "projectile", + "aim", + "rear_target", + "line_stability" + ] + }, + "PF-B06": { + "name_zh": "裂隙法师", + "tags": [ + "area", + "charge", + "echo", + "interrupt_cost" + ] + }, + "PF-B07": { + "name_zh": "仁心医者", + "tags": [ + "restore", + "remedy", + "cleanse", + "no_overheal_gain" + ] + }, + "PF-B08": { + "name_zh": "结契师", + "tags": [ + "enable", + "bond", + "neighbor_link", + "tag_variety" + ] + }, + "PF-B09": { + "name_zh": "机关匠", + "tags": [ + "construct", + "parts", + "device", + "salvage_on_move" + ] + }, + "PF-B10": { + "name_zh": "暮兆术士", + "tags": [ + "impair", + "omen", + "hinder", + "expiry_extend" + ] + }, + "PF-B11": { + "name_zh": "律钟师", + "tags": [ + "tempo", + "verse", + "ally_advance", + "enemy_delay" + ] + }, + "PF-B12": { + "name_zh": "辎重官", + "tags": [ + "supply", + "item_refresh", + "retreat_step", + "target_rotation" + ] + }, + "PF-A01": { + "name_zh": "门垒卫", + "tags": [ + "front_hold", + "directional_intercept", + "guard_refund" + ] + }, + "PF-A02": { + "name_zh": "冲城卫", + "tags": [ + "front_hold", + "guard_spend", + "push", + "follow_step" + ] + }, + "PF-A03": { + "name_zh": "终光刃", + "tags": [ + "single_pressure", + "edge_wager", + "execute", + "exposed_cost" + ] + }, + "PF-A04": { + "name_zh": "回锋剑士", + "tags": [ + "single_pressure", + "edge_reserve", + "block", + "return_cut" + ] + }, + "PF-A05": { + "name_zh": "钩阵统领", + "tags": [ + "reach_control", + "poise_spend", + "pull", + "enemy_reorder" + ] + }, + "PF-A06": { + "name_zh": "日耀枪骑", + "tags": [ + "reach_control", + "poise_spend", + "line_pierce", + "declining_damage" + ] + }, + "PF-A07": { + "name_zh": "传步刺客", + "tags": [ + "reorder", + "momentum_spend", + "swap", + "ally_advance" + ] + }, + "PF-A08": { + "name_zh": "逆风刃", + "tags": [ + "reorder", + "sprint", + "multi_target", + "exposed_cost" + ] + }, + "PF-A09": { + "name_zh": "定穴射手", + "tags": [ + "projectile", + "aim_spend", + "movement_delay", + "interrupt" + ] + }, + "PF-A10": { + "name_zh": "箭雨使", + "tags": [ + "projectile", + "aim_spend", + "three_targets", + "no_repeat" + ] + }, + "PF-A11": { + "name_zh": "风暴算师", + "tags": [ + "area", + "charge", + "cadence_chain", + "declining_damage" + ] + }, + "PF-A12": { + "name_zh": "空星术士", + "tags": [ + "area", + "charge_preserve", + "self_hinder", + "focused_release" + ] + }, + "PF-A13": { + "name_zh": "战地医师", + "tags": [ + "restore", + "remedy", + "heal", + "rear_swap" + ] + }, + "PF-A14": { + "name_zh": "延疗师", + "tags": [ + "restore", + "remedy_spend", + "delayed_heal", + "damage_bonus" + ] + }, + "PF-A15": { + "name_zh": "三联契师", + "tags": [ + "enable", + "bond", + "three_link", + "same_tag_fray" + ] + }, + "PF-A16": { + "name_zh": "担契者", + "tags": [ + "enable", + "bond_gain", + "cost_transfer", + "self_burden" + ] + }, + "PF-A17": { + "name_zh": "哨械师", + "tags": [ + "construct", + "parts_spend", + "rear_device", + "repeat_target" + ] + }, + "PF-A18": { + "name_zh": "陷阵工匠", + "tags": [ + "construct", + "parts_spend", + "position_trap", + "forced_move_trigger" + ] + }, + "PF-A19": { + "name_zh": "苍白先知", + "tags": [ + "impair", + "omen", + "linked_expiry", + "cleanse_risk" + ] + }, + "PF-A20": { + "name_zh": "苦兆术士", + "tags": [ + "impair", + "consume_hindrance", + "burst", + "expose" + ] + }, + "PF-A21": { + "name_zh": "行军钟师", + "tags": [ + "tempo", + "verse_spend", + "neighbor_sync", + "slowest_timing" + ] + }, + "PF-A22": { + "name_zh": "寂音师", + "tags": [ + "tempo", + "verse_spend", + "sound_interrupt", + "self_silence" + ] + }, + "PF-A23": { + "name_zh": "觅迹补给官", + "tags": [ + "supply", + "enemy_sample", + "shop_counter", + "future_value" + ] + }, + "PF-A24": { + "name_zh": "阵线搬运师", + "tags": [ + "supply", + "item_trigger_transfer", + "neighbor_only", + "item_stays" + ] + } + }, + "equipment": { + "EQ-041": { + "name_zh": "邻壁护墙", + "tags": [ + "guard", + "intercept", + "neighbor_protect" + ] + }, + "EQ-042": { + "name_zh": "疏孔圆盾", + "tags": [ + "guard", + "area_share", + "neighbor" + ] + }, + "EQ-043": { + "name_zh": "护值导环", + "tags": [ + "guard", + "transfer", + "expiry" + ] + }, + "EQ-044": { + "name_zh": "露锋战旗", + "tags": [ + "exposed", + "taunt", + "ally_cover" + ] + }, + "EQ-045": { + "name_zh": "二次受击甲", + "tags": [ + "guard", + "repeat_hit_resist", + "class_b" + ] + }, + "EQ-046": { + "name_zh": "医者护轨", + "tags": [ + "heal", + "push_resist", + "protection" + ] + }, + "EQ-047": { + "name_zh": "脆面巨盾", + "tags": [ + "guard", + "projectile_screen", + "opening" + ] + }, + "EQ-048": { + "name_zh": "后向神盾", + "tags": [ + "guard", + "rear_origin_block", + "front_protect" + ] + }, + "EQ-049": { + "name_zh": "借来的掩护", + "tags": [ + "guard", + "copy", + "behind_guard" + ] + }, + "EQ-050": { + "name_zh": "破露头盔", + "tags": [ + "exposed", + "action_advance", + "class_b" + ] + }, + "EQ-051": { + "name_zh": "静默拦截者", + "tags": [ + "intercept", + "no_resource_gain", + "class_b" + ] + }, + "EQ-052": { + "name_zh": "蓄热披风", + "tags": [ + "area_store", + "release_on_move", + "class_b" + ] + }, + "EQ-053": { + "name_zh": "空护笼", + "tags": [ + "guard", + "strip_convert", + "class_b" + ] + }, + "EQ-054": { + "name_zh": "三击灯台", + "tags": [ + "screen", + "spell_screen", + "hit_counter" + ] + }, + "EQ-055": { + "name_zh": "邻誓胸甲", + "tags": [ + "guard", + "choose_side", + "protection_scope" + ] + }, + "EQ-056": { + "name_zh": "回推框架", + "tags": [ + "push_reflect", + "front", + "class_b" + ] + }, + "EQ-057": { + "name_zh": "慈护屏", + "tags": [ + "projectile", + "survive_at_one", + "support" + ] + }, + "EQ-058": { + "name_zh": "低护围裙", + "tags": [ + "guard", + "share", + "low_vitality_neighbor" + ] + }, + "EQ-059": { + "name_zh": "逆印盾", + "tags": [ + "mark_convert", + "guard", + "cleanse" + ] + }, + "EQ-060": { + "name_zh": "仅邻共享甲", + "tags": [ + "guard", + "share_damage", + "one_neighbor" + ] + }, + "EQ-061": { + "name_zh": "提前钟", + "tags": [ + "tempo", + "earlier_tick", + "class_b" + ] + }, + "EQ-062": { + "name_zh": "屏息面罩", + "tags": [ + "windup", + "interrupt_survive", + "class_b" + ] + }, + "EQ-063": { + "name_zh": "断复剪", + "tags": [ + "defeat", + "recovery_clear", + "class_b" + ] + }, + "EQ-064": { + "name_zh": "后拍指环", + "tags": [ + "tempo", + "resource_gain", + "neighbor" + ] + }, + "EQ-065": { + "name_zh": "缓燃引信", + "tags": [ + "burst", + "delayed", + "follow" + ] + }, + "EQ-066": { + "name_zh": "速引顶针", + "tags": [ + "projectile", + "no_windup", + "class_b" + ] + }, + "EQ-067": { + "name_zh": "正午刻度盘", + "tags": [ + "cooldown_refresh", + "midpoint", + "class_b" + ] + }, + "EQ-068": { + "name_zh": "错拍扣具", + "tags": [ + "action_advance", + "store_release", + "class_b" + ] + }, + "EQ-069": { + "name_zh": "耐性笔羽", + "tags": [ + "hindrance", + "lengthen", + "patience" + ] + }, + "EQ-070": { + "name_zh": "断念烛", + "tags": [ + "interrupt", + "immediate_trigger", + "class_b" + ] + }, + "EQ-071": { + "name_zh": "邻位时钟", + "tags": [ + "cadence", + "opposite_band", + "choose_neighbor" + ] + }, + "EQ-072": { + "name_zh": "三巡齿轮", + "tags": [ + "device", + "repeat", + "multi_target" + ] + }, + "EQ-073": { + "name_zh": "破晓复元带", + "tags": [ + "recovery", + "interrupted", + "boss" + ] + }, + "EQ-074": { + "name_zh": "迟净药瓶", + "tags": [ + "cleanse", + "action_advance", + "class_b" + ] + }, + "EQ-075": { + "name_zh": "未成节拍器", + "tags": [ + "fizzle", + "fast_basic", + "class_b" + ] + }, + "EQ-076": { + "name_zh": "双启纺线", + "tags": [ + "linked", + "windup", + "neighbor" + ] + }, + "EQ-077": { + "name_zh": "歇步鞋", + "tags": [ + "reorder", + "recovery_end", + "step" + ] + }, + "EQ-078": { + "name_zh": "观首镜片", + "tags": [ + "boss", + "cooldown_pause", + "telegraph" + ] + }, + "EQ-079": { + "name_zh": "短忆弹簧", + "tags": [ + "repeat", + "windup_shorten", + "class_b" + ] + }, + "EQ-080": { + "name_zh": "长忆弹簧", + "tags": [ + "alternate", + "recovery_shorten", + "class_b" + ] + }, + "EQ-001": { + "name_zh": "退步靴", + "tags": [ + "first_direct_hit", + "rear_step", + "next_basic_delay" + ] + }, + "EQ-002": { + "name_zh": "钉前护胫", + "tags": [ + "first_push", + "push_resist", + "post_basic_hinder" + ] + }, + "EQ-003": { + "name_zh": "传步腰带", + "tags": [ + "ally_swap", + "ally_advance", + "wearer_delay" + ] + }, + "EQ-004": { + "name_zh": "寻隙凉鞋", + "tags": [ + "vacated_position", + "forward_step", + "guard_loss" + ] + }, + "EQ-005": { + "name_zh": "铅坠束带", + "tags": [ + "forced_move", + "movement_reduce", + "pace_cost" + ] + }, + "EQ-006": { + "name_zh": "后钩披风", + "tags": [ + "rear_target", + "front_pull", + "next_action_delay" + ] + }, + "EQ-007": { + "name_zh": "转轴脚环", + "tags": [ + "position_change", + "trigger_reset", + "cooldown_cost" + ] + }, + "EQ-008": { + "name_zh": "静路绑带", + "tags": [ + "stationary", + "poise_gain", + "move_reset" + ] + }, + "EQ-009": { + "name_zh": "救援环扣", + "tags": [ + "ally_low_hp", + "rescue_step", + "self_delay" + ] + }, + "EQ-010": { + "name_zh": "前位主张", + "tags": [ + "front_entry", + "target_priority", + "guard_cost" + ] + }, + "EQ-011": { + "name_zh": "邻位带扣", + "tags": [ + "neighbor_hit", + "guard_share", + "pace_cost" + ] + }, + "EQ-012": { + "name_zh": "开阵系带", + "tags": [ + "open_line", + "reach_bonus", + "guard_lock" + ] + }, + "EQ-013": { + "name_zh": "交步徽记", + "tags": [ + "cross_swap", + "action_advance", + "self_hinder" + ] + }, + "EQ-014": { + "name_zh": "静默后甲", + "tags": [ + "rear_position", + "projectile_resist", + "move_disable" + ] + }, + "EQ-015": { + "name_zh": "接力马刺", + "tags": [ + "wearer_swap", + "position_trigger_refresh", + "partner_lock" + ] + }, + "EQ-016": { + "name_zh": "空位铃", + "tags": [ + "empty_position", + "cadence_advance", + "position_fill_reset" + ] + }, + "EQ-017": { + "name_zh": "撑钩护腕", + "tags": [ + "guarded", + "pull_resist", + "guard_break_cost" + ] + }, + "EQ-018": { + "name_zh": "远行缎带", + "tags": [ + "three_positions", + "item_refresh", + "repeat_reset" + ] + }, + "EQ-019": { + "name_zh": "定步石", + "tags": [ + "stationary_count", + "guard_gain", + "forced_move_reset" + ] + }, + "EQ-020": { + "name_zh": "守线标签", + "tags": [ + "neighbor_move", + "safe_step", + "item_pause" + ] + }, + "EQ-021": { + "name_zh": "威胁镜片", + "tags": [ + "threat_preview", + "target_reveal", + "damage_cost" + ] + }, + "EQ-022": { + "name_zh": "前标粉笔", + "tags": [ + "foremost_mark", + "ally_focus", + "rear_exposed" + ] + }, + "EQ-023": { + "name_zh": "后阵呼哨", + "tags": [ + "rear_threat", + "target_redirect", + "front_delay" + ] + }, + "EQ-024": { + "name_zh": "仁慈刻度盘", + "tags": [ + "lowest_hp", + "heal_target", + "overheal_lock" + ] + }, + "EQ-025": { + "name_zh": "分选硬币", + "tags": [ + "equal_targets", + "alternate_family", + "critical_lock" + ] + }, + "EQ-026": { + "name_zh": "召唤刺针", + "tags": [ + "summon", + "target_mark", + "owner_delay" + ] + }, + "EQ-027": { + "name_zh": "伤势测带", + "tags": [ + "wounded_target", + "damage_bonus", + "healthy_cost" + ] + }, + "EQ-028": { + "name_zh": "破盾缝锥", + "tags": [ + "guarded_enemy", + "guard_strip", + "raw_damage_cost" + ] + }, + "EQ-029": { + "name_zh": "疾影墨水", + "tags": [ + "fastest_enemy", + "hinder", + "slow_target_lock" + ] + }, + "EQ-030": { + "name_zh": "末目标绳结", + "tags": [ + "last_target", + "repeat_bonus", + "retarget_cost" + ] + }, + "EQ-031": { + "name_zh": "护卫标签", + "tags": [ + "ally_targeted", + "intercept", + "self_exposed" + ] + }, + "EQ-032": { + "name_zh": "显形粉末", + "tags": [ + "screened_enemy", + "screen_remove", + "damage_lock" + ] + }, + "EQ-033": { + "name_zh": "对面镜盾", + "tags": [ + "front_attack", + "reflect", + "rear_bypass" + ] + }, + "EQ-034": { + "name_zh": "低潮量规", + "tags": [ + "low_resource", + "resource_gain", + "full_resource_delay" + ] + }, + "EQ-035": { + "name_zh": "开侧弦线", + "tags": [ + "open_flank", + "rear_target", + "foremost_damage_cost" + ] + }, + "EQ-036": { + "name_zh": "双重准星", + "tags": [ + "area_action", + "second_target", + "primary_damage_cost" + ] + }, + "EQ-037": { + "name_zh": "打断账册", + "tags": [ + "interrupt", + "cooldown_refresh", + "failed_interrupt_cost" + ] + }, + "EQ-038": { + "name_zh": "旧伤罗盘", + "tags": [ + "oldest_hindrance", + "cleanse", + "urgent_newer_cost" + ] + }, + "EQ-039": { + "name_zh": "崩解瞄具", + "tags": [ + "enemy_collapse", + "reserved_target", + "double_collapse_fizzle" + ] + }, + "EQ-040": { + "name_zh": "破选别针", + "tags": [ + "equal_target_families", + "alternate_default", + "critical_lock" + ] + } + }, + "enemies": { + "EN-R2-01": { + "name_zh": "芦苇钩手", + "tags": [ + "pull", + "rear_intrusion", + "order_pressure" + ] + }, + "EN-R2-02": { + "name_zh": "泥灯水蛭", + "tags": [ + "attach", + "low_vitality", + "healing_denial" + ] + }, + "EN-R2-03": { + "name_zh": "淤泥拟态", + "tags": [ + "copy", + "target_rule", + "misdirect" + ] + }, + "EN-R2-04": { + "name_zh": "堤岸陆龟", + "tags": [ + "position_mark", + "index_hazard", + "movement_punish" + ] + }, + "EN-R2-05": { + "name_zh": "沼笛手", + "tags": [ + "sound_windup", + "command", + "enemy_order" + ] + }, + "EN-R2-06": { + "name_zh": "咸水幽光", + "tags": [ + "hindrance", + "cleanse_punish", + "escalate" + ] + }, + "EN-R2-07": { + "name_zh": "渡船蟹", + "tags": [ + "sidestep", + "front_to_rear", + "reposition" + ] + }, + "EN-R2-08": { + "name_zh": "溺种荚", + "tags": [ + "defeat_seed", + "delayed", + "proximity" + ] + }, + "EN-R2-09": { + "name_zh": "泥沼医者", + "tags": [ + "restore", + "hindrance_oldest", + "enemy_support" + ] + }, + "EN-R2-10": { + "name_zh": "雾颌兽", + "tags": [ + "untargetable", + "melee_only", + "reveal_by_attack" + ] + }, + "EN-R2-11": { + "name_zh": "潮道寡妇", + "tags": [ + "thread", + "heal_transfer", + "linked_positions" + ] + }, + "EN-R2-12": { + "name_zh": "闸门长者", + "tags": [ + "safe_index", + "advance", + "hazard_shift" + ] + }, + "EN-R1-01": { + "name_zh": "晶板啮兽", + "tags": [ + "foremost_bite", + "repeat_count", + "neighbor_guard_chip" + ] + }, + "EN-R1-02": { + "name_zh": "疾风雀", + "tags": [ + "rear_projectile", + "rearmost_target", + "forward_step" + ] + }, + "EN-R1-03": { + "name_zh": "台地冲羊", + "tags": [ + "headwind_charge", + "foremost_target", + "push" + ] + }, + "EN-R1-04": { + "name_zh": "钟茎食草兽", + "tags": [ + "positive_tag", + "consume_oldest", + "no_damage" + ] + }, + "EN-R1-05": { + "name_zh": "晶背兽", + "tags": [ + "projectile_screen", + "first_reflect", + "no_chain" + ] + }, + "EN-R1-06": { + "name_zh": "原野潜猎者", + "tags": [ + "reach_filter", + "long_reach_target", + "foremost_fallback" + ] + }, + "EN-R1-07": { + "name_zh": "鸣哨飞蠓", + "tags": [ + "band_count", + "majority_delay", + "order_pressure" + ] + }, + "EN-R1-08": { + "name_zh": "玻根幼芽", + "tags": [ + "rear_growth", + "forward_step", + "rear_shield" + ] + }, + "EN-R1-09": { + "name_zh": "横风书记", + "tags": [ + "front_rear_line", + "swap_crossing", + "hinder" + ] + }, + "EN-R1-10": { + "name_zh": "崖沿跃客", + "tags": [ + "enemy_reorder", + "leap_front", + "changed_target_strike" + ] + }, + "EN-R1-11": { + "name_zh": "狂风执令官", + "tags": [ + "elite", + "indexed_position", + "push_or_projectile" + ] + }, + "EN-R1-12": { + "name_zh": "棱晶角兽", + "tags": [ + "elite", + "two_footprint", + "screen_rotation", + "damage_family" + ] + } + }, + "bosses": { + "BO-R2-01": { + "name_zh": "闸背母兽", + "tags": [ + "safe_index", + "eggs", + "hatch_pullers" + ] + }, + "BO-R2-02": { + "name_zh": "沉钟", + "tags": [ + "sound_pulse", + "linked_positions", + "phase_shift" + ] + }, + "BO-R1-01": { + "name_zh": "千镜角鹿", + "tags": [ + "boss", + "two_footprint", + "three_panes", + "sweep_push" + ] + }, + "BO-R1-02": { + "name_zh": "风织机", + "tags": [ + "boss", + "stationary", + "gust_footprint", + "spindle_reverse" + ] + } + }, + "artifacts": { + "AR-009": { + "name_zh": "熟悉的面孔", + "tags": [ + "duplicate_pairs", + "shop_composition", + "core_rule" + ] + }, + "AR-010": { + "name_zh": "广域招募", + "tags": [ + "distinct_professions", + "shop_composition", + "core_rule" + ] + }, + "AR-011": { + "name_zh": "窄阵先锋", + "tags": [ + "deploy_minus_one", + "front_guard", + "core_rule" + ] + }, + "AR-012": { + "name_zh": "长列远征", + "tags": [ + "deploy_plus_one", + "core_cap", + "core_rule" + ] + }, + "AR-013": { + "name_zh": "固定两端", + "tags": [ + "endpoint_lock", + "center_free", + "core_rule" + ] + }, + "AR-014": { + "name_zh": "流转两端", + "tags": [ + "endpoint_swap_free", + "once_per_battle", + "core_rule" + ] + }, + "AR-015": { + "name_zh": "成对执务", + "tags": [ + "pair_partition", + "enable_guard_share", + "core_rule" + ] + }, + "AR-001": { + "name_zh": "开放账册", + "tags": [ + "market_slots", + "recruit_plus", + "equipment_minus", + "conflict_ar002" + ] + }, + "AR-002": { + "name_zh": "巧匠橱窗", + "tags": [ + "market_slots", + "equipment_plus", + "recruit_minus", + "conflict_ar001" + ] + }, + "AR-003": { + "name_zh": "共同目标", + "tags": [ + "recruit_price", + "common_discount", + "rare_surcharge", + "conflict_ar004" + ] + }, + "AR-004": { + "name_zh": "窄域搜寻", + "tags": [ + "recruit_weights", + "narrow_profession", + "other_surcharge", + "conflict_ar003" + ] + }, + "AR-005": { + "name_zh": "二次面谈", + "tags": [ + "recruit_offer", + "extra_candidate", + "discard_one", + "conflict_ar006" + ] + }, + "AR-006": { + "name_zh": "新生资质", + "tags": [ + "trait_reroll", + "preserve_combat_fields", + "once_per_market", + "conflict_ar005" + ] + }, + "AR-007": { + "name_zh": "满员长凳", + "tags": [ + "reserve_capacity", + "capacity_plus", + "upkeep_cost", + "conflict_ar008" + ] + }, + "AR-008": { + "name_zh": "精简队伍", + "tags": [ + "reserve_capacity", + "capacity_minus", + "death_guarantee", + "conflict_ar007" + ] + } + }, + "region": { + "RG-02": { + "name_zh": "淤醒沼泽", + "tags": [ + "pull", + "index_hazard", + "hindrance_expiry", + "healing_denial" + ] + }, + "RG-01": { + "name_zh": "风镜原野", + "tags": [ + "region", + "windglass", + "push", + "projectile_screen" + ] + } + }, + "contracts": { + "CTR-R2-01": { + "name_zh": "淤醒渡口", + "tags": [ + "region_two", + "boss_sluice", + "region_r2" + ] + }, + "CTR-R2-02": { + "name_zh": "沉钟水道", + "tags": [ + "region_two", + "boss_bell", + "region_r2" + ] + }, + "CTR-R1-01": { + "name_zh": "风镜初渡", + "tags": [ + "contract", + "twelve_rounds", + "boss_bo_r1_01", + "clear_rg01_c01" + ] + }, + "CTR-R1-02": { + "name_zh": "风织机通路", + "tags": [ + "contract", + "twelve_rounds", + "boss_bo_r1_02", + "clear_rg01_c02" + ] + } + }, + "traits": { + "TR-001": { + "name_zh": "先行一步", + "tags": [ + "position_trigger", + "reach_bonus", + "foremost_filter_cost" + ] + }, + "TR-002": { + "name_zh": "耐心起手", + "tags": [ + "action_count", + "resource_gain", + "opening_delay" + ] + }, + "TR-003": { + "name_zh": "窄阵护卫", + "tags": [ + "neighbor_count", + "free_intercept", + "guard_transfer_lock" + ] + }, + "TR-004": { + "name_zh": "长久记忆", + "tags": [ + "repeat_target", + "duration_bonus", + "retarget_delay" + ] + }, + "TR-005": { + "name_zh": "空闲之手", + "tags": [ + "swap", + "carry_footprint", + "object_action_loss" + ] + }, + "TR-006": { + "name_zh": "开放步法", + "tags": [ + "forced_move", + "counterattack", + "self_exposed" + ] + }, + "TR-007": { + "name_zh": "深口袋", + "tags": [ + "consumable", + "duplicate_target", + "cooldown_cost" + ] + }, + "TR-008": { + "name_zh": "静默双手", + "tags": [ + "rear_cover", + "sound_immunity", + "resource_lock" + ] + }, + "TR-009": { + "name_zh": "配重步", + "tags": [ + "pull_resist", + "movement_transfer", + "failure_hinder" + ] + }, + "TR-010": { + "name_zh": "共饮药杯", + "tags": [ + "healing_received", + "neighbor_splash", + "self_heal_cost" + ] + }, + "TR-011": { + "name_zh": "醒目目标", + "tags": [ + "target_priority", + "survive_hit", + "focus_gain" + ] + }, + "TR-012": { + "name_zh": "迟步补位", + "tags": [ + "ally_fallen", + "delayed_collapse", + "action_lock" + ] + }, + "TR-013": { + "name_zh": "节俭节拍", + "tags": [ + "basic_count", + "item_cooldown", + "resource_reset" + ] + }, + "TR-014": { + "name_zh": "厚重袖口", + "tags": [ + "interrupt_convert", + "delay", + "slower_resolve" + ] + }, + "TR-015": { + "name_zh": "远听者", + "tags": [ + "enable_range", + "distant_receive", + "adjacent_lock" + ] + }, + "TR-016": { + "name_zh": "灰烬胃口", + "tags": [ + "hindered_kill", + "resource_restore", + "plain_kill_cost" + ] + }, + "TR-017": { + "name_zh": "一丝不苟", + "tags": [ + "reserved_target", + "reach_validate", + "forced_fizzle" + ] + }, + "TR-018": { + "name_zh": "薄血", + "tags": [ + "area_damage", + "action_advance", + "single_heal_cost" + ] + }, + "TR-019": { + "name_zh": "后阵守望", + "tags": [ + "rear_intruder", + "auto_mark", + "forward_reach_cost" + ] + }, + "TR-020": { + "name_zh": "器具誓约", + "tags": [ + "item_priority", + "first_trigger", + "technique_wait" + ] + }, + "TR-021": { + "name_zh": "松散阵形", + "tags": [ + "swap_role", + "resource_gain", + "repeat_partner_hinder" + ] + }, + "TR-022": { + "name_zh": "石刻计数", + "tags": [ + "same_attacker_count", + "guard_gain", + "mark_clear" + ] + }, + "TR-023": { + "name_zh": "借来节奏", + "tags": [ + "action_advance", + "cadence_copy", + "repeat_advance_lock" + ] + }, + "TR-024": { + "name_zh": "净断", + "tags": [ + "cleanse", + "positive_tag_cost", + "resource_gain" + ] + }, + "TR-025": { + "name_zh": "风险账本", + "tags": [ + "survive_exposed", + "shop_discount", + "clear_on_safe_end" + ] + }, + "TR-026": { + "name_zh": "空隙射程", + "tags": [ + "summon_pass", + "basic_reach", + "summon_no_damage" + ] + }, + "TR-027": { + "name_zh": "定声", + "tags": [ + "stationary", + "sound_uninterruptible", + "move_silence" + ] + }, + "TR-028": { + "name_zh": "二次草拟", + "tags": [ + "self_hindrance", + "short_replace", + "second_extend" + ] + }, + "TR-029": { + "name_zh": "摊开手掌", + "tags": [ + "resource_share", + "extended_range", + "adjacent_exclusion" + ] + }, + "TR-030": { + "name_zh": "反转带扣", + "tags": [ + "overpush", + "center_step", + "positive_tag_drop" + ] + }, + "TR-031": { + "name_zh": "风暴胆识", + "tags": [ + "enemy_windup", + "focus_gain", + "interrupt_reset" + ] + }, + "TR-032": { + "name_zh": "温和收尾", + "tags": [ + "overkill", + "remedy_convert", + "execute_lock" + ] + }, + "TR-033": { + "name_zh": "分心并顾", + "tags": [ + "equipment_mark", + "two_marks", + "profession_mark_cost" + ] + }, + "TR-034": { + "name_zh": "久经风霜", + "tags": [ + "region_hazard", + "first_immunity", + "second_extend" + ] + }, + "TR-035": { + "name_zh": "空剑鞘", + "tags": [ + "empty_armament", + "alternate_target", + "equip_cost" + ] + }, + "TR-036": { + "name_zh": "最后见证", + "tags": [ + "lone_band", + "forecast", + "reach_bonus", + "concealment_lock" + ] + } + } +}